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

Added support for parsing IdP metadata, and did some cleanup of the code.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@332 44740490-163a-0410-bde0-09ae8108e29a
parent fe766c96
No related branches found
No related tags found
No related merge requests found
......@@ -130,7 +130,9 @@ function processFile($filename) {
foreach($entities as $entity) {
addMetadata($filename, $entity->getMetadata1xSP(), 'shib13-sp-remote');
addMetadata($filename, $entity->getMetadata1xIdP(), 'shib13-idp-remote');
addMetadata($filename, $entity->getMetadata20SP(), 'saml20-sp-remote');
addMetadata($filename, $entity->getMetadata20IdP(), 'saml20-idp-remote');
}
}
......
......@@ -32,6 +32,11 @@ class SimpleSAML_Metadata_SAMLParser {
);
/**
* This is the binding used to send authentication requests in SAML 1.x.
*/
const SAML_1x_AUTHN_REQUEST = 'urn:mace:shibboleth:1.0:profiles:AuthnRequest';
/**
* This is the binding used for browser post in SAML 1.x.
*/
......@@ -59,18 +64,22 @@ class SimpleSAML_Metadata_SAMLParser {
/**
* This is an array with the processed SPSSODescriptor elements we have found in this
* metadata file.
* Each element in the array is an associative array with the following elements:
* - 'protocols': Array with the protocols this SPSSODescriptor supports.
* Each element in the array is an associative array with the elements from parseSSODescriptor and:
* - 'assertionConsumerServices': Array with the SP's assertion consumer services.
* Each assertion consumer service is stored as an associative array with the
* elements that parseGenericEndpoint returns.
* - 'singleLogoutServices': Array with the SP's single logout service endpoints. Each endpoint is stored
* as an associative array with the elements that parseGenericEndpoint returns.
* - 'nameIDFormats': The NameIDFormats that the SP accepts. This may be an empty array.
*/
private $spDescriptors;
/**
* This is an array with the processed IDPSSODescriptor elements we have found.
* Each element in the array is an associative array with the elements from parseSSODescriptor and:
* - 'singleSignOnServices': Array with the IdP's single signon service endpoints. Each endpoint is stored
* as an associative array with the elements that parseGenericEndpoint returns.
*/
private $idpDescriptors;
/**
* This is the constructor for the SAMLParser class.
......@@ -79,6 +88,7 @@ class SimpleSAML_Metadata_SAMLParser {
*/
private function __construct($entityElement) {
$this->spDescriptors = array();
$this->idpDescriptors = array();
assert('$entityElement instanceof DOMElement');
......@@ -104,6 +114,10 @@ class SimpleSAML_Metadata_SAMLParser {
if(SimpleSAML_Utilities::isDOMElementOfType($child, 'SPSSODescriptor', '@md') === TRUE) {
$this->processSPSSODescriptor($child);
}
if(SimpleSAML_Utilities::isDOMElementOfType($child, 'IDPSSODescriptor', '@md') === TRUE) {
$this->processIDPSSODescriptor($child);
}
}
}
......@@ -290,6 +304,62 @@ class SimpleSAML_Metadata_SAMLParser {
}
/**
* This function returns the metadata for SAML 2.0 IdPs in the format simpleSAMLphp expects.
* This is an associative array with the following fields:
* - 'entityID': The entity id of the entity described in the metadata.
* - 'name': Autogenerated name for this entity. Currently set to the entityID.
* - 'SingleSignOnService': String with the url of the SSO service which supports the redirect binding.
* - 'SingleLogoutService': String with the url where we should send logout requests/responses.
* - 'certFingerprint': Fingerprint of the X509Certificate from the metadata.
*
* Metadata must be loaded with one of the parse functions before this function can be called.
*
* @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 1.x IdP.
*/
public function getMetadata1xIdP() {
$ret = array();
$ret['entityID'] = $this->entityID;
$ret['name'] = $this->entityID;
/* Find IdP information which supports the SAML 1.x protocol. */
$idp = $this->getIdPDescriptors(self::$SAML1xProtocols);
if(count($idp) === 0) {
return NULL;
}
/* We currently only look at the first IDP descriptor which supports SAML 1.x. */
$idp = $idp[0];
/* Find the SSO service endpoint. */
$sso = $this->getDefaultEndpoint($idp['singleSignOnServices'], array(self::SAML_1x_AUTHN_REQUEST));
if($sso === NULL) {
throw new Exception('Could not find any valid SingleSignOnService endpoint.');
}
$ret['SingleSignOnService'] = $sso['location'];
/* Find the certificate fingerprint. */
foreach($idp['keys'] as $key) {
if($key['type'] !== 'X509Certificate') {
continue;
}
$certData = base64_decode($key['X509Certificate']);
if($certData === FALSE) {
throw new Exception('Unable to parse base64 encoded certificate data.');
}
$ret['certFingerprint'] = sha1($certData);
break;
}
return $ret;
}
/**
* This function returns the metadata for SAML 2.0 SPs in the format simpleSAMLphp expects.
* This is an associative array with the following fields:
......@@ -301,7 +371,7 @@ class SimpleSAML_Metadata_SAMLParser {
*
* Metadata must be loaded with one of the parse functions before this function can be called.
*
* @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 1.x SP.
* @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 2.x SP.
*/
public function getMetadata20SP() {
......@@ -330,13 +400,13 @@ class SimpleSAML_Metadata_SAMLParser {
/* Find the single logout service endpoint. */
$acs = $this->getDefaultEndpoint($spd['singleLogoutServices'], array(self::SAML_20_REDIRECT_BINDING));
if($acs === NULL) {
$slo = $this->getDefaultEndpoint($spd['singleLogoutServices'], array(self::SAML_20_REDIRECT_BINDING));
if($slo === NULL) {
throw new Exception('Could not find any valid SingleLogoutService.' .
' simpleSAMLphp currently supports only the http-redirect binding for SAML 1.0 logout.');
' simpleSAMLphp currently supports only the http-redirect binding for SAML 2.0 logout.');
}
$ret['SingleLogoutService'] = $acs['location'];
$ret['SingleLogoutService'] = $slo['location'];
/* Find the NameIDFormat. This may not exists. */
......@@ -350,36 +420,136 @@ class SimpleSAML_Metadata_SAMLParser {
/**
* This function extracts metadata from a SPSSODescriptor element.
* This function returns the metadata for SAML 2.0 IdPs in the format simpleSAMLphp expects.
* This is an associative array with the following fields:
* - 'entityID': The entity id of the entity described in the metadata.
* - 'name': Autogenerated name for this entity. Currently set to the entityID.
* - 'SingleSignOnService': String with the url of the SSO service which supports the redirect binding.
* - 'SingleLogoutService': String with the url where we should send logout requests/responses.
* - 'certFingerprint': Fingerprint of the X509Certificate from the metadata.
*
* @param $element The element which should be parsed.
* Metadata must be loaded with one of the parse functions before this function can be called.
*
* @return Associative array with metadata or NULL if we are unable to generate metadata for a SAML 2.0 IdP.
*/
private function processSPSSODescriptor($element) {
assert('$element instanceof DOMElement');
public function getMetadata20IdP() {
$sp = array();
$ret = array();
$sp['protocols'] = self::getSupportedProtocols($element);
$ret['entityID'] = $this->entityID;
/* Find all AssertionConsumerService elements. */
$sp['assertionConsumerServices'] = array();
$acs = SimpleSAML_Utilities::getDOMChildren($element, 'AssertionConsumerService', '@md');
foreach($acs as $child) {
$sp['assertionConsumerServices'][] = self::parseAssertionConsumerService($child);
$ret['name'] = $this->entityID;
/* Find IdP information which supports the SAML 2.0 protocol. */
$idp = $this->getIdPDescriptors(self::$SAML20Protocols);
if(count($idp) === 0) {
return NULL;
}
/* We currently only look at the first IDP descriptor which supports SAML 2.0. */
$idp = $idp[0];
/* Find the SSO service endpoint. */
$sso = $this->getDefaultEndpoint($idp['singleSignOnServices'], array(self::SAML_20_REDIRECT_BINDING));
if($sso === NULL) {
throw new Exception('Could not find any valid SingleSignOnService endpoint.');
}
$ret['SingleSignOnService'] = $sso['location'];
/* Find the single logout service endpoint. */
$slo = $this->getDefaultEndpoint($idp['singleLogoutServices'], array(self::SAML_20_REDIRECT_BINDING));
if($slo === NULL) {
throw new Exception('Could not find any valid SingleLogoutService.' .
' simpleSAMLphp currently supports only the http-redirect binding for SAML 2.0 logout.');
}
$ret['SingleLogoutService'] = $slo['location'];
/* Find the certificate fingerprint. */
foreach($idp['keys'] as $key) {
if($key['type'] !== 'X509Certificate') {
continue;
}
$certData = base64_decode($key['X509Certificate']);
if($certData === FALSE) {
throw new Exception('Unable to parse base64 encoded certificate data.');
}
$ret['certFingerprint'] = sha1($certData);
break;
}
return $ret;
}
/**
* This function extracts metadata from a SSODescriptor element.
*
* The returned associative array has the following elements:
* - 'protocols': Array with the protocols this SSODescriptor supports.
* - 'singleLogoutServices': Array with the single logout service endpoints. Each endpoint is stored
* as an associative array with the elements that parseGenericEndpoint returns.
* - 'nameIDFormats': The NameIDFormats supported by this SSODescriptor. This may be an empty array.
* - 'keys': Array of associative arrays with the elements from parseKeyDescriptor:
*
* @param $element The element we should extract metadata from.
* @return Associative array with metadata we have extracted from this element.
*/
private static function parseSSODescriptor($element) {
assert('$element instanceof DOMElement');
$sd = array();
$sd['protocols'] = self::getSupportedProtocols($element);
/* Find all SingleLogoutService elements. */
$sp['singleLogoutServices'] = array();
$sd['singleLogoutServices'] = array();
$sls = SimpleSAML_Utilities::getDOMChildren($element, 'SingleLogoutService', '@md');
foreach($sls as $child) {
$sp['singleLogoutServices'][] = self::parseSingleLogoutService($child);
$sd['singleLogoutServices'][] = self::parseSingleLogoutService($child);
}
/* Process NameIDFormat elements. */
$sp['nameIDFormats'] = array();
$sd['nameIDFormats'] = array();
$nif = SimpleSAML_Utilities::getDOMChildren($element, 'NameIDFormat', '@md');
if(count($nif) > 0) {
$sp['nameIDFormats'][] = self::parseNameIDFormat($nif[0]);
$sd['nameIDFormats'][] = self::parseNameIDFormat($nif[0]);
}
/* Process KeyDescriptor elements. */
$sd['keys'] = array();
$keys = SimpleSAML_Utilities::getDOMChildren($element, 'KeyDescriptor', '@md');
foreach($keys as $kd) {
$key = self::parseKeyDescriptor($kd);
if($key !== NULL) {
$sd['keys'][] = $key;
}
}
return $sd;
}
/**
* This function extracts metadata from a SPSSODescriptor element.
*
* @param $element The element which should be parsed.
*/
private function processSPSSODescriptor($element) {
assert('$element instanceof DOMElement');
$sp = self::parseSSODescriptor($element);
/* Find all AssertionConsumerService elements. */
$sp['assertionConsumerServices'] = array();
$acs = SimpleSAML_Utilities::getDOMChildren($element, 'AssertionConsumerService', '@md');
foreach($acs as $child) {
$sp['assertionConsumerServices'][] = self::parseAssertionConsumerService($child);
}
......@@ -387,6 +557,28 @@ class SimpleSAML_Metadata_SAMLParser {
}
/**
* This function extracts metadata from a IDPSSODescriptor element.
*
* @param $element The element which should be parsed.
*/
private function processIDPSSODescriptor($element) {
assert('$element instanceof DOMElement');
$idp = self::parseSSODescriptor($element);
/* Find all SingleSignOnService elements. */
$idp['singleSignOnServices'] = array();
$acs = SimpleSAML_Utilities::getDOMChildren($element, 'SingleSignOnService', '@md');
foreach($acs as $child) {
$idp['singleSignOnServices'][] = self::parseSingleSignOnService($child);
}
$this->idpDescriptors[] = $idp;
}
/**
* This function parses AssertionConsumerService elements.
*
......@@ -414,27 +606,28 @@ class SimpleSAML_Metadata_SAMLParser {
/**
* This function parses NameIDFormat elements.
* This function parses SingleSignOnService elements.
*
* @param $element The element which should be parsed.
* @return URN with the supported NameIDFormat.
* @return Associative array with the data we have extracted from the SingleLogoutService element.
*/
private static function parseNameIDFormat($element) {
private static function parseSingleSignOnService($element) {
assert('$element instanceof DOMElement');
$fmt = '';
return self::parseGenericEndpoint($element, FALSE);
}
for($i = 0; $i < $element->childNodes->length; $i++) {
$child = $element->childNodes->item($i);
if(!($child instanceof DOMText)) {
throw new Exception('NameIDFormat contained a non-text child node.');
}
$fmt .= $child->wholeText;
}
/**
* This function parses NameIDFormat elements.
*
* @param $element The element which should be parsed.
* @return URN with the supported NameIDFormat.
*/
private static function parseNameIDFormat($element) {
assert('$element instanceof DOMElement');
$fmt = trim($fmt);
return $fmt;
return SimpleSAML_Utilities::getDOMText($element);
}
......@@ -497,6 +690,65 @@ class SimpleSAML_Metadata_SAMLParser {
}
/**
* This function parses a KeyDescriptor element. It currently only supports keys with a single
* X509 certificate.
*
* The associative array for a key can contain:
* - 'encryption': Indicates wheter this key can be used for encryption.
* - 'signing': Indicates wheter this key can be used for signing.
* - 'type: The type of the key. 'X509Certificate' is the only key type we support.
* - 'X509Certificate': The contents of the first X509Certificate element (if the type is 'X509Certificate ').
*
* @param $kd The KeyDescriptor element.
* @return Associative array describing the key, or NULL if this is an unsupported key.
*/
private static function parseKeyDescriptor($kd) {
assert('$kd instanceof DOMElement');
$r = array();
if($kd->hasAttribute('use')) {
$use = $kd->getAttribute('use');
if($use === 'encryption') {
$r['encryption'] = TRUE;
$r['signing'] = FALSE;
} elseif($use === 'signing') {
$r['encryption'] = FALSE;
$r['signing'] = TRUE;
} else {
throw new Exception('Invalid use-value for KeyDescriptor: ' . $use);
}
} else {
$r['encryption'] = TRUE;
$r['signing'] = TRUE;
}
$keyInfo = SimpleSAML_Utilities::getDOMChildren($kd, 'KeyInfo', '@ds');
if(count($keyInfo) === 0) {
throw new Exception('Missing required KeyInfo field for KeyDescriptor.');
}
$keyInfo = $keyInfo[0];
$X509Data = SimpleSAML_Utilities::getDOMChildren($keyInfo, 'X509Data', '@ds');
if(count($X509Data) === 0) {
return NULL;
}
$X509Data = $X509Data[0];
$X509Certificate = SimpleSAML_Utilities::getDOMChildren($X509Data, 'X509Certificate', '@ds');
if(count($X509Certificate) === 0) {
return NULL;
}
$X509Certificate = $X509Certificate[0];
$r['type'] = 'X509Certificate';
$r['X509Certificate'] = SimpleSAML_Utilities::getDOMText($X509Certificate);
return $r;
}
/**
* This function attempts to locate the default endpoint which supports one of the given bindings.
*
......@@ -572,6 +824,28 @@ class SimpleSAML_Metadata_SAMLParser {
}
/**
* This function finds IdP descriptors which supports one of the given protocols.
*
* @param $protocols Array with the protocols we accept.
* @return Array with IdP descriptors which supports one of the given protocols.
*/
private function getIdPDescriptors($protocols) {
assert('is_array($protocols)');
$ret = array();
foreach($this->idpDescriptors as $idpd) {
$sharedProtocols = array_intersect($protocols, $idpd['protocols']);
if(count($sharedProtocols) > 0) {
$ret[] = $idpd;
}
}
return $ret;
}
/**
* This function locates the EntityDescriptor node in a DOMDocument. This node should
* be the first (and only) node in the document.
......
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