NAME
XML::Sig::OO - Modern XML Signatured validation
SYNOPSIS
use XML::Sig::OO;
# Sign our xml
my $s=new XML::Sig::OO(
xml=>'<?xml version="1.0" standalone="yes"?><data><test ID="A" /><test ID="B" /></data>',
key_file=>'rsa_key.pem'
cert_file=>'cert.pem',
);
my $result=$s->sign;
die "Failed to sign the xml, error was: $result" unless $result;
my $xml=$result->get_data;
# Example checking a signature
my $v=new XML::Sig::OO(xml=>$xml);
# validate our xml
my $result=$v->validate;
if($result) {
print "everything checks out!\n";
} else {
foreach my $chunk (@{$result->get_data}) {
my ($nth,$signature,$digest)=@{$chunk}{qw(nth signature digest)};
print "Results for processing chunk $nth\n";
print "Signature State: ".($signature ? "OK\n" : "Failed, error was $signature\n");
print "Digest State: ".($digest ? "OK\n" : "Failed, error was $digest\n");
}
}
DESCRIPTION
XML::Sig::OO is a project to create a stand alone perl module that does a good job creating and validating xml signatures. At its core This module is written around libxml2 better known as XML::LibXML.
Multiple signatures and keys
In the case of signing multiple //@ID elements, it is possible to sign each chunk with a different key, in fact you can even use completly different key types.
use Modern::Perl;
use XML::Sig::OO;
use File::Spec;
use FindBin qw($Bin);
use Crypt::OpenSSL::DSA;
use Crypt::OpenSSL::RSA;
# create our signign object
my $s=new XML::Sig::OO(
xml=>'<?xml version="1.0" standalone="yes"?><data><test ID="A" /><test ID="B" /></data>',
);
my $x=$s->build_xpath;
# sign our first xml chunk with our rsa key!
my $rsa_str=join '',IO::File->new(File::Spec->catfile($Bin,'x509_key.pem'))->getlines;
my $rsa=Crypt::OpenSSL::RSA->new_private_key($rsa_str);
$rsa->use_pkcs1_padding();
my $cert_str=join '',IO::File->new(File::Spec->catfile($Bin,'x509_cert.pem'))->getlines;
$s->sign_cert($rsa);
$s->key_type('rsa');
$s->cert_string($cert_str);
my $result=$s->sign_chunk($x,1);
die $result unless $result;
# Sign our 2nd chunk with our dsa key
my $dsa = Crypt::OpenSSL::DSA->read_priv_key(File::Spec->catfile($Bin,'dsa_priv.pem'));
$s->cert_string(undef);
$s->sign_cert($dsa);
$s->key_type('dsa');
$result=$s->sign_chunk($x,2);
die $result unless $result;
my ($node)=$x->findnodes($s->xpath_Root);
my $xml=$node->toString;
print "Our Signed XML IS: \n",$xml,"\n";
# Example checking a signature
my $v=new XML::Sig::OO(xml=>$xml);
$result=$v->validate;
die $result unless $result;
print "Our signed and xml passes validation\n";
Working with Net::SAML2
Net::SAML2 has many problems when it comes to signature validation of xml strings. This section documents how to use this module in place of the Net::SAML2 built ins.
use Net::SAML2::Protocol::Assertion;
use XML::Sig::OO;
use MIME::Base64;
# Lets assume we have a post binding response
my $saml_response=.....
my $xml=decode_base64($saml_response);
my $v=XML::Sig::OO->new(xml=>$xml,cacert=>'idp_cert.pem');
my $result=$v->validate;
die $result unless $result;
# we can now use the asertion knowing it was from our idp
my $assertion=Net::SAML2::Protocol::Assertion->new_from_xml(xml=>$xml)
Encrypted keys
Although this package does not directly support encrypted keys, it is possible to use encrypted keys by loading and exporting them with the Crypt::PK::RSA and Crypt::PK::DSA packages.
Constructor options
xml=>'...'
The base xml string to validate or sign. This option is always required.
cacert=>'/path/to/your/cacert.pem'
Optional, used to validate X509 certs.
build_parser=>sub { return XML::LibXML->new() }
Callback that returns a new XML Parser
namespaces=>{ ds=>'http://www.w3.org/2000/09/xmldsig#', ec=>'http://www.w3.org/2001/10/xml-exc-c14n#'}
Contains the list of namespaces to set in our XML::LibXML::XPathContext object.
digest_cbs=>{ ... }
Contains the digest callbacks. The default handlers can be found in %XML::SIG::OO::DIGEST.
digest_method=>'http://www.w3.org/2000/09/xmldsig#sha1'
Sets the digest method to be used when signing xml
key_type=>'rsa'
The signature method we will use
signature_method=>'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
Sets the signature method.
tune_cert_cbs=>{ ...}
A collection of callbacks to tune a certificate object for signing
mutate_cbs=>{....}
Transform and Canonization callbacks. The default callbacks are defined in %XML::Sig::OO::MUTATE.
Callbacks are usied in the following context
$cb->($self,$xpath_element);
Xpaths
The xpaths in this package are not hard coded, each xpath can be defined as an argument to the constructor. Since xml can contain multiple elements with signatures or multiple id elements to sign, most xpaths are prefixed with the $nth signature
Some cases the xpaths are used in the following context:
(/xpath)[$nth]
In special cases like finding a list of transforms or which key, signature, or digest:
(//ds::Signature)[$nth]/xpath
xpath_SignatureValue=>//ds:SignatureValue
Xpath used to find the signature value.
xpath_SignatureMethod=>'//ds:SignatureMethod/@Algorithm'
Xpath used to find the signature method algorithm.
xpath_CanonicalizationMethod=>'//ds:CanonicalizationMethod/@Algorithm'
Xpath used to find the list of canonicalization method(s).
xpath_SignedInfo=>'//ds:SignedInfo'
Xpath used to find the singed info.
xpath_Signature=>'//ds:Signature'
Xpath used to fetch the signature value
xpath_Transforms=>//ds:Transforms
Xpath Transform path
xpath_Transform=>'/ds:Transform/@Algorithm'
Xpath used to find the transform Algorithm
xpath_DigestValue=>'//ds:DigestValue'
Xpath used to fetch the digest value
xpath_DigestMethod=>'//ds:DigestMethod/@Algorithm'
Xpath used to find the digest method.
xpath_DigestId=>'//ds:Reference/@URI'
Xpath used to find the id of the node that should contain our digest.
digest_id_convert_cb=>sub { my ($self,$xpath_object,$id)=@_;$id =~ s/^#//;return "//*[\@ID='$id']" }
Code ref that converts the xpath_DigestId into the xpath lookup ised to find the digest node
xpath_ToSign=>'//[@ID]'
Xpath used to find what nodes to sign.
xpath_IdValue=>'//@ID'
Xpath used to find the value of the current id.
xpath_Root=>'/'
Root of the document expath
XPaths related to certs
This section documents all xpaths/options related to certs.
xpath_x509Data=>'/ds:KeyInfo/ds:X509Data/ds:X509Certificate'
Xpath used to find the x509 cert value. In reality the nth signature will be prepended to this xpath.
Actual xpath used:
(//ds:Signature)[$nth]/ds:KeyInfo/ds:X509Data/ds:X509Certificate
xpath_RSAKeyValue=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue'
Xpath used to find the RSA value tree.
xpath_RSA_Modulus=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Modulus'
Xpath used to find the RSA Modulus.
xpath_RSA_Exponent=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Exponent'
Xpath used to find the RSA Exponent.
xpath_DSAKeyValue=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue'
Xpath used for DSA key tree discovery.
xpath_DSA_P=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:P'
Xpath used to find DSA_P.
xpath_DSA_Q=>''
Xpath used to find DSA_Q.
xpath_DSA_G=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:G'
Xpath used to find DSA_G.
xpath_DSA_Y=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Y'
Xpath used to find DSA_Y
OO Signing Options
The following Signature options can be passed to the constructor object.
key_file=>'path/to/my.key'
Key file only used when signing.
envelope_method=>"http://www.w3.org/2000/09/xmldsig#enveloped-signature"
Sets the envelope method; This value most likely is the only valid value.
canon_method=>'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'
Sets the canonization method used when signing the code
tag_namespace=>'ds'
Default namespace of the tags being created. This must be defined in $self->namespaces.
sign_cert=>$cert_object
Optional: The Certificate object used to sign xml. If this option is set it is recomended that you set the "key_type" option as well.
cert_file=>'/path/to/cert.pem'
The path that contains the cert file used for signing.
cert_string=>undef
This optional argument lets you define the x509 pem text that will be used to generate the x509 portion of the xml.
OO Methods
my $xpath=$self->build_xpath(undef|$xml,{ns=>'url'}|undef);
Creates a new xpath object based on our current object state.
my $result=$self->validate;
Returns a Data::Result Object. When true validation passed, when false it contains why validation failed.
A better use case would be this:
my $result=$self->validate;
if($result) {
print "everything checks out\n";
} else {
foreach my $chunk (@{$result->get_data}) {
my ($nth,$signature,$digest)=@{$chunk}{qw(nth signature digest)};
print "Results for processing chunk $nth\n";
print "Signature State: ".($signature ? "OK\n" : "Failed, error was $signature\n";
print "Digest State: ".($digest ? "OK\n" : "Failed, error was $digest\n";
}
}
my $result=$self->verify_digest($nth)
Returns a Data::Result object: when true, the signature was verified, when false it contains why it failed.
my $result=$self->get_transforms($xpath_object,$nth)
Returns a Data::Reslt object, when true it contains an array ref that contains each digest transform, when false it contains why it failed.
Please note, the xpath generate is a concatination of $self->context($self->xpath_Transforms,$nth).$self->xpath_Transform, so keep that in mind when trying to change how transforms are looked up.
my $result=$self->get_digest_node($xpath_object)
Returns a Data::Result Object, when true it contains the Digest Node, when false it contains why it failed.
my $result=$self->get_digest_method($xpath_object,$nth)
Returns a Data::Result Object, when true it contains the Digest Method
my $result=$self->get_digest_value($xpath_object,$nth)
Returns a Data::Result Object, when true it contains the Digest Value.
my $result=$self->verify_signature($nth);
Returns a Data::Result Object, when true the signature was validated, when fails it contains why it failed.
my $result=$self->verify_dsa($x,$string,$nth)
Returns a Data::Result object, when true it validated the DSA signature.
my $xpath_string=$self->context($xpath,$nth)
Returns an xpath wrapped in the nth instance syntax.
Example
my $xpath="//something"
my $nth=2;
my $xpath_string=$self->context($xpath,$nth);
$xpath_string eq '(//something)[2]';
Note: if nth is not set it defaults to 1
my $result=$self->get_sig_canon($x,$nth)
Returns a Data::Result object, when true it contains the canon xml of the $nth signature node.
my $result=$self->verify_x509_sig($x,$string,$nth)
Returns a Data::Result Object, when true the x509 signature was validated.
my $result=$self->tune_cert_and_get_sig($x,$nth,$cert)
Returns a Data::Result object, when true it contains the following hashref
Structure:
cert: the tuned cert
sig: the binary signature to verify
xml: the xml to be verified against the signature
my $result=$self->verify_rsa($x,$nth)
Returns a Data::Result Object, when true the the rsa key verification passed.
my $result=$self->do_transforms($xpath_object,$node_to_transform,$nth_node);
Retruns a Data::Result Object, when true it contains the xml string of the context node.
my $result=$self->do_canon($xpath_object,$node_to_transform,$nth_node);
Returns a Data::Result Object, when true it contains the canonized string.
my $result=$self->get_canon($xpath_object,$nth)
Returns a Data::Result Object, when true it contains an array ref of the canon methods.
Special note, the xpath is generated as follows
my $xpath=$self->context($self->xpath_SignedInfo,$nth).$self->xpath_CanonicalizationMethod;
my $result=$self->get_signature_value($xpath_object,$nth)
Returns a Data::Result object, when true it contains the base64 decoded signature
my $result=$self->get_signed_info_node($xpath_object,$nth);
Given $xpath_object, Returns a Data::Result when true it will contains the signed info node
my $result=$self->get_signature_method($xpath_object,$nth_node,$cert|undef)
Returns a Data::Result object, when true it contains the SignatureMethod. If $cert is passed in, it will cert the hashing mode for the cert
my $result=$self->tune_cert($cert,$method)
Returns a Data::Result Object, when true Sets the hashing method for the $cert object.
my $x509=$self->clean_x509($string)
Converts a given string to an x509 certificate.
my $result=$self->transform($xpath_object,$node,$transformType,$nth)
Given the $node XML::LibXML::Element and $transformType, returns a Data::Result object. When true the call to $result->get_data will return the xml, when false it will contain a string that shows why it failed.
my $array_ref=$self->transforms
Returns an ArrayRef that contains the list of transform methods we will use when signing the xml.
This list is built out of the following:
0: $self->envelope_method
1: $self->canon_method
my $xml=$self->create_digest_xml($id,$digest)
Produces a text xml fragment to be used for an xml digest.
my $xml=$self->create_signedinfo_xml($digest_xml)
Produces text xml fragment to be used for an xml signature
my $xmlns=$self->create_xmlns
Creates our common xmlns string based on our namespaces.
my $xml=$self->create_signature_xml
Creates the signature xml for signing.
my $result=$self->load_cert_from_file($filename)
Returns a Data::Result structure, when true it contains a hasref with the following elements:
type: 'dsa|rsa|x509'
cert: $cert_object
my $result=$self->detect_cert($text)
Returns a Data::Result object, when true it contains the following hashref
type: 'dsa|rsa|x509'
cert: $cert_object
my $result=$self->load_rsa_string($string)
Returns a Data::Result object, when true it contains the following hashref:
type: 'rsa'
cert: $cert_object
my $result=$self->load_x509_string($string)
Returns a Data::Result object, when true it contains the following hashref:
type: 'x509'
cert: $cert_object
my $result=$self->load_dsa_string($string)
Returns a Data::Result object, when true it contains the following hashref:
type: 'dsa'
cert: $cert_object
my $result=$self->get_xml_to_sign($xpath_object,$nth)
Returns a Data::Result object, when true it contains the xml object to sign.
my $result=$self->get_signer_id($xpath_object,$nth)
Returns a Data::Result object, when true it contains the id value
my $result=$self->sign
Returns a Data::Result Object, when true it contains the signed xml string.
my $result=$self->sign_chunk($xpath_object,$nth)
Returns a Data::Result object, when true, the nth element with //@ID was signed and updated in $xpath_object. This method provides absolute granular control over what node is signed.
my $xml=$self->create_x509_xml($cert)
Creates the xml from the Certificate Object.
my $xml=$self->build_x509_xml($encoded_key)
Given the base64 encoded key, create a block of x509 xml.
my $result=$self->find_key_cert
Returns a Data::Result Object, when true it contains the x509 cert xml.
my $xml=$self->create_rsa_xml($cert)
Creates the xml from the Certificate Object.
my $xml=$self->create_dsa_xml($cert)
Creates the xml for the Key Object.
Limitations
This package currently has some limitations.
Supported Key Types and formats for signing/validation
Currently this module only supports RSA and DSA keys in pem format.
CaCert Validation
Currently CaCert validation only works with RSA keys.
Credits
This code is based on the following modules: XML::Sig, Net::SAML2::XML::Sig, Authen::NZRealMe::XMLSig, and Mojo::XMLSig and would not exist today withot them.
Bugs
Currently there are no known bugs, but if any are found please report them on our github project. Patches and pull requests are welcomed!
https://github.com/akalinux/xml-sig-oo
Author
AKALINUX <AKALINUX@CPAN.ORG>