diff --git a/config-templates/config.php b/config-templates/config.php index 71ad261a70850ed4875b4f3aa5bda1b0fc57092c..9323ddea40e34711d99b372b45674b58367116bd 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -363,6 +363,27 @@ $config = array ( 'memcache_store.expires' => 36 * (60*60), // 36 hours. + /* + * Should signing of generated metadata be enabled by default. + * + * Metadata signing can also be enabled for a individual SP or IdP by setting the + * same option in the metadata for the SP or IdP. + */ + 'metadata.sign.enable' => FALSE, + + /* + * The default key & certificate which should be used to sign generated metadata. These + * are files stored in the cert dir. + * These values can be overridden by the options with the same names in the SP or + * IdP metadata. + * + * If these aren't specified here or in the metadata for the SP or IdP, then + * the 'certificate' and 'privatekey' option in the metadata will be used. + * if those aren't set, signing of metadata will fail. + */ + 'metadata.sign.privatekey' => NULL, + 'metadata.sign.certificate' => NULL, + ); diff --git a/lib/SimpleSAML/Metadata/Signer.php b/lib/SimpleSAML/Metadata/Signer.php new file mode 100644 index 0000000000000000000000000000000000000000..2d69f0d11637938c8a7c1471fdd0da542ba706c2 --- /dev/null +++ b/lib/SimpleSAML/Metadata/Signer.php @@ -0,0 +1,183 @@ +<?php + +require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Configuration.php'); +require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'xmlseclibs.php'); + + +/** + * This class implements a helper function for signing of metadata. + * + * @author Olav Morken, UNINETT AS. + * @package simpleSAMLphp + * @version $Id$ + */ +class SimpleSAML_Metadata_Signer { + + /** + * This functions finds what key & certificate files should be used to sign the metadata + * for the given entity. + * + * @param $config Our SimpleSAML_Configuration instance. + * @param $entityMetadata The metadata of the entity. + * @param $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'. + * @return An associative array with the keys 'privatekey' and 'certificate'. + */ + private static function findKeyCert($config, $entityMetadata, $type) { + + /* First we look for metadata.privatekey and metadata.certificate in the metadata. */ + if(array_key_exists('metadata.sign.privatekey', $entityMetadata) + || array_key_exists('metadata.sign.certificate', $entityMetadata)) { + + if(!array_key_exists('metadata.sign.privatekey', $entityMetadata) + || !array_key_exists('metadata.sign.certificate', $entityMetadata)) { + + throw new Exception('Missing either the "metadata.sign.privatekey" or the' . + ' "metadata.sign.certificate" configuration option in the metadata for' . + ' the ' . $type . ' "' . $entityMetadata['entityid'] . '". If one of' . + ' these options is specified, then the other must also be specified.'); + } + return array( + 'privatekey' => $entityMetadata['metadata.sign.privatekey'], + 'certificate' => $entityMetadata['metadata.sign.certificate'] + ); + } + + /* Then we look for default values in the global configuration. */ + $privatekey = $config->getValue('metadata.sign.privatekey', NULL); + $certificate = $config->getValue('metadata.sign.certificate', NULL); + if($privatekey !== NULL || $certificate !== NULL) { + if($privatekey === NULL || $certificate === NULL) { + throw new Exception('Missing either the "metadata.sign.privatekey" or the' . + ' "metadata.sign.certificate" configuration option in the global' . + ' configuration. If one of these options is specified, then the other'. + ' must also be specified.'); + } + return array('privatekey' => $privatekey, 'certificate' => $certificate); + } + + /* As a last resort we attempt to use the privatekey and certificate option from the metadata. */ + if(array_key_exists('privatekey', $entityMetadata) + || array_key_exists('certificate', $entityMetadata)) { + + if(!array_key_exists('privatekey', $entityMetadata) + || !array_key_exists('certificate', $entityMetadata)) { + throw new Exception('Both the "privatekey" and the "certificate" option must' . + ' be set in the metadata for the ' . $type .' "' . + $entityMetadata['entityid'] . '" before it is possible to sign metadata' . + ' from this entity.'); + } + + return array( + 'privatekey' => $entityMetadata['privatekey'], + 'certificate' => $entityMetadata['certificate'] + ); + + } + + throw new Exception('Could not find what key & certificate should be used to sign the metadata' . + ' for the ' . $type . ' "' . $entityMetadata['entityid'] . '".'); + } + + + /** + * Determine whether metadata signing is enabled for the given metadata. + * + * @param $config Our SimpleSAML_Configuration instance. + * @param $entityMetadata The metadata of the entity. + * @param $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'. + */ + private static function isMetadataSigningEnabled($config, $entityMetadata, $type) { + + /* First check the metadata for the entity. */ + if(array_key_exists('metadata.sign.enable', $entityMetadata)) { + if(!is_bool($entityMetadata['metadata.sign.enable'])) { + throw new Exception( + 'Invalid value for the "metadata.sign.enable" configuration option for' . + ' the ' . $type .' "' . $entityMetadata['entityid'] . '". This option' . + ' should be a boolean.'); + } + + return $entityMetadata['metadata.sign.enable']; + } + + $enabled = $config->getValue('metadata.sign.enable', FALSE); + if(!is_bool($enabled)) { + throw new Exception('Invalid value for the "metadata.sign.enable" configuration option.' . + ' This option should be a boolean.'); + } + + return $enabled; + } + + + /** + * Signs the given metadata if metadata signing is enabled. + * + * @param $metadataString A string with the metadata. + * @param $entityMetadata The metadata of the entity. + * @param $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'. + * @return The $metadataString with the signature embedded. + */ + public static function sign($metadataString, $entityMetadata, $type) { + + $config = SimpleSAML_Configuration::getInstance(); + + /* Check if metadata signing is enabled. */ + if (!self::isMetadataSigningEnabled($config, $entityMetadata, $type)) { + return $metadataString; + } + + + /* Find the key & certificate which should be used to sign the metadata. */ + + $keyCertFiles = self::findKeyCert($config, $entityMetadata, $type); + + $keyFile = $config->getPathValue('certdir') . $keyCertFiles['privatekey']; + if (!file_exists($keyFile)) { + throw new Exception('Could not find private key file [' . $keyFile . '], which is needed to sign the metadata'); + } + $keyData = file_get_contents($keyFile); + + $certFile = $config->getPathValue('certdir') . $keyCertFiles['certificate']; + if (!file_exists($certFile)) { + throw new Exception('Could not find certificate file [' . $certFile . '], which is needed to sign the metadata'); + } + $certData = file_get_contents($certFile); + + + /* Convert the metadata to a DOM tree. */ + $xml = new DOMDocument(); + if(!$xml->loadXML($metadataString)) { + throw new Exception('Error parsing self-generated metadata.'); + } + + /* Load the private key. */ + $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'private')); + $objKey->loadKey($keyData, FALSE); + + /* Get the EntityDescriptor node we should sign. */ + $rootNode = $xml->firstChild; + + /* Sign the metadata with our private key. */ + $objXMLSecDSig = new XMLSecurityDSig(); + $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); + + $objXMLSecDSig->addReferenceList(array($rootNode), XMLSecurityDSig::SHA1, + array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N), + array('id_name' => 'ID')); + + $objXMLSecDSig->sign($objKey); + + /* Add the certificate to the signature. */ + $objXMLSecDSig->add509Cert($certData, true); + + /* Add the signature to the metadata. */ + $objXMLSecDSig->insertSignature($rootNode, $rootNode->firstChild); + + /* Return the DOM tree as a string. */ + return $xml->saveXML(); + } + +} + +?> \ No newline at end of file diff --git a/www/admin/metadata.php b/www/admin/metadata.php index d4177f772ce6edb02b61d913560770cac3cb6393..2a0337b0a8959407653230822b5dfe5a583c9244 100644 --- a/www/admin/metadata.php +++ b/www/admin/metadata.php @@ -34,7 +34,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'host'), - array('request.signing','certificate','privatekey', 'NameIDFormat', 'ForceAuthn', 'AuthnContextClassRef', 'SPNameQualifier', 'attributemap', 'attributealter', 'attributes') + array('request.signing','certificate','privatekey', 'NameIDFormat', 'ForceAuthn', 'AuthnContextClassRef', 'SPNameQualifier', 'attributemap', 'attributealter', 'attributes', 'metadata.sign.enable', 'metadata.sign.privatekey', 'metadata.sign.certificate') ); } $et->data['metadata.saml20-sp-hosted'] = $results; @@ -57,7 +57,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'host', 'privatekey', 'certificate', 'auth'), - array('requireconsent','request.signing', 'authority', 'attributemap', 'attributealter', 'userid.attribute') + array('requireconsent','request.signing', 'authority', 'attributemap', 'attributealter', 'userid.attribute', 'metadata.sign.enable', 'metadata.sign.privatekey', 'metadata.sign.certificate') ); } $et->data['metadata.saml20-idp-hosted'] = $results; @@ -94,7 +94,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'SingleSignOnService', 'certFingerprint'), - array('name', 'description', 'base64attributes') + array('name', 'description', 'base64attributes', 'metadata.sign.enable', 'metadata.sign.privatekey', 'metadata.sign.certificate') ); } $et->data['metadata.shib13-idp-remote'] = $results; @@ -117,7 +117,7 @@ try { foreach ($metalist AS $entityid => $mentry) { $results[$entityid] = SimpleSAML_Utilities::checkAssocArrayRules($mentry, array('entityid', 'AssertionConsumerService'), - array('base64attributes', 'audience', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description') + array('base64attributes', 'audience', 'attributemap', 'attributealter', 'simplesaml.attributes', 'attributes', 'name', 'description', 'metadata.sign.enable', 'metadata.sign.privatekey', 'metadata.sign.certificate') ); } $et->data['metadata.shib13-sp-remote'] = $results; diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php index 1ffa8d010d1a49de7e8a7cbad753a3351e29087c..b4c84f0691900af87b48226c8d6d08a0600a03a1 100644 --- a/www/saml2/idp/metadata.php +++ b/www/saml2/idp/metadata.php @@ -5,10 +5,9 @@ require_once('../../_include.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php'); +require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/Signer.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php'); -require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'xmlseclibs.php'); - /* Load simpleSAMLphp, configuration and metadata */ $config = SimpleSAML_Configuration::getInstance(); $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); @@ -91,8 +90,10 @@ try { </IDPSSODescriptor> </EntityDescriptor>'; - - + + /* Sign the metadata if enabled. */ + $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $idpmeta, 'SAML 2 IdP'); + if (array_key_exists('output', $_GET) && $_GET['output'] == 'xhtml') { $defaultidp = $config->getValue('default-saml20-idp'); diff --git a/www/saml2/sp/metadata.php b/www/saml2/sp/metadata.php index 6f8458d69ec6347aecbc4464c4fc8335324d50b6..e71a0d30c3287fe212312510b637fbcf45f5e467 100644 --- a/www/saml2/sp/metadata.php +++ b/www/saml2/sp/metadata.php @@ -5,6 +5,7 @@ require_once('../../_include.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php'); +require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/Signer.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php'); /* Load simpleSAMLphp, configuration and metadata */ @@ -83,6 +84,9 @@ try { </EntityDescriptor>'; + /* Sign the metadata if enabled. */ + $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $spmeta, 'SAML 2 SP'); + if (array_key_exists('output', $_GET) && $_GET['output'] == 'xhtml') { $defaultidp = $config->getValue('default-saml20-idp'); diff --git a/www/shib13/idp/metadata.php b/www/shib13/idp/metadata.php index a58f17a3cfc7fd8d11a79def08de3cac3631dd46..1ad41aa6e783e2b6244f86429b26911d5258a52d 100644 --- a/www/shib13/idp/metadata.php +++ b/www/shib13/idp/metadata.php @@ -5,10 +5,9 @@ require_once('../../_include.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php'); +require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/Signer.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php'); -require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'xmlseclibs.php'); - /* Load simpleSAMLphp, configuration and metadata */ $config = SimpleSAML_Configuration::getInstance(); $metadata = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); @@ -76,6 +75,9 @@ try { </ContactPerson> </EntityDescriptor>'; + + /* Sign the metadata if enabled. */ + $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $idpmeta, 'Shib 1.3 IdP'); if (array_key_exists('output', $_GET) && $_GET['output'] == 'xhtml') { diff --git a/www/shib13/sp/metadata.php b/www/shib13/sp/metadata.php index 58e509321924eccda8c66f55718bc4c8e4ae6e15..b9152e8d029d86bb97da9127afbe0e0f6b8038a6 100644 --- a/www/shib13/sp/metadata.php +++ b/www/shib13/sp/metadata.php @@ -5,6 +5,7 @@ require_once('../../_include.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Utilities.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Session.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/MetaDataStorageHandler.php'); +require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/Metadata/Signer.php'); require_once((isset($SIMPLESAML_INCPREFIX)?$SIMPLESAML_INCPREFIX:'') . 'SimpleSAML/XHTML/Template.php'); /* Load simpleSAMLphp, configuration and metadata */ @@ -55,6 +56,9 @@ try { </EntityDescriptor>'; + /* Sign the metadata if enabled. */ + $metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $spmeta, 'Shib 1.3 SP'); + if (array_key_exists('output', $_GET) && $_GET['output'] == 'xhtml') { $defaultidp = $config->getValue('default-shib13-idp');