[release/5.x] Cherry pick: Replace RSASSA-PKCS1-v1_5 with RSA-PSS in crypto API (#6415) (#6425)

Co-authored-by: Max <maxtropets@microsoft.com>
This commit is contained in:
CCF [bot] 2024-08-05 14:53:11 +01:00 коммит произвёл GitHub
Родитель 6006f8b9a0
Коммит 9217756d92
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
14 изменённых файлов: 162 добавлений и 41 удалений

Просмотреть файл

@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
[5.0.2]: https://github.com/microsoft/CCF/releases/tag/ccf-5.0.2
### Developer API
#### C++
- `RSAKeyPair::sign` and `RSAKeyPair::verify` now use `RSA-PSS` instead of `RSASSA-PKCS1-v1_5`.
- Users can specify `salt_length` (defaulted to `0`).
#### TypeScript/JavaScript
- `ccfapp.crypto.sign()` and `ccfapp.crypto.verifySignature()` no longer support `RSASSA-PKCS1-v1_5`, instead `RSA-PSS` has been added.
- `SigningAlgorithm` has been extended with optional `saltLength`, defaulted to `0` if not passed.
### Bug Fixes
- The `/tx` endpoint returns more accurate error messages for incorrectly formed transactions ids (#6359).

Просмотреть файл

@ -55,26 +55,31 @@ namespace ccf::crypto
virtual std::vector<uint8_t> public_key_der() const = 0;
virtual std::vector<uint8_t> sign(
std::span<const uint8_t> d, MDType md_type = MDType::NONE) const = 0;
std::span<const uint8_t> d,
MDType md_type = MDType::NONE,
size_t salt_length = 0) const = 0;
virtual bool verify(
const uint8_t* contents,
size_t contents_size,
const uint8_t* signature,
size_t signature_size,
MDType md_type = MDType::NONE) = 0;
MDType md_type = MDType::NONE,
size_t salt_length = 0) = 0;
virtual bool verify(
const std::vector<uint8_t>& contents,
const std::vector<uint8_t>& signature,
MDType md_type = MDType::NONE)
MDType md_type = MDType::NONE,
size_t salt_length = 0)
{
return verify(
contents.data(),
contents.size(),
signature.data(),
signature.size(),
md_type);
md_type,
salt_length);
}
virtual JsonWebKeyRSAPrivate private_key_jwk_rsa(

Просмотреть файл

@ -81,7 +81,8 @@ namespace ccf::crypto
size_t contents_size,
const uint8_t* signature,
size_t signature_size,
MDType md_type = MDType::NONE) = 0;
MDType md_type = MDType::NONE,
size_t salt_legth = 0) = 0;
struct Components
{

Просмотреть файл

@ -231,7 +231,7 @@ export interface CryptoKeyPair {
publicKey: string;
}
export type AlgorithmName = "RSASSA-PKCS1-v1_5" | "ECDSA" | "EdDSA" | "HMAC";
export type AlgorithmName = "RSA-PSS" | "ECDSA" | "EdDSA" | "HMAC";
export type DigestAlgorithm = "SHA-256" | "SHA-384" | "SHA-512";
@ -239,9 +239,14 @@ export interface SigningAlgorithm {
name: AlgorithmName;
/**
* Digest algorithm. It's necessary for "RSASSA-PKCS1-v1_5", "ECDSA", and "HMAC"
* Digest algorithm. It's necessary for "RSA-PSS", "ECDSA", and "HMAC"
*/
hash?: DigestAlgorithm;
/**
* Salt length, necessary for "RSA-PSS".
*/
saltLength?: number;
}
/**

Просмотреть файл

@ -142,8 +142,8 @@ class CCFPolyfill implements CCF {
let padding = undefined;
const privKey = jscrypto.createPrivateKey(key);
if (privKey.asymmetricKeyType == "rsa") {
if (algorithm.name === "RSASSA-PKCS1-v1_5") {
padding = jscrypto.constants.RSA_PKCS1_PADDING;
if (algorithm.name === "RSA-PSS") {
padding = jscrypto.constants.RSA_PKCS1_PSS_PADDING;
} else {
throw new Error("incompatible signing algorithm for given key type");
}
@ -168,6 +168,7 @@ class CCFPolyfill implements CCF {
key: privKey,
dsaEncoding: "ieee-p1363",
padding: padding,
saltLength: algorithm.saltLength ?? 0,
});
},
verifySignature(
@ -179,8 +180,8 @@ class CCFPolyfill implements CCF {
let padding = undefined;
const pubKey = jscrypto.createPublicKey(key);
if (pubKey.asymmetricKeyType == "rsa") {
if (algorithm.name === "RSASSA-PKCS1-v1_5") {
padding = jscrypto.constants.RSA_PKCS1_PADDING;
if (algorithm.name === "RSA-PSS") {
padding = jscrypto.constants.RSA_PKCS1_PSS_PADDING;
} else {
throw new Error("incompatible signing algorithm for given key type");
}
@ -211,6 +212,7 @@ class CCFPolyfill implements CCF {
key: pubKey,
dsaEncoding: "ieee-p1363",
padding: padding,
saltLength: algorithm.saltLength ?? 0,
},
new Uint8Array(signature),
);

Просмотреть файл

@ -167,7 +167,7 @@ describe("polyfill", function () {
});
});
describe("sign", function () {
it("performs RSASSA-PKCS1-v1_5 sign correctly", function () {
it("performs RSA-PSS sign correctly", function () {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: {
@ -182,7 +182,7 @@ describe("polyfill", function () {
const data = ccf.strToBuf("foo");
const signature = ccf.crypto.sign(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
privateKey,
@ -198,6 +198,7 @@ describe("polyfill", function () {
{
key: publicKey,
dsaEncoding: "ieee-p1363",
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
},
new Uint8Array(signature),
),
@ -208,7 +209,7 @@ describe("polyfill", function () {
assert.isTrue(
ccf.crypto.verifySignature(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
publicKey,
@ -392,7 +393,7 @@ describe("polyfill", function () {
});
});
describe("verifySignature", function () {
it("performs RSASSA-PKCS1-v1_5 validation correctly", function () {
it("performs RSA-PSS validation correctly", function () {
const { cert, publicKey, privateKey } = generateSelfSignedCert();
const signer = crypto.createSign("sha256");
const data = ccf.strToBuf("foo");
@ -400,12 +401,13 @@ describe("polyfill", function () {
signer.end();
const signature = signer.sign({
key: crypto.createPrivateKey(privateKey),
padding: crypto.constants.RSA_PKCS1_PADDING,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: 0,
});
assert.isTrue(
ccf.crypto.verifySignature(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
cert,
@ -416,7 +418,7 @@ describe("polyfill", function () {
assert.isTrue(
ccf.crypto.verifySignature(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
publicKey,
@ -427,7 +429,7 @@ describe("polyfill", function () {
assert.isNotTrue(
ccf.crypto.verifySignature(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
cert,
@ -494,7 +496,7 @@ describe("polyfill", function () {
assert.throws(() =>
ccf.crypto.verifySignature(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
publicKey,
@ -543,7 +545,7 @@ describe("polyfill", function () {
assert.throws(() =>
ccf.crypto.verifySignature(
{
name: "RSASSA-PKCS1-v1_5",
name: "RSA-PSS",
hash: "SHA-256",
},
publicKey,

Просмотреть файл

@ -205,12 +205,14 @@ namespace ccf::crypto
}
std::vector<uint8_t> RSAKeyPair_OpenSSL::sign(
std::span<const uint8_t> d, MDType md_type) const
std::span<const uint8_t> d, MDType md_type, size_t salt_length) const
{
std::vector<uint8_t> r(2048);
auto hash = OpenSSLHashProvider().Hash(d.data(), d.size(), md_type);
Unique_EVP_PKEY_CTX pctx(key);
CHECK1(EVP_PKEY_sign_init(pctx));
CHECK1(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
CHECK1(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, salt_length));
CHECK1(EVP_PKEY_CTX_set_signature_md(pctx, get_md_type(md_type)));
size_t olen = r.size();
CHECK1(EVP_PKEY_sign(pctx, r.data(), &olen, hash.data(), hash.size()));
@ -223,10 +225,11 @@ namespace ccf::crypto
size_t contents_size,
const uint8_t* signature,
size_t signature_size,
MDType md_type)
MDType md_type,
size_t salt_length)
{
return RSAPublicKey_OpenSSL::verify(
contents, contents_size, signature, signature_size, md_type);
contents, contents_size, signature, signature_size, md_type, salt_length);
}
JsonWebKeyRSAPrivate RSAKeyPair_OpenSSL::private_key_jwk_rsa(

Просмотреть файл

@ -36,14 +36,17 @@ namespace ccf::crypto
virtual std::vector<uint8_t> public_key_der() const override;
virtual std::vector<uint8_t> sign(
std::span<const uint8_t> d, MDType md_type = MDType::NONE) const override;
std::span<const uint8_t> d,
MDType md_type = MDType::NONE,
size_t salt_length = 0) const override;
virtual bool verify(
const uint8_t* contents,
size_t contents_size,
const uint8_t* signature,
size_t signature_size,
MDType md_type = MDType::NONE) override;
MDType md_type = MDType::NONE,
size_t salt_length = 0) override;
virtual JsonWebKeyRSAPrivate private_key_jwk_rsa(
const std::optional<std::string>& kid = std::nullopt) const override;

Просмотреть файл

@ -195,11 +195,14 @@ namespace ccf::crypto
size_t contents_size,
const uint8_t* signature,
size_t signature_size,
MDType md_type)
MDType md_type,
size_t salt_length)
{
auto hash = OpenSSLHashProvider().Hash(contents, contents_size, md_type);
Unique_EVP_PKEY_CTX pctx(key);
CHECK1(EVP_PKEY_verify_init(pctx));
CHECK1(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING));
CHECK1(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, salt_length));
CHECK1(EVP_PKEY_CTX_set_signature_md(pctx, get_md_type(md_type)));
return EVP_PKEY_verify(
pctx, signature, signature_size, hash.data(), hash.size()) == 1;

Просмотреть файл

@ -52,7 +52,8 @@ namespace ccf::crypto
size_t contents_size,
const uint8_t* signature,
size_t signature_size,
MDType md_type = MDType::NONE) override;
MDType md_type = MDType::NONE,
size_t salt_length = 0) override;
virtual Components components() const override;

Просмотреть файл

@ -931,4 +931,48 @@ TEST_CASE("Incremental hash")
}
}
ccf::crypto::openssl_sha256_shutdown();
}
TEST_CASE("Sign and verify with RSA key")
{
const auto kp = ccf::crypto::make_rsa_key_pair();
const auto pub = ccf::crypto::make_rsa_public_key(kp->public_key_pem());
const auto mdtype = ccf::crypto::MDType::SHA256;
vector<uint8_t> contents(contents_.begin(), contents_.end());
{
constexpr size_t salt_length = 0;
const auto sig = kp->sign(contents, mdtype, salt_length);
REQUIRE(pub->verify(
contents.data(),
contents.size(),
sig.data(),
sig.size(),
mdtype,
salt_length));
}
{
constexpr size_t sign_salt_length = 0, verify_salt_legth = 32;
const auto sig = kp->sign(contents, mdtype, sign_salt_length);
REQUIRE(!pub->verify(
contents.data(),
contents.size(),
sig.data(),
sig.size(),
mdtype,
verify_salt_legth));
}
{
constexpr size_t sign_salt_length = 32, verify_salt_legth = 32;
const auto sig = kp->sign(contents, mdtype, sign_salt_length);
REQUIRE(pub->verify(
contents.data(),
contents.size(),
sig.data(),
sig.size(),
mdtype,
verify_salt_legth));
}
}

Просмотреть файл

@ -884,10 +884,19 @@ namespace ccf::js::extensions
sig_der, key_pair->get_curve_id());
return JS_NewArrayBufferCopy(ctx, sig.data(), sig.size());
}
else if (algo_name == "RSASSA-PKCS1-v1_5")
else if (algo_name == "RSA-PSS")
{
auto key_pair = ccf::crypto::make_rsa_key_pair(key);
auto sig = key_pair->sign(contents, mdtype);
int64_t salt_length{};
std::ignore = JS_ToInt64(
jsctx,
&salt_length,
jsctx.get_property(algorithm, "saltLength").val);
auto sig =
key_pair->sign(contents, mdtype, static_cast<size_t>(salt_length));
return JS_NewArrayBufferCopy(ctx, sig.data(), sig.size());
}
else if (algo_name == "HMAC")
@ -900,8 +909,8 @@ namespace ccf::js::extensions
{
return JS_ThrowRangeError(
ctx,
"Unsupported signing algorithm, supported: RSASSA-PKCS1-v1_5, "
"ECDSA, EdDSA, HMAC");
"Unsupported signing algorithm, supported: RSA-PSS, ECDSA, EdDSA, "
"HMAC");
}
}
catch (const std::exception& ex)
@ -1010,12 +1019,12 @@ namespace ccf::js::extensions
ctx, "Unsupported hash algorithm, supported: SHA-256");
}
if (algo_name != "RSASSA-PKCS1-v1_5" && algo_name != "ECDSA")
if (algo_name != "RSA-PSS" && algo_name != "ECDSA")
{
return JS_ThrowRangeError(
ctx,
"Unsupported signing algorithm, supported: RSASSA-PKCS1-v1_5, "
"ECDSA, EdDSA");
"Unsupported signing algorithm, supported: RSA-PSS, ECDSA, "
"EdDSA");
}
std::vector<uint8_t> sig(signature, signature + signature_size);
@ -1035,13 +1044,29 @@ namespace ccf::js::extensions
valid =
verifier->verify(data, data_size, sig.data(), sig.size(), mdtype);
}
else
else if (algo_name == "ECDSA")
{
auto public_key = ccf::crypto::make_public_key(key);
valid =
public_key->verify(data, data_size, sig.data(), sig.size(), mdtype);
}
else
{
int64_t salt_length{};
std::ignore = JS_ToInt64(
jsctx,
&salt_length,
jsctx.get_property(algorithm, "saltLength").val);
auto public_key = ccf::crypto::make_rsa_public_key(key);
valid = public_key->verify(
data,
data_size,
sig.data(),
sig.size(),
mdtype,
static_cast<size_t>(salt_length));
}
return JS_NewBool(ctx, valid);
}
catch (const std::exception& ex)

Просмотреть файл

@ -213,8 +213,15 @@ def sign(algorithm: dict, key_pem: str, data: bytes) -> bytes:
else:
raise ValueError("Unsupported hash algorithm")
if isinstance(key, rsa.RSAPrivateKey):
if algorithm["name"] == "RSASSA-PKCS1-v1_5":
return key.sign(data, padding.PKCS1v15(), hash_alg)
if algorithm["name"] == "RSA-PSS":
return key.sign(
data,
padding=padding.PSS(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
salt_length=algorithm.get("saltLength", 0),
),
algorithm=hash_alg,
)
else:
raise ValueError("Unsupported signing algorithm")
elif isinstance(key, ec.EllipticCurvePrivateKey):
@ -254,7 +261,15 @@ def verify_signature(algorithm: dict, signature: bytes, data: bytes, key_pub_pem
if isinstance(key_pub, rsa.RSAPublicKey):
if algorithm["hash"] != "SHA-256":
raise ValueError("Unsupported hash algorithm")
key_pub.verify(signature, data, padding.PKCS1v15(), hashes.SHA256())
key_pub.verify(
signature,
data,
padding.PSS(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
salt_length=algorithm.get("saltLength", 0),
),
hashes.SHA256(),
)
elif isinstance(key_pub, ec.EllipticCurvePublicKey):
if algorithm["hash"] != "SHA-256":
raise ValueError("Unsupported hash algorithm")

Просмотреть файл

@ -438,7 +438,7 @@ def test_npm_app(network, args):
# Test RSA signing + verification
key_priv_pem, key_pub_pem = infra.crypto.generate_rsa_keypair(2048)
algorithm = {"name": "RSASSA-PKCS1-v1_5", "hash": "SHA-256"}
algorithm = {"name": "RSA-PSS", "hash": "SHA-256", "saltLength": 32}
data = rand_bytes(random.randint(2, 50))
r = c.post(
"/app/sign",
@ -551,7 +551,7 @@ def test_npm_app(network, args):
pass
key_priv_pem, key_pub_pem = infra.crypto.generate_rsa_keypair(2048)
algorithm = {"name": "RSASSA-PKCS1-v1_5", "hash": "SHA-256"}
algorithm = {"name": "RSA-PSS", "hash": "SHA-256", "saltLength": 32}
signature = infra.crypto.sign(algorithm, key_priv_pem, data)
r = c.post(
"/app/verifySignature",