Skip to content

Commit

Permalink
refactor: add alternative EVP signing method
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart committed Feb 21, 2025
1 parent 24eaea5 commit 5f93441
Show file tree
Hide file tree
Showing 6 changed files with 467 additions and 107 deletions.
1 change: 1 addition & 0 deletions codebuild/spec/buildspec_openssl3fips.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ phases:
# openssl3fips is still a work-in-progress. Not all tests pass.
- make -C build test -- ARGS="-R 's2n_build_test|s2n_fips_test'"
- make -C build test -- ARGS="-R 's2n_hash_test|s2n_hash_all_algs_test|s2n_openssl_test|s2n_init_test'"
- make -C build test -- ARGS="-R 's2n_evp_signing_test'"
191 changes: 152 additions & 39 deletions crypto/s2n_evp_signing.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "crypto/s2n_evp_signing.h"

#include "crypto/s2n_evp.h"
#include "crypto/s2n_libcrypto.h"
#include "crypto/s2n_pkey.h"
#include "crypto/s2n_rsa_pss.h"
#include "error/s2n_errno.h"
Expand All @@ -24,12 +25,6 @@

DEFINE_POINTER_CLEANUP_FUNC(EVP_PKEY_CTX *, EVP_PKEY_CTX_free);

/*
* 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
Expand All @@ -50,28 +45,16 @@ static S2N_RESULT s2n_evp_pkey_set_rsa_pss_saltlen(EVP_PKEY_CTX *pctx)
#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.
/* Always use EVP signing.
*
* TODO: Migrate the rest of the s2n_pkey methods to EVP and delete the legacy
* pkey logic and this method.
*/
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;
}
RESULT_ENSURE_REF(pkey);
pkey->sign = &s2n_evp_sign;
pkey->verify = &s2n_evp_verify;
return S2N_RESULT_OK;
}

Expand All @@ -89,15 +72,110 @@ static S2N_RESULT s2n_evp_signing_validate_sig_alg(const struct s2n_pkey *key, s
return S2N_RESULT_OK;
}

static EVP_PKEY_CTX *s2n_evp_pkey_ctx_new(EVP_PKEY *pkey, s2n_hash_algorithm hash_alg)
{
switch (hash_alg) {
#if S2N_LIBCRYPTO_SUPPORTS_PROVIDERS
/* For openssl-3.0, pkey methods will do an implicit fetch for the signing
* algorithm, which includes the hash algorithm. If using a legacy hash
* algorithm, specify the non-fips version.
*/
case S2N_HASH_MD5:
case S2N_HASH_MD5_SHA1:
case S2N_HASH_SHA1:
return EVP_PKEY_CTX_new_from_pkey(NULL, pkey, "-fips");
#endif
default:
return EVP_PKEY_CTX_new(pkey, NULL);
}
}

/* Our "digest-and-sign" EVP signing logic is intended to support FIPS 140-3.
* FIPS 140-3 does not allow signing or verifying externally calculated digests
* (except for signing, but not verifying, with ECDSA).
* See https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Digital-Signatures,
* and note that "component" tests only exist for ECDSA sign.
*
* In order to avoid signing externally calculated digests, we naively would
* need access to the full message to be signed at the time of signing. That's
* a problem for TLS1.2, where the client cert verify message requires signing
* every handshake message sent or received before the client cert verify message.
* To avoid storing every single handshake message in its entirety, we instead
* keep a running hash of the messages in an EVP hash state. Then, instead of
* digesting that hash state, we pass it unmodified to EVP_DigestSignFinal.
* That would normally not be allowed, since the hash state was initialized without
* a key using EVP_DigestInit instead of with a key using EVP_DigestSignInit.
* We make it work by using the EVP_MD_CTX_set_pkey_ctx method to attach a key
* to an existing hash state.
*
* All that means that "digest-and-sign" requires two things:
* - An EVP hash state to sign. So we MUST use EVP hashing instead of the legacy
* low-level hash implementation for all hash algs.
* - EVP_MD_CTX_set_pkey_ctx to exist and to behave as expected. Existence
* alone is not sufficient: the method exists in openssl-3.0-fips, but
* it cannot be use to setup a hash state for EVP_DigestSignFinal.
*
* Currently only awslc-fips meets both these requirements. New libcryptos
* should be assumed not to meet these requirements until proven otherwise.
*/
int s2n_evp_digest_and_sign(EVP_PKEY_CTX *pctx, s2n_signature_algorithm sig_alg,
struct s2n_hash_state *hash_state, struct s2n_blob *signature)
{
POSIX_ENSURE_REF(pctx);
POSIX_ENSURE_REF(hash_state);
POSIX_ENSURE_REF(signature);

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;
POSIX_GUARD_RESULT(s2n_evp_md_ctx_set_pkey_ctx(ctx, NULL));

return S2N_SUCCESS;
}

