1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
<?php
/**
* This class implements a helper function for signing of metadata.
*
* @author Olav Morken, UNINETT AS.
* @package simpleSAMLphp
*/
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', 'certificate', and optionally 'privatekey_pass'.
*/
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.');
}
$ret = array(
'privatekey' => $entityMetadata['metadata.sign.privatekey'],
'certificate' => $entityMetadata['metadata.sign.certificate']
);
if(array_key_exists('metadata.sign.privatekey_pass', $entityMetadata)) {
$ret['privatekey_pass'] = $entityMetadata['metadata.sign.privatekey_pass'];
}
return $ret;
}
/* Then we look for default values in the global configuration. */
$privatekey = $config->getString('metadata.sign.privatekey', NULL);
$certificate = $config->getString('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.');
}
$ret = array('privatekey' => $privatekey, 'certificate' => $certificate);
$privatekey_pass = $config->getString('metadata.sign.privatekey_pass', NULL);
if($privatekey_pass !== NULL) {
$ret['privatekey_pass'] = $privatekey_pass;
}
return $ret;
}
/* 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.');
}
$ret = array(
'privatekey' => $entityMetadata['privatekey'],
'certificate' => $entityMetadata['certificate']
);
if(array_key_exists('privatekey_pass', $entityMetadata)) {
$ret['privatekey_pass'] = $entityMetadata['privatekey_pass'];
}
return $ret;
}
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->getBoolean('metadata.sign.enable', FALSE);
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 = SimpleSAML_Utilities::resolveCert($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 = SimpleSAML_Utilities::resolveCert($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'));
if(array_key_exists('privatekey_pass', $keyCertFiles)) {
$objKey->passphrase = $keyCertFiles['privatekey_pass'];
}
$objKey->loadKey($keyData, FALSE);
/* Get the EntityDescriptor node we should sign. */
$rootNode = $xml->firstChild;
/* Sign the metadata with our private key. */
if ($type == 'ADFS IdP') {
$objXMLSecDSig = new sspmod_adfs_XMLSecurityDSig($metadataString);
} else {
$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();
}
}
?>
|