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