Skip to content

Commit

Permalink
Add unbounded AES-GCM verification
Browse files Browse the repository at this point in the history
This proves unbounded 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 requires a newer version of SAW to work than what aws-lc-verification's CI
currently uses, so I isolated these proofs into their own SAW scripts, Cryptol
specs, shell scripts, Docker image, and CI action. Moreover, 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 other CI jobs are currently using Z3-4.8.8,
so I specifically chose a newer version (Z3-4.8.14) for the new,
AES-GCM–specific CI job.

Co-authored-by: Robert Dockins <rdockins@galois.com>
Co-authored-by: Samuel Breese <samuel@chame.co>
Co-authored-by: Andrei Stefanescu <andrei@stefanescu.io>
  • Loading branch information
4 people committed Apr 24, 2024
1 parent 34892fe commit 78dc4cd
Show file tree
Hide file tree
Showing 27 changed files with 2,300 additions and 112 deletions.
8 changes: 8 additions & 0 deletions .github/actions/SAW_X86_64_AES_GCM/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

name: 'AWS-LC Formal Verification SAW Proofs'
description: 'Check SAW proofs to verify AWS-LC against Cryptol specs'
runs:
using: 'docker'
image: '../../../Dockerfile.saw_x86_aes_gcm'
15 changes: 15 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ jobs:
# Runs the formal verification action
- name: SAW X86_64 Proofs
uses: ./.github/actions/SAW_X86_64
saw-x86_64-aes-gcm:
# The type of runner that the job will run on
runs-on: aws-lc-verification_ubuntu-latest_16-core

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Check out main repo and submodules.
- uses: actions/checkout@v2
name: check out top-level repository and all submodules
with:
submodules: true

# Runs the formal verification action
- name: SAW X86_64 AES-GCM Proof
uses: ./.github/actions/SAW_X86_64_AES_GCM
saw-aarch64:
# The type of runner that the job will run on
runs-on: aws-lc-verification_ubuntu-latest_16-core
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
path = cryptol-specs
branch = sha-imperative
url = https://github.com/GaloisInc/cryptol-specs.git
[submodule "cryptol-specs-aes-gcm"]
path = cryptol-specs-aes-gcm
url = https://github.com/GaloisInc/cryptol-specs.git
[submodule "Coq/fiat-crypto"]
path = Coq/fiat-crypto
url = https://github.com/mit-plv/fiat-crypto
Expand Down
27 changes: 27 additions & 0 deletions Dockerfile.saw_x86_aes_gcm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0


FROM ubuntu:20.04
ENV GOROOT=/usr/local/go
ENV PATH="$GOROOT/bin:$PATH"
ARG GO_VERSION=1.20.1
ARG GO_ARCHIVE="go${GO_VERSION}.linux-amd64.tar.gz"
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt-get update
RUN apt-get install -y wget unzip time git cmake clang llvm python3-pip libncurses5 opam libgmp-dev cabal-install

