Skip to content
Snippets Groups Projects
Commit 36191a4a authored by Olav Morken's avatar Olav Morken
Browse files

Support for holder-of-key profile.

This patch adds support for the holder-of-key profile for both the
SAML 2.0 SP and the SAML 2.0 IdP.

Thanks to Andreas Mayer for implementing this!

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@3061 44740490-163a-0410-bde0-09ae8108e29a
parent f2b96ef5
No related branches found
No related tags found
No related merge requests found
...@@ -34,6 +34,8 @@ abstract class SAML2_Binding { ...@@ -34,6 +34,8 @@ abstract class SAML2_Binding {
return new SAML2_HTTPRedirect(); return new SAML2_HTTPRedirect();
case SAML2_Const::BINDING_HTTP_ARTIFACT: case SAML2_Const::BINDING_HTTP_ARTIFACT:
return new SAML2_HTTPArtifact(); return new SAML2_HTTPArtifact();
case SAML2_Const::BINDING_HOK_SSO:
return new SAML2_HTTPPost();
default: default:
throw new Exception('Unsupported binding: ' . var_export($urn, TRUE)); throw new Exception('Unsupported binding: ' . var_export($urn, TRUE));
} }
......
...@@ -39,12 +39,22 @@ class SAML2_Const { ...@@ -39,12 +39,22 @@ class SAML2_Const {
*/ */
const BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP'; const BINDING_SOAP = 'urn:oasis:names:tc:SAML:2.0:bindings:SOAP';
/**
* The URN for the Holder-of-Key Web Browser SSO Profile binding
*/
const BINDING_HOK_SSO = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
/** /**
* Bearer subject confirmation method. * Bearer subject confirmation method.
*/ */
const CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'; const CM_BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer';
/**
* Holder-of-Key subject confirmation method.
*/
const CM_HOK = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key';
/** /**
* The URN for the unspecified attribute NameFormat. * The URN for the unspecified attribute NameFormat.
...@@ -103,6 +113,10 @@ class SAML2_Const { ...@@ -103,6 +113,10 @@ class SAML2_Const {
*/ */
const NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; const NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
/**
* The namespace for the SAML 2 HoK Web Browser SSO Profile.
*/
const NS_HOK = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
/** /**
* Top-level status code indicating successful processing of the request. * Top-level status code indicating successful processing of the request.
......
...@@ -49,7 +49,18 @@ class SAML2_XML_saml_SubjectConfirmationData { ...@@ -49,7 +49,18 @@ class SAML2_XML_saml_SubjectConfirmationData {
/** /**
* Initialize (and parse? a SubjectConfirmationData element. * The various key information elements.
*
* Array with various elements describing this key.
* Unknown elements will be represented by SAML2_XML_Chunk.
*
* @var array
*/
public $info = array();
/**
* Initialize (and parse) a SubjectConfirmationData element.
* *
* @param DOMElement|NULL $xml The XML element we should load. * @param DOMElement|NULL $xml The XML element we should load.
*/ */
...@@ -74,6 +85,23 @@ class SAML2_XML_saml_SubjectConfirmationData { ...@@ -74,6 +85,23 @@ class SAML2_XML_saml_SubjectConfirmationData {
if ($xml->hasAttribute('Address')) { if ($xml->hasAttribute('Address')) {
$this->Address = $xml->getAttribute('Address'); $this->Address = $xml->getAttribute('Address');
} }
for ($n = $xml->firstChild; $n !== NULL; $n = $n->nextSibling) {
if (!($n instanceof DOMElement)) {
continue;
}
if ($n->namespaceURI !== XMLSecurityDSig::XMLDSIGNS) {
$this->info[] = new SAML2_XML_Chunk($n);
continue;
}
switch ($n->localName) {
case 'KeyInfo':
$this->info[] = new SAML2_XML_ds_KeyInfo($n);
break;
default:
$this->info[] = new SAML2_XML_Chunk($n);
break;
}
}
} }
...@@ -108,6 +136,9 @@ class SAML2_XML_saml_SubjectConfirmationData { ...@@ -108,6 +136,9 @@ class SAML2_XML_saml_SubjectConfirmationData {
if (isset($this->Address)) { if (isset($this->Address)) {
$e->setAttribute('Address', $this->Address); $e->setAttribute('Address', $this->Address);
} }
foreach ($this->info as $n) {
$n->toXML($e);
}
return $e; return $e;
} }
......
...@@ -190,6 +190,9 @@ class SimpleSAML_Metadata_SAMLBuilder { ...@@ -190,6 +190,9 @@ class SimpleSAML_Metadata_SAMLBuilder {
if (isset($ep['ResponseLocation'])) { if (isset($ep['ResponseLocation'])) {
$t->ResponseLocation = $ep['ResponseLocation']; $t->ResponseLocation = $ep['ResponseLocation'];
} }
if (isset($ep['hoksso:ProtocolBinding'])) {
$t->setAttributeNS(SAML2_Const::NS_HOK, 'hoksso:ProtocolBinding', SAML2_Const::BINDING_HTTP_REDIRECT);
}
if ($indexed) { if ($indexed) {
if (!isset($ep['index'])) { if (!isset($ep['index'])) {
......
...@@ -226,6 +226,9 @@ class sspmod_saml_IdP_SAML2 { ...@@ -226,6 +226,9 @@ class sspmod_saml_IdP_SAML2 {
if ($idpMetadata->getBoolean('saml20.sendartifact', FALSE)) { if ($idpMetadata->getBoolean('saml20.sendartifact', FALSE)) {
$supportedBindings[] = SAML2_Const::BINDING_HTTP_ARTIFACT; $supportedBindings[] = SAML2_Const::BINDING_HTTP_ARTIFACT;
} }
if ($idpMetadata->getBoolean('saml20.hok.assertion', FALSE)) {
$supportedBindings[] = SAML2_Const::BINDING_HOK_SSO;
}
if (isset($_REQUEST['spentityid'])) { if (isset($_REQUEST['spentityid'])) {
/* IdP initiated authentication. */ /* IdP initiated authentication. */
...@@ -763,11 +766,47 @@ class sspmod_saml_IdP_SAML2 { ...@@ -763,11 +766,47 @@ class sspmod_saml_IdP_SAML2 {
$a->setSessionIndex(SimpleSAML_Utilities::generateID()); $a->setSessionIndex(SimpleSAML_Utilities::generateID());
$sc = new SAML2_XML_saml_SubjectConfirmation(); $sc = new SAML2_XML_saml_SubjectConfirmation();
$sc->Method = SAML2_Const::CM_BEARER;
$sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); $sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData();
$sc->SubjectConfirmationData->NotOnOrAfter = time() + $assertionLifetime; $sc->SubjectConfirmationData->NotOnOrAfter = time() + $assertionLifetime;
$sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL']; $sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL'];
$sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId']; $sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId'];
/* ProtcolBinding of SP's <AuthnRequest> overwrites IdP hosted metadata configuration. */
$hokAssertion = NULL;
if ($state['saml:Binding'] === SAML2_Const::BINDING_HOK_SSO) {
$hokAssertion = TRUE;
}
if ($hokAssertion === NULL) {
$hokAssertion = $idpMetadata->getBoolean('saml20.hok.assertion', FALSE);
}
if ($hokAssertion) {
/* Holder-of-Key */
$sc->Method = SAML2_Const::CM_HOK;
if (SimpleSAML_Utilities::isHTTPS()) {
if (isset($_SERVER['SSL_CLIENT_CERT']) && !empty($_SERVER['SSL_CLIENT_CERT'])) {
/* Extract certificate data (if this is a certificate). */
$clientCert = $_SERVER['SSL_CLIENT_CERT'];
$pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
if (preg_match($pattern, $clientCert, $matches)) {
/* We have a client certificate from the browser which we add to the HoK assertion. */
$x509Certificate = new SAML2_XML_ds_X509Certificate();
$x509Certificate->certificate = str_replace(array("\r", "\n", " "), '', $matches[1]);
$x509Data = new SAML2_XML_ds_X509Data();
$x509Data->data[] = $x509Certificate;
$keyInfo = new SAML2_XML_ds_KeyInfo();
$keyInfo->info[] = $x509Data;
$sc->SubjectConfirmationData->info[] = $keyInfo;
} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No valid client certificate provided during TLS handshake with IdP');
} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No client certificate provided during TLS handshake with IdP');
} else throw new SimpleSAML_Error_Exception('Error creating HoK assertion: No HTTPS connection to IdP, but required for Holder-of-Key SSO');
} else {
/* Bearer */
$sc->Method = SAML2_Const::CM_BEARER;
}
$a->setSubjectConfirmation(array($sc)); $a->setSubjectConfirmation(array($sc));
/* Add attributes. */ /* Add attributes. */
......
...@@ -373,17 +373,12 @@ class sspmod_saml_Message { ...@@ -373,17 +373,12 @@ class sspmod_saml_Message {
)); ));
} }
$dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HTTP_REDIRECT));
$dst = $dst['Location'];
$ar->setIssuer($spMetadata->getString('entityid'));
$ar->setDestination($dst);
$ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE)); $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE));
$ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE)); $ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE));
$protbind = $spMetadata->getValueValidate('ProtocolBinding', array( $protbind = $spMetadata->getValueValidate('ProtocolBinding', array(
SAML2_Const::BINDING_HTTP_POST, SAML2_Const::BINDING_HTTP_POST,
SAML2_Const::BINDING_HOK_SSO,
SAML2_Const::BINDING_HTTP_ARTIFACT, SAML2_Const::BINDING_HTTP_ARTIFACT,
SAML2_Const::BINDING_HTTP_REDIRECT, SAML2_Const::BINDING_HTTP_REDIRECT,
), SAML2_Const::BINDING_HTTP_POST); ), SAML2_Const::BINDING_HTTP_POST);
...@@ -391,6 +386,17 @@ class sspmod_saml_Message { ...@@ -391,6 +386,17 @@ class sspmod_saml_Message {
/* Shoaib - setting the appropriate binding based on parameter in sp-metadata defaults to HTTP_POST */ /* Shoaib - setting the appropriate binding based on parameter in sp-metadata defaults to HTTP_POST */
$ar->setProtocolBinding($protbind); $ar->setProtocolBinding($protbind);
/* Select appropriate SSO endpoint */
if ($protbind === SAML2_Const::BINDING_HOK_SSO) {
$dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HOK_SSO));
} else {
$dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HTTP_REDIRECT));
}
$dst = $dst['Location'];
$ar->setIssuer($spMetadata->getString('entityid'));
$ar->setDestination($dst);
if ($spMetadata->hasValue('AuthnContextClassRef')) { if ($spMetadata->hasValue('AuthnContextClassRef')) {
$accr = $spMetadata->getArrayizeString('AuthnContextClassRef'); $accr = $spMetadata->getArrayizeString('AuthnContextClassRef');
$ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr)); $ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr));
...@@ -559,11 +565,84 @@ class sspmod_saml_Message { ...@@ -559,11 +565,84 @@ class sspmod_saml_Message {
$found = FALSE; $found = FALSE;
$lastError = 'No SubjectConfirmation element in Subject.'; $lastError = 'No SubjectConfirmation element in Subject.';
foreach ($assertion->getSubjectConfirmation() as $sc) { foreach ($assertion->getSubjectConfirmation() as $sc) {
if ($sc->Method !== SAML2_Const::CM_BEARER) { if ($sc->Method !== SAML2_Const::CM_BEARER && $sc->Method !== SAML2_Const::CM_HOK) {
$lastError = 'Invalid Method on SubjectConfirmation: ' . var_export($sc->Method, TRUE); $lastError = 'Invalid Method on SubjectConfirmation: ' . var_export($sc->Method, TRUE);
continue; continue;
} }
/* Is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration. */
$hok = $idpMetadata->getBoolean('saml20.hok.assertion', NULL);
if ($hok === NULL) {
$protocolBinding = $spMetadata->getString('ProtocolBinding', SAML2_Const::BINDING_HTTP_POST);
if ($protocolBinding === SAML2_Const::BINDING_HOK_SSO) {
$hok = TRUE;
} else {
$hok = FALSE;
}
}
if ($sc->Method === SAML2_Const::CM_BEARER && $hok) {
$lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
continue;
}
$scd = $sc->SubjectConfirmationData; $scd = $sc->SubjectConfirmationData;
if ($sc->Method === SAML2_Const::CM_HOK) {
/* Check HoK Assertion */
if (SimpleSAML_Utilities::isHTTPS() === FALSE) {
$lastError = 'No HTTPS connection, but required for Holder-of-Key SSO';
continue;
}
if (isset($_SERVER['SSL_CLIENT_CERT']) && empty($_SERVER['SSL_CLIENT_CERT'])) {
$lastError = 'No client certificate provided during TLS Handshake with SP';
continue;
}
/* Extract certificate data (if this is a certificate). */
$clientCert = $_SERVER['SSL_CLIENT_CERT'];
$pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
if (preg_match($pattern, $clientCert, $matches) === FALSE) {
$lastError = 'No valid client certificate provided during TLS Handshake with SP';
continue;
}
/* We have a valid client certificate from the browser. */
$clientCert = str_replace(array("\r", "\n", " "), '', $matches[1]);
foreach ($scd->info as $thing) {
if($thing instanceof SAML2_XML_ds_KeyInfo) {
$keyInfo[]=$thing;
}
}
if (count($keyInfo)!=1) {
$lastError = 'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in <SubjectConfirmationData> allowed';
continue;
}
foreach ($keyInfo[0]->info as $thing) {
if($thing instanceof SAML2_XML_ds_X509Data) {
$x509data[]=$thing;
}
}
if (count($x509data)!=1) {
$lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Data> element in <ds:KeyInfo> within <SubjectConfirmationData> allowed';
continue;
}
foreach ($x509data[0]->data as $thing) {
if($thing instanceof SAML2_XML_ds_X509Certificate) {
$x509cert[]=$thing;
}
}
if (count($x509cert)!=1) {
$lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Certificate> element in <ds:X509Data> within <SubjectConfirmationData> allowed';
continue;
}
$HoKCertificate = $x509cert[0]->certificate;
if ($HoKCertificate !== $clientCert) {
$lastError = 'Provided client certificate does not match the certificate bound to the Holder-of-Key assertion';
continue;
}
}
if ($scd->NotBefore && $scd->NotBefore > time() + 60) { if ($scd->NotBefore && $scd->NotBefore > time() + 60) {
$lastError = 'NotBefore in SubjectConfirmationData is in the future: ' . $scd->NotBefore; $lastError = 'NotBefore in SubjectConfirmationData is in the future: ' . $scd->NotBefore;
continue; continue;
......
...@@ -72,6 +72,13 @@ $acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01'; ...@@ -72,6 +72,13 @@ $acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01';
$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact'); $acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml1-acs.php/' . $sourceId . '/artifact');
$sp->AssertionConsumerService[] = $acs; $sp->AssertionConsumerService[] = $acs;
$acs = new SAML2_XML_md_IndexedEndpointType();
$acs->index = 4;
$acs->Binding = 'urn:oasis:names:tc:SAML:2.0:profiles:holder-of-key:SSO:browser';
$acs->ProtocolBinding = SAML2_Const::BINDING_HTTP_POST;
$acs->Location = SimpleSAML_Module::getModuleURL('saml/sp/saml2-acs.php/' . $sourceId);
$sp->AssertionConsumerService[] = $acs;
$keys = array(); $keys = array();
$certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_'); $certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_');
if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) { if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
......
...@@ -60,7 +60,9 @@ try { ...@@ -60,7 +60,9 @@ try {
$metaArray = array( $metaArray = array(
'metadata-set' => 'saml20-idp-remote', 'metadata-set' => 'saml20-idp-remote',
'entityid' => $idpentityid, 'entityid' => $idpentityid,
'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'), 'SingleSignOnService' => array(0 => array(
'Binding' => SAML2_Const::BINDING_HTTP_REDIRECT,
'Location' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'))),
'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'), 'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'),
); );
...@@ -79,6 +81,14 @@ try { ...@@ -79,6 +81,14 @@ try {
); );
} }
if ($idpmeta->getBoolean('saml20.hok.assertion', FALSE)) {
/* Prepend HoK SSO Service endpoint. */
array_unshift($metaArray['SingleSignOnService'], array(
'hoksso:ProtocolBinding' => SAML2_Const::BINDING_HTTP_REDIRECT,
'Binding' => SAML2_Const::BINDING_HOK_SSO,
'Location' => SimpleSAML_Utilities::getBaseURL() . 'saml2/idp/SSOService.php'));
}
$metaArray['NameIDFormat'] = $idpmeta->getString('NameIDFormat', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'); $metaArray['NameIDFormat'] = $idpmeta->getString('NameIDFormat', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient');
if ($idpmeta->hasValue('OrganizationName')) { if ($idpmeta->hasValue('OrganizationName')) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment