8241306: Add SignatureMethodParameterSpec subclass for RSASSA-PSS params

Reviewed-by: mullan
This commit is contained in:
Weijun Wang 2021-04-19 14:29:18 +00:00
parent b14e0ee4d8
commit 8dbf7aa1f9
11 changed files with 1210 additions and 161 deletions

View File

@ -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;

View File

@ -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";

View File

@ -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>.

View File

@ -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>
* &lt;xs:element name="RSAPSSParams" type="pss:RSAPSSParamsType"&gt;
* &lt;xs:annotation&gt;
* &lt;xs:documentation&gt;
* Top level element that can be used in xs:any namespace="#other"
* wildcard of ds:SignatureMethod content.
* &lt;/xs:documentation&gt;
* &lt;/xs:annotation&gt;
* &lt;/xs:element&gt;
* &lt;xs:complexType name="RSAPSSParamsType"&gt;
* &lt;xs:sequence&gt;
* &lt;xs:element ref="ds:DigestMethod" minOccurs="0"/&gt;
* &lt;xs:element name="MaskGenerationFunction"
* type="pss:MaskGenerationFunctionType" minOccurs="0"/&gt;
* &lt;xs:element name="SaltLength" type="xs:int"
* minOccurs="0"/&gt;
* &lt;xs:element name="TrailerField" type="xs:int"
* minOccurs="0"/&gt;
* &lt;/xs:sequence&gt;
* &lt;/xs:complexType&gt;
* &lt;xs:complexType name="MaskGenerationFunctionType"&gt;
* &lt;xs:sequence&gt;
* &lt;xs:element ref="ds:DigestMethod" minOccurs="0"/&gt;
* &lt;/xs:sequence&gt;
* &lt;xs:attribute name="Algorithm" type="xs:anyURI"
* default="http://www.w3.org/2007/05/xmldsig-more#MGF1"/&gt;
* &lt;/xs:complexType&gt;
* </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;
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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,

View 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());
}
}

View 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));
}
}

View 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";
}
}