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.wss4j.common.crypto;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.lang.reflect.Constructor;
26 import java.security.MessageDigest;
27 import java.security.NoSuchProviderException;
28 import java.security.cert.CertPath;
29 import java.security.cert.CertificateEncodingException;
30 import java.security.cert.CertificateException;
31 import java.security.cert.CertificateFactory;
32 import java.security.cert.X509Certificate;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39 import javax.security.auth.x500.X500Principal;
40
41 import org.apache.wss4j.common.ext.WSSecurityException;
42
43 /**
44 * This Abstract Base Class implements the accessor and keystore-independent methods and
45 * functionality of the Crypto interface.
46 */
47 public abstract class CryptoBase implements Crypto {
48 public static final String SKI_OID = "2.5.29.14"; //NOPMD - not an IP address
49 /**
50 * OID For the NameConstraints Extension to X.509
51 *
52 * http://java.sun.com/j2se/1.4.2/docs/api/
53 * http://www.ietf.org/rfc/rfc3280.txt (s. 4.2.1.11)
54 */
55 public static final String NAME_CONSTRAINTS_OID = "2.5.29.30"; //NOPMD - not an IP address
56
57 private static final org.slf4j.Logger LOG =
58 org.slf4j.LoggerFactory.getLogger(CryptoBase.class);
59
60 private static final Constructor<?> BC_509CLASS_CONS;
61
62 protected CertificateFactory certificateFactory;
63 private String defaultAlias;
64 private String cryptoProvider;
65 private String trustProvider;
66
67 static {
68 Constructor<?> cons = null;
69 try {
70 Class<?> c = Class.forName("org.bouncycastle.asn1.x500.X500Name");
71 cons = c.getConstructor(new Class[] {String.class});
72 } catch (Exception e) { //NOPMD
73 //ignore
74 }
75 BC_509CLASS_CONS = cons;
76 }
77
78 /**
79 * Constructor
80 */
81 protected CryptoBase() {
82 }
83
84 /**
85 * Get the crypto provider associated with this implementation
86 * @return the crypto provider
87 */
88 public String getCryptoProvider() {
89 return cryptoProvider;
90 }
91
92 /**
93 * Set the crypto provider associated with this implementation
94 * @param provider the crypto provider to set
95 */
96 public void setCryptoProvider(String provider) {
97 cryptoProvider = provider;
98 }
99
100 /**
101 * Set the crypto provider used for truststore operations associated with this implementation
102 * @param provider the name of the provider
103 */
104 public void setTrustProvider(String provider) {
105 trustProvider = provider;
106 }
107
108 /**
109 * Get the crypto provider used for truststore operation associated with this implementation.
110 * @return a crypto provider name
111 */
112 public String getTrustProvider() {
113 return trustProvider;
114 }
115
116 /**
117 * Retrieves the identifier name of the default certificate. This should be the certificate
118 * that is used for signature and encryption. This identifier corresponds to the certificate
119 * that should be used whenever KeyInfo is not present in a signed or an encrypted
120 * message. May return null. The identifier is implementation specific, e.g. it could be the
121 * KeyStore alias.
122 *
123 * @return name of the default X509 certificate.
124 */
125 public String getDefaultX509Identifier() throws WSSecurityException {
126 return defaultAlias;
127 }
128
129 /**
130 * Sets the identifier name of the default certificate. This should be the certificate
131 * that is used for signature and encryption. This identifier corresponds to the certificate
132 * that should be used whenever KeyInfo is not present in a signed or an encrypted
133 * message. The identifier is implementation specific, e.g. it could be the KeyStore alias.
134 *
135 * @param identifier name of the default X509 certificate.
136 */
137 public void setDefaultX509Identifier(String identifier) {
138 defaultAlias = identifier;
139 }
140
141 /**
142 * Sets the CertificateFactory instance on this Crypto instance
143 *
144 * @param certFactory the CertificateFactory the CertificateFactory instance to set
145 */
146 public void setCertificateFactory(CertificateFactory certFactory) {
147 this.certificateFactory = certFactory;
148 }
149
150 /**
151 * Get the CertificateFactory instance on this Crypto instance
152 *
153 * @return Returns a <code>CertificateFactory</code> to construct
154 * X509 certificates
155 * @throws WSSecurityException
156 */
157 public CertificateFactory getCertificateFactory() throws WSSecurityException {
158 if (certificateFactory != null) {
159 return certificateFactory;
160 }
161
162 try {
163 String provider = getCryptoProvider();
164 if (provider == null || provider.length() == 0) {
165 certificateFactory = CertificateFactory.getInstance("X.509");
166 } else {
167 certificateFactory = CertificateFactory.getInstance("X.509", provider);
168 }
169 } catch (CertificateException e) {
170 throw new WSSecurityException(
171 WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "unsupportedCertType"
172 );
173 } catch (NoSuchProviderException e) {
174 throw new WSSecurityException(
175 WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "noSecProvider"
176 );
177 }
178
179 return certificateFactory;
180 }
181
182 /**
183 * Load a X509Certificate from the input stream.
184 *
185 * @param in The <code>InputStream</code> containing the X509Certificate
186 * @return An X509 certificate
187 * @throws WSSecurityException
188 */
189 public X509Certificate loadCertificate(InputStream in) throws WSSecurityException {
190 try {
191 CertificateFactory certFactory = getCertificateFactory();
192 return (X509Certificate) certFactory.generateCertificate(in);
193 } catch (CertificateException e) {
194 throw new WSSecurityException(
195 WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError"
196 );
197 }
198 }
199
200 /**
201 * Reads the SubjectKeyIdentifier information from the certificate.
202 * <p/>
203 * If the the certificate does not contain a SKI extension then
204 * try to compute the SKI according to RFC3280 using the
205 * SHA-1 hash value of the public key. The second method described
206 * in RFC3280 is not support. Also only RSA public keys are supported.
207 * If we cannot compute the SKI throw a WSSecurityException.
208 *
209 * @param cert The certificate to read SKI
210 * @return The byte array containing the binary SKI data
211 */
212 public byte[] getSKIBytesFromCert(X509Certificate cert) throws WSSecurityException {
213 //
214 // Gets the DER-encoded OCTET string for the extension value (extnValue)
215 // identified by the passed-in oid String. The oid string is represented
216 // by a set of positive whole numbers separated by periods.
217 //
218 byte[] derEncodedValue = cert.getExtensionValue(SKI_OID);
219
220 if (cert.getVersion() < 3 || derEncodedValue == null) {
221 X509SubjectPublicKeyInfo spki = new X509SubjectPublicKeyInfo(cert.getPublicKey());
222 byte[] value = spki.getSubjectPublicKey();
223 try {
224 MessageDigest digest = MessageDigest.getInstance("SHA-1");
225 return digest.digest(value);
226 } catch (Exception ex) {
227 throw new WSSecurityException(
228 WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN, ex, "noSKIHandling",
229 new Object[] {"No SKI certificate extension and no SHA1 message digest available"}
230 );
231 }
232 }
233
234 //
235 // Strip away first (four) bytes from the DerValue (tag and length of
236 // ExtensionValue OCTET STRING and KeyIdentifier OCTET STRING)
237 //
238 DERDecoder extVal = new DERDecoder(derEncodedValue);
239 extVal.expect(DERDecoder.TYPE_OCTET_STRING); // ExtensionValue OCTET STRING
240 extVal.getLength();
241 extVal.expect(DERDecoder.TYPE_OCTET_STRING); // KeyIdentifier OCTET STRING
242 int keyIDLen = extVal.getLength();
243 return extVal.getBytes(keyIDLen);
244 }
245
246 /**
247 * Get a byte array given an array of X509 certificates.
248 * <p/>
249 *
250 * @param certs The certificates to convert
251 * @return The byte array for the certificates
252 * @throws WSSecurityException
253 */
254 public byte[] getBytesFromCertificates(X509Certificate[] certs)
255 throws WSSecurityException {
256 try {
257 CertPath path = getCertificateFactory().generateCertPath(Arrays.asList(certs));
258 return path.getEncoded();
259 } catch (CertificateEncodingException e) {
260 throw new WSSecurityException(
261 WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "encodeError"
262 );
263 } catch (CertificateException e) {
264 throw new WSSecurityException(
265 WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError"
266 );
267 }
268 }
269
270 /**
271 * Construct an array of X509Certificate's from the byte array.
272 * <p/>
273 *
274 * @param data The <code>byte</code> array containing the X509 data
275 * @return An array of X509 certificates
276 * @throws WSSecurityException
277 */
278 public X509Certificate[] getCertificatesFromBytes(byte[] data)
279 throws WSSecurityException {
280 CertPath path = null;
281 try (InputStream in = new ByteArrayInputStream(data)) {
282 path = getCertificateFactory().generateCertPath(in);
283 } catch (CertificateException | IOException e) {
284 throw new WSSecurityException(
285 WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError"
286 );
287 }
288
289 List<?> l = path.getCertificates();
290 X509Certificate[] certs = new X509Certificate[l.size()];
291 int i = 0;
292 for (Object cert : l) {
293 certs[i++] = (X509Certificate) cert;
294 }
295 return certs;
296 }
297
298 protected Object createBCX509Name(String s) {
299 if (BC_509CLASS_CONS != null) {
300 try {
301 return BC_509CLASS_CONS.newInstance(s);
302 } catch (Exception e) { //NOPMD
303 //ignore
304 }
305 }
306 return new X500Principal(s);
307 }
308
309 /**
310 * @return true if the certificate's SubjectDN matches the constraints defined in the
311 * subject DNConstraints; false, otherwise. The certificate subject DN only
312 * has to match ONE of the subject cert constraints (not all).
313 */
314 protected boolean
315 matchesSubjectDnPattern(
316 final X509Certificate cert, final Collection<Pattern> subjectDNPatterns
317 ) {
318 if (cert == null) {
319 LOG.debug("The certificate is null so no constraints matching was possible");
320 return false;
321 }
322 String subjectName = cert.getSubjectX500Principal().getName();
323 if (subjectDNPatterns == null || subjectDNPatterns.isEmpty()) {
324 LOG.warn("No Subject DN Certificate Constraints were defined. This could be a security issue");
325 return true;
326 }
327 return matchesName(subjectName, subjectDNPatterns);
328 }
329
330 /**
331 * @return true if the certificate's Issuer DN matches the constraints defined in the
332 * subject DNConstraints; false, otherwise. The certificate subject DN only
333 * has to match ONE of the subject cert constraints (not all).
334 */
335 protected boolean
336 matchesIssuerDnPattern(
337 final X509Certificate cert, final Collection<Pattern> issuerDNPatterns
338 ) {
339 if (cert == null) {
340 LOG.debug("The certificate is null so no constraints matching was possible");
341 return false;
342 }
343 String issuerDn = cert.getIssuerX500Principal().getName();
344 return matchesName(issuerDn, issuerDNPatterns);
345 }
346
347 /**
348 * @return true if the provided name matches the constraints defined in the
349 * subject DNConstraints; false, otherwise. The certificate (subject) DN only
350 * has to match ONE of the (subject) cert constraints (not all).
351 */
352 protected boolean
353 matchesName(
354 final String name, final Collection<Pattern> patterns
355 ) {
356 if (patterns != null && !patterns.isEmpty()) {
357 if (name == null || name.isEmpty()) {
358 LOG.debug("The name is null so no constraints matching was possible");
359 return false;
360 }
361 boolean subjectMatch = false;
362 for (Pattern subjectDNPattern : patterns) {
363 final Matcher matcher = subjectDNPattern.matcher(name);
364 if (matcher.matches()) {
365 LOG.debug("Name {} matches with pattern {}", name, subjectDNPattern);
366 subjectMatch = true;
367 break;
368 }
369 }
370 if (!subjectMatch) {
371 return false;
372 }
373 }
374
375 return true;
376 }
377
378 /**
379 * Extracts the NameConstraints sequence from the certificate.
380 * Handles the case where the data is encoded directly as {@link DERDecoder#TYPE_SEQUENCE}
381 * or where the sequence has been encoded as an {@link DERDecoder#TYPE_OCTET_STRING}.
382 * <p>
383 * By contract, the values retrieved from calls to {@link X509Certificate#getExtensionValue(String)}
384 * should always be DER-encoded OCTET strings; however, because of ambiguity in the RFC and
385 * the potential for a future breaking change to this contract, testing whether the bytes
386 * returned are tagged as a sequence or an encoded octet string is prudent. Considering the fact
387 * that it is a single byte comparison, the performance hit is negligible.
388 *
389 * @param cert the certificate to extract NameConstraints from
390 * @return the NameConstraints, or null if not present
391 * @throws WSSecurityException if a processing error occurs decoding the Octet String
392 */
393 protected byte[] getNameConstraints(final X509Certificate cert) throws WSSecurityException {
394 byte[] bytes = cert.getExtensionValue(NAME_CONSTRAINTS_OID);
395 if (bytes == null || bytes.length <= 0) {
396 return new byte[0];
397 }
398
399 switch (bytes[0]) {
400 case DERDecoder.TYPE_OCTET_STRING:
401 DERDecoder extVal = new DERDecoder(bytes);
402 extVal.expect(DERDecoder.TYPE_OCTET_STRING);
403 int seqLen = extVal.getLength();
404 return extVal.getBytes(seqLen);
405 case DERDecoder.TYPE_SEQUENCE:
406 return bytes;
407 default:
408 throw new IllegalArgumentException(
409 "Invalid type for NameConstraints; must be Sequence or OctetString-encoded Sequence");
410 }
411 }
412 }