Commit 2f2950ef2c for openssl.org
commit 2f2950ef2cc8ae10477144725c329077c419139c
Author: Daniel Kubec <kubec@openssl.foundation>
Date: Fri Oct 2 19:17:12 2026 +0200
EVP: Enforce the whole MAC life-cycle at the EVP layer
Extend the finalised flag into the full state machine of life_cycle-mac(7):
EVP_MAC_update(), EVP_MAC_final() and EVP_MAC_finalXOF() now also fail,
with EVP_R_OPERATION_NOT_INITIALIZED, on a context that has not been
initialised with EVP_MAC_init() or EVP_MAC_init_SKEY(). The output length
query (EVP_MAC_final() with a NULL output buffer) and the parameter
functions remain usable in every state.
The canonical transition table was out of step with reality and is
corrected rather than enforced as written: EVP_MAC_final() and
EVP_MAC_finalXOF() are legal straight after initialisation (a MAC over an
empty message), the parameter functions are legal in the finaled state
(needed by the length query and by the "final, set a new IV, re-init with
the old key" reuse pattern), and rows for EVP_MAC_CTX_dup() and
EVP_MAC_init_SKEY() are added.
A key supplied through EVP_MAC_CTX_set_params() does not initialise the context.
The only in-tree code relying on that, the CMAC used by AES-SIV in
crypto/modes/siv128.c, now keys its template context with EVP_MAC_init()
instead.
Assisted-by: Claude:claude-fable-5-1
Reviewed-by: Paul Dale <paul.dale@oracle.com>
Reviewed-by: Simo Sorce <simo@redhat.com>
Merge-date: Thu Nov 5 07:54:33 2026
Merged-from: https://github.com/openssl/openssl/pull/33084
diff --git a/CHANGES.md b/CHANGES.md
index 3cd8a334f7..4bdb6fc978 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -76,6 +76,18 @@ OpenSSL 4.2
*Viktor Dukhovni*
+ * The EVP layer now enforces the MAC life-cycle described in
+ life_cycle-mac(7). `EVP_MAC_update()`, `EVP_MAC_final()` and
+ `EVP_MAC_finalXOF()` fail with an error when called on an `EVP_MAC_CTX`
+ that has not been initialised with `EVP_MAC_init()` or
+ `EVP_MAC_init_SKEY()`, including one whose key was only supplied through
+ `EVP_MAC_CTX_set_params()`, or that has already been finalised and not
+ been re-initialised since. A failed `EVP_MAC_init()` or
+ `EVP_MAC_init_SKEY()` leaves the context uninitialised.
+ <!-- https://github.com/openssl/openssl/pull/33084-->
+
+ *Daniel Kubec*
+
* `EVP_SKEY_get0_raw_key()` now accepts a NULL key pointer to retrieve only
the key length. The length is obtained from the `OSSL_SKEY_PARAM_KEY_LENGTH`
key parameter without exporting the key, so it is also available for keys
diff --git a/crypto/evp/evp_local.h b/crypto/evp/evp_local.h
index 749bc09f55..346bcacec2 100644
--- a/crypto/evp/evp_local.h
+++ b/crypto/evp/evp_local.h
@@ -75,6 +75,20 @@ struct evp_mac_ctx_st {
* OSSL_FUNC_mac_newctx()
*/
void *algctx;
+ /*
+ * Life-cycle state, see life_cycle-mac(7). EVP_MAC_CTX_new() leaves it
+ * at EVP_MAC_STATE_NEW (zero), a successful EVP_MAC_init() or
+ * EVP_MAC_init_SKEY() moves to INITIALISED, a failed one back to NEW,
+ * EVP_MAC_update() to UPDATED and EVP_MAC_final()/EVP_MAC_finalXOF() to
+ * FINALISED. EVP_MAC_update(), EVP_MAC_final() and EVP_MAC_finalXOF()
+ * are rejected in the NEW and FINALISED states.
+ */
+ enum {
+ EVP_MAC_STATE_NEW = 0,
+ EVP_MAC_STATE_INITIALISED,
+ EVP_MAC_STATE_UPDATED,
+ EVP_MAC_STATE_FINALISED
+ } state;
} /* EVP_MAC_CTX */;
struct evp_kdf_ctx_st {
diff --git a/crypto/evp/mac_lib.c b/crypto/evp/mac_lib.c
index 404141dc11..1b19538e3b 100644
--- a/crypto/evp/mac_lib.c
+++ b/crypto/evp/mac_lib.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2025 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2018-2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@@ -119,7 +119,13 @@ int EVP_MAC_init(EVP_MAC_CTX *ctx, const unsigned char *key, size_t keylen,
ERR_raise(ERR_R_EVP_LIB, ERR_R_UNSUPPORTED);
return 0;
}
- return ctx->meth->init(ctx->algctx, key, keylen, params);
+ /* The implementation may have been partially re-keyed: start over */
+ if (!ctx->meth->init(ctx->algctx, key, keylen, params)) {
+ ctx->state = EVP_MAC_STATE_NEW;
+ return 0;
+ }
+ ctx->state = EVP_MAC_STATE_INITIALISED;
+ return 1;
}
int EVP_MAC_init_SKEY(EVP_MAC_CTX *ctx, EVP_SKEY *skey, const OSSL_PARAM params[])
@@ -130,12 +136,30 @@ int EVP_MAC_init_SKEY(EVP_MAC_CTX *ctx, EVP_SKEY *skey, const OSSL_PARAM params[
ERR_raise(ERR_R_EVP_LIB, ERR_R_UNSUPPORTED);
return 0;
}
- return ctx->meth->init_skey(ctx->algctx, skey->keydata, params);
+ if (!ctx->meth->init_skey(ctx->algctx, skey->keydata, params)) {
+ ctx->state = EVP_MAC_STATE_NEW;
+ return 0;
+ }
+ ctx->state = EVP_MAC_STATE_INITIALISED;
+ return 1;
}
int EVP_MAC_update(EVP_MAC_CTX *ctx, const unsigned char *data, size_t datalen)
{
- return ctx->meth->update(ctx->algctx, data, datalen);
+ switch (ctx->state) {
+ case EVP_MAC_STATE_NEW:
+ ERR_raise(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED);
+ return 0;
+ case EVP_MAC_STATE_FINALISED:
+ ERR_raise(ERR_LIB_EVP, EVP_R_UPDATE_ERROR);
+ return 0;
+ default:
+ break;
+ }
+ if (!ctx->meth->update(ctx->algctx, data, datalen))
+ return 0;
+ ctx->state = EVP_MAC_STATE_UPDATED;
+ return 1;
}
static int evp_mac_final(EVP_MAC_CTX *ctx, int xof,
@@ -164,6 +188,16 @@ static int evp_mac_final(EVP_MAC_CTX *ctx, int xof,
*outl = macsize;
return 1;
}
+ switch (ctx->state) {
+ case EVP_MAC_STATE_NEW:
+ ERR_raise(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED);
+ return 0;
+ case EVP_MAC_STATE_FINALISED:
+ ERR_raise(ERR_LIB_EVP, EVP_R_FINAL_ERROR);
+ return 0;
+ default:
+ break;
+ }
if (outsize < macsize) {
ERR_raise(ERR_LIB_EVP, EVP_R_BUFFER_TOO_SMALL);
return 0;
@@ -178,6 +212,7 @@ static int evp_mac_final(EVP_MAC_CTX *ctx, int xof,
}
}
res = ctx->meth->final(ctx->algctx, out, &l, outsize);
+ ctx->state = EVP_MAC_STATE_FINALISED;
if (outl != NULL)
*outl = l;
return res;
diff --git a/crypto/modes/siv128.c b/crypto/modes/siv128.c
index dea95be7aa..ef3c546c4f 100644
--- a/crypto/modes/siv128.c
+++ b/crypto/modes/siv128.c
@@ -170,7 +170,7 @@ int ossl_siv128_init(SIV128_CONTEXT *ctx, const unsigned char *key, int klen,
static const unsigned char zero[SIV_LEN] = { 0 };
size_t out_len = SIV_LEN;
EVP_MAC_CTX *mac_ctx = NULL;
- OSSL_PARAM params[3];
+ OSSL_PARAM params[2];
const char *cbc_name;
if (ctx == NULL)
@@ -190,14 +190,12 @@ int ossl_siv128_init(SIV128_CONTEXT *ctx, const unsigned char *key, int klen,
cbc_name = EVP_CIPHER_get0_name(cbc);
params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER,
(char *)cbc_name, 0);
- params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY,
- (void *)key, klen);
- params[2] = OSSL_PARAM_construct_end();
+ params[1] = OSSL_PARAM_construct_end();
if ((ctx->cipher_ctx = EVP_CIPHER_CTX_new()) == NULL
|| (ctx->mac = EVP_MAC_fetch(libctx, OSSL_MAC_NAME_CMAC, propq)) == NULL
|| (ctx->mac_ctx_init = EVP_MAC_CTX_new(ctx->mac)) == NULL
- || !EVP_MAC_CTX_set_params(ctx->mac_ctx_init, params)
+ || !EVP_MAC_init(ctx->mac_ctx_init, key, klen, params)
|| !EVP_EncryptInit_ex(ctx->cipher_ctx, ctr, NULL, key + klen, NULL)
|| (mac_ctx = EVP_MAC_CTX_dup(ctx->mac_ctx_init)) == NULL
|| !EVP_MAC_update(mac_ctx, zero, sizeof(zero))
diff --git a/doc/man3/EVP_MAC.pod b/doc/man3/EVP_MAC.pod
index 1ca411e5d7..2a2ffb33b8 100644
--- a/doc/man3/EVP_MAC.pod
+++ b/doc/man3/EVP_MAC.pod
@@ -150,7 +150,7 @@ Note: There are additional requirements for some MAC algorithms during
re-initialization (i.e. calling EVP_MAC_init() on an EVP_MAC after EVP_MAC_final()
has been called on the same object). See the NOTES section below.
-EVP_MAC_init() should be called before EVP_MAC_update() and EVP_MAC_final().
+EVP_MAC_init() must be called before EVP_MAC_update() and EVP_MAC_final().
EVP_MAC_init_SKEY() is similar to EVP_MAC_init() but it accepts an opaque
B<EVP_SKEY> object as a key.
@@ -170,6 +170,17 @@ call with I<out> pointing at the allocated space.
EVP_MAC_finalXOF() does the final computation for an XOF based MAC and stores
the result in the memory pointed at by I<out> of size I<outsize>.
+EVP_MAC_update(), EVP_MAC_final() and EVP_MAC_finalXOF() require I<ctx> to
+have been initialised with EVP_MAC_init() or EVP_MAC_init_SKEY() and fail
+otherwise. Once EVP_MAC_final() or EVP_MAC_finalXOF() has been called,
+I<ctx> is finalised and these three functions fail again until it is
+re-initialised. A failed EVP_MAC_init() or EVP_MAC_init_SKEY() leaves
+I<ctx> uninitialised, whatever its state was before, as the implementation
+may have been partially re-initialised. Calling EVP_MAC_final() with
+I<out> set to NULL to query the output length is possible in any state, and
+a copy made with EVP_MAC_CTX_dup() is in the same state as the original.
+Supplying a key with EVP_MAC_CTX_set_params() does not initialise I<ctx>.
+
EVP_MAC_get_params() retrieves details about the implementation
I<mac>.
The set of parameters given with I<params> determine exactly what
@@ -344,9 +355,8 @@ Anything else may give undefined results.
=head1 NOTES
-The MAC life-cycle is described in L<life_cycle-mac(7)>. In the future,
-the transitions described there will be enforced. When this is done, it will
-not be considered a breaking change to the API.
+The MAC life-cycle is described in L<life_cycle-mac(7)>. Since OpenSSL 4.2,
+the transitions described there are enforced as described above.
The usage of the parameter names "custom", "iv" and "salt" correspond to
the names used in the standard where the algorithm was defined.
@@ -497,6 +507,10 @@ These functions were added in OpenSSL 3.0.
The EVP_MAC_init_SKEY() function was added in OpenSSL 3.5.
+Since OpenSSL 4.2, EVP_MAC_update(), EVP_MAC_final() and EVP_MAC_finalXOF()
+fail on a context that has not been initialised, or that has been finalised
+and not re-initialised since. Previously results of such calls were undefined.
+
=head1 COPYRIGHT
Copyright 2018-2026 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man7/life_cycle-mac.pod b/doc/man7/life_cycle-mac.pod
index 60b8b55d4b..8126cd09d1 100644
--- a/doc/man7/life_cycle-mac.pod
+++ b/doc/man7/life_cycle-mac.pod
@@ -23,7 +23,7 @@ This state represents the MAC after it has been allocated.
=item initialised
This state represents the MAC when it is set up and capable of processing
-input.
+input or generating output.
=item updated
@@ -93,17 +93,19 @@ This is the canonical list.
=begin man
Function Call --------------------- Current State ----------------------
- start newed initialised updated finaled freed
+ start newed initialised updated finaled freed
EVP_MAC_CTX_new newed
- EVP_MAC_init initialised initialised initialised initialised
- EVP_MAC_update updated updated
- EVP_MAC_final finaled
- EVP_MAC_finalXOF finaled
- EVP_MAC_CTX_free freed freed freed freed freed
- EVP_MAC_CTX_get_params newed initialised updated
- EVP_MAC_CTX_set_params newed initialised updated
- EVP_MAC_CTX_gettable_params newed initialised updated
- EVP_MAC_CTX_settable_params newed initialised updated
+ EVP_MAC_CTX_dup newed initialised updated finaled
+ EVP_MAC_init initialised initialised initialised initialised
+ EVP_MAC_init_SKEY initialised initialised initialised initialised
+ EVP_MAC_update updated updated
+ EVP_MAC_final finaled finaled
+ EVP_MAC_finalXOF finaled finaled
+ EVP_MAC_CTX_free freed freed freed freed freed
+ EVP_MAC_CTX_get_params newed initialised updated finaled
+ EVP_MAC_CTX_set_params newed initialised updated finaled
+ EVP_MAC_CTX_gettable_params newed initialised updated finaled
+ EVP_MAC_CTX_settable_params newed initialised updated finaled
=end man
@@ -126,6 +128,13 @@ This is the canonical list.
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td></tr>
+<tr><th style="border:1px solid" align="left">EVP_MAC_CTX_dup</th>
+ <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">newed</td>
+ <td style="border:1px solid" align="center">initialised</td>
+ <td style="border:1px solid" align="center">updated</td>
+ <td style="border:1px solid" align="center">finaled</td>
+ <td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_init</th>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center">initialised</td>
@@ -133,6 +142,13 @@ This is the canonical list.
<td style="border:1px solid" align="center">initialised</td>
<td style="border:1px solid" align="center">initialised</td>
<td style="border:1px solid" align="center"></td></tr>
+<tr><th style="border:1px solid" align="left">EVP_MAC_init_SKEY</th>
+ <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">initialised</td>
+ <td style="border:1px solid" align="center">initialised</td>
+ <td style="border:1px solid" align="center">initialised</td>
+ <td style="border:1px solid" align="center">initialised</td>
+ <td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_update</th>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td>
@@ -143,15 +159,15 @@ This is the canonical list.
<tr><th style="border:1px solid" align="left">EVP_MAC_final</th>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td>
- <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_finalXOF</th>
- <td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center">finaled</td>
+ <td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_CTX_free</th>
@@ -166,28 +182,28 @@ This is the canonical list.
<td style="border:1px solid" align="center">newed</td>
<td style="border:1px solid" align="center">initialised</td>
<td style="border:1px solid" align="center">updated</td>
- <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_CTX_set_params</th>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center">newed</td>
<td style="border:1px solid" align="center">initialised</td>
<td style="border:1px solid" align="center">updated</td>
- <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_CTX_gettable_params</th>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center">newed</td>
<td style="border:1px solid" align="center">initialised</td>
<td style="border:1px solid" align="center">updated</td>
- <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center"></td></tr>
<tr><th style="border:1px solid" align="left">EVP_MAC_CTX_settable_params</th>
<td style="border:1px solid" align="center"></td>
<td style="border:1px solid" align="center">newed</td>
<td style="border:1px solid" align="center">initialised</td>
<td style="border:1px solid" align="center">updated</td>
- <td style="border:1px solid" align="center"></td>
+ <td style="border:1px solid" align="center">finaled</td>
<td style="border:1px solid" align="center"></td></tr>
</table>
@@ -195,8 +211,11 @@ This is the canonical list.
=head1 NOTES
-At some point the EVP layer will begin enforcing the transitions described
-herein.
+Since OpenSSL 4.2, the EVP layer enforces the transitions described herein:
+EVP_MAC_update(), EVP_MAC_final() and EVP_MAC_finalXOF() fail in the
+B<newed> and B<finaled> states, see L<EVP_MAC(3)>. Querying the output
+length by calling EVP_MAC_final() with a NULL output buffer is permitted in
+every state and does not change it.
=head1 SEE ALSO
@@ -208,7 +227,7 @@ The provider MAC interface was introduced in OpenSSL 3.0.
=head1 COPYRIGHT
-Copyright 2021 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2021-2026 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use
this file except in compliance with the License. You can obtain a copy
diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index 7182b1cb28..688d51a69a 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -2357,6 +2357,346 @@ err:
}
#endif
+/*
+ * The EVP layer enforces the life-cycle of life_cycle-mac(7): EVP_MAC_update(),
+ * EVP_MAC_final() and EVP_MAC_finalXOF() must fail on a context that has not
+ * been initialised, and once EVP_MAC_final() or EVP_MAC_finalXOF() has been
+ * called they must fail again until the context is re-initialised,
+ * irrespective of what the underlying MAC implementation would do.
+ */
+typedef struct {
+ const char *name;
+ const char *subalg_param; /* OSSL_MAC_PARAM_DIGEST/_CIPHER or NULL */
+ const char *subalg;
+ size_t keylen;
+ size_t bad_keylen; /* A key length the implementation rejects */
+ int needs_iv; /* The IV has to be supplied on every init (GMAC) */
+ int null_key_reinit; /* EVP_MAC_init() with a NULL key re-initialises */
+} MAC_LIFE_CYCLE_TEST;
+
+/* Every MAC implementation of the default provider */
+static const MAC_LIFE_CYCLE_TEST mac_life_cycle_tests[] = {
+ { "HMAC", OSSL_MAC_PARAM_DIGEST, "SHA256", 32, 0, 0, 1 },
+ { "KMAC128", NULL, NULL, 32, 1, 0, 1 },
+ { "KMAC256", NULL, NULL, 32, 1, 0, 1 },
+ { "GMAC", OSSL_MAC_PARAM_CIPHER, "AES-128-GCM", 16, 0, 1, 1 },
+#ifndef OPENSSL_NO_CMAC
+ { "CMAC", OSSL_MAC_PARAM_CIPHER, "AES-128-CBC", 16, 0, 0, 1 },
+#endif
+#ifndef OPENSSL_NO_BLAKE2
+ /* BLAKE2 accepts any key length from one byte up, except an empty one */
+ { "BLAKE2BMAC", NULL, NULL, 32, 0, 0, 1 },
+ { "BLAKE2SMAC", NULL, NULL, 32, 0, 0, 1 },
+#endif
+#ifndef OPENSSL_NO_POLY1305
+ { "Poly1305", NULL, NULL, 32, 1, 0, 0 },
+#endif
+#ifndef OPENSSL_NO_SIPHASH
+ { "SipHash", NULL, NULL, 16, 1, 0, 1 },
+#endif
+};
+
+static int test_evp_mac_life_cycle(int idx)
+{
+ const MAC_LIFE_CYCLE_TEST *t = &mac_life_cycle_tests[idx];
+ int ret = 0;
+ EVP_MAC *mac = NULL;
+ EVP_MAC_CTX *ctx = NULL, *dup = NULL, *pre = NULL;
+ OSSL_PARAM init_params[3], reinit_params[2], bad_params[2], *p;
+ static const unsigned char data[] = "EVP_MAC life-cycle enforcement";
+ unsigned char key[32], iv[12];
+ unsigned char mac1[EVP_MAX_MD_SIZE], mac2[EVP_MAX_MD_SIZE];
+ size_t mac1len = 0, mac2len = 0, i;
+
+ for (i = 0; i < sizeof(key); i++)
+ key[i] = (unsigned char)(0x10 + i);
+ for (i = 0; i < sizeof(iv); i++)
+ iv[i] = (unsigned char)(0xa0 + i);
+
+ p = init_params;
+ if (t->subalg != NULL)
+ *p++ = OSSL_PARAM_construct_utf8_string(t->subalg_param,
+ (char *)t->subalg, 0);
+ if (t->needs_iv)
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_IV,
+ iv, sizeof(iv));
+ *p = OSSL_PARAM_construct_end();
+ p = reinit_params;
+ if (t->needs_iv)
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_IV,
+ iv, sizeof(iv));
+ *p = OSSL_PARAM_construct_end();
+ /* An unknown sub-algorithm, for the implementations that take one */
+ if (t->subalg_param != NULL) {
+ bad_params[0] = OSSL_PARAM_construct_utf8_string(t->subalg_param,
+ (char *)"no-such-algorithm", 0);
+ bad_params[1] = OSSL_PARAM_construct_end();
+ }
+
+ ERR_clear_error();
+ if (!TEST_ptr(mac = EVP_MAC_fetch(testctx, t->name, testpropq))
+ || !TEST_ptr(ctx = EVP_MAC_CTX_new(mac)))
+ goto err;
+
+ /* Nothing but parameters and initialisation is accepted before init */
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)
+ || !TEST_false(EVP_MAC_final(ctx, mac1, &mac1len, sizeof(mac1)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)
+ || !TEST_false(EVP_MAC_finalXOF(ctx, mac1, sizeof(mac1)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)
+ || !TEST_true(EVP_MAC_final(ctx, NULL, &mac1len, 0))
+ || !TEST_true(EVP_MAC_CTX_set_params(ctx, init_params)))
+ goto err;
+
+ /*
+ * A failed initialisation leaves the context uninitialised. Use an
+ * unknown sub-algorithm where there is one, a bad key length otherwise.
+ */
+ if (!TEST_false(t->subalg_param != NULL
+ ? EVP_MAC_init(ctx, key, t->keylen, bad_params)
+ : EVP_MAC_init(ctx, key, t->bad_keylen, init_params)))
+ goto err;
+ ERR_clear_error();
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED))
+ goto err;
+
+ /*
+ * A copy of an uninitialised context is uninitialised too. Not every
+ * implementation can copy an unkeyed context (CMAC cannot), so only
+ * check the copy when there is one.
+ */
+ if ((dup = EVP_MAC_CTX_dup(ctx)) != NULL
+ && (!TEST_false(EVP_MAC_update(dup, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)))
+ goto err;
+ EVP_MAC_CTX_free(dup);
+ dup = NULL;
+ ERR_clear_error();
+
+ /* Output can be produced straight after initialisation (empty input) */
+ if (!TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_size_t_gt(mac2len, 0)
+ || !TEST_ulong_eq(ERR_peek_error(), 0))
+ goto err;
+
+ /* Regular use, ending with EVP_MAC_final() */
+ if (!TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_ptr(pre = EVP_MAC_CTX_dup(ctx))
+ || !TEST_true(EVP_MAC_final(ctx, mac1, &mac1len, sizeof(mac1)))
+ || !TEST_size_t_gt(mac1len, 0))
+ goto err;
+
+ /* A copy of an initialised context can be used on its own */
+ if (!TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_ptr(dup = EVP_MAC_CTX_dup(ctx))
+ || !TEST_true(EVP_MAC_update(dup, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_final(dup, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len))
+ goto err;
+ EVP_MAC_CTX_free(dup);
+ dup = NULL;
+
+ /* Input may be split over several updates */
+ if (!TEST_true(EVP_MAC_update(ctx, data, 1))
+ || !TEST_true(EVP_MAC_update(ctx, data + 1, sizeof(data) - 1))
+ || !TEST_true(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len))
+ goto err;
+
+ /* Re-initialising after input discards that input */
+ if (!TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_update(ctx, iv, sizeof(iv)))
+ || !TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len))
+ goto err;
+
+ /* A copy taken before finalisation is unaffected by it */
+ if (!TEST_true(EVP_MAC_final(pre, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len)
+ || !TEST_false(EVP_MAC_update(pre, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR))
+ goto err;
+
+ /* The context is now finalised: no more input, no more output */
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR)
+ || !TEST_false(EVP_MAC_update(ctx, data, 0))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR)
+ || !TEST_false(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_FINAL_ERROR)
+ || !TEST_false(EVP_MAC_finalXOF(ctx, mac2, sizeof(mac2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_FINAL_ERROR))
+ goto err;
+
+ /* Querying the output length and parameter access do not touch the state */
+ if (!TEST_true(EVP_MAC_final(ctx, NULL, &mac2len, 0))
+ || !TEST_size_t_eq(mac2len, mac1len)
+ || !TEST_size_t_eq(EVP_MAC_CTX_get_mac_size(ctx), mac1len)
+ || !TEST_true(EVP_MAC_CTX_set_params(ctx, reinit_params))
+ || !TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR))
+ goto err;
+
+ /* A copy of a finalised context is finalised too */
+ if (!TEST_ptr(dup = EVP_MAC_CTX_dup(ctx))
+ || !TEST_false(EVP_MAC_update(dup, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR)
+ || !TEST_false(EVP_MAC_final(dup, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_FINAL_ERROR))
+ goto err;
+
+ /* Re-initialising the finalised copy makes it usable again */
+ if (!TEST_true(EVP_MAC_init(dup, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_update(dup, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_final(dup, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len))
+ goto err;
+
+ if (t->null_key_reinit) {
+ /* Re-initialisation with the previously set key */
+ if (!TEST_true(EVP_MAC_init(ctx, NULL, 0, reinit_params))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len))
+ goto err;
+ } else {
+ /* A failed re-initialisation leaves the context uninitialised */
+ if (!TEST_false(EVP_MAC_init(ctx, NULL, 0, reinit_params)))
+ goto err;
+ ERR_clear_error();
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)
+ || !TEST_false(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED))
+ goto err;
+ }
+
+ /*
+ * A failed re-initialisation of a context with input pending discards
+ * the input along with the state: the implementation may have been
+ * partially re-initialised, so nothing is accepted until an
+ * initialisation succeeds.
+ */
+ ERR_clear_error();
+ if (!TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_false(t->subalg_param != NULL
+ ? EVP_MAC_init(ctx, key, t->keylen, bad_params)
+ : EVP_MAC_init(ctx, key, t->bad_keylen, init_params)))
+ goto err;
+ ERR_clear_error();
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)
+ || !TEST_false(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED))
+ goto err;
+
+ /* A successful re-initialisation with a key must not leave errors behind */
+ ERR_clear_error();
+ if (!TEST_true(EVP_MAC_init(ctx, key, t->keylen, init_params))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_final(ctx, mac2, &mac2len, sizeof(mac2)))
+ || !TEST_mem_eq(mac2, mac2len, mac1, mac1len)
+ || !TEST_ulong_eq(ERR_peek_error(), 0))
+ goto err;
+
+ ret = 1;
+err:
+ EVP_MAC_CTX_free(pre);
+ EVP_MAC_CTX_free(dup);
+ EVP_MAC_CTX_free(ctx);
+ EVP_MAC_free(mac);
+ return ret;
+}
+
+static const char *const mac_xof_life_cycle_names[] = { "KMAC128", "KMAC256" };
+
+/* Same as above, but the context is finalised with EVP_MAC_finalXOF() */
+static int test_evp_mac_life_cycle_xof(int idx)
+{
+ int ret = 0;
+ EVP_MAC *mac = NULL;
+ EVP_MAC_CTX *ctx = NULL, *dup = NULL;
+ static const unsigned char data[] = "EVP_MAC life-cycle enforcement";
+ static const unsigned char key[32] = { 0 };
+ unsigned char out1[64], out2[sizeof(out1)];
+ size_t len = 0, outlen = sizeof(out1);
+ OSSL_PARAM params[2];
+
+ params[0] = OSSL_PARAM_construct_size_t(OSSL_MAC_PARAM_SIZE, &outlen);
+ params[1] = OSSL_PARAM_construct_end();
+
+ ERR_clear_error();
+ if (!TEST_ptr(mac = EVP_MAC_fetch(testctx, mac_xof_life_cycle_names[idx],
+ testpropq))
+ || !TEST_ptr(ctx = EVP_MAC_CTX_new(mac)))
+ goto err;
+
+ /* Nothing is accepted before initialisation */
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED)
+ || !TEST_false(EVP_MAC_finalXOF(ctx, out1, sizeof(out1)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_OPERATION_NOT_INITIALIZED))
+ goto err;
+ ERR_clear_error();
+
+ /* Output can be produced straight after initialisation (empty input) */
+ if (!TEST_true(EVP_MAC_init(ctx, key, sizeof(key), params))
+ || !TEST_true(EVP_MAC_finalXOF(ctx, out2, sizeof(out2)))
+ || !TEST_ulong_eq(ERR_peek_error(), 0))
+ goto err;
+
+ /* Regular use, ending with EVP_MAC_finalXOF() */
+ if (!TEST_true(EVP_MAC_init(ctx, key, sizeof(key), params))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_finalXOF(ctx, out1, sizeof(out1))))
+ goto err;
+
+ /* The context is now finalised: no more input, no more output */
+ if (!TEST_false(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR)
+ || !TEST_false(EVP_MAC_finalXOF(ctx, out2, sizeof(out2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_FINAL_ERROR)
+ || !TEST_false(EVP_MAC_final(ctx, out2, &len, sizeof(out2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_FINAL_ERROR))
+ goto err;
+
+ /* Querying the output length still works */
+ if (!TEST_true(EVP_MAC_final(ctx, NULL, &len, 0))
+ || !TEST_size_t_eq(len, sizeof(out1)))
+ goto err;
+
+ /* A copy of a finalised context is finalised too */
+ if (!TEST_ptr(dup = EVP_MAC_CTX_dup(ctx))
+ || !TEST_false(EVP_MAC_update(dup, data, sizeof(data)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_UPDATE_ERROR)
+ || !TEST_false(EVP_MAC_finalXOF(dup, out2, sizeof(out2)))
+ || !TEST_err_r(ERR_LIB_EVP, EVP_R_FINAL_ERROR))
+ goto err;
+
+ /* Re-initialisation with the previously set key starts afresh */
+ ERR_clear_error();
+ if (!TEST_true(EVP_MAC_init(ctx, NULL, 0, NULL))
+ || !TEST_true(EVP_MAC_update(ctx, data, sizeof(data)))
+ || !TEST_true(EVP_MAC_finalXOF(ctx, out2, sizeof(out2)))
+ || !TEST_mem_eq(out2, sizeof(out2), out1, sizeof(out1))
+ || !TEST_ulong_eq(ERR_peek_error(), 0))
+ goto err;
+
+ ret = 1;
+err:
+ EVP_MAC_CTX_free(dup);
+ EVP_MAC_CTX_free(ctx);
+ EVP_MAC_free(mac);
+ return ret;
+}
+
static int test_d2i_AutoPrivateKey(int i)
{
int ret = 0;
@@ -9954,6 +10294,8 @@ int setup_tests(void)
#ifndef OPENSSL_NO_POLY1305
ADD_TEST(test_evp_mac_poly1305_no_key);
#endif
+ ADD_ALL_TESTS(test_evp_mac_life_cycle, OSSL_NELEM(mac_life_cycle_tests));
+ ADD_ALL_TESTS(test_evp_mac_life_cycle_xof, OSSL_NELEM(mac_xof_life_cycle_names));
ADD_ALL_TESTS(test_EVP_PKEY_sign, 3);
#ifndef OPENSSL_NO_DEPRECATED_3_0
ADD_ALL_TESTS(test_EVP_PKEY_sign_with_app_method, 2);