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.IOException;
23  import java.math.BigInteger;
24  import java.security.InvalidAlgorithmParameterException;
25  import java.security.KeyStore;
26  import java.security.KeyStoreException;
27  import java.security.NoSuchAlgorithmException;
28  import java.security.NoSuchProviderException;
29  import java.security.cert.CertPath;
30  import java.security.cert.CertPathValidator;
31  import java.security.cert.Certificate;
32  import java.security.cert.CertificateEncodingException;
33  import java.security.cert.CertificateException;
34  import java.security.cert.CertificateExpiredException;
35  import java.security.cert.CertificateNotYetValidException;
36  import java.security.cert.PKIXParameters;
37  import java.security.cert.TrustAnchor;
38  import java.security.cert.X509Certificate;
39  import java.util.Arrays;
40  import java.util.Collection;
41  import java.util.Enumeration;
42  import java.util.HashSet;
43  import java.util.List;
44  import java.util.Properties;
45  import java.util.Set;
46  import java.util.regex.Pattern;
47  
48  import org.apache.wss4j.common.ext.WSSecurityException;
49  
50  /**
51   * A Crypto implementation based on two Java KeyStore objects, one being the keystore, and one
52   * being the truststore. It differs from Merlin in that it searches the truststore for the
53   * issuing cert using the AuthorityKeyIdentifier bytes of the certificate, as opposed to the
54   * issuer DN.
55   */
56  public class MerlinAKI extends Merlin {
57  
58      private static final org.slf4j.Logger LOG =
59          org.slf4j.LoggerFactory.getLogger(MerlinAKI.class);
60  
61      public MerlinAKI() {
62          super();
63      }
64  
65      public MerlinAKI(boolean loadCACerts, String cacertsPasswd) {
66          super(loadCACerts, cacertsPasswd);
67      }
68  
69      public MerlinAKI(Properties properties, ClassLoader loader, PasswordEncryptor passwordEncryptor)
70          throws WSSecurityException, IOException {
71          super(properties, loader, passwordEncryptor);
72      }
73  
74      /**
75       * Evaluate whether a given certificate chain should be trusted.
76       *
77       * @param certs Certificate chain to validate
78       * @param enableRevocation whether to enable CRL verification or not
79       * @param subjectCertConstraints A set of constraints on the Subject DN of the certificates
80       *
81       * @throws WSSecurityException if the certificate chain is invalid
82       */
83      @Override
84      protected void verifyTrust(
85          X509Certificate[] certs,
86          boolean enableRevocation,
87          Collection<Pattern> subjectCertConstraints
88      ) throws WSSecurityException {
89          //
90          // FIRST step - Search the keystore for the transmitted certificate
91          //
92          if (certs.length == 1 && !enableRevocation) {
93              String issuerString = certs[0].getIssuerX500Principal().getName();
94              BigInteger issuerSerial = certs[0].getSerialNumber();
95  
96              CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
97              cryptoType.setIssuerSerial(issuerString, issuerSerial);
98              X509Certificate[] foundCerts = getX509Certificates(cryptoType);
99  
100             //
101             // If a certificate has been found, the certificates must be compared
102             // to ensure against phony DNs (compare encoded form including signature)
103             //
104             if (foundCerts != null && foundCerts.length > 0 && foundCerts[0] != null && foundCerts[0].equals(certs[0])) {
105                 try {
106                     certs[0].checkValidity();
107                 } catch (CertificateExpiredException | CertificateNotYetValidException e) {
108                     throw new WSSecurityException(
109                         WSSecurityException.ErrorCode.FAILED_CHECK, e, "invalidCert"
110                     );
111                 }
112                 LOG.debug(
113                     "Direct trust for certificate with {}", certs[0].getSubjectX500Principal().getName()
114                 );
115                 return;
116             }
117         }
118 
119         //
120         // SECOND step - Search for the issuer cert (chain) of the transmitted certificate in the
121         // keystore or the truststore
122         //
123         X509Certificate[] x509certs = certs;
124         String issuerString = certs[0].getIssuerX500Principal().getName();
125         try {
126             if (certs.length == 1) {
127                 byte[] keyIdentifierBytes =
128                     BouncyCastleUtils.getAuthorityKeyIdentifierBytes(certs[0]);
129                 X509Certificate[] foundCerts = getX509CertificatesFromKeyIdentifier(keyIdentifierBytes);
130 
131                 // If the certs have not been found, the issuer is not in the keystore/truststore
132                 // As a direct result, do not trust the transmitted certificate
133                 if (foundCerts == null || foundCerts.length < 1) {
134                     String subjectString = certs[0].getSubjectX500Principal().getName();
135                     LOG.debug(
136                         "No certs found in keystore for issuer {} of certificate for {}",
137                          issuerString, subjectString
138                     );
139                     throw new WSSecurityException(
140                         WSSecurityException.ErrorCode.FAILURE, "certpath", new Object[] {"No trusted certs found"}
141                     );
142                 }
143 
144                 //
145                 // Form a certificate chain from the transmitted certificate
146                 // and the certificate(s) of the issuer from the keystore/truststore
147                 //
148                 x509certs = new X509Certificate[foundCerts.length + 1];
149                 x509certs[0] = certs[0];
150                 System.arraycopy(foundCerts, 0, x509certs, 1, foundCerts.length);
151             }
152         } catch (NoSuchAlgorithmException | CertificateException ex) {
153             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "certpath");
154         }
155 
156         //
157         // THIRD step
158         // Check the certificate trust path for the issuer cert chain
159         //
160         LOG.debug(
161             "Preparing to validate certificate path for issuer {}", issuerString
162         );
163 
164         try {
165             // Generate cert path
166             List<X509Certificate> certList = Arrays.asList(x509certs);
167             CertPath path = getCertificateFactory().generateCertPath(certList);
168 
169             Set<TrustAnchor> set = new HashSet<>();
170             if (truststore != null) {
171                 addTrustAnchors(set, truststore);
172             }
173 
174             //
175             // Add certificates from the keystore - only if there is no TrustStore, apart from
176             // the case that the truststore is the JDK CA certs. This behaviour is preserved
177             // for backwards compatibility reasons
178             //
179             if (keystore != null && (truststore == null || loadCACerts)) {
180                 addTrustAnchors(set, keystore);
181             }
182 
183             // Verify the trust path using the above settings
184             String provider = getCryptoProvider();
185             CertPathValidator validator = null;
186             if (provider == null || provider.length() == 0) {
187                 validator = CertPathValidator.getInstance("PKIX");
188             } else {
189                 validator = CertPathValidator.getInstance("PKIX", provider);
190             }
191 
192             PKIXParameters param = createPKIXParameters(set, enableRevocation);
193             validator.validate(path, param);
194         } catch (NoSuchProviderException | NoSuchAlgorithmException
195             | CertificateException | InvalidAlgorithmParameterException
196             | java.security.cert.CertPathValidatorException
197             | KeyStoreException e) {
198                 throw new WSSecurityException(
199                     WSSecurityException.ErrorCode.FAILURE, e, "certpath"
200                 );
201         }
202 
203         // Finally check Cert Constraints
204         if (!matchesSubjectDnPattern(certs[0], subjectCertConstraints)) {
205             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
206         }
207     }
208 
209     private X509Certificate[] getX509CertificatesFromKeyIdentifier(
210         byte[] keyIdentifierBytes
211     ) throws WSSecurityException, NoSuchAlgorithmException, CertificateEncodingException {
212         if (keyIdentifierBytes == null) {
213             return new X509Certificate[0];
214         }
215 
216         Certificate[] certs = null;
217         if (keystore != null) {
218             certs = getCertificates(keyIdentifierBytes, keystore);
219         }
220 
221         //If we can't find the issuer in the keystore then look at the truststore
222         if ((certs == null || certs.length == 0) && truststore != null) {
223             certs = getCertificates(keyIdentifierBytes, truststore);
224         }
225 
226         if (certs == null || certs.length == 0) {
227             return new X509Certificate[0];
228         }
229 
230         return Arrays.copyOf(certs, certs.length, X509Certificate[].class);
231     }
232 
233     private Certificate[] getCertificates(
234         byte[] keyIdentifier,
235         KeyStore store
236     ) throws WSSecurityException, NoSuchAlgorithmException, CertificateEncodingException {
237         try {
238             for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) {
239                 String alias = e.nextElement();
240                 Certificate[] certs = store.getCertificateChain(alias);
241                 if (certs == null || certs.length == 0) {
242                     // no cert chain, so lets check if getCertificate gives us a result.
243                     Certificate cert = store.getCertificate(alias);
244                     if (cert != null) {
245                         certs = new Certificate[]{cert};
246                     }
247                 }
248 
249                 if (certs != null && certs.length > 0 && certs[0] instanceof X509Certificate) {
250                     byte[] subjectKeyIdentifier =
251                         BouncyCastleUtils.getSubjectKeyIdentifierBytes((X509Certificate)certs[0]);
252                     if (subjectKeyIdentifier != null
253                         && Arrays.equals(subjectKeyIdentifier, keyIdentifier)) {
254                         return certs;
255                     }
256                 }
257             }
258         } catch (KeyStoreException e) {
259             throw new WSSecurityException(
260                 WSSecurityException.ErrorCode.FAILURE, e, "keystore"
261             );
262         }
263         return new Certificate[]{};
264     }
265 
266 }