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