diff --git a/src/Constants.php b/src/Constants.php index 1a1f2656..7b39c159 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -152,4 +152,7 @@ class Constants extends \SimpleSAML\XML\Constants public const XMLENC_ELEMENT = 'http://www.w3.org/2001/04/xmlenc#Element'; public const XMLENC_ENCRYPTEDKEY = 'http://www.w3.org/2001/04/xmlenc#EncryptedKey'; public const XMLENC_EXI = 'http://www.w3.org/2009/xmlenc11#EXI'; + + // The namespace for the Elliptic Curve Diffie-Hellman Ephemeral Static (ECDH-ES) algorithm + public const XMLENC11_ECDH_ES = 'http://www.w3.org/2009/xmlenc11#ECDH-ES'; } diff --git a/src/XML/xenc/AbstractAgreementMethodType.php b/src/XML/xenc/AbstractAgreementMethodType.php new file mode 100644 index 00000000..009887d0 --- /dev/null +++ b/src/XML/xenc/AbstractAgreementMethodType.php @@ -0,0 +1,159 @@ + $children + */ + final public function __construct( + protected string $algorithm, + protected ?KANonce $kaNonce = null, + protected ?OriginatorKeyInfo $originatorKeyInfo = null, + protected ?RecipientKeyInfo $recipientKeyInfo = null, + protected array $children = [], + ) { + Assert::validURI($algorithm, SchemaViolationException::class); // Covers the empty string + + $this->setElements($children); + } + + + /** + * Get the URI identifying the algorithm used by this agreement method. + * + * @return string + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + + /** + * Get the KA-Nonce. + * + * @return \SimpleSAML\XMLSecurity\XML\xenc\KANonce|null + */ + public function getKANonce(): ?KANonce + { + return $this->kaNonce; + } + + + /** + * Get the Originator KeyInfo. + * + * @return \SimpleSAML\XMLSecurity\XML\xenc\OriginatorKeyInfo|null + */ + public function getOriginatorKeyInfo(): ?OriginatorKeyInfo + { + return $this->originatorKeyInfo; + } + + + /** + * Get the Recipient KeyInfo. + * + * @return \SimpleSAML\XMLSecurity\XML\xenc\RecipientKeyInfo|null + */ + public function getRecipientKeyInfo(): ?RecipientKeyInfo + { + return $this->recipientKeyInfo; + } + + + /** + * Initialize an AgreementMethod object from an existing XML. + * + * @param \DOMElement $xml + * @return static + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong + * @throws \SimpleSAML\XML\Exception\MissingAttributeException + * if the supplied element is missing one of the mandatory attributes + * @throws \SimpleSAML\XML\Exception\TooManyElementsException + * if too many child-elements of a type are specified + */ + public static function fromXML(DOMElement $xml): static + { + Assert::same($xml->localName, 'AgreementMethod', InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + $algorithm = self::getAttribute($xml, 'Algorithm'); + + $kaNonce = KANonce::getChildrenOfClass($xml); + Assert::maxCount($kaNonce, 1, TooManyElementsException::class); + + $originatorKeyInfo = OriginatorKeyInfo::getChildrenOfClass($xml); + Assert::maxCount($originatorKeyInfo, 1, TooManyElementsException::class); + + $recipientKeyInfo = RecipientKeyInfo::getChildrenOfClass($xml); + Assert::maxCount($recipientKeyInfo, 1, TooManyElementsException::class); + + $children = self::getChildElementsFromXML($xml); + + return new static( + $algorithm, + array_pop($kaNonce), + array_pop($originatorKeyInfo), + array_pop($recipientKeyInfo), + $children, + ); + } + + + /** + * Convert this AgreementMethod object to XML. + * + * @param \DOMElement|null $parent The element we should append this AgreementMethod to. + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + $e = $this->instantiateParentElement($parent); + $e->setAttribute('Algorithm', $this->getAlgorithm()); + + $this->getKANonce()?->toXML($e); + + foreach ($this->getElements() as $child) { + $child->toXML($e); + } + + $this->getOriginatorKeyInfo()?->toXML($e); + $this->getRecipientKeyInfo()?->toXML($e); + + return $e; + } +} diff --git a/src/XML/xenc/AgreementMethod.php b/src/XML/xenc/AgreementMethod.php new file mode 100644 index 00000000..3a7bc106 --- /dev/null +++ b/src/XML/xenc/AgreementMethod.php @@ -0,0 +1,14 @@ +some', + )->documentElement); + + $originatorKeyInfo = new OriginatorKeyInfo( + [ + new KeyName('testkey'), + new X509Data( + [ + new X509Certificate(self::$certificate), + new X509SubjectName(self::$certData['name']), + ], + ), + new Chunk(DOMDocumentFactory::fromString( + 'originator', + )->documentElement), + ], + 'fed123', + ); + + $recipientKeyInfo = new RecipientKeyInfo( + [ + new KeyName('testkey'), + new X509Data( + [ + new X509Certificate(self::$certificate), + new X509SubjectName(self::$certData['name']), + ], + ), + new Chunk(DOMDocumentFactory::fromString( + 'recipient', + )->documentElement), + ], + 'fed654', + ); + + $agreementMethod = new AgreementMethod( + C::XMLENC11_ECDH_ES, + $kaNonce, + $originatorKeyInfo, + $recipientKeyInfo, + [$someChunk], + ); + + $this->assertEquals( + self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement), + strval($agreementMethod), + ); + } + + + public function testMarshallingElementOrdering(): void + { + $kaNonce = new KANonce('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + + $someChunk = new Chunk(DOMDocumentFactory::fromString( + 'some', + )->documentElement); + + $originatorKeyInfo = new OriginatorKeyInfo( + [ + new KeyName('testkey'), + new X509Data( + [ + new X509Certificate(self::$certificate), + new X509SubjectName(self::$certData['name']), + ], + ), + new Chunk(DOMDocumentFactory::fromString( + 'originator', + )->documentElement), + ], + 'fed321', + ); + + $recipientKeyInfo = new RecipientKeyInfo( + [ + new KeyName('testkey'), + new X509Data( + [ + new X509Certificate(self::$certificate), + new X509SubjectName(self::$certData['name']), + ], + ), + new Chunk(DOMDocumentFactory::fromString( + 'recipient', + )->documentElement), + ], + 'fed654', + ); + + $agreementMethod = new AgreementMethod( + C::XMLENC11_ECDH_ES, + $kaNonce, + $originatorKeyInfo, + $recipientKeyInfo, + [$someChunk], + ); + + // Marshall it to a \DOMElement + $agreementMethodElement = $agreementMethod->toXML(); + + $xpCache = XPath::getXPath($agreementMethodElement); + + // Test for an KA-Nonce + /** @var \DOMElement[] $kaNonceElements */ + $kaNonceElements = XPath::xpQuery($agreementMethodElement, './xenc:KA-Nonce', $xpCache); + $this->assertCount(1, $kaNonceElements); + + // Test ordering of AgreementMethod contents + /** @var \DOMElement[] $agreementMethodElements */ + $agreementMethodElements = XPath::xpQuery( + $agreementMethodElement, + './xenc:KA-Nonce/following-sibling::*', + $xpCache, + ); + + $this->assertCount(3, $agreementMethodElements); + $this->assertEquals('ssp:Chunk', $agreementMethodElements[0]->tagName); + $this->assertEquals('xenc:OriginatorKeyInfo', $agreementMethodElements[1]->tagName); + $this->assertEquals('xenc:RecipientKeyInfo', $agreementMethodElements[2]->tagName); + } +} diff --git a/tests/resources/xml/xenc_AgreementMethod.xml b/tests/resources/xml/xenc_AgreementMethod.xml new file mode 100644 index 00000000..8d3ac53e --- /dev/null +++ b/tests/resources/xml/xenc_AgreementMethod.xml @@ -0,0 +1,20 @@ + + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= + some + + testkey + + MIICxDCCAi2gAwIBAgIUZ9QDx+SBFHednUWDFGm9tyVKrgQwDQYJKoZIhvcNAQELBQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcGA1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNVBAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjIxMjAzMTAzNTQwWhgPMjEyMjExMDkxMDM1NDBaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5vcmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDessdFRVDTMQQW3Na81B1CjJV1tmY3nopoIhZrkbDxLa+pv7jGDRcYreyu1DoQxEs06V2nHLoyOPhqJXSFivqtUwVYhR6NYgbNI6RRSsIJCweH0YOdlHna7gULPcLX0Bfbi4odStaFwG9yzDySwSEPtsKxm5pENPjNVGh+jJ+H/QIDAQABo1MwUTAdBgNVHQ4EFgQUvV75t8EoQo2fVa0E9otdtIGK5X0wHwYDVR0jBBgwFoAUvV75t8EoQo2fVa0E9otdtIGK5X0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQANQUeiwPJXkWMXuaDHToEBKcezYGqGEYnGUi9LMjeb+Kln7X8nn5iknlz4k77rWCbSwLPC/WDr0ySYQA+HagaeUaFpoiYFJKS6uFlK1HYWnM3W4PUiGHg1/xeZlMO44wTwybXVo0y9KMhchfB5XNbDdoJcqWYvi6xtmZZNRbxUyw== + /CN=selfsigned.simplesamlphp.org/O=SimpleSAMLphp HQ/L=Honolulu/ST=Hawaii/C=US + + originator + + + testkey + + MIICxDCCAi2gAwIBAgIUZ9QDx+SBFHednUWDFGm9tyVKrgQwDQYJKoZIhvcNAQELBQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcGA1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNVBAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjIxMjAzMTAzNTQwWhgPMjEyMjExMDkxMDM1NDBaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5vcmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDessdFRVDTMQQW3Na81B1CjJV1tmY3nopoIhZrkbDxLa+pv7jGDRcYreyu1DoQxEs06V2nHLoyOPhqJXSFivqtUwVYhR6NYgbNI6RRSsIJCweH0YOdlHna7gULPcLX0Bfbi4odStaFwG9yzDySwSEPtsKxm5pENPjNVGh+jJ+H/QIDAQABo1MwUTAdBgNVHQ4EFgQUvV75t8EoQo2fVa0E9otdtIGK5X0wHwYDVR0jBBgwFoAUvV75t8EoQo2fVa0E9otdtIGK5X0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQANQUeiwPJXkWMXuaDHToEBKcezYGqGEYnGUi9LMjeb+Kln7X8nn5iknlz4k77rWCbSwLPC/WDr0ySYQA+HagaeUaFpoiYFJKS6uFlK1HYWnM3W4PUiGHg1/xeZlMO44wTwybXVo0y9KMhchfB5XNbDdoJcqWYvi6xtmZZNRbxUyw== + /CN=selfsigned.simplesamlphp.org/O=SimpleSAMLphp HQ/L=Honolulu/ST=Hawaii/C=US + + recipient + +