1/*
2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.provider;
27
28import java.io.*;
29import java.net.*;
30import java.security.*;
31import java.security.cert.Certificate;
32import java.security.cert.CertificateFactory;
33import java.security.cert.CertificateException;
34import java.util.*;
35
36import sun.security.pkcs.EncryptedPrivateKeyInfo;
37import sun.security.util.PolicyUtil;
38
39/**
40 * This class provides the domain keystore type identified as "DKS".
41 * DKS presents a collection of separate keystores as a single logical keystore.
42 * The collection of keystores is specified in a domain configuration file which
43 * is passed to DKS in a {@link DomainLoadStoreParameter}.
44 * <p>
45 * The following properties are supported:
46 * <dl>
47 * <dt> {@code keystoreType="<type>"} </dt>
48 *     <dd> The keystore type. </dd>
49 * <dt> {@code keystoreURI="<url>"} </dt>
50 *     <dd> The keystore location. </dd>
51 * <dt> {@code keystoreProviderName="<name>"} </dt>
52 *     <dd> The name of the keystore's JCE provider. </dd>
53 * <dt> {@code keystorePasswordEnv="<environment-variable>"} </dt>
54 *     <dd> The environment variable that stores a keystore password.
55 * <dt> {@code entryNameSeparator="<separator>"} </dt>
56 *     <dd> The separator between a keystore name prefix and an entry name.
57 *          When specified, it applies to all the entries in a domain.
58 *          Its default value is a space. </dd>
59 * </dl>
60 *
61 * @since 1.8
62 */
63
64abstract class DomainKeyStore extends KeyStoreSpi {
65
66    // regular DKS
67    public static final class DKS extends DomainKeyStore {
68        String convertAlias(String alias) {
69            return alias.toLowerCase(Locale.ENGLISH);
70        }
71    }
72
73    // DKS property names
74    private static final String ENTRY_NAME_SEPARATOR = "entrynameseparator";
75    private static final String KEYSTORE_PROVIDER_NAME = "keystoreprovidername";
76    private static final String KEYSTORE_TYPE = "keystoretype";
77    private static final String KEYSTORE_URI = "keystoreuri";
78    private static final String KEYSTORE_PASSWORD_ENV = "keystorepasswordenv";
79
80    // RegEx meta characters
81    private static final String REGEX_META = ".$|()[{^?*+\\";
82
83    // Default prefix for keystores loaded-by-stream
84    private static final String DEFAULT_STREAM_PREFIX = "iostream";
85    private int streamCounter = 1;
86    private String entryNameSeparator = " ";
87    private String entryNameSeparatorRegEx = " ";
88
89    // Default keystore type
90    private static final String DEFAULT_KEYSTORE_TYPE =
91        KeyStore.getDefaultType();
92
93    // Domain keystores
94    private final Map<String, KeyStore> keystores = new HashMap<>();
95
96    DomainKeyStore() {
97    }
98
99    // convert an alias to internal form, overridden in subclasses:
100    // lower case for regular DKS
101    abstract String convertAlias(String alias);
102
103    /**
104     * Returns the key associated with the given alias, using the given
105     * password to recover it.
106     *
107     * @param alias the alias name
108     * @param password the password for recovering the key
109     *
110     * @return the requested key, or null if the given alias does not exist
111     * or does not identify a <i>key entry</i>.
112     *
113     * @exception NoSuchAlgorithmException if the algorithm for recovering the
114     * key cannot be found
115     * @exception UnrecoverableKeyException if the key cannot be recovered
116     * (e.g., the given password is wrong).
117     */
118    public Key engineGetKey(String alias, char[] password)
119        throws NoSuchAlgorithmException, UnrecoverableKeyException
120    {
121        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
122            getKeystoresForReading(alias);
123        Key key = null;
124
125        try {
126            String entryAlias = pair.getKey();
127            for (KeyStore keystore : pair.getValue()) {
128                key = keystore.getKey(entryAlias, password);
129                if (key != null) {
130                    break;
131                }
132            }
133        } catch (KeyStoreException e) {
134            throw new IllegalStateException(e);
135        }
136
137        return key;
138    }
139
140    /**
141     * Returns the certificate chain associated with the given alias.
142     *
143     * @param alias the alias name
144     *
145     * @return the certificate chain (ordered with the user's certificate first
146     * and the root certificate authority last), or null if the given alias
147     * does not exist or does not contain a certificate chain (i.e., the given
148     * alias identifies either a <i>trusted certificate entry</i> or a
149     * <i>key entry</i> without a certificate chain).
150     */
151    public Certificate[] engineGetCertificateChain(String alias) {
152
153        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
154            getKeystoresForReading(alias);
155        Certificate[] chain = null;
156
157        try {
158            String entryAlias = pair.getKey();
159            for (KeyStore keystore : pair.getValue()) {
160                chain = keystore.getCertificateChain(entryAlias);
161                if (chain != null) {
162                    break;
163                }
164            }
165        } catch (KeyStoreException e) {
166            throw new IllegalStateException(e);
167        }
168
169        return chain;
170    }
171
172    /**
173     * Returns the certificate associated with the given alias.
174     *
175     * <p>If the given alias name identifies a
176     * <i>trusted certificate entry</i>, the certificate associated with that
177     * entry is returned. If the given alias name identifies a
178     * <i>key entry</i>, the first element of the certificate chain of that
179     * entry is returned, or null if that entry does not have a certificate
180     * chain.
181     *
182     * @param alias the alias name
183     *
184     * @return the certificate, or null if the given alias does not exist or
185     * does not contain a certificate.
186     */
187    public Certificate engineGetCertificate(String alias) {
188
189        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
190            getKeystoresForReading(alias);
191        Certificate cert = null;
192
193        try {
194            String entryAlias = pair.getKey();
195            for (KeyStore keystore : pair.getValue()) {
196                cert = keystore.getCertificate(entryAlias);
197                if (cert != null) {
198                    break;
199                }
200            }
201        } catch (KeyStoreException e) {
202            throw new IllegalStateException(e);
203        }
204
205        return cert;
206    }
207
208    /**
209     * Returns the creation date of the entry identified by the given alias.
210     *
211     * @param alias the alias name
212     *
213     * @return the creation date of this entry, or null if the given alias does
214     * not exist
215     */
216    public Date engineGetCreationDate(String alias) {
217
218        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
219            getKeystoresForReading(alias);
220        Date date = null;
221
222        try {
223            String entryAlias = pair.getKey();
224            for (KeyStore keystore : pair.getValue()) {
225                date = keystore.getCreationDate(entryAlias);
226                if (date != null) {
227                    break;
228                }
229            }
230        } catch (KeyStoreException e) {
231            throw new IllegalStateException(e);
232        }
233
234        return date;
235    }
236
237    /**
238     * Assigns the given private key to the given alias, protecting
239     * it with the given password as defined in PKCS8.
240     *
241     * <p>The given java.security.PrivateKey <code>key</code> must
242     * be accompanied by a certificate chain certifying the
243     * corresponding public key.
244     *
245     * <p>If the given alias already exists, the keystore information
246     * associated with it is overridden by the given key and certificate
247     * chain.
248     *
249     * @param alias the alias name
250     * @param key the private key to be associated with the alias
251     * @param password the password to protect the key
252     * @param chain the certificate chain for the corresponding public
253     * key (only required if the given key is of type
254     * <code>java.security.PrivateKey</code>).
255     *
256     * @exception KeyStoreException if the given key is not a private key,
257     * cannot be protected, or this operation fails for some other reason
258     */
259    public void engineSetKeyEntry(String alias, Key key, char[] password,
260                                  Certificate[] chain)
261        throws KeyStoreException
262    {
263        AbstractMap.SimpleEntry<String,
264            AbstractMap.SimpleEntry<String, KeyStore>> pair =
265                getKeystoreForWriting(alias);
266
267        if (pair == null) {
268            throw new KeyStoreException("Error setting key entry for '" +
269                alias + "'");
270        }
271        String entryAlias = pair.getKey();
272        Map.Entry<String, KeyStore> keystore = pair.getValue();
273        keystore.getValue().setKeyEntry(entryAlias, key, password, chain);
274    }
275
276    /**
277     * Assigns the given key (that has already been protected) to the given
278     * alias.
279     *
280     * <p>If the protected key is of type
281     * <code>java.security.PrivateKey</code>, it must be accompanied by a
282     * certificate chain certifying the corresponding public key. If the
283     * underlying keystore implementation is of type <code>jks</code>,
284     * <code>key</code> must be encoded as an
285     * <code>EncryptedPrivateKeyInfo</code> as defined in the PKCS #8 standard.
286     *
287     * <p>If the given alias already exists, the keystore information
288     * associated with it is overridden by the given key (and possibly
289     * certificate chain).
290     *
291     * @param alias the alias name
292     * @param key the key (in protected format) to be associated with the alias
293     * @param chain the certificate chain for the corresponding public
294     * key (only useful if the protected key is of type
295     * <code>java.security.PrivateKey</code>).
296     *
297     * @exception KeyStoreException if this operation fails.
298     */
299    public void engineSetKeyEntry(String alias, byte[] key,
300                                  Certificate[] chain)
301        throws KeyStoreException
302    {
303        AbstractMap.SimpleEntry<String,
304            AbstractMap.SimpleEntry<String, KeyStore>> pair =
305                getKeystoreForWriting(alias);
306
307        if (pair == null) {
308            throw new KeyStoreException(
309                "Error setting protected key entry for '" + alias + "'");
310        }
311        String entryAlias = pair.getKey();
312        Map.Entry<String, KeyStore> keystore = pair.getValue();
313        keystore.getValue().setKeyEntry(entryAlias, key, chain);
314    }
315
316    /**
317     * Assigns the given certificate to the given alias.
318     *
319     * <p>If the given alias already exists in this keystore and identifies a
320     * <i>trusted certificate entry</i>, the certificate associated with it is
321     * overridden by the given certificate.
322     *
323     * @param alias the alias name
324     * @param cert the certificate
325     *
326     * @exception KeyStoreException if the given alias already exists and does
327     * not identify a <i>trusted certificate entry</i>, or this operation
328     * fails for some other reason.
329     */
330    public void engineSetCertificateEntry(String alias, Certificate cert)
331        throws KeyStoreException
332    {
333        AbstractMap.SimpleEntry<String,
334            AbstractMap.SimpleEntry<String, KeyStore>> pair =
335                getKeystoreForWriting(alias);
336
337        if (pair == null) {
338            throw new KeyStoreException("Error setting certificate entry for '"
339                + alias + "'");
340        }
341        String entryAlias = pair.getKey();
342        Map.Entry<String, KeyStore> keystore = pair.getValue();
343        keystore.getValue().setCertificateEntry(entryAlias, cert);
344    }
345
346    /**
347     * Deletes the entry identified by the given alias from this keystore.
348     *
349     * @param alias the alias name
350     *
351     * @exception KeyStoreException if the entry cannot be removed.
352     */
353    public void engineDeleteEntry(String alias) throws KeyStoreException
354    {
355        AbstractMap.SimpleEntry<String,
356            AbstractMap.SimpleEntry<String, KeyStore>> pair =
357                getKeystoreForWriting(alias);
358
359        if (pair == null) {
360            throw new KeyStoreException("Error deleting entry for '" + alias +
361                "'");
362        }
363        String entryAlias = pair.getKey();
364        Map.Entry<String, KeyStore> keystore = pair.getValue();
365        keystore.getValue().deleteEntry(entryAlias);
366    }
367
368    /**
369     * Lists all the alias names of this keystore.
370     *
371     * @return enumeration of the alias names
372     */
373    public Enumeration<String> engineAliases() {
374        final Iterator<Map.Entry<String, KeyStore>> iterator =
375            keystores.entrySet().iterator();
376
377        return new Enumeration<String>() {
378            private int index = 0;
379            private Map.Entry<String, KeyStore> keystoresEntry = null;
380            private String prefix = null;
381            private Enumeration<String> aliases = null;
382
383            public boolean hasMoreElements() {
384                try {
385                    if (aliases == null) {
386                        if (iterator.hasNext()) {
387                            keystoresEntry = iterator.next();
388                            prefix = keystoresEntry.getKey() +
389                                entryNameSeparator;
390                            aliases = keystoresEntry.getValue().aliases();
391                        } else {
392                            return false;
393                        }
394                    }
395                    if (aliases.hasMoreElements()) {
396                        return true;
397                    } else {
398                        if (iterator.hasNext()) {
399                            keystoresEntry = iterator.next();
400                            prefix = keystoresEntry.getKey() +
401                                entryNameSeparator;
402                            aliases = keystoresEntry.getValue().aliases();
403                        } else {
404                            return false;
405                        }
406                    }
407                } catch (KeyStoreException e) {
408                    return false;
409                }
410
411                return aliases.hasMoreElements();
412            }
413
414            public String nextElement() {
415                if (hasMoreElements()) {
416                    return prefix + aliases.nextElement();
417                }
418                throw new NoSuchElementException();
419            }
420        };
421    }
422
423    /**
424     * Checks if the given alias exists in this keystore.
425     *
426     * @param alias the alias name
427     *
428     * @return true if the alias exists, false otherwise
429     */
430    public boolean engineContainsAlias(String alias) {
431
432        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
433            getKeystoresForReading(alias);
434
435        try {
436            String entryAlias = pair.getKey();
437            for (KeyStore keystore : pair.getValue()) {
438                if (keystore.containsAlias(entryAlias)) {
439                    return true;
440                }
441            }
442        } catch (KeyStoreException e) {
443            throw new IllegalStateException(e);
444        }
445
446        return false;
447    }
448
449    /**
450     * Retrieves the number of entries in this keystore.
451     *
452     * @return the number of entries in this keystore
453     */
454    public int engineSize() {
455
456        int size = 0;
457        try {
458            for (KeyStore keystore : keystores.values()) {
459                size += keystore.size();
460            }
461        } catch (KeyStoreException e) {
462            throw new IllegalStateException(e);
463        }
464
465        return size;
466    }
467
468    /**
469     * Returns true if the entry identified by the given alias is a
470     * <i>key entry</i>, and false otherwise.
471     *
472     * @return true if the entry identified by the given alias is a
473     * <i>key entry</i>, false otherwise.
474     */
475    public boolean engineIsKeyEntry(String alias) {
476
477        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
478            getKeystoresForReading(alias);
479
480        try {
481            String entryAlias = pair.getKey();
482            for (KeyStore keystore : pair.getValue()) {
483                if (keystore.isKeyEntry(entryAlias)) {
484                    return true;
485                }
486            }
487        } catch (KeyStoreException e) {
488            throw new IllegalStateException(e);
489        }
490
491        return false;
492    }
493
494    /**
495     * Returns true if the entry identified by the given alias is a
496     * <i>trusted certificate entry</i>, and false otherwise.
497     *
498     * @return true if the entry identified by the given alias is a
499     * <i>trusted certificate entry</i>, false otherwise.
500     */
501    public boolean engineIsCertificateEntry(String alias) {
502
503        AbstractMap.SimpleEntry<String, Collection<KeyStore>> pair =
504            getKeystoresForReading(alias);
505
506        try {
507            String entryAlias = pair.getKey();
508            for (KeyStore keystore : pair.getValue()) {
509                if (keystore.isCertificateEntry(entryAlias)) {
510                    return true;
511                }
512            }
513        } catch (KeyStoreException e) {
514            throw new IllegalStateException(e);
515        }
516
517        return false;
518    }
519
520    /*
521     * Returns a keystore entry alias and a list of target keystores.
522     * When the supplied alias prefix identifies a keystore then that single
523     * keystore is returned. When no alias prefix is supplied then all the
524     * keystores are returned.
525     */
526    private AbstractMap.SimpleEntry<String, Collection<KeyStore>>
527        getKeystoresForReading(String alias) {
528
529        String[] splits = alias.split(this.entryNameSeparatorRegEx, 2);
530        if (splits.length == 2) { // prefixed alias
531            KeyStore keystore = keystores.get(splits[0]);
532            if (keystore != null) {
533                return new AbstractMap.SimpleEntry<>(splits[1],
534                    (Collection<KeyStore>) Collections.singleton(keystore));
535            }
536        } else if (splits.length == 1) { // unprefixed alias
537            // Check all keystores for the first occurrence of the alias
538            return new AbstractMap.SimpleEntry<>(alias, keystores.values());
539        }
540        return new AbstractMap.SimpleEntry<>("",
541            (Collection<KeyStore>) Collections.<KeyStore>emptyList());
542    }
543
544    /*
545     * Returns a keystore entry alias and a single target keystore.
546     * An alias prefix must be supplied.
547     */
548    private
549    AbstractMap.SimpleEntry<String, AbstractMap.SimpleEntry<String, KeyStore>>
550        getKeystoreForWriting(String alias) {
551
552        String[] splits = alias.split(this.entryNameSeparator, 2);
553        if (splits.length == 2) { // prefixed alias
554            KeyStore keystore = keystores.get(splits[0]);
555            if (keystore != null) {
556                return new AbstractMap.SimpleEntry<>(splits[1],
557                    new AbstractMap.SimpleEntry<>(splits[0], keystore));
558            }
559        }
560        return null;
561    }
562
563    /**
564     * Returns the (alias) name of the first keystore entry whose certificate
565     * matches the given certificate.
566     *
567     * <p>This method attempts to match the given certificate with each
568     * keystore entry. If the entry being considered
569     * is a <i>trusted certificate entry</i>, the given certificate is
570     * compared to that entry's certificate. If the entry being considered is
571     * a <i>key entry</i>, the given certificate is compared to the first
572     * element of that entry's certificate chain (if a chain exists).
573     *
574     * @param cert the certificate to match with.
575     *
576     * @return the (alias) name of the first entry with matching certificate,
577     * or null if no such entry exists in this keystore.
578     */
579    public String engineGetCertificateAlias(Certificate cert) {
580
581        try {
582
583            String alias = null;
584            for (KeyStore keystore : keystores.values()) {
585                if ((alias = keystore.getCertificateAlias(cert)) != null) {
586                    break;
587                }
588            }
589            return alias;
590
591        } catch (KeyStoreException e) {
592            throw new IllegalStateException(e);
593        }
594    }
595
596    /**
597     * Stores this keystore to the given output stream, and protects its
598     * integrity with the given password.
599     *
600     * @param stream the output stream to which this keystore is written.
601     * @param password the password to generate the keystore integrity check
602     *
603     * @exception IOException if there was an I/O problem with data
604     * @exception NoSuchAlgorithmException if the appropriate data integrity
605     * algorithm could not be found
606     * @exception CertificateException if any of the certificates included in
607     * the keystore data could not be stored
608     */
609    public void engineStore(OutputStream stream, char[] password)
610        throws IOException, NoSuchAlgorithmException, CertificateException
611    {
612        // Support storing to a stream only when a single keystore has been
613        // configured
614        try {
615            if (keystores.size() == 1) {
616                keystores.values().iterator().next().store(stream, password);
617                return;
618            }
619        } catch (KeyStoreException e) {
620            throw new IllegalStateException(e);
621        }
622
623        throw new UnsupportedOperationException(
624            "This keystore must be stored using a DomainLoadStoreParameter");
625    }
626
627    @Override
628    public void engineStore(KeyStore.LoadStoreParameter param)
629        throws IOException, NoSuchAlgorithmException, CertificateException
630    {
631        if (param instanceof DomainLoadStoreParameter) {
632            DomainLoadStoreParameter domainParameter =
633                (DomainLoadStoreParameter) param;
634            List<KeyStoreBuilderComponents> builders = getBuilders(
635                domainParameter.getConfiguration(),
636                    domainParameter.getProtectionParams());
637
638            for (KeyStoreBuilderComponents builder : builders) {
639
640                try {
641
642                    KeyStore.ProtectionParameter pp = builder.protection;
643                    if (!(pp instanceof KeyStore.PasswordProtection)) {
644                        throw new KeyStoreException(
645                            new IllegalArgumentException("ProtectionParameter" +
646                                " must be a KeyStore.PasswordProtection"));
647                    }
648                    char[] password =
649                        ((KeyStore.PasswordProtection) builder.protection)
650                            .getPassword();
651
652                    // Store the keystores
653                    KeyStore keystore = keystores.get(builder.name);
654
655                    try (FileOutputStream stream =
656                        new FileOutputStream(builder.file)) {
657
658                        keystore.store(stream, password);
659                    }
660                } catch (KeyStoreException e) {
661                    throw new IOException(e);
662                }
663            }
664        } else {
665            throw new UnsupportedOperationException(
666                "This keystore must be stored using a " +
667                "DomainLoadStoreParameter");
668        }
669    }
670
671    /**
672     * Loads the keystore from the given input stream.
673     *
674     * <p>If a password is given, it is used to check the integrity of the
675     * keystore data. Otherwise, the integrity of the keystore is not checked.
676     *
677     * @param stream the input stream from which the keystore is loaded
678     * @param password the (optional) password used to check the integrity of
679     * the keystore.
680     *
681     * @exception IOException if there is an I/O or format problem with the
682     * keystore data
683     * @exception NoSuchAlgorithmException if the algorithm used to check
684     * the integrity of the keystore cannot be found
685     * @exception CertificateException if any of the certificates in the
686     * keystore could not be loaded
687     */
688    public void engineLoad(InputStream stream, char[] password)
689        throws IOException, NoSuchAlgorithmException, CertificateException
690    {
691        // Support loading from a stream only for a JKS or default type keystore
692        try {
693            KeyStore keystore = null;
694
695            try {
696                keystore = KeyStore.getInstance("JKS");
697                keystore.load(stream, password);
698
699            } catch (Exception e) {
700                // Retry
701                if (!"JKS".equalsIgnoreCase(DEFAULT_KEYSTORE_TYPE)) {
702                    keystore = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
703                    keystore.load(stream, password);
704                } else {
705                    throw e;
706                }
707            }
708            String keystoreName = DEFAULT_STREAM_PREFIX + streamCounter++;
709            keystores.put(keystoreName, keystore);
710
711        } catch (Exception e) {
712            throw new UnsupportedOperationException(
713                "This keystore must be loaded using a " +
714                "DomainLoadStoreParameter");
715        }
716    }
717
718    @Override
719    public void engineLoad(KeyStore.LoadStoreParameter param)
720        throws IOException, NoSuchAlgorithmException, CertificateException
721    {
722        if (param instanceof DomainLoadStoreParameter) {
723            DomainLoadStoreParameter domainParameter =
724                (DomainLoadStoreParameter) param;
725            List<KeyStoreBuilderComponents> builders = getBuilders(
726                domainParameter.getConfiguration(),
727                    domainParameter.getProtectionParams());
728
729            for (KeyStoreBuilderComponents builder : builders) {
730
731                try {
732                    // Load the keystores (file-based and non-file-based)
733                    if (builder.file != null) {
734                        keystores.put(builder.name,
735                            KeyStore.Builder.newInstance(builder.type,
736                                builder.provider, builder.file,
737                                builder.protection)
738                                    .getKeyStore());
739                    } else {
740                        keystores.put(builder.name,
741                            KeyStore.Builder.newInstance(builder.type,
742                                builder.provider, builder.protection)
743                                    .getKeyStore());
744                    }
745                } catch (KeyStoreException e) {
746                    throw new IOException(e);
747                }
748            }
749        } else {
750            throw new UnsupportedOperationException(
751                "This keystore must be loaded using a " +
752                "DomainLoadStoreParameter");
753        }
754    }
755
756    /*
757     * Parse a keystore domain configuration file and associated collection
758     * of keystore passwords to create a collection of KeyStore.Builder.
759     */
760    private List<KeyStoreBuilderComponents> getBuilders(URI configuration,
761        Map<String, KeyStore.ProtectionParameter> passwords)
762            throws IOException {
763
764        PolicyParser parser = new PolicyParser(true); // expand properties
765        Collection<PolicyParser.DomainEntry> domains = null;
766        List<KeyStoreBuilderComponents> builders = new ArrayList<>();
767        String uriDomain = configuration.getFragment();
768
769        try (InputStreamReader configurationReader =
770            new InputStreamReader(
771                PolicyUtil.getInputStream(configuration.toURL()), "UTF-8")) {
772            parser.read(configurationReader);
773            domains = parser.getDomainEntries();
774
775        } catch (MalformedURLException mue) {
776            throw new IOException(mue);
777
778        } catch (PolicyParser.ParsingException pe) {
779            throw new IOException(pe);
780        }
781
782        for (PolicyParser.DomainEntry domain : domains) {
783            Map<String, String> domainProperties = domain.getProperties();
784
785            if (uriDomain != null &&
786                (!uriDomain.equalsIgnoreCase(domain.getName()))) {
787                continue; // skip this domain
788            }
789
790            if (domainProperties.containsKey(ENTRY_NAME_SEPARATOR)) {
791                this.entryNameSeparator =
792                    domainProperties.get(ENTRY_NAME_SEPARATOR);
793                // escape any regex meta characters
794                char ch = 0;
795                StringBuilder s = new StringBuilder();
796                for (int i = 0; i < this.entryNameSeparator.length(); i++) {
797                    ch = this.entryNameSeparator.charAt(i);
798                    if (REGEX_META.indexOf(ch) != -1) {
799                        s.append('\\');
800                    }
801                    s.append(ch);
802                }
803                this.entryNameSeparatorRegEx = s.toString();
804            }
805
806            Collection<PolicyParser.KeyStoreEntry> keystores =
807                domain.getEntries();
808            for (PolicyParser.KeyStoreEntry keystore : keystores) {
809                String keystoreName = keystore.getName();
810                Map<String, String> properties =
811                    new HashMap<>(domainProperties);
812                properties.putAll(keystore.getProperties());
813
814                String keystoreType = DEFAULT_KEYSTORE_TYPE;
815                if (properties.containsKey(KEYSTORE_TYPE)) {
816                    keystoreType = properties.get(KEYSTORE_TYPE);
817                }
818
819                Provider keystoreProvider = null;
820                if (properties.containsKey(KEYSTORE_PROVIDER_NAME)) {
821                    String keystoreProviderName =
822                        properties.get(KEYSTORE_PROVIDER_NAME);
823                    keystoreProvider =
824                        Security.getProvider(keystoreProviderName);
825                    if (keystoreProvider == null) {
826                        throw new IOException("Error locating JCE provider: " +
827                            keystoreProviderName);
828                    }
829                }
830
831                File keystoreFile = null;
832                if (properties.containsKey(KEYSTORE_URI)) {
833                    String uri = properties.get(KEYSTORE_URI);
834
835                    try {
836                        if (uri.startsWith("file://")) {
837                            keystoreFile = new File(new URI(uri));
838                        } else {
839                            keystoreFile = new File(uri);
840                        }
841
842                    } catch (URISyntaxException | IllegalArgumentException e) {
843                        throw new IOException(
844                            "Error processing keystore property: " +
845                                "keystoreURI=\"" + uri + "\"", e);
846                    }
847                }
848
849                KeyStore.ProtectionParameter keystoreProtection = null;
850                if (passwords.containsKey(keystoreName)) {
851                    keystoreProtection = passwords.get(keystoreName);
852
853                } else if (properties.containsKey(KEYSTORE_PASSWORD_ENV)) {
854                    String env = properties.get(KEYSTORE_PASSWORD_ENV);
855                    String pwd = System.getenv(env);
856                    if (pwd != null) {
857                        keystoreProtection =
858                            new KeyStore.PasswordProtection(pwd.toCharArray());
859                    } else {
860                        throw new IOException(
861                            "Error processing keystore property: " +
862                                "keystorePasswordEnv=\"" + env + "\"");
863                    }
864                } else {
865                    keystoreProtection = new KeyStore.PasswordProtection(null);
866                }
867
868                builders.add(new KeyStoreBuilderComponents(keystoreName,
869                    keystoreType, keystoreProvider, keystoreFile,
870                    keystoreProtection));
871            }
872            break; // skip other domains
873        }
874        if (builders.isEmpty()) {
875            throw new IOException("Error locating domain configuration data " +
876                "for: " + configuration);
877        }
878
879        return builders;
880    }
881
882/*
883 * Utility class that holds the components used to construct a KeyStore.Builder
884 */
885class KeyStoreBuilderComponents {
886    String name;
887    String type;
888    Provider provider;
889    File file;
890    KeyStore.ProtectionParameter protection;
891
892    KeyStoreBuilderComponents(String name, String type, Provider provider,
893        File file, KeyStore.ProtectionParameter protection) {
894        this.name = name;
895        this.type = type;
896        this.provider = provider;
897        this.file = file;
898        this.protection = protection;
899    }
900}
901}
902