mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-19 02:24:40 +02:00
8241306: Add SignatureMethodParameterSpec subclass for RSASSA-PSS params
Reviewed-by: mullan
This commit is contained in:
parent
b14e0ee4d8
commit
8dbf7aa1f9
11 changed files with 1210 additions and 161 deletions
|
@ -24,7 +24,7 @@
|
|||
/**
|
||||
* @test
|
||||
* @bug 4635230 6283345 6303830 6824440 6867348 7094155 8038184 8038349 8046949
|
||||
* 8046724 8079693 8177334 8205507 8210736 8217878
|
||||
* 8046724 8079693 8177334 8205507 8210736 8217878 8241306
|
||||
* @summary Basic unit tests for generating XML Signatures with JSR 105
|
||||
* @modules java.base/sun.security.util
|
||||
* java.base/sun.security.x509
|
||||
|
@ -108,7 +108,7 @@ public class GenerationTests {
|
|||
rsaSha1, rsaSha224, rsaSha256, rsaSha384, rsaSha512,
|
||||
ecdsaSha1, ecdsaSha224, ecdsaSha256, ecdsaSha384, ecdsaSha512,
|
||||
hmacSha1, hmacSha224, hmacSha256, hmacSha384, hmacSha512,
|
||||
rsaSha1mgf1, rsaSha224mgf1, rsaSha256mgf1, rsaSha384mgf1, rsaSha512mgf1;
|
||||
rsaSha1mgf1, rsaSha224mgf1, rsaSha256mgf1, rsaSha384mgf1, rsaSha512mgf1, rsaShaPSS;
|
||||
private static DigestMethod sha1, sha224, sha256, sha384, sha512,
|
||||
sha3_224, sha3_256, sha3_384, sha3_512;
|
||||
private static KeyInfo dsa1024, dsa2048, rsa, rsa1024, rsa2048,
|
||||
|
@ -214,7 +214,8 @@ public class GenerationTests {
|
|||
SignatureMethod.RSA_SHA256,
|
||||
SignatureMethod.ECDSA_SHA256,
|
||||
SignatureMethod.HMAC_SHA256,
|
||||
SignatureMethod.SHA256_RSA_MGF1);
|
||||
SignatureMethod.SHA256_RSA_MGF1,
|
||||
SignatureMethod.RSA_PSS);
|
||||
|
||||
private static final String[] allSignatureMethods
|
||||
= Stream.of(SignatureMethod.class.getDeclaredFields())
|
||||
|
@ -246,9 +247,9 @@ public class GenerationTests {
|
|||
})
|
||||
.toArray(String[]::new);
|
||||
|
||||
// As of JDK 11, the number of defined algorithms are...
|
||||
// As of JDK 17, the number of defined algorithms are...
|
||||
static {
|
||||
if (allSignatureMethods.length != 22
|
||||
if (allSignatureMethods.length != 23
|
||||
|| allDigestMethods.length != 9) {
|
||||
System.out.println(Arrays.toString(allSignatureMethods));
|
||||
System.out.println(Arrays.toString(allDigestMethods));
|
||||
|
@ -335,6 +336,7 @@ public class GenerationTests {
|
|||
test_create_signature_enveloping_sha512_rsa_sha256_mgf1();
|
||||
test_create_signature_enveloping_sha512_rsa_sha384_mgf1();
|
||||
test_create_signature_enveloping_sha512_rsa_sha512_mgf1();
|
||||
test_create_signature_enveloping_sha512_rsa_pss();
|
||||
test_create_signature_reference_dependency();
|
||||
test_create_signature_with_attr_in_no_namespace();
|
||||
test_create_signature_with_empty_id();
|
||||
|
@ -531,6 +533,7 @@ public class GenerationTests {
|
|||
rsaSha256mgf1 = fac.newSignatureMethod(SignatureMethod.SHA256_RSA_MGF1, null);
|
||||
rsaSha384mgf1 = fac.newSignatureMethod(SignatureMethod.SHA384_RSA_MGF1, null);
|
||||
rsaSha512mgf1 = fac.newSignatureMethod(SignatureMethod.SHA512_RSA_MGF1, null);
|
||||
rsaShaPSS = fac.newSignatureMethod(SignatureMethod. RSA_PSS, null);
|
||||
|
||||
ecdsaSha1 = fac.newSignatureMethod(SignatureMethod.ECDSA_SHA1, null);
|
||||
ecdsaSha224 = fac.newSignatureMethod(SignatureMethod.ECDSA_SHA224, null);
|
||||
|
@ -792,6 +795,14 @@ public class GenerationTests {
|
|||
System.out.println();
|
||||
}
|
||||
|
||||
static void test_create_signature_enveloping_sha512_rsa_pss()
|
||||
throws Exception {
|
||||
System.out.println("* Generating signature-enveloping-sha512_rsa_pss.xml");
|
||||
test_create_signature_enveloping(sha512, rsaShaPSS, rsa1024,
|
||||
getPrivateKey("RSA", 1024), kvks, false, true);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
static void test_create_signature_enveloping_p256_sha1() throws Exception {
|
||||
System.out.println("* Generating signature-enveloping-p256-sha1.xml");
|
||||
test_create_signature_enveloping(sha1, ecdsaSha1, p256ki,
|
||||
|
|
217
test/jdk/javax/xml/crypto/dsig/PSSSpec.java
Normal file
217
test/jdk/javax/xml/crypto/dsig/PSSSpec.java
Normal file
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.security.XMLUtils;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.dsig.DigestMethod;
|
||||
import javax.xml.crypto.dsig.SignatureMethod;
|
||||
import javax.xml.crypto.dsig.XMLSignatureFactory;
|
||||
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||
import javax.xml.crypto.dsig.spec.RSAPSSParameterSpec;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8241306
|
||||
* @library /test/lib
|
||||
* @modules java.xml.crypto
|
||||
* @summary Testing marshal and unmarshal of RSAPSSParameterSpec
|
||||
*/
|
||||
public class PSSSpec {
|
||||
private static final String P2SM = "//ds:Signature/ds:SignedInfo/ds:SignatureMethod";
|
||||
private static final String P2PSS = P2SM + "/pss:RSAPSSParams";
|
||||
private static final String P2MGF = P2PSS + "/pss:MaskGenerationFunction";
|
||||
|
||||
private static final PSSParameterSpec DEFAULT_SPEC
|
||||
= new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 32, PSSParameterSpec.TRAILER_FIELD_BC);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
unmarshal();
|
||||
marshal();
|
||||
spec();
|
||||
}
|
||||
|
||||
static void unmarshal() throws Exception {
|
||||
// Original document with all elements
|
||||
Document doc = XMLUtils.string2doc("""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ds:Signature
|
||||
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
|
||||
xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
xmlns:pss="http://www.w3.org/2007/05/xmldsig-more#">
|
||||
<ds:SignedInfo>
|
||||
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#WithComments"/>
|
||||
<ds:SignatureMethod Algorithm="http://www.w3.org/2007/05/xmldsig-more#rsa-pss">
|
||||
<pss:RSAPSSParams>
|
||||
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
|
||||
<pss:MaskGenerationFunction Algorithm="http://www.w3.org/2007/05/xmldsig-more#MGF1">
|
||||
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#sha384"/>
|
||||
</pss:MaskGenerationFunction>
|
||||
<pss:SaltLength>32</pss:SaltLength>
|
||||
<pss:TrailerField>2</pss:TrailerField>
|
||||
</pss:RSAPSSParams>
|
||||
</ds:SignatureMethod>
|
||||
<ds:Reference>
|
||||
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
|
||||
<ds:DigestValue>abc=</ds:DigestValue>
|
||||
</ds:Reference>
|
||||
</ds:SignedInfo>
|
||||
<ds:SignatureValue>abc=</ds:SignatureValue>
|
||||
</ds:Signature>
|
||||
""");
|
||||
|
||||
// Unknown DigestMethod
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withAttribute(doc, P2PSS + "/ds:DigestMethod", "Algorithm", "http://unknown")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Invalid digest algorithm"), e.getMessage()));
|
||||
// Unknown MGF algorithm
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withAttribute(doc, P2MGF, "Algorithm", "http://unknown")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Unknown MGF algorithm"), e.getMessage()));
|
||||
// Unknown MGF DigestMethod
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withAttribute(doc, P2MGF + "/ds:DigestMethod", "Algorithm", "http://unknown")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Invalid digest algorithm"), e.getMessage()));
|
||||
// Invalid SaltLength
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withText(doc, P2PSS + "/pss:SaltLength", "big")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Invalid salt length supplied"), e.getMessage()));
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withText(doc, P2PSS + "/pss:SaltLength", "-1")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Invalid salt length supplied"), e.getMessage()));
|
||||
// Invalid TrailerField
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withText(doc, P2PSS + "/pss:TrailerField", "small")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Invalid trailer field supplied"), e.getMessage()));
|
||||
Utils.runAndCheckException(
|
||||
() -> getSpec(XMLUtils.withText(doc, P2PSS + "/pss:TrailerField", "-1")),
|
||||
e -> Asserts.assertTrue(e instanceof MarshalException && e.getMessage().contains("Invalid trailer field supplied"), e.getMessage()));
|
||||
|
||||
// Spec in original doc
|
||||
checkSpec(doc, new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-384"), 32, 2));
|
||||
// Default MGF1 dm is same as PSS dm
|
||||
checkSpec(XMLUtils.withoutNode(doc, P2MGF + "/ds:DigestMethod"), // No dm in MGF
|
||||
new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-512"), 32, 2));
|
||||
checkSpec(XMLUtils.withoutNode(doc, P2MGF), // No MGF at all
|
||||
new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-512"), 32, 2));
|
||||
// Default TrailerField is 1
|
||||
checkSpec(XMLUtils.withoutNode(doc, P2PSS + "/pss:TrailerField"),
|
||||
new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-384"), 32, 1));
|
||||
// Default SaltLength is dm's SaltLength
|
||||
checkSpec(XMLUtils.withoutNode(doc, P2PSS + "/pss:SaltLength"),
|
||||
new PSSParameterSpec("SHA-512", "MGF1", new MGF1ParameterSpec("SHA-384"), 64, 2));
|
||||
// Default DigestMethod is 256
|
||||
checkSpec(XMLUtils.withoutNode(doc, P2PSS + "/ds:DigestMethod"),
|
||||
new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-384"), 32, 2));
|
||||
// Default PSS is SHA-256
|
||||
checkSpec(XMLUtils.withoutNode(doc, P2PSS), DEFAULT_SPEC);
|
||||
}
|
||||
|
||||
static void marshal() throws Exception {
|
||||
var keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
var signer = XMLUtils.signer(keyPairGenerator.generateKeyPair().getPrivate());
|
||||
PSSParameterSpec spec;
|
||||
Document doc = XMLUtils.string2doc("<a>x</a>");
|
||||
Document signedDoc;
|
||||
|
||||
// Default sm. No need to describe at all
|
||||
signer.sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(DEFAULT_SPEC));
|
||||
signedDoc = signer.sign(doc);
|
||||
Asserts.assertTrue(!XMLUtils.sub(signedDoc, P2SM).hasChildNodes());
|
||||
|
||||
// Special salt.
|
||||
spec = new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 40, PSSParameterSpec.TRAILER_FIELD_BC);
|
||||
signer.sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(spec));
|
||||
signedDoc = signer.sign(doc);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/pss:SaltLength").getTextContent().equals("40"));
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2MGF) == null);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/ds:DigestMethod") == null);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/pss:TrailerField") == null);
|
||||
|
||||
// Different MGF1 dm
|
||||
spec = new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-384"), 32, PSSParameterSpec.TRAILER_FIELD_BC);
|
||||
signer.sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(spec));
|
||||
signedDoc = signer.sign(doc);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2MGF + "/ds:DigestMethod").getAttribute("Algorithm").equals(DigestMethod.SHA384));
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/ds:DigestMethod") == null);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/pss:SaltLength") == null);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/pss:TrailerField") == null);
|
||||
|
||||
// Non default dm only
|
||||
spec = new PSSParameterSpec("SHA-384", "MGF1", new MGF1ParameterSpec("SHA-384"), 48, PSSParameterSpec.TRAILER_FIELD_BC);
|
||||
signer.sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(spec));
|
||||
signedDoc = signer.sign(doc);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/ds:DigestMethod").getAttribute("Algorithm").equals(DigestMethod.SHA384));
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2MGF) == null);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/pss:SaltLength") == null);
|
||||
Asserts.assertTrue(XMLUtils.sub(signedDoc, P2PSS + "/pss:TrailerField") == null);
|
||||
}
|
||||
|
||||
static void spec() throws Exception {
|
||||
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
|
||||
SignatureMethod sm = fac.newSignatureMethod(SignatureMethod.RSA_PSS, null);
|
||||
Asserts.assertTrue(equals(
|
||||
((RSAPSSParameterSpec)sm.getParameterSpec()).getPSSParameterSpec(),
|
||||
DEFAULT_SPEC));
|
||||
|
||||
PSSParameterSpec special = new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-384"), 33, 2);
|
||||
sm = fac.newSignatureMethod(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(special));
|
||||
Asserts.assertTrue(equals(
|
||||
((RSAPSSParameterSpec)sm.getParameterSpec()).getPSSParameterSpec(),
|
||||
special));
|
||||
}
|
||||
|
||||
static PSSParameterSpec getSpec(Document doc) throws Exception {
|
||||
var signatureNode = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
|
||||
DOMValidateContext valContext = new DOMValidateContext(new SecretKeySpec(new byte[1], "WHAT"), signatureNode);
|
||||
valContext.setProperty("org.jcp.xml.dsig.secureValidation", false);
|
||||
var signedInfo = XMLSignatureFactory.getInstance("DOM").unmarshalXMLSignature(valContext).getSignedInfo();
|
||||
var spec = signedInfo.getSignatureMethod().getParameterSpec();
|
||||
if (spec instanceof RSAPSSParameterSpec pspec) {
|
||||
return pspec.getPSSParameterSpec();
|
||||
} else {
|
||||
Asserts.fail("Not PSSParameterSpec: " + spec.getClass());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static void checkSpec(Document doc, PSSParameterSpec expected) throws Exception {
|
||||
Asserts.assertTrue(equals(getSpec(doc), expected));
|
||||
}
|
||||
|
||||
static boolean equals(PSSParameterSpec p1, PSSParameterSpec p2) {
|
||||
return p1.getDigestAlgorithm().equals(p2.getDigestAlgorithm())
|
||||
&& p1.getSaltLength() == p2.getSaltLength()
|
||||
&& p1.getTrailerField() == p2.getTrailerField()
|
||||
&& p1.getMGFAlgorithm().equals(p2.getMGFAlgorithm())
|
||||
&& ((MGF1ParameterSpec) p1.getMGFParameters()).getDigestAlgorithm()
|
||||
.equals(((MGF1ParameterSpec) p2.getMGFParameters()).getDigestAlgorithm());
|
||||
}
|
||||
}
|
145
test/jdk/javax/xml/crypto/dsig/SecureValidation.java
Normal file
145
test/jdk/javax/xml/crypto/dsig/SecureValidation.java
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8241306
|
||||
* @summary Tests for the jdk.xml.dsig.secureValidationPolicy security property
|
||||
* on the RSASSA-PSS signature method
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.security.tools.keytool
|
||||
* java.base/sun.security.x509
|
||||
*/
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.security.XMLUtils;
|
||||
import jdk.test.lib.Utils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import sun.security.tools.keytool.CertAndKeyGen;
|
||||
import sun.security.x509.X500Name;
|
||||
|
||||
import javax.xml.crypto.MarshalException;
|
||||
import javax.xml.crypto.dsig.DigestMethod;
|
||||
import javax.xml.crypto.dsig.SignatureMethod;
|
||||
import javax.xml.crypto.dsig.spec.RSAPSSParameterSpec;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.security.spec.PSSParameterSpec.TRAILER_FIELD_BC;
|
||||
|
||||
public class SecureValidation {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
Document doc = XMLUtils.string2doc("<a><b>Text</b>Raw</a>");
|
||||
|
||||
CertAndKeyGen g = new CertAndKeyGen("RSASSA-PSS", "RSASSA-PSS");
|
||||
g.generate(2048);
|
||||
X509Certificate cert = g.getSelfCertificate(new X500Name("CN=Me"), 100);
|
||||
PrivateKey privateKey = g.getPrivateKey();
|
||||
PSSParameterSpec pspec = new PSSParameterSpec("SHA-384", "MGF1",
|
||||
MGF1ParameterSpec.SHA512, 48, TRAILER_FIELD_BC);
|
||||
|
||||
// Sign with PSS with SHA-384 and SHA-512
|
||||
Document signed = XMLUtils.signer(privateKey, cert)
|
||||
.dm(DigestMethod.SHA384)
|
||||
.sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(pspec))
|
||||
.sign(doc);
|
||||
|
||||
XPath xp = XPathFactory.newInstance().newXPath();
|
||||
xp.setNamespaceContext(new NamespaceContext() {
|
||||
@Override
|
||||
public String getNamespaceURI(String prefix) {
|
||||
return switch (prefix) {
|
||||
case "ds" -> "http://www.w3.org/2000/09/xmldsig#";
|
||||
case "pss" -> "http://www.w3.org/2007/05/xmldsig-more#";
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(String namespaceURI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> getPrefixes(String namespaceURI) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
var validator = XMLUtils.validator();
|
||||
XMLUtils.addPolicy("disallowAlg " + DigestMethod.SHA256);
|
||||
Element e;
|
||||
|
||||
// 1. Modify the MGF1 digest algorithm in PSSParams to SHA-256
|
||||
e = (Element) xp.evaluate(
|
||||
"/a/ds:Signature/ds:SignedInfo/ds:SignatureMethod" +
|
||||
"/pss:RSAPSSParams/pss:MaskGenerationFunction/ds:DigestMethod",
|
||||
signed, XPathConstants.NODE);
|
||||
e.setAttribute("Algorithm", DigestMethod.SHA256);
|
||||
|
||||
// When secureValidation is true, validate() throws an exception
|
||||
Utils.runAndCheckException(() -> validator.secureValidation(true).validate(signed),
|
||||
t -> Asserts.assertTrue(t instanceof MarshalException
|
||||
&& t.getMessage().contains("in MGF1")
|
||||
&& t.getMessage().contains(DigestMethod.SHA256), Objects.toString(t)));
|
||||
// When secureValidation is false, validate() returns false
|
||||
Asserts.assertFalse(validator.secureValidation(false).validate(signed));
|
||||
|
||||
// Revert the change and confirm validate() returns true
|
||||
e.setAttribute("Algorithm", DigestMethod.SHA512);
|
||||
Asserts.assertTrue(validator.secureValidation(true).validate(signed));
|
||||
|
||||
// 2. Modify the digest algorithm in PSSParams to SHA-256
|
||||
e = (Element) xp.evaluate(
|
||||
"/a/ds:Signature/ds:SignedInfo/ds:SignatureMethod" +
|
||||
"/pss:RSAPSSParams/ds:DigestMethod",
|
||||
signed, XPathConstants.NODE);
|
||||
e.setAttribute("Algorithm", DigestMethod.SHA256);
|
||||
|
||||
// When secureValidation is true, validate() throws an exception
|
||||
Utils.runAndCheckException(() -> validator.secureValidation(true).validate(signed),
|
||||
t -> Asserts.assertTrue(t instanceof MarshalException
|
||||
&& t.getMessage().contains("in PSS")
|
||||
&& t.getMessage().contains(DigestMethod.SHA256), Objects.toString(t)));
|
||||
// When secureValidation is false, validate() returns false
|
||||
Asserts.assertFalse(validator.secureValidation(false).validate(signed));
|
||||
|
||||
// 3. Modify the digest algorithm in PSSParams to SHA-512
|
||||
e.setAttribute("Algorithm", DigestMethod.SHA512);
|
||||
|
||||
// No matter if secureValidation is true or false, validate()
|
||||
// returns false. This means the policy allows it.
|
||||
Asserts.assertFalse(validator.secureValidation(true).validate(signed));
|
||||
Asserts.assertFalse(validator.secureValidation(false).validate(signed));
|
||||
}
|
||||
}
|
565
test/lib/jdk/test/lib/security/XMLUtils.java
Normal file
565
test/lib/jdk/test/lib/security/XMLUtils.java
Normal file
|
@ -0,0 +1,565 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.test.lib.security;
|
||||
|
||||
import jdk.test.lib.Asserts;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.xml.crypto.*;
|
||||
import javax.xml.crypto.dom.DOMStructure;
|
||||
import javax.xml.crypto.dsig.*;
|
||||
import javax.xml.crypto.dsig.dom.DOMSignContext;
|
||||
import javax.xml.crypto.dsig.dom.DOMValidateContext;
|
||||
import javax.xml.crypto.dsig.keyinfo.*;
|
||||
import javax.xml.crypto.dsig.spec.*;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMResult;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.*;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
import java.util.*;
|
||||
|
||||
// A collection of test utility methods for parsing, validating and
|
||||
// generating XML Signatures.
|
||||
public class XMLUtils {
|
||||
|
||||
private static final XMLSignatureFactory FAC =
|
||||
XMLSignatureFactory.getInstance("DOM");
|
||||
|
||||
//////////// MAIN as TEST ////////////
|
||||
public static void main(String[] args) throws Exception {
|
||||
var x = "<a><b>c</b>x</a>";
|
||||
var p = Files.write(Path.of("x.xml"), List.of(x));
|
||||
var b = Path.of("").toUri().toString();
|
||||
var d = string2doc(x);
|
||||
// keytool -keystore ks -keyalg ec -storepass changeit -genkeypair -alias a -dname CN=a
|
||||
var pass = "changeit".toCharArray();
|
||||
var ks = KeyStore.getInstance(new File("ks"), pass);
|
||||
var c = (X509Certificate) ks.getCertificate("a");
|
||||
var pr = (PrivateKey) ks.getKey("a", pass);
|
||||
var pu = c.getPublicKey();
|
||||
var s0 = signer(pr); // No KeyInfo
|
||||
var s1 = signer(pr, pu); // KeyInfo is PublicKey
|
||||
var s2 = signer(pr, c); // KeyInfo is X509Data
|
||||
var s3 = signer(ks, "a", pass); // KeyInfo is KeyName
|
||||
var v1 = validator(); // knows nothing
|
||||
var v2 = validator(ks); // knows KeyName
|
||||
Asserts.assertTrue(v1.validate(s0.sign(d), pu)); // need PublicKey
|
||||
Asserts.assertTrue(v1.validate(s1.sign(d))); // can read KeyInfo
|
||||
Asserts.assertTrue(v1.validate(s2.sign(d))); // can read KeyInfo
|
||||
Asserts.assertTrue(v2.validate(s3.sign(d))); // can read KeyInfo
|
||||
Asserts.assertTrue(v2.secureValidation(false).validate(s3.sign(p.toUri()))); // can read KeyInfo
|
||||
Asserts.assertTrue(v2.secureValidation(false).baseURI(b).validate(
|
||||
s3.sign(p.getParent().toUri(), p.getFileName().toUri()))); // can read KeyInfo
|
||||
Asserts.assertTrue(v1.validate(s1.sign("text"))); // plain text
|
||||
Asserts.assertTrue(v1.validate(s1.sign("binary".getBytes()))); // raw data
|
||||
}
|
||||
|
||||
//////////// CONVERT ////////////
|
||||
|
||||
// Converts a Document object to string
|
||||
public static String doc2string(Document doc) throws Exception {
|
||||
TransformerFactory tf = TransformerFactory.newInstance();
|
||||
Transformer transformer = tf.newTransformer();
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
|
||||
// Indentation would invalidate the signature
|
||||
// transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
StringWriter writer = new StringWriter();
|
||||
transformer.transform(new DOMSource(doc), new StreamResult(writer));
|
||||
return writer.getBuffer().toString();
|
||||
}
|
||||
|
||||
// Parses a string into a Document object
|
||||
public static Document string2doc(String input) throws Exception {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
return factory.newDocumentBuilder().
|
||||
parse(new InputSource(new StringReader(input)));
|
||||
}
|
||||
|
||||
// Clones a document
|
||||
public static Document clone(Document d) throws Exception {
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db = dbf.newDocumentBuilder();
|
||||
Document copiedDocument = db.newDocument();
|
||||
Node copiedRoot = copiedDocument.importNode(d.getDocumentElement(), true);
|
||||
copiedDocument.appendChild(copiedRoot);
|
||||
return copiedDocument;
|
||||
}
|
||||
|
||||
//////////// QUERY AND EDIT ////////////
|
||||
|
||||
// An XPath object that recognizes ds and pss names
|
||||
public static final XPath XPATH;
|
||||
|
||||
static {
|
||||
XPATH = XPathFactory.newInstance().newXPath();
|
||||
XPATH.setNamespaceContext(new NamespaceContext() {
|
||||
@Override
|
||||
public String getNamespaceURI(String prefix) {
|
||||
return switch (prefix) {
|
||||
case "ds" -> "http://www.w3.org/2000/09/xmldsig#";
|
||||
case "pss" -> "http://www.w3.org/2007/05/xmldsig-more#";
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(String namespaceURI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> getPrefixes(String namespaceURI) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Returns an Element inside a Document
|
||||
public static Element sub(Document d, String path) throws Exception {
|
||||
return (Element) XMLUtils.XPATH.evaluate(path, d, XPathConstants.NODE);
|
||||
}
|
||||
|
||||
// Returns a new document with an attribute modified
|
||||
public static Document withAttribute(Document d, String path, String attr, String value) throws Exception {
|
||||
d = clone(d);
|
||||
sub(d, path).setAttribute(attr, value);
|
||||
return d;
|
||||
}
|
||||
|
||||
// Returns a new document with a text modified
|
||||
public static Document withText(Document d, String path, String value) throws Exception {
|
||||
d = clone(d);
|
||||
sub(d, path).setTextContent(value);
|
||||
return d;
|
||||
}
|
||||
|
||||
// Returns a new document without a child element
|
||||
public static Document withoutNode(Document d, String... paths) throws Exception {
|
||||
d = clone(d);
|
||||
for (String path : paths) {
|
||||
Element e = sub(d, path);
|
||||
e.getParentNode().removeChild(e);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
//////////// SIGN ////////////
|
||||
|
||||
// Creates a signer from a private key and a certificate
|
||||
public static Signer signer(PrivateKey privateKey, X509Certificate cert)
|
||||
throws Exception {
|
||||
return signer(privateKey).cert(cert);
|
||||
}
|
||||
|
||||
// Creates a signer from a private key and a public key
|
||||
public static Signer signer(PrivateKey privateKey, PublicKey publicKey)
|
||||
throws Exception {
|
||||
return signer(privateKey).publicKey(publicKey);
|
||||
}
|
||||
|
||||
// Creates a signer from a private key entry in a keystore. The KeyInfo
|
||||
// inside the signature will only contain a KeyName to alias
|
||||
public static Signer signer(KeyStore ks, String alias, char[] password)
|
||||
throws Exception {
|
||||
return signer((PrivateKey) ks.getKey(alias, password))
|
||||
.keyName(alias);
|
||||
}
|
||||
|
||||
// Creates a signer from a private key. There will be no KeyInfo inside
|
||||
// the signature and must be validated with a given public key.
|
||||
public static Signer signer(PrivateKey privateKey)
|
||||
throws Exception {
|
||||
return new Signer(privateKey);
|
||||
}
|
||||
|
||||
public static class Signer {
|
||||
|
||||
PrivateKey privateKey; // signer key, never null
|
||||
X509Certificate cert; // certificate, optional
|
||||
PublicKey publicKey; // public key, optional
|
||||
String keyName; // alias, optional
|
||||
|
||||
SignatureMethod sm; // default determined by privateKey
|
||||
DigestMethod dm; // default SHA-256
|
||||
CanonicalizationMethod cm; // default EXCLUSIVE
|
||||
Transform tr; // default ENVELOPED
|
||||
|
||||
public Signer(PrivateKey privateKey) throws Exception {
|
||||
this.privateKey = privateKey;
|
||||
dm(DigestMethod.SHA256);
|
||||
tr(Transform.ENVELOPED);
|
||||
cm(CanonicalizationMethod.EXCLUSIVE);
|
||||
String alg = privateKey.getAlgorithm();
|
||||
if (alg.equals("RSASSA-PSS")) {
|
||||
PSSParameterSpec pspec
|
||||
= (PSSParameterSpec) ((RSAKey) privateKey).getParams();
|
||||
if (pspec != null) {
|
||||
sm(SignatureMethod.RSA_PSS, new RSAPSSParameterSpec(pspec));
|
||||
} else {
|
||||
sm(SignatureMethod.RSA_PSS);
|
||||
}
|
||||
} else {
|
||||
sm(switch (privateKey.getAlgorithm()) {
|
||||
case "RSA" -> SignatureMethod.RSA_SHA256;
|
||||
case "DSA" -> SignatureMethod.DSA_SHA256;
|
||||
case "EC" -> SignatureMethod.ECDSA_SHA256;
|
||||
default -> throw new InvalidKeyException();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Change KeyInfo source
|
||||
|
||||
public Signer cert(X509Certificate cert) {
|
||||
this.cert = cert;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer publicKey(PublicKey key) {
|
||||
this.publicKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer keyName(String n) {
|
||||
keyName = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Change various methods
|
||||
|
||||
public Signer tr(String transform) throws Exception {
|
||||
TransformParameterSpec params = null;
|
||||
switch (transform) {
|
||||
case Transform.XPATH:
|
||||
params = new XPathFilterParameterSpec("//.");
|
||||
break;
|
||||
case Transform.XPATH2:
|
||||
params = new XPathFilter2ParameterSpec(
|
||||
Collections.singletonList(new XPathType("//.",
|
||||
XPathType.Filter.INTERSECT)));
|
||||
break;
|
||||
}
|
||||
tr = FAC.newTransform(transform, params);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer sm(String method) throws Exception {
|
||||
sm = FAC.newSignatureMethod(method, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer dm(String method) throws Exception {
|
||||
dm = FAC.newDigestMethod(method, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer cm(String method) throws Exception {
|
||||
cm = FAC.newCanonicalizationMethod(method, (C14NMethodParameterSpec) null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer sm(String method, SignatureMethodParameterSpec spec)
|
||||
throws Exception {
|
||||
sm = FAC.newSignatureMethod(method, spec);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Signer dm(String method, DigestMethodParameterSpec spec)
|
||||
throws Exception {
|
||||
dm = FAC.newDigestMethod(method, spec);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Signs different sources
|
||||
|
||||
// Signs an XML file in detached mode
|
||||
public Document sign(URI uri) throws Exception {
|
||||
Document newDocument = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().newDocument();
|
||||
FAC.newXMLSignature(buildSignedInfo(uri.toString()), buildKeyInfo()).sign(
|
||||
new DOMSignContext(privateKey, newDocument));
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
// Signs an XML file in a relative reference in detached mode
|
||||
public Document sign(URI base, URI ref) throws Exception {
|
||||
Document newDocument = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().newDocument();
|
||||
DOMSignContext ctxt = new DOMSignContext(privateKey, newDocument);
|
||||
ctxt.setBaseURI(base.toString());
|
||||
FAC.newXMLSignature(buildSignedInfo(ref.toString()), buildKeyInfo()).sign(ctxt);
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
// Signs a document in enveloped mode
|
||||
public Document sign(Document document) throws Exception {
|
||||
DOMResult result = new DOMResult();
|
||||
TransformerFactory.newInstance().newTransformer()
|
||||
.transform(new DOMSource(document), result);
|
||||
Document newDocument = (Document) result.getNode();
|
||||
FAC.newXMLSignature(buildSignedInfo(""), buildKeyInfo()).sign(
|
||||
new DOMSignContext(privateKey, newDocument.getDocumentElement()));
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
// Signs a document in enveloping mode
|
||||
public Document signEnveloping(Document document) throws Exception {
|
||||
Document newDocument = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().newDocument();
|
||||
FAC.newXMLSignature(
|
||||
buildSignedInfo(FAC.newReference("#object", dm)),
|
||||
buildKeyInfo(),
|
||||
List.of(FAC.newXMLObject(List.of(new DOMStructure(document.getDocumentElement())),
|
||||
"object", null, null)),
|
||||
null,
|
||||
null)
|
||||
.sign(new DOMSignContext(privateKey, newDocument));
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
// Signs a raw byte array
|
||||
public Document sign(byte[] data) throws Exception {
|
||||
Document newDocument = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().newDocument();
|
||||
FAC.newXMLSignature(
|
||||
buildSignedInfo(FAC.newReference("#object", dm, List.of
|
||||
(FAC.newTransform(Transform.BASE64,
|
||||
(TransformParameterSpec) null)), null, null)),
|
||||
buildKeyInfo(),
|
||||
List.of(FAC.newXMLObject(List.of(new DOMStructure(
|
||||
newDocument.createTextNode(Base64.getEncoder().encodeToString(data)))),
|
||||
"object", null, null)),
|
||||
null,
|
||||
null)
|
||||
.sign(new DOMSignContext(privateKey, newDocument));
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
// Signs a plain string
|
||||
public Document sign(String str) throws Exception {
|
||||
Document newDocument = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder().newDocument();
|
||||
FAC.newXMLSignature(
|
||||
buildSignedInfo(FAC.newReference("#object", dm)),
|
||||
buildKeyInfo(),
|
||||
List.of(FAC.newXMLObject(List.of(new DOMStructure(newDocument.createTextNode(str))),
|
||||
"object", null, null)),
|
||||
null,
|
||||
null)
|
||||
.sign(new DOMSignContext(privateKey, newDocument));
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
// Builds a SignedInfo for a string reference
|
||||
private SignedInfo buildSignedInfo(String ref) {
|
||||
return FAC.newSignedInfo(
|
||||
cm,
|
||||
sm,
|
||||
List.of(FAC.newReference(
|
||||
ref,
|
||||
dm,
|
||||
List.of(tr),
|
||||
null, null)));
|
||||
}
|
||||
|
||||
// Builds a SignedInfo for a Reference
|
||||
private SignedInfo buildSignedInfo(Reference ref) {
|
||||
return FAC.newSignedInfo(
|
||||
cm,
|
||||
sm,
|
||||
List.of(ref));
|
||||
}
|
||||
|
||||
// Builds a KeyInfo from different sources
|
||||
private KeyInfo buildKeyInfo() throws Exception {
|
||||
KeyInfoFactory keyInfoFactory = FAC.getKeyInfoFactory();
|
||||
if (cert != null) {
|
||||
return keyInfoFactory.newKeyInfo(List.of(
|
||||
keyInfoFactory.newX509Data(List.of(cert))));
|
||||
} else if (publicKey != null) {
|
||||
return keyInfoFactory.newKeyInfo(List.of(
|
||||
keyInfoFactory.newKeyValue(publicKey)));
|
||||
} else if (keyName != null) {
|
||||
return keyInfoFactory.newKeyInfo(List.of(
|
||||
keyInfoFactory.newKeyName(keyName)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////// VALIDATE ////////////
|
||||
|
||||
// Create a Validator, ks will be used if there is a KeyName
|
||||
public static Validator validator(KeyStore ks)
|
||||
throws Exception {
|
||||
return new Validator(ks);
|
||||
}
|
||||
|
||||
// Create a Validator, key will either be inside KeyInfo or
|
||||
// a key will be provided when validate() is called
|
||||
public static Validator validator()
|
||||
throws Exception {
|
||||
return new Validator(null);
|
||||
}
|
||||
|
||||
public static class Validator {
|
||||
|
||||
private Boolean secureValidation = null;
|
||||
private String baseURI = null;
|
||||
private final KeyStore ks;
|
||||
|
||||
public Validator(KeyStore ks) {
|
||||
this.ks = ks;
|
||||
}
|
||||
|
||||
public Validator secureValidation(boolean v) {
|
||||
this.secureValidation = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Validator baseURI(String base) {
|
||||
this.baseURI = base;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean validate(Document document) throws Exception {
|
||||
return validate(document, null);
|
||||
}
|
||||
|
||||
// If key is not null, any key from the signature will be ignored
|
||||
public boolean validate(Document document, PublicKey key)
|
||||
throws Exception {
|
||||
NodeList nodeList = document.getElementsByTagName("Signature");
|
||||
if (nodeList.getLength() == 1) {
|
||||
Node signatureNode = nodeList.item(0);
|
||||
if (signatureNode != null) {
|
||||
KeySelector ks = key == null ? new MyKeySelector(this.ks) : new KeySelector() {
|
||||
@Override
|
||||
public KeySelectorResult select(KeyInfo ki, Purpose p,
|
||||
AlgorithmMethod m, XMLCryptoContext c) {
|
||||
return () -> key;
|
||||
}
|
||||
};
|
||||
DOMValidateContext valContext
|
||||
= new DOMValidateContext(ks, signatureNode);
|
||||
if (baseURI != null) {
|
||||
valContext.setBaseURI(baseURI);
|
||||
}
|
||||
if (secureValidation != null) {
|
||||
valContext.setProperty("org.jcp.xml.dsig.secureValidation",
|
||||
secureValidation);
|
||||
valContext.setProperty("org.apache.jcp.xml.dsig.secureValidation",
|
||||
secureValidation);
|
||||
}
|
||||
return XMLSignatureFactory.getInstance("DOM")
|
||||
.unmarshalXMLSignature(valContext).validate(valContext);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find public key from KeyInfo, ks will be used if it's KeyName
|
||||
private static class MyKeySelector extends KeySelector {
|
||||
private final KeyStore ks;
|
||||
|
||||
public MyKeySelector(KeyStore ks) {
|
||||
this.ks = ks;
|
||||
}
|
||||
|
||||
public KeySelectorResult select(KeyInfo keyInfo,
|
||||
KeySelector.Purpose purpose,
|
||||
AlgorithmMethod method,
|
||||
XMLCryptoContext context)
|
||||
throws KeySelectorException {
|
||||
Objects.requireNonNull(keyInfo, "Null KeyInfo object!");
|
||||
|
||||
for (XMLStructure xmlStructure : keyInfo.getContent()) {
|
||||
PublicKey pk;
|
||||
if (xmlStructure instanceof KeyValue kv) {
|
||||
try {
|
||||
pk = kv.getPublicKey();
|
||||
} catch (KeyException ke) {
|
||||
throw new KeySelectorException(ke);
|
||||
}
|
||||
return () -> pk;
|
||||
} else if (xmlStructure instanceof X509Data x509) {
|
||||
for (Object data : x509.getContent()) {
|
||||
if (data instanceof X509Certificate) {
|
||||
pk = ((X509Certificate) data).getPublicKey();
|
||||
return () -> pk;
|
||||
}
|
||||
}
|
||||
} else if (xmlStructure instanceof KeyName kn) {
|
||||
try {
|
||||
pk = ks.getCertificate(kn.getName()).getPublicKey();
|
||||
} catch (KeyStoreException e) {
|
||||
throw new KeySelectorException(e);
|
||||
}
|
||||
return () -> pk;
|
||||
}
|
||||
}
|
||||
throw new KeySelectorException("No KeyValue element found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////// MISC ////////////
|
||||
|
||||
/**
|
||||
* Adds a new rule to "jdk.xml.dsig.secureValidationPolicy"
|
||||
*/
|
||||
public static void addPolicy(String rule) {
|
||||
String value = Security.getProperty("jdk.xml.dsig.secureValidationPolicy");
|
||||
value = rule + "," + value;
|
||||
Security.setProperty("jdk.xml.dsig.secureValidationPolicy", value);
|
||||
}
|
||||
|
||||
private XMLUtils() {
|
||||
assert false : "No one instantiates me";
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue