Skip to content

Instantly share code, notes, and snippets.

@sharif2008
Forked from rponte/SignedXml.java
Created November 2, 2022 13:34
Show Gist options
  • Save sharif2008/a14921405cac2aed5ebb5cf39303d343 to your computer and use it in GitHub Desktop.
Save sharif2008/a14921405cac2aed5ebb5cf39303d343 to your computer and use it in GitHub Desktop.

Revisions

  1. @rponte rponte revised this gist Jul 30, 2019. 1 changed file with 5 additions and 3 deletions.
    8 changes: 5 additions & 3 deletions XmlSignerTest.java
    Original file line number Diff line number Diff line change
    @@ -19,20 +19,22 @@ public class XmlSignerTest {

    @Test
    public void shouldSignXmlUsingAJavaKeyStore() throws Exception {

    InputStream xml = XmlSigner.class.getResourceAsStream("/request-info.xml");
    // scenario
    InputStream xml = XmlSigner.class.getResourceAsStream("/xml-document-sample.xml");
    InputStream jks = XmlSigner.class.getResourceAsStream("/my-private-key.jks");

    String alias = "root";
    String password = "secret";

    // action
    SignedXml signedXml = new XmlSigner()
    .withXml( "\n<request> "
    + "\n <another-tag name='foo' /> "
    + "\n</request> ") // it supports InputStream too
    + "\n</request> ") // it supports InputStream as well
    .withKeyStore(jks, alias, password)
    .sign();

    // validation
    String content = signedXml.getContent();
    logger.info("\n" + content); // just prints the result

  2. @rponte rponte revised this gist Oct 26, 2018. 4 changed files with 15 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions SignedXml.java
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,9 @@

    import java.util.Base64;

    /**
    * Represents a signed XML
    */
    public class SignedXml {

    private String content;
    6 changes: 6 additions & 0 deletions XmlSigner.java
    Original file line number Diff line number Diff line change
    @@ -49,6 +49,9 @@
    import org.w3c.dom.Document;
    import org.xml.sax.SAXException;

    /**
    * Responsible for signing a specific XML using private key certificate
    */
    public class XmlSigner {

    private static final String C14NEXC = "http://www.w3.org/2001/10/xml-exc-c14n#";
    @@ -57,6 +60,9 @@ public class XmlSigner {
    private InputStream sourceXml;

    /**
    * Signs a specific XML using a private key via Java Key Store format
    *
    * More information:
    * https://gist.github.com/rponte/4039958
    * https://github.com/SUNET/eduid-mm-service/tree/master/src/main/java/se/gov/minameddelanden/common
    * https://stackoverflow.com/questions/5330049/java-equivalent-of-c-sharp-xml-signing-method
    3 changes: 3 additions & 0 deletions XmlSignerTest.java
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,9 @@

    import org.apache.commons.io.IOUtils;

    /**
    * How to use XmlSigner
    */
    public class XmlSignerTest {

    private static final Logger logger = LoggerFactory.getLogger(XmlSignerTest.class);
    3 changes: 3 additions & 0 deletions keyStoreInfo.java
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,9 @@
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.CertificateException;

    /**
    * Holds information about the Java Key Store
    */
    public class keyStoreInfo {

    private static final String KEY_STORE_TYPE = "JKS";
  3. @rponte rponte revised this gist Oct 26, 2018. 1 changed file with 2 additions and 3 deletions.
    5 changes: 2 additions & 3 deletions XmlSignerTest.java
    Original file line number Diff line number Diff line change
    @@ -22,12 +22,11 @@ public void shouldSignXmlUsingAJavaKeyStore() throws Exception {

    String alias = "root";
    String password = "secret";
    String parsedXml = IOUtils.toString(xml, StandardCharsets.UTF_8);

    SignedXml signedXml = new XmlSigner()
    .withXml( "\n<request> "
    + "\n <another-tag name=\"foo\"/> "
    + "\n</request> ")
    + "\n <another-tag name='foo' /> "
    + "\n</request> ") // it supports InputStream too
    .withKeyStore(jks, alias, password)
    .sign();

  4. @rponte rponte revised this gist Oct 26, 2018. 1 changed file with 10 additions and 8 deletions.
    18 changes: 10 additions & 8 deletions XmlSignerTest.java
    Original file line number Diff line number Diff line change
    @@ -25,17 +25,19 @@ public void shouldSignXmlUsingAJavaKeyStore() throws Exception {
    String parsedXml = IOUtils.toString(xml, StandardCharsets.UTF_8);

    SignedXml signedXml = new XmlSigner()
    .withXml(parsedXml)
    .withKeyStore(jks, alias, password)
    .sign();
    .withXml( "\n<request> "
    + "\n <another-tag name=\"foo\"/> "
    + "\n</request> ")
    .withKeyStore(jks, alias, password)
    .sign();

    String content = signedXml.getContent();
    String content = signedXml.getContent();
    logger.info("\n" + content); // just prints the result

    assertThat(content)
    .contains("<X509Certificate>")
    .contains("</X509Data>")
    .contains("</Signature>")
    ;
    .contains("<X509Certificate>")
    .contains("</X509Data>")
    .contains("</Signature>")
    ;
    }
    }
  5. @rponte rponte renamed this gist Oct 26, 2018. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. @rponte rponte revised this gist Oct 26, 2018. 1 changed file with 41 additions and 0 deletions.
    41 changes: 41 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    package br.com.mdias.rponte.signature;

    import static org.assertj.core.api.Assertions.assertThat;

    import java.io.InputStream;
    import org.junit.Rule;
    import org.junit.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import org.apache.commons.io.IOUtils;

    public class XmlSignerTest {

    private static final Logger logger = LoggerFactory.getLogger(XmlSignerTest.class);

    @Test
    public void shouldSignXmlUsingAJavaKeyStore() throws Exception {

    InputStream xml = XmlSigner.class.getResourceAsStream("/request-info.xml");
    InputStream jks = XmlSigner.class.getResourceAsStream("/my-private-key.jks");

    String alias = "root";
    String password = "secret";
    String parsedXml = IOUtils.toString(xml, StandardCharsets.UTF_8);

    SignedXml signedXml = new XmlSigner()
    .withXml(parsedXml)
    .withKeyStore(jks, alias, password)
    .sign();

    String content = signedXml.getContent();
    logger.info("\n" + content); // just prints the result

    assertThat(content)
    .contains("<X509Certificate>")
    .contains("</X509Data>")
    .contains("</Signature>")
    ;
    }
    }
  7. @rponte rponte revised this gist Oct 26, 2018. 1 changed file with 8 additions and 8 deletions.
    16 changes: 8 additions & 8 deletions XmlSigner.java
    Original file line number Diff line number Diff line change
    @@ -68,7 +68,7 @@ public SignedXml sign() throws NoSuchAlgorithmException, InvalidKeyException, Ke
    char[] password = keyStoreInfo.getPassword().toCharArray();

    // Create a DOM XMLSignatureFactory that will be used to
    // generate the enveloped signature.
    // generate the enveloped signature.
    XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

    // Create a Reference to the enveloped document (in this case,
    @@ -113,18 +113,18 @@ public SignedXml sign() throws NoSuchAlgorithmException, InvalidKeyException, Ke
    DOMSignContext dsc = new DOMSignContext(key, doc.getDocumentElement());

    // Adds <Signature> tag before a specific tag inside XML - with or without namespace
    /*
    Node assertionTag = doc.getElementsByTagName("saml2:Assertion").item(0);
    Node afterTag = doc.getElementsByTagName("saml2:Subject").item(0);
    DOMSignContext dsc = new DOMSignContext(key, assertionTag, afterTag);
    dsc.setDefaultNamespacePrefix("ds");
    /*
    Node assertionTag = doc.getElementsByTagName("saml2:Assertion").item(0);
    Node afterTag = doc.getElementsByTagName("saml2:Subject").item(0);
    DOMSignContext dsc = new DOMSignContext(key, assertionTag, afterTag);
    dsc.setDefaultNamespacePrefix("ds");
    */

    // Create the XMLSignature, but don't sign it yet.
    XMLSignature signature = fac.newXMLSignature(si, keyInfo);
    signature.sign(dsc); // Marshal, generate, and sign the enveloped signature.
    signature.sign(dsc); // Marshal, generate, and sign the enveloped signature.

    ByteArrayOutputStream output = new ByteArrayOutputStream();
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    TransformerFactory.newInstance()
    .newTransformer()
    .transform(new DOMSource(doc), new StreamResult(output));
  8. @rponte rponte created this gist Oct 26, 2018.
    21 changes: 21 additions & 0 deletions SignedXml.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    package br.com.mdias.rponte.signature;

    import java.util.Base64;

    public class SignedXml {

    private String content;

    public SignedXml(String content) {
    this.content = content;
    }

    public String getContent() {
    return content;
    }

    public String toBase64() {
    String base64Xml = Base64.getEncoder().encodeToString(content.getBytes());
    return base64Xml;
    }
    }
    163 changes: 163 additions & 0 deletions XmlSigner.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,163 @@
    package br.com.rponte.signature;

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.Key;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SignatureException;
    import java.security.UnrecoverableEntryException;
    import java.security.cert.X509Certificate;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;

    import javax.xml.crypto.MarshalException;
    import javax.xml.crypto.XMLStructure;
    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.XMLSignatureException;
    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.X509Data;
    import javax.xml.crypto.dsig.keyinfo.X509IssuerSerial;
    import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
    import javax.xml.crypto.dsig.spec.TransformParameterSpec;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.ParserConfigurationException;
    import javax.xml.transform.TransformerConfigurationException;
    import javax.xml.transform.TransformerException;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.TransformerFactoryConfigurationError;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;

    import org.apache.commons.io.IOUtils;
    import org.w3c.dom.Document;
    import org.xml.sax.SAXException;

    public class XmlSigner {

    private static final String C14NEXC = "http://www.w3.org/2001/10/xml-exc-c14n#";

    private keyStoreInfo keyStoreInfo;
    private InputStream sourceXml;

    /**
    * https://gist.github.com/rponte/4039958
    * https://github.com/SUNET/eduid-mm-service/tree/master/src/main/java/se/gov/minameddelanden/common
    * https://stackoverflow.com/questions/5330049/java-equivalent-of-c-sharp-xml-signing-method
    */
    public SignedXml sign() throws NoSuchAlgorithmException, InvalidKeyException, KeyStoreException, SignatureException, IOException, InvalidAlgorithmParameterException, ParserConfigurationException, SAXException, MarshalException, XMLSignatureException, TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError, UnrecoverableEntryException {

    KeyStore keyStore = keyStoreInfo.getKeyStore();
    String alias = keyStoreInfo.getAlias();
    char[] password = keyStoreInfo.getPassword().toCharArray();

    // 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,
    // you are signing the whole document, so a URI of "" signifies
    // that, and also specify the SHA1 digest algorithm and
    // the ENVELOPED Transform.
    Transform envelopedTransform = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
    Transform c14NEXCTransform = fac.newTransform(C14NEXC, (TransformParameterSpec) null);
    List<Transform> transforms = Arrays.asList(envelopedTransform, c14NEXCTransform);

    DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA1, null);
    Reference ref = fac.newReference("", digestMethod, transforms, null, null);

    // Create the SignedInfo.
    CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null);
    SignatureMethod signatureMethod = fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
    SignedInfo si = fac.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(ref));

    // Create the KeyInfo containing the X509Data.
    KeyInfoFactory keyInfoFactory = fac.getKeyInfoFactory();
    X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);

    X509Data newX509Data = keyInfoFactory.newX509Data(Arrays.asList(certificate));
    X509IssuerSerial issuer = keyInfoFactory.newX509IssuerSerial(certificate.getIssuerX500Principal().getName(), certificate.getSerialNumber());

    List<XMLStructure> data = Arrays.asList(newX509Data, issuer);
    KeyInfo keyInfo = keyInfoFactory.newKeyInfo(data);

    // Converts XML to Document
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder builder = dbf.newDocumentBuilder();
    Document doc = builder.parse(sourceXml);

    // Create a DOMSignContext and specify the RSA PrivateKey and
    // location of the resulting XMLSignature's parent element.
    Key key = keyStore.getKey(alias, password);
    if (key == null) {
    throw new XmlSigningException("Private Key not found for alias '" + alias + "'");
    }

    DOMSignContext dsc = new DOMSignContext(key, doc.getDocumentElement());

    // Adds <Signature> tag before a specific tag inside XML - with or without namespace
    /*
    Node assertionTag = doc.getElementsByTagName("saml2:Assertion").item(0);
    Node afterTag = doc.getElementsByTagName("saml2:Subject").item(0);
    DOMSignContext dsc = new DOMSignContext(key, assertionTag, afterTag);
    dsc.setDefaultNamespacePrefix("ds");
    */

    // Create the XMLSignature, but don't sign it yet.
    XMLSignature signature = fac.newXMLSignature(si, keyInfo);
    signature.sign(dsc); // Marshal, generate, and sign the enveloped signature.

    ByteArrayOutputStream output = new ByteArrayOutputStream();
    TransformerFactory.newInstance()
    .newTransformer()
    .transform(new DOMSource(doc), new StreamResult(output));

    String rawSignedXml = new String(output.toByteArray());

    SignedXml xml = new SignedXml(rawSignedXml);
    return xml;
    }

    public XmlSigner withKeyStore(InputStream keyStore, String alias, String password) {

    if (keyStore == null)
    throw new XmlSigningException("KeyStore without private key");

    keyStoreInfo ksi = new keyStoreInfo(alias, password);
    ksi.load(keyStore);

    this.keyStoreInfo = ksi;
    return this;
    }

    public XmlSigner withXml(InputStream sourceXml) {

    if (sourceXml == null)
    throw new XmlSigningException("XML can not be null");

    this.sourceXml = sourceXml;
    return this;
    }

    public XmlSigner withXml(String sourceXml) {
    InputStream input = IOUtils.toInputStream(sourceXml, StandardCharsets.UTF_8);
    return withXml(input);
    }
    }
    15 changes: 15 additions & 0 deletions XmlSigningException.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    package br.com.mdias.rponte.signature;

    public class XmlSigningException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public XmlSigningException(String message) {
    super(message);
    }

    public XmlSigningException(String message, Throwable cause) {
    super(message, cause);
    }

    }
    46 changes: 46 additions & 0 deletions keyStoreInfo.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    package br.com.rponte.signature;

    import java.io.IOException;
    import java.io.InputStream;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.CertificateException;

    public class keyStoreInfo {

    private static final String KEY_STORE_TYPE = "JKS";

    private String alias;
    private String password;
    private KeyStore keyStore;

    public keyStoreInfo(String alias, String password) {
    this.alias = alias;
    this.password = password;
    }

    /**
    * Loads KeyStore from the given Private Key
    */
    public void load(InputStream privateKey) {
    try {
    this.keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
    this.keyStore.load(privateKey, password.toCharArray());
    } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
    e.printStackTrace();
    throw new XmlSigningException("Error loading KeyStore", e);
    }
    }

    public String getAlias() {
    return alias;
    }
    public String getPassword() {
    return password;
    }
    public KeyStore getKeyStore() {
    return keyStore;
    }

    }