Implement XML signature in Java

I needed to implement XML signature in Java, so I will summarize it.

environment

Preparation: Creating a private key / certificate

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)

Sample XML file

envelope.xml


<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="urn:envelope">
</Envelope>

XML signature program

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

XML signature verification program

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

Implement XML signature in Java
Format XML in Java
Implement two-step verification in Java
Implement Basic authentication in Java
Implement math combinations in Java
2 Implement simple parsing in Java
Implement Email Sending in Java
Implement functional quicksort in Java
Implement rm -rf in Java.
Implement Table Driven Test in Java 14
3 Implement a simple interpreter in Java
Try to implement Yubaba in Java
1 Implement simple lexical analysis in Java
How to implement date calculation in Java
How to implement Kalman filter in Java
Implement API Gateway Lambda Authorizer in Java Lambda
Partization in Java
Try to implement n-ary addition in Java
Changes in Java 11
Output Notes document as XML document in Java
Rock-paper-scissors in Java
How to implement coding conventions in Java
Pi in Java
FizzBuzz in Java
I tried to implement deep learning in Java
Interpreter implementation in Java
Make Blackjack in Java
Rock-paper-scissors app in Java
Constraint programming in Java
Put java8 in centos7
NVL-ish guy in Java
"Hello World" in Java
Callable Interface in Java
Comments in Java source
Azure functions in java
Simple htmlspecialchars in Java
Boyer-Moore implementation in Java
Use OpenCV in Java
webApi memorandum in java
Type determination in Java
Ping commands in Java
Heapsort implementation (in java)
Zabbix API in Java
ASCII art in Java
Compare Lists in Java
POST JSON in Java
Express failure in Java
Implement CustomView in code
Create JSON in Java
What's new in Java 8
Use PreparedStatement in Java
What's new in Java 9,10,11
Parallel execution in Java
Initializing HashMap in Java
I tried to implement Firebase push notification in Java
Quickly implement a singleton with an enum in Java
[Java] [XML signature] Add prefix and ID to Signature tag
Summary of how to implement default arguments in Java
I tried to implement the Euclidean algorithm in Java
Implement application function in Rails
Try using RocksDB in Java