/* * Copyright 2005-2014 the original author or authors. * * Licensed 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.springframework.ws.soap.security.xwss.callback; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderException; import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.PKIXBuilderParameters; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Enumeration; import javax.crypto.SecretKey; import com.sun.xml.wss.impl.callback.CertificateValidationCallback; import com.sun.xml.wss.impl.callback.DecryptionKeyCallback; import com.sun.xml.wss.impl.callback.EncryptionKeyCallback; import com.sun.xml.wss.impl.callback.SignatureKeyCallback; import com.sun.xml.wss.impl.callback.SignatureVerificationKeyCallback; import org.apache.xml.security.utils.RFC2253Parser; import org.springframework.beans.factory.InitializingBean; import org.springframework.ws.soap.security.support.KeyStoreUtils; /** * Callback handler that uses Java Security {@code KeyStore}s to handle cryptographic callbacks. Allows for * specific key stores to be set for various cryptographic operations. * * <p>This handler requires one or more key stores to be set. You can configure them in your application context by using a * {@code KeyStoreFactoryBean}. The exact stores to be set depends on the cryptographic operations that are to be * performed by this handler. The table underneath show the key store to be used for each operation: <table border="1"> * <tr> <td><strong>Cryptographic operation</strong></td> <td><strong>Key store used</strong></td> </tr> <tr> * <td>Certificate validation</td> <td>first {@code keyStore}, then {@code trustStore}</td> </tr> <tr> * <td>Decryption based on private key</td> <td>{@code keyStore}</td> </tr> <tr> <td>Decryption based on symmetric * key</td> <td>{@code symmetricStore}</td> </tr> <tr> <td>Encryption based on certificate</td> * <td>{@code trustStore}</td> </tr> <tr> <td>Encryption based on symmetric key</td> * <td>{@code symmetricStore}</td> </tr> <tr> <td>Signing</td> <td>{@code keyStore}</td> </tr> <tr> * <td>Signature verification</td> <td>{@code trustStore}</td> </tr> </table> * * <p><h3>Default key stores</h3> If the {@code symmetricStore} is not set, it will default to the * {@code keyStore}. If the key or trust store is not set, this handler will use the standard Java mechanism to * load or create it. See {@link #loadDefaultKeyStore()} and {@link #loadDefaultTrustStore()}. * * <p><h3>Examples</h3> For instance, if you want to use the {@code KeyStoreCallbackHandler} to validate incoming * certificates or signatures, you would use a trust store, like so: * <pre> * <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> * <property name="trustStore" ref="trustStore"/> * </bean> * * <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> * <property name="location" value="classpath:truststore.jks"/> * <property name="password" value="changeit"/> * </bean> * </pre> * If you want to use it to decrypt incoming certificates or sign outgoing messages, you would use a key store, like * so: * <pre> * <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> * <property name="keyStore" ref="keyStore"/> * <property name="privateKeyPassword" value="changeit"/> * </bean> * * <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> * <property name="location" value="classpath:keystore.jks"/> * <property name="password" value="changeit"/> * </bean> * </pre> * * <h3>Handled callbacks</h3> This class handles {@code CertificateValidationCallback}s, * {@code DecryptionKeyCallback}s, {@code EncryptionKeyCallback}s, {@code SignatureKeyCallback}s, and * {@code SignatureVerificationKeyCallback}s. It throws an {@code UnsupportedCallbackException} for others. * * @author Arjen Poutsma * @see KeyStore * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean * @see CertificateValidationCallback * @see DecryptionKeyCallback * @see EncryptionKeyCallback * @see SignatureKeyCallback * @see SignatureVerificationKeyCallback * @see <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager">The * standard Java trust store mechanism</a> * @since 1.0.0 */ public class KeyStoreCallbackHandler extends CryptographyCallbackHandler implements InitializingBean { private static final String X_509_CERTIFICATE_TYPE = "X.509"; private static final String SUBJECT_KEY_IDENTIFIER_OID = "2.5.29.14"; private KeyStore keyStore; private KeyStore symmetricStore; private KeyStore trustStore; private String defaultAlias; private char[] privateKeyPassword; private char[] symmetricKeyPassword; private boolean revocationEnabled = false; private static X509Certificate getCertificate(String alias, KeyStore store) throws IOException { try { return (X509Certificate) store.getCertificate(alias); } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } } private static X509Certificate getCertificate(PublicKey pk, KeyStore store) throws IOException { try { Enumeration<String> aliases = store.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); Certificate cert = store.getCertificate(alias); if (cert == null || !X_509_CERTIFICATE_TYPE.equals(cert.getType())) { continue; } X509Certificate x509Cert = (X509Certificate) cert; if (x509Cert.getPublicKey().equals(pk)) { return x509Cert; } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } /** Sets the key store alias for the default certificate and private key. */ public void setDefaultAlias(String defaultAlias) { this.defaultAlias = defaultAlias; } /** * Sets the default key store. This property is required for decription based on private keys, and signing. If this * property is not set, a default key store is loaded. * * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean * @see #loadDefaultTrustStore() */ public void setKeyStore(KeyStore keyStore) { this.keyStore = keyStore; } /** * Sets the password used to retrieve private keys from the keystore. This property is required for decription based * on private keys, and signing. */ public void setPrivateKeyPassword(String privateKeyPassword) { if (privateKeyPassword != null) { this.privateKeyPassword = privateKeyPassword.toCharArray(); } } /** * Sets the password used to retrieve keys from the symmetric keystore. If this property is not set, it default to * the private key password. * * @see #setPrivateKeyPassword(String) */ public void setSymmetricKeyPassword(String symmetricKeyPassword) { if (symmetricKeyPassword != null) { this.symmetricKeyPassword = symmetricKeyPassword.toCharArray(); } } /** * Sets the key store used for encryption and decryption using symmetric keys. If this property is not set, it * defaults to the {@code keyStore} property. * * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean * @see #setKeyStore(java.security.KeyStore) */ public void setSymmetricStore(KeyStore symmetricStore) { this.symmetricStore = symmetricStore; } /** * Sets the key store used for signature verifications and encryptions. If this property is not set, a default key * store will be loaded. * * @see org.springframework.ws.soap.security.support.KeyStoreFactoryBean * @see #loadDefaultTrustStore() */ public void setTrustStore(KeyStore trustStore) { this.trustStore = trustStore; } /** * Determines if certificate revocation checking is enabled or not. Default is * {@code false}. */ public void setRevocationEnabled(boolean revocationEnabled) { this.revocationEnabled = revocationEnabled; } @Override public void afterPropertiesSet() throws Exception { if (keyStore == null) { loadDefaultKeyStore(); } if (trustStore == null) { loadDefaultTrustStore(); } if (symmetricStore == null) { symmetricStore = keyStore; } if (symmetricKeyPassword == null) { symmetricKeyPassword = privateKeyPassword; } } @Override protected final void handleAliasPrivKeyCertRequest(SignatureKeyCallback callback, SignatureKeyCallback.AliasPrivKeyCertRequest request) throws IOException { PrivateKey privateKey = getPrivateKey(request.getAlias()); X509Certificate certificate = getCertificate(request.getAlias()); request.setPrivateKey(privateKey); request.setX509Certificate(certificate); } @Override protected final void handleAliasSymmetricKeyRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.AliasSymmetricKeyRequest request) throws IOException { SecretKey secretKey = getSymmetricKey(request.getAlias()); request.setSymmetricKey(secretKey); } // // Encryption // @Override protected final void handleAliasSymmetricKeyRequest(EncryptionKeyCallback callback, EncryptionKeyCallback.AliasSymmetricKeyRequest request) throws IOException { SecretKey secretKey = getSymmetricKey(request.getAlias()); request.setSymmetricKey(secretKey); } @Override protected final void handleAliasX509CertificateRequest(EncryptionKeyCallback callback, EncryptionKeyCallback.AliasX509CertificateRequest request) throws IOException { X509Certificate certificate = getCertificateFromTrustStore(request.getAlias()); request.setX509Certificate(certificate); } // // Certificate validation // @Override protected final void handleCertificateValidationCallback(CertificateValidationCallback callback) { callback.setValidator(new KeyStoreCertificateValidator()); } // // Signing // @Override protected final void handleDefaultPrivKeyCertRequest(SignatureKeyCallback callback, SignatureKeyCallback.DefaultPrivKeyCertRequest request) throws IOException { PrivateKey privateKey = getPrivateKey(defaultAlias); X509Certificate certificate = getCertificate(defaultAlias); request.setPrivateKey(privateKey); request.setX509Certificate(certificate); } @Override protected final void handleDefaultX509CertificateRequest(EncryptionKeyCallback callback, EncryptionKeyCallback.DefaultX509CertificateRequest request) throws IOException { X509Certificate certificate = getCertificateFromTrustStore(defaultAlias); request.setX509Certificate(certificate); } @Override protected final void handlePublicKeyBasedPrivKeyCertRequest(SignatureKeyCallback callback, SignatureKeyCallback.PublicKeyBasedPrivKeyCertRequest request) throws IOException { PrivateKey privateKey = getPrivateKey(request.getPublicKey()); X509Certificate certificate = getCertificate(request.getPublicKey()); request.setPrivateKey(privateKey); request.setX509Certificate(certificate); } // // Decryption // @Override protected final void handlePublicKeyBasedPrivKeyRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.PublicKeyBasedPrivKeyRequest request) throws IOException { PrivateKey key = getPrivateKey(request.getPublicKey()); request.setPrivateKey(key); } @Override protected final void handlePublicKeyBasedRequest(EncryptionKeyCallback callback, EncryptionKeyCallback.PublicKeyBasedRequest request) throws IOException { X509Certificate certificate = getCertificateFromTrustStore(request.getPublicKey()); request.setX509Certificate(certificate); } @Override protected final void handlePublicKeyBasedRequest(SignatureVerificationKeyCallback callback, SignatureVerificationKeyCallback.PublicKeyBasedRequest request) throws IOException { X509Certificate certificate = getCertificateFromTrustStore(request.getPublicKey()); request.setX509Certificate(certificate); } @Override protected final void handleX509CertificateBasedRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.X509CertificateBasedRequest request) throws IOException { PrivateKey privKey = getPrivateKey(request.getX509Certificate()); request.setPrivateKey(privKey); } @Override protected final void handleX509IssuerSerialBasedRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.X509IssuerSerialBasedRequest request) throws IOException { PrivateKey key = getPrivateKey(request.getIssuerName(), request.getSerialNumber()); request.setPrivateKey(key); } @Override protected final void handleX509IssuerSerialBasedRequest(SignatureVerificationKeyCallback callback, SignatureVerificationKeyCallback.X509IssuerSerialBasedRequest request) throws IOException { X509Certificate certificate = getCertificateFromTrustStore(request.getIssuerName(), request.getSerialNumber()); request.setX509Certificate(certificate); } @Override protected final void handleX509SubjectKeyIdentifierBasedRequest(DecryptionKeyCallback callback, DecryptionKeyCallback.X509SubjectKeyIdentifierBasedRequest request) throws IOException { PrivateKey key = getPrivateKey(request.getSubjectKeyIdentifier()); request.setPrivateKey(key); } // // Signature verification // @Override protected final void handleX509SubjectKeyIdentifierBasedRequest(SignatureVerificationKeyCallback callback, SignatureVerificationKeyCallback.X509SubjectKeyIdentifierBasedRequest request) throws IOException { X509Certificate certificate = getCertificateFromTrustStore(request.getSubjectKeyIdentifier()); request.setX509Certificate(certificate); } // Certificate methods protected X509Certificate getCertificate(String alias) throws IOException { return getCertificate(alias, keyStore); } protected X509Certificate getCertificate(PublicKey pk) throws IOException { return getCertificate(pk, keyStore); } protected X509Certificate getCertificateFromTrustStore(String alias) throws IOException { return getCertificate(alias, trustStore); } protected X509Certificate getCertificateFromTrustStore(byte[] subjectKeyIdentifier) throws IOException { try { Enumeration<String> aliases = trustStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); Certificate cert = trustStore.getCertificate(alias); if (cert == null || !X_509_CERTIFICATE_TYPE.equals(cert.getType())) { continue; } X509Certificate x509Cert = (X509Certificate) cert; byte[] keyId = getSubjectKeyIdentifier(x509Cert); if (keyId == null) { // Cert does not contain a key identifier continue; } if (Arrays.equals(subjectKeyIdentifier, keyId)) { return x509Cert; } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } protected X509Certificate getCertificateFromTrustStore(PublicKey pk) throws IOException { return getCertificate(pk, trustStore); } protected X509Certificate getCertificateFromTrustStore(String issuerName, BigInteger serialNumber) throws IOException { try { Enumeration<String> aliases = trustStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); Certificate cert = trustStore.getCertificate(alias); if (cert == null || !X_509_CERTIFICATE_TYPE.equals(cert.getType())) { continue; } X509Certificate x509Cert = (X509Certificate) cert; String thisIssuerName = RFC2253Parser.normalize(x509Cert.getIssuerDN().getName()); BigInteger thisSerialNumber = x509Cert.getSerialNumber(); if (thisIssuerName.equals(issuerName) && thisSerialNumber.equals(serialNumber)) { return x509Cert; } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } // Private Key methods protected PrivateKey getPrivateKey(String alias) throws IOException { try { return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } } protected PrivateKey getPrivateKey(PublicKey publicKey) throws IOException { try { Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (keyStore.isKeyEntry(alias)) { // Just returning the first one here return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } protected PrivateKey getPrivateKey(X509Certificate certificate) throws IOException { try { Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (!keyStore.isKeyEntry(alias)) { continue; } Certificate cert = keyStore.getCertificate(alias); if (cert != null && cert.equals(certificate)) { return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } protected PrivateKey getPrivateKey(byte[] keyIdentifier) throws IOException { try { Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (!keyStore.isKeyEntry(alias)) { continue; } Certificate cert = keyStore.getCertificate(alias); if (cert == null || !"X.509".equals(cert.getType())) { continue; } X509Certificate x509Cert = (X509Certificate) cert; byte[] keyId = getSubjectKeyIdentifier(x509Cert); if (keyId == null) { // Cert does not contain a key identifier continue; } if (Arrays.equals(keyIdentifier, keyId)) { return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } protected PrivateKey getPrivateKey(String issuerName, BigInteger serialNumber) throws IOException { try { Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (!keyStore.isKeyEntry(alias)) { continue; } Certificate cert = keyStore.getCertificate(alias); if (cert == null || !"X.509".equals(cert.getType())) { continue; } X509Certificate x509Cert = (X509Certificate) cert; String thisIssuerName = RFC2253Parser.normalize(x509Cert.getIssuerDN().getName()); BigInteger thisSerialNumber = x509Cert.getSerialNumber(); if (thisIssuerName.equals(issuerName) && thisSerialNumber.equals(serialNumber)) { return (PrivateKey) keyStore.getKey(alias, privateKeyPassword); } } } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } return null; } // Utility methods protected final byte[] getSubjectKeyIdentifier(X509Certificate cert) { byte[] subjectKeyIdentifier = cert.getExtensionValue(SUBJECT_KEY_IDENTIFIER_OID); if (subjectKeyIdentifier == null) { return null; } byte[] dest = new byte[subjectKeyIdentifier.length - 4]; System.arraycopy(subjectKeyIdentifier, 4, dest, 0, subjectKeyIdentifier.length - 4); return dest; } // // Symmetric key methods // protected SecretKey getSymmetricKey(String alias) throws IOException { try { return (SecretKey) symmetricStore.getKey(alias, symmetricKeyPassword); } catch (GeneralSecurityException e) { throw new IOException(e.getMessage()); } } /** Loads the key store indicated by system properties. Delegates to {@link KeyStoreUtils#loadDefaultKeyStore()}. */ protected void loadDefaultKeyStore() { try { keyStore = KeyStoreUtils.loadDefaultKeyStore(); if (logger.isDebugEnabled()) { logger.debug("Loaded default key store"); } } catch (Exception ex) { logger.warn("Could not open default key store", ex); } } /** Loads a default trust store. Delegates to {@link KeyStoreUtils#loadDefaultTrustStore()}. */ protected void loadDefaultTrustStore() { try { trustStore = KeyStoreUtils.loadDefaultTrustStore(); if (logger.isDebugEnabled()) { logger.debug("Loaded default trust store"); } } catch (Exception ex) { logger.warn("Could not open default trust store", ex); } } /** * Creates a {@code PKIXBuilderParameters} instance with the given parameters. * Default implementation simply instantiates one, without setting additional * parameters. * * @param trustStore the trust store to use * @param certSelector the certificate selector to use * @return the builder parameters * @throws GeneralSecurityException in case of errors * @see #setRevocationEnabled(boolean) */ protected PKIXBuilderParameters createBuilderParameters(KeyStore trustStore, X509CertSelector certSelector) throws GeneralSecurityException { return new PKIXBuilderParameters(trustStore, certSelector); } // // Inner classes // private class KeyStoreCertificateValidator implements CertificateValidationCallback.CertificateValidator { @Override public boolean validate(X509Certificate certificate) throws CertificateValidationCallback.CertificateValidationException { if (isOwnedCert(certificate)) { if (logger.isDebugEnabled()) { logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] is in private keystore"); } return true; } else if (trustStore == null) { return false; } try { certificate.checkValidity(); } catch (CertificateExpiredException e) { if (logger.isDebugEnabled()) { logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] has expired"); } return false; } catch (CertificateNotYetValidException e) { if (logger.isDebugEnabled()) { logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] is not yet valid"); } return false; } X509CertSelector certSelector = new X509CertSelector(); certSelector.setCertificate(certificate); PKIXBuilderParameters parameters; CertPathBuilder builder; try { parameters = createBuilderParameters(trustStore, certSelector); parameters.setRevocationEnabled(revocationEnabled); builder = CertPathBuilder.getInstance("PKIX"); } catch (GeneralSecurityException ex) { throw new CertificateValidationCallback.CertificateValidationException( "Could not create PKIX CertPathBuilder", ex); } try { builder.build(parameters); } catch (CertPathBuilderException e) { if (logger.isDebugEnabled()) { logger.debug("Certification path of certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] could not be validated"); } return false; } catch (InvalidAlgorithmParameterException e) { if (logger.isDebugEnabled()) { logger.debug("Algorithm of certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] could not be validated"); } return false; } if (logger.isDebugEnabled()) { logger.debug("Certificate with DN [" + certificate.getSubjectX500Principal().getName() + "] validated"); } return true; } private boolean isOwnedCert(X509Certificate cert) throws CertificateValidationCallback.CertificateValidationException { if (keyStore == null) { return false; } try { Enumeration<String> aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (keyStore.isKeyEntry(alias)) { X509Certificate x509Cert = (X509Certificate) keyStore.getCertificate(alias); if (x509Cert != null) { if (x509Cert.equals(cert)) { return true; } } } } return false; } catch (GeneralSecurityException e) { throw new CertificateValidationCallback.CertificateValidationException( "Could not determine whether certificate is contained in main key store", e); } } } }