Authen-NZRealMe
view release on metacpan or search on metacpan
lib/Authen/NZRealMe/XMLSig.pm view on Meta::CPAN
package Authen::NZRealMe::XMLSig;
$Authen::NZRealMe::XMLSig::VERSION = '1.23';
use strict;
use warnings;
=head1 NAME
Authen::NZRealMe::XMLSig - XML digital signature generation/verification
=head1 DESCRIPTION
This module implements the subset of http://www.w3.org/TR/xmldsig-core/
required to interface with the New Zealand RealMe Login service using SAML 2.0
messaging.
=cut
use Carp qw(croak);
use Digest::SHA qw(sha1 sha1_base64 sha256);
use MIME::Base64 qw(encode_base64 decode_base64);
use Authen::NZRealMe::CommonURIs qw(URI NS_PAIR);
require XML::LibXML;
require XML::LibXML::XPathContext;
require XML::Generator;
require Crypt::OpenSSL::RSA;
require Crypt::OpenSSL::X509;
my(%transforms_by_name, %transforms_by_uri);
__PACKAGE__->register_transform_method($_, URI($_)) foreach (qw(
c14n
c14n_wc
c14n11
c14n11_wc
ec14n
ec14n_wc
sha1
sha256
env_sig
));
my(%sig_alg_by_name, %sig_alg_by_uri);
__PACKAGE__->register_signature_methods($_, URI($_)) foreach (qw(
rsa_sha1
rsa_sha256
));
use constant WITH_COMMENTS => 1;
use constant WITHOUT_COMMENTS => 0;
sub new {
my $class = shift;
my $self = bless {
reference_transforms => [ 'env_sig', 'ec14n' ],
reference_digest_method => 'sha1',
c14n_method => 'ec14n',
signature_algorithm => 'rsa_sha1',
include_x509_cert => 0,
@_
}, $class;
return $self;
}
sub id_attr { shift->{id_attr}; }
sub reference_transforms { shift->{reference_transforms}; }
sub reference_digest_method { shift->{reference_digest_method}; }
sub c14n_method { shift->{c14n_method}; }
sub signature_algorithm { shift->{signature_algorithm}; }
lib/Authen/NZRealMe/XMLSig.pm view on Meta::CPAN
}
@elem = map { $_->ownerElement() } @attr;
}
return $elem[0];
}
sub _node_to_clarkian_path {
my($self, $node) = @_;
my $node_path = $node->nodePath();
my %frag_ns;
do {
if(my $prefix = $node->prefix) {
$frag_ns{$prefix} = $node->namespaceURI;
}
$node = $node->parentNode();
} while($node);
$node_path =~ s{([\w-]+):}{
my $prefix = $1;
my $uri = $frag_ns{$prefix};
"{$uri}";
}ge;
return $node_path;
}
sub _make_sig_xml {
my($self, $xc, %opt) = @_;
my $sig = {};
my $ref_specs = $opt{references} // [];
die "Can't make a signature without references" unless @$ref_specs;
my @references = map {
$_->{digest_method} //= $opt{reference_digest_method} if $opt{reference_digest_method};
$_->{transforms} //= $opt{reference_transforms} if $opt{reference_transforms};
$self->_make_reference($xc, $_);
} @$ref_specs;
$sig->{references} = \@references;
$sig->{c14n} = $self->_find_transform(
$opt{c14n} // $self->c14n_method()
);
if(my $ns_list = $opt{c14n_namespaces}) {
$sig->{c14n}->{namespaces} = $ns_list;
}
$sig->{signature_algorithm} = $self->_find_sig_alg(
$opt{signature_algorithm} // $self->signature_algorithm()
);
return $self->_sig_as_xml($sig);
}
sub _sig_as_xml {
my($self, $sig) = @_;
my $ns_ds = [ dsig => URI('ds') ];
my $x = XML::Generator->new(':strict', pretty => 2);
my @ref_blocks = map {
my @transforms = map {
$self->_transform_as_xml($x, 'Transform', $ns_ds, $_);
} @{ $_->{transforms} };
$x->Reference($ns_ds, { URI => '#' . $_->{ref_id} },
$x->Transforms($ns_ds,
@transforms,
),
$x->DigestMethod($ns_ds, { Algorithm => $_->{digest_method}->{uri} }),
$x->DigestValue($ns_ds, $_->{digest_value}),
),
} @{ $sig->{references} };
my $c14n = $sig->{c14n};
my $sig_alg = $sig->{signature_algorithm};
my @key_info;
if($self->include_x509_cert) {
my $cert_text = $self->pub_cert_text()
or die "Need pub_cert_file or pub_cert_text for include_x509_cert";
$cert_text =~ s{\A\s*-+\s*BEGIN CERTIFICATE\s*-+\s*}{};
$cert_text =~ s{\s*-+\s*END CERTIFICATE\s*-+\s*}{};
$cert_text =~ s{^\s+}{}mg;
@key_info = (
$x->KeyInfo($ns_ds,
$x->X509Data($ns_ds,
$x->X509Certificate($ns_ds,
$cert_text . "\n"
)
)
)
);
}
my $sig_xml = $x->Signature($ns_ds,
$x->SignedInfo($ns_ds,
$self->_transform_as_xml($x, 'CanonicalizationMethod', $ns_ds, $c14n),
$x->SignatureMethod($ns_ds, { Algorithm => $sig_alg->{uri} }),
@ref_blocks,
),
$x->SignatureValue($ns_ds),
@key_info,
) . '';
my $xc = $self->_xcdom_from_xml($sig_xml, @$ns_ds);
my $doc = $xc->getContextNode();
my($fragment) = [ $xc, $xc->findnodes('/ds:Signature/ds:SignedInfo') ];
my $plaintext = $self->_apply_transform($sig->{c14n}, $fragment);
my $sig_text = "\n" . $self->_create_signature(
$sig->{signature_algorithm},
$plaintext,
);
my($sig_node) = $xc->findnodes('//dsig:SignatureValue')
or die "Failed to find SignatureValue in generated signature XML";
$sig_node->addChild( $doc->ownerDocument->createTextNode($sig_text) );
# Serialising, parsing and reserialising simplifies ns attr and empty tags
return $self->_xml_to_dom( $doc->toStringEC14N() )->toString();
}
sub _transform_as_xml {
my($self, $x, $tag_name, $ns_ds, $trans) = @_;
my @content;
if(my $ns_list = $trans->{namespaces}) {
my $prefixes = join ' ', @$ns_list;
my $ec_ns = [ 'ec' => URI('ec14n') ];
push @content, $x->InclusiveNamespaces($ec_ns, { PrefixList => $prefixes });
}
my $xml = $x->$tag_name($ns_ds, { Algorithm => $trans->{uri} }, @content);
return $xml;
}
sub _make_reference {
my($self, $xc, $spec) = @_;
if((ref($spec) || '') ne 'HASH') {
die "references must be specified as hashrefs";
}
my $ref = {};
my $ref_uri = $ref->{ref_id} = $spec->{ref_id}
// die "need a 'ref_id' to create a reference";
my $target_node = $self->_find_element_by_uri_reference($xc, $ref_uri);
$ref->{xml_node} = $target_node;
my $fragment = [$xc, $target_node];
my @transforms = map {
$self->_find_transform($_)
} @{ $spec->{transforms} // $self->reference_transforms() };
if(my $ns_list = $spec->{namespaces}) {
if($transforms[-1]->{uri} ne URI('ec14n')) {
$transforms[-1] = $self->_find_transform('ec14n');
}
$transforms[-1]->{namespaces} = $ns_list;
}
$ref->{transforms} = \@transforms;
foreach my $transform ( @transforms ) {
$fragment = $self->_apply_transform($transform, $fragment);
}
my $digest_method = $ref->{digest_method} = $self->_find_transform(
$spec->{digest_method} // $self->reference_digest_method()
);
$ref->{digest_value} = $self->_apply_transform($digest_method, $fragment);
return $ref;
( run in 0.460 second using v1.01-cache-2.11-cpan-71847e10f99 )