View Javadoc

1   /*
2    * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.xml.security.keyinfo.provider;
18  
19  import java.math.BigInteger;
20  import java.security.PublicKey;
21  import java.security.cert.CRLException;
22  import java.security.cert.CertificateException;
23  import java.security.cert.X509CRL;
24  import java.security.cert.X509Certificate;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.List;
28  
29  import javax.security.auth.x500.X500Principal;
30  
31  import org.opensaml.xml.XMLObject;
32  import org.opensaml.xml.security.CriteriaSet;
33  import org.opensaml.xml.security.SecurityException;
34  import org.opensaml.xml.security.credential.Credential;
35  import org.opensaml.xml.security.credential.CredentialContext;
36  import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
37  import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
38  import org.opensaml.xml.security.keyinfo.KeyInfoProvider;
39  import org.opensaml.xml.security.keyinfo.KeyInfoResolutionContext;
40  import org.opensaml.xml.security.x509.BasicX509Credential;
41  import org.opensaml.xml.security.x509.InternalX500DNHandler;
42  import org.opensaml.xml.security.x509.X500DNHandler;
43  import org.opensaml.xml.security.x509.X509Credential;
44  import org.opensaml.xml.security.x509.X509Util;
45  import org.opensaml.xml.signature.KeyValue;
46  import org.opensaml.xml.signature.X509Data;
47  import org.opensaml.xml.signature.X509IssuerSerial;
48  import org.opensaml.xml.signature.X509SKI;
49  import org.opensaml.xml.signature.X509SubjectName;
50  import org.opensaml.xml.util.Base64;
51  import org.opensaml.xml.util.DatatypeHelper;
52  import org.opensaml.xml.util.LazySet;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  /**
57   * Implementation of {@link KeyInfoProvider} which provides basic support for extracting a {@link X509Credential} 
58   * from an {@link X509Data} child of KeyInfo.
59   * 
60   * This provider supports only inline {@link X509Certificate}'s and  {@link X509CRL}'s.
61   * If only one certificate is present, it is assumed to be the end-entity certificate containing
62   * the public key represented by this KeyInfo.  If multiple certificates are present, and any instances
63   * of {@link X509SubjectName}, {@link X509IssuerSerial}, or {@link X509SKI} are also present, they
64   * will be used to identify the end-entity certificate, in accordance with the XML Signature specification.
65   * If a public key from a previously resolved {@link KeyValue} is available in the resolution context,
66   * it will also be used to identify the end-entity certificate. If the end-entity certificate can not
67   * otherwise be identified, the cert contained in the first X509Certificate element will be treated as
68   * the end-entity certificate.
69   * 
70   */
71  public class InlineX509DataProvider extends AbstractKeyInfoProvider {
72      
73      /** Class logger. */
74      private final Logger log = LoggerFactory.getLogger(InlineX509DataProvider.class);
75      
76      /** Responsible for parsing and serializing X.500 names to/from {@link X500Principal} instances. */
77      private X500DNHandler x500DNHandler;
78      
79      /**
80       * Constructor.
81       */
82      public InlineX509DataProvider() {
83          x500DNHandler = new InternalX500DNHandler();
84      }
85  
86      /**
87       * Get the handler which process X.500 distinguished names.
88       * 
89       * @return returns the X500DNHandler instance
90       */
91      public X500DNHandler getX500DNHandler() {
92          return x500DNHandler;
93      }
94  
95      /**
96       * Set the handler which process X.500 distinguished names.
97       * 
98       * @param handler the new X500DNHandler instance
99       */
100     public void setX500DNHandler(X500DNHandler handler) {
101         if (handler == null) {
102             throw new IllegalArgumentException("X500DNHandler may not be null");
103         }
104         x500DNHandler = handler;
105     }
106 
107     /** {@inheritDoc} */
108     public boolean handles(XMLObject keyInfoChild) {
109         return keyInfoChild instanceof X509Data;
110     }
111 
112     /** {@inheritDoc} */
113     public Collection<Credential> process(KeyInfoCredentialResolver resolver, XMLObject keyInfoChild, 
114             CriteriaSet criteriaSet, KeyInfoResolutionContext kiContext) throws SecurityException {
115         
116         if (! handles(keyInfoChild)) {
117             return null;
118         }
119         
120         X509Data x509Data = (X509Data) keyInfoChild;
121         
122         log.debug("Attempting to extract credential from an X509Data");
123         
124         List<X509Certificate> certs = extractCertificates(x509Data);
125         if (certs.isEmpty()) {
126             log.info("The X509Data contained no X509Certificate elements, skipping credential extraction");
127             return null;
128         }
129         List<X509CRL> crls = extractCRLs(x509Data);
130         
131         PublicKey resolvedPublicKey = null;
132         if (kiContext != null && kiContext.getKey() != null && kiContext.getKey() instanceof PublicKey) {
133             resolvedPublicKey = (PublicKey) kiContext.getKey();
134         }
135         X509Certificate entityCert = findEntityCert(certs, x509Data, resolvedPublicKey);
136         if (entityCert == null) {
137             log.warn("The end-entity cert could not be identified, skipping credential extraction");
138             return null;
139         }
140         
141         BasicX509Credential cred = new BasicX509Credential();
142         cred.setEntityCertificate(entityCert);
143         cred.setCRLs(crls);
144         cred.setEntityCertificateChain(certs);
145         
146         if (kiContext != null) {
147             cred.getKeyNames().addAll(kiContext.getKeyNames());
148         }
149         
150         CredentialContext credContext = buildCredentialContext(kiContext);
151         if (credContext != null) {
152             cred.getCredentalContextSet().add(credContext);
153         }
154         
155         LazySet<Credential> credentialSet = new LazySet<Credential>();
156         credentialSet.add(cred);
157         return credentialSet;
158     }
159 
160     /**
161      * Extract CRL's from the X509Data.
162      * 
163      * @param x509Data the X509Data element
164      * @return a list of X509CRLs
165      * @throws SecurityException thrown if there is an error extracting CRL's
166      */
167     private List<X509CRL> extractCRLs(X509Data x509Data) throws SecurityException {
168         List<X509CRL> crls = null;
169         try {
170             crls = KeyInfoHelper.getCRLs(x509Data);
171         } catch (CRLException e) {
172             log.error("Error extracting CRL's from X509Data", e);
173             throw new SecurityException("Error extracting CRL's from X509Data", e);
174         }
175         
176         log.debug("Found {} X509CRLs", crls.size());
177         return crls;
178     }
179 
180     /**
181      * Extract certificates from the X509Data.
182      * 
183      * @param x509Data the X509Data element
184      * @return a list of X509Certificates
185      * @throws SecurityException thrown if there is an error extracting certificates
186      */
187     private List<X509Certificate> extractCertificates(X509Data x509Data) throws SecurityException {
188         List<X509Certificate> certs = null;
189         try {
190             certs = KeyInfoHelper.getCertificates(x509Data);
191         } catch (CertificateException e) {
192             log.error("Error extracting certificates from X509Data", e);
193             throw new SecurityException("Error extracting certificates from X509Data", e);
194         }
195         log.debug("Found {} X509Certificates", certs.size());
196         return certs;
197     }
198 
199     /**
200      * Find the end-entity cert in the list of certs contained in the X509Data.
201      * 
202      * @param certs list of {@link java.security.cert.X509Certificate}
203      * @param x509Data X509Data element which might contain other info helping to finding the end-entity cert
204      * @param resolvedKey a key which might have previously been resolved from a KeyValue
205      * @return the end-entity certificate, if found
206      */
207     protected X509Certificate findEntityCert(List<X509Certificate> certs, X509Data x509Data, PublicKey resolvedKey) {
208         if (certs == null || certs.isEmpty()) {
209             return null;
210         }
211         
212         // If there is only 1 certificate, treat it as the end-entity certificate
213         if (certs.size() == 1) {
214             log.debug("Single certificate was present, treating as end-entity certificate");
215             return certs.get(0);
216         }
217         
218         X509Certificate cert = null;
219         
220         //Check against public key already resolved in resolution context
221         cert = findCertFromKey(certs, resolvedKey);
222         if (cert != null) {
223             log.debug("End-entity certificate resolved by matching previously resolved public key");
224             return cert;
225         }
226  
227         //Check against any subject names
228         cert = findCertFromSubjectNames(certs, x509Data.getX509SubjectNames());
229         if (cert != null) {
230             log.debug("End-entity certificate resolved by matching X509SubjectName");
231             return cert;
232         }
233 
234         //Check against issuer serial
235         cert = findCertFromIssuerSerials(certs, x509Data.getX509IssuerSerials());
236         if (cert != null) {
237             log.debug("End-entity certificate resolved by matching X509IssuerSerial");
238             return cert;
239         }
240 
241         //Check against any subject key identifiers
242         cert = findCertFromSubjectKeyIdentifier(certs, x509Data.getX509SKIs());
243         if (cert != null) {
244             log.debug("End-entity certificate resolved by matching X509SKI");
245             return cert;
246         }
247         
248         // TODO use some heuristic algorithm to try and figure it out based on the cert list alone.
249         //      This would be in X509Utils or somewhere else external to this class.
250         
251         // As a final fallback, treat the first cert in the X509Data element as the entity cert
252         log.debug("Treating the first certificate in the X509Data as the end-entity certificate");
253         return certs.get(0);
254     }
255     
256     /**
257      * Find the certificate from the chain that contains the specified key.
258      * 
259      * @param certs list of certificates to evaluate
260      * @param key key to use as search criteria
261      * @return the matching certificate, or null
262      */
263     protected X509Certificate findCertFromKey(List<X509Certificate> certs, PublicKey key) {
264         if (key != null) {
265             for (X509Certificate cert : certs) {
266                 if (cert.getPublicKey().equals(key)) {
267                     return cert;
268                 }
269             }
270         }
271         return null;
272     }
273     
274     /**
275      * Find the certificate from the chain that contains one of the specified subject names.
276      * 
277      * @param certs list of certificates to evaluate
278      * @param names X509 subject names to use as search criteria
279      * @return the matching certificate, or null
280      */
281     protected X509Certificate findCertFromSubjectNames(List<X509Certificate> certs, List<X509SubjectName> names) {
282         for (X509SubjectName subjectName : names) {
283             if (! DatatypeHelper.isEmpty(subjectName.getValue())) {
284                 X500Principal subjectX500Principal = x500DNHandler.parse(subjectName.getValue());
285                 for (X509Certificate cert : certs) {
286                     if (cert.getSubjectX500Principal().equals(subjectX500Principal)) {
287                         return cert;
288                     }
289                 }
290             }
291         }
292         return null;
293     }
294     
295     /**
296      * Find the certificate from the chain identified by one of the specified issuer serials.
297      * 
298      * @param certs list of certificates to evaluate
299      * @param serials X509 issuer serials to use as search criteria
300      * @return the matching certificate, or null
301      */
302     protected X509Certificate findCertFromIssuerSerials(List<X509Certificate> certs, List<X509IssuerSerial> serials) {
303         for (X509IssuerSerial issuerSerial : serials) {
304             if (issuerSerial.getX509IssuerName() == null || issuerSerial.getX509SerialNumber() == null) {
305                 continue;
306             }
307             String issuerNameValue = issuerSerial.getX509IssuerName().getValue();
308             BigInteger serialNumber  = issuerSerial.getX509SerialNumber().getValue();
309             if (! DatatypeHelper.isEmpty(issuerNameValue)) {
310                 X500Principal issuerX500Principal = x500DNHandler.parse(issuerNameValue);
311                 for (X509Certificate cert : certs) {
312                     if (cert.getIssuerX500Principal().equals(issuerX500Principal) &&
313                             cert.getSerialNumber().equals(serialNumber)) {
314                         return cert;
315                     }
316                 }
317             }
318         }
319         return null;
320     }
321     
322     /**
323      * Find the certificate from the chain that contains one of the specified subject key identifiers.
324      * 
325      * @param certs list of certificates to evaluate
326      * @param skis X509 subject key identifiers to use as search criteria
327      * @return the matching certificate, or null
328      */
329     protected X509Certificate findCertFromSubjectKeyIdentifier(List<X509Certificate> certs, List<X509SKI> skis) {
330         for (X509SKI ski : skis) {
331             if (! DatatypeHelper.isEmpty(ski.getValue())) {
332                 byte[] xmlValue = Base64.decode(ski.getValue());
333                 for (X509Certificate cert : certs) {
334                     byte[] certValue = X509Util.getSubjectKeyIdentifier(cert);
335                     if (certValue != null && Arrays.equals(xmlValue, certValue)) {
336                         return cert;
337                     }
338                 }
339             }
340         }
341         return null;
342     } 
343 }