XML-Sig-OO
view release on metacpan or search on metacpan
lib/XML/Sig/OO.pm view on Meta::CPAN
=head2 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 L<Crypt::PK::RSA> and L<Crypt::PK::DSA> packages.
=head1 Constructor options
=cut
=over 4
=item * xml=>'...'
The base xml string to validate or sign. This option is always required.
=cut
has xml=>(
is=>'ro',
isa=>Str,
required=>1,
);
=item * cacert=>'/path/to/your/cacert.pem'
Optional, used to validate X509 certs.
=cut
has cacert=>(
is=>'rw',
isa=>sub { my ($f)=@_; croak "cacert must be a readable file" unless defined($f) && -r $f },
required=>0,
clearer=>1,
);
=item * nocacheck=>0|1
Turns off ca cert checking.. this may not always be possible!
=cut
has nocacheck=>(
default=>0,
is=>'rw',
isa=>Bool,
);
=item * build_parser=>sub { return XML::LibXML->new() }
Callback that returns a new XML Parser
=cut
has build_parser=>(
is=>'ro',
isa=>CodeRef,
default=>sub { sub { XML::LibXML->new() } },
);
=item * 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.
=cut
has namespaces=>(
is=>'ro',
isa=>HashRef,
default=>sub {
{
ds=>'http://www.w3.org/2000/09/xmldsig#',
ec=>'http://www.w3.org/2001/10/xml-exc-c14n#',
samlp=>"urn:oasis:names:tc:SAML:2.0:protocol",
}
},
);
=item * digest_cbs=>{ ... }
Contains the digest callbacks. The default handlers can be found in %XML::SIG::OO::DIGEST.
=cut
our %DIGEST=(
'http://www.w3.org/2000/09/xmldsig#sha1' => sub { my ($self,$content)=@_; $self->_get_digest(sha1 => $content) },
'http://www.w3.org/2001/04/xmlenc#sha256' => sub { my ($self,$content)=@_; $self->_get_digest(sha256 => $content) },
'http://www.w3.org/2001/04/xmlenc#sha512' => sub { my ($self,$content)=@_; $self->_get_digest(sha512 => $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha224' => sub { my ($self,$content)=@_; $self->_get_digest(sha224 => $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha384' => sub { my ($self,$content)=@_; $self->_get_digest(sha384 => $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha512' => sub { my ($self,$content)=@_; $self->_get_digest(sha512 => $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha1024' => sub { my ($self,$content)=@_; $self->_get_digest(sha1024 => $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha2048' => sub { my ($self,$content)=@_; $self->_get_digest(sha2048=> $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha3072' => sub { my ($self,$content)=@_; $self->_get_digest(sha3072=> $content) },
'http://www.w3.org/2001/04/xmldsig-more#sha4096' => sub { my ($self,$content)=@_; $self->_get_digest(sha4096=> $content) },
);
=item * digest_method=>'http://www.w3.org/2000/09/xmldsig#sha1'
Sets the digest method to be used when signing xml
=cut
has digest_method=>(
isa=>sub { exists $DIGEST{$_[0]} or croak "$_[0] is not a supported digest" },
is=>'ro',
default=>'http://www.w3.org/2000/09/xmldsig#sha1',
);
=item * key_type=>'rsa'
The signature method we will use
=cut
has key_type=>(
isa=>sub { croak "unsuported key type: $_[0]" unless $_[0]=~ /^(?:dsa|rsa|x509)$/s },
is=>'rw',
required=>0,
lazy=>1,
default=>'x509',
);
has digest_cbs=>(
isa=>HashRef,
is=>'ro',
default=>sub { return { %DIGEST} },
);
sub _get_digest {
my ($self,$algo, $content) = @_;
my $digest = Digest::SHA->can("${algo}_base64")->($content);
while (length($digest) % 4) { $digest .= '=' }
return $digest;
}
our %TUNE_CERT=(
'http://www.w3.org/2000/09/xmldsig#dsa-sha1' => sub { _tune_cert(@_,'sha1') },
'http://www.w3.org/2000/09/xmldsig#rsa-sha1' => sub { _tune_cert(@_,'sha1') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224' => sub { _tune_cert(@_,'sha224') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' => sub { _tune_cert(@_,'sha256') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384' => sub { _tune_cert(@_,'sha384') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512' => sub { _tune_cert(@_,'sha512') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha1024' => sub { _tune_cert(@_,'sha1024') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha2048' => sub { _tune_cert(@_,'sha2048') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha3072' => sub { _tune_cert(@_,'sha3072') },
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha4096' => sub { _tune_cert(@_,'sha4096') },
);
=item * signature_method=>'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
Sets the signature method.
=cut
has signature_method=>(
isa=>Str,
is=>'ro',
default=>'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
);
sub _tune_cert {
my ($self,$cert,$alg)=@_;
my $method="use_${alg}_hash";
if($cert->can($method)) {
$cert->$method();
}
}
=item * tune_cert_cbs=>{ ...}
A collection of callbacks to tune a certificate object for signing
=cut
has tune_cert_cbs=>(
isa=>HashRef,
is=>'ro',
default=>sub {
return {%TUNE_CERT}
}
);
=item * 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);
=cut
sub _build_canon_coderef {
my ($method,$comment)=@_;
return sub {
my ($self,$x,$node,$nth,$ec14n_inclusive_prefixes)=@_;
if ($method eq "toStringEC14N")
{
return $node->$method($comment, undef, $ec14n_inclusive_prefixes);
}
else
{
return $node->$method($comment);
}
};
}
sub _envelope_transform {
my ($self,$x,$node,$nth)=@_;
my $xpath=$self->context($self->xpath_Signature,$nth);
my ($target)=$x->findnodes($xpath,$node);
$node->removeChild($target) if defined($target);
return $node->toString;
}
our %MUTATE=(
'http://www.w3.org/2000/09/xmldsig#enveloped-signature'=>\&_envelope_transform,
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315' => _build_canon_coderef('toStringC14N',0),
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments' => _build_canon_coderef('toStringC14N',1),
'http://www.w3.org/2006/12/xml-c14n11' => _build_canon_coderef('toStringC14N_v1_1',0),
'http://www.w3.org/2006/12/xml-c14n11#WithComments' => _build_canon_coderef('toStringC14N_v1_1',1),
'http://www.w3.org/2001/10/xml-exc-c14n#' => _build_canon_coderef('toStringEC14N',0),
'http://www.w3.org/2001/10/xml-exc-c14n#WithComments' => _build_canon_coderef('toStringEC14N',1),
);
has mutate_cbs=>(
isa=>HashRef,
is=>'ro',
default=>sub { return {%MUTATE} },
);
=back
=head2 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
=over 4
=item * xpath_SignatureValue=>//ds:SignatureValue
Xpath used to find the signature value.
=cut
has xpath_SignatureValue=>(
isa=>Str,
is=>'ro',
default=>'//ds:SignatureValue',
);
=item * xpath_SignatureMethod=>'//ds:SignatureMethod/@Algorithm'
Xpath used to find the signature method algorithm.
=cut
has xpath_SignatureMethod=>(
isa=>Str,
is=>'ro',
default=>'//ds:SignatureMethod/@Algorithm',
);
=item * xpath_CanonicalizationMethod=>'//ds:CanonicalizationMethod/@Algorithm'
Xpath used to find the list of canonicalization method(s).
=cut
lib/XML/Sig/OO.pm view on Meta::CPAN
isa=>Str,
default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:P',
);
=item * xpath_DSA_Q=>''
Xpath used to find DSA_Q.
=cut
has xpath_DSA_Q=>(
is=>'ro',
isa=>Str,
default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Q',
);
=item * xpath_DSA_G=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:G'
Xpath used to find DSA_G.
=cut
has xpath_DSA_G=>(
is=>'ro',
isa=>Str,
default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:G',
);
=item * xpath_DSA_Y=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Y'
Xpath used to find DSA_Y
=cut
has xpath_DSA_Y=>(
is=>'ro',
isa=>Str,
default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Y',
);
=back
=head3 OO Signing Options
The following Signature options can be passed to the constructor object.
=over 4
=item * key_file=>'path/to/my.key'
Key file only used when signing.
=cut
has key_file=>(
isa=>Str,
required=>0,
is=>'ro',
);
=item * envelope_method=>"http://www.w3.org/2000/09/xmldsig#enveloped-signature"
Sets the envelope method; This value most likely is the only valid value.
=cut
has envelope_method=>(
isa=>Str,
is=>'ro',
default=>"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
);
#=item * canon_method=>'http://www.w3.org/2001/10/xml-exc-c14n#'
=item * canon_method=>'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'
Sets the canonization method used when signing the code
=cut
has canon_method=>(
isa=>Str,
#default=>"http://www.w3.org/2001/10/xml-exc-c14n#",
default=>"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
is=>'ro',
);
=item * tag_namespace=>'ds'
Default namespace of the tags being created. This must be defined in $self->namespaces.
=cut
has tag_namespace=>(
isa=>Str,
default=>'ds',
is=>'ro',
);
=item * 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.
=cut
has sign_cert=>(
isa=>Object,
is=>'rw',
required=>0,
lazy=>1,
);
=item * cert_file=>'/path/to/cert.pem'
The path that contains the cert file used for signing.
=cut
has cert_file=>(
isa=>sub {
my ($file)=@_;
croak "$file must be defined" unless defined($file);
croak "$file must be readable" unless -r $file;
},
is=>'rw',
required=>0,
lazy=>1,
);
=item * 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.
=cut
has cert_string=>(
is=>'rw',
required=>0,
lazy=>1,
);
=back
=cut
sub BUILD {
my ($self)=@_;
# sanity check dsa signature method
croak 'dsa key types only work with signature_method: http://www.w3.org/2000/09/xmldsig#dsa-sha1'
if $self->key_type eq 'dsa' && $self->signature_method ne 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
croak "namespaces does not contain: ".$self->tag_namespace unless exists $self->namespaces->{$self->tag_namespace};
croak $self->signature_method." is an unsupported signature method" unless exists $self->tune_cert_cbs->{$self->signature_method};
if(defined($self->key_file) && !defined($self->sign_cert)) {
my $result=$self->load_cert_from_file($self->key_file);
croak $result unless $result;
my ($key_type,$cert)=@{$result->get_data}{qw(type cert)};
$self->sign_cert($cert);
$self->key_type($key_type);
}
}
=head1 OO Methods
=head2 my $xpath=$self->build_xpath(undef|$xml,{ns=>'url'}|undef);
Creates a new xpath object based on our current object state.
=cut
sub build_xpath {
my ($self,$xml,$ns)=@_;
$xml=$self->xml unless defined($xml);
$ns=$self->namespaces unless defined($ns);
my $p=XML::LibXML->new(clean_namespaces=>1);
my $dom = $p->parse_string( $xml);
my $x=XML::LibXML::XPathContext->new($dom);
while(my ($key,$value)=each %{$ns}) {
$x->registerNs($key,$value);
}
return $x;
}
=head2 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";
}
}
=cut
sub validate {
my ($self)=@_;
my $total=$self->build_xpath->findnodes($self->xpath_Signature)->size;
( run in 1.093 second using v1.01-cache-2.11-cpan-71847e10f99 )