Found that it was not possible with Merlin cause it only allow to define a single CRL File. I have done a quick change that enable a comma separated list of crl. Here is the change. Can someone review it and if it's ok add it to the official source code ? // // Load the CRL file // String crlLocations = properties.getProperty(prefix + X509_CRL_FILE); if (crlLocations != null) { crlLocations = crlLocations.trim(); String[] splittedCrlsLocation=crlLocations.split(","); List<X509CRL> crls=new ArrayList(); for (int i = 0; i < splittedCrlsLocation.length; i++) { String crlLocation = splittedCrlsLocation[i]; InputStream is = loadInputStream(loader, crlLocation);
try { CertificateFactory cf = getCertificateFactory(); X509CRL crl = (X509CRL)cf.generateCRL(is); crls.add(crl); } catch (Exception e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "ioError00", e); } finally { if (is != null) { is.close(); } } } try { if (provider == null || provider.length() == 0) { crlCertStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters(crls) ); } else { crlCertStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters(crls), provider ); } } catch (Exception e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "ioError00", e); } if (DO_DEBUG) { LOG.debug( "The CRL " + crlLocations + " has been loaded" ); } Best Regards, Claude 2016-09-30 15:14 GMT+02:00 Claude Libois <clibois.w...@gmail.com>: > Hi, > I got the following pki chain Root CA>Intermediate CA>Client signing > certificate > A suggested by Colm, I have set in my truststore my Intermediate CA and my > Root CA. > However, by doing this, CRL verification doesn't work. In fact, it seems > to validate my Intermediate CA against the Root CA crl while I'm only > interested to verify the client certificate. > I'm not sure how revocation validation works but it seems to validate CRL > for every certificate(except the Root). > However, I don't know how to specify multiple CRL in WSS4J or if it > possible to merge 2 crl files into a common one ? > I have provided 2 logs. The first one with the Intermediate CA CRL. We can > see that validation of the Intermediate CA against Root CRL failed since > it's not provided. > The second one is with the Root CA CRL. Intermediate CA validation succeed > but the signing certificate then failed... > > Best Regards, > Claude >
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.wss4j.common.crypto; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.x500.X500Principal; import org.apache.wss4j.common.ext.WSPasswordCallback; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.util.Loader; /** * A Crypto implementation based on two Java KeyStore objects, one being the keystore, and one * being the truststore. */ public class Merlin extends CryptoBase { public static final String ENCRYPTED_PASSWORD_PREFIX = "ENC("; public static final String ENCRYPTED_PASSWORD_SUFFIX = ")"; public static final String PREFIX = "org.apache.wss4j.crypto.merlin."; public static final String OLD_PREFIX = "org.apache.ws.security.crypto.merlin."; /* * Deprecated types */ public static final String OLD_KEYSTORE_FILE = "file"; /* * Crypto providers */ public static final String CRYPTO_KEYSTORE_PROVIDER = "keystore.provider"; public static final String CRYPTO_CERT_PROVIDER = "cert.provider"; /* * KeyStore configuration types */ public static final String KEYSTORE_FILE = "keystore.file"; public static final String KEYSTORE_PASSWORD ="keystore.password"; public static final String KEYSTORE_TYPE ="keystore.type"; public static final String KEYSTORE_ALIAS ="keystore.alias"; public static final String KEYSTORE_PRIVATE_PASSWORD ="keystore.private.password"; /* * TrustStore configuration types */ public static final String LOAD_CA_CERTS ="load.cacerts"; public static final String TRUSTSTORE_FILE ="truststore.file"; public static final String TRUSTSTORE_PASSWORD ="truststore.password"; public static final String TRUSTSTORE_TYPE = "truststore.type"; /* * CRL configuration */ public static final String X509_CRL_FILE = "x509crl.file"; private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(Merlin.class); private static final boolean DO_DEBUG = LOG.isDebugEnabled(); protected Properties properties; protected KeyStore keystore; protected KeyStore truststore; protected CertStore crlCertStore; protected boolean loadCACerts; protected boolean privatePasswordSet; protected PasswordEncryptor passwordEncryptor; public Merlin() { // default constructor } public Merlin(boolean loadCACerts, String cacertsPasswd) { super(); if (truststore == null && loadCACerts) { InputStream cacertsIs = null; try { String cacertsPath = System.getProperty("java.home") + "/lib/security/cacerts"; cacertsIs = new FileInputStream(cacertsPath); truststore = KeyStore.getInstance(KeyStore.getDefaultType()); truststore.load(cacertsIs, cacertsPasswd.toCharArray()); loadCACerts = true; } catch (Exception e) { LOG.warn("CA certs could not be loaded: " + e.getMessage()); } finally { if (cacertsIs != null) { try { cacertsIs.close(); } catch (IOException e) { LOG.debug(e.getMessage(), e); //ignore } } } } } public Merlin(Properties properties, ClassLoader loader, PasswordEncryptor passwordEncryptor) throws WSSecurityException, IOException { loadProperties(properties, loader, passwordEncryptor); } public void loadProperties( Properties properties, ClassLoader loader, PasswordEncryptor passwordEncryptor ) throws WSSecurityException, IOException { if (properties == null) { return; } this.properties = properties; this.passwordEncryptor = passwordEncryptor; String prefix = PREFIX; for (Object key : properties.keySet()) { if (key instanceof String) { String propKey = (String)key; if (propKey.startsWith(PREFIX)) { break; } else if (propKey.startsWith(OLD_PREFIX)) { prefix = OLD_PREFIX; break; } } } // // Load the provider(s) // String provider = properties.getProperty(prefix + CRYPTO_KEYSTORE_PROVIDER); if (provider != null) { provider = provider.trim(); } String certProvider = properties.getProperty(prefix + CRYPTO_CERT_PROVIDER); if (certProvider != null) { setCryptoProvider(certProvider); } // // Load the KeyStore // String alias = properties.getProperty(prefix + KEYSTORE_ALIAS); if (alias != null) { alias = alias.trim(); defaultAlias = alias; } String keyStoreLocation = properties.getProperty(prefix + KEYSTORE_FILE); if (keyStoreLocation == null) { keyStoreLocation = properties.getProperty(prefix + OLD_KEYSTORE_FILE); } if (keyStoreLocation != null) { keyStoreLocation = keyStoreLocation.trim(); InputStream is = loadInputStream(loader, keyStoreLocation); try { String passwd = properties.getProperty(prefix + KEYSTORE_PASSWORD, "security"); if (passwd != null) { passwd = passwd.trim(); passwd = decryptPassword(passwd, passwordEncryptor); } String type = properties.getProperty(prefix + KEYSTORE_TYPE, KeyStore.getDefaultType()); if (type != null) { type = type.trim(); } keystore = load(is, passwd, provider, type); if (DO_DEBUG) { LOG.debug( "The KeyStore " + keyStoreLocation + " of type " + type + " has been loaded" ); } String privatePasswd = properties.getProperty(prefix + KEYSTORE_PRIVATE_PASSWORD); if (privatePasswd != null) { privatePasswordSet = true; } } finally { if (is != null) { is.close(); } } } else { if (DO_DEBUG) { LOG.debug("The KeyStore is not loaded as KEYSTORE_FILE is null"); } } // // Load the TrustStore // String trustStoreLocation = properties.getProperty(prefix + TRUSTSTORE_FILE); if (trustStoreLocation != null) { trustStoreLocation = trustStoreLocation.trim(); InputStream is = loadInputStream(loader, trustStoreLocation); try { String passwd = properties.getProperty(prefix + TRUSTSTORE_PASSWORD, "changeit"); if (passwd != null) { passwd = passwd.trim(); passwd = decryptPassword(passwd, passwordEncryptor); } String type = properties.getProperty(prefix + TRUSTSTORE_TYPE, KeyStore.getDefaultType()); if (type != null) { type = type.trim(); } truststore = load(is, passwd, provider, type); if (DO_DEBUG) { LOG.debug( "The TrustStore " + trustStoreLocation + " of type " + type + " has been loaded" ); } loadCACerts = false; } finally { if (is != null) { is.close(); } } } else { String loadCacerts = properties.getProperty(prefix + LOAD_CA_CERTS, "false"); if (loadCacerts != null) { loadCacerts = loadCacerts.trim(); } if (Boolean.valueOf(loadCacerts)) { String cacertsPath = System.getProperty("java.home") + "/lib/security/cacerts"; if (cacertsPath != null) { cacertsPath = cacertsPath.trim(); } InputStream is = new FileInputStream(cacertsPath); try { String cacertsPasswd = properties.getProperty(prefix + TRUSTSTORE_PASSWORD, "changeit"); if (cacertsPasswd != null) { cacertsPasswd = cacertsPasswd.trim(); cacertsPasswd = decryptPassword(cacertsPasswd, passwordEncryptor); } truststore = load(is, cacertsPasswd, null, KeyStore.getDefaultType()); if (DO_DEBUG) { LOG.debug("CA certs have been loaded"); } loadCACerts = true; } finally { if (is != null) { is.close(); } } } } // // Load the CRL file // String crlLocations = properties.getProperty(prefix + X509_CRL_FILE); if (crlLocations != null) { crlLocations = crlLocations.trim(); String[] splittedCrlsLocation=crlLocations.split(","); List<X509CRL> crls=new ArrayList(); for (int i = 0; i < splittedCrlsLocation.length; i++) { String crlLocation = splittedCrlsLocation[i]; InputStream is = loadInputStream(loader, crlLocation); try { CertificateFactory cf = getCertificateFactory(); X509CRL crl = (X509CRL)cf.generateCRL(is); crls.add(crl); } catch (Exception e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "ioError00", e); } finally { if (is != null) { is.close(); } } } try { if (provider == null || provider.length() == 0) { crlCertStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters(crls) ); } else { crlCertStore = CertStore.getInstance( "Collection", new CollectionCertStoreParameters(crls), provider ); } } catch (Exception e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "ioError00", e); } if (DO_DEBUG) { LOG.debug( "The CRL " + crlLocations + " has been loaded" ); } } } /** * Load a KeyStore object as an InputStream, using the ClassLoader and location arguments */ public static InputStream loadInputStream(ClassLoader loader, String location) throws WSSecurityException, IOException { InputStream is = null; if (location != null) { java.net.URL url = Loader.getResource(loader, location); if (url != null) { is = url.openStream(); } // // If we don't find it, then look on the file system. // if (is == null) { try { is = new FileInputStream(location); } catch (Exception e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "proxyNotFound", e, location ); } } } return is; } /** * Loads the keystore from an <code>InputStream </code>. * <p/> * * @param input <code>InputStream</code> to read from * @throws WSSecurityException */ public KeyStore load(InputStream input, String storepass, String provider, String type) throws WSSecurityException { KeyStore ks = null; try { if (provider == null || provider.length() == 0) { ks = KeyStore.getInstance(type); } else { ks = KeyStore.getInstance(type, provider); } ks.load(input, storepass == null || storepass.length() == 0 ? new char[0] : storepass.toCharArray()); } catch (IOException e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "ioError00", e); } catch (GeneralSecurityException e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "secError00", e); } catch (Exception e) { if (DO_DEBUG) { LOG.debug(e.getMessage(), e); } throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "error00", e); } return ks; } // // Accessor methods // /** * Gets the Keystore that was loaded * * @return the Keystore */ public KeyStore getKeyStore() { return keystore; } /** * Set the Keystore on this Crypto instance * * @param keyStore the Keystore to set */ public void setKeyStore(KeyStore keyStore) { keystore = keyStore; } /** * Gets the trust store that was loaded by the underlying implementation * * @return the trust store */ public KeyStore getTrustStore() { return truststore; } /** * Set the trust store on this Crypto instance * * @param trustStore the trust store to set */ public void setTrustStore(KeyStore trustStore) { truststore = trustStore; } /** * Set the CertStore from which to obtain a list of CRLs for Certificate Revocation * checking. * @param crlCertStore the CertStore from which to obtain a list of CRLs for Certificate * Revocation checking. */ public void setCRLCertStore(CertStore crlCertStore) { this.crlCertStore = crlCertStore; } /** * Get the CertStore from which to obtain a list of CRLs for Certificate Revocation * checking. * @return the CertStore from which to obtain a list of CRLs for Certificate * Revocation checking. */ public CertStore getCRLCertStore() { return crlCertStore; } /** * Singleton certificate factory for this Crypto instance. * <p/> * * @return Returns a <code>CertificateFactory</code> to construct * X509 certificates * @throws WSSecurityException */ @Override public CertificateFactory getCertificateFactory() throws WSSecurityException { String provider = getCryptoProvider(); String keyStoreProvider = null; if (keystore != null) { keyStoreProvider = keystore.getProvider().getName(); } //Try to find a CertificateFactory that generates certs that are fully //compatible with the certs in the KeyStore (Sun -> Sun, BC -> BC, etc...) CertificateFactory factory = null; if (provider != null) { factory = certFactMap.get(provider); } else if (keyStoreProvider != null) { factory = certFactMap.get(mapKeystoreProviderToCertProvider(keyStoreProvider)); if (factory == null) { factory = certFactMap.get(keyStoreProvider); } } else { factory = certFactMap.get("DEFAULT"); } if (factory == null) { try { if (provider == null || provider.length() == 0) { if (keyStoreProvider != null && keyStoreProvider.length() != 0) { try { factory = CertificateFactory.getInstance( "X.509", mapKeystoreProviderToCertProvider(keyStoreProvider) ); certFactMap.put(keyStoreProvider, factory); certFactMap.put( mapKeystoreProviderToCertProvider(keyStoreProvider), factory ); } catch (Exception ex) { LOG.debug(ex.getMessage(), ex); //Ignore, we'll just use the default since they didn't specify one. //Hopefully that will work for them. } } if (factory == null) { factory = CertificateFactory.getInstance("X.509"); certFactMap.put("DEFAULT", factory); } } else { factory = CertificateFactory.getInstance("X.509", provider); certFactMap.put(provider, factory); } certFactMap.put(factory.getProvider().getName(), factory); } catch (CertificateException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, "unsupportedCertType", e ); } catch (NoSuchProviderException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, "noSecProvider", e ); } } return factory; } private String mapKeystoreProviderToCertProvider(String s) { if ("SunJSSE".equals(s)) { return "SUN"; } return s; } /** * Retrieves the identifier name of the default certificate. This should be the certificate * that is used for signature and encryption. This identifier corresponds to the certificate * that should be used whenever KeyInfo is not present in a signed or an encrypted * message. May return null. The identifier is implementation specific, e.g. it could be the * KeyStore alias. * * @return name of the default X509 certificate. */ @Override public String getDefaultX509Identifier() throws WSSecurityException { if (defaultAlias != null) { return defaultAlias; } if (keystore != null) { try { Enumeration<String> as = keystore.aliases(); if (as.hasMoreElements()) { String alias = as.nextElement(); if (!as.hasMoreElements()) { defaultAlias = alias; return alias; } } } catch (KeyStoreException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "keystore", ex ); } } return null; } // // Keystore-specific Crypto functionality methods // /** * Get an X509Certificate (chain) corresponding to the CryptoType argument. The supported * types are as follows: * * TYPE.ISSUER_SERIAL - A certificate (chain) is located by the issuer name and serial number * TYPE.THUMBPRINT_SHA1 - A certificate (chain) is located by the SHA1 of the (root) cert * TYPE.SKI_BYTES - A certificate (chain) is located by the SKI bytes of the (root) cert * TYPE.SUBJECT_DN - A certificate (chain) is located by the Subject DN of the (root) cert * TYPE.ALIAS - A certificate (chain) is located by an alias, which for this implementation * means an alias of the keystore or truststore. */ public X509Certificate[] getX509Certificates(CryptoType cryptoType) throws WSSecurityException { if (cryptoType == null) { return null; } CryptoType.TYPE type = cryptoType.getType(); X509Certificate[] certs = null; switch (type) { case ISSUER_SERIAL: { certs = getX509Certificates(cryptoType.getIssuer(), cryptoType.getSerial()); break; } case THUMBPRINT_SHA1: { certs = getX509Certificates(cryptoType.getBytes()); break; } case SKI_BYTES: { certs = getX509CertificatesSKI(cryptoType.getBytes()); break; } case SUBJECT_DN: { certs = getX509CertificatesSubjectDN(cryptoType.getSubjectDN()); break; } case ALIAS: { certs = getX509Certificates(cryptoType.getAlias()); break; } case ENDPOINT: { break; } } return certs; } /** * Get the implementation-specific identifier corresponding to the cert parameter. In this * case, the identifier corresponds to a KeyStore alias. * @param cert The X509Certificate for which to search for an identifier * @return the identifier corresponding to the cert parameter * @throws WSSecurityException */ public String getX509Identifier(X509Certificate cert) throws WSSecurityException { String identifier = null; if (keystore != null) { identifier = getIdentifier(cert, keystore); } if (identifier == null && truststore != null) { identifier = getIdentifier(cert, truststore); } return identifier; } /** * Gets the private key corresponding to the certificate. * * @param certificate The X509Certificate corresponding to the private key * @param callbackHandler The callbackHandler needed to get the password * @return The private key */ public PrivateKey getPrivateKey( X509Certificate certificate, CallbackHandler callbackHandler ) throws WSSecurityException { if (keystore == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", "The keystore is null"); } if (callbackHandler == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", "The CallbackHandler is null"); } String identifier = getIdentifier(certificate, keystore); try { if (identifier == null || !keystore.isKeyEntry(identifier)) { String msg = "Cannot find key for alias: [" + identifier + "]"; String logMsg = createKeyStoreErrorMessage(keystore); LOG.error(msg + logMsg); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", msg); } String password = getPassword(identifier, callbackHandler); if (password == null && privatePasswordSet) { password = properties.getProperty(PREFIX + KEYSTORE_PRIVATE_PASSWORD); if (password == null) { password = properties.getProperty(OLD_PREFIX + KEYSTORE_PRIVATE_PASSWORD); } if (password != null) { password = password.trim(); password = decryptPassword(password, passwordEncryptor); } } Key keyTmp = keystore.getKey(identifier, password == null ? new char[]{} : password.toCharArray()); if (!(keyTmp instanceof PrivateKey)) { String msg = "Key is not a private key, alias: [" + identifier + "]"; String logMsg = createKeyStoreErrorMessage(keystore); LOG.error(msg + logMsg); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", msg); } return (PrivateKey) keyTmp; } catch (KeyStoreException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPrivateKey", ex, ex.getMessage() ); } catch (UnrecoverableKeyException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPrivateKey", ex, ex.getMessage() ); } catch (NoSuchAlgorithmException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPrivateKey", ex, ex.getMessage() ); } } /** * Gets the private key corresponding to the identifier. * * @param identifier The implementation-specific identifier corresponding to the key * @param password The password needed to get the key * @return The private key */ public PrivateKey getPrivateKey( String identifier, String password ) throws WSSecurityException { if (keystore == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", "The keystore is null"); } try { if (identifier == null || !keystore.isKeyEntry(identifier)) { String msg = "Cannot find key for alias: [" + identifier + "]"; String logMsg = createKeyStoreErrorMessage(keystore); LOG.error(msg + logMsg); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", msg); } if (password == null && privatePasswordSet) { password = properties.getProperty(PREFIX + KEYSTORE_PRIVATE_PASSWORD); if (password == null) { password = properties.getProperty(OLD_PREFIX + KEYSTORE_PRIVATE_PASSWORD); } if (password != null) { password = password.trim(); } } Key keyTmp = keystore.getKey(identifier, password == null ? new char[]{} : password.toCharArray()); if (!(keyTmp instanceof PrivateKey)) { String msg = "Key is not a private key, alias: [" + identifier + "]"; String logMsg = createKeyStoreErrorMessage(keystore); LOG.error(msg + logMsg); throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty", msg); } return (PrivateKey) keyTmp; } catch (KeyStoreException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPrivateKey", ex, ex.getMessage() ); } catch (UnrecoverableKeyException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPrivateKey", ex, ex.getMessage() ); } catch (NoSuchAlgorithmException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPrivateKey", ex, ex.getMessage() ); } } /** * Evaluate whether a given certificate chain should be trusted. * * @param certs Certificate chain to validate * @param enableRevocation whether to enable CRL verification or not * @param subjectCertConstraints A set of constraints on the Subject DN of the certificates * * @throws WSSecurityException if the certificate chain is invalid */ public void verifyTrust( X509Certificate[] certs, boolean enableRevocation, Collection<Pattern> subjectCertConstraints ) throws WSSecurityException { // // FIRST step - Search the keystore for the transmitted certificate // if (certs.length == 1 && !enableRevocation) { String issuerString = certs[0].getIssuerX500Principal().getName(); BigInteger issuerSerial = certs[0].getSerialNumber(); CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL); cryptoType.setIssuerSerial(issuerString, issuerSerial); X509Certificate[] foundCerts = getX509Certificates(cryptoType); // // If a certificate has been found, the certificates must be compared // to ensure against phony DNs (compare encoded form including signature) // if (foundCerts != null && foundCerts[0] != null && foundCerts[0].equals(certs[0])) { try { certs[0].checkValidity(); } catch (CertificateExpiredException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILED_CHECK, "invalidCert", e ); } catch (CertificateNotYetValidException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILED_CHECK, "invalidCert", e ); } if (LOG.isDebugEnabled()) { LOG.debug( "Direct trust for certificate with " + certs[0].getSubjectX500Principal().getName() ); } return; } } // // SECOND step - Search for the issuer cert (chain) of the transmitted certificate in the // keystore or the truststore // X509Certificate[] x509certs = certs; String issuerString = certs[0].getIssuerX500Principal().getName(); if (certs.length == 1) { CryptoType cryptoType = new CryptoType(CryptoType.TYPE.SUBJECT_DN); cryptoType.setSubjectDN(issuerString); X509Certificate[] foundCerts = getX509Certificates(cryptoType); // If the certs have not been found, the issuer is not in the keystore/truststore // As a direct result, do not trust the transmitted certificate if (foundCerts == null || foundCerts.length < 1) { String subjectString = certs[0].getSubjectX500Principal().getName(); if (LOG.isDebugEnabled()) { LOG.debug( "No certs found in keystore for issuer " + issuerString + " of certificate for " + subjectString ); } throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", "No trusted certs found" ); } // // Form a certificate chain from the transmitted certificate // and the certificate(s) of the issuer from the keystore/truststore // x509certs = new X509Certificate[foundCerts.length + 1]; x509certs[0] = certs[0]; System.arraycopy(foundCerts, 0, x509certs, 1, foundCerts.length); } // // THIRD step // Check the certificate trust path for the issuer cert chain // if (LOG.isDebugEnabled()) { LOG.debug( "Preparing to validate certificate path for issuer " + issuerString ); } try { // Generate cert path List<X509Certificate> certList = Arrays.asList(x509certs); CertPath path = getCertificateFactory().generateCertPath(certList); Set<TrustAnchor> set = new HashSet<TrustAnchor>(); if (truststore != null) { Enumeration<String> truststoreAliases = truststore.aliases(); while (truststoreAliases.hasMoreElements()) { String alias = truststoreAliases.nextElement(); X509Certificate cert = (X509Certificate) truststore.getCertificate(alias); if (cert != null) { TrustAnchor anchor = new TrustAnchor(cert, cert.getExtensionValue(NAME_CONSTRAINTS_OID)); set.add(anchor); } } } // // Add certificates from the keystore - only if there is no TrustStore, apart from // the case that the truststore is the JDK CA certs. This behaviour is preserved // for backwards compatibility reasons // if (keystore != null && (truststore == null || loadCACerts)) { Enumeration<String> aliases = keystore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); X509Certificate cert = (X509Certificate) keystore.getCertificate(alias); if (cert != null) { TrustAnchor anchor = new TrustAnchor(cert, cert.getExtensionValue(NAME_CONSTRAINTS_OID)); set.add(anchor); } } } // Verify the trust path using the above settings String provider = getCryptoProvider(); CertPathValidator validator = null; if (provider == null || provider.length() == 0) { validator = CertPathValidator.getInstance("PKIX"); } else { validator = CertPathValidator.getInstance("PKIX", provider); } PKIXParameters param = createPKIXParameters(set, enableRevocation); validator.validate(path, param); } catch (NoSuchProviderException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", e ); } catch (NoSuchAlgorithmException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", e, e.getMessage() ); } catch (CertificateException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", e ); } catch (InvalidAlgorithmParameterException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", e ); } catch (java.security.cert.CertPathValidatorException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILED_AUTHENTICATION, "certpath", e ); } catch (KeyStoreException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", e ); } catch (NullPointerException e) { // NPE thrown by JDK 1.7 for one of the test cases throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "certpath", e ); } // Finally check Cert Constraints if (!matches(certs[0], subjectCertConstraints)) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION); } } // Separated out to allow subclasses to override it protected PKIXParameters createPKIXParameters( Set<TrustAnchor> trustAnchors, boolean enableRevocation ) throws InvalidAlgorithmParameterException { PKIXParameters param = new PKIXParameters(trustAnchors); param.setRevocationEnabled(enableRevocation); if (enableRevocation && crlCertStore != null) { param.addCertStore(crlCertStore); } return param; } /** * Evaluate whether a given public key should be trusted. * * @param publicKey The PublicKey to be evaluated * @throws WSSecurityException if the PublicKey is invalid */ public void verifyTrust(PublicKey publicKey) throws WSSecurityException { // // If the public key is null, do not trust the signature // if (publicKey == null) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION); } // // Search the keystore for the transmitted public key (direct trust). If not found // then search the truststore for the transmitted public key (direct trust) // if (!findPublicKeyInKeyStore(publicKey, keystore) && !findPublicKeyInKeyStore(publicKey, truststore)) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION); } } /** * Get an X509 Certificate (chain) according to a given serial number and issuer string. * * @param issuer The Issuer String * @param serialNumber The serial number of the certificate * @return an X509 Certificate (chain) corresponding to the found certificate(s) * @throws WSSecurityException */ private X509Certificate[] getX509Certificates( String issuer, BigInteger serialNumber ) throws WSSecurityException { // // Convert the subject DN to a java X500Principal object first. This is to ensure // interop with a DN constructed from .NET, where e.g. it uses "S" instead of "ST". // Then convert it to a BouncyCastle X509Name, which will order the attributes of // the DN in a particular way (see WSS-168). If the conversion to an X500Principal // object fails (e.g. if the DN contains "E" instead of "EMAILADDRESS"), then fall // back on a direct conversion to a BC X509Name // Object issuerName = null; try { X500Principal issuerRDN = new X500Principal(issuer); issuerName = createBCX509Name(issuerRDN.getName()); } catch (IllegalArgumentException ex) { issuerName = createBCX509Name(issuer); } Certificate[] certs = null; if (keystore != null) { certs = getCertificates(issuerName, serialNumber, keystore); } //If we can't find the issuer in the keystore then look at the truststore if ((certs == null || certs.length == 0) && truststore != null) { certs = getCertificates(issuerName, serialNumber, truststore); } if (certs == null || certs.length == 0) { return null; } X509Certificate[] x509certs = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509certs[i] = (X509Certificate) certs[i]; } return x509certs; } /** * Get an X509 Certificate (chain) of the X500Principal argument in the supplied KeyStore * @param issuerRDN either an X500Principal or a BouncyCastle X509Name instance. * @param store The KeyStore * @return an X509 Certificate (chain) * @throws WSSecurityException */ private Certificate[] getCertificates( Object issuerRDN, BigInteger serialNumber, KeyStore store ) throws WSSecurityException { try { for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); Certificate cert = null; Certificate[] certs = store.getCertificateChain(alias); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. cert = store.getCertificate(alias); if (cert == null) { continue; } certs = new Certificate[]{cert}; } else { cert = certs[0]; } if (cert instanceof X509Certificate) { X509Certificate x509cert = (X509Certificate) cert; if (x509cert.getSerialNumber().compareTo(serialNumber) == 0) { Object certName = createBCX509Name(x509cert.getIssuerX500Principal().getName()); if (certName.equals(issuerRDN)) { return certs; } } } } } catch (KeyStoreException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "keystore", e ); } return new Certificate[]{}; } /** * Get an X509 Certificate (chain) according to a given Thumbprint. * * @param thumbprint The SHA1 thumbprint info bytes * @return the X509 Certificate (chain) that was found (can be null) * @throws WSSecurityException if problems during keystore handling or wrong certificate */ private X509Certificate[] getX509Certificates(byte[] thumbprint) throws WSSecurityException { MessageDigest sha = null; try { sha = MessageDigest.getInstance("SHA1"); } catch (NoSuchAlgorithmException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "decoding.general", e ); } Certificate[] certs = null; if (keystore != null) { certs = getCertificates(thumbprint, keystore, sha); } //If we can't find the issuer in the keystore then look at the truststore if ((certs == null || certs.length == 0) && truststore != null) { certs = getCertificates(thumbprint, truststore, sha); } if (certs == null || certs.length == 0) { return null; } X509Certificate[] x509certs = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509certs[i] = (X509Certificate) certs[i]; } return x509certs; } /** * Get an X509 Certificate (chain) of the X500Principal argument in the supplied KeyStore * @param thumbprint * @param store The KeyStore * @return an X509 Certificate (chain) * @throws WSSecurityException */ private Certificate[] getCertificates( byte[] thumbprint, KeyStore store, MessageDigest sha ) throws WSSecurityException { try { for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); Certificate cert = null; Certificate[] certs = store.getCertificateChain(alias); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. cert = store.getCertificate(alias); if (cert == null) { continue; } certs = new Certificate[]{cert}; } else { cert = certs[0]; } if (cert instanceof X509Certificate) { X509Certificate x509cert = (X509Certificate) cert; try { sha.update(x509cert.getEncoded()); } catch (CertificateEncodingException ex) { throw new WSSecurityException( WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, "encodeError", ex ); } byte[] data = sha.digest(); if (Arrays.equals(data, thumbprint)) { return certs; } } } } catch (KeyStoreException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "keystore", e ); } return new Certificate[]{}; } /** * Get an X509 Certificate (chain) according to a given SubjectKeyIdentifier. * * @param skiBytes The SKI bytes * @return the X509 certificate (chain) that was found (can be null) */ private X509Certificate[] getX509CertificatesSKI(byte[] skiBytes) throws WSSecurityException { Certificate[] certs = null; if (keystore != null) { certs = getCertificates(skiBytes, keystore); } //If we can't find the issuer in the keystore then look at the truststore if ((certs == null || certs.length == 0) && truststore != null) { certs = getCertificates(skiBytes, truststore); } if (certs == null || certs.length == 0) { return null; } X509Certificate[] x509certs = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509certs[i] = (X509Certificate) certs[i]; } return x509certs; } /** * Get an X509 Certificate (chain) of the X500Principal argument in the supplied KeyStore * @param skiBytes * @param store The KeyStore * @return an X509 Certificate (chain) * @throws WSSecurityException */ private Certificate[] getCertificates( byte[] skiBytes, KeyStore store ) throws WSSecurityException { try { for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); Certificate cert = null; Certificate[] certs = store.getCertificateChain(alias); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. cert = store.getCertificate(alias); if (cert == null) { continue; } certs = new Certificate[]{cert}; } else { cert = certs[0]; } if (cert instanceof X509Certificate) { X509Certificate x509cert = (X509Certificate) cert; byte[] data = getSKIBytesFromCert(x509cert); if (data.length == skiBytes.length && Arrays.equals(data, skiBytes)) { return certs; } } } } catch (KeyStoreException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "keystore", e ); } return new Certificate[]{}; } /** * Get an X509 Certificate (chain) according to a given DN of the subject of the certificate * * @param subjectDN The DN of subject to look for * @return An X509 Certificate (chain) with the same DN as given in the parameters * @throws WSSecurityException */ private X509Certificate[] getX509CertificatesSubjectDN(String subjectDN) throws WSSecurityException { // // Convert the subject DN to a java X500Principal object first. This is to ensure // interop with a DN constructed from .NET, where e.g. it uses "S" instead of "ST". // Then convert it to a BouncyCastle X509Name, which will order the attributes of // the DN in a particular way (see WSS-168). If the conversion to an X500Principal // object fails (e.g. if the DN contains "E" instead of "EMAILADDRESS"), then fall // back on a direct conversion to a BC X509Name // Object subject; try { X500Principal subjectRDN = new X500Principal(subjectDN); subject = createBCX509Name(subjectRDN.getName()); } catch (IllegalArgumentException ex) { subject = createBCX509Name(subjectDN); } Certificate[] certs = null; if (keystore != null) { certs = getCertificates(subject, keystore); } //If we can't find the issuer in the keystore then look at the truststore if ((certs == null || certs.length == 0) && truststore != null) { certs = getCertificates(subject, truststore); } if (certs == null || certs.length == 0) { return null; } X509Certificate[] x509certs = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509certs[i] = (X509Certificate) certs[i]; } return x509certs; } /** * Get an X509 Certificate (chain) that correspond to the identifier. For this implementation, * the identifier corresponds to the KeyStore alias. * * @param identifier The identifier that corresponds to the returned certs * @return an X509 Certificate (chain) that corresponds to the identifier */ private X509Certificate[] getX509Certificates(String identifier) throws WSSecurityException { Certificate[] certs = null; try { if (keystore != null) { // There's a chance that there can only be a set of trust stores certs = keystore.getCertificateChain(identifier); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. Certificate cert = keystore.getCertificate(identifier); if (cert != null) { certs = new Certificate[]{cert}; } } } if (certs == null && truststore != null) { // Now look into the trust stores certs = truststore.getCertificateChain(identifier); if (certs == null) { Certificate cert = truststore.getCertificate(identifier); if (cert != null) { certs = new Certificate[]{cert}; } } } if (certs == null) { return null; } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "keystore", e); } X509Certificate[] x509certs = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509certs[i] = (X509Certificate) certs[i]; } return x509certs; } /** * Find the Public Key in a keystore. */ private boolean findPublicKeyInKeyStore(PublicKey publicKey, KeyStore keyStoreToSearch) { if (keyStoreToSearch == null) { return false; } try { for (Enumeration<String> e = keyStoreToSearch.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); Certificate[] certs = keyStoreToSearch.getCertificateChain(alias); Certificate cert; if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. cert = keyStoreToSearch.getCertificate(alias); if (cert == null) { continue; } } else { cert = certs[0]; } if (!(cert instanceof X509Certificate)) { continue; } X509Certificate x509cert = (X509Certificate) cert; if (publicKey.equals(x509cert.getPublicKey())) { return true; } } } catch (KeyStoreException e) { return false; } return false; } /** * Get an X509 Certificate (chain) of the X500Principal argument in the supplied KeyStore * @param subjectRDN either an X500Principal or a BouncyCastle X509Name instance. * @param store The KeyStore * @return an X509 Certificate (chain) * @throws WSSecurityException */ private Certificate[] getCertificates(Object subjectRDN, KeyStore store) throws WSSecurityException { try { for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); Certificate cert = null; Certificate[] certs = store.getCertificateChain(alias); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. cert = store.getCertificate(alias); if (cert == null) { continue; } certs = new Certificate[]{cert}; } else { cert = certs[0]; } if (cert instanceof X509Certificate) { X500Principal foundRDN = ((X509Certificate) cert).getSubjectX500Principal(); Object certName = createBCX509Name(foundRDN.getName()); if (subjectRDN.equals(certName)) { return certs; } } } } catch (KeyStoreException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "keystore", e ); } return new Certificate[]{}; } private static String createKeyStoreErrorMessage(KeyStore keystore) throws KeyStoreException { Enumeration<String> aliases = keystore.aliases(); StringBuilder sb = new StringBuilder(keystore.size() * 7); boolean firstAlias = true; while (aliases.hasMoreElements()) { if (!firstAlias) { sb.append(", "); } sb.append(aliases.nextElement()); firstAlias = false; } String msg = " in keystore of type [" + keystore.getType() + "] from provider [" + keystore.getProvider() + "] with size [" + keystore.size() + "] and aliases: {" + sb.toString() + "}"; return msg; } /** * Get an implementation-specific identifier that corresponds to the X509Certificate. In * this case, the identifier is the KeyStore alias. * @param cert The X509Certificate corresponding to the returned identifier * @param store The KeyStore to search * @return An implementation-specific identifier that corresponds to the X509Certificate */ private String getIdentifier(X509Certificate cert, KeyStore store) throws WSSecurityException { try { for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) { String alias = e.nextElement(); Certificate[] certs = store.getCertificateChain(alias); Certificate retrievedCert = null; if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a result. retrievedCert = store.getCertificate(alias); if (retrievedCert == null) { continue; } } else { retrievedCert = certs[0]; } if (!(retrievedCert instanceof X509Certificate)) { continue; } if (retrievedCert.equals(cert)) { return alias; } } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "keystore", e); } return null; } /** * Get a password from the CallbackHandler * @param identifier The identifier to give to the Callback * @param cb The CallbackHandler * @return The password retrieved from the CallbackHandler * @throws WSSecurityException */ private String getPassword( String identifier, CallbackHandler cb ) throws WSSecurityException { WSPasswordCallback pwCb = new WSPasswordCallback(identifier, WSPasswordCallback.DECRYPT); try { Callback[] callbacks = new Callback[]{pwCb}; cb.handle(callbacks); } catch (IOException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPassword", e, identifier ); } catch (UnsupportedCallbackException e) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "noPassword", e, identifier ); } return pwCb.getPassword(); } protected String decryptPassword(String password, PasswordEncryptor passwordEncryptor) { if (password.startsWith(ENCRYPTED_PASSWORD_PREFIX) && password.endsWith(ENCRYPTED_PASSWORD_SUFFIX)) { if (passwordEncryptor == null) { String error = "The Crypto properties has an encrypted password, but no PasswordEncryptor is configured!"; LOG.debug(error); return password; } String substring = password.substring(ENCRYPTED_PASSWORD_PREFIX.length(), password.length() - 1); return passwordEncryptor.decrypt(substring); } return password; } public void setPasswordEncryptor(PasswordEncryptor passwordEncryptor) { this.passwordEncryptor = passwordEncryptor; } }