1

Like SOAPUI I am able to sign the SOAP XML with Key-Identifier as X509SubjectKeyIdentifier and I have compare the xml's mostly they are identical. By using the following function WS_Security_signature_KeyIdentifier(SOAPMessage) I am generating the xml as follows.

Even though XML's are same, I am getting Incorrect message signing. I think their is a problem in the signing function. Please help me to solve the issue.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header />
  <SOAP-ENV:Body>
    <SOAP-ENV:Fault>
      <faultcode>SOAP-ENV:Client</faultcode>
      <faultstring>Incorrect message signing</faultstring>
    </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/" xmlns:tem2="http://tempuri.org/">
  <soapenv:Header>
    <tem:Add xmlns:tem="http://tempuri.org/">
        <tem:intA>3</tem:intA>
        <tem:intB>4</tem:intB>
    </tem:Add>
    
    <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-wssecurity-utility-1.0.xsd">
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
            <ds:InclusiveNamespaces xmlns:ds="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soapenv tem tem2" />
          </ds:CanonicalizationMethod>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
          <ds:Reference URI="#MsgBody">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ds:InclusiveNamespaces xmlns:ds="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="tem tem2" />
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
            <ds:DigestValue> *** Digest Encode Value *** </ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue> *** Signature Value *** </ds:SignatureValue>
        <ds:KeyInfo>
          <wsse:SecurityTokenReference>
            <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier"> *** X509v3SubjectKeyIdentifier_CertEncoded ***</wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="MsgBody">
    <tem2:Add>
        <tem2:intA>3</tem2:intA>
        <tem2:intB>4</tem2:intB>
    </tem2:Add>
  </soapenv:Body>
</soapenv:Envelope>
static final String SOAP_PROTOCOL = SOAPConstants.SOAP_1_1_PROTOCOL;
static String certEncodedID_KeyIdentifier_WsuID = "X509Token", timeStampID = "Timestamp", signedBodyID = "MsgBody";

static boolean inclusiveNamespaceCanonicalization = true, inclusiveNamespaceTransform = true, useTimeStamp = false;
static String transformPrefixListName = "v1 v11";

