I am able to sign the SOAP XML using a certificate for the WS-Security signature. But i am unable to verify its signature. On verifying the signature it leads to Exception, Some help will be appreciated to resolve the issue
SOAP Enveloped WS-Security signature:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<ws:Security xmlns:ws="http://schemas.xmlsoap.org/..." s:actor="test">
<dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/..." id="Sample">
...
</dsig:Signature>
</ws:Security>
</s:Header>
<s:Body>
...
</s:Body>
</s:Envelope>
When i try to verify the SOAP XML i am getting the following Exception:
org.apache.xml.security.signature.MissingResourceFailureException: The Reference for URI #Body has no XMLSignatureInput
public static boolean isSOAPXmlWSSEDigitalSignatureValid(String signedXmlFilePath, PublicKey publicKey) throws Exception {
String xmlContent = getFileString(signedXmlFilePath);
Document signedDoc = getDocument(xmlContent.trim(), true);
signedDoc.createElementNS(DSIG_NS, "ds"); // qualifiedName = "ds";
signedDoc.createElementNS(WSU_NS, "wsu");
signedDoc.createElementNS(WSSE_NS, "wsse");
// load signature
NodeList signatureNodes = signedDoc.getElementsByTagNameNS(javax.xml.crypto.dsig.XMLSignature.XMLNS, "Signature");
if (signatureNodes.getLength() == 0) {
throw new Exception("No XML Digital Signature Found, document is discarded");
}
Element sigElement = (Element) signatureNodes.item(0);
if (sigElement == null)
throw new Exception("Signature element is null!");
org.apache.xml.security.Init.init();
org.apache.xml.security.signature.XMLSignature signature = new XMLSignature(sigElement, "");
return signature.checkSignatureValue(publicKey);
}
By Using the following function i am able to sign the XML
public static SOAPMessage WS_Security_signature(String inputFile, boolean isDataXML) throws Exception {
SOAPMessage soapMsg;
Document docBody;
if (isDataXML) {
System.out.println("Sample DATA xml - Create SOAP Message");
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage soapMessage = messageFactory.createMessage();
soapMsg = soapMessage;
String xmlContent = getFileString(inputFile);
docBody = getDocument(xmlContent.trim(), true);
System.out.println("Data Document: "+docBody.getDocumentElement());
} else {
System.out.println("SOAP XML with Envelope");
Document doc = getDocument(inputFile, false); // SOAP MSG removing comment elements
String docStr = toStringDocument(doc); // https://mcmap.net/q/327252/-xml-document-to-string
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(docStr.getBytes());
MimeHeaders mimeHeaders = new MimeHeaders();
// SOAPMessage message = MessageFactory.newInstance().createMessage(null, fileInputStream);
SOAPMessage message = MessageFactory.newInstance("SOAP 1.2 Protocol").createMessage(mimeHeaders, byteArrayInputStream);
soapMsg = message;
docBody = soapMsg.getSOAPBody().extractContentAsDocument();
System.out.println("SOAP DATA Document: "+docBody.getDocumentElement());
}
// 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.addDocument(docBody);
soapBody.addAttribute(soapEnv.createName("Id", "wsu", WSU_NS), "Body");
if (soapHeader == null) {
soapHeader = soapEnv.addHeader();
System.out.println("Provided SOAP XML does not contains any Header part. So creating it.");
}
// <wsse:Security> element adding to Header Part
SOAPElement securityElement = soapHeader.addChildElement("Security", "wsse", WSSE_NS);
securityElement.addNamespaceDeclaration("wsu", WSU_NS);
String certEncodedID = "X509Token", timeStampID = "TS", signedBodyID = "Body";
// (ii) Add Binary Security Token.
// <wsse:BinarySecurityToken EncodingType="...#Base64Binary" ValueType="...#X509v3" wsu:Id="X509Token">The base64 encoded value of the ROS digital certificate.</wsse:BinarySecurityToken>
SOAPElement binarySecurityToken = securityElement.addChildElement("BinarySecurityToken", "wsse");
binarySecurityToken.setAttribute("ValueType", binarySecurityToken_Value);
binarySecurityToken.setAttribute("EncodingType", binarySecurityToken_Encoding);
binarySecurityToken.setAttribute("wsu:Id", certEncodedID);
byte[] certByte = loadPublicKeyX509.getEncoded();
String encodeToString = Base64.getEncoder().encodeToString(certByte);
binarySecurityToken.addTextNode(encodeToString);
//(iii) Add TimeStamp element - <wsu:Timestamp wsu:Id="TS">
SOAPElement timestamp = securityElement.addChildElement("Timestamp", "wsu");
timestamp.addAttribute(soapEnv.createName("Id", "wsu", WSU_NS), timeStampID);
String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
DateTimeFormatter timeStampFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
timestamp.addChildElement("Created", "wsu").setValue(timeStampFormatter.format(ZonedDateTime.now().toInstant().atZone(ZoneId.of("UTC"))));
timestamp.addChildElement("Expires", "wsu").setValue(timeStampFormatter.format(ZonedDateTime.now().plusSeconds(30).toInstant().atZone(ZoneId.of("UTC"))));
// (iv) Add signature element
// <wsse:Security> <ds:Signature> <ds:KeyInfo> <wsse:SecurityTokenReference>
SOAPElement securityTokenReference = securityElement.addChildElement("SecurityTokenReference", "wsse");
SOAPElement reference = securityTokenReference.addChildElement("Reference", "wsse");
reference.setAttribute("URI", "#"+certEncodedID); // <wsse:BinarySecurityToken wsu:Id="X509Token"
// <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_SHA1, null);
ArrayList<Transform> transformList = new ArrayList<Transform>();
//Transform - <ds:Reference URI="#Body">
Transform envTransform = xmlSignatureFactory.newTransform(transformAlog, (TransformParameterSpec) null);
transformList.add(envTransform);
//References <ds:Reference URI="#Body">
ArrayList<Reference> refList = new ArrayList<Reference>();
Reference refTS = xmlSignatureFactory.newReference("#"+timeStampID, digestMethod, transformList, null, null);
Reference refBody = xmlSignatureFactory.newReference("#"+signedBodyID, digestMethod, transformList, null, null);
refList.add(refBody);
refList.add(refTS);
javax.xml.crypto.dsig.CanonicalizationMethod cm = xmlSignatureFactory.newCanonicalizationMethod(canonicalizerAlog, (C14NMethodParameterSpec) null);
javax.xml.crypto.dsig.SignatureMethod sm = xmlSignatureFactory.newSignatureMethod(signatureMethodAlog_SHA1, null);
SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(cm, sm, refList);
DOMSignContext signContext = new DOMSignContext(privateKey, securityElement);
signContext.setDefaultNamespacePrefix("ds");
signContext.putNamespacePrefix(DSIG_NS, "ds");
signContext.putNamespacePrefix(WSU_NS, "wsu");
signContext.setIdAttributeNS(soapBody, WSU_NS, "Id");
signContext.setIdAttributeNS(timestamp, 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;
}
Full Example which uses the Certificate And Private Key Baeldung.cer, Baeldung.p12 (password = “password”)
// dependency: groupId:xml-security, artifactId:xmlsec, version:1.3.0
// dependency: groupId:xalan, artifactId:xalan, version:2.7.1
public class SOAP_Security_Signature {
static final String
WSSE_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
WSU_NS = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
DSIG_NS = "http://www.w3.org/2000/09/xmldsig#", // javax.xml.crypto.dsig.XMLSignature.XMLNS, Constants.SignatureSpecNS
binarySecurityToken_Encoding = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary",
binarySecurityToken_Value = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3",
signatureMethodAlog_SHA1 = DSIG_NS + "rsa-sha1", // XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1
digestMethodAlog_SHA1 = Constants.ALGO_ID_DIGEST_SHA1, // DSIG_NS + "sha1", // Constants.ALGO_ID_DIGEST_SHA1
transformAlog = Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, //"http://www.w3.org/2001/10/xml-exc-c14n#";
canonicalizerAlog = Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS; //"http://www.w3.org/2001/10/xml-exc-c14n#"; CanonicalizationMethod.EXCLUSIVE
static {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
}
public static X509Certificate loadPublicKeyX509(InputStream cerFileStream) throws CertificateException, NoSuchProviderException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(cerFileStream);
return x509Certificate;
}
public static PrivateKey loadPrivateKeyforSigning(InputStream cerFileStream, String password) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, NoSuchProviderException {
KeyStore keyStore = KeyStore.getInstance("PKCS12"); //, "BC");
keyStore.load(cerFileStream, password.toCharArray());
Enumeration<String> keyStoreAliasEnum = keyStore.aliases();
PrivateKey privateKey = null;
String alias = null;
if ( keyStoreAliasEnum.hasMoreElements() ) {
alias = keyStoreAliasEnum.nextElement();
if (password != null) {
privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
}
}
return privateKey;
}
static X509Certificate loadPublicKeyX509;
static PrivateKey privateKey;
static String path = "C:/Yash/SOAP/", privateKeyFilePath = path+"Baeldung.p12", publicKeyFilePath = path+"Baeldung.cer",
inputFile= path+"Soap1.xml", outputFile = path+"output.xml";
public static void main(String[] args) throws Exception {
InputStream pkcs_FileStream = new FileInputStream(privateKeyFilePath);
privateKey = loadPrivateKeyforSigning(pkcs_FileStream, "password");
System.out.println("privateKey : "+privateKey);
InputStream cerFileStream = new FileInputStream(publicKeyFilePath);
loadPublicKeyX509 = loadPublicKeyX509(cerFileStream);
PublicKey publicKey = loadPublicKeyX509.getPublicKey();
System.out.println("loadPublicKey : "+ publicKey);
System.setProperty("javax.xml.soap.MessageFactory", "com.sun.xml.internal.messaging.saaj.soap.ver1_2.SOAPMessageFactory1_2Impl");
System.setProperty("javax.xml.bind.JAXBContext", "com.sun.xml.internal.bind.v2.ContextFactory");
SOAPMessage soapMsg = WS_Security_signature(inputFile, false);
outputSOAPMessageToFile(soapMsg);
System.out.println("Signature Succesfull. Verify the Signature");
boolean soapXmlWSSEDigitalSignatureValid = isSOAPXmlWSSEDigitalSignatureValid(outputFile, publicKey);
System.out.println("isSOAPXmlDigitalSignatureValid :"+soapXmlWSSEDigitalSignatureValid);
}
public static void outputSOAPMessageToFile(SOAPMessage soapMessage) throws SOAPException, IOException {
File outputFileNew = new File(outputFile);
java.io.FileOutputStream fos = new java.io.FileOutputStream(outputFileNew);
soapMessage.writeTo(fos);
fos.close();
}
public static String toStringDocument(Document doc) throws TransformerException {
StringWriter sw = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.transform(new DOMSource(doc), new StreamResult(sw));
return sw.toString();
}
public static String getFileString(String xmlFilePath) throws FileNotFoundException {
File file = new File(xmlFilePath);
//FileInputStream parseXMLStream = new FileInputStream(file.getAbsolutePath());
Scanner scanner = new Scanner( file, "UTF-8" );
String xmlContent = scanner.useDelimiter("\\A").next();
scanner.close(); // Put this call in a finally block
System.out.println("Str:"+xmlContent);
return xmlContent;
}
public static Document getDocument(String xmlData, boolean isXMLData) throws Exception {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
dbFactory.setIgnoringComments(true);
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc;
if (isXMLData) {
InputSource ips = new org.xml.sax.InputSource(new StringReader(xmlData));
doc = dBuilder.parse(ips);
} else {
doc = dBuilder.parse( new File(xmlData) );
}
return doc;
}
private void callTheWebServiceFromFile() throws IOException, SOAPException {
//load the soap request file
File soapFile = new File(outputFile);
FileInputStream fis = new FileInputStream(soapFile);
javax.xml.transform.stream.StreamSource ss = new javax.xml.transform.stream.StreamSource(fis);
// Create a SOAP Message Object
SOAPMessage msg = MessageFactory.newInstance().createMessage();
SOAPPart soapPart = msg.getSOAPPart();
// Set the soapPart Content with the stream source
soapPart.setContent(ss);
// Create a webService connection
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConnection = soapConnectionFactory.createConnection();
// Invoke the webService.
String soapEndpointUrl = "https://softwaretest.ros.ie/paye-employers/v1/soap";
SOAPMessage resp = soapConnection.call(msg, soapEndpointUrl);
// Reading result
resp.writeTo(System.out);
fis.close();
soapConnection.close();
}
}
Input SOAP XML to Sign:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<product version="11.1.2.4.0"> <!-- Data XML -->
<name>API Gateway</name>
<company>Oracle</company>
<description>SOA Security and Management</description>
</product>
</env:Body>
</env:Envelope>
WS-Security Signed XML:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope
xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env: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-wssecurity-utility-1.0.xsd">
<wsse:BinarySecurityToken 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#X509v3" wsu:Id="X509Token">MIIDPjCCAiagAwIBAgIJAPvd1gx14C3CMA0GCSqGSIb3DQEBBQUAMEcxCzAJBgNVBAYTAk1BMRAwDgYDVQQIEwdNb3JvY2NvMRMwEQYDVQQHEwpDYXNhYmxhbmNhMREwDwYDVQQDEwhCYWVsZHVuZzAeFw0xNzEwMTIxMDQzMTRaFw0yNzEwMTMxMDQzMTRaMEcxCzAJBgNVBAYTAk1BMRAwDgYDVQQIEwdNb3JvY2NvMRMwEQYDVQQHEwpDYXNhYmxhbmNhMREwDwYDVQQDEwhCYWVsZHVuZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMyi5GmOeN4QaH/CP5gSOyHX8znb5TDHWV8wc+ZT7kNU8zt5tGMhjozK6hax155/6tOsBDR0rSYBhL+Dm/+uCVS7qOlRHhf6cNGtzGF1gnNJB2WjI8oMAYm24xpLj1WphKUwKrn3nTMPnQup5OoNAMYl99flANrRYVjjxrLQvDZDUio6IujrCZ2TtXGM0g/gP++28KT7g1KlUui3xtB0u33wx7UN8Fix3JmjOaPHGwxGpwP3VGSjfs8cuhqVwRQaZpCOoHU/P8wpXKw80sSdhz+SRueMPtVYqK0CiLL5/O0h0Y3le4IVwhgg3KG1iTGOWn60UMFn1EYmQ18k5Nsma6UCAwEAAaMtMCswCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBPAwCwYDVR0PBAQDAgUgMA0GCSqGSIb3DQEBBQUAA4IBAQC8DDBmJ3p4xytxBiE0s4p1715WT6Dm/QJHp0XC0hkSoyZKDh+XVmrzm+J3SiW1vpswb5hLgPo040YX9jnDmgOD+TpleTuKHxZRYj92UYWmdjkWLVtFMcvOh+gxBiAPpHIqZsqo8lfcyAuh8Jx834IXbknfCUtERDLG/rU9P/3XJhrM2GC5qPQznrW4EYhUCGPyIJXmvATMVvXMWCtfogAL+n42vjYXQXZoAWomHhLHoNbSJUErnNdWDOh4WoJtXJCxA6U5LSBplqb3wB2hUTqw+0admKltvmy+KA1PD7OxoGiY7V544zeGqJam1qxUia7y5BL6uOa/4ShSV8pcJDYz</wsse:BinarySecurityToken>
<wsu:Timestamp wsu:Id="TS">
<wsu:Created>2020-08-27T12:03:23.288Z</wsu:Created>
<wsu:Expires>2020-08-27T12:03:53.293Z</wsu:Expires>
</wsu:Timestamp>
<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:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#Body">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>RU8XbQ1/kx/CPZpgxG9fa/lia8Q=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#TS">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>YZTSgGN/tvrcpeFZ00aWCpVrXZU=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>hgGYDvkc3YPG1ptb6FMnQJV0t1GExjHIOGsXjAwG6uIYLDvkt10ve+AOynBAiSP0CR1NDI6mlXd+
5v5oa2XLEcbuO62v8yj0o1Kr+gkiaf1zo/qZkpwyT5iBkmabNcEzRE8iDCKwTOfFkG4PVRkCOE0D
Q4I84AeYnLAHaoF9IGpbjk0MdXKFIsSTmAmR92BHnjsOyUi1CD6N/7GDscNLfYoEtMEwovFbupUP
qbJNaq+M/bi5dnTEVqG/TIGftc/me8NVXQiohRq6U8sSAMbLdF5P+iGivcqlLD5xthXfBPHpwruK
euXNan7Jxc9cc5QFx3Rcirvqg/iJ0sPAHRuPGQ==</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference>
<wsse:Reference URI="#X509Token"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</env:Header>
<env:Body
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Body">
<product version="11.1.2.4.0">
<name>API Gateway</name>
<company>Oracle</company>
<description>SOA Security and Management</description>
</product>
</env:Body>
</env:Envelope>
For SOAP XML digital signature i am able to sign and verify
.
SOAP XML Enveloped Signature:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/..." id="Sample">
...
</dsig:Signature>
</s:Header>
<s:Body>
...
</s:Body>
</s:Envelope>