Skip to content

Commit 7f09bf4

Browse files
committed
WIP: Service Provider; Deal with encrypted elements
1 parent 2962312 commit 7f09bf4

File tree

7 files changed

+217
-69
lines changed

7 files changed

+217
-69
lines changed

src/SAML2/Entity/ServiceProvider.php

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
};
3030
use SimpleSAML\SAML2\XML\samlp\Response;
3131
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
32+
use SimpleSAML\XMLSecurity\Alg\KeyTransport\KeyTransportAlgorithmFactory;
33+
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
3234
use SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException;
3335
use SimpleSAML\XMLSecurity\XML\{
3436
EncryptableElementInterface,
@@ -49,6 +51,9 @@ final class ServiceProvider
4951
protected ?StateProviderInterface $stateProvider = null;
5052
protected ?StorageProviderInterface $storageProvider = null;
5153
protected ?Metadata\IdentityProvider $idpMetadata = null;
54+
protected SignatureAlgorithmFactory $signatureAlgorithmFactory;
55+
protected EncryptionAlgorithmFactory $encryptionAlgorithmFactory;
56+
protected KeyTransportAlgorithmFactory $keyTransportAlgorithmFactory;
5257

5358

5459
/**
@@ -75,6 +80,9 @@ public function __construct(
7580
// Use with caution - will leave any form of constraint validation up to the implementer
7681
protected readonly bool $bypassConstraintValidation = false,
7782
) {
83+
$this->signatureAlgorithmFactory = new SignatureAlgorithmFactory();
84+
$this->encryptionAlgorithmFactory = new EncryptionAlgorithmFactory();
85+
$this->keyTransportAlgorithmFactory = new KeyTransportAlgorithmFactory();
7886
}
7987

8088

@@ -205,6 +213,10 @@ public function receiveResponse(ServerRequestInterface $request): Response
205213
);
206214
$responseValidator->validate($verifiedResponse);
207215

216+
if ($this->encryptedAssertions === true) {
217+
Assert::allIsInstanceOf($verifiedResponse->getAssertions(), EncryptedAssertion::class);
218+
}
219+
208220
// Decrypt and verify assertions, then rebuild the response.
209221
$verifiedAssertions = $this->decryptAndVerifyAssertions($verifiedResponse->getAssertions());
210222
$decryptedResponse = new Response(
@@ -241,6 +253,8 @@ public function receiveResponse(ServerRequestInterface $request): Response
241253
*/
242254
protected function decryptAndVerifyAssertions(array $unverifiedAssertions): array
243255
{
256+
$wantAssertionsSigned = $this->spMetadata->getWantAssertionsSigned();
257+
244258
/**
245259
* See paragraph 6.2 of the SAML 2.0 core specifications for the applicable processing rules
246260
*
@@ -254,6 +268,11 @@ protected function decryptAndVerifyAssertions(array $unverifiedAssertions): arra
254268
? $this->decryptElement($assertion)
255269
: $assertion;
256270

271+
// Verify that the request is signed, if we require this by configuration
272+
if ($wantAssertionsSigned === true) {
273+
Assert::true($decryptedAssertion->isSigned(), RuntimeException::class);
274+
}
275+
257276
// Verify the signature on the assertions (if any)
258277
$verifiedAssertion = $this->verifyElementSignature($decryptedAssertion);
259278

@@ -262,7 +281,12 @@ protected function decryptAndVerifyAssertions(array $unverifiedAssertions): arra
262281

263282
if ($nameID instanceof EncryptedID) {
264283
$decryptedNameID = $this->decryptElement($nameID);
265-
$subject = new Subject($decryptedNameID, $verifiedAssertion->getSubjectConfirmation());
284+
// Anything we can't decrypt, we leave up for the application to deal with
285+
try {
286+
$subject = new Subject($decryptedNameID, $verifiedAssertion->getSubjectConfirmation());
287+
} catch (RuntimeException) {
288+
$subject = $verifiedAssertion->getSubject();
289+
}
266290
} else {
267291
$subject = $verifiedAssertion->getSubject();
268292
}
@@ -274,7 +298,12 @@ protected function decryptAndVerifyAssertions(array $unverifiedAssertions): arra
274298
$attributes = $statement->getAttributes();
275299
if ($statement->hasEncryptedAttributes()) {
276300
foreach ($statement->getEncryptedAttributes() as $encryptedAttribute) {
277-
$attributes[] = $this->decryptElement($encryptedAttribute);
301+
// Anything we can't decrypt, we leave up for the application to deal with
302+
try {
303+
$attributes[] = $this->decryptElement($encryptedAttribute);
304+
} catch (RuntimeException) {
305+
$attributes[] = $encryptedAttribute;
306+
}
278307
}
279308
}
280309

@@ -307,13 +336,26 @@ protected function decryptAndVerifyAssertions(array $unverifiedAssertions): arra
307336
*/
308337
protected function decryptElement(EncryptedElementInterface $element): EncryptableElementInterface
309338
{
310-
$factory = $this->spMetadata->getEncryptionAlgorithmFactory();
339+
// TODO: When CBC-mode encryption is used, the assertion OR the Response must be signed
340+
$factory = $this->encryptionAlgorithmFactory;
311341

312-
$encryptionAlgorithm = ($factory instanceof EncryptionAlgorithmFactory)
313-
? $element->getEncryptedData()->getEncryptionMethod()
314-
: $element->getEncryptedKey()->getEncryptionMethod();
342+
// If the IDP has a pre-shared key, try decrypting with that
343+
$preSharedKey = $this->idpMetadata->getPreSharedKey();
344+
if ($preSharedKey !== null) {
345+
$encryptionAlgorithm = $element?->getEncryptedKey()?->getEncryptionMethod()
346+
?? $this->idpMetadata->getPreSharedKeyAlgorithm();
347+
348+
$decryptor = $factory->getAlgorithm($encryptionAlgorithm, $preSharedKey);
349+
try {
350+
return $element->decrypt($decryptor);
351+
} catch (Exception $e) {
352+
// Continue to try decrypting with asymmetric keys.
353+
}
354+
}
315355

316-
foreach ($this->spMetadata->getDecriptionKeys() as $decryptionKey) {
356+
$encryptionAlgorithm = $element->getEncryptedKey()->getEncryptionMethod()->getAlgorithm();
357+
foreach ($this->spMetadata->getDecryptionKeys() as $decryptionKey) {
358+
$factory = $this->keyTransportAlgorithmFactory;
317359
$decryptor = $factory->getAlgorithm($encryptionAlgorithm, $decryptionKey);
318360
try {
319361
return $element->decrypt($decryptor);
@@ -339,11 +381,10 @@ protected function decryptElement(EncryptedElementInterface $element): Encryptab
339381
*/
340382
protected function verifyElementSignature(SignedElementInterface $element): SignableElementInterface
341383
{
342-
$factory = $this->spMetadata->getSignatureAlgorithmFactory();
343384
$signatureAlgorithm = $element->getSignature()->getSignedInfo()->getSignatureMethod()->getAlgorithm();
344385

345386
foreach ($this->idpMetadata->getValidatingKeys() as $validatingKey) {
346-
$verifier = $factory->getAlgorithm($signatureAlgorithm, $validatingKey);
387+
$verifier = $this->signatureAlgorithmFactory->getAlgorithm($signatureAlgorithm, $validatingKey);
347388

348389
try {
349390
return $element->verify($verifier);

src/SAML2/Metadata/AbstractProvider.php

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
namespace SimpleSAML\SAML2\Metadata;
66

77
use SimpleSAML\Assert\Assert;
8-
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
9-
use SimpleSAML\XMLSecurity\Alg\KeyTransport\KeyTransportAlgorithmFactory;
10-
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
8+
use SimpleSAML\XMLSecurity\Constants as C;
119
use SimpleSAML\XMLSecurity\Key\{PrivateKey, PublicKey, SymmetricKey};
1210

11+
use function array_keys;
12+
1313
/**
1414
* Class holding common configuration for SAML2 entities.
1515
*
@@ -21,38 +21,23 @@ abstract class AbstractProvider
2121
*/
2222
protected function __construct(
2323
protected string $entityId,
24-
protected EncryptionAlgorithmFactory|KeyTransportAlgorithmFactory|null $encryptionAlgorithmFactory,
25-
protected SignatureAlgorithmFactory|null $signatureAlgorithmFactory,
2624
protected string $signatureAlgorithm,
2725
protected array $validatingKeys,
28-
protected PrivateKey|null $signingKey,
29-
protected PublicKey|SymmetricKey|null $encryptionKey,
26+
protected ?PrivateKey $signingKey,
27+
protected ?PublicKey $encryptionKey,
3028
protected array $decryptionKeys,
29+
protected ?SymmetricKey $preSharedKey,
30+
protected string $preSharedKeyAlgorithm,
3131
protected array $IDPList,
3232
) {
3333
Assert::validURI($entityId);
3434
Assert::validURI($signatureAlgorithm);
35-
Assert::allIsInstanceOfAny($decryptionKeys, [SymmetricKey::class, PrivateKey::class]);
35+
Assert::oneOf($signatureAlgorithm, array_keys(C::$RSA_DIGESTS));
36+
Assert::allIsInstanceOf($decryptionKeys, PrivateKey::class);
3637
Assert::allIsInstanceOf($validatingKeys, PublicKey::class);
3738
Assert::allValidURI($IDPList);
38-
}
39-
40-
41-
/**
42-
* Retrieve the SignatureAlgorithmFactory used for signing and verifying messages.
43-
*/
44-
public function getSignatureAlgorithmFactory(): ?SignatureAlgorithmFactory
45-
{
46-
return $this->signatureAlgorithmFactory;
47-
}
48-
49-
50-
/**
51-
* Retrieve the EncryptionAlgorithmFactory used for encrypting and decrypting messages.
52-
*/
53-
public function getEncryptionAlgorithmFactory(): EncryptionAlgorithmFactory|KeyTransportAlgorithmFactory|null
54-
{
55-
return $this->encryptionAlgorithmFactory;
39+
Assert::nullOrValidURI($preSharedKeyAlgorithm);
40+
Assert::oneOf($preSharedKeyAlgorithm, array_keys(C::$BLOCK_CIPHER_ALGORITHMS));
5641
}
5742

5843

@@ -88,20 +73,42 @@ public function getValidatingKeys(): array
8873

8974

9075
/**
91-
* Get the private key to use for signing messages.
76+
* Get the public key to use for encrypting messages.
9277
*
93-
* @return \SimpleSAML\XMLSecurity\Key\PublicKey|\SimpleSAML\XMLSecurity\Key\SymmetricKey|null
78+
* @return \SimpleSAML\XMLSecurity\Key\PublicKey|null
9479
*/
95-
public function getEncryptionKey(): PublicKey|SymmetricKey|null
80+
public function getEncryptionKey(): ?PublicKey
9681
{
9782
return $this->encryptionKey;
9883
}
9984

10085

86+
/**
87+
* Get the symmetric key to use for encrypting/decrypting messages.
88+
*
89+
* @return \SimpleSAML\XMLSecurity\Key\SymmetricKey|null
90+
*/
91+
public function getPreSharedKey(): ?SymmetricKey
92+
{
93+
return $this->preSharedKey;
94+
}
95+
96+
97+
/**
98+
* Get the symmetric encrypting/decrypting algorithm to use.
99+
*
100+
* @return string|null
101+
*/
102+
public function getPreSharedKeyAlgorithm(): ?string
103+
{
104+
return $this->preSharedKeyAlgorithm;
105+
}
106+
107+
101108
/**
102109
* Get the decryption keys to decrypt the assertion with.
103110
*
104-
* @return array<\SimpleSAML\XMLSecurity\Key\PrivateKey|\SimpleSAML\XMLSecurity\Key\SymmetricKey>
111+
* @return array<\SimpleSAML\XMLSecurity\Key\PrivateKey>
105112
*/
106113
public function getDecryptionKeys(): array
107114
{

src/SAML2/Metadata/IdentityProvider.php

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@
55
namespace SimpleSAML\SAML2\Metadata;
66

77
use SimpleSAML\XMLSecurity\Constants as C;
8-
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
9-
use SimpleSAML\XMLSecurity\Alg\KeyTransport\KeyTransportAlgorithmFactory;
10-
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
118
use SimpleSAML\XMLSecurity\Key\{PrivateKey, PublicKey, SymmetricKey};
129

1310
/**
@@ -21,24 +18,24 @@ class IdentityProvider extends AbstractProvider
2118
*/
2219
public function __construct(
2320
string $entityId,
24-
EncryptionAlgorithmFactory|KeyTransportAlgorithmFactory|null $encryptionAlgorithmFactory = null,
25-
SignatureAlgorithmFactory|null $signatureAlgorithmFactory = null,
2621
string $signatureAlgorithm = C::SIG_RSA_SHA256,
2722
array $validatingKeys = [],
28-
PrivateKey|null $signingKey = null,
29-
PublicKey|SymmetricKey|null $encryptionKey = null,
23+
?PrivateKey $signingKey = null,
24+
?PublicKey $encryptionKey = null,
3025
array $decryptionKeys = [],
26+
?SymmetricKey $preSharedKey = null,
27+
string $preSharedKeyAlgorithm = C::BLOCK_ENC_AES256_GCM,
3128
array $IDPList = [],
3229
) {
3330
parent::__construct(
3431
$entityId,
35-
$encryptionAlgorithmFactory,
36-
$signatureAlgorithmFactory,
3732
$signatureAlgorithm,
3833
$validatingKeys,
3934
$signingKey,
4035
$encryptionKey,
4136
$decryptionKeys,
37+
$preSharedKey,
38+
$preSharedKeyAlgorithm,
4239
$IDPList,
4340
);
4441
}

src/SAML2/Metadata/ServiceProvider.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
use SimpleSAML\Assert\Assert;
88
use SimpleSAML\SAML2\XML\md\AssertionConsumerService;
99
use SimpleSAML\XMLSecurity\Constants as C;
10-
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
11-
use SimpleSAML\XMLSecurity\Alg\KeyTransport\KeyTransportAlgorithmFactory;
12-
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
1310
use SimpleSAML\XMLSecurity\Key\{PrivateKey, PublicKey, SymmetricKey};
1411

1512
/**
@@ -23,27 +20,28 @@ class ServiceProvider extends AbstractProvider
2320
*/
2421
public function __construct(
2522
string $entityId,
26-
EncryptionAlgorithmFactory|KeyTransportAlgorithmFactory|null $encryptionAlgorithmFactory = null,
27-
SignatureAlgorithmFactory|null $signatureAlgorithmFactory = null,
2823
string $signatureAlgorithm = C::SIG_RSA_SHA256,
2924
array $validatingKeys = [],
30-
PrivateKey|null $signingKey = null,
31-
PublicKey|SymmetricKey|null $encryptionKey = null,
25+
?PrivateKey $signingKey = null,
26+
?PublicKey $encryptionKey = null,
3227
protected array $assertionConsumerService = [],
3328
array $decryptionKeys = [],
29+
?SymmetricKey $preSharedKey = null,
30+
string $preSharedKeyAlgorithm = C::BLOCK_ENC_AES256_GCM,
3431
array $IDPList = [],
32+
protected bool $wantAssertionsSigned = false, // Default false by specification
3533
) {
3634
Assert::allIsInstanceOf($assertionConsumerService, AssertionConsumerService::class);
3735

3836
parent::__construct(
3937
$entityId,
40-
$encryptionAlgorithmFactory,
41-
$signatureAlgorithmFactory,
4238
$signatureAlgorithm,
4339
$validatingKeys,
4440
$signingKey,
4541
$encryptionKey,
4642
$decryptionKeys,
43+
$preSharedKey,
44+
$preSharedKeyAlgorithm,
4745
$IDPList,
4846
);
4947
}
@@ -58,4 +56,15 @@ public function getAssertionConsumerService(): array
5856
{
5957
return $this->assertionConsumerService;
6058
}
59+
60+
61+
/**
62+
* Retrieve the configured value for whether assertions must be signed.
63+
*
64+
* @return bool
65+
*/
66+
public function getWantAssertionsSigned(): bool
67+
{
68+
return $this->wantAssertionsSigned;
69+
}
6170
}

0 commit comments

Comments
 (0)