const OSSL_HPKE_KDF_INFO *kdf_info;
const OSSL_HPKE_AEAD_INFO *aead_info;
EVP_CIPHER *aead_ciph;
+ int role; /* sender(0) or receiver(1) */
uint64_t seq; /* aead sequence number */
unsigned char *shared_secret; /* KEM output, zz */
size_t shared_secretlen;
* in doc/man3/OSSL_HPKE_CTX_new.pod to avoid duplication
*/
-OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
+OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite, int role,
OSSL_LIB_CTX *libctx, const char *propq)
{
OSSL_HPKE_CTX *ctx = NULL;
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
return NULL;
}
+ if (role != OSSL_HPKE_ROLE_SENDER && role != OSSL_HPKE_ROLE_RECEIVER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
ctx = OPENSSL_zalloc(sizeof(*ctx));
if (ctx == NULL)
return NULL;
goto err;
}
}
+ ctx->role = role;
ctx->mode = mode;
ctx->suite = suite;
ctx->kem_info = kem_info;
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_SENDER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
OPENSSL_clear_free(ctx->ikme, ctx->ikmelen);
ctx->ikme = OPENSSL_memdup(ikme, ikmelen);
if (ctx->ikme == NULL)
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_SENDER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
EVP_PKEY_free(ctx->authpriv);
ctx->authpriv = EVP_PKEY_dup(priv);
if (ctx->authpriv == NULL)
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_RECEIVER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
/* check the value seems like a good public key for this kem */
kem_info = ossl_HPKE_KEM_INFO_find_id(ctx->suite.kem_id);
if (kem_info == NULL)
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
+ /*
+ * We disallow senders from doing this as it's dangerous
+ * Receivers are ok to use this, as no harm should ensue
+ * if they go wrong.
+ */
+ if (ctx->role == OSSL_HPKE_ROLE_SENDER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
ctx->seq = seq;
return 1;
}
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_SENDER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
if (infolen > OSSL_HPKE_MAX_INFOLEN) {
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_RECEIVER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
if (infolen > OSSL_HPKE_MAX_INFOLEN) {
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_SENDER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
if ((ctx->seq + 1) == 0) { /* wrap around imminent !!! */
ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
+ if (ctx->role != OSSL_HPKE_ROLE_RECEIVER) {
+ ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
+ return 0;
+ }
if ((ctx->seq + 1) == 0) { /* wrap around imminent !!! */
ERR_raise(ERR_LIB_CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
uint16_t aead_id;
} OSSL_HPKE_SUITE;
- OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
+ OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite, int role,
OSSL_LIB_CTX *libctx, const char *propq);
void OSSL_HPKE_CTX_free(OSSL_HPKE_CTX *ctx);
=back
-For further information related to authentication see L</Pre-Shared Key HPKE modes>
-and L</Sender-authenticated HPKE Modes>.
+For further information related to authentication see L</Pre-Shared Key HPKE
+modes> and L</Sender-authenticated HPKE Modes>.
+
+=head2 HPKE Roles
+
+HPKE contexts have a role - either sender or receiver. This is used
+to control which functions can be called and so that senders do not
+re-use a key and nonce with different plaintexts.
+
+OSSL_HPKE_CTX_free(), OSSL_HPKE_export(), OSSL_HPKE_CTX_set1_psk(),
+and OSSL_HPKE_CTX_get_seq() can be called regardless of role.
+
+=over 4
+
+=item B<OSSL_HPKE_ROLE_SENDER>, 0
+
+An I<OSSL_HPKE_CTX> with this role can be used with
+OSSL_HPKE_encap(), OSSL_HPKE_seal(), OSSL_HPKE_CTX_set1_ikme() and
+OSSL_HPKE_CTX_set1_authpriv().
+
+=item B<OSSL_HPKE_ROLE_RECEIVER>, 1
+
+An I<OSSL_HPKE_CTX> with this role can be used with OSSL_HPKE_decap(),
+OSSL_HPKE_open(), OSSL_HPKE_CTX_set1_authpub() and OSSL_HPKE_CTX_set_seq().
+
+=back
+
+Calling a function with an incorrect role set on I<OSSL_HPKE_CTX> will result
+in an error.
=head2 Parameter Size Limits
=head2 Context Construct/Free
-OSSL_HPKE_CTX_new() creates a B<OSSL_HPKE_CTX> context object used for subsequent
-HPKE operations, given a I<mode> (See L</HPKE Modes>) and
-I<suite> (see L</OSSL_HPKE_SUITE Identifiers>). The I<libctx> and I<propq>
-are used when fetching algorithms from providers and may be set to NULL.
+OSSL_HPKE_CTX_new() creates a B<OSSL_HPKE_CTX> context object used for
+subsequent HPKE operations, given a I<mode> (See L</HPKE Modes>), I<suite> (see
+L</OSSL_HPKE_SUITE Identifiers>) and a I<role> (see L</HPKE Roles>). The
+I<libctx> and I<propq> are used when fetching algorithms from providers and may
+be set to NULL.
-OSSL_HPKE_CTX_free() frees the I<ctx> B<OSSL_HPKE_CTX> that was created previously
-by a call to OSSL_HPKE_CTX_new().
+OSSL_HPKE_CTX_free() frees the I<ctx> B<OSSL_HPKE_CTX> that was created
+previously by a call to OSSL_HPKE_CTX_new().
=head2 Sender APIs
Some protocols may have to deal with packet loss while still being able to
decrypt arriving packets later. We provide a way to set the increment used for
-the nonce to the next subsequent call to OSSL_HPKE_seal() or OSSL_HPKE_open().
-The OSSL_HPKE_CTX_set_seq() API can be used for such purposes with the I<seq>
-parameter value resetting the internal nonce to be used for the next call.
+the nonce to the next subsequent call to OSSL_HPKE_open() (but not to
+OSSL_HPKE_seal() as explained below). The OSSL_HPKE_CTX_set_seq() API can be
+used for such purposes with the I<seq> parameter value resetting the internal
+nonce increment to be used for the next call.
A baseline nonce value is established based on the encapsulation or
decapsulation operation and is then incremented by 1 for each call to seal or
-open. (In other words, the I<seq> is a zero-based counter.)
+open. (In other words, the first I<seq> increment defaults to zero.)
If a caller needs to determine how many calls to seal or open have been made
the OSSL_HPKE_CTX_get_seq() API can be used to retrieve the increment (in the
return 0 before the first call a sender made to OSSL_HPKE_seal() and 1 after
that first call.
+Note that re-use of the same nonce and key with different plaintexts would
+be very dangerous and could lead to loss of confidentiality and integrity.
+We therefore only support application control over I<seq> for decryption
+(i.e. OSSL_HPKE_open()) operations.
+
For compatibility with other implementations these I<seq> increments are
represented as I<uint64_t>.
-Note that re-use of the same nonce and key with different plaintexts is very
-dangerous and can lead to loss of confidentiality. Applications therefore need
-to exercise extreme caution in using these APIs and would be better off avoiding
-them entirely.
-
=head2 Protocol Convenience Functions
Additional convenience APIs allow the caller to access internal details of
-local HPKE support and/or algorithms, such as parmameter lengths.
+local HPKE support and/or algorithms, such as parameter lengths.
OSSL_HPKE_suite_check() checks if a specific B<OSSL_HPKE_SUITE> I<suite>
is supported locally.
goto err;
/* sender's actions - encrypt data using the receivers public key */
- if ((sctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite, NULL, NULL)) == NULL)
+ if ((sctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
+ NULL, NULL)) == NULL)
goto err;
if (OSSL_HPKE_encap(sctx, enc, &enclen, pub, publen, info, infolen) != 1)
goto err;
goto err;
/* receiver's actions - decrypt data using the recievers private key */
- if ((rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite, NULL, NULL)) == NULL)
+ if ((rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
+ NULL, NULL)) == NULL)
goto err;
if (OSSL_HPKE_decap(rctx, enc, enclen, priv, info, infolen) != 1)
goto err;
# define OSSL_HPKE_AEADSTR_CP "chacha20-poly1305" /* AEAD id 3 */
# define OSSL_HPKE_AEADSTR_EXP "exporter" /* AEAD id 0xff */
+/*
+ * Roles for use in creating an OSSL_HPKE_CTX, most
+ * important use of this is to control nonce re-use.
+ */
+# define OSSL_HPKE_ROLE_SENDER 0
+# define OSSL_HPKE_ROLE_RECEIVER 1
+
typedef struct {
uint16_t kem_id; /* Key Encapsulation Method id */
uint16_t kdf_id; /* Key Derivation Function id */
typedef struct ossl_hpke_ctx_st OSSL_HPKE_CTX;
-OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
+OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite, int role,
OSSL_LIB_CTX *libctx, const char *propq);
void OSSL_HPKE_CTX_free(OSSL_HPKE_CTX *ctx);
if (!TEST_true(cmpkey(privE, base->expected_pkEm, base->expected_pkEmlen)))
goto end;
if (!TEST_ptr(sealctx = OSSL_HPKE_CTX_new(base->mode, base->suite,
+ OSSL_HPKE_ROLE_SENDER,
libctx, propq)))
goto end;
if (!TEST_true(OSSL_HPKE_CTX_set1_ikme(sealctx, base->ikmE, base->ikmElen)))
goto end;
}
if (!TEST_ptr(openctx = OSSL_HPKE_CTX_new(base->mode, base->suite,
+ OSSL_HPKE_ROLE_RECEIVER,
libctx, propq)))
goto end;
if (base->mode == OSSL_HPKE_MODE_PSK
OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
size_t plainlen = OSSL_HPKE_TSTSIZE;
unsigned char plain[OSSL_HPKE_TSTSIZE];
- uint64_t startseq = 0;
OSSL_HPKE_CTX *rctx = NULL;
OSSL_HPKE_CTX *ctx = NULL;
NULL, 0, testctx, NULL)))
overallresult = 0;
if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
overallresult = 0;
if (hpke_mode == OSSL_HPKE_MODE_PSK
authpriv)))
overallresult = 0;
}
- if (COIN_IS_HEADS) {
- if (!TEST_int_eq(1, RAND_bytes_ex(testctx,
- (unsigned char *) &startseq,
- sizeof(startseq), 0))
- || !TEST_true(OSSL_HPKE_CTX_set_seq(ctx, startseq)))
- overallresult = 0;
- } else {
- startseq = 0;
- }
if (!TEST_true(OSSL_HPKE_encap(ctx, senderpub,
&senderpublen,
pub, publen,
overallresult = 0;
OSSL_HPKE_CTX_free(ctx);
memset(clear, 0, clearlen);
- if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode,
- hpke_suite,
- testctx, NULL)))
+ rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
+ testctx, NULL);
+ if (!TEST_ptr(rctx))
overallresult = 0;
if (hpke_mode == OSSL_HPKE_MODE_PSK
|| hpke_mode == OSSL_HPKE_MODE_PSKAUTH) {
authpublen)))
overallresult = 0;
}
- if (startseq != 0) {
- if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, startseq)))
- overallresult = 0;
- }
if (!TEST_true(OSSL_HPKE_decap(rctx, senderpub,
senderpublen, privp,
infop, infolen)))
NULL, 0, testctx, NULL)))
goto end;
if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
goto end;
/* a few error cases 1st */
if (!TEST_mem_eq(exp, sizeof(exp), exp2, sizeof(exp2)))
goto end;
if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
testctx, NULL)))
goto end;
if (!TEST_true(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
/* a psk context with no psk => encap fail */
if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_PSK, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
goto end;
/* set bad length psk */
/* bad suite */
if (!TEST_ptr_null(ctx = OSSL_HPKE_CTX_new(hpke_mode, bad_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
goto end;
/* bad mode */
if (!TEST_ptr_null(ctx = OSSL_HPKE_CTX_new(bad_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
goto end;
/* make good ctx */
if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
goto end;
/* too long ikm */
if (!TEST_false(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
plain, plainlen)))
goto end;
- /* the sequence ought not have been incremented, so good to start over */
plainlen = sizeof(plain);
- /* seq wrap around test */
- if (!TEST_true(OSSL_HPKE_CTX_set_seq(ctx, -1)))
- goto end;
- if (!TEST_false(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
- plain, plainlen)))
- goto end;
- /* reset seq */
- if (!TEST_true(OSSL_HPKE_CTX_set_seq(ctx, 0)))
- goto end;
/* working seal */
if (!TEST_true(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
plain, plainlen)))
/* receiver side */
/* decap fail with psk mode but no psk set */
if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_PSK, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
testctx, NULL)))
goto end;
if (!TEST_false(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
/* back good calls for base mode */
if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
testctx, NULL)))
goto end;
/* open before decap */
NULL, 0, testctx, NULL)))
goto end;
if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
testctx, NULL)))
goto end;
if (!TEST_true(OSSL_HPKE_CTX_set1_authpriv(ctx, authpriv)))
/* receiver side providing compressed form of auth public */
if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
testctx, NULL)))
goto end;
if (!TEST_true(OSSL_HPKE_CTX_set1_authpub(rctx, authpub, authpublen)))
return erv;
}
+/*
+ * Test that nonce reuse calls are prevented as we expect
+ */
+static int test_hpke_noncereuse(void)
+{
+ int erv = 0;
+ EVP_PKEY *privp = NULL;
+ unsigned char pub[OSSL_HPKE_TSTSIZE];
+ size_t publen = sizeof(pub);
+ int hpke_mode = OSSL_HPKE_MODE_BASE;
+ OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
+ OSSL_HPKE_CTX *ctx = NULL;
+ OSSL_HPKE_CTX *rctx = NULL;
+ unsigned char plain[] = "quick brown fox";
+ size_t plainlen = sizeof(plain);
+ unsigned char enc[OSSL_HPKE_TSTSIZE];
+ size_t enclen = sizeof(enc);
+ unsigned char cipher[OSSL_HPKE_TSTSIZE];
+ size_t cipherlen = sizeof(cipher);
+ unsigned char clear[OSSL_HPKE_TSTSIZE];
+ size_t clearlen = sizeof(clear);
+ uint64_t seq = 0xbad1dea;
+
+ /* sender side is not allowed set seq once some crypto done */
+ if (!TEST_true(OSSL_HPKE_keygen(hpke_suite, pub, &publen, &privp,
+ NULL, 0, testctx, NULL)))
+ goto end;
+ if (!TEST_ptr(ctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_SENDER,
+ testctx, NULL)))
+ goto end;
+ /* set seq will fail before any crypto done */
+ if (!TEST_false(OSSL_HPKE_CTX_set_seq(ctx, seq)))
+ goto end;
+ if (!TEST_true(OSSL_HPKE_encap(ctx, enc, &enclen, pub, publen, NULL, 0)))
+ goto end;
+ /* set seq will also fail after some crypto done */
+ if (!TEST_false(OSSL_HPKE_CTX_set_seq(ctx, seq + 1)))
+ goto end;
+ if (!TEST_true(OSSL_HPKE_seal(ctx, cipher, &cipherlen, NULL, 0,
+ plain, plainlen)))
+ goto end;
+
+ /* receiver side is allowed control seq */
+ if (!TEST_ptr(rctx = OSSL_HPKE_CTX_new(hpke_mode, hpke_suite,
+ OSSL_HPKE_ROLE_RECEIVER,
+ testctx, NULL)))
+ goto end;
+ /* set seq will work before any crypto done */
+ if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, seq)))
+ goto end;
+ if (!TEST_true(OSSL_HPKE_decap(rctx, enc, enclen, privp, NULL, 0)))
+ goto end;
+ /* set seq will work for receivers even after crypto done */
+ if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, seq)))
+ goto end;
+ /* but that value isn't good so decap will fail */
+ if (!TEST_false(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+ cipher, cipherlen)))
+ goto end;
+ /* reset seq to correct value and _open() should work */
+ if (!TEST_true(OSSL_HPKE_CTX_set_seq(rctx, 0)))
+ goto end;
+ if (!TEST_true(OSSL_HPKE_open(rctx, clear, &clearlen, NULL, 0,
+ cipher, cipherlen)))
+ goto end;
+ erv = 1;
+
+end:
+ EVP_PKEY_free(privp);
+ OSSL_HPKE_CTX_free(ctx);
+ OSSL_HPKE_CTX_free(rctx);
+ return erv;
+}
+
typedef enum OPTION_choice {
OPT_ERR = -1,
OPT_EOF = 0,
ADD_TEST(test_hpke_random_suites);
ADD_TEST(test_hpke_oddcalls);
ADD_TEST(test_hpke_compressed);
+ ADD_TEST(test_hpke_noncereuse);
return 1;
}