Search code examples
javasoapwsdlcxfws-security

How to convert a pfx file into jks and then use it to sign an outgoing soap request by using the classes generated from a wsdl


I am looking for a code example which shows how to access a secure web service over SSL using a PFX certificate. I have the certificate and its password and I started by creating a KeyStore instance using the command mentioned below.

keytool -importkeystore -destkeystore "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\dvs.keystore" -srckeystore "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\key.pfx" -srcstoretype pkcs12 -deststoretype JKS -srcstorepass *******

I then used wsimport -keep -verbose -extension https://sandpit.dvshub.com.au:19443/Bus/VerificationServiceBus.svc?wsdl command to generate Java files.

After which I created a main class in which I specified several parameters such as the location of these certificates.

        System.setProperty("javax.net.ssl.trustStore", trustStoreFile);
        System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);

        System.setProperty("javax.net.ssl.keyStore", certificateFile);
        System.setProperty("javax.net.ssl.keyStorePassword", certificatePassword);
        System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");

        System.setProperty("javax.net.ssl.keyStore", "C:\\Users\\Administrator\\Desktop\\dvs\\key.pfx");
        System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");
        System.setProperty("javax.net.ssl.keyStorePassword", certificatePassword);

Then I eventually called the web method that was created by wsimport using the service it generated.

CreatedService service = ServiceFactory/Port/Creator.getCreatedService(); // Where 'CreatedService' and 'ServiceFactory/Port/Creator' were created by wsimport: this code entirely depends on the WSDL provided.
service.[ws method](...);

I then created a handler to keep track of what is being passed inside the header but I cannot see any signature being added to it at all. Am I missing something over here. I am only getting request timed out errors.

I have a working example of it in Soap UI so I know that the service is running properly.

Any help with this would be very much appreciated. Please point me in the right direction as I am ready to try anything at this point.

Thanks in advance.

[Edit] Is WSO2 Application server the way to go : Reference

This is where I picked up my current approach Reference


