Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unbounded AES-GCM verification #143

Closed
wants to merge 8 commits into from

Conversation

RyanGlScott
Copy link
Contributor

This proves AES-GCM functions, building on top of the cryptol-specs work in GaloisInc/cryptol-specs#72 and the saw-script work in GaloisInc/saw-script#2037. This supersedes previous efforts in #80 and #139.

Notes:

  • This requires a newer version of SAW to work than what aws-lc-verification's CI currently uses, so I needed to update the SAW binary distribution as a result. This version of SAW uses a newer version of Cryptol which overhauls the module system, and as a result, the Cryptol code in the aws-lc-verification repo (some of which is unrelated to the AES-GCM proofs) needed to be refactored slightly to work with the newer version of Cryptol. (See the "Update Cryptol code to work with latest Cryptol/cryptol-specs" commit for the details.)
  • The bulk of the proofs are found in the Add unbounded AES-GCM verification. commit, with some minor tweaks of my own in subsequent commits to make the proofs succeed in a more reasonable amount of time.
  • Some of the AES-GCM proofs use Z3's Constrained Horn Clause (CHC) feature, which is buggy on Z3-4.8.8. aws-lc-verification's CI is currently using Z3-4.8.8, so I upgrade it to a more recent version.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@@ -40,6 +40,7 @@ AWS libcrypto includes many cryptographic algorithm implementations for several
| [SHA-2](SPEC.md#SHA-2) | 384 | EVP_DigestInit, EVP_DigestUpdate, EVP_DigestFinal | neoverse-n1, neoverse-v1 | NoEngine, MemCorrect, ArmSpecGap, ToolGap | SAW, NSym |
| [HMAC](SPEC.md#HMAC-with-SHA-384) | with <nobr>SHA-384</nobr> | HMAC_CTX_init, HMAC_Init_ex, HMAC_Update, HMAC_Final, HMAC | SandyBridge+ | NoEngine, MemCorrect, InitZero, NoInline, CRYPTO_once_Correct | SAW |
| [<nobr>AES-KW(P)</nobr>](SPEC.md#AES-KWP) | 256 | AES_wrap_key, AES_unwrap_key, AES_wrap_key_padded, AES_unwrap_key_padded | SandyBridge+ | InputLength, MemCorrect, NoInline | SAW |
| [<nobr>AES-GCM</nobr>](SPEC.md#AES-GCM) | 256 | gcm_ghash_avx, aes_hw_ctr32_encrypt_blocks, aesni_gcm_encrypt, aesni_gcm_decrypt | SandyBridge+ | MemCorrect, NoInline | SAW |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apetcher-amazon: The AES-GCM proofs are documented here in the README.md...

SPEC.md Outdated
Comment on lines 38 to 47
## AES-GCM

These functions are verified to have the following properties related to AES-GCM using AES-256. These functions are defined in [NIST SP 800-38D](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf). For more detailed specifications, see [AES-GCM.saw](SAW/proof/AES/AES-GCM.saw).

| Function | Preconditions | Postconditions |
| ---------------| -------------| --------------- |
| gcm_ghash_avx | <ul><li>The hash table parameter points to values precomputed by calling the `gcm_init_Htable` Cryptol function on a symbolic 128-bit value.</li><li>The input length is a symbolic integer 16 * k such that 0 < k < 18.</li></ul> | <ul><li>The Xi buffer holds the correct hash value as defined by the AES-GCM specification.</li></ul> |
| aes_hw_ctr32_encrypt_blocks | <ul><li>The global variable `OPENSSL_ia32cap_P` exists and points to a value that [disables AVX512[F|DQ|BW|VL] instructions](https://www.openssl.org/docs/manmaster/man3/OPENSSL_ia32cap.html).</li><li>The length is a symbolic integer 16 * k such that 0 < k < 18.</li><li>The key parameter points to a 32-byte AES-256 key.</li></ul> | <ul><li>The global variable `OPENSSL_ia32cap_P` continues to point to the same value.</li><li>The output buffer holds the value produced by the GCTR function as defined by the AES-GCM specification.</li></ul> |
| aesni_gcm_encrypt | <ul><li>The input length is a symbolic integer k such that k < 2^36.</li><li>The key parameter points to a 32-byte AES-256 key.</li><li>The hash table parameter points to values precomputed by calling the `get_Htable` Cryptol function on the AES-256 key.</li></ul> | <ul><li>If k >= 288, then the first k bytes of the output buffer will point to the ciphertext produced by the GCM-AE_K function as defined by the AES-GCM specification. If k < 288, then the output buffer will not be modified.</li><li>If k >= 288, then the Xi buffer will point to the updated Xi value computed by the `aesni_gcm_encrypt` Cryptol function. If k < 288, then the Xi buffer will not be modified.</li><li>If k >= 288, then the initialization vector buffer will point to the updated initialization vector value computed by the `aesni_gcm_encrypt` Cryptol function. If k < 288, then the initialization vector will not be updated.</li><li>The return value will be equal to 96 * floor(k / 96).</li></ul> |
| aesni_gcm_decrypt | <ul><li>The input length is a symbolic integer k such that k < 2^36.</li><li>The key parameter points to a 32-byte AES-256 key.</li><li>The hash table parameter points to values precomputed by calling `get_Htable` on the AES-256 key.</li></ul> | <ul><li>If k >= 96, then the first k bytes of the output buffer will point to the ciphertext produced by the GCM-AD_K function as defined by the AES-GCM specification. If k < 96, then the output buffer will not be modified.</li><li>If k >= 96, then the Xi buffer will point to the updated Xi value computed by the `aesni_gcm_decrypt` Cryptol function. If k < 96, then the Xi buffer will not be modified.</li><li>If k >= 96, then the initialization vector buffer will point to the updated initialization vector value computed by the `aesni_gcm_decrypt` Cryptol function. If k < 96, then the initialization vector will not be updated.</li><li>The return value will be equal to 96 * floor(k / 96).</li></ul> |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...as well as here in the SPEC.md.

@RyanGlScott RyanGlScott force-pushed the rgs/aes-gcm-unbounded branch from 072de4c to 8ae6e6a Compare April 23, 2024 21:09
@RyanGlScott RyanGlScott force-pushed the rgs/aes-gcm-unbounded branch from 8ae6e6a to 2d7002d Compare April 23, 2024 22:23
@RyanGlScott
Copy link
Contributor Author

Every invocation of SAW is failing with something like this:

Proof results for ['/usr/bin/time', 'saw', 'proof/SHA512/verify-SHA384-x86.saw']: 0.002012973999967471s
saw: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by saw)
saw: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by saw)
saw: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by saw)
Command exited with non-zero status 1
0.00user 0.00system 0:00.00elapsed ?%CPU (0avgtext+0avgdata 720maxresident)k
0inputs+0outputs (0major+64minor)pagefaults 0swaps

Exit 1

I think this might be happening because the version of SAW binary located at the new SAW_URL is built for Ubuntu 22.04, but the Docker image is based on Ubuntu 20.04 (and therefore uses an older version of glibc than what the SAW binary supports). We should replace the SAW_URL binary with a Ubuntu 20.04 build of SAW, such as the one located at the saw-1.1.0.99-ubuntu-20.04-X64 (GHC 9.2.8) link here. @pennyannn, can you help me do this?

@pennyannn
Copy link
Contributor

@RyanGlScott , Sorry about that! I've uploaded a new SAW for Ubuntu20.04 at https://saw-builds.s3.us-west-2.amazonaws.com/saw-1.1.0.99-2024-04-23-48dcbf7bc-PR2037-Linux-x86_64.tar.gz.

@RyanGlScott RyanGlScott force-pushed the rgs/aes-gcm-unbounded branch from 2d7002d to 44639c8 Compare April 23, 2024 23:09
RyanGlScott and others added 8 commits April 24, 2024 10:10
This patch:

* Bumps the `cryptol-specs` submodule to bring in the changes from
  GaloisInc/cryptol-specs#72. These changes are
  necessary to support a later commit which adds proofs for AES-GCM.

  As part of bumping the submodule, the names of some modules were renamed, so
  this patch updates the imports on the `aws-lc-verification` side accordingly.
* Updates the Cryptol specifications to support the syntax used in Cryptol 3.
  Cryptol 3 overhauls its module system, and as a result, some Cryptol code
  which used to work in Cryptol 2 (primarily involving anonymous submodules)
  no longer works in Cryptol 3. Luckily, the code is easily repaired by moving
  definitions around.
Co-authored-by: Robert Dockins <[email protected]>
Co-authored-by: Samuel Breese <[email protected]>
The `EVP_EncryptUpdate`, `EVP_DecryptUpdate`, `aesni_gcm_encrypt`, and
`aesni_gcm_decrypt` proofs are particularly tricky and require particular SAW
settings in order to go through. They also require the use of Constrained Horn
Clause (`chc`) in Z3, so we make use of `llvm_verify_fixpoint_chc_x86` instead
of `llvm_verify_fixpoint_x86`.
Doing so requires some additional rewrites, which I've added to
`goal-rewrites-AES.saw`.

This requires splitting up the EVP_EncryptUpdate proof into two parts: one
where the `len` is zero, and one where the `len` is non-zero.
This updates the `SAW_URL` environment variables to point to a more recent
version of SAW that includes the changes from
GaloisInc/saw-script#2037, which are necessary to
support the AES-GCM proofs.
Z3's Constrained Horn Clause (CHC) solving is buggy on Z3-4.8.8 and will return
incorrect answers, which affects the AES-GCM proof. This patch upgrades to a
newer version of Z3 which is not affected by the bug.
@RyanGlScott RyanGlScott force-pushed the rgs/aes-gcm-unbounded branch from 44639c8 to 158e8b7 Compare April 24, 2024 14:10
@RyanGlScott
Copy link
Contributor Author

Based on the most recent pipeline feedback, I was able to identify a failure in the saw-aarch64 proofs that was caused by me forgetting to update a reference to a file in cryptol-specs that has changed locations. I've now fixed this.

I am less clear on what is causing the failures in the other jobs, however. Specifically:

  • The coq job is failing, but looking at the last lines in the CI log, all I see is:

    COQC src/Everything.v
    COQC src/PerfTesting/PerfTestPrint.v
    COQC src/PerfTesting/PerfTestSearch.v
    COQC src/PerfTesting/PerfTestSearchPattern.v
    Finished transaction in 2.784 secs (2.624u,0.159s) (successful)
    Finished transaction in 2.784 secs (2.656u,0.127s) (successful)
    Finished transaction in 2.817 secs (2.677u,0.14s) (successful)
    Finished transaction in 0.052 secs (0.052u,0.s) (successful)
    Finished transaction in 0. secs (0.u,0.s) (successful)
    Finished transaction in 0.005 secs (0.005u,0.s) (successful)
    Finished transaction in 0. secs (0.u,0.s) (successful)
    Finished transaction in 1.314 secs (1.239u,0.075s) (successful)
    Finished transaction in 1.307 secs (1.263u,0.043s) (successful)
    Finished transaction in 2.788 secs (2.771u,0.015s) (successful)
    Finished transaction in 11.063 secs (10.906u,0.151s) (successful)
    Finished transaction in 9.561 secs (9.499u,0.059s) (successful)
    Finished transaction in 4.481 secs (4.439u,0.039s) (successful)
    make[1]: Leaving directory '/github/workspace/Coq/fiat-crypto'
    

    Nothing there (or in the rest of the log) stands out as being a source of failure, so I'm unclear what is going on in that job.

  • The saw-x86_64 job is also failing, but for even more mysterious reasons. Here are the last lines in the CI log:

    + ./scripts/x86_64/post_build.sh
    + mkdir -p build/llvm_x86/crypto build/x86/crypto
    + cp build_src/llvm_x86/crypto/crypto_test build/llvm_x86/crypto/crypto_test
    + cp build_src/x86/crypto/crypto_test build/x86/crypto/crypto_test
    + extract-bc build/llvm_x86/crypto/crypto_test
    + ./scripts/x86_64/run_checks_release.sh
    + [ -n  ]
    + /usr/bin/python3 ./scripts/parallel.py --file ./scripts/x86_64/release_jobs.sh
    

    Does the parallel.py script make it possible to view the output at the point that something fails?

@apetcher-amazon
Copy link
Contributor

If you scroll up in the output of the coq job, you will see that the build is failing because the Coq files generated by this version of SAW appear to be different than what is expected.

I suspect that it may take some work to get everything working under one SAW build. Unless you think there is a simple way to resolve all these issues, it may be better to separate the new AES-GCM proof from the others so that it can use a different version of SAW. One way to do this is to create a separate Dockerfile and scripts for this proof, and then create a new workflow job and action that checks the proof.

@RyanGlScott
Copy link
Contributor Author

If you scroll up in the output of the coq job, you will see that the build is failing because the Coq files generated by this version of SAW appear to be different than what is expected.

Ah, I overlooked the fact that the Coq proofs are using a saw-script submodule, and I didn't update the submodule in this PR. This is the specific error:

[14:33:33.067] /github/workspace/Coq/saw-script/saw-core-coq/saw/generate_scaffolding.saw:3:1-3:172: Type Mismatch, expected: [t.2] -> t.3 but got: String -> [(String, String)] -> [String] -> TopLevel ()
 at "_" (REPL)
Type constructors mismatch. Expected: ([]) but got String at "_" (REPL)

[14:33:33.077] /github/workspace/Coq/saw-script/saw-core-coq/saw/generate_scaffolding.saw:3:1-3:172: Type Mismatch, expected: [t.2] -> t.3 but got: String -> [(String, String)] -> [String] -> TopLevel ()
 at "_" (REPL)
Type constructors mismatch. Expected: ([]) but got String at "_" (REPL)

COQC src/Coqprime/Tactic/Tactic.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Ltac2Lib/Msg.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Ltac2Lib/Constr.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Ltac2Lib/Char.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Ltac2Lib/Pervasives.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Ltac2Lib/Log.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Tactics/ident_to_string.v
COQC /github/workspace/Coq/fiat-crypto/rupicola/bedrock2/deps/coqutil/src/coqutil/Macros/unique.v
make[2]: *** No rule to make target 'generated/CryptolToCoq/CryptolPrimitivesForSAWCore.v', needed by '.Makefile.coq.d'.  Stop.
make[1]: Leaving directory '/github/workspace/Coq/saw-script/saw-core-coq/coq'
make[1]: *** [Makefile.coq:387: all] Error 2
make: *** [Makefile:18: saw-script] Error 2
make: *** Waiting for unfinished jobs....

And indeed, the offending line is different in the more recent version of SAW that I updated to. As you say, it's unclear to me how much work it would take to update the Coq proofs, so best to continue using the previous version of SAW for those.

Unless you think there is a simple way to resolve all these issues, it may be better to separate the new AES-GCM proof from the others so that it can use a different version of SAW.

I agree. The question will be how well we can isolate the AES-GCM proofs from the rest of the proofs. For example, both the AES_KW and AES_KWP proofs depend on AES-GCM.saw, which I modify in this PR. Moreover, the AES-GCM proofs require a more recent version of cryptol-specs, but updating cryptol-specs requires changes in the other proofs, which also depend on those specs. Disentangling everything sounds challenging.

What if we use the previous version of SAW for the Coq proofs, but the newer version of SAW for all other proofs? My understanding is that the Coq proofs are relatively isolated from everything else, so this sounds more feasible.

@apetcher-amazon
Copy link
Contributor

For example, both the AES_KW and AES_KWP proofs depend on AES-GCM.saw.

This is the only dependency, and it would be okay for AES_KW and AES_KWP to depend on only bounded proofs of AES, which work using the previous version of SAW.

What if we use the previous version of SAW for the Coq proofs, but the newer version of SAW for all other proofs?

That would also be fine.

@pennyannn
Copy link
Contributor

@RyanGlScott for the error in the saw-x86_64 job, that is very weird. If there is an error in SAW proofs, then parallel.py will print it. I suspect it might be caused by large memory usage. The script assumes a rough memory usage per job, but if AES-GCM uses way more than that, the total usage might exceed machine limit. I will try to see if I can reproduce it on my machine.
I agree with Adam that maybe it is easiest to separate out the AES-GCM proof. We can move the files that AES-KW(P) depends on into AES-KW. Then the AES directory becomes AES-GCM.

@RyanGlScott
Copy link
Contributor Author

I agree with Adam that maybe it is easiest to separate out the AES-GCM proof. We can move the files that AES-KW(P) depends on into AES-KW. Then the AES directory becomes AES-GCM.

That sounds reasonable to me. I'll prepare a separate PR for that, since it will likely be more self-contained than this one.

I don't want to abandon this PR just yet, as it will be useful should we ever want to migrate the non-AES-GCM proofs over to the latest SAW. I'll leave it open for now.

@RyanGlScott
Copy link
Contributor Author

I've opened #144, an alternative version of this PR which splits out the AES-GCM proofs into something more self-contained.

@pennyannn
Copy link
Contributor

An update on the saw-x86_64 workflow failure. I tried to reproduce the problem and found that the culprit is the proof for HMAC_Init_ex in the HMAC proof. The Z3 process generated from that proof seems to be stuck for a long time and my guess is that Github Runner just kills the whole job (therefore no results are printed to the stdout/stderr).

The high-level explanation is that this new version of SAW or Z3 does not work with the HMAC_Init_ex proof.

@RyanGlScott
Copy link
Contributor Author

RyanGlScott commented Apr 24, 2024

Ah, good catch. Indeed, testing locally, it appears that the use of Z3-4.8.14 is (at least partially) to blame, as the SHAUpdate_maintains_SHAState_eq_thm lemma (used in the HMAC proofs) takes much, much longer to prove with 4.8.14 than it does with 4.8.8. Sigh.

@pennyannn
Copy link
Contributor

pennyannn commented Apr 24, 2024

Correction. In my previous experiment, I used the new SAW but old Z3-4.8.8.
When using new SAW and new Z3-4.8.14, here're the results:

Succeeded fast: SHA384, SHA512, AES_KW and AES_KW(P)
Succeeded: AES-GCM (about 50mins)
Stuck: HMAC, HKDF, P384, ECDH, and ECDSA

It will be a non-trivial amount of work to fix all these proofs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants