by mo

Generating XML is not exactly the most exciting subject. Let’s try to have some fun with it.

Let’s pretend that I need to generate an XML represenation of some sort of policy to be distributed to clients. This policy has one piece of configuration called name .

# policy.rb class Policy attr_accessor :id , :name end

Using this ruby class, I want to generate the following XML representation.

<Policy ID= "_baf8cc51-2133-4312-87ee-76ccda7a8fbe" > <Name> Audit </Name> </Policy>

To accomplish this we can use builder and tilt.

The following changes should help us accomplish the goal of generating an xml document.

# policy.rb class Policy attr_accessor :id , :name def template_path File . join ( __dir__ , 'policy.builder' ) end def to_xml ( options = {}) Tilt . new ( template_path ). render ( self , options ) end end # policy.builder xml . instruct! xml . Policy ID : id do xml . Name name end # app.rb policy = Policy . new policy . id = SecureRandom . uuid policy . name = "Audit" IO . write ( "policy.xml" , policy . to_xml )

XML Digital Signature

Now we need a mechanism for clients to verify that the generated XML file has not been tampered with and was issued by the party that they trust. To accomplish this we can generate a XML digital signature.

We can use xml-kit to help sign our xml document. There are a few changes that we need to make to the code to generate a signed xml document.

include the Xml::Kit::Templatable module.

module. use the signature_for helper method in the xml builder template.

helper method in the xml builder template. specify a key pair to use for generating the signature.

# policy.rb class Policy include Xml :: Kit :: Templatable attr_accessor :id , :name def template_path File . join ( __dir__ , 'policy.builder' ) end end # policy.builder xml . instruct! xml . Policy ID : id do signature_for reference_id: id , xml: xml xml . Name name end # app.rb policy = Policy . new policy . id = Xml :: Kit :: Id . generate policy . name = "Audit" policy . sign_with ( Xml :: Kit :: KeyPair . generate ( use: :signing )) IO . write ( "policy.xml" , policy . to_xml )

This will generate the same XML document with an added XML digital signature. The above example code uses xml-kit to generate a self signed certificate and key pair. This is helpful for development but not recommended in a production like environment.

The output of the above code is:

<Policy ID= "_329d2b29-6a82-4fc9-927f-5575ecc633e5" > <Signature xmlns= "http://www.w3.org/2000/09/xmldsig#" > <SignedInfo> <CanonicalizationMethod Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> <SignatureMethod Algorithm= "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /> <Reference URI= "#_329d2b29-6a82-4fc9-927f-5575ecc633e5" > <Transforms> <Transform Algorithm= "http://www.w3.org/2000/09/xmldsig#enveloped-signature" /> <Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#sha256" /> <DigestValue> ... </DigestValue> </Reference> </SignedInfo> <SignatureValue> ... </SignatureValue> <KeyInfo> <X509Data> <X509Certificate> ... </X509Certificate> </X509Data> </KeyInfo> </Signature> <Name> Audit </Name> </Policy>

Clients of this policy should receive the X509 certificate via a trusted transport mechanism so that it can verify that the document was signed with a key pair associated with a trusted certificate. The client can verify the signature to ensure that the document was not altered.

The current version of xml-kit only supports enveloped signatures.

XML Encryption

Next we can look at encrypting the <Name>..</Name> element using the XML Encryption standard.

To do this we can use the encrypt_with method to specify the x509 certificate of the client so that we can encrypt the data for a specific client. The xml builder template uses the encrypt_data_for helper to create an XML encryption element.

The revised version of the code looks like:

# policy.rb class Policy include Xml :: Kit :: Templatable attr_accessor :id , :name def template_path File . join ( __dir__ , 'policy.builder' ) end end # policy.builder xml . instruct! xml . Policy ID : id do signature_for reference_id: id , xml: xml encrypt_data_for xml: xml do | xml | xml . Name name end end # app.rb policy = Policy . new policy . id = Xml :: Kit :: Id . generate policy . name = "Audit" policy . sign_with ( Xml :: Kit :: KeyPair . generate ( use: :signing )) policy . encrypt_with ( Xml :: Kit :: KeyPair . generate ( use: :encryption ). certificate ) IO . write ( "policy.xml" , policy . to_xml )

The final version of the signed and encrypted document looks like:

<?xml version="1.0"?> <Policy ID= "_3fe07010-a5d4-4114-b919-4219a2d5bb0c" > <Signature xmlns= "http://www.w3.org/2000/09/xmldsig#" > <SignedInfo> <CanonicalizationMethod Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> <SignatureMethod Algorithm= "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /> <Reference URI= "#_3fe07010-a5d4-4114-b919-4219a2d5bb0c" > <Transforms> <Transform Algorithm= "http://www.w3.org/2000/09/xmldsig#enveloped-signature" /> <Transform Algorithm= "http://www.w3.org/2001/10/xml-exc-c14n#" /> </Transforms> <DigestMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#sha256" /> <DigestValue> ... </DigestValue> </Reference> </SignedInfo> <SignatureValue> ... </SignatureValue> <KeyInfo> <X509Data> <X509Certificate> ... </X509Certificate> </X509Data> </KeyInfo> </Signature> <EncryptedData xmlns= "http://www.w3.org/2001/04/xmlenc#" > <EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#aes256-cbc" /> <KeyInfo xmlns= "http://www.w3.org/2000/09/xmldsig#" > <EncryptedKey xmlns= "http://www.w3.org/2001/04/xmlenc#" > <EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <CipherData> <CipherValue> ... </CipherValue> </CipherData> </EncryptedKey> </KeyInfo> <CipherData> <CipherValue> ... </CipherValue> </CipherData> </EncryptedData> </Policy>

Fin

XML is verbose and difficult to read. However, generating a signed and encrypted xml document is a little bit easier with xml-kit.

xml-kit hasn’t reached a 1.0 release yet, so the public API is considered unstable and subject to change. Please feel free to contribute.