Jake Massimo
90d · built 2026-09-10
Performance
What Jake Massimo shipped in the selected window, measured in ETV, and how it compares with the 90 days before it.
Effective capacity
+14.3engineers
delivers like 15.3 (15.3x pre-AI)
Output (ETV)
3.4ETV
+1.2% vs 3.3 prior
Features share
63.7%
+5.0 pp vs prior window
Fixes share
2.1%
−0.9 pp vs prior window
Work mix
63.7% Features2.1% Maintenance30.1% Tests2.1% Docs2.1% Fixes
4 commits over 90 days, ending 2026-09-10.
Daily performance
Daily ETV, stacked by Features, Maintenance, Tests, Docs and Fixes.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 10 by ETV in the last 90 days.
- 2.0ETVAdd ML-KEM support to HPKE (draft-ietf-hpke-pq-05) (#3277) Implements ML-KEM-512, ML-KEM-768 and ML-KEM-1024 as HPKE KEMs per [draft-ietf-hpke-pq-05](https://datatracker.ietf.org/doc/html/draft-ietf-hpke-pq-05), enabling post-quantum HPKE. Also adds HKDF-SHA384, so the priority suite HPKE(ML-KEM-1024, HKDF-SHA384, AES-256-GCM) is available. Built on AWS-LC's existing ML-KEM implementation in `crypto/fipsmodule/ml_kem/`; no new cryptographic primitives are introduced. ## Spec conformance | | Value | Source | |---|---|---| | KEM IDs | 0x0040 / 0x0041 / 0x0042 | draft §8.1 Table 2, IANA HPKE registry | | Nsecret | 32 (all three) | draft §3 | | Nenc | 768 / 1088 / 1568 | draft §8.1 | | Npk | 800 / 1184 / 1568 | draft §8.1 | | Nsk | **64** (all three) | draft §3 | Three points worth calling out explicitly: **The ML-KEM shared secret is used directly, with no `ExtractAndExpand`.** DHKEM runs its shared secret through `ExtractAndExpand`; ML-KEM does not. The draft defines Encap/Decap as ML-KEM.Encaps and ML-KEM.Decaps directly, and Nsecret = 32 matches ML-KEM's native shared secret length. The generic RFC 9180 key schedule is unchanged and still processes the shared secret. **A private key is the 64-byte `d || z` seed, not the expanded decapsulation key.** FIPS 203 returns the expanded form, `dk = dk_PKE || ek_PKE || H(ek_PKE) || z`: FIPS 203 page 16 <img width="622" height="120" alt="Screenshot 2026-05-29 at 1 46 17 PM" src="https://github.com/user-attachments/assets/4edf7c44-c335-4b8e-a1e6-af367db62940" /> The draft does not use that form. It is explicit: "the decapsulation key is returned in seed format rather than the expanded form returned by ML-KEM.KeyGen", and Nsk is 64 for every parameter set. So `EVP_HPKE_KEM_private_key_len()` returns 64, not 1632/2400/3168, and `EVP_HPKE_MAX_PRIVATE_KEY_LENGTH` stays at 64. That is the serialized form: the seed is what `EVP_HPKE_KEY_init` accepts and what `EVP_HPKE_KEY_private_key` emits. Internally the seed is expanded once, at import or generation, with `ml_kem_*_keypair_deterministic`, which takes exactly the 64-byte seed, and the expanded key is cached in the struct — see the FIPS section for why. This matters for interoperability — every published test vector's `skRm` is 64 bytes, and a peer implementing the draft cannot import an expanded key. BoringSSL reaches the same conclusion independently: [`EVP_HPKE_MAX_PRIVATE_KEY_LENGTH` is 64](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/include/openssl/hpke.h#L72) there too. **Auth mode is refused for ML-KEM.** ML-KEM cannot do AuthEncap/AuthDecap (draft §7.2), so the `auth_encap_with_seed` and `auth_decap` hooks are NULL and `EVP_HPKE_CTX_setup_auth_sender`/`_auth_recipient` fail with `EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE`. The encapsulation key is validated on encap via `ml_kem_*_check_pk`, since the draft requires an encapsulation key check failure to surface as an HPKE EncapError. `mlk_kem_enc_derand` in the backend already performs the same modulus check, so this call is belt-and-braces — it makes the intent explicit and gives the failure a distinguishable reason code. Error codes differ slightly from upstream: a bad ML-KEM peer key raises `EVP_R_INVALID_PEER_KEY` where upstream raises `EVP_R_DECODE_ERROR`. Ours is consistent with the X25519 path in the same file and with RFC 9180's EncapError, so it is intentional. ## Behaviour change to `EVP_HPKE_KEY_cleanup` `EVP_HPKE_KEY_cleanup` was a documented no-op. It now cleanses both secrets — the seed and the cached expanded decapsulation key — and clears `kem`, returning the key to the zero state, and tolerates NULL. Clearing `kem` matters more than it looks. Cleansing alone would leave `kem` set and the public key intact with an all-zero private key — and every 64-byte string is a valid ML-KEM seed, while a zero X25519 scalar is clamped to a valid one. A use-after-cleanup would therefore *succeed*, decapsulating under a key anyone can compute, rather than failing. Clearing `kem` makes that path fail instead. Six entry points read `key->kem` without checking it, so on a key with no KEM they dereference NULL: `EVP_HPKE_KEY_public_key`, `EVP_HPKE_KEY_private_key`, `EVP_HPKE_CTX_setup_recipient`, both auth-sender setups, and `EVP_HPKE_CTX_setup_auth_recipient`. That is pre-existing — `main` has the same unguarded code — but there it was only reachable by passing a key that had never been initialized, and clearing `kem` in cleanup adds a second route to it. All six now fail with `EVP_R_NO_KEY_SET`. `HPKETest.ZeroedKeyFailsCleanly` covers them; with the guards removed it terminates with SIGSEGV rather than failing, so the test detects the guard rather than passing by construction. Secrets also no longer outlive the calls that use them. The derived shared secret is cleansed once the key schedule has consumed it, and the encapsulation entropy is cleansed after sender setup returns, in both the base and auth paths. A failed `EVP_HPKE_KEY_init` or `EVP_HPKE_KEY_generate` now cleanses as well, rather than only clearing `kem`. `mlkem_init_key` derives the expanded decapsulation key straight into the struct, so a failure after that point would otherwise leave key material behind in a key the caller has been told is unusable. ## FIPS builds **Service indicator.** HPKE is not an approved service, so these APIs must leave the service indicator unchanged. Without that, an HPKE call leaves a counter differential from the approved primitives underneath — HKDF (`hkdf.c:35-50`), AES-GCM (`AEAD_GCM_verify_service_indicator`) and `RAND_bytes` (`rand.c:588`) — which a caller would read as an approved service having been performed. The 11 public entry points that perform crypto now lock the indicator for the duration of the call, with the bodies moved to `static` functions; the other ten are lifecycle and accessor functions that only move or clear memory, so there is nothing to suppress. Exported symbols are unchanged. This is not ML-KEM specific — HPKE over X25519 was moving the counter before this change too. Verified on FIPS builds on x86-64 and aarch64: a `ServiceIndicatorNotApproved` test per ML-KEM suite, plus an X25519 test covering the four entry points the ML-KEM suites cannot reach (auth-mode sender, auth-mode recipient, and both deterministic sender setups). Removing only the lock/unlock calls makes the corresponding tests fail, so they detect the lock rather than passing by construction. **Where the keygen PCT lands.** In FIPS builds the ML-KEM key generation entry point runs a pairwise consistency test — a full encapsulation and decapsulation — gated by `MLK_CONFIG_KEYGEN_PCT`, which is set exactly when AWS-LC is built in FIPS mode. The expanded decapsulation key has to be derived through that entry point, because `crypto/fipsmodule/ml_kem/ml_kem.h` exposes no PCT-free seed-expansion function today and `crypto/fipsmodule/ml_kem/mlkem/` is a pristine import driven by `importer.sh`, so it should not be patched from `crypto/hpke`. The struct therefore caches the expanded key at import or generation, so the PCT is paid once per key rather than on every decapsulation. That placement is deliberate: without the cache, a key generation health test with fatal module-failure semantics would sit on a path reached from the network. One consequence worth noting for reviewers is that `EVP_HPKE_KEY_init`, which imports a caller-supplied seed rather than generating a key, also pays the PCT. ## ABI impact — needs a maintainer decision `struct evp_hpke_key_st` is public and stack-allocatable. An ML-KEM-1024 encapsulation key is 1568 bytes, so the struct cannot keep holding keys in 32-byte inline arrays; `sizeof(EVP_HPKE_KEY)` goes from 72 to 4808 bytes. `EVP_HPKE_MAX_PUBLIC_KEY_LENGTH` and `EVP_HPKE_MAX_ENC_LENGTH` also change from 32 to 1568, and the struct additionally caches the expanded ML-KEM decapsulation key (`EVP_HPKE_MAX_EXPANDED_PRIVATE_KEY_LENGTH`, 3168 bytes) so that decapsulation performs no key generation. See the FIPS section above for why that cache is there. The four `abidiff` jobs will therefore report an ABI change, and there is no suppression mechanism in `.github/docker_images/abidiff/diff.sh` — it fails on any `abidiff` exit >= 4. Per `docs/SymbolVersioning.md` this implies an `ABI_VERSION` bump and a new SONAME, which is a release-level decision affecting every consumer, so I have not made it here. **Please advise whether you want the bump in this PR or handled as part of a release.** Some notes to inform that decision: - No layout preserves ABI. Any struct able to hold a 1568-byte key changes size, so the break is unavoidable rather than a consequence of this particular design. - The layout follows [upstream BoringSSL's](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/include/openssl/hpke.h#L394-L398) fixed-size-inline-array approach, though it is no longer byte-identical to it: we additionally cache the expanded decapsulation key, which upstream does not, because upstream's seed expansion is PCT-free and ours is not. Adding a future KEM is still a matter of raising these constants rather than changing the shape of the struct. - An earlier revision of this PR used heap pointers inside the struct, which kept `sizeof` small but made `EVP_HPKE_KEY` a non-trivially-copyable type with owning pointers behind an API documented as stack-allocatable. That caused several problems — re-initialising a key leaked its old key material, `EVP_HPKE_KEY_copy(k, k)` freed the key and returned success, and `EVP_HPKE_KEY_zero` silently stopped scrubbing the private key. Fixed-size inline storage removes that whole class of bug, and matches how the library handles the same situation elsewhere (`union evp_aead_ctx_st_state`'s `opaque[564]` in `include/openssl/aead.h`, and `CRYPTO_MUTEX`'s sized padding in `include/openssl/thread.h`). Two ECH stack buffers in libssl are sized by these macros and grow accordingly (`ssl/handshake_client.cc:339`, `ssl/encrypted_client_hello.cc:522`). ECH negotiates only DHKEM(X25519, HKDF-SHA256), so they cannot actually be filled beyond 32 bytes; sizing them by the X25519 lengths instead would avoid the growth, but that is a separate cleanup and is left out to keep this PR focused. Relatedly, `ECHServerConfig::Init` now rejects an ECHConfig whose `kem_id` is not DHKEM(X25519, HKDF-SHA256). It already required the config's `kem_id` to match the configured key, but an `EVP_HPKE_KEY` can now hold an ML-KEM key, so a config and key which agreed on ML-KEM were accepted for a protocol that is only defined over X25519. `SSL_marshal_ech_config` takes the KEM from the key, so such a config was reachable through the public API. `SSLTest.UnsupportedECHConfig` covers it, and fails if the check is removed. ## Relationship to BoringSSL Upstream implements the same draft. Links below are pinned to [`e5a214a2`](https://github.com/google/boringssl/commit/e5a214a259892b5b9b9384a69d2beb61fb8c3521): - [`crypto/hpke/hpke.cc`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/crypto/hpke/hpke.cc) — their implementation, in particular [`struct MLKEMHPKE`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/crypto/hpke/hpke.cc#L811-L913), [`PRIVATE_KEY_LEN = MLKEM_SEED_BYTES`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/crypto/hpke/hpke.cc#L813) and [`HpkeDecap`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/crypto/hpke/hpke.cc#L895-L911), which re-expands the stored seed on every decapsulation. This change stores the same 64-byte seed, but expands it once at import or generation and caches the result, for the FIPS reason above. - [`include/openssl/hpke.h`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/include/openssl/hpke.h) — their public header, in particular [`struct evp_hpke_key_st`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/include/openssl/hpke.h#L394-L398) and [`EVP_HPKE_MAX_PRIVATE_KEY_LENGTH 64`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/include/openssl/hpke.h#L72), which independently corroborates the seed key format. This change deliberately follows upstream's public API while diverging on implementation: - **API alignment.** Macros are `EVP_HPKE_MLKEM512` / `_MLKEM768` / `_MLKEM1024` (no `KEM_` infix), matching upstream's spelling, as `EVP_hpke_mlkem768` and `EVP_HPKE_HKDF_SHA384` already did. `EVP_HPKE_MAX_PRIVATE_KEY_LENGTH 64` and the seed-format private key match upstream too, so consumers built against either library see the same API and the same serialized key format. - **Kept in C.** Upstream's HPKE is now C++ (`hpke.cc`), part of a library-wide C-to-C++ migration — their `crypto/fipsmodule` has no `.c` files left. AWS-LC is not following that migration, so this stays in C. Upstream's ML-KEM parameterisation needs C++ templates because their ML-KEM API is built on opaque types; ours is byte buffers plus lengths, so a small `MLKEM_METHOD` table of function pointers expresses the same thing and the three parameter sets share one implementation. - **Uses AWS-LC's ML-KEM.** Upstream's HPKE is written against its own `<openssl/mlkem.h>` and a BCM layer (`BCM_mlkem768_encap_external_entropy`) that AWS-LC does not have. We use `crypto/fipsmodule/ml_kem/`, which already exposes deterministic encapsulation directly, so no equivalent plumbing is needed. - **ML-KEM-512 is exposed.** Upstream ships only 768 and 1024. The draft registers 512 and includes it "in the interest of completeness" while preferring 768/1024, so it is available here for callers that need it. ## Testing Known-answer tests come from the WG's machine-readable vectors, the `[TestVectors]` citation in the draft: `crypto/hpke/test-vectors-pq.json`, fetched from https://github.com/hpkewg/hpke-pq. These are vendored the same way RFC 9180's `test-vectors.json` already is, so `translate_test_vectors.py` stays reproducible. Following upstream, which keeps [`hpke_test_vectors_pq.txt`](https://github.com/google/boringssl/blob/e5a214a259892b5b9b9384a69d2beb61fb8c3521/crypto/hpke/hpke_test_vectors_pq.txt) separate from the RFC 9180 file, the PQ vectors are generated into their own `crypto/hpke/hpke_test_vectors_pq.txt`. `crypto/hpke/hpke_test_vectors.txt` is regenerated and is byte-for-byte unchanged from `main`. Three suites are covered — (ML-KEM-512, HKDF-SHA256, AES-128-GCM), (ML-KEM-768, HKDF-SHA256, AES-128-GCM) and (ML-KEM-1024, HKDF-SHA384, AES-256-GCM). The fourth ML-KEM vector in the JSON uses TurboSHAKE256, which this library does not implement, and is filtered out by the script. These are real KATs, so they pin the wire format rather than just internal self-consistency: `enc` fixes Nenc and the encapsulation, `skRm` at 64 bytes fixes Nsk, `pkRm` fixes Npk, and the ciphertexts and exported values fix the key schedule including the HKDF-SHA384 (Nh = 48) path. I confirmed the vectors are genuinely exercised by corrupting one `enc` value and checking the suite fails, rather than trusting a green run. The vector harness gained an optional `kem_id` attribute, defaulting to DHKEM(X25519) when absent so the RFC 9180 vectors are unaffected. ML-KEM vectors carry `ikmE` and `enc` where DHKEM vectors carry `skEm` and `pkEm`, because ML-KEM has no ephemeral key pair — encapsulation takes 32 bytes of entropy and emits a ciphertext. `HPKETest.RoundTrip` now sweeps every KEM rather than just X25519, as upstream's does, skipping auth mode for the ML-KEM KEMs. That covers combinations the fixed parameter table misses, notably ML-KEM-512/768 with HKDF-SHA384 and with ChaCha20-Poly1305, across three `info` and three `ad` values. New tests beyond the KATs cover round-trip and multi-message sealing for each suite, key serialization round-trip, copy/move including self-copy and self-move, auth-mode rejection, re-initialising an already-initialised key, rejection of an invalid encapsulation key on encap, an ML-KEM-1024 encapsulation key passed where an `enc` is expected, seed perturbation producing a distinct valid key, implicit rejection of a corrupted encapsulation, use-after-cleanup failing, re-initialization after cleanup succeeding, the zero state after a failed initialization, every entry point rejecting a key with no KEM, cleanup of a NULL key and cleanup leaving the zero state, the service indicator behaviour described above, and the length and buffer-size error paths. Every negative test asserts the specific reason code rather than just a false return, matching the existing X25519 tests. One note on the vendored JSON: the WG file double-encodes `info` and `pt` — the values are the hex encoding of an ASCII hex string, so `info` decodes to the text `4f6465206f6e2061204772656369616e2055726e` rather than to "Ode on a Grecian Urn". The published ciphertexts were computed over those literal bytes, so they are passed through verbatim. There is a comment in `translate_test_vectors.py` to stop someone "fixing" it later. ## Performance 1000 iterations. Encap/Decap are `setup_sender`/`setup_recipient`, so they include the key schedule. Keygen is measured separately, and for ML-KEM it carries the seed expansion. Re-measured after the expanded key was cached, so decap no longer expands the seed. | Ciphersuite | Keygen | Encap | Decap | Encap+Decap | |---|---|---|---|---| | X25519 + SHA256 + AES-128-GCM | 28.7 us | 25.9 us | 18.5 us | 44.4 us | | ML-KEM-512 + SHA256 + AES-128-GCM | 5.6 us | 8.0 us | 7.3 us | 15.3 us | | ML-KEM-768 + SHA256 + AES-256-GCM | 8.1 us | 10.7 us | 10.6 us | 21.3 us | | ML-KEM-1024 + SHA384 + AES-256-GCM | 10.5 us | 15.0 us | 15.7 us | 30.7 us | Non-FIPS `RelWithDebInfo` build, Apple M-series, macOS. Relative numbers are what matter; these are not comparable to the x86 figures in an earlier revision of this description. Every ML-KEM suite is faster than X25519 DHKEM here, including ML-KEM-1024. The seed expansion sits in the keygen column rather than in decap, which is the point of caching it: a key is imported or generated once and then decapsulates many times. FIPS builds add the keygen PCT to keygen and to key import, as described above; these numbers do not include it. ## Re-verified against draft-ietf-hpke-pq-05 Draft -05 was published on 6 July 2026, after this work started. Section 3 (ML-KEM) is unchanged from -04, and the IANA parameter table rows for 0x0040/0x0041/0x0042 are identical, so nothing normative moved for this implementation and no code change was needed. The section numbers cited here and in the code (§3, §7.2, §8.1) are unchanged in -05. The published test vectors *were* regenerated in -05 — none of the -04 ML-KEM values survive. The vendored `test-vectors-pq.json` is already the -05 set: all four ML-KEM suites' `ikmE`, `skRm` and `shared_secret` values appear in -05 and none appear in -04. Regenerating from it leaves `hpke_test_vectors_pq.txt` byte-identical, so the KATs are current and were not touched. One item for future work: TurboSHAKE is now RFC 9861 rather than `draft-irtf-cfrg-kangarootwelve`, which matters if the single-stage SHAKE/TurboSHAKE KDFs (0x0010-0x0013) are implemented later. ## Out of scope Deliberately not included, to keep this reviewable: - The PQ/T hybrids, including X-Wing (0x647a). - The single-stage SHAKE and TurboSHAKE KDFs (0x0010-0x0013), and therefore the one ML-KEM test vector that uses TurboSHAKE256. - `DeriveKeyPair` / `EVP_HPKE_KEY_derive`, which the draft defines over SHAKE256. Upstream has it. Not required by anything here, since the KATs load `skRm` directly, but it does mean the vectors' `ikmR` path is not exercised. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.github.com-aws-aws-lc · 3b4b0895 · 2026-09-03
- 1.3ETVML-DSA: import and enable aarch64 assembly backend from mldsa-native (#3219) ## Summary - Imports the **complete** AArch64 native arithmetic backend from mldsa-native into ML-DSA, providing Neon-accelerated assembly for all polynomial operations including NTT, INTT, rejection sampling, and polyz unpack. - All 17 AArch64 `.S` files have completed HOL-Light functional correctness proofs upstream (in mldsa-native main, with the corresponding s2n-bignum proofs in flight or merged). - Follows the same integration pattern as #3195 (x86_64 backend) and ML-KEM's AArch64 backend, using s2n-bignum macros for symbol visibility. Merged [aws/aws-lc-rs#1113](https://github.com/aws/aws-lc-rs/pull/1113) so the aws-lc-rs CC builder can discover the new aarch64 `.S` files (mirrors #1110 for the x86_64 backend). ## Benchmark Measured on AWS Graviton3 (Neoverse-V1), `bssl speed -filter MLDSA`, single run (ops/sec, higher is better): ``` ┌─────────────────────┬──────────┬──────────┬─────────┐ │ Operation │ Before │ After │ Speedup │ ├─────────────────────┼──────────┼──────────┼─────────┤ │ MLDSA44 keygen │ 9,309 │ 19,804 │ 2.13x │ │ MLDSA44 signing │ 2,597 │ 6,052 │ 2.33x │ │ MLDSA44 verify │ 9,106 │ 20,269 │ 2.23x │ │ MLDSA65 keygen │ 5,229 │ 11,515 │ 2.20x │ │ MLDSA65 signing │ 1,621 │ 3,874 │ 2.39x │ │ MLDSA65 verify │ 5,638 │ 12,227 │ 2.17x │ │ MLDSA87 keygen │ 3,448 │ 7,045 │ 2.04x │ │ MLDSA87 signing │ 1,272 │ 3,038 │ 2.39x │ │ MLDSA87 verify │ 3,412 │ 7,389 │ 2.16x │ └─────────────────────┴──────────┴──────────┴─────────┘ ``` Roughly 2.0×–2.4× across the board. The full assembly coverage (NTT/INTT included) is what makes this possible vs. the previous partial-coverage version. ## Changes - **New files**: 17 AArch64 assembly files imported under `mldsa/native/aarch64/src/` (`ntt_aarch64_asm.S`, `intt_aarch64_asm.S`, `pointwise_montgomery_aarch64_asm.S`, `mld_polyvecl_pointwise_acc_montgomery_l{4,5,7}_aarch64_asm.S`, `poly_caddq_aarch64_asm.S`, `poly_chknorm_aarch64_asm.S`, `poly_decompose_{32,88}_aarch64_asm.S`, `poly_use_hint_{32,88}_aarch64_asm.S`, `polyz_unpack_{17,19}_aarch64_asm.S`, `rej_uniform_aarch64_asm.S`, `rej_uniform_eta{2,4}_aarch64_asm.S`), plus `meta.h`, `arith_native_aarch64.h`, and 4 constant-table `.c` files (`aarch64_zetas.c`, `polyz_unpack_table.c`, `rej_uniform_eta_table.c`, `rej_uniform_table.c`). - **Modified**: `mldsa_native_backend.h` — dispatches to upstream `mldsa/native/aarch64/meta.h` on `OPENSSL_AARCH64`. Unlike x86_64 (which needs a custom `mldsa_x86_64_meta.h` because upstream advertises C-intrinsic ops we don't import), aarch64 is 100% assembly so we use the upstream `meta.h` verbatim. ## Functions accelerated All imported AArch64 functions have completed HOL-Light formal verification proofs upstream: - NTT / INTT - rej_uniform / rej_uniform_eta{2,4} - polyz_unpack_{17,19} - poly_decompose_{32,88} - poly_use_hint_{32,88} - poly_caddq, poly_chknorm - pointwise Montgomery multiplication - polyvecl pointwise accumulate (l=4,5,7) See the [mldsa-native HOL Light README](https://github.com/pq-code-package/mldsa-native/blob/main/proofs/hol_light/README.md) for the authoritative list. ## Call-outs - Compile-time dispatch via `OPENSSL_AARCH64`; no runtime CPU feature check is needed (Neon is mandatory on AArch64). ## Testing - All 82 ML-DSA tests pass on aarch64 (KAT, Wycheproof verify/sign-with-seed/sign-without-seed, expanded key validation). By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. --------- Signed-off-by: Jake Massimo <jakemas@amazon.com> Co-authored-by: Ubuntu <ubuntu@ip-172-31-61-117.us-west-2.compute.internal>github.com-aws-aws-lc · 03311a01 · 2026-06-25
- 0.0ETVci: opt in to allow-unsafe-pr-checkout for gated pull_request_target jobs (#3313) ## Summary `actions/checkout@v7` (released **2026-06-18**) now refuses by default to check out fork PR code when the workflow is triggered by `pull_request_target`, to block the common "pwn request" pattern. See [GitHub changelog](https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/) and [Securely using pull_request_target](https://gh.io/securely-using-pull_request_target). aws-lc picked up `checkout@v7` via the github-actions Dependabot bump (#3307). This broke **every fork PR** that hits our Android CI (and the security-review job), failing the checkout step with: > Refusing to check out fork pull request code from a 'pull_request_target' workflow ... To opt in ... set 'allow-unsafe-pr-checkout: true' on the actions/checkout step. This is currently blocking merges (e.g. #3219). ## Change Set `allow-unsafe-pr-checkout: true` on the three `pull_request_target` jobs that intentionally check out the PR head SHA: - `android-omnibus.yml` — `device-farm` - `image-build-android.yml` — `build` - `security-review.yml` — `execute` ## Why this is safe This is **not** a workaround that weakens our posture — it re-asserts a protection we already have. Per our [AWS-542](https://www.aristotle.a2z.com/implementations/AWS-542) deployment architecture, each of these jobs gates the fork-head checkout behind a deployment-environment approval (`environment: ${{ needs.*.outputs.approval-env }}`), so fork code is only checked out **after** a maintainer manually approves the run. That approval gate is exactly the human-in-the-loop mitigation GitHub's new default is designed to force everyone to add — which we built precisely so we can use OIDC + `pull_request_target` while still safely checking out untrusted code. The opt-in restores the prior (intended) behavior on jobs that are already gated; it does not bypass the gate. `abidiff.yml` also checks out the PR head SHA but triggers on `pull_request` (not `pull_request_target`), so it is unaffected and left unchanged. A sweep of all workflows confirms these three are the only `pull_request_target` jobs that check out the PR head, so the fix is complete. ## Reverting the bump is not a durable fix Downgrading `checkout` back to v4–v6 would only mask this until **2026-07-16**, when GitHub [backports the same enforcement to floating `v4`/`v5`/`v6` major tags](https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/). Since we pin floating majors, the refusal would silently return then. The opt-in is required regardless of the action version. ## Note on this PR's own CI `pull_request_target` workflow definitions are read from the **base branch**, so this change cannot take effect until it lands on `main`. This PR's own Android/security-review checks will therefore stay red (they run main's current definition); they'll pass once this is merged and open fork PRs (including #3219) are re-run. ## Testing - YAML validated for all three edited workflows. - No logic changes beyond the single opt-in input per affected checkout step. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license. Signed-off-by: Jake Massimo <jakemas@amazon.com>github.com-aws-aws-lc · e8ea407e · 2026-06-25
- 0.0ETVci: pin security review to the PR head commit (#3425) `security-review.yml` starts the CodeBuild review with `SOURCE_VERSION="pr/<number>"`. CodeBuild resolves that ref when it fetches the source, so it races pushes to the PR, while the commit status is posted against `github.event.pull_request.head.sha`. When those disagree a maintainer sees `security-review / report: success` on the commit they are about to merge, for a review of different code. It is the only unpinned reference in the workflow — the checkout (line 60) and the status (line 128) already use `head.sha`. Seen on #3277: three runs completed `success` for `82871d85d`, `4d12f7bb6` and `dcb7a8317`, while the report stayed on `a5bf56b3` and kept listing findings already fixed in the newer commits. The workflow only checks that CodeBuild exited cleanly, never which commit it fetched. Fix: use the reference-and-commit-ID form, `refs/pull/<number>/head^{<sha>}`. Per the [source version docs](https://docs.aws.amazon.com/codebuild/latest/userguide/sample-source-version.html) a bare commit ID would make CodeBuild "download the entire repository to find the version", while naming the ref means it "downloads only the specified branch" — so this pins the commit without the clone cost `pr/<number>` avoids. It also does not change what is reviewed: the docs give the pull request form as `refs/pull/1/head`, so `pr/<number>` already resolved to the PR head, not a merge commit. Not runnable outside this repo's CI (needs the `SecurityReview-aws-lc` project and OIDC role). Verified offline: the workflow parses, the value renders to `refs/pull/3277/head^{dcb7a8317...}` and survives the quoted shell expansion, and the other `pull_request.number` uses are correctly per-PR. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.github.com-aws-aws-lc · 8ed4798e · 2026-08-18