/* "digest-then-sign" means that we calculate the digest for a hash state,
* then sign the digest bytes. That is not allowed by FIPS 140-3, but is allowed
* in all other cases.
*
* This version of EVP signing works with either EVP hash states or legacy
* low-level hash states.
*/
int s2n_evp_digest_then_sign(EVP_PKEY_CTX *pctx,
struct s2n_hash_state *hash_state, struct s2n_blob *signature)
{
POSIX_ENSURE_REF(pctx);
POSIX_ENSURE_REF(hash_state);
POSIX_ENSURE_REF(signature);

uint8_t digest_length = 0;
POSIX_GUARD(s2n_hash_digest_size(hash_state->alg, &digest_length));
POSIX_ENSURE_LTE(digest_length, S2N_MAX_DIGEST_LEN);

uint8_t digest_out[S2N_MAX_DIGEST_LEN] = { 0 };
POSIX_GUARD(s2n_hash_digest(hash_state, digest_out, digest_length));

size_t signature_size = signature->size;
POSIX_GUARD_OSSL(EVP_PKEY_sign(pctx, signature->data, &signature_size,
digest_out, digest_length),
S2N_ERR_SIGN);
POSIX_ENSURE(signature_size <= signature->size, S2N_ERR_SIZE_MISMATCH);
signature->size = signature_size;

return S2N_SUCCESS;
}

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);

DEFER_CLEANUP(EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(priv->pkey, NULL), EVP_PKEY_CTX_free_pointer);
DEFER_CLEANUP(EVP_PKEY_CTX *pctx = s2n_evp_pkey_ctx_new(priv->pkey, hash_state->alg), EVP_PKEY_CTX_free_pointer);
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);
Expand All @@ -107,15 +185,51 @@ int s2n_evp_sign(const struct s2n_pkey *priv, s2n_signature_algorithm sig_alg,
POSIX_GUARD_RESULT(s2n_evp_pkey_set_rsa_pss_saltlen(pctx));
}

if (s2n_libcrypto_is_awslc_fips()) {
POSIX_GUARD(s2n_evp_digest_and_sign(pctx, sig_alg, hash_state, signature));
} else {
POSIX_GUARD(s2n_evp_digest_then_sign(pctx, hash_state, signature));
}

return S2N_SUCCESS;
}

/* See s2n_evp_digest_and_sign for more information */
int s2n_evp_digest_and_verify(EVP_PKEY_CTX *pctx, s2n_signature_algorithm sig_alg,
struct s2n_hash_state *hash_state, struct s2n_blob *signature)
{
POSIX_ENSURE_REF(pctx);
POSIX_ENSURE_REF(hash_state);
POSIX_ENSURE_REF(signature);

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;
POSIX_GUARD_OSSL(EVP_DigestVerifyFinal(ctx, signature->data, signature->size), S2N_ERR_VERIFY_SIGNATURE);
POSIX_GUARD_RESULT(s2n_evp_md_ctx_set_pkey_ctx(ctx, NULL));

return S2N_SUCCESS;
}

