0
    We can create XML Digital Signature using RSA keys. But how do I use elliptic curve keys to sign xml files ? I get exception at this line xmlSignature.sign(domSignCtx) as - 
    
    javax.xml.crypto.MarshalException: Invalid ECParameterSpec
        at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMKeyValue$EC.marshalPublicKey(DOMKeyValue.java:493)
        at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMKeyValue.marshal(DOMKeyValue.java:121)
        at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMKeyInfo.marshal(DOMKeyInfo.java:207)
        at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMKeyInfo.marshal(DOMKeyInfo.java:197)
        at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMXMLSignature.marshal(DOMXMLSignature.java:233)
        at java.xml.crypto/org.jcp.xml.dsig.internal.dom.DOMXMLSignature.sign(DOMXMLSignature.java:333)
        at com.test.GenerateXMLSignature.generateXMLDigitalSignature(GenerateXMLSignature.java:117)
        at com.test.GenerateXMLSignature.main(GenerateXMLSignature.java:60)

I am trying to add digital signature to the existing xml document. I am reading private key and public key from .pem file. And I can generate and verify same for SignatureMethod.RSA_SHA256. But for ECDSA_SHA256 I am getting Invalid ECParameterSpec exception. Please suggest how to generate XML Signature for secp256k1 curve using SHA256WithECDSA algorithm in java.

using: java version "15.0.1" 2020-10-20 Java(TM) SE Runtime Environment (build 15.0.1+9-18) Java HotSpot(TM) 64-Bit Server VM (build 15.0.1+9-18, mixed mode, sharing)

Below is my source code:

