Search code examples
javaweb-servicessoapx509spring-ws

Validate SOAP response xml timestamp and signature X509 spring-ws-security


I am new to spring-ws-security and have read almost all articles on google and stacktrace but could not get this working.
I have requirement to validate response XML signature, timestamp and then retrieve data. When I am skipping validation, then no problems but moment I add validation code, I receive error :

WARN : Could not validate request: The signature or decryption was invalid; nested exception is org.apache.ws.security.WSSecurityException: The signature or decryption was invalid

@Configuration
public class SoapClientConfig {

final String generatedResource = "packageName";

@Bean
public KeyStoreCallbackHandler securityCallbackHandler() {
    KeyStoreCallbackHandler callbackHandler = new KeyStoreCallbackHandler();
    callbackHandler.setPrivateKeyPassword("serverkeystorepassword");
    return callbackHandler;
}

@Bean
public Wss4jSecurityInterceptor securityInterceptor() throws Exception {
    Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();

    // set security actions
    securityInterceptor.setSecurementActions("Timestamp Signature");
    securityInterceptor.setSecurementUsername("clientkeystoreusername");
    securityInterceptor.setSecurementPassword("clientkeystorepassword");

    //sign both body and timestamp - default body will be signed
    securityInterceptor.setSecurementSignatureParts("{}{http://schemas.xmlsoap.org/soap/envelope/}Body;{}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp");

    //This will generate binarySecurityToken in header
    securityInterceptor.setSecurementSignatureKeyIdentifier("DirectReference");
    securityInterceptor.setSecurementSignatureCrypto(getRequestCryptoBean().getObject());

    //This is validation code, which is not validating response.
    securityInterceptor.setValidationActions("Timestamp Signature");
    securityInterceptor.setValidationSignatureCrypto(getResponseCryptoBean().getObject());
    securityInterceptor.setValidationCallbackHandler(securityCallbackHandler());

    return securityInterceptor;
}

@Bean
public CryptoFactoryBean getRequestCryptoBean() throws IOException, URISyntaxException {

    CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
    cryptoFactoryBean.setKeyStorePassword("clientkeystorepassword");
    cryptoFactoryBean.setKeyStoreLocation("client.jks");
    return cryptoFactoryBean;
}

@Bean
public CryptoFactoryBean getResponseCryptoBean() throws Exception {

    CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
    cryptoFactoryBean.setDefaultX509Alias("1");
    cryptoFactoryBean.setKeyStorePassword("serverkeystorepassword");
    cryptoFactoryBean.setKeyStoreLocation("server.jks");
    cryptoFactoryBean.afterPropertiesSet();
    return cryptoFactoryBean;
}

@Bean
public Jaxb2Marshaller getMarshaller() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setContextPath(generatedResource);
    return marshaller;
}

@Bean
public Card getAvailableCardsClient() throws Exception {
    Card memberCard = new Card();
    memberCard.setMarshaller(getMarshaller());
    memberCard.setUnmarshaller(getMarshaller());

    //Set timeout for soap service
    HttpComponentsMessageSender sender = new HttpComponentsMessageSender();
    sender.setConnectionTimeout(2000);
    sender.setReadTimeout(2000);
    memberCard.setMessageSender(sender);
    //end timeout

    memberCard.setDefaultUri("url");

    //add interceptor for adding and validating signature
    ClientInterceptor[] interceptors = new ClientInterceptor[]{securityInterceptor()};
    memberCard.setInterceptors(interceptors);

    return memberCard;
}

}

** server.jks contains public key of server. And also this authentication is X509 certificates. Please help me figure out, how to validate response.


Solution

  • I found my solution and posting for other who are in same boat.

    In my scenario, since I had to use two different certificates(server.jks, client.jks) for request and response validation; I could not use same interceptor for this. I ended up creating two different interceptors, one for request and one for response.

    Here is working code copy:

    @Configuration
    public class SoapClientConfig {
    
        @Bean
        public KeyStoreCallbackHandler securityCallbackHandler() throws Exception {
            KeyStoreCallbackHandler callbackHandler = new KeyStoreCallbackHandler();
            callbackHandler.setSymmetricKeyPassword("serverPassword");
            return callbackHandler;
        }
    
        @Bean
        public Wss4jSecurityInterceptor securityInterceptor() throws IOException, Exception {
    
            Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
    
            // set security actions
            securityInterceptor.setSecurementActions("Timestamp Signature");
            securityInterceptor.setSecurementUsername("clientAias");
            securityInterceptor.setSecurementPassword("clientPassword");
    
            //sign both body and timestamp - default body will be signed
            securityInterceptor.setSecurementSignatureParts("{}{http://schemas.xmlsoap.org/soap/envelope/}Body;{}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp");
    
            //This will generate binarySecurityToken in header
            securityInterceptor.setSecurementSignatureKeyIdentifier("DirectReference");
            securityInterceptor.setSecurementSignatureCrypto(getRequestCryptoBean().getObject());
    
            return securityInterceptor;
        }
    
        @Bean
        public CryptoFactoryBean getRequestCryptoBean() throws IOException {
    
            CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
            cryptoFactoryBean.setKeyStorePassword("clientPassword");
            cryptoFactoryBean.setKeyStoreLocation("clientCertLoc");
            return cryptoFactoryBean;
        }
    
        @Bean
        public Wss4jSecurityInterceptor responseSecurityInterceptor() throws IOException, Exception {
    
            Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
            securityInterceptor.setValidationActions("Timestamp Signature");
            securityInterceptor.setValidationSignatureCrypto(getResponseCryptoBean().getObject());
            securityInterceptor.setValidationCallbackHandler(securityCallbackHandler());
    
            return securityInterceptor;
        }
    
        @Bean
        public CryptoFactoryBean getResponseCryptoBean() throws Exception {
    
            CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
            cryptoFactoryBean.setKeyStoreLocation("serverCertLoc");
            cryptoFactoryBean.setDefaultX509Alias("serverAlias");
            cryptoFactoryBean.setKeyStorePassword("serverPassword");
            cryptoFactoryBean.afterPropertiesSet();
            return cryptoFactoryBean;
        }
    
        @Bean
        public Jaxb2Marshaller getMarshaller() {
            Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
            marshaller.setContextPath(generatedResource);
            return marshaller;
        }
    
        @Bean
        public WebServiceClass getPojoClassMethod() throws ConnectException, Exception {
    
            WebServiceClass pClass= new WebServiceClass();
            pClass.setMarshaller(getMarshaller());
            pClass.setUnmarshaller(getMarshaller());
    
            //Set timeout for soap service
            HttpComponentsMessageSender sender = new HttpComponentsMessageSender();
            int timeout;
            if (null == stringFromEnvironmentOrIllegalStateException(env, timeoutInMs)) {
                timeout = 10000;
            } else {
                timeout = Integer.parseInt(stringFromEnvironmentOrIllegalStateException(env, timeoutInMs));
            }
            sender.setConnectionTimeout(timeout);
            sender.setReadTimeout(timeout);
            pClass.setMessageSender(sender);
            //end timeout config
    
            pClass.setDefaultUri("actionURL");
            ClientInterceptor[] interceptors = new ClientInterceptor[]{securityInterceptor(), responseSecurityInterceptor()};
            pClass.setInterceptors(interceptors);
    
            return pClass;
        }
    
    }