8241306: Add SignatureMethodParameterSpec subclass for RSASSA-PSS params
Reviewed-by: mullan
This commit is contained in:
parent
b14e0ee4d8
commit
8dbf7aa1f9
@ -42,6 +42,7 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Text;
|
||||
|
||||
import javax.xml.crypto.dsig.DigestMethod;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
|
||||
@ -584,9 +585,14 @@ public abstract class SignatureBaseRSA extends SignatureAlgorithmSpi {
|
||||
PSSParameterSpec pssParameterSpec;
|
||||
|
||||
public enum DigestAlgorithm {
|
||||
SHA256("SHA-256", "http://www.w3.org/2001/04/xmlenc#sha256", 32),
|
||||
SHA384("SHA-384", "http://www.w3.org/2001/04/xmldsig-more#sha384", 48),
|
||||
SHA512("SHA-512", "http://www.w3.org/2001/04/xmlenc#sha512", 64);
|
||||
SHA224("SHA-224", DigestMethod.SHA224, 28),
|
||||
SHA256("SHA-256", DigestMethod.SHA256, 32),
|
||||
SHA384("SHA-384", DigestMethod.SHA384, 48),
|
||||
SHA512("SHA-512", DigestMethod.SHA512, 64),
|
||||
SHA3_224("SHA3-224", DigestMethod.SHA3_224, 28),
|
||||
SHA3_256("SHA3-256", DigestMethod.SHA3_256, 32),
|
||||
SHA3_384("SHA3-384", DigestMethod.SHA3_384, 48),
|
||||
SHA3_512("SHA3-512", DigestMethod.SHA3_512, 64);
|
||||
|
||||
private final String xmlDigestAlgorithm;
|
||||
private final String digestAlgorithm;
|
||||
|
@ -141,6 +141,9 @@ public final class Constants {
|
||||
/** Tag of Element RSAPSSParams **/
|
||||
public static final String _TAG_RSAPSSPARAMS = "RSAPSSParams";
|
||||
|
||||
/** Tag of Element MaskGenerationFunction **/
|
||||
public static final String _TAG_MGF = "MaskGenerationFunction";
|
||||
|
||||
/** Tag of Element SaltLength **/
|
||||
public static final String _TAG_SALTLENGTH = "SaltLength";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 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
|
||||
@ -235,6 +235,26 @@ public interface SignatureMethod extends XMLStructure, AlgorithmMethod {
|
||||
*/
|
||||
String HMAC_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512";
|
||||
|
||||
|
||||
/**
|
||||
* The <a href="http://www.w3.org/2007/05/xmldsig-more#rsa-pss">
|
||||
* RSASSA-PSS</a> signature method algorithm URI.
|
||||
* <p>
|
||||
* Calling {@link XMLSignatureFactory#newSignatureMethod
|
||||
* XMLSignatureFactory.newSignatureMethod(RSA_PSS, null)} returns a
|
||||
* {@code SignatureMethod} object that uses the default parameter as defined in
|
||||
* <a href="https://tools.ietf.org/html/rfc6931#section-2.3.9">RFC 6931 Section 2.3.9</a>,
|
||||
* which uses SHA-256 as the {@code DigestMethod}, MGF1 with SHA-256 as the
|
||||
* {@code MaskGenerationFunction}, 32 as {@code SaltLength}, and 1 as
|
||||
* {@code TrailerField}. This default parameter is represented as an
|
||||
* {@link javax.xml.crypto.dsig.spec.RSAPSSParameterSpec RSAPSSParameterSpec}
|
||||
* type and returned by the {@link #getParameterSpec()} method
|
||||
* of the {@code SignatureMethod} object.
|
||||
*
|
||||
* @since 17
|
||||
*/
|
||||
String RSA_PSS = "http://www.w3.org/2007/05/xmldsig-more#rsa-pss";
|
||||
|
||||
/**
|
||||
* Returns the algorithm-specific input parameters of this
|
||||
* <code>SignatureMethod</code>.
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* 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 javax.xml.crypto.dsig.spec;
|
||||
|
||||
import javax.xml.crypto.dsig.SignatureMethod;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Parameters for the <a href="http://www.w3.org/2007/05/xmldsig-more#rsa-pss">
|
||||
* XML Signature RSASSA-PSS Algorithm</a>. The parameters are represented as a
|
||||
* {@link PSSParameterSpec} object.
|
||||
* <p>
|
||||
* The XML Schema Definition is defined as:
|
||||
* <pre><code>
|
||||
* <xs:element name="RSAPSSParams" type="pss:RSAPSSParamsType">
|
||||
* <xs:annotation>
|
||||
* <xs:documentation>
|
||||
* Top level element that can be used in xs:any namespace="#other"
|
||||
* wildcard of ds:SignatureMethod content.
|
||||
* </xs:documentation>
|
||||
* </xs:annotation>
|
||||
* </xs:element>
|
||||
* <xs:complexType name="RSAPSSParamsType">
|
||||
* <xs:sequence>
|
||||
* <xs:element ref="ds:DigestMethod" minOccurs="0"/>
|
||||
* <xs:element name="MaskGenerationFunction"
|
||||
* type="pss:MaskGenerationFunctionType" minOccurs="0"/>
|
||||
* <xs:element name="SaltLength" type="xs:int"
|
||||
* minOccurs="0"/>
|
||||
* <xs:element name="TrailerField" type="xs:int"
|
||||
* minOccurs="0"/>
|
||||
* </xs:sequence>
|
||||
* </xs:complexType>
|
||||
* <xs:complexType name="MaskGenerationFunctionType">
|
||||
* <xs:sequence>
|
||||
* <xs:element ref="ds:DigestMethod" minOccurs="0"/>
|
||||
* </xs:sequence>
|
||||
* <xs:attribute name="Algorithm" type="xs:anyURI"
|
||||
* default="http://www.w3.org/2007/05/xmldsig-more#MGF1"/>
|
||||
* </xs:complexType>
|
||||
* </code></pre>
|
||||
*
|
||||
* @since 17
|
||||
* @see SignatureMethod#RSA_PSS
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc6931.txt">RFC 6931</a>
|
||||
*/
|
||||
public final class RSAPSSParameterSpec implements SignatureMethodParameterSpec {
|
||||
|
||||
private final PSSParameterSpec spec;
|
||||
|
||||
/**
|
||||
* Creates a new {@code RSAPSSParameterSpec} object with the specified
|
||||
* {@link PSSParameterSpec} object.
|
||||
*
|
||||
* @param spec the input {@code PSSParameterSpec} object
|
||||
*
|
||||
* @throws NullPointerException if {@code spec} is null
|
||||
*/
|
||||
public RSAPSSParameterSpec(PSSParameterSpec spec) {
|
||||
this.spec = Objects.requireNonNull(spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code PSSParameterSpec} object inside.
|
||||
*
|
||||
* @return the {@code PSSParameterSpec} object inside
|
||||
*/
|
||||
public PSSParameterSpec getPSSParameterSpec() {
|
||||
return spec;
|
||||
}
|
||||
}
|
@ -21,12 +21,13 @@
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
*/
|
||||
package org.jcp.xml.dsig.internal.dom;
|
||||
|
||||
import javax.xml.crypto.*;
|
||||
import javax.xml.crypto.dsig.*;
|
||||
import javax.xml.crypto.dsig.spec.RSAPSSParameterSpec;
|
||||
import javax.xml.crypto.dsig.spec.SignatureMethodParameterSpec;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -46,7 +47,6 @@ import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
|
||||
|
||||
/**
|
||||
* DOM-based abstract implementation of SignatureMethod for RSA-PSS.
|
||||
*
|
||||
*/
|
||||
public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMethod {
|
||||
|
||||
@ -62,9 +62,12 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
static final String RSA_PSS =
|
||||
"http://www.w3.org/2007/05/xmldsig-more#rsa-pss";
|
||||
|
||||
private int trailerField = 1;
|
||||
private int saltLength = 32;
|
||||
private String digestName = "SHA-256";
|
||||
private static final RSAPSSParameterSpec DEFAULT_PSS_SPEC
|
||||
= new RSAPSSParameterSpec(new PSSParameterSpec(
|
||||
"SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"),
|
||||
32, PSSParameterSpec.TRAILER_FIELD_BC));
|
||||
|
||||
private PSSParameterSpec spec;
|
||||
|
||||
/**
|
||||
* Creates a {@code DOMSignatureMethod}.
|
||||
@ -82,7 +85,7 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
("params must be of type SignatureMethodParameterSpec");
|
||||
}
|
||||
if (params == null) {
|
||||
params = getDefaultParameterSpec();
|
||||
params = DEFAULT_PSS_SPEC;
|
||||
}
|
||||
checkParams((SignatureMethodParameterSpec)params);
|
||||
this.params = (SignatureMethodParameterSpec)params;
|
||||
@ -100,7 +103,7 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
if (paramsElem != null) {
|
||||
params = unmarshalParams(paramsElem);
|
||||
} else {
|
||||
params = getDefaultParameterSpec();
|
||||
params = DEFAULT_PSS_SPEC;
|
||||
}
|
||||
try {
|
||||
checkParams(params);
|
||||
@ -113,25 +116,13 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
void checkParams(SignatureMethodParameterSpec params)
|
||||
throws InvalidAlgorithmParameterException
|
||||
{
|
||||
if (params != null) {
|
||||
if (!(params instanceof RSAPSSParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("params must be of type RSAPSSParameterSpec");
|
||||
}
|
||||
|
||||
if (((RSAPSSParameterSpec)params).getTrailerField() > 0) {
|
||||
trailerField = ((RSAPSSParameterSpec)params).getTrailerField();
|
||||
LOG.debug("Setting trailerField from RSAPSSParameterSpec to: {}", trailerField);
|
||||
}
|
||||
if (((RSAPSSParameterSpec)params).getSaltLength() > 0) {
|
||||
saltLength = ((RSAPSSParameterSpec)params).getSaltLength();
|
||||
LOG.debug("Setting saltLength from RSAPSSParameterSpec to: {}", saltLength);
|
||||
}
|
||||
if (((RSAPSSParameterSpec)params).getDigestName() != null) {
|
||||
digestName = ((RSAPSSParameterSpec)params).getDigestName();
|
||||
LOG.debug("Setting digestName from RSAPSSParameterSpec to: {}", digestName);
|
||||
}
|
||||
if (!(params instanceof RSAPSSParameterSpec)) {
|
||||
throw new InvalidAlgorithmParameterException
|
||||
("params must be of type RSAPSSParameterSpec");
|
||||
}
|
||||
|
||||
spec = ((RSAPSSParameterSpec) params).getPSSParameterSpec();
|
||||
LOG.debug("Setting RSAPSSParameterSpec to: {}", params.toString());
|
||||
}
|
||||
|
||||
public final AlgorithmParameterSpec getParameterSpec() {
|
||||
@ -146,28 +137,70 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
Element rsaPssParamsElement = ownerDoc.createElementNS(Constants.XML_DSIG_NS_MORE_07_05, "pss" + ":" + Constants._TAG_RSAPSSPARAMS);
|
||||
rsaPssParamsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:" + "pss", Constants.XML_DSIG_NS_MORE_07_05);
|
||||
|
||||
Element digestMethodElement = DOMUtils.createElement(rsaPssParamsElement.getOwnerDocument(), Constants._TAG_DIGESTMETHOD,
|
||||
XMLSignature.XMLNS, prefix);
|
||||
DigestAlgorithm digestAlgorithm;
|
||||
try {
|
||||
digestMethodElement.setAttributeNS(null, Constants._ATT_ALGORITHM, DigestAlgorithm.fromDigestAlgorithm(digestName).getXmlDigestAlgorithm());
|
||||
digestAlgorithm = DigestAlgorithm.fromDigestAlgorithm(spec.getDigestAlgorithm());
|
||||
String xmlDigestAlgorithm = digestAlgorithm.getXmlDigestAlgorithm();
|
||||
if (!xmlDigestAlgorithm.equals(DigestMethod.SHA256)) {
|
||||
Element digestMethodElement = DOMUtils.createElement(rsaPssParamsElement.getOwnerDocument(), Constants._TAG_DIGESTMETHOD,
|
||||
XMLSignature.XMLNS, prefix);
|
||||
digestMethodElement.setAttributeNS(null, Constants._ATT_ALGORITHM, xmlDigestAlgorithm);
|
||||
rsaPssParamsElement.appendChild(digestMethodElement);
|
||||
}
|
||||
if (spec.getSaltLength() != digestAlgorithm.getSaltLength()) {
|
||||
Element saltLengthElement = rsaPssParamsElement.getOwnerDocument().createElementNS(Constants.XML_DSIG_NS_MORE_07_05, "pss" + ":" + Constants._TAG_SALTLENGTH);
|
||||
Text saltLengthText = rsaPssParamsElement.getOwnerDocument().createTextNode(String.valueOf(spec.getSaltLength()));
|
||||
saltLengthElement.appendChild(saltLengthText);
|
||||
rsaPssParamsElement.appendChild(saltLengthElement);
|
||||
}
|
||||
} catch (DOMException | com.sun.org.apache.xml.internal.security.signature.XMLSignatureException e) {
|
||||
throw new MarshalException("Invalid digest name supplied: " + digestName);
|
||||
throw new MarshalException("Invalid digest name supplied: " + spec.getDigestAlgorithm());
|
||||
}
|
||||
rsaPssParamsElement.appendChild(digestMethodElement);
|
||||
|
||||
Element saltLengthElement = rsaPssParamsElement.getOwnerDocument().createElementNS(Constants.XML_DSIG_NS_MORE_07_05, "pss" + ":" + Constants._TAG_SALTLENGTH);
|
||||
Text saltLengthText = rsaPssParamsElement.getOwnerDocument().createTextNode(String.valueOf(saltLength));
|
||||
saltLengthElement.appendChild(saltLengthText);
|
||||
if (!spec.getMGFAlgorithm().equals("MGF1")) {
|
||||
throw new MarshalException("Unsupported MGF algorithm supplied: " + spec.getMGFAlgorithm());
|
||||
}
|
||||
|
||||
rsaPssParamsElement.appendChild(saltLengthElement);
|
||||
MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec)spec.getMGFParameters();
|
||||
try {
|
||||
DigestAlgorithm mgfDigestAlgorithm = DigestAlgorithm.fromDigestAlgorithm(mgfSpec.getDigestAlgorithm());
|
||||
if (mgfDigestAlgorithm != digestAlgorithm) {
|
||||
Element mgfElement = rsaPssParamsElement.getOwnerDocument().createElementNS(Constants.XML_DSIG_NS_MORE_07_05, "pss" + ":" + Constants._TAG_MGF);
|
||||
try {
|
||||
mgfElement.setAttributeNS(null, Constants._ATT_ALGORITHM, "http://www.w3.org/2007/05/xmldsig-more#MGF1");
|
||||
} catch (DOMException e) {
|
||||
throw new MarshalException("Should not happen");
|
||||
}
|
||||
Element mgfDigestMethodElement = DOMUtils.createElement(rsaPssParamsElement.getOwnerDocument(), Constants._TAG_DIGESTMETHOD,
|
||||
XMLSignature.XMLNS, prefix);
|
||||
String xmlDigestAlgorithm = mgfDigestAlgorithm.getXmlDigestAlgorithm();
|
||||
mgfDigestMethodElement.setAttributeNS(null, Constants._ATT_ALGORITHM, xmlDigestAlgorithm);
|
||||
mgfElement.appendChild(mgfDigestMethodElement);
|
||||
rsaPssParamsElement.appendChild(mgfElement);
|
||||
}
|
||||
} catch (DOMException | com.sun.org.apache.xml.internal.security.signature.XMLSignatureException e) {
|
||||
throw new MarshalException("Invalid digest name supplied: " + mgfSpec.getDigestAlgorithm());
|
||||
}
|
||||
|
||||
Element trailerFieldElement = rsaPssParamsElement.getOwnerDocument().createElementNS(Constants.XML_DSIG_NS_MORE_07_05, "pss" + ":" + Constants._TAG_TRAILERFIELD);
|
||||
Text trailerFieldText = rsaPssParamsElement.getOwnerDocument().createTextNode(String.valueOf(trailerField));
|
||||
trailerFieldElement.appendChild(trailerFieldText);
|
||||
if (spec.getTrailerField() != 1) {
|
||||
Element trailerFieldElement = rsaPssParamsElement.getOwnerDocument().createElementNS(Constants.XML_DSIG_NS_MORE_07_05, "pss" + ":" + Constants._TAG_TRAILERFIELD);
|
||||
Text trailerFieldText = rsaPssParamsElement.getOwnerDocument().createTextNode(String.valueOf(spec.getTrailerField()));
|
||||
trailerFieldElement.appendChild(trailerFieldText);
|
||||
rsaPssParamsElement.appendChild(trailerFieldElement);
|
||||
}
|
||||
|
||||
rsaPssParamsElement.appendChild(trailerFieldElement);
|
||||
if (rsaPssParamsElement.hasChildNodes()) {
|
||||
parent.appendChild(rsaPssParamsElement);
|
||||
}
|
||||
}
|
||||
|
||||
parent.appendChild(rsaPssParamsElement);
|
||||
private static DigestAlgorithm validateDigestAlgorithm(String input)
|
||||
throws MarshalException {
|
||||
try {
|
||||
return DigestAlgorithm.fromXmlDigestAlgorithm(input);
|
||||
} catch (com.sun.org.apache.xml.internal.security.signature.XMLSignatureException e) {
|
||||
throw new MarshalException("Invalid digest algorithm supplied: " + input);
|
||||
}
|
||||
}
|
||||
|
||||
SignatureMethodParameterSpec unmarshalParams(Element paramsElem)
|
||||
@ -176,35 +209,45 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
if (paramsElem != null) {
|
||||
Element saltLengthNode = XMLUtils.selectNode(paramsElem.getFirstChild(), Constants.XML_DSIG_NS_MORE_07_05, Constants._TAG_SALTLENGTH, 0);
|
||||
Element trailerFieldNode = XMLUtils.selectNode(paramsElem.getFirstChild(), Constants.XML_DSIG_NS_MORE_07_05, Constants._TAG_TRAILERFIELD, 0);
|
||||
int trailerField = 1;
|
||||
if (trailerFieldNode != null) {
|
||||
try {
|
||||
trailerField = Integer.parseInt(trailerFieldNode.getTextContent());
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new MarshalException("Invalid trailer field supplied: " + trailerFieldNode.getTextContent());
|
||||
Element digestAlgorithmNode = XMLUtils.selectDsNode(paramsElem.getFirstChild(), Constants._TAG_DIGESTMETHOD, 0);
|
||||
Element mgfNode = XMLUtils.selectNode(paramsElem.getFirstChild(), Constants.XML_DSIG_NS_MORE_07_05, Constants._TAG_MGF, 0);
|
||||
|
||||
DigestAlgorithm digestAlgorithm = digestAlgorithmNode != null
|
||||
? validateDigestAlgorithm(digestAlgorithmNode.getAttribute(Constants._ATT_ALGORITHM))
|
||||
: DigestAlgorithm.SHA256;
|
||||
|
||||
DigestAlgorithm mgfDigestAlgorithm = digestAlgorithm;
|
||||
if (mgfNode != null) {
|
||||
String mgfAlgorithm = mgfNode.getAttribute(Constants._ATT_ALGORITHM);
|
||||
if (!mgfAlgorithm.equals("http://www.w3.org/2007/05/xmldsig-more#MGF1")) {
|
||||
throw new MarshalException("Unknown MGF algorithm: " + mgfAlgorithm);
|
||||
}
|
||||
Element mgfDigestAlgorithmNode = XMLUtils.selectDsNode(mgfNode.getFirstChild(), Constants._TAG_DIGESTMETHOD, 0);
|
||||
if (mgfDigestAlgorithmNode != null) {
|
||||
mgfDigestAlgorithm = validateDigestAlgorithm(mgfDigestAlgorithmNode.getAttribute(Constants._ATT_ALGORITHM));
|
||||
}
|
||||
}
|
||||
String xmlAlgorithm = XMLUtils.selectDsNode(paramsElem.getFirstChild(), Constants._TAG_DIGESTMETHOD, 0).getAttribute(Constants._ATT_ALGORITHM);
|
||||
DigestAlgorithm digestAlgorithm;
|
||||
try {
|
||||
digestAlgorithm = DigestAlgorithm.fromXmlDigestAlgorithm(xmlAlgorithm);
|
||||
} catch (com.sun.org.apache.xml.internal.security.signature.XMLSignatureException e) {
|
||||
throw new MarshalException("Invalid digest algorithm supplied: " + xmlAlgorithm);
|
||||
}
|
||||
String digestName = digestAlgorithm.getDigestAlgorithm();
|
||||
|
||||
RSAPSSParameterSpec params = new RSAPSSParameterSpec();
|
||||
params.setTrailerField(trailerField);
|
||||
int saltLength;
|
||||
try {
|
||||
int saltLength = saltLengthNode == null ? digestAlgorithm.getSaltLength() : Integer.parseInt(saltLengthNode.getTextContent());
|
||||
params.setSaltLength(saltLength);
|
||||
saltLength = saltLengthNode == null ? digestAlgorithm.getSaltLength() : Integer.parseUnsignedInt(saltLengthNode.getTextContent());
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new MarshalException("Invalid salt length supplied: " + saltLengthNode.getTextContent());
|
||||
}
|
||||
params.setDigestName(digestName);
|
||||
return params;
|
||||
|
||||
int trailerField;
|
||||
try {
|
||||
trailerField = trailerFieldNode == null ? 1 : Integer.parseUnsignedInt(trailerFieldNode.getTextContent());
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new MarshalException("Invalid trailer field supplied: " + trailerFieldNode.getTextContent());
|
||||
}
|
||||
|
||||
return new RSAPSSParameterSpec(new PSSParameterSpec(
|
||||
digestAlgorithm.getDigestAlgorithm(),
|
||||
"MGF1", new MGF1ParameterSpec(mgfDigestAlgorithm.getDigestAlgorithm()),
|
||||
saltLength, trailerField));
|
||||
}
|
||||
return getDefaultParameterSpec();
|
||||
return DEFAULT_PSS_SPEC;
|
||||
}
|
||||
|
||||
boolean verify(Key key, SignedInfo si, byte[] sig,
|
||||
@ -230,7 +273,7 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
}
|
||||
signature.initVerify((PublicKey)key);
|
||||
try {
|
||||
signature.setParameter(new PSSParameterSpec(digestName, "MGF1", new MGF1ParameterSpec(digestName), saltLength, trailerField));
|
||||
signature.setParameter(spec);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new XMLSignatureException(e);
|
||||
}
|
||||
@ -270,7 +313,7 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
}
|
||||
signature.initSign((PrivateKey)key);
|
||||
try {
|
||||
signature.setParameter(new PSSParameterSpec(digestName, "MGF1", new MGF1ParameterSpec(digestName), saltLength, trailerField));
|
||||
signature.setParameter(spec);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new XMLSignatureException(e);
|
||||
}
|
||||
@ -292,14 +335,6 @@ public abstract class DOMRSAPSSSignatureMethod extends AbstractDOMSignatureMetho
|
||||
return getParameterSpec().equals(spec);
|
||||
}
|
||||
|
||||
private SignatureMethodParameterSpec getDefaultParameterSpec() {
|
||||
RSAPSSParameterSpec params = new RSAPSSParameterSpec();
|
||||
params.setTrailerField(trailerField);
|
||||
params.setSaltLength(saltLength);
|
||||
params.setDigestName(digestName);
|
||||
return params;
|
||||
}
|
||||
|
||||
static final class RSAPSS extends DOMRSAPSSSignatureMethod {
|
||||
RSAPSS(AlgorithmParameterSpec params)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
|
@ -21,13 +21,14 @@
|
||||
* under the License.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
*/
|
||||
package org.jcp.xml.dsig.internal.dom;
|
||||
|
||||
import javax.xml.crypto.*;
|
||||
import javax.xml.crypto.dom.DOMCryptoContext;
|
||||
import javax.xml.crypto.dsig.*;
|
||||
import javax.xml.crypto.dsig.spec.RSAPSSParameterSpec;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@ -35,8 +36,12 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.Provider;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.security.spec.MGF1ParameterSpec;
|
||||
import java.security.spec.PSSParameterSpec;
|
||||
import java.util.*;
|
||||
|
||||
import com.sun.org.apache.xml.internal.security.algorithms.implementations.SignatureBaseRSA;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
@ -152,6 +157,33 @@ public final class DOMSignedInfo extends DOMStructure implements SignedInfo {
|
||||
" when secure validation is enabled"
|
||||
);
|
||||
}
|
||||
if (secVal && signatureMethod instanceof DOMRSAPSSSignatureMethod.RSAPSS) {
|
||||
AlgorithmParameterSpec spec = signatureMethod.getParameterSpec();
|
||||
if (spec instanceof RSAPSSParameterSpec) {
|
||||
try {
|
||||
PSSParameterSpec pspec = ((RSAPSSParameterSpec) spec).getPSSParameterSpec();
|
||||
String da = SignatureBaseRSA.SignatureRSASSAPSS.DigestAlgorithm
|
||||
.fromDigestAlgorithm(pspec.getDigestAlgorithm()).getXmlDigestAlgorithm();
|
||||
if (Policy.restrictAlg(da)) {
|
||||
throw new MarshalException(
|
||||
"It is forbidden to use algorithm " + da + " in PSS when secure validation is enabled"
|
||||
);
|
||||
}
|
||||
AlgorithmParameterSpec mspec = pspec.getMGFParameters();
|
||||
if (mspec instanceof MGF1ParameterSpec) {
|
||||
String da2 = SignatureBaseRSA.SignatureRSASSAPSS.DigestAlgorithm
|
||||
.fromDigestAlgorithm(((MGF1ParameterSpec) mspec).getDigestAlgorithm()).getXmlDigestAlgorithm();
|
||||
if (Policy.restrictAlg(da2)) {
|
||||
throw new MarshalException(
|
||||
"It is forbidden to use algorithm " + da2 + " in MGF1 when secure validation is enabled"
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (com.sun.org.apache.xml.internal.security.signature.XMLSignatureException e) {
|
||||
// Unknown digest algorithm. Ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unmarshal References
|
||||
ArrayList<Reference> refList = new ArrayList<>(5);
|
||||
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
* reserved comment block
|
||||
* DO NOT REMOVE OR ALTER!
|
||||
*/
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.jcp.xml.dsig.internal.dom;
|
||||
|
||||
import javax.xml.crypto.dsig.spec.SignatureMethodParameterSpec;
|
||||
|
||||
public class RSAPSSParameterSpec implements SignatureMethodParameterSpec {
|
||||
|
||||
private int trailerField;
|
||||
private int saltLength;
|
||||
private String digestName;
|
||||
|
||||
public int getTrailerField() {
|
||||
return trailerField;
|
||||
}
|
||||
public void setTrailerField(int trailerField) {
|
||||
this.trailerField = trailerField;
|
||||
}
|
||||
public int getSaltLength() {
|
||||
return saltLength;
|
||||
}
|
||||
public void setSaltLength(int saltLength) {
|
||||
this.saltLength = saltLength;
|
||||
}
|
||||
public String getDigestName() {
|
||||
return digestName;
|
||||
}
|
||||
public void setDigestName(String digestName) {
|
||||
this.digestName = digestName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((digestName == null) ? 0 : digestName.hashCode());
|
||||
result = prime * result + saltLength;
|
||||
result = prime * result + trailerField;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RSAPSSParameterSpec other = (RSAPSSParameterSpec)obj;
|
||||
if (digestName == null) {
|
||||
if (other.digestName != null)
|
||||
return false;
|
||||
} else if (!digestName.equals(other.digestName))
|
||||
return false;
|
||||
if (saltLength != other.saltLength)
|
||||
return false;
|
||||
return trailerField == other.trailerField;
|
||||
}
|
||||
|
||||
}
|
@ -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…
x
Reference in New Issue
Block a user