Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8259535

ECDSA SignatureValue do not always have the specified length

    XMLWordPrintable

    Details

    • Subcomponent:
    • Resolved In Build:
      b12
    • CPU:
      generic
    • OS:
      generic
    • Verification:
      Verified

      Backports

        Description

        A DESCRIPTION OF THE PROBLEM :
        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


          Attachments

            Issue Links

              Activity

                People

                Assignee:
                weijun Weijun Wang
                Reporter:
                webbuggrp Webbug Group
                Votes:
                0 Vote for this issue
                Watchers:
                6 Start watching this issue

                  Dates

                  Created:
                  Updated:
                  Resolved: