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

saml: Add support for key rollover.

git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@2617 44740490-163a-0410-bde0-09ae8108e29a
parent ba370312
No related branches found
No related tags found
No related merge requests found
...@@ -27,6 +27,7 @@ SimpleSAMLphp Documentation ...@@ -27,6 +27,7 @@ SimpleSAMLphp Documentation
* [Theming simpleSAMLphp](simplesamlphp-theming) * [Theming simpleSAMLphp](simplesamlphp-theming)
* [simpleSAMLphp Modules](simplesamlphp-modules) - how to create own customized modules * [simpleSAMLphp Modules](simplesamlphp-modules) - how to create own customized modules
* [Installing third party modules with the pack.php tool](pack) * [Installing third party modules with the pack.php tool](pack)
* [Key rollover](./saml:keyrollover)
Documentation on specific simpleSAMLphp modules: Documentation on specific simpleSAMLphp modules:
......
Key rollover with simpleSAMLphp
===============================
This document gives a quick guide to doing key rollover with a simpleSAMLphp service provider or identity provider.
1. Create the new key and certificate
-------------------------------------
First you must create the new key that you are going to use.
To create a self signed certificate, you may use the following command:
cd cert
openssl req -new -x509 -days 3652 -nodes -out new.crt -keyout new.pem
2. Add the new key to simpleSAMLphp
-----------------------------------
Where you add the new key depends on whether you are doing key rollover for a service provider or an identity provider.
If you are doing key rollover for a service provider, the new key must be added to `config/authsources.php`.
To do key rollover for an identity provider, you must add the new key to `metadata/saml20-idp-hosted.php` and/or `metadata/shib13-idp-hosted.php`.
The new certificate and key is added to the configuration with the prefix `new_`:
Example:
'default-sp' => array(
'saml:SP',
'privatekey' => 'old.pem',
'certificate' => 'old.crt',
'new_privatekey' => 'new.pem',
'new_certificate' => 'new.crt',
),
When the new key is added, simpleSAMLphp will attempt to use both the new key and the old key for decryption of messages, but only the old key will be used for signing messages.
The metadata will be updated to list the new key for signing and encryption, and the old key will no longer listed as available for encryption.
This ensures that both those entities that use your old metadata and those that use your new metadata will be able to send and receive messages from you.
3. Distribute your new metadata
-------------------------------
Now, you need to make sure that all your peers are using your new metadata.
How you go about this depends on how your peers have added your metadata.
4. Remove the old key
---------------------
Once you are certain that all your peers are using the new metadata, you must remove the old key.
Replace the existing `privatekey` and `certificate` options in your configuration with the `new_privatekey` and `new_certificate` options.
Example:
'default-sp' => array(
'saml:SP',
'privatekey' => 'new.pem',
'certificate' => 'new.crt',
),
This will cause your old key to be removed from your metadata.
5. Distribute your final metadata
---------------------------------
Now you need to update the metadata of all your peers again, so that your old signing certificate is removed.
...@@ -260,35 +260,49 @@ class sspmod_saml_Message { ...@@ -260,35 +260,49 @@ class sspmod_saml_Message {
/** /**
* Retrieve the decryption key from metadata. * Retrieve the decryption keys from metadata.
* *
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP). * @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP). * @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @return XMLSecurityKey The decryption key. * @return array Array of decryption keys.
*/ */
public static function getDecryptionKey(SimpleSAML_Configuration $srcMetadata, public static function getDecryptionKeys(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata) { SimpleSAML_Configuration $dstMetadata) {
$sharedKey = $srcMetadata->getString('sharedkey', NULL); $sharedKey = $srcMetadata->getString('sharedkey', NULL);
if ($sharedKey !== NULL) { if ($sharedKey !== NULL) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC); $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey); $key->loadKey($sharedKey);
} else { return array($key);
/* Find the private key we should use to decrypt messages to this SP. */ }
$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, TRUE);
if (!array_key_exists('PEM', $keyArray)) { $keys = array();
throw new Exception('Unable to locate key we should use to decrypt the message.');
} /* Load the new private key if it exists. */
$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, FALSE, 'new_');
if ($keyArray !== NULL) {
assert('isset($keyArray["PEM"])');
/* Extract the public key from the certificate for encryption. */
$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private')); $key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
if (array_key_exists('password', $keyArray)) { if (array_key_exists('password', $keyArray)) {
$key->passphrase = $keyArray['password']; $key->passphrase = $keyArray['password'];
} }
$key->loadKey($keyArray['PEM']); $key->loadKey($keyArray['PEM']);
$keys[] = $key;
} }
return $key; /* Find the existing private key. */
$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, TRUE);
assert('isset($keyArray["PEM"])');
$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
if (array_key_exists('password', $keyArray)) {
$key->passphrase = $keyArray['password'];
}
$key->loadKey($keyArray['PEM']);
$keys[] = $key;
return $keys;
} }
...@@ -322,12 +336,23 @@ class sspmod_saml_Message { ...@@ -322,12 +336,23 @@ class sspmod_saml_Message {
} }
try { try {
$key = self::getDecryptionKey($srcMetadata, $dstMetadata); $keys = self::getDecryptionKeys($srcMetadata, $dstMetadata);
} catch (Exception $e) { } catch (Exception $e) {
throw new SimpleSAML_Error_Exception('Error decrypting assertion: ' . $e->getMessage()); throw new SimpleSAML_Error_Exception('Error decrypting assertion: ' . $e->getMessage());
} }
return $assertion->getAssertion($key); $lastException = NULL;
foreach ($keys as $i => $key) {
try {
$ret = $assertion->getAssertion($key);
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.');
return $ret;
} catch (Exception $e) {
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage());
$lastException = $e;
}
}
throw $lastException;
} }
...@@ -605,12 +630,26 @@ class sspmod_saml_Message { ...@@ -605,12 +630,26 @@ class sspmod_saml_Message {
/* Decrypt the NameID element if it is encrypted. */ /* Decrypt the NameID element if it is encrypted. */
if ($assertion->isNameIdEncrypted()) { if ($assertion->isNameIdEncrypted()) {
try { try {
$key = self::getDecryptionKey($idpMetadata, $spMetadata); $key = self::getDecryptionKeys($idpMetadata, $spMetadata);
} catch (Exception $e) { } catch (Exception $e) {
throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage());
} }
$assertion->decryptNameId($key); $lastException = NULL;
foreach ($keys as $i => $key) {
try {
$assertion->decryptNameId($key);
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.');
$lastException = NULL;
break;
} catch (Exception $e) {
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage());
$lastException = $e;
}
}
if ($lastException !== NULL) {
throw $lastException;
}
} }
return $assertion; return $assertion;
......
...@@ -72,8 +72,11 @@ $acs->Binding = 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01'; ...@@ -72,8 +72,11 @@ $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;
$certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig); $keys = array();
$certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_');
if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) { if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
$hasNewCert = TRUE;
$certData = $certInfo['certData']; $certData = $certInfo['certData'];
$kd = SAML2_Utils::createKeyDescriptor($certData); $kd = SAML2_Utils::createKeyDescriptor($certData);
$kd->use = 'signing'; $kd->use = 'signing';
...@@ -82,6 +85,39 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) { ...@@ -82,6 +85,39 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
$kd = SAML2_Utils::createKeyDescriptor($certData); $kd = SAML2_Utils::createKeyDescriptor($certData);
$kd->use = 'encryption'; $kd->use = 'encryption';
$sp->KeyDescriptor[] = $kd; $sp->KeyDescriptor[] = $kd;
$keys[] = array(
'type' => 'X509Certificate',
'signing' => TRUE,
'encryption' => TRUE,
'X509Certificate' => $certInfo['certData'],
);
} else {
$hasNewCert = FALSE;
}
$certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig);
if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
$certData = $certInfo['certData'];
$kd = SAML2_Utils::createKeyDescriptor($certData);
$kd->use = 'signing';
$sp->KeyDescriptor[] = $kd;
if (!$hasNewCert) {
/* Don't include the old certificate for encryption when we have a newer certificate. */
$kd = SAML2_Utils::createKeyDescriptor($certData);
$kd->use = 'encryption';
$sp->KeyDescriptor[] = $kd;
}
$keys[] = array(
'type' => 'X509Certificate',
'signing' => TRUE,
'encryption' => ($hasNewCert ? FALSE : TRUE),
'X509Certificate' => $certInfo['certData'],
);
} else { } else {
$certData = NULL; $certData = NULL;
} }
...@@ -168,8 +204,10 @@ $xml = $ed->toXML(); ...@@ -168,8 +204,10 @@ $xml = $ed->toXML();
SimpleSAML_Utilities::formatDOMElement($xml); SimpleSAML_Utilities::formatDOMElement($xml);
$xml = $xml->ownerDocument->saveXML($xml); $xml = $xml->ownerDocument->saveXML($xml);
if ($certData !== NULL) { if (count($keys) === 1) {
$metaArray20['certData'] = $certData; $metaArray20['certData'] = $keys[0]['X509Certificate'];
} elseif (count($keys) > 1) {
$metaArray20['keys'] = $keys;
} }
if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') { if (array_key_exists('output', $_REQUEST) && $_REQUEST['output'] == 'xhtml') {
......
...@@ -64,11 +64,23 @@ if ($message instanceof SAML2_LogoutResponse) { ...@@ -64,11 +64,23 @@ if ($message instanceof SAML2_LogoutResponse) {
if ($message->isNameIdEncrypted()) { if ($message->isNameIdEncrypted()) {
try { try {
$key = sspmod_saml_Message::getDecryptionKey($idpMetadata, $spMetadata); $keys = sspmod_saml_Message::getDecryptionKeys($srcMetadata, $dstMetadata);
} catch (Exception $e) { } catch (Exception $e) {
throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage());
} }
$message->decryptNameId($key);
$lastException = NULL;
foreach ($keys as $i => $key) {
try {
$ret = $assertion->getAssertion($key);
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.');
return $ret;
} catch (Exception $e) {
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage());
$lastException = $e;
}
}
throw $lastException;
} }
$nameId = $message->getNameId(); $nameId = $message->getNameId();
......
...@@ -19,11 +19,37 @@ try { ...@@ -19,11 +19,37 @@ try {
$idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted'); $idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $metadata->getMetaDataCurrentEntityID('saml20-idp-hosted');
$idpmeta = $metadata->getMetaDataConfig($idpentityid, 'saml20-idp-hosted'); $idpmeta = $metadata->getMetaDataConfig($idpentityid, 'saml20-idp-hosted');
$keys = array();
$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, FALSE, 'new_');
if ($certInfo !== NULL) {
$keys[] = array(
'type' => 'X509Certificate',
'signing' => TRUE,
'encryption' => TRUE,
'X509Certificate' => $certInfo['certData'],
);
$hasNewCert = TRUE;
} else {
$hasNewCert = FALSE;
}
$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE); $certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE);
$certFingerprint = $certInfo['certFingerprint']; $keys[] = array(
if (count($certFingerprint) === 1) { 'type' => 'X509Certificate',
/* Only one valid certificate. */ 'signing' => TRUE,
$certFingerprint = $certFingerprint[0]; 'encryption' => ($hasNewCert ? FALSE : TRUE),
'X509Certificate' => $certInfo['certData'],
);
if ($idpmeta->hasValue('https.certificate')) {
$httpsCert = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE, 'https.');
assert('isset($httpsCert["certData"])');
$keys[] = array(
'type' => 'X509Certificate',
'signing' => TRUE,
'encryption' => FALSE,
'X509Certificate' => $httpsCert['certData'],
);
} }
$metaArray = array( $metaArray = array(
...@@ -31,9 +57,14 @@ try { ...@@ -31,9 +57,14 @@ try {
'entityid' => $idpentityid, 'entityid' => $idpentityid,
'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'), 'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted'),
'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'), 'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted'),
'certFingerprint' => $certFingerprint,
); );
if (count($keys) === 1) {
$metaArray['certData'] = $keys[0]['X509Certificate'];
} else {
$metaArray['keys'] = $keys;
}
if ($idpmeta->getBoolean('saml20.sendartifact', FALSE)) { if ($idpmeta->getBoolean('saml20.sendartifact', FALSE)) {
/* Artifact sending enabled. */ /* Artifact sending enabled. */
$metaArray['ArtifactResolutionService'][] = array( $metaArray['ArtifactResolutionService'][] = array(
...@@ -59,16 +90,8 @@ try { ...@@ -59,16 +90,8 @@ try {
$metaArray['scope'] = $idpmeta->getArray('scope'); $metaArray['scope'] = $idpmeta->getArray('scope');
} }
if ($idpmeta->hasValue('https.certificate')) {
$httpsCert = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE, 'https.');
assert('isset($httpsCert["certData"])');
$metaArray['https.certData'] = $httpsCert['certData'];
}
$metaflat = '$metadata[' . var_export($idpentityid, TRUE) . '] = ' . var_export($metaArray, TRUE) . ';'; $metaflat = '$metadata[' . var_export($idpentityid, TRUE) . '] = ' . var_export($metaArray, TRUE) . ';';
$metaArray['certData'] = $certInfo['certData'];
$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid); $metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid);
$metaBuilder->addMetadataIdP20($metaArray); $metaBuilder->addMetadataIdP20($metaArray);
$metaBuilder->addOrganizationInfo($metaArray); $metaBuilder->addOrganizationInfo($metaArray);
......
...@@ -20,20 +20,37 @@ try { ...@@ -20,20 +20,37 @@ try {
$idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $metadata->getMetaDataCurrentEntityID('shib13-idp-hosted'); $idpentityid = isset($_GET['idpentityid']) ? $_GET['idpentityid'] : $metadata->getMetaDataCurrentEntityID('shib13-idp-hosted');
$idpmeta = $metadata->getMetaDataConfig($idpentityid, 'shib13-idp-hosted'); $idpmeta = $metadata->getMetaDataConfig($idpentityid, 'shib13-idp-hosted');
$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE); $keys = array();
$certFingerprint = $certInfo['certFingerprint']; $certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, FALSE, 'new_');
if (count($certFingerprint) === 1) { if ($certInfo !== NULL) {
/* Only one valid certificate. */ $keys[] = array(
$certFingerprint = $certFingerprint[0]; 'type' => 'X509Certificate',
'signing' => TRUE,
'encryption' => FALSE,
'X509Certificate' => $certInfo['certData'],
);
} }
$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE);
$keys[] = array(
'type' => 'X509Certificate',
'signing' => TRUE,
'encryption' => FALSE,
'X509Certificate' => $certInfo['certData'],
);
$metaArray = array( $metaArray = array(
'metadata-set' => 'shib13-idp-remote', 'metadata-set' => 'shib13-idp-remote',
'entityid' => $idpentityid, 'entityid' => $idpentityid,
'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted'), 'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted'),
'certFingerprint' => $certFingerprint,
); );
if (count($keys) === 1) {
$metaArray['certData'] = $keys[0]['X509Certificate'];
} else {
$metaArray['keys'] = $keys;
}
$metaArray['NameIDFormat'] = $idpmeta->getString('NameIDFormat', 'urn:mace:shibboleth:1.0:nameIdentifier'); $metaArray['NameIDFormat'] = $idpmeta->getString('NameIDFormat', 'urn:mace:shibboleth:1.0:nameIdentifier');
if ($idpmeta->hasValue('OrganizationName')) { if ($idpmeta->hasValue('OrganizationName')) {
...@@ -49,7 +66,6 @@ try { ...@@ -49,7 +66,6 @@ try {
$metaflat = '$metadata[' . var_export($idpentityid, TRUE) . '] = ' . var_export($metaArray, TRUE) . ';'; $metaflat = '$metadata[' . var_export($idpentityid, TRUE) . '] = ' . var_export($metaArray, TRUE) . ';';
$metaArray['certData'] = $certInfo['certData'];
$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid); $metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid);
$metaBuilder->addMetadataIdP11($metaArray); $metaBuilder->addMetadataIdP11($metaArray);
$metaBuilder->addOrganizationInfo($metaArray); $metaBuilder->addOrganizationInfo($metaArray);
......
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