View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.components.crypto;
21  
22  import org.apache.ws.security.WSSecurityException;
23  
24  import java.math.BigInteger;
25  import java.security.MessageDigest;
26  import java.security.NoSuchAlgorithmException;
27  import java.security.PrivateKey;
28  import java.security.PublicKey;
29  import java.security.cert.CertPath;
30  import java.security.cert.CertPathValidator;
31  import java.security.cert.CertificateEncodingException;
32  import java.security.cert.PKIXParameters;
33  import java.security.cert.TrustAnchor;
34  import java.security.cert.X509Certificate;
35  import java.util.Arrays;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Set;
39  
40  import javax.security.auth.callback.CallbackHandler;
41  import javax.security.auth.x500.X500Principal;
42  
43  /**
44   * A Crypto implementation based on a simple array of X509Certificate(s). PrivateKeys are not
45   * supported, so this cannot be used for signature creation, or decryption.
46   */
47  public class CertificateStore extends CryptoBase {
48      
49      protected X509Certificate[] trustedCerts;
50      
51      /**
52       * Constructor
53       */
54      public CertificateStore(X509Certificate[] trustedCerts) {
55          this.trustedCerts = trustedCerts;
56      }
57     
58      /**
59       * Get an X509Certificate (chain) corresponding to the CryptoType argument. The supported
60       * types are as follows:
61       * 
62       * TYPE.ISSUER_SERIAL - A certificate (chain) is located by the issuer name and serial number
63       * TYPE.THUMBPRINT_SHA1 - A certificate (chain) is located by the SHA1 of the (root) cert
64       * TYPE.SKI_BYTES - A certificate (chain) is located by the SKI bytes of the (root) cert
65       * TYPE.SUBJECT_DN - A certificate (chain) is located by the Subject DN of the (root) cert
66       * TYPE.ALIAS - A certificate (chain) is located by an alias. In this case, it duplicates the
67       * TYPE.SUBJECT_DN functionality.
68       */
69      public X509Certificate[] getX509Certificates(CryptoType cryptoType) throws WSSecurityException {
70          if (cryptoType == null) {
71              return null;
72          }
73          CryptoType.TYPE type = cryptoType.getType();
74          X509Certificate[] certs = null;
75          switch (type) {
76          case ISSUER_SERIAL: {
77              certs = getX509Certificates(cryptoType.getIssuer(), cryptoType.getSerial());
78              break;
79          }
80          case THUMBPRINT_SHA1: {
81              certs = getX509Certificates(cryptoType.getBytes());
82              break;
83          }
84          case SKI_BYTES: {
85              certs = getX509CertificatesSKI(cryptoType.getBytes());
86              break;
87          }
88          case ALIAS:
89          case SUBJECT_DN: {
90              certs = getX509CertificatesSubjectDN(cryptoType.getSubjectDN());
91              break;
92          }
93          }
94          return certs;
95      }
96      
97      /**
98       * Get the implementation-specific identifier corresponding to the cert parameter. In this
99       * case, the identifier refers to the subject DN.
100      * @param cert The X509Certificate for which to search for an identifier
101      * @return the identifier corresponding to the cert parameter
102      * @throws WSSecurityException
103      */
104     public String getX509Identifier(X509Certificate cert) throws WSSecurityException {
105         return cert.getSubjectDN().toString();
106     }
107     
108     /**
109      * Gets the private key corresponding to the certificate. Not supported.
110      *
111      * @param certificate The X509Certificate corresponding to the private key
112      * @param callbackHandler The callbackHandler needed to get the password
113      * @return The private key
114      */
115     public PrivateKey getPrivateKey(
116         X509Certificate certificate, CallbackHandler callbackHandler
117     ) throws WSSecurityException {
118         return null;
119     }
120    
121     /**
122      * Gets the private key corresponding to the identifier. Not supported.
123      *
124      * @param identifier The implementation-specific identifier corresponding to the key
125      * @param password The password needed to get the key
126      * @return The private key
127      */
128     public PrivateKey getPrivateKey(
129         String identifier,
130         String password
131     ) throws WSSecurityException {
132         return null;
133     }
134     
135     /**
136      * Evaluate whether a given certificate chain should be trusted.
137      *
138      * @param certs Certificate chain to validate
139      * @return true if the certificate chain is valid, false otherwise
140      * @throws WSSecurityException
141      */
142     @Deprecated
143     public boolean verifyTrust(X509Certificate[] certs) throws WSSecurityException {
144         return verifyTrust(certs, false);
145     }
146     
147     /**
148      * Evaluate whether a given certificate chain should be trusted.
149      *
150      * @param certs Certificate chain to validate
151      * @param enableRevocation whether to enable CRL verification or not
152      * @return true if the certificate chain is valid, false otherwise
153      * @throws WSSecurityException
154      */
155     public boolean verifyTrust(
156         X509Certificate[] certs, 
157         boolean enableRevocation
158     ) throws WSSecurityException {
159         try {
160             // Generate cert path
161             List<X509Certificate> certList = Arrays.asList(certs);
162             CertPath path = getCertificateFactory().generateCertPath(certList);
163 
164             Set<TrustAnchor> set = new HashSet<TrustAnchor>();
165             if (trustedCerts != null) {
166                 for (X509Certificate cert : trustedCerts) {
167                     TrustAnchor anchor = 
168                         new TrustAnchor(cert, cert.getExtensionValue(NAME_CONSTRAINTS_OID));
169                     set.add(anchor);
170                 }
171             }
172 
173             PKIXParameters param = new PKIXParameters(set);
174             param.setRevocationEnabled(enableRevocation);
175 
176             // Verify the trust path using the above settings
177             String provider = getCryptoProvider();
178             CertPathValidator validator = null;
179             if (provider == null || provider.length() == 0) {
180                 validator = CertPathValidator.getInstance("PKIX");
181             } else {
182                 validator = CertPathValidator.getInstance("PKIX", provider);
183             }
184             validator.validate(path, param);
185             return true;
186         } catch (java.security.NoSuchProviderException e) {
187                 throw new WSSecurityException(
188                     WSSecurityException.FAILURE, "certpath",
189                     new Object[] { e.getMessage() }, e
190                 );
191         } catch (java.security.NoSuchAlgorithmException e) {
192                 throw new WSSecurityException(
193                     WSSecurityException.FAILURE, "certpath", 
194                     new Object[] { e.getMessage() }, e
195                 );
196         } catch (java.security.cert.CertificateException e) {
197                 throw new WSSecurityException(
198                     WSSecurityException.FAILURE, "certpath", 
199                     new Object[] { e.getMessage() }, e
200                 );
201         } catch (java.security.InvalidAlgorithmParameterException e) {
202                 throw new WSSecurityException(
203                     WSSecurityException.FAILURE, "certpath",
204                     new Object[] { e.getMessage() }, e
205                 );
206         } catch (java.security.cert.CertPathValidatorException e) {
207                 throw new WSSecurityException(
208                     WSSecurityException.FAILURE, "certpath",
209                     new Object[] { e.getMessage() }, e
210                 );
211         }
212     }
213     
214     /**
215      * Evaluate whether a given public key should be trusted.
216      * 
217      * @param publicKey The PublicKey to be evaluated
218      * @return whether the PublicKey parameter is trusted or not
219      */
220     public boolean verifyTrust(PublicKey publicKey) throws WSSecurityException {
221         //
222         // If the public key is null, do not trust the signature
223         //
224         if (publicKey == null) {
225             return false;
226         }
227         
228         //
229         // Search the trusted certs for the transmitted public key (direct trust)
230         //
231         for (X509Certificate trustedCert : trustedCerts) {
232             if (publicKey.equals(trustedCert.getPublicKey())) {
233                 return true;
234             }
235         }
236         return false;
237     }
238     
239     /**
240      * Get an X509 Certificate (chain) according to a given serial number and issuer string.
241      *
242      * @param issuer The Issuer String
243      * @param serialNumber The serial number of the certificate
244      * @return the X509 Certificate (chain) that was found
245      * @throws WSSecurityException
246      */
247     private X509Certificate[] getX509Certificates(
248         String issuer, 
249         BigInteger serialNumber
250     ) throws WSSecurityException {
251         //
252         // Convert the subject DN to a java X500Principal object first. This is to ensure
253         // interop with a DN constructed from .NET, where e.g. it uses "S" instead of "ST".
254         // Then convert it to a BouncyCastle X509Name, which will order the attributes of
255         // the DN in a particular way (see WSS-168). If the conversion to an X500Principal
256         // object fails (e.g. if the DN contains "E" instead of "EMAILADDRESS"), then fall
257         // back on a direct conversion to a BC X509Name
258         //
259         Object issuerName = null;
260         try {
261             X500Principal issuerRDN = new X500Principal(issuer);
262             issuerName = createBCX509Name(issuerRDN.getName());
263         } catch (java.lang.IllegalArgumentException ex) {
264             issuerName = createBCX509Name(issuer);
265         }
266         
267         for (X509Certificate trustedCert : trustedCerts) {
268             if (trustedCert.getSerialNumber().compareTo(serialNumber) == 0) {
269                 Object certName = 
270                     createBCX509Name(trustedCert.getIssuerX500Principal().getName());
271                 if (certName.equals(issuerName)) {
272                     return new X509Certificate[]{trustedCert};
273                 }
274             }
275         }
276         
277         return null;
278     }
279     
280     /**
281      * Get an X509 Certificate (chain) according to a given Thumbprint.
282      *
283      * @param thumb The SHA1 thumbprint info bytes
284      * @return the X509 certificate (chain) that was found (can be null)
285      * @throws WSSecurityException if problems during keystore handling or wrong certificate
286      */
287     private X509Certificate[] getX509Certificates(byte[] thumb) throws WSSecurityException {
288         MessageDigest sha = null;
289         
290         if (trustedCerts == null) {
291             return null;
292         }
293         
294         try {
295             sha = MessageDigest.getInstance("SHA1");
296         } catch (NoSuchAlgorithmException e) {
297             throw new WSSecurityException(
298                 WSSecurityException.FAILURE, "noSHA1availabe", null, e
299             );
300         }
301         for (X509Certificate trustedCert : trustedCerts) {
302             try {
303                 sha.update(trustedCert.getEncoded());
304             } catch (CertificateEncodingException ex) {
305                 throw new WSSecurityException(
306                     WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError",
307                     null, ex
308                 );
309             }
310             byte[] data = sha.digest();
311 
312             if (Arrays.equals(data, thumb)) {
313                 return new X509Certificate[]{trustedCert};
314             }
315         }
316         return null;
317     }
318     
319     /**
320      * Get an X509 Certificate (chain) according to a given SubjectKeyIdentifier.
321      *
322      * @param skiBytes The SKI bytes
323      * @return the X509 certificate (chain) that was found (can be null)
324      */
325     private X509Certificate[] getX509CertificatesSKI(byte[] skiBytes) throws WSSecurityException {
326         if (trustedCerts == null) {
327             return null;
328         }
329         for (X509Certificate trustedCert : trustedCerts) {
330             byte[] data = getSKIBytesFromCert(trustedCert);
331             if (data.length == skiBytes.length && Arrays.equals(data, skiBytes)) {
332                 return new X509Certificate[]{trustedCert};
333             }
334         } 
335         return null;
336     }
337     
338     /**
339      * Get an X509 Certificate (chain) according to a given DN of the subject of the certificate
340      *
341      * @param subjectDN The DN of subject to look for
342      * @return An X509 Certificate (chain) with the same DN as given in the parameters
343      * @throws WSSecurityException
344      */
345     private X509Certificate[] getX509CertificatesSubjectDN(String subjectDN) 
346         throws WSSecurityException {
347         //
348         // Convert the subject DN to a java X500Principal object first. This is to ensure
349         // interop with a DN constructed from .NET, where e.g. it uses "S" instead of "ST".
350         // Then convert it to a BouncyCastle X509Name, which will order the attributes of
351         // the DN in a particular way (see WSS-168). If the conversion to an X500Principal
352         // object fails (e.g. if the DN contains "E" instead of "EMAILADDRESS"), then fall
353         // back on a direct conversion to a BC X509Name
354         //
355         Object subject;
356         try {
357             X500Principal subjectRDN = new X500Principal(subjectDN);
358             subject = createBCX509Name(subjectRDN.getName());
359         } catch (java.lang.IllegalArgumentException ex) {
360             subject = createBCX509Name(subjectDN);
361         }
362         
363         if (trustedCerts != null) {
364             for (X509Certificate trustedCert : trustedCerts) {
365                 X500Principal foundRDN = trustedCert.getSubjectX500Principal();
366                 Object certName = createBCX509Name(foundRDN.getName());
367     
368                 if (subject.equals(certName)) {
369                     return new X509Certificate[]{trustedCert};
370                 }
371             }
372         }
373         
374         return null;
375     }
376     
377 }