Conversation
|
See #29115 for provenance |
|
We could really use eyes on the provider bits, or generally any OpenSSL "glue" |
|
This needs a rebase to a fresher master. My offer to help still stands. |
|
Regarding the provider bits, I could see nothing wrong. A cursory look at the build shows that the algorithm is discoverable and what the params for it are: $ util/wrap.pl apps/openssl list -cipher-algorithms -verbose
...
ASCON-AEAD128 @ default
description: undefined
retrievable algorithm parameters:
blocksize: unsigned integer (max 8 bytes large)
keylen: unsigned integer (max 8 bytes large)
ivlen: unsigned integer (max 8 bytes large)
aead: unsigned integer (max 8 bytes large)
retrievable operation parameters:
keylen: unsigned integer (max 8 bytes large)
ivlen: unsigned integer (max 8 bytes large)
taglen: unsigned integer (max 8 bytes large)
tag: octet string (arbitrary size)
updated-iv: octet string (arbitrary size)
settable operation parameters:
tag: octet string (arbitrary size)
taglen: unsigned integer (max 8 bytes large)
...Running the EVP tests also shows that the provider does its work (this is essentially what $ util/wrap.pl test/evp_test test/recipes/30-test_evp_data/evpciph_ascon_aead128.txt
1..1
# Subtest: run_file_tests
1..1
# INFO: @ ../test/testutil/stanza.c:21
# Reading test/recipes/30-test_evp_data/evpciph_ascon_aead128.txt
# INFO: @ ../test/testutil/stanza.c:123
# Starting "Ascon AEAD 128 Test Vectors" tests at line 14
# INFO: @ ../test/evp_test.c:1028
# ascon-aead128 is fetched
# INFO: @ ../test/evp_test.c:1594
# RUNNING TEST FOR CIPHER ASCON-AEAD128
#
# INFO: @ ../test/evp_test.c:1028
# ascon-aead128 is fetched
# INFO: @ ../test/evp_test.c:1594
# RUNNING TEST FOR CIPHER ASCON-AEAD128
#
...
# INFO: @ ../test/evp_test.c:1028
# ascon-aead128 is fetched
# INFO: @ ../test/evp_test.c:1594
# RUNNING TEST FOR CIPHER ASCON-AEAD128
#
# INFO: @ ../test/testutil/stanza.c:32
# Completed 1089 tests with 0 errors and 0 skipped
ok 1 - iteration 1
ok 1 - run_file_testsNot sure what more to say at this level. |
Thank you. I'm still hoping OP pushes some additional commits to add more (text-based) EVP tests, including negative tests. And perhaps at the same time, rebases off fresh master (thank you for your offer on the other thread, but let's see if OP sorts it first.) |
t8m
left a comment
There was a problem hiding this comment.
I do not see any major issue but there are a few nits that need to be fixed before this can be merged.
There could be an option to take over the PR by the team to finalize it if the contributor prefers that.
🔒 Aisle Security AnalysisWe found 6 potential security issue(s) in this PR:
Each finding is detailed in a separate comment below. Analyzed PR: #29934 at commit |
| /* Handle reinitialization with NULL key but new IV */ | ||
| if (key == NULL && iv != NULL && ctx->key_set) { | ||
| /* Preserve tag for decryption - it may have been set before reinitialization */ | ||
| uint8_t saved_tag[FIXED_TAG_LENGTH]; | ||
| int tag_was_set = ctx->is_tag_set; | ||
|
|
||
| if (tag_was_set && direction == DECRYPTION) | ||
| memcpy(saved_tag, ctx->tag, FIXED_TAG_LENGTH); | ||
|
|
||
| ascon_aead128_cleanctx(ctx); | ||
| ctx->direction = direction; | ||
| /* Use stored key for reinitialization */ | ||
| ossl_ascon_aead128_init(ctx->internal_ctx, ctx->key, iv); | ||
| /* Store the IV for get_updated_iv */ | ||
| memcpy(ctx->iv, iv, ASCON_AEAD_NONCE_LEN); | ||
| ctx->iv_set = true; | ||
| ctx->is_ongoing = true; | ||
|
|
||
| /* Restore tag for decryption if it was set before reinitialization */ | ||
| if (tag_was_set && direction == DECRYPTION) { | ||
| memcpy(ctx->tag, saved_tag, FIXED_TAG_LENGTH); | ||
| ctx->is_tag_set = true; | ||
| } | ||
|
|
||
| return OSSL_RV_SUCCESS; | ||
| } | ||
|
|
||
| /* Only clean and initialize if both key and IV are provided */ | ||
| if (key != NULL && iv != NULL) { | ||
| /* Preserve tag for decryption - it may have been set before reinitialization */ | ||
| uint8_t saved_tag[FIXED_TAG_LENGTH]; | ||
| int tag_was_set = ctx->is_tag_set; | ||
|
|
||
| if (tag_was_set && direction == DECRYPTION) | ||
| memcpy(saved_tag, ctx->tag, FIXED_TAG_LENGTH); | ||
|
|
||
| ascon_aead128_cleanctx(ctx); | ||
| ctx->direction = direction; | ||
| ossl_ascon_aead128_init(ctx->internal_ctx, key, iv); | ||
| /* Store the key and IV for reinitialization */ | ||
| memcpy(ctx->key, key, ASCON_AEAD128_KEY_LEN); | ||
| ctx->key_set = true; | ||
| memcpy(ctx->iv, iv, ASCON_AEAD_NONCE_LEN); | ||
| ctx->iv_set = true; | ||
| ctx->is_ongoing = true; | ||
|
|
||
| /* Restore tag for decryption if it was set before reinitialization */ | ||
| if (tag_was_set && direction == DECRYPTION) { | ||
| memcpy(ctx->tag, saved_tag, FIXED_TAG_LENGTH); | ||
| ctx->is_tag_set = true; | ||
| } | ||
|
|
||
| return OSSL_RV_SUCCESS; | ||
| } | ||
|
|
||
| /* If only direction is being set (key/IV not provided yet), just set direction */ | ||
| ctx->direction = direction; | ||
| return OSSL_RV_SUCCESS; |
There was a problem hiding this comment.
2. 🟡 ASCON-AEAD128 provider init can silently reuse stale key when key and IV are set in multiple steps
| Property | Value |
|---|---|
| Severity | Medium |
| CWE | CWE-320 |
Description
The ASCON-AEAD128 provider implementation mishandles multi-step initialisation sequences that are valid/expected in OpenSSL EVP (e.g. setting a new key first, then setting the IV later).
In ascon_aead128_internal_init():
- A call that supplies a new key with
iv == NULLonly validates the key length and then does not store the new key, does not clear/refresh internal state, and does not updatectx->key_set. - A subsequent call that supplies a new IV with
key == NULLwill take the special reinitialisation path whenctx->key_setis already true and will initialise the cipher using the previously stored key (ctx->key). - Because
ascon_aead128_cleanctx()intentionally does not clearctx->key/ctx->key_set, the stale key persists across operations.
This can lead to encryption proceeding under an unintended old key silently, which can cause cross-session/key confusion and unintended disclosure if an attacker-controlled key remains active in a reused EVP_CIPHER_CTX.
Vulnerable code (key reuse on IV-only init + ignoring key-only init):
if (key == NULL && iv != NULL && ctx->key_set) {
ascon_aead128_cleanctx(ctx);
ossl_ascon_aead128_init(ctx->internal_ctx, ctx->key, iv);
...
return OSSL_RV_SUCCESS;
}
/* Only clean and initialize if both key and IV are provided */
if (key != NULL && iv != NULL) {
...
memcpy(ctx->key, key, ASCON_AEAD128_KEY_LEN);
ctx->key_set = true;
...
return OSSL_RV_SUCCESS;
}
/* Otherwise, key-only init is effectively ignored */
ctx->direction = direction;
return OSSL_RV_SUCCESS;Recommendation
Handle EVP-valid partial init sequences safely by tracking key/IV independently and only starting the operation when both are available.
Suggested approach:
- If
key != NULL, always copy it intoctx->keyand setctx->key_set = true(even wheniv == NULL). - If
iv != NULL, always copy it intoctx->ivand setctx->iv_set = true. - When either key or IV is updated, reset per-message state (
ascon_aead128_cleanctx()), but preserve the stored key/IV buffers as needed. - Only call
ossl_ascon_aead128_init()and setctx->is_ongoing = truewhen bothctx->key_set && ctx->iv_set. - If an IV-only init occurs when
!ctx->key_set, either store the IV and wait for the key, or return an explicit error; do not silently succeed without becoming usable.
Example sketch:
if (key != NULL) {
if (keylen != ASCON_AEAD128_KEY_LEN) return 0;
memcpy(ctx->key, key, ASCON_AEAD128_KEY_LEN);
ctx->key_set = true;
}
if (iv != NULL) {
if (ivlen != ASCON_AEAD_NONCE_LEN) return 0;
memcpy(ctx->iv, iv, ASCON_AEAD_NONCE_LEN);
ctx->iv_set = true;
}
ascon_aead128_cleanctx(ctx); /* reset per-message state */
ctx->direction = direction;
if (ctx->key_set && ctx->iv_set) {
ossl_ascon_aead128_init(ctx->internal_ctx, ctx->key, ctx->iv);
ctx->is_ongoing = true;
}This prevents stale-key reuse and ensures EVP_EncryptInit_ex() multi-step sequences behave consistently with other provider AEAD ciphers (e.g., GCM).
There was a problem hiding this comment.
This is a valid suggestion. Another option would be to return a failure if key is set but iv is NULL, but other algorithms handle this case gracefully. I.e. they allow setting a key first and an iv later.
There was a problem hiding this comment.
other algorithms handle this case gracefully
@t8m Would it make sense to have a unit test for all AEAD schemes for this? At first glance, this doesn't seem ASCON-specific.
There was a problem hiding this comment.
other algorithms handle this case gracefully
@t8m Would it make sense to have a unit test for all AEAD schemes for this? At first glance, this doesn't seem ASCON-specific.
Sure. We should unify this behavior across all the ciphers. Not sure why AEAD should be specific.
There was a problem hiding this comment.
I wrote a test for this bug and a couple of different multi-step initialization orders, and the test successfully found the bug. I ran it against this fork/branch. Here is the PR for the tests: https://github.com/openssl/openssl/pull/30141
# ERROR: (memory) 'ct_reinit == ct_onestep' failed @ test/evp_extra_test.c:5743
# --- ct_reinit
# +++ ct_onestep
# 0000:-981aaa8bd1190a7e 9a3302378166bc71 b17457158a4fd9de 32518f84b6cdb5
# 0000:+9212cc88f2472d1d b73d27b799d601b9 80bf4ffb3f645834 82cc23104015f1
# ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
#
# INFO: @ test/evp_extra_test.c:5757
# evp_stale_key_integrity_test 76, CT_MISMATCH_SINGLE_vs_KEYIV: ASCON-AEAD128
# # OPENSSL_TEST_RAND_SEED=1772048948
Additionally, the decrypt test had a similar issue but instead of mismatching the key is not set correctly:
# ERROR: (bool) 'EVP_DecryptUpdate(ctx_dec, rt, &rt_len, ct, ct_len) == true' failed @ test/evp_extra_test.c:5957
# false
# INFO: @ test/evp_extra_test.c:5978
# evp_decrypt_roundtrip_multistep 76, DEC_UPDATE: ASCON-AEAD128
# C0F7CA86747F0000:error:1C800072:Provider routines:ascon_aead128_update:no key set:providers/implementations/ciphers/cipher_ascon_aead128.c:236:
There was a problem hiding this comment.
Fixed! Albeit I did not have time today to test against @Jlg1061 changes.
5. 🔵 ASCON-AEAD128 registered in default provider without OPENSSL_NO_ASCON128 guard
DescriptionThe default provider registers the new ASCON cipher unconditionally:
Vulnerable code: ALG(PROV_NAMES_ASCON_AEAD128, ossl_ascon_aead128_functions),RecommendationGuard the registration the same way the dispatch table is guarded, using the same macro name everywhere. Example: #ifndef OPENSSL_NO_ASCON128
ALG(PROV_NAMES_ASCON_AEAD128, ossl_ascon_aead128_functions),
#endifAlso ensure the build system and tests use the same disable token/macro (see next report) so |
|
I will be working on these requested changes throughout the week and merging into one commit so we don't have a bunch of commit churn again. Thanks all, sorry for the delay on my part. |
discover all provided ciphers with non-zero IV length and verify correct multi-step initialization semantics. The EVP API permits key and IV to be supplied in separate `EVP_CipherInit_ex()` calls (e.g. key-only followed by IV-only). A recent bug (PR openssl#29934, ASCON-AEAD128) demonstrated that a provider may silently ignore a key-only init, resulting in reuse of a previously loaded key during a subsequent IV-only init. To prevent similar regressions, this change introduces three generic tests that automatically cover all IV-taking ciphers: Verifies that: - `init(key) → init(iv)` - `init(iv) → init(key)` produce identical ciphertext (and authentication tag for AEAD ciphers) compared to single-call `init(key, iv)`. Primes a context with `key1/iv1`, then re-initializes via `init(key2) → init(iv2)` and verifies the output matches a fresh `encrypt(key2, iv2)` operation, ensuring that no previously stored key is reused. Encrypts using single-call initialization and then decrypts using multi-step initialization, verifying plaintext recovery. For AEAD ciphers, this also exercises tag verification through the multi-step path. Ciphers are discovered using `EVP_CIPHER_do_all_provided()`, requiring no maintenance when new IV-taking ciphers are added. SIV mode is skipped due to its synthetic IV semantics. CCM mode handling includes required length declarations. This provides broad regression coverage for the provider implementations that support multi-step EVP initialization. @bbbrumley CLA: [https://docuseal.com/e/4ToT8de57L7nHb](url) <!-- Thank you for your pull request. Please review these requirements: Contributors guide: https://github.com/openssl/openssl/blob/master/CONTRIBUTING.md Other than that, provide a description above this comment if there isn't one already If this fixes a GitHub issue, make sure to have a line saying 'Fixes #XXXX' (without quotes) in the commit message. --> <!-- Remove items that do not apply. For completed items, change [ ] to [x]. --> - [x] documentation is added or updated - [x] tests are added or updated
|
This PR is in a state where it requires action by @openssl/committers but the last update was 30 days ago |
|
Resolved all addressable comments here |
@evil-cry thanks for the new commits!
|
39699a0 to
fcb74ab
Compare
|
rebased on master and conflict resolved -- reviews can continue IMO |
|
OK so follow-up to my own test tweak ☝️ I'm writing another generic AEAD interface test, on a dev branch now but will file the PR tomorrow, I get so
What do we prefer? (so I can adjust this PR, and also file a generic AEAD regression test in another PR) |
discover all provided ciphers with non-zero IV length and verify correct multi-step initialization semantics. The EVP API permits key and IV to be supplied in separate `EVP_CipherInit_ex()` calls (e.g. key-only followed by IV-only). A recent bug (PR openssl#29934, ASCON-AEAD128) demonstrated that a provider may silently ignore a key-only init, resulting in reuse of a previously loaded key during a subsequent IV-only init. To prevent similar regressions, this change introduces three generic tests that automatically cover all IV-taking ciphers: Verifies that: - `init(key) → init(iv)` - `init(iv) → init(key)` produce identical ciphertext (and authentication tag for AEAD ciphers) compared to single-call `init(key, iv)`. Primes a context with `key1/iv1`, then re-initializes via `init(key2) → init(iv2)` and verifies the output matches a fresh `encrypt(key2, iv2)` operation, ensuring that no previously stored key is reused. Encrypts using single-call initialization and then decrypts using multi-step initialization, verifying plaintext recovery. For AEAD ciphers, this also exercises tag verification through the multi-step path. Ciphers are discovered using `EVP_CIPHER_do_all_provided()`, requiring no maintenance when new IV-taking ciphers are added. SIV mode is skipped due to its synthetic IV semantics. CCM mode handling includes required length declarations. This provides broad regression coverage for the provider implementations that support multi-step EVP initialization. Reviewed-by: Dmitry Belyavskiy <[email protected]> Reviewed-by: Paul Dale <[email protected]> MergeDate: Thu Apr 16 07:08:17 2026 (Merged from openssl#30141)
dd5a0ea to
980a67e
Compare
980a67e to
5b57786
Compare
done in #32587 rebased I think we're almost there. I've got one more related PR open to sort. |
nhorman
left a comment
There was a problem hiding this comment.
Just the one nit regarding the extra ASCONF_INLINE macro, but don't change it unless you're going to make other updates
| #include <openssl/crypto.h> | ||
|
|
||
| /* semi-portable inline declaration */ | ||
| #define ASCON_INLINE ossl_inline |
There was a problem hiding this comment.
not at all critical, but I'm not sure we need the additional indirection here, you can just use ossl_inline directly.
There was a problem hiding this comment.
not sure we need the additional indirection here
oof thought I stripped out all of those already, solely an upstream thing
fixed in fb8eff0
thanks
|
here is the planned |
Integrate ASCON-AEAD128 authenticated encryption cipher into OpenSSL's default provider and libcrypto. ASCON-AEAD128 is specified in NIST SP 800-232 and provides authenticated encryption with associated data (AEAD) using a 128-bit key and 128-bit nonce. Implementation details: - Add cleanroom crypto/ascon implementation - Add provider cipher implementation following OpenSSL stream cipher patterns with generated parameter dispatch tables - Support standard AEAD operations including AAD handling, tag generation/verification, and context reinitialization Fixes: openssl#28271 Co-authored-by: Billy Brumley <[email protected]>
…and negative check the following for test provenance https://gitlab.com/platsec/sp800232-rit
5b57786 to
7b4aa70
Compare
|
This pull request is ready to merge |
Integrate ASCON-AEAD128 authenticated encryption cipher into OpenSSL's default provider and libcrypto. ASCON-AEAD128 is specified in NIST SP 800-232 and provides authenticated encryption with associated data (AEAD) using a 128-bit key and 128-bit nonce. Implementation details: - Add cleanroom crypto/ascon implementation - Add provider cipher implementation following OpenSSL stream cipher patterns with generated parameter dispatch tables - Support standard AEAD operations including AAD handling, tag generation/verification, and context reinitialization Fixes: #28271 Co-authored-by: Billy Brumley <[email protected]> Reviewed-by: Neil Horman <[email protected]> Reviewed-by: Andrew Dinh <[email protected]> Merge-date: Wed Sep 16 14:25:19 2026 Merged-from: #29934
…and negative check the following for test provenance https: //gitlab.com/platsec/sp800232-rit Reviewed-by: Neil Horman <[email protected]> Reviewed-by: Andrew Dinh <[email protected]> Merge-date: Wed Sep 16 14:25:19 2026 Merged-from: #29934
|
Merged to master. Thank you! @bbbrumley please open a PR for the changes.md. |
|
Wow finally! Great! |
|
Wooo! @bbbrumley Thank you for helping get this across the finish line on my behalf. A massive thank you to the entire cohort of reviewers and maintainers as well! |
Add Ascon-AEAD128 cipher provider implementation
This PR adds a complete OpenSSL provider implementation for ASCON-AEAD128. The implementation supports both streaming and one-shot encryption/decryption operations with AAD.
Fixes: #28271
Co-authored-by: Billy Brumley [email protected]