diff --git a/config-templates/config.php b/config-templates/config.php index 3d1609c8b032287a847c495e3a8d143f7288a7f2..b584d7b6a381858a27073d8dc65e9ce032aaaddb 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -41,10 +41,10 @@ $config = array ( 'showerrors' => true, /** - * This option allows you to enable validation of SAML 2.0 messages against their + * This option allows you to enable validation of SAML messages against their * schemas. A warning will be written to the log if validation fails. */ - 'debug.validatesaml2messages' => false, + 'debug.validatesamlmessages' => false, /** * This password must be kept secret, and modified from the default value 123. diff --git a/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php b/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php index ed9ee715abe5fed44df5d7914b830758fcfcfc1e..cf7f0bbef675cb704f4e24ce8ce833086bd9edeb 100644 --- a/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php +++ b/lib/SimpleSAML/Bindings/SAML20/HTTPPost.php @@ -20,7 +20,7 @@ class SimpleSAML_Bindings_SAML20_HTTPPost { public function sendResponseUnsigned($response, $idpentityid, $spentityid, $relayState = null, $endpoint = 'AssertionConsumerService') { - SimpleSAML_Utilities::validateSAML2Message($response); + SimpleSAML_Utilities::validateSAMLMessage($response, 'saml20'); $idpmd = $this->metadata->getMetaData($idpentityid, 'saml20-idp-hosted'); $spmd = $this->metadata->getMetaData($spentityid, 'saml20-sp-remote'); @@ -183,7 +183,7 @@ class SimpleSAML_Bindings_SAML20_HTTPPost { } $response = $responsedom->saveXML(); - SimpleSAML_Utilities::validateSAML2Message($response); + SimpleSAML_Utilities::validateSAMLMessage($response, 'saml20'); # openssl genrsa -des3 -out server.key 1024 # openssl rsa -in server.key -out server.pem @@ -231,7 +231,7 @@ class SimpleSAML_Bindings_SAML20_HTTPPost { $samlResponseXML = base64_decode( $rawResponse ); - SimpleSAML_Utilities::validateSAML2Message($samlResponseXML); + SimpleSAML_Utilities::validateSAMLMessage($samlResponseXML, 'saml20'); //error_log("Response is: " . $samlResponseXML); diff --git a/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php b/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php index 2bf82072d968bc651efb76dfd72140cc01a809bb..c9f610cfdeff7710c98a93580ffe0b60ce2b13c4 100644 --- a/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php +++ b/lib/SimpleSAML/Bindings/SAML20/HTTPRedirect.php @@ -180,7 +180,7 @@ class SimpleSAML_Bindings_SAML20_HTTPRedirect { public function sendMessage($request, $localentityid, $remoteentityid, $relayState = null, $endpoint = 'SingleSignOnService', $direction = 'SAMLRequest', $mode = 'SP') { - SimpleSAML_Utilities::validateSAML2Message($request); + SimpleSAML_Utilities::validateSAMLMessage($request, 'saml20'); $redirectURL = $this->getRedirectURL($request, $localentityid, $remoteentityid, $relayState, $endpoint, $direction, $mode); @@ -234,7 +234,7 @@ class SimpleSAML_Bindings_SAML20_HTTPRedirect { throw new Exception('Could not gzinflate base64 decoded SAMLRequest: ' . $error['message'] ); } - SimpleSAML_Utilities::validateSAML2Message($samlRequestXML); + SimpleSAML_Utilities::validateSAMLMessage($samlRequestXML, 'saml20'); $samlRequest = new SimpleSAML_XML_SAML20_AuthnRequest($this->configuration, $this->metadata); @@ -272,7 +272,7 @@ class SimpleSAML_Bindings_SAML20_HTTPRedirect { throw new Exception('Could not gzinflate base64 decoded SAMLRequest: ' . $error['message'] ); } - SimpleSAML_Utilities::validateSAML2Message($samlRequestXML); + SimpleSAML_Utilities::validateSAMLMessage($samlRequestXML, 'saml20'); $samlRequest = new SimpleSAML_XML_SAML20_LogoutRequest($this->configuration, $this->metadata); @@ -311,7 +311,7 @@ class SimpleSAML_Bindings_SAML20_HTTPRedirect { throw new Exception('Could not gzinflate base64 decoded SAMLRequest: ' . $error['message'] ); } - SimpleSAML_Utilities::validateSAML2Message($samlRequestXML); + SimpleSAML_Utilities::validateSAMLMessage($samlRequestXML, 'saml20'); $samlRequest = new SimpleSAML_XML_SAML20_LogoutResponse($this->configuration, $this->metadata); diff --git a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php index 37adb633a7cba8d30a1091c248ea4f1f1950cc00..36af8c220b3cd18a1c5b83ecc7415a224ed6644b 100644 --- a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php +++ b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php @@ -20,6 +20,8 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { public function sendResponseUnsigned($response, $idpentityid, $spentityid, $relayState = null, $endpoint = 'AssertionConsumerService') { + SimpleSAML_Utilities::validateSAMLMessage($response, 'saml11'); + $idpmd = $this->metadata->getMetaData($idpentityid, 'saml20-idp-hosted'); $spmd = $this->metadata->getMetaData($spentityid, 'saml20-sp-remote'); @@ -62,6 +64,8 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { */ public function sendResponse($response, $idpmetaindex, $spentityid, $relayState = null, $claimedacs = null) { + SimpleSAML_Utilities::validateSAMLMessage($response, 'saml11'); + $idpmd = $this->metadata->getMetaData($idpmetaindex, 'shib13-idp-hosted'); $spmd = $this->metadata->getMetaData($spentityid, 'shib13-sp-remote'); @@ -201,6 +205,8 @@ class SimpleSAML_Bindings_Shib13_HTTPPost { $relaystate = $post["TARGET"]; $samlResponseXML = base64_decode( $rawResponse ); + + SimpleSAML_Utilities::validateSAMLMessage($samlResponseXML, 'saml11'); $samlResponse = new SimpleSAML_XML_Shib13_AuthnResponse($this->configuration, $this->metadata); diff --git a/lib/SimpleSAML/Utilities.php b/lib/SimpleSAML/Utilities.php index a46f31171e33233848e024feab3501506a71f81b..474cee47a1c53914ac23b38285e4de8a574839b6 100644 --- a/lib/SimpleSAML/Utilities.php +++ b/lib/SimpleSAML/Utilities.php @@ -793,24 +793,44 @@ class SimpleSAML_Utilities { /** - * This function validates a SAML2 message against its schema. - * A warning will be printed to the log if validation fails. + * This function performs some sanity checks on SAML messages, and optionally validates them + * against their schema. A warning will be printed to the log if validation fails. * * @param $message The message which should be validated. + * @param $type The type of message - can be either 'saml20' or 'saml11'. */ - public static function validateSAML2Message($message) { + public static function validateSAMLMessage($message, $type) { assert('is_string($message)'); + assert($type === 'saml11' || $type === 'saml20'); - $enabled = SimpleSAML_Configuration::getInstance()->getValue('debug.validatesaml2messages', FALSE); - if(!is_bool($enabled)) { - throw new Exception('Expected "debug.validatesaml2messages" to be set to a boolean value.'); + /* A SAML message should not contain a doctype-declaration. */ + if(strpos($message, '<!DOCTYPE') !== FALSE) { + throw new Exception('SAML message contained a doctype declaration.'); + } + + $enabled = SimpleSAML_Configuration::getInstance()->getValue('debug.validatesamlmessages', NULL); + if($enabled === NULL) { + /* Fall back to old configuration option. */ + $enabled = SimpleSAML_Configuration::getInstance()->getValue('debug.validatesaml2messages', FALSE); + if(!is_bool($enabled)) { + throw new Exception('Expected "debug.validatesaml2messages" to be set to a boolean value.'); + } + } elseif(!is_bool($enabled)) { + throw new Exception('Expected "debug.validatesamlmessages" to be set to a boolean value.'); } if(!$enabled) { return; } - $result = self::validateXML($message, 'saml-schema-protocol-2.0.xsd'); + if($type === 'saml11') { + $result = self::validateXML($message, 'oasis-sstc-saml-schema-protocol-1.1.xsd'); + } elseif($type === 'saml20') { + $result = self::validateXML($message, 'saml-schema-protocol-2.0.xsd'); + } else { + throw new Exception('Invalid message type.'); + } + if($result !== '') { SimpleSAML_Logger::warning($result); } diff --git a/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd b/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd new file mode 100644 index 0000000000000000000000000000000000000000..4cc2bf33815f970084c3a2ea13313af5a7bf1bca --- /dev/null +++ b/schemas/oasis-sstc-saml-schema-assertion-1.1.xsd @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="UTF-8"?> +<schema targetNamespace="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="1.1"> + <import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd"/> + <annotation> + <documentation> + Document identifier: oasis-sstc-saml-schema-assertion-1.1 + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November, 2002): + Initial standard schema. + V1.1 (September, 2003): + * Note that V1.1 of this schema has the same XML namespace as V1.0. + Rebased ID content directly on XML Schema types + Added DoNotCacheCondition element and DoNotCacheConditionType + </documentation> + </annotation> + <simpleType name="DecisionType"> + <restriction base="string"> + <enumeration value="Permit"/> + <enumeration value="Deny"/> + <enumeration value="Indeterminate"/> + </restriction> + </simpleType> + <element name="AssertionIDReference" type="NCName"/> + <element name="Assertion" type="saml:AssertionType"/> + <complexType name="AssertionType"> + <sequence> + <element ref="saml:Conditions" minOccurs="0"/> + <element ref="saml:Advice" minOccurs="0"/> + <choice maxOccurs="unbounded"> + <element ref="saml:Statement"/> + <element ref="saml:SubjectStatement"/> + <element ref="saml:AuthenticationStatement"/> + <element ref="saml:AuthorizationDecisionStatement"/> + <element ref="saml:AttributeStatement"/> + </choice> + <element ref="ds:Signature" minOccurs="0"/> + </sequence> + <attribute name="MajorVersion" type="integer" use="required"/> + <attribute name="MinorVersion" type="integer" use="required"/> + <attribute name="AssertionID" type="ID" use="required"/> + <attribute name="Issuer" type="string" use="required"/> + <attribute name="IssueInstant" type="dateTime" use="required"/> + </complexType> + <element name="Conditions" type="saml:ConditionsType"/> + <complexType name="ConditionsType"> + <choice minOccurs="0" maxOccurs="unbounded"> + <element ref="saml:AudienceRestrictionCondition"/> + <element ref="saml:DoNotCacheCondition"/> + <element ref="saml:Condition"/> + </choice> + <attribute name="NotBefore" type="dateTime" use="optional"/> + <attribute name="NotOnOrAfter" type="dateTime" use="optional"/> + </complexType> + <element name="Condition" type="saml:ConditionAbstractType"/> + <complexType name="ConditionAbstractType" abstract="true"/> + <element name="AudienceRestrictionCondition" type="saml:AudienceRestrictionConditionType"/> + <complexType name="AudienceRestrictionConditionType"> + <complexContent> + <extension base="saml:ConditionAbstractType"> + <sequence> + <element ref="saml:Audience" maxOccurs="unbounded"/> + </sequence> + </extension> + </complexContent> + </complexType> + <element name="Audience" type="anyURI"/> + <element name="DoNotCacheCondition" type="saml:DoNotCacheConditionType"/> + <complexType name="DoNotCacheConditionType"> + <complexContent> + <extension base="saml:ConditionAbstractType"/> + </complexContent> + </complexType> + <element name="Advice" type="saml:AdviceType"/> + <complexType name="AdviceType"> + <choice minOccurs="0" maxOccurs="unbounded"> + <element ref="saml:AssertionIDReference"/> + <element ref="saml:Assertion"/> + <any namespace="##other" processContents="lax"/> + </choice> + </complexType> + <element name="Statement" type="saml:StatementAbstractType"/> + <complexType name="StatementAbstractType" abstract="true"/> + <element name="SubjectStatement" type="saml:SubjectStatementAbstractType"/> + <complexType name="SubjectStatementAbstractType" abstract="true"> + <complexContent> + <extension base="saml:StatementAbstractType"> + <sequence> + <element ref="saml:Subject"/> + </sequence> + </extension> + </complexContent> + </complexType> + <element name="Subject" type="saml:SubjectType"/> + <complexType name="SubjectType"> + <choice> + <sequence> + <element ref="saml:NameIdentifier"/> + <element ref="saml:SubjectConfirmation" minOccurs="0"/> + </sequence> + <element ref="saml:SubjectConfirmation"/> + </choice> + </complexType> + <element name="NameIdentifier" type="saml:NameIdentifierType"/> + <complexType name="NameIdentifierType"> + <simpleContent> + <extension base="string"> + <attribute name="NameQualifier" type="string" use="optional"/> + <attribute name="Format" type="anyURI" use="optional"/> + </extension> + </simpleContent> + </complexType> + <element name="SubjectConfirmation" type="saml:SubjectConfirmationType"/> + <complexType name="SubjectConfirmationType"> + <sequence> + <element ref="saml:ConfirmationMethod" maxOccurs="unbounded"/> + <element ref="saml:SubjectConfirmationData" minOccurs="0"/> + <element ref="ds:KeyInfo" minOccurs="0"/> + </sequence> + </complexType> + <element name="SubjectConfirmationData" type="anyType"/> + <element name="ConfirmationMethod" type="anyURI"/> + <element name="AuthenticationStatement" type="saml:AuthenticationStatementType"/> + <complexType name="AuthenticationStatementType"> + <complexContent> + <extension base="saml:SubjectStatementAbstractType"> + <sequence> + <element ref="saml:SubjectLocality" minOccurs="0"/> + <element ref="saml:AuthorityBinding" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + <attribute name="AuthenticationMethod" type="anyURI" use="required"/> + <attribute name="AuthenticationInstant" type="dateTime" use="required"/> + </extension> + </complexContent> + </complexType> + <element name="SubjectLocality" type="saml:SubjectLocalityType"/> + <complexType name="SubjectLocalityType"> + <attribute name="IPAddress" type="string" use="optional"/> + <attribute name="DNSAddress" type="string" use="optional"/> + </complexType> + <element name="AuthorityBinding" type="saml:AuthorityBindingType"/> + <complexType name="AuthorityBindingType"> + <attribute name="AuthorityKind" type="QName" use="required"/> + <attribute name="Location" type="anyURI" use="required"/> + <attribute name="Binding" type="anyURI" use="required"/> + </complexType> + <element name="AuthorizationDecisionStatement" type="saml:AuthorizationDecisionStatementType"/> + <complexType name="AuthorizationDecisionStatementType"> + <complexContent> + <extension base="saml:SubjectStatementAbstractType"> + <sequence> + <element ref="saml:Action" maxOccurs="unbounded"/> + <element ref="saml:Evidence" minOccurs="0"/> + </sequence> + <attribute name="Resource" type="anyURI" use="required"/> + <attribute name="Decision" type="saml:DecisionType" use="required"/> + </extension> + </complexContent> + </complexType> + <element name="Action" type="saml:ActionType"/> + <complexType name="ActionType"> + <simpleContent> + <extension base="string"> + <attribute name="Namespace" type="anyURI"/> + </extension> + </simpleContent> + </complexType> + <element name="Evidence" type="saml:EvidenceType"/> + <complexType name="EvidenceType"> + <choice maxOccurs="unbounded"> + <element ref="saml:AssertionIDReference"/> + <element ref="saml:Assertion"/> + </choice> + </complexType> + <element name="AttributeStatement" type="saml:AttributeStatementType"/> + <complexType name="AttributeStatementType"> + <complexContent> + <extension base="saml:SubjectStatementAbstractType"> + <sequence> + <element ref="saml:Attribute" maxOccurs="unbounded"/> + </sequence> + </extension> + </complexContent> + </complexType> + <element name="AttributeDesignator" type="saml:AttributeDesignatorType"/> + <complexType name="AttributeDesignatorType"> + <attribute name="AttributeName" type="string" use="required"/> + <attribute name="AttributeNamespace" type="anyURI" use="required"/> + </complexType> + <element name="Attribute" type="saml:AttributeType"/> + <complexType name="AttributeType"> + <complexContent> + <extension base="saml:AttributeDesignatorType"> + <sequence> + <element ref="saml:AttributeValue" maxOccurs="unbounded"/> + </sequence> + </extension> + </complexContent> + </complexType> + <element name="AttributeValue" type="anyType"/> +</schema> diff --git a/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd b/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd new file mode 100644 index 0000000000000000000000000000000000000000..9df108d938617ad01f60b7d66fa84bc9b057f561 --- /dev/null +++ b/schemas/oasis-sstc-saml-schema-protocol-1.1.xsd @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="UTF-8"?> +<schema targetNamespace="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns="http://www.w3.org/2001/XMLSchema" elementFormDefault="unqualified" attributeFormDefault="unqualified" version="1.1"> + <import namespace="urn:oasis:names:tc:SAML:1.0:assertion" schemaLocation="oasis-sstc-saml-schema-assertion-1.1.xsd"/> + <import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd"/> + <annotation> + <documentation> + Document identifier: oasis-sstc-saml-schema-protocol-1.1 + Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security + Revision history: + V1.0 (November, 2002): + Initial standard schema. + V1.1 (September, 2003): + * Note that V1.1 of this schema has the same XML namespace as V1.0. + Rebased ID content directly on XML Schema types + </documentation> + </annotation> + <complexType name="RequestAbstractType" abstract="true"> + <sequence> + <element ref="samlp:RespondWith" minOccurs="0" maxOccurs="unbounded"/> + <element ref="ds:Signature" minOccurs="0"/> + </sequence> + <attribute name="RequestID" type="ID" use="required"/> + <attribute name="MajorVersion" type="integer" use="required"/> + <attribute name="MinorVersion" type="integer" use="required"/> + <attribute name="IssueInstant" type="dateTime" use="required"/> + </complexType> + <element name="RespondWith" type="QName"/> + <element name="Request" type="samlp:RequestType"/> + <complexType name="RequestType"> + <complexContent> + <extension base="samlp:RequestAbstractType"> + <choice> + <element ref="samlp:Query"/> + <element ref="samlp:SubjectQuery"/> + <element ref="samlp:AuthenticationQuery"/> + <element ref="samlp:AttributeQuery"/> + <element ref="samlp:AuthorizationDecisionQuery"/> + <element ref="saml:AssertionIDReference" maxOccurs="unbounded"/> + <element ref="samlp:AssertionArtifact" maxOccurs="unbounded"/> + </choice> + </extension> + </complexContent> + </complexType> + <element name="AssertionArtifact" type="string"/> + <element name="Query" type="samlp:QueryAbstractType"/> + <complexType name="QueryAbstractType" abstract="true"/> + <element name="SubjectQuery" type="samlp:SubjectQueryAbstractType"/> + <complexType name="SubjectQueryAbstractType" abstract="true"> + <complexContent> + <extension base="samlp:QueryAbstractType"> + <sequence> + <element ref="saml:Subject"/> + </sequence> + </extension> + </complexContent> + </complexType> + <element name="AuthenticationQuery" type="samlp:AuthenticationQueryType"/> + <complexType name="AuthenticationQueryType"> + <complexContent> + <extension base="samlp:SubjectQueryAbstractType"> + <attribute name="AuthenticationMethod" type="anyURI"/> + </extension> + </complexContent> + </complexType> + <element name="AttributeQuery" type="samlp:AttributeQueryType"/> + <complexType name="AttributeQueryType"> + <complexContent> + <extension base="samlp:SubjectQueryAbstractType"> + <sequence> + <element ref="saml:AttributeDesignator" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + <attribute name="Resource" type="anyURI" use="optional"/> + </extension> + </complexContent> + </complexType> + <element name="AuthorizationDecisionQuery" type="samlp:AuthorizationDecisionQueryType"/> + <complexType name="AuthorizationDecisionQueryType"> + <complexContent> + <extension base="samlp:SubjectQueryAbstractType"> + <sequence> + <element ref="saml:Action" maxOccurs="unbounded"/> + <element ref="saml:Evidence" minOccurs="0"/> + </sequence> + <attribute name="Resource" type="anyURI" use="required"/> + </extension> + </complexContent> + </complexType> + <complexType name="ResponseAbstractType" abstract="true"> + <sequence> + <element ref="ds:Signature" minOccurs="0"/> + </sequence> + <attribute name="ResponseID" type="ID" use="required"/> + <attribute name="InResponseTo" type="NCName" use="optional"/> + <attribute name="MajorVersion" type="integer" use="required"/> + <attribute name="MinorVersion" type="integer" use="required"/> + <attribute name="IssueInstant" type="dateTime" use="required"/> + <attribute name="Recipient" type="anyURI" use="optional"/> + </complexType> + <element name="Response" type="samlp:ResponseType"/> + <complexType name="ResponseType"> + <complexContent> + <extension base="samlp:ResponseAbstractType"> + <sequence> + <element ref="samlp:Status"/> + <element ref="saml:Assertion" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + </extension> + </complexContent> + </complexType> + <element name="Status" type="samlp:StatusType"/> + <complexType name="StatusType"> + <sequence> + <element ref="samlp:StatusCode"/> + <element ref="samlp:StatusMessage" minOccurs="0"/> + <element ref="samlp:StatusDetail" minOccurs="0"/> + </sequence> + </complexType> + <element name="StatusCode" type="samlp:StatusCodeType"/> + <complexType name="StatusCodeType"> + <sequence> + <element ref="samlp:StatusCode" minOccurs="0"/> + </sequence> + <attribute name="Value" type="QName" use="required"/> + </complexType> + <element name="StatusMessage" type="string"/> + <element name="StatusDetail" type="samlp:StatusDetailType"/> + <complexType name="StatusDetailType"> + <sequence> + <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/> + </sequence> + </complexType> +</schema>