Skip to content

Commit 45340f0

Browse files
committed
added high-level formatting tests for signature and encryption
1 parent de75c28 commit 45340f0

File tree

5 files changed

+720
-0
lines changed

5 files changed

+720
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.xml.security.test.dom.encryption;
20+
21+
import org.apache.xml.security.Init;
22+
import org.apache.xml.security.encryption.EncryptedData;
23+
import org.apache.xml.security.encryption.EncryptedKey;
24+
import org.apache.xml.security.encryption.XMLCipher;
25+
import org.apache.xml.security.formatting.FormattingChecker;
26+
import org.apache.xml.security.formatting.FormattingCheckerFactory;
27+
import org.apache.xml.security.formatting.FormattingTest;
28+
import org.apache.xml.security.keys.KeyInfo;
29+
import org.apache.xml.security.test.dom.DSNamespaceContext;
30+
import org.apache.xml.security.test.dom.TestUtils;
31+
import org.apache.xml.security.utils.XMLUtils;
32+
import org.junit.jupiter.api.Test;
33+
import org.w3c.dom.Document;
34+
import org.w3c.dom.Element;
35+
import org.w3c.dom.NodeList;
36+
37+
import javax.crypto.spec.SecretKeySpec;
38+
import javax.xml.xpath.XPath;
39+
import javax.xml.xpath.XPathConstants;
40+
import javax.xml.xpath.XPathFactory;
41+
import java.io.ByteArrayInputStream;
42+
import java.io.ByteArrayOutputStream;
43+
import java.io.IOException;
44+
import java.io.InputStream;
45+
import java.nio.charset.StandardCharsets;
46+
import java.security.GeneralSecurityException;
47+
import java.security.Key;
48+
import java.security.KeyStore;
49+
import java.security.cert.Certificate;
50+
import java.util.Map;
51+
import java.util.Random;
52+
53+
import static org.junit.jupiter.api.Assertions.*;
54+
55+
/**
56+
* This is a {@link FormattingTest}, it is expected to be run with different system properties
57+
* to check various formatting configurations.
58+
*
59+
* The test uses AES-256-GCM encryption with RSA-OAEP key wrapping to generate a document containing encrypted data
60+
* and data encryption key.
61+
*/
62+
@FormattingTest
63+
public class EncryptionFormattingTest {
64+
private final Random random = new Random();
65+
private final FormattingChecker formattingChecker;
66+
private KeyStore keyStore;
67+
private XPath xpath;
68+
69+
public EncryptionFormattingTest() throws Exception {
70+
Init.init();
71+
formattingChecker = FormattingCheckerFactory.getFormattingChecker();
72+
keyStore = KeyStore.getInstance("PKCS12");
73+
try (InputStream in = getClass()
74+
.getResourceAsStream("/org/apache/xml/security/samples/input/rsa.p12")) {
75+
keyStore.load(in, "xmlsecurity".toCharArray());
76+
} catch (IOException | GeneralSecurityException e) {
77+
fail("Cannot load test keystore", e);
78+
}
79+
80+
XPathFactory xPathFactory = XPathFactory.newInstance();
81+
xpath = xPathFactory.newXPath();
82+
xpath.setNamespaceContext(new DSNamespaceContext(Map.of(
83+
"xenc", "http://www.w3.org/2001/04/xmlenc#"
84+
)));
85+
}
86+
87+
@Test
88+
public void testEncryptedFormatting() throws Exception {
89+
/* this test checks formatting of base64binary values */
90+
byte[] testData = new byte[128]; // long enough for line breaks
91+
random.nextBytes(testData);
92+
93+
Document doc = createDocument(testData);
94+
95+
String str;
96+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
97+
XMLUtils.outputDOM(doc, baos);
98+
str = baos.toString(StandardCharsets.UTF_8);
99+
formattingChecker.checkDocument(str);
100+
}
101+
102+
NodeList elements = (NodeList) xpath.evaluate("//xenc:CipherData", doc, XPathConstants.NODESET);
103+
assertEquals(2, elements.getLength());
104+
formattingChecker.checkBase64Value(elements.item(0).getTextContent());
105+
formattingChecker.checkBase64Value(elements.item(1).getTextContent());
106+
}
107+
108+
@Test
109+
public void testEncryptDecrypt() throws Exception {
110+
/* this test ensures that the encrypted data can be processed with various formatting settings */
111+
byte[] testData = new byte[128]; // long enough for line breaks
112+
random.nextBytes(testData);
113+
114+
Document doc = createDocument(testData);
115+
Element encryptedKeyElement =
116+
(Element) xpath.evaluate("//xenc:EncryptedKey[1]", doc, XPathConstants.NODE);
117+
Element encryptedDataElement =
118+
(Element) xpath.evaluate("//xenc:EncryptedData[1]", doc, XPathConstants.NODE);
119+
120+
Key kek = keyStore.getKey("test", "xmlsecurity".toCharArray());
121+
XMLCipher keyCipher = XMLCipher.getInstance("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
122+
null, "http://www.w3.org/2001/04/xmlenc#sha512");
123+
keyCipher.init(XMLCipher.UNWRAP_MODE, kek);
124+
EncryptedKey encryptedKey = keyCipher.loadEncryptedKey(doc, encryptedKeyElement);
125+
Key sessionKey = keyCipher.decryptKey(encryptedKey, "http://www.w3.org/2009/xmlenc11#aes256-gcm");
126+
127+
XMLCipher dataCipher = XMLCipher.getInstance("http://www.w3.org/2009/xmlenc11#aes256-gcm");
128+
dataCipher.init(XMLCipher.DECRYPT_MODE, sessionKey);
129+
byte[] decryptedData = dataCipher.decryptToByteArray(encryptedDataElement);
130+
131+
assertArrayEquals(testData, decryptedData);
132+
}
133+
134+
private Key generateSessionKey() {
135+
byte[] keyBytes = new byte[32];
136+
random.nextBytes(keyBytes);
137+
return new SecretKeySpec(keyBytes, "AES");
138+
}
139+
140+
private Document createDocument(byte[] data) throws Exception {
141+
Document doc = TestUtils.newDocument();
142+
Key sessionKey = generateSessionKey();
143+
Certificate cert = keyStore.getCertificate("test");
144+
145+
XMLCipher keyCipher = XMLCipher.getInstance("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p",
146+
null, "http://www.w3.org/2001/04/xmlenc#sha512");
147+
keyCipher.init(XMLCipher.WRAP_MODE, cert.getPublicKey());
148+
EncryptedKey encryptedKey = keyCipher.encryptKey(doc, sessionKey);
149+
150+
XMLCipher dataCipher = XMLCipher.getInstance("http://www.w3.org/2009/xmlenc11#aes256-gcm");
151+
dataCipher.init(XMLCipher.ENCRYPT_MODE, sessionKey);
152+
153+
EncryptedData builder = dataCipher.getEncryptedData();
154+
KeyInfo builderKeyInfo = builder.getKeyInfo();
155+
if (builderKeyInfo == null) {
156+
builderKeyInfo = new KeyInfo(doc);
157+
builder.setKeyInfo(builderKeyInfo);
158+
}
159+
builderKeyInfo.add(encryptedKey);
160+
161+
EncryptedData encData = dataCipher.encryptData(doc, null, new ByteArrayInputStream(data));
162+
Element encDataElement = dataCipher.martial(encData);
163+
164+
doc.appendChild(encDataElement);
165+
166+
return doc;
167+
}
168+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.xml.security.test.dom.signature;
20+
21+
import org.apache.xml.security.Init;
22+
import org.apache.xml.security.formatting.FormattingChecker;
23+
import org.apache.xml.security.formatting.FormattingCheckerFactory;
24+
import org.apache.xml.security.formatting.FormattingTest;
25+
import org.apache.xml.security.signature.XMLSignature;
26+
import org.apache.xml.security.signature.XMLSignatureByteInput;
27+
import org.apache.xml.security.signature.XMLSignatureInput;
28+
import org.apache.xml.security.test.dom.DSNamespaceContext;
29+
import org.apache.xml.security.test.dom.TestUtils;
30+
import org.apache.xml.security.utils.Constants;
31+
import org.apache.xml.security.utils.ElementProxy;
32+
import org.apache.xml.security.utils.XMLUtils;
33+
import org.apache.xml.security.utils.resolver.ResourceResolverContext;
34+
import org.apache.xml.security.utils.resolver.ResourceResolverException;
35+
import org.apache.xml.security.utils.resolver.ResourceResolverSpi;
36+
import org.junit.jupiter.api.Test;
37+
import org.w3c.dom.Document;
38+
import org.w3c.dom.Element;
39+
import org.w3c.dom.Node;
40+
41+
import javax.xml.crypto.dsig.DigestMethod;
42+
import javax.xml.xpath.XPath;
43+
import javax.xml.xpath.XPathConstants;
44+
import javax.xml.xpath.XPathExpressionException;
45+
import javax.xml.xpath.XPathFactory;
46+
import java.io.ByteArrayOutputStream;
47+
import java.io.IOException;
48+
import java.io.InputStream;
49+
import java.nio.charset.StandardCharsets;
50+
import java.security.*;
51+
import java.security.cert.X509Certificate;
52+
53+
import static org.junit.jupiter.api.Assertions.assertTrue;
54+
import static org.junit.jupiter.api.Assertions.fail;
55+
56+
/**
57+
* This is a {@link FormattingTest}, it is expected to be run with different system properties
58+
* to check various formatting configurations.
59+
*
60+
* The test creates a detached signature with a single reference and uses mock resource resolver.
61+
* RSA-2048 and SHA-512 are used to create longer binary values.
62+
*/
63+
@FormattingTest
64+
public class SignatureFormattingTest {
65+
private final static byte[] MOCK_DATA = new byte[]{ 0x0a, 0x0b, 0x0c, 0x0d };
66+
67+
private final FormattingChecker formattingChecker;
68+
private KeyStore keyStore;
69+
private XPath xpath;
70+
private ResourceResolverSpi resolver;
71+
72+
public SignatureFormattingTest() throws Exception {
73+
Init.init();
74+
ElementProxy.setDefaultPrefix(Constants.SignatureSpecNS, "ds");
75+
formattingChecker = FormattingCheckerFactory.getFormattingChecker();
76+
keyStore = KeyStore.getInstance("PKCS12");
77+
try (InputStream in = getClass()
78+
.getResourceAsStream("/org/apache/xml/security/samples/input/rsa.p12")) {
79+
keyStore.load(in, "xmlsecurity".toCharArray());
80+
} catch (IOException | GeneralSecurityException e) {
81+
fail("Cannot load test keystore", e);
82+
}
83+
84+
resolver = new TestResourceResolver(MOCK_DATA);
85+
86+
XPathFactory xPathFactory = XPathFactory.newInstance();
87+
xpath = xPathFactory.newXPath();
88+
xpath.setNamespaceContext(new DSNamespaceContext());
89+
}
90+
91+
@Test
92+
public void testSignatureFormatting() throws Exception {
93+
/* this test checks formatting of base64Binary values */
94+
Document doc = createDocument();
95+
96+
String docStr;
97+
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
98+
XMLUtils.outputDOM(doc, out);
99+
out.flush();
100+
docStr = out.toString(StandardCharsets.UTF_8);
101+
}
102+
103+
formattingChecker.checkDocument(docStr);
104+
105+
XPathFactory xPathFactory = XPathFactory.newInstance();
106+
XPath xpath = xPathFactory.newXPath();
107+
xpath.setNamespaceContext(new DSNamespaceContext());
108+
109+
Element digest = findElementByXpath("//ds:DigestValue[1]", doc);
110+
formattingChecker.checkBase64Value(digest.getTextContent());
111+
112+
Element signatureValue = findElementByXpath("//ds:SignatureValue[1]", doc);
113+
formattingChecker.checkBase64ValueWithSpacing(signatureValue.getTextContent());
114+
115+
Element x509certValue = findElementByXpath("//ds:X509Certificate[1]", doc);
116+
formattingChecker.checkBase64ValueWithSpacing(x509certValue.getTextContent());
117+
}
118+
119+
@Test
120+
public void testSignVerify() throws Exception {
121+
/* this test checks the signature can be verified with given formatting settings */
122+
Document doc = createDocument();
123+
Element signatureElement = findElementByXpath("//ds:Signature[1]", doc);
124+
XMLSignature signature = new XMLSignature(signatureElement, null);
125+
signature.addResourceResolver(resolver);
126+
127+
PublicKey publicKey = keyStore.getCertificate("test").getPublicKey();
128+
assertTrue(signature.checkSignatureValue(publicKey));
129+
}
130+
131+
private Document createDocument() throws Exception {
132+
Document doc = TestUtils.newDocument();
133+
134+
XMLSignature signature = new XMLSignature(doc, null, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512);
135+
signature.addResourceResolver(resolver);
136+
137+
signature.addDocument("some.resource", null, DigestMethod.SHA512);
138+
139+
PrivateKey privateKey = (PrivateKey) keyStore.getKey("test", "xmlsecurity".toCharArray());
140+
X509Certificate certificate = (X509Certificate) keyStore.getCertificate("test");
141+
142+
signature.addKeyInfo(certificate);
143+
signature.sign(privateKey);
144+
145+
doc.appendChild(signature.getElement());
146+
147+
return doc;
148+
}
149+
150+
private Element findElementByXpath(String expression, Node node) throws XPathExpressionException {
151+
return (Element) xpath.evaluate(expression, node, XPathConstants.NODE);
152+
}
153+
154+
/**
155+
* Resolver implementation which resolves every URI to the same given mock data.
156+
*/
157+
private static class TestResourceResolver extends ResourceResolverSpi {
158+
private byte[] mockData;
159+
160+
/**
161+
* Creates new resolver.
162+
* @param mockData Mock data bytes
163+
*/
164+
public TestResourceResolver(byte[] mockData) {
165+
this.mockData = mockData;
166+
}
167+
168+
@Override
169+
public XMLSignatureInput engineResolveURI(ResourceResolverContext context) throws ResourceResolverException {
170+
return new XMLSignatureByteInput(mockData);
171+
}
172+
173+
@Override
174+
public boolean engineCanResolveURI(ResourceResolverContext context) {
175+
return true;
176+
}
177+
}
178+
}

0 commit comments

Comments
 (0)