Solution

  • So what I was looking for was a way to sign my soap request, I am going to go into details as to how I managed to generate Java classes using the wsdl that was provided to me, how I managed to generate a Java keystore from the pfx file that was provided to me and then managed to sign the soap request going out using it.

    WSDL to Java classes:

    So I copied the content of of the wsdl that was hiding behind an ssl certificate to a file and then used the plugin defined in the pom below to generate Java classes. I then moved these classes from the target folder to the src directory.

    pom.xml

     <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.javacodegeeks.examples.jaxws.client</groupId>
        <artifactId>JavaWsClient</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.6</source>
                        <target>1.6</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.cxf</groupId>
                    <artifactId>cxf-codegen-plugin</artifactId>
                    <version>3.1.12</version>
                    <executions>
                        <execution>
                            <id>generate-sources</id>
                            <phase>generate-sources</phase>
                            <configuration>
                                <sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
                                <wsdlOptions>
                                    <wsdlOption>
                                        <wsdl>${basedir}/src/main/resources/wsdl.xml</wsdl>
                                    </wsdlOption>
                                </wsdlOptions>
                            </configuration>
                            <goals>
                                <goal>wsdl2java</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-rt-frontend-jaxws</artifactId>
                <version>3.1.12</version>
            </dependency>
            <dependency>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-rt-transports-http</artifactId>
                <version>3.1.12</version>
            </dependency>
            <dependency>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-rt-ws-security</artifactId>
                <version>3.1.12</version>
            </dependency>
    
            <!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-rt-transports-http-jetty -->
            <dependency>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-rt-transports-http-jetty</artifactId>
                <version>3.1.12</version>
            </dependency>
    <!--
    
            <dependency>
                <groupId>org.springframework.ws</groupId>
                <artifactId>spring-ws-security</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.santuario</groupId>
                        <artifactId>xmlsec</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>-->
            <dependency>
                <groupId>org.apache.santuario</groupId>
                <artifactId>xmlsec</artifactId>
                <version>2.0.8</version>
            </dependency>
    
    
        </dependencies>
    </project>
    

    Using mvn generate-resources would create Java classes in the target folder for you.

    Next step was to convert the pfx file I was provided with into a JavaKeyStore 'JKS'. Steps for which are mentioned down below. You will have to download weblogic to get the jars necessary for the conversion.

    1.Run the following OpenSSL command to extract your certificates and key from the .pfx file: openssl pkcs12 -in yourfilename.pfx -out tempcertfile.crt -nodes You should now have a file called tempcertfile.crt. Open this file with a text editor (such as WordPad). You will see the private key listed first, followed by your certificate information.

    -----BEGIN RSA PRIVATE KEY-----
    (Block of Encrypted Text)
    -----END RSA PRIVATE KEY-----
    

    2.Cut and paste all of the private key, including the BEGIN and END tags to a new text file and save it as your_domain_name.key

    3.The certificates remaining in your tempcertfile.crt will be in the following order: Server Certificate, Root Certificate, and Intermediate Certificate. However, depending on your .pfx export there could be 2–4 certificates inside the file. As long as you exported the certificates correctly, whatever you have in this file are the certificates that you are supposed to have.

    4.Make sure the private key was removed (not just copied and pasted), then save the file as your_domain_name.pem.

    5Create a identity certificate keystore by running the following two lines as one command in keytool:

    java utils.ImportPrivateKey -keystore new_identity_keystore.jks -storepass 
    YOURPASSWORD -storetype JKS -keypass YOURPASSWORD -alias 
    server -certfile tempcertfile.crt -keyfile your_domain_name.key 
    -keyfilepass PFXPASSWORD
    

    Remember to replace YOURPASSWORD with your password. Also replace PFXPASSWORD with the password that you created when you created your .pfx file.

    Reference

    Here are the commands that I executed based on the reference.

    openssl pkcs12 -in "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\file.pfx" -out "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\tempcertfile.crt" -nodes
    
    openssl x509 -outform der -in "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\file.pem" -out "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\file.der"
    
    
    java -cp C:\Oracle\Middleware\Oracle_Home\wlserver\server\lib\weblogic.jar utils.ImportPrivateKey -keystore "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\file.jks" -storepass mypass-storetype JKS -keypass mypass-alias myalias -certfile "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\file.pem" -keyfile "C:\Program Files\Java\jdk1.8.0_131\jre\lib\security\file.key" -keyfilepass mypass
    

    Next step was to use the jks and sign my outgoing request using cfx. Here is the Java class along with the configuration file for it.

    /*
     * To change this license header, choose License Headers in Project Properties.
     * To change this template file, choose Tools | Templates
     * and open the template in the editor.
     */
    package dvstest;
    
    
    import dvs.common._2014._06.contract.data.Gender;
    import dvs.common._2014._06.contract.data.RegistrationState;
    import dvs.common._2014._06.contract.data.manager.*;
    import dvs.common._2014._06.contract.service.manager.IVerification;
    import org.apache.cxf.endpoint.Client;
    import org.apache.cxf.endpoint.Endpoint;
    import org.apache.cxf.frontend.ClientProxy;
    import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
    import org.apache.cxf.transport.http.HTTPConduit;
    import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
    import org.apache.cxf.ws.addressing.WSAddressingFeature;
    import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
    import org.apache.wss4j.dom.WSConstants;
    import org.apache.wss4j.dom.handler.WSHandlerConstants;
    
    import javax.xml.bind.JAXBElement;
    import javax.xml.datatype.DatatypeFactory;
    import javax.xml.datatype.XMLGregorianCalendar;
    import java.util.Date;
    import java.util.GregorianCalendar;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    /**
     * @author Sadiq
     */
    public class DVSTest {
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            try {
    
                // These params are used to print the soap request going in and out.
                System.setProperty("com.sun.xml.ws.transport.http.client.HttpTransportPipe.dump", "true");
                System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
                System.setProperty("com.sun.xml.ws.transport.http.HttpAdapter.dump", "true");
                System.setProperty("com.sun.xml.internal.ws.transport.http.HttpAdapter.dump", "true");
    
                //Path to java keystore which holds the ssl certificate, might come in handy later on.
                /*String trustStoreFile = "C:\\Program Files\\Java\\jdk1.8.0_131\\jre\\lib\\security\\cacerts";
                String trustStorePassword = "changeit";
    
    
                System.setProperty("javax.net.ssl.trustStore", trustStoreFile);
                System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
                System.setProperty("javax.net.ssl.trustStoreType", "JKS");
                System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");*/
    
    
                /*
                This is how we can extra namespaces if needed.
    
                Map<String, String> nsMap = new HashMap();
    
                nsMap.put("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                nsMap.put("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
                nsMap.put("man", "http://DVS/Common/2014/06/Contract/Service/Manager");
                nsMap.put("man1", "http://DVS/Common/2014/06/Contract/Data/Manager");
                nsMap.put("ds", "http://www.w3.org/2000/09/xmldsig#");
                nsMap.put("ec", "http://www.w3.org/2001/10/xml-exc-c14n#");
    
                client.getRequestContext().put("soap.env.ns.map", nsMap);
                */
    
    
                //Creating a factory and setting the service interface using which we can make soap requests.
                JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
                factory.setServiceClass(IVerification.class);
    
                //Path to endpoint
                //You can get this path by looking inside the wsdl
                factory.setAddress("https://urlhere/Https");
    
                //Pointing the post request to be soap12 compliant
                factory.setBindingId("http://schemas.xmlsoap.org/wsdl/soap12/");
    
                //Adding address feature to the outgoing request, this will add <To><MessageId><ReplyTo> part to soap request.
                factory.getFeatures().add(new WSAddressingFeature());
    
                //Creating a port for the verification interface using the factory.
                IVerification port = (IVerification) factory.create();
    
                //Creating client, this will be used to specify various outgoing props.
                Client client = ClientProxy.getClient(port);
    
                //Setting content type and creating a conduit.
                HTTPConduit http = (HTTPConduit) client.getConduit();
                HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
                httpClientPolicy.setContentType("application/soap+xml");
                http.setClient(httpClientPolicy);
    
                //Endpoint fetched using client
                Endpoint cxfEndpoint = client.getEndpoint();
    
                //Setting cfx related props
                Map<String, Object> outProps = new HashMap<String, Object>();
                outProps.put(WSHandlerConstants.ACTION, "Signature Timestamp");
                outProps.put(WSHandlerConstants.USER, "myalias");
                outProps.put(WSHandlerConstants.SIG_PROP_FILE, "client_sign.properties");
                //Used to add the digest part to the soap post request
                outProps.put(WSHandlerConstants.SIG_KEY_ID, "DirectReference");
                //Used to sign the <To> element.
                outProps.put(WSHandlerConstants.SIGNATURE_PARTS, "{Element}{http://www.w3.org/2005/08/addressing}To");
                // Password type : plain text
                outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
                // for hashed password use:
                //properties.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
                // Callback used to retrieve password for given user.
                outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS,
                        ClientPasswordCallback.class.getName());
    
                //Setting props to post request.
                WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
                cxfEndpoint.getOutInterceptors().add(wssOut);
    
    
                System.out.println(passportRequest(port).getVerificationResultCode());
    
                System.out.println(driverLicenseRequest(port).getVerificationResultCode());
    
    
    
            } catch (Exception ex) {
                Logger.getLogger(DVSTest.class.getName()).log(Level.SEVERE, null, ex);
            }
    
        }
    
        /**
         * Sets properties to PassportRequest and makes a soap request using the IVerification object.
         *
         * @param port Needs a IVerification object created by the factory.
         * @return VerificationResponse as a response of soap request.
         * @throws Exception
         */
        public static VerificationResponse passportRequest(IVerification port) throws Exception {
    
            //Creating a passport request
            PassportRequest request = new PassportRequest();
    
            //Creating a DVSDate object and the creating a jaxb element to be assigned to the PassportRequest object.
            DVSDate date = new DVSDate();
            date.setDay(1);
            date.setMonth(1);
            date.setYear(2017);
            ObjectFactory objectFactory = new ObjectFactory();
            JAXBElement<DVSDate> documentRequest = objectFactory.createDVSDate(date);
            request.setBirthDate(documentRequest);
    
            request.setDocumentTypeCode(DocumentType.PP);
            JAXBElement<String> familyName = objectFactory.createCertificateRequestFamilyName2("D");
            request.setFamilyName(familyName);
            JAXBElement<String> givenName = objectFactory.createCertificateRequestGivenName2("T");
            request.setGivenName(givenName);
            request.setOriginatingAgencyCode("1");
            GregorianCalendar c = new GregorianCalendar();
            c.setTime(new Date(System.currentTimeMillis()));
            XMLGregorianCalendar requestDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(c);
            request.setRequestDateTime(requestDate);
            request.setVerificationRequestNumber("1");
            request.setVersionNumber("1");
            JAXBElement<Gender> gender = objectFactory.createPassportRequestGender(Gender.M);
            request.setGender(gender);
            request.setTravelDocumentNumber("1");
    
            return port.verifyDocument(request);
        }
    
        /**
         * Sets properties to DriverLicenseRequest and makes a soap request using the IVerification object.
         *
         * @param port Needs a IVerification object created by the factory.
         * @return VerificationResponse as a response of soap request.
         * @throws Exception
         */
        public static VerificationResponse driverLicenseRequest(IVerification port) throws Exception {
    
            //Creating a passport request
            DriverLicenceRequest request = new DriverLicenceRequest();
    
            //Creating a DVSDate object and the creating a jaxb element to be assigned to the PassportRequest object.
            DVSDate date = new DVSDate();
            date.setDay(1);
            date.setMonth(1);
            date.setYear(2017);
            ObjectFactory objectFactory = new ObjectFactory();
            JAXBElement<DVSDate> documentRequest = objectFactory.createDVSDate(date);
            request.setBirthDate(documentRequest);
    
            request.setDocumentTypeCode(DocumentType.DL);
            JAXBElement<String> familyName = objectFactory.createCertificateRequestFamilyName2("D");
            request.setFamilyName(familyName);
            JAXBElement<String> givenName = objectFactory.createCertificateRequestGivenName2("T");
            request.setGivenName(givenName);
            request.setOriginatingAgencyCode("1");
            GregorianCalendar c = new GregorianCalendar();
            c.setTime(new Date(System.currentTimeMillis()));
            XMLGregorianCalendar requestDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(c);
            request.setRequestDateTime(requestDate);
            request.setVerificationRequestNumber("1");
            request.setVersionNumber("1");
            request.setLicenceNumber("1");
            JAXBElement<String> middleName = objectFactory.createDriverLicenceRequestMiddleName("Joseph");
            request.setMiddleName(middleName);
    
            dvs.common._2014._06.contract.data.ObjectFactory objectFactoryData = new dvs.common._2014._06.contract.data.ObjectFactory();
            JAXBElement<RegistrationState> registrationState = objectFactoryData.createRegistrationState(RegistrationState.NSW);
            request.setStateOfIssue(registrationState.getValue());
            JAXBElement<Gender> gender = objectFactory.createPassportRequestGender(Gender.M);
    
    
    
    
            return port.verifyDocument(request);
        }
    
    
    }
    

    client_sign.properties file:

    org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
    org.apache.ws.security.crypto.merlin.keystore.type=jks
    org.apache.ws.security.crypto.merlin.keystore.password=mypass
    org.apache.ws.security.crypto.merlin.keystore.alias=myalias
    org.apache.ws.security.crypto.merlin.keystore.file=C:\\Program Files\\Java\\jdk1.8.0_131\\jre\\lib\\security\\file.jks
    

    Last but not the least the password callback handler.

    package dvstest;
    
    import java.io.IOException;
    import javax.security.auth.callback.Callback;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.callback.UnsupportedCallbackException;
    import org.apache.wss4j.common.ext.WSPasswordCallback;
    
    public class ClientPasswordCallback implements CallbackHandler {
    
        @Override
        public void handle(Callback[] callbacks) throws IOException,
                UnsupportedCallbackException {
    
            WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
    
            // set the password for our message.
            pc.setPassword("mypass");
        }
    
    }
    

    I hope this helps somebody. Took me a while to gather all the info that was required.

    Reference2 Reference3