Skip to content

Commit 1209dca

Browse files
authored
Add support for RSA-PSS signatures (#94)
* Add support for RSA-PSS signatures * Remove the default signing algorithm: Let the implementor make it explicit * Refactor * Refactor
1 parent f43c6c8 commit 1209dca

10 files changed

Lines changed: 263 additions & 13 deletions

File tree

src/Alg/Signature/AbstractSigner.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use SimpleSAML\Assert\Assert;
88
use SimpleSAML\XMLSecurity\Backend;
99
use SimpleSAML\XMLSecurity\Backend\SignatureBackend;
10+
use SimpleSAML\XMLSecurity\Backend\SignaturePadding;
1011
use SimpleSAML\XMLSecurity\Exception\UnsupportedAlgorithmException;
1112
use SimpleSAML\XMLSecurity\Key\KeyInterface;
1213

@@ -52,6 +53,10 @@ public function __construct(
5253
$backend = new (static::DEFAULT_BACKEND)();
5354
$this->setBackend($backend);
5455
$this->backend->setDigestAlg($digest);
56+
57+
if ($this->backend instanceof SignaturePadding) {
58+
$this->backend->setSignaturePadding($algId);
59+
}
5560
}
5661

5762

src/Alg/Signature/HMAC.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class HMAC extends AbstractSigner implements SignatureAlgorithmInterface
2727
public function __construct(
2828
#[\SensitiveParameter]
2929
SymmetricKey $key,
30-
string $algId = C::SIG_HMAC_SHA256,
30+
string $algId,
3131
) {
3232
parent::__construct($key, $algId, C::$HMAC_DIGESTS[$algId]);
3333
}

src/Alg/Signature/RSA.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ final class RSA extends AbstractSigner implements SignatureAlgorithmInterface
2323
public function __construct(
2424
#[\SensitiveParameter]
2525
AsymmetricKey $key,
26-
string $algId = C::SIG_RSA_SHA256,
26+
string $algId,
2727
) {
2828
parent::__construct($key, $algId, C::$RSA_DIGESTS[$algId]);
2929
}

src/Alg/Signature/SignatureAlgorithmFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ final class SignatureAlgorithmFactory
2323
/**
2424
* An array of blacklisted algorithms.
2525
*
26-
* Defaults to RSA-SHA1 & HMAC-SHA1 due to the weakness of SHA1.
26+
* Defaults to RSA-SHA1, RSAPSS-SHA1 & HMAC-SHA1 due to the weakness of SHA1.
2727
*
2828
* @var string[]
2929
*/
3030
public const array DEFAULT_BLACKLIST = [
3131
C::SIG_RSA_SHA1,
32+
C::SIG_RSA_PSS_SHA1,
3233
C::SIG_HMAC_SHA1,
3334
];
3435

src/Backend/OpenSSL.php

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@
3030
*
3131
* @package SimpleSAML\XMLSecurity\Backend
3232
*/
33-
final class OpenSSL implements EncryptionBackend, SignatureBackend
33+
final class OpenSSL implements EncryptionBackend, SignatureBackend, SignaturePadding
3434
{
3535
public const int AUTH_TAG_LEN = 16;
3636

3737

3838
// digital signature options
3939
protected string $digest;
4040

41+
// signature padding method
42+
protected int $sig_padding = OPENSSL_PKCS1_PADDING;
43+
4144
// asymmetric encryption options
42-
protected int $padding = OPENSSL_PKCS1_OAEP_PADDING;
45+
protected int $enc_padding = OPENSSL_PKCS1_OAEP_PADDING;
4346

4447
// symmetric encryption options
4548
protected string $cipher;
@@ -83,7 +86,7 @@ public function encrypt(
8386
}
8487

8588
$ciphertext = '';
86-
if (!$fn($plaintext, $ciphertext, $key->getMaterial(), $this->padding)) {
89+
if (!$fn($plaintext, $ciphertext, $key->getMaterial(), $this->enc_padding)) {
8790
throw new OpenSSLException('Cannot encrypt data');
8891
}
8992
return $ciphertext;
@@ -139,7 +142,7 @@ public function decrypt(
139142
}
140143

141144
$plaintext = '';
142-
if (!$fn($ciphertext, $plaintext, $key->getMaterial(), $this->padding)) {
145+
if (!$fn($ciphertext, $plaintext, $key->getMaterial(), $this->enc_padding)) {
143146
throw new OpenSSLException('Cannot decrypt data');
144147
}
145148
return $plaintext;
@@ -192,7 +195,7 @@ public function sign(
192195
KeyInterface $key,
193196
string $plaintext,
194197
): string {
195-
if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest)) {
198+
if (!openssl_sign($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding)) {
196199
throw new OpenSSLException('Cannot sign data');
197200
}
198201
return $signature;
@@ -214,7 +217,7 @@ public function verify(
214217
string $plaintext,
215218
string $signature,
216219
): bool {
217-
return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest) === 1;
220+
return openssl_verify($plaintext, $signature, $key->getMaterial(), $this->digest, $this->sig_padding) === 1;
218221
}
219222

220223

@@ -236,11 +239,11 @@ public function setCipher(string $cipher): void
236239
$this->cipher = $cipher;
237240
switch ($cipher) {
238241
case C::KEY_TRANSPORT_RSA_1_5:
239-
$this->padding = OPENSSL_PKCS1_PADDING;
242+
$this->enc_padding = OPENSSL_PKCS1_PADDING;
240243
break;
241244
case C::KEY_TRANSPORT_OAEP:
242245
case C::KEY_TRANSPORT_OAEP_MGF1P:
243-
$this->padding = OPENSSL_PKCS1_OAEP_PADDING;
246+
$this->enc_padding = OPENSSL_PKCS1_OAEP_PADDING;
244247
break;
245248
case C::BLOCK_ENC_AES128_GCM:
246249
case C::BLOCK_ENC_AES192_GCM:
@@ -255,6 +258,27 @@ public function setCipher(string $cipher): void
255258
}
256259

257260

261+
/**
262+
* Set the padding method to be used by this backend.
263+
*
264+
* @param string $algId The identifier of the signature algorithm.
265+
*/
266+
public function setSignaturePadding(string $algId): void
267+
{
268+
$padding = match ($algId) {
269+
C::SIG_RSA_PSS_SHA1 => OPENSSL_PKCS1_PSS_PADDING,
270+
C::SIG_RSA_PSS_SHA224 => OPENSSL_PKCS1_PSS_PADDING,
271+
C::SIG_RSA_PSS_SHA256 => OPENSSL_PKCS1_PSS_PADDING,
272+
C::SIG_RSA_PSS_SHA384 => OPENSSL_PKCS1_PSS_PADDING,
273+
C::SIG_RSA_PSS_SHA512 => OPENSSL_PKCS1_PSS_PADDING,
274+
275+
default => OPENSSL_PKCS1_PADDING,
276+
};
277+
278+
$this->sig_padding = $padding;
279+
}
280+
281+
258282
/**
259283
* Set the digest algorithm to be used by this backend.
260284
*

src/Backend/SignaturePadding.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\Backend;
6+
7+
/**
8+
* Interface for backends implementing signature padding.
9+
*
10+
* @package simplesamlphp/xml-security
11+
*/
12+
interface SignaturePadding
13+
{
14+
/**
15+
* Set the padding method to be used by this backend.
16+
*
17+
* @param string $algId The identifier of the signature algorithm.
18+
*/
19+
public function setSignaturePadding(string $algId): void;
20+
}

src/Constants.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class Constants extends \SimpleSAML\XML\Constants
102102
public const string C14N11_INCLUSIVE_WITHOUT_COMMENTS = 'http://www.w3.org/2006/12/xml-c14n11#WithComments';
103103

104104
/**
105-
* Signature algorithms
105+
* RSA Signature algorithms
106106
*/
107107
public const string SIG_RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
108108

@@ -116,6 +116,9 @@ class Constants extends \SimpleSAML\XML\Constants
116116

117117
public const string SIG_RSA_RIPEMD160 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160';
118118

119+
/**
120+
* HMAC Signature algorithms
121+
*/
119122
public const string SIG_HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
120123

121124
public const string SIG_HMAC_SHA224 = 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha224';
@@ -128,6 +131,19 @@ class Constants extends \SimpleSAML\XML\Constants
128131

129132
public const string SIG_HMAC_RIPEMD160 = 'http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160';
130133

134+
/**
135+
* RSA-PSS Signature algorithms
136+
*/
137+
public const string SIG_RSA_PSS_SHA1 = 'http://www.w3.org/2007/05/xmldsig-more#sha1-rsa-MGF1';
138+
139+
public const string SIG_RSA_PSS_SHA224 = 'http://www.w3.org/2007/05/xmldsig-more#sha224-rsa-MGF1';
140+
141+
public const string SIG_RSA_PSS_SHA256 = 'http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1';
142+
143+
public const string SIG_RSA_PSS_SHA384 = 'http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1';
144+
145+
public const string SIG_RSA_PSS_SHA512 = 'http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1';
146+
131147
/**
132148
* Encoding algorithms
133149
*/
@@ -232,6 +248,11 @@ class Constants extends \SimpleSAML\XML\Constants
232248
self::SIG_RSA_SHA384 => self::DIGEST_SHA384,
233249
self::SIG_RSA_SHA512 => self::DIGEST_SHA512,
234250
self::SIG_RSA_RIPEMD160 => self::DIGEST_RIPEMD160,
251+
self::SIG_RSA_PSS_SHA1 => self::DIGEST_SHA1,
252+
self::SIG_RSA_PSS_SHA224 => self::DIGEST_SHA224,
253+
self::SIG_RSA_PSS_SHA256 => self::DIGEST_SHA256,
254+
self::SIG_RSA_PSS_SHA384 => self::DIGEST_SHA384,
255+
self::SIG_RSA_PSS_SHA512 => self::DIGEST_SHA512,
235256
];
236257

237258
/** @var array<string, string> */

src/XML/SignedElementTrait.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ public function verify(?SignatureAlgorithmInterface $verifier = null): SignedEle
281281
$verifier->getAlgorithmId(),
282282
$algId,
283283
'Algorithm provided in key does not match algorithm used in signature.',
284+
SignatureVerificationFailedException::class,
284285
);
285286

286287
return $this->verifyInternal($verifier);
@@ -303,7 +304,6 @@ public function verify(?SignatureAlgorithmInterface $verifier = null): SignedEle
303304
"-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----",
304305
strval($data->getContent()),
305306
);
306-
307307
$cert = new Key\X509Certificate(PEM::fromString($cert));
308308
$verifier = $factory->getAlgorithm($algId->getValue(), $cert->getPublicKey());
309309

