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