package com.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.util.Arrays;
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.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class GenerateXMLSignature {
    public static final String INPUT_FILE_NAME = "D:\\XML\\staff-dom.xml";
    public static final String OUTPUT_FILE_NAME = "D:\\XML\\sample.xml";
    public static final String CERT_FILE_PATH = "D:\\XML\\openssl-0.9.8k_X64\\bin\\cert.pem";
    public static final String PRIVATE_KEY_FILE_PATH = "D:\\XML\\openssl-0.9.8k_X64\\bin\\pkcs8file.pem";
    public static final String PUBLIC_KEY_FILE_PATH = "D:\\XML\\openssl-0.9.8k_X64\\bin\\PublicKey.pem";
    public static void main(String[] args) throws Exception {
        generateXMLDigitalSignature(INPUT_FILE_NAME, OUTPUT_FILE_NAME, PRIVATE_KEY_FILE_PATH, PUBLIC_KEY_FILE_PATH);
    }

    /**
     * Method used to attach a generated digital signature to the existing document
     *
     * @param originalXmlFilePath
     * @param destnSignedXmlFilePath
     * @param privateKeyFilePath
     * @param publicKeyFilePath
     * @throws Exception
     */

    public static void generateXMLDigitalSignature(String originalXmlFilePath, String destnSignedXmlFilePath,
            String privateKeyFilePath, String publicKeyFilePath) throws Exception {
        
        int addProvider = Security.addProvider(new BouncyCastleProvider());
        System.out.println(addProvider);
        String[] curves = Security.getProvider("SunEC").getProperty("AlgorithmParameters.EC SupportedCurves").split("\\|");
        System.out.println(Arrays.toString(curves));
        
        // Get the XML Document object
        Document doc = getXmlDocument(originalXmlFilePath);
        // Create XML Signature Factory
        XMLSignatureFactory xmlSigFactory = XMLSignatureFactory.getInstance("DOM");
        PrivateKey privateKey = new KryptoUtil().readECPrivateKey(privateKeyFilePath);
        System.out.println("Private Key===="+ privateKey);
        DOMSignContext domSignCtx = new DOMSignContext((Key) privateKey, doc.getDocumentElement());
        Reference ref = null;
        SignedInfo signedInfo = null;
        try {
            ref = xmlSigFactory.newReference("", xmlSigFactory.newDigestMethod(DigestMethod.SHA256, null),
                    Collections.singletonList(
                            xmlSigFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)),
                    null, null);
            signedInfo = xmlSigFactory.newSignedInfo(
                    xmlSigFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE,
                            (C14NMethodParameterSpec) null),
                    xmlSigFactory.newSignatureMethod(SignatureMethod.ECDSA_SHA256, null), Collections.singletonList(ref));
            System.out.println(signedInfo.getSignatureMethod().getAlgorithm());
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        } catch (InvalidAlgorithmParameterException ex) {
            ex.printStackTrace();
        }
        // Pass the Public Key File Path
        KeyInfo keyInfo = null;
        try {
            keyInfo = getKeyInfo(xmlSigFactory, publicKeyFilePath);
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        // Create a new XML Signature
        XMLSignature xmlSignature = xmlSigFactory.newXMLSignature(signedInfo, keyInfo);
        try {
            // Sign the document
            xmlSignature.sign(domSignCtx);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        // Store the digitally signed document int0 a location
        storeSignedDoc(doc, destnSignedXmlFilePath);
    }

    /**
     * Method used to get the KeyInfo
     *
     * @param xmlSigFactory
     * @param publicKeyPath
     * @return KeyInfo
     * @throws Exception 
     * @throws CertificateException 
     */
    private static KeyInfo getKeyInfo(XMLSignatureFactory xmlSigFactory, String publicKeyPath) throws CertificateException, Exception {
        KeyInfo keyInfo = null;
        KeyValue keyValue = null;
        PublicKey publicKey = new KryptoUtil().readECPublicKey(publicKeyPath);
        System.out.println("Public Key===="+ publicKey);
        KeyInfoFactory keyInfoFact = xmlSigFactory.getKeyInfoFactory();

        try {
            keyValue = keyInfoFact.newKeyValue(publicKey);
        } catch (KeyException ex) {
            ex.printStackTrace();
        }
        keyInfo = keyInfoFact.newKeyInfo(Collections.singletonList(keyValue));
        return keyInfo;
    }

    private static Document getXmlDocument(String originalXmlFilePath) {
         Document doc = null;
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            try {
                System.out.println(originalXmlFilePath);
                doc = dbf.newDocumentBuilder().parse(new FileInputStream(originalXmlFilePath));
            } catch (ParserConfigurationException ex) {
                ex.printStackTrace();
            } catch (FileNotFoundException ex) {
                ex.printStackTrace();
            } catch (SAXException ex) {
                ex.printStackTrace();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return doc;
        }
    
    
     /*
     * Method used to store the signed XMl document
     */
    private static void storeSignedDoc(Document doc, String destnSignedXmlFilePath) {
        TransformerFactory transFactory = TransformerFactory.newInstance();
        Transformer trans = null;
        try {
            trans = transFactory.newTransformer();
        } catch (TransformerConfigurationException ex) {
            ex.printStackTrace();
        }
        try {
            StreamResult streamRes = new StreamResult(new File(destnSignedXmlFilePath));
            trans.transform(new DOMSource(doc), streamRes);
        } catch (TransformerException ex) {
            ex.printStackTrace();
        }
        System.out.println("XML file with attached digital signature generated successfully ...");
    }

}


    /**
     * Method used to get the generated Private Key
     *
     * @param filePath of the PrivateKey file
     * @return PrivateKey
     * @throws Exception 
     * @throws GeneralSecurityException 
     */
    public PrivateKey readECPrivateKey(String filePath) throws Exception {
         Security.addProvider(new BouncyCastleProvider());
            File f = new File(filePath);
            FileInputStream fis = new FileInputStream(f);
            DataInputStream dis = new DataInputStream(fis);
            byte[] keyBytes = new byte[(int) f.length()];
            dis.readFully(keyBytes);
            dis.close();
            String temp = new String(keyBytes);
            String privatePem = temp.replace("-----BEGIN PRIVATE KEY-----\n", "");
            privatePem = privatePem.replace("-----END PRIVATE KEY-----", "");
            org.bouncycastle.util.encoders.Base64 b64 = new org.bouncycastle.util.encoders.Base64();
            byte[] decoded = b64.decode(privatePem);
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
            KeyFactory factory = KeyFactory.getInstance("EC");
            PrivateKey privateKey = factory.generatePrivate(spec);
            System.out.println("PrivateKey===="+ privateKey);
            return privateKey;
    }

/**
     * Method used to get the generated Public Key
     *
     * @param filePath of the PublicKey file
     * @return PublicKey
     * @throws IOException 
     * @throws NoSuchProviderException 
     * @throws InvalidKeySpecException 
     * @throws NoSuchAlgorithmException 
     */
    public PublicKey readECPublicKey(String filePath) throws IOException, NoSuchProviderException, Exception {
        Security.addProvider(new BouncyCastleProvider());
        File f = new File(filePath);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();
        String temp = new String(keyBytes);
        String publicKeyPEM = temp.replace("-----BEGIN PUBLIC KEY-----\n", "");
        publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
        org.bouncycastle.util.encoders.Base64 b64 = new org.bouncycastle.util.encoders.Base64();
        byte[] decoded = b64.decode(publicKeyPEM);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
        KeyFactory kf = KeyFactory.getInstance("EC", "BC");
        return kf.generatePublic(spec);
    }

0 Answers0