-
Bug
-
Resolution: Fixed
-
P4
-
8, 11, 16
-
b12
-
generic
-
generic
-
Verified
Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8280789 | 15.0.7 | Yuri Nesterenko | P4 | Resolved | Fixed | b01 |
JDK-8280800 | 13.0.11 | Yuri Nesterenko | P4 | Resolved | Fixed | b01 |
JDK-8270172 | 11.0.13-oracle | Evan Whelan | P4 | Resolved | Fixed | b02 |
JDK-8271212 | 11.0.13 | Martin Doerr | P4 | Resolved | Fixed | b01 |
JDK-8270359 | 8u311 | Evan Whelan | P4 | Resolved | Fixed | b03 |
Following https://www.w3.org/TR/xmldsig-core/#sec-ECDSA and RFC 3447, SignatureValue should have length = twice "the size of the base point order of the curve in bytes (e.g. 32 for the P-256 curve and 66 for the P-521 curve)".
On some occasions, where R and S are smaller and do not require 32 bytes to be encoded, the Signature Value is shorter.
This is a specification violation, which is caused by com.sun.org.apache.xml.internal.security.algorithms.implementations.ECDSAUtils.convertASN1toXMLDSIG and com.sun.org.apache.xml.internal.security.algorithms.implementations.ECDSAUtils.convertXMLDSIGtoASN1 not having access to the desired length for the Crypto Algorithm in question; though the class DOMXMLSignature have the Crypto Algorithm specification available, in the end the ECDSA utils do not receive either the algorithm, or the size of a raw-encoded Public Key, or the size of the signature.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Sign multiple XML files using Elliptic Curve P256 and eventually with probability 44/3236331 %= 0.0014% there will be a signature produced with has less than 64 bytes.
The source code below contains a self contained Test to generate and validate signatures with an embedded Public and Private Key for the above elliptic curve. It also contains a simple XML that is repeatedly signed & verified until a short signature is found; when this happens the signedXML is output and the program stops.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
We expected that all signatures would have 64 bytes.
<testXmlFile>
<element>Value</element>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>yLYfpzHwkcYGpibiLAWJ44SOSIYAUE1MON3vKW0Bxhw=</DigestValue></Reference></SignedInfo><SignatureValue>AOEmJJO7eKkofzjFMdoO29IuxROCUDjVraQ1TTNXBSsAU0nFZvDNtOinF8xKTrevJTMOeX9NUr6uoJNxGEjcow==</SignatureValue><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName>CN=DCO_CA,OU=07</X509IssuerName><X509SerialNumber>88394215025268693265449480746622700251</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo></Signature></testXmlFile>
We would expect that SignatureValue would be
AOEmJJO7eKkofzjFMdoO29IuxROCUDjVraQ1TTNXBSsAU0nFZvDNtOinF8xKTrevJTMOeX9NUr6uoJNxGEjcow==
which in hexadecimal is:
00e1262493bb78a9287f38c531da0edbd22ec513825038d5ada4354d3357052b
005349c566f0cdb4e8a717cc4a4eb7af25330e797f4d52beaea093711848dca3
with length = 64
ACTUAL -
Some signatures have less than 64 bytes.
See for instance:
<testXmlFile>
<element>Value</element>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>yLYfpzHwkcYGpibiLAWJ44SOSIYAUE1MON3vKW0Bxhw=</DigestValue></Reference></SignedInfo><SignatureValue>4SYkk7t4qSh/OMUx2g7b0i7FE4JQONWtpDVNM1cFK1NJxWbwzbTopxfMSk63ryUzDnl/TVK+rqCT
cRhI3KM=</SignatureValue><KeyInfo><X509Data><X509IssuerSerial><X509IssuerName>CN=DCO_CA,OU=07</X509IssuerName><X509SerialNumber>88394215025268693265449480746622700251</X509SerialNumber></X509IssuerSerial></X509Data></KeyInfo></Signature></testXmlFile>
SignatureValue: 4SYkk7t4qSh/OMUx2g7b0i7FE4JQONWtpDVNM1cFK1NJxWbwzbTopxfMSk63ryUzDnl/TVK+rqCT
cRhI3KM=
which is in hexadecimal
e1262493bb78a9287f38c531da0edbd22ec513825038d5ada4354d3357052b
5349c566f0cdb4e8a717cc4a4eb7af25330e797f4d52beaea093711848dca3
with length = 62.
---------- BEGIN SOURCE ----------
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collections;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.DigestMethodParameterSpec;
import javax.xml.crypto.dsig.spec.SignatureMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class XmlSignatureTest {
public static class SigningInfo {
public static final String XML = "<testXmlFile>\n"
+ "\t<element>Value</element>\n"
+ "</testXmlFile>";
private static final String CERTIFICATE_BASE_64 = "MIIBmTCCAT6gAwIBAgIQQoAbR7K5zOTecRJObXbK2zAKBggqhkjOPQQDAjAeMQsw"
+ "CQYDVQQLDAIwNzEPMA0GA1UEAwwGRENPX0NBMCAXDTE4MDEwMTAwMDAwMFoYDzIx"
+ "MTgwMTAxMDAwMDAwWjAhMQswCQYDVQQLDAIwNDESMBAGA1UELQMJAJCz1R8wAAAC"
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEt9wWszdOrTp17C2GrT75pKxFQ8kV"
+ "o8s8ZQlPnp/DzyeBE1BkYc/IdXwBCxJCktApMuTOtaEFcoksR2l4bpx9vaNZMFcw"
+ "HQYDVR0gAQH/BBMwETAPBg0qhjoAAYSPuQ8BAgEBMBEGA1UdDgQKBAhKuB+SK9L+"
+ "SzATBgNVHSMEDDAKgAhBXTR3OTOS2jAOBgNVHQ8BAf8EBAMCB4AwCgYIKoZIzj0E"
+ "AwIDSQAwRgIhANPxRYPL9PCwupqEVkBYZWP7lwrYb6vBtT87VxcOB1ZLAiEAhf1H"
+ "ZCwkOMCfdjR6g5HSD5XUMdMhVfLOuxUIdRPUess=";
private static final String PRIVATE_KEY_BASE_64 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgP6yNCRsISznuzY4D"
+ "0cwkBjgV8uu2lQ2tCPxdam7Fx9OhRANCAAS33BazN06tOnXsLYatPvmkrEVDyRWj"
+ "yzxlCU+en8PPJ4ETUGRhz8h1fAELEkKS0Cky5M61oQVyiSxHaXhunH29";
private static X509Certificate certificate = null;
private static PrivateKey privateKey = null;
public static X509Certificate getCertificate() throws Exception {
if (certificate == null) {
certificate = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(CERTIFICATE_BASE_64)));
}
return certificate;
}
public static PrivateKey getPrivateKey() throws Exception {
if (privateKey == null) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY_BASE_64));
KeyFactory kf = KeyFactory.getInstance("EC");
privateKey = kf.generatePrivate(keySpec);
}
return privateKey;
}
}
public static class XmlUtils {
public static Document convertStringToDocument(String xmlString) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xmlString)));
}
public static String getFirstElementValueByName(Document document, String elementName) {
String value = "";
NodeList nodeList = document.getElementsByTagName(elementName);
if (nodeList.getLength() == 1) {
value = nodeList.item(0).getFirstChild().getNodeValue();
}
return value;
}
public static String convertDocumentToString(Document document) throws TransformerException {
document.setXmlStandalone(true);
StringWriter writer = new StringWriter();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty("omit-xml-declaration", "yes");
transformer.transform(new DOMSource(document), new StreamResult(writer));
return writer.toString();
}
}
public static class XmlSigningUtils {
private static final XMLSignatureFactory XML_SIGNATURE_FACTORY = XMLSignatureFactory.getInstance("DOM");
public static Document signDocument(Document document, X509Certificate certificate, PrivateKey privateKey) throws Exception {
DOMResult result = new DOMResult();
TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), result);
Document newDocument = (Document) result.getNode();
XML_SIGNATURE_FACTORY.newXMLSignature(buildSignedInfo(), buildKeyInfo(certificate))
.sign(new DOMSignContext(privateKey, newDocument.getDocumentElement()));
return newDocument;
}
public static boolean validateSignature(Document document, X509Certificate certificate) throws Exception {
NodeList nodeList = document.getElementsByTagName("Signature");
if (nodeList.getLength() == 1) {
Node signatureNode = nodeList.item(0);
if (signatureNode != null) {
DOMValidateContext valContext = new DOMValidateContext(certificate.getPublicKey(), signatureNode);
return XMLSignatureFactory.getInstance("DOM").unmarshalXMLSignature(valContext).validate(valContext);
}
}
return false;
}
private static SignedInfo buildSignedInfo() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
CanonicalizationMethod canonicalizationMethod = XML_SIGNATURE_FACTORY
.newCanonicalizationMethod("http://www.w3.org/2001/10/xml-exc-c14n#", (C14NMethodParameterSpec) null);
SignatureMethod signatureMethod = XML_SIGNATURE_FACTORY
.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", (SignatureMethodParameterSpec) null);
Transform transform = XML_SIGNATURE_FACTORY
.newTransform("http://www.w3.org/2000/09/xmldsig#enveloped-signature", (TransformParameterSpec) null);
DigestMethod digestMethod = XML_SIGNATURE_FACTORY
.newDigestMethod("http://www.w3.org/2001/04/xmlenc#sha256", (DigestMethodParameterSpec) null);
Reference reference = XML_SIGNATURE_FACTORY
.newReference("", digestMethod, Collections.singletonList(transform), (String) null, (String) null);
return XML_SIGNATURE_FACTORY.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference));
}
private static KeyInfo buildKeyInfo(X509Certificate certificate) {
KeyInfoFactory keyInfoFactory = XML_SIGNATURE_FACTORY.getKeyInfoFactory();
X509IssuerSerial x509IssuerSerial = keyInfoFactory
.newX509IssuerSerial(certificate.getIssuerX500Principal().getName(), certificate.getSerialNumber());
X509Data x509Data = keyInfoFactory.newX509Data(Collections.singletonList(x509IssuerSerial));
return keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data));
}
}
public static void main(String[] args) throws Exception {
final Document document = XmlUtils.convertStringToDocument(SigningInfo.XML);
final X509Certificate certificate = SigningInfo.getCertificate();
final PrivateKey privateKey = SigningInfo.getPrivateKey();
boolean valid;
Document signedDocument;
do {
signedDocument = XmlSigningUtils.signDocument(document, certificate, privateKey);
valid = XmlSigningUtils.validateSignature(signedDocument, certificate);
valid &= Base64.getDecoder()
.decode(XmlUtils.getFirstElementValueByName(signedDocument, "SignatureValue").replace("\n", ""))
.length == 64;
} while (valid);
System.out.println(XmlUtils.convertDocumentToString(signedDocument));
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
When generating signatures, pad them manually with as many 0 bytes as needed on both R and S to achieve length of 64 bytes.
FREQUENCY : always
- backported by
-
JDK-8270172 ECDSA SignatureValue do not always have the specified length
-
- Resolved
-
-
JDK-8270359 ECDSA SignatureValue do not always have the specified length
-
- Resolved
-
-
JDK-8271212 ECDSA SignatureValue do not always have the specified length
-
- Resolved
-
-
JDK-8280789 ECDSA SignatureValue do not always have the specified length
-
- Resolved
-
-
JDK-8280800 ECDSA SignatureValue do not always have the specified length
-
- Resolved
-
- relates to
-
JDK-8270797 ShortECDSA.java test is not complete
-
- Resolved
-
-
JDK-8312214 ECDSA SignatureValue do not always have the specified length
-
- Open
-
- links to
-
Commit openjdk/jdk11u-dev/63c4ec23
-
Commit openjdk/jdk13u-dev/2a2a1393
-
Commit openjdk/jdk15u-dev/4bb9d4bf
-
Commit openjdk/jdk/a4c24961
-
Review openjdk/jdk11u-dev/150
-
Review openjdk/jdk13u-dev/315
-
Review openjdk/jdk15u-dev/161
-
Review openjdk/jdk/2550