Skip to content

Commit 0c27170

Browse files
committed
Add element xenc:AgreementMethod
1 parent 26d0811 commit 0c27170

File tree

5 files changed

+403
-0
lines changed

5 files changed

+403
-0
lines changed

src/Constants.php

+3
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,7 @@ class Constants extends \SimpleSAML\XML\Constants
152152
public const XMLENC_ELEMENT = 'http://www.w3.org/2001/04/xmlenc#Element';
153153
public const XMLENC_ENCRYPTEDKEY = 'http://www.w3.org/2001/04/xmlenc#EncryptedKey';
154154
public const XMLENC_EXI = 'http://www.w3.org/2009/xmlenc11#EXI';
155+
156+
// The namespace for the Elliptic Curve Diffie-Hellman Ephemeral Static (ECDH-ES) algorithm
157+
public const XMLENC11_ECDH_ES = 'http://www.w3.org/2009/xmlenc11#ECDH-ES';
155158
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\XML\xenc;
6+
7+
use DOMElement;
8+
use SimpleSAML\Assert\Assert;
9+
use SimpleSAML\XML\Exception\InvalidDOMElementException;
10+
use SimpleSAML\XML\Exception\SchemaViolationException;
11+
use SimpleSAML\XML\Exception\TooManyElementsException;
12+
use SimpleSAML\XML\ExtendableElementTrait;
13+
use SimpleSAML\XML\XsNamespace as NS;
14+
15+
use function array_pop;
16+
17+
/**
18+
* A class implementing the xenc:AbstractAgreementMethodType element.
19+
*
20+
* @package simplesamlphp/xml-security
21+
*/
22+
abstract class AbstractAgreementMethodType extends AbstractXencElement
23+
{
24+
use ExtendableElementTrait;
25+
26+
/** The namespace-attribute for the xs:any element */
27+
public const XS_ANY_ELT_NAMESPACE = NS::OTHER;
28+
29+
30+
/**
31+
* AgreementMethodType constructor.
32+
*
33+
* @param string $algorithm
34+
* @param \SimpleSAML\XMLSecurity\XML\xenc\KANonce|null $kaNonce
35+
* @param \SimpleSAML\XMLSecurity\XML\xenc\OriginatorKeyInfo|null $originatorKeyInfo
36+
* @param \SimpleSAML\XMLSecurity\XML\xenc\RecipientKeyInfo|null $recipientKeyInfo
37+
* @param list<\SimpleSAML\XML\SerializableElementInterface> $children
38+
*/
39+
final public function __construct(
40+
protected string $algorithm,
41+
protected ?KANonce $kaNonce = null,
42+
protected ?OriginatorKeyInfo $originatorKeyInfo = null,
43+
protected ?RecipientKeyInfo $recipientKeyInfo = null,
44+
protected array $children = [],
45+
) {
46+
Assert::validURI($algorithm, SchemaViolationException::class); // Covers the empty string
47+
48+
$this->setElements($children);
49+
}
50+
51+
52+
/**
53+
* Get the URI identifying the algorithm used by this agreement method.
54+
*
55+
* @return string
56+
*/
57+
public function getAlgorithm(): string
58+
{
59+
return $this->algorithm;
60+
}
61+
62+
63+
/**
64+
* Get the KA-Nonce.
65+
*
66+
* @return \SimpleSAML\XMLSecurity\XML\xenc\KANonce|null
67+
*/
68+
public function getKANonce(): ?KANonce
69+
{
70+
return $this->kaNonce;
71+
}
72+
73+
74+
/**
75+
* Get the Originator KeyInfo.
76+
*
77+
* @return \SimpleSAML\XMLSecurity\XML\xenc\OriginatorKeyInfo|null
78+
*/
79+
public function getOriginatorKeyInfo(): ?OriginatorKeyInfo
80+
{
81+
return $this->originatorKeyInfo;
82+
}
83+
84+
85+
/**
86+
* Get the Recipient KeyInfo.
87+
*
88+
* @return \SimpleSAML\XMLSecurity\XML\xenc\RecipientKeyInfo|null
89+
*/
90+
public function getRecipientKeyInfo(): ?RecipientKeyInfo
91+
{
92+
return $this->recipientKeyInfo;
93+
}
94+
95+
96+
/**
97+
* Initialize an AgreementMethod object from an existing XML.
98+
*
99+
* @param \DOMElement $xml
100+
* @return static
101+
*
102+
* @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
103+
* if the qualified name of the supplied element is wrong
104+
* @throws \SimpleSAML\XML\Exception\MissingAttributeException
105+
* if the supplied element is missing one of the mandatory attributes
106+
* @throws \SimpleSAML\XML\Exception\TooManyElementsException
107+
* if too many child-elements of a type are specified
108+
*/
109+
public static function fromXML(DOMElement $xml): static
110+
{
111+
Assert::same($xml->localName, 'AgreementMethod', InvalidDOMElementException::class);
112+
Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
113+
114+
$algorithm = self::getAttribute($xml, 'Algorithm');
115+
116+
$kaNonce = KANonce::getChildrenOfClass($xml);
117+
Assert::maxCount($kaNonce, 1, TooManyElementsException::class);
118+
119+
$originatorKeyInfo = OriginatorKeyInfo::getChildrenOfClass($xml);
120+
Assert::maxCount($originatorKeyInfo, 1, TooManyElementsException::class);
121+
122+
$recipientKeyInfo = RecipientKeyInfo::getChildrenOfClass($xml);
123+
Assert::maxCount($recipientKeyInfo, 1, TooManyElementsException::class);
124+
125+
$children = self::getChildElementsFromXML($xml);
126+
127+
return new static(
128+
$algorithm,
129+
array_pop($kaNonce),
130+
array_pop($originatorKeyInfo),
131+
array_pop($recipientKeyInfo),
132+
$children,
133+
);
134+
}
135+
136+
137+
/**
138+
* Convert this AgreementMethod object to XML.
139+
*
140+
* @param \DOMElement|null $parent The element we should append this AgreementMethod to.
141+
* @return \DOMElement
142+
*/
143+
public function toXML(DOMElement $parent = null): DOMElement
144+
{
145+
$e = $this->instantiateParentElement($parent);
146+
$e->setAttribute('Algorithm', $this->getAlgorithm());
147+
148+
$this->getKANonce()?->toXML($e);
149+
150+
foreach ($this->getElements() as $child) {
151+
$child->toXML($e);
152+
}
153+
154+
$this->getOriginatorKeyInfo()?->toXML($e);
155+
$this->getRecipientKeyInfo()?->toXML($e);
156+
157+
return $e;
158+
}
159+
}

