Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add additional EC key validation for FIPS #4452

Merged
merged 6 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions crypto/s2n_ecc_evp.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

#include <stdint.h>

#include "crypto/s2n_fips.h"
#include "crypto/s2n_libcrypto.h"
#include "tls/s2n_connection.h"
#include "tls/s2n_ecc_preferences.h"
#include "tls/s2n_tls_parameters.h"
Expand Down Expand Up @@ -118,6 +120,15 @@ int s2n_is_evp_apis_supported()
return EVP_APIS_SUPPORTED;
}

bool s2n_ecc_evp_supports_fips_check()
{
#ifdef S2N_LIBCRYPTO_SUPPORTS_EC_KEY_CHECK_FIPS
return true;
#else
return false;
#endif
}

#if EVP_APIS_SUPPORTED
static int s2n_ecc_evp_generate_key_x25519(const struct s2n_ecc_named_curve *named_curve, EVP_PKEY **evp_pkey)
{
Expand Down Expand Up @@ -163,24 +174,47 @@ static int s2n_ecc_evp_generate_own_key(const struct s2n_ecc_named_curve *named_
return named_curve->generate_key(named_curve, evp_pkey);
}

static S2N_RESULT s2n_ecc_check_key(EC_KEY *ec_key)
{
RESULT_ENSURE_REF(ec_key);

#ifdef S2N_LIBCRYPTO_SUPPORTS_EC_KEY_CHECK_FIPS
if (s2n_is_in_fips_mode()) {
RESULT_GUARD_OSSL(EC_KEY_check_fips(ec_key), S2N_ERR_ECDHE_INVALID_PUBLIC_KEY_FIPS);
return S2N_RESULT_OK;
}
#endif

RESULT_GUARD_OSSL(EC_KEY_check_key(ec_key), S2N_ERR_ECDHE_INVALID_PUBLIC_KEY);

return S2N_RESULT_OK;
}

static int s2n_ecc_evp_compute_shared_secret(EVP_PKEY *own_key, EVP_PKEY *peer_public, uint16_t iana_id, struct s2n_blob *shared_secret)
{
POSIX_ENSURE_REF(peer_public);
POSIX_ENSURE_REF(own_key);

/* From RFC 8446(TLS1.3) Section 4.2.8.2: For the curves secp256r1, secp384r1, and secp521r1, peers MUST validate
* each other's public value Q by ensuring that the point is a valid point on the elliptic curve.
* For the curve x25519 and x448 the peer public-key validation check doesn't apply.
* From RFC 8422(TLS1.2) Section 5.11: With the NIST curves, each party MUST validate the public key sent by its peer
* in the ClientKeyExchange and ServerKeyExchange messages. A receiving party MUST check that the x and y parameters from
* the peer's public value satisfy the curve equation, y^2 = x^3 + ax + b mod p.
* Note that the `EC_KEY_check_key` validation is a MUST for only NIST curves, if a non-NIST curve is added to s2n-tls
* this is an additional validation step that increases security but decreases performance.
/**
*= https://tools.ietf.org/rfc/rfc8446#section-4.2.8.2
*# For the curves secp256r1, secp384r1, and secp521r1, peers MUST
*# validate each other's public value Q by ensuring that the point is a
*# valid point on the elliptic curve.
*
*= https://tools.ietf.org/rfc/rfc8422#section-5.11
*# With the NIST curves, each party MUST validate the public key sent by
*# its peer in the ClientKeyExchange and ServerKeyExchange messages. A
*# receiving party MUST check that the x and y parameters from the
*# peer's public value satisfy the curve equation, y^2 = x^3 + ax + b
*# mod p.
*
* The validation requirement for the public key value only applies to NIST curves. The
* validation is skipped with non-NIST curves for increased performance.
*/
if (iana_id != TLS_EC_CURVE_ECDH_X25519 && iana_id != TLS_EC_CURVE_ECDH_X448) {
DEFER_CLEANUP(EC_KEY *ec_key = EVP_PKEY_get1_EC_KEY(peer_public), EC_KEY_free_pointer);
S2N_ERROR_IF(ec_key == NULL, S2N_ERR_ECDHE_UNSUPPORTED_CURVE);
POSIX_GUARD_OSSL(EC_KEY_check_key(ec_key), S2N_ERR_ECDHE_SHARED_SECRET);
POSIX_ENSURE(ec_key, S2N_ERR_ECDHE_UNSUPPORTED_CURVE);
POSIX_GUARD_RESULT(s2n_ecc_check_key(ec_key));
}

size_t shared_secret_size = 0;
Expand Down
1 change: 1 addition & 0 deletions crypto/s2n_ecc_evp.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,4 @@ int s2n_ecc_evp_parse_params(struct s2n_connection *conn,
int s2n_ecc_evp_find_supported_curve(struct s2n_connection *conn, struct s2n_blob *iana_ids, const struct s2n_ecc_named_curve **found);
int s2n_ecc_evp_params_free(struct s2n_ecc_evp_params *ecc_evp_params);
int s2n_is_evp_apis_supported();
bool s2n_ecc_evp_supports_fips_check();
2 changes: 2 additions & 0 deletions error/s2n_errno.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ static const char *no_such_error = "Internal s2n error";
ERR_ENTRY(S2N_ERR_ECDHE_GEN_KEY, "Failed to generate an ECDHE key") \
ERR_ENTRY(S2N_ERR_ECDHE_SHARED_SECRET, "Error computing ECDHE shared secret") \
ERR_ENTRY(S2N_ERR_ECDHE_UNSUPPORTED_CURVE, "Unsupported EC curve was presented during an ECDHE handshake") \
ERR_ENTRY(S2N_ERR_ECDHE_INVALID_PUBLIC_KEY, "Failed to validate the peer's point on the elliptic curve") \
ERR_ENTRY(S2N_ERR_ECDHE_INVALID_PUBLIC_KEY_FIPS, "Failed to validate the peer's point on the elliptic curve, per FIPS requirements") \
ERR_ENTRY(S2N_ERR_ECDSA_UNSUPPORTED_CURVE, "Unsupported EC curve was presented during an ECDSA SignatureScheme handshake") \
ERR_ENTRY(S2N_ERR_ECDHE_SERIALIZING, "Error serializing ECDHE public") \
ERR_ENTRY(S2N_ERR_KEM_UNSUPPORTED_PARAMS, "Unsupported KEM params was presented during a handshake that uses a KEM") \
Expand Down
2 changes: 2 additions & 0 deletions error/s2n_errno.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ typedef enum {
S2N_ERR_ECDHE_GEN_KEY,
S2N_ERR_ECDHE_SHARED_SECRET,
S2N_ERR_ECDHE_UNSUPPORTED_CURVE,
S2N_ERR_ECDHE_INVALID_PUBLIC_KEY,
S2N_ERR_ECDHE_INVALID_PUBLIC_KEY_FIPS,
S2N_ERR_ECDSA_UNSUPPORTED_CURVE,
S2N_ERR_ECDHE_SERIALIZING,
S2N_ERR_KEM_UNSUPPORTED_PARAMS,
Expand Down
23 changes: 23 additions & 0 deletions tests/features/S2N_LIBCRYPTO_SUPPORTS_EC_KEY_CHECK_FIPS.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 <openssl/ec.h>

int main()
{
EC_KEY *ec_key = NULL;
EC_KEY_check_fips(ec_key);
return 0;
}
Empty file.
85 changes: 85 additions & 0 deletions tests/unit/s2n_ecc_evp_test.c
goatgoose marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "crypto/s2n_ecc_evp.h"

#include "api/s2n.h"
#include "crypto/s2n_fips.h"
#include "crypto/s2n_libcrypto.h"
#include "s2n_test.h"
#include "stuffer/s2n_stuffer.h"
#include "testlib/s2n_testlib.h"
Expand All @@ -27,10 +29,19 @@

extern const struct s2n_ecc_named_curve s2n_unsupported_curve;

DEFINE_POINTER_CLEANUP_FUNC(EC_KEY*, EC_KEY_free);
DEFINE_POINTER_CLEANUP_FUNC(EC_POINT*, EC_POINT_free);

int main(int argc, char** argv)
{
BEGIN_TEST();
EXPECT_SUCCESS(s2n_disable_tls13_in_test());

/* Test the EC_KEY_CHECK_FIPS feature probe. AWS-LC is a libcrypto known to support this feature. */
if (s2n_libcrypto_is_awslc()) {
EXPECT_TRUE(s2n_ecc_evp_supports_fips_check());
}

{
/* Test generate ephemeral keys for all supported curves */
for (size_t i = 0; i < s2n_all_supported_curves_list_len; i++) {
Expand Down Expand Up @@ -405,5 +416,79 @@ int main(int argc, char** argv)
EXPECT_SUCCESS(s2n_ecc_evp_params_free(&client_params));
}
};

/**
*= https://tools.ietf.org/rfc/rfc8446#section-4.2.8.2
*= type=test
*# For the curves secp256r1, secp384r1, and secp521r1, peers MUST
*# validate each other's public value Q by ensuring that the point is a
*# valid point on the elliptic curve. The appropriate validation
*# procedures are defined in Section 4.3.7 of [ECDSA] and alternatively
*# in Section 5.6.2.3 of [KEYAGREEMENT]. This process consists of three
*# steps: (1) verify that Q is not the point at infinity (O), (2) verify
*# that for Q = (x, y) both integers x and y are in the correct
*# interval, and (3) ensure that (x, y) is a correct solution to the
*# elliptic curve equation. For these curves, implementors do not need
*# to verify membership in the correct subgroup.
*
* s2n-tls performs this validation by invoking the libcrypto APIs: EC_KEY_check_key, and
* EC_KEY_check_fips. To ensure that these APIs are properly called, step (1) is invalidated.
*/
{
const struct s2n_ecc_named_curve* const nist_curves[] = {
&s2n_ecc_curve_secp256r1,
&s2n_ecc_curve_secp384r1,
&s2n_ecc_curve_secp521r1,
};

for (size_t i = 0; i < s2n_array_len(nist_curves); i++) {
const struct s2n_ecc_named_curve* curve = nist_curves[i];

DEFER_CLEANUP(struct s2n_ecc_evp_params server_params = { 0 }, s2n_ecc_evp_params_free);
DEFER_CLEANUP(struct s2n_ecc_evp_params client_params = { 0 }, s2n_ecc_evp_params_free);
DEFER_CLEANUP(struct s2n_blob shared_key = { 0 }, s2n_free);

/* Create a server key. */
server_params.negotiated_curve = curve;
EXPECT_SUCCESS(s2n_ecc_evp_generate_ephemeral_key(&server_params));
EXPECT_NOT_NULL(server_params.evp_pkey);

/* Create a client key. */
client_params.negotiated_curve = curve;
EXPECT_SUCCESS(s2n_ecc_evp_generate_ephemeral_key(&client_params));
EXPECT_NOT_NULL(client_params.evp_pkey);

/* Retrieve the existing client public key. */
DEFER_CLEANUP(EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(client_params.evp_pkey),
EC_KEY_free_pointer);
EXPECT_NOT_NULL(ec_key);
const EC_GROUP* group = EC_KEY_get0_group(ec_key);
EXPECT_NOT_NULL(group);
const EC_POINT* public_key = EC_KEY_get0_public_key(ec_key);
EXPECT_NOT_NULL(public_key);

/* Invalidate the public key by setting the coordinate to infinity. */
DEFER_CLEANUP(EC_POINT* invalid_public_key = EC_POINT_dup(public_key, group),
EC_POINT_free_pointer);
EXPECT_NOT_NULL(invalid_public_key);
EXPECT_EQUAL(EC_POINT_set_to_infinity(group, invalid_public_key), 1);
EXPECT_EQUAL(EC_KEY_set_public_key(ec_key, invalid_public_key), 1);
EXPECT_EQUAL(EVP_PKEY_set1_EC_KEY(client_params.evp_pkey, ec_key), 1);

/* Compute the server's shared secret. */
int ret = s2n_ecc_evp_compute_shared_secret_from_params(&server_params,
&client_params, &shared_key);

/* If s2n-tls is in FIPS mode and the libcrypto supports the EC_KEY_check_fips API,
* ensure that this API is called by checking for the correct error.
*/
if (s2n_is_in_fips_mode() && s2n_ecc_evp_supports_fips_check()) {
EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_ECDHE_INVALID_PUBLIC_KEY_FIPS);
} else {
EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_ECDHE_INVALID_PUBLIC_KEY);
}
}
}

END_TEST();
}
2 changes: 2 additions & 0 deletions tls/s2n_alerts.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ static S2N_RESULT s2n_translate_protocol_error_to_alert(int error_code, uint8_t
S2N_NO_ALERT(S2N_ERR_ECDHE_GEN_KEY);
S2N_NO_ALERT(S2N_ERR_ECDHE_SHARED_SECRET);
S2N_NO_ALERT(S2N_ERR_ECDHE_UNSUPPORTED_CURVE);
S2N_NO_ALERT(S2N_ERR_ECDHE_INVALID_PUBLIC_KEY);
S2N_NO_ALERT(S2N_ERR_ECDHE_INVALID_PUBLIC_KEY_FIPS);
S2N_NO_ALERT(S2N_ERR_ECDSA_UNSUPPORTED_CURVE);
S2N_NO_ALERT(S2N_ERR_ECDHE_SERIALIZING);
S2N_NO_ALERT(S2N_ERR_KEM_UNSUPPORTED_PARAMS);
Expand Down
Loading