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.validate;
21  
22  import java.math.BigInteger;
23  import java.security.PublicKey;
24  import java.security.cert.CertificateExpiredException;
25  import java.security.cert.CertificateNotYetValidException;
26  import java.security.cert.X509Certificate;
27  import java.util.Collection;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import org.apache.ws.security.WSSecurityException;
32  import org.apache.ws.security.components.crypto.Crypto;
33  import org.apache.ws.security.components.crypto.CryptoType;
34  import org.apache.ws.security.handler.RequestData;
35  
36  /**
37   * This class verifies trust in a credential used to verify a signature, which is extracted
38   * from the Credential passed to the validate method.
39   */
40  public class SignatureTrustValidator implements Validator {
41      
42      private static final org.apache.commons.logging.Log LOG = 
43          org.apache.commons.logging.LogFactory.getLog(SignatureTrustValidator.class);
44      
45      /**
46       * Validate the credential argument. It must contain a non-null X509Certificate chain
47       * or a PublicKey. A Crypto implementation is also required to be set.
48       * 
49       * This implementation first attempts to verify trust on the certificate (chain). If
50       * this is not successful, then it will attempt to verify trust on the Public Key.
51       * 
52       * @param credential the Credential to be validated
53       * @param data the RequestData associated with the request
54       * @throws WSSecurityException on a failed validation
55       */
56      public Credential validate(Credential credential, RequestData data) throws WSSecurityException {
57          if (credential == null) {
58              throw new WSSecurityException(WSSecurityException.FAILURE, "noCredential");
59          }
60          X509Certificate[] certs = credential.getCertificates();
61          PublicKey publicKey = credential.getPublicKey();
62          Crypto crypto = getCrypto(data);
63          if (crypto == null) {
64              throw new WSSecurityException(WSSecurityException.FAILURE, "noSigCryptoFile");
65          }
66          
67          if (certs != null && certs.length > 0) {
68              validateCertificates(certs);
69              boolean trust = false;
70              boolean enableRevocation = data.isRevocationEnabled();
71              if (certs.length == 1) {
72                  trust = verifyTrustInCert(certs[0], crypto, data, enableRevocation);
73              } else {
74                  trust = verifyTrustInCerts(certs, crypto, data, enableRevocation);
75              }
76              if (trust) {
77                  return credential;
78              }
79          }
80          if (publicKey != null) {
81              boolean trust = validatePublicKey(publicKey, crypto);
82              if (trust) {
83                  return credential;
84              }
85          }
86          throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
87      }
88  
89  
90      protected Crypto getCrypto(RequestData data) {
91          return data.getSigCrypto();
92      }
93  
94  
95      /**
96       * Validate the certificates by checking the validity of each cert
97       * @throws WSSecurityException
98       */
99      protected void validateCertificates(X509Certificate[] certificates) 
100         throws WSSecurityException {
101         try {
102             for (int i = 0; i < certificates.length; i++) {
103                 certificates[i].checkValidity();
104             }
105         } catch (CertificateExpiredException e) {
106             throw new WSSecurityException(
107                 WSSecurityException.FAILED_CHECK, "invalidCert", null, e
108             );
109         } catch (CertificateNotYetValidException e) {
110             throw new WSSecurityException(
111                 WSSecurityException.FAILED_CHECK, "invalidCert", null, e
112             );
113         }
114     }
115     
116     @Deprecated
117     protected boolean verifyTrustInCert(X509Certificate cert, Crypto crypto) 
118         throws WSSecurityException {
119         return verifyTrustInCert(cert, crypto, new RequestData(), false);
120     }
121     
122     @Deprecated
123     protected boolean verifyTrustInCert(X509Certificate cert, Crypto crypto, boolean enableRevocation) 
124         throws WSSecurityException {
125         return verifyTrustInCert(cert, crypto, new RequestData(), enableRevocation);
126     }
127     
128     /**
129      * Evaluate whether a given certificate should be trusted.
130      * 
131      * Policy used in this implementation:
132      * 1. Search the keystore for the transmitted certificate
133      * 2. Search the keystore for a connection to the transmitted certificate
134      * (that is, search for certificate(s) of the issuer of the transmitted certificate
135      * 3. Verify the trust path for those certificates found because the search for the issuer 
136      * might be fooled by a phony DN (String!)
137      *
138      * @param cert the certificate that should be validated against the keystore
139      * @param crypto A crypto instance to use for trust validation
140      * @param data A RequestData instance
141      * @param enableRevocation Whether revocation is enabled or not
142      * @return true if the certificate is trusted, false if not
143      * @throws WSSecurityException
144      */
145     protected boolean verifyTrustInCert(
146         X509Certificate cert, 
147         Crypto crypto,
148         RequestData data,
149         boolean enableRevocation
150     ) throws WSSecurityException {
151         String subjectString = cert.getSubjectX500Principal().getName();
152         String issuerString = cert.getIssuerX500Principal().getName();
153         BigInteger issuerSerial = cert.getSerialNumber();
154 
155         if (LOG.isDebugEnabled()) {
156             LOG.debug("Transmitted certificate has subject " + subjectString);
157             LOG.debug(
158                 "Transmitted certificate has issuer " + issuerString + " (serial " 
159                 + issuerSerial + ")"
160             );
161         }
162 
163         //
164         // FIRST step - Search the keystore for the transmitted certificate
165         //
166         if (!enableRevocation && isCertificateInKeyStore(crypto, cert)) {
167             return true;
168         }
169 
170         //
171         // SECOND step - Search for the issuer cert (chain) of the transmitted certificate in the 
172         // keystore or the truststore
173         //
174         CryptoType cryptoType = new CryptoType(CryptoType.TYPE.SUBJECT_DN);
175         cryptoType.setSubjectDN(issuerString);
176         X509Certificate[] foundCerts = crypto.getX509Certificates(cryptoType);
177 
178         // If the certs have not been found, the issuer is not in the keystore/truststore
179         // As a direct result, do not trust the transmitted certificate
180         if (foundCerts == null || foundCerts.length < 1) {
181             if (LOG.isDebugEnabled()) {
182                 LOG.debug(
183                     "No certs found in keystore for issuer " + issuerString 
184                     + " of certificate for " + subjectString
185                 );
186             }
187             return false;
188         }
189 
190         //
191         // THIRD step
192         // Check the certificate trust path for the issuer cert chain
193         //
194         if (LOG.isDebugEnabled()) {
195             LOG.debug(
196                 "Preparing to validate certificate path for issuer " + issuerString
197             );
198         }
199         //
200         // Form a certificate chain from the transmitted certificate
201         // and the certificate(s) of the issuer from the keystore/truststore
202         //
203         X509Certificate[] x509certs = new X509Certificate[foundCerts.length + 1];
204         x509certs[0] = cert;
205         for (int j = 0; j < foundCerts.length; j++) {
206             x509certs[j + 1] = (X509Certificate)foundCerts[j];
207         }
208 
209         //
210         // Use the validation method from the crypto to check whether the subjects' 
211         // certificate was really signed by the issuer stated in the certificate
212         //
213         if (crypto.verifyTrust(x509certs, enableRevocation)) {
214             if (LOG.isDebugEnabled()) {
215                 LOG.debug(
216                     "Certificate path has been verified for certificate with subject " 
217                      + subjectString
218                 );
219             }
220             Collection<Pattern> subjectCertConstraints = data.getSubjectCertConstraints();
221             if (matches(cert, subjectCertConstraints)) {
222                 return true;
223             }
224         }
225 
226         if (LOG.isDebugEnabled()) {
227             LOG.debug(
228                 "Certificate path could not be verified for certificate with subject " 
229                 + subjectString
230             );
231         }
232         return false;
233     }
234     
235     /**
236      * Check to see if the certificate argument is in the keystore
237      * @param crypto A Crypto instance to use for trust validation
238      * @param cert The certificate to check
239      * @return true if cert is in the keystore
240      * @throws WSSecurityException
241      */
242     protected boolean isCertificateInKeyStore(
243         Crypto crypto,
244         X509Certificate cert
245     ) throws WSSecurityException {
246         String issuerString = cert.getIssuerX500Principal().getName();
247         BigInteger issuerSerial = cert.getSerialNumber();
248         
249         CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
250         cryptoType.setIssuerSerial(issuerString, issuerSerial);
251         X509Certificate[] foundCerts = crypto.getX509Certificates(cryptoType);
252 
253         //
254         // If a certificate has been found, the certificates must be compared
255         // to ensure against phony DNs (compare encoded form including signature)
256         //
257         if (foundCerts != null && foundCerts[0] != null && foundCerts[0].equals(cert)) {
258             if (LOG.isDebugEnabled()) {
259                 LOG.debug(
260                     "Direct trust for certificate with " + cert.getSubjectX500Principal().getName()
261                 );
262             }
263             return true;
264         }
265         if (LOG.isDebugEnabled()) {
266             LOG.debug(
267                 "No certificate found for subject from issuer with " + issuerString 
268                 + " (serial " + issuerSerial + ")"
269             );
270         }
271         return false;
272     }
273     
274     @Deprecated
275     protected boolean verifyTrustInCerts(
276         X509Certificate[] certificates, 
277         Crypto crypto
278     ) throws WSSecurityException {
279         return verifyTrustInCerts(certificates, crypto, new RequestData(), false);
280     }
281     
282     @Deprecated
283     protected boolean verifyTrustInCerts(
284         X509Certificate[] certificates, 
285         Crypto crypto,
286         boolean enableRevocation
287     ) throws WSSecurityException {
288         return verifyTrustInCerts(certificates, crypto, new RequestData(), enableRevocation);
289     }
290     
291     /**
292      * Evaluate whether the given certificate chain should be trusted.
293      * 
294      * @param certificates the certificate chain that should be validated against the keystore
295      * @param crypto A Crypto instance
296      * @param data A RequestData instance
297      * @param enableRevocation Whether revocation is enabled or not
298      * @return true if the certificate chain is trusted, false if not
299      * @throws WSSecurityException
300      */
301     protected boolean verifyTrustInCerts(
302         X509Certificate[] certificates, 
303         Crypto crypto,
304         RequestData data,
305         boolean enableRevocation
306     ) throws WSSecurityException {
307         if (certificates == null || certificates.length < 2) {
308             return false;
309         }
310         
311         String subjectString = certificates[0].getSubjectX500Principal().getName();
312         //
313         // Use the validation method from the crypto to check whether the subjects' 
314         // certificate was really signed by the issuer stated in the certificate
315         //
316         if (crypto.verifyTrust(certificates, enableRevocation)) {
317             if (LOG.isDebugEnabled()) {
318                 LOG.debug(
319                     "Certificate path has been verified for certificate with subject " 
320                     + subjectString
321                 );
322             }
323             Collection<Pattern> subjectCertConstraints = data.getSubjectCertConstraints();
324             if (matches(certificates[0], subjectCertConstraints)) {
325                 return true;
326             }
327         }
328         
329         if (LOG.isDebugEnabled()) {
330             LOG.debug(
331                 "Certificate path could not be verified for certificate with subject " 
332                 + subjectString
333             );
334         }
335             
336         return false;
337     }
338     
339     /**
340      * Validate a public key
341      * @throws WSSecurityException
342      */
343     protected boolean validatePublicKey(PublicKey publicKey, Crypto crypto) 
344         throws WSSecurityException {
345         return crypto.verifyTrust(publicKey);
346     }
347     
348     /**
349      * @return      true if the certificate's SubjectDN matches the constraints defined in the
350      *              subject DNConstraints; false, otherwise. The certificate subject DN only
351      *              has to match ONE of the subject cert constraints (not all).
352      */
353     protected boolean
354     matches(
355         final java.security.cert.X509Certificate cert,
356         final Collection<Pattern> subjectDNPatterns
357     ) {
358         if (subjectDNPatterns.isEmpty()) {
359             LOG.warn("No Subject DN Certificate Constraints were defined. This could be a security issue");
360         }
361         if (!subjectDNPatterns.isEmpty()) {
362             if (cert == null) {
363                 LOG.debug("The certificate is null so no constraints matching was possible");
364                 return false;
365             }
366             String subjectName = cert.getSubjectX500Principal().getName();
367             boolean subjectMatch = false;
368             for (Pattern subjectDNPattern : subjectDNPatterns) {
369                 final Matcher matcher = subjectDNPattern.matcher(subjectName);
370                 if (matcher.matches()) {
371                     LOG.debug("Subject DN " + subjectName + " matches with pattern " + subjectDNPattern);
372                     subjectMatch = true;
373                     break;
374                 }
375             }
376             if (!subjectMatch) {
377                 return false;
378             }
379         }
380         
381         return true;
382     }
383     
384 }