/* See s2n_evp_digest_then_sign for more information */
int s2n_evp_digest_then_verify(EVP_PKEY_CTX *pctx,
struct s2n_hash_state *hash_state, struct s2n_blob *signature)
{
POSIX_ENSURE_REF(pctx);
POSIX_ENSURE_REF(hash_state);
POSIX_ENSURE_REF(signature);

uint8_t digest_length = 0;
POSIX_GUARD(s2n_hash_digest_size(hash_state->alg, &digest_length));
POSIX_ENSURE_LTE(digest_length, S2N_MAX_DIGEST_LEN);

uint8_t digest_out[S2N_MAX_DIGEST_LEN] = { 0 };
POSIX_GUARD(s2n_hash_digest(hash_state, digest_out, digest_length));

POSIX_GUARD_OSSL(EVP_PKEY_verify(pctx, signature->data, signature->size,
digest_out, digest_length),
S2N_ERR_VERIFY_SIGNATURE);
return S2N_SUCCESS;
}

Expand All @@ -125,10 +239,9 @@ int s2n_evp_verify(const struct s2n_pkey *pub, s2n_signature_algorithm sig_alg,
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_sig_alg(pub, sig_alg));

DEFER_CLEANUP(EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pub->pkey, NULL), EVP_PKEY_CTX_free_pointer);
DEFER_CLEANUP(EVP_PKEY_CTX *pctx = s2n_evp_pkey_ctx_new(pub->pkey, hash_state->alg), EVP_PKEY_CTX_free_pointer);
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);
Expand All @@ -138,11 +251,11 @@ int s2n_evp_verify(const struct s2n_pkey *pub, s2n_signature_algorithm sig_alg,
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));
if (s2n_libcrypto_is_awslc_fips()) {
POSIX_GUARD(s2n_evp_digest_and_verify(pctx, sig_alg, hash_state, signature));
} else {
POSIX_GUARD(s2n_evp_digest_then_verify(pctx, hash_state, signature));
}

POSIX_GUARD_OSSL(EVP_DigestVerifyFinal(ctx, signature->data, signature->size), S2N_ERR_VERIFY_SIGNATURE);
POSIX_GUARD_RESULT(s2n_evp_md_ctx_set_pkey_ctx(ctx, NULL));
return S2N_SUCCESS;
}
1 change: 0 additions & 1 deletion crypto/s2n_evp_signing.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#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);
Expand Down
4 changes: 2 additions & 2 deletions crypto/s2n_rsa_pss.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ static int s2n_rsa_pss_validate_sign_verify_match(const struct s2n_pkey *pub, co

/* Sign and Verify the Hash of the Random Blob */
s2n_stack_blob(signature_data, RSA_PSS_SIGN_VERIFY_SIGNATURE_SIZE, RSA_PSS_SIGN_VERIFY_SIGNATURE_SIZE);
POSIX_GUARD(s2n_rsa_pss_key_sign(priv, S2N_SIGNATURE_RSA_PSS_PSS, &sign_hash, &signature_data));
POSIX_GUARD(s2n_rsa_pss_key_verify(pub, S2N_SIGNATURE_RSA_PSS_PSS, &verify_hash, &signature_data));
POSIX_GUARD(s2n_pkey_sign(priv, S2N_SIGNATURE_RSA_PSS_PSS, &sign_hash, &signature_data));
POSIX_GUARD(s2n_pkey_verify(pub, S2N_SIGNATURE_RSA_PSS_PSS, &verify_hash, &signature_data));

return 0;
}
Expand Down
3 changes: 3 additions & 0 deletions tests/features/S2N_LIBCRYPTO_SUPPORTS_PROVIDERS.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ int main()
EVP_MD *md = EVP_MD_fetch(NULL, NULL, NULL);
EVP_MD_free(md);

/* Supports property queries for pkey context implicit fetching */
EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, NULL, NULL);

return 0;
}
Loading

0 comments on commit 5f93441

Please sign in to comment.