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 }