1 /* 2 * Copyright (c) 2015, 2017, 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.certpath.ldap; 27 28 import java.io.ByteArrayInputStream; 29 import java.net.URI; 30 import java.util.*; 31 import javax.naming.CompositeName; 32 import javax.naming.Context; 33 import javax.naming.InvalidNameException; 34 import javax.naming.NamingEnumeration; 35 import javax.naming.NamingException; 36 import javax.naming.NameNotFoundException; 37 import javax.naming.directory.Attribute; 38 import javax.naming.directory.Attributes; 39 import javax.naming.directory.BasicAttributes; 40 41 import java.security.*; 42 import java.security.cert.Certificate; 43 import java.security.cert.*; 44 import javax.naming.CommunicationException; 45 import javax.naming.ldap.InitialLdapContext; 46 import javax.naming.ldap.LdapContext; 47 import javax.security.auth.x500.X500Principal; 48 49 import com.sun.jndi.ldap.LdapReferralException; 50 import sun.security.util.HexDumpEncoder; 51 import sun.security.provider.certpath.X509CertificatePair; 52 import sun.security.util.Cache; 53 import sun.security.util.Debug; 54 55 /** 56 * Core implementation of a LDAP Cert Store. 57 * @see java.security.cert.CertStore 58 * 59 * @since 9 60 */ 61 final class LDAPCertStoreImpl { 62 63 private static final Debug debug = Debug.getInstance("certpath"); 64 65 private final static boolean DEBUG = false; 66 67 /** 68 * LDAP attribute identifiers. 69 */ 70 private static final String USER_CERT = "userCertificate;binary"; 71 private static final String CA_CERT = "cACertificate;binary"; 72 private static final String CROSS_CERT = "crossCertificatePair;binary"; 73 private static final String CRL = "certificateRevocationList;binary"; 74 private static final String ARL = "authorityRevocationList;binary"; 75 private static final String DELTA_CRL = "deltaRevocationList;binary"; 76 77 // Constants for various empty values 78 private final static String[] STRING0 = new String[0]; 79 80 private final static byte[][] BB0 = new byte[0][]; 81 82 private final static Attributes EMPTY_ATTRIBUTES = new BasicAttributes(); 83 84 // cache related constants 85 private final static int DEFAULT_CACHE_SIZE = 750; 86 private final static int DEFAULT_CACHE_LIFETIME = 30; 87 88 private final static int LIFETIME; 89 90 private final static String PROP_LIFETIME = 91 "sun.security.certpath.ldap.cache.lifetime"; 92 93 /* 94 * Internal system property, that when set to "true", disables the 95 * JNDI application resource files lookup to prevent recursion issues 96 * when validating signed JARs with LDAP URLs in certificates. 97 */ 98 private final static String PROP_DISABLE_APP_RESOURCE_FILES = 99 "sun.security.certpath.ldap.disable.app.resource.files"; 100 101 static { 102 String s = AccessController.doPrivileged( 103 (PrivilegedAction<String>) () -> System.getProperty(PROP_LIFETIME)); 104 if (s != null) { 105 LIFETIME = Integer.parseInt(s); // throws NumberFormatException 106 } else { 107 LIFETIME = DEFAULT_CACHE_LIFETIME; 108 } 109 } 110 111 /** 112 * The CertificateFactory used to decode certificates from 113 * their binary stored form. 114 */ 115 private CertificateFactory cf; 116 /** 117 * The JNDI directory context. 118 */ 119 private LdapContext ctx; 120 121 /** 122 * Flag indicating that communication error occurred. 123 */ 124 private boolean communicationError = false; 125 126 /** 127 * Flag indicating whether we should prefetch CRLs. 128 */ 129 private boolean prefetchCRLs = false; 130 131 private final Cache<String, byte[][]> valueCache; 132 133 private int cacheHits = 0; 134 private int cacheMisses = 0; 135 private int requests = 0; 136 137 /** 138 * Creates a <code>CertStore</code> with the specified parameters. 139 */ LDAPCertStoreImpl(String serverName, int port)140 LDAPCertStoreImpl(String serverName, int port) 141 throws InvalidAlgorithmParameterException { 142 createInitialDirContext(serverName, port); 143 // Create CertificateFactory for use later on 144 try { 145 cf = CertificateFactory.getInstance("X.509"); 146 } catch (CertificateException e) { 147 throw new InvalidAlgorithmParameterException( 148 "unable to create CertificateFactory for X.509"); 149 } 150 if (LIFETIME == 0) { 151 valueCache = Cache.newNullCache(); 152 } else if (LIFETIME < 0) { 153 valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE); 154 } else { 155 valueCache = Cache.newSoftMemoryCache(DEFAULT_CACHE_SIZE, LIFETIME); 156 } 157 } 158 159 /** 160 * Create InitialDirContext. 161 * 162 * @param server Server DNS name hosting LDAP service 163 * @param port Port at which server listens for requests 164 * @throws InvalidAlgorithmParameterException if creation fails 165 */ createInitialDirContext(String server, int port)166 private void createInitialDirContext(String server, int port) 167 throws InvalidAlgorithmParameterException { 168 String url = "ldap://" + server + ":" + port; 169 Hashtable<String,Object> env = new Hashtable<>(); 170 env.put(Context.INITIAL_CONTEXT_FACTORY, 171 "com.sun.jndi.ldap.LdapCtxFactory"); 172 env.put(Context.PROVIDER_URL, url); 173 174 // If property is set to true, disable application resource file lookup. 175 boolean disableAppResourceFiles = AccessController.doPrivileged( 176 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean(PROP_DISABLE_APP_RESOURCE_FILES)); 177 if (disableAppResourceFiles) { 178 if (debug != null) { 179 debug.println("LDAPCertStore disabling app resource files"); 180 } 181 env.put("com.sun.naming.disable.app.resource.files", "true"); 182 } 183 184 try { 185 ctx = new InitialLdapContext(env, null); 186 /* 187 * Always deal with referrals here. 188 */ 189 ctx.addToEnvironment(Context.REFERRAL, "throw"); 190 } catch (NamingException e) { 191 if (debug != null) { 192 debug.println("LDAPCertStore.engineInit about to throw " 193 + "InvalidAlgorithmParameterException"); 194 e.printStackTrace(); 195 } 196 Exception ee = new InvalidAlgorithmParameterException 197 ("unable to create InitialDirContext using supplied parameters"); 198 ee.initCause(e); 199 throw (InvalidAlgorithmParameterException)ee; 200 } 201 } 202 203 /** 204 * Private class encapsulating the actual LDAP operations and cache 205 * handling. Use: 206 * 207 * LDAPRequest request = new LDAPRequest(dn); 208 * request.addRequestedAttribute(CROSS_CERT); 209 * request.addRequestedAttribute(CA_CERT); 210 * byte[][] crossValues = request.getValues(CROSS_CERT); 211 * byte[][] caValues = request.getValues(CA_CERT); 212 * 213 * At most one LDAP request is sent for each instance created. If all 214 * getValues() calls can be satisfied from the cache, no request 215 * is sent at all. If a request is sent, all requested attributes 216 * are always added to the cache irrespective of whether the getValues() 217 * method is called. 218 */ 219 private class LDAPRequest { 220 221 private final String name; 222 private Map<String, byte[][]> valueMap; 223 private final List<String> requestedAttributes; 224 LDAPRequest(String name)225 LDAPRequest(String name) throws CertStoreException { 226 this.name = checkName(name); 227 requestedAttributes = new ArrayList<>(5); 228 } 229 checkName(String name)230 private String checkName(String name) throws CertStoreException { 231 if (name == null) { 232 throw new CertStoreException("Name absent"); 233 } 234 try { 235 if (new CompositeName(name).size() > 1) { 236 throw new CertStoreException("Invalid name: " + name); 237 } 238 } catch (InvalidNameException ine) { 239 throw new CertStoreException("Invalid name: " + name, ine); 240 } 241 return name; 242 } 243 getName()244 String getName() { 245 return name; 246 } 247 addRequestedAttribute(String attrId)248 void addRequestedAttribute(String attrId) { 249 if (valueMap != null) { 250 throw new IllegalStateException("Request already sent"); 251 } 252 requestedAttributes.add(attrId); 253 } 254 255 /** 256 * Gets one or more binary values from an attribute. 257 * 258 * @param attrId the attribute identifier 259 * @return an array of binary values (byte arrays) 260 * @throws NamingException if a naming exception occurs 261 */ getValues(String attrId)262 byte[][] getValues(String attrId) throws NamingException { 263 if (DEBUG && ((cacheHits + cacheMisses) % 50 == 0)) { 264 System.out.println("Cache hits: " + cacheHits + "; misses: " 265 + cacheMisses); 266 } 267 String cacheKey = name + "|" + attrId; 268 byte[][] values = valueCache.get(cacheKey); 269 if (values != null) { 270 cacheHits++; 271 return values; 272 } 273 cacheMisses++; 274 Map<String, byte[][]> attrs = getValueMap(); 275 values = attrs.get(attrId); 276 return values; 277 } 278 279 /** 280 * Get a map containing the values for this request. The first time 281 * this method is called on an object, the LDAP request is sent, 282 * the results parsed and added to a private map and also to the 283 * cache of this LDAPCertStore. Subsequent calls return the private 284 * map immediately. 285 * 286 * The map contains an entry for each requested attribute. The 287 * attribute name is the key, values are byte[][]. If there are no 288 * values for that attribute, values are byte[0][]. 289 * 290 * @return the value Map 291 * @throws NamingException if a naming exception occurs 292 */ getValueMap()293 private Map<String, byte[][]> getValueMap() throws NamingException { 294 if (valueMap != null) { 295 return valueMap; 296 } 297 if (DEBUG) { 298 System.out.println("Request: " + name + ":" + requestedAttributes); 299 requests++; 300 if (requests % 5 == 0) { 301 System.out.println("LDAP requests: " + requests); 302 } 303 } 304 valueMap = new HashMap<>(8); 305 String[] attrIds = requestedAttributes.toArray(STRING0); 306 Attributes attrs; 307 308 if (communicationError) { 309 ctx.reconnect(null); 310 communicationError = false; 311 } 312 313 try { 314 attrs = ctx.getAttributes(name, attrIds); 315 } catch (LdapReferralException lre) { 316 // LdapCtx has a hopCount field to avoid infinite loop 317 while (true) { 318 try { 319 String newName = (String) lre.getReferralInfo(); 320 URI newUri = new URI(newName); 321 if (!newUri.getScheme().equalsIgnoreCase("ldap")) { 322 throw new IllegalArgumentException("Not LDAP"); 323 } 324 String newDn = newUri.getPath(); 325 if (newDn != null && newDn.charAt(0) == '/') { 326 newDn = newDn.substring(1); 327 } 328 checkName(newDn); 329 } catch (Exception e) { 330 throw new NamingException("Cannot follow referral to " 331 + lre.getReferralInfo()); 332 } 333 LdapContext refCtx = 334 (LdapContext)lre.getReferralContext(); 335 336 // repeat the original operation at the new context 337 try { 338 attrs = refCtx.getAttributes(name, attrIds); 339 break; 340 } catch (LdapReferralException re) { 341 lre = re; 342 continue; 343 } finally { 344 // Make sure we close referral context 345 refCtx.close(); 346 } 347 } 348 } catch (CommunicationException ce) { 349 communicationError = true; 350 throw ce; 351 } catch (NameNotFoundException e) { 352 // name does not exist on this LDAP server 353 // treat same as not attributes found 354 attrs = EMPTY_ATTRIBUTES; 355 } 356 for (String attrId : requestedAttributes) { 357 Attribute attr = attrs.get(attrId); 358 byte[][] values = getAttributeValues(attr); 359 cacheAttribute(attrId, values); 360 valueMap.put(attrId, values); 361 } 362 return valueMap; 363 } 364 365 /** 366 * Add the values to the cache. 367 */ cacheAttribute(String attrId, byte[][] values)368 private void cacheAttribute(String attrId, byte[][] values) { 369 String cacheKey = name + "|" + attrId; 370 valueCache.put(cacheKey, values); 371 } 372 373 /** 374 * Get the values for the given attribute. If the attribute is null 375 * or does not contain any values, a zero length byte array is 376 * returned. NOTE that it is assumed that all values are byte arrays. 377 */ getAttributeValues(Attribute attr)378 private byte[][] getAttributeValues(Attribute attr) 379 throws NamingException { 380 byte[][] values; 381 if (attr == null) { 382 values = BB0; 383 } else { 384 values = new byte[attr.size()][]; 385 int i = 0; 386 NamingEnumeration<?> enum_ = attr.getAll(); 387 while (enum_.hasMore()) { 388 Object obj = enum_.next(); 389 if (debug != null) { 390 if (obj instanceof String) { 391 debug.println("LDAPCertStore.getAttrValues() " 392 + "enum.next is a string!: " + obj); 393 } 394 } 395 byte[] value = (byte[])obj; 396 values[i++] = value; 397 } 398 } 399 return values; 400 } 401 402 } 403 404 /* 405 * Gets certificates from an attribute id and location in the LDAP 406 * directory. Returns a Collection containing only the Certificates that 407 * match the specified CertSelector. 408 * 409 * @param name the location holding the attribute 410 * @param id the attribute identifier 411 * @param sel a CertSelector that the Certificates must match 412 * @return a Collection of Certificates found 413 * @throws CertStoreException if an exception occurs 414 */ getCertificates(LDAPRequest request, String id, X509CertSelector sel)415 private Collection<X509Certificate> getCertificates(LDAPRequest request, 416 String id, X509CertSelector sel) throws CertStoreException { 417 418 /* fetch encoded certs from storage */ 419 byte[][] encodedCert; 420 try { 421 encodedCert = request.getValues(id); 422 } catch (NamingException namingEx) { 423 throw new CertStoreException(namingEx); 424 } 425 426 int n = encodedCert.length; 427 if (n == 0) { 428 return Collections.emptySet(); 429 } 430 431 List<X509Certificate> certs = new ArrayList<>(n); 432 /* decode certs and check if they satisfy selector */ 433 for (int i = 0; i < n; i++) { 434 ByteArrayInputStream bais = new ByteArrayInputStream(encodedCert[i]); 435 try { 436 Certificate cert = cf.generateCertificate(bais); 437 if (sel.match(cert)) { 438 certs.add((X509Certificate)cert); 439 } 440 } catch (CertificateException e) { 441 if (debug != null) { 442 debug.println("LDAPCertStore.getCertificates() encountered " 443 + "exception while parsing cert, skipping the bad data: "); 444 HexDumpEncoder encoder = new HexDumpEncoder(); 445 debug.println( 446 "[ " + encoder.encodeBuffer(encodedCert[i]) + " ]"); 447 } 448 } 449 } 450 451 return certs; 452 } 453 454 /* 455 * Gets certificate pairs from an attribute id and location in the LDAP 456 * directory. 457 * 458 * @param name the location holding the attribute 459 * @param id the attribute identifier 460 * @return a Collection of X509CertificatePairs found 461 * @throws CertStoreException if an exception occurs 462 */ getCertPairs( LDAPRequest request, String id)463 private Collection<X509CertificatePair> getCertPairs( 464 LDAPRequest request, String id) throws CertStoreException { 465 466 /* fetch the encoded cert pairs from storage */ 467 byte[][] encodedCertPair; 468 try { 469 encodedCertPair = request.getValues(id); 470 } catch (NamingException namingEx) { 471 throw new CertStoreException(namingEx); 472 } 473 474 int n = encodedCertPair.length; 475 if (n == 0) { 476 return Collections.emptySet(); 477 } 478 479 List<X509CertificatePair> certPairs = new ArrayList<>(n); 480 /* decode each cert pair and add it to the Collection */ 481 for (int i = 0; i < n; i++) { 482 try { 483 X509CertificatePair certPair = 484 X509CertificatePair.generateCertificatePair(encodedCertPair[i]); 485 certPairs.add(certPair); 486 } catch (CertificateException e) { 487 if (debug != null) { 488 debug.println( 489 "LDAPCertStore.getCertPairs() encountered exception " 490 + "while parsing cert, skipping the bad data: "); 491 HexDumpEncoder encoder = new HexDumpEncoder(); 492 debug.println( 493 "[ " + encoder.encodeBuffer(encodedCertPair[i]) + " ]"); 494 } 495 } 496 } 497 498 return certPairs; 499 } 500 501 /* 502 * Looks at certificate pairs stored in the crossCertificatePair attribute 503 * at the specified location in the LDAP directory. Returns a Collection 504 * containing all X509Certificates stored in the forward component that match 505 * the forward X509CertSelector and all Certificates stored in the reverse 506 * component that match the reverse X509CertSelector. 507 * <p> 508 * If either forward or reverse is null, all certificates from the 509 * corresponding component will be rejected. 510 * 511 * @param name the location to look in 512 * @param forward the forward X509CertSelector (or null) 513 * @param reverse the reverse X509CertSelector (or null) 514 * @return a Collection of X509Certificates found 515 * @throws CertStoreException if an exception occurs 516 */ getMatchingCrossCerts( LDAPRequest request, X509CertSelector forward, X509CertSelector reverse)517 private Collection<X509Certificate> getMatchingCrossCerts( 518 LDAPRequest request, X509CertSelector forward, 519 X509CertSelector reverse) 520 throws CertStoreException { 521 // Get the cert pairs 522 Collection<X509CertificatePair> certPairs = 523 getCertPairs(request, CROSS_CERT); 524 525 // Find Certificates that match and put them in a list 526 ArrayList<X509Certificate> matchingCerts = new ArrayList<>(); 527 for (X509CertificatePair certPair : certPairs) { 528 X509Certificate cert; 529 if (forward != null) { 530 cert = certPair.getForward(); 531 if ((cert != null) && forward.match(cert)) { 532 matchingCerts.add(cert); 533 } 534 } 535 if (reverse != null) { 536 cert = certPair.getReverse(); 537 if ((cert != null) && reverse.match(cert)) { 538 matchingCerts.add(cert); 539 } 540 } 541 } 542 return matchingCerts; 543 } 544 545 /** 546 * Returns a <code>Collection</code> of <code>X509Certificate</code>s that 547 * match the specified selector. If no <code>X509Certificate</code>s 548 * match the selector, an empty <code>Collection</code> will be returned. 549 * <p> 550 * It is not practical to search every entry in the LDAP database for 551 * matching <code>X509Certificate</code>s. Instead, the 552 * <code>X509CertSelector</code> is examined in order to determine where 553 * matching <code>Certificate</code>s are likely to be found (according 554 * to the PKIX LDAPv2 schema, RFC 2587). 555 * If the subject is specified, its directory entry is searched. If the 556 * issuer is specified, its directory entry is searched. If neither the 557 * subject nor the issuer are specified (or the selector is not an 558 * <code>X509CertSelector</code>), a <code>CertStoreException</code> is 559 * thrown. 560 * 561 * @param xsel a <code>X509CertSelector</code> used to select which 562 * <code>Certificate</code>s should be returned. 563 * @return a <code>Collection</code> of <code>X509Certificate</code>s that 564 * match the specified selector 565 * @throws CertStoreException if an exception occurs 566 */ getCertificates(X509CertSelector xsel, String ldapDN)567 synchronized Collection<X509Certificate> getCertificates 568 (X509CertSelector xsel, String ldapDN) throws CertStoreException { 569 570 if (ldapDN == null) { 571 ldapDN = xsel.getSubjectAsString(); 572 } 573 int basicConstraints = xsel.getBasicConstraints(); 574 String issuer = xsel.getIssuerAsString(); 575 HashSet<X509Certificate> certs = new HashSet<>(); 576 if (debug != null) { 577 debug.println("LDAPCertStore.engineGetCertificates() basicConstraints: " 578 + basicConstraints); 579 } 580 581 // basicConstraints: 582 // -2: only EE certs accepted 583 // -1: no check is done 584 // 0: any CA certificate accepted 585 // >1: certificate's basicConstraints extension pathlen must match 586 if (ldapDN != null) { 587 if (debug != null) { 588 debug.println("LDAPCertStore.engineGetCertificates() " 589 + " subject is not null"); 590 } 591 LDAPRequest request = new LDAPRequest(ldapDN); 592 if (basicConstraints > -2) { 593 request.addRequestedAttribute(CROSS_CERT); 594 request.addRequestedAttribute(CA_CERT); 595 request.addRequestedAttribute(ARL); 596 if (prefetchCRLs) { 597 request.addRequestedAttribute(CRL); 598 } 599 } 600 if (basicConstraints < 0) { 601 request.addRequestedAttribute(USER_CERT); 602 } 603 604 if (basicConstraints > -2) { 605 certs.addAll(getMatchingCrossCerts(request, xsel, null)); 606 if (debug != null) { 607 debug.println("LDAPCertStore.engineGetCertificates() after " 608 + "getMatchingCrossCerts(subject,xsel,null),certs.size(): " 609 + certs.size()); 610 } 611 certs.addAll(getCertificates(request, CA_CERT, xsel)); 612 if (debug != null) { 613 debug.println("LDAPCertStore.engineGetCertificates() after " 614 + "getCertificates(subject,CA_CERT,xsel),certs.size(): " 615 + certs.size()); 616 } 617 } 618 if (basicConstraints < 0) { 619 certs.addAll(getCertificates(request, USER_CERT, xsel)); 620 if (debug != null) { 621 debug.println("LDAPCertStore.engineGetCertificates() after " 622 + "getCertificates(subject,USER_CERT, xsel),certs.size(): " 623 + certs.size()); 624 } 625 } 626 } else { 627 if (debug != null) { 628 debug.println 629 ("LDAPCertStore.engineGetCertificates() subject is null"); 630 } 631 if (basicConstraints == -2) { 632 throw new CertStoreException("need subject to find EE certs"); 633 } 634 if (issuer == null) { 635 throw new CertStoreException("need subject or issuer to find certs"); 636 } 637 } 638 if (debug != null) { 639 debug.println("LDAPCertStore.engineGetCertificates() about to " 640 + "getMatchingCrossCerts..."); 641 } 642 if ((issuer != null) && (basicConstraints > -2)) { 643 LDAPRequest request = new LDAPRequest(issuer); 644 request.addRequestedAttribute(CROSS_CERT); 645 request.addRequestedAttribute(CA_CERT); 646 request.addRequestedAttribute(ARL); 647 if (prefetchCRLs) { 648 request.addRequestedAttribute(CRL); 649 } 650 651 certs.addAll(getMatchingCrossCerts(request, null, xsel)); 652 if (debug != null) { 653 debug.println("LDAPCertStore.engineGetCertificates() after " 654 + "getMatchingCrossCerts(issuer,null,xsel),certs.size(): " 655 + certs.size()); 656 } 657 certs.addAll(getCertificates(request, CA_CERT, xsel)); 658 if (debug != null) { 659 debug.println("LDAPCertStore.engineGetCertificates() after " 660 + "getCertificates(issuer,CA_CERT,xsel),certs.size(): " 661 + certs.size()); 662 } 663 } 664 if (debug != null) { 665 debug.println("LDAPCertStore.engineGetCertificates() returning certs"); 666 } 667 return certs; 668 } 669 670 /* 671 * Gets CRLs from an attribute id and location in the LDAP directory. 672 * Returns a Collection containing only the CRLs that match the 673 * specified X509CRLSelector. 674 * 675 * @param name the location holding the attribute 676 * @param id the attribute identifier 677 * @param sel a X509CRLSelector that the CRLs must match 678 * @return a Collection of CRLs found 679 * @throws CertStoreException if an exception occurs 680 */ getCRLs(LDAPRequest request, String id, X509CRLSelector sel)681 private Collection<X509CRL> getCRLs(LDAPRequest request, String id, 682 X509CRLSelector sel) throws CertStoreException { 683 684 /* fetch the encoded crls from storage */ 685 byte[][] encodedCRL; 686 try { 687 encodedCRL = request.getValues(id); 688 } catch (NamingException namingEx) { 689 throw new CertStoreException(namingEx); 690 } 691 692 int n = encodedCRL.length; 693 if (n == 0) { 694 return Collections.emptySet(); 695 } 696 697 List<X509CRL> crls = new ArrayList<>(n); 698 /* decode each crl and check if it matches selector */ 699 for (int i = 0; i < n; i++) { 700 try { 701 CRL crl = cf.generateCRL(new ByteArrayInputStream(encodedCRL[i])); 702 if (sel.match(crl)) { 703 crls.add((X509CRL)crl); 704 } 705 } catch (CRLException e) { 706 if (debug != null) { 707 debug.println("LDAPCertStore.getCRLs() encountered exception" 708 + " while parsing CRL, skipping the bad data: "); 709 HexDumpEncoder encoder = new HexDumpEncoder(); 710 debug.println("[ " + encoder.encodeBuffer(encodedCRL[i]) + " ]"); 711 } 712 } 713 } 714 715 return crls; 716 } 717 718 /** 719 * Returns a <code>Collection</code> of <code>X509CRL</code>s that 720 * match the specified selector. If no <code>X509CRL</code>s 721 * match the selector, an empty <code>Collection</code> will be returned. 722 * <p> 723 * It is not practical to search every entry in the LDAP database for 724 * matching <code>X509CRL</code>s. Instead, the <code>X509CRLSelector</code> 725 * is examined in order to determine where matching <code>X509CRL</code>s 726 * are likely to be found (according to the PKIX LDAPv2 schema, RFC 2587). 727 * If issuerNames or certChecking are specified, the issuer's directory 728 * entry is searched. If neither issuerNames or certChecking are specified 729 * (or the selector is not an <code>X509CRLSelector</code>), a 730 * <code>CertStoreException</code> is thrown. 731 * 732 * @param xsel A <code>X509CRLSelector</code> used to select which 733 * <code>CRL</code>s should be returned. Specify <code>null</code> 734 * to return all <code>CRL</code>s. 735 * @return A <code>Collection</code> of <code>X509CRL</code>s that 736 * match the specified selector 737 * @throws CertStoreException if an exception occurs 738 */ getCRLs(X509CRLSelector xsel, String ldapDN)739 synchronized Collection<X509CRL> getCRLs(X509CRLSelector xsel, 740 String ldapDN) throws CertStoreException { 741 742 HashSet<X509CRL> crls = new HashSet<>(); 743 744 // Look in directory entry for issuer of cert we're checking. 745 Collection<Object> issuerNames; 746 X509Certificate certChecking = xsel.getCertificateChecking(); 747 if (certChecking != null) { 748 issuerNames = new HashSet<>(); 749 X500Principal issuer = certChecking.getIssuerX500Principal(); 750 issuerNames.add(issuer.getName(X500Principal.RFC2253)); 751 } else { 752 // But if we don't know which cert we're checking, try the directory 753 // entries of all acceptable CRL issuers 754 if (ldapDN != null) { 755 issuerNames = new HashSet<>(); 756 issuerNames.add(ldapDN); 757 } else { 758 issuerNames = xsel.getIssuerNames(); 759 if (issuerNames == null) { 760 throw new CertStoreException("need issuerNames or" 761 + " certChecking to find CRLs"); 762 } 763 } 764 } 765 for (Object nameObject : issuerNames) { 766 String issuerName; 767 if (nameObject instanceof byte[]) { 768 try { 769 X500Principal issuer = new X500Principal((byte[])nameObject); 770 issuerName = issuer.getName(X500Principal.RFC2253); 771 } catch (IllegalArgumentException e) { 772 continue; 773 } 774 } else { 775 issuerName = (String)nameObject; 776 } 777 // If all we want is CA certs, try to get the (probably shorter) ARL 778 Collection<X509CRL> entryCRLs = Collections.emptySet(); 779 if (certChecking == null || certChecking.getBasicConstraints() != -1) { 780 LDAPRequest request = new LDAPRequest(issuerName); 781 request.addRequestedAttribute(CROSS_CERT); 782 request.addRequestedAttribute(CA_CERT); 783 request.addRequestedAttribute(ARL); 784 if (prefetchCRLs) { 785 request.addRequestedAttribute(CRL); 786 } 787 try { 788 entryCRLs = getCRLs(request, ARL, xsel); 789 if (entryCRLs.isEmpty()) { 790 // no ARLs found. We assume that means that there are 791 // no ARLs on this server at all and prefetch the CRLs. 792 prefetchCRLs = true; 793 } else { 794 crls.addAll(entryCRLs); 795 } 796 } catch (CertStoreException e) { 797 if (debug != null) { 798 debug.println("LDAPCertStore.engineGetCRLs non-fatal error " 799 + "retrieving ARLs:" + e); 800 e.printStackTrace(); 801 } 802 } 803 } 804 // Otherwise, get the CRL 805 // if certChecking is null, we don't know if we should look in ARL or CRL 806 // attribute, so check both for matching CRLs. 807 if (entryCRLs.isEmpty() || certChecking == null) { 808 LDAPRequest request = new LDAPRequest(issuerName); 809 request.addRequestedAttribute(CRL); 810 entryCRLs = getCRLs(request, CRL, xsel); 811 crls.addAll(entryCRLs); 812 } 813 } 814 return crls; 815 } 816 } 817