public static SOAPMessage WS_Security_signature_KeyIdentifier(SOAPMessage soapMsg) throws Exception {
    
    // A new SOAPMessage object contains: •SOAPPart object •SOAPEnvelope object •SOAPBody object •SOAPHeader object 
    SOAPPart soapPart = soapMsg.getSOAPPart();
    SOAPEnvelope soapEnv = soapPart.getEnvelope();
    SOAPHeader soapHeader = soapEnv.getHeader(); // soapMessage.getSOAPHeader();
    SOAPBody soapBody = soapEnv.getBody(); // soapMessage.getSOAPBody()
    
    soapBody.addAttribute(soapEnv.createName("Id", MessageConstants.WSU_PREFIX, MessageConstants.WSU_NS), signedBodyID);
    
    // Adding NameSpaces to the Envelope
    soapEnv.addNamespaceDeclaration(MessageConstants.WSSE_PREFIX, MessageConstants.WSSE_NS);
    soapEnv.addNamespaceDeclaration(MessageConstants.WSU_PREFIX, MessageConstants.WSU_NS);
    soapEnv.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");
    soapEnv.addNamespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");
    
    // <wsse:Security> element adding to Header Part
    SOAPElement securityElement = soapHeader.addChildElement("Security", MessageConstants.WSSE_PREFIX, MessageConstants.WSSE_NS);
    //securityElement.addNamespaceDeclaration("wsu", WSU_NS);
    
    /** SecurityTokenReference (Start) */
    // Add signature element - <wsse:Security> <ds:Signature> <ds:KeyInfo> <wsse:SecurityTokenReference>
    SOAPElement securityTokenReference = securityElement.addChildElement("SecurityTokenReference", MessageConstants.WSSE_PREFIX);
    
    SOAPElement reference = securityTokenReference.addChildElement("KeyIdentifier", MessageConstants.WSSE_PREFIX);
    reference.setAttributeNS(null, "EncodingType", MessageConstants.BASE64_ENCODING_NS);
    reference.setAttributeNS(null, "ValueType", MessageConstants.X509SubjectKeyIdentifier_NS);
    reference.addTextNode( getX509v3SubjectKeyIdentifier_CertEncoded(loadPublicKeyX509) );
    
    /** SecurityTokenReference (End) */
    
    // <ds:SignedInfo>
    String providerName = System.getProperty("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI");
    XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM", (Provider) Class.forName(providerName).newInstance());

    //Digest method - <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
    javax.xml.crypto.dsig.DigestMethod digestMethod = xmlSignatureFactory.newDigestMethod(digestMethodAlog, null);
    
    ArrayList<Transform> transformList = new ArrayList<Transform>();
    //Transform - <ds:Reference URI="#Body">
    Transform envTransform = null;
    if (inclusiveNamespaceTransform) {
        List<String> prefixList = new ArrayList<String>();
        String[] split = transformPrefixListName.split(" ");
        for (String string : split) {
            System.out.println("Transform InclusiveNamespaces Prefix:"+string);
            prefixList.add(string); // How to add these prefix values dynamically excluding soapenv.
        }
        ExcC14NParameterSpec excC14NParameterSpec = new ExcC14NParameterSpec(prefixList);
        envTransform = xmlSignatureFactory.newTransform(MessageConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, excC14NParameterSpec);
        transformList.add(envTransform);
    } else {
        envTransform = xmlSignatureFactory.newTransform(MessageConstants.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, (TransformParameterSpec) null);
        transformList.add(envTransform);
    }
        //References <ds:Reference URI="#Body">
        ArrayList<Reference> refList = new ArrayList<Reference>();
            Reference refBody = xmlSignatureFactory.newReference("#"+signedBodyID, digestMethod, transformList, null, null);
        refList.add(refBody);
        if (useTimeStamp) {
            Reference refTS   = xmlSignatureFactory.newReference("#"+timeStampID,  digestMethod, transformList, null, null);
        refList.add(refTS);
        }

    // <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
    javax.xml.crypto.dsig.CanonicalizationMethod cm;
    if (inclusiveNamespaceCanonicalization) {
        List<String> prefixList = new ArrayList<String>();
        Iterator namespacePrefixes = soapEnv.getNamespacePrefixes();
            for (Iterator iterator = namespacePrefixes; iterator.hasNext();) {
                String prefix = (String) iterator.next();
                System.out.println("CanonicalizationMethod InclusiveNamespaces Prefix:"+prefix);
                prefixList.add(prefix);
            }
            
        ExcC14NParameterSpec excC14NParameterSpec = new ExcC14NParameterSpec(prefixList);
        
        cm = xmlSignatureFactory.newCanonicalizationMethod(canonicalizationMethod_Algo, excC14NParameterSpec);
    } else {
        cm = xmlSignatureFactory.newCanonicalizationMethod(canonicalizationMethod_Algo, (C14NMethodParameterSpec) null);
    }
    //javax.xml.crypto.dsig.CanonicalizationMethod cm = xmlSignatureFactory.newCanonicalizationMethod(canonicalizationMethodAlog_INCLUSIVE, (C14NMethodParameterSpec) null);

    javax.xml.crypto.dsig.SignatureMethod sm = xmlSignatureFactory.newSignatureMethod(signatureMethod_Algo, null);
    SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(cm, sm, refList);

    DOMSignContext signContext = new DOMSignContext(privateKey, securityElement);
    signContext.setDefaultNamespacePrefix(MessageConstants.DSIG_PREFIX);
    signContext.putNamespacePrefix(MessageConstants.DSIG_NS, MessageConstants.DSIG_PREFIX);
    signContext.putNamespacePrefix(MessageConstants.WSU_NS, MessageConstants.WSU_PREFIX);

    signContext.setIdAttributeNS(soapBody, MessageConstants.WSU_NS, "Id");
    if (useTimeStamp ) {
        SOAPElement timeStamp = getTimeStamp(soapEnv, securityElement);
        signContext.setIdAttributeNS(timeStamp, MessageConstants.WSU_NS, "Id");
    }
    
    KeyInfoFactory keyFactory = KeyInfoFactory.getInstance();
    DOMStructure domKeyInfo = new DOMStructure(securityTokenReference);
    javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo = keyFactory.newKeyInfo(java.util.Collections.singletonList(domKeyInfo));
    javax.xml.crypto.dsig.XMLSignature signature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo);
    signContext.setBaseURI("");

    signature.sign(signContext);
    return soapMsg;
}
public static String getX509v3SubjectKeyIdentifier_CertEncoded(X509Certificate cert) throws IOException, CertificateEncodingException {
    // https://github.com/mulderbaba/webservices-osgi/blob/7090b58bd4cdf5fab4af14d54cb20bb45c074de2/com/sun/xml/wss/core/reference/X509SubjectKeyIdentifier.java#L108
    String SUBJECT_KEY_IDENTIFIER_OID = "2.5.29.14", Authority_KEY_IDENTIFIER_OID = "2.5.29.35";
    byte[] subjectKeyIdentifier = cert.getExtensionValue(SUBJECT_KEY_IDENTIFIER_OID); //  org.bouncycastle.asn1.x509.Extension.subjectKeyIdentifier.getId()
    System.out.println("X509SubjectKeyIdentifier ExtensionValue #"+subjectKeyIdentifier);
    if (subjectKeyIdentifier == null) {
        getCertInfo(cert);
        
        System.err.println("PKIX Certificate: Certificate is Self-Signed (or) CAs MUST mark this extension as non-critical. https://tools.ietf.org/html/rfc5280#page-28");
        // https://stackoverflow.com/a/31183447/5081877
        byte[] extensionValue = cert.getExtensionValue(Authority_KEY_IDENTIFIER_OID);
        System.out.println("Authority Key Identifier ExtensionValue #"+extensionValue);
        //byte[] octets = DEROctetString.getInstance(extensionValue).getOctets();
        //AuthorityKeyIdentifier authorityKeyIdentifier23 = AuthorityKeyIdentifier.getInstance(octets);
        //byte[] keyIdentifier = authorityKeyIdentifier23.getKeyIdentifier();
        
        throw new NullPointerException("SubjectKeyIdentifier OBJECT IDENTIFIER Value is Empty.");
    }
    sun.security.util.DerValue derVal = new sun.security.util.DerValue(
            new sun.security.util.DerInputStream(subjectKeyIdentifier).getOctetString());
    sun.security.x509.KeyIdentifier keyId = new sun.security.x509.KeyIdentifier(derVal.getOctetString());
    byte[] keyIDF = keyId.getIdentifier();
    String encodeToString = java.util.Base64.getEncoder().encodeToString(keyIDF);
    System.out.println("Subject Key Identifier Encoded Val: "+encodeToString );
    return encodeToString;
}

