From b55ab81fb9f7d61df82af5d5b3227d2f6d401d31 Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Fri, 17 Dec 2021 21:44:35 -0800 Subject: [PATCH] Use libcrypto signing methods in compliance with FIPS 140-3 (#3142) --- CMakeLists.txt | 13 + crypto/s2n_ecdsa.c | 2 + crypto/s2n_evp.h | 7 + crypto/s2n_evp_signing.c | 153 ++++++++++++ crypto/s2n_evp_signing.h | 29 +++ crypto/s2n_hash.c | 12 +- crypto/s2n_hash.h | 1 + crypto/s2n_rsa.c | 2 + crypto/s2n_rsa_pss.c | 2 + crypto/s2n_rsa_signing.c | 28 +-- error/s2n_errno.c | 2 +- error/s2n_errno.h | 1 + s2n.mk | 6 + tests/features/evp_md_ctx_set_pkey_ctx.c | 20 ++ tests/unit/s2n_ecdsa_test.c | 2 +- tests/unit/s2n_evp_signing_test.c | 292 +++++++++++++++++++++++ 16 files changed, 544 insertions(+), 28 deletions(-) create mode 100644 crypto/s2n_evp_signing.c create mode 100644 crypto/s2n_evp_signing.h create mode 100644 tests/features/evp_md_ctx_set_pkey_ctx.c create mode 100644 tests/unit/s2n_evp_signing_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c50bea15844..127f9394880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -422,6 +422,19 @@ if (LIBCRYPTO_SUPPORTS_EVP_MD5_SHA1_HASH) target_compile_options(${PROJECT_NAME} PUBLIC -DS2N_LIBCRYPTO_SUPPORTS_EVP_MD5_SHA1_HASH) endif() +# Determine if EVP_MD_CTX_set_pkey_ctx is available in libcrypto +try_compile( + LIBCRYPTO_SUPPORTS_EVP_MD_CTX_SET_PKEY_CTX + ${CMAKE_BINARY_DIR} + SOURCES "${CMAKE_CURRENT_LIST_DIR}/tests/features/evp_md_ctx_set_pkey_ctx.c" + LINK_LIBRARIES crypto ${OS_LIBS} + CMAKE_FLAGS + "-DINCLUDE_DIRECTORIES=$" +) +if (LIBCRYPTO_SUPPORTS_EVP_MD_CTX_SET_PKEY_CTX) + target_compile_options(${PROJECT_NAME} PUBLIC -DS2N_LIBCRYPTO_SUPPORTS_EVP_MD_CTX_SET_PKEY_CTX) +endif() + if (S2N_INTERN_LIBCRYPTO) if (NOT LibCrypto_STATIC_LIBRARY) message(FATAL_ERROR "libcrypto interning requires a static build of libcrypto.a to be available") diff --git a/crypto/s2n_ecdsa.c b/crypto/s2n_ecdsa.c index 39c6eae7ac9..23050ffea46 100644 --- a/crypto/s2n_ecdsa.c +++ b/crypto/s2n_ecdsa.c @@ -27,6 +27,7 @@ #include "crypto/s2n_ecdsa.h" #include "crypto/s2n_ecc_evp.h" +#include "crypto/s2n_evp_signing.h" #include "crypto/s2n_hash.h" #include "crypto/s2n_openssl.h" #include "crypto/s2n_pkey.h" @@ -183,6 +184,7 @@ int s2n_ecdsa_pkey_init(struct s2n_pkey *pkey) { pkey->match = &s2n_ecdsa_keys_match; pkey->free = &s2n_ecdsa_key_free; pkey->check_key = &s2n_ecdsa_check_key_exists; + POSIX_GUARD_RESULT(s2n_evp_signing_set_pkey_overrides(pkey)); return 0; } diff --git a/crypto/s2n_evp.h b/crypto/s2n_evp.h index de2e3cbd2be..6c443efba75 100644 --- a/crypto/s2n_evp.h +++ b/crypto/s2n_evp.h @@ -45,5 +45,12 @@ struct s2n_evp_hmac_state { #define S2N_EVP_MD_CTX_FREE(md_ctx) (EVP_MD_CTX_destroy(md_ctx)) #endif +/* On some versions of OpenSSL, "EVP_PKEY_CTX_set_signature_md()" is just a macro that casts digest_alg to "void*", + * which fails to compile when the "-Werror=cast-qual" compiler flag is enabled. So we work around this OpenSSL + * issue by turning off this compiler check for this one function with a cast through. + */ +#define S2N_EVP_PKEY_CTX_set_signature_md(ctx, md) \ + EVP_PKEY_CTX_set_signature_md(ctx, (EVP_MD*) (uintptr_t) md) + extern int s2n_digest_allow_md5_for_fips(struct s2n_evp_digest *evp_digest); extern S2N_RESULT s2n_digest_is_md5_allowed_for_fips(struct s2n_evp_digest *evp_digest, bool *out); diff --git a/crypto/s2n_evp_signing.c b/crypto/s2n_evp_signing.c new file mode 100644 index 00000000000..d0d714124c4 --- /dev/null +++ b/crypto/s2n_evp_signing.c @@ -0,0 +1,153 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "error/s2n_errno.h" + +#include "crypto/s2n_evp.h" +#include "crypto/s2n_evp_signing.h" +#include "crypto/s2n_pkey.h" +#include "crypto/s2n_rsa_pss.h" + +#include "utils/s2n_safety.h" + +/* + * FIPS 140-3 requires that we don't pass raw digest bytes to the libcrypto signing methods. + * In order to do that, we need to use signing methods that both calculate the digest and + * perform the signature. + */ + +static S2N_RESULT s2n_evp_md_ctx_set_pkey_ctx(EVP_MD_CTX *ctx, EVP_PKEY_CTX *pctx) +{ +#ifdef S2N_LIBCRYPTO_SUPPORTS_EVP_MD_CTX_SET_PKEY_CTX + EVP_MD_CTX_set_pkey_ctx(ctx, pctx); + return S2N_RESULT_OK; +#else + RESULT_BAIL(S2N_ERR_UNIMPLEMENTED); +#endif +} + +static S2N_RESULT s2n_evp_pkey_set_rsa_pss_saltlen(EVP_PKEY_CTX *pctx) +{ +#if RSA_PSS_SIGNING_SUPPORTED + RESULT_GUARD_OSSL(EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, RSA_PSS_SALTLEN_DIGEST), S2N_ERR_PKEY_CTX_INIT); + return S2N_RESULT_OK; +#else + RESULT_BAIL(S2N_ERR_UNIMPLEMENTED); +#endif +} + +bool s2n_evp_signing_supported() +{ +#ifdef S2N_LIBCRYPTO_SUPPORTS_EVP_MD_CTX_SET_PKEY_CTX + /* We can only use EVP signing if the hash state has an EVP_MD_CTX + * that we can pass to the EVP signing methods. + */ + return s2n_hash_evp_fully_supported(); +#else + return false; +#endif +} + +/* If using EVP signing, override the sign and verify pkey methods. + * The EVP methods can handle all pkey types / signature algorithms. + */ +S2N_RESULT s2n_evp_signing_set_pkey_overrides(struct s2n_pkey *pkey) +{ + if (s2n_evp_signing_supported()) { + RESULT_ENSURE_REF(pkey); + pkey->sign = &s2n_evp_sign; + pkey->verify = &s2n_evp_verify; + } + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_evp_signing_validate_hash_alg(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg) +{ + switch(hash_alg) { + case S2N_HASH_NONE: + case S2N_HASH_MD5: + /* MD5 alone is never supported */ + RESULT_BAIL(S2N_ERR_HASH_INVALID_ALGORITHM); + break; + case S2N_HASH_MD5_SHA1: + /* Only RSA supports MD5+SHA1. + * This should not be a problem, as we only allow MD5+SHA1 when + * falling back to TLS1.0 or 1.1, which only support RSA. + */ + RESULT_ENSURE(sig_alg == S2N_SIGNATURE_RSA, S2N_ERR_HASH_INVALID_ALGORITHM); + break; + default: + break; + } + /* Hash algorithm must be recognized and supported by EVP_MD */ + RESULT_ENSURE(s2n_hash_alg_to_evp_md(hash_alg) != NULL, S2N_ERR_HASH_INVALID_ALGORITHM); + return S2N_RESULT_OK; +} + +int s2n_evp_sign(const struct s2n_pkey *priv, s2n_signature_algorithm sig_alg, + struct s2n_hash_state *hash_state, struct s2n_blob *signature) +{ + POSIX_ENSURE_REF(priv); + POSIX_ENSURE_REF(hash_state); + POSIX_ENSURE_REF(signature); + POSIX_ENSURE(s2n_evp_signing_supported(), S2N_ERR_HASH_NOT_READY); + POSIX_GUARD_RESULT(s2n_evp_signing_validate_hash_alg(sig_alg, hash_state->alg)); + + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(priv->pkey, NULL); + POSIX_ENSURE_REF(pctx); + POSIX_GUARD_OSSL(EVP_PKEY_sign_init(pctx), S2N_ERR_PKEY_CTX_INIT); + POSIX_GUARD_OSSL(S2N_EVP_PKEY_CTX_set_signature_md(pctx, s2n_hash_alg_to_evp_md(hash_state->alg)), S2N_ERR_PKEY_CTX_INIT); + + if (sig_alg == S2N_SIGNATURE_RSA_PSS_RSAE || sig_alg == S2N_SIGNATURE_RSA_PSS_PSS) { + POSIX_GUARD_OSSL(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING), S2N_ERR_PKEY_CTX_INIT); + POSIX_GUARD_RESULT(s2n_evp_pkey_set_rsa_pss_saltlen(pctx)); + } + + EVP_MD_CTX *ctx = hash_state->digest.high_level.evp.ctx; + POSIX_ENSURE_REF(ctx); + POSIX_GUARD_RESULT(s2n_evp_md_ctx_set_pkey_ctx(ctx, pctx)); + + size_t signature_size = signature->size; + POSIX_GUARD_OSSL(EVP_DigestSignFinal(ctx, signature->data, &signature_size), S2N_ERR_SIGN); + POSIX_ENSURE(signature_size <= signature->size, S2N_ERR_SIZE_MISMATCH); + signature->size = signature_size; + return S2N_SUCCESS; +} + +int s2n_evp_verify(const struct s2n_pkey *pub, s2n_signature_algorithm sig_alg, + struct s2n_hash_state *hash_state, struct s2n_blob *signature) +{ + POSIX_ENSURE_REF(pub); + POSIX_ENSURE_REF(hash_state); + POSIX_ENSURE_REF(signature); + POSIX_ENSURE(s2n_evp_signing_supported(), S2N_ERR_HASH_NOT_READY); + POSIX_GUARD_RESULT(s2n_evp_signing_validate_hash_alg(sig_alg, hash_state->alg)); + + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pub->pkey, NULL); + POSIX_ENSURE_REF(pctx); + POSIX_GUARD_OSSL(EVP_PKEY_verify_init(pctx), S2N_ERR_PKEY_CTX_INIT); + POSIX_GUARD_OSSL(S2N_EVP_PKEY_CTX_set_signature_md(pctx, s2n_hash_alg_to_evp_md(hash_state->alg)), S2N_ERR_PKEY_CTX_INIT); + + if (sig_alg == S2N_SIGNATURE_RSA_PSS_RSAE || sig_alg == S2N_SIGNATURE_RSA_PSS_PSS) { + POSIX_GUARD_OSSL(EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING), S2N_ERR_PKEY_CTX_INIT); + } + + EVP_MD_CTX *ctx = hash_state->digest.high_level.evp.ctx; + POSIX_ENSURE_REF(ctx); + POSIX_GUARD_RESULT(s2n_evp_md_ctx_set_pkey_ctx(ctx, pctx)); + + POSIX_GUARD_OSSL(EVP_DigestVerifyFinal(ctx, signature->data, signature->size), S2N_ERR_VERIFY_SIGNATURE); + return S2N_SUCCESS; +} diff --git a/crypto/s2n_evp_signing.h b/crypto/s2n_evp_signing.h new file mode 100644 index 00000000000..872f63b682c --- /dev/null +++ b/crypto/s2n_evp_signing.h @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#pragma once + +#include "api/s2n.h" + +#include "crypto/s2n_hash.h" +#include "crypto/s2n_signature.h" +#include "utils/s2n_blob.h" + +bool s2n_evp_signing_supported(); +S2N_RESULT s2n_evp_signing_set_pkey_overrides(struct s2n_pkey *pkey); +int s2n_evp_sign(const struct s2n_pkey *priv, s2n_signature_algorithm sig_alg, + struct s2n_hash_state *digest, struct s2n_blob *signature); +int s2n_evp_verify(const struct s2n_pkey *pub, s2n_signature_algorithm sig_alg, + struct s2n_hash_state *digest, struct s2n_blob *signature); diff --git a/crypto/s2n_hash.c b/crypto/s2n_hash.c index 4a247fd6725..b2d817851d9 100644 --- a/crypto/s2n_hash.c +++ b/crypto/s2n_hash.c @@ -31,6 +31,16 @@ static bool s2n_use_custom_md5_sha1() #endif } +static bool s2n_use_evp_impl() +{ + return s2n_is_in_fips_mode(); +} + +bool s2n_hash_evp_fully_supported() +{ + return s2n_use_evp_impl() && !s2n_use_custom_md5_sha1(); +} + const EVP_MD* s2n_hash_alg_to_evp_md(s2n_hash_algorithm alg) { switch (alg) { @@ -475,7 +485,7 @@ static const struct s2n_hash s2n_evp_hash = { static int s2n_hash_set_impl(struct s2n_hash_state *state) { state->hash_impl = &s2n_low_level_hash; - if (s2n_is_in_fips_mode()) { + if (s2n_use_evp_impl()) { state->hash_impl = &s2n_evp_hash; } return S2N_SUCCESS; diff --git a/crypto/s2n_hash.h b/crypto/s2n_hash.h index 54b44812660..6ae9b13386a 100644 --- a/crypto/s2n_hash.h +++ b/crypto/s2n_hash.h @@ -87,6 +87,7 @@ struct s2n_hash { int (*free) (struct s2n_hash_state *state); }; +bool s2n_hash_evp_fully_supported(); const EVP_MD* s2n_hash_alg_to_evp_md(s2n_hash_algorithm alg); extern int s2n_hash_digest_size(s2n_hash_algorithm alg, uint8_t *out); extern int s2n_hash_block_size(s2n_hash_algorithm alg, uint64_t *block_size); diff --git a/crypto/s2n_rsa.c b/crypto/s2n_rsa.c index 6ee9ac876a5..164496f93fd 100644 --- a/crypto/s2n_rsa.c +++ b/crypto/s2n_rsa.c @@ -22,6 +22,7 @@ #include "crypto/s2n_drbg.h" #include "crypto/s2n_hash.h" #include "crypto/s2n_pkey.h" +#include "crypto/s2n_evp_signing.h" #include "crypto/s2n_rsa_signing.h" #include "error/s2n_errno.h" #include "stuffer/s2n_stuffer.h" @@ -193,6 +194,7 @@ int s2n_rsa_pkey_init(struct s2n_pkey *pkey) pkey->match = &s2n_rsa_keys_match; pkey->free = &s2n_rsa_key_free; pkey->check_key = &s2n_rsa_check_key_exists; + POSIX_GUARD_RESULT(s2n_evp_signing_set_pkey_overrides(pkey)); return 0; } diff --git a/crypto/s2n_rsa_pss.c b/crypto/s2n_rsa_pss.c index 7e868895db4..da034d6ad30 100644 --- a/crypto/s2n_rsa_pss.c +++ b/crypto/s2n_rsa_pss.c @@ -21,6 +21,7 @@ #include "stuffer/s2n_stuffer.h" +#include "crypto/s2n_evp_signing.h" #include "crypto/s2n_hash.h" #include "crypto/s2n_openssl.h" #include "crypto/s2n_rsa.h" @@ -223,6 +224,7 @@ int s2n_rsa_pss_pkey_init(struct s2n_pkey *pkey) pkey->match = &s2n_rsa_pss_keys_match; pkey->free = &s2n_rsa_pss_key_free; + POSIX_GUARD_RESULT(s2n_evp_signing_set_pkey_overrides(pkey)); return 0; } diff --git a/crypto/s2n_rsa_signing.c b/crypto/s2n_rsa_signing.c index 9acd2649e6f..1af69edf41a 100644 --- a/crypto/s2n_rsa_signing.c +++ b/crypto/s2n_rsa_signing.c @@ -34,7 +34,8 @@ static int s2n_hash_alg_to_NID[] = { [S2N_HASH_SHA224] = NID_sha224, [S2N_HASH_SHA256] = NID_sha256, [S2N_HASH_SHA384] = NID_sha384, - [S2N_HASH_SHA512] = NID_sha512 }; + [S2N_HASH_SHA512] = NID_sha512 +}; int s2n_hash_NID_type(s2n_hash_algorithm alg, int *out) { @@ -117,32 +118,9 @@ int s2n_is_rsa_pss_signing_supported() #if RSA_PSS_SIGNING_SUPPORTED -const EVP_MD* s2n_hash_alg_to_evp_alg(s2n_hash_algorithm alg) -{ - switch (alg) { - case S2N_HASH_MD5_SHA1: - return EVP_md5_sha1(); - case S2N_HASH_SHA1: - return EVP_sha1(); - case S2N_HASH_SHA224: - return EVP_sha224(); - case S2N_HASH_SHA256: - return EVP_sha256(); - case S2N_HASH_SHA384: - return EVP_sha384(); - case S2N_HASH_SHA512: - return EVP_sha512(); - default: - return NULL; - } -} - -/* On some versions of OpenSSL, "EVP_PKEY_CTX_set_signature_md()" is just a macro that casts digest_alg to "void*", - * which fails to compile when the "-Werror=cast-qual" compiler flag is enabled. So we work around this OpenSSL - * issue by turning off this compiler check for this one function with a cast through. */ static int s2n_evp_pkey_ctx_set_rsa_signature_digest(EVP_PKEY_CTX *ctx, const EVP_MD* digest_alg) { - POSIX_GUARD_OSSL(EVP_PKEY_CTX_set_signature_md(ctx,(EVP_MD*) (uintptr_t) digest_alg), S2N_ERR_INVALID_SIGNATURE_ALGORITHM); + POSIX_GUARD_OSSL(S2N_EVP_PKEY_CTX_set_signature_md(ctx, digest_alg), S2N_ERR_INVALID_SIGNATURE_ALGORITHM); POSIX_GUARD_OSSL(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, (EVP_MD*) (uintptr_t) digest_alg), S2N_ERR_INVALID_SIGNATURE_ALGORITHM); return 0; } diff --git a/error/s2n_errno.c b/error/s2n_errno.c index 47e9a92a998..6fc59ce5f74 100755 --- a/error/s2n_errno.c +++ b/error/s2n_errno.c @@ -264,7 +264,7 @@ static const char *no_such_error = "Internal s2n error"; ERR_ENTRY(S2N_ERR_INSUFFICIENT_MEM_SIZE, "The provided buffer size is not large enough to contain the output data. Try increasing the allocation size.") \ ERR_ENTRY(S2N_ERR_KEYING_MATERIAL_EXPIRED, "The lifetime of the connection keying material has exceeded the limit. Perform a new full handshake.") \ ERR_ENTRY(S2N_ERR_EARLY_DATA_TRIAL_DECRYPT, "Unable to decrypt rejected early data") \ - + ERR_ENTRY(S2N_ERR_PKEY_CTX_INIT, "Unable to initialize the libcrypto pkey context") \ /* clang-format on */ #define ERR_STR_CASE(ERR, str) case ERR: return str; diff --git a/error/s2n_errno.h b/error/s2n_errno.h index ebc9ee73d39..b197de29fc2 100755 --- a/error/s2n_errno.h +++ b/error/s2n_errno.h @@ -206,6 +206,7 @@ typedef enum { S2N_ERR_PQ_DISABLED, S2N_ERR_INVALID_CERT_STATE, S2N_ERR_INVALID_EARLY_DATA_STATE, + S2N_ERR_PKEY_CTX_INIT, S2N_ERR_T_INTERNAL_END, /* S2N_ERR_T_USAGE */ diff --git a/s2n.mk b/s2n.mk index e01807bc552..ebd6bd9a045 100644 --- a/s2n.mk +++ b/s2n.mk @@ -199,6 +199,12 @@ ifeq ($(TRY_EVP_MD5_SHA1_HASH), 0) DEFAULT_CFLAGS += -DS2N_LIBCRYPTO_SUPPORTS_EVP_MD5_SHA1_HASH endif +# Determine if EVP_MD_CTX_set_pkey_ctx is available +TRY_EVP_MD_CTX_SET_PKEY_CTX := $(call try_compile,$(S2N_ROOT)/tests/features/evp_md_ctx_set_pkey_ctx.c) +ifeq ($(TRY_EVP_MD_CTX_SET_PKEY_CTX), 0) + DEFAULT_CFLAGS += -DS2N_LIBCRYPTO_SUPPORTS_EVP_MD_CTX_SET_PKEY_CTX +endif + CFLAGS_LLVM = ${DEFAULT_CFLAGS} -emit-llvm -c -g -O1 $(BITCODE_DIR)%.bc: %.c diff --git a/tests/features/evp_md_ctx_set_pkey_ctx.c b/tests/features/evp_md_ctx_set_pkey_ctx.c new file mode 100644 index 00000000000..042df7d0b0d --- /dev/null +++ b/tests/features/evp_md_ctx_set_pkey_ctx.c @@ -0,0 +1,20 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +int main() { + EVP_MD_CTX_set_pkey_ctx(NULL, NULL); + return 0; +} diff --git a/tests/unit/s2n_ecdsa_test.c b/tests/unit/s2n_ecdsa_test.c index baa0226235d..1ef72637fbb 100644 --- a/tests/unit/s2n_ecdsa_test.c +++ b/tests/unit/s2n_ecdsa_test.c @@ -152,7 +152,7 @@ int main(int argc, char **argv) for (int i = 0; i < s2n_array_len(supported_hash_algorithms); i++) { int hash_alg = supported_hash_algorithms[i]; - if (!s2n_hash_is_available(hash_alg)) { + if (!s2n_hash_is_available(hash_alg) || hash_alg == S2N_HASH_NONE) { /* Skip hash algorithms that are not available. */ continue; } diff --git a/tests/unit/s2n_evp_signing_test.c b/tests/unit/s2n_evp_signing_test.c new file mode 100644 index 00000000000..09a846c6f57 --- /dev/null +++ b/tests/unit/s2n_evp_signing_test.c @@ -0,0 +1,292 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" + +#include "crypto/s2n_evp_signing.h" + +#include "crypto/s2n_ecdsa.h" +#include "crypto/s2n_fips.h" +#include "crypto/s2n_rsa_pss.h" +#include "crypto/s2n_rsa_signing.h" + +/* The ecdsa sign/verify methods are static */ +#include "crypto/s2n_ecdsa.c" +#include "crypto/s2n_rsa.c" + +#define INPUT_DATA_SIZE 100 +#define OUTPUT_DATA_SIZE 1000 + +#define EXPECT_PKEY_USES_EVP_SIGNING(pkey) \ + EXPECT_EQUAL(pkey->sign, &s2n_evp_sign); \ + EXPECT_EQUAL(pkey->verify, &s2n_evp_verify) + +const uint8_t input_data[INPUT_DATA_SIZE] = "hello hash"; + +static bool s2n_hash_alg_is_supported(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg) +{ + return (hash_alg != S2N_HASH_NONE) && + (hash_alg != S2N_HASH_MD5) && + (hash_alg != S2N_HASH_MD5_SHA1 || sig_alg == S2N_SIGNATURE_RSA); +} + +static S2N_RESULT s2n_test_hash_init(struct s2n_hash_state *hash_state, s2n_hash_algorithm hash_alg) +{ + RESULT_GUARD_POSIX(s2n_hash_init(hash_state, hash_alg)); + RESULT_GUARD_POSIX(s2n_hash_allow_md5_for_fips(hash_state)); + RESULT_GUARD_POSIX(s2n_hash_update(hash_state, input_data, s2n_array_len(input_data))); + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_setup_public_key(struct s2n_pkey *public_key, struct s2n_cert_chain_and_key *chain) +{ + s2n_pkey_type pkey_type = S2N_PKEY_TYPE_UNKNOWN; + EXPECT_SUCCESS(s2n_asn1der_to_public_key_and_type(public_key, &pkey_type, + &chain->cert_chain->head->raw)); + EXPECT_EQUAL(pkey_type, chain->cert_chain->head->pkey_type); + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_test_evp_sign(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg, + struct s2n_pkey *private_key, struct s2n_blob *evp_signature_out) +{ + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + RESULT_GUARD_POSIX(s2n_hash_new(&hash_state)); + RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg)); + RESULT_GUARD_POSIX(s2n_evp_sign(private_key, sig_alg, &hash_state, evp_signature_out)); + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_test_evp_verify(s2n_signature_algorithm sig_alg, s2n_hash_algorithm hash_alg, + struct s2n_pkey *public_key, + struct s2n_blob *evp_signature, struct s2n_blob *expected_signature) +{ + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + RESULT_GUARD_POSIX(s2n_hash_new(&hash_state)); + + /* Verify that the EVP methods can verify their own signature */ + RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg)); + RESULT_GUARD_POSIX(s2n_evp_verify(public_key, sig_alg, &hash_state, evp_signature)); + + /* Verify that using the pkey directly can verify own signature */ + RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg)); + RESULT_GUARD_POSIX(s2n_pkey_verify(public_key, sig_alg, &hash_state, evp_signature)); + + /* Verify that the EVP methods can verify the known good signature */ + RESULT_GUARD(s2n_test_hash_init(&hash_state, hash_alg)); + RESULT_GUARD_POSIX(s2n_evp_verify(public_key, sig_alg, &hash_state, expected_signature)); + + return S2N_RESULT_OK; +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + /* Sanity check that we're enabling evp signing properly. + * awslc-fips is known to require evp signing. + */ +#if defined(OPENSSL_IS_AWSLC) + if (s2n_is_in_fips_mode()) { + EXPECT_TRUE(s2n_evp_signing_supported()); + } +#endif + + if (!s2n_evp_signing_supported()) { + END_TEST(); + } + + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + EXPECT_SUCCESS(s2n_hash_new(&hash_state)); + + struct s2n_cert_chain_and_key *rsa_cert_chain = NULL; + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&rsa_cert_chain, + S2N_RSA_2048_PKCS1_CERT_CHAIN, S2N_RSA_2048_PKCS1_KEY)); + + /* Test that unsupported hash algs are treated as invalid. + * Later tests will ignore unsupported algs, so ensure they are actually invalid. */ + { + /* This pkey should never actually be needed -- any pkey will do */ + struct s2n_pkey *pkey = rsa_cert_chain->private_key; + + for (s2n_signature_algorithm sig_alg = 0; sig_alg <= UINT8_MAX; sig_alg++) { + for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) { + if (s2n_hash_alg_is_supported(sig_alg, hash_alg)) { + continue; + } + + s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_ERROR_WITH_ERRNO(s2n_test_evp_sign(sig_alg, hash_alg, pkey, &evp_signature), + S2N_ERR_HASH_INVALID_ALGORITHM); + EXPECT_ERROR_WITH_ERRNO(s2n_test_evp_verify(sig_alg, hash_alg, pkey, &evp_signature, &evp_signature), + S2N_ERR_HASH_INVALID_ALGORITHM); + } + } + } + + /* EVP signing must match RSA signing */ + { + s2n_signature_algorithm sig_alg = S2N_SIGNATURE_RSA; + + DEFER_CLEANUP(struct s2n_pkey public_key_parsed = {0}, s2n_pkey_free); + EXPECT_OK(s2n_setup_public_key(&public_key_parsed, rsa_cert_chain)); + + struct s2n_pkey *private_key = rsa_cert_chain->private_key; + struct s2n_pkey *public_key = &public_key_parsed; + EXPECT_PKEY_USES_EVP_SIGNING(private_key); + EXPECT_PKEY_USES_EVP_SIGNING(public_key); + + for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) { + if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) { + continue; + } + + /* Calculate the signature using EVP methods */ + s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature)); + + /* Calculate the signature using RSA methods */ + s2n_stack_blob(rsa_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_rsa_pkcs1v15_sign(private_key, &hash_state, &rsa_signature)); + + /* Verify that the EVP methods can verify both signatures */ + EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &rsa_signature)); + + /* Verify that the RSA methods can verify the EVP signature */ + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_rsa_pkcs1v15_verify(public_key, &hash_state, &evp_signature)); + } + } + + /* EVP signing must match ECDSA signing */ + { + s2n_signature_algorithm sig_alg = S2N_SIGNATURE_ECDSA; + + struct s2n_cert_chain_and_key *ecdsa_cert_chain = NULL; + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&ecdsa_cert_chain, + S2N_ECDSA_P384_PKCS1_CERT_CHAIN, S2N_ECDSA_P384_PKCS1_KEY)); + DEFER_CLEANUP(struct s2n_pkey public_key_parsed = {0}, s2n_pkey_free); + EXPECT_OK(s2n_setup_public_key(&public_key_parsed, ecdsa_cert_chain)); + + struct s2n_pkey *private_key = ecdsa_cert_chain->private_key; + struct s2n_pkey *public_key = &public_key_parsed; + EXPECT_PKEY_USES_EVP_SIGNING(private_key); + EXPECT_PKEY_USES_EVP_SIGNING(public_key); + + for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) { + if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) { + continue; + } + + /* Calculate the signature using EVP methods */ + s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature)); + + /* Calculate the signature using ECDSA methods */ + s2n_stack_blob(ecdsa_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_ecdsa_sign(private_key, sig_alg, &hash_state, &ecdsa_signature)); + + /* Verify that the EVP methods can verify both signatures */ + EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &ecdsa_signature)); + + /* Verify that the ECDSA methods can verify the EVP signature */ + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_ecdsa_verify(public_key, sig_alg, &hash_state, &evp_signature)); + } + + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(ecdsa_cert_chain)); + } + + /* EVP signing must match RSA-PSS-RSAE signing */ + if (s2n_is_rsa_pss_signing_supported()) { + s2n_signature_algorithm sig_alg = S2N_SIGNATURE_RSA_PSS_RSAE; + + DEFER_CLEANUP(struct s2n_pkey public_key_parsed = {0}, s2n_pkey_free); + EXPECT_OK(s2n_setup_public_key(&public_key_parsed, rsa_cert_chain)); + + struct s2n_pkey *private_key = rsa_cert_chain->private_key; + struct s2n_pkey *public_key = &public_key_parsed; + EXPECT_PKEY_USES_EVP_SIGNING(private_key); + EXPECT_PKEY_USES_EVP_SIGNING(public_key); + + for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) { + if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) { + continue; + } + + /* Calculate the signature using EVP methods */ + s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature)); + + /* Calculate the signature using RSA-PSS methods */ + s2n_stack_blob(rsa_pss_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_rsa_pss_sign(private_key, &hash_state, &rsa_pss_signature)); + + /* Verify that the EVP methods can verify both signatures */ + EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &rsa_pss_signature)); + + /* Verify that the RSA-PSS methods can verify the EVP signature */ + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_rsa_pss_verify(public_key, &hash_state, &evp_signature)); + } + } + + /* EVP signing must match RSA-PSS-PSS signing */ + if (s2n_is_rsa_pss_certs_supported()) { + s2n_signature_algorithm sig_alg = S2N_SIGNATURE_RSA_PSS_PSS; + + struct s2n_cert_chain_and_key *rsa_pss_cert_chain = NULL; + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&rsa_pss_cert_chain, + S2N_RSA_PSS_2048_SHA256_LEAF_CERT, S2N_RSA_PSS_2048_SHA256_LEAF_KEY)); + DEFER_CLEANUP(struct s2n_pkey public_key_parsed = {0}, s2n_pkey_free); + EXPECT_OK(s2n_setup_public_key(&public_key_parsed, rsa_pss_cert_chain)); + + struct s2n_pkey *private_key = rsa_pss_cert_chain->private_key; + struct s2n_pkey *public_key = &public_key_parsed; + EXPECT_PKEY_USES_EVP_SIGNING(private_key); + EXPECT_PKEY_USES_EVP_SIGNING(public_key); + + for (s2n_hash_algorithm hash_alg = 0; hash_alg < S2N_HASH_SENTINEL; hash_alg++) { + if (!s2n_hash_alg_is_supported(sig_alg, hash_alg)) { + continue; + } + + /* Calculate the signature using EVP methods */ + s2n_stack_blob(evp_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_evp_sign(sig_alg, hash_alg, private_key, &evp_signature)); + + /* Calculate the signature using RSA-PSS methods */ + s2n_stack_blob(rsa_pss_signature, OUTPUT_DATA_SIZE, OUTPUT_DATA_SIZE); + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_rsa_pss_sign(private_key, &hash_state, &rsa_pss_signature)); + + /* Verify that the EVP methods can verify both signatures */ + EXPECT_OK(s2n_test_evp_verify(sig_alg, hash_alg, public_key, &evp_signature, &rsa_pss_signature)); + + /* Verify that the RSA-PSS methods can verify the EVP signature */ + EXPECT_OK(s2n_test_hash_init(&hash_state, hash_alg)); + EXPECT_SUCCESS(s2n_rsa_pss_verify(public_key, &hash_state, &evp_signature)); + } + + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(rsa_pss_cert_chain)); + } + + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(rsa_cert_chain)); + END_TEST(); +}