1/* 2 * Copyright (c) 2002, 2012, 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; 27 28import java.util.*; 29 30import java.security.InvalidAlgorithmParameterException; 31import java.security.cert.*; 32 33import javax.security.auth.x500.X500Principal; 34 35/** 36 * A <code>CertStore</code> that retrieves <code>Certificates</code> and 37 * <code>CRL</code>s from a <code>Collection</code>. 38 * <p> 39 * This implementation is functionally equivalent to CollectionCertStore 40 * with two differences: 41 * <ol> 42 * <li>Upon construction, the elements in the specified Collection are 43 * partially indexed. X509Certificates are indexed by subject, X509CRLs 44 * by issuer, non-X509 Certificates and CRLs are copied without indexing, 45 * other objects are ignored. This increases CertStore construction time 46 * but allows significant speedups for searches which specify the indexed 47 * attributes, in particular for large Collections (reduction from linear 48 * time to effectively constant time). Searches for non-indexed queries 49 * are as fast (or marginally faster) than for the standard 50 * CollectionCertStore. Certificate subjects and CRL issuers 51 * were found to be specified in most searches used internally by the 52 * CertPath provider. Additional attributes could indexed if there are 53 * queries that justify the effort. 54 * 55 * <li>Changes to the specified Collection after construction time are 56 * not detected and ignored. This is because there is no way to efficiently 57 * detect if a Collection has been modified, a full traversal would be 58 * required. That would degrade lookup performance to linear time and 59 * eliminated the benefit of indexing. We may fix this via the introduction 60 * of new public APIs in the future. 61 * </ol> 62 * <p> 63 * Before calling the {@link #engineGetCertificates engineGetCertificates} or 64 * {@link #engineGetCRLs engineGetCRLs} methods, the 65 * {@link #CollectionCertStore(CertStoreParameters) 66 * CollectionCertStore(CertStoreParameters)} constructor is called to 67 * create the <code>CertStore</code> and establish the 68 * <code>Collection</code> from which <code>Certificate</code>s and 69 * <code>CRL</code>s will be retrieved. If the specified 70 * <code>Collection</code> contains an object that is not a 71 * <code>Certificate</code> or <code>CRL</code>, that object will be 72 * ignored. 73 * <p> 74 * <b>Concurrent Access</b> 75 * <p> 76 * As described in the javadoc for <code>CertStoreSpi</code>, the 77 * <code>engineGetCertificates</code> and <code>engineGetCRLs</code> methods 78 * must be thread-safe. That is, multiple threads may concurrently 79 * invoke these methods on a single <code>CollectionCertStore</code> 80 * object (or more than one) with no ill effects. 81 * <p> 82 * This is achieved by requiring that the <code>Collection</code> passed to 83 * the {@link #CollectionCertStore(CertStoreParameters) 84 * CollectionCertStore(CertStoreParameters)} constructor (via the 85 * <code>CollectionCertStoreParameters</code> object) must have fail-fast 86 * iterators. Simultaneous modifications to the <code>Collection</code> can thus be 87 * detected and certificate or CRL retrieval can be retried. The fact that 88 * <code>Certificate</code>s and <code>CRL</code>s must be thread-safe is also 89 * essential. 90 * 91 * @see java.security.cert.CertStore 92 * @see CollectionCertStore 93 * 94 * @author Andreas Sterbenz 95 */ 96public class IndexedCollectionCertStore extends CertStoreSpi { 97 98 /** 99 * Map X500Principal(subject) -> X509Certificate | List of X509Certificate 100 */ 101 private Map<X500Principal, Object> certSubjects; 102 /** 103 * Map X500Principal(issuer) -> X509CRL | List of X509CRL 104 */ 105 private Map<X500Principal, Object> crlIssuers; 106 /** 107 * Sets of non-X509 certificates and CRLs 108 */ 109 private Set<Certificate> otherCertificates; 110 private Set<CRL> otherCRLs; 111 112 /** 113 * Creates a <code>CertStore</code> with the specified parameters. 114 * For this class, the parameters object must be an instance of 115 * <code>CollectionCertStoreParameters</code>. 116 * 117 * @param params the algorithm parameters 118 * @exception InvalidAlgorithmParameterException if params is not an 119 * instance of <code>CollectionCertStoreParameters</code> 120 */ 121 public IndexedCollectionCertStore(CertStoreParameters params) 122 throws InvalidAlgorithmParameterException { 123 super(params); 124 if (!(params instanceof CollectionCertStoreParameters)) { 125 throw new InvalidAlgorithmParameterException( 126 "parameters must be CollectionCertStoreParameters"); 127 } 128 Collection<?> coll = ((CollectionCertStoreParameters)params).getCollection(); 129 if (coll == null) { 130 throw new InvalidAlgorithmParameterException 131 ("Collection must not be null"); 132 } 133 buildIndex(coll); 134 } 135 136 /** 137 * Index the specified Collection copying all references to Certificates 138 * and CRLs. 139 */ 140 private void buildIndex(Collection<?> coll) { 141 certSubjects = new HashMap<X500Principal, Object>(); 142 crlIssuers = new HashMap<X500Principal, Object>(); 143 otherCertificates = null; 144 otherCRLs = null; 145 for (Object obj : coll) { 146 if (obj instanceof X509Certificate) { 147 indexCertificate((X509Certificate)obj); 148 } else if (obj instanceof X509CRL) { 149 indexCRL((X509CRL)obj); 150 } else if (obj instanceof Certificate) { 151 if (otherCertificates == null) { 152 otherCertificates = new HashSet<Certificate>(); 153 } 154 otherCertificates.add((Certificate)obj); 155 } else if (obj instanceof CRL) { 156 if (otherCRLs == null) { 157 otherCRLs = new HashSet<CRL>(); 158 } 159 otherCRLs.add((CRL)obj); 160 } else { 161 // ignore 162 } 163 } 164 if (otherCertificates == null) { 165 otherCertificates = Collections.<Certificate>emptySet(); 166 } 167 if (otherCRLs == null) { 168 otherCRLs = Collections.<CRL>emptySet(); 169 } 170 } 171 172 /** 173 * Add an X509Certificate to the index. 174 */ 175 private void indexCertificate(X509Certificate cert) { 176 X500Principal subject = cert.getSubjectX500Principal(); 177 Object oldEntry = certSubjects.put(subject, cert); 178 if (oldEntry != null) { // assume this is unlikely 179 if (oldEntry instanceof X509Certificate) { 180 if (cert.equals(oldEntry)) { 181 return; 182 } 183 List<X509Certificate> list = new ArrayList<>(2); 184 list.add(cert); 185 list.add((X509Certificate)oldEntry); 186 certSubjects.put(subject, list); 187 } else { 188 @SuppressWarnings("unchecked") // See certSubjects javadoc. 189 List<X509Certificate> list = (List<X509Certificate>)oldEntry; 190 if (list.contains(cert) == false) { 191 list.add(cert); 192 } 193 certSubjects.put(subject, list); 194 } 195 } 196 } 197 198 /** 199 * Add an X509CRL to the index. 200 */ 201 private void indexCRL(X509CRL crl) { 202 X500Principal issuer = crl.getIssuerX500Principal(); 203 Object oldEntry = crlIssuers.put(issuer, crl); 204 if (oldEntry != null) { // assume this is unlikely 205 if (oldEntry instanceof X509CRL) { 206 if (crl.equals(oldEntry)) { 207 return; 208 } 209 List<X509CRL> list = new ArrayList<>(2); 210 list.add(crl); 211 list.add((X509CRL)oldEntry); 212 crlIssuers.put(issuer, list); 213 } else { 214 // See crlIssuers javadoc. 215 @SuppressWarnings("unchecked") 216 List<X509CRL> list = (List<X509CRL>)oldEntry; 217 if (list.contains(crl) == false) { 218 list.add(crl); 219 } 220 crlIssuers.put(issuer, list); 221 } 222 } 223 } 224 225 /** 226 * Returns a <code>Collection</code> of <code>Certificate</code>s that 227 * match the specified selector. If no <code>Certificate</code>s 228 * match the selector, an empty <code>Collection</code> will be returned. 229 * 230 * @param selector a <code>CertSelector</code> used to select which 231 * <code>Certificate</code>s should be returned. Specify <code>null</code> 232 * to return all <code>Certificate</code>s. 233 * @return a <code>Collection</code> of <code>Certificate</code>s that 234 * match the specified selector 235 * @throws CertStoreException if an exception occurs 236 */ 237 @Override 238 public Collection<? extends Certificate> engineGetCertificates(CertSelector selector) 239 throws CertStoreException { 240 241 // no selector means match all 242 if (selector == null) { 243 Set<Certificate> matches = new HashSet<>(); 244 matchX509Certs(new X509CertSelector(), matches); 245 matches.addAll(otherCertificates); 246 return matches; 247 } 248 249 if (selector instanceof X509CertSelector == false) { 250 Set<Certificate> matches = new HashSet<>(); 251 matchX509Certs(selector, matches); 252 for (Certificate cert : otherCertificates) { 253 if (selector.match(cert)) { 254 matches.add(cert); 255 } 256 } 257 return matches; 258 } 259 260 if (certSubjects.isEmpty()) { 261 return Collections.<X509Certificate>emptySet(); 262 } 263 X509CertSelector x509Selector = (X509CertSelector)selector; 264 // see if the subject is specified 265 X500Principal subject; 266 X509Certificate matchCert = x509Selector.getCertificate(); 267 if (matchCert != null) { 268 subject = matchCert.getSubjectX500Principal(); 269 } else { 270 subject = x509Selector.getSubject(); 271 } 272 if (subject != null) { 273 // yes, narrow down candidates to indexed possibilities 274 Object entry = certSubjects.get(subject); 275 if (entry == null) { 276 return Collections.<X509Certificate>emptySet(); 277 } 278 if (entry instanceof X509Certificate) { 279 X509Certificate x509Entry = (X509Certificate)entry; 280 if (x509Selector.match(x509Entry)) { 281 return Collections.singleton(x509Entry); 282 } else { 283 return Collections.<X509Certificate>emptySet(); 284 } 285 } else { 286 // See certSubjects javadoc. 287 @SuppressWarnings("unchecked") 288 List<X509Certificate> list = (List<X509Certificate>)entry; 289 Set<X509Certificate> matches = new HashSet<>(16); 290 for (X509Certificate cert : list) { 291 if (x509Selector.match(cert)) { 292 matches.add(cert); 293 } 294 } 295 return matches; 296 } 297 } 298 // cannot use index, iterate all 299 Set<Certificate> matches = new HashSet<>(16); 300 matchX509Certs(x509Selector, matches); 301 return matches; 302 } 303 304 /** 305 * Iterate through all the X509Certificates and add matches to the 306 * collection. 307 */ 308 private void matchX509Certs(CertSelector selector, 309 Collection<Certificate> matches) { 310 311 for (Object obj : certSubjects.values()) { 312 if (obj instanceof X509Certificate) { 313 X509Certificate cert = (X509Certificate)obj; 314 if (selector.match(cert)) { 315 matches.add(cert); 316 } 317 } else { 318 // See certSubjects javadoc. 319 @SuppressWarnings("unchecked") 320 List<X509Certificate> list = (List<X509Certificate>)obj; 321 for (X509Certificate cert : list) { 322 if (selector.match(cert)) { 323 matches.add(cert); 324 } 325 } 326 } 327 } 328 } 329 330 /** 331 * Returns a <code>Collection</code> of <code>CRL</code>s that 332 * match the specified selector. If no <code>CRL</code>s 333 * match the selector, an empty <code>Collection</code> will be returned. 334 * 335 * @param selector a <code>CRLSelector</code> used to select which 336 * <code>CRL</code>s should be returned. Specify <code>null</code> 337 * to return all <code>CRL</code>s. 338 * @return a <code>Collection</code> of <code>CRL</code>s that 339 * match the specified selector 340 * @throws CertStoreException if an exception occurs 341 */ 342 @Override 343 public Collection<CRL> engineGetCRLs(CRLSelector selector) 344 throws CertStoreException { 345 346 if (selector == null) { 347 Set<CRL> matches = new HashSet<>(); 348 matchX509CRLs(new X509CRLSelector(), matches); 349 matches.addAll(otherCRLs); 350 return matches; 351 } 352 353 if (selector instanceof X509CRLSelector == false) { 354 Set<CRL> matches = new HashSet<>(); 355 matchX509CRLs(selector, matches); 356 for (CRL crl : otherCRLs) { 357 if (selector.match(crl)) { 358 matches.add(crl); 359 } 360 } 361 return matches; 362 } 363 364 if (crlIssuers.isEmpty()) { 365 return Collections.<CRL>emptySet(); 366 } 367 X509CRLSelector x509Selector = (X509CRLSelector)selector; 368 // see if the issuer is specified 369 Collection<X500Principal> issuers = x509Selector.getIssuers(); 370 if (issuers != null) { 371 HashSet<CRL> matches = new HashSet<>(16); 372 for (X500Principal issuer : issuers) { 373 Object entry = crlIssuers.get(issuer); 374 if (entry == null) { 375 // empty 376 } else if (entry instanceof X509CRL) { 377 X509CRL crl = (X509CRL)entry; 378 if (x509Selector.match(crl)) { 379 matches.add(crl); 380 } 381 } else { // List 382 // See crlIssuers javadoc. 383 @SuppressWarnings("unchecked") 384 List<X509CRL> list = (List<X509CRL>)entry; 385 for (X509CRL crl : list) { 386 if (x509Selector.match(crl)) { 387 matches.add(crl); 388 } 389 } 390 } 391 } 392 return matches; 393 } 394 // cannot use index, iterate all 395 Set<CRL> matches = new HashSet<>(16); 396 matchX509CRLs(x509Selector, matches); 397 return matches; 398 } 399 400 /** 401 * Iterate through all the X509CRLs and add matches to the 402 * collection. 403 */ 404 private void matchX509CRLs(CRLSelector selector, Collection<CRL> matches) { 405 for (Object obj : crlIssuers.values()) { 406 if (obj instanceof X509CRL) { 407 X509CRL crl = (X509CRL)obj; 408 if (selector.match(crl)) { 409 matches.add(crl); 410 } 411 } else { 412 // See crlIssuers javadoc. 413 @SuppressWarnings("unchecked") 414 List<X509CRL> list = (List<X509CRL>)obj; 415 for (X509CRL crl : list) { 416 if (selector.match(crl)) { 417 matches.add(crl); 418 } 419 } 420 } 421 } 422 } 423 424} 425