12 November 2018

xml-kit

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.
  • use the signature_for 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 encryption_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
  encryption_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.

💎