Browse Source

prevent HPKE sender setting seq unwisely

Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/19840)
Stephen Farrell 1 year ago
parent
commit
cae72eefc3
4 changed files with 199 additions and 50 deletions
  1. 44 1
      crypto/hpke/hpke.c
  2. 54 21
      doc/man3/OSSL_HPKE_CTX_new.pod
  3. 8 1
      include/openssl/hpke.h
  4. 93 27
      test/hpke_test.c

+ 44 - 1
crypto/hpke/hpke.c

@@ -56,6 +56,7 @@ struct ossl_hpke_ctx_st
     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;
@@ -801,7 +802,7 @@ err:
  * 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;
@@ -817,6 +818,10 @@ OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
         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;
@@ -833,6 +838,7 @@ OSSL_HPKE_CTX *OSSL_HPKE_CTX_new(int mode, OSSL_HPKE_SUITE suite,
             goto err;
         }
     }
+    ctx->role = role;
     ctx->mode = mode;
     ctx->suite = suite;
     ctx->kem_info = kem_info;
@@ -915,6 +921,10 @@ int OSSL_HPKE_CTX_set1_ikme(OSSL_HPKE_CTX *ctx,
         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)
@@ -934,6 +944,10 @@ int OSSL_HPKE_CTX_set1_authpriv(OSSL_HPKE_CTX *ctx, EVP_PKEY *priv)
         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)
@@ -959,6 +973,10 @@ int OSSL_HPKE_CTX_set1_authpub(OSSL_HPKE_CTX *ctx,
         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)
@@ -1020,6 +1038,15 @@ int OSSL_HPKE_CTX_set_seq(OSSL_HPKE_CTX *ctx, uint64_t seq)
         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;
 }
@@ -1036,6 +1063,10 @@ int OSSL_HPKE_encap(OSSL_HPKE_CTX *ctx,
         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;
@@ -1069,6 +1100,10 @@ int OSSL_HPKE_decap(OSSL_HPKE_CTX *ctx,
         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;
@@ -1105,6 +1140,10 @@ int OSSL_HPKE_seal(OSSL_HPKE_CTX *ctx,
         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;
@@ -1143,6 +1182,10 @@ int OSSL_HPKE_open(OSSL_HPKE_CTX *ctx,
         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;

+ 54 - 21
doc/man3/OSSL_HPKE_CTX_new.pod

@@ -24,7 +24,7 @@ OSSL_HPKE_CTX_get_seq, OSSL_HPKE_CTX_set_seq
      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);
 
@@ -182,8 +182,35 @@ supplied before the encapsulation/decapsulation operation will work.
 
 =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
 
@@ -202,13 +229,14 @@ for the I<infolen> parameter.
 
 =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
 
@@ -363,13 +391,14 @@ or values that leak.
 
 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
@@ -377,18 +406,18 @@ I<seq> output) that will be used in the next call to seal or open. That would
 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.
@@ -483,7 +512,9 @@ This example demonstrates a minimal round-trip using HPKE.
             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;
@@ -491,7 +522,9 @@ This example demonstrates a minimal round-trip using HPKE.
             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;

+ 8 - 1
include/openssl/hpke.h

@@ -65,6 +65,13 @@
 # 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 */
@@ -84,7 +91,7 @@ typedef struct {
 
 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);
 

+ 93 - 27
test/hpke_test.c

@@ -123,6 +123,7 @@ static int do_testhpke(const TEST_BASEDATA *base,
     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)))
@@ -172,6 +173,7 @@ static int do_testhpke(const TEST_BASEDATA *base,
             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
@@ -913,7 +915,6 @@ static int test_hpke_modes_suites(void)
         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;
 
@@ -995,6 +996,7 @@ static int test_hpke_modes_suites(void)
                                                     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
@@ -1009,15 +1011,6 @@ static int test_hpke_modes_suites(void)
                                                                    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,
@@ -1037,9 +1030,10 @@ static int test_hpke_modes_suites(void)
                         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) {
@@ -1063,10 +1057,6 @@ static int test_hpke_modes_suites(void)
                                                                   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)))
@@ -1142,6 +1132,7 @@ static int test_hpke_export(void)
                                     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 */
@@ -1168,6 +1159,7 @@ static int test_hpke_export(void)
     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)))
@@ -1403,6 +1395,7 @@ static int test_hpke_oddcalls(void)
 
     /* 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 */
@@ -1422,14 +1415,17 @@ static int test_hpke_oddcalls(void)
 
     /* 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 */
@@ -1472,17 +1468,7 @@ static int test_hpke_oddcalls(void)
     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)))
@@ -1491,6 +1477,7 @@ static int test_hpke_oddcalls(void)
     /* 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)))
@@ -1500,6 +1487,7 @@ static int test_hpke_oddcalls(void)
 
     /* 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 */
@@ -1815,6 +1803,7 @@ static int test_hpke_compressed(void)
                                     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)))
@@ -1827,6 +1816,7 @@ static int test_hpke_compressed(void)
 
     /* 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)))
@@ -1846,6 +1836,81 @@ end:
     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,
@@ -1894,6 +1959,7 @@ int setup_tests(void)
     ADD_TEST(test_hpke_random_suites);
     ADD_TEST(test_hpke_oddcalls);
     ADD_TEST(test_hpke_compressed);
+    ADD_TEST(test_hpke_noncereuse);
     return 1;
 }