зеркало из https://github.com/microsoft/CCF.git
Co-authored-by: Max <maxtropets@microsoft.com>
This commit is contained in:
Родитель
6006f8b9a0
Коммит
9217756d92
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -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",
|
||||
|
|
Загрузка…
Ссылка в новой задаче