Skip to content

Commit c19f8bf

Browse files
committed
Add missing samlp:NewEncryptedID
1 parent 8c6c025 commit c19f8bf

File tree

4 files changed

+397
-0
lines changed

4 files changed

+397
-0
lines changed

phpcs.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<exclude-pattern>tests/SAML2/XML/saml/EncryptedIDTest.php</exclude-pattern>
4444
<exclude-pattern>tests/SAML2/XML/saml/SubjectConfirmationTest.php</exclude-pattern>
4545
<exclude-pattern>tests/SAML2/XML/samlp/AuthnRequestTest.php</exclude-pattern>
46+
<exclude-pattern>tests/SAML2/XML/samlp/NewEncryptedIDTest.php</exclude-pattern>
4647
<exclude-pattern>tests/SAML2/XML/samlp/RequestedAuthnContextTest.php</exclude-pattern>
4748
<exclude-pattern>tests/SAML2/XML/samlp/StatusDetailTest.php</exclude-pattern>
4849
<exclude-pattern>tests/SAML2/XML/shibmd/KeyAuthorityTest.php</exclude-pattern>

src/XML/samlp/NewEncryptedID.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\SAML2\XML\samlp;
6+
7+
use InvalidArgumentException;
8+
use SimpleSAML\SAML2\Constants as C;
9+
use SimpleSAML\SAML2\XML\saml\AbstractBaseID;
10+
use SimpleSAML\SAML2\XML\saml\AbstractEncryptedElement;
11+
use SimpleSAML\SAML2\XML\saml\IdentifierInterface;
12+
use SimpleSAML\SAML2\XML\saml\NameID;
13+
use SimpleSAML\XML\DOMDocumentFactory;
14+
use SimpleSAML\XML\SchemaValidatableElementInterface;
15+
use SimpleSAML\XML\SchemaValidatableElementTrait;
16+
use SimpleSAML\XML\SerializableElementInterface;
17+
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmInterface;
18+
19+
use function implode;
20+
21+
/**
22+
* Class representing an encrypted identifier.
23+
*
24+
* @package simplesamlphp/saml2
25+
*/
26+
final class NewEncryptedID extends AbstractEncryptedElement implements
27+
IdentifierInterface,
28+
SchemaValidatableElementInterface
29+
{
30+
use SchemaValidatableElementTrait;
31+
32+
33+
/** @var string */
34+
public const NS = C::NS_SAMLP;
35+
36+
/** @var string */
37+
public const NS_PREFIX = 'samlp';
38+
39+
/** @var string */
40+
public const SCHEMA = 'resources/schemas/saml-schema-protocol-2.0.xsd';
41+
42+
43+
/**
44+
* @inheritDoc
45+
*
46+
* @return \SimpleSAML\XML\SerializableElementInterface
47+
*/
48+
public function decrypt(EncryptionAlgorithmInterface $decryptor): SerializableElementInterface
49+
{
50+
$xml = DOMDocumentFactory::fromString($this->decryptData($decryptor))->documentElement;
51+
52+
$id = implode(':', [$xml->namespaceURI, $xml->localName]);
53+
switch ($id) {
54+
case NameID::NS . ':NameID':
55+
return NameID::fromXML($xml);
56+
case AbstractBaseID::NS . ':BaseID':
57+
return AbstractBaseID::fromXML($xml);
58+
default:
59+
// Fall thru
60+
}
61+
throw new InvalidArgumentException('Unknown or unsupported encrypted identifier.');
62+
}
63+
}
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Test\SAML2\XML\saml;
6+
7+
use InvalidArgumentException;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\Group;
10+
use PHPUnit\Framework\TestCase;
11+
use SimpleSAML\SAML2\Compat\AbstractContainer;
12+
use SimpleSAML\SAML2\Compat\ContainerSingleton;
13+
use SimpleSAML\SAML2\Type\SAMLAnyURIValue;
14+
use SimpleSAML\SAML2\Type\SAMLStringValue;
15+
use SimpleSAML\SAML2\Utils\XPath;
16+
use SimpleSAML\SAML2\XML\saml\AbstractEncryptedElement;
17+
use SimpleSAML\SAML2\XML\saml\Attribute;
18+
use SimpleSAML\SAML2\XML\saml\Audience;
19+
use SimpleSAML\SAML2\XML\saml\NameID;
20+
use SimpleSAML\SAML2\XML\saml\UnknownID;
21+
use SimpleSAML\SAML2\XML\samlp\NewEncryptedID;
22+
use SimpleSAML\Test\SAML2\Constants as C;
23+
use SimpleSAML\Test\SAML2\CustomBaseID;
24+
use SimpleSAML\XML\DOMDocumentFactory;
25+
use SimpleSAML\XML\TestUtils\SchemaValidationTestTrait;
26+
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;
27+
use SimpleSAML\XMLSchema\Type\AnyURIValue;
28+
use SimpleSAML\XMLSchema\Type\Base64BinaryValue;
29+
use SimpleSAML\XMLSchema\Type\IDValue;
30+
use SimpleSAML\XMLSchema\Type\StringValue;
31+
use SimpleSAML\XMLSecurity\Alg\KeyTransport\KeyTransportAlgorithmFactory;
32+
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
33+
use SimpleSAML\XMLSecurity\XML\ds\KeyInfo;
34+
use SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName;
35+
use SimpleSAML\XMLSecurity\XML\xenc\CipherData;
36+
use SimpleSAML\XMLSecurity\XML\xenc\CipherValue;
37+
use SimpleSAML\XMLSecurity\XML\xenc\DataReference;
38+
use SimpleSAML\XMLSecurity\XML\xenc\EncryptedData;
39+
use SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey;
40+
use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod;
41+
use SimpleSAML\XMLSecurity\XML\xenc\ReferenceList;
42+
43+
use function dirname;
44+
use function strval;
45+
46+
/**
47+
* Class NewEncryptedIDTest
48+
*
49+
* @package simplesamlphp/saml2
50+
*/
51+
#[Group('saml')]
52+
#[CoversClass(NewEncryptedID::class)]
53+
#[CoversClass(AbstractEncryptedElement::class)]
54+
final class NewEncryptedIDTest extends TestCase
55+
{
56+
use SchemaValidationTestTrait;
57+
use SerializableElementTestTrait;
58+
59+
60+
/** @var \SimpleSAML\SAML2\Compat\AbstractContainer */
61+
private static AbstractContainer $containerBackup;
62+
63+
64+
/**
65+
*/
66+
public static function setUpBeforeClass(): void
67+
{
68+
self::$containerBackup = ContainerSingleton::getInstance();
69+
70+
self::$testedClass = NewEncryptedID::class;
71+
72+
self::$xmlRepresentation = DOMDocumentFactory::fromFile(
73+
dirname(__FILE__, 4) . '/resources/xml/samlp_NewEncryptedID.xml',
74+
);
75+
76+
$container = clone self::$containerBackup;
77+
$container->setBlacklistedAlgorithms(null);
78+
$container->registerExtensionHandler(CustomBaseID::class);
79+
ContainerSingleton::setContainer($container);
80+
}
81+
82+
83+
/**
84+
*/
85+
public static function tearDownAfterClass(): void
86+
{
87+
ContainerSingleton::setContainer(self::$containerBackup);
88+
}
89+
90+
91+
// marshalling
92+
93+
94+
/**
95+
*/
96+
public function testMarshalling(): void
97+
{
98+
$ed = new EncryptedData(
99+
cipherData: new CipherData(
100+
new CipherValue(
101+
Base64BinaryValue::fromString('720FAxwOXcv8ast9YvQutUoue+YA2FgLLNaD/FZrWiNexTkPyZ8CWrcf2zZj2zrOwTjQ9KJvzvCuzq4fM51sU1boOakLpz05NonDdMgeWW/eWcOJJfOZs0tYvYc5qZ/R+BzRnJsGG6w2ZmipEi88X/8uA85c'),
102+
),
103+
),
104+
type: AnyURIValue::fromString(C::XMLENC_ELEMENT),
105+
encryptionMethod: new EncryptionMethod(
106+
AnyURIValue::fromString('http://www.w3.org/2009xmlenc11#aes256-gcm'),
107+
),
108+
keyInfo: new KeyInfo([
109+
new EncryptedKey(
110+
cipherData: new CipherData(
111+
new CipherValue(
112+
Base64BinaryValue::fromString('he5ZBjtfp/1/Y3PgE/CWspDPADig9vuZ7yZyYXDQ1wA/HBTPCldtL/p6UT5RCAFYUwN6kp3jnHkhK1yMjrI1SMw0n5NEc2wO9N5inQIeQOZ8XD9yD9M5fHvWz2ByNMGlB35RWMnBRHzDi1PRV7Irwcs9WoiODh3i6j2vYXP7cAo='),
113+
),
114+
),
115+
encryptionMethod: new EncryptionMethod(
116+
AnyURIValue::fromString('http://www.w3.org/2009/xmlenc11#rsa-oaep'),
117+
),
118+
),
119+
]),
120+
);
121+
$ek = new EncryptedKey(
122+
cipherData: new CipherData(
123+
new CipherValue(
124+
Base64BinaryValue::fromString('he5ZBjtfp/1/Y3PgE/CWspDPADig9vuZ7yZyYXDQ1wA/HBTPCldtL/p6UT5RCAFYUwN6kp3jnHkhK1yMjrI1SMw0n5NEc2wO9N5inQIeQOZ8XD9yD9M5fHvWz2ByNMGlB35RWMnBRHzDi1PRV7Irwcs9WoiODh3i6j2vYXP7cAo='),
125+
),
126+
),
127+
id: IDValue::fromString('Encrypted_KEY_ID'),
128+
recipient: StringValue::fromString(C::ENTITY_SP),
129+
carriedKeyName: new CarriedKeyName(
130+
StringValue::fromString('Name of the key'),
131+
),
132+
encryptionMethod: new EncryptionMethod(
133+
AnyURIValue::fromString('http://www.w3.org/2009/xmlenc11#rsa-oaep'),
134+
),
135+
referenceList: new ReferenceList([
136+
new DataReference(
137+
AnyURIValue::fromString('#Encrypted_DATA_ID'),
138+
),
139+
]),
140+
);
141+
$eid = new NewEncryptedID($ed, [$ek]);
142+
143+
$this->assertEquals(
144+
self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
145+
strval($eid),
146+
);
147+
}
148+
149+
150+
/**
151+
*/
152+
public function testMarshallingElementOrdering(): void
153+
{
154+
$ed = new EncryptedData(
155+
cipherData: new CipherData(
156+
new CipherValue(
157+
Base64BinaryValue::fromString('iFz/8KASJCLCAHqaAKhZXWOG/TPZlgTxcQ25lTGxdSdEsGYz7cg5lfZAbcN3UITCP9MkJsyjMlRsQouIqBkoPCGZz8NXibDkQ8OUeE7JdkFgKvgUMXawp+uDL4gHR8L7l6SPAmWZU3Hx/Wg9pTJBOpTjwoS0'),
158+
),
159+
),
160+
encryptionMethod: new EncryptionMethod(
161+
AnyURIValue::fromString('http://www.w3.org/2001/04/xmlenc#aes128-cbc'),
162+
),
163+
type: AnyURIValue::fromString(C::XMLENC_ELEMENT),
164+
keyInfo: new KeyInfo([
165+
new EncryptedKey(
166+
cipherData: new CipherData(
167+
new CipherValue(
168+
Base64BinaryValue::fromString('GMhpk09X+quNC/SsnxcDglZU/DCLAu9bMJ5bPcgaBK4s3F1eXciU8hlOYNaskSwP86HmA704NbzSDOHAgN6ckR+iCssxA7XCBjz0hltsgfn5p9Rh8qKtKltiXvxo/xXTcSXXZXNcE0R2KTya0P4DjZvYYgbIls/AH8ZyDV07ntI='),
169+
),
170+
),
171+
encryptionMethod: new EncryptionMethod(
172+
AnyURIValue::fromString('http://www.w3.org/2009/xmlenc11#rsa-oaep'),
173+
),
174+
),
175+
]),
176+
);
177+
$ek = new EncryptedKey(
178+
cipherData: new CipherData(
179+
new CipherValue(
180+
Base64BinaryValue::fromString('GMhpk09X+quNC/SsnxcDglZU/DCLAu9bMJ5bPcgaBK4s3F1eXciU8hlOYNaskSwP86HmA704NbzSDOHAgN6ckR+iCssxA7XCBjz0hltsgfn5p9Rh8qKtKltiXvxo/xXTcSXXZXNcE0R2KTya0P4DjZvYYgbIls/AH8ZyDV07ntI='),
181+
),
182+
),
183+
id: IDValue::fromString('Encrypted_KEY_ID'),
184+
recipient: StringValue::fromString(C::ENTITY_SP),
185+
carriedKeyName: new CarriedKeyName(
186+
StringValue::fromString('Name of the key'),
187+
),
188+
encryptionMethod: new EncryptionMethod(
189+
AnyURIValue::fromString('http://www.w3.org/2001/04/xmlenc#rsa-1_5'),
190+
),
191+
referenceList: new ReferenceList([
192+
new DataReference(
193+
AnyURIValue::fromString('#Encrypted_DATA_ID'),
194+
),
195+
]),
196+
);
197+
$eid = new NewEncryptedID($ed, [$ek]);
198+
$eidElement = $eid->toXML();
199+
200+
// Test for an NewEncryptedID
201+
$xpCache = XPath::getXPath($eidElement);
202+
$eidElements = XPath::xpQuery($eidElement, './xenc:EncryptedData', $xpCache);
203+
$this->assertCount(1, $eidElements);
204+
205+
// Test ordering of NewEncryptedID contents
206+
/** @psalm-var \DOMElement[] $eidElements */
207+
$eidElements = XPath::xpQuery($eidElement, './xenc:EncryptedData/following-sibling::*', $xpCache);
208+
$this->assertCount(1, $eidElements);
209+
$this->assertEquals('xenc:EncryptedKey', $eidElements[0]->tagName);
210+
}
211+
212+
213+
/**
214+
* Test encryption / decryption
215+
*/
216+
public function testEncryption(): void
217+
{
218+
// Create keys
219+
$privKey = PEMCertificatesMock::getPrivateKey(PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY);
220+
$pubKey = PEMCertificatesMock::getPublicKey(PEMCertificatesMock::SELFSIGNED_PUBLIC_KEY);
221+
222+
// Create encryptor
223+
$encryptor = (new KeyTransportAlgorithmFactory())->getAlgorithm(
224+
C::KEY_TRANSPORT_OAEP,
225+
$pubKey,
226+
);
227+
228+
// test with a NameID
229+
$nameid = new NameID(
230+
SAMLStringValue::fromString('value'),
231+
SAMLStringValue::fromString('urn:x-simplesamlphp:namequalifier'),
232+
);
233+
234+
$encid = new NewEncryptedID($nameid->encrypt($encryptor));
235+
/** @psalm-suppress ArgumentTypeCoercion */
236+
$doc = DOMDocumentFactory::fromString(strval($encid));
237+
238+
$encid = NewEncryptedID::fromXML($doc->documentElement);
239+
/** @psalm-suppress PossiblyNullArgument */
240+
$decryptor = (new KeyTransportAlgorithmFactory())->getAlgorithm(
241+
$encid->getEncryptedKeys()[0]->getEncryptionMethod()?->getAlgorithm()->getValue(),
242+
$privKey,
243+
);
244+
$id = $encid->decrypt($decryptor);
245+
/** @psalm-suppress ArgumentTypeCoercion */
246+
$this->assertEquals(strval($nameid), strval($id));
247+
248+
// test a custom BaseID that's registered
249+
$customId = new CustomBaseID(
250+
[
251+
new Audience(
252+
SAMLAnyURIValue::fromString('urn:some:audience'),
253+
),
254+
],
255+
SAMLStringValue::fromString('urn:x-simplesamlphp:namequalifier'),
256+
SAMLStringValue::fromString('urn:x-simplesamlphp:spnamequalifier'),
257+
);
258+
259+
$encid = new NewEncryptedID($customId->encrypt($encryptor));
260+
/** @psalm-suppress ArgumentTypeCoercion */
261+
$doc = DOMDocumentFactory::fromString(strval($encid));
262+
263+
$encid = NewEncryptedID::fromXML($doc->documentElement);
264+
/** @psalm-suppress PossiblyNullArgument */
265+
$decryptor = (new KeyTransportAlgorithmFactory())->getAlgorithm(
266+
$encid->getEncryptedKeys()[0]->getEncryptionMethod()?->getAlgorithm()->getValue(),
267+
$privKey,
268+
);
269+
$id = $encid->decrypt($decryptor);
270+
$this->assertInstanceOf(CustomBaseID::class, $id);
271+
/** @psalm-suppress ArgumentTypeCoercion */
272+
$this->assertEquals(strval($customId), strval($id));
273+
274+
// Remove registration by using a clean container
275+
$container = clone self::$containerBackup;
276+
$container->setBlacklistedAlgorithms(null);
277+
ContainerSingleton::setContainer($container);
278+
279+
// test a custom BaseID that's unregistered
280+
$unknownId = $customId;
281+
282+
$encid = new NewEncryptedID($unknownId->encrypt($encryptor));
283+
/** @psalm-suppress ArgumentTypeCoercion */
284+
$doc = DOMDocumentFactory::fromString(strval($encid));
285+
286+
$encid = NewEncryptedID::fromXML($doc->documentElement);
287+
/** @psalm-suppress PossiblyNullArgument */
288+
$decryptor = (new KeyTransportAlgorithmFactory())->getAlgorithm(
289+
$encid->getEncryptedKeys()[0]->getEncryptionMethod()?->getAlgorithm()->getValue(),
290+
$privKey,
291+
);
292+
$id = $encid->decrypt($decryptor);
293+
$this->assertInstanceOf(UnknownID::class, $id);
294+
/** @psalm-suppress ArgumentTypeCoercion */
295+
$this->assertEquals(strval($unknownId), strval($id));
296+
297+
// test with unsupported ID
298+
$attr = new Attribute(
299+
SAMLStringValue::fromString('name'),
300+
);
301+
$encid = new NewEncryptedID($attr->encrypt($encryptor));
302+
303+
$this->expectException(InvalidArgumentException::class);
304+
$this->expectExceptionMessage('Unknown or unsupported encrypted identifier.');
305+
$encid->decrypt($decryptor);
306+
}
307+
}

0 commit comments

Comments
 (0)