##@file # SAML Metadata object for Lemonldap::NG ##@class # SAML Metadata object for Lemonldap::NG package Lemonldap::NG::Common::Conf::SAML::Metadata; use strict; use Mouse; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::X509; use HTML::Template; use MIME::Base64; use Safe; use Encode; our $VERSION = '2.1.0'; my $dataStart = tell(DATA); ## @method public string serviceToXML # Return all SAML parameters in well formated XML format, corresponding to # SAML 2 description. # @return string sub serviceToXML { my ( $self, $conf, $type ) = @_; seek DATA, $dataStart, 0; my $s = join '', ; my $template = HTML::Template->new( scalarref => \$s, die_on_bad_params => 0, cache => 0, ); # Automatic parameters my @param_auto = qw( samlEntityID samlOrganizationName samlOrganizationDisplayName samlOrganizationURL ); if ( $type and $type eq 'idp' ) { $template->param( 'hideSPMetadata', 1 ); } if ( $type and $type eq 'sp' ) { $template->param( 'hideIDPMetadata', 1 ); } foreach (@param_auto) { $template->param( $_, $self->getValue( $_, $conf ) ); } # When asked to provide only IDP metadata, take into account EntityID override if ( $type eq "idp" and $conf->{samlOverrideIDPEntityID} ) { $template->param( 'samlEntityID', $conf->{samlOverrideIDPEntityID} ); } # Boolean parameters my @param_boolean = qw( samlSPSSODescriptorAuthnRequestsSigned samlSPSSODescriptorWantAssertionsSigned samlIDPSSODescriptorWantAuthnRequestsSigned ); foreach (@param_boolean) { $template->param( $_, $self->getValue( $_, $conf ) ? 'true' : 'false' ); } # Format public keys my @param_keys = qw( samlServicePublicKeySig samlServicePublicKeyEnc ); foreach (@param_keys) { my $str = ''; my $val = $self->getValue( $_, $conf ); # A default value for samlServicePublicKeyEnc parameter if ( $_ =~ /samlServicePublicKeyEnc/ ) { unless ( $val && length $val gt 0 ) { $val = $conf->{samlServicePublicKeySig}; } } # Generate XML if ( defined $val && length $val gt 0 ) { # Public Key ? if ( $val =~ /^-----BEGIN PUBLIC KEY-----/ and my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($val) ) { my @params = $rsa_pub->get_key_parameters(); my $mod = encode_base64( $params[0]->to_bin() ); my $exp = encode_base64( $params[1]->to_bin() ); $str = '' . "\n\t" . '' . "\n\t\t" . '' . $mod . '' . "\n\t\t" . '' . $exp . '' . "\n\t" . '' . "\n" . ''; } # Certificate ? if ( $val =~ /^-----BEGIN CERTIFICATE-----/ and my $certificate = Crypt::OpenSSL::X509->new_from_string($val) ) { $certificate = $certificate->as_string(); $certificate =~ s/^-----BEGIN CERTIFICATE-----\n?//g; $certificate =~ s/\n?-----END CERTIFICATE-----$//g; $str = '' . "\n\t" . '' . "\n\t" . $certificate . '' . "\n" . ''; } } $template->param( $_, $str ); } # Rebuilded parameters for SAML services # A samlService value is formated like the following: # "binding;location;responseLocation" # The last value, responseLocation, is optional. my @param_service = qw( samlSPSSODescriptorSingleLogoutServiceHTTPRedirect samlSPSSODescriptorSingleLogoutServiceHTTPPost samlSPSSODescriptorSingleLogoutServiceSOAP samlIDPSSODescriptorSingleSignOnServiceHTTPRedirect samlIDPSSODescriptorSingleSignOnServiceHTTPPost samlIDPSSODescriptorSingleSignOnServiceHTTPArtifact samlIDPSSODescriptorSingleLogoutServiceHTTPRedirect samlIDPSSODescriptorSingleLogoutServiceHTTPPost samlIDPSSODescriptorSingleLogoutServiceSOAP samlAttributeAuthorityDescriptorAttributeServiceSOAP ); foreach (@param_service) { my @_tab = split( /;/, $self->getValue( $_, $conf ) ); $template->param( $_ . 'Binding', $_tab[0] ); $template->param( $_ . 'Location', $_tab[1] ); $template->param( $_ . 'ResponseLocation', $_tab[2] ); } # Rebuilded parameters for SAML assertions # A samlAssertion value is formated like the following: # "default;index;binding;location" my @param_assertion = qw( samlSPSSODescriptorAssertionConsumerServiceHTTPArtifact samlSPSSODescriptorAssertionConsumerServiceHTTPPost samlSPSSODescriptorArtifactResolutionServiceArtifact samlIDPSSODescriptorArtifactResolutionServiceArtifact ); my %indexed_endpoints; foreach (@param_assertion) { my @_tab = split( /;/, $self->getValue( $_, $conf ) ); $indexed_endpoints{ $_ . 'Default' } = ( $_tab[0] ? 'true' : 'false' ); $indexed_endpoints{ $_ . 'Index' } = $_tab[1]; $indexed_endpoints{ $_ . 'Binding' } = $_tab[2]; $indexed_endpoints{ $_ . 'Location' } = $_tab[3]; } $template->param(%indexed_endpoints); if ( $indexed_endpoints{samlSPSSODescriptorAssertionConsumerServiceHTTPArtifactDefault} eq 'true' ) { $template->param( "ACSArtifactDefault" => 1 ); } # Return the XML metadata. return $template->output; } #@method string getValue(string key, hashref conf) # Get the value for a metadata configuration key # Replace #PORTAL# macro # @param key Configuration key # @param conf Configuration hash ref # @return value sub getValue { my ( $self, $key, $conf ) = @_; # Get portal value my $portal = $conf->{portal} || "http://auth.example.com/"; $portal =~ s/\/$//; # Try to get value for the given key in configuraiton my $value = $conf->{$key}; return unless defined $value; # Replace #PORTAL# macro $value =~ s/#PORTAL#/$portal/g; # Return value return $value; } 1; __DATA__ "> " protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> " index="" Binding="" Location="" /> " Location="" /> " Location="" ResponseLocation="" /> " Location="" ResponseLocation="" /> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos urn:oasis:names:tc:SAML:2.0:nameid-format:entity urn:oasis:names:tc:SAML:2.0:nameid-format:transient " Location="" ResponseLocation="" /> " Location="" ResponseLocation="" /> " Location="" ResponseLocation="" /> " WantAssertionsSigned="" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> " index="" Binding="" Location="" /> " Location="" /> " Location="" ResponseLocation="" /> " Location="" ResponseLocation="" /> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos urn:oasis:names:tc:SAML:2.0:nameid-format:entity urn:oasis:names:tc:SAML:2.0:nameid-format:transient " index="" Binding="" Location="" /> " index="" Binding="" Location="" /> " index="" Binding="" Location="" /> " index="" Binding="" Location="" /> " Location=""/> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos urn:oasis:names:tc:SAML:2.0:nameid-format:entity urn:oasis:names:tc:SAML:2.0:nameid-format:transient