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