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 
  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 sun.security.pkcs.EncryptedPrivateKeyInfo;
  37 import 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 
  64 abstract 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  */
 885 class 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 }