RUN wget "https://dl.google.com/go/${GO_ARCHIVE}" && tar -xvf $GO_ARCHIVE && \
mkdir $GOROOT && mv go/* $GOROOT && rm $GO_ARCHIVE
RUN pip3 install wllvm
RUN pip3 install psutil

ADD ./SAW/scripts/x86_64 /lc/scripts
RUN /lc/scripts/docker_install_aes_gcm.sh
ENV CRYPTOLPATH="../../../cryptol-specs-aes-gcm:../../spec"

# This container expects all files in the directory to be mounted or copied.
# The GitHub action will mount the workspace and set the working directory of the container.
# Another way to mount the files is: docker run -v `pwd`:`pwd` -w `pwd` <name>

ENTRYPOINT ["./SAW/scripts/x86_64/docker_entrypoint_aes_gcm.sh"]
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ The easiest way to build the library and run the proofs is to use [Docker](https
2. Build a Docker image:
`docker build --pull --no-cache -f Dockerfile.[...] -t awslc-saw .`
1. For running SAW proofs on X86_64, use: `Dockerfile.saw_x86`
2. For running SAW proofs on AARCH64, use: `Dockerfile.saw_aarch64`
3. For running Coq proofs, use: `Dockerfile.coq`
4. For running NSym proofs, use: `Dockerfile.nsym`
1. For running SAW proofs for AES-GCM on X86_64, use: `Dockerfile.saw_x86_aes_gcm`
3. For running SAW proofs on AARCH64, use: `Dockerfile.saw_aarch64`
4. For running Coq proofs, use: `Dockerfile.coq`
5. For running NSym proofs, use: `Dockerfile.nsym`

3. Run the SAW proofs inside the Docker container: ``docker run -v `pwd`:`pwd` -w `pwd` awslc-saw``
1. Run SAW proofs on X86_64: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint SAW/scripts/x86_64/docker_entrypoint.sh awslc-saw``
1. This step will also run formally-verified tests on certain hard-coded constant values.
2. Run SAW proofs on AARCH64: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint SAW/scripts/aarch64/docker_entrypoint.sh awslc-saw``
3. Use Coq to validate the Cryptol specs used in the SAW proofs: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint Coq/docker_entrypoint.sh awslc-saw``
4. Run NSym for AARCH64 assembly: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint NSym/scripts/docker_entrypoint.sh awslc-saw``
2. RUN SAW proofs for AES-GCM on x86_64: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint SAW/scripts/x86_64/docker_entrypoint_aes_gcm.sh awslc-saw``
3. Run SAW proofs on AARCH64: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint SAW/scripts/aarch64/docker_entrypoint.sh awslc-saw``
4. Use Coq to validate the Cryptol specs used in the SAW proofs: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint Coq/docker_entrypoint.sh awslc-saw``
5. Run NSym for AARCH64 assembly: ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint NSym/scripts/docker_entrypoint.sh awslc-saw``

Running ``docker run -it -v `pwd`:`pwd` -w `pwd` --entrypoint bash awslc-saw`` will enter an interactive shell within the container, which is often useful for debugging.

Expand All @@ -40,6 +42,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 |
| [Elliptic Curve Keys and Parameters](SPEC.md#Elliptic-Curve-Keys-and-Parameters) | with <nobr>P-384</nobr> | EVP_PKEY_CTX_new_id, EVP_PKEY_CTX_new, EVP_PKEY_paramgen_init, EVP_PKEY_CTX_set_ec_paramgen_curve_nid, EVP_PKEY_paramgen, EVP_PKEY_keygen_init, EVP_PKEY_keygen | SandyBridge+ | SAWCore_Coq, EC_Fiat_Crypto, ToolGap, NoEngine, MemCorrect, CRYPTO_refcount_Correct, CRYPTO_once_Correct, OptNone, SAWBreakpoint | SAW, Coq |
| [ECDSA](SPEC.md#ECDSA) | with <nobr>P-384</nobr>, <nobr>SHA-384</nobr> | EVP_DigestSignInit, EVP_DigestVerifyInit, EVP_DigestSignUpdate, EVP_DigestVerifyUpdate, EVP_DigestSignFinal, EVP_DigestVerifyFinal, EVP_DigestSign, EVP_DigestVerify | SandyBridge+ | EC_Pub_Mul_Correct, EC_Constants_Correct, EC_Conversion_Correct, SAWCore_Coq, EC_Fiat_Crypto, NoEngine, MemCorrect, ECDSA_k_Valid, ECDSA_SignatureLength, CRYPTO_refcount_Correct, CRYPTO_once_Correct, ERR_put_error_Correct, NoInline | SAW, Coq |
| [ECDH](SPEC.md#ECDH) | with <nobr>P-384</nobr> | EVP_PKEY_derive_init, EVP_PKEY_derive | SandyBridge+ | SAWCore_Coq, EC_Fiat_Crypto, ECDH_InfinityTestCorrect, ToolGap, MemCorrect, NoEngine, CRYPTO_refcount_Correct, PubKeyValid | SAW, Coq |
Expand Down
168 changes: 168 additions & 0 deletions SAW/proof/AES/AES-CTR32.saw
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

// Using experimental proof command "crucible_llvm_verify_x86"
enable_experimental;


////////////////////////////////////////////////////////////////////////////////
// Specifications

let aes_hw_ctr32_encrypt_blocks_spec len = do {
let len' = eval_size {| len * AES_BLOCK_SIZE |};
global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};

(in_, in_ptr) <- ptr_to_fresh_readonly "in" (llvm_array len' (llvm_int 8));
out_ptr <- crucible_alloc (llvm_array len' (llvm_int 8));
key_ptr <- crucible_alloc_readonly (llvm_struct "struct.aes_key_st");
key <- fresh_aes_key_st;
points_to_aes_key_st key_ptr key;
(ivec, ivec_ptr) <- ptr_to_fresh_readonly "ivec" (llvm_array AES_BLOCK_SIZE (llvm_int 8));

crucible_execute_func [in_ptr, out_ptr, (crucible_term {{ `len : [64] }}), key_ptr, ivec_ptr];

crucible_points_to out_ptr (crucible_term {{ aes_hw_ctr32_encrypt_blocks in_ key ivec }});

global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};
};

let aes_hw_ctr32_encrypt_blocks_spec' blocks = do {
let len' = eval_size {| blocks * AES_BLOCK_SIZE |};
global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};

(in_, in_ptr) <- ptr_to_fresh_readonly "in" (llvm_array len' (llvm_int 8));
out_ptr <- crucible_alloc (llvm_array len' (llvm_int 8));
key_ptr <- crucible_alloc_readonly (llvm_struct "struct.aes_key_st");
key <- fresh_aes_key_st;
points_to_aes_key_st key_ptr key;
(ivec, ivec_ptr) <- ptr_to_fresh_readonly "ivec" (llvm_array AES_BLOCK_SIZE (llvm_int 8));

crucible_execute_func [in_ptr, out_ptr, (crucible_term {{ `blocks : [64] }}), key_ptr, ivec_ptr];

crucible_points_to out_ptr (crucible_term {{ split`{each=8} (join (aes_ctr32_encrypt_blocks (join key) (join (take ivec)) (join (drop ivec)) (split (join in_)))) }});

global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};
};

let aes_hw_ctr32_encrypt_blocks_array_spec blocks = do {
let len = eval_size {| blocks * AES_BLOCK_SIZE |};
global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};

(in_, in_ptr) <- ptr_to_fresh_array_readonly "in" {{ `len : [64] }};
(out, out_ptr) <- ptr_to_fresh_array "out" {{ `len : [64] }};
key_ptr <- crucible_alloc_readonly (llvm_struct "struct.aes_key_st");
key <- fresh_aes_key_st;
points_to_aes_key_st key_ptr key;
(ivec, ivec_ptr) <- ptr_to_fresh_readonly "ivec" (llvm_array AES_BLOCK_SIZE (llvm_int 8));

crucible_execute_func [in_ptr, out_ptr, (crucible_term {{ `blocks : [64] }}), key_ptr, ivec_ptr];

crucible_points_to_array_prefix
out_ptr
{{ aes_ctr32_encrypt_blocks_array (join key) (join (take ivec)) (join (drop ivec)) in_ `blocks 0 out }}
{{ `len : [64] }};

global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};
};

let aes_hw_ctr32_encrypt_bounded_array_spec = do {
global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};

blocks <- llvm_fresh_var "blocks" (llvm_int 64);
let len = {{ blocks * `AES_BLOCK_SIZE }};
crucible_precond {{ 0 < blocks }};
crucible_precond {{ blocks < `MIN_BULK_BLOCKS }};

(in_, in_ptr) <- ptr_to_fresh_array_readonly "in" len;
(out, out_ptr) <- ptr_to_fresh_array "out" len;

key_ptr <- crucible_alloc_readonly (llvm_struct "struct.aes_key_st");
key <- fresh_aes_key_st;
points_to_aes_key_st key_ptr key;
(ivec, ivec_ptr) <- ptr_to_fresh_readonly "ivec" (llvm_array AES_BLOCK_SIZE (llvm_int 8));

crucible_execute_func [in_ptr, out_ptr, crucible_term blocks, key_ptr, ivec_ptr];

crucible_points_to_array_prefix
out_ptr
{{ aes_ctr32_encrypt_blocks_array (join key) (join (take ivec)) (join (drop ivec)) in_ blocks 0 out }}
len;

global_alloc_init "OPENSSL_ia32cap_P" {{ ia32cap }};
};


////////////////////////////////////////////////////////////////////////////////
// Proof commands
/*
When verifying aes_hw_ctr32_encrypt_blocks, the binary analysis must locally
treat r11 as callee-preserved. This is necessary because this routine saves
the original stack pointer in r11 and then calls helper routines, preventing
the binary analysis from inferring that the return address is still on the stack
when the routine returns. The called helper routines do not modify r11.
*/

