Hello,
I’m working on code which should first encrypt Body element content (xml is
a soap message) and then sign this encrypted xml. I’ve noticed a strange
behavior. I’ve prepared a unit tests to show where the problem is:
public class MyTest extends org.junit.Assert
{
String
mainKey="MIIC+DCCAeSgAwIBAgIQAw19soViIYFA0LfhGDibeTAJBgUrDgMCHQUAMBUxEzARBgNVBAMTClJvb3RDQVRlc3QwHhcNMTQwNDE0MDczMzUyWhcNMzkxMjMxMjM1OTU5WjAXMRUwEwYDVQQDEwxyb2JvY2lrUHJhY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQClHbEhJZiBIjWW0pc2Efpns/xTxXvSl11NlNGqCd6teCVpJd/PAySG/f8oUFyf3LW7wHLhKGbAtg1Lf4pO4KJbWR9BxFyuImwtIqtLU7YAVnPkIpOmn1QFul8KqUl4W5pklh+8HLm7qZHZWOsF0y6gGy4cN2rI/yiSy+65ee7sQUCVWk9AKFoexyf5V3x2WnDZr22BOCrxfMnyhk8Exxj2AsLSt+q94BKzmdh9hVOcvaZivmMrqgRCrF9HZZtF3cek2VFkXIRiOVkiDqKl++k611nKtroeKTRMeJr3seowh6ZfYKdMluf/v3YwDrhEvAzX9wUxWTqxML3Ve0NGPTSJAgMBAAGjSjBIMEYGA1UdAQQ/MD2AEDuq/TTQAqG/IzsZKVWFIF6hFzAVMRMwEQYDVQQDEwpSb290Q0FUZXN0ghAE58iucYM4tULQsZhppop1MAkGBSsOAwIdBQADggEBABy5DeYmLY5L2Zp5JeiwNov1oteej1h01j8fSHjj2JgTiITAHRI4IVcKpa/r4zSYXrtnXsrfmmMZorz6viFBpog4f6BmNIQTzaW1CNEvbaRAEr0U0NRc5gfDL/albdnx8cFu+4QZ9elQd7s7Xf5De0ceC34m1k9gRyzn5axNf7BwSxLeHp1PbFDQPIKDtzNF0/bvoJmDVQi8pGEJIp/LhWeQ7vWNZovGpFmlvuDmOsZp4U+m3/AcRZlKfHn5naWKZUYRMR7VE0UMnirJ3Lfc2r6Ur0AHqQ5PHb7YqgGL3RZSGdh30JUYPlbsyU+iaNRRiQswDZnmgmqHyg/9Q8XJtU0=";
SecretKey secretKey;
PublicKey pk;
String xml="<v:Envelope xmlns:v=\"
http://www.w3.org/2003/05/soap-envelope\" xmlns:i=\"
http://www.w3.org/2001/XMLSchema-instance\" xmlns:d=\"
http://www.w3.org/2001/XMLSchema\" xmlns:c=\"
http://www.w3.org/2003/05/soap-encoding\" xmlns:u=\"
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"
xmlns:a=\"http://www.w3.org/2005/08/addressing\" xmlns:s=\"
http://www.w3.org/2003/05/soap-envelope\"><v:Header><n0:Action xmlns:n0=\"
http://www.w3.org/2005/08/addressing\" v:mustUnderstand=\"1\"
u:Id=\"uuid-5061ec6e-f05b-4c3f-960d-815ee74ba8ad\">
http://tempuri.org/IWS_StarService/HelloWorld</n0:Action><n1:MessageID
xmlns:n1=\"http://www.w3.org/2005/08/addressing\"
u:Id=\"uuid-a5a15cfa-21cb-4842-b9b3-83ca109cb4ca\">urn:uuid:814a0516-6d5c-4812-86dc-7f3f5d0b2ed2</n1:MessageID><n2:ReplyTo
xmlns:n2=\"http://www.w3.org/2005/08/addressing\"
u:Id=\"uuid-5f957f93-d071-440c-9278-24b79fd8c611\"><n2:Address>
http://www.w3.org/2005/08/addressing/anonymous</n2:Address></n2:ReplyTo><n3:To
xmlns:n3=\"http://www.w3.org/2005/08/addressing\" v:mustUnderstand=\"1\"
u:Id=\"uuid-01269a2c-cf46-49bf-97ec-bf6bc6e248b6\">
http://localhost:4774/API/WS_StarService.svc/Test</n3:To><Security xmlns=\"
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"
s:mustUnderstand=\"1\"><u:Timestamp
u:Id=\"uuid-3367eb13-35c7-4b60-8ea9-a83e31de989f\"><u:Created>2014-11-29T08:12:50.297Z</u:Created><u:Expires>2014-11-29T14:12:50.297Z</u:Expires></u:Timestamp></Security></v:Header><v:Body
u:Id=\"_0\"><n4:HelloWorld xmlns:n4=\"http://tempuri.org/\"><n4:message>my
message</n4:message></n4:HelloWorld></v:Body></v:Envelope>";
public MyTest() throws NoSuchAlgorithmException, CertificateException,
Base64DecodingException {
InputStream mainKeyStream = new
ByteArrayInputStream(Base64.decode(mainKey) );
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert =
(X509Certificate)cf.generateCertificate(mainKeyStream);
pk =cert.getPublicKey();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
secretKey = keyGen.generateKey();
org.apache.xml.security.Init.init();
}
@org.junit.Test
public void test_sign_document_after_writing_and_reading_from_stream()
throws Exception {
Document doc1=signDocument(false);//DIFFERENCE IN THE LAST
PARAMETER!
//here we first write xml to stream and read it again. This
simulates sending soap message and receiving it on the another side
InputStream outputStream = documentToStream(doc1);
doc1=streamToDocument(outputStream);
boolean isValid1=ensureValidSignature(doc1,secretKey);
assertTrue(isValid1);
}
@org.junit.Test
public void
test_sign_document_with_additional_NS_after_writing_and_reading_from_stream()
throws Exception {
Document doc1=signDocument(true);//DIFFERENCE IN THE LAST PARAMETER!
//here we first write xml to stream and read it again. This
simulates sending soap message and receiving it on the another side
InputStream outputStream = documentToStream(doc1);
doc1=streamToDocument(outputStream);
boolean isValid1=ensureValidSignature(doc1,secretKey);
assertTrue(isValid1);
}
@org.junit.Test
public void test_sign_document() throws Exception {
Document doc1=signDocument(false);//DIFFERENCE IN THE LAST
PARAMETER!
boolean isValid1=ensureValidSignature(doc1,secretKey);
assertTrue(isValid1);
}
@org.junit.Test
public void test_sign_document_with_additional_NS() throws Exception {
Document doc1=signDocument(true);//DIFFERENCE IN THE LAST PARAMETER!
boolean isValid1=ensureValidSignature(doc1,secretKey);
assertTrue(isValid1);
}
Document signDocument(boolean addNamespaceAttribute) throws Exception {
InputStream stream = new ByteArrayInputStream(xml.getBytes() );
Document doc=streamToDocument(stream);
NodeList nodes = doc.getElementsByTagNameNS("
http://www.w3.org/2003/05/soap-envelope","Body");
Element bodyElement= (Element) nodes.item(0);
bodyElement.setAttributeNS("
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
"u:Id", "_0");
bodyElement.setIdAttributeNS("
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
"Id", true);
XMLCipher clipper = XMLCipher.getInstance(XMLCipher.RSA_v1dot5);
clipper.init(XMLCipher.WRAP_MODE,pk);
clipper.setKEK(pk);
EncryptedKey encryptedKey=clipper.encryptKey(doc,secretKey);
Element encryptedKeyElement=clipper.martial(encryptedKey);
Element securityElement= (Element) doc.getElementsByTagNameNS("
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
","Security").item(0);
securityElement.appendChild(encryptedKeyElement);
clipper = XMLCipher.getInstance(XMLCipher.AES_128);
clipper.init(XMLCipher.ENCRYPT_MODE,secretKey);
clipper.setKEK(secretKey);
EncryptedData encryptedBody=clipper.getEncryptedData();
String encryptedBodyId="uuid-"+ UUID.randomUUID().toString();
bodyElement= (Element) doc.getElementsByTagNameNS("
http://www.w3.org/2003/05/soap-envelope","Body").item(0);
encryptedBody.setId(encryptedBodyId);
KeyInfo keyInfo = new KeyInfo(doc);
encryptedBody.setKeyInfo(keyInfo);
Element securityTokenReferenceElement=doc.createElementNS("
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
","SecurityTokenReference");
securityTokenReferenceElement.setAttributeNS("
http://www.w3.org/2000/xmlns/","xmlns","
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
");
securityTokenReferenceElement.setAttributeNS("
http://www.w3.org/2000/xmlns/","xmlns:k","
http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd");
securityTokenReferenceElement.setAttributeNS("
http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd
","k:TokenType","
http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey
");
keyInfo.addUnknownElement(securityTokenReferenceElement);
if(addNamespaceAttribute)
{
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! THIS IS ONLY ONE DIFFERENCE.
WE ADD A NAMESPACE ATTRIBUTE EXPLICITLY
Element keyInfoElement=keyInfo.getElement();
keyInfoElement.setAttributeNS("http://www.w3.org/2000/xmlns/
","xmlns:ds","http://www.w3.org/2000/09/xmldsig#");
}
clipper.doFinal(doc,bodyElement,true);
//now sign xml
XMLSignature sig = new XMLSignature(doc, "",
XMLSignature.ALGO_ID_MAC_HMAC_SHA1,
Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
org.apache.xml.security.transforms.Transforms transforms = new
org.apache.xml.security.transforms.Transforms(doc);
transforms.addTransform(org.apache.xml.security.transforms.Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
sig.addDocument("#_0", transforms);
Element elemt=sig.getElement();
securityElement.appendChild(elemt);
sig.sign(secretKey);
return doc;
}
Document streamToDocument(InputStream stream) throws
ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
DocumentBuilder builder = f.newDocumentBuilder();
builder.setErrorHandler(new
org.apache.xml.security.utils.IgnoreAllErrorHandler());
Document doc = builder.parse(new BufferedInputStream(stream));
return doc;
}
InputStream documentToStream(Document doc) throws TransformerException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Source xmlSource = new DOMSource(doc);
javax.xml.transform.Result outputTarget = new
StreamResult(outputStream);
TransformerFactory.newInstance().newTransformer().transform(xmlSource,
outputTarget);
InputStream is = new
ByteArrayInputStream(outputStream.toByteArray());
return is;
}
boolean ensureValidSignature(Document doc,SecretKey secretKey) throws
XPathExpressionException, XMLSecurityException {
NodeList
signs1=doc.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature");
if(signs1.getLength()>0)
{
NodeList nodes = doc.getElementsByTagNameNS("
http://www.w3.org/2003/05/soap-envelope", "Body");
Element bodyElement= (Element) nodes.item(0);
bodyElement.setIdAttributeNS("
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
"Id", true);
Element signElem= (Element) signs1.item(0);
XMLSignature signature = new XMLSignature(signElem, "test");
boolean isValid=signature.checkSignatureValue(secretKey);
return isValid;
}
return false;
}
}
Entire code is quite long but the problem is easy to explain. So there are
4 tests. 3 of them pass, 1 fails (at least on my machine). Basically when I
create a DOM Document, encrypt and sign it and then verify a signature then
everything works great (test: test_sign_document and
test_sign_document_with_additional_NS). But when I first write the signed
DOM document to stream and then read it again (and create a DOM Document)
and then I verify a signature then results are different. Two additional
tests shows this situation and as you can see one test pass and second
fails. When you see how xml looks in both tests (after writing them to file
or String variable) then you will notice that they are almost the same (I
mean the Body part). In both xml (after serialization)
Envelope\Body\EncryptedData\KeyInfo there is a namespace attribute (
http://www.w3.org/2000/09/xmldsig#). But code to calculate signature create
a digest value without this namespace attribute. In other words when you
compare testes: test_sign_document_after_writing_and_reading_from_stream
and
test_sign_document_with_additional_NS_after_writing_and_reading_from_stream
you will see that the only difference is that in working tests I add this
attribute manually:
if(addNamespaceAttribute)
{
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! THIS IS ONLY ONE DIFFERENCE. WE ADD A
NAMESPACE ATTRIBUTE EXPLICITLY
Element keyInfoElement=keyInfo.getElement();
keyInfoElement.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:ds","
http://www.w3.org/2000/09/xmldsig#");
}
But as I mentioned after writing xml to String this attribute is in both
xml content. From my point of view I shouldn't add this namespace attribute
to generate a valid signature.
Is this a bug?
Thanks
Romek