Skip to content

Commit

Permalink
Add pre-TLS13 libcrypto PRF implementation (#4020)
Browse files Browse the repository at this point in the history
  • Loading branch information
goatgoose authored Jun 6, 2023
1 parent a61ba00 commit 56a897c
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 32 deletions.
1 change: 1 addition & 0 deletions error/s2n_errno.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ static const char *no_such_error = "Internal s2n error";
ERR_ENTRY(S2N_ERR_HASH_INVALID_ALGORITHM, "invalid hash algorithm") \
ERR_ENTRY(S2N_ERR_PRF_INVALID_ALGORITHM, "invalid prf hash algorithm") \
ERR_ENTRY(S2N_ERR_PRF_INVALID_SEED, "invalid prf seeds provided") \
ERR_ENTRY(S2N_ERR_PRF_DERIVE, "error deriving a secret from the PRF") \
ERR_ENTRY(S2N_ERR_P_HASH_INVALID_ALGORITHM, "invalid p_hash algorithm") \
ERR_ENTRY(S2N_ERR_P_HASH_INIT_FAILED, "error initializing p_hash") \
ERR_ENTRY(S2N_ERR_P_HASH_UPDATE_FAILED, "error updating p_hash") \
Expand Down
1 change: 1 addition & 0 deletions error/s2n_errno.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ typedef enum {
S2N_ERR_HASH_INVALID_ALGORITHM,
S2N_ERR_PRF_INVALID_ALGORITHM,
S2N_ERR_PRF_INVALID_SEED,
S2N_ERR_PRF_DERIVE,
S2N_ERR_P_HASH_INVALID_ALGORITHM,
S2N_ERR_P_HASH_INIT_FAILED,
S2N_ERR_P_HASH_UPDATE_FAILED,
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/s2n_tls_prf_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
#include <string.h>

#include "api/s2n.h"
#include "crypto/s2n_openssl.h"
#include "s2n_test.h"
#include "stuffer/s2n_stuffer.h"
#include "testlib/s2n_testlib.h"
/* To gain access to handshake_read and handshake_write */
#include "tls/s2n_handshake_io.c"

#define TEST_BLOB_SIZE 64

/*
* Grabbed from gnutls-cli --insecure -d 9 www.example.com --ciphers AES --macs SHA --protocols TLS1.0
*
Expand Down Expand Up @@ -310,5 +313,76 @@ int main(int argc, char **argv)
};
};

/* Ensure that the libcrypto TLS PRF API is only enabled for AWSLC */
if (s2n_libcrypto_is_awslc()) {
EXPECT_TRUE(s2n_libcrypto_supports_tls_prf());
} else {
EXPECT_FALSE(s2n_libcrypto_supports_tls_prf());
}

/* s2n_prf tests */
{
s2n_stack_blob(secret, TEST_BLOB_SIZE, TEST_BLOB_SIZE);
s2n_stack_blob(label, TEST_BLOB_SIZE, TEST_BLOB_SIZE);
s2n_stack_blob(seed_a, TEST_BLOB_SIZE, TEST_BLOB_SIZE);
s2n_stack_blob(seed_b, TEST_BLOB_SIZE, TEST_BLOB_SIZE);
s2n_stack_blob(seed_c, TEST_BLOB_SIZE, TEST_BLOB_SIZE);
s2n_stack_blob(out, TEST_BLOB_SIZE, TEST_BLOB_SIZE);

/* Safety */
{
DEFER_CLEANUP(struct s2n_connection *connection = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);

EXPECT_FAILURE_WITH_ERRNO(s2n_prf(NULL, &secret, &label, &seed_a, &seed_b, &seed_c, &out),
S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(s2n_prf(connection, NULL, &label, &seed_a, &seed_b, &seed_c, &out),
S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(s2n_prf(connection, &secret, NULL, &seed_a, &seed_b, &seed_c, &out),
S2N_ERR_NULL);
EXPECT_FAILURE_WITH_ERRNO(s2n_prf(connection, &secret, &label, &seed_a, &seed_b, &seed_c, NULL),
S2N_ERR_NULL);

/* seed_a is required */
EXPECT_FAILURE_WITH_ERRNO(s2n_prf(connection, &secret, &label, NULL, &seed_b, &seed_c, &out),
S2N_ERR_PRF_INVALID_SEED);

/* seed_b and seed_c are optional */
EXPECT_SUCCESS(s2n_prf(connection, &secret, &label, &seed_a, NULL, NULL, &out));

/* seed_b is required if seed_c is provided */
EXPECT_FAILURE_WITH_ERRNO(s2n_prf(connection, &secret, &label, &seed_a, NULL, &seed_c, &out),
S2N_ERR_PRF_INVALID_SEED);

/* seed_c is optional */
EXPECT_SUCCESS(s2n_prf(connection, &secret, &label, &seed_a, &seed_b, NULL, &out));
}

/* The custom PRF implementation is used when s2n-tls is not operating in FIPS mode */
if (!s2n_is_in_fips_mode()) {
DEFER_CLEANUP(struct s2n_connection *connection = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);

uint8_t zeros[S2N_MAX_DIGEST_LEN] = { 0 };
EXPECT_EQUAL(memcmp(connection->prf_space->digest0, zeros, S2N_MAX_DIGEST_LEN), 0);

EXPECT_SUCCESS(s2n_prf(connection, &secret, &label, &seed_a, &seed_b, &seed_c, &out));

/* The custom PRF implementation should modify the digest fields in the prf_space */
EXPECT_NOT_EQUAL(memcmp(connection->prf_space->digest0, zeros, S2N_MAX_DIGEST_LEN), 0);
}

/* The libcrypto PRF implementation is used when s2n-tls is linked with AWSLC-FIPS */
if (s2n_libcrypto_is_awslc() && s2n_is_in_fips_mode()) {
DEFER_CLEANUP(struct s2n_connection *connection = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);

uint8_t zeros[S2N_MAX_DIGEST_LEN] = { 0 };
EXPECT_EQUAL(memcmp(connection->prf_space->digest0, zeros, S2N_MAX_DIGEST_LEN), 0);

EXPECT_SUCCESS(s2n_prf(connection, &secret, &label, &seed_a, &seed_b, &seed_c, &out));

/* The libcrypto PRF implementation will not modify the digest fields in the prf_space */
EXPECT_EQUAL(memcmp(connection->prf_space->digest0, zeros, S2N_MAX_DIGEST_LEN), 0);
}
}

END_TEST();
}
168 changes: 136 additions & 32 deletions tls/s2n_prf.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,33 +96,35 @@ static int s2n_sslv3_prf(struct s2n_connection *conn, struct s2n_blob *secret, s
return 0;
}

static int s2n_init_md_from_hmac_alg(struct s2n_prf_working_space *ws, s2n_hmac_algorithm alg)
static S2N_RESULT s2n_md_from_hmac_alg(s2n_hmac_algorithm alg, const EVP_MD **md)
{
RESULT_ENSURE_REF(md);

switch (alg) {
case S2N_HMAC_SSLv3_MD5:
case S2N_HMAC_MD5:
ws->p_hash.evp_hmac.evp_digest.md = EVP_md5();
*md = EVP_md5();
break;
case S2N_HMAC_SSLv3_SHA1:
case S2N_HMAC_SHA1:
ws->p_hash.evp_hmac.evp_digest.md = EVP_sha1();
*md = EVP_sha1();
break;
case S2N_HMAC_SHA224:
ws->p_hash.evp_hmac.evp_digest.md = EVP_sha224();
*md = EVP_sha224();
break;
case S2N_HMAC_SHA256:
ws->p_hash.evp_hmac.evp_digest.md = EVP_sha256();
*md = EVP_sha256();
break;
case S2N_HMAC_SHA384:
ws->p_hash.evp_hmac.evp_digest.md = EVP_sha384();
*md = EVP_sha384();
break;
case S2N_HMAC_SHA512:
ws->p_hash.evp_hmac.evp_digest.md = EVP_sha512();
*md = EVP_sha512();
break;
default:
POSIX_BAIL(S2N_ERR_P_HASH_INVALID_ALGORITHM);
RESULT_BAIL(S2N_ERR_P_HASH_INVALID_ALGORITHM);
}
return S2N_SUCCESS;
return S2N_RESULT_OK;
}

#if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_IS_AWSLC)
Expand Down Expand Up @@ -152,7 +154,7 @@ static int s2n_evp_pkey_p_hash_digest_init(struct s2n_prf_working_space *ws)
static int s2n_evp_pkey_p_hash_init(struct s2n_prf_working_space *ws, s2n_hmac_algorithm alg, struct s2n_blob *secret)
{
/* Initialize the message digest */
POSIX_GUARD(s2n_init_md_from_hmac_alg(ws, alg));
POSIX_GUARD_RESULT(s2n_md_from_hmac_alg(alg, &ws->p_hash.evp_hmac.evp_digest.md));

/* Initialize the mac key using the provided secret */
POSIX_ENSURE_REF(ws->p_hash.evp_hmac.ctx.evp_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, secret->data, secret->size));
Expand Down Expand Up @@ -240,7 +242,7 @@ static int s2n_evp_hmac_p_hash_alloc(struct s2n_prf_working_space *ws)
static int s2n_evp_hmac_p_hash_init(struct s2n_prf_working_space *ws, s2n_hmac_algorithm alg, struct s2n_blob *secret)
{
/* Figure out the correct EVP_MD from s2n_hmac_algorithm */
POSIX_GUARD(s2n_init_md_from_hmac_alg(ws, alg));
POSIX_GUARD_RESULT(s2n_md_from_hmac_alg(alg, &ws->p_hash.evp_hmac.evp_digest.md));

/* Initialize the mac and digest */
POSIX_GUARD_OSSL(HMAC_Init_ex(ws->p_hash.evp_hmac.ctx.hmac_ctx, secret->data, secret->size, ws->p_hash.evp_hmac.evp_digest.md, NULL), S2N_ERR_P_HASH_INIT_FAILED);
Expand Down Expand Up @@ -459,43 +461,145 @@ S2N_RESULT s2n_prf_free(struct s2n_connection *conn)
return S2N_RESULT_OK;
}

static int s2n_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label, struct s2n_blob *seed_a,
struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out)
bool s2n_libcrypto_supports_tls_prf()
{
POSIX_ENSURE_REF(conn);
POSIX_ENSURE_REF(secret);
POSIX_ENSURE_REF(conn->prf_space);
POSIX_ENSURE_REF(conn->secure);

/* seed_a is always required, seed_b is optional, if seed_c is provided seed_b must also be provided */
S2N_ERROR_IF(seed_a == NULL, S2N_ERR_PRF_INVALID_SEED);
S2N_ERROR_IF(seed_b == NULL && seed_c != NULL, S2N_ERR_PRF_INVALID_SEED);

if (conn->actual_protocol_version == S2N_SSLv3) {
return s2n_sslv3_prf(conn, secret, seed_a, seed_b, seed_c, out);
}
#if S2N_LIBCRYPTO_SUPPORTS_TLS_PRF
return true;
#else
return false;
#endif
}

S2N_RESULT s2n_custom_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label,
struct s2n_blob *seed_a, struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out)
{
/* We zero the out blob because p_hash works by XOR'ing with the existing
* buffer. This is a little convoluted but means we can avoid dynamic memory
* allocation. When we call p_hash once (in the TLS1.2 case) it will produce
* the right values. When we call it twice in the regular case, the two
* outputs will be XORd just ass the TLS 1.0 and 1.1 RFCs require.
*/
POSIX_GUARD(s2n_blob_zero(out));
RESULT_GUARD_POSIX(s2n_blob_zero(out));

if (conn->actual_protocol_version == S2N_TLS12) {
return s2n_p_hash(conn->prf_space, conn->secure->cipher_suite->prf_alg, secret, label, seed_a, seed_b,
seed_c, out);
RESULT_GUARD_POSIX(s2n_p_hash(conn->prf_space, conn->secure->cipher_suite->prf_alg, secret, label, seed_a,
seed_b, seed_c, out));
return S2N_RESULT_OK;
}

struct s2n_blob half_secret = { 0 };
POSIX_GUARD(s2n_blob_init(&half_secret, secret->data, (secret->size + 1) / 2));
RESULT_GUARD_POSIX(s2n_blob_init(&half_secret, secret->data, (secret->size + 1) / 2));

POSIX_GUARD(s2n_p_hash(conn->prf_space, S2N_HMAC_MD5, &half_secret, label, seed_a, seed_b, seed_c, out));
RESULT_GUARD_POSIX(s2n_p_hash(conn->prf_space, S2N_HMAC_MD5, &half_secret, label, seed_a, seed_b, seed_c, out));
half_secret.data += secret->size - half_secret.size;
POSIX_GUARD(s2n_p_hash(conn->prf_space, S2N_HMAC_SHA1, &half_secret, label, seed_a, seed_b, seed_c, out));
RESULT_GUARD_POSIX(s2n_p_hash(conn->prf_space, S2N_HMAC_SHA1, &half_secret, label, seed_a, seed_b, seed_c, out));

return 0;
return S2N_RESULT_OK;
}

#if S2N_LIBCRYPTO_SUPPORTS_TLS_PRF

/* The AWSLC TLS PRF API is exported in all AWSLC versions. However, in the AWSLC FIPS branch, this
* API is defined in a private header:
* https://github.com/aws/aws-lc/blob/d251b365b73a6e6acff6ee634aa8f077f23cdea4/crypto/fipsmodule/tls/internal.h#L27
*
* AWSLC has committed to this API definition, and the API has been added to a public header in the
* main branch: https://github.com/aws/aws-lc/pull/1033. As such, this API is forward-declared in
* order to make it accessible to s2n-tls when linked to AWSLC-FIPS.
*/
int CRYPTO_tls1_prf(const EVP_MD *digest,
uint8_t *out, size_t out_len,
const uint8_t *secret, size_t secret_len,
const char *label, size_t label_len,
const uint8_t *seed1, size_t seed1_len,
const uint8_t *seed2, size_t seed2_len);

S2N_RESULT s2n_libcrypto_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label,
struct s2n_blob *seed_a, struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out)
{
const EVP_MD *digest = NULL;
if (conn->actual_protocol_version < S2N_TLS12) {
/* md5_sha1 is a digest that indicates both MD5 and SHA1 should be used in the PRF calculation.
* This is needed for pre-TLS12 PRFs.
*/
digest = EVP_md5_sha1();
} else {
RESULT_GUARD(s2n_md_from_hmac_alg(conn->secure->cipher_suite->prf_alg, &digest));
}
RESULT_ENSURE_REF(digest);

DEFER_CLEANUP(struct s2n_stuffer seed_b_stuffer = { 0 }, s2n_stuffer_free);
size_t seed_b_len = 0;
uint8_t *seed_b_data = NULL;

if (seed_b != NULL) {
struct s2n_blob seed_b_blob = { 0 };
RESULT_GUARD_POSIX(s2n_blob_init(&seed_b_blob, seed_b->data, seed_b->size));
RESULT_GUARD_POSIX(s2n_stuffer_init_written(&seed_b_stuffer, &seed_b_blob));

if (seed_c != NULL) {
/* The AWSLC TLS PRF implementation only provides two seed arguments. If three seeds
* were provided, pass in the third seed by concatenating it with the second seed.
*/
RESULT_GUARD_POSIX(s2n_stuffer_alloc(&seed_b_stuffer, seed_b->size + seed_c->size));
RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(&seed_b_stuffer, seed_b->data, seed_b->size));
RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(&seed_b_stuffer, seed_c->data, seed_c->size));
}