let aes_hw_ctr32_tactic = do {
simplify (cryptol_ss ());
simplify (addsimps slice_384_thms basic_ss);
goal_eval_unint ["AESRound", "AESFinalRound", "aesenc", "aesenclast"];
simplify (addsimps add_xor_slice_thms basic_ss);
simplify (addsimps aesenclast_thms basic_ss);
w4_unint_yices ["AESRound", "AESFinalRound"];
};

add_x86_preserved_reg "r11";
aes_hw_ctr32_encrypt_blocks_encrypt_ov <- llvm_verify_x86 m "../../build/x86/crypto/crypto_test" "aes_hw_ctr32_encrypt_blocks"
[]
true
(aes_hw_ctr32_encrypt_blocks_spec whole_blocks_after_bulk_encrypt)
aes_hw_ctr32_tactic;

aes_hw_ctr32_encrypt_blocks_decrypt_ov <- llvm_verify_x86 m "../../build/x86/crypto/crypto_test" "aes_hw_ctr32_encrypt_blocks"
[]
true
(aes_hw_ctr32_encrypt_blocks_spec whole_blocks_after_bulk_decrypt)
aes_hw_ctr32_tactic;
default_x86_preserved_reg;

aes_hw_ctr32_copy_thms <- for (eval_list {{ [ 1:[16] .. < MIN_BULK_BLOCKS ] }}) (\i -> do {
let blocks = eval_int i;
print (str_concat "aes_hw_ctr32 copy lemma: " (show blocks));
prove_theorem
(do {
w4_unint_z3 ["aes_ctr32_encrypt_block"];
})
(rewrite (cryptol_ss ()) {{ \key iv ctr in out ->
arrayEq
(arrayCopy out 0 (aes_ctr32_encrypt_blocks_array key iv ctr in `blocks 0 out) 0 `(16*blocks))
(aes_ctr32_encrypt_blocks_array key iv ctr in `blocks 0 out)
}});
});

aes_hw_ctr32_encrypt_blocks_concrete_ovs <- for (eval_list {{ [ 1:[16] .. < MIN_BULK_BLOCKS ] }}) (\i -> do {
let blocks = eval_int i;
print (str_concat "aes_hw_ctr32_encrypt blocks=" (show blocks));
add_x86_preserved_reg "r11";
ov <- llvm_verify_x86 m "../../build/x86/crypto/crypto_test"
"aes_hw_ctr32_encrypt_blocks"
[]
true
(aes_hw_ctr32_encrypt_blocks_spec' blocks)
aes_hw_ctr32_tactic;
default_x86_preserved_reg;
llvm_refine_spec' m "aes_hw_ctr32_encrypt_blocks"
[ov]
(aes_hw_ctr32_encrypt_blocks_array_spec blocks)
(w4_unint_z3 ["aes_ctr32_encrypt_block"]);
});

aes_hw_ctr32_encrypt_blocks_bounded_array_ov <- llvm_refine_spec' m "aes_hw_ctr32_encrypt_blocks"
aes_hw_ctr32_encrypt_blocks_concrete_ovs
aes_hw_ctr32_encrypt_bounded_array_spec
(do {
simplify (addsimps aes_hw_ctr32_copy_thms (cryptol_ss ()));
w4_unint_z3 ["aes_ctr32_encrypt_blocks_array"];
});

Loading

0 comments on commit 78dc4cd

Please sign in to comment.