1 /*
   2  * Copyright (c) 2004, 2007, 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.ssl;
  27 
  28 import java.lang.ref.*;
  29 import java.util.*;
  30 import static java.util.Locale.ENGLISH;
  31 import java.util.concurrent.atomic.AtomicLong;
  32 import java.net.Socket;
  33 
  34 import java.security.*;
  35 import java.security.KeyStore.*;
  36 import java.security.cert.*;
  37 import java.security.cert.Certificate;
  38 
  39 import javax.net.ssl.*;
  40 
  41 /**
  42  * The new X509 key manager implementation. The main differences to the
  43  * old SunX509 key manager are:
  44  *  . it is based around the KeyStore.Builder API. This allows it to use
  45  *    other forms of KeyStore protection or password input (e.g. a
  46  *    CallbackHandler) or to have keys within one KeyStore protected by
  47  *    different keys.
  48  *  . it can use multiple KeyStores at the same time.
  49  *  . it is explicitly designed to accomodate KeyStores that change over
  50  *    the lifetime of the process.
  51  *  . it makes an effort to choose the key that matches best, i.e. one that
  52  *    is not expired and has the appropriate certificate extensions.
  53  *
  54  * Note that this code is not explicitly performance optimzied yet.
  55  *
  56  * @author  Andreas Sterbenz
  57  */
  58 final class X509KeyManagerImpl extends X509ExtendedKeyManager
  59         implements X509KeyManager {
  60 
  61     private static final Debug debug = Debug.getInstance("ssl");
  62 
  63     private final static boolean useDebug =
  64                             (debug != null) && Debug.isOn("keymanager");
  65 
  66     // for unit testing only, set via privileged reflection
  67     private static Date verificationDate;
  68 
  69     // list of the builders
  70     private final List<Builder> builders;
  71 
  72     // counter to generate unique ids for the aliases
  73     private final AtomicLong uidCounter;
  74 
  75     // cached entries
  76     private final Map<String,Reference<PrivateKeyEntry>> entryCacheMap;
  77 
  78     X509KeyManagerImpl(Builder builder) {
  79         this(Collections.singletonList(builder));
  80     }
  81 
  82     X509KeyManagerImpl(List<Builder> builders) {
  83         this.builders = builders;
  84         uidCounter = new AtomicLong();
  85         entryCacheMap = Collections.synchronizedMap
  86                         (new SizedMap<String,Reference<PrivateKeyEntry>>());
  87     }
  88 
  89     // LinkedHashMap with a max size of 10
  90     // see LinkedHashMap JavaDocs
  91     private static class SizedMap<K,V> extends LinkedHashMap<K,V> {
  92         @Override protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  93             return size() > 10;
  94         }
  95     }
  96 
  97     //
  98     // public methods
  99     //
 100 
 101     public X509Certificate[] getCertificateChain(String alias) {
 102         PrivateKeyEntry entry = getEntry(alias);
 103         return entry == null ? null :
 104                 (X509Certificate[])entry.getCertificateChain();
 105     }
 106 
 107     public PrivateKey getPrivateKey(String alias) {
 108         PrivateKeyEntry entry = getEntry(alias);
 109         return entry == null ? null : entry.getPrivateKey();
 110     }
 111 
 112     public String chooseClientAlias(String[] keyTypes, Principal[] issuers,
 113             Socket socket) {
 114         return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT);
 115     }
 116 
 117     public String chooseEngineClientAlias(String[] keyTypes,
 118             Principal[] issuers, SSLEngine engine) {
 119         return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT);
 120     }
 121 
 122     public String chooseServerAlias(String keyType,
 123             Principal[] issuers, Socket socket) {
 124         return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER);
 125     }
 126 
 127     public String chooseEngineServerAlias(String keyType,
 128             Principal[] issuers, SSLEngine engine) {
 129         return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER);
 130     }
 131 
 132     public String[] getClientAliases(String keyType, Principal[] issuers) {
 133         return getAliases(keyType, issuers, CheckType.CLIENT);
 134     }
 135 
 136     public String[] getServerAliases(String keyType, Principal[] issuers) {
 137         return getAliases(keyType, issuers, CheckType.SERVER);
 138     }
 139 
 140     //
 141     // implementation private methods
 142     //
 143 
 144     // we construct the alias we return to JSSE as seen in the code below
 145     // a unique id is included to allow us to reliably cache entries
 146     // between the calls to getCertificateChain() and getPrivateKey()
 147     // even if tokens are inserted or removed
 148     private String makeAlias(EntryStatus entry) {
 149         return uidCounter.incrementAndGet() + "." + entry.builderIndex + "."
 150                 + entry.alias;
 151     }
 152 
 153     private PrivateKeyEntry getEntry(String alias) {
 154         // if the alias is null, return immediately
 155         if (alias == null) {
 156             return null;
 157         }
 158 
 159         // try to get the entry from cache
 160         Reference<PrivateKeyEntry> ref = entryCacheMap.get(alias);
 161         PrivateKeyEntry entry = (ref != null) ? ref.get() : null;
 162         if (entry != null) {
 163             return entry;
 164         }
 165 
 166         // parse the alias
 167         int firstDot = alias.indexOf('.');
 168         int secondDot = alias.indexOf('.', firstDot + 1);
 169         if ((firstDot == -1) || (secondDot == firstDot)) {
 170             // invalid alias
 171             return null;
 172         }
 173         try {
 174             int builderIndex = Integer.parseInt
 175                                 (alias.substring(firstDot + 1, secondDot));
 176             String keyStoreAlias = alias.substring(secondDot + 1);
 177             Builder builder = builders.get(builderIndex);
 178             KeyStore ks = builder.getKeyStore();
 179             Entry newEntry = ks.getEntry
 180                     (keyStoreAlias, builder.getProtectionParameter(alias));
 181             if (newEntry instanceof PrivateKeyEntry == false) {
 182                 // unexpected type of entry
 183                 return null;
 184             }
 185             entry = (PrivateKeyEntry)newEntry;
 186             entryCacheMap.put(alias, new SoftReference(entry));
 187             return entry;
 188         } catch (Exception e) {
 189             // ignore
 190             return null;
 191         }
 192     }
 193 
 194     // Class to help verify that the public key algorithm (and optionally
 195     // the signature algorithm) of a certificate matches what we need.
 196     private static class KeyType {
 197 
 198         final String keyAlgorithm;
 199         final String sigKeyAlgorithm;
 200 
 201         KeyType(String algorithm) {
 202             int k = algorithm.indexOf("_");
 203             if (k == -1) {
 204                 keyAlgorithm = algorithm;
 205                 sigKeyAlgorithm = null;
 206             } else {
 207                 keyAlgorithm = algorithm.substring(0, k);
 208                 sigKeyAlgorithm = algorithm.substring(k + 1);
 209             }
 210         }
 211 
 212         boolean matches(Certificate[] chain) {
 213             if (!chain[0].getPublicKey().getAlgorithm().equals(keyAlgorithm)) {
 214                 return false;
 215             }
 216             if (sigKeyAlgorithm == null) {
 217                 return true;
 218             }
 219             if (chain.length > 1) {
 220                 // if possible, check the public key in the issuer cert
 221                 return sigKeyAlgorithm.equals(chain[1].getPublicKey().getAlgorithm());
 222             } else {
 223                 // Check the signature algorithm of the certificate itself.
 224                 // Look for the "withRSA" in "SHA1withRSA", etc.
 225                 X509Certificate issuer = (X509Certificate)chain[0];
 226                 String sigAlgName = issuer.getSigAlgName().toUpperCase(ENGLISH);
 227                 String pattern = "WITH" + sigKeyAlgorithm.toUpperCase(ENGLISH);
 228                 return sigAlgName.contains(pattern);
 229             }
 230         }
 231     }
 232 
 233     private static List<KeyType> getKeyTypes(String ... keyTypes) {
 234         if ((keyTypes == null) || (keyTypes.length == 0) || (keyTypes[0] == null)) {
 235             return null;
 236         }
 237         List<KeyType> list = new ArrayList<KeyType>(keyTypes.length);
 238         for (String keyType : keyTypes) {
 239             list.add(new KeyType(keyType));
 240         }
 241         return list;
 242     }
 243 
 244     /*
 245      * Return the best alias that fits the given parameters.
 246      * The algorithm we use is:
 247      *   . scan through all the aliases in all builders in order
 248      *   . as soon as we find a perfect match, return
 249      *     (i.e. a match with a cert that has appropriate key usage
 250      *      and is not expired).
 251      *   . if we do not find a perfect match, keep looping and remember
 252      *     the imperfect matches
 253      *   . at the end, sort the imperfect matches. we prefer expired certs
 254      *     with appropriate key usage to certs with the wrong key usage.
 255      *     return the first one of them.
 256      */
 257     private String chooseAlias(List<KeyType> keyTypeList,
 258             Principal[] issuers, CheckType checkType) {
 259         if (keyTypeList == null || keyTypeList.size() == 0) {
 260             return null;
 261         }
 262 
 263         Set<Principal> issuerSet = getIssuerSet(issuers);
 264         List<EntryStatus> allResults = null;
 265         for (int i = 0, n = builders.size(); i < n; i++) {
 266             try {
 267                 List<EntryStatus> results =
 268                     getAliases(i, keyTypeList, issuerSet, false, checkType);
 269                 if (results != null) {
 270                     // the results will either be a single perfect match
 271                     // or 1 or more imperfect matches
 272                     // if it's a perfect match, return immediately
 273                     EntryStatus status = results.get(0);
 274                     if (status.checkResult == CheckResult.OK) {
 275                         if (useDebug) {
 276                             debug.println("KeyMgr: choosing key: " + status);
 277                         }
 278                         return makeAlias(status);
 279                     }
 280                     if (allResults == null) {
 281                         allResults = new ArrayList<EntryStatus>();
 282                     }
 283                     allResults.addAll(results);
 284                 }
 285             } catch (Exception e) {
 286                 // ignore
 287             }
 288         }
 289         if (allResults == null) {
 290             if (useDebug) {
 291                 debug.println("KeyMgr: no matching key found");
 292             }
 293             return null;
 294         }
 295         Collections.sort(allResults);
 296         if (useDebug) {
 297             debug.println("KeyMgr: no good matching key found, "
 298                         + "returning best match out of:");
 299             debug.println(allResults.toString());
 300         }
 301         return makeAlias(allResults.get(0));
 302     }
 303 
 304     /*
 305      * Return all aliases that (approximately) fit the parameters.
 306      * These are perfect matches plus imperfect matches (expired certificates
 307      * and certificates with the wrong extensions).
 308      * The perfect matches will be first in the array.
 309      */
 310     public String[] getAliases(String keyType, Principal[] issuers,
 311             CheckType checkType) {
 312         if (keyType == null) {
 313             return null;
 314         }
 315 
 316         Set<Principal> issuerSet = getIssuerSet(issuers);
 317         List<KeyType> keyTypeList = getKeyTypes(keyType);
 318         List<EntryStatus> allResults = null;
 319         for (int i = 0, n = builders.size(); i < n; i++) {
 320             try {
 321                 List<EntryStatus> results =
 322                         getAliases(i, keyTypeList, issuerSet, true, checkType);
 323                 if (results != null) {
 324                     if (allResults == null) {
 325                         allResults = new ArrayList<EntryStatus>();
 326                     }
 327                     allResults.addAll(results);
 328                 }
 329             } catch (Exception e) {
 330                 // ignore
 331             }
 332         }
 333         if (allResults == null || allResults.size() == 0) {
 334             if (useDebug) {
 335                 debug.println("KeyMgr: no matching alias found");
 336             }
 337             return null;
 338         }
 339         Collections.sort(allResults);
 340         if (useDebug) {
 341             debug.println("KeyMgr: getting aliases: " + allResults);
 342         }
 343         return toAliases(allResults);
 344     }
 345 
 346     // turn candidate entries into unique aliases we can return to JSSE
 347     private String[] toAliases(List<EntryStatus> results) {
 348         String[] s = new String[results.size()];
 349         int i = 0;
 350         for (EntryStatus result : results) {
 351             s[i++] = makeAlias(result);
 352         }
 353         return s;
 354     }
 355 
 356     // make a Set out of the array
 357     private Set<Principal> getIssuerSet(Principal[] issuers) {
 358         if ((issuers != null) && (issuers.length != 0)) {
 359             return new HashSet<Principal>(Arrays.asList(issuers));
 360         } else {
 361             return null;
 362         }
 363     }
 364 
 365     // a candidate match
 366     // identifies the entry by builder and alias
 367     // and includes the result of the certificate check
 368     private static class EntryStatus implements Comparable<EntryStatus> {
 369 
 370         final int builderIndex;
 371         final int keyIndex;
 372         final String alias;
 373         final CheckResult checkResult;
 374 
 375         EntryStatus(int builderIndex, int keyIndex, String alias,
 376                 Certificate[] chain, CheckResult checkResult) {
 377             this.builderIndex = builderIndex;
 378             this.keyIndex = keyIndex;
 379             this.alias = alias;
 380             this.checkResult = checkResult;
 381         }
 382 
 383         public int compareTo(EntryStatus other) {
 384             int result = this.checkResult.compareTo(other.checkResult);
 385             return (result == 0) ? (this.keyIndex - other.keyIndex) : result;
 386         }
 387 
 388         public String toString() {
 389             String s = alias + " (verified: " + checkResult + ")";
 390             if (builderIndex == 0) {
 391                 return s;
 392             } else {
 393                 return "Builder #" + builderIndex + ", alias: " + s;
 394             }
 395         }
 396     }
 397 
 398     // enum for the type of certificate check we want to perform
 399     // (client or server)
 400     // also includes the check code itself
 401     private static enum CheckType {
 402 
 403         // enum constant for "no check" (currently not used)
 404         NONE(Collections.<String>emptySet()),
 405 
 406         // enum constant for "tls client" check
 407         // valid EKU for TLS client: any, tls_client
 408         CLIENT(new HashSet<String>(Arrays.asList(new String[] {
 409             "2.5.29.37.0", "1.3.6.1.5.5.7.3.2" }))),
 410 
 411         // enum constant for "tls server" check
 412         // valid EKU for TLS server: any, tls_server, ns_sgc, ms_sgc
 413         SERVER(new HashSet<String>(Arrays.asList(new String[] {
 414             "2.5.29.37.0", "1.3.6.1.5.5.7.3.1", "2.16.840.1.113730.4.1",
 415             "1.3.6.1.4.1.311.10.3.3" })));
 416 
 417         // set of valid EKU values for this type
 418         final Set<String> validEku;
 419 
 420         CheckType(Set<String> validEku) {
 421             this.validEku = validEku;
 422         }
 423 
 424         private static boolean getBit(boolean[] keyUsage, int bit) {
 425             return (bit < keyUsage.length) && keyUsage[bit];
 426         }
 427 
 428         // check if this certificate is appropriate for this type of use
 429         // first check extensions, if they match, check expiration
 430         // note: we may want to move this code into the sun.security.validator
 431         // package
 432         CheckResult check(X509Certificate cert, Date date) {
 433             if (this == NONE) {
 434                 return CheckResult.OK;
 435             }
 436 
 437             // check extensions
 438             try {
 439                 // check extended key usage
 440                 List<String> certEku = cert.getExtendedKeyUsage();
 441                 if ((certEku != null) && Collections.disjoint(validEku, certEku)) {
 442                     // if extension present and it does not contain any of
 443                     // the valid EKU OIDs, return extension_mismatch
 444                     return CheckResult.EXTENSION_MISMATCH;
 445                 }
 446 
 447                 // check key usage
 448                 boolean[] ku = cert.getKeyUsage();
 449                 if (ku != null) {
 450                     String algorithm = cert.getPublicKey().getAlgorithm();
 451                     boolean kuSignature = getBit(ku, 0);
 452                     if (algorithm.equals("RSA")) {
 453                         // require either signature bit
 454                         // or if server also allow key encipherment bit
 455                         if (kuSignature == false) {
 456                             if ((this == CLIENT) || (getBit(ku, 2) == false)) {
 457                                 return CheckResult.EXTENSION_MISMATCH;
 458                             }
 459                         }
 460                     } else if (algorithm.equals("DSA")) {
 461                         // require signature bit
 462                         if (kuSignature == false) {
 463                             return CheckResult.EXTENSION_MISMATCH;
 464                         }
 465                     } else if (algorithm.equals("DH")) {
 466                         // require keyagreement bit
 467                         if (getBit(ku, 4) == false) {
 468                             return CheckResult.EXTENSION_MISMATCH;
 469                         }
 470                     } else if (algorithm.equals("EC")) {
 471                         // require signature bit
 472                         if (kuSignature == false) {
 473                             return CheckResult.EXTENSION_MISMATCH;
 474                         }
 475                         // For servers, also require key agreement.
 476                         // This is not totally accurate as the keyAgreement bit
 477                         // is only necessary for static ECDH key exchange and
 478                         // not ephemeral ECDH. We leave it in for now until
 479                         // there are signs that this check causes problems
 480                         // for real world EC certificates.
 481                         if ((this == SERVER) && (getBit(ku, 4) == false)) {
 482                             return CheckResult.EXTENSION_MISMATCH;
 483                         }
 484                     }
 485                 }
 486             } catch (CertificateException e) {
 487                 // extensions unparseable, return failure
 488                 return CheckResult.EXTENSION_MISMATCH;
 489             }
 490 
 491             try {
 492                 cert.checkValidity(date);
 493                 return CheckResult.OK;
 494             } catch (CertificateException e) {
 495                 return CheckResult.EXPIRED;
 496             }
 497         }
 498     }
 499 
 500     // enum for the result of the extension check
 501     // NOTE: the order of the constants is important as they are used
 502     // for sorting, i.e. OK is best, followed by EXPIRED and EXTENSION_MISMATCH
 503     private static enum CheckResult {
 504         OK,                     // ok or not checked
 505         EXPIRED,                // extensions valid but cert expired
 506         EXTENSION_MISMATCH,     // extensions invalid (expiration not checked)
 507     }
 508 
 509     /*
 510      * Return a List of all candidate matches in the specified builder
 511      * that fit the parameters.
 512      * We exclude entries in the KeyStore if they are not:
 513      *  . private key entries
 514      *  . the certificates are not X509 certificates
 515      *  . the algorithm of the key in the EE cert doesn't match one of keyTypes
 516      *  . none of the certs is issued by a Principal in issuerSet
 517      * Using those entries would not be possible or they would almost
 518      * certainly be rejected by the peer.
 519      *
 520      * In addition to those checks, we also check the extensions in the EE
 521      * cert and its expiration. Even if there is a mismatch, we include
 522      * such certificates because they technically work and might be accepted
 523      * by the peer. This leads to more graceful failure and better error
 524      * messages if the cert expires from one day to the next.
 525      *
 526      * The return values are:
 527      *   . null, if there are no matching entries at all
 528      *   . if 'findAll' is 'false' and there is a perfect match, a List
 529      *     with a single element (early return)
 530      *   . if 'findAll' is 'false' and there is NO perfect match, a List
 531      *     with all the imperfect matches (expired, wrong extensions)
 532      *   . if 'findAll' is 'true', a List with all perfect and imperfect
 533      *     matches
 534      */
 535     private List<EntryStatus> getAliases(int builderIndex,
 536             List<KeyType> keyTypes, Set<Principal> issuerSet,
 537             boolean findAll, CheckType checkType) throws Exception {
 538         Builder builder = builders.get(builderIndex);
 539         KeyStore ks = builder.getKeyStore();
 540         List<EntryStatus> results = null;
 541         Date date = verificationDate;
 542         boolean preferred = false;
 543         for (Enumeration<String> e = ks.aliases(); e.hasMoreElements(); ) {
 544             String alias = e.nextElement();
 545             // check if it is a key entry (private key or secret key)
 546             if (ks.isKeyEntry(alias) == false) {
 547                 continue;
 548             }
 549 
 550             Certificate[] chain = ks.getCertificateChain(alias);
 551             if ((chain == null) || (chain.length == 0)) {
 552                 // must be secret key entry, ignore
 553                 continue;
 554             }
 555             // check keytype
 556             int keyIndex = -1;
 557             int j = 0;
 558             for (KeyType keyType : keyTypes) {
 559                 if (keyType.matches(chain)) {
 560                     keyIndex = j;
 561                     break;
 562                 }
 563                 j++;
 564             }
 565             if (keyIndex == -1) {
 566                 if (useDebug) {
 567                     debug.println("Ignoring alias " + alias
 568                                 + ": key algorithm does not match");
 569                 }
 570                 continue;
 571             }
 572             // check issuers
 573             if (issuerSet != null) {
 574                 boolean found = false;
 575                 for (Certificate cert : chain) {
 576                     if (cert instanceof X509Certificate == false) {
 577                         // not an X509Certificate, ignore this entry
 578                         break;
 579                     }
 580                     X509Certificate xcert = (X509Certificate)cert;
 581                     if (issuerSet.contains(xcert.getIssuerX500Principal())) {
 582                         found = true;
 583                         break;
 584                     }
 585                 }
 586                 if (found == false) {
 587                     if (useDebug) {
 588                         debug.println("Ignoring alias " + alias
 589                                     + ": issuers do not match");
 590                     }
 591                     continue;
 592                 }
 593             }
 594             if (date == null) {
 595                 date = new Date();
 596             }
 597             CheckResult checkResult =
 598                     checkType.check((X509Certificate)chain[0], date);
 599             EntryStatus status =
 600                     new EntryStatus(builderIndex, keyIndex,
 601                                         alias, chain, checkResult);
 602             if (!preferred && checkResult == CheckResult.OK && keyIndex == 0) {
 603                 preferred = true;
 604             }
 605             if (preferred && (findAll == false)) {
 606                 // if we have a good match and do not need all matches,
 607                 // return immediately
 608                 return Collections.singletonList(status);
 609             } else {
 610                 if (results == null) {
 611                     results = new ArrayList<EntryStatus>();
 612                 }
 613                 results.add(status);
 614             }
 615         }
 616         return results;
 617     }
 618 
 619 }