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