diff --git a/content/config-exsaml.xml b/content/config-exsaml.xml index ecd6d8d..94073af 100644 --- a/content/config-exsaml.xml +++ b/content/config-exsaml.xml @@ -27,7 +27,7 @@ - + diff --git a/content/exsaml.xqm b/content/exsaml.xqm index f8884aa..85f4175 100644 --- a/content/exsaml.xqm +++ b/content/exsaml.xqm @@ -31,6 +31,7 @@ declare %private variable $exsaml:idp-certfile := data($exsaml:config/idp/@ce declare %private variable $exsaml:idp-unsolicited := data($exsaml:config/idp/@accept-unsolicited); declare %private variable $exsaml:idp-force-rs := data($exsaml:config/idp/@force-relaystate); declare %private variable $exsaml:idp-verify-issuer := data($exsaml:config/idp/@verify-issuer); +declare %private variable $exsaml:idp-verify-xmlsignature := data($exsaml:config/idp/@verify-xmlsignature); declare %private variable $exsaml:hmac-key := data($exsaml:config/crypto/@hmac-key); declare %private variable $exsaml:hmac-alg := data($exsaml:config/crypto/@hmac-alg); @@ -171,7 +172,7 @@ declare function exsaml:process-saml-response-post() { ) let $debug := exsaml:log("debug", "START SAML RESPONSE ") - let $debug := exsaml:log("debug", $resp) + let $debug := exsaml:log("debug", fn:serialize($resp)) let $debug := exsaml:log("debug", "END SAML RESPONSE ") return @@ -255,11 +256,11 @@ declare %private function exsaml:validate-saml-response($resp as node()) { let $sig := $resp/ds:Signature let $result := - (: check SAML response status. there are ~20 failure codes, check - : for success only, return errmsg in @data - :) - if (not($resp/samlp:Status/samlp:StatusCode/@Value = $exsaml:status-success)) then - + (: verify response signature if present :) + if ($exsaml:idp-verify-xmlsignature = "true" and boolean($sig) + and not(exsaml:verify-response-signature($sig))) then ( + + ) (: check that "Issuer" is the expected IDP. Not stricty required by SAML specs, but adds extra protection against forged SAML responses. :) @@ -269,31 +270,32 @@ declare %private function exsaml:validate-saml-response($resp as node()) { ) - (: verify response signature if present :) -(: COMMENTED OUT until crypto-lib issues resolved :) -(: else if (boolean($sig) and not(exsaml:verify-response-signature($sig))) then :) -(: :) + (: check SAML response status. there are ~20 failure codes, check + : for success only, return errmsg in @data + :) + else if (not($resp//samlp:Status/samlp:StatusCode/@Value = $exsaml:status-success)) then ( + + ) (: must contain at least one assertion :) else if (empty($as)) then ( - + ) - (: validate all assertions - only first by now :) - else ( - exsaml:validate-saml-assertion($as) - ) + + (: validate all assertions - only first by now :) + else ( + exsaml:validate-saml-assertion($as) + ) return $result }; (: validate a SAML assertion :) declare %private function exsaml:validate-saml-assertion($assertion as item()) { - if(empty($assertion)) - then ( + if (empty($assertion)) then ( let $log := exsaml:log("info", "Error: Empty Assertion") return - ) else ( let $log := exsaml:log("info", "validate-saml-assertion: " || fn:serialize($assertion)) @@ -304,29 +306,31 @@ declare %private function exsaml:validate-saml-assertion($assertion as item()) { let $result := + (: verify assertion signature if present :) + if ($exsaml:idp-verify-xmlsignature = "true" and boolean($sig) and not(exsaml:verify-assertion-signature($assertion))) then ( + + ) + (: check that "Issuer" is the expected IDP. Not stricty required by SAML specs, but adds extra protection against forged SAML responses. :) - if ($exsaml:idp-verify-issuer = "true" and boolean($assertion/saml:Issuer) and not($assertion/saml:Issuer = $exsaml:idp-ent)) then ( + else if ($exsaml:idp-verify-issuer = "true" and boolean($assertion/saml:Issuer) and not($assertion/saml:Issuer = $exsaml:idp-ent)) then ( let $msg := "SAML assertion from unexpected IDP: " || $assertion/saml:Issuer return ) - (: verify assertion signature if present :) -(: COMMENTED OUT until crypto-lib issues resolved :) -(: else if (boolean($sig) and not(exsaml:verify-assertion-signature($assertion))) then :) -(: :) - (: maybe verify SubjectConfirmation/@Method :) (: verify SubjectConfirmationData/@Recipient is SP URL ($sp-uri) :) - else if (not($subj-confirm-data/@Recipient = $exsaml:sp-uri)) then + else if (not($subj-confirm-data/@Recipient = $exsaml:sp-uri)) then ( + ) (: verify SubjectConfirmationData/@NotOnOrAfter is not later than now :) - else if (xs:dateTime(fn:current-dateTime()) >= xs:dateTime($subj-confirm-data/@NotOnOrAfter)) then - + else if (xs:dateTime(fn:current-dateTime()) >= xs:dateTime($subj-confirm-data/@NotOnOrAfter)) then ( + + ) (: verify SubjectConfirmationData/@InResponseTo is present in the SAML response :) else if (not($reqid)) then ( @@ -340,27 +344,29 @@ declare %private function exsaml:validate-saml-assertion($assertion as item()) { (: else verify SubjectConfirmationData/@InResponseTo equal to orig AuthnRequest ID :) else if (not(exsaml:check-authnreqid($reqid))) then ( - + ) (: verify assertions are valid in other respects - none yet :) (: verify Conditions/@NotBefore is not earlier than now :) else if (xs:dateTime(fn:current-dateTime()) < xs:dateTime($conds/@NotBefore)) then ( - + ) (: verify Conditions/@NotOnOrAfter is not later than now :) else if (xs:dateTime(fn:current-dateTime()) >= xs:dateTime($conds/@NotOnOrAfter)) then ( - + ) (: verify Conditions/AudienceRestriction/Audience is myself ($sp-ent) :) - else if (not($conds/saml:AudienceRestriction/saml:Audience = $exsaml:sp-ent)) then - + else if (not($conds/saml:AudienceRestriction/saml:Audience = $exsaml:sp-ent)) then ( + + ) - else + else ( + ) return $result ) @@ -378,30 +384,40 @@ declare %private function exsaml:check-authnreqid($reqid as xs:string) { (: verify XML signature of a SAML response :) declare %private function exsaml:verify-response-signature($resp as item()) { - let $log := exsaml:log("debug", "verify-response-signature: " || $resp) + let $log := exsaml:log("debug", "verify-response-signature: " || fn:serialize($resp)) + let $key-embedded := boolean($resp/saml:Assertion/ds:Signature/ds:KeyInfo) let $res := - (: if $idp-certfile is configured, use that to validate XML signature :) - if ($exsaml:idp-certfile != "") then ( -(: crypto:validate-signature-by-certfile($resp, $exsaml:idp-certfile):) + if ($key-embedded) then ( + let $log := exsaml:log("info", "signature contains KeyInfo") + return crypto:validate-signature($resp) ) else ( - let $log := exsaml:log("info", "cert to verify response signature is missing - could not verify signature! ") - return - false() + (: if $idp-certfile is configured, use that to validate XML signature :) + if ($exsaml:idp-certfile != "") then ( + let $log := exsaml:log("info", "no KeyInfo, validate signature using cert " || $exsaml:idp-certfile) + return + crypto:validate-signature-by-certfile($resp, $exsaml:idp-certfile) + ) + else ( + let $log := exsaml:log("info", "cert to verify response signature is missing - could not verify signature!") + return + false() + ) ) return $res }; (: verify XML signature of a SAML assertion :) declare %private function exsaml:verify-assertion-signature($assertion as item()) { - let $log := exsaml:log("debug", "verify-assertion-signature " || $assertion) + let $log := exsaml:log("debug", "verify-assertion-signature " || fn:serialize($assertion)) + let $res := (: if $idp-certfile is configured, use that to validate XML signature :) if ($exsaml:idp-certfile != "") then ( -(: crypto:validate-signature-by-certfile($assertion, $exsaml:idp-certfile):) + crypto:validate-signature-by-certfile($assertion, $exsaml:idp-certfile) ) else ( - let $log := exsaml:log("info", "cert to verify assertion signature is missing - could not verify signature! ") + let $log := exsaml:log("info", "cert to verify assertion signature is missing - could not verify signature!") return false() )