seed_b_len = s2n_stuffer_data_available(&seed_b_stuffer);
seed_b_data = s2n_stuffer_raw_read(&seed_b_stuffer, seed_b_len);
RESULT_ENSURE_REF(seed_b_data);
}

RESULT_GUARD_OSSL(CRYPTO_tls1_prf(digest,
out->data, out->size,
secret->data, secret->size,
(const char *) label->data, label->size,
seed_a->data, seed_a->size,
seed_b_data, seed_b_len),
S2N_ERR_PRF_DERIVE);

return S2N_RESULT_OK;
}
#else
S2N_RESULT s2n_libcrypto_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label,
struct s2n_blob *seed_a, struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out)
{
RESULT_BAIL(S2N_ERR_UNIMPLEMENTED);
}
#endif /* S2N_LIBCRYPTO_SUPPORTS_TLS_PRF */

int s2n_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label, struct s2n_blob *seed_a,
struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out)
{
POSIX_ENSURE_REF(conn);
POSIX_ENSURE_REF(conn->secure);
POSIX_ENSURE_REF(conn->secure->cipher_suite);
POSIX_ENSURE_REF(conn->prf_space);
POSIX_ENSURE_REF(secret);
POSIX_ENSURE_REF(label);
POSIX_ENSURE_REF(out);

/* seed_a is always required, seed_b is optional, if seed_c is provided seed_b must also be provided */
POSIX_ENSURE(seed_a != NULL, S2N_ERR_PRF_INVALID_SEED);
POSIX_ENSURE(S2N_IMPLIES(seed_c != NULL, seed_b != NULL), S2N_ERR_PRF_INVALID_SEED);

if (conn->actual_protocol_version == S2N_SSLv3) {
POSIX_GUARD(s2n_sslv3_prf(conn, secret, seed_a, seed_b, seed_c, out));
return S2N_SUCCESS;
}

/* By default, s2n-tls uses a custom PRF implementation. When operating in FIPS mode, the
* FIPS-validated libcrypto implementation is used instead, if an implementation is provided.
*/
if (s2n_is_in_fips_mode() && s2n_libcrypto_supports_tls_prf()) {
POSIX_GUARD_RESULT(s2n_libcrypto_prf(conn, secret, label, seed_a, seed_b, seed_c, out));
return S2N_SUCCESS;
}

POSIX_GUARD_RESULT(s2n_custom_prf(conn, secret, label, seed_a, seed_b, seed_c, out));

return S2N_SUCCESS;
}

