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
+
+