Update
I have figured out why the reference validation is failing. Unfortunately, I can't figure out what to do about it.
When parsing the "sigString" String variable into the "doc" Document variable, the LINEFEED characters are being converted into SPACE characters. I have confirmed this by doing a manual generation of the digest value and comparing it to the value that is computed by the Java code.
Does anybody know how to preserve the LINEFEED characters when parsing an XML string into a org.w3c.dom.Document variable?
Original Post
I am attempting to write some java code to verify the XML digital signature of a SAML response. I have verified the SAML response with other tools, so I know it is valid (excluding timing issues, not a factor to the digital signature). Below is the code I have used that I believe should be able to do this validation as well as the signature I am trying to validate.
When I run the code, I get the following output
Signature 0:
..Signature failed core validation
....signature validation status: true
....ref[0, #id14167335278088961501144300] validation status: false
Signature 1:
..Signature passed core validation
....signature validation status: true
....ref[0, #id141673352781342501524143644] validation status: true
I have no idea why the digest for reference id14167335278088961501144300 is not validating. Can anyone shed some light on what I am doing wrong?
Note: I am loading the XSD from a URL in this example so I don't have to include 4 XSD files in my question. However, because of this, the program can take a minute to run. I know this slowdown can be eliminated with local XSD files, it just is not feasible to do that way with the posted code
XMLDSigVerifier.java:
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.test.dsig.X509KeySelector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class XMLDSigVerifier {
public static void main (String[] args) throws ParserConfigurationException, SAXException, IOException, KeyStoreException, MarshalException, XMLSignatureException, NoSuchAlgorithmException, CertificateException {
//Get XML as a string, will be parameter in final version
String sigString = new String(Files.readAllBytes(Paths.get("src/signature.xml")), StandardCharsets.UTF_8);
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(sigString));
//Get X509Certificate as a string, will be parameter in final version
String samlCertString= "-----BEGIN CERTIFICATE-----MIIDpDCCAoygAwIBAgIGAVjUwdcaMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi02NjI2ODExHDAaBgkqhkiG9w0BCQEWDWluZm9Ab2t0YS5jb20wHhcNMTYxMjA2MTUyOTIzWhcNMjYxMjA2MTUzMDIzWjCBkjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtNjYyNjgxMRwwGgYJKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvLvIOJ11216IzuqZAbTiAoJy6WYxWuGXeUn4rpYkXLGOO0OoDahzhyquCQgLQC8mGlxCAq8gETQcdL+SX7lOlavHcNYiaYUD9IipMV0Kqt8TgfLO8UuYLb2jNNaQp+0tbcYv4SHpC4nXTndlo2nk3cJVELvXYfvjqKzDvtMwACy37Vc01GZbFQXhSEfBt9J2aQzLPFzH/RxKeOjzKW0kxWgYpfP0NZPtwkHrsdZaqpaR+039v5bckVSQvs0ZMz1Ionv+keWzM6YpQg7sF/OsvN05u0tkrGYDq1BoM9yH1h11bXrVhLvdOjo4bSVjeiAZ2LNOTurGO4JfH+CqT15c5wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQC8jLBoAM35/6pwtUJ86BhYKvtjB6t3k+5uFRUx8rBWYL5atirRPF73W4f6AIkIp36zkS0os0RHuFK0bG2FPnjQj+FpErd8zji8PVFQ2LZ0WPNLYP7g7BWxAoNct2q0Iw3TACY6h722Cq6WS9ZP4O2iv3kkpo4A7JZvuf4yGGY2nVfx5nLZAmcEA9bZmHhcgmPLs2FBYpLYPs/5P4nd2HeiTJW+F6M75g9E4wG+sf3q2zqzh+AmV5kHffWnGx2MPdUmyFPU80zcDzEpodVU73YUxJIJScwjjXzytuQSB2FcM0TMqwYUq2qGVAPhBw4nnAxeScbMyMbFVDrNyMeejDhq-----END CERTIFICATE-----";
X509Certificate samlCert;
samlCert = parseCertificate(samlCertString);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("a", samlCert);
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// File xsd = new File("src/saml-schema-protocol-2.0.xsd");
// Schema schema = schemaFactory.newSchema(xsd);
Schema schema = schemaFactory.newSchema(new URL("http://docs.oasis-open.org/security/saml/v2.0/saml-schema-protocol-2.0.xsd"));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setSchema(schema);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document doc = builder.parse(is);
NodeList signatureNodeList = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
Node signatureNode;
for (int sigIndex = 0; sigIndex < signatureNodeList.getLength(); sigIndex++) {
signatureNode = signatureNodeList.item(sigIndex);
if (sigIndex > 0) {
System.out.println("");
}
System.out.println("Signature " + sigIndex + ":");
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(ks), signatureNode);
valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = factory.unmarshalXMLSignature(valContext);
boolean coreValidity = signature.validate(valContext);
//Check Validity
if (coreValidity == false) {
System.err.println("..Signature failed core validation");
try {
//Sleep because of eclipse bug
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("..Signature passed core validation");
}
//Check validity Parts
//Validity Part 1: Check Signature Validation
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("....signature validation status: " + sv);
//Validity Part 2: Check References
Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
for (int j = 0; i.hasNext(); j++) {
Reference ref = (Reference) i.next();
boolean refValid = ref.validate(valContext);
System.out.println("....ref[" + j + ", " + ref.getURI() + "] validation status: " + refValid);
}
}
}
public static X509Certificate parseCertificate(String certStr) throws CertificateException{
java.util.Base64.Decoder dec = java.util.Base64.getDecoder();
String beginCert = "-----BEGIN CERTIFICATE-----";
String endCert = "-----END CERTIFICATE-----";
byte [] decoded = dec.decode(
certStr
.replaceAll(beginCert, "")
.replaceAll(endCert, "")
);
return (X509Certificate)CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(decoded));
}
}
signature.xml
<?xml version="1.0" encoding="UTF-8"?><saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://apps.surdellpartners.com/ords/kohls_wmj/workamajig/okta/acs/" ID="id14167335278088961501144300" IssueInstant="2017-01-27T18:21:53.483Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"><saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://www.okta.com/exk8y9z9v7FSYL34Y0h7</saml2:Issuer><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/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#id14167335278088961501144300"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><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="xs"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>7HyoOBjYlu8fbvSMNIY4O0fc6BhrkAUaPrF9EYWq/wE=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>dOhPiIwGvLJac40XW4x5Xn5soIzImitr/HAxRojDwSbAbfRp9t/VuRFT2Rat5oGgV3tWHedN7VBNpSGzBfZsYMBB/s/WYH0EQisTuS8iGXpNzxUIb0e9zbSFZIDiy3M1Zi8afC/EPrbP/SlBnXR0UXuyJW8KbfMZq9suw2GghVLI03q8FjLdFGBb4VZ2X5hqr4+qSpnS1+8RB+0bqtADKjYt9DP7vuhxz1jBI5o29OQOFZjW2K2g9Qr7ANUzaNFnIW50Lo4BH/qt60tA/UuZUNxwY+z3MM5ARb4zQYa8O/yQOPfvcKb0Ff9V4hHjlNtiZoZCOWq/2+ir9t7wxPwvNg==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAVjUwdcaMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi02NjI2ODExHDAaBgkqhkiG9w0BCQEW
DWluZm9Ab2t0YS5jb20wHhcNMTYxMjA2MTUyOTIzWhcNMjYxMjA2MTUzMDIzWjCBkjELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtNjYyNjgxMRwwGgYJ
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
vLvIOJ11216IzuqZAbTiAoJy6WYxWuGXeUn4rpYkXLGOO0OoDahzhyquCQgLQC8mGlxCAq8gETQc
dL+SX7lOlavHcNYiaYUD9IipMV0Kqt8TgfLO8UuYLb2jNNaQp+0tbcYv4SHpC4nXTndlo2nk3cJV
ELvXYfvjqKzDvtMwACy37Vc01GZbFQXhSEfBt9J2aQzLPFzH/RxKeOjzKW0kxWgYpfP0NZPtwkHr
sdZaqpaR+039v5bckVSQvs0ZMz1Ionv+keWzM6YpQg7sF/OsvN05u0tkrGYDq1BoM9yH1h11bXrV
hLvdOjo4bSVjeiAZ2LNOTurGO4JfH+CqT15c5wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQC8jLBo
AM35/6pwtUJ86BhYKvtjB6t3k+5uFRUx8rBWYL5atirRPF73W4f6AIkIp36zkS0os0RHuFK0bG2F
PnjQj+FpErd8zji8PVFQ2LZ0WPNLYP7g7BWxAoNct2q0Iw3TACY6h722Cq6WS9ZP4O2iv3kkpo4A
7JZvuf4yGGY2nVfx5nLZAmcEA9bZmHhcgmPLs2FBYpLYPs/5P4nd2HeiTJW+F6M75g9E4wG+sf3q
2zqzh+AmV5kHffWnGx2MPdUmyFPU80zcDzEpodVU73YUxJIJScwjjXzytuQSB2FcM0TMqwYUq2qG
VAPhBw4nnAxeScbMyMbFVDrNyMeejDhq</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2p:Status xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"><saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></saml2p:Status><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="id141673352781342501524143644" IssueInstant="2017-01-27T18:21:53.483Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"><saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://www.okta.com/exk8y9z9v7FSYL34Y0h7</saml2:Issuer><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/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#id141673352781342501524143644"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><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="xs"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>EhCfdS3vR7BT/a3ITrTVoSazAvQbiKlAeMpaOJv9wEs=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>GSbmKitI52o4eZm94ivVGfXpLHiiTCvvvy6uEfO+37Z95kK8hU3OniWeZW01NlqxrDqC1eZkrPBEqMWPW0+K5oeiHkaedCpVafn1mZLNYQNKJetKhYNczK7tiCZn9P66JOXfPfALOlsC0nVvL10m5CmLjQ/m1VW8BE5N58OsNO8mCDycupvFMp/Q0tvvAf5DUkY2A3Y6Chx/i4cAYpJKGjBppNNpdzVYNlvKYvImk6d25Gx+1J7H7+0uYheYkEPFLd+7Kr8rUpqV2t33iEyILc3LXabQmmNiNHxaGD9gmccsGNTddm3Q1Uso2SXm2lw6/FqdozbKFxs6qJKrm9VmLQ==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAVjUwdcaMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG
A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi02NjI2ODExHDAaBgkqhkiG9w0BCQEW
DWluZm9Ab2t0YS5jb20wHhcNMTYxMjA2MTUyOTIzWhcNMjYxMjA2MTUzMDIzWjCBkjELMAkGA1UE
BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtNjYyNjgxMRwwGgYJ
KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
vLvIOJ11216IzuqZAbTiAoJy6WYxWuGXeUn4rpYkXLGOO0OoDahzhyquCQgLQC8mGlxCAq8gETQc
dL+SX7lOlavHcNYiaYUD9IipMV0Kqt8TgfLO8UuYLb2jNNaQp+0tbcYv4SHpC4nXTndlo2nk3cJV
ELvXYfvjqKzDvtMwACy37Vc01GZbFQXhSEfBt9J2aQzLPFzH/RxKeOjzKW0kxWgYpfP0NZPtwkHr
sdZaqpaR+039v5bckVSQvs0ZMz1Ionv+keWzM6YpQg7sF/OsvN05u0tkrGYDq1BoM9yH1h11bXrV
hLvdOjo4bSVjeiAZ2LNOTurGO4JfH+CqT15c5wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQC8jLBo
AM35/6pwtUJ86BhYKvtjB6t3k+5uFRUx8rBWYL5atirRPF73W4f6AIkIp36zkS0os0RHuFK0bG2F
PnjQj+FpErd8zji8PVFQ2LZ0WPNLYP7g7BWxAoNct2q0Iw3TACY6h722Cq6WS9ZP4O2iv3kkpo4A
7JZvuf4yGGY2nVfx5nLZAmcEA9bZmHhcgmPLs2FBYpLYPs/5P4nd2HeiTJW+F6M75g9E4wG+sf3q
2zqzh+AmV5kHffWnGx2MPdUmyFPU80zcDzEpodVU73YUxJIJScwjjXzytuQSB2FcM0TMqwYUq2qG
VAPhBw4nnAxeScbMyMbFVDrNyMeejDhq</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2:Subject xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">jason.lyle88#gmail.com</saml2:NameID><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml2:SubjectConfirmationData NotOnOrAfter="2017-01-27T18:26:53.483Z" Recipient="https://apps.surdellpartners.com/ords/kohls_wmj/workamajig/okta/acs/"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="2017-01-27T18:16:53.483Z" NotOnOrAfter="2017-01-27T18:26:53.483Z" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:AudienceRestriction><saml2:Audience>http://localhost:8081/spring-security-saml2-sample/saml/metadata</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant="2017-01-27T18:21:53.483Z" SessionIndex="id1485541313483.890561695" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement><saml2:AttributeStatement xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"><saml2:Attribute Name="firstName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Jason</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name="lastName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Lyle</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>
Related
Actually I'm working on a project where I have a XML code And I want to do Digital Signature of that particular XML.
Can anyone help me in that?
O60\<?xml version="1.0" encoding="UTF-8" standalone="yes"?\>\<Otp uid="608559509930" ac="STGPDL0001" sa="STGPDL0001" ver="2.5" txn="AUANSDL001:20221219170045" lk="MKfOOHltrbemb9dlt1As6uaMC42HiP3F4Ar6CJTC7s8CoIJRY8U" ts="2022-12-19T17:00:45" type="A" xmlns="http://www.uidai.gov.in/authentication/otp/1.0"\>\<Opts ch="01"/\>
I have this block of code
and I want like this
O60<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Otp uid="608559509930" ac="STGPDL0001" sa="STGPDL0001" ver="2.5" txn="AUANSDL001:20221219162103" lk="MKfOOHltrbemb9dlt1As6uaMCniP3F4Ar6CJTC7s8CoIJRY8U" ts="2022-12-19T16:21:03" type="A" xmlns="http://www.uidai.gov.in/authentication/otp/1.0"><Opts ch="01"/> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>vWmg/+0CO6BsWzMlcne+FRPMGS8=</DigestValue></Reference></SignedInfo><SignatureValue>UD7m2YQStmwaUerMDEdJfhqtCl2V4EZAH6G4pNCYqW2YYTt58yviobqFwGQydDXWJlQFm4GNiMtIZiNwSRweTMPn5X7hvVysL+nIFjR80OaeshUzJbitGHzN4d7KEp3yUlGAhcS66kcrYjH4cS8wQ2X7vi9CgTQ2OIpih1hOp/55YhFvGrXP3brrGR6bkLlwPUbjo2eNsbGDQMnWRoJPT5dEPhdj2a1wRs4/aGyOg/57svlKjoYtM65LLbLYqt6Xqh3vUiemm/2S0Wg+/OptRuiF+QDIqQI/dzIr5zciXk6zHUdzCZgw8CioVcaCyn4bu11XUbxze0d/mgIxGB4Rg==</SignatureValue><KeyInfo><X509Data><X509SubjectName>OID.2.5.4.65=4497104453fb4a27896fb19cea63c468, L=AGRA, CN=DS PAISALO DIGITAL LIMITED, SERIALNUMBER=89507f93cc805e639f7fc0041c047f367b9b045535f94ddc07fbdef5d8f99fc5, S=UTTAR PRADESH, PostalCode=282002, Phone=52cc883ad0fc4dcf59e2e71efe6e1cfd46456a5493651902bd117e0f30232df8, OU=PAISALO DIGITAL LIMITED, O=PAISALO DIGITAL LIMITED, C=IN</X509SubjectName> <X509Certificate>encrypted string</X509Certificate> </X509Data></KeyInfo></Signature></Otp>
I also tried this with chilkat library but it did not work for me
if some one have any idea about any type of library which is compatible with android It will too good for me but if some one has solution in java, will also acceptable
Thanks
How to Sign XML request with DSC certificate in java?
You need a private key to perform the signing. I have added the code which could help you to sign and add the public certificate into the input XML. You need a keystore with private key and public certificate.
I used the following xml as an input for testing purpose.
<?xml version="1.0" encoding="UTF-8"?>
<Otp uid="608559509930" ac="STGPDL0001" sa="STGPDL0001" ver="2.5" txn="AUANSDL001:20221219170045" lk="MKfOOHltrbemb9dlt1As6uaMC42HiP3F4Ar6CJTC7s8CoIJRY8U" ts="2022-12-19T17:00:45" type="A">
<Opts ch="01" />
</Otp>
You don't need any external dependency to perform the signing. I just used java 8, created a test keystore with private key and certificate. Make sure to provide valid keystore file, keystore password and private key's pin.
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
public class Application {
private final static String KEYSTORE_FILE = "keystore.p12";
private final static String KEYSTORE_PASSWORD = "password";
private final static String PRIVATE_KEY_PIN = "password";
public static void main(String[] args) throws Exception {
String inputXML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n"
+ "<Otp uid=\"608559509930\" ac=\"STGPDL0001\" sa=\"STGPDL0001\" ver=\"2.5\" txn=\"AUANSDL001:20221219170045\" lk=\"MKfOOHltrbemb9dlt1As6uaMC42HiP3F4Ar6CJTC7s8CoIJRY8U\" ts=\"2022-12-19T17:00:45\" type=\"A\"><Opts ch=\"01\"/></Otp>";
System.out.println("Input XML:\n" + inputXML);
String signedXML = signXML(inputXML);
System.out.println("\nSigned XML:\n" + signedXML);
}
public static String signXML(String xml) throws Exception {
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
Reference ref = factory.newReference("", factory.newDigestMethod(DigestMethod.SHA1, null),
Collections.singletonList(factory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
null, null);
SignedInfo signedInfo = factory.newSignedInfo(
factory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null),
factory.newSignatureMethod(SignatureMethod.RSA_SHA1, null), Collections.singletonList(ref));
KeyStore ks = KeyStore.getInstance("pkcs12");
InputStream fis = new FileInputStream(KEYSTORE_FILE);
ks.load(fis, KEYSTORE_PASSWORD.toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey key = (PrivateKey) ks.getKey(alias, PRIVATE_KEY_PIN.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
X509Certificate cert = (X509Certificate) chain[0];
KeyInfoFactory keyInfoFactory = factory.getKeyInfoFactory();
List<Serializable> x509Content = new ArrayList<>();
x509Content.add(cert.getSubjectX500Principal().getName());
x509Content.add(cert);
X509Data x509Data = keyInfoFactory.newX509Data(x509Content);
KeyInfo keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data));
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Document doc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
DOMSignContext dsc = new DOMSignContext(key, doc.getDocumentElement());
XMLSignature signature = factory.newXMLSignature(signedInfo, keyInfo);
signature.sign(dsc);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
StringWriter writer = new StringWriter();
trans.transform(new DOMSource(doc), new StreamResult(writer));
return writer.toString();
}
}
Output after execution of above code: Signed XML
<?xml version="1.0" encoding="UTF-8"?><Otp ac="STGPDL0001" lk="MKfOOHltrbemb9dlt1As6uaMC42HiP3F4Ar6CJTC7s8CoIJRY8U" sa="STGPDL0001" ts="2022-12-19T17:00:45" txn="AUANSDL001:20221219170045" type="A" uid="608559509930" ver="2.5"><Opts ch="01"/><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>kVVjeAce1xEXIXqwDqOubFIjVxg=</DigestValue></Reference></SignedInfo><SignatureValue>KFA+Jcd+HmNzVL3LKmQ+E0B8EXnVSP8lFZyChBG74xU0T74827xuuHymCOrNhbffzBpzUJiibEhC
mtPPmMarvhRMtxtRzjyCaNetqDgRkesNV6fpeyHg4HIWuRvRsp1u+jchj3O04ng0TCbBxxK5C4Q4
TPEn/Rpa9YzEoDmmg1BstCvxRYK1naXdV63gBr1UhPfteVpHYY/bJbID/FldPNMIkJHHxzrY3CD6
mprroKD9svGbxvRFTh3aHqC3CJOoi+dkMaJiIwDlXHFoCqL1EutGWDbwJYNptDVhH9GuCA7P0jYW
wmSE1QOKeiJJMyrd3GExtRf79rCZCFHCBBbm/w==</SignatureValue><KeyInfo><X509Data><X509SubjectName>CN=Test,OU=NA,O=NA,L=NA,ST=NA,C=NA</X509SubjectName><X509Certificate>MIIDHDCCAgSgAwIBAgIEY62nqTANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJOQTELMAkGA1UE
CAwCTkExCzAJBgNVBAcMAk5BMQswCQYDVQQKDAJOQTELMAkGA1UECwwCTkExDTALBgNVBAMMBFRl
c3QwHhcNMjIxMjI5MTQ0MzUzWhcNMjMxMjI5MTQ0MzUzWjBQMQswCQYDVQQGEwJOQTELMAkGA1UE
CAwCTkExCzAJBgNVBAcMAk5BMQswCQYDVQQKDAJOQTELMAkGA1UECwwCTkExDTALBgNVBAMMBFRl
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRGXlJ/+Gj8f4ID0zv6QIxqEl7EenC
79jS6elE5Zqp7Iofhep7KWVeqhJmgSsOfg/MXL/ZCMrhFd8rMWdWnWkkRg4fGXm5VNa/FOXu7FYl
jcCmizxAHmhVT2ln7HDOOfZ75wyjfT5crz9KlofvqK+gAByQee9Bv5A1HorHxzM4t/6GW7Yc5gHp
l4CQ3m28c74m5PmTg7C0cMAL+fj1vQezMFpSQp2kehylwVPMy1+SH3cbkIFDPmOKffYjh245Q0vD
+c4yRcM6srP9lW2yO7UbYUl2cV3fKGw7V56u3hhu/5U6Udc7Gn/vWX4+rcY45ZP0kzQfWRVi6rFW
P94Fk/s5AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAGiUORJhosazNJOwpi02VYR0Oedpq3apUiFV
dhWvTcH43mcXPCgf+w8oahahkow3zJJZBCYQxQ2jUWU6a+smixrik/+QUZrheV0EHjqXOxVJTWUu
V62fpXy0AZQlSvsq4hDdgsmMnK/U2Kcx5DpDkqE5CBe/cMoarcOOGZUsokMp+GVbpq9ZR16KWoBd
uehy4RdYLOWao6DqmOrqgHJqe0DOjqQmb2Lsvuel8tq8t3imIa4wu+VVq/WDaPNk68kr2aB0B3ed
Q3sB8L+tucYuYCw2MEXwz1+da9xVORrhQ/pgGKFtXhgyqddMvPKAvIxZO0FUyk7ZMXJMeCCYb40L
1Mk=</X509Certificate></X509Data></KeyInfo></Signature></Otp>
Test keystore file
keystore.p12
I have created an method that digitally signs an xml (XAdES with Timestamping) using xades4j library https://github.com/luisgoncalves/xades4j
Unfortunately the xml's are quite big sometimes (1,8 GB) and I was wondering if there is a way to do that by streaming the XML instead of creating a DOM and loading the whole document in memory. Is there a way? Can I do that with xades4j?
Below is the current code that signs the document using a DOM representation of the xml. The initial method that is called first is the signXml().
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyStoreException;
import javax.annotation.PostConstruct;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.safe.AbstractManager;
import com.safe.model.DirectPasswordProvider;
import xades4j.algorithms.ExclusiveCanonicalXMLWithoutComments;
import xades4j.production.Enveloped;
import xades4j.production.SignatureAlgorithms;
import xades4j.production.XadesSigner;
import xades4j.production.XadesTSigningProfile;
import xades4j.providers.KeyingDataProvider;
import xades4j.providers.impl.FileSystemKeyStoreKeyingDataProvider;
import xades4j.providers.impl.HttpTsaConfiguration;
import xades4j.providers.impl.KeyStoreKeyingDataProvider;
import xades4j.utils.DOMHelper;
#Component
public class FileOperationsManager extends AbstractManager {
#Value("${certificates.digital-signature.filepath}")
private String certPath;
#Value("${certificates.digital-signature.password}")
private String certPass;
#Value("${certificates.digital-signature.type}")
private String certType;
private DocumentBuilder db;
private TransformerFactory tf;
#PostConstruct
public void init() throws Exception {
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
this.db = dbf.newDocumentBuilder();
this.tf = TransformerFactory.newInstance();
}
public Path signXml(final Path xmlFile, final Path targetDir) {
final String baseName = FilenameUtils.getBaseName(xmlFile.getFileName().toString())
.concat("_Signed")
.concat(".")
.concat(FilenameUtils.getExtension(xmlFile.getFileName().toString()));
final Path target = Paths.get(targetDir.toString(), baseName);
try (final FileInputStream fis = new FileInputStream(String.valueOf(xmlFile))) {
final Document doc = this.parseDocument(fis);
final Element elementToSign = doc.getDocumentElement();
final SignatureAlgorithms algorithms = new SignatureAlgorithms()
.withCanonicalizationAlgorithmForTimeStampProperties(new ExclusiveCanonicalXMLWithoutComments("ds", "xades"))
.withCanonicalizationAlgorithmForSignature(new ExclusiveCanonicalXMLWithoutComments());
final KeyingDataProvider kdp = this.createFileSystemKeyingDataProvider(certType, certPath, certPass, true);
final XadesSigner signer = new XadesTSigningProfile(kdp)
.withSignatureAlgorithms(algorithms)
.with(new HttpTsaConfiguration("http://timestamp.digicert.com"))
.newSigner();
new Enveloped(signer).sign(elementToSign);
this.exportDocument(doc, target);
} catch (final FileNotFoundException e) {
throw new RuntimeException();
} catch (final Exception e) {
throw new RuntimeException();
}
return target;
}
private FileSystemKeyStoreKeyingDataProvider createFileSystemKeyingDataProvider(
final String keyStoreType,
final String keyStorePath,
final String keyStorePwd,
final boolean returnFullChain) throws KeyStoreException {
return FileSystemKeyStoreKeyingDataProvider
.builder(keyStoreType, keyStorePath, KeyStoreKeyingDataProvider.SigningCertificateSelector.single())
.storePassword(new DirectPasswordProvider(keyStorePwd))
.entryPassword(new DirectPasswordProvider(keyStorePwd))
.fullChain(returnFullChain)
.build();
}
public Document parseDocument(final InputStream is) {
try {
final Document doc = this.db.parse(is);
final Element elem = doc.getDocumentElement();
DOMHelper.useIdAsXmlId(elem);
return doc;
} catch (final Exception e) {
throw new RuntimeException();
}
}
public void exportDocument(final Document doc, final Path target) {
try (final FileOutputStream out = new FileOutputStream(target.toFile())) {
this.tf.newTransformer().transform(
new DOMSource(doc),
new StreamResult(out));
} catch (final Exception e) {
throw new RuntimeException();
}
}
Unfortunately xades4j doesn't support streaming on the XML document to which the signature will be appended. I don't know if there are other alternative libraries that do.
A possible workaround using xades4j is to use a detached signature instead of an enveloped signature. The signature can be added to an empty XML document and the large XML file is explicitly added as a Reference to that signature.
xades4j delegates the core XML-DSIG handling to Apache Santuario, so if Santuario uses streaming for Reference resolution, this should avoid your issue. I'm not sure, though, but it may be worth testing.
https://github.com/luisgoncalves/xades4j/wiki/DefiningSignedResources
You may need to use a file URI and/or base URIs.
I am newbie to the WSO2 API Manager version 1.9.1. I wanted x-jwt-assertion to be decoded using the OpenSAML library (http://mvnrepository.com/artifact/org.opensaml/opensaml/2.6.4). I wanted same x-jwt-assertion (shown in this link Decode X-JWT-Assertion using axiom-api in java) to be decoded/parse, but when Implement below code I see following errors coming. Please guide.
Is this possible to decode x-jwt-assertion of WSO2 APIM (API manager) using OpenSAML ?
The code for reference:
Exception in thread "main" org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:257)
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:347)
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:121)
at com.mkyong.app.OpenSAMLDemo.main(OpenSAMLDemo.java:46)
The code for reference:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.codec.binary.Base64;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Response;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
public class OpenSAMLDemo {
public static void main(String[] args) throws IOException,
ParserConfigurationException, SAXException, ConfigurationException, UnmarshallingException {
Properties prop = new Properties();
prop.load(OpenSAMLDemo.class.getClassLoader().getResourceAsStream("jwtAssertion.properties"));
String responseMessage = prop.getProperty("jwt");
System.out.println(responseMessage);
Base64 base64 = new Base64();
byte[] base64DecodedResponse = base64.decode(responseMessage);
DefaultBootstrap.bootstrap();
ByteArrayInputStream is = new ByteArrayInputStream(base64DecodedResponse);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = docBuilder.parse(is);
Element element = document.getDocumentElement();
UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
XMLObject responseXmlObj = unmarshaller.unmarshall(element);
Response response = (Response) responseXmlObj;
Assertion assertion = response.getAssertions().get(0);
String subject = assertion.getSubject().getNameID().getValue();
System.out.println("SUBJECT : " + subject );
String issuer = assertion.getIssuer().getValue();
System.out.println("ISSUER : " + issuer);
String audience = assertion.getConditions().getAudienceRestrictions().get(0).getAudiences().get(0).getAudienceURI();
System.out.println("AUDIENCE : " + audience );
}
}
You're using WSO2 API Manager tool. API Manager gives X-JWT-Assertion which is different from WSO2 SAML Assertion. so OpenSAML library won't works. Because it's not developed to support the X-JWT-Assertion. In order to parse the X-JWT-Assertion, you need to use the axiom-api.
Please refer this site and solution provided.
Decode X-JWT-Assertion using axiom-api in java
I have a SOAP server. The SOAP request that is receiving at the server has ws security headers. Following are the main nodes of the request XML.
BinarySecurityToken (X509PKIPathv1 certificate)
DigestMethod
DigestValue
SignatureValue
SecurityTokenReference
Data (data that is send by
client in SOAP body)
I have to verify the request using the certificate (.cer files) which is provided by the client (sender of the request).
What are the steps to validate the requests? Please explain the concept. No libraries are available for doing this. After a long research I am able to match BinarySecurityToken with base64_encode($certFile) $certFile is the requester's certifiate. Now I am researching how to matchDigestValue with what.
WS-Security headers can be verified by the following. I have written an utility for that. Have a look at it.
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.security.KeyStore;
import java.security.Provider;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
public class WSUtil {
public static void main(String[] args) throws Exception {
String req = "SOAPMESSAGE";
Document p = createXMLDocument(req);
InputStream inStream = new FileInputStream("certificate.p12"); //Provide your certificate file
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inStream, "pass".toCharArray()); //Certificate password - pass
String alias = ks.aliases().nextElement();
X509Certificate certificate = (X509Certificate) ks.getCertificate(alias);
validateSignature(p.getElementsByTagName("ds:Signature").item(0),p.getElementsByTagName("soapenv:Body").item(0),certificate.getPublicKey());//True if the message is valid
}
public static Document createXMLDocument(String xmlString) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder;
Document document = null;
try {
builder = factory.newDocumentBuilder();
document = builder.parse(new InputSource(
new StringReader(xmlString)));
} catch (Exception e) {
throw e;
}
return document;
}
private static boolean validateSignature(Node signatureNode, Node bodyTag, PublicKey publicKey) {
boolean signatureIsValid = false;
try {
// Create a DOM XMLSignatureFactory that will be used to unmarshal the
// document containing the XMLSignature
String providerName = System.getProperty
("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI");
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",
(Provider) Class.forName(providerName).newInstance());
// Create a DOMValidateContext and specify a KeyValue KeySelector
// and document context
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(publicKey), signatureNode);
valContext.setIdAttributeNS((Element) bodyTag, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id");
// Unmarshal the XMLSignature.
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature.
signatureIsValid = signature.validate(valContext);
} catch (Exception ex) {
ex.printStackTrace();
}
return signatureIsValid;
}
}
Note
You have to provide the SOAP message as it is. You shouldn't do any XML format or any empty space somewhere. The security added SOAP message is very sensitive. Even a space at the end will make the SOAP message as invalid.
X509KeySelector constructor is not correct type
I have some classes that already use DOM4J to read XML files and provide
getter methods to the data. Now, I need to add the possibility of checking XML digital
signatures.
Using org.w3c.dom and following http://java.sun.com/developer/technicalArticles/xml/dig_signature_api/
everything works correctly.
So, I try to use DOMWriter to convert from org.dom4j.Document to
org.w3c.dom.Document, but after this the signature validation doesn't work. I think it
happens because DOMWiter is changing the XML tree (as doc4.asXML() seems to show).
I try to find something to set in order to mantain the integrity of the document, but
DOMWriter don't have such methods.
Below is the code demonstrating the asymmetric conversion.
The file used for tests is http://www.robertodiasduarte.com.br/files/nfe/131090007910044_v1.10-procNFe.xml
Does someone know reasons/workarounds to this?
Thanks (and sorry my poor english).
package testevalidanfe;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import javax.swing.JOptionPane;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.dom4j.io.XMLWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class Testevalidanfe {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.parse("exemplo-nfe.xml");
Node no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
DOMValidateContext valContext = new DOMValidateContext(new X509KeySelector(), no);
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
JOptionPane.showMessageDialog(null, "Validation using org.w3c.dom: " + signature.validate(valContext));
org.dom4j.io.DOMReader domreader = new org.dom4j.io.DOMReader();
org.dom4j.Document doc4 = domreader.read(d);
org.dom4j.io.DOMWriter domwriter = new org.dom4j.io.DOMWriter();
d = domwriter.write(doc4);
String after = doc4.asXML();
PrintWriter writer = new PrintWriter(new File("after-convertion.xml"));
writer.print(after);
writer.close();
no = d.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
valContext = new DOMValidateContext(new X509KeySelector(), no);
fac = XMLSignatureFactory.getInstance("DOM");
signature = fac.unmarshalXMLSignature(valContext);
JOptionPane.showMessageDialog(null, "Validation after convert: " + signature.validate(valContext));
}
}
package testevalidanfe;
import java.security.Key;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.X509Data;
public class X509KeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo,
KeySelector.Purpose purpose,
AlgorithmMethod method,
XMLCryptoContext context)
throws KeySelectorException {
Iterator ki = keyInfo.getContent().iterator();
while (ki.hasNext()) {
XMLStructure info = (XMLStructure) ki.next();
if (!(info instanceof X509Data))
continue;
X509Data x509Data = (X509Data) info;
Iterator xi = x509Data.getContent().iterator();
while (xi.hasNext()) {
Object o = xi.next();
if (!(o instanceof X509Certificate))
continue;
final PublicKey key = ((X509Certificate)o).getPublicKey();
if (algEquals(method.getAlgorithm(), key.getAlgorithm())) {
return new KeySelectorResult() {
public Key getKey() { return key; }
};
}
}
}
throw new KeySelectorException("No key found!");
}
static boolean algEquals(String algURI, String algName) {
if ((algName.equalsIgnoreCase("DSA") &&
algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) ||
(algName.equalsIgnoreCase("RSA") &&
algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))) {
return true;
} else {
return false;
}
}
}
For example, if the original XML starts with:
<nfeProc versao="1.10" xmlns="http://www.portalfiscal.inf.br/nfe">
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
<infNFe Id="NFe31090807301671000131550010001000216008030809" versao="1.10" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
doc4.asXML() return this:
<nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.10">
<NFe>
<infNFe xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Id="NFe31090807301671000131550010001000216008030809" versao="1.10">
...
I had a closer look at this, and it turns out that DOM4J DOMWriter is doing something odd w.r.t. namespaces that obviously confuses the canonicalization process. I haven't pin pointed the exact reason, but I think it has to do with DOMWriter inserting extra xmlns attributes in the DOM elements. You can see the effect if you turn on logging for the XML digital signature API (as described in the article you refer to), the canonicalized <SignedInfo> element lacks namespace declaration in the DOM document produced by DOM4J.
However, instead of using DOMWriter, you can produce a DOM document by transformation, using a DOM4J DocumentSource and a DOMResult.
/**
* Create a DOM document from a DOM4J document
*/
static Document copy(org.dom4j.Document orig) {
try {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
DOMResult result = new DOMResult();
t.transform(new DocumentSource(orig), result);
return (Document) result.getNode();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Using the resulting DOM document, the validation works.