Skip to content

Commit

Permalink
Merge pull request #117 from waterkip/GH-116-signing_and_encryption_key
Browse files Browse the repository at this point in the history
Fix bug where two keys with different usage fails
  • Loading branch information
timlegge authored Sep 1, 2022
2 parents c57b2b3 + e95e7c2 commit f29670e
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 79 deletions.
2 changes: 1 addition & 1 deletion Makefile.PL
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ my %WriteMakefileArgs = (
"URI::URL" => 0,
"XML::LibXML::XPathContext" => 0
},
"VERSION" => "0.59",
"VERSION" => "0.60",
"test" => {
"TESTS" => "t/*.t t/author/*.t"
}
Expand Down
2 changes: 1 addition & 1 deletion README
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ NAME
Net::SAML2 - SAML2 bindings and protocol implementation

VERSION
version 0.59
version 0.60

SYNOPSIS
See TUTORIAL.md for implementation documentation and
Expand Down
2 changes: 1 addition & 1 deletion lib/Net/SAML2.pm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use strict;
use warnings;
package Net::SAML2;
our $VERSION = "0.59";
our $VERSION = "0.60";

require 5.008_001;

Expand Down
130 changes: 54 additions & 76 deletions lib/Net/SAML2/IdP.pm
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use strict;
use warnings;
package Net::SAML2::IdP;
use Moose;

# VERSION

use Moose;
use MooseX::Types::URI qw/ Uri /;