Any help will be appreciated to solve my issue. Full code is available at this Github Gist page.

Input Soap XML

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:temp="http://tempuri.org/" 
 xmlns:temp2="http://tempuri.org/">
   <soapenv:Header>
    <temp:Add>
        <temp:intA>3</temp:intA>
        <temp:intB>4</temp:intB>
    </temp:Add>
   </soapenv:Header>
   <soapenv:Body>
    <temp2:Add>
        <temp2:intA>3</temp2:intA>
        <temp2:intB>4</temp2:intB>
    </temp2:Add>
   </soapenv:Body>
</soapenv:Envelope>

SOAP XML file to SOAPMessage java Object

SOAPMessage soapMessageEnv = getSoapMessage_File(fileLocation_SoapTemplate);

public static SOAPMessage getSoapMessage_File(String sopaEnvelopFile) throws Exception {
    Document doc = getDocument(sopaEnvelopFile, false); // SOAP MSG removing comment elements
    String docStr = toStringDocument(doc);
    //docStr=docStr.replaceAll("\\<\\?xml(.+?)\\?\\>", "").trim();
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(docStr.getBytes());
    
    MimeHeaders mimeHeaders = new MimeHeaders();
    // SOAPMessage message = MessageFactory.newInstance().createMessage(null, fileInputStream);
    SOAPMessage message = MessageFactory.newInstance(SOAP_PROTOCOL).createMessage(mimeHeaders, byteArrayInputStream);
    
    return message;
}
Yash
  • 9,250
  • 2
  • 69
  • 74

1 Answers1

0

Above Signature is Cool but. While transferring form Saop XML to String XML with "INDENT:yes" i have received "SOAP XML: Incorrect message signing". So i just changed "INDENT:no". which solved the problem.

FROM:   transformer.setOutputProperty(OutputKeys.INDENT, "yes");
  To:   transformer.setOutputProperty(OutputKeys.INDENT, "no"); 
public static String toStringDocument(Document doc) throws TransformerException {
    StringWriter sw = new StringWriter();
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    // If Yes then it removes <?xml version="1.0" encoding="UTF-8"?>
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    transformer.setOutputProperty(OutputKeys.METHOD, "xml");
    // On using "INDENT:yes" i have received "SOAP XML: Incorrect message signing". So use as "INDENT:no"
    transformer.setOutputProperty(OutputKeys.INDENT, "no");
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

    transformer.transform(new DOMSource(doc), new StreamResult(sw));
    return sw.toString();
}
Yash
  • 9,250
  • 2
  • 69
  • 74