Skip to content

Commit 9217756

Browse files
CCF [bot]maxtropets
CCF [bot]
andauthored
[release/5.x] Cherry pick: Replace RSASSA-PKCS1-v1_5 with RSA-PSS in crypto API (#6415) (#6425)
Co-authored-by: Max <[email protected]>
1 parent 6006f8b commit 9217756

File tree

14 files changed

+163
-42
lines changed

14 files changed

+163
-42
lines changed

CHANGELOG.md

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
[5.0.2]: https://github.com/microsoft/CCF/releases/tag/ccf-5.0.2
1111

12+
### Developer API
13+
14+
#### C++
15+
16+
- `RSAKeyPair::sign` and `RSAKeyPair::verify` now use `RSA-PSS` instead of `RSASSA-PKCS1-v1_5`.
17+
- Users can specify `salt_length` (defaulted to `0`).
18+
19+
#### TypeScript/JavaScript
20+
21+
- `ccfapp.crypto.sign()` and `ccfapp.crypto.verifySignature()` no longer support `RSASSA-PKCS1-v1_5`, instead `RSA-PSS` has been added.
22+
- `SigningAlgorithm` has been extended with optional `saltLength`, defaulted to `0` if not passed.
23+
1224
### Bug Fixes
1325

1426
- The `/tx` endpoint returns more accurate error messages for incorrectly formed transactions ids (#6359).

include/ccf/crypto/rsa_key_pair.h

+9-4
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,31 @@ namespace ccf::crypto
5555
virtual std::vector<uint8_t> public_key_der() const = 0;
5656

5757
virtual std::vector<uint8_t> sign(
58-
std::span<const uint8_t> d, MDType md_type = MDType::NONE) const = 0;
58+
std::span<const uint8_t> d,
59+
MDType md_type = MDType::NONE,
60+
size_t salt_length = 0) const = 0;
5961

6062
virtual bool verify(
6163
const uint8_t* contents,
6264
size_t contents_size,
6365
const uint8_t* signature,
6466
size_t signature_size,
65-
MDType md_type = MDType::NONE) = 0;
67+
MDType md_type = MDType::NONE,
68+
size_t salt_length = 0) = 0;
6669

6770
virtual bool verify(
6871
const std::vector<uint8_t>& contents,
6972
const std::vector<uint8_t>& signature,
70-
MDType md_type = MDType::NONE)
73+
MDType md_type = MDType::NONE,
74+
size_t salt_length = 0)
7175
{
7276
return verify(
7377
contents.data(),
7478
contents.size(),
7579
signature.data(),
7680
signature.size(),
77-
md_type);
81+
md_type,
82+
salt_length);
7883
}
7984

8085
virtual JsonWebKeyRSAPrivate private_key_jwk_rsa(

include/ccf/crypto/rsa_public_key.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ namespace ccf::crypto
8181
size_t contents_size,
8282
const uint8_t* signature,
8383
size_t signature_size,
84-
MDType md_type = MDType::NONE) = 0;
84+
MDType md_type = MDType::NONE,
85+
size_t salt_legth = 0) = 0;
8586

