1/* 2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.security.provider.certpath.ldap; 27 28import java.io.ByteArrayInputStream; 29import java.io.IOException; 30import java.math.BigInteger; 31import java.net.URI; 32import java.util.*; 33import javax.naming.Context; 34import javax.naming.NamingEnumeration; 35import javax.naming.NamingException; 36import javax.naming.NameNotFoundException; 37import javax.naming.directory.Attribute; 38import javax.naming.directory.Attributes; 39import javax.naming.directory.BasicAttributes; 40import javax.naming.directory.DirContext; 41import javax.naming.directory.InitialDirContext; 42 43import java.security.*; 44import java.security.cert.Certificate; 45import java.security.cert.*; 46import javax.security.auth.x500.X500Principal; 47 48import sun.misc.HexDumpEncoder; 49import sun.security.provider.certpath.X509CertificatePair; 50import sun.security.util.Cache; 51import sun.security.util.Debug; 52import sun.security.x509.X500Name; 53import sun.security.action.GetBooleanAction; 54import sun.security.action.GetPropertyAction; 55 56/** 57 * A <code>CertStore</code> that retrieves <code>Certificates</code> and 58 * <code>CRL</code>s from an LDAP directory, using the PKIX LDAP V2 Schema 59 * (RFC 2587): 60 * <a href="http://www.ietf.org/rfc/rfc2587.txt"> 61 * http://www.ietf.org/rfc/rfc2587.txt</a>. 62 * <p> 63 * Before calling the {@link #engineGetCertificates engineGetCertificates} or 64 * {@link #engineGetCRLs engineGetCRLs} methods, the 65 * {@link #LDAPCertStore(CertStoreParameters) 66 * LDAPCertStore(CertStoreParameters)} constructor is called to create the 67 * <code>CertStore</code> and establish the DNS name and port of the LDAP 68 * server from which <code>Certificate</code>s and <code>CRL</code>s will be 69 * retrieved. 70 * <p> 71 * <b>Concurrent Access</b> 72 * <p> 73 * As described in the javadoc for <code>CertStoreSpi</code>, the 74 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods 75 * must be thread-safe. That is, multiple threads may concurrently 76 * invoke these methods on a single <code>LDAPCertStore</code> object 77 * (or more than one) with no ill effects. This allows a 78 * <code>CertPathBuilder</code> to search for a CRL while simultaneously 79 * searching for further certificates, for instance. 80 * <p> 81 * This is achieved by adding the <code>synchronized</code> keyword to the 82 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods. 83 * <p> 84 * This classes uses caching and requests multiple attributes at once to 85 * minimize LDAP round trips. The cache is associated with the CertStore 86 * instance. It uses soft references to hold the values to minimize impact 87 * on footprint and currently has a maximum size of 750 attributes and a 88 * 30 second default lifetime. 89 * <p> 90 * We always request CA certificates, cross certificate pairs, and ARLs in 91 * a single LDAP request when any one of them is needed. The reason is that 92 * we typically need all of them anyway and requesting them in one go can 93 * reduce the number of requests to a third. Even if we don't need them, 94 * these attributes are typically small enough not to cause a noticeable 95 * overhead. In addition, when the prefetchCRLs flag is true, we also request 96 * the full CRLs. It is currently false initially but set to true once any 97 * request for an ARL to the server returns an null value. The reason is 98 * that CRLs could be rather large but are rarely used. This implementation 99 * should improve performance in most cases. 100 * 101 * @see java.security.cert.CertStore 102 * 103 * @since 1.4 104 * @author Steve Hanna 105 * @author Andreas Sterbenz 106 */ 107public final class LDAPCertStore extends CertStoreSpi { 108 109 private static final Debug debug = Debug.getInstance("certpath"); 110 111 private final static boolean DEBUG = false; 112 113 /** 114 * LDAP attribute identifiers. 115 */ 116 private static final String USER_CERT = "userCertificate;binary"; 117 private static final String CA_CERT = "cACertificate;binary"; 118 private static final String CROSS_CERT = "crossCertificatePair;binary"; 119 private static final String CRL = "certificateRevocationList;binary"; 120 private static final String ARL = "authorityRevocationList;binary"; 121 private static final String DELTA_CRL = "deltaRevocationList;binary"; 122 123 // Constants for various empty values 124 private final static String[] STRING0 = new String[0]; 125 126 private final static byte[][] BB0 = new byte[0][]; 127 128 private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes(); 129 130 // cache related constants 131 private final static int DEFAULT_CACHE_SIZE = 750; 132 private final static int DEFAULT_CACHE_LIFETIME = 30; 133 134 private final static int LIFETIME; 135 136 private final static String PROP_LIFETIME = 137 "sun.security.certpath.ldap.cache.lifetime"; 138 139 /* 140 * Internal system property, that when set to "true", disables the 141 * JNDI application resource files lookup to prevent recursion issues 142 * when validating signed JARs with LDAP URLs in certificates. 143 */ 144 private final static String PROP_DISABLE_APP_RESOURCE_FILES = 145 "sun.security.certpath.ldap.disable.app.resource.files"; 146 147 static { 148 String s = AccessController.doPrivileged( 149 new GetPropertyAction(PROP_LIFETIME)); 150 if (s != null) { 151 LIFETIME = Integer.parseInt(s); // throws NumberFormatException 152 } else { 153 LIFETIME = DEFAULT_CACHE_LIFETIME; 154 } 155 } 156 157 /** 158 * The CertificateFactory used to decode certificates from 159 * their binary stored form. 160 */ 161 private CertificateFactory cf; 162 /** 163 * The JNDI directory context. 164 */ 165 private DirContext ctx; 166 167 /** 168 * Flag indicating whether we should prefetch CRLs. 169 */ 170 private boolean prefetchCRLs = false; 171 172 private final Cache<String, byte[][]> valueCache; 173 174 private int cacheHits = 0; 175 private int cacheMisses = 0; 176 private int requests = 0; 177 178 /** 179 * Creates a <code>CertStore</code> with the specified parameters. 180 * For this class, the parameters object must be an instance of 181 * <code>LDAPCertStoreParameters</code>. 182 * 183 * @param params the algorithm parameters 184 * @exception InvalidAlgorithmParameterException if params is not an 185 * instance of <code>LDAPCertStoreParameters</code> 186 */ 187 public LDAPCertStore(CertStoreParameters params) 188 throws InvalidAlgorithmParameterException { 189 super(params); 190 if (!(params instanceof LDAPCertStoreParameters)) 191 throw new InvalidAlgorithmParameterException( 192 "parameters must be LDAPCertStoreParameters"); 193 194 LDAPCertStoreParameters lparams = (LDAPCertStoreParameters) params; 195 196 // Create InitialDirContext needed to communicate with the server 197 createInitialDirContext(lparams.getServerName(), lparams.getPort()); 198 199 // Create CertificateFactory for use later on 200 try { 201 cf = CertificateFactory.getInstance("X.509"); 202 } catch (CertificateException e) { 203 throw new InvalidAlgorithmParameterException( 204 "unable to create CertificateFactory for X.509"); 205 } 206 if (LIFETIME == 0) { 207 valueCache = Cache.newNullCache(); 208 } else if (LIFETIME < 0) { 209 valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE); 210 } else { 211 valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME); 212 } 213 } 214 215 /** 216 * Returns an LDAP CertStore. This method consults a cache of 217 * CertStores (shared per JVM) using the LDAP server/port as a key. 218 */ 219 private static final Cache<LDAPCertStoreParameters, CertStore> 220 certStoreCache = Cache.newSoftMemoryCache(185); 221 static synchronized CertStore getInstance(LDAPCertStoreParameters params) 222 throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { 223 CertStore lcs = certStoreCache.get(params); 224 if (lcs == null) { 225 lcs = CertStore.getInstance("LDAP", params); 226 certStoreCache.put(params, lcs); 227 } else { 228 if (debug != null) { 229 debug.println("LDAPCertStore.getInstance: cache hit"); 230 } 231 } 232 return lcs; 233 } 234 235 /** 236 * Create InitialDirContext. 237 * 238 * @param server Server DNS name hosting LDAP service 239 * @param port Port at which server listens for requests 240 * @throws InvalidAlgorithmParameterException if creation fails 241 */ 242 private void createInitialDirContext(String server, int port) 243 throws InvalidAlgorithmParameterException { 244 String url = "ldap://" + server + ":" + port; 245 Hashtable<String,Object> env = new Hashtable<>(); 246 env.put(Context.INITIAL_CONTEXT_FACTORY, 247 "com.sun.jndi.ldap.LdapCtxFactory"); 248 env.put(Context.PROVIDER_URL, url); 249 250 // If property is set to true, disable application resource file lookup. 251 boolean disableAppResourceFiles = AccessController.doPrivileged( 252 new GetBooleanAction(PROP_DISABLE_APP_RESOURCE_FILES)); 253 if (disableAppResourceFiles) { 254 if (debug != null) { 255 debug.println("LDAPCertStore disabling app resource files"); 256 } 257 env.put("com.sun.naming.disable.app.resource.files", "true"); 258 } 259 260 try { 261 ctx = new InitialDirContext(env); 262 /* 263 * By default, follow referrals unless application has 264 * overridden property in an application resource file. 265 */ 266 Hashtable<?,?> currentEnv = ctx.getEnvironment(); 267 if (currentEnv.get(Context.REFERRAL) == null) { 268 ctx.addToEnvironment(Context.REFERRAL, "follow"); 269 } 270 } catch (NamingException e) { 271 if (debug != null) { 272 debug.println("LDAPCertStore.engineInit about to throw " 273 + "InvalidAlgorithmParameterException"); 274 e.printStackTrace(); 275 } 276 Exception ee = new InvalidAlgorithmParameterException 277 ("unable to create InitialDirContext using supplied parameters"); 278 ee.initCause(e); 279 throw (InvalidAlgorithmParameterException)ee; 280 } 281 } 282 283 /** 284 * Private class encapsulating the actual LDAP operations and cache 285 * handling. Use: 286 * 287 * LDAPRequest request = new LDAPRequest(dn); 288 * request.addRequestedAttribute(CROSS_CERT); 289 * request.addRequestedAttribute(CA_CERT); 290 * byte[][] crossValues = request.getValues(CROSS_CERT); 291 * byte[][] caValues = request.getValues(CA_CERT); 292 * 293 * At most one LDAP request is sent for each instance created. If all 294 * getValues() calls can be satisfied from the cache, no request 295 * is sent at all. If a request is sent, all requested attributes 296 * are always added to the cache irrespective of whether the getValues() 297 * method is called. 298 */ 299 private class LDAPRequest { 300 301 private final String name; 302 private Map<String, byte[][]> valueMap; 303 private final List<String> requestedAttributes; 304 305 LDAPRequest(String name) { 306 this.name = name; 307 requestedAttributes = new ArrayList<>(5); 308 } 309 310 String getName() { 311 return name; 312 } 313 314 void addRequestedAttribute(String attrId) { 315 if (valueMap != null) { 316 throw new IllegalStateException("Request already sent"); 317 } 318 requestedAttributes.add(attrId); 319 } 320 321 /** 322 * Gets one or more binary values from an attribute. 323 * 324 * @param name the location holding the attribute 325 * @param attrId the attribute identifier 326 * @return an array of binary values (byte arrays) 327 * @throws NamingException if a naming exception occurs 328 */ 329 byte[][] getValues(String attrId) throws NamingException { 330 if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) { 331 System.out.println("Cache hits: " + cacheHits + "; misses: " 332 + cacheMisses); 333 } 334 String cacheKey = name + "|" + attrId; 335 byte[][] values = valueCache.get(cacheKey); 336 if (values != null) { 337 cacheHits++; 338 return values; 339 } 340 cacheMisses++; 341 Map<String, byte[][]> attrs = getValueMap(); 342 values = attrs.get(attrId); 343 return values; 344 } 345 346 /** 347 * Get a map containing the values for this request. The first time 348 * this method is called on an object, the LDAP request is sent, 349 * the results parsed and added to a private map and also to the 350 * cache of this LDAPCertStore. Subsequent calls return the private 351 * map immediately. 352 * 353 * The map contains an entry for each requested attribute. The 354 * attribute name is the key, values are byte[][]. If there are no 355 * values for that attribute, values are byte[0][]. 356 * 357 * @return the value Map 358 * @throws NamingException if a naming exception occurs 359 */ 360 private Map<String, byte[][]> getValueMap() throws NamingException { 361 if (valueMap != null) { 362 return valueMap; 363 } 364 if (DEBUG) { 365 System.out.println("Request: " + name + ":" + requestedAttributes); 366 requests++; 367 if (requests % 5 == 0) { 368 System.out.println("LDAP requests: " + requests); 369 } 370 } 371 valueMap = new HashMap<>(8); 372 String[] attrIds = requestedAttributes.toArray(STRING0); 373 Attributes attrs; 374 try { 375 attrs = ctx.getAttributes(name, attrIds); 376 } catch (NameNotFoundException e) { 377 // name does not exist on this LDAP server 378 // treat same as not attributes found 379 attrs = EMPTY_ATTRIBUTES; 380 } 381 for (String attrId : requestedAttributes) { 382 Attribute attr = attrs.get(attrId); 383 byte[][] values = getAttributeValues(attr); 384 cacheAttribute(attrId, values); 385 valueMap.put(attrId, values); 386 } 387 return valueMap; 388 } 389 390 /** 391 * Add the values to the cache. 392 */ 393 private void cacheAttribute(String attrId, byte[][] values) { 394 String cacheKey = name + "|" + attrId; 395 valueCache.put(cacheKey, values); 396 } 397 398 /** 399 * Get the values for the given attribute. If the attribute is null 400 * or does not contain any values, a zero length byte array is 401 * returned. NOTE that it is assumed that all values are byte arrays. 402 */ 403 private byte[][] getAttributeValues(Attribute attr) 404 throws NamingException { 405 byte[][] values; 406 if (attr == null) { 407 values = BB0; 408 } else { 409 values = new byte[attr.size()][]; 410 int i = 0; 411 NamingEnumeration<?> enum_ = attr.getAll(); 412 while (enum_.hasMore()) { 413 Object obj = enum_.next(); 414 if (debug != null) { 415 if (obj instanceof String) { 416 debug.println("LDAPCertStore.getAttrValues() " 417 + "enum.next is a string!: " + obj); 418 } 419 } 420 byte[] value = (byte[])obj; 421 values[i++] = value; 422 } 423 } 424 return values; 425 } 426 427 } 428 429 /* 430 * Gets certificates from an attribute id and location in the LDAP 431 * directory. Returns a Collection containing only the Certificates that 432 * match the specified CertSelector. 433 * 434 * @param name the location holding the attribute 435 * @param id the attribute identifier 436 * @param sel a CertSelector that the Certificates must match 437 * @return a Collection of Certificates found 438 * @throws CertStoreException if an exception occurs 439 */ 440 private Collection<X509Certificate> getCertificates(LDAPRequest request, 441 String id, X509CertSelector sel) throws CertStoreException { 442 443 /* fetch encoded certs from storage */ 444 byte[][] encodedCert; 445 try { 446 encodedCert = request.getValues(id); 447 } catch (NamingException namingEx) { 448 throw new CertStoreException(namingEx); 449 } 450 451 int n = encodedCert.length; 452 if (n == 0) { 453 return Collections.emptySet(); 454 } 455 456 List<X509Certificate> certs = new ArrayList<>(n); 457 /* decode certs and check if they satisfy selector */ 458 for (int i = 0; i < n; i++) { 459 ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]); 460 try { 461 Certificate cert = cf.generateCertificate(bais); 462 if (sel.match(cert)) { 463 certs.add((X509Certificate)cert); 464 } 465 } catch (CertificateException e) { 466 if (debug != null) { 467 debug.println("LDAPCertStore.getCertificates() encountered " 468 + "exception while parsing cert, skipping the bad data: "); 469 HexDumpEncoder encoder = new HexDumpEncoder(); 470 debug.println( 471 "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]"); 472 } 473 } 474 } 475 476 return certs; 477 } 478 479 /* 480 * Gets certificate pairs from an attribute id and location in the LDAP 481 * directory. 482 * 483 * @param name the location holding the attribute 484 * @param id the attribute identifier 485 * @return a Collection of X509CertificatePairs found 486 * @throws CertStoreException if an exception occurs 487 */ 488 private Collection<X509CertificatePair> getCertPairs( 489 LDAPRequest request, String id) throws CertStoreException { 490 491 /* fetch the encoded cert pairs from storage */ 492 byte[][] encodedCertPair; 493 try { 494 encodedCertPair = request.getValues(id); 495 } catch (NamingException namingEx) { 496 throw new CertStoreException(namingEx); 497 } 498 499 int n = encodedCertPair.length; 500 if (n == 0) { 501 return Collections.emptySet(); 502 } 503 504 List<X509CertificatePair> certPairs = new ArrayList<>(n); 505 /* decode each cert pair and add it to the Collection */ 506 for (int i = 0; i < n; i++) { 507 try { 508 X509CertificatePair certPair = 509 X509CertificatePair.generateCertificatePair(encodedCertPair[i]); 510 certPairs.add(certPair); 511 } catch (CertificateException e) { 512 if (debug != null) { 513 debug.println( 514 "LDAPCertStore.getCertPairs() encountered exception " 515 + "while parsing cert, skipping the bad data: "); 516 HexDumpEncoder encoder = new HexDumpEncoder(); 517 debug.println( 518 "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]"); 519 } 520 } 521 } 522 523 return certPairs; 524 } 525 526 /* 527 * Looks at certificate pairs stored in the crossCertificatePair attribute 528 * at the specified location in the LDAP directory. Returns a Collection 529 * containing all Certificates stored in the forward component that match 530 * the forward CertSelector and all Certificates stored in the reverse 531 * component that match the reverse CertSelector. 532 * <p> 533 * If either forward or reverse is null, all certificates from the 534 * corresponding component will be rejected. 535 * 536 * @param name the location to look in 537 * @param forward the forward CertSelector (or null) 538 * @param reverse the reverse CertSelector (or null) 539 * @return a Collection of Certificates found 540 * @throws CertStoreException if an exception occurs 541 */ 542 private Collection<X509Certificate> getMatchingCrossCerts( 543 LDAPRequest request, X509CertSelector forward, 544 X509CertSelector reverse) 545 throws CertStoreException { 546 // Get the cert pairs 547 Collection<X509CertificatePair> certPairs = 548 getCertPairs(request, CROSS_CERT); 549 550 // Find Certificates that match and put them in a list 551 ArrayList<X509Certificate> matchingCerts = new ArrayList<>(); 552 for (X509CertificatePair certPair : certPairs) { 553 X509Certificate cert; 554 if (forward != null) { 555 cert = certPair.getForward(); 556 if ((cert != null) && forward.match(cert)) { 557 matchingCerts.add(cert); 558 } 559 } 560 if (reverse != null) { 561 cert = certPair.getReverse(); 562 if ((cert != null) && reverse.match(cert)) { 563 matchingCerts.add(cert); 564 } 565 } 566 } 567 return matchingCerts; 568 } 569 570 /** 571 * Returns a <code>Collection</code> of <code>Certificate</code>s that 572 * match the specified selector. If no <code>Certificate</code>s 573 * match the selector, an empty <code>Collection</code> will be returned. 574 * <p> 575 * It is not practical to search every entry in the LDAP database for 576 * matching <code>Certificate</code>s. Instead, the <code>CertSelector</code> 577 * is examined in order to determine where matching <code>Certificate</code>s 578 * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). 579 * If the subject is specified, its directory entry is searched. If the 580 * issuer is specified, its directory entry is searched. If neither the 581 * subject nor the issuer are specified (or the selector is not an 582 * <code>X509CertSelector</code>), a <code>CertStoreException</code> is 583 * thrown. 584 * 585 * @param selector a <code>CertSelector</code> used to select which 586 * <code>Certificate</code>s should be returned. 587 * @return a <code>Collection</code> of <code>Certificate</code>s that 588 * match the specified selector 589 * @throws CertStoreException if an exception occurs 590 */ 591 public synchronized Collection<X509Certificate> engineGetCertificates 592 (CertSelector selector) throws CertStoreException { 593 if (debug != null) { 594 debug.println("LDAPCertStore.engineGetCertificates() selector: " 595 + String.valueOf(selector)); 596 } 597 598 if (selector == null) { 599 selector = new X509CertSelector(); 600 } 601 if (!(selector instanceof X509CertSelector)) { 602 throw new CertStoreException("LDAPCertStore needs an X509CertSelector " + 603 "to find certs"); 604 } 605 X509CertSelector xsel = (X509CertSelector) selector; 606 int basicConstraints = xsel.getBasicConstraints(); 607 String subject = xsel.getSubjectAsString(); 608 String issuer = xsel.getIssuerAsString(); 609 HashSet<X509Certificate> certs = new HashSet<>(); 610 if (debug != null) { 611 debug.println("LDAPCertStore.engineGetCertificates() basicConstraints: " 612 + basicConstraints); 613 } 614 615 // basicConstraints: 616 // -2: only EE certs accepted 617 // -1: no check is done 618 // 0: any CA certificate accepted 619 // >1: certificate's basicConstraints extension pathlen must match 620 if (subject != null) { 621 if (debug != null) { 622 debug.println("LDAPCertStore.engineGetCertificates() " 623 + "subject is not null"); 624 } 625 LDAPRequest request = new LDAPRequest(subject); 626 if (basicConstraints > -2) { 627 request.addRequestedAttribute(CROSS_CERT); 628 request.addRequestedAttribute(CA_CERT); 629 request.addRequestedAttribute(ARL); 630 if (prefetchCRLs) { 631 request.addRequestedAttribute(CRL); 632 } 633 } 634 if (basicConstraints < 0) { 635 request.addRequestedAttribute(USER_CERT); 636 } 637 638 if (basicConstraints > -2) { 639 certs.addAll(getMatchingCrossCerts(request, xsel, null)); 640 if (debug != null) { 641 debug.println("LDAPCertStore.engineGetCertificates() after " 642 + "getMatchingCrossCerts(subject,xsel,null),certs.size(): " 643 + certs.size()); 644 } 645 certs.addAll(getCertificates(request, CA_CERT, xsel)); 646 if (debug != null) { 647 debug.println("LDAPCertStore.engineGetCertificates() after " 648 + "getCertificates(subject,CA_CERT,xsel),certs.size(): " 649 + certs.size()); 650 } 651 } 652 if (basicConstraints < 0) { 653 certs.addAll(getCertificates(request, USER_CERT, xsel)); 654 if (debug != null) { 655 debug.println("LDAPCertStore.engineGetCertificates() after " 656 + "getCertificates(subject,USER_CERT, xsel),certs.size(): " 657 + certs.size()); 658 } 659 } 660 } else { 661 if (debug != null) { 662 debug.println 663 ("LDAPCertStore.engineGetCertificates() subject is null"); 664 } 665 if (basicConstraints == -2) { 666 throw new CertStoreException("need subject to find EE certs"); 667 } 668 if (issuer == null) { 669 throw new CertStoreException("need subject or issuer to find certs"); 670 } 671 } 672 if (debug != null) { 673 debug.println("LDAPCertStore.engineGetCertificates() about to " 674 + "getMatchingCrossCerts..."); 675 } 676 if ((issuer != null) && (basicConstraints > -2)) { 677 LDAPRequest request = new LDAPRequest(issuer); 678 request.addRequestedAttribute(CROSS_CERT); 679 request.addRequestedAttribute(CA_CERT); 680 request.addRequestedAttribute(ARL); 681 if (prefetchCRLs) { 682 request.addRequestedAttribute(CRL); 683 } 684 685 certs.addAll(getMatchingCrossCerts(request, null, xsel)); 686 if (debug != null) { 687 debug.println("LDAPCertStore.engineGetCertificates() after " 688 + "getMatchingCrossCerts(issuer,null,xsel),certs.size(): " 689 + certs.size()); 690 } 691 certs.addAll(getCertificates(request, CA_CERT, xsel)); 692 if (debug != null) { 693 debug.println("LDAPCertStore.engineGetCertificates() after " 694 + "getCertificates(issuer,CA_CERT,xsel),certs.size(): " 695 + certs.size()); 696 } 697 } 698 if (debug != null) { 699 debug.println("LDAPCertStore.engineGetCertificates() returning certs"); 700 } 701 return certs; 702 } 703 704 /* 705 * Gets CRLs from an attribute id and location in the LDAP directory. 706 * Returns a Collection containing only the CRLs that match the 707 * specified CRLSelector. 708 * 709 * @param name the location holding the attribute 710 * @param id the attribute identifier 711 * @param sel a CRLSelector that the CRLs must match 712 * @return a Collection of CRLs found 713 * @throws CertStoreException if an exception occurs 714 */ 715 private Collection<X509CRL> getCRLs(LDAPRequest request, String id, 716 X509CRLSelector sel) throws CertStoreException { 717 718 /* fetch the encoded crls from storage */ 719 byte[][] encodedCRL; 720 try { 721 encodedCRL = request.getValues(id); 722 } catch (NamingException namingEx) { 723 throw new CertStoreException(namingEx); 724 } 725 726 int n = encodedCRL.length; 727 if (n == 0) { 728 return Collections.emptySet(); 729 } 730 731 List<X509CRL> crls = new ArrayList<>(n); 732 /* decode each crl and check if it matches selector */ 733 for (int i = 0; i < n; i++) { 734 try { 735 CRL crl = cf.generateCRL(new ByteArrayInputStream(encodedCRL[i])); 736 if (sel.match(crl)) { 737 crls.add((X509CRL)crl); 738 } 739 } catch (CRLException e) { 740 if (debug != null) { 741 debug.println("LDAPCertStore.getCRLs() encountered exception" 742 + " while parsing CRL, skipping the bad data: "); 743 HexDumpEncoder encoder = new HexDumpEncoder(); 744 debug.println("[ " + encoder.encodeBuffer(encodedCRL[i]) + " ]"); 745 } 746 } 747 } 748 749 return crls; 750 } 751 752 /** 753 * Returns a <code>Collection</code> of <code>CRL</code>s that 754 * match the specified selector. If no <code>CRL</code>s 755 * match the selector, an empty <code>Collection</code> will be returned. 756 * <p> 757 * It is not practical to search every entry in the LDAP database for 758 * matching <code>CRL</code>s. Instead, the <code>CRLSelector</code> 759 * is examined in order to determine where matching <code>CRL</code>s 760 * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). 761 * If issuerNames or certChecking are specified, the issuer's directory 762 * entry is searched. If neither issuerNames or certChecking are specified 763 * (or the selector is not an <code>X509CRLSelector</code>), a 764 * <code>CertStoreException</code> is thrown. 765 * 766 * @param selector A <code>CRLSelector</code> used to select which 767 * <code>CRL</code>s should be returned. Specify <code>null</code> 768 * to return all <code>CRL</code>s. 769 * @return A <code>Collection</code> of <code>CRL</code>s that 770 * match the specified selector 771 * @throws CertStoreException if an exception occurs 772 */ 773 public synchronized Collection<X509CRL> engineGetCRLs(CRLSelector selector) 774 throws CertStoreException { 775 if (debug != null) { 776 debug.println("LDAPCertStore.engineGetCRLs() selector: " 777 + selector); 778 } 779 // Set up selector and collection to hold CRLs 780 if (selector == null) { 781 selector = new X509CRLSelector(); 782 } 783 if (!(selector instanceof X509CRLSelector)) { 784 throw new CertStoreException("need X509CRLSelector to find CRLs"); 785 } 786 X509CRLSelector xsel = (X509CRLSelector) selector; 787 HashSet<X509CRL> crls = new HashSet<>(); 788 789 // Look in directory entry for issuer of cert we're checking. 790 Collection<Object> issuerNames; 791 X509Certificate certChecking = xsel.getCertificateChecking(); 792 if (certChecking != null) { 793 issuerNames = new HashSet<>(); 794 X500Principal issuer = certChecking.getIssuerX500Principal(); 795 issuerNames.add(issuer.getName(X500Principal.RFC2253)); 796 } else { 797 // But if we don't know which cert we're checking, try the directory 798 // entries of all acceptable CRL issuers 799 issuerNames = xsel.getIssuerNames(); 800 if (issuerNames == null) { 801 throw new CertStoreException("need issuerNames or certChecking to " 802 + "find CRLs"); 803 } 804 } 805 for (Object nameObject : issuerNames) { 806 String issuerName; 807 if (nameObject instanceof byte[]) { 808 try { 809 X500Principal issuer = new X500Principal((byte[])nameObject); 810 issuerName = issuer.getName(X500Principal.RFC2253); 811 } catch (IllegalArgumentException e) { 812 continue; 813 } 814 } else { 815 issuerName = (String)nameObject; 816 } 817 // If all we want is CA certs, try to get the (probably shorter) ARL 818 Collection<X509CRL> entryCRLs = Collections.emptySet(); 819 if (certChecking == null || certChecking.getBasicConstraints() != -1) { 820 LDAPRequest request = new LDAPRequest(issuerName); 821 request.addRequestedAttribute(CROSS_CERT); 822 request.addRequestedAttribute(CA_CERT); 823 request.addRequestedAttribute(ARL); 824 if (prefetchCRLs) { 825 request.addRequestedAttribute(CRL); 826 } 827 try { 828 entryCRLs = getCRLs(request, ARL, xsel); 829 if (entryCRLs.isEmpty()) { 830 // no ARLs found. We assume that means that there are 831 // no ARLs on this server at all and prefetch the CRLs. 832 prefetchCRLs = true; 833 } else { 834 crls.addAll(entryCRLs); 835 } 836 } catch (CertStoreException e) { 837 if (debug != null) { 838 debug.println("LDAPCertStore.engineGetCRLs non-fatal error " 839 + "retrieving ARLs:" + e); 840 e.printStackTrace(); 841 } 842 } 843 } 844 // Otherwise, get the CRL 845 // if certChecking is null, we don't know if we should look in ARL or CRL 846 // attribute, so check both for matching CRLs. 847 if (entryCRLs.isEmpty() || certChecking == null) { 848 LDAPRequest request = new LDAPRequest(issuerName); 849 request.addRequestedAttribute(CRL); 850 entryCRLs = getCRLs(request, CRL, xsel); 851 crls.addAll(entryCRLs); 852 } 853 } 854 return crls; 855 } 856 857 // converts an LDAP URI into LDAPCertStoreParameters 858 static LDAPCertStoreParameters getParameters(URI uri) { 859 String host = uri.getHost(); 860 if (host == null) { 861 return new SunLDAPCertStoreParameters(); 862 } else { 863 int port = uri.getPort(); 864 return (port == -1 865 ? new SunLDAPCertStoreParameters(host) 866 : new SunLDAPCertStoreParameters(host, port)); 867 } 868 } 869 870 /* 871 * Subclass of LDAPCertStoreParameters with overridden equals/hashCode 872 * methods. This is necessary because the parameters are used as 873 * keys in the LDAPCertStore cache. 874 */ 875 private static class SunLDAPCertStoreParameters 876 extends LDAPCertStoreParameters { 877 878 private volatile int hashCode = 0; 879 880 SunLDAPCertStoreParameters(String serverName, int port) { 881 super(serverName, port); 882 } 883 SunLDAPCertStoreParameters(String serverName) { 884 super(serverName); 885 } 886 SunLDAPCertStoreParameters() { 887 super(); 888 } 889 public boolean equals(Object obj) { 890 if (!(obj instanceof LDAPCertStoreParameters)) { 891 return false; 892 } 893 LDAPCertStoreParameters params = (LDAPCertStoreParameters) obj; 894 return (getPort() == params.getPort() && 895 getServerName().equalsIgnoreCase(params.getServerName())); 896 } 897 public int hashCode() { 898 if (hashCode == 0) { 899 int result = 17; 900 result = 37*result + getPort(); 901 result = 37*result + 902 getServerName().toLowerCase(Locale.ENGLISH).hashCode(); 903 hashCode = result; 904 } 905 return hashCode; 906 } 907 } 908 909 /* 910 * This inner class wraps an existing X509CertSelector and adds 911 * additional criteria to match on when the certificate's subject is 912 * different than the LDAP Distinguished Name entry. The LDAPCertStore 913 * implementation uses the subject DN as the directory entry for 914 * looking up certificates. This can be problematic if the certificates 915 * that you want to fetch have a different subject DN than the entry 916 * where they are stored. You could set the selector's subject to the 917 * LDAP DN entry, but then the resulting match would fail to find the 918 * desired certificates because the subject DNs would not match. This 919 * class avoids that problem by introducing a certSubject which should 920 * be set to the certificate's subject DN when it is different than 921 * the LDAP DN. 922 */ 923 static class LDAPCertSelector extends X509CertSelector { 924 925 private X500Principal certSubject; 926 private X509CertSelector selector; 927 private X500Principal subject; 928 929 /** 930 * Creates an LDAPCertSelector. 931 * 932 * @param selector the X509CertSelector to wrap 933 * @param certSubject the subject DN of the certificate that you want 934 * to retrieve via LDAP 935 * @param ldapDN the LDAP DN where the certificate is stored 936 */ 937 LDAPCertSelector(X509CertSelector selector, X500Principal certSubject, 938 String ldapDN) throws IOException { 939 this.selector = selector == null ? new X509CertSelector() : selector; 940 this.certSubject = certSubject; 941 this.subject = new X500Name(ldapDN).asX500Principal(); 942 } 943 944 // we only override the get (accessor methods) since the set methods 945 // will not be invoked by the code that uses this LDAPCertSelector. 946 public X509Certificate getCertificate() { 947 return selector.getCertificate(); 948 } 949 public BigInteger getSerialNumber() { 950 return selector.getSerialNumber(); 951 } 952 public X500Principal getIssuer() { 953 return selector.getIssuer(); 954 } 955 public String getIssuerAsString() { 956 return selector.getIssuerAsString(); 957 } 958 public byte[] getIssuerAsBytes() throws IOException { 959 return selector.getIssuerAsBytes(); 960 } 961 public X500Principal getSubject() { 962 // return the ldap DN 963 return subject; 964 } 965 public String getSubjectAsString() { 966 // return the ldap DN 967 return subject.getName(); 968 } 969 public byte[] getSubjectAsBytes() throws IOException { 970 // return the encoded ldap DN 971 return subject.getEncoded(); 972 } 973 public byte[] getSubjectKeyIdentifier() { 974 return selector.getSubjectKeyIdentifier(); 975 } 976 public byte[] getAuthorityKeyIdentifier() { 977 return selector.getAuthorityKeyIdentifier(); 978 } 979 public Date getCertificateValid() { 980 return selector.getCertificateValid(); 981 } 982 public Date getPrivateKeyValid() { 983 return selector.getPrivateKeyValid(); 984 } 985 public String getSubjectPublicKeyAlgID() { 986 return selector.getSubjectPublicKeyAlgID(); 987 } 988 public PublicKey getSubjectPublicKey() { 989 return selector.getSubjectPublicKey(); 990 } 991 public boolean[] getKeyUsage() { 992 return selector.getKeyUsage(); 993 } 994 public Set<String> getExtendedKeyUsage() { 995 return selector.getExtendedKeyUsage(); 996 } 997 public boolean getMatchAllSubjectAltNames() { 998 return selector.getMatchAllSubjectAltNames(); 999 } 1000 public Collection<List<?>> getSubjectAlternativeNames() { 1001 return selector.getSubjectAlternativeNames(); 1002 } 1003 public byte[] getNameConstraints() { 1004 return selector.getNameConstraints(); 1005 } 1006 public int getBasicConstraints() { 1007 return selector.getBasicConstraints(); 1008 } 1009 public Set<String> getPolicy() { 1010 return selector.getPolicy(); 1011 } 1012 public Collection<List<?>> getPathToNames() { 1013 return selector.getPathToNames(); 1014 } 1015 1016 public boolean match(Certificate cert) { 1017 // temporarily set the subject criterion to the certSubject 1018 // so that match will not reject the desired certificates 1019 selector.setSubject(certSubject); 1020 boolean match = selector.match(cert); 1021 selector.setSubject(subject); 1022 return match; 1023 } 1024 } 1025 1026 /** 1027 * This class has the same purpose as LDAPCertSelector except it is for 1028 * X.509 CRLs. 1029 */ 1030 static class LDAPCRLSelector extends X509CRLSelector { 1031 1032 private X509CRLSelector selector; 1033 private Collection<X500Principal> certIssuers; 1034 private Collection<X500Principal> issuers; 1035 private HashSet<Object> issuerNames; 1036 1037 /** 1038 * Creates an LDAPCRLSelector. 1039 * 1040 * @param selector the X509CRLSelector to wrap 1041 * @param certIssuers the issuer DNs of the CRLs that you want 1042 * to retrieve via LDAP 1043 * @param ldapDN the LDAP DN where the CRL is stored 1044 */ 1045 LDAPCRLSelector(X509CRLSelector selector, 1046 Collection<X500Principal> certIssuers, String ldapDN) 1047 throws IOException { 1048 this.selector = selector == null ? new X509CRLSelector() : selector; 1049 this.certIssuers = certIssuers; 1050 issuerNames = new HashSet<>(); 1051 issuerNames.add(ldapDN); 1052 issuers = new HashSet<>(); 1053 issuers.add(new X500Name(ldapDN).asX500Principal()); 1054 } 1055 // we only override the get (accessor methods) since the set methods 1056 // will not be invoked by the code that uses this LDAPCRLSelector. 1057 public Collection<X500Principal> getIssuers() { 1058 // return the ldap DN 1059 return Collections.unmodifiableCollection(issuers); 1060 } 1061 public Collection<Object> getIssuerNames() { 1062 // return the ldap DN 1063 return Collections.unmodifiableCollection(issuerNames); 1064 } 1065 public BigInteger getMinCRL() { 1066 return selector.getMinCRL(); 1067 } 1068 public BigInteger getMaxCRL() { 1069 return selector.getMaxCRL(); 1070 } 1071 public Date getDateAndTime() { 1072 return selector.getDateAndTime(); 1073 } 1074 public X509Certificate getCertificateChecking() { 1075 return selector.getCertificateChecking(); 1076 } 1077 public boolean match(CRL crl) { 1078 // temporarily set the issuer criterion to the certIssuers 1079 // so that match will not reject the desired CRL 1080 selector.setIssuers(certIssuers); 1081 boolean match = selector.match(crl); 1082 selector.setIssuers(issuers); 1083 return match; 1084 } 1085 } 1086} 1087