src/XML/xenc/AgreementMethod.php

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\XML\xenc;
6+
7+
/**
8+
* A class implementing the xenc:AgreementMethod element.
9+
*
10+
* @package simplesamlphp/xml-security
11+
*/
12+
final class AgreementMethod extends AbstractAgreementMethodType
13+
{
14+
}
+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\Test\XML\xenc;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\TestCase;
9+
use SimpleSAML\XML\Chunk;
10+
use SimpleSAML\XML\DOMDocumentFactory;
11+
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;
12+
use SimpleSAML\XMLSecurity\Constants as C;
13+
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
14+
use SimpleSAML\XMLSecurity\Utils\XPath;
15+
use SimpleSAML\XMLSecurity\XML\ds\{KeyName, X509Certificate, X509Data, X509SubjectName};
16+
use SimpleSAML\XMLSecurity\XML\xenc\{AbstractAgreementMethodType, AbstractXencElement};
17+
use SimpleSAML\XMLSecurity\XML\xenc\{AgreementMethod, KANonce, OriginatorKeyInfo, RecipientKeyInfo};
18+
19+
use function dirname;
20+
use function openssl_x509_parse;
21+
use function str_replace;
22+
use function strval;
23+
24+
/**
25+
* Class \SimpleSAML\XMLSecurity\Test\XML\xenc\AgreementMethodTest
26+
*
27+
* @package simplesamlphp/xml-security
28+
*/
29+
#[CoversClass(AbstractXencElement::class)]
30+
#[CoversClass(AbstractAgreementMethodType::class)]
31+
#[CoversClass(AgreementMethod::class)]
32+
final class AgreementMethodTest extends TestCase
33+
{
34+
use SerializableElementTestTrait;
35+
36+
/** @var string */
37+
private static string $certificate;
38+
39+
/** @var string[] */
40+
private static array $certData;
41+
42+
43+
/**
44+
*/
45+
public function setUp(): void
46+
{
47+
self::$testedClass = AgreementMethod::class;
48+
49+
self::$xmlRepresentation = DOMDocumentFactory::fromFile(
50+
dirname(__FILE__, 3) . '/resources/xml/xenc_AgreementMethod.xml',
51+
);
52+
53+
self::$certificate = str_replace(
54+
[
55+
'-----BEGIN CERTIFICATE-----',
56+
'-----END CERTIFICATE-----',
57+
'-----BEGIN RSA PUBLIC KEY-----',
58+
'-----END RSA PUBLIC KEY-----',
59+
"\r\n",
60+
"\n",
61+
],
62+
[
63+
'',
64+
'',
65+
'',
66+
'',
67+
"\n",
68+
'',
69+
],
70+
PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
71+
);
72+
73+
self::$certData = openssl_x509_parse(
74+
PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
75+
);
76+
}
77+
78+
79+
/**
80+
*/
81+
public function testMarshalling(): void
82+
{
83+
$kaNonce = new KANonce('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=');
84+
85+
$someChunk = new Chunk(DOMDocumentFactory::fromString(
86+
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">some</ssp:Chunk>',
87+
)->documentElement);
88+
89+
$originatorKeyInfo = new OriginatorKeyInfo(
90+
[
91+
new KeyName('testkey'),
92+
new X509Data(
93+
[
94+
new X509Certificate(self::$certificate),
95+
new X509SubjectName(self::$certData['name']),
96+
],
97+
),
98+
new Chunk(DOMDocumentFactory::fromString(
99+
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">originator</ssp:Chunk>',
100+
)->documentElement),
101+
],
102+
'fed123',
103+
);
104+
105+
$recipientKeyInfo = new RecipientKeyInfo(
106+
[
107+
new KeyName('testkey'),
108+
new X509Data(
109+
[
110+
new X509Certificate(self::$certificate),
111+
new X509SubjectName(self::$certData['name']),
112+
],
113+
),
114+
new Chunk(DOMDocumentFactory::fromString(
115+
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">recipient</ssp:Chunk>',
116+
)->documentElement),
117+
],
118+
'fed654',
119+
);
120+
121+
$agreementMethod = new AgreementMethod(
122+
C::XMLENC11_ECDH_ES,
123+
$kaNonce,
124+
$originatorKeyInfo,
125+
$recipientKeyInfo,
126+
[$someChunk],
127+
);
128+
129+
$this->assertEquals(
130+
self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
131+
strval($agreementMethod),
132+
);
133+
}
134+
135+
136+
public function testMarshallingElementOrdering(): void
137+
{
138+
$kaNonce = new KANonce('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=');
139+
140+
$someChunk = new Chunk(DOMDocumentFactory::fromString(
141+
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">some</ssp:Chunk>',
142+
)->documentElement);
143+
144+
$originatorKeyInfo = new OriginatorKeyInfo(
145+
[
146+
new KeyName('testkey'),
147+
new X509Data(
148+
[
149+
new X509Certificate(self::$certificate),
150+
new X509SubjectName(self::$certData['name']),
151+
],
152+
),
153+
new Chunk(DOMDocumentFactory::fromString(
154+
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">originator</ssp:Chunk>',
155+
)->documentElement),
156+
],
157+
'fed321',
158+
);
159+
160+
$recipientKeyInfo = new RecipientKeyInfo(
161+
[
162+
new KeyName('testkey'),
163+
new X509Data(
164+
[
165+
new X509Certificate(self::$certificate),
166+
new X509SubjectName(self::$certData['name']),
167+
],
168+
),
169+
new Chunk(DOMDocumentFactory::fromString(
170+
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">recipient</ssp:Chunk>',
171+
)->documentElement),
172+
],
173+
'fed654',
174+
);
175+
176+
$agreementMethod = new AgreementMethod(
177+
C::XMLENC11_ECDH_ES,
178+
$kaNonce,
179+
$originatorKeyInfo,
180+
$recipientKeyInfo,
181+
[$someChunk],
182+
);
183+
184+
// Marshall it to a \DOMElement
185+
$agreementMethodElement = $agreementMethod->toXML();
186+
187+
$xpCache = XPath::getXPath($agreementMethodElement);
188+
189+
// Test for an KA-Nonce
190+
/** @var \DOMElement[] $kaNonceElements */
191+
$kaNonceElements = XPath::xpQuery($agreementMethodElement, './xenc:KA-Nonce', $xpCache);
192+
$this->assertCount(1, $kaNonceElements);
193+
194+
// Test ordering of AgreementMethod contents
195+
/** @var \DOMElement[] $agreementMethodElements */
196+
$agreementMethodElements = XPath::xpQuery(
197+
$agreementMethodElement,
198+
'./xenc:KA-Nonce/following-sibling::*',
199+
$xpCache,
200+
);
201+
202+
$this->assertCount(3, $agreementMethodElements);
203+
$this->assertEquals('ssp:Chunk', $agreementMethodElements[0]->tagName);
204+
$this->assertEquals('xenc:OriginatorKeyInfo', $agreementMethodElements[1]->tagName);
205+
$this->assertEquals('xenc:RecipientKeyInfo', $agreementMethodElements[2]->tagName);
206+
}
207+
}

0 commit comments

Comments
 (0)