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.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.getIssuerDN().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 }