View Javadoc

1   /*
2    * Copyright [2006] [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.x509;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.security.GeneralSecurityException;
22  import java.security.cert.CRLException;
23  import java.security.cert.CertificateException;
24  import java.security.cert.CertificateFactory;
25  import java.security.cert.CertificateParsingException;
26  import java.security.cert.X509CRL;
27  import java.security.cert.X509Certificate;
28  import java.util.Collection;
29  import java.util.Iterator;
30  import java.util.LinkedList;
31  import java.util.List;
32  
33  import javax.security.auth.x500.X500Principal;
34  
35  import org.apache.commons.ssl.TrustMaterial;
36  import org.bouncycastle.asn1.ASN1InputStream;
37  import org.bouncycastle.asn1.DERObject;
38  import org.bouncycastle.asn1.DERObjectIdentifier;
39  import org.bouncycastle.asn1.DERSequence;
40  import org.bouncycastle.asn1.DERSet;
41  import org.bouncycastle.asn1.DERString;
42  import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
43  import org.bouncycastle.asn1.x509.X509Extensions;
44  import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
45  import org.opensaml.xml.util.DatatypeHelper;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  /**
50   * Utility class for working with X509 objects.
51   */
52  public class X509Util {
53  
54      /** Encoding used to store a key or certificate in a file. */
55      public static enum ENCODING_FORMAT {
56          PEM, DER
57      };
58  
59      /** Common Name (CN) OID. */
60      public static final String CN_OID = "2.5.4.3";
61  
62      /** RFC 2459 Other Subject Alt Name type. */
63      public static final Integer OTHER_ALT_NAME = new Integer(0);
64  
65      /** RFC 2459 RFC 822 (email address) Subject Alt Name type. */
66      public static final Integer RFC822_ALT_NAME = new Integer(1);
67  
68      /** RFC 2459 DNS Subject Alt Name type. */
69      public static final Integer DNS_ALT_NAME = new Integer(2);
70  
71      /** RFC 2459 X.400 Address Subject Alt Name type. */
72      public static final Integer X400ADDRESS_ALT_NAME = new Integer(3);
73  
74      /** RFC 2459 Directory Name Subject Alt Name type. */
75      public static final Integer DIRECTORY_ALT_NAME = new Integer(4);
76  
77      /** RFC 2459 EDI Party Name Subject Alt Name type. */
78      public static final Integer EDI_PARTY_ALT_NAME = new Integer(5);
79  
80      /** RFC 2459 URI Subject Alt Name type. */
81      public static final Integer URI_ALT_NAME = new Integer(6);
82  
83      /** RFC 2459 IP Address Subject Alt Name type. */
84      public static final Integer IP_ADDRESS_ALT_NAME = new Integer(7);
85  
86      /** RFC 2459 Registered ID Subject Alt Name type. */
87      public static final Integer REGISTERED_ID_ALT_NAME = new Integer(8);
88  
89      /** Class logger. */
90      private static Logger log = LoggerFactory.getLogger(X509Util.class);
91  
92      /** Constructed. */
93      protected X509Util() {
94  
95      }
96  
97      /**
98       * Gets the commons names that appear within the given distinguished name. The returned list provides the names in
99       * the order they appeared in the DN.
100      * 
101      * @param dn the DN to extract the common names from
102      * 
103      * @return the common names that appear in the DN in the order they appear or null if the given DN is null
104      */
105     public static List<String> getCommonNames(X500Principal dn) {
106         if (dn == null) {
107             return null;
108         }
109 
110         log.debug("Extracting CNs from the following DN: {}", dn.toString());
111         List<String> commonNames = new LinkedList<String>();
112         try {
113             ASN1InputStream asn1Stream = new ASN1InputStream(dn.getEncoded());
114             DERObject parent = asn1Stream.readObject();
115 
116             String cn = null;
117             DERObject dnComponent;
118             DERSequence grandChild;
119             DERObjectIdentifier componentId;
120             for (int i = 0; i < ((DERSequence) parent).size(); i++) {
121                 dnComponent = ((DERSequence) parent).getObjectAt(i).getDERObject();
122                 if (!(dnComponent instanceof DERSet)) {
123                     log.debug("No DN components.");
124                     continue;
125                 }
126 
127                 // Each DN component is a set
128                 for (int j = 0; j < ((DERSet) dnComponent).size(); j++) {
129                     grandChild = (DERSequence) ((DERSet) dnComponent).getObjectAt(j).getDERObject();
130 
131                     if (grandChild.getObjectAt(0) != null
132                             && grandChild.getObjectAt(0).getDERObject() instanceof DERObjectIdentifier) {
133                         componentId = (DERObjectIdentifier) grandChild.getObjectAt(0).getDERObject();
134 
135                         if (CN_OID.equals(componentId.getId())) {
136                             // OK, this dn component is actually a cn attribute
137                             if (grandChild.getObjectAt(1) != null
138                                     && grandChild.getObjectAt(1).getDERObject() instanceof DERString) {
139                                 cn = ((DERString) grandChild.getObjectAt(1).getDERObject()).getString();
140                                 commonNames.add(cn);
141                             }
142                         }
143                     }
144                 }
145             }
146 
147             asn1Stream.close();
148 
149             return commonNames;
150 
151         } catch (IOException e) {
152             log.error("Unable to extract common names from DN: ASN.1 parsing failed: " + e);
153             return null;
154         }
155     }
156 
157     /**
158      * Gets the list of alternative names of a given name type.
159      * 
160      * @param certificate the certificate to extract the alternative names from
161      * @param nameTypes the name types
162      * 
163      * @return the alt names, of the given type, within the cert
164      */
165     public static List getAltNames(X509Certificate certificate, Integer[] nameTypes) {
166         if (certificate == null) {
167             return null;
168         }
169 
170         List<Object> names = new LinkedList<Object>();
171         try {
172             Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
173             if (altNames != null) {
174                 // 0th position represents the alt name type
175                 // 1st position contains the alt name data
176                 List altName;
177                 for (Iterator<List<?>> nameIterator = altNames.iterator(); nameIterator.hasNext();) {
178                     altName = nameIterator.next();
179                     for (int i = 0; i < nameTypes.length; i++) {
180                         if (altName.get(0).equals(nameTypes[i])) {
181                             names.add(altName.get(1));
182                             break;
183                         }
184                     }
185                 }
186             }
187         } catch (CertificateParsingException e1) {
188             log.error("Encountered an problem trying to extract Subject Alternate "
189                     + "Name from supplied certificate: " + e1);
190         }
191 
192         return names;
193     }
194 
195     /**
196      * Gets the common name components of the issuer and all the subject alt names of a given type.
197      * 
198      * @param certificate certificate to extract names from
199      * @param altNameTypes type of alt names to extract
200      * 
201      * @return list of subject names in the certificate
202      */
203     @SuppressWarnings("unchecked")
204     public static List getSubjectNames(X509Certificate certificate, Integer[] altNameTypes) {
205         List issuerNames = new LinkedList();
206 
207         List<String> entityCertCNs = X509Util.getCommonNames(certificate.getSubjectX500Principal());
208         issuerNames.add(entityCertCNs.get(0));
209         issuerNames.addAll(X509Util.getAltNames(certificate, altNameTypes));
210 
211         return issuerNames;
212     }
213 
214     /**
215      * Get the plain (non-DER encoded) value of the Subject Key Identifier extension of an X.509 certificate, if
216      * present.
217      * 
218      * @param certificate an X.509 certificate possibly containing a subject key identifier
219      * @return the plain (non-DER encoded) value of the Subject Key Identifier extension, or null if the certificate
220      *         does not contain the extension
221      * @throws IOException
222      */
223     public static byte[] getSubjectKeyIdentifier(X509Certificate certificate) {
224         byte[] derValue = certificate.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId());
225         if (derValue == null || derValue.length == 0) {
226             return null;
227         }
228 
229         SubjectKeyIdentifier ski = null;
230         try {
231             ski = new SubjectKeyIdentifierStructure(derValue);
232         } catch (IOException e) {
233             log.error("Unable to extract subject key identifier from certificate: ASN.1 parsing failed: " + e);
234             return null;
235         }
236 
237         if (ski != null) {
238             return ski.getKeyIdentifier();
239         } else {
240             return null;
241         }
242     }
243 
244     /**
245      * Decodes X.509 certificates in DER or PEM format.
246      * 
247      * @param certs encoded certs
248      * 
249      * @return decoded certs
250      * 
251      * @throws CertificateException thrown if the certificates can not be decoded
252      */
253     @SuppressWarnings("unchecked")
254     public static Collection<X509Certificate> decodeCertificate(byte[] certs) throws CertificateException {
255         try {
256             TrustMaterial tm = new TrustMaterial(certs);
257             return tm.getCertificates();
258         } catch (Exception e) {
259             throw new CertificateException("Unable to decode X.509 certificates", e);
260         }
261     }
262 
263     /**
264      * Decodes CRLS in DER or PKCS#7 format. If in PKCS#7 format only the CRLs are decode, the rest of the content is
265      * ignored.
266      * 
267      * @param crls encoded CRLs
268      * 
269      * @return decoded CRLs
270      * 
271      * @throws CRLException thrown if the CRLs can not be decoded
272      */
273     @SuppressWarnings("unchecked")
274     public static Collection<X509CRL> decodeCRLs(byte[] crls) throws CRLException {
275         try {
276             CertificateFactory cf = CertificateFactory.getInstance("X.509");
277             return (Collection<X509CRL>) cf.generateCRLs(new ByteArrayInputStream(crls));
278         } catch (GeneralSecurityException e) {
279             throw new CRLException("Unable to decode X.509 certificates");
280         }
281     }
282 
283     /**
284      * Gets a formatted string representing identifier information from the supplied credential.
285      * 
286      * <p>This could for example be used in logging messages.</p>
287      * 
288      * <p>Often it will be the case that a given credential that is being evaluated will NOT have a value for the
289      * entity ID property. So extract the certificate subject DN, and if present, the credential's entity ID.</p>
290      * 
291      * @param credential the credential for which to produce a token.
292      * @param handler the X.500 DN handler to use.  If null, a new instance of 
293      *          {@link InternalX500DNHandler} will be used.
294      * 
295      * @return a formatted string containing identifier information present in the credential
296      */
297     public static String getIdentifiersToken(X509Credential credential, X500DNHandler handler) {
298         X500DNHandler x500DNHandler;
299         if (handler != null) {
300             x500DNHandler = handler;
301         } else {
302             x500DNHandler = new InternalX500DNHandler();
303         }
304         X500Principal x500Principal = credential.getEntityCertificate().getSubjectX500Principal();
305         StringBuilder builder = new StringBuilder();
306         builder.append('[');
307         builder.append(String.format("subjectName='%s'", x500DNHandler.getName(x500Principal)));
308         if (!DatatypeHelper.isEmpty(credential.getEntityId())) {
309             builder.append(String.format(" |credential entityID='%s'", DatatypeHelper.safeTrimOrNullString(credential
310                     .getEntityId())));
311         }
312         builder.append(']');
313         return builder.toString();
314     }
315 }