8687
struct Components
8788
{

js/ccf-app/src/global.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,22 @@ export interface CryptoKeyPair {
231231
publicKey: string;
232232
}
233233

234-
export type AlgorithmName = "RSASSA-PKCS1-v1_5" | "ECDSA" | "EdDSA" | "HMAC";
234+
export type AlgorithmName = "RSA-PSS" | "ECDSA" | "EdDSA" | "HMAC";
235235

236236
export type DigestAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
237237

238238
export interface SigningAlgorithm {
239239
name: AlgorithmName;
240240

241241
/**
242-
* Digest algorithm. It's necessary for "RSASSA-PKCS1-v1_5", "ECDSA", and "HMAC"
242+
* Digest algorithm. It's necessary for "RSA-PSS", "ECDSA", and "HMAC"
243243
*/
244244
hash?: DigestAlgorithm;
245+
246+
/**
247+
* Salt length, necessary for "RSA-PSS".
248+
*/
249+
saltLength?: number;
245250
}
246251

247252
/**

js/ccf-app/src/polyfill.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ class CCFPolyfill implements CCF {
142142
let padding = undefined;
143143
const privKey = jscrypto.createPrivateKey(key);
144144
if (privKey.asymmetricKeyType == "rsa") {
145-
if (algorithm.name === "RSASSA-PKCS1-v1_5") {
146-
padding = jscrypto.constants.RSA_PKCS1_PADDING;
145+
if (algorithm.name === "RSA-PSS") {
146+
padding = jscrypto.constants.RSA_PKCS1_PSS_PADDING;
147147
} else {
148148
throw new Error("incompatible signing algorithm for given key type");
149149
}
@@ -168,6 +168,7 @@ class CCFPolyfill implements CCF {
168168
key: privKey,
169169
dsaEncoding: "ieee-p1363",
170170
padding: padding,
171+
saltLength: algorithm.saltLength ?? 0,
171172
});
172173
},
173174
verifySignature(
@@ -179,8 +180,8 @@ class CCFPolyfill implements CCF {
179180
let padding = undefined;
180181
const pubKey = jscrypto.createPublicKey(key);
181182
if (pubKey.asymmetricKeyType == "rsa") {
182-
if (algorithm.name === "RSASSA-PKCS1-v1_5") {
183-
padding = jscrypto.constants.RSA_PKCS1_PADDING;
183+
if (algorithm.name === "RSA-PSS") {
184+
padding = jscrypto.constants.RSA_PKCS1_PSS_PADDING;
184185
} else {
185186
throw new Error("incompatible signing algorithm for given key type");
186187
}
@@ -211,6 +212,7 @@ class CCFPolyfill implements CCF {
211212
key: pubKey,
212213
dsaEncoding: "ieee-p1363",
213214
padding: padding,
215+
saltLength: algorithm.saltLength ?? 0,
214216
},
215217
new Uint8Array(signature),
216218
);

js/ccf-app/test/polyfill.test.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe("polyfill", function () {
167167
});
168168
});
169169
describe("sign", function () {
170-
it("performs RSASSA-PKCS1-v1_5 sign correctly", function () {
170+
it("performs RSA-PSS sign correctly", function () {
171171
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
172172
modulusLength: 2048,
173173
publicKeyEncoding: {
@@ -182,7 +182,7 @@ describe("polyfill", function () {
182182
const data = ccf.strToBuf("foo");
183183
const signature = ccf.crypto.sign(
184184
{
185-
name: "RSASSA-PKCS1-v1_5",
185+
name: "RSA-PSS",
186186
hash: "SHA-256",
187187
},
188188
privateKey,
@@ -198,6 +198,7 @@ describe("polyfill", function () {
198198
{
199199
key: publicKey,
200200
dsaEncoding: "ieee-p1363",
201+
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
201202
},
202203
new Uint8Array(signature),
203204
),
@@ -208,7 +209,7 @@ describe("polyfill", function () {
208209
assert.isTrue(
209210
ccf.crypto.verifySignature(
210211
{
211-
name: "RSASSA-PKCS1-v1_5",
212+
name: "RSA-PSS",
212213
hash: "SHA-256",
213214
},
214215
publicKey,
@@ -392,20 +393,21 @@ describe("polyfill", function () {
392393
});
393394
});
394395
describe("verifySignature", function () {
395-
it("performs RSASSA-PKCS1-v1_5 validation correctly", function () {
396+
it("performs RSA-PSS validation correctly", function () {
396397
const { cert, publicKey, privateKey } = generateSelfSignedCert();
397398
const signer = crypto.createSign("sha256");
398399
const data = ccf.strToBuf("foo");
399400
signer.update(new Uint8Array(data));
400401
signer.end();
401402
const signature = signer.sign({
402403
key: crypto.createPrivateKey(privateKey),
403-
padding: crypto.constants.RSA_PKCS1_PADDING,
404+
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
405+
saltLength: 0,
404406
});
405407
assert.isTrue(
406408
ccf.crypto.verifySignature(
407409
{
408-
name: "RSASSA-PKCS1-v1_5",
410+
name: "RSA-PSS",
409411
hash: "SHA-256",
410412
},
411413
cert,
@@ -416,7 +418,7 @@ describe("polyfill", function () {
416418
assert.isTrue(
417419
ccf.crypto.verifySignature(
418420
{
419-
name: "RSASSA-PKCS1-v1_5",
421+
name: "RSA-PSS",
420422
hash: "SHA-256",
421423
},
422424
publicKey,
@@ -427,7 +429,7 @@ describe("polyfill", function () {
427429
assert.isNotTrue(
428430
ccf.crypto.verifySignature(
429431
{
430-
name: "RSASSA-PKCS1-v1_5",
432+
name: "RSA-PSS",
431433
hash: "SHA-256",
432434
},
433435
cert,
@@ -494,7 +496,7 @@ describe("polyfill", function () {
494496
assert.throws(() =>
495497
ccf.crypto.verifySignature(
496498
{
497-
name: "RSASSA-PKCS1-v1_5",
499+
name: "RSA-PSS",
498500
hash: "SHA-256",
499501
},
500502
publicKey,
@@ -543,7 +545,7 @@ describe("polyfill", function () {
543545
assert.throws(() =>
544546
ccf.crypto.verifySignature(
545547
{
546-
name: "RSASSA-PKCS1-v1_5",
548+
name: "RSA-PSS",
547549
hash: "SHA-256",
548550
},
549551
publicKey,

src/crypto/openssl/rsa_key_pair.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,14 @@ namespace ccf::crypto
205205
}
206206

207207
std::vector<uint8_t> RSAKeyPair_OpenSSL::sign(
208-
std::span<const uint8_t> d, MDType md_type) const
208+
std::span<const uint8_t> d, MDType md_type, size_t salt_length) const
209209
{
210210
std::vector<uint8_t> r(2048);
211211
auto hash = OpenSSLHashProvider().Hash(d.data(), d.size(), md_type);
212212
Unique_EVP_PKEY_CTX pctx(key);
213213
CHECK1(EVP_PKEY_sign_init(pctx));
214+
CHECK1(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
215+
CHECK1(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, salt_length));
214216
CHECK1(EVP_PKEY_CTX_set_signature_md(pctx, get_md_type(md_type)));
215217
size_t olen = r.size();
216218
CHECK1(EVP_PKEY_sign(pctx, r.data(), &olen, hash.data(), hash.size()));
@@ -223,10 +225,11 @@ namespace ccf::crypto
223225
size_t contents_size,
224226
const uint8_t* signature,
225227
size_t signature_size,
226-
MDType md_type)
228+
MDType md_type,
229+
size_t salt_length)
227230
{
228231
return RSAPublicKey_OpenSSL::verify(
229-
contents, contents_size, signature, signature_size, md_type);
232+
contents, contents_size, signature, signature_size, md_type, salt_length);
230233
}
231234

232235
JsonWebKeyRSAPrivate RSAKeyPair_OpenSSL::private_key_jwk_rsa(

src/crypto/openssl/rsa_key_pair.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ namespace ccf::crypto
3636
virtual std::vector<uint8_t> public_key_der() const override;
3737

3838
virtual std::vector<uint8_t> sign(
39-
std::span<const uint8_t> d, MDType md_type = MDType::NONE) const override;
39+
std::span<const uint8_t> d,
40+
MDType md_type = MDType::NONE,
41+
size_t salt_length = 0) const override;
4042

4143
virtual bool verify(
4244
const uint8_t* contents,
4345
size_t contents_size,
4446
const uint8_t* signature,
4547
size_t signature_size,
46-
MDType md_type = MDType::NONE) override;
48+
MDType md_type = MDType::NONE,
49+
size_t salt_length = 0) override;
4750

4851
virtual JsonWebKeyRSAPrivate private_key_jwk_rsa(
4952
const std::optional<std::string>& kid = std::nullopt) const override;

src/crypto/openssl/rsa_public_key.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,14 @@ namespace ccf::crypto
195195
size_t contents_size,
196196
const uint8_t* signature,
197197
size_t signature_size,
198-
MDType md_type)
198+
MDType md_type,
199+
size_t salt_length)
199200
{
200201
auto hash = OpenSSLHashProvider().Hash(contents, contents_size, md_type);
201202
Unique_EVP_PKEY_CTX pctx(key);
202203
CHECK1(EVP_PKEY_verify_init(pctx));
204+
CHECK1(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
205+
CHECK1(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, salt_length));
203206
CHECK1(EVP_PKEY_CTX_set_signature_md(pctx, get_md_type(md_type)));
204207
return EVP_PKEY_verify(
205208
pctx, signature, signature_size, hash.data(), hash.size()) == 1;

src/crypto/openssl/rsa_public_key.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ namespace ccf::crypto
5252
size_t contents_size,
5353
const uint8_t* signature,
5454
size_t signature_size,
55-
MDType md_type = MDType::NONE) override;
55+
MDType md_type = MDType::NONE,
56+
size_t salt_length = 0) override;
5657

5758
virtual Components components() const override;
5859

src/crypto/test/crypto.cpp

+44
Original file line numberDiff line numberDiff line change
@@ -931,4 +931,48 @@ TEST_CASE("Incremental hash")
931931
}
932932
}
933933
ccf::crypto::openssl_sha256_shutdown();
934+
}
935+
936+
TEST_CASE("Sign and verify with RSA key")
937+
{
938+
const auto kp = ccf::crypto::make_rsa_key_pair();
939+
const auto pub = ccf::crypto::make_rsa_public_key(kp->public_key_pem());
940+
const auto mdtype = ccf::crypto::MDType::SHA256;
941+
vector<uint8_t> contents(contents_.begin(), contents_.end());
942+
943+
{
944+
constexpr size_t salt_length = 0;
945+
const auto sig = kp->sign(contents, mdtype, salt_length);
946+
REQUIRE(pub->verify(
947+
contents.data(),
948+
contents.size(),
949+
sig.data(),
950+
sig.size(),
951+
mdtype,
952+
salt_length));
953+
}
954+
955+
{
956+
constexpr size_t sign_salt_length = 0, verify_salt_legth = 32;
957+
const auto sig = kp->sign(contents, mdtype, sign_salt_length);
958+
REQUIRE(!pub->verify(
959+
contents.data(),
960+
contents.size(),
961+
sig.data(),
962+
sig.size(),
963+
mdtype,
964+
verify_salt_legth));
965+
}
966+
967+
{
968+
constexpr size_t sign_salt_length = 32, verify_salt_legth = 32;
969+
const auto sig = kp->sign(contents, mdtype, sign_salt_length);
970+
REQUIRE(pub->verify(
971+
contents.data(),
972+
contents.size(),
973+
sig.data(),
974+
sig.size(),
975+
mdtype,
976+
verify_salt_legth));
977+
}
934978
}

0 commit comments

Comments
 (0)