[JAVA] Sign XML using Apache Santuario

Apache Santuario (XML Security) (http://santuario.apache.org/) seems to be the de facto way to sign XML in Java.

1. Prepare your certificate and private key

First of all, you need a certificate and a private key to sign. Please prepare in advance. I prepared the private key with pem, but it was not recognized, so I will leave the private key in the format of pkcs # 8. There may be a way to read the pem directly, but here we will convert the key.

$ openssl pkcs8 -in xxx.pem -topk8 -nocrypt -outform DER -out mypriv.key

2. Read certificate and private key

Read the certificate and private key as follows: Oh, the code also uses lombok.

public static X509Certificate loadCert(InputStream certFile) throws CertificateException {
    CertificateFactory cf = CertificateFactory.getInstance("X.509");

    return (X509Certificate)cf.generateCertificate(certFile);
}

public static PrivateKey loadPrivateKey(InputStream privateKey) throws IOException, GeneralSecurityException {
    val buf = readPrivateKey(privateKey);
    val keySPec = new PKCS8EncodedKeySpec(buf);
    	
    return KeyFactory.getInstance("RSA").generatePrivate(keySPec);
}

private static byte[] readPrivateKey(InputStream in) throws IOException {
    byte[] data = new byte[in.available()];
    in.read(data);
		
    return data;
}

3. Sign

Now that the certificate and private key are ready, sign the XML.

First create an object to sign. Specify the URI and signature algorithm for the XML Document object to be processed when creating the object and its file. And after that, I'm adding an element to sign the original XML.

val sig = new XMLSignature(doc, "file:/test.xml", XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256);
doc.getDocumentElement().appendChild(sig.getElement())

By the way, test.xml

<?xml version="1.0" encoding="UTF-8"?>
<elem1>
  <elemt2>xxx</elem2>
</elem1>

Next, register the Resource Resolver. This is a ResourceResolver, which is then an object to get the elements to digest and the contents of another file. The ResourceResolver code will be explained later.

After that, I set the object to do transforms. ResourceResolver is just responsible for retrieving the content as a string of bytes. You need canonicalize to get the digest from the XML element. Transfomrs are people who parse XML from bytes and canonicalize it.

The first question I wondered when signing XML was how to normalize it, or if it wasn't normalized, the digest wouldn't be correct. This is [Standardized as Canonical XML](https: / /www.w3.org/TR/xml-c14n11/), and transfomrs is responsible for that.

Specify these transforms with uri in addDocument. Specify null for files that do not need to be converted.

sig.getSignedInfo().addResourceResolver(new MyResolver());

val transforms = new Transforms(kouseiBase);
transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS);

sig.addDocument("#elem2", transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
sig.addDocument("somefile.txt", null, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);

After that, specify the certificate and private key to sign. The signature information is now added to the first doc.

```java` sig.addKeyInfo(certificate); sig.addKeyInfo(certificate.getPublicKey()); sig.sign(privateKey);


 4. ResourceResolverSpi

 Use REsourceResolverSpi to resolve the URI. There are two methods to implement, engineCanResolveURI and engineResolveURI. The former checks whether the URI specified by addDocument can be resolved by this resolver. The latter is a method for getting content from that uri. Returns an object called XMLSignatureInput.
 I throw a ResourceResolverException when an exception occurs, but I don't know how to make it correctly. The constructor for the code here is deprecated.

```java
class MyResolver extends ResourceResolverSpi {
    @override
    public XMLSignatureInput engineResolveURI(ResourceResolverContext context) throws ResourceResolverException {
        val uri = context.attr.getValue();
        try {
            if (uri.equals("#elem2")) {
                val result = new XMLSignatureInput(new ByteArrayInputStream("<elemt2>xxx</elem2>".getBytes("utf-8")));
                result.setSourceURI(uri);
                result.setMIMEType("text/xml");

                return result;
            }
            if (uri.equals("somefile.txt")) {
                val result = new XMLSignatureInput(new ByteArrayInputStream(new FileInputStream(uri)));
                result.setSourceURI(uri);
                result.setMIMEType("application/octet-stream");
 
                return result;
            }
        } catch (Exception e) {
            throw new ResourceResolverException("generic.EmptyMessage", e, uri, context.baseUri);
        }
        return null;
    }

    @override
    public boolean engineCanResolveURI(ResourceResolverContext context) {
        val uri = context.attr.getValue();
        if (uri.equals("#elem2")) return true;
        if (uri.equals("somefile.txt")) return true;

        return false;
    }
}

5. End

You can sign XML with a code like this. XML signing is pretty confusing, but the library makes it fairly easy to sign. However, it seems that there are few documents ...

Dewadewa

Recommended Posts

Sign XML using Apache Santuario
Data processing using Apache Flink
I tried using Apache Wicket
Excel output using Apache POI!