int s2n_tls_prf_master_secret(struct s2n_connection *conn, struct s2n_blob *premaster_secret)
Expand Down
15 changes: 15 additions & 0 deletions tls/s2n_prf.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
/* Enough to support TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, 2*SHA384_DIGEST_LEN + 2*AES256_KEY_SIZE */
#define S2N_MAX_KEY_BLOCK_LEN 160

#if defined(OPENSSL_IS_AWSLC)
#define S2N_LIBCRYPTO_SUPPORTS_TLS_PRF 1
#else
#define S2N_LIBCRYPTO_SUPPORTS_TLS_PRF 0
#endif

union p_hash_state {
struct s2n_hmac_state s2n_hmac;
struct s2n_evp_hmac_state evp_hmac;
Expand Down Expand Up @@ -54,6 +60,8 @@ S2N_RESULT s2n_prf_new(struct s2n_connection *conn);
S2N_RESULT s2n_prf_wipe(struct s2n_connection *conn);
S2N_RESULT s2n_prf_free(struct s2n_connection *conn);

int s2n_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label, struct s2n_blob *seed_a,
struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out);
int s2n_prf_calculate_master_secret(struct s2n_connection *conn, struct s2n_blob *premaster_secret);
int s2n_tls_prf_master_secret(struct s2n_connection *conn, struct s2n_blob *premaster_secret);
int s2n_hybrid_prf_master_secret(struct s2n_connection *conn, struct s2n_blob *premaster_secret);
Expand All @@ -62,3 +70,10 @@ S2N_RESULT s2n_prf_get_digest_for_ems(struct s2n_connection *conn, struct s2n_bl
int s2n_prf_key_expansion(struct s2n_connection *conn);
int s2n_prf_server_finished(struct s2n_connection *conn);
int s2n_prf_client_finished(struct s2n_connection *conn);

bool s2n_libcrypto_supports_tls_prf();

S2N_RESULT s2n_custom_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label,
struct s2n_blob *seed_a, struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out);
S2N_RESULT s2n_libcrypto_prf(struct s2n_connection *conn, struct s2n_blob *secret, struct s2n_blob *label,
struct s2n_blob *seed_a, struct s2n_blob *seed_b, struct s2n_blob *seed_c, struct s2n_blob *out);

0 comments on commit 56a897c

Please sign in to comment.