tests/XML/SignableElementTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,57 @@ public function testSigningWithDifferentRoot(): void
263263
);
264264
$customSignable->toXML($doc->documentElement);
265265
}
266+
267+
268+
/**
269+
* This test ensures we can sign using RSA-PSS algorithms and verify the signed element again.
270+
*/
271+
public function testSigningAndVerifyingRsaPssSha256(): void
272+
{
273+
$customSignable = CustomSignable::fromXML(
274+
self::$xmlRepresentation->documentElement,
275+
);
276+
277+
$factory = new SignatureAlgorithmFactory();
278+
279+
$signer = $factory->getAlgorithm(
280+
C::SIG_RSA_PSS_SHA256,
281+
self::$key,
282+
);
283+
284+
$keyInfo = new KeyInfo([
285+
new X509Data([
286+
new X509Certificate(
287+
Base64BinaryValue::fromString(self::$certificate),
288+
),
289+
]),
290+
]);
291+
292+
$customSignable->sign(
293+
$signer,
294+
C::C14N_EXCLUSIVE_WITHOUT_COMMENTS,
295+
$keyInfo,
296+
);
297+
298+
$verified = CustomSignable::fromXML($customSignable->toXML())->verify();
299+
300+
$signature = $customSignable->getSignature();
301+
$this->assertEquals(
302+
C::SIG_RSA_PSS_SHA256,
303+
$signature
304+
->getSignedInfo()
305+
->getSignatureMethod()
306+
->getAlgorithm()
307+
->getValue(),
308+
);
309+
310+
$this->assertInstanceOf(CustomSignable::class, $verified);
311+
$this->assertFalse($verified->isSigned());
312+
313+
$this->assertEquals(
314+
'<ssp:CustomSignable xmlns:ssp="urn:x-simplesamlphp:namespace">'
315+
. '<ssp:Chunk>Some</ssp:Chunk></ssp:CustomSignable>',
316+
strval($verified),
317+
);
318+
}
266319
}

0 commit comments

Comments
 (0)