Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.5k views
in Technique[技术] by (71.8m points)

web services - Manually sign SOAP message Java

I have to sign a Soap message using java, but I can't use WSS4J, SAAJ, AXIS2 or CXF for classpath problems on my websphere server.

I have to sign the body message of the SOAP call.

I generate a XML with body inside body data correctly for the soap message and look like this:

<SERVICE_DISPATCHER_REQUEST xmlns="http://inti.notariado.org/XML" xmlns:ns2="http://ancert.notariado.org/XML/CSN"><ns2:SERVICIO_CONSIGNACION_NOTARIAL><ns2:REQUEST><ns2:CABECERA><ns2:ID_COMUNICACION>2147483647</ns2:ID_COMUNICACION><ns2:FECHA>2016-03-22</ns2:FECHA><ns2:HORA>10:55:00</ns2:HORA><ns2:TIPO_OPERACION>TRANSFERENCIA</ns2:TIPO_OPERACION></ns2:CABECERA><ns2:MENSAJE><ns2:TRANSFERENCIA><ns2:CODIGO_SERVICIO>DEUDAS</ns2:CODIGO_SERVICIO><ns2:REFERENCIA>999988887777666655</ns2:REFERENCIA><ns2:FECHA_EMISION>2016-03-24</ns2:FECHA_EMISION><ns2:FECHA_VALOR>2016-03-25</ns2:FECHA_VALOR><ns2:NOTARIO><ns2:CODIGO_ULTIMAS_VOLUNTADES>9900005</ns2:CODIGO_ULTIMAS_VOLUNTADES><ns2:TIP_DOC_IDENTIFICACION>1</ns2:TIP_DOC_IDENTIFICACION><ns2:NUM_DOC_IDENTIFICACION>11711111H</ns2:NUM_DOC_IDENTIFICACION></ns2:NOTARIO><ns2:IMPORTE>1.00</ns2:IMPORTE><ns2:MONEDA>EUR</ns2:MONEDA><ns2:ORDENANTE>JUAN PEREZ SANCHEZ</ns2:ORDENANTE><ns2:CONCEPTO>Pago deuda inmueble Santurze</ns2:CONCEPTO></ns2:TRANSFERENCIA><ns2:TRANSFERENCIA><ns2:CODIGO_SERVICIO>DEUDAS</ns2:CODIGO_SERVICIO><ns2:REFERENCIA>999988887777666655</ns2:REFERENCIA><ns2:FECHA_EMISION>2016-03-24</ns2:FECHA_EMISION><ns2:FECHA_VALOR>2016-03-25</ns2:FECHA_VALOR><ns2:NOTARIO><ns2:CODIGO_ULTIMAS_VOLUNTADES>9900005</ns2:CODIGO_ULTIMAS_VOLUNTADES><ns2:TIP_DOC_IDENTIFICACION>1</ns2:TIP_DOC_IDENTIFICACION><ns2:NUM_DOC_IDENTIFICACION>11711111H</ns2:NUM_DOC_IDENTIFICACION></ns2:NOTARIO><ns2:IMPORTE>2.00</ns2:IMPORTE><ns2:MONEDA>EUR</ns2:MONEDA><ns2:ORDENANTE>JUAN PEREZ SANCHEZ</ns2:ORDENANTE><ns2:CONCEPTO>Pago deuda inmueble Santurze</ns2:CONCEPTO></ns2:TRANSFERENCIA></ns2:MENSAJE></ns2:REQUEST>

Formated for best view:

<SERVICE_DISPATCHER_REQUEST xmlns="http://inti.notariado.org/XML" xmlns:ns2="http://ancert.notariado.org/XML/CSN">
<ns2:SERVICIO_CONSIGNACION_NOTARIAL>
    <ns2:REQUEST>
        <ns2:CABECERA>
            <ns2:ID_COMUNICACION>2147483647</ns2:ID_COMUNICACION>
            <ns2:FECHA>2016-03-22</ns2:FECHA>
            <ns2:HORA>10:55:00</ns2:HORA>
            <ns2:TIPO_OPERACION>TRANSFERENCIA</ns2:TIPO_OPERACION>
        </ns2:CABECERA>
        <ns2:MENSAJE>
            <ns2:TRANSFERENCIA>
                <ns2:CODIGO_SERVICIO>DEUDAS</ns2:CODIGO_SERVICIO>
                <ns2:REFERENCIA>999988887777666655</ns2:REFERENCIA>
                <ns2:FECHA_EMISION>2016-03-24</ns2:FECHA_EMISION>
                <ns2:FECHA_VALOR>2016-03-25</ns2:FECHA_VALOR>
                <ns2:NOTARIO>
                    <ns2:CODIGO_ULTIMAS_VOLUNTADES>9900005</ns2:CODIGO_ULTIMAS_VOLUNTADES>
                    <ns2:TIP_DOC_IDENTIFICACION>1</ns2:TIP_DOC_IDENTIFICACION>
                    <ns2:NUM_DOC_IDENTIFICACION>11711111H</ns2:NUM_DOC_IDENTIFICACION>
                </ns2:NOTARIO><ns2:IMPORTE>1.00</ns2:IMPORTE>
                <ns2:MONEDA>EUR</ns2:MONEDA>
                <ns2:ORDENANTE>JUAN PEREZ SANCHEZ</ns2:ORDENANTE>
                <ns2:CONCEPTO>Pago deuda inmueble Santurze</ns2:CONCEPTO>
            </ns2:TRANSFERENCIA>
            <ns2:TRANSFERENCIA>
                <ns2:CODIGO_SERVICIO>DEUDAS</ns2:CODIGO_SERVICIO>
                <ns2:REFERENCIA>999988887777666655</ns2:REFERENCIA>
                <ns2:FECHA_EMISION>2016-03-24</ns2:FECHA_EMISION>
                <ns2:FECHA_VALOR>2016-03-25</ns2:FECHA_VALOR>
                <ns2:NOTARIO>
                    <ns2:CODIGO_ULTIMAS_VOLUNTADES>9900005</ns2:CODIGO_ULTIMAS_VOLUNTADES>
                    <ns2:TIP_DOC_IDENTIFICACION>1</ns2:TIP_DOC_IDENTIFICACION>
                    <ns2:NUM_DOC_IDENTIFICACION>11711111H</ns2:NUM_DOC_IDENTIFICACION>
                </ns2:NOTARIO>
                <ns2:IMPORTE>2.00</ns2:IMPORTE>
                <ns2:MONEDA>EUR</ns2:MONEDA>
                <ns2:ORDENANTE>JUAN PEREZ SANCHEZ</ns2:ORDENANTE>
                <ns2:CONCEPTO>Pago deuda inmueble Santurze</ns2:CONCEPTO>
            </ns2:TRANSFERENCIA>
        </ns2:MENSAJE>
    </ns2:REQUEST>
</ns2:SERVICIO_CONSIGNACION_NOTARIAL>

Then I do the Hash value to create the digest value using the previous XML with body start and end tag:

        // Change value if there is any problem on websphere
        // wsuId = ""#MsgBody"";
        wsuId = ""#id-" + UUIDGenerator.getUUID() + """;
        // Create XML data for body SOAP
        String dataXMLSoap = generateBodySoap(doc);
        String startBodyHash = "<soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" wsu:Id=" + wsuId+ ">";
        String endBodyHash  = "</soapenv:Body>";
        MessageDigest messageDigestFromBody= MessageDigest.getInstance("SHA-1");            
        String xmlHash = startBodyHash + dataXMLSoap + endBodyHash;
        byte[] hashSoapBody = messageDigestFromBody.digest(xmlHash.getBytes());
        String digestValueFromBody = new BASE64Encoder().encode(hashSoapBody);

Example of XML hash:

<soapenv:Body xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" wsu:Id="#id-D1DD0227ECDC46640D147197713960845"><SERVICE_DISPATCHER_REQUEST xmlns="http://inti.notariado.org/XML" xmlns:ns2="http://ancert.notariado.org/XML/CSN"><ns2:SERVICIO_CONSIGNACION_NOTARIAL><ns2:REQUEST><ns2:CABECERA><ns2:ID_COMUNICACION>2147483647</ns2:ID_COMUNICACION><ns2:FECHA>2016-03-22</ns2:FECHA><ns2:HORA>10:55:00</ns2:HORA><ns2:TIPO_OPERACION>TRANSFERENCIA</ns2:TIPO_OPERACION></ns2:CABECERA><ns2:MENSAJE><ns2:TRANSFERENCIA><ns2:CODIGO_SERVICIO>DEUDAS</ns2:CODIGO_SERVICIO><ns2:REFERENCIA>999988887777666655</ns2:REFERENCIA><ns2:FECHA_EMISION>2016-03-24</ns2:FECHA_EMISION><ns2:FECHA_VALOR>2016-03-25</ns2:FECHA_VALOR><ns2:NOTARIO><ns2:CODIGO_ULTIMAS_VOLUNTADES>9900005</ns2:CODIGO_ULTIMAS_VOLUNTADES><ns2:TIP_DOC_IDENTIFICACION>1</ns2:TIP_DOC_IDENTIFICACION><ns2:NUM_DOC_IDENTIFICACION>11711111H</ns2:NUM_DOC_IDENTIFICACION></ns2:NOTARIO><ns2:IMPORTE>1.00</ns2:IMPORTE><ns2:MONEDA>EUR</ns2:MONEDA><ns2:ORDENANTE>JUAN PEREZ SANCHEZ</ns2:ORDENANTE><ns2:CONCEPTO>Pago deuda inmueble Santurze</ns2:CONCEPTO></ns2:TRANSFERENCIA><ns2:TRANSFERENCIA><ns2:CODIGO_SERVICIO>DEUDAS</ns2:CODIGO_SERVICIO><ns2:REFERENCIA>999988887777666655</ns2:REFERENCIA><ns2:FECHA_EMISION>2016-03-24</ns2:FECHA_EMISION><ns2:FECHA_VALOR>2016-03-25</ns2:FECHA_VALOR><ns2:NOTARIO><ns2:CODIGO_ULTIMAS_VOLUNTADES>9900005</ns2:CODIGO_ULTIMAS_VOLUNTADES><ns2:TIP_DOC_IDENTIFICACION>1</ns2:TIP_DOC_IDENTIFICACION><ns2:NUM_DOC_IDENTIFICACION>11711111H</ns2:NUM_DOC_IDENTIFICACION></ns2:NOTARIO><ns2:IMPORTE>2.00</ns2:IMPORTE><ns2:MONEDA>EUR</ns2:MONEDA><ns2:ORDENANTE>JUAN PEREZ SANCHEZ</ns2:ORDENANTE><ns2:CONCEPTO>Pago deuda inmueble Santurze</ns2:CONCEPTO></ns2:TRANSFERENCIA></ns2:MENSAJE></ns2:REQUEST></ns2:SERVICIO_CONSIGNACION_NOTARIAL></SERVICE_DISPATCHER_REQUEST></soapenv:Body>

With teh digest value generated I create a SignedInfo node:

String xmlDigestFromBody =
            "<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">" 
            + "<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"></ds:CanonicalizationMethod>" 
            + "<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>" 
            + "<ds:Reference URI=" + wsuId + ">" 
            + "<ds:Transforms>" 
            + "<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">" 
            + "</ds:Transform>" 
            + "</ds:Transforms>"
            + "<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">" 
            + "</ds:DigestMethod>"
            + "<ds:DigestValue>" 
            + digestValueFromBody 
            + "</ds:DigestValue>" 
            + "</ds:Reference>" 
            + "</ds:SignedInfo>";

The xml for signature looks like this:

<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod><ds:Reference URI="#id-D1DD0227ECDC46640D147197713960845"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod><ds:DigestValue>Dd7cqOPVujwavDwKWwWqI8NHfYw=</ds:DigestValue></ds:Reference></ds:SignedInfo>

With this XML and my X509 Certificate info I use this method to get the sign value:

byte[] signDigest = sign(privateKey, xmlDigestFromBody, algorithm);
String signValueDigest = new BASE64Encoder().encode(signDigest);

// privateKey-> private key from X509 Certificate
// data -> Data to get signed
// algorith -> algorith for sign, in this case with value -> SHA1withRSA
private static byte[] sign(PrivateKey privateKey, String data, String algorithm) throws GeneralSecurityException
{
    Signature signature = Signature.getInstance(algorithm);
    signature.initSign(privateKey);
    signature.update(data.getBytes());
    return signature.sign();
}

With all information generated I form the complete Soap message.

String signedSoap = "<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">" 
            + "<soap:Header>"
            + "<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecuri

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I answer to my question with my solution. @pedrofb tell a better way to fix my problem but I couldn't use on my WAS server.

I use a private method to canonicalized my XML to create the digest and later to create the signature:

private String canonicalize(String xml)
{
    try
    {
        String CHARSET = "UTF-8";
        Init.init();
        Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_WITH_COMMENTS);
        String canoninalizacion = new String(canon.canonicalize(xml.getBytes(CHARSET)), CHARSET);
        return canoninalizacion;
    }
    catch (Exception e)
    {
        e.printStackTrace();
        return xml;
    }
}

Then we transform the XML correctly to set the namespaces and prefix in the right place. For my needed I use:

private String createBodyXML(String dataXML)
{
    String docXML = canonicalize(dataXML);
    // Manual introduction of prefixes
    docXML = docXML.replaceAll("</", "</ns2:").replaceAll("<", "<ns2:").replaceAll("<ns2:/ns2:", "</ns2:");

    String startBodyHash = "<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" Id="MsgBody">";
    String endBodyHash = "</soap:Body>";
    String dataXMLSoap = "<SERVICE_DISPATCHER_REQUEST xmlns="http://inti.notariado.org/XML">" 
        + "<ns2:SERVICIO_CONSIGNACION_NOTARIAL xmlns:ns2="http://ancert.notariado.org/XML/CSN">" 
        + docXML 
        + "</ns2:SERVICIO_CONSIGNACION_NOTARIAL>" 
        + "</SERVICE_DISPATCHER_REQUEST>";

    String xmlHash = startBodyHash + dataXMLSoap + endBodyHash;
    // this xmlHash is the same body of the SOAP call
    return xmlHash;
}

We do at the time of the creation of the XML of body and XML of signedInfo. Is not the best way to make it:

String xmlDigest = "<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">" 
            + "<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">"
            + "<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap"></ec:InclusiveNamespaces>" 
            + "</ds:CanonicalizationMethod>" 
            + "<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>"
            + "<ds:Reference URI="#MsgBody">" 
            + "<ds:Transforms>" 
            + "<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">" 
            + "<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList=""></ec:InclusiveNamespaces>" 
            + "</ds:Transform>"
            + "</ds:Transforms>" 
            + "<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>" 
            + "<ds:DigestValue>" 
            + digestValue 
            + "</ds:DigestValue>" 
            + "</ds:Reference>" 
            + "</ds:SignedInfo>";

I want to share how I call my web service with the SOAP message well signed, It is not in relation with this question , but maybe wil be usefull for someone:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
...
private String sendMessage(String xmlAEnviar) throws Exception
{
    try
    {
        // Metodo envio
        String POST = "POST";
        String urlEndpoint = "https://test.dominio.org/servicedispatcher/services/ServiceDispatcherSigned";
        final String httpsProxyHost = "180.100.14.70";
        final String httpsProxyPort = "8080";
        final String userProxy = "user";
        final String passProxy = "password";

        URL httpsURL = new URL(urlEndpoint);

        HttpsURLConnection httpsURLConnection = null;

        if (httpsProxyHost.length() > 0 && httpsProxyPort.length() > 0)
        {
            Proxy miProxy = new Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(httpsProxyHost, Integer.parseInt(httpsProxyPort)));
            httpsURLConnection = (HttpsURLConnection) httpsURL.openConnection(miProxy);
            if (userProxy.length() > 0 && passProxy.length() > 0)
            {
                String userPassword = userProxy + ":" + passProxy;
                sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
                String encodedLogin = encoder.encode(userPassword.getBytes());
                Authenticator.setDefault(new Authenticator()
                {
                    protected PasswordAuthentication getPasswordAuthentication()
                    {
                        return new PasswordAuthentication(userProxy, passProxy.toCharArray());
                    }
                });
                httpsURLConnection.setRequestProperty("Proxy-Authorization", (new StringBuilder("Basic ")).append(encodedLogin).toString());
            }
        }

        else
        {
            httpsURLConnection = (HttpsURLConnection) httpsURL.openConnection();
        }

        httpsURLConnection.setDoOutput(true);
        httpsURLConnection.setRequestMethod(POST);
        httpsURLConnection.setRequestProperty("Content-Type", "text/xml; charset="UTF-8"");
        httpsURLConnection.setRequestProperty("SOAPAction", "");

        OutputStream outputStream = httpsURLConnection.getOutputStream();
        outputStream.write(xmlAEnviar.getBytes());
        outputStream.flush();

        InputStream httpsInputStream = httpsURLConnection.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader((httpsInputStream)));
        String lineanext = "";
        String outputnext = "";
        while ((lineanext = bufferedReader.readLine()) != null)
        {
            outputnext = outputnext.concat(lineanext);
        }
        httpsURLConnection.disconnect();

        return outputnext;
    }
    catch (NumberFormatException e)
    {
        e.printStackTrace();
        throw new Exception(e);
    }
    catch (MalformedURLException e)
    {
        e.printStackTrace();
        throw new Exception(e);
    }
    catch (ProtocolException e)
    {
        e.printStackTrace();
        throw new Exception(e);
    }
    catch (IOException e)
    {
        e.printStackTrace();
        throw new Exception(e);
    }
}

Thanks for everyone, especially to @pedrofb.

A cordial greeting.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...