mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-10 22:08:09 -05:00
Compare commits
274 Commits
v0.1.0-alp
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1897f0d1e6 | ||
|
|
2101285f7f | ||
|
|
98210e4059 | ||
|
|
9dfac850d5 | ||
|
|
b41d678829 | ||
|
|
1ebefa27d8 | ||
|
|
4fe5c1defd | ||
|
|
0e8e547300 | ||
|
|
22cc88907a | ||
|
|
cec4756e0e | ||
|
|
0919e1f2b3 | ||
|
|
43b9f57e1f | ||
|
|
c51331d63d | ||
|
|
3905d9351c | ||
|
|
f8a67bc8e7 | ||
|
|
952a7011bf | ||
|
|
0673818e4e | ||
|
|
a5749d81f1 | ||
|
|
f2e119bb66 | ||
|
|
271ac3771e | ||
|
|
f69dd7a239 | ||
|
|
79f5160cae | ||
|
|
5fef2af698 | ||
|
|
5b2083e211 | ||
|
|
d26bb02d2e | ||
|
|
a766b64184 | ||
|
|
0885d40ddf | ||
|
|
610411aae4 | ||
|
|
37df1baed7 | ||
|
|
aeaebc5c60 | ||
|
|
2e7e3db11d | ||
|
|
0a68837d0a | ||
|
|
0ec2392716 | ||
|
|
f99fce5b5a | ||
|
|
6b9f44e7e5 | ||
|
|
bf1cf2302a | ||
|
|
2884be17e0 | ||
|
|
df8d79c152 | ||
|
|
82d509266b | ||
|
|
d5ad768e7c | ||
|
|
d25fb320d4 | ||
|
|
0539268da7 | ||
|
|
427b2896b5 | ||
|
|
89d1e594d1 | ||
|
|
b4380f021e | ||
|
|
8a823d18ec | ||
|
|
7bcfc56bd8 | ||
|
|
2909d5ebaa | ||
|
|
7918494ccc | ||
|
|
92dd47b376 | ||
|
|
5474a748ce | ||
|
|
92da5adc24 | ||
|
|
e0ce1ad31a | ||
|
|
3b76877920 | ||
|
|
783355772a | ||
|
|
e5c59da90b | ||
|
|
f059c53c2d | ||
|
|
a1367b5428 | ||
|
|
9d8124ac9d | ||
|
|
5034366c72 | ||
|
|
afd8f44261 | ||
|
|
21086d2883 | ||
|
|
cca9a318a4 | ||
|
|
cb804a6025 | ||
|
|
9f849e7c18 | ||
|
|
389bceddef | ||
|
|
657838671a | ||
|
|
2f072b2578 | ||
|
|
33153d1124 | ||
|
|
2d399d5e24 | ||
|
|
b6d7249b6d | ||
|
|
2a8c1c3382 | ||
|
|
7c27162875 | ||
|
|
eef813712d | ||
|
|
2e94e08fa6 | ||
|
|
97d9475335 | ||
|
|
38820d6a3f | ||
|
|
af85fa100f | ||
|
|
008b901913 | ||
|
|
db85f68328 | ||
|
|
fb80aa4cc9 | ||
|
|
8dae57d6a7 | ||
|
|
f2ff4ba792 | ||
|
|
9bf3371873 | ||
|
|
9d853eb496 | ||
|
|
6923ceefd3 | ||
|
|
5239c2328a | ||
|
|
6a7c5384a9 | ||
|
|
7e469006c0 | ||
|
|
55091b5e94 | ||
|
|
bc1eba18c9 | ||
|
|
c128ab16ce | ||
|
|
a87125ff88 | ||
|
|
0933d711d2 | ||
|
|
79c230f2fa | ||
|
|
345d5d45ad | ||
|
|
55a26aad77 | ||
|
|
1132d441e1 | ||
|
|
fa2fdfd601 | ||
|
|
24e10d664f | ||
|
|
c0e084c1ca | ||
|
|
b6845dfc5c | ||
|
|
31def9ea81 | ||
|
|
878fe7e87d | ||
|
|
3348ac34b6 | ||
|
|
82767ca2d5 | ||
|
|
c9aaf2e0fa | ||
|
|
241ed3b5a3 | ||
|
|
56f088db7d | ||
|
|
f5250479bd | ||
|
|
0e2eabb833 | ||
|
|
ad530ca500 | ||
|
|
8b1cac6fe0 | ||
|
|
555f65e6b2 | ||
|
|
046485188c | ||
|
|
db53814ee7 | ||
|
|
d924bd6deb | ||
|
|
b3558bef9c | ||
|
|
33c4b9d16f | ||
|
|
edc2a1783d | ||
|
|
c2a6546deb | ||
|
|
2dfa386415 | ||
|
|
5a188e75c7 | ||
|
|
a8bf1026ca | ||
|
|
f900fc51cd | ||
|
|
6ccf102ec8 | ||
|
|
2c500b13bd | ||
|
|
2da0c242cb | ||
|
|
798c22409a | ||
|
|
3b5ac20d5b | ||
|
|
a063f8cc14 | ||
|
|
6f6b24e76c | ||
|
|
a28718923b | ||
|
|
19447aabe5 | ||
|
|
8afb7a4c11 | ||
|
|
43c6877ec0 | ||
|
|
39e14949a0 | ||
|
|
31f62982b5 | ||
|
|
6623734ca0 | ||
|
|
41e215f912 | ||
|
|
9e0f79125b | ||
|
|
7bdd3a724b | ||
|
|
baa486ccfd | ||
|
|
de7a47de5b | ||
|
|
3a57134b3a | ||
|
|
86fed1a90c | ||
|
|
82964c273b | ||
|
|
81aaa338e6 | ||
|
|
f331a7a3c5 | ||
|
|
adb407d03b | ||
|
|
3e54119867 | ||
|
|
71aa90de88 | ||
|
|
93535ca955 | ||
|
|
a34dd57926 | ||
|
|
92d7b59ee8 | ||
|
|
c8e9cb370e | ||
|
|
4dc5570a31 | ||
|
|
198e24c5e4 | ||
|
|
f16d7238e5 | ||
|
|
9253adaaa4 | ||
|
|
8c889ac498 | ||
|
|
f0e2200d22 | ||
|
|
224e41a186 | ||
|
|
328c2af162 | ||
|
|
cdb80e1458 | ||
|
|
eeccbef909 | ||
|
|
190b7b0bf6 | ||
|
|
c70caa5ed9 | ||
|
|
20137b8c6c | ||
|
|
4cdd1395e8 | ||
|
|
c1b3d64d5d | ||
|
|
61ce838f8c | ||
|
|
efca281222 | ||
|
|
b24041b9f5 | ||
|
|
9649d6e4cf | ||
|
|
bc69683ecf | ||
|
|
6c468a91cf | ||
|
|
dcff0b9152 | ||
|
|
5f91926154 | ||
|
|
0496cbaeb1 | ||
|
|
d8747d49e3 | ||
|
|
6fe328581c | ||
|
|
6d1140355b | ||
|
|
5246beabf5 | ||
|
|
29efc35d14 | ||
|
|
32d25e5c69 | ||
|
|
ca9d364fc9 | ||
|
|
5cbafe17f5 | ||
|
|
acabb7761b | ||
|
|
c384a393bf | ||
|
|
be0be19018 | ||
|
|
63bd6abc5d | ||
|
|
cb13169b82 | ||
|
|
25d65734c0 | ||
|
|
119ae4b2a8 | ||
|
|
f59153b0a0 | ||
|
|
bffe9ebb0b | ||
|
|
65299d7def | ||
|
|
c03418a642 | ||
|
|
7bec5a84ee | ||
|
|
85e0f5b467 | ||
|
|
cacca108ed | ||
|
|
c9592f44a1 | ||
|
|
e6be5e1cc9 | ||
|
|
d974fb71d5 | ||
|
|
c0c1c0caa1 | ||
|
|
7d88d1c20b | ||
|
|
c10c9155a7 | ||
|
|
faab999339 | ||
|
|
e6bc93c1f1 | ||
|
|
c6dc262a5e | ||
|
|
db90e28e44 | ||
|
|
30e4e37c0d | ||
|
|
6344410cad | ||
|
|
1d663596c1 | ||
|
|
2c045e5de7 | ||
|
|
38104bca1a | ||
|
|
99ba47c25d | ||
|
|
2042089132 | ||
|
|
504967d09a | ||
|
|
6e80d03ac7 | ||
|
|
b3f79a9e2b | ||
|
|
99e02fb388 | ||
|
|
6b845fd473 | ||
|
|
66db5344ac | ||
|
|
1d4c50f804 | ||
|
|
61ff3a8255 | ||
|
|
2ac9de1edd | ||
|
|
a7a8a83410 | ||
|
|
4d5102b6e1 | ||
|
|
0596a9a245 | ||
|
|
43d2c04f6f | ||
|
|
ca328fadca | ||
|
|
b724d6a1d2 | ||
|
|
dfc162929d | ||
|
|
6e20930283 | ||
|
|
7de49c8ed6 | ||
|
|
17476bc2cf | ||
|
|
b76b8314ad | ||
|
|
6ed3337739 | ||
|
|
3de203e8ac | ||
|
|
79c00fcedb | ||
|
|
e00828bd03 | ||
|
|
8f400bf1e2 | ||
|
|
53ff873b3a | ||
|
|
a4a0de02f9 | ||
|
|
80a9a61e9e | ||
|
|
67dc7c865d | ||
|
|
9bbb2fb66c | ||
|
|
32df1380a7 | ||
|
|
d179150c39 | ||
|
|
98a520ddd7 | ||
|
|
8fa593c111 | ||
|
|
0b1eef12f3 | ||
|
|
6eaf4a3d2d | ||
|
|
cc01b24759 | ||
|
|
0a3a1db520 | ||
|
|
b3316dede7 | ||
|
|
ab24a6d3aa | ||
|
|
17e31687bd | ||
|
|
b9ae8f9271 | ||
|
|
c8524d934b | ||
|
|
7f46596068 | ||
|
|
6031254963 | ||
|
|
2d44cc4b60 | ||
|
|
bdebd7a9b2 | ||
|
|
3201c38ad7 | ||
|
|
2205cb3b2c | ||
|
|
040608bb6e | ||
|
|
e14d0cf563 | ||
|
|
7377eaf661 | ||
|
|
b24aea4fad | ||
|
|
d8401ee177 | ||
|
|
44210c350b |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/.git
|
||||
3
.github/codecov.yml
vendored
Normal file
3
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github_checks:
|
||||
annotations: false
|
||||
comment: false
|
||||
13
.github/scripts/build-server.sh
vendored
13
.github/scripts/build-server.sh
vendored
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
# https://github.com/tlsnotary/tlsn/pull/419
|
||||
set -ex
|
||||
|
||||
environment=$1
|
||||
|
||||
aws s3 sync .git s3://tlsn-deploy/$environment/.git --delete
|
||||
|
||||
cd notary/server
|
||||
cargo build --release
|
||||
aws s3 cp ../target/release/notary-server s3://tlsn-deploy/$environment/
|
||||
|
||||
exit 0
|
||||
27
.github/scripts/deploy-server.sh
vendored
27
.github/scripts/deploy-server.sh
vendored
@@ -1,27 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
environment=$1
|
||||
branch=$2
|
||||
|
||||
INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Name,Values=[tlsnotary-backend-v1] Name=instance-state-name,Values=[running] --query "Reservations[*].Instances[*][InstanceId]" --output text)
|
||||
aws ec2 create-tags --resources $INSTANCE_ID --tags "Key=$environment,Value=$branch"
|
||||
|
||||
COMMIT_HASH=$(git rev-parse HEAD)
|
||||
DEPLOY_ID=$(aws deploy create-deployment --application-name tlsn-$environment-v1 --deployment-group-name tlsn-$environment-v1-group --github-location repository=$GITHUB_REPOSITORY,commitId=$COMMIT_HASH --ignore-application-stop-failures --file-exists OVERWRITE --output text)
|
||||
|
||||
while true; do
|
||||
STATUS=$(aws deploy get-deployment --deployment-id $DEPLOY_ID --query 'deploymentInfo.status' --output text)
|
||||
if [ $STATUS != "InProgress" ] && [ $STATUS != "Created" ]; then
|
||||
if [ $STATUS = "Succeeded" ]; then
|
||||
echo "SUCCESS"
|
||||
exit 0
|
||||
else
|
||||
echo "Failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Deploying..."
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
33
.github/scripts/modify-proxy.sh
vendored
33
.github/scripts/modify-proxy.sh
vendored
@@ -1,33 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script is triggered by Deploy server workflow in order to send an execution command of cd-scripts/modify_proxy.sh via AWS SSM to the proxy server
|
||||
|
||||
set -e
|
||||
|
||||
GH_OWNER="tlsnotary"
|
||||
GH_REPO="tlsn"
|
||||
BACKEND_INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Name,Values=[tlsnotary-backend-v1] Name=instance-state-name,Values=[running] --query "Reservations[*].Instances[*][InstanceId]" --output text)
|
||||
PROXY_INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Name,Values=[tlsnotary-web] Name=instance-state-name,Values=[running] --query "Reservations[*].Instances[*][InstanceId]" --output text)
|
||||
TAGS=$(aws ec2 describe-instances --instance-ids $BACKEND_INSTANCE_ID --query 'Reservations[*].Instances[*].Tags')
|
||||
|
||||
TAG=$(echo $TAGS | jq -r '.[][][] | select(.Key == "stable").Value')
|
||||
PORT=$(echo $TAGS | jq -r '.[][][] | select(.Key == "port").Value')
|
||||
|
||||
COMMAND_ID=$(aws ssm send-command --document-name "AWS-RunRemoteScript" --instance-ids $PROXY_INSTANCE_ID --parameters '{"sourceType":["GitHub"],"sourceInfo":["{\"owner\":\"'${GH_OWNER}'\", \"repository\":\"'${GH_REPO}'\", \"getOptions\":\"branch:'${TAG}'\", \"path\": \"cd-scripts\"}"],"commandLine":["modify_proxy.sh '${PORT}' '${TAG}' "]}' --output text --query "Command.CommandId")
|
||||
|
||||
while true; do
|
||||
SSM_STATUS=$(aws ssm list-command-invocations --command-id $COMMAND_ID --details --query "CommandInvocations[].Status" --output text)
|
||||
|
||||
if [ $SSM_STATUS != "Success" ] && [ $SSM_STATUS != "InProgress" ]; then
|
||||
echo "Proxy modification failed"
|
||||
aws ssm list-command-invocations --command-id $COMMAND_ID --details --query "CommandInvocations[].CommandPlugins[].{Status:Status,Output:Output}"
|
||||
exit 1
|
||||
elif [ $SSM_STATUS = "Success" ]; then
|
||||
aws ssm list-command-invocations --command-id $COMMAND_ID --details --query "CommandInvocations[].CommandPlugins[].{Status:Status,Output:Output}"
|
||||
echo "Success"
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
exit 0
|
||||
26
.github/workflows/bench.yml
vendored
26
.github/workflows/bench.yml
vendored
@@ -1,7 +1,16 @@
|
||||
name: Run Benchmarks
|
||||
name: Run Benchmarks (Native or Browser)
|
||||
on:
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bench_type:
|
||||
description: "Specify the benchmark type (native or browser)"
|
||||
required: true
|
||||
default: "native"
|
||||
type: choice
|
||||
options:
|
||||
- native
|
||||
- browser
|
||||
|
||||
jobs:
|
||||
run-benchmarks:
|
||||
@@ -12,16 +21,21 @@ jobs:
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t tlsn-bench . -f ./tlsn/benches/benches.Dockerfile
|
||||
docker build -t tlsn-bench . -f ./crates/harness/harness.Dockerfile
|
||||
|
||||
- name: Run Benchmarks
|
||||
run: |
|
||||
docker run --privileged -v ${{ github.workspace }}/tlsn/benches/:/benches tlsn-bench
|
||||
docker run --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "runner setup; runner --target ${{ github.event.inputs.bench_type }} bench"
|
||||
|
||||
- name: Upload runtime_vs_latency.html
|
||||
- name: Plot Benchmarks
|
||||
run: |
|
||||
docker run -v ./crates/harness/:/benches tlsn-bench bash -c "tlsn-harness-plot /benches/bench.toml /benches/metrics.csv --min-max-band --prover-kind ${{ github.event.inputs.bench_type }}"
|
||||
- name: Upload graphs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_graphs
|
||||
path: |
|
||||
./tlsn/benches/runtime_vs_latency.html
|
||||
./tlsn/benches/runtime_vs_bandwidth.html
|
||||
./crates/harness/metrics.csv
|
||||
./crates/harness/bench.toml
|
||||
./crates/harness/runtime_vs_latency.html
|
||||
./crates/harness/runtime_vs_bandwidth.html
|
||||
92
.github/workflows/cd-server.yml
vendored
92
.github/workflows/cd-server.yml
vendored
@@ -1,92 +0,0 @@
|
||||
name: Deploy server
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Environment"
|
||||
required: true
|
||||
default: "nightly"
|
||||
type: choice
|
||||
options:
|
||||
- nightly
|
||||
- stable
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DATA_ENV: ${{ github.event.inputs.environment || 'nightly' }}
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Manipulate Environment
|
||||
id: manipulate
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "push" ] && [ "$GITHUB_REF_NAME" = "dev" ]; then
|
||||
echo "env=nightly" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" = "refs/tags/"* ]]; then
|
||||
echo "env=stable" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "env=${{ env.DATA_ENV }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Operation not permitted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Wait for test workflow to succeed
|
||||
if: github.event_name == 'push'
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
# Have to specify '(notary)', as we are using matrix for build_and_test job in ci.yml, else it will fail, more details [here](https://github.com/lewagon/wait-on-check-action#check-name)
|
||||
check-name: 'Build and test (notary)'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# How frequent (in seconds) this job will call GitHub API to check the status of the job specified at 'check-name'
|
||||
wait-interval: 60
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::490752553772:role/tlsn-deploy-slc
|
||||
role-duration-seconds: 1800
|
||||
aws-region: eu-central-1
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.5.0
|
||||
with:
|
||||
workspaces: ${{ matrix.package }} -> target
|
||||
|
||||
- name: Cargo build
|
||||
run: |
|
||||
.github/scripts/build-server.sh ${{ steps.manipulate.outputs.env }}
|
||||
|
||||
- name: Trigger Deployment
|
||||
run: |
|
||||
.github/scripts/deploy-server.sh ${{ steps.manipulate.outputs.env }} $GITHUB_REF_NAME
|
||||
|
||||
- name: Modify Proxy
|
||||
if: ${{ steps.manipulate.outputs.env == 'stable' }}
|
||||
run: |
|
||||
.github/scripts/modify-proxy.sh
|
||||
52
.github/workflows/cd.yml
vendored
52
.github/workflows/cd.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: cd
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
|
||||
env:
|
||||
CONTAINER_REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
build_and_publish_notary_server_image:
|
||||
name: Build and publish notary server's image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Wait for test workflow to succeed
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
# Have to specify '(notary)', as we are using matrix for build_and_test job in ci.yml, else it will fail, more details [here](https://github.com/lewagon/wait-on-check-action#check-name)
|
||||
check-name: 'Build and test (notary)'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# How frequent (in seconds) this job will call GitHub API to check the status of the job specified at 'check-name'
|
||||
wait-interval: 60
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.CONTAINER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker image of notary server
|
||||
id: meta-notary-server
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}/notary-server
|
||||
|
||||
- name: Build and push Docker image of notary server
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta-notary-server.outputs.tags }}
|
||||
labels: ${{ steps.meta-notary-server.outputs.labels }}
|
||||
file: ./notary/server/notary-server.Dockerfile
|
||||
224
.github/workflows/ci.yml
vendored
224
.github/workflows/ci.yml
vendored
@@ -7,90 +7,196 @@ on:
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
# We need a higher number of parallel rayon tasks than the default (which is 4)
|
||||
# in order to prevent a deadlock, c.f.
|
||||
# - https://github.com/tlsnotary/tlsn/issues/548
|
||||
# - https://github.com/privacy-ethereum/mpz/issues/178
|
||||
# 32 seems to be big enough for the foreseeable future
|
||||
RAYON_NUM_THREADS: 32
|
||||
RUST_VERSION: 1.92.0
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- components/cipher
|
||||
- components/universal-hash
|
||||
- components/aead
|
||||
- components/key-exchange
|
||||
- components/prf
|
||||
- components/tls
|
||||
- components/tls/tls-mpc
|
||||
- tlsn
|
||||
- notary
|
||||
- tlsn/tests-integration
|
||||
include:
|
||||
- package: components/tls/tls-mpc
|
||||
ignored: true
|
||||
release: true
|
||||
- package: notary
|
||||
release: true
|
||||
- package: tlsn
|
||||
all-features: true
|
||||
- package: tlsn/tests-integration
|
||||
release: true
|
||||
ignored: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.package }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
components: clippy
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --keep-going --all-features --all-targets --locked
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# We use nightly to support `imports_granularity` feature
|
||||
- name: Install nightly rust toolchain with rustfmt
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- name: "Check formatting"
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo +nightly fmt --check --all
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
build-and-test:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
|
||||
- name: "Clippy"
|
||||
run: cargo clippy --all-features --examples -- -D warnings
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.3
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Build
|
||||
run: cargo build --all-targets --locked
|
||||
|
||||
- name: Test
|
||||
run: cargo test --no-fail-fast --locked
|
||||
|
||||
wasm:
|
||||
name: Build and Test wasm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
workspaces: ${{ matrix.package }}
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: "Build"
|
||||
run: cargo build ${{ matrix.release && '--release' }}
|
||||
- name: Install nightly rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown,x86_64-unknown-linux-gnu
|
||||
toolchain: nightly
|
||||
components: rust-src
|
||||
|
||||
- name: Add custom DNS entry to /etc/hosts for notary TLS test
|
||||
if: matrix.package == 'notary'
|
||||
run: echo "127.0.0.1 tlsnotaryserver.io" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: "Test ${{ matrix.release && '--release' || '' }} ${{ matrix.all-features && '--all-features' || '' }}"
|
||||
env:
|
||||
RELEASE_OPTION: ${{ matrix.release && '--release' || '' }}
|
||||
# Includes tests marked as `ignore`.
|
||||
IGNORED_OPTION: ${{ matrix.ignored && '--include-ignored' || '' }}
|
||||
ALL_FEATURES_OPTION: ${{ matrix.all-features && '--all-features' || '' }}
|
||||
# Run all tests (bins, examples, lib, integration and docs)
|
||||
# https://doc.rust-lang.org/cargo/commands/cargo-test.html#target-selection
|
||||
- name: Install chromedriver
|
||||
run: |
|
||||
echo "Running command: cargo test $RELEASE_OPTION $ALL_FEATURES_OPTION -- $IGNORED_OPTION"
|
||||
cargo test $RELEASE_OPTION $ALL_FEATURES_OPTION -- $IGNORED_OPTION
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-chromedriver
|
||||
|
||||
- name: "Check that benches compile"
|
||||
run: cargo bench --no-run
|
||||
- name: Install wasm-pack
|
||||
# we install a specific version which supports custom profiles
|
||||
run: cargo install --git https://github.com/rustwasm/wasm-pack.git --rev 32e52ca
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Build harness
|
||||
working-directory: crates/harness
|
||||
run: ./build.sh
|
||||
|
||||
- name: Run tests
|
||||
working-directory: crates/harness
|
||||
run: |
|
||||
./bin/runner setup
|
||||
./bin/runner --target browser test
|
||||
|
||||
- name: Run build
|
||||
working-directory: crates/wasm
|
||||
run: ./build.sh
|
||||
|
||||
- name: Dry Run NPM Publish
|
||||
working-directory: crates/wasm/pkg
|
||||
run: npm publish --dry-run
|
||||
|
||||
- name: Save tlsn-wasm package for tagged builds
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.ref_name }}-tlsn-wasm-pkg
|
||||
path: ./crates/wasm/pkg
|
||||
if-no-files-found: error
|
||||
|
||||
tests-integration:
|
||||
name: Run tests release build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Run integration tests
|
||||
run: cargo test --locked --profile tests-integration --workspace --exclude tlsn-tls-client --exclude tlsn-tls-core --no-fail-fast -- --include-ignored
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --all-features --workspace --locked --lcov --output-path lcov.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
|
||||
create-release-draft:
|
||||
name: Create Release Draft
|
||||
needs: build-and-test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.')
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create GitHub Release Draft
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
|
||||
62
.github/workflows/releng.yml
vendored
Normal file
62
.github/workflows/releng.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Publish tlsn-wasm to NPM
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to publish to NPM'
|
||||
required: true
|
||||
default: 'v0.1.0-alpha.14-pre'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
steps:
|
||||
- name: Find and download tlsn-wasm build from the tagged ci workflow
|
||||
id: find_run
|
||||
run: |
|
||||
# Find the workflow run ID for the tag
|
||||
RUN_ID=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/tlsnotary/tlsn/actions/workflows/ci.yml/runs?per_page=100" \
|
||||
--jq '.workflow_runs[] | select(.head_branch == "${{ github.event.inputs.tag }}") | .id' | sort | tail -1)
|
||||
|
||||
if [ -z "$RUN_ID" ]; then
|
||||
echo "No run found for tag ${{ github.event.inputs.tag }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found run: $RUN_ID"
|
||||
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Find the download URL for the build artifact
|
||||
DOWNLOAD_URL=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
/repos/tlsnotary/tlsn/actions/runs/${RUN_ID}/artifacts \
|
||||
--jq '.artifacts[] | select(.name == "${{ github.event.inputs.tag }}-tlsn-wasm-pkg") | .archive_download_url')
|
||||
|
||||
if [ -z "$DOWNLOAD_URL" ]; then
|
||||
echo "No download url for build artifact ${{ github.event.inputs.tag }}-tlsn-wasm-pkg in run $RUN_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Download and unzip the build artifact
|
||||
mkdir tlsn-wasm-pkg
|
||||
curl -L -H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-o tlsn-wasm-pkg.zip \
|
||||
${DOWNLOAD_URL}
|
||||
unzip -q tlsn-wasm-pkg.zip -d tlsn-wasm-pkg
|
||||
|
||||
|
||||
- name: NPM Publish for tlsn-wasm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
cd tlsn-wasm-pkg
|
||||
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc
|
||||
npm publish
|
||||
rm .npmrc
|
||||
13
.github/workflows/rustdoc.yml
vendored
13
.github/workflows/rustdoc.yml
vendored
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
branches: [dev]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -14,7 +13,7 @@ jobs:
|
||||
rustdoc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust Toolchain (Stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -22,18 +21,12 @@ jobs:
|
||||
toolchain: stable
|
||||
|
||||
- name: "rustdoc"
|
||||
run: cd tlsn; cargo doc -p tlsn-core -p tlsn-prover -p tlsn-verifier --no-deps --all-features
|
||||
# --target-dir ${GITHUB_WORKSPACE}/docs
|
||||
|
||||
# https://dev.to/deciduously/prepare-your-rust-api-docs-for-github-pages-2n5i
|
||||
- name: "Add index file -> tlsn_prover"
|
||||
run: |
|
||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=tlsn_prover\">" > tlsn/target/doc/index.html
|
||||
run: crates/wasm/build-docs.sh
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: tlsn/target/doc/
|
||||
publish_dir: target/wasm32-unknown-unknown/doc/
|
||||
# cname: rustdocs.tlsnotary.org
|
||||
|
||||
25
.github/workflows/updatemain.yml
vendored
Normal file
25
.github/workflows/updatemain.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Fast-forward main branch to published release tag
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
ff-main-to-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Fast-forward main to release tag
|
||||
run: |
|
||||
tag="${{ github.event.release.tag_name }}"
|
||||
git fetch origin "refs/tags/$tag:refs/tags/$tag"
|
||||
git merge --ff-only "refs/tags/$tag"
|
||||
git push origin main
|
||||
47
.github/workflows/wasm.yml
vendored
47
.github/workflows/wasm.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: wasm-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build for target wasm32-unknown-unknown
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- tlsn/tlsn-core
|
||||
- tlsn/tlsn-prover
|
||||
- components/tls/tls-client
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.package }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: stable
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.5.0
|
||||
with:
|
||||
workspaces: ${{ matrix.package }} -> ../target
|
||||
|
||||
- name: "Build"
|
||||
run: cargo build --target wasm32-unknown-unknown
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,10 +3,6 @@
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
@@ -32,4 +28,4 @@ Cargo.lock
|
||||
*.log
|
||||
|
||||
# metrics
|
||||
*.csv
|
||||
*.csv
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
@@ -16,27 +16,22 @@ keywords.
|
||||
|
||||
Try to do one pull request per change.
|
||||
|
||||
### Updating the changelog
|
||||
**Disclaimer**: While we appreciate all contributions, we do not prioritize minor grammatical fixes (e.g., correcting typos, rewording sentences) unless they significantly improve clarity in technical documentation. These contributions can be a distraction for the team. If you notice a grammatical error, please let us know on our Discord.
|
||||
|
||||
Update the changes you have made in
|
||||
[CHANGELOG](CHANGELOG.md)
|
||||
file under the **Unreleased** section.
|
||||
## Linting
|
||||
|
||||
Add the changes of your pull request to one of the following subsections,
|
||||
depending on the types of changes defined by
|
||||
[Keep a changelog](https://keepachangelog.com/en/1.0.0/):
|
||||
Before a Pull Request (PR) can be merged, the Continuous Integration (CI) pipeline automatically lints all code using [Clippy](https://doc.rust-lang.org/stable/clippy/usage.html). To ensure your code is free of linting issues before creating a PR, run the following command:
|
||||
|
||||
- `Added` for new features.
|
||||
- `Changed` for changes in existing functionality.
|
||||
- `Deprecated` for soon-to-be removed features.
|
||||
- `Removed` for now removed features.
|
||||
- `Fixed` for any bug fixes.
|
||||
- `Security` in case of vulnerabilities.
|
||||
```sh
|
||||
cargo clippy --all-features --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
If the required subsection does not exist yet under **Unreleased**, create it!
|
||||
This command will lint your code with all features and targets enabled, and treat any warnings as errors, ensuring that your code meets the required standards.
|
||||
|
||||
## Style
|
||||
|
||||
This repository includes a `rustfmt.toml` file with custom formatting settings that are automatically validated by CI before any Pull Requests (PRs) can be merged. To ensure your code adheres to these standards, format your code using this configuration before submitting a PR. We strongly recommend enabling *auto format on save* to streamline this process. In Visual Studio Code (VSCode), you can enable this feature by turning on [`editor.formatOnSave`](https://code.visualstudio.com/docs/editor/codebasics#_formatting) in the settings.
|
||||
|
||||
### Capitalization and punctuation
|
||||
|
||||
Both line comments and doc comments must be capitalized. Each sentence must end with a period.
|
||||
@@ -66,3 +61,21 @@ Comments for function arguments must adhere to this pattern:
|
||||
/// * `arg2` - The second argument.
|
||||
pub fn compute(...
|
||||
```
|
||||
|
||||
## Cargo.lock
|
||||
|
||||
We check in `Cargo.lock` to ensure reproducible builds. It must be updated whenever `Cargo.toml` changes. The TLSNotary team typically updates `Cargo.lock` in a separate commit after dependency changes.
|
||||
|
||||
If you want to hide `Cargo.lock` changes from your local `git diff`, run:
|
||||
|
||||
```sh
|
||||
git update-index --assume-unchanged Cargo.lock
|
||||
```
|
||||
|
||||
To start tracking changes again:
|
||||
```sh
|
||||
git update-index --no-assume-unchanged Cargo.lock
|
||||
```
|
||||
|
||||
> ⚠️ Note: This only affects your local view. The file is still tracked in the repository and will be checked and used in CI.
|
||||
|
||||
|
||||
9149
Cargo.lock
generated
Normal file
9149
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
170
Cargo.toml
Normal file
170
Cargo.toml
Normal file
@@ -0,0 +1,170 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/attestation",
|
||||
"crates/components/deap",
|
||||
"crates/components/cipher",
|
||||
"crates/components/hmac-sha256",
|
||||
"crates/components/key-exchange",
|
||||
"crates/core",
|
||||
"crates/data-fixtures",
|
||||
"crates/examples",
|
||||
"crates/formats",
|
||||
"crates/server-fixture/certs",
|
||||
"crates/server-fixture/server",
|
||||
"crates/tls/backend",
|
||||
"crates/tls/client",
|
||||
"crates/tls/client-async",
|
||||
"crates/tls/core",
|
||||
"crates/mpc-tls",
|
||||
"crates/tls/server-fixture",
|
||||
"crates/wasm",
|
||||
"crates/harness/core",
|
||||
"crates/harness/executor",
|
||||
"crates/harness/runner",
|
||||
"crates/harness/plot",
|
||||
"crates/tlsn",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# enum_glob_use = "deny"
|
||||
|
||||
[profile.tests-integration]
|
||||
inherits = "release"
|
||||
opt-level = 1
|
||||
|
||||
[profile.wasm]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.dependencies]
|
||||
tls-server-fixture = { path = "crates/tls/server-fixture" }
|
||||
tlsn-attestation = { path = "crates/attestation" }
|
||||
tlsn-cipher = { path = "crates/components/cipher" }
|
||||
tlsn-core = { path = "crates/core" }
|
||||
tlsn-data-fixtures = { path = "crates/data-fixtures" }
|
||||
tlsn-deap = { path = "crates/components/deap" }
|
||||
tlsn-formats = { path = "crates/formats" }
|
||||
tlsn-hmac-sha256 = { path = "crates/components/hmac-sha256" }
|
||||
tlsn-key-exchange = { path = "crates/components/key-exchange" }
|
||||
tlsn-mpc-tls = { path = "crates/mpc-tls" }
|
||||
tlsn-server-fixture = { path = "crates/server-fixture/server" }
|
||||
tlsn-server-fixture-certs = { path = "crates/server-fixture/certs" }
|
||||
tlsn-tls-backend = { path = "crates/tls/backend" }
|
||||
tlsn-tls-client = { path = "crates/tls/client" }
|
||||
tlsn-tls-client-async = { path = "crates/tls/client-async" }
|
||||
tlsn-tls-core = { path = "crates/tls/core" }
|
||||
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
tlsn-harness-core = { path = "crates/harness/core" }
|
||||
tlsn-harness-executor = { path = "crates/harness/executor" }
|
||||
tlsn-harness-runner = { path = "crates/harness/runner" }
|
||||
tlsn-wasm = { path = "crates/wasm" }
|
||||
tlsn = { path = "crates/tlsn" }
|
||||
|
||||
mpz-circuits = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-circuits-data = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-common = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-garble = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-ole = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-ot = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-fields = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-zk = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-hash = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-ideal-vm = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
|
||||
rangeset = { version = "0.4" }
|
||||
serio = { version = "0.2" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6f1a934" }
|
||||
uid-mux = { version = "0.2" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6f1a934" }
|
||||
|
||||
aead = { version = "0.4" }
|
||||
aes = { version = "0.8" }
|
||||
aes-gcm = { version = "0.9" }
|
||||
anyhow = { version = "1.0" }
|
||||
async-trait = { version = "0.1" }
|
||||
axum = { version = "0.8" }
|
||||
bcs = { version = "0.1" }
|
||||
bincode = { version = "1.3" }
|
||||
blake3 = { version = "1.5" }
|
||||
bon = { version = "3.6" }
|
||||
bytes = { version = "1.4" }
|
||||
cfg-if = { version = "1" }
|
||||
chromiumoxide = { version = "0.7" }
|
||||
chrono = { version = "0.4" }
|
||||
cipher = { version = "0.4" }
|
||||
clap = { version = "4.5" }
|
||||
criterion = { version = "0.5" }
|
||||
ctr = { version = "0.9" }
|
||||
derive_builder = { version = "0.12" }
|
||||
digest = { version = "0.10" }
|
||||
elliptic-curve = { version = "0.13" }
|
||||
enum-try-as-inner = { version = "0.1" }
|
||||
env_logger = { version = "0.10" }
|
||||
futures = { version = "0.3" }
|
||||
futures-rustls = { version = "0.25" }
|
||||
generic-array = { version = "0.14" }
|
||||
ghash = { version = "0.5" }
|
||||
hex = { version = "0.4" }
|
||||
hmac = { version = "0.12" }
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper = { version = "1.1" }
|
||||
hyper-util = { version = "0.1" }
|
||||
ipnet = { version = "2.11" }
|
||||
inventory = { version = "0.3" }
|
||||
itybity = { version = "0.2" }
|
||||
js-sys = { version = "0.3" }
|
||||
k256 = { version = "0.13" }
|
||||
log = { version = "0.4" }
|
||||
once_cell = { version = "1.19" }
|
||||
opaque-debug = { version = "0.3" }
|
||||
p256 = { version = "0.13" }
|
||||
pin-project-lite = { version = "0.2" }
|
||||
pollster = { version = "0.4" }
|
||||
rand = { version = "0.9" }
|
||||
rand_chacha = { version = "0.9" }
|
||||
rand_core = { version = "0.9" }
|
||||
rand06-compat = { version = "0.1" }
|
||||
rayon = { version = "1.10" }
|
||||
regex = { version = "1.10" }
|
||||
ring = { version = "0.17" }
|
||||
rs_merkle = { git = "https://github.com/tlsnotary/rs-merkle.git", rev = "85f3e82" }
|
||||
rstest = { version = "0.17" }
|
||||
rustls = { version = "0.21" }
|
||||
rustls-pemfile = { version = "1.0" }
|
||||
rustls-webpki = { version = "0.103" }
|
||||
rustls-pki-types = { version = "1.12" }
|
||||
sct = { version = "0.7" }
|
||||
semver = { version = "1.0" }
|
||||
serde = { version = "1.0" }
|
||||
serde_json = { version = "1.0" }
|
||||
sha2 = { version = "0.10" }
|
||||
signature = { version = "2.2" }
|
||||
thiserror = { version = "1.0" }
|
||||
tiny-keccak = { version = "2.0" }
|
||||
tokio = { version = "1.38" }
|
||||
tokio-util = { version = "0.7" }
|
||||
toml = { version = "0.8" }
|
||||
tower = { version = "0.5" }
|
||||
tower-http = { version = "0.5" }
|
||||
tower-service = { version = "0.3" }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
web-spawn = { version = "0.2" }
|
||||
web-time = { version = "0.2" }
|
||||
webpki-roots = { version = "1.0" }
|
||||
webpki-root-certs = { version = "1.0" }
|
||||
ws_stream_wasm = { version = "0.7.5" }
|
||||
zeroize = { version = "1.8" }
|
||||
33
README.md
33
README.md
@@ -8,11 +8,11 @@
|
||||
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[apache-badge]: https://img.shields.io/github/license/saltstack/salt
|
||||
[actions-badge]: https://github.com/tlsnotary/tlsn/actions/workflows/ci.yml/badge.svg
|
||||
[actions-badge]: https://github.com/tlsnotary/tlsn/actions/workflows/ci.yml/badge.svg?branch=dev
|
||||
[actions-url]: https://github.com/tlsnotary/tlsn/actions?query=workflow%3Aci+branch%3Adev
|
||||
|
||||
[Website](https://tlsnotary.org) |
|
||||
[Documentation](https://docs.tlsnotary.org) |
|
||||
[Documentation](https://tlsnotary.org/docs/intro) |
|
||||
[API Docs](https://tlsnotary.github.io/tlsn) |
|
||||
[Discord](https://discord.gg/9XwESXtcN7)
|
||||
|
||||
@@ -43,16 +43,31 @@ at your option.
|
||||
|
||||
## Directory
|
||||
|
||||
- [tlsn](./tlsn/): The home for examples and API libraries.
|
||||
- [examples](./tlsn/examples/): Examples on how to use the TLSNotary protocol.
|
||||
- [tlsn-prover](./tlsn/tlsn-prover/): The library for the prover component.
|
||||
- [tlsn-verifier](./tlsn/tlsn-verifier/): The library for the verifier component.
|
||||
- [notary](./notary/): Implements the [notary server](https://docs.tlsnotary.org/intro.html#tls-verification-with-a-general-purpose-notary) and its client.
|
||||
- [components](./components/): Houses low-level libraries utilized by [tlsn](./tlsn/).
|
||||
- [examples](./crates/examples/): Examples on how to use the TLSNotary protocol.
|
||||
- [tlsn](./crates/tlsn/): The TLSNotary library.
|
||||
|
||||
This repository contains the source code for the Rust implementation of the TLSNotary protocol. For additional tools and implementations related to TLSNotary, visit <https://github.com/tlsnotary>. This includes repositories such as [`tlsn-js`](https://github.com/tlsnotary/tlsn-js), [`tlsn-extension`](https://github.com/tlsnotary/tlsn-extension), [`explorer`](https://github.com/tlsnotary/explorer), among others.
|
||||
This repository contains the source code for the Rust implementation of the TLSNotary protocol. For additional tools and implementations related to TLSNotary, visit <https://github.com/tlsnotary>. This includes repositories such as [`tlsn-js`](https://github.com/tlsnotary/tlsn-js), [`tlsn-extension`](https://github.com/tlsnotary/tlsn-extension), among others.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Note on Rust-to-WASM Compilation**: This project requires compiling Rust into WASM, which needs [`clang`](https://clang.llvm.org/) version 16.0.0 or newer. MacOS users, be aware that Xcode's default `clang` might be older. If you encounter the error `No available targets are compatible with triple "wasm32-unknown-unknown"`, it's likely due to an outdated `clang`. Updating `clang` to a newer version should resolve this issue.
|
||||
>
|
||||
> For MacOS aarch64 users, if Apple's default `clang` isn't working, try installing `llvm` via Homebrew (`brew install llvm`). You can then prioritize the Homebrew `clang` over the default macOS version by modifying your `PATH`. Add the following line to your shell configuration file (e.g., `.bashrc`, `.zshrc`):
|
||||
> ```sh
|
||||
> export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
|
||||
> ```
|
||||
|
||||
If you run into this error:
|
||||
```
|
||||
Could not find directory of OpenSSL installation, and this `-sys` crate cannot
|
||||
proceed without this knowledge. If OpenSSL is installed and this crate had
|
||||
trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
|
||||
compilation process.
|
||||
```
|
||||
Make sure you have the development packages of OpenSSL installed (`libssl-dev` on Ubuntu or `openssl-devel` on Fedora).
|
||||
|
||||
## Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
|
||||
31
appspec.yml
31
appspec.yml
@@ -1,31 +0,0 @@
|
||||
# AWS CodeDeploy application specification file
|
||||
version: 0.0
|
||||
os: linux
|
||||
files:
|
||||
- source: /
|
||||
destination: /home/ubuntu/tlsn
|
||||
permissions:
|
||||
- object: /home/ubuntu/tlsn
|
||||
owner: ubuntu
|
||||
group: ubuntu
|
||||
hooks:
|
||||
BeforeInstall:
|
||||
- location: cd-scripts/appspec-scripts/before_install.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
AfterInstall:
|
||||
- location: cd-scripts/appspec-scripts/after_install.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ApplicationStart:
|
||||
- location: cd-scripts/appspec-scripts/start_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ApplicationStop:
|
||||
- location: cd-scripts/appspec-scripts/stop_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ValidateService:
|
||||
- location: cd-scripts/appspec-scripts/validate_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
11
build_all.sh
11
build_all.sh
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
for package in components/uid-mux components/actors/actor-ot components/cipher components/universal-hash components/aead components/key-exchange components/point-addition components/prf components/tls tlsn; do
|
||||
pushd $package
|
||||
# cargo update
|
||||
cargo clean
|
||||
cargo build
|
||||
cargo test
|
||||
cargo clippy --all-features -- -D warnings || exit
|
||||
popd
|
||||
done
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
TAG=$(curl http://169.254.169.254/latest/meta-data/tags/instance/stable)
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
# Prepare directories for stable versions
|
||||
sudo mkdir ~/${APP_NAME}_${TAG}
|
||||
sudo mv ~/tlsn ~/${APP_NAME}_${TAG}
|
||||
sudo mkdir -p ~/${APP_NAME}_${TAG}/tlsn/notary/target/release
|
||||
sudo chown -R ubuntu.ubuntu ~/${APP_NAME}_${TAG}
|
||||
|
||||
# Download .git directory
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/.git ~/${APP_NAME}_${TAG}/tlsn/.git --recursive
|
||||
|
||||
# Download binary
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/notary-server ~/${APP_NAME}_${TAG}/tlsn/notary/target/release
|
||||
chmod +x ~/${APP_NAME}_${TAG}/tlsn/notary/target/release/notary-server
|
||||
else
|
||||
# Prepare directory for dev
|
||||
sudo rm -rf ~/$APP_NAME/tlsn
|
||||
sudo mv ~/tlsn/ ~/$APP_NAME
|
||||
sudo mkdir -p ~/$APP_NAME/tlsn/notary/target/release
|
||||
sudo chown -R ubuntu.ubuntu ~/$APP_NAME
|
||||
|
||||
# Download .git directory
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/.git ~/$APP_NAME/tlsn/.git --recursive
|
||||
|
||||
# Download binary
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/notary-server ~/$APP_NAME/tlsn/notary/target/release
|
||||
chmod +x ~/$APP_NAME/tlsn/notary/target/release/notary-server
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "nightly" ]; then
|
||||
if [ ! -d ~/$APP_NAME ]; then
|
||||
mkdir ~/$APP_NAME
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Port tagging will also be used to manipulate proxy server via modify_proxy.sh script
|
||||
set -ex
|
||||
|
||||
TAG=$(curl http://169.254.169.254/latest/meta-data/tags/instance/stable)
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
# Check if all stable ports are in use. If true, terminate the deployment
|
||||
[[ $(netstat -lnt4 | egrep -c ':(7047|7057|7067)\s') -eq 3 ]] && { echo "All stable ports are in use"; exit 1; }
|
||||
STABLE_PORTS="7047 7057 7067"
|
||||
for PORT in $STABLE_PORTS; do
|
||||
PORT_LISTENING=$(netstat -lnt4 | egrep -cw $PORT || true)
|
||||
if [ $PORT_LISTENING -eq 0 ]; then
|
||||
cd ~/${APP_NAME}_${TAG}/tlsn/notary
|
||||
target/release/notary-server --config-file ~/.notary/${APP_NAME}_${PORT}/config.yaml &> ~/${APP_NAME}_${TAG}/tlsn/notary.log &
|
||||
# Create a tag that will be used for service validation
|
||||
INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
|
||||
aws ec2 create-tags --resources $INSTANCE_ID --tags "Key=port,Value=$PORT"
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
cd ~/$APP_NAME/tlsn/notary
|
||||
target/release/notary-server --config-file ~/.notary/$APP_NAME/config.yaml &> ~/$APP_NAME/tlsn/notary.log &
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
# AWS CodeDeploy hook sequence: https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#appspec-hooks-server
|
||||
set -ex
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
VERSIONS_DEPLOYED=$(find ~/ -maxdepth 1 -type d -name 'stable_*')
|
||||
VERSIONS_DEPLOYED_COUNT=$(echo $VERSIONS_DEPLOYED | wc -w)
|
||||
|
||||
# Remove oldest version if exists
|
||||
if [ $VERSIONS_DEPLOYED_COUNT -eq 3 ]; then
|
||||
echo "Candidate versions to be removed:"
|
||||
OLDEST_DIR=""
|
||||
OLDEST_TIME=""
|
||||
|
||||
for DIR in $VERSIONS_DEPLOYED; do
|
||||
TIME=$(stat -c %W $DIR)
|
||||
|
||||
if [ -z $OLDEST_TIME ] || [ $TIME -lt $OLDEST_TIME ]; then
|
||||
OLDEST_DIR=$DIR
|
||||
OLDEST_TIME=$TIME
|
||||
fi
|
||||
done
|
||||
|
||||
echo "The oldest version is running under: $OLDEST_DIR"
|
||||
PID=$(lsof $OLDEST_DIR/tlsn/notary/target/release/notary-server | awk '{ print $2 }' | tail -1)
|
||||
kill -15 $PID || true
|
||||
rm -rf $OLDEST_DIR
|
||||
fi
|
||||
else
|
||||
PID=$(pgrep -f notary.*$APP_NAME)
|
||||
kill -15 $PID || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Verify proccess is running
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
# Verify that listening sockets exist
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
PORT=$(curl http://169.254.169.254/latest/meta-data/tags/instance/port)
|
||||
ps -ef | grep notary.*$APP_NAME.*$PORT | grep -v grep
|
||||
[ $? -eq 0 ] || exit 1
|
||||
else
|
||||
PORT=7048
|
||||
pgrep -f notary.*$APP_NAME
|
||||
[ $? -eq 0 ] || exit 1
|
||||
fi
|
||||
|
||||
EXPOSED_PORTS=$(netstat -lnt4 | egrep -cw $PORT)
|
||||
[ $EXPOSED_PORTS -eq 1 ] || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script is executed on proxy side, in order to assign the available port to latest stable version
|
||||
set -e
|
||||
|
||||
PORT=$1
|
||||
VERSION=$2
|
||||
|
||||
sed -i "/# Port $PORT/{n;s/v[0-9].[0-9].[0-9]-[a-z]*.[0-9]*/$VERSION/g}" /etc/nginx/sites-available/tlsnotary-pse
|
||||
sed -i "/# Port $PORT/{n;n;s/v[0-9].[0-9].[0-9]-[a-z]*.[0-9]*/$VERSION/g}" /etc/nginx/sites-available/tlsnotary-pse
|
||||
|
||||
nginx -t
|
||||
nginx -s reload
|
||||
|
||||
exit 0
|
||||
@@ -1,39 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-aead"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "This crate provides an implementation of a two-party version of AES-GCM behind an AEAD trait"
|
||||
keywords = ["tls", "mpc", "2pc", "aead", "aes", "aes-gcm"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.6"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "aead"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = ["mpz-common/test-utils", "dep:mpz-ot"]
|
||||
|
||||
[dependencies]
|
||||
tlsn-block-cipher = { path = "../cipher/block-cipher" }
|
||||
tlsn-stream-cipher = { path = "../cipher/stream-cipher" }
|
||||
tlsn-universal-hash = { path = "../universal-hash" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac", optional = true, features = [
|
||||
"ideal",
|
||||
] }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
serio = "0.1"
|
||||
|
||||
async-trait = "0.1"
|
||||
derive_builder = "0.12"
|
||||
thiserror = "1"
|
||||
futures = "0.3"
|
||||
serde = "1"
|
||||
tracing = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
aes-gcm = "0.10"
|
||||
@@ -1,36 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Protocol role.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Role {
|
||||
Leader,
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// Configuration for AES-GCM.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct AesGcmConfig {
|
||||
/// The id of this instance.
|
||||
#[builder(setter(into))]
|
||||
id: String,
|
||||
/// The protocol role.
|
||||
role: Role,
|
||||
}
|
||||
|
||||
impl AesGcmConfig {
|
||||
/// Creates a new builder for the AES-GCM configuration.
|
||||
pub fn builder() -> AesGcmConfigBuilder {
|
||||
AesGcmConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the id of this instance.
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Returns the protocol role.
|
||||
pub fn role(&self) -> &Role {
|
||||
&self.role
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
/// AES-GCM error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct AesGcmError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl AesGcmError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn kind(&self) -> ErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_tag() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Tag,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn peer(reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::PeerMisbehaved,
|
||||
source: Some(reason.into().into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn payload(reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Payload,
|
||||
source: Some(reason.into().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Io,
|
||||
BlockCipher,
|
||||
StreamCipher,
|
||||
Ghash,
|
||||
Tag,
|
||||
PeerMisbehaved,
|
||||
Payload,
|
||||
}
|
||||
|
||||
impl Display for AesGcmError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Io => write!(f, "io error")?,
|
||||
ErrorKind::BlockCipher => write!(f, "block cipher error")?,
|
||||
ErrorKind::StreamCipher => write!(f, "stream cipher error")?,
|
||||
ErrorKind::Ghash => write!(f, "ghash error")?,
|
||||
ErrorKind::Tag => write!(f, "payload has corrupted tag")?,
|
||||
ErrorKind::PeerMisbehaved => write!(f, "peer misbehaved")?,
|
||||
ErrorKind::Payload => write!(f, "payload error")?,
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AesGcmError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self::new(ErrorKind::Io, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<block_cipher::BlockCipherError> for AesGcmError {
|
||||
fn from(err: block_cipher::BlockCipherError) -> Self {
|
||||
Self::new(ErrorKind::BlockCipher, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn_stream_cipher::StreamCipherError> for AesGcmError {
|
||||
fn from(err: tlsn_stream_cipher::StreamCipherError) -> Self {
|
||||
Self::new(ErrorKind::StreamCipher, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn_universal_hash::UniversalHashError> for AesGcmError {
|
||||
fn from(err: tlsn_universal_hash::UniversalHashError) -> Self {
|
||||
Self::new(ErrorKind::Ghash, err)
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
//! Mock implementation of AES-GCM for testing purposes.
|
||||
|
||||
use block_cipher::{BlockCipherConfig, MpcBlockCipher};
|
||||
use mpz_common::executor::{test_st_executor, STExecutor};
|
||||
use mpz_garble::protocol::deap::mock::{MockFollower, MockLeader};
|
||||
use mpz_ot::ideal::ot::ideal_ot;
|
||||
use serio::channel::MemoryDuplex;
|
||||
use tlsn_stream_cipher::{MpcStreamCipher, StreamCipherConfig};
|
||||
use tlsn_universal_hash::ghash::ideal_ghash;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Creates a mock AES-GCM pair.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - The id of the AES-GCM instances.
|
||||
/// * `(leader, follower)` - The leader and follower vms.
|
||||
/// * `leader_config` - The configuration of the leader.
|
||||
/// * `follower_config` - The configuration of the follower.
|
||||
pub async fn create_mock_aes_gcm_pair(
|
||||
id: &str,
|
||||
(leader, follower): (MockLeader, MockFollower),
|
||||
leader_config: AesGcmConfig,
|
||||
follower_config: AesGcmConfig,
|
||||
) -> (
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
) {
|
||||
let block_cipher_id = format!("{}/block_cipher", id);
|
||||
let (ctx_leader, ctx_follower) = test_st_executor(128);
|
||||
|
||||
let (leader_ot_send, follower_ot_recv) = ideal_ot();
|
||||
let (follower_ot_send, leader_ot_recv) = ideal_ot();
|
||||
|
||||
let block_leader = leader
|
||||
.new_thread(ctx_leader, leader_ot_send, leader_ot_recv)
|
||||
.unwrap();
|
||||
|
||||
let block_follower = follower
|
||||
.new_thread(ctx_follower, follower_ot_send, follower_ot_recv)
|
||||
.unwrap();
|
||||
|
||||
let leader_block_cipher = MpcBlockCipher::new(
|
||||
BlockCipherConfig::builder()
|
||||
.id(block_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
block_leader,
|
||||
);
|
||||
let follower_block_cipher = MpcBlockCipher::new(
|
||||
BlockCipherConfig::builder()
|
||||
.id(block_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
block_follower,
|
||||
);
|
||||
|
||||
let stream_cipher_id = format!("{}/stream_cipher", id);
|
||||
let leader_stream_cipher = MpcStreamCipher::new(
|
||||
StreamCipherConfig::builder()
|
||||
.id(stream_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader,
|
||||
);
|
||||
let follower_stream_cipher = MpcStreamCipher::new(
|
||||
StreamCipherConfig::builder()
|
||||
.id(stream_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower,
|
||||
);
|
||||
|
||||
let (ctx_a, ctx_b) = test_st_executor(128);
|
||||
let (leader_ghash, follower_ghash) = ideal_ghash(ctx_a, ctx_b);
|
||||
|
||||
let (ctx_a, ctx_b) = test_st_executor(128);
|
||||
let leader = MpcAesGcm::new(
|
||||
leader_config,
|
||||
ctx_a,
|
||||
Box::new(leader_block_cipher),
|
||||
Box::new(leader_stream_cipher),
|
||||
Box::new(leader_ghash),
|
||||
);
|
||||
|
||||
let follower = MpcAesGcm::new(
|
||||
follower_config,
|
||||
ctx_b,
|
||||
Box::new(follower_block_cipher),
|
||||
Box::new(follower_stream_cipher),
|
||||
Box::new(follower_ghash),
|
||||
);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
@@ -1,709 +0,0 @@
|
||||
//! This module provides an implementation of 2PC AES-GCM.
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
mod tag;
|
||||
|
||||
pub use config::{AesGcmConfig, AesGcmConfigBuilder, AesGcmConfigBuilderError, Role};
|
||||
pub use error::AesGcmError;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use block_cipher::{Aes128, BlockCipher};
|
||||
use futures::TryFutureExt;
|
||||
use mpz_common::Context;
|
||||
use mpz_garble::value::ValueRef;
|
||||
use tlsn_stream_cipher::{Aes128Ctr, StreamCipher};
|
||||
use tlsn_universal_hash::UniversalHash;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{
|
||||
aes_gcm::tag::{compute_tag, verify_tag, TAG_LEN},
|
||||
Aead,
|
||||
};
|
||||
|
||||
/// MPC AES-GCM.
|
||||
pub struct MpcAesGcm<Ctx> {
|
||||
config: AesGcmConfig,
|
||||
ctx: Ctx,
|
||||
aes_block: Box<dyn BlockCipher<Aes128>>,
|
||||
aes_ctr: Box<dyn StreamCipher<Aes128Ctr>>,
|
||||
ghash: Box<dyn UniversalHash>,
|
||||
}
|
||||
|
||||
impl<Ctx> std::fmt::Debug for MpcAesGcm<Ctx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MpcAesGcm")
|
||||
.field("config", &self.config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: Context> MpcAesGcm<Ctx> {
|
||||
/// Creates a new instance of [`MpcAesGcm`].
|
||||
pub fn new(
|
||||
config: AesGcmConfig,
|
||||
context: Ctx,
|
||||
aes_block: Box<dyn BlockCipher<Aes128>>,
|
||||
aes_ctr: Box<dyn StreamCipher<Aes128Ctr>>,
|
||||
ghash: Box<dyn UniversalHash>,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
ctx: context,
|
||||
aes_block,
|
||||
aes_ctr,
|
||||
ghash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Ctx: Context> Aead for MpcAesGcm<Ctx> {
|
||||
type Error = AesGcmError;
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn set_key(&mut self, key: ValueRef, iv: ValueRef) -> Result<(), AesGcmError> {
|
||||
self.aes_block.set_key(key.clone());
|
||||
self.aes_ctr.set_key(key, iv);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn decode_key_private(&mut self) -> Result<(), AesGcmError> {
|
||||
self.aes_ctr
|
||||
.decode_key_private()
|
||||
.await
|
||||
.map_err(AesGcmError::from)
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn decode_key_blind(&mut self) -> Result<(), AesGcmError> {
|
||||
self.aes_ctr
|
||||
.decode_key_blind()
|
||||
.await
|
||||
.map_err(AesGcmError::from)
|
||||
}
|
||||
|
||||
fn set_transcript_id(&mut self, id: &str) {
|
||||
self.aes_ctr.set_transcript_id(id)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), err)]
|
||||
async fn setup(&mut self) -> Result<(), AesGcmError> {
|
||||
self.ghash.setup().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), err)]
|
||||
async fn preprocess(&mut self, len: usize) -> Result<(), AesGcmError> {
|
||||
futures::try_join!(
|
||||
// Preprocess the GHASH key block.
|
||||
self.aes_block
|
||||
.preprocess(block_cipher::Visibility::Public, 1)
|
||||
.map_err(AesGcmError::from),
|
||||
self.aes_ctr.preprocess(len).map_err(AesGcmError::from),
|
||||
self.ghash.preprocess().map_err(AesGcmError::from),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn start(&mut self) -> Result<(), AesGcmError> {
|
||||
let h_share = self.aes_block.encrypt_share(vec![0u8; 16]).await?;
|
||||
self.ghash.set_key(h_share).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_public(explicit_nonce.clone(), plaintext)
|
||||
.await?;
|
||||
|
||||
let tag = compute_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
explicit_nonce,
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_private(explicit_nonce.clone(), plaintext)
|
||||
.await?;
|
||||
|
||||
let tag = compute_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
explicit_nonce,
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext_len: usize,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_blind(explicit_nonce.clone(), plaintext_len)
|
||||
.await?;
|
||||
|
||||
let tag = compute_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
explicit_nonce,
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext = self
|
||||
.aes_ctr
|
||||
.decrypt_public(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext = self
|
||||
.aes_ctr
|
||||
.decrypt_private(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.decrypt_blind(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce,
|
||||
ciphertext,
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext = self
|
||||
.aes_ctr
|
||||
.prove_plaintext(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn prove_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
self.aes_ctr
|
||||
.prove_plaintext(explicit_nonce, ciphertext)
|
||||
.map_err(AesGcmError::from)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.verify_plaintext(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
self.aes_ctr
|
||||
.verify_plaintext(explicit_nonce, ciphertext)
|
||||
.map_err(AesGcmError::from)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
aes_gcm::{mock::create_mock_aes_gcm_pair, AesGcmConfigBuilder, Role},
|
||||
Aead,
|
||||
};
|
||||
use ::aes_gcm::{
|
||||
aead::{AeadInPlace, KeyInit},
|
||||
Aes128Gcm, Nonce,
|
||||
};
|
||||
use error::ErrorKind;
|
||||
use mpz_common::executor::STExecutor;
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory};
|
||||
use serio::channel::MemoryDuplex;
|
||||
|
||||
fn reference_impl(
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
explicit_nonce: &[u8],
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let cipher = Aes128Gcm::new_from_slice(key).unwrap();
|
||||
let nonce = [iv, explicit_nonce].concat();
|
||||
let nonce = Nonce::from_slice(nonce.as_slice());
|
||||
|
||||
let mut ciphertext = plaintext.to_vec();
|
||||
cipher
|
||||
.encrypt_in_place(nonce, aad, &mut ciphertext)
|
||||
.unwrap();
|
||||
|
||||
ciphertext
|
||||
}
|
||||
|
||||
async fn setup_pair(
|
||||
key: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
) -> (
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
) {
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
let leader_key = leader_vm
|
||||
.new_public_array_input::<u8>("key", key.len())
|
||||
.unwrap();
|
||||
let leader_iv = leader_vm
|
||||
.new_public_array_input::<u8>("iv", iv.len())
|
||||
.unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key.clone()).unwrap();
|
||||
leader_vm.assign(&leader_iv, iv.clone()).unwrap();
|
||||
|
||||
let follower_key = follower_vm
|
||||
.new_public_array_input::<u8>("key", key.len())
|
||||
.unwrap();
|
||||
let follower_iv = follower_vm
|
||||
.new_public_array_input::<u8>("iv", iv.len())
|
||||
.unwrap();
|
||||
|
||||
follower_vm.assign(&follower_key, key.clone()).unwrap();
|
||||
follower_vm.assign(&follower_iv, iv.clone()).unwrap();
|
||||
|
||||
let leader_config = AesGcmConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.role(Role::Leader)
|
||||
.build()
|
||||
.unwrap();
|
||||
let follower_config = AesGcmConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.role(Role::Follower)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let (mut leader, mut follower) = create_mock_aes_gcm_pair(
|
||||
"test",
|
||||
(leader_vm, follower_vm),
|
||||
leader_config,
|
||||
follower_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
futures::try_join!(
|
||||
leader.set_key(leader_key, leader_iv),
|
||||
follower.set_key(follower_key, follower_iv)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
futures::try_join!(leader.setup(), follower.setup()).unwrap();
|
||||
futures::try_join!(leader.start(), follower.start()).unwrap();
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_encrypt_private() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_private(explicit_nonce.clone(), plaintext.clone(), aad.clone(),),
|
||||
follower.encrypt_blind(explicit_nonce.clone(), plaintext.len(), aad.clone())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
assert_eq!(
|
||||
leader_ciphertext,
|
||||
reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_encrypt_public() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_public(explicit_nonce.clone(), plaintext.clone(), aad.clone(),),
|
||||
follower.encrypt_public(explicit_nonce.clone(), plaintext.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
assert_eq!(
|
||||
leader_ciphertext,
|
||||
reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_decrypt_private() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_plaintext, _) = tokio::try_join!(
|
||||
leader.decrypt_private(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_blind(explicit_nonce.clone(), ciphertext, aad.clone(),)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_plaintext, plaintext);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_decrypt_private_bad_tag() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let len = ciphertext.len();
|
||||
|
||||
// corrupt tag
|
||||
let mut corrupted = ciphertext.clone();
|
||||
corrupted[len - 1] -= 1;
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// leader receives corrupted tag
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_private(explicit_nonce.clone(), corrupted.clone(), aad.clone(),),
|
||||
follower.decrypt_blind(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// follower receives corrupted tag
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_private(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_blind(explicit_nonce.clone(), corrupted.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_decrypt_public() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_plaintext, follower_plaintext) = tokio::try_join!(
|
||||
leader.decrypt_public(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_public(explicit_nonce.clone(), ciphertext, aad.clone(),)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_plaintext, plaintext);
|
||||
assert_eq!(leader_plaintext, follower_plaintext);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_decrypt_public_bad_tag() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let len = ciphertext.len();
|
||||
|
||||
// Corrupt tag.
|
||||
let mut corrupted = ciphertext.clone();
|
||||
corrupted[len - 1] -= 1;
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// Leader receives corrupted tag.
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_public(explicit_nonce.clone(), corrupted.clone(), aad.clone(),),
|
||||
follower.decrypt_public(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// Follower receives corrupted tag.
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_public(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_public(explicit_nonce.clone(), corrupted.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_gcm_verify_tag() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let len = ciphertext.len();
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
tokio::try_join!(
|
||||
leader.verify_tag(explicit_nonce.clone(), ciphertext.clone(), aad.clone()),
|
||||
follower.verify_tag(explicit_nonce.clone(), ciphertext.clone(), aad.clone())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//Corrupt tag.
|
||||
let mut corrupted = ciphertext.clone();
|
||||
corrupted[len - 1] -= 1;
|
||||
|
||||
let (leader_res, follower_res) = tokio::join!(
|
||||
leader.verify_tag(explicit_nonce.clone(), corrupted.clone(), aad.clone()),
|
||||
follower.verify_tag(explicit_nonce.clone(), corrupted, aad.clone())
|
||||
);
|
||||
|
||||
assert_eq!(leader_res.unwrap_err().kind(), ErrorKind::Tag);
|
||||
assert_eq!(follower_res.unwrap_err().kind(), ErrorKind::Tag);
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
use futures::TryFutureExt;
|
||||
use mpz_common::Context;
|
||||
use mpz_core::{
|
||||
commit::{Decommitment, HashCommit},
|
||||
hash::Hash,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use std::ops::Add;
|
||||
use tlsn_stream_cipher::{Aes128Ctr, StreamCipher};
|
||||
use tlsn_universal_hash::UniversalHash;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::aes_gcm::{AesGcmError, Role};
|
||||
|
||||
pub(crate) const TAG_LEN: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct TagShare([u8; TAG_LEN]);
|
||||
|
||||
impl AsRef<[u8]> for TagShare {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for TagShare {
|
||||
type Output = [u8; TAG_LEN];
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
core::array::from_fn(|i| self.0[i] ^ rhs.0[i])
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all, err)]
|
||||
async fn compute_tag_share<C: StreamCipher<Aes128Ctr> + ?Sized, H: UniversalHash + ?Sized>(
|
||||
aes_ctr: &mut C,
|
||||
hasher: &mut H,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<TagShare, AesGcmError> {
|
||||
let (j0, hash) = futures::try_join!(
|
||||
aes_ctr
|
||||
.share_keystream_block(explicit_nonce, 1)
|
||||
.map_err(AesGcmError::from),
|
||||
hasher
|
||||
.finalize(build_ghash_data(aad, ciphertext))
|
||||
.map_err(AesGcmError::from)
|
||||
)?;
|
||||
|
||||
debug_assert!(j0.len() == TAG_LEN);
|
||||
debug_assert!(hash.len() == TAG_LEN);
|
||||
|
||||
let tag_share = core::array::from_fn(|i| j0[i] ^ hash[i]);
|
||||
|
||||
Ok(TagShare(tag_share))
|
||||
}
|
||||
|
||||
/// Computes the tag for a ciphertext and additional data.
|
||||
///
|
||||
/// The commit-reveal step is not required for computing a tag sent to the Server, as it
|
||||
/// will be able to detect if the tag is incorrect.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn compute_tag<
|
||||
Ctx: Context,
|
||||
C: StreamCipher<Aes128Ctr> + ?Sized,
|
||||
H: UniversalHash + ?Sized,
|
||||
>(
|
||||
ctx: &mut Ctx,
|
||||
aes_ctr: &mut C,
|
||||
hasher: &mut H,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<[u8; TAG_LEN], AesGcmError> {
|
||||
let tag_share = compute_tag_share(aes_ctr, hasher, explicit_nonce, ciphertext, aad).await?;
|
||||
|
||||
// TODO: The follower doesn't really need to learn the tag,
|
||||
// we could reduce some latency by not sending it.
|
||||
let io = ctx.io_mut();
|
||||
io.send(tag_share.clone()).await?;
|
||||
let other_tag_share: TagShare = io.expect_next().await?;
|
||||
|
||||
let tag = tag_share + other_tag_share;
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
|
||||
/// Verifies a purported tag against the ciphertext and additional data.
|
||||
///
|
||||
/// Verifying a tag requires a commit-reveal protocol between the leader and follower.
|
||||
/// Without it, the party which receives the other's tag share first could trivially compute
|
||||
/// a tag share which would cause an invalid message to be accepted.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn verify_tag<
|
||||
Ctx: Context,
|
||||
C: StreamCipher<Aes128Ctr> + ?Sized,
|
||||
H: UniversalHash + ?Sized,
|
||||
>(
|
||||
ctx: &mut Ctx,
|
||||
aes_ctr: &mut C,
|
||||
hasher: &mut H,
|
||||
role: Role,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
purported_tag: [u8; TAG_LEN],
|
||||
) -> Result<(), AesGcmError> {
|
||||
let tag_share = compute_tag_share(aes_ctr, hasher, explicit_nonce, ciphertext, aad).await?;
|
||||
|
||||
let io = ctx.io_mut();
|
||||
let tag = match role {
|
||||
Role::Leader => {
|
||||
// Send commitment of tag share to follower.
|
||||
let (tag_share_decommitment, tag_share_commitment) = tag_share.clone().hash_commit();
|
||||
|
||||
io.send(tag_share_commitment).await?;
|
||||
|
||||
let follower_tag_share: TagShare = io.expect_next().await?;
|
||||
|
||||
// Send decommitment (tag share) to follower.
|
||||
io.send(tag_share_decommitment).await?;
|
||||
|
||||
tag_share + follower_tag_share
|
||||
}
|
||||
Role::Follower => {
|
||||
// Wait for commitment from leader.
|
||||
let commitment: Hash = io.expect_next().await?;
|
||||
|
||||
// Send tag share to leader.
|
||||
io.send(tag_share.clone()).await?;
|
||||
|
||||
// Expect decommitment (tag share) from leader.
|
||||
let decommitment: Decommitment<TagShare> = io.expect_next().await?;
|
||||
|
||||
// Verify decommitment.
|
||||
decommitment.verify(&commitment).map_err(|_| {
|
||||
AesGcmError::peer("leader tag share commitment verification failed")
|
||||
})?;
|
||||
|
||||
let leader_tag_share = decommitment.into_inner();
|
||||
|
||||
tag_share + leader_tag_share
|
||||
}
|
||||
};
|
||||
|
||||
// Reject if tag is incorrect.
|
||||
if tag != purported_tag {
|
||||
return Err(AesGcmError::invalid_tag());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds padded data for GHASH.
|
||||
fn build_ghash_data(mut aad: Vec<u8>, mut ciphertext: Vec<u8>) -> Vec<u8> {
|
||||
let associated_data_bitlen = (aad.len() as u64) * 8;
|
||||
let text_bitlen = (ciphertext.len() as u64) * 8;
|
||||
|
||||
let len_block = ((associated_data_bitlen as u128) << 64) + (text_bitlen as u128);
|
||||
|
||||
// Pad data to be a multiple of 16 bytes.
|
||||
let aad_padded_block_count = (aad.len() / 16) + (aad.len() % 16 != 0) as usize;
|
||||
aad.resize(aad_padded_block_count * 16, 0);
|
||||
|
||||
let ciphertext_padded_block_count =
|
||||
(ciphertext.len() / 16) + (ciphertext.len() % 16 != 0) as usize;
|
||||
ciphertext.resize(ciphertext_padded_block_count * 16, 0);
|
||||
|
||||
let mut data: Vec<u8> = Vec::with_capacity(aad.len() + ciphertext.len() + 16);
|
||||
data.extend(aad);
|
||||
data.extend(ciphertext);
|
||||
data.extend_from_slice(&len_block.to_be_bytes());
|
||||
|
||||
data
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
//! This crate provides implementations of 2PC AEADs for authenticated encryption with
|
||||
//! a shared key.
|
||||
//!
|
||||
//! Both parties can work together to encrypt and decrypt messages with different visibility
|
||||
//! configurations. See [`Aead`] for more information on the interface.
|
||||
//!
|
||||
//! For example, one party can privately provide the plaintext to encrypt, while both parties
|
||||
//! can see the ciphertext and the tag. Or, both parties can cooperate to decrypt a ciphertext
|
||||
//! and verify the tag, while only one party can see the plaintext.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod aes_gcm;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
/// An error that can occur during AEAD operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AeadError {
|
||||
#[error(transparent)]
|
||||
BlockCipherError(#[from] block_cipher::BlockCipherError),
|
||||
#[error(transparent)]
|
||||
StreamCipherError(#[from] tlsn_stream_cipher::StreamCipherError),
|
||||
#[error(transparent)]
|
||||
UniversalHashError(#[from] tlsn_universal_hash::UniversalHashError),
|
||||
#[error("Corrupted Tag")]
|
||||
CorruptedTag,
|
||||
#[error("Validation Error: {0}")]
|
||||
ValidationError(String),
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
/// This trait defines the interface for AEADs.
|
||||
#[async_trait]
|
||||
pub trait Aead: Send {
|
||||
/// The error type for the AEAD.
|
||||
type Error: std::error::Error + Send + Sync + 'static;
|
||||
|
||||
/// Sets the key for the AEAD.
|
||||
async fn set_key(&mut self, key: ValueRef, iv: ValueRef) -> Result<(), Self::Error>;
|
||||
|
||||
/// Decodes the key for the AEAD, revealing it to this party.
|
||||
async fn decode_key_private(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Decodes the key for the AEAD, revealing it to the other party(s).
|
||||
async fn decode_key_blind(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Sets the transcript id.
|
||||
///
|
||||
/// The AEAD assigns unique identifiers to each byte of plaintext
|
||||
/// during encryption and decryption.
|
||||
///
|
||||
/// For example, if the transcript id is set to `foo`, then the first byte will
|
||||
/// be assigned the id `foo/0`, the second byte `foo/1`, and so on.
|
||||
///
|
||||
/// Each transcript id has an independent counter.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The state of a transcript counter is preserved between calls to `set_transcript_id`.
|
||||
fn set_transcript_id(&mut self, id: &str);
|
||||
|
||||
/// Performs any necessary one-time setup for the AEAD.
|
||||
async fn setup(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Preprocesses for the given number of bytes.
|
||||
async fn preprocess(&mut self, len: usize) -> Result<(), Self::Error>;
|
||||
|
||||
/// Starts the AEAD.
|
||||
///
|
||||
/// This method performs initialization for the AEAD after setting the key.
|
||||
async fn start(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Encrypts a plaintext message, returning the ciphertext and tag.
|
||||
///
|
||||
/// The plaintext is provided by both parties.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for encryption.
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Encrypts a plaintext message, hiding it from the other party, returning the ciphertext and tag.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for encryption.
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Encrypts a plaintext message provided by the other party, returning
|
||||
/// the ciphertext and tag.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for encryption.
|
||||
/// * `plaintext_len` - The length of the plaintext to encrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext_len: usize,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext to both parties.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext only to this party.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext only to the other party.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Verifies the tag of a ciphertext message.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn verify_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Locally decrypts the provided ciphertext and then proves in ZK to the other party(s) that the
|
||||
/// plaintext is correct.
|
||||
///
|
||||
/// Returns the plaintext.
|
||||
///
|
||||
/// This method requires this party to know the encryption key, which can be achieved by calling
|
||||
/// the `decode_key_private` method.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `payload` - The ciphertext and tag to decrypt and prove.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Locally decrypts the provided ciphertext and then proves in ZK to the other party(s) that the
|
||||
/// plaintext is correct.
|
||||
///
|
||||
/// Returns the plaintext.
|
||||
///
|
||||
/// This method requires this party to know the encryption key, which can be achieved by calling
|
||||
/// the `decode_key_private` method.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// This method does not verify the tag of the ciphertext. Only use this if you know what you're doing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to decrypt and prove.
|
||||
async fn prove_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Verifies the other party(s) can prove they know a plaintext which encrypts to the given ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `payload` - The ciphertext and tag to verify.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Verifies the other party(s) can prove they know a plaintext which encrypts to the given ciphertext.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// This method does not verify the tag of the ciphertext. Only use this if you know what you're doing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to verify.
|
||||
async fn verify_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[workspace]
|
||||
members = ["stream-cipher", "block-cipher"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# tlsn
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "45370cc" }
|
||||
|
||||
# crypto
|
||||
aes = "0.8"
|
||||
ctr = "0.9.2"
|
||||
cipher = "0.4.3"
|
||||
|
||||
# async
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1", default-features = false }
|
||||
|
||||
# testing
|
||||
rstest = "0.17"
|
||||
criterion = "0.5"
|
||||
|
||||
# error/log
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
|
||||
# misc
|
||||
derive_builder = "0.12"
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-block-cipher"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "2PC block cipher implementation"
|
||||
keywords = ["tls", "mpc", "2pc", "block-cipher"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.6"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "block_cipher"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits.workspace = true
|
||||
mpz-garble.workspace = true
|
||||
tlsn-utils.workspace = true
|
||||
async-trait.workspace = true
|
||||
thiserror.workspace = true
|
||||
derive_builder.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
aes.workspace = true
|
||||
cipher.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
@@ -1,277 +0,0 @@
|
||||
use std::{collections::VecDeque, marker::PhantomData};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::{value::ValueRef, Decode, DecodePrivate, Execute, Load, Memory};
|
||||
use tracing::instrument;
|
||||
use utils::id::NestedId;
|
||||
|
||||
use crate::{BlockCipher, BlockCipherCircuit, BlockCipherConfig, BlockCipherError, Visibility};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
private_execution_id: NestedId,
|
||||
public_execution_id: NestedId,
|
||||
preprocessed_private: VecDeque<BlockVars>,
|
||||
preprocessed_public: VecDeque<BlockVars>,
|
||||
key: Option<ValueRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlockVars {
|
||||
msg: ValueRef,
|
||||
ciphertext: ValueRef,
|
||||
}
|
||||
|
||||
/// An MPC block cipher.
|
||||
#[derive(Debug)]
|
||||
pub struct MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Execute + Decode + DecodePrivate + Send + Sync,
|
||||
{
|
||||
state: State,
|
||||
|
||||
executor: E,
|
||||
|
||||
_cipher: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C, E> MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Execute + Decode + DecodePrivate + Send + Sync,
|
||||
{
|
||||
/// Creates a new MPC block cipher.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The configuration for the block cipher.
|
||||
/// * `executor` - The executor to use for the MPC.
|
||||
pub fn new(config: BlockCipherConfig, executor: E) -> Self {
|
||||
let private_execution_id = NestedId::new(&config.id)
|
||||
.append_string("private")
|
||||
.append_counter();
|
||||
let public_execution_id = NestedId::new(&config.id)
|
||||
.append_string("public")
|
||||
.append_counter();
|
||||
Self {
|
||||
state: State {
|
||||
private_execution_id,
|
||||
public_execution_id,
|
||||
preprocessed_private: VecDeque::new(),
|
||||
preprocessed_public: VecDeque::new(),
|
||||
key: None,
|
||||
},
|
||||
executor,
|
||||
_cipher: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn define_block(&mut self, vis: Visibility) -> BlockVars {
|
||||
let (id, msg) = match vis {
|
||||
Visibility::Private => {
|
||||
let id = self
|
||||
.state
|
||||
.private_execution_id
|
||||
.increment_in_place()
|
||||
.to_string();
|
||||
let msg = self
|
||||
.executor
|
||||
.new_private_input::<C::BLOCK>(&format!("{}/msg", &id))
|
||||
.expect("message is not defined");
|
||||
(id, msg)
|
||||
}
|
||||
Visibility::Blind => {
|
||||
let id = self
|
||||
.state
|
||||
.private_execution_id
|
||||
.increment_in_place()
|
||||
.to_string();
|
||||
let msg = self
|
||||
.executor
|
||||
.new_blind_input::<C::BLOCK>(&format!("{}/msg", &id))
|
||||
.expect("message is not defined");
|
||||
(id, msg)
|
||||
}
|
||||
Visibility::Public => {
|
||||
let id = self
|
||||
.state
|
||||
.public_execution_id
|
||||
.increment_in_place()
|
||||
.to_string();
|
||||
let msg = self
|
||||
.executor
|
||||
.new_public_input::<C::BLOCK>(&format!("{}/msg", &id))
|
||||
.expect("message is not defined");
|
||||
(id, msg)
|
||||
}
|
||||
};
|
||||
|
||||
let ciphertext = self
|
||||
.executor
|
||||
.new_output::<C::BLOCK>(&format!("{}/ciphertext", &id))
|
||||
.expect("message is not defined");
|
||||
|
||||
BlockVars { msg, ciphertext }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, E> BlockCipher<C> for MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Load + Execute + Decode + DecodePrivate + Send + Sync + Send,
|
||||
{
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
fn set_key(&mut self, key: ValueRef) {
|
||||
self.state.key = Some(key);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn preprocess(
|
||||
&mut self,
|
||||
visibility: Visibility,
|
||||
count: usize,
|
||||
) -> Result<(), BlockCipherError> {
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(|| BlockCipherError::key_not_set())?;
|
||||
|
||||
for _ in 0..count {
|
||||
let vars = self.define_block(visibility);
|
||||
|
||||
self.executor
|
||||
.load(
|
||||
C::circuit(),
|
||||
&[key.clone(), vars.msg.clone()],
|
||||
&[vars.ciphertext.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
match visibility {
|
||||
Visibility::Private | Visibility::Blind => {
|
||||
self.state.preprocessed_private.push_back(vars)
|
||||
}
|
||||
Visibility::Public => self.state.preprocessed_public.push_back(vars),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_private(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let len = plaintext.len();
|
||||
let block: C::BLOCK = plaintext
|
||||
.try_into()
|
||||
.map_err(|_| BlockCipherError::invalid_message_length::<C>(len))?;
|
||||
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(|| BlockCipherError::key_not_set())?;
|
||||
|
||||
let BlockVars { msg, ciphertext } =
|
||||
if let Some(vars) = self.state.preprocessed_private.pop_front() {
|
||||
vars
|
||||
} else {
|
||||
self.define_block(Visibility::Private)
|
||||
};
|
||||
|
||||
self.executor.assign(&msg, block)?;
|
||||
|
||||
self.executor
|
||||
.execute(C::circuit(), &[key, msg], &[ciphertext.clone()])
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.executor.decode(&[ciphertext]).await?;
|
||||
|
||||
let ciphertext: C::BLOCK = if let Ok(ciphertext) = outputs
|
||||
.pop()
|
||||
.expect("ciphertext should be present")
|
||||
.try_into()
|
||||
{
|
||||
ciphertext
|
||||
} else {
|
||||
panic!("ciphertext should be a block")
|
||||
};
|
||||
|
||||
Ok(ciphertext.into())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_blind(&mut self) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(|| BlockCipherError::key_not_set())?;
|
||||
|
||||
let BlockVars { msg, ciphertext } =
|
||||
if let Some(vars) = self.state.preprocessed_private.pop_front() {
|
||||
vars
|
||||
} else {
|
||||
self.define_block(Visibility::Blind)
|
||||
};
|
||||
|
||||
self.executor
|
||||
.execute(C::circuit(), &[key, msg], &[ciphertext.clone()])
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.executor.decode(&[ciphertext]).await?;
|
||||
|
||||
let ciphertext: C::BLOCK = if let Ok(ciphertext) = outputs
|
||||
.pop()
|
||||
.expect("ciphertext should be present")
|
||||
.try_into()
|
||||
{
|
||||
ciphertext
|
||||
} else {
|
||||
panic!("ciphertext should be a block")
|
||||
};
|
||||
|
||||
Ok(ciphertext.into())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_share(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let len = plaintext.len();
|
||||
let block: C::BLOCK = plaintext
|
||||
.try_into()
|
||||
.map_err(|_| BlockCipherError::invalid_message_length::<C>(len))?;
|
||||
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(|| BlockCipherError::key_not_set())?;
|
||||
|
||||
let BlockVars { msg, ciphertext } =
|
||||
if let Some(vars) = self.state.preprocessed_public.pop_front() {
|
||||
vars
|
||||
} else {
|
||||
self.define_block(Visibility::Public)
|
||||
};
|
||||
|
||||
self.executor.assign(&msg, block)?;
|
||||
|
||||
self.executor
|
||||
.execute(C::circuit(), &[key, msg], &[ciphertext.clone()])
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.executor.decode_shared(&[ciphertext]).await?;
|
||||
|
||||
let share: C::BLOCK =
|
||||
if let Ok(share) = outputs.pop().expect("share should be present").try_into() {
|
||||
share
|
||||
} else {
|
||||
panic!("share should be a block")
|
||||
};
|
||||
|
||||
Ok(share.into())
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_circuits::{
|
||||
circuits::AES128,
|
||||
types::{StaticValueType, Value},
|
||||
Circuit,
|
||||
};
|
||||
|
||||
/// A block cipher circuit.
|
||||
pub trait BlockCipherCircuit: Default + Clone + Send + Sync {
|
||||
/// The key type.
|
||||
type KEY: StaticValueType + Send + Sync;
|
||||
/// The block type.
|
||||
type BLOCK: StaticValueType + TryFrom<Vec<u8>> + TryFrom<Value> + Into<Vec<u8>> + Send + Sync;
|
||||
|
||||
/// The length of the key.
|
||||
const KEY_LEN: usize;
|
||||
/// The length of the block.
|
||||
const BLOCK_LEN: usize;
|
||||
|
||||
/// Returns the circuit of the cipher.
|
||||
fn circuit() -> Arc<Circuit>;
|
||||
}
|
||||
|
||||
/// Aes128 block cipher circuit.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Aes128;
|
||||
|
||||
impl BlockCipherCircuit for Aes128 {
|
||||
type KEY = [u8; 16];
|
||||
type BLOCK = [u8; 16];
|
||||
|
||||
const KEY_LEN: usize = 16;
|
||||
const BLOCK_LEN: usize = 16;
|
||||
|
||||
fn circuit() -> Arc<Circuit> {
|
||||
AES128.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Configuration for a block cipher.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct BlockCipherConfig {
|
||||
/// The ID of the block cipher.
|
||||
#[builder(setter(into))]
|
||||
pub(crate) id: String,
|
||||
}
|
||||
|
||||
impl BlockCipherConfig {
|
||||
/// Creates a new builder for the block cipher configuration.
|
||||
pub fn builder() -> BlockCipherConfigBuilder {
|
||||
BlockCipherConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use core::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::BlockCipherCircuit;
|
||||
|
||||
/// A block cipher error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct BlockCipherError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl BlockCipherError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key_not_set() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Key,
|
||||
source: Some("key not set".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_message_length<C: BlockCipherCircuit>(len: usize) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Msg,
|
||||
source: Some(
|
||||
format!(
|
||||
"message length does not equal block length: {} != {}",
|
||||
len,
|
||||
C::BLOCK_LEN
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Vm,
|
||||
Key,
|
||||
Msg,
|
||||
}
|
||||
|
||||
impl fmt::Display for BlockCipherError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Vm => write!(f, "vm error")?,
|
||||
ErrorKind::Key => write!(f, "key error")?,
|
||||
ErrorKind::Msg => write!(f, "message error")?,
|
||||
}
|
||||
|
||||
if let Some(ref source) = self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::MemoryError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::MemoryError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::LoadError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::LoadError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::ExecutionError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::ExecutionError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::DecodeError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::DecodeError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
//! This crate provides a 2PC block cipher implementation.
|
||||
//!
|
||||
//! Both parties work together to encrypt or share an encrypted block using a shared key.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
mod cipher;
|
||||
mod circuit;
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
pub use crate::{
|
||||
cipher::MpcBlockCipher,
|
||||
circuit::{Aes128, BlockCipherCircuit},
|
||||
};
|
||||
pub use config::{BlockCipherConfig, BlockCipherConfigBuilder, BlockCipherConfigBuilderError};
|
||||
pub use error::BlockCipherError;
|
||||
|
||||
/// Visibility of a message plaintext.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Visibility {
|
||||
/// Private message.
|
||||
Private,
|
||||
/// Blind message.
|
||||
Blind,
|
||||
/// Public message.
|
||||
Public,
|
||||
}
|
||||
|
||||
/// A trait for MPC block ciphers.
|
||||
#[async_trait]
|
||||
pub trait BlockCipher<Cipher>: Send + Sync
|
||||
where
|
||||
Cipher: BlockCipherCircuit,
|
||||
{
|
||||
/// Sets the key for the block cipher.
|
||||
fn set_key(&mut self, key: ValueRef);
|
||||
|
||||
/// Preprocesses `count` blocks.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `visibility` - The visibility of the plaintext.
|
||||
/// * `count` - The number of blocks to preprocess.
|
||||
async fn preprocess(
|
||||
&mut self,
|
||||
visibility: Visibility,
|
||||
count: usize,
|
||||
) -> Result<(), BlockCipherError>;
|
||||
|
||||
/// Encrypts the given plaintext keeping it hidden from the other party(s).
|
||||
///
|
||||
/// Returns the ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
async fn encrypt_private(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError>;
|
||||
|
||||
/// Encrypts a plaintext provided by the other party(s).
|
||||
///
|
||||
/// Returns the ciphertext.
|
||||
async fn encrypt_blind(&mut self) -> Result<Vec<u8>, BlockCipherError>;
|
||||
|
||||
/// Encrypts a plaintext provided by both parties. Fails if the
|
||||
/// plaintext provided by both parties does not match.
|
||||
///
|
||||
/// Returns an additive share of the ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
async fn encrypt_share(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory};
|
||||
|
||||
use crate::circuit::Aes128;
|
||||
|
||||
use ::aes::Aes128 as TestAes128;
|
||||
use ::cipher::{BlockEncrypt, KeyInit};
|
||||
|
||||
fn aes128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
let mut msg = msg.into();
|
||||
let cipher = TestAes128::new(&key.into());
|
||||
cipher.encrypt_block(&mut msg);
|
||||
msg.into()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_block_cipher_blind() {
|
||||
let leader_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
let follower_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
|
||||
let key = [0u8; 16];
|
||||
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
// Key is public just for this test, typically it is private.
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key);
|
||||
|
||||
let plaintext = [0u8; 16];
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_private(plaintext.to_vec()),
|
||||
follower.encrypt_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
assert_eq!(leader_ciphertext, expected.to_vec());
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_block_cipher_share() {
|
||||
let leader_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
let follower_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
|
||||
let key = [0u8; 16];
|
||||
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
// Key is public just for this test, typically it is private.
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key);
|
||||
|
||||
let plaintext = [0u8; 16];
|
||||
|
||||
let (leader_share, follower_share) = tokio::try_join!(
|
||||
leader.encrypt_share(plaintext.to_vec()),
|
||||
follower.encrypt_share(plaintext.to_vec())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
let result: [u8; 16] = std::array::from_fn(|i| leader_share[i] ^ follower_share[i]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_block_cipher_preprocess() {
|
||||
let leader_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
let follower_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
|
||||
let key = [0u8; 16];
|
||||
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
// Key is public just for this test, typically it is private.
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key);
|
||||
|
||||
let plaintext = [0u8; 16];
|
||||
|
||||
tokio::try_join!(
|
||||
leader.preprocess(Visibility::Private, 1),
|
||||
follower.preprocess(Visibility::Blind, 1)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_private(plaintext.to_vec()),
|
||||
follower.encrypt_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
assert_eq!(leader_ciphertext, expected.to_vec());
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
|
||||
tokio::try_join!(
|
||||
leader.preprocess(Visibility::Public, 1),
|
||||
follower.preprocess(Visibility::Public, 1)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (leader_share, follower_share) = tokio::try_join!(
|
||||
leader.encrypt_share(plaintext.to_vec()),
|
||||
follower.encrypt_share(plaintext.to_vec())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
let result: [u8; 16] = std::array::from_fn(|i| leader_share[i] ^ follower_share[i]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-stream-cipher"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "2PC stream cipher implementation"
|
||||
keywords = ["tls", "mpc", "2pc", "stream-cipher"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.6"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
rayon = ["mpz-garble/rayon"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits.workspace = true
|
||||
mpz-garble.workspace = true
|
||||
tlsn-utils.workspace = true
|
||||
aes.workspace = true
|
||||
ctr.workspace = true
|
||||
cipher.workspace = true
|
||||
async-trait.workspace = true
|
||||
thiserror.workspace = true
|
||||
derive_builder.workspace = true
|
||||
tracing.workspace = true
|
||||
opaque-debug = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
futures.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rstest = { workspace = true, features = ["async-timeout"] }
|
||||
criterion = { workspace = true, features = ["async_tokio"] }
|
||||
|
||||
[[bench]]
|
||||
name = "mock"
|
||||
harness = false
|
||||
@@ -1,132 +0,0 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory};
|
||||
use tlsn_stream_cipher::{
|
||||
Aes128Ctr, CtrCircuit, MpcStreamCipher, StreamCipher, StreamCipherConfigBuilder,
|
||||
};
|
||||
|
||||
async fn bench_stream_cipher_encrypt(len: usize) {
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let leader_iv = leader_vm.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, [0u8; 16]).unwrap();
|
||||
leader_vm.assign(&leader_iv, [0u8; 4]).unwrap();
|
||||
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_iv = follower_vm.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
follower_vm.assign(&follower_key, [0u8; 16]).unwrap();
|
||||
follower_vm.assign(&follower_iv, [0u8; 4]).unwrap();
|
||||
|
||||
let leader_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let follower_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcStreamCipher::<Aes128Ctr, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key, leader_iv);
|
||||
|
||||
let mut follower = MpcStreamCipher::<Aes128Ctr, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key, follower_iv);
|
||||
|
||||
let plaintext = vec![0u8; len];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
|
||||
_ = tokio::try_join!(
|
||||
leader.encrypt_private(explicit_nonce.clone(), plaintext),
|
||||
follower.encrypt_blind(explicit_nonce, len)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_ = tokio::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn bench_stream_cipher_zk(len: usize) {
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let leader_iv = leader_vm.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
leader_vm.assign(&leader_iv, iv).unwrap();
|
||||
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_iv = follower_vm.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
follower_vm.assign(&follower_iv, iv).unwrap();
|
||||
|
||||
let leader_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let follower_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcStreamCipher::<Aes128Ctr, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key, leader_iv);
|
||||
|
||||
let mut follower = MpcStreamCipher::<Aes128Ctr, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key, follower_iv);
|
||||
|
||||
futures::try_join!(leader.decode_key_private(), follower.decode_key_blind()).unwrap();
|
||||
|
||||
let plaintext = vec![0u8; len];
|
||||
let explicit_nonce = [0u8; 8];
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 2, &explicit_nonce, &plaintext).unwrap();
|
||||
|
||||
_ = tokio::try_join!(
|
||||
leader.prove_plaintext(explicit_nonce.to_vec(), plaintext),
|
||||
follower.verify_plaintext(explicit_nonce.to_vec(), ciphertext)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_ = tokio::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let len = 1024;
|
||||
|
||||
let mut group = c.benchmark_group("stream_cipher/encrypt_private");
|
||||
group.throughput(Throughput::Bytes(len as u64));
|
||||
group.bench_function(BenchmarkId::from_parameter(len), |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { bench_stream_cipher_encrypt(len).await })
|
||||
});
|
||||
|
||||
drop(group);
|
||||
|
||||
let mut group = c.benchmark_group("stream_cipher/zk");
|
||||
group.throughput(Throughput::Bytes(len as u64));
|
||||
group.bench_function(BenchmarkId::from_parameter(len), |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { bench_stream_cipher_zk(len).await })
|
||||
});
|
||||
|
||||
drop(group);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,118 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{StaticValueType, Value},
|
||||
Circuit,
|
||||
};
|
||||
|
||||
use crate::{circuit::AES_CTR, StreamCipherError};
|
||||
|
||||
/// A counter-mode block cipher circuit.
|
||||
pub trait CtrCircuit: Default + Clone + Send + Sync + 'static {
|
||||
/// The key type.
|
||||
type KEY: StaticValueType + TryFrom<Vec<u8>> + Send + Sync + 'static;
|
||||
/// The block type.
|
||||
type BLOCK: StaticValueType
|
||||
+ TryFrom<Vec<u8>>
|
||||
+ TryFrom<Value>
|
||||
+ Into<Vec<u8>>
|
||||
+ Default
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
/// The IV type.
|
||||
type IV: StaticValueType
|
||||
+ TryFrom<Vec<u8>>
|
||||
+ TryFrom<Value>
|
||||
+ Into<Vec<u8>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
/// The nonce type.
|
||||
type NONCE: StaticValueType
|
||||
+ TryFrom<Vec<u8>>
|
||||
+ TryFrom<Value>
|
||||
+ Into<Vec<u8>>
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ Send
|
||||
+ Sync
|
||||
+ std::fmt::Debug
|
||||
+ 'static;
|
||||
|
||||
/// The length of the key.
|
||||
const KEY_LEN: usize;
|
||||
/// The length of the block.
|
||||
const BLOCK_LEN: usize;
|
||||
/// The length of the IV.
|
||||
const IV_LEN: usize;
|
||||
/// The length of the nonce.
|
||||
const NONCE_LEN: usize;
|
||||
|
||||
/// Returns the circuit of the cipher.
|
||||
fn circuit() -> Arc<Circuit>;
|
||||
|
||||
/// Applies the keystream to the message.
|
||||
fn apply_keystream(
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
start_ctr: usize,
|
||||
explicit_nonce: &[u8],
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
}
|
||||
|
||||
/// A circuit for AES-128 in counter mode.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Aes128Ctr;
|
||||
|
||||
impl CtrCircuit for Aes128Ctr {
|
||||
type KEY = [u8; 16];
|
||||
type BLOCK = [u8; 16];
|
||||
type IV = [u8; 4];
|
||||
type NONCE = [u8; 8];
|
||||
|
||||
const KEY_LEN: usize = 16;
|
||||
const BLOCK_LEN: usize = 16;
|
||||
const IV_LEN: usize = 4;
|
||||
const NONCE_LEN: usize = 8;
|
||||
|
||||
fn circuit() -> Arc<Circuit> {
|
||||
AES_CTR.clone()
|
||||
}
|
||||
|
||||
fn apply_keystream(
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
start_ctr: usize,
|
||||
explicit_nonce: &[u8],
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
use ::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
|
||||
use aes::Aes128;
|
||||
use ctr::Ctr32BE;
|
||||
|
||||
let key: &[u8; 16] = key
|
||||
.try_into()
|
||||
.map_err(|_| StreamCipherError::key_len::<Self>(key.len()))?;
|
||||
let iv: &[u8; 4] = iv
|
||||
.try_into()
|
||||
.map_err(|_| StreamCipherError::iv_len::<Self>(iv.len()))?;
|
||||
let explicit_nonce: &[u8; 8] = explicit_nonce
|
||||
.try_into()
|
||||
.map_err(|_| StreamCipherError::explicit_nonce_len::<Self>(explicit_nonce.len()))?;
|
||||
|
||||
let mut full_iv = [0u8; 16];
|
||||
full_iv[0..4].copy_from_slice(iv);
|
||||
full_iv[4..12].copy_from_slice(explicit_nonce);
|
||||
let mut cipher = Ctr32BE::<Aes128>::new(key.into(), &full_iv.into());
|
||||
let mut buf = msg.to_vec();
|
||||
|
||||
cipher
|
||||
.try_seek(start_ctr * Self::BLOCK_LEN)
|
||||
.expect("start counter is less than keystream length");
|
||||
cipher.apply_keystream(&mut buf);
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use mpz_circuits::{circuits::aes128_trace, once_cell::sync::Lazy, trace, Circuit, CircuitBuilder};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// AES encrypts a counter block.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// 0. KEY: 16-byte encryption key
|
||||
/// 1. IV: 4-byte IV
|
||||
/// 2. EXPLICIT_NONCE: 8-byte explicit nonce
|
||||
/// 3. CTR: 4-byte counter
|
||||
///
|
||||
/// # Outputs
|
||||
///
|
||||
/// 0. ECB: 16-byte output
|
||||
pub(crate) static AES_CTR: Lazy<Arc<Circuit>> = Lazy::new(|| {
|
||||
let builder = CircuitBuilder::new();
|
||||
let key = builder.add_array_input::<u8, 16>();
|
||||
let iv = builder.add_array_input::<u8, 4>();
|
||||
let nonce = builder.add_array_input::<u8, 8>();
|
||||
let ctr = builder.add_array_input::<u8, 4>();
|
||||
let ecb = aes_ctr_trace(builder.state(), key, iv, nonce, ctr);
|
||||
builder.add_output(ecb);
|
||||
|
||||
Arc::new(builder.build().unwrap())
|
||||
});
|
||||
|
||||
#[trace]
|
||||
#[dep(aes_128, aes128_trace)]
|
||||
#[allow(dead_code)]
|
||||
fn aes_ctr(key: [u8; 16], iv: [u8; 4], explicit_nonce: [u8; 8], ctr: [u8; 4]) -> [u8; 16] {
|
||||
let block: Vec<_> = iv.into_iter().chain(explicit_nonce).chain(ctr).collect();
|
||||
aes_128(key, block.try_into().unwrap())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn aes_128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
use aes::{
|
||||
cipher::{BlockEncrypt, KeyInit},
|
||||
Aes128,
|
||||
};
|
||||
|
||||
let aes = Aes128::new_from_slice(&key).unwrap();
|
||||
let mut ciphertext = msg.into();
|
||||
aes.encrypt_block(&mut ciphertext);
|
||||
ciphertext.into()
|
||||
}
|
||||
|
||||
/// Builds a circuit for computing the XOR of two arrays.
|
||||
pub(crate) fn build_array_xor(len: usize) -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let a = builder.add_vec_input::<u8>(len);
|
||||
let b = builder.add_vec_input::<u8>(len);
|
||||
let c = a.into_iter().zip(b).map(|(a, b)| a ^ b).collect::<Vec<_>>();
|
||||
builder.add_output(c);
|
||||
Arc::new(builder.build().expect("circuit is valid"))
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Configuration for a stream cipher.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct StreamCipherConfig {
|
||||
/// The ID of the stream cipher.
|
||||
#[builder(setter(into))]
|
||||
pub(crate) id: String,
|
||||
/// The start block counter value.
|
||||
#[builder(default = "2")]
|
||||
pub(crate) start_ctr: usize,
|
||||
/// Transcript ID used to determine the unique identifiers
|
||||
/// for the plaintext bytes during encryption and decryption.
|
||||
#[builder(setter(into), default = "\"transcript\".to_string()")]
|
||||
pub(crate) transcript_id: String,
|
||||
}
|
||||
|
||||
impl StreamCipherConfig {
|
||||
/// Creates a new builder for the stream cipher configuration.
|
||||
pub fn builder() -> StreamCipherConfigBuilder {
|
||||
StreamCipherConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum InputText {
|
||||
Public { ids: Vec<String>, text: Vec<u8> },
|
||||
Private { ids: Vec<String>, text: Vec<u8> },
|
||||
Blind { ids: Vec<String> },
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for InputText {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Public { ids, .. } => f
|
||||
.debug_struct("Public")
|
||||
.field("ids", ids)
|
||||
.field("text", &"{{ ... }}")
|
||||
.finish(),
|
||||
Self::Private { ids, .. } => f
|
||||
.debug_struct("Private")
|
||||
.field("ids", ids)
|
||||
.field("text", &"{{ ... }}")
|
||||
.finish(),
|
||||
Self::Blind { ids, .. } => f.debug_struct("Blind").field("ids", ids).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum ExecutionMode {
|
||||
Mpc,
|
||||
Prove,
|
||||
Verify,
|
||||
}
|
||||
|
||||
pub(crate) fn is_valid_mode(mode: &ExecutionMode, input_text: &InputText) -> bool {
|
||||
match (mode, input_text) {
|
||||
(ExecutionMode::Mpc, _) => true,
|
||||
(ExecutionMode::Prove, InputText::Private { .. }) => true,
|
||||
(ExecutionMode::Verify, InputText::Blind { .. }) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
use core::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::CtrCircuit;
|
||||
|
||||
/// A stream cipher error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct StreamCipherError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl StreamCipherError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key_len<C: CtrCircuit>(len: usize) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Key,
|
||||
source: Some(
|
||||
format!("invalid key length: expected {}, got {}", C::KEY_LEN, len).into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn iv_len<C: CtrCircuit>(len: usize) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Iv,
|
||||
source: Some(format!("invalid iv length: expected {}, got {}", C::IV_LEN, len).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn explicit_nonce_len<C: CtrCircuit>(len: usize) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::ExplicitNonce,
|
||||
source: Some(
|
||||
format!(
|
||||
"invalid explicit nonce length: expected {}, got {}",
|
||||
C::NONCE_LEN,
|
||||
len
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key_not_set() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Key,
|
||||
source: Some("key not set".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Vm,
|
||||
Key,
|
||||
Iv,
|
||||
ExplicitNonce,
|
||||
}
|
||||
|
||||
impl fmt::Display for StreamCipherError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Vm => write!(f, "vm error")?,
|
||||
ErrorKind::Key => write!(f, "key error")?,
|
||||
ErrorKind::Iv => write!(f, "iv error")?,
|
||||
ErrorKind::ExplicitNonce => write!(f, "explicit nonce error")?,
|
||||
}
|
||||
|
||||
if let Some(ref source) = self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::MemoryError> for StreamCipherError {
|
||||
fn from(error: mpz_garble::MemoryError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::LoadError> for StreamCipherError {
|
||||
fn from(error: mpz_garble::LoadError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::ExecutionError> for StreamCipherError {
|
||||
fn from(error: mpz_garble::ExecutionError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::ProveError> for StreamCipherError {
|
||||
fn from(error: mpz_garble::ProveError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::VerifyError> for StreamCipherError {
|
||||
fn from(error: mpz_garble::VerifyError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::DecodeError> for StreamCipherError {
|
||||
fn from(error: mpz_garble::DecodeError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
use std::{collections::VecDeque, marker::PhantomData};
|
||||
|
||||
use mpz_garble::{value::ValueRef, Execute, Load, Memory, Prove, Thread, Verify};
|
||||
use tracing::instrument;
|
||||
use utils::id::NestedId;
|
||||
|
||||
use crate::{config::ExecutionMode, CtrCircuit, StreamCipherError};
|
||||
|
||||
pub(crate) struct KeyStream<C> {
|
||||
block_counter: NestedId,
|
||||
preprocessed: BlockVars,
|
||||
_pd: PhantomData<C>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct BlockVars {
|
||||
blocks: VecDeque<ValueRef>,
|
||||
nonces: VecDeque<ValueRef>,
|
||||
ctrs: VecDeque<ValueRef>,
|
||||
}
|
||||
|
||||
impl BlockVars {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.blocks.is_empty()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.blocks.len()
|
||||
}
|
||||
|
||||
fn drain(&mut self, count: usize) -> BlockVars {
|
||||
let blocks = self.blocks.drain(0..count).collect();
|
||||
let nonces = self.nonces.drain(0..count).collect();
|
||||
let ctrs = self.ctrs.drain(0..count).collect();
|
||||
|
||||
BlockVars {
|
||||
blocks,
|
||||
nonces,
|
||||
ctrs,
|
||||
}
|
||||
}
|
||||
|
||||
fn extend(&mut self, vars: BlockVars) {
|
||||
self.blocks.extend(vars.blocks);
|
||||
self.nonces.extend(vars.nonces);
|
||||
self.ctrs.extend(vars.ctrs);
|
||||
}
|
||||
|
||||
fn iter(&self) -> impl Iterator<Item = (&ValueRef, &ValueRef, &ValueRef)> {
|
||||
self.blocks
|
||||
.iter()
|
||||
.zip(self.nonces.iter())
|
||||
.zip(self.ctrs.iter())
|
||||
.map(|((block, nonce), ctr)| (block, nonce, ctr))
|
||||
}
|
||||
|
||||
fn flatten(&self, len: usize) -> Vec<ValueRef> {
|
||||
self.blocks
|
||||
.iter()
|
||||
.flat_map(|block| block.iter())
|
||||
.cloned()
|
||||
.take(len)
|
||||
.map(|byte| ValueRef::Value { id: byte })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CtrCircuit> KeyStream<C> {
|
||||
pub(crate) fn new(id: &str) -> Self {
|
||||
let block_counter = NestedId::new(id).append_counter();
|
||||
Self {
|
||||
block_counter,
|
||||
preprocessed: BlockVars::default(),
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn define_vars(
|
||||
&mut self,
|
||||
mem: &mut impl Memory,
|
||||
count: usize,
|
||||
) -> Result<BlockVars, StreamCipherError> {
|
||||
let mut vars = BlockVars::default();
|
||||
for _ in 0..count {
|
||||
let block_id = self.block_counter.increment_in_place();
|
||||
let block = mem.new_output::<C::BLOCK>(&block_id.to_string())?;
|
||||
let nonce =
|
||||
mem.new_public_input::<C::NONCE>(&block_id.append_string("nonce").to_string())?;
|
||||
let ctr =
|
||||
mem.new_public_input::<[u8; 4]>(&block_id.append_string("ctr").to_string())?;
|
||||
|
||||
vars.blocks.push_back(block);
|
||||
vars.nonces.push_back(nonce);
|
||||
vars.ctrs.push_back(ctr);
|
||||
}
|
||||
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn preprocess<T>(
|
||||
&mut self,
|
||||
thread: &mut T,
|
||||
key: &ValueRef,
|
||||
iv: &ValueRef,
|
||||
len: usize,
|
||||
) -> Result<(), StreamCipherError>
|
||||
where
|
||||
T: Thread + Memory + Load + Send + 'static,
|
||||
{
|
||||
let block_count = (len / C::BLOCK_LEN) + (len % C::BLOCK_LEN != 0) as usize;
|
||||
let vars = self.define_vars(thread, block_count)?;
|
||||
|
||||
let calls = vars
|
||||
.iter()
|
||||
.map(|(block, nonce, ctr)| {
|
||||
(
|
||||
C::circuit(),
|
||||
vec![key.clone(), iv.clone(), nonce.clone(), ctr.clone()],
|
||||
vec![block.clone()],
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (circ, inputs, outputs) in calls {
|
||||
thread.load(circ, &inputs, &outputs).await?;
|
||||
}
|
||||
|
||||
self.preprocessed.extend(vars);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn compute<T>(
|
||||
&mut self,
|
||||
thread: &mut T,
|
||||
mode: ExecutionMode,
|
||||
key: &ValueRef,
|
||||
iv: &ValueRef,
|
||||
explicit_nonce: Vec<u8>,
|
||||
start_ctr: usize,
|
||||
len: usize,
|
||||
) -> Result<ValueRef, StreamCipherError>
|
||||
where
|
||||
T: Thread + Memory + Execute + Prove + Verify + Send + 'static,
|
||||
{
|
||||
let block_count = (len / C::BLOCK_LEN) + (len % C::BLOCK_LEN != 0) as usize;
|
||||
let explicit_nonce_len = explicit_nonce.len();
|
||||
let explicit_nonce: C::NONCE = explicit_nonce
|
||||
.try_into()
|
||||
.map_err(|_| StreamCipherError::explicit_nonce_len::<C>(explicit_nonce_len))?;
|
||||
|
||||
// Take any preprocessed blocks if available, and define new ones if needed.
|
||||
let vars = if !self.preprocessed.is_empty() {
|
||||
let mut vars = self
|
||||
.preprocessed
|
||||
.drain(block_count.min(self.preprocessed.len()));
|
||||
if vars.len() < block_count {
|
||||
vars.extend(self.define_vars(thread, block_count - vars.len())?)
|
||||
}
|
||||
vars
|
||||
} else {
|
||||
self.define_vars(thread, block_count)?
|
||||
};
|
||||
|
||||
let mut calls = Vec::with_capacity(vars.len());
|
||||
let mut inputs = Vec::with_capacity(vars.len() * 4);
|
||||
for (i, (block, nonce_ref, ctr_ref)) in vars.iter().enumerate() {
|
||||
thread.assign(nonce_ref, explicit_nonce)?;
|
||||
thread.assign(ctr_ref, ((start_ctr + i) as u32).to_be_bytes())?;
|
||||
|
||||
inputs.push(key.clone());
|
||||
inputs.push(iv.clone());
|
||||
inputs.push(nonce_ref.clone());
|
||||
inputs.push(ctr_ref.clone());
|
||||
|
||||
calls.push((
|
||||
C::circuit(),
|
||||
vec![key.clone(), iv.clone(), nonce_ref.clone(), ctr_ref.clone()],
|
||||
vec![block.clone()],
|
||||
));
|
||||
}
|
||||
|
||||
match mode {
|
||||
ExecutionMode::Mpc => {
|
||||
thread.commit(&inputs).await?;
|
||||
for (circ, inputs, outputs) in calls {
|
||||
thread.execute(circ, &inputs, &outputs).await?;
|
||||
}
|
||||
}
|
||||
ExecutionMode::Prove => {
|
||||
// Note that after the circuit execution, the value of `block` can be considered as
|
||||
// implicitly authenticated since `key` and `iv` have already been authenticated earlier
|
||||
// and `nonce_ref` and `ctr_ref` are public.
|
||||
// [Prove::prove] will **not** be called on `block` at any later point.
|
||||
thread.commit_prove(&inputs).await?;
|
||||
for (circ, inputs, outputs) in calls {
|
||||
thread.execute_prove(circ, &inputs, &outputs).await?;
|
||||
}
|
||||
}
|
||||
ExecutionMode::Verify => {
|
||||
thread.commit_verify(&inputs).await?;
|
||||
for (circ, inputs, outputs) in calls {
|
||||
thread.execute_verify(circ, &inputs, &outputs).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let keystream = thread.array_from_values(&vars.flatten(len))?;
|
||||
|
||||
Ok(keystream)
|
||||
}
|
||||
}
|
||||
@@ -1,462 +0,0 @@
|
||||
//! This crate provides a 2PC stream cipher implementation using a block cipher in counter mode.
|
||||
//!
|
||||
//! Each party plays a specific role, either the `StreamCipherLeader` or the `StreamCipherFollower`.
|
||||
//! Both parties work together to encrypt and decrypt messages using a shared key.
|
||||
//!
|
||||
//! # Transcript
|
||||
//!
|
||||
//! Using the `record` flag, the `StreamCipherFollower` can optionally use a dedicated stream when
|
||||
//! encoding the plaintext labels, which allows the `StreamCipherLeader` to build a transcript of
|
||||
//! active labels which are pushed to the provided `TranscriptSink`.
|
||||
//!
|
||||
//! Afterwards, the `StreamCipherLeader` can create commitments to the transcript which can be used in
|
||||
//! a selective disclosure protocol.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
mod cipher;
|
||||
mod circuit;
|
||||
mod config;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod keystream;
|
||||
mod stream_cipher;
|
||||
|
||||
pub use self::cipher::{Aes128Ctr, CtrCircuit};
|
||||
pub use config::{StreamCipherConfig, StreamCipherConfigBuilder, StreamCipherConfigBuilderError};
|
||||
pub use error::StreamCipherError;
|
||||
pub use stream_cipher::MpcStreamCipher;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
/// A trait for MPC stream ciphers.
|
||||
#[async_trait]
|
||||
pub trait StreamCipher<Cipher>: Send + Sync
|
||||
where
|
||||
Cipher: cipher::CtrCircuit,
|
||||
{
|
||||
/// Sets the key and iv for the stream cipher.
|
||||
fn set_key(&mut self, key: ValueRef, iv: ValueRef);
|
||||
|
||||
/// Decodes the key for the stream cipher, revealing it to this party.
|
||||
async fn decode_key_private(&mut self) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Decodes the key for the stream cipher, revealing it to the other party(s).
|
||||
async fn decode_key_blind(&mut self) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Sets the transcript id
|
||||
///
|
||||
/// The stream cipher assigns unique identifiers to each byte of plaintext
|
||||
/// during encryption and decryption.
|
||||
///
|
||||
/// For example, if the transcript id is set to `foo`, then the first byte will
|
||||
/// be assigned the id `foo/0`, the second byte `foo/1`, and so on.
|
||||
///
|
||||
/// Each transcript id has an independent counter.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The state of a transcript counter is preserved between calls to `set_transcript_id`.
|
||||
fn set_transcript_id(&mut self, id: &str);
|
||||
|
||||
/// Preprocesses the keystream for the given number of bytes.
|
||||
async fn preprocess(&mut self, len: usize) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Applies the keystream to the given plaintext, where all parties
|
||||
/// provide the plaintext as an input.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `plaintext` - The message to apply the keystream to.
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Applies the keystream to the given plaintext without revealing it
|
||||
/// to the other party(s).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `plaintext` - The message to apply the keystream to.
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Applies the keystream to a plaintext provided by another party.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `len` - The length of the plaintext provided by another party.
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
len: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Decrypts a ciphertext by removing the keystream, where the plaintext
|
||||
/// is revealed to all parties.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to decrypt.
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Decrypts a ciphertext by removing the keystream, where the plaintext
|
||||
/// is only revealed to this party.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to decrypt.
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Decrypts a ciphertext by removing the keystream, where the plaintext
|
||||
/// is not revealed to this party.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to decrypt.
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Locally decrypts the provided ciphertext and then proves in ZK to the other party(s) that the
|
||||
/// plaintext is correct.
|
||||
///
|
||||
/// Returns the plaintext.
|
||||
///
|
||||
/// This method requires this party to know the encryption key, which can be achieved by calling
|
||||
/// the `decode_key_private` method.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to decrypt and prove.
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Verifies the other party(s) can prove they know a plaintext which encrypts to the given ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to verify.
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Returns an additive share of the keystream block for the given explicit nonce and counter.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream block.
|
||||
/// * `ctr` - The counter to use for the keystream block.
|
||||
async fn share_keystream_block(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ctr: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::cipher::Aes128Ctr;
|
||||
|
||||
use super::*;
|
||||
|
||||
use mpz_garble::{
|
||||
protocol::deap::mock::{create_mock_deap_vm, MockFollower, MockLeader},
|
||||
Memory,
|
||||
};
|
||||
use rstest::*;
|
||||
|
||||
async fn create_test_pair<C: CtrCircuit>(
|
||||
start_ctr: usize,
|
||||
key: [u8; 16],
|
||||
iv: [u8; 4],
|
||||
) -> (
|
||||
MpcStreamCipher<C, MockLeader>,
|
||||
MpcStreamCipher<C, MockFollower>,
|
||||
) {
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let leader_iv = leader_vm.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
leader_vm.assign(&leader_iv, iv).unwrap();
|
||||
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_iv = follower_vm.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
follower_vm.assign(&follower_iv, iv).unwrap();
|
||||
|
||||
let leader_config = StreamCipherConfig::builder()
|
||||
.id("test")
|
||||
.start_ctr(start_ctr)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let follower_config = StreamCipherConfig::builder()
|
||||
.id("test")
|
||||
.start_ctr(start_ctr)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcStreamCipher::<C, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key, leader_iv);
|
||||
|
||||
let mut follower = MpcStreamCipher::<C, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key, follower_iv);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_public() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [0u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let (mut leader, mut follower) = create_test_pair::<Aes128Ctr>(1, key, iv).await;
|
||||
|
||||
let leader_fut = async {
|
||||
let leader_encrypted_msg = leader
|
||||
.encrypt_public(explicit_nonce.to_vec(), msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_decrypted_msg = leader
|
||||
.decrypt_public(explicit_nonce.to_vec(), leader_encrypted_msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(leader_encrypted_msg, leader_decrypted_msg)
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
let follower_encrypted_msg = follower
|
||||
.encrypt_public(explicit_nonce.to_vec(), msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let follower_decrypted_msg = follower
|
||||
.decrypt_public(explicit_nonce.to_vec(), follower_encrypted_msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(follower_encrypted_msg, follower_decrypted_msg)
|
||||
};
|
||||
|
||||
let (
|
||||
(leader_encrypted_msg, leader_decrypted_msg),
|
||||
(follower_encrypted_msg, follower_decrypted_msg),
|
||||
) = futures::join!(leader_fut, follower_fut);
|
||||
|
||||
let reference = Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
assert_eq!(leader_encrypted_msg, reference);
|
||||
assert_eq!(leader_decrypted_msg, msg);
|
||||
assert_eq!(follower_encrypted_msg, reference);
|
||||
assert_eq!(follower_decrypted_msg, msg);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_private() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [1u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
let (mut leader, mut follower) = create_test_pair::<Aes128Ctr>(1, key, iv).await;
|
||||
|
||||
let leader_fut = async {
|
||||
let leader_decrypted_msg = leader
|
||||
.decrypt_private(explicit_nonce.to_vec(), ciphertext.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_encrypted_msg = leader
|
||||
.encrypt_private(explicit_nonce.to_vec(), leader_decrypted_msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(leader_encrypted_msg, leader_decrypted_msg)
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
follower
|
||||
.decrypt_blind(explicit_nonce.to_vec(), ciphertext.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
follower
|
||||
.encrypt_blind(explicit_nonce.to_vec(), msg.len())
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let ((leader_encrypted_msg, leader_decrypted_msg), follower_encrypted_msg) =
|
||||
futures::join!(leader_fut, follower_fut);
|
||||
|
||||
assert_eq!(leader_encrypted_msg, ciphertext);
|
||||
assert_eq!(leader_decrypted_msg, msg);
|
||||
assert_eq!(follower_encrypted_msg, ciphertext);
|
||||
|
||||
futures::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_share_key_block() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [0u8; 8];
|
||||
|
||||
let (mut leader, mut follower) = create_test_pair::<Aes128Ctr>(1, key, iv).await;
|
||||
|
||||
let leader_fut = async {
|
||||
leader
|
||||
.share_keystream_block(explicit_nonce.to_vec(), 1)
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
follower
|
||||
.share_keystream_block(explicit_nonce.to_vec(), 1)
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let (leader_share, follower_share) = futures::join!(leader_fut, follower_fut);
|
||||
|
||||
let key_block = leader_share
|
||||
.into_iter()
|
||||
.zip(follower_share)
|
||||
.map(|(a, b)| a ^ b)
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let reference =
|
||||
Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &[0u8; 16]).unwrap();
|
||||
|
||||
assert_eq!(reference, key_block);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_zk() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [1u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 2, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
let (mut leader, mut follower) = create_test_pair::<Aes128Ctr>(2, key, iv).await;
|
||||
|
||||
futures::try_join!(leader.decode_key_private(), follower.decode_key_blind()).unwrap();
|
||||
|
||||
futures::try_join!(
|
||||
leader.prove_plaintext(explicit_nonce.to_vec(), ciphertext.clone()),
|
||||
follower.verify_plaintext(explicit_nonce.to_vec(), ciphertext)
|
||||
)
|
||||
.unwrap();
|
||||
futures::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::one_block(16)]
|
||||
#[case::partial(17)]
|
||||
#[case::extra(128)]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_preprocess(#[case] len: usize) {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [1u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
let (mut leader, mut follower) = create_test_pair::<Aes128Ctr>(1, key, iv).await;
|
||||
|
||||
let leader_fut = async {
|
||||
leader.preprocess(len).await.unwrap();
|
||||
|
||||
leader
|
||||
.decrypt_private(explicit_nonce.to_vec(), ciphertext.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
follower.preprocess(len).await.unwrap();
|
||||
|
||||
follower
|
||||
.decrypt_blind(explicit_nonce.to_vec(), ciphertext.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let (leader_decrypted_msg, _) = futures::join!(leader_fut, follower_fut);
|
||||
|
||||
assert_eq!(leader_decrypted_msg, msg);
|
||||
|
||||
futures::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,670 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use mpz_circuits::types::Value;
|
||||
use std::collections::HashMap;
|
||||
use tracing::instrument;
|
||||
|
||||
use mpz_garble::{value::ValueRef, Decode, DecodePrivate, Execute, Load, Prove, Thread, Verify};
|
||||
use utils::id::NestedId;
|
||||
|
||||
use crate::{
|
||||
cipher::CtrCircuit,
|
||||
circuit::build_array_xor,
|
||||
config::{is_valid_mode, ExecutionMode, InputText, StreamCipherConfig},
|
||||
keystream::KeyStream,
|
||||
StreamCipher, StreamCipherError,
|
||||
};
|
||||
|
||||
/// An MPC stream cipher.
|
||||
#[derive(Debug)]
|
||||
pub struct MpcStreamCipher<C, E>
|
||||
where
|
||||
C: CtrCircuit,
|
||||
E: Thread + Execute + Decode + DecodePrivate + Send + Sync,
|
||||
{
|
||||
config: StreamCipherConfig,
|
||||
state: State<C>,
|
||||
thread: E,
|
||||
}
|
||||
|
||||
struct State<C> {
|
||||
/// Encoded key and IV for the cipher.
|
||||
encoded_key_iv: Option<EncodedKeyAndIv>,
|
||||
/// Key and IV for the cipher.
|
||||
key_iv: Option<KeyAndIv>,
|
||||
/// Keystream state.
|
||||
keystream: KeyStream<C>,
|
||||
/// Current transcript.
|
||||
transcript: Transcript,
|
||||
/// Maps a transcript ID to the corresponding transcript.
|
||||
transcripts: HashMap<String, Transcript>,
|
||||
/// Number of messages operated on.
|
||||
counter: usize,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(State<C>);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EncodedKeyAndIv {
|
||||
key: ValueRef,
|
||||
iv: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct KeyAndIv {
|
||||
key: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
}
|
||||
|
||||
/// A subset of plaintext bytes processed by the stream cipher.
|
||||
///
|
||||
/// Note that `Transcript` does not store the actual bytes. Instead, it provides IDs which are
|
||||
/// assigned to plaintext bytes of the stream cipher.
|
||||
struct Transcript {
|
||||
/// The ID of this transcript.
|
||||
id: String,
|
||||
/// The ID for the next plaintext byte.
|
||||
plaintext: NestedId,
|
||||
}
|
||||
|
||||
impl Transcript {
|
||||
fn new(id: &str) -> Self {
|
||||
Self {
|
||||
id: id.to_string(),
|
||||
plaintext: NestedId::new(id).append_counter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns unique identifiers for the next plaintext bytes in the transcript.
|
||||
fn extend_plaintext(&mut self, len: usize) -> Vec<String> {
|
||||
(0..len)
|
||||
.map(|_| self.plaintext.increment_in_place().to_string())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, E> MpcStreamCipher<C, E>
|
||||
where
|
||||
C: CtrCircuit,
|
||||
E: Thread + Execute + Load + Prove + Verify + Decode + DecodePrivate + Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new counter-mode cipher.
|
||||
pub fn new(config: StreamCipherConfig, thread: E) -> Self {
|
||||
let keystream = KeyStream::new(&config.id);
|
||||
let transcript = Transcript::new(&config.transcript_id);
|
||||
Self {
|
||||
config,
|
||||
state: State {
|
||||
encoded_key_iv: None,
|
||||
key_iv: None,
|
||||
keystream,
|
||||
transcript,
|
||||
transcripts: HashMap::new(),
|
||||
counter: 0,
|
||||
},
|
||||
thread,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying thread.
|
||||
pub fn thread_mut(&mut self) -> &mut E {
|
||||
&mut self.thread
|
||||
}
|
||||
|
||||
/// Computes a keystream of the given length.
|
||||
async fn compute_keystream(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
start_ctr: usize,
|
||||
len: usize,
|
||||
mode: ExecutionMode,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.as_ref()
|
||||
.ok_or_else(|| StreamCipherError::key_not_set())?;
|
||||
|
||||
let keystream = self
|
||||
.state
|
||||
.keystream
|
||||
.compute(
|
||||
&mut self.thread,
|
||||
mode,
|
||||
key,
|
||||
iv,
|
||||
explicit_nonce,
|
||||
start_ctr,
|
||||
len,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.state.counter += 1;
|
||||
|
||||
Ok(keystream)
|
||||
}
|
||||
|
||||
/// Applies the keystream to the provided input text.
|
||||
async fn apply_keystream(
|
||||
&mut self,
|
||||
mode: ExecutionMode,
|
||||
input_text: InputText,
|
||||
keystream: ValueRef,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
debug_assert!(
|
||||
is_valid_mode(&mode, &input_text),
|
||||
"invalid execution mode for input text"
|
||||
);
|
||||
|
||||
let input_text = match input_text {
|
||||
InputText::Public { ids, text } => {
|
||||
let refs = text
|
||||
.into_iter()
|
||||
.zip(ids)
|
||||
.map(|(byte, id)| {
|
||||
let value_ref = self.thread.new_public_input::<u8>(&id)?;
|
||||
self.thread.assign(&value_ref, byte)?;
|
||||
|
||||
Ok::<_, StreamCipherError>(value_ref)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
self.thread.array_from_values(&refs)?
|
||||
}
|
||||
InputText::Private { ids, text } => {
|
||||
let refs = text
|
||||
.into_iter()
|
||||
.zip(ids)
|
||||
.map(|(byte, id)| {
|
||||
let value_ref = self.thread.new_private_input::<u8>(&id)?;
|
||||
self.thread.assign(&value_ref, byte)?;
|
||||
|
||||
Ok::<_, StreamCipherError>(value_ref)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
self.thread.array_from_values(&refs)?
|
||||
}
|
||||
InputText::Blind { ids } => {
|
||||
let refs = ids
|
||||
.into_iter()
|
||||
.map(|id| self.thread.new_blind_input::<u8>(&id))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
self.thread.array_from_values(&refs)?
|
||||
}
|
||||
};
|
||||
|
||||
let output_text = self.thread.new_array_output::<u8>(
|
||||
&format!("{}/out/{}", self.config.id, self.state.counter),
|
||||
input_text.len(),
|
||||
)?;
|
||||
|
||||
let circ = build_array_xor(input_text.len());
|
||||
|
||||
match mode {
|
||||
ExecutionMode::Mpc => {
|
||||
self.thread
|
||||
.execute(circ, &[input_text, keystream], &[output_text.clone()])
|
||||
.await?;
|
||||
}
|
||||
ExecutionMode::Prove => {
|
||||
self.thread
|
||||
.execute_prove(circ, &[input_text, keystream], &[output_text.clone()])
|
||||
.await?;
|
||||
}
|
||||
ExecutionMode::Verify => {
|
||||
self.thread
|
||||
.execute_verify(circ, &[input_text, keystream], &[output_text.clone()])
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output_text)
|
||||
}
|
||||
|
||||
async fn decode_public(&mut self, value: ValueRef) -> Result<Value, StreamCipherError> {
|
||||
self.thread
|
||||
.decode(&[value])
|
||||
.await
|
||||
.map_err(StreamCipherError::from)
|
||||
.map(|mut output| output.pop().unwrap())
|
||||
}
|
||||
|
||||
async fn decode_shared(&mut self, value: ValueRef) -> Result<Value, StreamCipherError> {
|
||||
self.thread
|
||||
.decode_shared(&[value])
|
||||
.await
|
||||
.map_err(StreamCipherError::from)
|
||||
.map(|mut output| output.pop().unwrap())
|
||||
}
|
||||
|
||||
async fn decode_private(&mut self, value: ValueRef) -> Result<Value, StreamCipherError> {
|
||||
self.thread
|
||||
.decode_private(&[value])
|
||||
.await
|
||||
.map_err(StreamCipherError::from)
|
||||
.map(|mut output| output.pop().unwrap())
|
||||
}
|
||||
|
||||
async fn decode_blind(&mut self, value: ValueRef) -> Result<(), StreamCipherError> {
|
||||
self.thread.decode_blind(&[value]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prove(&mut self, value: ValueRef) -> Result<(), StreamCipherError> {
|
||||
self.thread.prove(&[value]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn verify(&mut self, value: ValueRef, expected: Value) -> Result<(), StreamCipherError> {
|
||||
self.thread.verify(&[value], &[expected]).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, E> StreamCipher<C> for MpcStreamCipher<C, E>
|
||||
where
|
||||
C: CtrCircuit,
|
||||
E: Thread + Execute + Load + Prove + Verify + Decode + DecodePrivate + Send + Sync + 'static,
|
||||
{
|
||||
fn set_key(&mut self, key: ValueRef, iv: ValueRef) {
|
||||
self.state.encoded_key_iv = Some(EncodedKeyAndIv { key, iv });
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decode_key_private(&mut self) -> Result<(), StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.clone()
|
||||
.ok_or_else(|| StreamCipherError::key_not_set())?;
|
||||
|
||||
let [key, iv]: [_; 2] = self
|
||||
.thread
|
||||
.decode_private(&[key, iv])
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("decoded 2 values");
|
||||
|
||||
let key: Vec<u8> = key.try_into().expect("key is an array");
|
||||
let iv: Vec<u8> = iv.try_into().expect("iv is an array");
|
||||
|
||||
self.state.key_iv = Some(KeyAndIv { key, iv });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decode_key_blind(&mut self) -> Result<(), StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.clone()
|
||||
.ok_or_else(|| StreamCipherError::key_not_set())?;
|
||||
|
||||
self.thread.decode_blind(&[key, iv]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_transcript_id(&mut self, id: &str) {
|
||||
if id == self.state.transcript.id {
|
||||
return;
|
||||
}
|
||||
|
||||
let transcript = self
|
||||
.state
|
||||
.transcripts
|
||||
.remove(id)
|
||||
.unwrap_or_else(|| Transcript::new(id));
|
||||
let old_transcript = std::mem::replace(&mut self.state.transcript, transcript);
|
||||
self.state
|
||||
.transcripts
|
||||
.insert(old_transcript.id.clone(), old_transcript);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn preprocess(&mut self, len: usize) -> Result<(), StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.as_ref()
|
||||
.ok_or_else(|| StreamCipherError::key_not_set())?;
|
||||
|
||||
self.state
|
||||
.keystream
|
||||
.preprocess(&mut self.thread, key, iv, len)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
plaintext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Mpc,
|
||||
InputText::Public {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext,
|
||||
},
|
||||
keystream,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext: Vec<u8> = self
|
||||
.decode_public(ciphertext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("ciphertext is array");
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
plaintext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Mpc,
|
||||
InputText::Private {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext,
|
||||
},
|
||||
keystream,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext: Vec<u8> = self
|
||||
.decode_public(ciphertext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("ciphertext is array");
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
len: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
len,
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(len);
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Mpc,
|
||||
InputText::Blind { ids: plaintext_ids },
|
||||
keystream,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext: Vec<u8> = self
|
||||
.decode_public(ciphertext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("ciphertext is array");
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
// TODO: We may want to support writing to the transcript when decrypting
|
||||
// in public mode.
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext_ids = (0..ciphertext.len())
|
||||
.map(|i| format!("ct/{}/{}", self.state.counter, i))
|
||||
.collect();
|
||||
let plaintext = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Mpc,
|
||||
InputText::Public {
|
||||
ids: ciphertext_ids,
|
||||
text: ciphertext,
|
||||
},
|
||||
keystream,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext: Vec<u8> = self
|
||||
.decode_public(plaintext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("plaintext is array");
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream_ref = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let keystream: Vec<u8> = self
|
||||
.decode_private(keystream_ref.clone())
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("keystream is array");
|
||||
|
||||
let plaintext = ciphertext
|
||||
.into_iter()
|
||||
.zip(keystream)
|
||||
.map(|(c, k)| c ^ k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Prove plaintext encrypts back to ciphertext.
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Prove,
|
||||
InputText::Private {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext.clone(),
|
||||
},
|
||||
keystream_ref,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.prove(ciphertext).await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError> {
|
||||
let keystream_ref = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.decode_blind(keystream_ref.clone()).await?;
|
||||
|
||||
// Verify the plaintext encrypts back to ciphertext.
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(ciphertext.len());
|
||||
let ciphertext_ref = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Verify,
|
||||
InputText::Blind { ids: plaintext_ids },
|
||||
keystream_ref,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.verify(ciphertext_ref, ciphertext.into()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let KeyAndIv { key, iv } = self
|
||||
.state
|
||||
.key_iv
|
||||
.clone()
|
||||
.ok_or_else(|| StreamCipherError::key_not_set())?;
|
||||
|
||||
let plaintext = C::apply_keystream(
|
||||
&key,
|
||||
&iv,
|
||||
self.config.start_ctr,
|
||||
&explicit_nonce,
|
||||
&ciphertext,
|
||||
)?;
|
||||
|
||||
// Prove plaintext encrypts back to ciphertext.
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
plaintext.len(),
|
||||
ExecutionMode::Prove,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Prove,
|
||||
InputText::Private {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext.clone(),
|
||||
},
|
||||
keystream,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.prove(ciphertext).await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Verify,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.state.transcript.extend_plaintext(ciphertext.len());
|
||||
let ciphertext_ref = self
|
||||
.apply_keystream(
|
||||
ExecutionMode::Verify,
|
||||
InputText::Blind { ids: plaintext_ids },
|
||||
keystream,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.verify(ciphertext_ref, ciphertext.into()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn share_keystream_block(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ctr: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.as_ref()
|
||||
.ok_or_else(|| StreamCipherError::key_not_set())?;
|
||||
|
||||
let key_block = self
|
||||
.state
|
||||
.keystream
|
||||
.compute(
|
||||
&mut self.thread,
|
||||
ExecutionMode::Mpc,
|
||||
key,
|
||||
iv,
|
||||
explicit_nonce,
|
||||
ctr,
|
||||
C::BLOCK_LEN,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let share = self
|
||||
.decode_shared(key_block)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("key block is array");
|
||||
|
||||
Ok(share)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-key-exchange"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "Implementation of the 3-party key-exchange protocol"
|
||||
keywords = ["tls", "mpc", "2pc", "pms", "key-exchange"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.6"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "key_exchange"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac", features = [
|
||||
"ideal",
|
||||
] }
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
|
||||
p256 = { version = "0.13", features = ["ecdh", "serde"] }
|
||||
async-trait = "0.1"
|
||||
thiserror = "1"
|
||||
serde = "1"
|
||||
futures = "0.3"
|
||||
serio = "0.1"
|
||||
derive_builder = "0.12"
|
||||
tracing = "0.1"
|
||||
rand = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac", features = [
|
||||
"ideal",
|
||||
] }
|
||||
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
rand_core = "0.6"
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
@@ -1,43 +0,0 @@
|
||||
//! This module provides the circuits used in the key exchange protocol.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_circuits::{circuits::big_num::nbyte_add_mod_trace, Circuit, CircuitBuilder};
|
||||
|
||||
/// NIST P-256 prime big-endian.
|
||||
static P: [u8; 32] = [
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
];
|
||||
|
||||
/// Circuit for combining additive shares of the PMS, twice
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// 0. PMS_SHARE_A0: 32 bytes PMS Additive Share
|
||||
/// 1. PMS_SHARE_B0: 32 bytes PMS Additive Share
|
||||
/// 2. PMS_SHARE_A1: 32 bytes PMS Additive Share
|
||||
/// 3. PMS_SHARE_B1: 32 bytes PMS Additive Share
|
||||
///
|
||||
/// # Outputs
|
||||
/// 0. PMS_0: Pre-master Secret = PMS_SHARE_A0 + PMS_SHARE_B0
|
||||
/// 1. PMS_1: Pre-master Secret = PMS_SHARE_A1 + PMS_SHARE_B1
|
||||
/// 2. EQ: Equality check of PMS_0 and PMS_1
|
||||
pub(crate) fn build_pms_circuit() -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let share_a0 = builder.add_array_input::<u8, 32>();
|
||||
let share_b0 = builder.add_array_input::<u8, 32>();
|
||||
let share_a1 = builder.add_array_input::<u8, 32>();
|
||||
let share_b1 = builder.add_array_input::<u8, 32>();
|
||||
|
||||
let pms_0 = nbyte_add_mod_trace(builder.state(), share_a0, share_b0, P);
|
||||
let pms_1 = nbyte_add_mod_trace(builder.state(), share_a1, share_b1, P);
|
||||
|
||||
let eq: [_; 32] = std::array::from_fn(|i| pms_0[i] ^ pms_1[i]);
|
||||
|
||||
builder.add_output(pms_0);
|
||||
builder.add_output(pms_1);
|
||||
builder.add_output(eq);
|
||||
|
||||
Arc::new(builder.build().expect("pms circuit is valid"))
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Role in the key exchange protocol.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Role {
|
||||
/// Leader.
|
||||
Leader,
|
||||
/// Follower.
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// A config used for [MpcKeyExchange](super::MpcKeyExchange).
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct KeyExchangeConfig {
|
||||
/// Protocol role.
|
||||
role: Role,
|
||||
}
|
||||
|
||||
impl KeyExchangeConfig {
|
||||
/// Creates a new builder for the key exchange configuration.
|
||||
pub fn builder() -> KeyExchangeConfigBuilder {
|
||||
KeyExchangeConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Get the role of this instance.
|
||||
pub fn role(&self) -> &Role {
|
||||
&self.role
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
use core::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
/// A key exchange error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct KeyExchangeError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl KeyExchangeError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn kind(&self) -> &ErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
pub(crate) fn state(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::State,
|
||||
source: Some(msg.into().into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn role(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Role,
|
||||
source: Some(msg.into().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Io,
|
||||
Context,
|
||||
Vm,
|
||||
ShareConversion,
|
||||
Key,
|
||||
State,
|
||||
Role,
|
||||
}
|
||||
|
||||
impl fmt::Display for KeyExchangeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Io => write!(f, "io error")?,
|
||||
ErrorKind::Context => write!(f, "context error")?,
|
||||
ErrorKind::Vm => write!(f, "vm error")?,
|
||||
ErrorKind::ShareConversion => write!(f, "share conversion error")?,
|
||||
ErrorKind::Key => write!(f, "key error")?,
|
||||
ErrorKind::State => write!(f, "state error")?,
|
||||
ErrorKind::Role => write!(f, "role error")?,
|
||||
}
|
||||
|
||||
if let Some(ref source) = self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_common::ContextError> for KeyExchangeError {
|
||||
fn from(error: mpz_common::ContextError) -> Self {
|
||||
Self::new(ErrorKind::Context, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::MemoryError> for KeyExchangeError {
|
||||
fn from(error: mpz_garble::MemoryError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::LoadError> for KeyExchangeError {
|
||||
fn from(error: mpz_garble::LoadError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::ExecutionError> for KeyExchangeError {
|
||||
fn from(error: mpz_garble::ExecutionError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::DecodeError> for KeyExchangeError {
|
||||
fn from(error: mpz_garble::DecodeError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_share_conversion::ShareConversionError> for KeyExchangeError {
|
||||
fn from(error: mpz_share_conversion::ShareConversionError) -> Self {
|
||||
Self::new(ErrorKind::ShareConversion, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<p256::elliptic_curve::Error> for KeyExchangeError {
|
||||
fn from(error: p256::elliptic_curve::Error) -> Self {
|
||||
Self::new(ErrorKind::Key, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for KeyExchangeError {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
Self::new(ErrorKind::Io, error)
|
||||
}
|
||||
}
|
||||
@@ -1,653 +0,0 @@
|
||||
//! This module implements the key exchange logic.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_common::{scoped_futures::ScopedFutureExt, Allocate, Context, Preprocess};
|
||||
use mpz_garble::{value::ValueRef, Decode, Execute, Load, Memory};
|
||||
|
||||
use mpz_fields::{p256::P256, Field};
|
||||
use mpz_share_conversion::{ShareConversionError, ShareConvert};
|
||||
use p256::{EncodedPoint, PublicKey, SecretKey};
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use std::fmt::Debug;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use crate::{
|
||||
circuit::build_pms_circuit,
|
||||
config::{KeyExchangeConfig, Role},
|
||||
error::ErrorKind,
|
||||
point_addition::derive_x_coord_share,
|
||||
KeyExchange, KeyExchangeError, Pms,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Initialized,
|
||||
Setup {
|
||||
share_a0: ValueRef,
|
||||
share_b0: ValueRef,
|
||||
share_a1: ValueRef,
|
||||
share_b1: ValueRef,
|
||||
pms_0: ValueRef,
|
||||
pms_1: ValueRef,
|
||||
eq: ValueRef,
|
||||
},
|
||||
Preprocessed {
|
||||
share_a0: ValueRef,
|
||||
share_b0: ValueRef,
|
||||
share_a1: ValueRef,
|
||||
share_b1: ValueRef,
|
||||
pms_0: ValueRef,
|
||||
pms_1: ValueRef,
|
||||
eq: ValueRef,
|
||||
},
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn is_preprocessed(&self) -> bool {
|
||||
matches!(self, Self::Preprocessed { .. })
|
||||
}
|
||||
|
||||
fn take(&mut self) -> Self {
|
||||
std::mem::replace(self, Self::Error)
|
||||
}
|
||||
}
|
||||
|
||||
/// An MPC key exchange protocol.
|
||||
///
|
||||
/// Can be either a leader or a follower depending on the `role` field in [`KeyExchangeConfig`].
|
||||
#[derive(Debug)]
|
||||
pub struct MpcKeyExchange<Ctx, C0, C1, E> {
|
||||
ctx: Ctx,
|
||||
/// Share conversion protocol 0.
|
||||
converter_0: C0,
|
||||
/// Share conversion protocol 1.
|
||||
converter_1: C1,
|
||||
/// MPC executor.
|
||||
executor: E,
|
||||
/// The private key of the party behind this instance, either follower or leader.
|
||||
private_key: Option<SecretKey>,
|
||||
/// The public key of the server.
|
||||
server_key: Option<PublicKey>,
|
||||
/// The config used for the key exchange protocol.
|
||||
config: KeyExchangeConfig,
|
||||
/// The state of the protocol.
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl<Ctx, C0, C1, E> MpcKeyExchange<Ctx, C0, C1, E> {
|
||||
/// Creates a new [`MpcKeyExchange`].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - Key exchange configuration.
|
||||
/// * `ctx` - Thread context.
|
||||
/// * `converter_0` - Share conversion protocol instance 0.
|
||||
/// * `converter_1` - Share conversion protocol instance 1.
|
||||
/// * `executor` - MPC executor.
|
||||
pub fn new(
|
||||
config: KeyExchangeConfig,
|
||||
ctx: Ctx,
|
||||
converter_0: C0,
|
||||
converter_1: C1,
|
||||
executor: E,
|
||||
) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
converter_0,
|
||||
converter_1,
|
||||
executor,
|
||||
private_key: None,
|
||||
server_key: None,
|
||||
config,
|
||||
state: State::Initialized,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx, C0, C1, E> MpcKeyExchange<Ctx, C0, C1, E>
|
||||
where
|
||||
Ctx: Context,
|
||||
E: Execute + Load + Memory + Decode + Send,
|
||||
C0: ShareConvert<Ctx, P256> + Send,
|
||||
C1: ShareConvert<Ctx, P256> + Send,
|
||||
{
|
||||
async fn compute_pms_shares(
|
||||
&mut self,
|
||||
server_key: PublicKey,
|
||||
private_key: SecretKey,
|
||||
) -> Result<(P256, P256), KeyExchangeError> {
|
||||
compute_pms_shares(
|
||||
&mut self.ctx,
|
||||
*self.config.role(),
|
||||
&mut self.converter_0,
|
||||
&mut self.converter_1,
|
||||
server_key,
|
||||
private_key,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Computes the PMS using both parties' shares, performing an equality check
|
||||
// to ensure the shares are equal.
|
||||
async fn compute_pms_with(
|
||||
&mut self,
|
||||
share_0: P256,
|
||||
share_1: P256,
|
||||
) -> Result<Pms, KeyExchangeError> {
|
||||
let State::Preprocessed {
|
||||
share_a0,
|
||||
share_b0,
|
||||
share_a1,
|
||||
share_b1,
|
||||
pms_0,
|
||||
pms_1,
|
||||
eq,
|
||||
} = self.state.take()
|
||||
else {
|
||||
return Err(KeyExchangeError::state("not in preprocessed state"));
|
||||
};
|
||||
|
||||
let share_0_bytes: [u8; 32] = share_0
|
||||
.to_be_bytes()
|
||||
.try_into()
|
||||
.expect("pms share is 32 bytes");
|
||||
let share_1_bytes: [u8; 32] = share_1
|
||||
.to_be_bytes()
|
||||
.try_into()
|
||||
.expect("pms share is 32 bytes");
|
||||
|
||||
match self.config.role() {
|
||||
Role::Leader => {
|
||||
self.executor.assign(&share_a0, share_0_bytes)?;
|
||||
self.executor.assign(&share_a1, share_1_bytes)?;
|
||||
}
|
||||
Role::Follower => {
|
||||
self.executor.assign(&share_b0, share_0_bytes)?;
|
||||
self.executor.assign(&share_b1, share_1_bytes)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.executor
|
||||
.execute(
|
||||
build_pms_circuit(),
|
||||
&[share_a0, share_b0, share_a1, share_b1],
|
||||
&[pms_0.clone(), pms_1, eq.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let eq: [u8; 32] = self
|
||||
.executor
|
||||
.decode(&[eq])
|
||||
.await?
|
||||
.pop()
|
||||
.expect("output 0 is eq")
|
||||
.try_into()
|
||||
.expect("eq is 32 bytes");
|
||||
|
||||
// Eq should be all zeros if pms_1 == pms_2.
|
||||
if eq != [0u8; 32] {
|
||||
return Err(KeyExchangeError::new(
|
||||
ErrorKind::ShareConversion,
|
||||
"PMS values not equal",
|
||||
));
|
||||
}
|
||||
|
||||
// Both parties use pms_0 as the pre-master secret.
|
||||
Ok(Pms::new(pms_0))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Ctx, C0, C1, E> KeyExchange for MpcKeyExchange<Ctx, C0, C1, E>
|
||||
where
|
||||
Ctx: Context,
|
||||
E: Execute + Load + Memory + Decode + Send,
|
||||
C0: Allocate + Preprocess<Ctx, Error = ShareConversionError> + ShareConvert<Ctx, P256> + Send,
|
||||
C1: Allocate + Preprocess<Ctx, Error = ShareConversionError> + ShareConvert<Ctx, P256> + Send,
|
||||
{
|
||||
fn server_key(&self) -> Option<PublicKey> {
|
||||
self.server_key
|
||||
}
|
||||
|
||||
async fn set_server_key(&mut self, server_key: PublicKey) -> Result<(), KeyExchangeError> {
|
||||
let Role::Leader = self.config.role() else {
|
||||
return Err(KeyExchangeError::role("follower cannot set server key"));
|
||||
};
|
||||
|
||||
// Send server public key to follower.
|
||||
self.ctx.io_mut().send(server_key).await?;
|
||||
|
||||
self.server_key = Some(server_key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn setup(&mut self) -> Result<Pms, KeyExchangeError> {
|
||||
let State::Initialized = self.state.take() else {
|
||||
return Err(KeyExchangeError::state("not in initialized state"));
|
||||
};
|
||||
|
||||
// 2 A2M, 1 M2A.
|
||||
self.converter_0.alloc(3);
|
||||
self.converter_1.alloc(3);
|
||||
|
||||
let (share_a0, share_b0, share_a1, share_b1) = match self.config.role() {
|
||||
Role::Leader => {
|
||||
let share_a0 = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_a0")?;
|
||||
let share_b0 = self.executor.new_blind_input::<[u8; 32]>("pms/share_b0")?;
|
||||
let share_a1 = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_a1")?;
|
||||
let share_b1 = self.executor.new_blind_input::<[u8; 32]>("pms/share_b1")?;
|
||||
|
||||
(share_a0, share_b0, share_a1, share_b1)
|
||||
}
|
||||
Role::Follower => {
|
||||
let share_a0 = self.executor.new_blind_input::<[u8; 32]>("pms/share_a0")?;
|
||||
let share_b0 = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_b0")?;
|
||||
let share_a1 = self.executor.new_blind_input::<[u8; 32]>("pms/share_a1")?;
|
||||
let share_b1 = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_b1")?;
|
||||
|
||||
(share_a0, share_b0, share_a1, share_b1)
|
||||
}
|
||||
};
|
||||
|
||||
let pms_0 = self.executor.new_output::<[u8; 32]>("pms_0")?;
|
||||
let pms_1 = self.executor.new_output::<[u8; 32]>("pms_1")?;
|
||||
let eq = self.executor.new_output::<[u8; 32]>("eq")?;
|
||||
|
||||
self.state = State::Setup {
|
||||
share_a0,
|
||||
share_b0,
|
||||
share_a1,
|
||||
share_b1,
|
||||
pms_0: pms_0.clone(),
|
||||
pms_1,
|
||||
eq,
|
||||
};
|
||||
|
||||
Ok(Pms::new(pms_0))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn preprocess(&mut self) -> Result<(), KeyExchangeError> {
|
||||
let State::Setup {
|
||||
share_a0,
|
||||
share_b0,
|
||||
share_a1,
|
||||
share_b1,
|
||||
pms_0,
|
||||
pms_1,
|
||||
eq,
|
||||
} = self.state.take()
|
||||
else {
|
||||
return Err(KeyExchangeError::state("not in setup state"));
|
||||
};
|
||||
|
||||
// Preprocess share conversion and garbled circuits concurrently.
|
||||
futures::try_join!(
|
||||
async {
|
||||
self.ctx
|
||||
.try_join(
|
||||
|ctx| self.converter_0.preprocess(ctx).scope_boxed(),
|
||||
|ctx| self.converter_1.preprocess(ctx).scope_boxed(),
|
||||
)
|
||||
.await??;
|
||||
|
||||
Ok::<_, KeyExchangeError>(())
|
||||
},
|
||||
async {
|
||||
self.executor
|
||||
.load(
|
||||
build_pms_circuit(),
|
||||
&[
|
||||
share_a0.clone(),
|
||||
share_b0.clone(),
|
||||
share_a1.clone(),
|
||||
share_b1.clone(),
|
||||
],
|
||||
&[pms_0.clone(), pms_1.clone(), eq.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok::<_, KeyExchangeError>(())
|
||||
}
|
||||
)?;
|
||||
|
||||
// Follower can forward their key share immediately.
|
||||
if let Role::Follower = self.config.role() {
|
||||
let private_key = self
|
||||
.private_key
|
||||
.get_or_insert_with(|| SecretKey::random(&mut rand::rngs::OsRng));
|
||||
|
||||
self.ctx.io_mut().send(private_key.public_key()).await?;
|
||||
|
||||
debug!("sent public key share to leader");
|
||||
}
|
||||
|
||||
self.state = State::Preprocessed {
|
||||
share_a0,
|
||||
share_b0,
|
||||
share_a1,
|
||||
share_b1,
|
||||
pms_0,
|
||||
pms_1,
|
||||
eq,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn client_key(&mut self) -> Result<PublicKey, KeyExchangeError> {
|
||||
if let Role::Leader = self.config.role() {
|
||||
let private_key = self
|
||||
.private_key
|
||||
.get_or_insert_with(|| SecretKey::random(&mut rand::rngs::OsRng));
|
||||
let public_key = private_key.public_key();
|
||||
|
||||
// Receive public key share from follower.
|
||||
let follower_public_key: PublicKey = self.ctx.io_mut().expect_next().await?;
|
||||
|
||||
debug!("received public key share from follower");
|
||||
|
||||
// Combine public keys.
|
||||
let client_public_key = PublicKey::from_affine(
|
||||
(public_key.to_projective() + follower_public_key.to_projective()).to_affine(),
|
||||
)?;
|
||||
|
||||
Ok(client_public_key)
|
||||
} else {
|
||||
Err(KeyExchangeError::role("follower does not learn client key"))
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn compute_pms(&mut self) -> Result<Pms, KeyExchangeError> {
|
||||
if !self.state.is_preprocessed() {
|
||||
return Err(KeyExchangeError::state("not in preprocessed state"));
|
||||
}
|
||||
|
||||
let server_key = match self.config.role() {
|
||||
Role::Leader => self
|
||||
.server_key
|
||||
.ok_or_else(|| KeyExchangeError::state("server public key not set"))?,
|
||||
Role::Follower => {
|
||||
// Receive server public key from leader.
|
||||
let server_key = self.ctx.io_mut().expect_next().await?;
|
||||
|
||||
self.server_key = Some(server_key);
|
||||
|
||||
server_key
|
||||
}
|
||||
};
|
||||
|
||||
let private_key = self
|
||||
.private_key
|
||||
.take()
|
||||
.ok_or(KeyExchangeError::state("private key not set"))?;
|
||||
|
||||
let (pms_share_0, pms_share_1) = self.compute_pms_shares(server_key, private_key).await?;
|
||||
let pms = self.compute_pms_with(pms_share_0, pms_share_1).await?;
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(pms)
|
||||
}
|
||||
}
|
||||
|
||||
async fn compute_pms_shares<
|
||||
Ctx: Context,
|
||||
C0: ShareConvert<Ctx, P256> + Send,
|
||||
C1: ShareConvert<Ctx, P256> + Send,
|
||||
>(
|
||||
ctx: &mut Ctx,
|
||||
role: Role,
|
||||
converter_0: &mut C0,
|
||||
converter_1: &mut C1,
|
||||
server_key: PublicKey,
|
||||
private_key: SecretKey,
|
||||
) -> Result<(P256, P256), KeyExchangeError> {
|
||||
// Compute the leader's/follower's share of the pre-master secret.
|
||||
//
|
||||
// We need to mimic the [diffie-hellman](p256::ecdh::diffie_hellman) function without the
|
||||
// [SharedSecret](p256::ecdh::SharedSecret) wrapper, because this makes it harder to get
|
||||
// the result as an EC curve point.
|
||||
let shared_secret = {
|
||||
let public_projective = server_key.to_projective();
|
||||
(public_projective * private_key.to_nonzero_scalar().as_ref()).to_affine()
|
||||
};
|
||||
|
||||
let encoded_point = EncodedPoint::from(PublicKey::from_affine(shared_secret)?);
|
||||
|
||||
let (pms_share_0, pms_share_1) = ctx
|
||||
.try_join(
|
||||
|ctx| {
|
||||
async { derive_x_coord_share(role, ctx, converter_0, encoded_point).await }
|
||||
.scope_boxed()
|
||||
},
|
||||
|ctx| {
|
||||
async { derive_x_coord_share(role, ctx, converter_1, encoded_point).await }
|
||||
.scope_boxed()
|
||||
},
|
||||
)
|
||||
.await??;
|
||||
|
||||
Ok((pms_share_0, pms_share_1))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mpz_common::executor::{test_st_executor, STExecutor};
|
||||
use mpz_garble::protocol::deap::mock::{create_mock_deap_vm, MockFollower, MockLeader};
|
||||
use mpz_share_conversion::ideal::{ideal_share_converter, IdealShareConverter};
|
||||
use p256::{NonZeroScalar, PublicKey, SecretKey};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
use rand_core::SeedableRng;
|
||||
use serio::channel::MemoryDuplex;
|
||||
|
||||
fn create_pair() -> (
|
||||
MpcKeyExchange<
|
||||
STExecutor<MemoryDuplex>,
|
||||
IdealShareConverter,
|
||||
IdealShareConverter,
|
||||
MockLeader,
|
||||
>,
|
||||
MpcKeyExchange<
|
||||
STExecutor<MemoryDuplex>,
|
||||
IdealShareConverter,
|
||||
IdealShareConverter,
|
||||
MockFollower,
|
||||
>,
|
||||
) {
|
||||
let (leader_ctx, follower_ctx) = test_st_executor(8);
|
||||
let (leader_converter_0, follower_converter_0) = ideal_share_converter();
|
||||
let (follower_converter_1, leader_converter_1) = ideal_share_converter();
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
let leader = MpcKeyExchange::new(
|
||||
KeyExchangeConfig::builder()
|
||||
.role(Role::Leader)
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader_ctx,
|
||||
leader_converter_0,
|
||||
leader_converter_1,
|
||||
leader_vm,
|
||||
);
|
||||
|
||||
let follower = MpcKeyExchange::new(
|
||||
KeyExchangeConfig::builder()
|
||||
.role(Role::Follower)
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower_ctx,
|
||||
follower_converter_0,
|
||||
follower_converter_1,
|
||||
follower_vm,
|
||||
);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_key_exchange() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&NonZeroScalar::random(&mut rng));
|
||||
|
||||
let (mut leader, mut follower) = create_pair();
|
||||
|
||||
leader.private_key = Some(leader_private_key.clone());
|
||||
follower.private_key = Some(follower_private_key.clone());
|
||||
|
||||
tokio::try_join!(leader.setup(), follower.setup()).unwrap();
|
||||
tokio::try_join!(leader.preprocess(), follower.preprocess()).unwrap();
|
||||
|
||||
let client_public_key = leader.client_key().await.unwrap();
|
||||
leader.set_server_key(server_public_key).await.unwrap();
|
||||
|
||||
let expected_client_public_key = PublicKey::from_affine(
|
||||
(leader_private_key.public_key().to_projective()
|
||||
+ follower_private_key.public_key().to_projective())
|
||||
.to_affine(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(client_public_key, expected_client_public_key);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compute_pms() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_private_key = NonZeroScalar::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&server_private_key);
|
||||
|
||||
let (mut leader, mut follower) = create_pair();
|
||||
|
||||
leader.private_key = Some(leader_private_key);
|
||||
follower.private_key = Some(follower_private_key);
|
||||
|
||||
tokio::try_join!(leader.setup(), follower.setup()).unwrap();
|
||||
tokio::try_join!(leader.preprocess(), follower.preprocess()).unwrap();
|
||||
|
||||
leader.set_server_key(server_public_key).await.unwrap();
|
||||
|
||||
let (_leader_pms, _follower_pms) =
|
||||
tokio::try_join!(leader.compute_pms(), follower.compute_pms()).unwrap();
|
||||
|
||||
assert_eq!(leader.server_key.unwrap(), server_public_key);
|
||||
assert_eq!(follower.server_key.unwrap(), server_public_key);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compute_pms_shares() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0_u8; 32]);
|
||||
let (mut ctx_leader, mut ctx_follower) = test_st_executor(8);
|
||||
let (mut leader_converter_0, mut follower_converter_0) = ideal_share_converter();
|
||||
let (mut follower_converter_1, mut leader_converter_1) = ideal_share_converter();
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_private_key = NonZeroScalar::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&server_private_key);
|
||||
|
||||
let client_public_key = PublicKey::from_affine(
|
||||
(leader_private_key.public_key().to_projective()
|
||||
+ follower_private_key.public_key().to_projective())
|
||||
.to_affine(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ((leader_share_0, leader_share_1), (follower_share_0, follower_share_1)) =
|
||||
tokio::try_join!(
|
||||
compute_pms_shares(
|
||||
&mut ctx_leader,
|
||||
Role::Leader,
|
||||
&mut leader_converter_0,
|
||||
&mut leader_converter_1,
|
||||
server_public_key,
|
||||
leader_private_key
|
||||
),
|
||||
compute_pms_shares(
|
||||
&mut ctx_follower,
|
||||
Role::Follower,
|
||||
&mut follower_converter_0,
|
||||
&mut follower_converter_1,
|
||||
server_public_key,
|
||||
follower_private_key
|
||||
)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_ecdh_x =
|
||||
p256::ecdh::diffie_hellman(server_private_key, client_public_key.as_affine());
|
||||
|
||||
assert_eq!(
|
||||
(leader_share_0 + follower_share_0).to_be_bytes(),
|
||||
expected_ecdh_x.raw_secret_bytes().to_vec()
|
||||
);
|
||||
assert_eq!(
|
||||
(leader_share_1 + follower_share_1).to_be_bytes(),
|
||||
expected_ecdh_x.raw_secret_bytes().to_vec()
|
||||
);
|
||||
|
||||
assert_ne!(leader_share_0, follower_share_0);
|
||||
assert_ne!(leader_share_1, follower_share_1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compute_pms_fail() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_private_key = NonZeroScalar::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&server_private_key);
|
||||
|
||||
let (mut leader, mut follower) = create_pair();
|
||||
|
||||
leader.private_key = Some(leader_private_key.clone());
|
||||
follower.private_key = Some(follower_private_key.clone());
|
||||
|
||||
tokio::try_join!(leader.setup(), follower.setup()).unwrap();
|
||||
tokio::try_join!(leader.preprocess(), follower.preprocess()).unwrap();
|
||||
|
||||
leader.set_server_key(server_public_key).await.unwrap();
|
||||
|
||||
let ((mut share_a0, share_a1), (share_b0, share_b1)) = tokio::try_join!(
|
||||
leader.compute_pms_shares(server_public_key, leader_private_key),
|
||||
follower.compute_pms_shares(server_public_key, follower_private_key)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
share_a0 = share_a0 + P256::one();
|
||||
|
||||
let (leader_res, follower_res) = tokio::join!(
|
||||
leader.compute_pms_with(share_a0, share_a1),
|
||||
follower.compute_pms_with(share_b0, share_b1)
|
||||
);
|
||||
|
||||
let leader_err = leader_res.unwrap_err();
|
||||
let follower_err = follower_res.unwrap_err();
|
||||
|
||||
assert!(matches!(leader_err.kind(), ErrorKind::ShareConversion));
|
||||
assert!(matches!(follower_err.kind(), ErrorKind::ShareConversion));
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
//! # The Key Exchange Protocol
|
||||
//!
|
||||
//! This crate implements a key exchange protocol with 3 parties, namely server, leader and
|
||||
//! follower. The goal is to end up with a shared secret (ECDH) between the server and the client.
|
||||
//! The client in this context is leader and follower combined, which means that each of them will
|
||||
//! end up with a share of the shared secret. The leader will do all the necessary communication
|
||||
//! with the server alone and forward all messages from and to the follower.
|
||||
//!
|
||||
//! A detailed description of this protocol can be found in our documentation
|
||||
//! <https://docs.tlsnotary.org/protocol/notarization/key_exchange.html>.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod circuit;
|
||||
mod config;
|
||||
pub(crate) mod error;
|
||||
mod exchange;
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
pub(crate) mod point_addition;
|
||||
|
||||
pub use config::{
|
||||
KeyExchangeConfig, KeyExchangeConfigBuilder, KeyExchangeConfigBuilderError, Role,
|
||||
};
|
||||
pub use error::KeyExchangeError;
|
||||
pub use exchange::MpcKeyExchange;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_garble::value::ValueRef;
|
||||
use p256::PublicKey;
|
||||
|
||||
/// Pre-master secret.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pms(ValueRef);
|
||||
|
||||
impl Pms {
|
||||
/// Creates a new PMS.
|
||||
pub fn new(value: ValueRef) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Gets the value of the PMS.
|
||||
pub fn into_value(self) -> ValueRef {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for the 3-party key exchange protocol.
|
||||
#[async_trait]
|
||||
pub trait KeyExchange {
|
||||
/// Gets the server's public key.
|
||||
fn server_key(&self) -> Option<PublicKey>;
|
||||
|
||||
/// Sets the server's public key.
|
||||
async fn set_server_key(&mut self, server_key: PublicKey) -> Result<(), KeyExchangeError>;
|
||||
|
||||
/// Computes the client's public key.
|
||||
///
|
||||
/// The client's public key in this context is the combined public key (EC point addition) of
|
||||
/// the leader's public key and the follower's public key.
|
||||
async fn client_key(&mut self) -> Result<PublicKey, KeyExchangeError>;
|
||||
|
||||
/// Performs any necessary one-time setup, returning a reference to the PMS.
|
||||
///
|
||||
/// The PMS will not be assigned until `compute_pms` is called.
|
||||
async fn setup(&mut self) -> Result<Pms, KeyExchangeError>;
|
||||
|
||||
/// Preprocesses the key exchange.
|
||||
async fn preprocess(&mut self) -> Result<(), KeyExchangeError>;
|
||||
|
||||
/// Computes the PMS.
|
||||
async fn compute_pms(&mut self) -> Result<Pms, KeyExchangeError>;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
//! This module provides mock types for key exchange leader and follower and a function to create
|
||||
//! such a pair.
|
||||
|
||||
use crate::{KeyExchangeConfig, MpcKeyExchange, Role};
|
||||
|
||||
use mpz_common::executor::{test_st_executor, STExecutor};
|
||||
use mpz_garble::{Decode, Execute, Memory};
|
||||
use mpz_share_conversion::ideal::{ideal_share_converter, IdealShareConverter};
|
||||
use serio::channel::MemoryDuplex;
|
||||
|
||||
/// A mock key exchange instance.
|
||||
pub type MockKeyExchange<E> =
|
||||
MpcKeyExchange<STExecutor<MemoryDuplex>, IdealShareConverter, IdealShareConverter, E>;
|
||||
|
||||
/// Creates a mock pair of key exchange leader and follower.
|
||||
pub fn create_mock_key_exchange_pair<E: Memory + Execute + Decode + Send>(
|
||||
leader_executor: E,
|
||||
follower_executor: E,
|
||||
) -> (MockKeyExchange<E>, MockKeyExchange<E>) {
|
||||
let (leader_ctx, follower_ctx) = test_st_executor(8);
|
||||
let (leader_converter_0, follower_converter_0) = ideal_share_converter();
|
||||
let (leader_converter_1, follower_converter_1) = ideal_share_converter();
|
||||
|
||||
let key_exchange_config_leader = KeyExchangeConfig::builder()
|
||||
.role(Role::Leader)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let key_exchange_config_follower = KeyExchangeConfig::builder()
|
||||
.role(Role::Follower)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let leader = MpcKeyExchange::new(
|
||||
key_exchange_config_leader,
|
||||
leader_ctx,
|
||||
leader_converter_0,
|
||||
leader_converter_1,
|
||||
leader_executor,
|
||||
);
|
||||
|
||||
let follower = MpcKeyExchange::new(
|
||||
key_exchange_config_follower,
|
||||
follower_ctx,
|
||||
follower_converter_0,
|
||||
follower_converter_1,
|
||||
follower_executor,
|
||||
);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_garble::protocol::deap::mock::create_mock_deap_vm;
|
||||
|
||||
use crate::KeyExchange;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mock_is_ke() {
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
let (leader, follower) = create_mock_key_exchange_pair(leader_vm, follower_vm);
|
||||
|
||||
fn is_key_exchange<T: KeyExchange>(_: T) {}
|
||||
|
||||
is_key_exchange(leader);
|
||||
is_key_exchange(follower);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
//! This module contains the message types exchanged between the prover and the TLS verifier.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use p256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey as P256PublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A type for messages exchanged between the prover and the TLS verifier during the key exchange
|
||||
/// protocol.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum KeyExchangeMessage {
|
||||
FollowerPublicKey(PublicKey),
|
||||
ServerPublicKey(PublicKey),
|
||||
}
|
||||
|
||||
/// A wrapper for a serialized public key.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PublicKey {
|
||||
/// The sec1 serialized public key.
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
/// An error that can occur during parsing of a public key.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct KeyParseError(#[from] p256::elliptic_curve::Error);
|
||||
|
||||
impl Display for KeyParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Unable to parse public key: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P256PublicKey> for PublicKey {
|
||||
fn from(value: P256PublicKey) -> Self {
|
||||
let key = value.to_encoded_point(false).as_bytes().to_vec();
|
||||
PublicKey { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PublicKey> for P256PublicKey {
|
||||
type Error = KeyParseError;
|
||||
|
||||
fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
|
||||
P256PublicKey::from_sec1_bytes(&value.key).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
[workspace]
|
||||
members = ["hmac-sha256-circuits", "hmac-sha256"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# tlsn
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
|
||||
# async
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = "1"
|
||||
|
||||
# error/log
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
|
||||
# testing
|
||||
criterion = "0.5"
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-hmac-sha256-circuits"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "The 2PC circuits for TLS HMAC-SHA256 PRF"
|
||||
keywords = ["tls", "mpc", "2pc", "hmac", "sha256"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.6"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "hmac_sha256_circuits"
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ring = "0.17"
|
||||
@@ -1,157 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
circuits::{sha256, sha256_compress, sha256_compress_trace, sha256_trace},
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
static SHA256_INITIAL_STATE: [u32; 8] = [
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
];
|
||||
|
||||
/// Returns the outer and inner states of HMAC-SHA256 with the provided key.
|
||||
///
|
||||
/// Outer state is H(key ⊕ opad)
|
||||
///
|
||||
/// Inner state is H(key ⊕ ipad)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - Reference to builder state.
|
||||
/// * `key` - N-byte key (must be <= 64 bytes).
|
||||
pub fn hmac_sha256_partial_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
key: &[Tracer<'a, U8>],
|
||||
) -> ([Tracer<'a, U32>; 8], [Tracer<'a, U32>; 8]) {
|
||||
assert!(key.len() <= 64);
|
||||
|
||||
let mut opad = [Tracer::new(
|
||||
builder_state,
|
||||
builder_state.borrow_mut().get_constant(0x5cu8),
|
||||
); 64];
|
||||
|
||||
let mut ipad = [Tracer::new(
|
||||
builder_state,
|
||||
builder_state.borrow_mut().get_constant(0x36u8),
|
||||
); 64];
|
||||
|
||||
key.iter().enumerate().for_each(|(i, k)| {
|
||||
opad[i] = opad[i] ^ *k;
|
||||
ipad[i] = ipad[i] ^ *k;
|
||||
});
|
||||
|
||||
let sha256_initial_state: [_; 8] = SHA256_INITIAL_STATE
|
||||
.map(|v| Tracer::new(builder_state, builder_state.borrow_mut().get_constant(v)));
|
||||
|
||||
let outer_state = sha256_compress_trace(builder_state, sha256_initial_state, opad);
|
||||
let inner_state = sha256_compress_trace(builder_state, sha256_initial_state, ipad);
|
||||
|
||||
(outer_state, inner_state)
|
||||
}
|
||||
|
||||
/// Reference implementation of HMAC-SHA256 partial function.
|
||||
///
|
||||
/// Returns the outer and inner states of HMAC-SHA256 with the provided key.
|
||||
///
|
||||
/// Outer state is H(key ⊕ opad)
|
||||
///
|
||||
/// Inner state is H(key ⊕ ipad)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - N-byte key (must be <= 64 bytes).
|
||||
pub fn hmac_sha256_partial(key: &[u8]) -> ([u32; 8], [u32; 8]) {
|
||||
assert!(key.len() <= 64);
|
||||
|
||||
let mut opad = [0x5cu8; 64];
|
||||
let mut ipad = [0x36u8; 64];
|
||||
|
||||
key.iter().enumerate().for_each(|(i, k)| {
|
||||
opad[i] ^= k;
|
||||
ipad[i] ^= k;
|
||||
});
|
||||
|
||||
let outer_state = sha256_compress(SHA256_INITIAL_STATE, opad);
|
||||
let inner_state = sha256_compress(SHA256_INITIAL_STATE, ipad);
|
||||
|
||||
(outer_state, inner_state)
|
||||
}
|
||||
|
||||
/// HMAC-SHA256 finalization function.
|
||||
///
|
||||
/// Returns the HMAC-SHA256 digest of the provided message using existing outer and inner states.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - 256-bit outer state.
|
||||
/// * `inner_state` - 256-bit inner state.
|
||||
/// * `msg` - N-byte message.
|
||||
pub fn hmac_sha256_finalize_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
msg: &[Tracer<'a, U8>],
|
||||
) -> [Tracer<'a, U8>; 32] {
|
||||
sha256_trace(
|
||||
builder_state,
|
||||
outer_state,
|
||||
64,
|
||||
&sha256_trace(builder_state, inner_state, 64, msg),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reference implementation of the HMAC-SHA256 finalization function.
|
||||
///
|
||||
/// Returns the HMAC-SHA256 digest of the provided message using existing outer and inner states.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - 256-bit outer state.
|
||||
/// * `inner_state` - 256-bit inner state.
|
||||
/// * `msg` - N-byte message.
|
||||
pub fn hmac_sha256_finalize(outer_state: [u32; 8], inner_state: [u32; 8], msg: &[u8]) -> [u8; 32] {
|
||||
sha256(outer_state, 64, &sha256(inner_state, 64, msg))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::{test_circ, CircuitBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha256_partial() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let key = builder.add_array_input::<u8, 48>();
|
||||
let (outer_state, inner_state) = hmac_sha256_partial_trace(builder.state(), &key);
|
||||
builder.add_output(outer_state);
|
||||
builder.add_output(inner_state);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let key = [69u8; 48];
|
||||
|
||||
test_circ!(circ, hmac_sha256_partial, fn(&key) -> ([u32; 8], [u32; 8]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha256_finalize() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let msg = builder.add_array_input::<u8, 47>();
|
||||
let hash = hmac_sha256_finalize_trace(builder.state(), outer_state, inner_state, &msg);
|
||||
builder.add_output(hash);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let key = [69u8; 32];
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&key);
|
||||
let msg = [42u8; 47];
|
||||
|
||||
test_circ!(
|
||||
circ,
|
||||
hmac_sha256_finalize,
|
||||
fn(outer_state, inner_state, &msg) -> [u8; 32]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//! HMAC-SHA256 circuits.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod hmac_sha256;
|
||||
mod prf;
|
||||
mod session_keys;
|
||||
mod verify_data;
|
||||
|
||||
pub use hmac_sha256::{
|
||||
hmac_sha256_finalize, hmac_sha256_finalize_trace, hmac_sha256_partial,
|
||||
hmac_sha256_partial_trace,
|
||||
};
|
||||
|
||||
pub use prf::{prf, prf_trace};
|
||||
pub use session_keys::{session_keys, session_keys_trace};
|
||||
pub use verify_data::{verify_data, verify_data_trace};
|
||||
|
||||
use mpz_circuits::{Circuit, CircuitBuilder, Tracer};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Builds session key derivation circuit.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub fn build_session_keys() -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let pms = builder.add_array_input::<u8, 32>();
|
||||
let client_random = builder.add_array_input::<u8, 32>();
|
||||
let server_random = builder.add_array_input::<u8, 32>();
|
||||
let (cwk, swk, civ, siv, outer_state, inner_state) =
|
||||
session_keys_trace(builder.state(), pms, client_random, server_random);
|
||||
builder.add_output(cwk);
|
||||
builder.add_output(swk);
|
||||
builder.add_output(civ);
|
||||
builder.add_output(siv);
|
||||
builder.add_output(outer_state);
|
||||
builder.add_output(inner_state);
|
||||
Arc::new(builder.build().expect("session keys should build"))
|
||||
}
|
||||
|
||||
/// Builds a verify data circuit.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub fn build_verify_data(label: &[u8]) -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let handshake_hash = builder.add_array_input::<u8, 32>();
|
||||
let vd = verify_data_trace(
|
||||
builder.state(),
|
||||
outer_state,
|
||||
inner_state,
|
||||
&label
|
||||
.iter()
|
||||
.map(|v| Tracer::new(builder.state(), builder.get_constant(*v).to_inner()))
|
||||
.collect::<Vec<_>>(),
|
||||
handshake_hash,
|
||||
);
|
||||
builder.add_output(vd);
|
||||
Arc::new(builder.build().expect("verify data should build"))
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
//! This module provides an implementation of the HMAC-SHA256 PRF defined in [RFC 5246](https://www.rfc-editor.org/rfc/rfc5246#section-5).
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
use crate::hmac_sha256::{hmac_sha256_finalize, hmac_sha256_finalize_trace};
|
||||
|
||||
fn p_hash_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
seed: &[Tracer<'a, U8>],
|
||||
iterations: usize,
|
||||
) -> Vec<Tracer<'a, U8>> {
|
||||
// A() is defined as:
|
||||
//
|
||||
// A(0) = seed
|
||||
// A(i) = HMAC_hash(secret, A(i-1))
|
||||
let mut a_cache: Vec<_> = Vec::with_capacity(iterations + 1);
|
||||
a_cache.push(seed.to_vec());
|
||||
|
||||
for i in 0..iterations {
|
||||
let a_i = hmac_sha256_finalize_trace(builder_state, outer_state, inner_state, &a_cache[i]);
|
||||
a_cache.push(a_i.to_vec());
|
||||
}
|
||||
|
||||
// HMAC_hash(secret, A(i) + seed)
|
||||
let mut output: Vec<_> = Vec::with_capacity(iterations * 32);
|
||||
for i in 0..iterations {
|
||||
let mut a_i_seed = a_cache[i + 1].clone();
|
||||
a_i_seed.extend_from_slice(seed);
|
||||
|
||||
let hash = hmac_sha256_finalize_trace(builder_state, outer_state, inner_state, &a_i_seed);
|
||||
output.extend_from_slice(&hash);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn p_hash(outer_state: [u32; 8], inner_state: [u32; 8], seed: &[u8], iterations: usize) -> Vec<u8> {
|
||||
// A() is defined as:
|
||||
//
|
||||
// A(0) = seed
|
||||
// A(i) = HMAC_hash(secret, A(i-1))
|
||||
let mut a_cache: Vec<_> = Vec::with_capacity(iterations + 1);
|
||||
a_cache.push(seed.to_vec());
|
||||
|
||||
for i in 0..iterations {
|
||||
let a_i = hmac_sha256_finalize(outer_state, inner_state, &a_cache[i]);
|
||||
a_cache.push(a_i.to_vec());
|
||||
}
|
||||
|
||||
// HMAC_hash(secret, A(i) + seed)
|
||||
let mut output: Vec<_> = Vec::with_capacity(iterations * 32);
|
||||
for i in 0..iterations {
|
||||
let mut a_i_seed = a_cache[i + 1].clone();
|
||||
a_i_seed.extend_from_slice(seed);
|
||||
|
||||
let hash = hmac_sha256_finalize(outer_state, inner_state, &a_i_seed);
|
||||
output.extend_from_slice(&hash);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Computes PRF(secret, label, seed).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - Reference to builder state.
|
||||
/// * `outer_state` - The outer state of HMAC-SHA256.
|
||||
/// * `inner_state` - The inner state of HMAC-SHA256.
|
||||
/// * `seed` - The seed to use.
|
||||
/// * `label` - The label to use.
|
||||
/// * `bytes` - The number of bytes to output.
|
||||
pub fn prf_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
seed: &[Tracer<'a, U8>],
|
||||
label: &[Tracer<'a, U8>],
|
||||
bytes: usize,
|
||||
) -> Vec<Tracer<'a, U8>> {
|
||||
let iterations = bytes / 32 + (bytes % 32 != 0) as usize;
|
||||
let mut label_seed = label.to_vec();
|
||||
label_seed.extend_from_slice(seed);
|
||||
|
||||
let mut output = p_hash_trace(
|
||||
builder_state,
|
||||
outer_state,
|
||||
inner_state,
|
||||
&label_seed,
|
||||
iterations,
|
||||
);
|
||||
output.truncate(bytes);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Reference implementation of PRF(secret, label, seed).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - The outer state of HMAC-SHA256.
|
||||
/// * `inner_state` - The inner state of HMAC-SHA256.
|
||||
/// * `seed` - The seed to use.
|
||||
/// * `label` - The label to use.
|
||||
/// * `bytes` - The number of bytes to output.
|
||||
pub fn prf(
|
||||
outer_state: [u32; 8],
|
||||
inner_state: [u32; 8],
|
||||
seed: &[u8],
|
||||
label: &[u8],
|
||||
bytes: usize,
|
||||
) -> Vec<u8> {
|
||||
let iterations = bytes / 32 + (bytes % 32 != 0) as usize;
|
||||
let mut label_seed = label.to_vec();
|
||||
label_seed.extend_from_slice(seed);
|
||||
|
||||
let mut output = p_hash(outer_state, inner_state, &label_seed, iterations);
|
||||
output.truncate(bytes);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::{evaluate, CircuitBuilder};
|
||||
|
||||
use crate::hmac_sha256::hmac_sha256_partial;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_p_hash() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let seed = builder.add_array_input::<u8, 64>();
|
||||
let output = p_hash_trace(builder.state(), outer_state, inner_state, &seed, 2);
|
||||
builder.add_output(output);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let outer_state = [0u32; 8];
|
||||
let inner_state = [1u32; 8];
|
||||
let seed = [42u8; 64];
|
||||
|
||||
let expected = p_hash(outer_state, inner_state, &seed, 2);
|
||||
let actual = evaluate!(circ, fn(outer_state, inner_state, &seed) -> Vec<u8>).unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prf() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let seed = builder.add_array_input::<u8, 64>();
|
||||
let label = builder.add_array_input::<u8, 13>();
|
||||
let output = prf_trace(builder.state(), outer_state, inner_state, &seed, &label, 48);
|
||||
builder.add_output(output);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let master_secret = [0u8; 48];
|
||||
let seed = [43u8; 64];
|
||||
let label = b"master secret";
|
||||
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&master_secret);
|
||||
|
||||
let expected = prf(outer_state, inner_state, &seed, label, 48);
|
||||
let actual =
|
||||
evaluate!(circ, fn(outer_state, inner_state, &seed, label) -> Vec<u8>).unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let mut expected_ring = [0u8; 48];
|
||||
ring_prf::prf(&mut expected_ring, &master_secret, label, &seed);
|
||||
|
||||
assert_eq!(actual, expected_ring);
|
||||
}
|
||||
|
||||
// Borrowed from Rustls for testing
|
||||
// https://github.com/rustls/rustls/blob/main/rustls/src/tls12/prf.rs
|
||||
mod ring_prf {
|
||||
use ring::{hmac, hmac::HMAC_SHA256};
|
||||
|
||||
fn concat_sign(key: &hmac::Key, a: &[u8], b: &[u8]) -> hmac::Tag {
|
||||
let mut ctx = hmac::Context::with_key(key);
|
||||
ctx.update(a);
|
||||
ctx.update(b);
|
||||
ctx.sign()
|
||||
}
|
||||
|
||||
fn p(out: &mut [u8], secret: &[u8], seed: &[u8]) {
|
||||
let hmac_key = hmac::Key::new(HMAC_SHA256, secret);
|
||||
|
||||
// A(1)
|
||||
let mut current_a = hmac::sign(&hmac_key, seed);
|
||||
let chunk_size = HMAC_SHA256.digest_algorithm().output_len();
|
||||
for chunk in out.chunks_mut(chunk_size) {
|
||||
// P_hash[i] = HMAC_hash(secret, A(i) + seed)
|
||||
let p_term = concat_sign(&hmac_key, current_a.as_ref(), seed);
|
||||
chunk.copy_from_slice(&p_term.as_ref()[..chunk.len()]);
|
||||
|
||||
// A(i+1) = HMAC_hash(secret, A(i))
|
||||
current_a = hmac::sign(&hmac_key, current_a.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn concat(a: &[u8], b: &[u8]) -> Vec<u8> {
|
||||
let mut ret = Vec::new();
|
||||
ret.extend_from_slice(a);
|
||||
ret.extend_from_slice(b);
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn prf(out: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]) {
|
||||
let joined_seed = concat(label, seed);
|
||||
p(out, secret, &joined_seed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
hmac_sha256::{hmac_sha256_partial, hmac_sha256_partial_trace},
|
||||
prf::{prf, prf_trace},
|
||||
};
|
||||
|
||||
/// Session Keys.
|
||||
///
|
||||
/// Computes expanded p1 which consists of client_write_key + server_write_key.
|
||||
/// Computes expanded p2 which consists of client_IV + server_IV.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - Reference to builder state.
|
||||
/// * `pms` - 32-byte premaster secret.
|
||||
/// * `client_random` - 32-byte client random.
|
||||
/// * `server_random` - 32-byte server random.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `client_write_key` - 16-byte client write key.
|
||||
/// * `server_write_key` - 16-byte server write key.
|
||||
/// * `client_IV` - 4-byte client IV.
|
||||
/// * `server_IV` - 4-byte server IV.
|
||||
/// * `outer_hash_state` - 256-bit master-secret outer HMAC state.
|
||||
/// * `inner_hash_state` - 256-bit master-secret inner HMAC state.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn session_keys_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
pms: [Tracer<'a, U8>; 32],
|
||||
client_random: [Tracer<'a, U8>; 32],
|
||||
server_random: [Tracer<'a, U8>; 32],
|
||||
) -> (
|
||||
[Tracer<'a, U8>; 16],
|
||||
[Tracer<'a, U8>; 16],
|
||||
[Tracer<'a, U8>; 4],
|
||||
[Tracer<'a, U8>; 4],
|
||||
[Tracer<'a, U32>; 8],
|
||||
[Tracer<'a, U32>; 8],
|
||||
) {
|
||||
let (pms_outer_state, pms_inner_state) = hmac_sha256_partial_trace(builder_state, &pms);
|
||||
|
||||
let master_secret = {
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"master secret"
|
||||
.map(|v| Tracer::new(builder_state, builder_state.borrow_mut().get_constant(v)));
|
||||
|
||||
prf_trace(
|
||||
builder_state,
|
||||
pms_outer_state,
|
||||
pms_inner_state,
|
||||
&seed,
|
||||
&label,
|
||||
48,
|
||||
)
|
||||
};
|
||||
|
||||
let (master_secret_outer_state, master_secret_inner_state) =
|
||||
hmac_sha256_partial_trace(builder_state, &master_secret);
|
||||
|
||||
let key_material = {
|
||||
let seed = server_random
|
||||
.iter()
|
||||
.chain(&client_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"key expansion"
|
||||
.map(|v| Tracer::new(builder_state, builder_state.borrow_mut().get_constant(v)));
|
||||
|
||||
prf_trace(
|
||||
builder_state,
|
||||
master_secret_outer_state,
|
||||
master_secret_inner_state,
|
||||
&seed,
|
||||
&label,
|
||||
40,
|
||||
)
|
||||
};
|
||||
|
||||
let cwk = key_material[0..16].try_into().unwrap();
|
||||
let swk = key_material[16..32].try_into().unwrap();
|
||||
let civ = key_material[32..36].try_into().unwrap();
|
||||
let siv = key_material[36..40].try_into().unwrap();
|
||||
|
||||
(
|
||||
cwk,
|
||||
swk,
|
||||
civ,
|
||||
siv,
|
||||
master_secret_outer_state,
|
||||
master_secret_inner_state,
|
||||
)
|
||||
}
|
||||
|
||||
/// Reference implementation of session keys derivation.
|
||||
pub fn session_keys(
|
||||
pms: [u8; 32],
|
||||
client_random: [u8; 32],
|
||||
server_random: [u8; 32],
|
||||
) -> ([u8; 16], [u8; 16], [u8; 4], [u8; 4]) {
|
||||
let (pms_outer_state, pms_inner_state) = hmac_sha256_partial(&pms);
|
||||
|
||||
let master_secret = {
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"master secret";
|
||||
|
||||
prf(pms_outer_state, pms_inner_state, &seed, label, 48)
|
||||
};
|
||||
|
||||
let (master_secret_outer_state, master_secret_inner_state) =
|
||||
hmac_sha256_partial(&master_secret);
|
||||
|
||||
let key_material = {
|
||||
let seed = server_random
|
||||
.iter()
|
||||
.chain(&client_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"key expansion";
|
||||
|
||||
prf(
|
||||
master_secret_outer_state,
|
||||
master_secret_inner_state,
|
||||
&seed,
|
||||
label,
|
||||
40,
|
||||
)
|
||||
};
|
||||
|
||||
let cwk = key_material[0..16].try_into().unwrap();
|
||||
let swk = key_material[16..32].try_into().unwrap();
|
||||
let civ = key_material[32..36].try_into().unwrap();
|
||||
let siv = key_material[36..40].try_into().unwrap();
|
||||
|
||||
(cwk, swk, civ, siv)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::{evaluate, CircuitBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_session_keys() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let pms = builder.add_array_input::<u8, 32>();
|
||||
let client_random = builder.add_array_input::<u8, 32>();
|
||||
let server_random = builder.add_array_input::<u8, 32>();
|
||||
let (cwk, swk, civ, siv, outer_state, inner_state) =
|
||||
session_keys_trace(builder.state(), pms, client_random, server_random);
|
||||
builder.add_output(cwk);
|
||||
builder.add_output(swk);
|
||||
builder.add_output(civ);
|
||||
builder.add_output(siv);
|
||||
builder.add_output(outer_state);
|
||||
builder.add_output(inner_state);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let pms = [0u8; 32];
|
||||
let client_random = [42u8; 32];
|
||||
let server_random = [69u8; 32];
|
||||
|
||||
let (expected_cwk, expected_swk, expected_civ, expected_siv) =
|
||||
session_keys(pms, client_random, server_random);
|
||||
|
||||
let (cwk, swk, civ, siv, _, _) = evaluate!(
|
||||
circ,
|
||||
fn(
|
||||
pms,
|
||||
client_random,
|
||||
server_random,
|
||||
) -> ([u8; 16], [u8; 16], [u8; 4], [u8; 4], [u32; 8], [u32; 8])
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cwk, expected_cwk);
|
||||
assert_eq!(swk, expected_swk);
|
||||
assert_eq!(civ, expected_civ);
|
||||
assert_eq!(siv, expected_siv);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
use crate::prf::{prf, prf_trace};
|
||||
|
||||
/// Computes verify_data as specified in RFC 5246, Section 7.4.9.
|
||||
///
|
||||
/// verify_data
|
||||
/// PRF(master_secret, finished_label, Hash(handshake_messages))[0..verify_data_length-1];
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - The builder state.
|
||||
/// * `outer_state` - The outer HMAC state of the master secret.
|
||||
/// * `inner_state` - The inner HMAC state of the master secret.
|
||||
/// * `label` - The label to use.
|
||||
/// * `hs_hash` - The handshake hash.
|
||||
pub fn verify_data_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
label: &[Tracer<'a, U8>],
|
||||
hs_hash: [Tracer<'a, U8>; 32],
|
||||
) -> [Tracer<'a, U8>; 12] {
|
||||
let vd = prf_trace(builder_state, outer_state, inner_state, &hs_hash, label, 12);
|
||||
|
||||
vd.try_into().expect("vd is 12 bytes")
|
||||
}
|
||||
|
||||
/// Reference implementation of verify_data as specified in RFC 5246, Section 7.4.9.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - The outer HMAC state of the master secret.
|
||||
/// * `inner_state` - The inner HMAC state of the master secret.
|
||||
/// * `label` - The label to use.
|
||||
/// * `hs_hash` - The handshake hash.
|
||||
pub fn verify_data(
|
||||
outer_state: [u32; 8],
|
||||
inner_state: [u32; 8],
|
||||
label: &[u8],
|
||||
hs_hash: [u8; 32],
|
||||
) -> [u8; 12] {
|
||||
let vd = prf(outer_state, inner_state, &hs_hash, label, 12);
|
||||
|
||||
vd.try_into().expect("vd is 12 bytes")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mpz_circuits::{evaluate, CircuitBuilder};
|
||||
|
||||
const CF_LABEL: &[u8; 15] = b"client finished";
|
||||
|
||||
#[test]
|
||||
fn test_verify_data() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let label = builder.add_array_input::<u8, 15>();
|
||||
let hs_hash = builder.add_array_input::<u8, 32>();
|
||||
let vd = verify_data_trace(builder.state(), outer_state, inner_state, &label, hs_hash);
|
||||
builder.add_output(vd);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let outer_state = [0u32; 8];
|
||||
let inner_state = [1u32; 8];
|
||||
let hs_hash = [42u8; 32];
|
||||
|
||||
let expected = prf(outer_state, inner_state, &hs_hash, CF_LABEL, 12);
|
||||
|
||||
let actual = evaluate!(
|
||||
circ,
|
||||
fn(outer_state, inner_state, CF_LABEL, hs_hash) -> [u8; 12]
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(actual.to_vec(), expected);
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use hmac_sha256::{MpcPrf, Prf, PrfConfig, Role};
|
||||
use mpz_common::executor::test_mt_executor;
|
||||
use mpz_garble::{config::Role as DEAPRole, protocol::deap::DEAPThread, Memory};
|
||||
use mpz_ot::ideal::ot::ideal_ot;
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("prf");
|
||||
group.sample_size(10);
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
group.bench_function("prf_preprocess", |b| b.to_async(&rt).iter(preprocess));
|
||||
group.bench_function("prf", |b| b.to_async(&rt).iter(prf));
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
|
||||
async fn preprocess() {
|
||||
let (mut leader_exec, mut follower_exec) = test_mt_executor(128);
|
||||
|
||||
let (leader_ot_send_0, follower_ot_recv_0) = ideal_ot();
|
||||
let (follower_ot_send_0, leader_ot_recv_0) = ideal_ot();
|
||||
let (leader_ot_send_1, follower_ot_recv_1) = ideal_ot();
|
||||
let (follower_ot_send_1, leader_ot_recv_1) = ideal_ot();
|
||||
|
||||
let leader_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_0,
|
||||
leader_ot_recv_0,
|
||||
);
|
||||
let leader_thread_1 = leader_thread_0
|
||||
.new_thread(
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_1,
|
||||
leader_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let follower_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Follower,
|
||||
[0u8; 32],
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_0,
|
||||
follower_ot_recv_0,
|
||||
);
|
||||
let follower_thread_1 = follower_thread_0
|
||||
.new_thread(
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_1,
|
||||
follower_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leader_pms = leader_thread_0.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread_0
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_thread_0,
|
||||
leader_thread_1,
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_thread_0,
|
||||
follower_thread_1,
|
||||
);
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader.setup(leader_pms).await.unwrap();
|
||||
leader.set_client_random(Some([0u8; 32])).await.unwrap();
|
||||
leader.preprocess().await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower.setup(follower_pms).await.unwrap();
|
||||
follower.set_client_random(None).await.unwrap();
|
||||
follower.preprocess().await.unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async fn prf() {
|
||||
let (mut leader_exec, mut follower_exec) = test_mt_executor(128);
|
||||
|
||||
let (leader_ot_send_0, follower_ot_recv_0) = ideal_ot();
|
||||
let (follower_ot_send_0, leader_ot_recv_0) = ideal_ot();
|
||||
let (leader_ot_send_1, follower_ot_recv_1) = ideal_ot();
|
||||
let (follower_ot_send_1, leader_ot_recv_1) = ideal_ot();
|
||||
|
||||
let leader_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_0,
|
||||
leader_ot_recv_0,
|
||||
);
|
||||
let leader_thread_1 = leader_thread_0
|
||||
.new_thread(
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_1,
|
||||
leader_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let follower_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Follower,
|
||||
[0u8; 32],
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_0,
|
||||
follower_ot_recv_0,
|
||||
);
|
||||
let follower_thread_1 = follower_thread_0
|
||||
.new_thread(
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_1,
|
||||
follower_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leader_pms = leader_thread_0.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread_0
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_thread_0,
|
||||
leader_thread_1,
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_thread_0,
|
||||
follower_thread_1,
|
||||
);
|
||||
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [0u8; 32];
|
||||
let server_random = [1u8; 32];
|
||||
let cf_hs_hash = [2u8; 32];
|
||||
let sf_hs_hash = [3u8; 32];
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader.setup(leader_pms.clone()).await.unwrap();
|
||||
leader.set_client_random(Some(client_random)).await.unwrap();
|
||||
leader.preprocess().await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower.setup(follower_pms.clone()).await.unwrap();
|
||||
follower.set_client_random(None).await.unwrap();
|
||||
follower.preprocess().await.unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
leader.thread_mut().assign(&leader_pms, pms).unwrap();
|
||||
follower.thread_mut().assign(&follower_pms, pms).unwrap();
|
||||
|
||||
let (_leader_keys, _follower_keys) = futures::try_join!(
|
||||
leader.compute_session_keys(server_random),
|
||||
follower.compute_session_keys(server_random)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = futures::try_join!(
|
||||
leader.compute_client_finished_vd(cf_hs_hash),
|
||||
follower.compute_client_finished_vd(cf_hs_hash)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = futures::try_join!(
|
||||
leader.compute_server_finished_vd(sf_hs_hash),
|
||||
follower.compute_server_finished_vd(sf_hs_hash)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
futures::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Role of this party in the PRF.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Role {
|
||||
/// The leader provides the private inputs to the PRF.
|
||||
Leader,
|
||||
/// The follower is blind to the inputs to the PRF.
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// Configuration for the PRF.
|
||||
#[derive(Debug, Builder)]
|
||||
pub struct PrfConfig {
|
||||
/// The role of this party in the PRF.
|
||||
pub(crate) role: Role,
|
||||
}
|
||||
|
||||
impl PrfConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> PrfConfigBuilder {
|
||||
PrfConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
//! This module contains the protocol for computing TLS SHA-256 HMAC PRF.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod prf;
|
||||
|
||||
pub use config::{PrfConfig, PrfConfigBuilder, PrfConfigBuilderError, Role};
|
||||
pub use error::PrfError;
|
||||
pub use prf::MpcPrf;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
pub(crate) static CF_LABEL: &[u8] = b"client finished";
|
||||
pub(crate) static SF_LABEL: &[u8] = b"server finished";
|
||||
|
||||
/// Session keys computed by the PRF.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionKeys {
|
||||
/// Client write key.
|
||||
pub client_write_key: ValueRef,
|
||||
/// Server write key.
|
||||
pub server_write_key: ValueRef,
|
||||
/// Client IV.
|
||||
pub client_iv: ValueRef,
|
||||
/// Server IV.
|
||||
pub server_iv: ValueRef,
|
||||
}
|
||||
|
||||
/// PRF trait for computing TLS PRF.
|
||||
#[async_trait]
|
||||
pub trait Prf {
|
||||
/// Sets up the PRF.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pms` - The pre-master secret.
|
||||
async fn setup(&mut self, pms: ValueRef) -> Result<SessionKeys, PrfError>;
|
||||
|
||||
/// Sets the client random.
|
||||
///
|
||||
/// This must be set after calling [`Prf::setup`].
|
||||
///
|
||||
/// Only the leader can provide the client random.
|
||||
async fn set_client_random(&mut self, client_random: Option<[u8; 32]>) -> Result<(), PrfError>;
|
||||
|
||||
/// Preprocesses the PRF.
|
||||
async fn preprocess(&mut self) -> Result<(), PrfError>;
|
||||
|
||||
/// Computes the client finished verify data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `handshake_hash` - The handshake transcript hash.
|
||||
async fn compute_client_finished_vd(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError>;
|
||||
|
||||
/// Computes the server finished verify data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `handshake_hash` - The handshake transcript hash.
|
||||
async fn compute_server_finished_vd(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError>;
|
||||
|
||||
/// Computes the session keys.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `server_random` - The server random.
|
||||
async fn compute_session_keys(
|
||||
&mut self,
|
||||
server_random: [u8; 32],
|
||||
) -> Result<SessionKeys, PrfError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_common::executor::test_st_executor;
|
||||
use mpz_garble::{config::Role as DEAPRole, protocol::deap::DEAPThread, Decode, Memory};
|
||||
|
||||
use hmac_sha256_circuits::{hmac_sha256_partial, prf, session_keys};
|
||||
use mpz_ot::ideal::ot::ideal_ot;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn compute_ms(pms: [u8; 32], client_random: [u8; 32], server_random: [u8; 32]) -> [u8; 48] {
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&pms);
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
let ms = prf(outer_state, inner_state, &seed, b"master secret", 48);
|
||||
ms.try_into().unwrap()
|
||||
}
|
||||
|
||||
fn compute_vd(ms: [u8; 48], label: &[u8], hs_hash: [u8; 32]) -> [u8; 12] {
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&ms);
|
||||
let vd = prf(outer_state, inner_state, &hs_hash, label, 12);
|
||||
vd.try_into().unwrap()
|
||||
}
|
||||
|
||||
#[ignore = "expensive"]
|
||||
#[tokio::test]
|
||||
async fn test_prf() {
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [69u8; 32];
|
||||
let server_random: [u8; 32] = [96u8; 32];
|
||||
let ms = compute_ms(pms, client_random, server_random);
|
||||
|
||||
let (leader_ctx_0, follower_ctx_0) = test_st_executor(128);
|
||||
let (leader_ctx_1, follower_ctx_1) = test_st_executor(128);
|
||||
|
||||
let (leader_ot_send_0, follower_ot_recv_0) = ideal_ot();
|
||||
let (follower_ot_send_0, leader_ot_recv_0) = ideal_ot();
|
||||
let (leader_ot_send_1, follower_ot_recv_1) = ideal_ot();
|
||||
let (follower_ot_send_1, leader_ot_recv_1) = ideal_ot();
|
||||
|
||||
let leader_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_ctx_0,
|
||||
leader_ot_send_0,
|
||||
leader_ot_recv_0,
|
||||
);
|
||||
let leader_thread_1 = leader_thread_0
|
||||
.new_thread(leader_ctx_1, leader_ot_send_1, leader_ot_recv_1)
|
||||
.unwrap();
|
||||
|
||||
let follower_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Follower,
|
||||
[0u8; 32],
|
||||
follower_ctx_0,
|
||||
follower_ot_send_0,
|
||||
follower_ot_recv_0,
|
||||
);
|
||||
let follower_thread_1 = follower_thread_0
|
||||
.new_thread(follower_ctx_1, follower_ot_send_1, follower_ot_recv_1)
|
||||
.unwrap();
|
||||
|
||||
// Set up public PMS for testing.
|
||||
let leader_pms = leader_thread_0.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread_0
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
leader_thread_0.assign(&leader_pms, pms).unwrap();
|
||||
follower_thread_0.assign(&follower_pms, pms).unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_thread_0,
|
||||
leader_thread_1,
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_thread_0,
|
||||
follower_thread_1,
|
||||
);
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader.setup(leader_pms).await.unwrap();
|
||||
leader.set_client_random(Some(client_random)).await.unwrap();
|
||||
leader.preprocess().await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower.setup(follower_pms).await.unwrap();
|
||||
follower.set_client_random(None).await.unwrap();
|
||||
follower.preprocess().await.unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
let (leader_session_keys, follower_session_keys) = futures::try_join!(
|
||||
leader.compute_session_keys(server_random),
|
||||
follower.compute_session_keys(server_random)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: leader_cwk,
|
||||
server_write_key: leader_swk,
|
||||
client_iv: leader_civ,
|
||||
server_iv: leader_siv,
|
||||
} = leader_session_keys;
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: follower_cwk,
|
||||
server_write_key: follower_swk,
|
||||
client_iv: follower_civ,
|
||||
server_iv: follower_siv,
|
||||
} = follower_session_keys;
|
||||
|
||||
// Decode session keys
|
||||
let (leader_session_keys, follower_session_keys) = futures::try_join!(
|
||||
async {
|
||||
leader
|
||||
.thread_mut()
|
||||
.decode(&[leader_cwk, leader_swk, leader_civ, leader_siv])
|
||||
.await
|
||||
},
|
||||
async {
|
||||
follower
|
||||
.thread_mut()
|
||||
.decode(&[follower_cwk, follower_swk, follower_civ, follower_siv])
|
||||
.await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leader_cwk: [u8; 16] = leader_session_keys[0].clone().try_into().unwrap();
|
||||
let leader_swk: [u8; 16] = leader_session_keys[1].clone().try_into().unwrap();
|
||||
let leader_civ: [u8; 4] = leader_session_keys[2].clone().try_into().unwrap();
|
||||
let leader_siv: [u8; 4] = leader_session_keys[3].clone().try_into().unwrap();
|
||||
|
||||
let follower_cwk: [u8; 16] = follower_session_keys[0].clone().try_into().unwrap();
|
||||
let follower_swk: [u8; 16] = follower_session_keys[1].clone().try_into().unwrap();
|
||||
let follower_civ: [u8; 4] = follower_session_keys[2].clone().try_into().unwrap();
|
||||
let follower_siv: [u8; 4] = follower_session_keys[3].clone().try_into().unwrap();
|
||||
|
||||
let (expected_cwk, expected_swk, expected_civ, expected_siv) =
|
||||
session_keys(pms, client_random, server_random);
|
||||
|
||||
assert_eq!(leader_cwk, expected_cwk);
|
||||
assert_eq!(leader_swk, expected_swk);
|
||||
assert_eq!(leader_civ, expected_civ);
|
||||
assert_eq!(leader_siv, expected_siv);
|
||||
|
||||
assert_eq!(follower_cwk, expected_cwk);
|
||||
assert_eq!(follower_swk, expected_swk);
|
||||
assert_eq!(follower_civ, expected_civ);
|
||||
assert_eq!(follower_siv, expected_siv);
|
||||
|
||||
let cf_hs_hash = [1u8; 32];
|
||||
let sf_hs_hash = [2u8; 32];
|
||||
|
||||
let (cf_vd, _) = futures::try_join!(
|
||||
leader.compute_client_finished_vd(cf_hs_hash),
|
||||
follower.compute_client_finished_vd(cf_hs_hash)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_cf_vd = compute_vd(ms, b"client finished", cf_hs_hash);
|
||||
|
||||
assert_eq!(cf_vd, expected_cf_vd);
|
||||
|
||||
let (sf_vd, _) = futures::try_join!(
|
||||
leader.compute_server_finished_vd(sf_hs_hash),
|
||||
follower.compute_server_finished_vd(sf_hs_hash)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_sf_vd = compute_vd(ms, b"server finished", sf_hs_hash);
|
||||
|
||||
assert_eq!(sf_vd, expected_sf_vd);
|
||||
}
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use hmac_sha256_circuits::{build_session_keys, build_verify_data};
|
||||
use mpz_circuits::Circuit;
|
||||
use mpz_common::cpu::CpuBackend;
|
||||
use mpz_garble::{config::Visibility, value::ValueRef, Decode, Execute, Load, Memory};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{Prf, PrfConfig, PrfError, Role, SessionKeys, CF_LABEL, SF_LABEL};
|
||||
|
||||
/// Circuit for computing TLS session keys.
|
||||
static SESSION_KEYS_CIRC: OnceLock<Arc<Circuit>> = OnceLock::new();
|
||||
/// Circuit for computing TLS client verify data.
|
||||
static CLIENT_VD_CIRC: OnceLock<Arc<Circuit>> = OnceLock::new();
|
||||
/// Circuit for computing TLS server verify data.
|
||||
static SERVER_VD_CIRC: OnceLock<Arc<Circuit>> = OnceLock::new();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Randoms {
|
||||
pub(crate) client_random: ValueRef,
|
||||
pub(crate) server_random: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct HashState {
|
||||
pub(crate) ms_outer_hash_state: ValueRef,
|
||||
pub(crate) ms_inner_hash_state: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct VerifyData {
|
||||
pub(crate) handshake_hash: ValueRef,
|
||||
pub(crate) vd: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum State {
|
||||
Initialized,
|
||||
SessionKeys {
|
||||
pms: ValueRef,
|
||||
randoms: Randoms,
|
||||
hash_state: HashState,
|
||||
keys: crate::SessionKeys,
|
||||
cf_vd: VerifyData,
|
||||
sf_vd: VerifyData,
|
||||
},
|
||||
ClientFinished {
|
||||
hash_state: HashState,
|
||||
cf_vd: VerifyData,
|
||||
sf_vd: VerifyData,
|
||||
},
|
||||
ServerFinished {
|
||||
hash_state: HashState,
|
||||
sf_vd: VerifyData,
|
||||
},
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn take(&mut self) -> State {
|
||||
std::mem::replace(self, State::Error)
|
||||
}
|
||||
}
|
||||
|
||||
/// MPC PRF for computing TLS HMAC-SHA256 PRF.
|
||||
pub struct MpcPrf<E> {
|
||||
config: PrfConfig,
|
||||
state: State,
|
||||
thread_0: E,
|
||||
thread_1: E,
|
||||
}
|
||||
|
||||
impl<E> Debug for MpcPrf<E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MpcPrf")
|
||||
.field("config", &self.config)
|
||||
.field("state", &self.state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> MpcPrf<E>
|
||||
where
|
||||
E: Load + Memory + Execute + Decode + Send,
|
||||
{
|
||||
/// Creates a new instance of the PRF.
|
||||
pub fn new(config: PrfConfig, thread_0: E, thread_1: E) -> MpcPrf<E> {
|
||||
MpcPrf {
|
||||
config,
|
||||
state: State::Initialized,
|
||||
thread_0,
|
||||
thread_1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the MPC thread.
|
||||
pub fn thread_mut(&mut self) -> &mut E {
|
||||
&mut self.thread_0
|
||||
}
|
||||
|
||||
/// Executes a circuit which computes TLS session keys.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn execute_session_keys(
|
||||
&mut self,
|
||||
server_random: [u8; 32],
|
||||
) -> Result<SessionKeys, PrfError> {
|
||||
let State::SessionKeys {
|
||||
pms,
|
||||
randoms: randoms_refs,
|
||||
hash_state,
|
||||
keys,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
} = self.state.take()
|
||||
else {
|
||||
return Err(PrfError::state("session keys not initialized"));
|
||||
};
|
||||
|
||||
let circ = SESSION_KEYS_CIRC
|
||||
.get()
|
||||
.expect("session keys circuit is set");
|
||||
|
||||
self.thread_0
|
||||
.assign(&randoms_refs.server_random, server_random)?;
|
||||
|
||||
self.thread_0
|
||||
.execute(
|
||||
circ.clone(),
|
||||
&[pms, randoms_refs.client_random, randoms_refs.server_random],
|
||||
&[
|
||||
keys.client_write_key.clone(),
|
||||
keys.server_write_key.clone(),
|
||||
keys.client_iv.clone(),
|
||||
keys.server_iv.clone(),
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.state = State::ClientFinished {
|
||||
hash_state,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
};
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn execute_cf_vd(&mut self, handshake_hash: [u8; 32]) -> Result<[u8; 12], PrfError> {
|
||||
let State::ClientFinished {
|
||||
hash_state,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
} = self.state.take()
|
||||
else {
|
||||
return Err(PrfError::state("PRF not in client finished state"));
|
||||
};
|
||||
|
||||
let circ = CLIENT_VD_CIRC.get().expect("client vd circuit is set");
|
||||
|
||||
self.thread_0
|
||||
.assign(&cf_vd.handshake_hash, handshake_hash)?;
|
||||
|
||||
self.thread_0
|
||||
.execute(
|
||||
circ.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
cf_vd.handshake_hash,
|
||||
],
|
||||
&[cf_vd.vd.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.thread_0.decode(&[cf_vd.vd]).await?;
|
||||
let vd: [u8; 12] = outputs.remove(0).try_into().expect("vd is 12 bytes");
|
||||
|
||||
self.state = State::ServerFinished { hash_state, sf_vd };
|
||||
|
||||
Ok(vd)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn execute_sf_vd(&mut self, handshake_hash: [u8; 32]) -> Result<[u8; 12], PrfError> {
|
||||
let State::ServerFinished { hash_state, sf_vd } = self.state.take() else {
|
||||
return Err(PrfError::state("PRF not in server finished state"));
|
||||
};
|
||||
|
||||
let circ = SERVER_VD_CIRC.get().expect("server vd circuit is set");
|
||||
|
||||
self.thread_0
|
||||
.assign(&sf_vd.handshake_hash, handshake_hash)?;
|
||||
|
||||
self.thread_0
|
||||
.execute(
|
||||
circ.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state,
|
||||
hash_state.ms_inner_hash_state,
|
||||
sf_vd.handshake_hash,
|
||||
],
|
||||
&[sf_vd.vd.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.thread_0.decode(&[sf_vd.vd]).await?;
|
||||
let vd: [u8; 12] = outputs.remove(0).try_into().expect("vd is 12 bytes");
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(vd)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E> Prf for MpcPrf<E>
|
||||
where
|
||||
E: Memory + Load + Execute + Decode + Send,
|
||||
{
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn setup(&mut self, pms: ValueRef) -> Result<SessionKeys, PrfError> {
|
||||
let State::Initialized = self.state.take() else {
|
||||
return Err(PrfError::state("PRF not in initialized state"));
|
||||
};
|
||||
|
||||
let thread = &mut self.thread_0;
|
||||
|
||||
let randoms = Randoms {
|
||||
// The client random is kept private so that the handshake transcript
|
||||
// hashes do not leak information about the server's identity.
|
||||
client_random: thread.new_input::<[u8; 32]>(
|
||||
"client_random",
|
||||
match self.config.role {
|
||||
Role::Leader => Visibility::Private,
|
||||
Role::Follower => Visibility::Blind,
|
||||
},
|
||||
)?,
|
||||
server_random: thread.new_input::<[u8; 32]>("server_random", Visibility::Public)?,
|
||||
};
|
||||
|
||||
let keys = SessionKeys {
|
||||
client_write_key: thread.new_output::<[u8; 16]>("client_write_key")?,
|
||||
server_write_key: thread.new_output::<[u8; 16]>("server_write_key")?,
|
||||
client_iv: thread.new_output::<[u8; 4]>("client_write_iv")?,
|
||||
server_iv: thread.new_output::<[u8; 4]>("server_write_iv")?,
|
||||
};
|
||||
|
||||
let hash_state = HashState {
|
||||
ms_outer_hash_state: thread.new_output::<[u32; 8]>("ms_outer_hash_state")?,
|
||||
ms_inner_hash_state: thread.new_output::<[u32; 8]>("ms_inner_hash_state")?,
|
||||
};
|
||||
|
||||
let cf_vd = VerifyData {
|
||||
handshake_hash: thread.new_input::<[u8; 32]>("cf_hash", Visibility::Public)?,
|
||||
vd: thread.new_output::<[u8; 12]>("cf_vd")?,
|
||||
};
|
||||
|
||||
let sf_vd = VerifyData {
|
||||
handshake_hash: thread.new_input::<[u8; 32]>("sf_hash", Visibility::Public)?,
|
||||
vd: thread.new_output::<[u8; 12]>("sf_vd")?,
|
||||
};
|
||||
|
||||
self.state = State::SessionKeys {
|
||||
pms,
|
||||
randoms,
|
||||
hash_state,
|
||||
keys: keys.clone(),
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
};
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn set_client_random(&mut self, client_random: Option<[u8; 32]>) -> Result<(), PrfError> {
|
||||
let State::SessionKeys { randoms, .. } = &self.state else {
|
||||
return Err(PrfError::state("PRF not set up"));
|
||||
};
|
||||
|
||||
if self.config.role == Role::Leader {
|
||||
let Some(client_random) = client_random else {
|
||||
return Err(PrfError::role("leader must provide client random"));
|
||||
};
|
||||
|
||||
self.thread_0
|
||||
.assign(&randoms.client_random, client_random)?;
|
||||
} else if client_random.is_some() {
|
||||
return Err(PrfError::role("only leader can set client random"));
|
||||
}
|
||||
|
||||
self.thread_0
|
||||
.commit(&[randoms.client_random.clone()])
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn preprocess(&mut self) -> Result<(), PrfError> {
|
||||
let State::SessionKeys {
|
||||
pms,
|
||||
randoms,
|
||||
hash_state,
|
||||
keys,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
} = self.state.take()
|
||||
else {
|
||||
return Err(PrfError::state("PRF not set up"));
|
||||
};
|
||||
|
||||
// Builds all circuits in parallel and preprocesses the session keys circuit.
|
||||
futures::try_join!(
|
||||
async {
|
||||
if SESSION_KEYS_CIRC.get().is_none() {
|
||||
_ = SESSION_KEYS_CIRC.set(CpuBackend::blocking(build_session_keys).await);
|
||||
}
|
||||
|
||||
let circ = SESSION_KEYS_CIRC
|
||||
.get()
|
||||
.expect("session keys circuit should be built");
|
||||
|
||||
self.thread_0
|
||||
.load(
|
||||
circ.clone(),
|
||||
&[
|
||||
pms.clone(),
|
||||
randoms.client_random.clone(),
|
||||
randoms.server_random.clone(),
|
||||
],
|
||||
&[
|
||||
keys.client_write_key.clone(),
|
||||
keys.server_write_key.clone(),
|
||||
keys.client_iv.clone(),
|
||||
keys.server_iv.clone(),
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok::<_, PrfError>(())
|
||||
},
|
||||
async {
|
||||
if CLIENT_VD_CIRC.get().is_none() {
|
||||
_ = CLIENT_VD_CIRC
|
||||
.set(CpuBackend::blocking(move || build_verify_data(CF_LABEL)).await);
|
||||
}
|
||||
|
||||
Ok::<_, PrfError>(())
|
||||
},
|
||||
async {
|
||||
if SERVER_VD_CIRC.get().is_none() {
|
||||
_ = SERVER_VD_CIRC
|
||||
.set(CpuBackend::blocking(move || build_verify_data(SF_LABEL)).await);
|
||||
}
|
||||
|
||||
Ok::<_, PrfError>(())
|
||||
}
|
||||
)?;
|
||||
|
||||
// Finishes preprocessing the verify data circuits.
|
||||
futures::try_join!(
|
||||
async {
|
||||
self.thread_0
|
||||
.load(
|
||||
CLIENT_VD_CIRC
|
||||
.get()
|
||||
.expect("client finished circuit should be built")
|
||||
.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
cf_vd.handshake_hash.clone(),
|
||||
],
|
||||
&[cf_vd.vd.clone()],
|
||||
)
|
||||
.await
|
||||
},
|
||||
async {
|
||||
self.thread_1
|
||||
.load(
|
||||
SERVER_VD_CIRC
|
||||
.get()
|
||||
.expect("server finished circuit should be built")
|
||||
.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
sf_vd.handshake_hash.clone(),
|
||||
],
|
||||
&[sf_vd.vd.clone()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
)?;
|
||||
|
||||
self.state = State::SessionKeys {
|
||||
pms,
|
||||
randoms,
|
||||
hash_state,
|
||||
keys,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn compute_client_finished_vd(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError> {
|
||||
self.execute_cf_vd(handshake_hash).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn compute_server_finished_vd(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError> {
|
||||
self.execute_sf_vd(handshake_hash).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn compute_session_keys(
|
||||
&mut self,
|
||||
server_random: [u8; 32],
|
||||
) -> Result<SessionKeys, PrfError> {
|
||||
self.execute_session_keys(server_random).await
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"tls-client",
|
||||
"tls-backend",
|
||||
"tls-core",
|
||||
"tls-mpc",
|
||||
"tls-client-async",
|
||||
"tls-server-fixture",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# rand
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
# crypto
|
||||
aes = "0.8"
|
||||
aes-gcm = "0.9"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
sct = "0.7"
|
||||
digest = "0.10"
|
||||
webpki = "0.22"
|
||||
webpki-roots = "0.26"
|
||||
ring = "0.17"
|
||||
p256 = "0.13"
|
||||
rustls-pemfile = "1"
|
||||
rustls = "0.20"
|
||||
futures-rustls = "0.26"
|
||||
|
||||
# async
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7"
|
||||
http-body-util = "0.1"
|
||||
hyper = { version = "1.1", features = ["client", "http1"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
|
||||
# serialization
|
||||
bytes = "1"
|
||||
serde = "1"
|
||||
|
||||
# error/log
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
thiserror = "1"
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
|
||||
# testing
|
||||
rstest = "0.12"
|
||||
|
||||
# misc
|
||||
derive_builder = "0.12"
|
||||
enum-try-as-inner = "0.1"
|
||||
web-time = "0.2"
|
||||
@@ -1,13 +0,0 @@
|
||||
/// This build script allows us to enable the `read_buf` language feature only
|
||||
/// for Rust Nightly.
|
||||
///
|
||||
/// See the comment in lib.rs to understand why we need this.
|
||||
|
||||
#[cfg_attr(feature = "read_buf", rustversion::not(nightly))]
|
||||
fn main() {}
|
||||
|
||||
#[cfg(feature = "read_buf")]
|
||||
#[rustversion::nightly]
|
||||
fn main() {
|
||||
println!("cargo:rustc-cfg=read_buf");
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub(crate) mod persist;
|
||||
|
||||
#[cfg(test)]
|
||||
mod persist_test;
|
||||
@@ -1,526 +0,0 @@
|
||||
use crate::{client::ServerName, ticketer::TimeBase};
|
||||
use std::cmp;
|
||||
#[cfg(feature = "tls12")]
|
||||
use std::mem;
|
||||
#[cfg(feature = "tls12")]
|
||||
use tls_core::suites::Tls12CipherSuite;
|
||||
use tls_core::{
|
||||
msgs::{
|
||||
base::{PayloadU16, PayloadU8},
|
||||
codec::{Codec, Reader},
|
||||
enums::{CipherSuite, ProtocolVersion},
|
||||
handshake::{CertificatePayload, SessionID},
|
||||
},
|
||||
suites::{SupportedCipherSuite, Tls13CipherSuite},
|
||||
};
|
||||
|
||||
// These are the keys and values we store in session storage.
|
||||
|
||||
// --- Client types ---
|
||||
/// Keys for session resumption and tickets.
|
||||
/// Matching value is a `ClientSessionValue`.
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSessionKey {
|
||||
kind: &'static [u8],
|
||||
name: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Codec for ClientSessionKey {
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
bytes.extend_from_slice(self.kind);
|
||||
bytes.extend_from_slice(&self.name);
|
||||
}
|
||||
|
||||
// Don't need to read these.
|
||||
fn read(_r: &mut Reader) -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientSessionKey {
|
||||
pub fn session_for_server_name(server_name: &ServerName) -> Self {
|
||||
Self {
|
||||
kind: b"session",
|
||||
name: server_name.encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hint_for_server_name(server_name: &ServerName) -> Self {
|
||||
Self {
|
||||
kind: b"kx-hint",
|
||||
name: server_name.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientSessionValue {
|
||||
Tls13(Tls13ClientSessionValue),
|
||||
#[cfg(feature = "tls12")]
|
||||
Tls12(Tls12ClientSessionValue),
|
||||
}
|
||||
|
||||
impl ClientSessionValue {
|
||||
pub fn read(
|
||||
reader: &mut Reader<'_>,
|
||||
suite: CipherSuite,
|
||||
supported: &[SupportedCipherSuite],
|
||||
) -> Option<Self> {
|
||||
match supported.iter().find(|s| s.suite() == suite)? {
|
||||
SupportedCipherSuite::Tls13(inner) => {
|
||||
Tls13ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls13)
|
||||
}
|
||||
#[cfg(feature = "tls12")]
|
||||
SupportedCipherSuite::Tls12(inner) => {
|
||||
Tls12ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn common(&self) -> &ClientSessionCommon {
|
||||
match self {
|
||||
Self::Tls13(inner) => &inner.common,
|
||||
#[cfg(feature = "tls12")]
|
||||
Self::Tls12(inner) => &inner.common,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tls13ClientSessionValue> for ClientSessionValue {
|
||||
fn from(v: Tls13ClientSessionValue) -> Self {
|
||||
Self::Tls13(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl From<Tls12ClientSessionValue> for ClientSessionValue {
|
||||
fn from(v: Tls12ClientSessionValue) -> Self {
|
||||
Self::Tls12(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Retrieved<T> {
|
||||
pub value: T,
|
||||
retrieved_at: TimeBase,
|
||||
}
|
||||
|
||||
impl<T> Retrieved<T> {
|
||||
pub fn new(value: T, retrieved_at: TimeBase) -> Self {
|
||||
Self {
|
||||
value,
|
||||
retrieved_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Retrieved<&Tls13ClientSessionValue> {
|
||||
pub fn obfuscated_ticket_age(&self) -> u32 {
|
||||
let age_secs = self
|
||||
.retrieved_at
|
||||
.as_secs()
|
||||
.saturating_sub(self.value.common.epoch);
|
||||
let age_millis = age_secs as u32 * 1000;
|
||||
age_millis.wrapping_add(self.value.age_add)
|
||||
}
|
||||
}
|
||||
|
||||
impl Retrieved<ClientSessionValue> {
|
||||
pub fn tls13(&self) -> Option<Retrieved<&Tls13ClientSessionValue>> {
|
||||
match &self.value {
|
||||
ClientSessionValue::Tls13(value) => Some(Retrieved::new(value, self.retrieved_at)),
|
||||
#[cfg(feature = "tls12")]
|
||||
ClientSessionValue::Tls12(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_expired(&self) -> bool {
|
||||
let common = self.value.common();
|
||||
common.lifetime_secs != 0
|
||||
&& common.epoch + u64::from(common.lifetime_secs) < self.retrieved_at.as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Retrieved<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tls13ClientSessionValue {
|
||||
suite: &'static Tls13CipherSuite,
|
||||
age_add: u32,
|
||||
max_early_data_size: u32,
|
||||
pub common: ClientSessionCommon,
|
||||
}
|
||||
|
||||
impl Tls13ClientSessionValue {
|
||||
pub fn new(
|
||||
suite: &'static Tls13CipherSuite,
|
||||
ticket: Vec<u8>,
|
||||
secret: Vec<u8>,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
age_add: u32,
|
||||
max_early_data_size: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
suite,
|
||||
age_add,
|
||||
max_early_data_size,
|
||||
common: ClientSessionCommon::new(
|
||||
ticket,
|
||||
secret,
|
||||
time_now,
|
||||
lifetime_secs,
|
||||
server_cert_chain,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] with an extra `suite` argument.
|
||||
///
|
||||
/// We decode the `suite` argument separately because it allows us to
|
||||
/// decide whether we're decoding an 1.2 or 1.3 session value.
|
||||
pub fn read(suite: &'static Tls13CipherSuite, r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
suite,
|
||||
age_add: u32::read(r)?,
|
||||
max_early_data_size: u32::read(r)?,
|
||||
common: ClientSessionCommon::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inherent implementation of the [`Codec::get_encoding()`] method.
|
||||
///
|
||||
/// (See `read()` for why this is inherent here.)
|
||||
pub fn get_encoding(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
self.suite.common.suite.encode(&mut bytes);
|
||||
self.age_add.encode(&mut bytes);
|
||||
self.max_early_data_size.encode(&mut bytes);
|
||||
self.common.encode(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn max_early_data_size(&self) -> u32 {
|
||||
self.max_early_data_size
|
||||
}
|
||||
|
||||
pub fn suite(&self) -> &'static Tls13CipherSuite {
|
||||
self.suite
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Tls13ClientSessionValue {
|
||||
type Target = ClientSessionCommon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
#[derive(Debug)]
|
||||
pub struct Tls12ClientSessionValue {
|
||||
suite: &'static Tls12CipherSuite,
|
||||
pub session_id: SessionID,
|
||||
extended_ms: bool,
|
||||
pub common: ClientSessionCommon,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl Tls12ClientSessionValue {
|
||||
pub fn new(
|
||||
suite: &'static Tls12CipherSuite,
|
||||
session_id: SessionID,
|
||||
ticket: Vec<u8>,
|
||||
master_secret: Vec<u8>,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
extended_ms: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
suite,
|
||||
session_id,
|
||||
extended_ms,
|
||||
common: ClientSessionCommon::new(
|
||||
ticket,
|
||||
master_secret,
|
||||
time_now,
|
||||
lifetime_secs,
|
||||
server_cert_chain,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] with an extra `suite` argument.
|
||||
///
|
||||
/// We decode the `suite` argument separately because it allows us to
|
||||
/// decide whether we're decoding an 1.2 or 1.3 session value.
|
||||
fn read(suite: &'static Tls12CipherSuite, r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
suite,
|
||||
session_id: SessionID::read(r)?,
|
||||
extended_ms: u8::read(r)? == 1,
|
||||
common: ClientSessionCommon::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inherent implementation of the [`Codec::get_encoding()`] method.
|
||||
///
|
||||
/// (See `read()` for why this is inherent here.)
|
||||
pub fn get_encoding(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
self.suite.common.suite.encode(&mut bytes);
|
||||
self.session_id.encode(&mut bytes);
|
||||
(if self.extended_ms { 1u8 } else { 0u8 }).encode(&mut bytes);
|
||||
self.common.encode(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn take_ticket(&mut self) -> Vec<u8> {
|
||||
mem::take(&mut self.common.ticket.0)
|
||||
}
|
||||
|
||||
pub fn extended_ms(&self) -> bool {
|
||||
self.extended_ms
|
||||
}
|
||||
|
||||
pub fn suite(&self) -> &'static Tls12CipherSuite {
|
||||
self.suite
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl std::ops::Deref for Tls12ClientSessionValue {
|
||||
type Target = ClientSessionCommon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSessionCommon {
|
||||
ticket: PayloadU16,
|
||||
secret: PayloadU8,
|
||||
epoch: u64,
|
||||
lifetime_secs: u32,
|
||||
server_cert_chain: CertificatePayload,
|
||||
}
|
||||
|
||||
impl ClientSessionCommon {
|
||||
fn new(
|
||||
ticket: Vec<u8>,
|
||||
secret: Vec<u8>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ticket: PayloadU16(ticket),
|
||||
secret: PayloadU8(secret),
|
||||
epoch: time_now.as_secs(),
|
||||
lifetime_secs: cmp::min(lifetime_secs, MAX_TICKET_LIFETIME),
|
||||
server_cert_chain,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] is inherent here to avoid leaking the [`Codec`]
|
||||
/// implementation through [`Deref`] implementations on
|
||||
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
|
||||
fn read(r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
ticket: PayloadU16::read(r)?,
|
||||
secret: PayloadU8::read(r)?,
|
||||
epoch: u64::read(r)?,
|
||||
lifetime_secs: u32::read(r)?,
|
||||
server_cert_chain: CertificatePayload::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// [`Codec::encode()`] is inherent here to avoid leaking the [`Codec`]
|
||||
/// implementation through [`Deref`] implementations on
|
||||
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
self.ticket.encode(bytes);
|
||||
self.secret.encode(bytes);
|
||||
self.epoch.encode(bytes);
|
||||
self.lifetime_secs.encode(bytes);
|
||||
self.server_cert_chain.encode(bytes);
|
||||
}
|
||||
|
||||
pub fn server_cert_chain(&self) -> &[tls_core::key::Certificate] {
|
||||
self.server_cert_chain.as_ref()
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &[u8] {
|
||||
self.secret.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn ticket(&self) -> &[u8] {
|
||||
self.ticket.0.as_ref()
|
||||
}
|
||||
|
||||
/// Test only: wind back epoch by delta seconds.
|
||||
pub fn rewind_epoch(&mut self, delta: u32) {
|
||||
self.epoch -= delta as u64;
|
||||
}
|
||||
}
|
||||
|
||||
static MAX_TICKET_LIFETIME: u32 = 7 * 24 * 60 * 60;
|
||||
|
||||
/// This is the maximum allowed skew between server and client clocks, over
|
||||
/// the maximum ticket lifetime period. This encompasses TCP retransmission
|
||||
/// times in case packet loss occurs when the client sends the ClientHello
|
||||
/// or receives the NewSessionTicket, _and_ actual clock skew over this period.
|
||||
static MAX_FRESHNESS_SKEW_MS: u32 = 60 * 1000;
|
||||
|
||||
// --- Server types ---
|
||||
pub type ServerSessionKey = SessionID;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ServerSessionValue {
|
||||
pub sni: Option<webpki::DnsName>,
|
||||
pub version: ProtocolVersion,
|
||||
pub cipher_suite: CipherSuite,
|
||||
pub master_secret: PayloadU8,
|
||||
pub extended_ms: bool,
|
||||
pub client_cert_chain: Option<CertificatePayload>,
|
||||
pub alpn: Option<PayloadU8>,
|
||||
pub application_data: PayloadU16,
|
||||
pub creation_time_sec: u64,
|
||||
pub age_obfuscation_offset: u32,
|
||||
freshness: Option<bool>,
|
||||
}
|
||||
|
||||
impl Codec for ServerSessionValue {
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
if let Some(ref sni) = self.sni {
|
||||
1u8.encode(bytes);
|
||||
let sni_bytes: &str = sni.as_ref().into();
|
||||
PayloadU8::new(Vec::from(sni_bytes)).encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
self.version.encode(bytes);
|
||||
self.cipher_suite.encode(bytes);
|
||||
self.master_secret.encode(bytes);
|
||||
(if self.extended_ms { 1u8 } else { 0u8 }).encode(bytes);
|
||||
if let Some(ref chain) = self.client_cert_chain {
|
||||
1u8.encode(bytes);
|
||||
chain.encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
if let Some(ref alpn) = self.alpn {
|
||||
1u8.encode(bytes);
|
||||
alpn.encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
self.application_data.encode(bytes);
|
||||
self.creation_time_sec.encode(bytes);
|
||||
self.age_obfuscation_offset.encode(bytes);
|
||||
}
|
||||
|
||||
fn read(r: &mut Reader) -> Option<Self> {
|
||||
let has_sni = u8::read(r)?;
|
||||
let sni = if has_sni == 1 {
|
||||
let dns_name = PayloadU8::read(r)?;
|
||||
let dns_name = webpki::DnsNameRef::try_from_ascii(&dns_name.0).ok()?;
|
||||
Some(dns_name.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let v = ProtocolVersion::read(r)?;
|
||||
let cs = CipherSuite::read(r)?;
|
||||
let ms = PayloadU8::read(r)?;
|
||||
let ems = u8::read(r)?;
|
||||
let has_ccert = u8::read(r)? == 1;
|
||||
let ccert = if has_ccert {
|
||||
Some(CertificatePayload::read(r)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_alpn = u8::read(r)? == 1;
|
||||
let alpn = if has_alpn {
|
||||
Some(PayloadU8::read(r)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let application_data = PayloadU16::read(r)?;
|
||||
let creation_time_sec = u64::read(r)?;
|
||||
let age_obfuscation_offset = u32::read(r)?;
|
||||
|
||||
Some(Self {
|
||||
sni,
|
||||
version: v,
|
||||
cipher_suite: cs,
|
||||
master_secret: ms,
|
||||
extended_ms: ems == 1u8,
|
||||
client_cert_chain: ccert,
|
||||
alpn,
|
||||
application_data,
|
||||
creation_time_sec,
|
||||
age_obfuscation_offset,
|
||||
freshness: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerSessionValue {
|
||||
pub fn new(
|
||||
sni: Option<&webpki::DnsName>,
|
||||
v: ProtocolVersion,
|
||||
cs: CipherSuite,
|
||||
ms: Vec<u8>,
|
||||
client_cert_chain: Option<CertificatePayload>,
|
||||
alpn: Option<Vec<u8>>,
|
||||
application_data: Vec<u8>,
|
||||
creation_time: TimeBase,
|
||||
age_obfuscation_offset: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
sni: sni.cloned(),
|
||||
version: v,
|
||||
cipher_suite: cs,
|
||||
master_secret: PayloadU8::new(ms),
|
||||
extended_ms: false,
|
||||
client_cert_chain,
|
||||
alpn: alpn.map(PayloadU8::new),
|
||||
application_data: PayloadU16::new(application_data),
|
||||
creation_time_sec: creation_time.as_secs(),
|
||||
age_obfuscation_offset,
|
||||
freshness: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_extended_ms_used(&mut self) {
|
||||
self.extended_ms = true;
|
||||
}
|
||||
|
||||
pub fn set_freshness(mut self, obfuscated_client_age_ms: u32, time_now: TimeBase) -> Self {
|
||||
let client_age_ms = obfuscated_client_age_ms.wrapping_sub(self.age_obfuscation_offset);
|
||||
let server_age_ms =
|
||||
(time_now.as_secs().saturating_sub(self.creation_time_sec) as u32).saturating_mul(1000);
|
||||
|
||||
let age_difference = if client_age_ms < server_age_ms {
|
||||
server_age_ms - client_age_ms
|
||||
} else {
|
||||
client_age_ms - server_age_ms
|
||||
};
|
||||
|
||||
self.freshness = Some(age_difference <= MAX_FRESHNESS_SKEW_MS);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_fresh(&self) -> bool {
|
||||
self.freshness.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
use super::persist::*;
|
||||
use crate::ticketer::TimeBase;
|
||||
use std::convert::TryInto;
|
||||
use tls_core::{
|
||||
key::Certificate,
|
||||
msgs::{
|
||||
codec::{Codec, Reader},
|
||||
enums::*,
|
||||
},
|
||||
suites::TLS13_AES_128_GCM_SHA256,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn clientsessionkey_is_debug() {
|
||||
let name = "hello".try_into().unwrap();
|
||||
let csk = ClientSessionKey::session_for_server_name(&name);
|
||||
println!("{:?}", csk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clientsessionkey_cannot_be_read() {
|
||||
let bytes = [0; 1];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
assert!(ClientSessionKey::read(&mut rd).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clientsessionvalue_is_debug() {
|
||||
let csv = ClientSessionValue::from(Tls13ClientSessionValue::new(
|
||||
TLS13_AES_128_GCM_SHA256.tls13().unwrap(),
|
||||
vec![],
|
||||
vec![1, 2, 3],
|
||||
vec![Certificate(b"abc".to_vec()), Certificate(b"def".to_vec())],
|
||||
TimeBase::now().unwrap(),
|
||||
15,
|
||||
10,
|
||||
128,
|
||||
));
|
||||
println!("{:?}", csv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_is_debug() {
|
||||
let ssv = ServerSessionValue::new(
|
||||
None,
|
||||
ProtocolVersion::TLSv1_3,
|
||||
CipherSuite::TLS13_AES_128_GCM_SHA256,
|
||||
vec![1, 2, 3],
|
||||
None,
|
||||
None,
|
||||
vec![4, 5, 6],
|
||||
TimeBase::now().unwrap(),
|
||||
0x12345678,
|
||||
);
|
||||
println!("{:?}", ssv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_no_sni() {
|
||||
let bytes = [
|
||||
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
|
||||
];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
let ssv = ServerSessionValue::read(&mut rd).unwrap();
|
||||
assert_eq!(ssv.get_encoding(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_with_cert() {
|
||||
let bytes = [
|
||||
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
|
||||
];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
let ssv = ServerSessionValue::read(&mut rd).unwrap();
|
||||
assert_eq!(ssv.get_encoding(), bytes);
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
// This program does benchmarking of the functions in verify.rs,
|
||||
// that do certificate chain validation and signature verification.
|
||||
//
|
||||
// Note: we don't use any of the standard 'cargo bench', 'test::Bencher',
|
||||
// etc. because it's unstable at the time of writing.
|
||||
|
||||
use crate::{anchors, verify, verify::ServerCertVerifier, OwnedTrustAnchor};
|
||||
use std::convert::TryInto;
|
||||
use web_time::{Duration, Instant, SystemTime};
|
||||
|
||||
use webpki_roots;
|
||||
|
||||
fn duration_nanos(d: Duration) -> u64 {
|
||||
((d.as_secs() as f64) * 1e9 + (d.subsec_nanos() as f64)) as u64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reddit_cert() {
|
||||
Context::new(
|
||||
"reddit",
|
||||
"reddit.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-reddit.0.der"),
|
||||
include_bytes!("testdata/cert-reddit.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_cert() {
|
||||
Context::new(
|
||||
"github",
|
||||
"github.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-github.0.der"),
|
||||
include_bytes!("testdata/cert-github.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arstechnica_cert() {
|
||||
Context::new(
|
||||
"arstechnica",
|
||||
"arstechnica.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-arstechnica.0.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.1.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.2.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.3.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_servo_cert() {
|
||||
Context::new(
|
||||
"servo",
|
||||
"servo.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-servo.0.der"),
|
||||
include_bytes!("testdata/cert-servo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_twitter_cert() {
|
||||
Context::new(
|
||||
"twitter",
|
||||
"twitter.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-twitter.0.der"),
|
||||
include_bytes!("testdata/cert-twitter.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wikipedia_cert() {
|
||||
Context::new(
|
||||
"wikipedia",
|
||||
"wikipedia.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-wikipedia.0.der"),
|
||||
include_bytes!("testdata/cert-wikipedia.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_google_cert() {
|
||||
Context::new(
|
||||
"google",
|
||||
"www.google.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-google.0.der"),
|
||||
include_bytes!("testdata/cert-google.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hn_cert() {
|
||||
Context::new(
|
||||
"hn",
|
||||
"news.ycombinator.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-hn.0.der"),
|
||||
include_bytes!("testdata/cert-hn.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stackoverflow_cert() {
|
||||
Context::new(
|
||||
"stackoverflow",
|
||||
"stackoverflow.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-stackoverflow.0.der"),
|
||||
include_bytes!("testdata/cert-stackoverflow.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duckduckgo_cert() {
|
||||
Context::new(
|
||||
"duckduckgo",
|
||||
"duckduckgo.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-duckduckgo.0.der"),
|
||||
include_bytes!("testdata/cert-duckduckgo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rustlang_cert() {
|
||||
Context::new(
|
||||
"rustlang",
|
||||
"www.rust-lang.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-rustlang.0.der"),
|
||||
include_bytes!("testdata/cert-rustlang.1.der"),
|
||||
include_bytes!("testdata/cert-rustlang.2.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wapo_cert() {
|
||||
Context::new(
|
||||
"wapo",
|
||||
"www.washingtonpost.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-wapo.0.der"),
|
||||
include_bytes!("testdata/cert-wapo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
struct Context {
|
||||
name: &'static str,
|
||||
domain: &'static str,
|
||||
roots: anchors::RootCertStore,
|
||||
chain: Vec<tls_core::key::Certificate>,
|
||||
now: SystemTime,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new(name: &'static str, domain: &'static str, certs: &[&'static [u8]]) -> Self {
|
||||
let mut roots = anchors::RootCertStore::empty();
|
||||
roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
Self {
|
||||
name,
|
||||
domain,
|
||||
roots,
|
||||
chain: certs
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|bytes| tls_core::key::Certificate(bytes.to_vec()))
|
||||
.collect(),
|
||||
now: SystemTime::UNIX_EPOCH + Duration::from_secs(1640870720),
|
||||
}
|
||||
}
|
||||
|
||||
fn bench(&self, count: usize) {
|
||||
let verifier = verify::WebPkiVerifier::new(self.roots.clone(), None);
|
||||
const SCTS: &[&[u8]] = &[];
|
||||
const OCSP_RESPONSE: &[u8] = &[];
|
||||
let mut times = Vec::new();
|
||||
|
||||
let (end_entity, intermediates) = self.chain.split_first().unwrap();
|
||||
for _ in 0..count {
|
||||
let start = Instant::now();
|
||||
let server_name = self.domain.try_into().unwrap();
|
||||
verifier
|
||||
.verify_server_cert(
|
||||
end_entity,
|
||||
intermediates,
|
||||
&server_name,
|
||||
&mut SCTS.iter().copied(),
|
||||
OCSP_RESPONSE,
|
||||
self.now,
|
||||
)
|
||||
.unwrap();
|
||||
times.push(duration_nanos(Instant::now().duration_since(start)));
|
||||
}
|
||||
|
||||
println!(
|
||||
"verify_server_cert({}): min {:?}us",
|
||||
self.name,
|
||||
times.iter().min().unwrap() / 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDl30Srs7laSdaAOzoB
|
||||
kCiehcf1HXv7NqGQBECqshrtHxGEX6bAnBB7JgyDs28NvPGhZANiAAS7F1hCrNsV
|
||||
woFtTnAut6Qj40KNKt1Zz3NSduJ+pl8MaYN/fJ80eJ94h3GANKSoBJ3gbLUjrFzo
|
||||
WzWdPQwLxK94knPAcllp7RG1lY2N4PSrekOmNbFxpGlCIj/W3uZSEEU=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,13 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB8jCCAZegAwIBAgICAxUwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcq
|
||||
hkjOPQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5w
|
||||
wCG8k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD7
|
||||
1yD1+DG/cjK1okLZIVhbSQyjgZswgZgwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMC
|
||||
BsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFFBkko+0OE2piFRx
|
||||
h9m2UonFYQFEMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAc
|
||||
MRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQYIBezAKBggqhkjOPQQDAgNJADBG
|
||||
AiEAvyquOUQlqAWkSlfwH3nYNmmEG9CT/jjzNs1OBr1RD6ACIQDtmqdbttqgqKAZ
|
||||
Wi5lCzftwM6Hy5aA0qy1v80H4xBJyw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,37 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB8jCCAZegAwIBAgICAxUwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcq
|
||||
hkjOPQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5w
|
||||
wCG8k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD7
|
||||
1yD1+DG/cjK1okLZIVhbSQyjgZswgZgwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMC
|
||||
BsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFFBkko+0OE2piFRx
|
||||
h9m2UonFYQFEMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAc
|
||||
MRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQYIBezAKBggqhkjOPQQDAgNJADBG
|
||||
AiEAvyquOUQlqAWkSlfwH3nYNmmEG9CT/jjzNs1OBr1RD6ACIQDtmqdbttqgqKAZ
|
||||
Wi5lCzftwM6Hy5aA0qy1v80H4xBJyw==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,6 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDALKtA1q+8ZBeLi2Gsq
|
||||
UxFTBxNPPhOuyNRkvwRKis/glf9GgEHgvM0qVaxWnRsdCE6hZANiAATx0R97foSC
|
||||
0Ra9a13pJzfI1hh3G6476MIMslLHxg5wwCG8k5mMHia2hGOBbdGjoY0C1wJLNrUS
|
||||
ov5SfcsYX6/VjHQH/elmb/KOO1AGwPD71yD1+DG/cjK1okLZIVhbSQw=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,8 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBEzCBmQIBADAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcqhkjO
|
||||
PQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5wwCG8
|
||||
k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD71yD1
|
||||
+DG/cjK1okLZIVhbSQygADAKBggqhkjOPQQDAgNpADBmAjEA8p3W7yFCJ73dOmYQ
|
||||
rpMpLkYNcfxxpNfCWgqaPyWu3UeOcHvC7ihklnFTWzpmEO+PAjEA8O5P4mXlYUtl
|
||||
Dsw8qOrqWSdQ1IykXhM4NxPOkt0TMQZvvrpSsJU6PhwSbJGjVfBR
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,13 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB9zCCAZ6gAwIBAgICAcgwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAZMRcwFQYDVQQDDA50ZXN0c2VydmVyLmNvbTBZMBMGByqG
|
||||
SM49AgEGCCqGSM49AwEHA0IABPprdHsWc3TtNne2409qO+fC9OFiiXFevQwJjUUC
|
||||
J/X0ihomRsHAnrJvcNyOEWsdu7OwOj4PD9QFMifDEHGYtHOjgcAwgb0wDAYDVR0T
|
||||
AQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFOXZcb/0+/Xql1fOb4pVblzV
|
||||
vUcZMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAcMRowGAYD
|
||||
VQQDDBFwb255dG93biBFQ0RTQSBDQYIBezA7BgNVHREENDAygg50ZXN0c2VydmVy
|
||||
LmNvbYIVc2Vjb25kLnRlc3RzZXJ2ZXIuY29tgglsb2NhbGhvc3QwCgYIKoZIzj0E
|
||||
AwIDRwAwRAIgXONA4IOh4PbHTuK6oaHtguOIvmxxXCqp8kwJlI1e+MMCICOSrk1F
|
||||
e+VsbKeFQlJ6EM65CLTezDUIZKCmoNWvyTGy
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,37 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB9zCCAZ6gAwIBAgICAcgwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAZMRcwFQYDVQQDDA50ZXN0c2VydmVyLmNvbTBZMBMGByqG
|
||||
SM49AgEGCCqGSM49AwEHA0IABPprdHsWc3TtNne2409qO+fC9OFiiXFevQwJjUUC
|
||||
J/X0ihomRsHAnrJvcNyOEWsdu7OwOj4PD9QFMifDEHGYtHOjgcAwgb0wDAYDVR0T
|
||||
AQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFOXZcb/0+/Xql1fOb4pVblzV
|
||||
vUcZMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAcMRowGAYD
|
||||
VQQDDBFwb255dG93biBFQ0RTQSBDQYIBezA7BgNVHREENDAygg50ZXN0c2VydmVy
|
||||
LmNvbYIVc2Vjb25kLnRlc3RzZXJ2ZXIuY29tgglsb2NhbGhvc3QwCgYIKoZIzj0E
|
||||
AwIDRwAwRAIgXONA4IOh4PbHTuK6oaHtguOIvmxxXCqp8kwJlI1e+MMCICOSrk1F
|
||||
e+VsbKeFQlJ6EM65CLTezDUIZKCmoNWvyTGy
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,5 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdoMBbIGRw+L9but3
|
||||
PO4WSJfS8wbvUNrF1VuQjsDVMKmhRANCAAT6a3R7FnN07TZ3tuNPajvnwvThYolx
|
||||
Xr0MCY1FAif19IoaJkbBwJ6yb3DcjhFrHbuzsDo+Dw/UBTInwxBxmLRz
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,7 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIHTMHsCAQAwGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wWTATBgcqhkjOPQIB
|
||||
BggqhkjOPQMBBwNCAAT6a3R7FnN07TZ3tuNPajvnwvThYolxXr0MCY1FAif19Ioa
|
||||
JkbBwJ6yb3DcjhFrHbuzsDo+Dw/UBTInwxBxmLRzoAAwCgYIKoZIzj0EAwIDSAAw
|
||||
RQIgA9G3IaH4syAQYGJ3ESqXQaoKSrZsDMBD0MgG2g2FC78CIQD+RRTETPkFq0as
|
||||
cca9W/yqg8QN/ZGzE38iEpohyGda/w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,12 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,5 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdniIWGzkYuZcwh/H
|
||||
9hDbaITfndAs+Hin6j+0XjD01MShRANCAARi1GU/KSFPRgueNxKxt7yt+YnuiN9a
|
||||
4ciYMbEOkXm8nbn2CI732oURxlOLt029AYFALpuxRZ11OXcFzuDGIKvd
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,7 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIHoMIGQAgEAMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50
|
||||
ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcS
|
||||
sbe8rfmJ7ojfWuHImDGxDpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7g
|
||||
xiCr3aAAMAoGCCqGSM49BAMCA0cAMEQCIFeMseiKS80m8KmHkl7W8lRXavH5yx/h
|
||||
qTFM+f3T4AnZAiBRR8+rFop/TR51gISUfbMj2W3yTAGxOkCdlPgT+Jxqwg==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,9 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
|
||||
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
|
||||
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
|
||||
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
|
||||
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIJ+e0HfxvslYFLr6ehAnsW6ZnOSu3Cume9OGJhVVLjnK
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,11 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw
|
||||
NTEzMjg1MVowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
|
||||
uCBt6D15TvwyMNP5/nRo9v38yGIeSGQCM1tjyzXFGHWjgZswgZgwDAYDVR0TAQH/
|
||||
BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O
|
||||
BBYEFHdQ+goYXFvGem83oCHVJ87Ak0fyMEQGA1UdIwQ9MDuAFBcSMFONOrRtwrD1
|
||||
pB1qn7lE+PGroSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF
|
||||
BgMrZXADQQA4GfyvVIs/xQQTv3igqhzabhraQKEd4z2HHudJgdHHV7M7yfuNqN3x
|
||||
NCr3hfDfUQuEOgm02d9Q4TZfik+czUML
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,19 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBVzCCAQmgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE
|
||||
U0EgQ0EwHhcNMTkwODE2MTMyODUxWhcNMjkwODEzMTMyODUxWjAuMSwwKgYDVQQD
|
||||
DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh
|
||||
AD4h3t0UCoMDGgIq4UW4P5zDngsY4vy1pE3wzLPFI4Vdo14wXDAdBgNVHQ4EFgQU
|
||||
FxIwU406tG3CsPWkHWqfuUT48aswIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MAUGAytlcANBAAZFvMek
|
||||
Z71I8CXsBmx/0E6Weoaan9mJHgKqgQdK4w4h4dRg6DjNG957IbrLFO3vZduBMnna
|
||||
qHP3xTFF+11Eyg8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
|
||||
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
|
||||
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
|
||||
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
|
||||
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,30 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw
|
||||
NTEzMjg1MVowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
|
||||
uCBt6D15TvwyMNP5/nRo9v38yGIeSGQCM1tjyzXFGHWjgZswgZgwDAYDVR0TAQH/
|
||||
BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O
|
||||
BBYEFHdQ+goYXFvGem83oCHVJ87Ak0fyMEQGA1UdIwQ9MDuAFBcSMFONOrRtwrD1
|
||||
pB1qn7lE+PGroSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF
|
||||
BgMrZXADQQA4GfyvVIs/xQQTv3igqhzabhraQKEd4z2HHudJgdHHV7M7yfuNqN3x
|
||||
NCr3hfDfUQuEOgm02d9Q4TZfik+czUML
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBVzCCAQmgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE
|
||||
U0EgQ0EwHhcNMTkwODE2MTMyODUxWhcNMjkwODEzMTMyODUxWjAuMSwwKgYDVQQD
|
||||
DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh
|
||||
AD4h3t0UCoMDGgIq4UW4P5zDngsY4vy1pE3wzLPFI4Vdo14wXDAdBgNVHQ4EFgQU
|
||||
FxIwU406tG3CsPWkHWqfuUT48aswIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MAUGAytlcANBAAZFvMek
|
||||
Z71I8CXsBmx/0E6Weoaan9mJHgKqgQdK4w4h4dRg6DjNG957IbrLFO3vZduBMnna
|
||||
qHP3xTFF+11Eyg8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
|
||||
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
|
||||
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
|
||||
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
|
||||
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,3 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEILxmhRq5n4/1iik9J5wfqy6VC6pff4Lxu2wWQd2YUWy7
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,6 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIGZME0CAQAwGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
|
||||
uCBt6D15TvwyMNP5/nRo9v38yGIeSGQCM1tjyzXFGHWgADAFBgMrZXADQQCEpbl9
|
||||
6XUAJzBV0nMKpashbf7JNAyBQYtH5IHI+fE90GEcTuRZgSg9vy6+smL+Exo/9MD9
|
||||
toz+25pltMgE2YYE
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,12 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAWqgAwIBAgICAcgwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw
|
||||
NTEzMjg1MVowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wKjAFBgMrZXADIQAQ
|
||||
9M4hrE+Ucw4QUmaKOeKfphklBJi1qsqtX4u+knbseqOBwDCBvTAMBgNVHRMBAf8E
|
||||
AjAAMAsGA1UdDwQEAwIGwDAdBgNVHQ4EFgQUa/gnV4+a22BUKTouAYX6nfLnPKYw
|
||||
RAYDVR0jBD0wO4AUFxIwU406tG3CsPWkHWqfuUT48auhIKQeMBwxGjAYBgNVBAMM
|
||||
EXBvbnl0b3duIEVkRFNBIENBggF7MDsGA1UdEQQ0MDKCDnRlc3RzZXJ2ZXIuY29t
|
||||
ghVzZWNvbmQudGVzdHNlcnZlci5jb22CCWxvY2FsaG9zdDAFBgMrZXADQQApDiBQ
|
||||
ns3fuvsWuFpIS+osj2B/gQ0b6eBAZ1UBxRyDlAo5++JZ0PtaEROyGo2t2gqi2Lyz
|
||||
47mLyGCvqgVbC6cH
|
||||
-----END CERTIFICATE-----
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user