# ABSTRACT: Net::SAML2::IdP - SAML Identity Provider object
Expand Down Expand Up @@ -121,36 +120,24 @@ sub new_from_xml {

my $data;

for my $sso (
$xpath->findnodes(
'//md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService')
)
{
my $basepath = '//md:EntityDescriptor/md:IDPSSODescriptor';

for my $sso ($xpath->findnodes("$basepath/md:SingleSignOnService")) {
my $binding = $sso->getAttribute('Binding');
$data->{SSO}->{$binding} = $sso->getAttribute('Location');
}

for my $slo (
$xpath->findnodes(
'//md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService')
)
{
for my $slo ($xpath->findnodes("$basepath/md:SingleLogoutService")) {
my $binding = $slo->getAttribute('Binding');
$data->{SLO}->{$binding} = $slo->getAttribute('Location');
}

for my $art (
$xpath->findnodes(
'//md:EntityDescriptor/md:IDPSSODescriptor/md:ArtifactResolutionService')
)
{
for my $art ($xpath->findnodes("$basepath/md:ArtifactResolutionService")) {
my $binding = $art->getAttribute('Binding');
$data->{Art}->{$binding} = $art->getAttribute('Location');
}

for my $format (
$xpath->findnodes('//md:EntityDescriptor/md:IDPSSODescriptor/md:NameIDFormat'))
{
for my $format ($xpath->findnodes("$basepath/md:NameIDFormat")) {
$format = $format->string_value;
$format =~ s/^\s+//g;
$format =~ s/\s+$//g;
Expand All @@ -164,50 +151,26 @@ sub new_from_xml {
}
}

my @certs = ();

for my $key (
$xpath->findnodes('//md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor'))
{
my @uses;
push (@uses, $key->getAttribute('use') || 'signing');
push (@uses, 'encryption') if !$key->getAttribute('use');


$key->setNamespace('http://www.w3.org/2000/09/xmldsig#', 'ds');

my ($text)
= $key->findvalue("ds:KeyInfo/ds:X509Data/ds:X509Certificate", $key)
=~ /^\s*(.+?)\s*$/s;

# rewrap the base64 data from the metadata; it may not
# be wrapped at 64 characters as PEM requires
$text =~ s/\n//g;

my @lines;
while(length $text > 64) {
push @lines, substr $text, 0, 64, '';
my %certs = ();
for my $key ($xpath->findnodes("$basepath/md:KeyDescriptor")) {
my $use = $key->getAttribute('use');
my $pem = $class->_get_pem_from_keynode($key);
if (!$use) {
push(@{$certs{signing}}, $pem);
push(@{$certs{encryption}}, $pem);
}
push @lines, $text;

$text = join "\n", @lines;

# form a PEM certificate
for my $use (@uses) {
my $pem->{$use}
= sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n",
$text);
push (@certs, $pem);
else {
push(@{$certs{$use}}, $pem);
}
}

my $self = $class->new(
return $class->new(
entityid => $xpath->findvalue('//md:EntityDescriptor/@entityID'),
sso_urls => $data->{SSO},
slo_urls => $data->{SLO} || {},
art_urls => $data->{Art} || {},
certs => \@certs,
cacert => $args{cacert},
certs => \%certs,
cacert => $args{cacert},
$data->{DefaultFormat}
? (
default_format => $data->{DefaultFormat},
Expand All @@ -216,9 +179,34 @@ sub new_from_xml {
: (),
);

return $self;
}

sub _get_pem_from_keynode {
my $self = shift;
my $node = shift;

$node->setNamespace('http://www.w3.org/2000/09/xmldsig#', 'ds');

my ($text)
= $node->findvalue("ds:KeyInfo/ds:X509Data/ds:X509Certificate", $node)
=~ /^\s*(.+?)\s*$/s;

# rewrap the base64 data from the metadata; it may not
# be wrapped at 64 characters as PEM requires
$text =~ s/\n//g;

my @lines;
while(length $text > 64) {
push @lines, substr $text, 0, 64, '';
}
push @lines, $text;

$text = join "\n", @lines;

return "-----BEGIN CERTIFICATE-----\n$text\n-----END CERTIFICATE-----\n";
}


# BUILDARGS ( hashref of the parameters passed to the constructor )
#
# Called after the object is created to validate the IdP using the cacert
Expand All @@ -233,32 +221,22 @@ around BUILDARGS => sub {
if ($params{cacert}) {
my $ca = Crypt::OpenSSL::Verify->new($params{cacert}, { strict_certs => 0, });

my $verified = 0;
my %errors;
my %certs;

for my $pem (@{ $params{certs} }) {
for my $use (keys %{$pem}) {
my @tmpcrt;
my $cert = Crypt::OpenSSL::X509->new_from_string($pem->{$use});
my %certificates;
for my $use (keys %{$params{certs}}) {
my $certs = $params{certs}{$use};
for my $pem (@{$certs}) {
my $cert = Crypt::OpenSSL::X509->new_from_string($pem);
## BUGBUG this is failing for valid things ...
eval { $ca->verify($cert) };
if ($@) {
$errors{$cert->fingerprint_sha256} = $@;
warn "Can't verify IdP cert: $@";
next;
}
$verified = 1;
push @tmpcrt, $pem->{$use};

$certs{$use} = \@tmpcrt;
push(@{$certificates{$use}}, $pem);
}
}

$params{certs} = \%certs;

if (!$verified) {
warn "Can't verify IdP signing cert: ", %errors, "\n";
}
$params{certs} = \%certificates;
}

return $self->$orig(%params);
Expand Down
18 changes: 18 additions & 0 deletions t/01-create-idp.t
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,22 @@ XML
);
}

{
my $xml = path('t/data/idp-metadata-signing-encryption.xml')->slurp;
my $idp = Net::SAML2::IdP->new_from_xml(
xml => $xml,
);

isa_ok($idp, "Net::SAML2::IdP");
is(@{$idp->cert('signing')}, 1, 'Got one signing cert');
is(@{$idp->cert('encryption')}, 1, 'Got one encryption cert');
}

{
my $xml = path('t/data/idp-metadata-multiple-invalid-use.xml')->slurp;
my $idp = Net::SAML2::IdP->new_from_xml(xml => $xml);
is(@{$idp->cert('signing')}, 1, 'Got one signing cert');
is(@{$idp->cert('encryption')}, 2, 'Got two encryption certs');
}

done_testing;
53 changes: 53 additions & 0 deletions t/data/idp-metadata-multiple-invalid-use.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://accounts.google.com/o/saml2?idpid=C01nccos6" validUntil="2022-04-20T00:56:59.000Z">
<md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVuOAyTCMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTcwNDIx
MDA1NjU5WhcNMjIwNDIwMDA1NjU5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsziYPBZSqLzs5pFfoZPTVyoHwM4fmI9qQIScWJB0435ss5HDRDKBTvO8rg2sGbfM
SYVINLvesTeTNkPkMfoSplne6uWjAWkoskvls+0bzp8jAXb+UJ1cZplQxhWW25TsEcpcMX+p4J0v
/syO9lyDubNSpchPGIuzitKXdwFkgcOiCFk+01MF3COKaO6HoLr5Jqf7I6qjJy72h3ZC7rGUtwjC
QPaAfeLIM2uGmdOcm7Ql/89hBSjJYvJOXOyEZqppYd2tat7UPh4q8UTmPhwETaVssThfNZUpt9iZ
cCFcWaVh/mljxD/V20VNMAlzN8VXM/WjbBSAePS30VRF6xXlzwIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQAKxc4M+il/bQQcmm/JNKF5ypeC0YH+oRjSwec84pznP2qqjWosR2uR9hL6CN2dOZU/uCaX
1nZviGIlsw9n7JUEfxMs4azYWfdTz7C7l9x6HpF/QK5NwBe1z3iPZAiKR8hBqt7IeN3nlT4RsaMr
0E9UbG5pimxEG0uves5BoHS+3Xfb8sd33dNRyCm22nb2wWGG+jTKy2G8/24gHB2c0X6/AiD3dZbY
qf7pFRWrXefkMaUMaCMnQh+owjjxVNEAAQ8QU/2Hwhz6pR0eEGL6UwWufJX8uSGaxqn+397MdrI4
CE0gQAZXrB1L3PU9tdyiap2hWQPo8T2STpOtvcovFPHs</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAWj+rlteMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTkwMjE4
MDMzNzQ1WhcNMjQwMjE3MDMzNzQ1WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAubE4/cl70Lc2f7VV+ZJyzYIzuAMj6ejlbtRnym2kgyYjaaO0MVU/r38oRC7UqdQI
XwA0/S1Eu0k6EYcCUiyTGWz3HKv/OSOSnSDpN4wEWaZbmJYvu8SjWZQCVdcM4fx1kzrtE+LEBTOK
gj0k2G1qUMNI7xaqiJONO9aIeCic5zbACNpc+IOZoRS4RaY5Ie7W1ZIXAJ0xWL3snVdqklaJzzU2
Myt5QX6W1Sd411Hzo+Ihi5ksq18ML1tgMTFIqLkY2Luf5JJdZFRcCgvHrFF2CQywE0ftZtSSg5wD
+hp3PJpL5bxxF1vSdyKQbUia1Buc8+Cy6NTIbmLLIrcyULyHSwIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQAXKjWrRzhgUW1+pK3V51MGl2b/yf33Ac4fm7GQql0Ag0Neye1EmdLjD2N9gVeFawMcfRT4
GABBHtS5bu01M8QHHAGjbBKfqOaPJc39v0Y/RCSd/FzXg99hNT5UggAWVR+vC6a1IrVSUa5eKe82
yBAubbdftvGtKHG90HIAsb1iyMKK2rGnTupgJfJIUTWhWnWuemIVwduErFCxng//jYXViyEloz73
0faMIp6eNSD2+2cCssVGFb6FxhCvVuNh6tgXv4vErVSWerFk/GcIh5n/biaDy/gEtAqgK154AfOi
fpDP3l3ZV/celj1wSwcLF90e84XaVIkzb3veTcWhqaaq</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://accounts.google.com/o/saml2/idp?idpid=C01nccos6"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://accounts.google.com/o/saml2/idp?idpid=C01nccos6"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

53 changes: 53 additions & 0 deletions t/data/idp-metadata-signing-encryption.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://accounts.google.com/o/saml2?idpid=C01nccos6" validUntil="2022-04-20T00:56:59.000Z">
<md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAVuOAyTCMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTcwNDIx
MDA1NjU5WhcNMjIwNDIwMDA1NjU5WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsziYPBZSqLzs5pFfoZPTVyoHwM4fmI9qQIScWJB0435ss5HDRDKBTvO8rg2sGbfM
SYVINLvesTeTNkPkMfoSplne6uWjAWkoskvls+0bzp8jAXb+UJ1cZplQxhWW25TsEcpcMX+p4J0v
/syO9lyDubNSpchPGIuzitKXdwFkgcOiCFk+01MF3COKaO6HoLr5Jqf7I6qjJy72h3ZC7rGUtwjC
QPaAfeLIM2uGmdOcm7Ql/89hBSjJYvJOXOyEZqppYd2tat7UPh4q8UTmPhwETaVssThfNZUpt9iZ
cCFcWaVh/mljxD/V20VNMAlzN8VXM/WjbBSAePS30VRF6xXlzwIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQAKxc4M+il/bQQcmm/JNKF5ypeC0YH+oRjSwec84pznP2qqjWosR2uR9hL6CN2dOZU/uCaX
1nZviGIlsw9n7JUEfxMs4azYWfdTz7C7l9x6HpF/QK5NwBe1z3iPZAiKR8hBqt7IeN3nlT4RsaMr
0E9UbG5pimxEG0uves5BoHS+3Xfb8sd33dNRyCm22nb2wWGG+jTKy2G8/24gHB2c0X6/AiD3dZbY
qf7pFRWrXefkMaUMaCMnQh+owjjxVNEAAQ8QU/2Hwhz6pR0eEGL6UwWufJX8uSGaxqn+397MdrI4
CE0gQAZXrB1L3PU9tdyiap2hWQPo8T2STpOtvcovFPHs</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAWj+rlteMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMTkwMjE4
MDMzNzQ1WhcNMjQwMjE3MDMzNzQ1WjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAubE4/cl70Lc2f7VV+ZJyzYIzuAMj6ejlbtRnym2kgyYjaaO0MVU/r38oRC7UqdQI
XwA0/S1Eu0k6EYcCUiyTGWz3HKv/OSOSnSDpN4wEWaZbmJYvu8SjWZQCVdcM4fx1kzrtE+LEBTOK
gj0k2G1qUMNI7xaqiJONO9aIeCic5zbACNpc+IOZoRS4RaY5Ie7W1ZIXAJ0xWL3snVdqklaJzzU2
Myt5QX6W1Sd411Hzo+Ihi5ksq18ML1tgMTFIqLkY2Luf5JJdZFRcCgvHrFF2CQywE0ftZtSSg5wD
+hp3PJpL5bxxF1vSdyKQbUia1Buc8+Cy6NTIbmLLIrcyULyHSwIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQAXKjWrRzhgUW1+pK3V51MGl2b/yf33Ac4fm7GQql0Ag0Neye1EmdLjD2N9gVeFawMcfRT4
GABBHtS5bu01M8QHHAGjbBKfqOaPJc39v0Y/RCSd/FzXg99hNT5UggAWVR+vC6a1IrVSUa5eKe82
yBAubbdftvGtKHG90HIAsb1iyMKK2rGnTupgJfJIUTWhWnWuemIVwduErFCxng//jYXViyEloz73
0faMIp6eNSD2+2cCssVGFb6FxhCvVuNh6tgXv4vErVSWerFk/GcIh5n/biaDy/gEtAqgK154AfOi
fpDP3l3ZV/celj1wSwcLF90e84XaVIkzb3veTcWhqaaq</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://accounts.google.com/o/saml2/idp?idpid=C01nccos6"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://accounts.google.com/o/saml2/idp?idpid=C01nccos6"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

0 comments on commit f29670e

Please sign in to comment.