I needed to implement XML signature in Java, so I will summarize it.
Create a pair of RSA private key and X.509 public key certificate
$ openssl req -out alice.crt -nodes -keyout alice.pem -newkey rsa:4096 -sha256 -x509 -days 365
Certificate verification
$ openssl x509 -in alice.crt -noout -text
To read the private key in Java, it is necessary to convert the private key to PKCS # 8 DER format.
$ openssl pkcs8 -in alice.pem -outform DER -out alice.pk8 -topk8 -nocrypt
The status of the file is as follows.
--alice.pk8: PKCS # 8 ยท DER format private key (RSA 4096 bit) --alice.crt: X.509 public key certificate (RSA / SHA-256) --alice.pem: PEM format private key (not used this time)
envelope.xml
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="urn:envelope">
</Envelope>
GenEnveloped.java
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.*;
import javax.xml.crypto.dsig.spec.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collections;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
public class GenEnveloped {
public static void main(String[] args) {
if (args.length != 4) {
System.err.println("Usage: java GenEnveloped [input XML path] [output XML path] [private key path (pk8)] [certificate path]");
System.exit(1);
}
try {
genEnveloped(args[0], args[1], args[2], args[3]);
} catch (Exception e) {
System.err.println(e);
System.exit(1);
}
}
public static void genEnveloped(String inXmlPath, String outXmlPath, String privateKeyPath, String certPath) throws Exception {
// Create a DOM XMLSignatureFactory that will be used to generate the
// enveloped signature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create a Reference to the enveloped document (in this case we are
// signing the whole document, so a URI of "" signifies that) and
// also specify the SHA256 digest algorithm and the ENVELOPED Transform.
DigestMethod dm = fac.newDigestMethod(DigestMethod.SHA256, null);
List<Transform> transforms = Collections.singletonList(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
Reference ref = fac.newReference("", dm, transforms, null, null);
// Create the SignedInfo
CanonicalizationMethod cm = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (C14NMethodParameterSpec) null);
SignatureMethod sm = fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null);
List<Reference> references = Collections.singletonList(ref);
SignedInfo si = fac.newSignedInfo(cm, sm, references);
// Read a RSA private key
FileInputStream fis = new FileInputStream(privateKeyPath);
byte[] privateKeyByte = new byte[fis.available()];
fis.read(privateKeyByte);
fis.close();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyByte);
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPrivateKey privateKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
// Read a X.509 certificate
KeyInfoFactory kif = fac.getKeyInfoFactory();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(new FileInputStream(certPath));
X509Data x509Data = kif.newX509Data(Collections.singletonList(cert));
// Create a KeyInfo and add the X509Data to it
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509Data));
// Instantiate the document to be signed
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(inXmlPath));
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element
DOMSignContext dsc = new DOMSignContext(privateKey, doc.getDocumentElement());
// Create the XMLSignature (but don't sign it yet)
XMLSignature signature = fac.newXMLSignature(si, ki);
// Marshal, generate (and sign) the enveloped signature
signature.sign(dsc);
// output the resulting document
OutputStream os = new FileOutputStream(outXmlPath);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.transform(new DOMSource(doc), new StreamResult(os));
}
}
compile
$ javac GenEnveloped.java
Usage
$ java GenEnveloped [input XML path] [output XML path] [private key path (pk8)] [certificate path]
Execution example
$ java GenEnveloped envelope.xml envelopedSignature.xml alice.pk8 alice.crt
Validate.java
import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.*;
import java.io.FileInputStream;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
public class Validate {
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Validate [input XML path]");
System.exit(1);
}
try {
validate(args[0]);
} catch (Exception e) {
System.err.println(e);
System.exit(1);
}
}
public static boolean validate(String inXmlPath) throws Exception {
// Instantiate the document to be validated
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(inXmlPath));
// Find Signature element
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0)
throw new Exception("Cannot find Signature element");
// Create a DOM XMLSignatureFactory that will be used to unmarshal the
// document containing the XMLSignature
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
// Create a DOMValidateContext and specify a KeyValue KeySelector
// and document context
DOMValidateContext valContext = new DOMValidateContext(new KeyValueKeySelector(), nl.item(0));
// unmarshal the XMLSignature
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Validate the XMLSignature (generated above)
boolean coreValidity = signature.validate(valContext);
// Check core validation status
if (!coreValidity) {
System.err.println("Signature failed core validation");
boolean sv = signature.getSignatureValue().validate(valContext);
System.out.println("signature validation status: " + sv);
// check the validation status of each Reference
Iterator i = signature.getSignedInfo().getReferences().iterator();
for (int j = 0; i.hasNext(); j++) {
boolean refValid = ((Reference) i.next()).validate(valContext);
System.out.println("ref[" + j + "] validity status: " + refValid);
}
} else
System.out.println("Signature passed core validation");
return coreValidity;
}
/**
* KeySelector which retrieves the public key out of the
* KeyValue element and returns it.
* NOTE: If the key algorithm doesn't match signature algorithm,
* then the public key will be ignored.
*/
private static class KeyValueKeySelector extends KeySelector {
public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
if (keyInfo == null)
throw new KeySelectorException("Null KeyInfo object!");
SignatureMethod sm = (SignatureMethod) method;
for (Object keyInfoContent : keyInfo.getContent()) {
if (keyInfoContent instanceof X509Data) {
for (Object x509Content : ((X509Data) keyInfoContent).getContent()) {
X509Certificate cert = (X509Certificate) x509Content;
PublicKey pk = cert.getPublicKey();
// make sure algorithm is compatible with method
if (algEquals(sm.getAlgorithm(), pk.getAlgorithm()))
return new SimpleKeySelectorResult(pk);
}
}
}
throw new KeySelectorException("No KeyValue element found!");
}
static boolean algEquals(String algURI, String algName) {
if (algName.equalsIgnoreCase("RSA") && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA256))
return true;
else if (algName.equalsIgnoreCase("DSA") && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1))
return true;
else if (algName.equalsIgnoreCase("RSA") && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))
return true;
else
return false;
}
}
private static class SimpleKeySelectorResult implements KeySelectorResult {
private PublicKey pk;
SimpleKeySelectorResult(PublicKey pk) {
this.pk = pk;
}
public Key getKey() {
return pk;
}
}
}
compile
$ javac Validate.java
Usage
$ java Validate [input XML path]
Execution example (verification successful)
$ java Validate envelopedSignature.xml
Signature passed core validation
Execution example (verification failure)
$ java Validate envelopedSignature.xml
Signature failed core validation
signature validation status: false
ref[0] validity status: true
P.S. Click here for the source code. haru52/xmldsig
References -Java XML Digital Signature API --XML signature using Java's XML digital signature API --Qiita --Verify XML signature using Java's XML digital signature API. --Qiita
Recommended Posts