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