diff --git a/lib/SimpleSAML/Configuration.php b/lib/SimpleSAML/Configuration.php index bc55b38eca8f32993665fbd4ca0316f4fe56530d..2f07bd17e370ff13951a02dce6aaf367d27f3739 100644 --- a/lib/SimpleSAML/Configuration.php +++ b/lib/SimpleSAML/Configuration.php @@ -1153,8 +1153,10 @@ class Configuration implements Utils\ClearableState } elseif ($this->hasValue($prefix . 'certData')) { $certData = $this->getString($prefix . 'certData'); $certData = preg_replace('/\s+/', '', $certData); + $keyName = $this->getString($prefix . 'key_name', null); return [ [ + 'name' => $keyName, 'encryption' => true, 'signing' => true, 'type' => 'X509Certificate', @@ -1182,9 +1184,11 @@ class Configuration implements Utils\ClearableState ); } $certData = preg_replace('/\s+/', '', $matches[1]); + $keyName = $this->getString($prefix . 'key_name', null); return [ [ + 'name' => $keyName, 'encryption' => true, 'signing' => true, 'type' => 'X509Certificate', diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php index 2c6d9d3055af585c704deab169ac13636c3aaa28..91f39443aba43db0560f9841d32da09b468ad29b 100644 --- a/lib/SimpleSAML/Metadata/SAMLBuilder.php +++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php @@ -681,12 +681,13 @@ class SAMLBuilder * @param \SAML2\XML\md\RoleDescriptor $rd The RoleDescriptor the certificate should be added to. * @param string $use The value of the 'use' attribute. * @param string $x509data The certificate data. + * @param string|null $keyName The name of the key. Should be valid for usage in an ID attribute, e.g. not start with a digit. */ - private function addX509KeyDescriptor(RoleDescriptor $rd, string $use, string $x509data): void + private function addX509KeyDescriptor(RoleDescriptor $rd, string $use, string $x509data, ?string $keyName = null): void { Assert::oneOf($use, ['encryption', 'signing']); - $keyDescriptor = \SAML2\Utils::createKeyDescriptor($x509data); + $keyDescriptor = \SAML2\Utils::createKeyDescriptor($x509data, $keyName); $keyDescriptor->setUse($use); $rd->addKeyDescriptor($keyDescriptor); } @@ -708,10 +709,10 @@ class SAMLBuilder continue; } if (!isset($key['signing']) || $key['signing'] === true) { - $this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate']); + $this->addX509KeyDescriptor($rd, 'signing', $key['X509Certificate'], $key['name'] ?? null); } if (!isset($key['encryption']) || $key['encryption'] === true) { - $this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate']); + $this->addX509KeyDescriptor($rd, 'encryption', $key['X509Certificate'], $key['name'] ?? null); } } diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php index bf5880a1922709cf2d45e73054712a6598324b35..0be2ec3ee136158723abd9f7977f952124cb34d2 100644 --- a/lib/SimpleSAML/Utils/Crypto.php +++ b/lib/SimpleSAML/Utils/Crypto.php @@ -274,6 +274,7 @@ class Crypto chunk_split($certData, 64) . "-----END CERTIFICATE-----\n"; return [ + 'name' => $key['name'] ?? null, 'certData' => $certData, 'PEM' => $pem, ]; diff --git a/modules/saml/docs/sp.md b/modules/saml/docs/sp.md index 499b46231535f171f5c823b40fe011fadc91377c..b6d174eec1568f3d31906383756d218f24580f56 100644 --- a/modules/saml/docs/sp.md +++ b/modules/saml/docs/sp.md @@ -248,6 +248,9 @@ Options `IsPassive` : IsPassive allows you to enable passive authentication by default for this SP. +`key_name` +: The name of the certificate. It is possible the IDP requires your certificate to have a name. + If provided, it will be exposed in the SAML 2.0 metadata as `KeyName` inside the `KeyDescriptor`. This also requires a certificate to be provided. `name` : The name of this SP. diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php index 867bf732782cb53dffb9c7ec0d3812d8a320d675..5e53b1eacab6f5eb9954ca11ef0fc917444469bd 100644 --- a/modules/saml/lib/Auth/Source/SP.php +++ b/modules/saml/lib/Auth/Source/SP.php @@ -224,7 +224,7 @@ class SP extends \SimpleSAML\Auth\Source 'prefix' => 'new_' ] ), - 'name' => 'sp', + 'name' => $certInfo['name'] ?? null, ]; } @@ -244,7 +244,7 @@ class SP extends \SimpleSAML\Auth\Source 'prefix' => '' ] ), - 'name' => 'sp', + 'name' => $certInfo['name'] ?? null, ]; } diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index 3b9394318b206e5365311879db95b363ea716848..d0cc00008e406db388859a868106595ff5b73dba 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -787,6 +787,7 @@ class SAML2 $hasNewCert = false; if ($certInfo !== null) { $keys[] = [ + 'name' => $certInfo['name'] ?? null, 'type' => 'X509Certificate', 'signing' => true, 'encryption' => true, @@ -799,6 +800,7 @@ class SAML2 /** @var array $certInfo */ $certInfo = $cryptoUtils->loadPublicKey($config, true); $keys[] = [ + 'name' => $certInfo['name'] ?? null, 'type' => 'X509Certificate', 'signing' => true, 'encryption' => $hasNewCert === false, @@ -810,6 +812,7 @@ class SAML2 /** @var array $httpsCert */ $httpsCert = $cryptoUtils->loadPublicKey($config, true, 'https.'); $keys[] = [ + 'name' => $httpsCert['name'] ?? null, 'type' => 'X509Certificate', 'signing' => true, 'encryption' => false, diff --git a/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php b/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php index 844a6325b4ec45b4cbe6a8282d6f6c04563b882c..cfb0f173a9cfc8c4a8f65812fa06020be67a3907 100644 --- a/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php +++ b/tests/lib/SimpleSAML/Metadata/SAMLBuilderTest.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace SimpleSAML\Test\Metadata; use PHPUnit\Framework\TestCase; +use SimpleSAML\Configuration; use SimpleSAML\Metadata\SAMLBuilder; +use SimpleSAML\Module\saml\Auth\Source\SP; /** * Class SAMLBuilderTest @@ -14,6 +16,20 @@ use SimpleSAML\Metadata\SAMLBuilder; */ class SAMLBuilderTest extends TestCase { + /** + */ + protected function setUp(): void + { + Configuration::loadFromArray([], '', 'simplesaml'); + } + + /** + */ + protected function tearDown(): void + { + Configuration::clearInternalState(); + } + /** * Test the requested attributes are valued correctly. */ @@ -370,4 +386,58 @@ class SAMLBuilderTest extends TestCase $this->assertEquals(1, $sn->length); $this->assertEquals("Doe", $sn->item(0)->nodeValue); } + + /* + * Test certificate data. + */ + public function testCertificateData(): void + { + $info = ['AuthId' => 'default-sp']; + $metadata = [ + 'certificate' => __DIR__ . '/test-metadata/www.example.com.cert', + 'privatekey' => __DIR__ . '/test-metadata/www.example.com.key', + ]; + + // Without a key name, it should have KeyDescriptors but no KeyNames. + $samlBuilder = new SAMLBuilder('default-sp'); + $sp = new SP($info, $metadata); + $samlBuilder->addMetadataSP20($sp->getHostedMetadata()); + $spDesc = $samlBuilder->getEntityDescriptor(); + + $this->assertEquals(2, $spDesc->getElementsByTagName("KeyDescriptor")->length); + $this->assertEquals(0, $spDesc->getElementsByTagName("KeyName")->length); + + // Add key name. + $metadata['key_name'] = 'my-key-name'; + + // It should now also have 2 KeyNames. + $samlBuilder = new SAMLBuilder('default-sp'); + $sp = new SP($info, $metadata); + $samlBuilder->addMetadataSP20($sp->getHostedMetadata()); + $spDesc = $samlBuilder->getEntityDescriptor(); + + $this->assertEquals(2, $spDesc->getElementsByTagName("KeyDescriptor")->length); + $keyNames = $spDesc->getElementsByTagName("KeyName"); + $this->assertEquals(2, $keyNames->length); + $this->assertEquals('my-key-name', $keyNames->item(0)->textContent); + $this->assertEquals('my-key-name', $keyNames->item(1)->textContent); + + // Add rollover configuration. + $metadata['new_certificate'] = __DIR__ . '/test-metadata/www.example.com_new.cert'; + $metadata['new_privatekey'] = __DIR__ . '/test-metadata/www.example.com_new.key'; + $metadata['new_key_name'] = 'my-new-key-name'; + + // It should now have 3 KeyNames. + $samlBuilder = new SAMLBuilder('default-sp'); + $sp = new SP($info, $metadata); + $samlBuilder->addMetadataSP20($sp->getHostedMetadata()); + $spDesc = $samlBuilder->getEntityDescriptor(); + + $this->assertEquals(3, $spDesc->getElementsByTagName("KeyDescriptor")->length); + $keyNames = $spDesc->getElementsByTagName("KeyName"); + $this->assertEquals(3, $keyNames->length); + $this->assertEquals('my-new-key-name', $keyNames->item(0)->textContent); + $this->assertEquals('my-new-key-name', $keyNames->item(1)->textContent); + $this->assertEquals('my-key-name', $keyNames->item(2)->textContent); + } } diff --git a/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com.cert b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com.cert new file mode 100644 index 0000000000000000000000000000000000000000..a8ce458e6d0bcf5eebdb2625ebb06db46eb1b7c9 --- /dev/null +++ b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com.cert @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC1TCCAb2gAwIBAgIJAIFrAocUOGOjMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV +BAMTD3d3dy5leGFtcGxlLmNvbTAeFw0yMjAxMDYxMDQ0MTlaFw0zMjAxMDQxMDQ0 +MTlaMBoxGDAWBgNVBAMTD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAPLmvsD7GylHLS5Hc7k3H/IVvRDMaX3IjVaqOKOkUCKb +uN3xncK4NNbOULH0F3BVKP20Yk5icd002WZEs6Qn8MFvnTbMEZRNRljxjZCtVLOC +7NoIO+biY7APi/Mbd+r/KtYxSqiWYi3O0jysXxtR60oZS+/SNVrkJe/+yV/xUaPl +tlPp95KUkDKM9qn7QPYpNIjrhYINDujxWmclV5uG7PZZnsnXxwI55XtfMvVe8WbI +/beDxaXx08P2TZsTDRmp+R/4pBaPp0/j8xU3ASrBeR0CiA/eZYEk2c5pp0LnvVcX +4cCYP2nVQmxFj+Mlg96zNPhy7Tz3BCuiJxps5gH9mEECAwEAAaMeMBwwGgYDVR0R +BBMwEYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQAQa91+JNKt +BD94E3UhUOhquOkN64wDgDeFy14+1mclied/j/4iSe78FjxBSnmQY2F0hGalzBhC +X7WNyaV450i3al6P+YIpnE19oHjxcGJ0SY9jAn3paQJKlfEZi1V7M/gILFUMKFWW +S5n8fMTCqLUzfvhrG3VnZU4KYGj+T25/jnPtT1XIB6wFz9/ApQYfRr8QR56cXccC +D/zbb2hx4C7pLlBM7dv1NdnREoKyCW1kWvu0bajtEYc4D3VZ5kQq/R9tv3t5OjUb +MpQCoobWwXO/lxbZ1j1rOoKBxxpQWzS3qzuh+6QHcZtQEL2DESRjOWqL0ietS3A1 +A/J6/QSDrRZl +-----END CERTIFICATE----- diff --git a/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com.key b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com.key new file mode 100644 index 0000000000000000000000000000000000000000..9a20bf12993980de7b4790c4c0f044c8db1edaac --- /dev/null +++ b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA8ua+wPsbKUctLkdzuTcf8hW9EMxpfciNVqo4o6RQIpu43fGd +wrg01s5QsfQXcFUo/bRiTmJx3TTZZkSzpCfwwW+dNswRlE1GWPGNkK1Us4Ls2gg7 +5uJjsA+L8xt36v8q1jFKqJZiLc7SPKxfG1HrShlL79I1WuQl7/7JX/FRo+W2U+n3 +kpSQMoz2qftA9ik0iOuFgg0O6PFaZyVXm4bs9lmeydfHAjnle18y9V7xZsj9t4PF +pfHTw/ZNmxMNGan5H/ikFo+nT+PzFTcBKsF5HQKID95lgSTZzmmnQue9VxfhwJg/ +adVCbEWP4yWD3rM0+HLtPPcEK6InGmzmAf2YQQIDAQABAoIBAQDUb3QjSTn/Bu3/ +zKPsN8brrZF2MKCOTqk2Q5dXnywqqHCtQ1RLaVllCHnQuP8K0qAQCwPzM+wLn94G +sE1AY2IPezNPKnixcEf3IANEpiMvAHFvWsYw7oxq/Z3TV5GwZ8wqGmAGQ8fH8lsy +jzp6pVGXs7oTG5BoVqLLW9T44RAauudQfYEPa5Wbb6axPx3Be8/d8dDrpCLBX1nR +CdJBr/BM981FpoWqxq6NTocsHgEFTsv5JlewQk9Tw2F4nEgCJrRJZ3Ues7+hSegL +mo3T9fz6KXBtiGrNPEYb41Y0NbeBFiRIZvC/SFJO4Y3PqcGgKCJ/XYvmlHcB1ojL +0mOCpi9VAoGBAP5AMetC+0OvB/uRVZD/4kTAJYyXfYb5kdlAE2T9Xm3K8T8ykj4j +CXM65jtOXWvL7QekkXX329aPVPU9rOU9gzgOoyv0YgvSSZDTAbLUpq+uY4gAA5y2 +j3VuE4VSnk4Rvx4248BVAdHitvaLa/tU544y7c5X4fHDzsampLIsDTYHAoGBAPSS +j4tfACIFOy17aKrj1lnQHhOwsaJzIYzlwViQuPCYCxCkGHVcfybK0Y02Ao63B5C5 +E7B1op18cAxw1DWPmCPy18z1XO1i3EL12kZMjCFzWDySm91ldtTe2pZe1tnJRH+H +vkpggQ3ulTg06UKIUFFxhTss6wbz0DPqlAZPQ+13AoGBAKjHEpw7FbMjkOgF3Uhp +JNpAt2xx8AlWyOPv7i//Jd06eBVcy8nl1lMhCU7bQZbag5msPEeUZuIyudImxAxV +XjMrPFRkYWW5jc5O1HTTR2eeG0JfyAYTBn6MuParFp16mGVFSMEXbSLYHl7hxKfN +//zcgBKXMk0cj7o9S11fctGnAoGARWaVbyIdIopDeauMTvnqKIBDGKlKLuPmwFmu +HNiscjFi6mz2N89wkWx6PEz4OtE7R1kNekRXScM29IDL5wsBTCosDJAPt5kXEbU8 +JDiyhwd5IW8k5ZVWPB+k/YiaBSD03A+D8w0hcfeixllVW7jcuc+x09HyO33SNfk5 +2fSCPQ8CgYAdhSbWhIT1qKPn3ATzObf8jGESDKtJbxMkzuwvwNyL7oXOX5DO9Wbk +5YXtVBYGCcl4i/rXlaR778kvtWkEeHLgVAc36g61Aw9fuCDKtSqGK8bMpdFh2tJ1 +FsUzeE92WK3oTEMMvVT3QXuNFTIR4brOk+gukHP1utNzQBjefsKtoA== +-----END RSA PRIVATE KEY----- diff --git a/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com_new.cert b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com_new.cert new file mode 100644 index 0000000000000000000000000000000000000000..97d125929b5b43d26c284bb8ce55258791f299ad --- /dev/null +++ b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com_new.cert @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC1TCCAb2gAwIBAgIJAOEDanQk6EwRMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV +BAMTD3d3dy5leGFtcGxlLmNvbTAeFw0yMjAxMDYxMTI5MjRaFw0zMjAxMDQxMTI5 +MjRaMBoxGDAWBgNVBAMTD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMqNZmdRIdThsNTy1uUXafphCK2PIHeeIFXirC5MwxBq +CFTHORc1i8fKme4QNJLBw1VlkEdaBOd9rQ0Q+2XDOhsrT2cpFaX2wgVkeqkJfTB7 +OVtZx0S+85Z0VqSiTEjkoSgPF7vf7LRNSAzeK1okT1NpQ2DN4GsKOhe2JugGxbuN +Lv5lt4U/EpR0C6vaPNcqtLRAOIVY+PeEvWs3v/pz/P0Y+m1yYifDoY1meCPx1hqb +TaVK0FiRA8vNNTv0GBQX7BsFzAi5K8QcXvkVGXa8nXhtlE2Nmf8Q49IZh3EgK2AX +ZK7aF0jAst822nM+G6WB0KTmTPMixkk1xSDJvfWAwAECAwEAAaMeMBwwGgYDVR0R +BBMwEYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBBQUAA4IBAQClFX357axP +0qVxY0pMCT0MJj8K1zY0vDpmNTOrSOy2XpRbIik2tkMw3zHzgzPkCZV3HT7QjzsB +l0tasEpkeiEdYeDAZeBCC0WQ/hzwCKY7B7BUtg/gpjms0L8m8YJnw/WBzeRhGoen +iNOReBFDW3xoGt5lzFjT7FWH0wIhDLalGeyEh6VzTNix0xD7uFWWpf2vELBYfmPL +ErUiEfx/kLirc2JbzE7PZ/uoTYrolLlxbvvMW6WzK8PfrCFC2n6Zkd9YKbm7yZA7 +NmVD1xN1PMxTRqNXilgdT763BAJM4Etv1Rl9NWcu6ZnYjjR6Sw6GThSKv47ArwIf +ySvrIzWRtm0A +-----END CERTIFICATE----- diff --git a/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com_new.key b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com_new.key new file mode 100644 index 0000000000000000000000000000000000000000..0a61f40eef3daa32d39e87f3239f0eabb1d4e5b2 --- /dev/null +++ b/tests/lib/SimpleSAML/Metadata/test-metadata/www.example.com_new.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAyo1mZ1Eh1OGw1PLW5Rdp+mEIrY8gd54gVeKsLkzDEGoIVMc5 +FzWLx8qZ7hA0ksHDVWWQR1oE532tDRD7ZcM6GytPZykVpfbCBWR6qQl9MHs5W1nH +RL7zlnRWpKJMSOShKA8Xu9/stE1IDN4rWiRPU2lDYM3gawo6F7Ym6AbFu40u/mW3 +hT8SlHQLq9o81yq0tEA4hVj494S9aze/+nP8/Rj6bXJiJ8OhjWZ4I/HWGptNpUrQ +WJEDy801O/QYFBfsGwXMCLkrxBxe+RUZdrydeG2UTY2Z/xDj0hmHcSArYBdkrtoX +SMCy3zbacz4bpYHQpOZM8yLGSTXFIMm99YDAAQIDAQABAoIBAAnBDXFkQtDRnYZj +u12E5yGbkqNpBRM9likMpWYFZE9iC8ypW2J2vah9ZTRFq4J1ukZegbgt6ZaMQs0i +SDj6Uc4FI+m/3L8FRwqjcBS71D+Fb5mqlSIGYAyaxaFf/3RzLh+Tunzdp7R3FEUq +XcQVg4xswUXkJC6Da5DAwNbjnJoPeHrI8UzFcs6hU93kqCRu5Q1aIL/4s6US1AYF +b/Ir4UkjB4Pfb4bmVs61sAlbcScbczFSFjRQheGiGTvZHBdn+4lZNvKA8kMbk/o1 +5v/w96fN0LEYQh5GeAGNSKK5YRnjZ/YKyIgnpvSGRQxIAnJTtE63xqskrSYb0FdR +9wAYxVECgYEA9LIlpBH2DeEBEjPnTWthPDg5je5nm25ooKvXaGgaY6uwwV9Pw0BI +MTT0xFZdAmZCPbmasDJPhsLwqfwUCOuc1K81K6XtLZFaWb6bQfZFCoMBLPXuc9gL +haMsygkZazSbTfWW+/JngYBMUC0+OjyaY41nZW+vIwTbxGzxd1zyUu8CgYEA0+jZ +ihBtEVk86U8GPOD+4uNFcP1a2K/YNfyUvsg5hXGA1DyeSgfG5l1FWlRGbZ3pQsxv +mKd+oiw6usmUygGKGju71kT0fQ7q+g05LQKUKMwY+hZT8BYZ8sjwdLnznyJb0Svm +GiAmAAnO1VdvRiCy+WMWo8npt85+d9meqGqAXA8CgYEAk7hoWOAu9rn69441+Nr2 +XHBk7nYaPg8tQrH63KDcLYecsWBkuq635lzd1xl8FNK+8px18iCtOeG9gCEZxzjV ++N+87ZjB0lyJetxCxlNx4qKrtwTQ60Zlzktv4pgTrFCZ4Tp956OzMM7PQyfNBUNI +wQjAftApnq50LeTG8RQ/hikCgYEAxz9YU+Wv97D1gdWI4vMXFdRl9aByq+1jGRfd +8CipVRxs6qH4n1kCnpWyYQV+lxD0Q5efkmRiwC9gJULmwK2D4biqnASH8ZJ2RBjs +2rJjBp0pGvSlhcfyLALdfJNfSxBuTpW9LHFv6XdPX+9vM/wI7E5L+kMem3HwHdaj +xG0nNecCgYEA3+fQzyc0UgmQn/DU1G3RrZLo0r4GL20PqNzB2rgG07bQMZKLGHhZ +7UYHmvuaOa7bX1wY8YPcu1jMxjfH1gLqwE0/NnMnuBef4ACkunTzk8Zky9mJEx3Q +2CM7KMQqagBjgIQDzQmpwEjtSI39lJku307ld2HnbqfZ36gqBD/ugTU= +-----END RSA PRIVATE KEY-----