├── .cargohome └── .keep ├── .cargotarget └── .keep ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── push.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── check_copyrights.sh ├── docker ├── .gitignore ├── Dockerfile ├── apt.conf ├── aws-nitro.Cargo.lock ├── build_eif.sh ├── clang+llvm-11.1.0-x86_64-linux-gnu-ubuntu-20.10.tar.xz.sha256 ├── ms.sources.list ├── nitro_start.sh ├── sample_sgx_default_qcnl_azure.conf ├── sgx.sources.list ├── sgx_runtime_libraries.sh └── sources.list ├── docs ├── Healing.md ├── Messages.md ├── svr2.tla └── svr3spec │ ├── .gitignore │ ├── README.md │ ├── svr3.bib │ ├── svr3.pdf │ └── svr3.tex ├── enclave ├── .gitignore ├── Makefile ├── Makefile.HOST ├── Makefile.SGX ├── Makefile.TEST ├── Makefile.X86 ├── Makefile.base ├── Makefile.subdir ├── README.md ├── attestation │ ├── nitro │ │ ├── nitro.cc │ │ ├── nitro.h │ │ └── tests │ │ │ └── nitro.cc │ ├── oe │ │ ├── attestation.cc │ │ └── attestation.h │ ├── sev │ │ ├── sev.cc │ │ ├── sev.h │ │ └── tests │ │ │ └── sev.cc │ ├── tpm2 │ │ ├── tests │ │ │ └── tpm2.cc │ │ ├── tpm2.cc │ │ └── tpm2.h │ └── tpm2snp │ │ ├── tests │ │ └── tpm2snp.cc │ │ ├── tpm2snp.cc │ │ └── tpm2snp.h ├── client │ ├── client.cc │ └── client.h ├── context │ ├── context.cc │ ├── context.h │ └── tests │ │ ├── acquire_lock.cc │ │ └── measure_cpu.cc ├── core │ ├── core.cc │ ├── core.h │ ├── coretest │ │ ├── replicagroup.cc │ │ ├── replicagroup.h │ │ ├── testingclient.cc │ │ ├── testingclient.h │ │ ├── testingcore.cc │ │ └── testingcore.h │ ├── internal.h │ └── tests │ │ └── core.cc ├── db │ ├── db.cc │ ├── db.h │ ├── db2.cc │ ├── db2.h │ ├── db3.cc │ ├── db3.h │ ├── db4.cc │ ├── db4.h │ └── tests │ │ ├── db2.cc │ │ ├── db3.cc │ │ └── db4.cc ├── ecalls │ └── ecalls.cc ├── env │ ├── azuresnp │ │ └── azuresnp.cc │ ├── env.cc │ ├── env.h │ ├── gcpsnp │ │ └── gcpsnp.cc │ ├── nsm │ │ └── nsm.cc │ ├── sgx │ │ └── sgx.cc │ ├── socket │ │ ├── socket.cc │ │ └── socket.h │ └── test │ │ ├── test.cc │ │ ├── test.h │ │ └── tests │ │ └── testrand.cc ├── find_header.sh ├── fs │ ├── fs.cc │ └── fs.h ├── groupclock │ ├── groupclock.cc │ ├── groupclock.h │ └── tests │ │ └── groupclock.cc ├── gtest │ ├── gtest-all.cc │ └── gtest_main.cc ├── hmac │ ├── hmac.cc │ ├── hmac.h │ └── tests │ │ └── hmac.cc ├── initmain │ └── initmain.cc ├── merkle │ ├── merkle.cc │ ├── merkle.h │ └── tests │ │ └── merkle.cc ├── metrics │ ├── counters.h │ ├── gauges.h │ ├── metrics.cc │ ├── metrics.h │ └── tests │ │ └── metrics.cc ├── minimums │ ├── minimums.cc │ ├── minimums.h │ └── tests │ │ └── minimums.cc ├── noise │ ├── noise.cc │ ├── noise.h │ └── tests │ │ └── encrypt_decrypt.cc ├── noisewrap │ ├── tests │ │ └── noisewrap.cc │ └── wrap.cc ├── peerid │ ├── peerid.cc │ ├── peerid.h │ └── tests │ │ └── peerid.cc ├── peers │ ├── peers.cc │ ├── peers.h │ └── tests │ │ └── peermanager.cc ├── proto │ ├── clientlog.proto │ ├── e2e.proto │ ├── raft.proto │ └── tests.proto ├── protobuf-lite │ ├── README.md │ ├── any_lite.cc │ ├── arena.cc │ ├── arenastring.cc │ ├── arenaz_sampler.cc │ ├── bytestream.cc │ ├── coded_stream.cc │ ├── common.cc │ ├── extension_set.cc │ ├── generated_enum_util.cc │ ├── generated_message_tctable_lite.cc │ ├── generated_message_util.cc │ ├── implicit_weak_message.cc │ ├── inlined_string_field.cc │ ├── int128.cc │ ├── io_win32.cc │ ├── map.cc │ ├── message_lite.cc │ ├── parse_context.cc │ ├── repeated_field.cc │ ├── repeated_ptr_field.cc │ ├── status.cc │ ├── statusor.cc │ ├── stringpiece.cc │ ├── stringprintf.cc │ ├── strtod.cc │ ├── structurally_valid.cc │ ├── strutil.cc │ ├── time.cc │ ├── wire_format_lite.cc │ ├── zero_copy_stream.cc │ ├── zero_copy_stream_impl.cc │ └── zero_copy_stream_impl_lite.cc ├── queue │ ├── queue.h │ └── tests │ │ └── queue.cc ├── raft │ ├── internal.h │ ├── log.cc │ ├── log.h │ ├── membership.cc │ ├── membership.h │ ├── raft.cc │ ├── raft.h │ ├── tests │ │ ├── log.cc │ │ ├── membership.cc │ │ ├── raft.cc │ │ └── setdiffsize.cc │ └── types.h ├── releases │ ├── .keep │ ├── gcpsnp │ │ ├── 0.20240911.184407.eventlog │ │ ├── 0.20240911.184407.tar.gz │ │ ├── 0.20240912.192702.tar.gz │ │ └── 0.20240927.165003.tar.gz │ ├── nitro │ │ ├── nitro.5d16a1fd.52b91975.6c355155.eif │ │ ├── nitro.75882c69.52b91975.65d6db28.eif │ │ ├── nitro.c4f21f2c.52b91975.6b055bb7.eif │ │ └── nitro.dd9495f9.52b91975.64681a07.eif │ └── sgx │ │ ├── default.093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6 │ │ ├── default.9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570 │ │ ├── small.2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91 │ │ └── small.38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3 ├── ristretto │ ├── ristretto.cc │ └── ristretto.h ├── sender │ ├── sender.cc │ ├── sender.h │ └── tests │ │ └── sender.cc ├── sevtypes │ ├── sev-guest.h │ └── sevtypes.h ├── sha │ ├── sha.cc │ └── sha.h ├── sip │ ├── halfsiphash.c │ ├── halfsiphash.h │ ├── hasher.cc │ ├── hasher.h │ ├── siphash.c │ ├── siphash.h │ └── tests │ │ └── hasher.cc ├── socketmain │ └── socketmain.cc ├── socketwrap │ ├── socket.cc │ ├── socket.h │ └── tests │ │ └── socket.cc ├── svr2.conf ├── svr2 │ └── .keep ├── svr2_small.conf ├── svr2_test.conf ├── test_deps.sh ├── testhost │ └── testhost.cc ├── timeout │ ├── tests │ │ └── timeout.cc │ ├── timeout.cc │ └── timeout.h └── util │ ├── base64.cc │ ├── base64.h │ ├── bytes.h │ ├── constant.h │ ├── cpu.h │ ├── endian.h │ ├── hex.cc │ ├── hex.h │ ├── log.cc │ ├── log.h │ ├── macros.h │ ├── mutex.h │ ├── tests │ ├── base64.cc │ ├── constant.cc │ ├── endian.cc │ └── hex.cc │ ├── threadsafetyannotations.h │ ├── ticks.cc │ └── ticks.h ├── host ├── .gitignore ├── .tool-versions ├── Makefile ├── README.md ├── auth │ ├── auth.go │ └── auth_test.go ├── cmd │ ├── control │ │ └── main.go │ ├── get_sev_chain │ │ └── main.go │ ├── svr2client │ │ └── main.go │ ├── svr3client │ │ └── main.go │ └── svr3gcp │ │ └── main.go ├── config │ ├── config.go │ ├── config_test.go │ ├── peer.go │ ├── raft.go │ ├── rate.go │ ├── redis.go │ └── request.go ├── dispatch │ ├── dispatcher.go │ ├── dispatcher_test.go │ ├── metrics.go │ └── metrics_test.go ├── enclave.config.sample ├── enclave │ ├── callback.go │ ├── enclave_test.go │ ├── iface.go │ ├── logging.go │ ├── sgx.go │ └── socket.go ├── go.mod ├── go.sum ├── health │ ├── health.go │ └── health_test.go ├── host.config.sample ├── integration │ ├── integration_test.go │ └── testdata │ │ └── enclave.config ├── logger │ └── logger.go ├── main.go ├── miniredis │ └── miniredis.go ├── peer │ ├── client.go │ ├── client_test.go │ ├── peer.go │ ├── peerdb │ │ ├── peerdb.go │ │ └── peerdb_test.go │ ├── sequence_number.go │ ├── serialize.go │ ├── server.go │ └── server_test.go ├── peerid │ └── peerid.go ├── proto │ ├── control.proto │ ├── error.go │ ├── error_test.go │ └── peerdb.proto ├── raftmanager │ ├── raftmanager.go │ └── raftmanager_test.go ├── rate │ ├── rate.go │ └── rate_test.go ├── rustclient │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── proto │ ├── rustclient_test.go │ ├── src │ │ └── main.rs │ └── target ├── service │ ├── service.go │ └── service_test.go ├── servicetest │ └── servicetest.go ├── util │ ├── clock.go │ ├── clock_test.go │ ├── txid.go │ ├── user_agent.go │ ├── user_agent_test.go │ └── util.go └── web │ ├── client │ ├── control_client.go │ └── svr.go │ ├── handlers │ ├── control.go │ ├── delete_backup.go │ ├── handlers.go │ ├── set_log_level.go │ ├── set_log_level_test.go │ └── websocket.go │ ├── middleware │ ├── auth.go │ ├── metrics.go │ └── rate.go │ └── server_test.go ├── shared ├── .gitignore ├── proto │ ├── attestation.proto │ ├── client.proto │ ├── client3.proto │ ├── client4.proto │ ├── enclaveconfig.proto │ ├── error.proto │ ├── metrics.proto │ ├── minimums.proto │ ├── msgs.proto │ ├── sev.proto │ ├── socketmain.proto │ └── tpm2snp.proto └── svr2.edl └── trustedimage ├── .gitignore ├── Makefile ├── README.md ├── azure.sh ├── azure_config.example ├── azure_copy_blob.sh ├── debian1.pkr.hcl ├── debian1 ├── example-preseed.txt └── preseed.txt ├── debian2.pkr.hcl ├── debian2 ├── azure-provisioning.service ├── azure-provisioning.sh ├── azure.sh ├── chroot.sh ├── enclave.azuresnp ├── enclave.gcpsnp ├── gcp.sh ├── initramfs_hook.sh ├── initramfs_local-bottom.sh ├── initramfs_local-top.sh ├── run.sh ├── svr3 ├── svr3.service ├── svr3gcp └── svr3test ├── gcp.sh ├── gcp_config.example ├── template.pkr.hcl └── verify_gcpsnp_release.sh /.cargohome/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalapp/SecureValueRecovery2/9d5df31e6a6616f1d91d953e89f219cf1b211b34/.cargohome/.keep -------------------------------------------------------------------------------- /.cargotarget/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalapp/SecureValueRecovery2/9d5df31e6a6616f1d91d953e89f219cf1b211b34/.cargotarget/.keep -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .gopath 2 | .gocache 3 | .cargohome 4 | .cargotarget 5 | .git 6 | enclave/core.* 7 | docker/Dockerfile 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | indent_size = 4 14 | ij_go_add_parentheses_for_single_import = true 15 | ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true 16 | ij_go_group_stdlib_imports = true 17 | ij_go_import_sorting = goimports 18 | ij_go_local_group_mode = project 19 | ij_go_local_package_prefixes = "github.com/signalapp/svr2" 20 | ij_go_move_all_imports_in_one_declaration = true 21 | ij_go_move_all_stdlib_imports_in_one_group = true 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | enclave/releases/** filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | 4 | env: 5 | DOCKER_BUILD_ARGS: --cache-from type=gha --cache-to type=gha 6 | DOCKER_BUILDKIT: 1 7 | BUILDX_CONTAINER: container 8 | V: 1 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | packages: read 15 | contents: read 16 | id-token: write 17 | 18 | steps: 19 | - name: Checkout main project 20 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | with: 22 | submodules: recursive 23 | 24 | - name: Expose Docker environmental variables for gha cache 25 | # This action takes in the ID tokens etc provided by the permissions, 26 | # as well as some environmental data, and exposes them to future steps 27 | # in the correct locations and formats for their use with the `gha` 28 | # GitHub Actions cache for Docker. 29 | uses: crazy-max/ghaction-github-runtime@3cb05d89e1f492524af3d41a1c98c83bc3025124 # v3.1.0 30 | 31 | - name: Setup Docker 32 | run: docker buildx create --use --name container --driver docker-container 33 | 34 | - name: Test 35 | run: make docker_enclave_test 36 | 37 | - name: Build 38 | run: make 39 | 40 | - name: Validate 41 | run: make docker_validate 42 | 43 | - name: Valgrind 44 | run: make docker_enclave_valgrind 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .gocache 3 | .gopath 4 | .cargohome 5 | .cargotarget 6 | **/.devcontainer 7 | **/.vscode 8 | **/*.code-workspace 9 | .tool-versions 10 | .idea 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "enclave/protobuf"] 2 | path = enclave/protobuf 3 | url = https://github.com/protocolbuffers/protobuf.git 4 | [submodule "enclave/noise-c"] 5 | path = enclave/noise-c 6 | url = https://github.com/signalapp/noise-c.git 7 | [submodule "enclave/SipHash"] 8 | path = enclave/SipHash 9 | url = https://github.com/veorq/SipHash 10 | [submodule "enclave/googletest"] 11 | path = enclave/googletest 12 | url = https://github.com/google/googletest 13 | [submodule "enclave/libsodium"] 14 | path = enclave/libsodium 15 | url = https://github.com/jedisct1/libsodium 16 | [submodule "docker/aws-nitro-enclaves-nsm-api"] 17 | path = docker/aws-nitro-enclaves-nsm-api 18 | url = https://github.com/aws/aws-nitro-enclaves-nsm-api.git 19 | [submodule "enclave/boringssl"] 20 | path = enclave/boringssl 21 | url = https://boringssl.googlesource.com/boringssl 22 | [submodule "enclave/tinycbor"] 23 | path = enclave/tinycbor 24 | url = https://github.com/intel/tinycbor 25 | [submodule "enclave/sev-guest"] 26 | path = enclave/sev-guest 27 | url = https://github.com/AMDESE/sev-guest/ 28 | [submodule "enclave/rapidjson"] 29 | path = enclave/rapidjson 30 | url = https://github.com/Tencent/rapidjson 31 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Reporting a Vulnerability 2 | 3 | If you've found a security vulnerability in this repository, 4 | please report it via email to . 5 | 6 | Please only use this address to report security flaws in the Signal application (including this 7 | repository). For questions, support, or feature requests concerning the app, please submit a 8 | [support request][] or join the [unofficial community forum][]. 9 | 10 | [support request]: https://support.signal.org/hc/requests/new 11 | [unofficial community forum]: https://community.signalusers.org/ 12 | -------------------------------------------------------------------------------- /check_copyrights.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OUT=0 4 | for pattern in '*.c' '*.cc' '*.h' '*.go' '*.proto' '*.sh' 'Makefile*'; do 5 | for file in `find ./ -name $pattern -type f | grep -v \\.cargo | 6 | grep -v -f <(cat .gitmodules | 7 | grep path | 8 | awk '{print $3}') | 9 | egrep -v 'gopath|enclave/build|host/enclave/c'`; do 10 | if ! grep -q Copyright $file; then 11 | OUT=1 12 | echo "Missing copyright in '$file'" 1>&2 13 | fi 14 | done 15 | done 16 | exit $OUT 17 | -------------------------------------------------------------------------------- /docker/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /docker/apt.conf: -------------------------------------------------------------------------------- 1 | Apt { 2 | Architecture "amd64"; 3 | Architectures "amd64"; 4 | }; 5 | 6 | Acquire::Check-Valid-Until "false"; 7 | Acquire::Languages "none"; 8 | Binary::apt-get::Acquire::AllowInsecureRepositories "false"; 9 | 10 | APT::Install-Recommends "false"; 11 | 12 | // go easy on snapshot.debian.org 13 | Acquire::http::Dl-Limit "10000"; 14 | Acquire::https::Dl-Limit "10000"; 15 | Acquire::Retries "5"; 16 | -------------------------------------------------------------------------------- /docker/build_eif.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | STDOUT=$(mktemp) 5 | TMPDIR=$(mktemp -d) 6 | nitro-cli build-enclave --docker-uri ${DOCKER_IMAGE} --output-file ${TMPDIR}/svr2.eif >$STDOUT 7 | chown ${CHOWN_TO} ${TMPDIR}/svr2.eif 8 | cp -vn ${TMPDIR}/svr2.eif ${OUTPUT_DIR}/nitro.$(cat $STDOUT | jq -r '.Measurements.PCR0[:8] + "." + .Measurements.PCR1[:8] + "." + .Measurements.PCR2[:8]').eif 9 | -------------------------------------------------------------------------------- /docker/clang+llvm-11.1.0-x86_64-linux-gnu-ubuntu-20.10.tar.xz.sha256: -------------------------------------------------------------------------------- 1 | 29b07422da4bcea271a88f302e5f84bd34380af137df18e33251b42dd20c26d7 clang+llvm-11.1.0-x86_64-linux-gnu-ubuntu-20.10.tar.xz 2 | -------------------------------------------------------------------------------- /docker/ms.sources.list: -------------------------------------------------------------------------------- 1 | deb [arch=amd64] https://packages.microsoft.com/ubuntu/22.04/prod jammy main 2 | -------------------------------------------------------------------------------- /docker/nitro_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x -e -o pipefail 3 | 4 | echo "Available releases:" 5 | ls -la /releases/ 6 | 7 | ENCLAVE_INFO="$(mktemp)" 8 | if ! nitro-cli run-enclave --eif-path "$ENCLAVE_PATH" --cpu-count ${CPUS:-2} --memory ${RAM:-1024} | tee $ENCLAVE_INFO; then 9 | echo "ERROR RUNNING ENCLAVE" 10 | for file in /var/log/nitro_enclaves/err*.log; do 11 | echo "=== Error file: $file ===" 12 | cat $file 13 | done 14 | exit 1 15 | fi 16 | 17 | ENCLAVE_CID="$(cat $ENCLAVE_INFO | jq .EnclaveCID)" 18 | exec /bin/svr2 --enclave_type nitro --nitro_cid "$ENCLAVE_CID" --nitro_port 27427 "$@" 19 | -------------------------------------------------------------------------------- /docker/sample_sgx_default_qcnl_azure.conf: -------------------------------------------------------------------------------- 1 | { 2 | // Sample config for the DCAP quote provider library that should be provided at /etc/sgx_default_qcnl.conf. See 3 | // https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/main/QuoteGeneration/qcnl/linux/sgx_default_qcnl.conf 4 | // https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/main/QuoteGeneration/qcnl/linux/sgx_default_qcnl_azure.conf 5 | 6 | // PCCS server address, where to fetch PCS certificates. On azure, this should be the global 7 | // azure Trusted Hardware Identity Management cache 8 | "pccs_url": "https://global.acccache.azure.net/sgx/certification/v4/" 9 | 10 | // Where to fetch PCS collateral 11 | "collateral_service": "https://api.trustedservices.intel.com/sgx/certification/v4/", 12 | 13 | "pccs_api_version": "3.1", 14 | 15 | "retry_times": 6, 16 | 17 | "retry_delay": 5, 18 | 19 | // If local_pck_url is defined, the QCNL will try to retrieve PCK cert chain from local_pck_url 20 | // first, and failover to pccs_url as in legacy mode. On azure this should be the local 21 | // metadata Trusted Hardware Identity Management cache 22 | "local_pck_url": "http://169.254.169.254/metadata/THIM/sgx/certification/v4/", 23 | 24 | "pck_cache_expire_hours": 48, 25 | 26 | // custom request headers and parameters to the get certificate API 27 | "custom_request_options" : { 28 | "get_cert" : { 29 | "headers": { 30 | "metadata": "true" 31 | }, 32 | "params": { 33 | "api-version": "2021-07-22-preview" 34 | } 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /docker/sgx.sources.list: -------------------------------------------------------------------------------- 1 | deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu jammy main 2 | -------------------------------------------------------------------------------- /docker/sgx_runtime_libraries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux -o pipefail 3 | 4 | apt-get update 5 | apt-get install -y \ 6 | gpg \ 7 | gnupg2 \ 8 | wget \ 9 | software-properties-common \ 10 | debian-archive-keyring \ 11 | ## apt-get install 12 | echo "deb [arch=amd64] https://packages.microsoft.com/ubuntu/22.04/prod jammy main" | tee /etc/apt/sources.list.d/msprod.list 13 | echo "deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu jammy main" | tee /etc/apt/sources.list.d/sgx.list 14 | 15 | wget -qO - https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | apt-key add - 16 | wget -qO - https://packages.microsoft.com/keys/microsoft.asc | apt-key add - 17 | 18 | apt-get update 19 | apt-get -y install \ 20 | libsgx-ae-epid=2.25.100.3-jammy1 \ 21 | libsgx-ae-id-enclave=1.22.100.3-jammy1 \ 22 | libsgx-ae-le=2.25.100.3-jammy1 \ 23 | libsgx-ae-pce=2.25.100.3-jammy1 \ 24 | libsgx-ae-qe3=1.22.100.3-jammy1 \ 25 | libsgx-ae-qve=1.22.100.3-jammy1 \ 26 | libsgx-aesm-ecdsa-plugin=2.25.100.3-jammy1 \ 27 | libsgx-aesm-epid-plugin=2.25.100.3-jammy1 \ 28 | libsgx-aesm-launch-plugin=2.25.100.3-jammy1 \ 29 | libsgx-aesm-pce-plugin=2.25.100.3-jammy1 \ 30 | libsgx-aesm-quote-ex-plugin=2.25.100.3-jammy1 \ 31 | libsgx-dcap-default-qpl=1.22.100.3-jammy1 \ 32 | libsgx-dcap-default-qpl-dev=1.22.100.3-jammy1 \ 33 | libsgx-dcap-ql=1.22.100.3-jammy1 \ 34 | libsgx-dcap-ql-dev=1.22.100.3-jammy1 \ 35 | libsgx-dcap-quote-verify=1.22.100.3-jammy1 \ 36 | libsgx-enclave-common=2.25.100.3-jammy1 \ 37 | libsgx-headers=2.25.100.3-jammy1 \ 38 | libsgx-launch=2.25.100.3-jammy1 \ 39 | libsgx-pce-logic=1.22.100.3-jammy1 \ 40 | libsgx-qe3-logic=1.22.100.3-jammy1 \ 41 | libsgx-quote-ex=2.25.100.3-jammy1 \ 42 | libsgx-urts=2.25.100.3-jammy1 \ 43 | sgx-aesm-service=2.25.100.3-jammy1 \ 44 | ## apt-get install 45 | 46 | apt-get clean 47 | -------------------------------------------------------------------------------- /docker/sources.list: -------------------------------------------------------------------------------- 1 | deb http://snapshot.debian.org/archive/debian/20250312T000000Z/ bookworm main 2 | deb http://snapshot.debian.org/archive/debian/20250312T000000Z/ bookworm-updates main 3 | 4 | # for old libprotobuf 5 | deb http://snapshot.debian.org/archive/debian/20250312T000000Z/ bullseye main 6 | -------------------------------------------------------------------------------- /docs/svr3spec/.gitignore: -------------------------------------------------------------------------------- 1 | svr3.aux 2 | svr3.bbl 3 | svr3.blg 4 | svr3.log 5 | svr3.out -------------------------------------------------------------------------------- /docs/svr3spec/README.md: -------------------------------------------------------------------------------- 1 | ## Building the PDF 2 | 3 | To build the pdf from source, you will need to install pdflatex and bibtex - the [TeXLive](https://www.tug.org/texlive/) distribution is probably the simplest way to do this. Alternatively tou can use an online system like [OVerleaf](https://overleaf.com) that will take care of most of the LaTeX related headaches for you. 4 | 5 | With these installed, run the following commands: 6 | ``` 7 | pdflatex svr3.tex # produces initial pdf and computes references needed in svr3.aux 8 | bibtex svr3 # builds bibliography 9 | pdflatex svr3.tex # incorporates bilbiography into the pdf 10 | ``` 11 | If prompted for input during the last run of pdflatex, press "enter" to continue. 12 | -------------------------------------------------------------------------------- /docs/svr3spec/svr3.bib: -------------------------------------------------------------------------------- 1 | 2 | @misc{jkkx, 3 | author = {Stanislaw Jarecki and Aggelos Kiayias and Hugo Krawczyk and Jiayu Xu}, 4 | title = {Highly-Efficient and Composable Password-Protected Secret Sharing (Or: How to Protect Your Bitcoin Wallet Online)}, 5 | howpublished = {Cryptology ePrint Archive, Paper 2016/144}, 6 | year = {2016}, 7 | note = {\url{https://eprint.iacr.org/2016/144}}, 8 | url = {https://eprint.iacr.org/2016/144} 9 | } 10 | 11 | @misc{poprf, 12 | author = {Nirvan Tyagi and Sofı́a Celi and Thomas Ristenpart and Nick Sullivan and Stefano Tessaro and Christopher A. Wood}, 13 | title = {A Fast and Simple Partially Oblivious PRF, with Applications}, 14 | howpublished = {Cryptology ePrint Archive, Paper 2021/864}, 15 | year = {2021}, 16 | note = {\url{https://eprint.iacr.org/2021/864}}, 17 | url = {https://eprint.iacr.org/2021/864} 18 | } 19 | 20 | @book{bonehshoup, 21 | author = {Dan Boneh and Victor Shoup}, 22 | title = {A Graduate Course in Applied Cryptography}, 23 | note = {\url{https://toc.cryptobook.us/book.pdf}}, 24 | url = {https://toc.cryptobook.us/book.pdf} 25 | } 26 | @misc{2hashdh, 27 | author = {Stanislaw Jarecki and Aggelos Kiayias and Hugo Krawczyk}, 28 | title = {Round-Optimal Password-Protected Secret Sharing and T-PAKE in the Password-Only Model}, 29 | howpublished = {Cryptology ePrint Archive, Paper 2014/650}, 30 | year = {2014}, 31 | note = {\url{https://eprint.iacr.org/2014/650}}, 32 | url = {https://eprint.iacr.org/2014/650} 33 | } 34 | 35 | @misc {ietf-oprf, 36 | author = {A. Davidson, A. Faz-Hernandez, N. Sullivan, C. A. Wood}, 37 | title = {Oblivious Pseudorandom Functions (OPRFs) using Prime-Order Groups}, 38 | url = {https://www.ietf.org/id/draft-irtf-cfrg-voprf-21.html#name-informative-references-7}, 39 | note = {\url{https://www.ietf.org/id/draft-irtf-cfrg-voprf-21.html}} 40 | } -------------------------------------------------------------------------------- /docs/svr3spec/svr3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalapp/SecureValueRecovery2/9d5df31e6a6616f1d91d953e89f219cf1b211b34/docs/svr3spec/svr3.pdf -------------------------------------------------------------------------------- /enclave/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .testdepends 3 | *.pem 4 | noise-c/ 5 | -------------------------------------------------------------------------------- /enclave/Makefile.HOST: -------------------------------------------------------------------------------- 1 | include Makefile.base 2 | 3 | CFLAGS ?= $(HOST_CFLAGS) 4 | CXXFLAGS ?= $(HOST_CXXFLAGS) 5 | -------------------------------------------------------------------------------- /enclave/Makefile.SGX: -------------------------------------------------------------------------------- 1 | include Makefile.base 2 | 3 | CFLAGS ?= $(SGX_CFLAGS) 4 | CXXFLAGS ?= $(SGX_CXXFLAGS) 5 | -------------------------------------------------------------------------------- /enclave/Makefile.TEST: -------------------------------------------------------------------------------- 1 | include Makefile.base 2 | 3 | CFLAGS ?= $(TEST_CFLAGS) 4 | CXXFLAGS ?= $(TEST_CXXFLAGS) 5 | -------------------------------------------------------------------------------- /enclave/Makefile.X86: -------------------------------------------------------------------------------- 1 | include Makefile.base 2 | 3 | CFLAGS ?= $(X86_CFLAGS) 4 | CXXFLAGS ?= $(X86_CXXFLAGS) 5 | -------------------------------------------------------------------------------- /enclave/Makefile.subdir: -------------------------------------------------------------------------------- 1 | include Makefile.$(ENV) 2 | 3 | all: 4 | .PHONY: all 5 | 6 | BUILD = build/$(DIR) 7 | $(BUILD): 8 | $(QUIET) echo -e "MKDIR\t$@" 9 | $(QUIET) mkdir -p $(BUILD) 10 | 11 | # We use WARNING_CFLAGS only when the file exists, is not a symlink, 12 | # and isn't generated code (IE: it's not in build/...) 13 | NO_WARNINGS=-w 14 | 15 | OBJECTS=$(patsubst %,build/%.$(ENV).o,$(wildcard $(DIR)/*.c) $(wildcard $(DIR)/*.cc)) $(patsubst %,%.$(ENV).o,$(wildcard $(BUILD)/*.c) $(wildcard $(BUILD)/*.cc)) 16 | 17 | $(BUILD)/%.cc.$(ENV).d $(BUILD)/%.cc.$(ENV).o: $(BUILD)/%.cc | $(BUILD) 18 | $(QUIET) echo -e "CXX\t$<.$(ENV)" 19 | $(QUIET) $(CXX) -c -o $(BUILD)/$*.cc.$(ENV).o $(CXXFLAGS) -MF $(BUILD)/$*.cc.$(ENV).d -MMD $< $(NO_WARNINGS) 20 | 21 | $(BUILD)/%.c.$(ENV).d $(BUILD)/%.c.$(ENV).o: $(BUILD)/%.c | $(BUILD) 22 | $(QUIET) echo -e "CC\t$<.$(ENV)" 23 | $(QUIET) $(CC) -c -o $(BUILD)/$*.c.$(ENV).o $(CFLAGS) -MF $(BUILD)/$*.c.$(ENV).d -MMD $< $(NO_WARNINGS) 24 | 25 | $(BUILD)/%.cc.$(ENV).d $(BUILD)/%.cc.$(ENV).o: $(DIR)/%.cc | $(BUILD) 26 | $(QUIET) echo -e "CXX\t$<.$(ENV)" 27 | $(QUIET) $(CXX) -c -o $(BUILD)/$*.cc.$(ENV).o $(CXXFLAGS) -MF $(BUILD)/$*.cc.$(ENV).d -MMD $< \ 28 | $(shell if [ ! -L $(DIR)/$*.cc ]; then echo $(WARNING_CFLAGS); else echo $(NO_WARNINGS); fi) 29 | 30 | $(BUILD)/%.c.$(ENV).d $(BUILD)/%.c.$(ENV).o: $(DIR)/%.c | $(BUILD) 31 | $(QUIET) echo -e "CC\t$<.$(ENV)" 32 | $(QUIET) $(CC) -c -o $(BUILD)/$*.c.$(ENV).o $(CFLAGS) -MF $(BUILD)/$*.c.$(ENV).d -MMD $< \ 33 | $(shell if [ ! -L $(DIR)/$*.c ]; then echo $(WARNING_CFLAGS); else echo $(NO_WARNINGS); fi) 34 | 35 | $(BUILD)/$(ENV).a: $(OBJECTS) | $(BUILD) 36 | $(QUIET) echo -e "AR\t$@" 37 | $(QUIET) ar rcs $@ $^ 38 | 39 | $(foreach f,$(patsubst %.o,%.d,$(OBJECTS)),$(eval include $f)) 40 | 41 | all: $(BUILD)/$(ENV).a 42 | -------------------------------------------------------------------------------- /enclave/attestation/nitro/nitro.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ATTESTATION_NITRO_NITRO_H__ 5 | #define __SVR2_ATTESTATION_NITRO_NITRO_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "proto/error.pb.h" 16 | #include "util/ticks.h" 17 | 18 | namespace svr2::attestation::nitro { 19 | 20 | typedef std::string TextString; 21 | typedef std::vector ByteString; 22 | 23 | struct CoseSign1 { 24 | ByteString protected_header; 25 | // nitro has no unprotected header 26 | ByteString payload; 27 | ByteString signature; 28 | 29 | void Clear(); 30 | bool Valid() const; 31 | error::Error ParseFromBytes(const uint8_t* in, size_t size); 32 | std::pair SigningBytes() const; 33 | }; 34 | 35 | struct AttestationDoc { 36 | std::string module_id; 37 | std::string digest; 38 | int64_t timestamp; 39 | std::map> pcrs; 40 | std::vector certificate; 41 | std::vector> cabundle; 42 | std::vector public_key; 43 | std::vector user_data; 44 | std::vector nonce; 45 | 46 | void Clear(); 47 | std::pair, error::Error> Certificate() const; 48 | std::pair, error::Error> CABundle() const; 49 | 50 | // Valid returns true if this AttestationDoc passes simple checks for validity. 51 | // It does not perform heavier-handed checks like parsing certificates; that's 52 | // done as part of verification. 53 | bool Valid() const; 54 | error::Error ParseFromBytes(const uint8_t* in, size_t size); 55 | }; 56 | 57 | error::Error Verify(const AttestationDoc& doc, const CoseSign1& from, util::UnixSecs now); 58 | 59 | } // namespace svr2::attestation::nitro 60 | 61 | #endif // __SVR2_ATTESTATION_NITRO_NITRO_H__ 62 | -------------------------------------------------------------------------------- /enclave/attestation/oe/attestation.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ATTESTATION_ATTESTATION_H__ 5 | #define __SVR2_ATTESTATION_ATTESTATION_H__ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "proto/error.pb.h" 13 | 14 | 15 | namespace svr2::attestation { 16 | 17 | extern const oe_uuid_t sgx_remote_uuid; 18 | 19 | /** 20 | * Helper function used to make the claim-finding process more convenient. Given 21 | * the claim name, claim list, and its size, returns the claim with that claim 22 | * name in the list. 23 | */ 24 | const oe_claim_t* FindClaim(const oe_claim_t* claims, size_t claims_size, 25 | const char* name); 26 | /** 27 | * Deserializes Open Enclave format custom claims then finds, validates, 28 | * and returns the public key claim. 29 | * 30 | * claims serialized OpenEnclave claims 31 | * claims_length number of claims 32 | * out: array where public key will be written 33 | * returns Env_CustomClaimsMissing, Env_CustomClaimsDeserialize, 34 | * Env_AttestationPubkeyMissing, Env_AttestationPubkeyInvalidSize 35 | */ 36 | error::Error ReadKeyFromVerifiedClaims(oe_claim_t* claims, size_t claims_length, 37 | std::array* out); 38 | 39 | /** 40 | * Verifies evidence and endorsements and returns the parsed array 41 | * of claims in Open Enclave format. 42 | * 43 | * The returned pointer most be freed with `oe_free_claims` 44 | */ 45 | std::pair VerifyAndReadClaims( 46 | const std::string& evidence, const std::string& endorsements); 47 | 48 | }; // namespace svr2::attestation 49 | 50 | 51 | #endif // __SVR2_ATTESTATION_ATTESTATION_H__ 52 | -------------------------------------------------------------------------------- /enclave/attestation/sev/sev.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ATTESTATION_SEV_SEV_H__ 5 | #define __SVR2_ATTESTATION_SEV_SEV_H__ 6 | 7 | #include 8 | #include 9 | #include "proto/error.pb.h" 10 | #include "proto/sev.pb.h" 11 | #include "proto/minimums.pb.h" 12 | #include "env/env.h" 13 | #include "util/ticks.h" 14 | 15 | namespace svr2::attestation::sev { 16 | 17 | #include 18 | 19 | // Read in an SevSnpEndorsements proto from the file with the given filename. 20 | // Returns true if this is successful, false on error. 21 | bool EndorsementsFromFile(const char* filename, SevSnpEndorsements* endorsements); 22 | 23 | // CertificatesToEndorsements parses certificates provided via SNP_GET_EXT_REPORT. 24 | // If it finds any, it overwrites the *endorsements fields with what it finds. 25 | error::Error CertificatesToEndorsements(const uint8_t* certs, uint32_t certs_size, SevSnpEndorsements* endorsements); 26 | 27 | // Pulls an SEV-SNP attestation report from the given attestation object, 28 | // without doing any crypto verification. 29 | std::pair ReportFromUnverifiedAttestation(const e2e::Attestation& attestation); 30 | 31 | std::pair ReportFromVerifiedBuffer(const std::string& buffer, const SevSnpEndorsements& endorsements, util::UnixSecs now); 32 | 33 | // Pulls a public key from the given attestation, verifying that attestation 34 | // as much as possible in the process. Will return an error if the attestation 35 | // is invalid, does not match the given local attestation in the necessary ways, 36 | // or does not verify against known AMD public keys. 37 | std::pair DataFromVerifiedAttestation(const attestation_report& local, const e2e::Attestation& attestation, util::UnixSecs now); 38 | 39 | minimums::MinimumValues MinimumsFromReport(const attestation_report& report); 40 | 41 | } // namespace svr2::attestation::sev 42 | 43 | std::ostream& operator<<(std::ostream& os, const ::svr2::attestation::sev::attestation_report& report); 44 | 45 | #endif // __SVR2_ATTESTATION_SEV_SEV_H__ 46 | -------------------------------------------------------------------------------- /enclave/context/context.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "context/context.h" 5 | #include "metrics/metrics.h" 6 | #include "util/cpu.h" 7 | #include "util/macros.h" 8 | 9 | namespace svr2::context { 10 | 11 | Context::Context() : cpu_current_(nullptr), cpu_top_(nullptr, COUNTER(context, cpu_uncategorized)) { 12 | cpu_top_.SetContext(this); 13 | } 14 | 15 | CPUMeasurement::CPUMeasurement(Context* ctx, metrics::Counter* counter) 16 | : ctx_(nullptr), counter_(counter), ticks_(util::asm_rdtsc()) { 17 | if (ctx != nullptr) { 18 | SetContext(ctx); 19 | } 20 | } 21 | 22 | CPUMeasurement::~CPUMeasurement() { 23 | uint64_t ticks = util::asm_rdtsc(); 24 | if (counter_ != nullptr) { 25 | counter_->IncrementBy(ticks - ticks_); 26 | } 27 | if (parent_ != nullptr) { 28 | parent_->ticks_ = ticks; 29 | } 30 | ctx_->cpu_current_ = parent_; 31 | } 32 | 33 | void CPUMeasurement::SetContext(Context* ctx) { 34 | CHECK(ctx_ == nullptr); 35 | ctx_ = ctx; 36 | parent_ = ctx_->cpu_current_; 37 | ctx_->cpu_current_ = this; 38 | if (parent_ != nullptr && parent_->counter_ != nullptr) { 39 | // If there's a parent CPUMeasurement, increment its ticks-so-far. 40 | // When we're destroyed, we'll push parent_->ticks_ forward so ticks 41 | // during our lifetime are not double-counted. 42 | parent_->counter_->IncrementBy(ticks_ - parent_->ticks_); 43 | } 44 | } 45 | 46 | CPUMeasurement Context::MeasureCPU(metrics::Counter* counter) { 47 | return CPUMeasurement(this, counter); 48 | } 49 | 50 | } // namespace svr2::context 51 | -------------------------------------------------------------------------------- /enclave/context/tests/acquire_lock.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP context 6 | //TESTDEP metrics 7 | //TESTDEP proto 8 | //TESTDEP util 9 | //TESTDEP env 10 | //TESTDEP libsodium 11 | //TESTDEP protobuf-lite 12 | #include 13 | #include "context/context.h" 14 | #include "util/macros.h" 15 | #include "util/mutex.h" 16 | #include 17 | #include 18 | #include 19 | 20 | namespace svr2::util { 21 | 22 | class AcquireLockTest : public ::testing::Test { 23 | public: 24 | util::mutex mu; 25 | int in_use GUARDED_BY(mu) = 0; 26 | 27 | static void* AcquireAndSleep(void* in) { 28 | auto t = (AcquireLockTest*) in; 29 | context::Context ctx; 30 | ACQUIRE_LOCK(t->mu, &ctx, lock_test); 31 | t->in_use++; 32 | for (int i = 0; i < 10; i++) { 33 | usleep(100000); 34 | CHECK(t->in_use == 1); 35 | } 36 | t->in_use--; 37 | CHECK(t->in_use == 0); 38 | return NULL; 39 | } 40 | 41 | static void* AcquireNamedAndSleep(void* in) { 42 | auto t = (AcquireLockTest*) in; 43 | context::Context ctx; 44 | ACQUIRE_NAMED_LOCK(lock, t->mu, &ctx, lock_test); 45 | t->in_use++; 46 | for (int i = 0; i < 10; i++) { 47 | usleep(100000); 48 | CHECK(t->in_use == 1); 49 | } 50 | t->in_use--; 51 | CHECK(t->in_use == 0); 52 | return NULL; 53 | } 54 | }; 55 | 56 | TEST_F(AcquireLockTest, Unnamed) { 57 | pthread_t t1, t2, t3, t4; 58 | auto start = time(NULL); 59 | CHECK(0 == pthread_create(&t1, NULL, &AcquireLockTest::AcquireAndSleep, this)); 60 | CHECK(0 == pthread_create(&t2, NULL, &AcquireLockTest::AcquireNamedAndSleep, this)); 61 | CHECK(0 == pthread_create(&t3, NULL, &AcquireLockTest::AcquireAndSleep, this)); 62 | CHECK(0 == pthread_create(&t4, NULL, &AcquireLockTest::AcquireNamedAndSleep, this)); 63 | CHECK(0 == pthread_join(t1, NULL)); 64 | CHECK(0 == pthread_join(t2, NULL)); 65 | CHECK(0 == pthread_join(t3, NULL)); 66 | CHECK(0 == pthread_join(t4, NULL)); 67 | auto diff = time(NULL) - start; 68 | ASSERT_GE(diff, 3); 69 | ASSERT_LE(diff, 5); 70 | } 71 | 72 | } // namespace svr2::util 73 | -------------------------------------------------------------------------------- /enclave/context/tests/measure_cpu.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP context 6 | //TESTDEP util 7 | //TESTDEP metrics 8 | //TESTDEP env 9 | //TESTDEP libsodium 10 | //TESTDEP proto 11 | //TESTDEP protobuf-lite 12 | #include 13 | #include "context/context.h" 14 | 15 | namespace svr2::context { 16 | 17 | class MeasureCPUTest : public ::testing::Test { 18 | }; 19 | 20 | TEST_F(MeasureCPUTest, Unnamed) { 21 | // Make sure we don't cause any null-ptr exceptions, even 22 | // with repeated/interspersed IGNORE_CPU calls. 23 | context::Context ctx; 24 | MEASURE_CPU(&ctx, lock_test); 25 | IGNORE_CPU(&ctx); 26 | IGNORE_CPU(&ctx); 27 | MEASURE_CPU(&ctx, lock_test); 28 | MEASURE_CPU(&ctx, lock_test); 29 | MEASURE_CPU(&ctx, lock_test); 30 | IGNORE_CPU(&ctx); 31 | MEASURE_CPU(&ctx, lock_test); 32 | IGNORE_CPU(&ctx); 33 | } 34 | 35 | } // namespace svr2::util 36 | -------------------------------------------------------------------------------- /enclave/core/internal.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_CORE_INTERNAL_H__ 5 | #define __SVR2_CORE_INTERNAL_H__ 6 | 7 | #include 8 | 9 | #include "raft/log.h" 10 | #include "raft/raft.h" 11 | #include "db/db.h" 12 | #include "proto/e2e.pb.h" 13 | #include "proto/msgs.pb.h" 14 | #include "proto/raft.pb.h" 15 | 16 | namespace svr2::core::internal { 17 | 18 | typedef uint64_t TransactionID; 19 | 20 | struct WaitingForFirstConnection { 21 | peerid::PeerID peer; 22 | TransactionID join_tx; 23 | }; 24 | struct Loading { 25 | enclaveconfig::RaftGroupConfig group_config; 26 | raft::ReplicaGroup replica_group; 27 | std::unique_ptr log; 28 | std::unique_ptr db; 29 | std::unique_ptr mem; 30 | peerid::PeerID load_from; 31 | TransactionID join_tx; 32 | bool started; 33 | uint64_t replication_id; 34 | uint64_t replication_sequence; 35 | std::string lexigraphically_largest_row_loaded_into_db; 36 | }; 37 | struct Loaded { 38 | enclaveconfig::RaftGroupConfig group_config; 39 | std::unique_ptr raft; 40 | std::unique_ptr db; 41 | raft::LogIdx db_last_applied_log; 42 | std::unique_ptr db_last_applied_log_leaf; 43 | 44 | error::Error VerifyLastAppliedLog(context::Context* ctx); 45 | void UpdateLastAppliedLog(context::Context* ctx, raft::LogIdx idx); 46 | }; 47 | struct Raft { 48 | Raft() { ClearState(); } 49 | void ClearState() REQUIRES(mu) { 50 | state = svr2::RAFTSTATE_NO_STATE; 51 | waiting_for_first_connection = { 52 | .peer = peerid::PeerID(), 53 | .join_tx = 0, 54 | }; 55 | loading = { 56 | .group_config = enclaveconfig::RaftGroupConfig(), 57 | .replica_group = raft::ReplicaGroup(), 58 | .log = nullptr, 59 | .db = nullptr, 60 | .join_tx = 0, 61 | .started = false, 62 | .replication_sequence = 0, 63 | .lexigraphically_largest_row_loaded_into_db = "", 64 | }; 65 | loaded = { 66 | .group_config = enclaveconfig::RaftGroupConfig(), 67 | .raft = nullptr, 68 | .db = nullptr, 69 | .db_last_applied_log = 0, 70 | .db_last_applied_log_leaf = nullptr, 71 | }; 72 | } 73 | mutable util::mutex mu; // protects everything else in this struct. 74 | RaftState state GUARDED_BY(mu); 75 | WaitingForFirstConnection waiting_for_first_connection GUARDED_BY(mu); 76 | Loading loading GUARDED_BY(mu); 77 | Loaded loaded GUARDED_BY(mu); 78 | }; 79 | 80 | } // namespace svr2::core::internal 81 | #endif // __SVR2_CORE_INTERNAL_H__ 82 | -------------------------------------------------------------------------------- /enclave/db/db.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "db/db.h" 5 | #include "db/db2.h" 6 | #include "db/db3.h" 7 | #include "db/db4.h" 8 | 9 | #include 10 | 11 | namespace svr2::db { 12 | 13 | const std::string DB::GLOBAL_KEY(""); 14 | 15 | const DB::Protocol* DB::P(enclaveconfig::DatabaseVersion version) { 16 | switch (version) { 17 | case enclaveconfig::DATABASE_VERSION_SVR2: 18 | return &db2_protocol; 19 | case enclaveconfig::DATABASE_VERSION_SVR3: 20 | return &db3_protocol; 21 | case enclaveconfig::DATABASE_VERSION_SVR4: 22 | return &db4_protocol; 23 | default: 24 | return nullptr; 25 | } 26 | } 27 | 28 | } // namespace svr2::db 29 | -------------------------------------------------------------------------------- /enclave/env/env.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ENV_ENV_H__ 5 | #define __SVR2_ENV_ENV_H__ 6 | 7 | #include 8 | #include 9 | #include "proto/error.pb.h" 10 | #include "proto/e2e.pb.h" 11 | #include "proto/msgs.pb.h" 12 | #include "proto/attestation.pb.h" 13 | #include "util/macros.h" 14 | #include "util/ticks.h" 15 | #include "context/context.h" 16 | 17 | namespace svr2::env { 18 | 19 | typedef std::array PublicKey; 20 | 21 | class Environment { 22 | public: 23 | DELETE_COPY_AND_ASSIGN(Environment); 24 | Environment(); 25 | virtual ~Environment() {} 26 | virtual void Init(); 27 | // Given a 32-byte key, return evidence of that key (an OpenEnclave report). 28 | virtual std::pair Evidence( 29 | context::Context* ctx, 30 | const attestation::AttestationData& attestation) const = 0; 31 | // Given evidence and endorsements, extract the key. 32 | virtual std::pair Attest( 33 | context::Context* ctx, 34 | util::UnixSecs now, 35 | const e2e::Attestation& attestation) const = 0; 36 | // Given a string of size N, rewrite all bytes in that string with 37 | // random bytes. 38 | virtual error::Error RandomBytes( 39 | void* bytes, 40 | size_t size) const = 0; 41 | // Send a message from enclave to host. [msg] should be a serialized 42 | // EnclaveMessage. 43 | virtual error::Error SendMessage(context::Context* ctx, const std::string& msg) const = 0; 44 | // Log a message to a logging framework. 45 | virtual void Log(int level, const std::string& msg) const = 0; 46 | // Update env-specific statistics. 47 | virtual error::Error UpdateEnvStats() const = 0; 48 | // FlushAllLogsIfAble attempts to log everything that's been seen by 49 | // Log() up to a place where operators can see it. 50 | virtual void FlushAllLogsIfAble() const = 0; 51 | virtual error::Error Metadata( 52 | context::Context* ctx, EnvMetadataRequest req, EnvMetadataResponse* resp) const { 53 | return error::Env_MetadataNotSupported; 54 | } 55 | }; 56 | 57 | extern std::unique_ptr environment; 58 | 59 | static const bool SIMULATED = true; 60 | static const bool NOT_SIMULATED = false; 61 | void Init(bool is_simulated); 62 | 63 | } // namespace svr2::env 64 | 65 | #endif // __SVR2_ENV_ENV_H__ 66 | -------------------------------------------------------------------------------- /enclave/env/socket/socket.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ENV_SOCKET_SOCKET_H__ 5 | #define __SVR2_ENV_SOCKET_SOCKET_H__ 6 | 7 | #include "socketwrap/socket.h" 8 | #include "proto/error.pb.h" 9 | #include "env/env.h" 10 | 11 | namespace svr2::env::socket { 12 | 13 | class Environment : public ::svr2::env::Environment { 14 | public: 15 | DELETE_COPY_AND_ASSIGN(Environment); 16 | Environment(bool simulated); 17 | virtual ~Environment(); 18 | virtual error::Error SendMessage(context::Context* ctx, const std::string& msg) const; 19 | virtual void Log(int level, const std::string& msg) const; 20 | virtual void FlushAllLogsIfAble() const; 21 | virtual error::Error RandomBytes(void* bytes, size_t size) const; 22 | 23 | protected: 24 | bool simulated() const { return simulated_; } 25 | std::pair SimulatedEvidence( 26 | context::Context* ctx, 27 | const attestation::AttestationData& data) const; 28 | std::pair SimulatedAttest( 29 | context::Context* ctx, 30 | util::UnixSecs now, 31 | const e2e::Attestation& attestation) const; 32 | 33 | 34 | private: 35 | bool simulated_; 36 | }; 37 | 38 | // Send all outstanding messages, in order, up to the host. 39 | // Blocks forever. 40 | error::Error SendSocketMessages(socketwrap::Socket* sock); 41 | error::Error SendOutboundMessage(context::Context* ctx, const google::protobuf::MessageLite& msg); 42 | 43 | } // namespace svr2::env::nsm 44 | 45 | #endif // __SVR2_ENV_SOCKET_SOCKET_H__ 46 | -------------------------------------------------------------------------------- /enclave/env/test/test.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ENV_TEST_TEST_H__ 5 | #define __SVR2_ENV_TEST_TEST_H__ 6 | 7 | #include 8 | #include 9 | #include "proto/msgs.pb.h" 10 | 11 | namespace svr2::env::test { 12 | 13 | std::vector SentMessages(); 14 | void ResetRandomNumberGenerator(); 15 | 16 | extern uint64_t minimums_test_version; 17 | 18 | } // namespace svr2::env::test 19 | 20 | #endif // __SVR2_ENV_TEST_TEST_H__ 21 | -------------------------------------------------------------------------------- /enclave/env/test/tests/testrand.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP util 6 | //TESTDEP env 7 | //TESTDEP env/test 8 | //TESTDEP context 9 | //TESTDEP metrics 10 | //TESTDEP proto 11 | //TESTDEP protobuf-lite 12 | //TESTDEP libsodium 13 | 14 | #include 15 | #include "env/env.h" 16 | #include "util/log.h" 17 | #include "util/hex.h" 18 | 19 | namespace svr2::env { 20 | 21 | TEST(EnvTest, Random) { 22 | Init(SIMULATED); 23 | uint8_t got[260]; 24 | ASSERT_EQ(error::OK, environment->RandomBytes(got, sizeof(got))); 25 | LOG(INFO) << "Bytes: " << util::BytesToHex(got, 8); 26 | uint8_t expect_first[] = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; 27 | ASSERT_EQ(0, memcmp(got, expect_first, sizeof(expect_first))); 28 | } 29 | 30 | } // namespace svr2::env 31 | -------------------------------------------------------------------------------- /enclave/find_header.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Given a compiler and a header file, return what directory that header is located in. 4 | # 5 | set -e 6 | if [[ $# != 2 ]]; then 7 | echo 1>&2 "Usage: $0
" 8 | exit 1 9 | fi 10 | COMPILER=$1 11 | HEADER=$2 12 | LISTING="" 13 | "$COMPILER" -E -x c++ - -v &1 | while read line 14 | do 15 | if [[ $line == "#include <...> search starts here:" ]]; then 16 | LISTING=1 17 | elif [[ $line == "End of search list." ]]; then 18 | exit 1 19 | elif [[ $LISTING != "" ]]; then 20 | if ls "$line/$HEADER" >/dev/null 2>/dev/null; then 21 | echo "$line" 22 | exit 0 23 | fi 24 | fi 25 | done 26 | -------------------------------------------------------------------------------- /enclave/fs/fs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_FS_FS_H__ 5 | #define __SVR2_FS_FS_H__ 6 | 7 | #include 8 | #include 9 | #include "proto/error.pb.h" 10 | #include "util/macros.h" 11 | 12 | namespace svr2::fs { 13 | 14 | std::pair FileContents(const std::string& filename); 15 | 16 | class TmpDir { 17 | public: 18 | DELETE_COPY_AND_ASSIGN(TmpDir); 19 | TmpDir() : name_("") {} 20 | TmpDir(TmpDir&& other) { 21 | name_ = other.name_; 22 | other.name_ = ""; 23 | } 24 | ~TmpDir(); 25 | error::Error Init(); 26 | const std::string& name() { return name_; } 27 | private: 28 | std::string name_; 29 | }; 30 | 31 | } // namespace svr2::fs 32 | 33 | #endif // __SVR2_FS_FS_H__ 34 | -------------------------------------------------------------------------------- /enclave/groupclock/groupclock.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "groupclock/groupclock.h" 5 | 6 | #include 7 | #include 8 | #include "util/log.h" 9 | 10 | namespace svr2::groupclock { 11 | 12 | void Clock::SetLocalTime(util::UnixSecs secs) { 13 | local_.store(secs); 14 | } 15 | 16 | void Clock::SetRemoteTime(context::Context* ctx, const peerid::PeerID& peer, util::UnixSecs secs) { 17 | ACQUIRE_LOCK(mu_, ctx, lock_groupclock); 18 | remotes_[peer] = secs; 19 | } 20 | 21 | util::UnixSecs Clock::GetTime(context::Context* ctx, const std::set& remotes) const { 22 | std::vector secs(1 /* local_ */ + remotes.size()); 23 | ACQUIRE_LOCK(mu_, ctx, lock_groupclock); 24 | auto set_iter = remotes.begin(); 25 | auto map_iter = remotes_.begin(); 26 | secs[0] = local_.load(); 27 | size_t secs_size = 1; 28 | while (set_iter != remotes.end() && map_iter != remotes_.end()) { 29 | const peerid::PeerID& set_peer = *set_iter; 30 | const peerid::PeerID& map_peer = map_iter->first; 31 | if (set_peer < map_peer) { 32 | ++set_iter; 33 | } else if (map_peer < set_peer) { 34 | ++map_iter; 35 | } else { 36 | secs[secs_size++] = map_iter->second; 37 | ++set_iter; 38 | ++map_iter; 39 | } 40 | } 41 | secs.resize(secs_size); 42 | // `secs` now contains a list of my timestamp and the timestamps of all 43 | // peers in `remotes` that we've received a timestamp from. Get the median. 44 | std::sort(secs.begin(), secs.end()); 45 | return secs[secs.size()/2]; 46 | } 47 | 48 | util::UnixSecs Clock::GetLocalTime() const { 49 | return local_.load(); 50 | } 51 | 52 | } // namespace svr2::groupclock 53 | -------------------------------------------------------------------------------- /enclave/groupclock/groupclock.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_GROUPCLOCK_GROUPCLOCK_H__ 5 | #define __SVR2_GROUPCLOCK_GROUPCLOCK_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "util/macros.h" 11 | #include "util/mutex.h" 12 | #include "util/ticks.h" 13 | #include "peerid/peerid.h" 14 | #include "context/context.h" 15 | 16 | namespace svr2::groupclock { 17 | 18 | // Clock that returns time based on times reported from a group of 19 | // peers. The reported time will be the median of all reported times. 20 | class Clock { 21 | public: 22 | DELETE_COPY_AND_ASSIGN(Clock); 23 | Clock() : local_(0) {}; 24 | void SetLocalTime(util::UnixSecs secs); 25 | void SetRemoteTime(context::Context* ctx, const peerid::PeerID& peer, util::UnixSecs secs) EXCLUDES(mu_); 26 | util::UnixSecs GetTime(context::Context* ctx, const std::set& remotes) const EXCLUDES(mu_); 27 | util::UnixSecs GetLocalTime() const; 28 | 29 | private: 30 | mutable util::mutex mu_; 31 | std::atomic local_; 32 | std::map remotes_ GUARDED_BY(mu_); 33 | }; 34 | 35 | } // namespace svr2::groupclock 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /enclave/groupclock/tests/groupclock.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP peerid 6 | //TESTDEP sip 7 | //TESTDEP sender 8 | //TESTDEP context 9 | //TESTDEP env 10 | //TESTDEP env/test 11 | //TESTDEP util 12 | //TESTDEP metrics 13 | //TESTDEP proto 14 | //TESTDEP protobuf-lite 15 | //TESTDEP libsodium 16 | 17 | #include 18 | #include "groupclock/groupclock.h" 19 | #include "env/env.h" 20 | #include "context/context.h" 21 | 22 | namespace svr2::groupclock { 23 | 24 | class ClockTest : public ::testing::Test { 25 | protected: 26 | static void SetUpTestCase() { 27 | env::Init(env::SIMULATED); 28 | } 29 | context::Context ctx; 30 | }; 31 | 32 | TEST_F(ClockTest, BasicUsage) { 33 | Clock c; 34 | EXPECT_EQ(0, c.GetTime(&ctx, std::set{})); 35 | c.SetLocalTime(1000); 36 | EXPECT_EQ(1000, c.GetTime(&ctx, std::set{})); 37 | peerid::PeerID p1((uint8_t[32]){1}); 38 | peerid::PeerID p2((uint8_t[32]){2}); 39 | peerid::PeerID p3((uint8_t[32]){3}); 40 | peerid::PeerID p4((uint8_t[32]){4}); 41 | c.SetRemoteTime(&ctx, p1, 1001); 42 | c.SetRemoteTime(&ctx, p2, 1002); 43 | c.SetRemoteTime(&ctx, p3, 1003); 44 | c.SetRemoteTime(&ctx, p4, 1004); 45 | EXPECT_EQ(1001, c.GetTime(&ctx, std::set{p1})); 46 | EXPECT_EQ(1001, c.GetTime(&ctx, std::set{p1, p2})); 47 | EXPECT_EQ(1002, c.GetTime(&ctx, std::set{p1, p2, p3})); 48 | EXPECT_EQ(1002, c.GetTime(&ctx, std::set{p1, p2, p3, p4})); 49 | c.SetLocalTime(1005); 50 | EXPECT_EQ(1003, c.GetTime(&ctx, std::set{p1, p2, p3, p4})); 51 | c.SetRemoteTime(&ctx, p1, 1004); 52 | EXPECT_EQ(1004, c.GetTime(&ctx, std::set{p1, p2, p3, p4})); 53 | } 54 | 55 | } // namespace svr2::groupclock 56 | -------------------------------------------------------------------------------- /enclave/gtest/gtest-all.cc: -------------------------------------------------------------------------------- 1 | ../googletest/googletest/src/gtest-all.cc -------------------------------------------------------------------------------- /enclave/gtest/gtest_main.cc: -------------------------------------------------------------------------------- 1 | ../googletest/googletest/src/gtest_main.cc -------------------------------------------------------------------------------- /enclave/hmac/hmac.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "hmac/hmac.h" 5 | #include 6 | #include 7 | 8 | namespace svr2::hmac { 9 | 10 | sha::Sha256Sum HmacSha256(const HmacSha256Key& key, const uint8_t* data_start, size_t data_size) { 11 | sha::Sha256Sum out; 12 | crypto_auth_hmacsha256(out.data(), data_start, data_size, key.data()); 13 | return out; 14 | } 15 | 16 | } // namespace svr2::hmac 17 | -------------------------------------------------------------------------------- /enclave/hmac/hmac.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_HMAC_HMAC_H__ 5 | #define __SVR2_HMAC_HMAC_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "sha/sha.h" 11 | 12 | namespace svr2::hmac { 13 | 14 | typedef std::array HmacSha256Key; 15 | 16 | sha::Sha256Sum HmacSha256(const HmacSha256Key& key, const uint8_t* data_start, size_t data_size); 17 | 18 | inline sha::Sha256Sum HmacSha256(const HmacSha256Key& key, const char* c_str) { 19 | return HmacSha256(key, reinterpret_cast(c_str), strlen(c_str)); 20 | } 21 | template 22 | sha::Sha256Sum HmacSha256(const HmacSha256Key& key, const T& data) { 23 | return HmacSha256(key, reinterpret_cast(data.data()), data.size()); 24 | } 25 | 26 | } // namespace svr2::hmac 27 | 28 | #endif // __SVR2_HMAC_HMAC_H__ 29 | -------------------------------------------------------------------------------- /enclave/hmac/tests/hmac.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP hmac 6 | //TESTDEP noise-c 7 | //TESTDEP libsodium 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include "hmac/hmac.h" 15 | 16 | namespace svr2::hmac { 17 | 18 | class HmacTest : public ::testing::Test { 19 | }; 20 | 21 | TEST_F(HmacTest, BasicUsage) { 22 | std::array key = { 23 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 24 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 25 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 26 | '1', '2'}; 27 | std::array out = HmacSha256(key, "abc"); 28 | 29 | // Python3: 30 | // >>> import base64 31 | // >>> import hmac 32 | // >>> import hashlib 33 | // >>> base64.b16encode(hmac.digest(b'12345678901234567890123456789012', b'abc', hashlib.sha256)) 34 | // b'26B7F4C64769835D3F654DC635D5362988C270883270E1EFD65372B5F3100BAF' 35 | std::array expected = { 36 | 0x26, 0xB7, 0xF4, 0xC6, 0x47, 0x69, 0x83, 0x5D, 37 | 0x3F, 0x65, 0x4D, 0xC6, 0x35, 0xD5, 0x36, 0x29, 38 | 0x88, 0xC2, 0x70, 0x88, 0x32, 0x70, 0xE1, 0xEF, 39 | 0xD6, 0x53, 0x72, 0xB5, 0xF3, 0x10, 0x0B, 0xAF, 40 | }; 41 | EXPECT_EQ(out, expected); 42 | } 43 | 44 | } // namespace svr2::hmac 45 | -------------------------------------------------------------------------------- /enclave/initmain/initmain.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "env/env.h" 5 | #include "proto/enclaveconfig.pb.h" 6 | #include "util/log.h" 7 | 8 | int main(int argc, char** argv) { 9 | svr2::util::SetLogLevel(svr2::enclaveconfig::LOG_LEVEL_VERBOSE); 10 | svr2::env::Init(svr2::env::NOT_SIMULATED); 11 | } 12 | -------------------------------------------------------------------------------- /enclave/metrics/gauges.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // This file contains all gauge metrics used within SVR2. 5 | // 6 | // They're created with the macro CREATE_GAUGE, which takes arguments: 7 | // * ns - namespace of the gauge (generally, module name) 8 | // * varname - name of the variable used to reference this gauge, must be 9 | // unique within the namespace (ns). Also the exported name. 10 | // 11 | // Once these gauges are created here, they're used with the incantation: 12 | // GAUGE(ns, varname)->GaugeFunction(); 13 | // IE: 14 | // GAUGE(sender, enclave_messages_sent)->Set(12); 15 | // 16 | // Gauges are only exported after their first Set call, to avoid sending up 17 | // spurious invalid values to metrics. If Clear is called, they will no longer 18 | // be exported. 19 | 20 | CREATE_GAUGE(raft, role) 21 | CREATE_GAUGE(raft, is_voting) 22 | CREATE_GAUGE(raft, current_term) 23 | CREATE_GAUGE(raft, commit_index) 24 | CREATE_GAUGE(raft, promise_index) 25 | CREATE_GAUGE(raft, log_oldest_stored_log_index) 26 | CREATE_GAUGE(raft, log_last_log_term) 27 | CREATE_GAUGE(raft, log_last_log_index) 28 | CREATE_GAUGE(raft, log_size) 29 | CREATE_GAUGE(raft, log_total_size) 30 | CREATE_GAUGE(raft, log_entries) 31 | 32 | CREATE_GAUGE(core, raft_state) 33 | CREATE_GAUGE(core, last_index_applied_to_db) 34 | CREATE_GAUGE(core, current_local_time) 35 | CREATE_GAUGE(core, current_groupclock_time) 36 | 37 | 38 | CREATE_GAUGE(peers, peers) 39 | 40 | CREATE_GAUGE(client, clients) 41 | 42 | CREATE_GAUGE(db, rows) 43 | 44 | CREATE_GAUGE(timeout, timeouts) 45 | 46 | CREATE_GAUGE(test, test1) 47 | CREATE_GAUGE(test, test2) 48 | 49 | CREATE_GAUGE(env, total_heap_size) 50 | CREATE_GAUGE(env, allocated_heap_size) 51 | CREATE_GAUGE(env, peak_heap_size) 52 | 53 | CREATE_GAUGE(socketwrap, output_buffer_size) 54 | CREATE_GAUGE(socketwrap, output_buffer_cap) 55 | -------------------------------------------------------------------------------- /enclave/minimums/minimums.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_WATERMARKS_WATERMARKS_H__ 5 | #define __SVR2_WATERMARKS_WATERMARKS_H__ 6 | 7 | #include 8 | #include 9 | #include "proto/minimums.pb.h" 10 | #include "proto/error.pb.h" 11 | #include "util/mutex.h" 12 | #include "util/bytes.h" 13 | #include "util/endian.h" 14 | #include "context/context.h" 15 | 16 | namespace svr2::minimums { 17 | class Minimums; 18 | } // namespace svr2::Minimums; 19 | 20 | std::ostream& operator<<(std::ostream& os, const svr2::minimums::Minimums& min); 21 | 22 | namespace svr2::minimums { 23 | 24 | class Minimums { 25 | public: 26 | Minimums() {} 27 | Minimums(const Minimums& cpy) { 28 | util::unique_lock cl(cpy.mu_); 29 | s_.MergeFrom(cpy.s_); 30 | } 31 | error::Error UpdateLimits(context::Context* ctx, const MinimumLimits& s); 32 | error::Error CheckValues(context::Context* ctx, const MinimumValues& values) const; 33 | static std::string U64(uint64_t value) { 34 | std::string v(8, '\0'); 35 | util::BigEndian64Bytes(value, reinterpret_cast(v.data())); 36 | return v; 37 | } 38 | static std::string U8(uint8_t value) { 39 | std::string v(1, value); 40 | return v; 41 | } 42 | // CombineMin combines two MinimumValues. In cases where both have the same 43 | // key, the minimum value is returned. Note that we choose the minimum 44 | // because this is _values_ we're combining. 45 | static void CombineValues(const MinimumValues& from, MinimumValues* into); 46 | private: 47 | friend std::ostream& ::operator<<(std::ostream& os, const svr2::minimums::Minimums& min); 48 | static error::Error CheckValuesAgainstSet(const MinimumLimits& s, const MinimumValues& values); 49 | static error::Error CheckValueAgainstSet(const MinimumLimits& s, const std::string& key, const std::string& value); 50 | 51 | mutable util::mutex mu_; 52 | MinimumLimits s_ GUARDED_BY(mu_); 53 | }; 54 | 55 | } // namespace svr2::minimums 56 | 57 | std::ostream& operator<<(std::ostream& os, const svr2::minimums::MinimumValues& val); 58 | std::ostream& operator<<(std::ostream& os, const svr2::minimums::MinimumLimits& lim); 59 | 60 | #endif // __SVR2_WATERMARKS_WATERMARKS_H__ 61 | -------------------------------------------------------------------------------- /enclave/noise/noise.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_NOISE_NOISE_H__ 5 | #define __SVR2_NOISE_NOISE_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "proto/error.pb.h" 14 | 15 | // This module provides simple RAII wrappers around noise-c pointers. 16 | // The pointers are exposed publicly as .state, allowing use of noise_* functions 17 | // directly on them, but with the guarantee that when the *State objects fall 18 | // out of scope, the correct noise_*_free function will be called on them. 19 | 20 | #include "util/macros.h" 21 | 22 | namespace svr2::noise { 23 | 24 | const size_t HANDSHAKE_INIT_SIZE = 4096; 25 | const size_t HANDSHAKE_HFS_INIT_SIZE = 4096; 26 | 27 | inline uint8_t* StrU8Ptr(std::string* s) { 28 | return reinterpret_cast(s->data()); 29 | } 30 | inline const uint8_t* StrU8Ptr(const std::string& s) { 31 | return reinterpret_cast(s.data()); 32 | } 33 | 34 | inline NoiseBuffer BufferOutputFromString(std::string* s) { 35 | NoiseBuffer b; 36 | noise_buffer_set_output(b, StrU8Ptr(s), s->size()); 37 | return b; 38 | } 39 | 40 | inline NoiseBuffer BufferInputFromString(std::string* s) { 41 | NoiseBuffer b; 42 | noise_buffer_set_input(b, StrU8Ptr(s), s->size()); 43 | return b; 44 | } 45 | 46 | inline NoiseBuffer BufferInoutFromString(std::string* s, size_t substr) { 47 | CHECK(substr <= s->size()); 48 | NoiseBuffer b; 49 | noise_buffer_set_inout(b, StrU8Ptr(s), substr, s->size()); 50 | return b; 51 | } 52 | 53 | typedef std::unique_ptr HandshakeState; 54 | inline HandshakeState WrapHandshakeState(NoiseHandshakeState* s) { 55 | return HandshakeState(s, noise_handshakestate_free); 56 | } 57 | 58 | typedef std::unique_ptr DHState; 59 | inline DHState WrapDHState(NoiseDHState* s) { 60 | return DHState(s, noise_dhstate_free); 61 | } 62 | 63 | DHState CloneDHState(const DHState& s); 64 | 65 | typedef std::unique_ptr CipherState; 66 | inline CipherState WrapCipherState(NoiseCipherState* s) { 67 | return CipherState(s, noise_cipherstate_free); 68 | } 69 | 70 | // Encrypt the given string. 71 | std::pair Encrypt(NoiseCipherState* cs, const std::string& plaintext); 72 | // Decrypt the given string. 73 | std::pair Decrypt(NoiseCipherState* cs, const std::string& ciphertext); 74 | 75 | } // namespace svr2::noise 76 | 77 | #endif // __SVR2_NOISE_NOISE_H__ 78 | -------------------------------------------------------------------------------- /enclave/noisewrap/tests/noisewrap.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP noise-c 6 | //TESTDEP noisewrap 7 | //TESTDEP util 8 | //TESTDEP env 9 | //TESTDEP env/test 10 | //TESTDEP metrics 11 | //TESTDEP context 12 | //TESTDEP proto 13 | //TESTDEP protobuf-lite 14 | //TESTDEP libsodium 15 | 16 | #include 17 | #include 18 | #include "env/env.h" 19 | #include "util/log.h" 20 | #include 21 | #include 22 | #include "util/hex.h" 23 | 24 | namespace svr2 { 25 | 26 | TEST(NoiseWrap, RandomnessIsWrappedDeterministically) { 27 | svr2::env::Init(env::SIMULATED); 28 | std::array out; 29 | ASSERT_EQ(NOISE_ERROR_NONE, noise_randstate_generate_simple(out.data(), out.size())); 30 | LOG(INFO) << "RAND: " << util::ToHex(out); 31 | uint8_t expect[8] = {0x4f, 0x6f, 0xa8, 0x48, 0x32, 0xaa, 0x7d, 0x32}; 32 | ASSERT_EQ(0, memcmp(out.data(), expect, 8)); 33 | } 34 | 35 | } // namespace svr2 36 | -------------------------------------------------------------------------------- /enclave/noisewrap/wrap.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "env/env.h" 5 | #include "proto/error.pb.h" 6 | #include "util/macros.h" 7 | #include "context/context.h" 8 | 9 | extern "C" { 10 | 11 | // Wrap Noise's call to get randomness so it uses our enclave's random generator. 12 | void __wrap_noise_rand_bytes(void* bytes, size_t size) { 13 | CHECK(::svr2::error::OK == ::svr2::env::environment->RandomBytes(bytes, size)); 14 | } 15 | 16 | } // extern "C" 17 | -------------------------------------------------------------------------------- /enclave/peerid/peerid.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include 5 | #include 6 | 7 | #include "peerid/peerid.h" 8 | #include "sip/halfsiphash.h" 9 | #include "util/log.h" 10 | #include "metrics/metrics.h" 11 | #include "util/hex.h" 12 | 13 | namespace svr2::peerid { 14 | 15 | static std::array zero_id = {0}; 16 | 17 | size_t PeerIDHasher::operator()(const PeerID& id) const { 18 | return HashU64(id.id_.data(), id.id_.size()); 19 | } 20 | 21 | PeerID::PeerID(const uint8_t array[32]) { 22 | std::copy(array, array+32, id_.begin()); 23 | } 24 | PeerID::PeerID() : id_({0}) {} 25 | error::Error PeerID::FromString(const std::string& s) { 26 | if (s.size() != id_.size()) { 27 | return COUNTED_ERROR(Peers_InvalidID); 28 | } 29 | std::copy(s.begin(), s.end(), id_.begin()); 30 | return error::OK; 31 | } 32 | bool PeerID::Valid() const { 33 | // https://cr.yp.to/ecdh.html#validate 34 | return id_ != zero_id; 35 | } 36 | void PeerID::ToString(std::string* s) const { 37 | s->resize(32, 0); 38 | std::copy(id_.begin(), id_.end(), s->begin()); 39 | } 40 | std::string PeerID::DebugString() const { 41 | return util::PrefixToHex(id_, 4); 42 | } 43 | 44 | std::ostream& operator<<(std::ostream& os, const PeerID& peer_id) { 45 | os << peer_id.DebugString(); 46 | return os; 47 | } 48 | 49 | } // namespace svr2::peerid 50 | -------------------------------------------------------------------------------- /enclave/peerid/peerid.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_PEERID_PEERID_H__ 5 | #define __SVR2_PEERID_PEERID_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "context/context.h" 12 | #include "proto/error.pb.h" 13 | #include "sip/hasher.h" 14 | 15 | namespace svr2::peerid { 16 | 17 | class PeerID; 18 | 19 | class PeerIDHasher : public sip::Half { 20 | public: 21 | size_t operator()(const PeerID& id) const; 22 | }; 23 | 24 | class PeerID { 25 | public: 26 | PeerID(PeerID&& moved) = default; 27 | PeerID(const PeerID& copied) = default; 28 | PeerID& operator=(const PeerID& other) = default; 29 | PeerID(); // all zeros, invalid 30 | PeerID(const uint8_t array[32]); 31 | error::Error FromString(const std::string& s); 32 | void ToString(std::string* s) const; 33 | const std::array& Get() const { return id_; } 34 | bool Valid() const; 35 | bool operator==(const PeerID& other) const { return id_ == other.id_; } 36 | bool operator!=(const PeerID& other) const { return id_ != other.id_; } 37 | bool operator<(const PeerID& other) const { return id_ < other.id_; } 38 | std::string DebugString() const; 39 | std::string AsString() const { std::string out; ToString(&out); return out; } 40 | 41 | // Prints DebugString() to an ostream. Overload is acceptable because 42 | // PeerID represents a value and DebugString() does not expose any implementation 43 | // details of the object (https://google.github.io/styleguide/cppguide.html#Streams) 44 | friend std::ostream& operator<<(std::ostream& os, const PeerID& peer_id); 45 | 46 | 47 | private: 48 | std::array id_; 49 | friend class PeerIDHasher; 50 | }; 51 | 52 | } // namespace svr2::peerid 53 | 54 | #endif // __SVR2_PEERID_PEERID_H__ 55 | -------------------------------------------------------------------------------- /enclave/proto/clientlog.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.client; 7 | option optimize_for = LITE_RUNTIME; 8 | 9 | import "client.proto"; 10 | import "client3.proto"; 11 | import "client4.proto"; 12 | 13 | // Log2 is the logged message used by the SVR2 (db2) database. 14 | message Log2 { 15 | bytes backup_id = 1; 16 | client.Request req = 2; 17 | } 18 | 19 | // Log3 is the logged message used by the SVR3 (db3) database. 20 | message Log3 { 21 | bytes backup_id = 1; 22 | client.Request3 req = 2; 23 | // If req.create(), then we need to generate new keys. 24 | // These fields will be filled in with the generated keys. 25 | bytes create_privkey = 3; 26 | } 27 | 28 | // Log4 is the logged message used by the SVR4 (db4) database. 29 | message Log4 { 30 | bytes backup_id = 1; 31 | client.Request4 req = 2; 32 | } 33 | 34 | // Effect4 is the effect of applying a Log4 on a SVR4 (db4) database. 35 | message Effect4 { 36 | client.Response4 resp = 1; 37 | message Restore2State { 38 | bytes auth_commitment = 1; 39 | bytes encryption_secretshare = 2; 40 | bytes new_encryption_secretshare = 3; 41 | fixed64 version = 4; 42 | fixed64 new_version = 5; 43 | bytes blinded_point = 6; 44 | } 45 | Restore2State restore2_client_state = 2; 46 | } 47 | -------------------------------------------------------------------------------- /enclave/proto/raft.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.raft; 7 | option optimize_for = LITE_RUNTIME; 8 | 9 | message Replica { 10 | bytes peer_id = 1; 11 | bool voting = 2; 12 | } 13 | 14 | // ReplicaGroup contains information on the current configuration (set of 15 | // replicas) for Raft. Importantly, for a particular raft.pb.cc instantiation, 16 | // it must have a deterministic serialization. This means that, while 17 | // serialization might change were the underlying protobuf library version 18 | // to be bumped, the generated code for a static version of the protobuf 19 | // library should be deterministic. In particular, `map` fields should not 20 | // be present in this proto or its children. This determinism is necessary 21 | // since the log's hash chain is updated based on the serialization of this 22 | // proto. 23 | message ReplicaGroup { 24 | repeated Replica replicas = 1; 25 | } 26 | 27 | message RaftMessage { 28 | uint64 group = 1; 29 | uint64 term = 2; 30 | oneof inner { 31 | VoteRequest vote_request = 3; 32 | VoteResponse vote_response = 4; 33 | AppendRequest append_request = 5; 34 | AppendResponse append_response = 6; 35 | bool timeout_now = 7; // force an election timeout on the recipient 36 | }; 37 | } 38 | 39 | message VoteRequest { 40 | uint64 last_log_idx = 1; 41 | uint64 last_log_term = 2; 42 | bytes last_log_hash_chain = 3; 43 | } 44 | 45 | message VoteResponse { 46 | bool vote_granted = 1; 47 | } 48 | 49 | message AppendRequest { 50 | uint64 prev_log_idx = 1; 51 | uint64 prev_log_term = 2; 52 | uint64 leader_commit = 3; 53 | repeated LogEntry entries = 4; 54 | uint64 leader_promise = 5; 55 | bytes prev_log_hash_chain = 6; 56 | } 57 | 58 | message AppendResponse { 59 | bool success = 1; 60 | uint64 match_idx = 2; 61 | uint64 last_log_idx = 3; 62 | uint64 promise_idx = 4; 63 | bytes match_hash_chain = 5; 64 | } 65 | 66 | message LogEntry { 67 | uint64 term = 1; 68 | oneof inner { 69 | bytes data = 2; 70 | ReplicaGroup membership_change = 3; 71 | // Serialization of minimums.MinimumLimits, which isn't deterministically 72 | // serializable due to containing a map. 73 | bytes minimums = 5; 74 | } 75 | bytes hash_chain = 4; 76 | } 77 | 78 | message LogLocation { 79 | uint64 term = 1; 80 | uint64 idx = 2; 81 | bytes hash_chain = 3; 82 | } 83 | -------------------------------------------------------------------------------- /enclave/proto/tests.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.tests; 7 | option optimize_for = LITE_RUNTIME; 8 | 9 | message SimplePB { 10 | string str = 1; 11 | } 12 | -------------------------------------------------------------------------------- /enclave/protobuf-lite/README.md: -------------------------------------------------------------------------------- 1 | # Compilation of libprotobuf-lite.a 2 | 3 | Rather than rely on libprotobuf to build libprotobuf-lite.a, we just 4 | symlink all necessary files here, then build with our typical 5 | `Makefile.subdir` approach. This makes absolutely sure that we're 6 | only linking to and compiling with the normal mechanisms. 7 | 8 | ## Which files? 9 | 10 | If you're a future person that's looking to update the protobuf dependency, 11 | this list of symlinks was found by doing: 12 | 13 | ``` 14 | cd ../protobuf 15 | autoreconf -i 16 | ./configure 17 | (cd src && make libprotobuf-lite.la) 18 | ``` 19 | 20 | and looking at the `CXX` rules that were executed. 21 | -------------------------------------------------------------------------------- /enclave/protobuf-lite/any_lite.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/any_lite.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/arena.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/arena.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/arenastring.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/arenastring.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/arenaz_sampler.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/arenaz_sampler.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/bytestream.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/bytestream.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/coded_stream.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/io/coded_stream.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/common.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/common.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/extension_set.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/extension_set.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/generated_enum_util.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/generated_enum_util.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/generated_message_tctable_lite.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/generated_message_tctable_lite.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/generated_message_util.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/generated_message_util.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/implicit_weak_message.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/implicit_weak_message.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/inlined_string_field.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/inlined_string_field.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/int128.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/int128.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/io_win32.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/io/io_win32.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/map.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/map.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/message_lite.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/message_lite.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/parse_context.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/parse_context.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/repeated_field.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/repeated_field.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/repeated_ptr_field.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/repeated_ptr_field.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/status.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/status.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/statusor.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/statusor.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/stringpiece.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/stringpiece.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/stringprintf.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/stringprintf.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/strtod.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/io/strtod.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/structurally_valid.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/structurally_valid.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/strutil.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/strutil.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/time.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/stubs/time.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/wire_format_lite.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/wire_format_lite.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/zero_copy_stream.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/io/zero_copy_stream.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/zero_copy_stream_impl.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc -------------------------------------------------------------------------------- /enclave/protobuf-lite/zero_copy_stream_impl_lite.cc: -------------------------------------------------------------------------------- 1 | ../protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc -------------------------------------------------------------------------------- /enclave/queue/queue.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_QUEUE_QUEUE_H__ 5 | #define __SVR2_QUEUE_QUEUE_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "util/macros.h" 11 | 12 | namespace svr2::queue { 13 | 14 | template 15 | class Queue { 16 | public: 17 | Queue(size_t max_size) : max_size_(max_size) {} 18 | 19 | void Push(T val) { 20 | std::unique_lock lock(mu_); 21 | notfull_.wait(lock, [this]{ return d_.size() < max_size_; }); 22 | d_.emplace_back(std::move(val)); 23 | lock.unlock(); 24 | full_.notify_one(); 25 | } 26 | 27 | T Pop() { 28 | std::unique_lock lock(mu_); 29 | full_.wait(lock, [this]{ return d_.size() > 0; }); 30 | T out = std::move(d_.front()); 31 | d_.pop_front(); 32 | popped_++; 33 | lock.unlock(); 34 | notfull_.notify_all(); 35 | return out; 36 | } 37 | 38 | bool Flush(uint32_t millis) { 39 | std::unique_lock lock(mu_); 40 | auto wait_for = popped_ + d_.size(); 41 | return notfull_.wait_for(lock, std::chrono::milliseconds(millis), [this, wait_for]{ return popped_ >= wait_for; }); 42 | } 43 | 44 | private: 45 | std::mutex mu_; 46 | std::condition_variable full_; 47 | std::condition_variable notfull_; 48 | std::deque d_; 49 | size_t max_size_; 50 | size_t popped_; 51 | }; 52 | 53 | } // namespace svr2::queue 54 | 55 | #endif // __SVR2_QUEUE_QUEUE_H__ 56 | -------------------------------------------------------------------------------- /enclave/queue/tests/queue.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | #include 6 | #include 7 | #include 8 | #include "queue/queue.h" 9 | #include 10 | 11 | namespace svr2::queue { 12 | 13 | class QueueTest : public ::testing::Test {}; 14 | 15 | void QueueReadThread(Queue* q, int n) { 16 | int sum = 0; 17 | for (int i = 0; i < n; i++) { 18 | sum += q->Pop(); 19 | } 20 | ASSERT_EQ(sum, n); 21 | } 22 | 23 | void QueueWriteThread(Queue* q, int n) { 24 | for (int i = 0; i < n; i++) { 25 | q->Push(1); 26 | } 27 | } 28 | 29 | TEST_F(QueueTest, BasicUsage) { 30 | std::vector threads; 31 | Queue q(16); 32 | for (int i = 0; i < 10; i++) { 33 | threads.emplace_back(QueueReadThread, &q, 1000); 34 | } 35 | sleep(1); 36 | for (int i = 0; i < 5; i++) { 37 | threads.emplace_back(QueueWriteThread, &q, 2000); 38 | } 39 | for (int i = 0; i < threads.size(); i++) { 40 | threads[i].join(); 41 | } 42 | } 43 | 44 | } // namespace svr2::queue 45 | -------------------------------------------------------------------------------- /enclave/raft/membership.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_RAFT_MEMBERSHIP_H__ 5 | #define __SVR2_RAFT_MEMBERSHIP_H__ 6 | 7 | #include 8 | #include "peerid/peerid.h" 9 | #include "proto/error.pb.h" 10 | #include "proto/raft.pb.h" 11 | 12 | namespace svr2::raft { 13 | 14 | size_t SetDiffSize(const std::set& a, const std::set& b); 15 | 16 | class Membership { 17 | public: 18 | DELETE_ASSIGN(Membership); 19 | // First returns a membership from a proto, considering this to be 20 | // the first membership of Raft. 21 | static std::unique_ptr First(const peerid::PeerID& me); 22 | // FromProto does minimal error checking and returns the membership as 23 | // ReplicaGroup describes it. 24 | static std::pair, error::Error> FromProto(const ReplicaGroup& group); 25 | 26 | const std::set& all_replicas() const { return all_replicas_; } 27 | const std::set& voting_replicas() const { return voting_replicas_; } 28 | 29 | // ValidProgressionForLeader checks if a change in membership from [from] to 30 | // [to] should be accepted by raft leader [leader]. If so, returns error::OK. 31 | // If not, returns an error explaining the issue. 32 | static error::Error ValidProgressionForLeader( 33 | const peerid::PeerID& leader, 34 | const Membership& from, 35 | const Membership& to, 36 | size_t super_majority); 37 | 38 | ReplicaGroup AsProto() const; 39 | 40 | public_for_test: 41 | Membership(const Membership& other) = default; // allow copy 42 | private: 43 | Membership() = default; 44 | // all_replicas includes all peers, including me. 45 | std::set all_replicas_; 46 | // voting_replicas includes all replicas that can vote. 47 | std::set voting_replicas_; 48 | }; 49 | 50 | } // namespace svr2::raft 51 | 52 | #endif // __SVR2_RAFT_MEMBERSHIP_H__ 53 | -------------------------------------------------------------------------------- /enclave/raft/tests/setdiffsize.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP peerid 6 | //TESTDEP context 7 | //TESTDEP sip 8 | //TESTDEP sender 9 | //TESTDEP env 10 | //TESTDEP env/test 11 | //TESTDEP env 12 | //TESTDEP util 13 | //TESTDEP metrics 14 | //TESTDEP proto 15 | //TESTDEP protobuf-lite 16 | //TESTDEP libsodium 17 | 18 | #include 19 | #include 20 | #include "peerid/peerid.h" 21 | #include "raft/membership.h" 22 | 23 | namespace svr2::raft { 24 | 25 | class SetDiffTest : public ::testing::Test {}; 26 | 27 | TEST_F(SetDiffTest, Basic) { 28 | std::set a; 29 | std::set b; 30 | ASSERT_EQ(0, SetDiffSize(a, b)); 31 | uint8_t p1[32] = {1}; 32 | uint8_t p2[32] = {2}; 33 | uint8_t p3[32] = {3}; 34 | uint8_t p4[32] = {4}; 35 | a.insert(peerid::PeerID(p1)); 36 | ASSERT_EQ(1, SetDiffSize(a, b)); 37 | ASSERT_EQ(0, SetDiffSize(b, a)); 38 | b.insert(peerid::PeerID(p2)); 39 | b.insert(peerid::PeerID(p3)); 40 | b.insert(peerid::PeerID(p4)); 41 | ASSERT_EQ(1, SetDiffSize(a, b)); 42 | ASSERT_EQ(3, SetDiffSize(b, a)); 43 | a.insert(peerid::PeerID(p4)); 44 | ASSERT_EQ(1, SetDiffSize(a, b)); 45 | ASSERT_EQ(2, SetDiffSize(b, a)); 46 | } 47 | 48 | } // namespace svr2::raft 49 | -------------------------------------------------------------------------------- /enclave/raft/types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_RAFT_TYPES_H__ 5 | #define __SVR2_RAFT_TYPES_H__ 6 | 7 | #include 8 | 9 | namespace svr2::raft { 10 | 11 | typedef uint64_t LogIdx; 12 | typedef uint64_t TermId; 13 | typedef uint64_t GroupId; 14 | 15 | } // namespace svr2::raft 16 | 17 | #endif // __SVR2_RAFT_TYPES_H__ 18 | -------------------------------------------------------------------------------- /enclave/releases/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signalapp/SecureValueRecovery2/9d5df31e6a6616f1d91d953e89f219cf1b211b34/enclave/releases/.keep -------------------------------------------------------------------------------- /enclave/releases/gcpsnp/0.20240911.184407.eventlog: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:736398fcd83b8b2bd649b5412e18997f73f6bf34401296d795af8a98484e08fd 3 | size 39270 4 | -------------------------------------------------------------------------------- /enclave/releases/gcpsnp/0.20240911.184407.tar.gz: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:874cbaa1d93e6ef579356f7db006608c22486f569f36f0e9e576307559fd805d 3 | size 594447925 4 | -------------------------------------------------------------------------------- /enclave/releases/gcpsnp/0.20240912.192702.tar.gz: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:35c1f6b9e8253d4bc9e91463f5fbb8a9557dee6fab88b6eedc295fdfe30150f8 3 | size 590273631 4 | -------------------------------------------------------------------------------- /enclave/releases/gcpsnp/0.20240927.165003.tar.gz: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9203b660e797315c5f932cb3b1e843bbbc0d0a51fdcb3c9c404d07122d08c2ac 3 | size 568351371 4 | -------------------------------------------------------------------------------- /enclave/releases/nitro/nitro.5d16a1fd.52b91975.6c355155.eif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:058f57fbb6033bb63e487f8958a3400a0a8795af71d57b7f04b01a48fa4abc75 3 | size 186120235 4 | -------------------------------------------------------------------------------- /enclave/releases/nitro/nitro.75882c69.52b91975.65d6db28.eif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4652ac7de4430eaaf8d71cafaa7993b9377abab5ebd01c4d2355a1b03f1bc6fa 3 | size 186404907 4 | -------------------------------------------------------------------------------- /enclave/releases/nitro/nitro.c4f21f2c.52b91975.6b055bb7.eif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f3a7bef26b859e8b48395198130c28450b6496e66e66374e0a0033650e9c0a85 3 | size 186161195 4 | -------------------------------------------------------------------------------- /enclave/releases/nitro/nitro.dd9495f9.52b91975.64681a07.eif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:932482faba1085d20ec62a07ad1e0b3975d54ee083d50d93636004b7603353dd 3 | size 186153003 4 | -------------------------------------------------------------------------------- /enclave/releases/sgx/default.093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:96d8c7174a81ccee5752dc9002a1992aaf059e809fde21d92a48e9270fbc4751 3 | size 34830088 4 | -------------------------------------------------------------------------------- /enclave/releases/sgx/default.9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b0f805d81f14aa4f1d9c7cd872701ee524a769fa5df9dcae71f1a477c5c96125 3 | size 34805024 4 | -------------------------------------------------------------------------------- /enclave/releases/sgx/small.2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:21bc876f948650c485800f82df726c83ec87c7bea2388fd05e4b08a1a0abbe9c 3 | size 34830088 4 | -------------------------------------------------------------------------------- /enclave/releases/sgx/small.38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9d12a3f4eceb78032efb538b29330694967cd69c2ca8e03dc3e849035e1d4ae2 3 | size 34805024 4 | -------------------------------------------------------------------------------- /enclave/sender/sender.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "sender/sender.h" 5 | #include "env/env.h" 6 | #include "metrics/metrics.h" 7 | 8 | namespace svr2::sender { 9 | 10 | // Send a message to the host. 11 | void Send(context::Context* ctx, const EnclaveMessage& msg) { 12 | std::string serialized; 13 | { 14 | MEASURE_CPU(ctx, cpu_sender_serialize); 15 | CHECK(msg.SerializeToString(&serialized)); 16 | } 17 | { 18 | MEASURE_CPU(ctx, cpu_sender_send); 19 | CHECK(error::OK == env::environment->SendMessage(ctx, serialized)); 20 | } 21 | COUNTER(sender, enclave_messages_sent)->Increment(); 22 | COUNTER(sender, enclave_bytes_sent)->IncrementBy(serialized.size()); 23 | } 24 | 25 | } // namespace svr2::sender 26 | -------------------------------------------------------------------------------- /enclave/sender/sender.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_SENDER_SENDER_H__ 5 | #define __SVR2_SENDER_SENDER_H__ 6 | 7 | #include "proto/msgs.pb.h" 8 | #include "proto/error.pb.h" 9 | #include "context/context.h" 10 | 11 | namespace svr2::sender { 12 | 13 | // Send a message to the host. 14 | void Send(context::Context* ctx, const EnclaveMessage& msg); 15 | 16 | } // namespace svr2::sender 17 | 18 | #endif // __SVR2_SENDER_SENDER_H__ 19 | -------------------------------------------------------------------------------- /enclave/sender/tests/sender.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP env 6 | //TESTDEP env/test 7 | //TESTDEP context 8 | //TESTDEP util 9 | //TESTDEP metrics 10 | //TESTDEP proto 11 | //TESTDEP protobuf-lite 12 | //TESTDEP libsodium 13 | 14 | #include 15 | #include 16 | #include 17 | #include "proto/msgs.pb.h" 18 | #include "proto/error.pb.h" 19 | #include "env/env.h" 20 | #include "env/test/test.h" 21 | #include "sender/sender.h" 22 | 23 | namespace svr2::sender { 24 | 25 | TEST(SenderTest, SendViaTestEnv) { 26 | env::Init(env::SIMULATED); 27 | EnclaveMessage m; 28 | m.mutable_peer_message()->set_syn("abc"); 29 | context::Context ctx; 30 | Send(&ctx, m); 31 | Send(&ctx, m); 32 | Send(&ctx, m); 33 | std::vector got = env::test::SentMessages(); 34 | ASSERT_EQ(3, got.size()); 35 | ASSERT_EQ("abc", got[0].peer_message().syn()); 36 | ASSERT_EQ("abc", got[1].peer_message().syn()); 37 | ASSERT_EQ("abc", got[2].peer_message().syn()); 38 | } 39 | 40 | } // namespace svr2::sender 41 | -------------------------------------------------------------------------------- /enclave/sevtypes/sev-guest.h: -------------------------------------------------------------------------------- 1 | // FROM https://raw.githubusercontent.com/torvalds/linux/a27648c742104a833a01c54becc24429898d85bf/include/uapi/linux/sev-guest.h 2 | /* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ 3 | /* 4 | * Userspace interface for AMD SEV and SNP guest driver. 5 | * 6 | * Copyright (C) 2021 Advanced Micro Devices, Inc. 7 | * 8 | * Author: Brijesh Singh 9 | * 10 | * SEV API specification is available at: https://developer.amd.com/sev/ 11 | */ 12 | 13 | #ifndef __UAPI_LINUX_SEV_GUEST_H_ 14 | #define __UAPI_LINUX_SEV_GUEST_H_ 15 | 16 | struct snp_report_req { 17 | /* user data that should be included in the report */ 18 | __u8 user_data[64]; 19 | 20 | /* The vmpl level to be included in the report */ 21 | __u32 vmpl; 22 | 23 | /* Must be zero filled */ 24 | __u8 rsvd[28]; 25 | }; 26 | 27 | struct snp_report_resp { 28 | /* response data, see SEV-SNP spec for the format */ 29 | __u8 data[4000]; 30 | }; 31 | 32 | struct snp_derived_key_req { 33 | __u32 root_key_select; 34 | __u32 rsvd; 35 | __u64 guest_field_select; 36 | __u32 vmpl; 37 | __u32 guest_svn; 38 | __u64 tcb_version; 39 | }; 40 | 41 | struct snp_derived_key_resp { 42 | /* response data, see SEV-SNP spec for the format */ 43 | __u8 data[64]; 44 | }; 45 | 46 | struct snp_guest_request_ioctl { 47 | /* message version number (must be non-zero) */ 48 | __u8 msg_version; 49 | 50 | /* Request and response structure address */ 51 | __u64 req_data; 52 | __u64 resp_data; 53 | 54 | /* bits[63:32]: VMM error code, bits[31:0] firmware error code (see psp-sev.h) */ 55 | union { 56 | __u64 exitinfo2; 57 | struct { 58 | __u32 fw_error; 59 | __u32 vmm_error; 60 | }; 61 | }; 62 | }; 63 | 64 | struct snp_ext_report_req { 65 | struct snp_report_req data; 66 | 67 | /* where to copy the certificate blob */ 68 | __u64 certs_address; 69 | 70 | /* length of the certificate blob */ 71 | __u32 certs_len; 72 | }; 73 | 74 | #define SNP_GUEST_REQ_IOC_TYPE 'S' 75 | 76 | /* Get SNP attestation report */ 77 | #define SNP_GET_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, struct snp_guest_request_ioctl) 78 | 79 | /* Get a derived key from the root */ 80 | #define SNP_GET_DERIVED_KEY _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, struct snp_guest_request_ioctl) 81 | 82 | /* Get SNP extended report as defined in the GHCB specification version 2. */ 83 | #define SNP_GET_EXT_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, struct snp_guest_request_ioctl) 84 | 85 | /* Guest message request EXIT_INFO_2 constants */ 86 | #define SNP_GUEST_FW_ERR_MASK GENMASK_ULL(31, 0) 87 | #define SNP_GUEST_VMM_ERR_SHIFT 32 88 | #define SNP_GUEST_VMM_ERR(x) (((u64)x) << SNP_GUEST_VMM_ERR_SHIFT) 89 | 90 | #define SNP_GUEST_VMM_ERR_INVALID_LEN 1 91 | #define SNP_GUEST_VMM_ERR_BUSY 2 92 | 93 | #endif /* __UAPI_LINUX_SEV_GUEST_H_ */ 94 | -------------------------------------------------------------------------------- /enclave/sevtypes/sevtypes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // WARNING: This header includes non-namespaced outputs. 5 | 6 | #ifndef __SVR2_SEVTYPES_SEVTYPES_H__ 7 | #define __SVR2_SEVTYPES_SEVTYPES_H__ 8 | 9 | #include 10 | 11 | typedef uint8_t __u8; 12 | typedef uint16_t __u16; 13 | typedef uint32_t __u32; 14 | typedef unsigned long long __u64; 15 | #define __packed __attribute__((__packed__)) 16 | #include "sevtypes/sev-guest.h" // From Linux kernel headers (linux-hwe-5.19-headers-5.19.0-43) LICENSE=GPL2 17 | #define SEV_FW_BLOB_MAX_SIZE (16 << 10) // Max blob size (16kb) 18 | 19 | #endif // __SVR2_SEVTYPES_SEVTYPES_H__ 20 | -------------------------------------------------------------------------------- /enclave/sha/sha.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "sha/sha.h" 5 | #include 6 | #include 7 | 8 | namespace svr2::sha { 9 | 10 | Sha256Sum Sha256( 11 | const uint8_t* d1, size_t s1, 12 | const uint8_t* d2, size_t s2, 13 | const uint8_t* d3, size_t s3) { 14 | Sha256Sum h; 15 | crypto_hash_sha256_state s; 16 | crypto_hash_sha256_init(&s); 17 | crypto_hash_sha256_update(&s, d1, s1); 18 | crypto_hash_sha256_update(&s, d2, s2); 19 | crypto_hash_sha256_update(&s, d3, s3); 20 | crypto_hash_sha256_final(&s, h.data()); 21 | return h; 22 | } 23 | 24 | Sha512Sum Sha512( 25 | const uint8_t* d1, size_t s1, 26 | const uint8_t* d2, size_t s2, 27 | const uint8_t* d3, size_t s3) { 28 | Sha512Sum h; 29 | crypto_hash_sha512_state s; 30 | crypto_hash_sha512_init(&s); 31 | crypto_hash_sha512_update(&s, d1, s1); 32 | crypto_hash_sha512_update(&s, d2, s2); 33 | crypto_hash_sha512_update(&s, d3, s3); 34 | crypto_hash_sha512_final(&s, h.data()); 35 | return h; 36 | } 37 | 38 | } // namespace svr2::sha 39 | -------------------------------------------------------------------------------- /enclave/sha/sha.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_SHA_SHA_H__ 5 | #define __SVR2_SHA_SHA_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace svr2::sha { 12 | 13 | typedef std::array Sha256Sum; 14 | typedef std::array Sha512Sum; 15 | 16 | Sha256Sum Sha256( 17 | const uint8_t* d1, size_t s1, 18 | const uint8_t* d2, size_t s2, 19 | const uint8_t* d3, size_t s3); 20 | Sha512Sum Sha512( 21 | const uint8_t* d1, size_t s1, 22 | const uint8_t* d2, size_t s2, 23 | const uint8_t* d3, size_t s3); 24 | 25 | template 26 | Sha256Sum Sha256(const T1& t1) { 27 | return Sha256( 28 | reinterpret_cast(t1.data()), t1.size(), 29 | nullptr, 0, 30 | nullptr, 0); 31 | } 32 | template 33 | Sha256Sum Sha256(const T1& t1, const T2& t2) { 34 | return Sha256( 35 | reinterpret_cast(t1.data()), t1.size(), 36 | reinterpret_cast(t2.data()), t2.size(), 37 | nullptr, 0); 38 | } 39 | template 40 | Sha256Sum Sha256(const T1& t1, const T2& t2, const T3& t3) { 41 | return Sha256( 42 | reinterpret_cast(t1.data()), t1.size(), 43 | reinterpret_cast(t2.data()), t2.size(), 44 | reinterpret_cast(t3.data()), t3.size()); 45 | } 46 | 47 | template 48 | Sha512Sum Sha512(const T1& t1) { 49 | return Sha512( 50 | reinterpret_cast(t1.data()), t1.size(), 51 | nullptr, 0, 52 | nullptr, 0); 53 | } 54 | template 55 | Sha512Sum Sha512(const T1& t1, const T2& t2) { 56 | return Sha512( 57 | reinterpret_cast(t1.data()), t1.size(), 58 | reinterpret_cast(t2.data()), t2.size(), 59 | nullptr, 0); 60 | } 61 | template 62 | Sha512Sum Sha512(const T1& t1, const T2& t2, const T3& t3) { 63 | return Sha512( 64 | reinterpret_cast(t1.data()), t1.size(), 65 | reinterpret_cast(t2.data()), t2.size(), 66 | reinterpret_cast(t3.data()), t3.size()); 67 | } 68 | 69 | } // namespace svr2::sha 70 | 71 | #endif // __SVR2_SHA_SHA_H__ 72 | -------------------------------------------------------------------------------- /enclave/sip/halfsiphash.c: -------------------------------------------------------------------------------- 1 | ../SipHash/halfsiphash.c -------------------------------------------------------------------------------- /enclave/sip/halfsiphash.h: -------------------------------------------------------------------------------- 1 | ../SipHash/halfsiphash.h -------------------------------------------------------------------------------- /enclave/sip/hasher.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "sip/hasher.h" 5 | #include "env/env.h" 6 | #include "util/endian.h" 7 | extern "C" { 8 | #include "sip/halfsiphash.h" 9 | #include "sip/siphash.h" 10 | } // extern "C" 11 | 12 | namespace svr2::sip { 13 | 14 | static const std::array half_zeros = {0}; 15 | Half HalfZero(half_zeros); 16 | static const std::array full_zeros = {0}; 17 | Full FullZero(full_zeros); 18 | 19 | Half::Half() { 20 | CHECK(error::OK == env::environment->RandomBytes(halfsiphash_key_, sizeof(halfsiphash_key_))); 21 | } 22 | Half::Half(const Half& copy) { 23 | memcpy(halfsiphash_key_, copy.halfsiphash_key_, sizeof(halfsiphash_key_)); 24 | } 25 | Half::Half(const std::array& key) { 26 | ResetKey(key); 27 | } 28 | std::array Half::Hash8(const void* data, size_t size) const { 29 | std::array out; 30 | halfsiphash(data, size, halfsiphash_key_, out.data(), out.size()); 31 | return out; 32 | } 33 | void Half::ResetKey(const std::array& key) { 34 | CHECK(sizeof(halfsiphash_key_) == key.size()); 35 | memcpy(halfsiphash_key_, key.data(), sizeof(halfsiphash_key_)); 36 | } 37 | uint64_t Half::HashU64(const void* data, size_t size) const { 38 | auto bytes = Hash8(data, size); 39 | return util::BigEndian64FromBytes(bytes.data()); 40 | } 41 | 42 | Full::Full() { 43 | CHECK(error::OK == env::environment->RandomBytes(siphash_key_, sizeof(siphash_key_))); 44 | } 45 | Full::Full(const Full& copy) { 46 | memcpy(siphash_key_, copy.siphash_key_, sizeof(siphash_key_)); 47 | } 48 | Full::Full(const std::array& key) { 49 | ResetKey(key); 50 | } 51 | std::array Full::Hash8(const void* data, size_t size) const { 52 | std::array out; 53 | siphash(data, size, siphash_key_, out.data(), out.size()); 54 | return out; 55 | } 56 | uint64_t Full::HashU64(const void* data, size_t size) const { 57 | auto bytes = Hash8(data, size); 58 | return util::BigEndian64FromBytes(bytes.data()); 59 | } 60 | std::array Full::Hash16(const void* data, size_t size) const { 61 | std::array out; 62 | siphash(data, size, siphash_key_, out.data(), out.size()); 63 | return out; 64 | } 65 | void Full::ResetKey(const std::array& key) { 66 | CHECK(sizeof(siphash_key_) == key.size()); 67 | memcpy(siphash_key_, key.data(), sizeof(siphash_key_)); 68 | } 69 | 70 | } // namespace svr2::sip 71 | -------------------------------------------------------------------------------- /enclave/sip/hasher.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_SIP_HASHER_H__ 5 | #define __SVR2_SIP_HASHER_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace svr2::sip { 12 | 13 | class Half { 14 | public: 15 | Half(); 16 | Half(const Half& copy); 17 | Half(const std::array& key); 18 | uint64_t HashU64(const void* data, size_t bytes) const; 19 | std::array Hash8(const void* data, size_t bytes) const; 20 | void ResetKey(const std::array& key); 21 | private: 22 | uint8_t halfsiphash_key_[8]; 23 | }; 24 | 25 | class Full { 26 | public: 27 | Full(); 28 | Full(const Full& copy); 29 | Full(const std::array& key); 30 | uint64_t HashU64(const void* data, size_t bytes) const; 31 | std::array Hash8(const void* data, size_t bytes) const; 32 | std::array Hash16(const void* data, size_t bytes) const; 33 | void ResetKey(const std::array& key); 34 | private: 35 | uint8_t siphash_key_[16]; 36 | }; 37 | 38 | extern Half HalfZero; 39 | extern Full FullZero; 40 | 41 | } // namespace svr2::sip 42 | 43 | #endif // __SVR2_SIP_HASHER_H__ 44 | -------------------------------------------------------------------------------- /enclave/sip/siphash.c: -------------------------------------------------------------------------------- 1 | ../SipHash/siphash.c -------------------------------------------------------------------------------- /enclave/sip/siphash.h: -------------------------------------------------------------------------------- 1 | ../SipHash/siphash.h -------------------------------------------------------------------------------- /enclave/sip/tests/hasher.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP sip 5 | //TESTDEP env 6 | //TESTDEP env/test 7 | //TESTDEP gtest 8 | //TESTDEP context 9 | //TESTDEP metrics 10 | //TESTDEP util 11 | //TESTDEP proto 12 | //TESTDEP protobuf-lite 13 | //TESTDEP libsodium 14 | 15 | #include 16 | #include 17 | #include 18 | #include "sip/hasher.h" 19 | #include "env/env.h" 20 | 21 | namespace svr2::sip { 22 | 23 | class HashInts : public Half { 24 | public: 25 | size_t operator()(const uint32_t& a) const { 26 | return HashU64(&a, sizeof(a)); 27 | } 28 | }; 29 | 30 | class HasherTest : public ::testing::Test { 31 | protected: 32 | static void SetUpTestSuite() { 33 | env::Init(env::SIMULATED); 34 | } 35 | }; 36 | 37 | TEST_F(HasherTest, HashInts) { 38 | std::unordered_map m; 39 | for (uint32_t i = 0; i < 5000; i++) { 40 | m[i] = i; 41 | } 42 | for (uint32_t i = 0; i < 5000; i++) { 43 | ASSERT_EQ(m[i], i); 44 | } 45 | } 46 | 47 | } // namespace svr2::sip 48 | -------------------------------------------------------------------------------- /enclave/svr2.conf: -------------------------------------------------------------------------------- 1 | Debug=0 2 | # Each TCS requires its own stack, plus a few "bonus" pages: 3 | # 4 | # - 1 TCS page (enclave independent) 5 | # - 2 State Save Area (SSA) pages (enclave independent) 6 | # - 1 guard page (enclave independent) 7 | # - 1 TLS page (depends on enclave binary - number of pages needed to hold .tdata and .tbss in the enclave.signed elf file. At time of writing this fits in 1 page.) 8 | # - 1 page for thread-specific data (TSD) slots 9 | # 10 | # ...and so each TCS consumes (6 + NumStackPages) EPC pages, so NumHeapPages = NumEpcPages - (NumTCS * (6 + NumStackPages)). 11 | # 12 | # On top of that, attestation services may consume additional EPC memory (6 MiB in our case). 13 | # 14 | # This configuration requires a host that has at least 120 GiB EPC memory available. 15 | # 16 | # 120 GiB - 6 MiB => 128842727424 bytes => NumEpcPages = 31457280 17 | NumHeapPages=31324288 18 | NumStackPages=2048 19 | NumTCS=64 20 | ProductID=1 21 | SecurityVersion=1 22 | CapturePFGPExceptions=1 23 | -------------------------------------------------------------------------------- /enclave/svr2/.keep: -------------------------------------------------------------------------------- 1 | This file exists so that Git knows to keep this directory around. 2 | -------------------------------------------------------------------------------- /enclave/svr2_small.conf: -------------------------------------------------------------------------------- 1 | Debug=0 2 | # Roughly 8G 3 | NumHeapPages=2000000 4 | NumStackPages=2048 5 | NumTCS=16 6 | ProductID=1 7 | SecurityVersion=1 8 | CapturePFGPExceptions=1 9 | -------------------------------------------------------------------------------- /enclave/svr2_test.conf: -------------------------------------------------------------------------------- 1 | # Enclave settings: a small enclave with debug enabled for testing purposes. 2 | Debug=1 3 | NumHeapPages=20000 4 | NumStackPages=2048 5 | NumTCS=64 6 | ProductID=1 7 | SecurityVersion=1 8 | CapturePFGPExceptions=1 9 | -------------------------------------------------------------------------------- /enclave/test_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for testfile in `find ./ -type f -wholename '*/tests/*.cc'`; do 4 | testfile="$(echo "$testfile" | sed 's#./##')" 5 | testname="$(echo "$testfile" | sed 's/\.cc$/\.test/')" 6 | echo 1>&2 "TEST: $testfile -> $testname" 7 | deps="build/$(dirname $(dirname "$testfile"))/TEST.a$(grep '^//TESTDEP ' "$testfile" | awk '{printf " build/%s/TEST.a",$2}')" 8 | echo 1>&2 " Deps: $deps" 9 | args="$(grep '^//TESTARG ' "$testfile" | awk '{printf "%s ",$2}')" 10 | echo 1>&2 " Args: $args" 11 | echo "build/$testname: $testfile $deps" 12 | echo -e '\t$(QUIET) echo -e "BUILD\t$@"' 13 | echo -e '\t$(QUIET) mkdir -p \$(@D)' 14 | echo -e "\t\$(QUIET) \$(CXX) \$(TEST_CXXFLAGS) -o \$@ $testfile -Wl,--start-group $deps -Wl,--end-group $args \$(TEST_LDFLAGS)" 15 | echo "test: build/$testname.success" 16 | echo "valgrind: build/$testname.valgrind" 17 | echo ".PRECIOUS: build/$testname build/$testname.out" 18 | done | tee .testdepends 19 | -------------------------------------------------------------------------------- /enclave/timeout/tests/timeout.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP timeout 6 | //TESTDEP metrics 7 | //TESTDEP util 8 | //TESTDEP context 9 | //TESTDEP env 10 | //TESTDEP env/test 11 | //TESTDEP env 12 | //TESTDEP sip 13 | //TESTDEP proto 14 | //TESTDEP protobuf-lite 15 | //TESTDEP libsodium 16 | 17 | #include 18 | #include "timeout/timeout.h" 19 | #include "context/context.h" 20 | #include "env/env.h" 21 | #include "env/test/test.h" 22 | 23 | namespace svr2::timeout { 24 | 25 | class TimeoutTest : public ::testing::Test { 26 | protected: 27 | static void SetUpTestCase() { 28 | env::Init(env::SIMULATED); 29 | } 30 | 31 | Timeout t; 32 | context::Context ctx; 33 | }; 34 | 35 | TEST_F(TimeoutTest, TicksStartAtZero) { 36 | ASSERT_EQ(t.ticks(), 0); 37 | } 38 | 39 | TEST_F(TimeoutTest, TimeoutRuns) { 40 | bool ran = false; 41 | t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran = true; }); 42 | ASSERT_FALSE(ran); 43 | t.TimerTick(&ctx); 44 | ASSERT_TRUE(ran); 45 | } 46 | 47 | TEST_F(TimeoutTest, TimeoutCancels) { 48 | bool ran = false; 49 | Cancel c = t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran = true; }); 50 | ASSERT_FALSE(ran); 51 | t.CancelTimeout(&ctx, c); 52 | t.TimerTick(&ctx); 53 | ASSERT_FALSE(ran); 54 | } 55 | 56 | TEST_F(TimeoutTest, TimeoutCancelAfterRunIsFine) { 57 | bool ran = false; 58 | Cancel c = t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran = true; }); 59 | ASSERT_FALSE(ran); 60 | t.TimerTick(&ctx); 61 | ASSERT_TRUE(ran); 62 | t.CancelTimeout(&ctx, c); 63 | } 64 | 65 | TEST_F(TimeoutTest, MultipleTimeoutsAtSameTick) { 66 | int ran = 0; 67 | t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran++; }); 68 | Cancel c = t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran++; }); 69 | t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran++; }); 70 | t.SetTimeout(&ctx, 1, [&ran](context::Context* ctx){ ran++; }); 71 | t.CancelTimeout(&ctx, c); 72 | t.TimerTick(&ctx); 73 | ASSERT_EQ(ran, 3); 74 | } 75 | 76 | TEST_F(TimeoutTest, FarFutureTimeout) { 77 | bool ran = false; 78 | t.SetTimeout(&ctx, 1001, [&ran](context::Context* ctx){ ran = true; }); 79 | for (int i = 0; i < 1000; i++) { 80 | t.TimerTick(&ctx); 81 | ASSERT_FALSE(ran); 82 | } 83 | t.TimerTick(&ctx); 84 | ASSERT_TRUE(ran); 85 | } 86 | 87 | } // namespace svr2::timeout 88 | -------------------------------------------------------------------------------- /enclave/timeout/timeout.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_TIMEOUT_TIMEOUT_H__ 5 | #define __SVR2_TIMEOUT_TIMEOUT_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "util/ticks.h" 13 | #include "context/context.h" 14 | 15 | namespace svr2::timeout { 16 | 17 | class Timeout; 18 | class Cancel { 19 | public: 20 | Cancel() : at_tick_(0), cancel_id_(0) {} 21 | private: 22 | Cancel(util::Ticks at_tick, int64_t cancel_id) : at_tick_(at_tick), cancel_id_(cancel_id) {} 23 | util::Ticks at_tick_; 24 | int64_t cancel_id_; 25 | friend class Timeout; 26 | }; 27 | 28 | typedef std::function TimeoutFn; 29 | 30 | class Timeout { 31 | public: 32 | Timeout(); 33 | // SetTimeout provides a function that will be called [ticks_from_now] ticks in the future (min 1). 34 | // This function will be called at that time, once, unless CancelTimeout is called on the returned 35 | // value before that time. `fn` will NOT be called as part of this function call, even if 36 | // `ticks_from_now` is zero; it will be called at least one `TimerTick` call after this function 37 | // returns. Thus, if locks etc. are held while `SetTimeout` is called, so long as they're released 38 | // before the next `TimerTick` call, they can be reacquired by `fn`. 39 | Cancel SetTimeout(context::Context* ctx, util::Ticks ticks_from_now, TimeoutFn fn) EXCLUDES(mu_); 40 | // CancelTimeout cancels a function that was scheduled for the future. May be called any number 41 | // of times on a Cancel, and may be called after the ticks for the given function have 42 | // passed. 43 | void CancelTimeout(context::Context* ctx, const Cancel& c) EXCLUDES(mu_); 44 | // Called whenever the host gives us a TimerTick. 45 | void TimerTick(context::Context* ctx) EXCLUDES(mu_); 46 | 47 | #ifdef IS_TEST 48 | util::Ticks ticks() const EXCLUDES(mu_) { 49 | util::unique_lock lock(mu_); 50 | return ticks_; 51 | } 52 | #endif 53 | 54 | private: 55 | // Time and Timeouts 56 | mutable util::mutex mu_; 57 | util::Ticks ticks_ GUARDED_BY(mu_); 58 | int64_t timeout_cancel_gen_ GUARDED_BY(mu_); 59 | typedef std::unordered_map TimeoutSet; 60 | std::unordered_map timeouts_ GUARDED_BY(mu_); 61 | }; 62 | 63 | } // namespace svr2::timeout 64 | 65 | #endif // __SVR2_TIMEOUT_TIMEOUT_H__ 66 | -------------------------------------------------------------------------------- /enclave/util/base64.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_ATTESTATION_BASE64_BASE64_H__ 5 | #define __SVR2_ATTESTATION_BASE64_BASE64_H__ 6 | 7 | #include 8 | #include "proto/error.pb.h" 9 | 10 | namespace svr2::util { 11 | 12 | struct Base64Encoding { 13 | const char decode[256]; 14 | const char* encode; 15 | }; 16 | 17 | extern const Base64Encoding* const B64URL; 18 | extern const Base64Encoding* const B64STD; 19 | 20 | // B64DecodeInline takes in a string containing base64 and modifies 21 | // it to contain the base64-decoded data. `*inout` will be modified, 22 | // and should an error be returned, it will most likely not contain 23 | // the same data as it had when this function was initially called. 24 | error::Error B64DecodeInline(std::string* inout, const Base64Encoding* const encoding); 25 | std::string Base64Encode(const uint8_t* in, size_t in_size, const Base64Encoding* const encoding, bool padding); 26 | template 27 | std::string Base64Encode(const T& t, const Base64Encoding* const encoding, bool padding) { 28 | return Base64Encode(reinterpret_cast(t.data()), t.size(), encoding, padding); 29 | } 30 | std::string Base64Encode(const char* c, const Base64Encoding* const encoding, bool padding); 31 | } // namespace svr2::util 32 | 33 | #endif // __SVR2_ATTESTATION_BASE64_BASE64_H__ 34 | -------------------------------------------------------------------------------- /enclave/util/bytes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_BYTES_H 5 | #define __SVR2_UTIL_BYTES_H 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "util/macros.h" 12 | #include "proto/error.pb.h" 13 | 14 | namespace svr2::util { 15 | 16 | template 17 | std::string ByteArrayToString(const std::array& bytes) { 18 | std::string result; 19 | result.resize(N, '\0'); 20 | std::copy(bytes.begin(), bytes.end(), result.begin()); 21 | return result; 22 | } 23 | 24 | template 25 | error::Error StringIntoByteArray(const std::string& str, std::array* result) { 26 | if (str.size() > N) { 27 | return error::Util_ArrayCopyTooBig; 28 | } 29 | std::copy(str.begin(), str.end(), result->begin()); 30 | return error::OK; 31 | } 32 | 33 | template 34 | std::pair, error::Error> StringToByteArray(const std::string& str) { 35 | std::array result{0}; 36 | error::Error err = StringIntoByteArray(str, &result); 37 | return std::make_pair(result, err); 38 | } 39 | 40 | std::string ByteVectorToString(const std::vector& bytes); 41 | inline std::string ByteVectorToString(const std::vector& bytes) { 42 | std::string result; 43 | result.resize(bytes.size()); 44 | std::copy(bytes.begin(), bytes.end(), result.begin()); 45 | return result; 46 | } 47 | 48 | } // namespace svr2::util 49 | 50 | #endif // __SVR2_UTIL_BYTES_H 51 | -------------------------------------------------------------------------------- /enclave/util/constant.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_CONSTANT_H__ 5 | #define __SVR2_UTIL_CONSTANT_H__ 6 | 7 | namespace svr2::util { 8 | 9 | static inline bool ConstantTimeEqualsBytes(const uint8_t* a, const uint8_t* b, size_t size) { 10 | uint8_t out = 0; 11 | while (size--) { 12 | out |= (*a++) ^ (*b++); 13 | } 14 | return out == 0; 15 | } 16 | 17 | // Templatized to work on std::array and std::string. 18 | template 19 | static bool ConstantTimeEqualsPrefix(const T1& a, const T2& b, size_t prefix_size) { 20 | if (a.size() < prefix_size || b.size() < prefix_size) return false; // not constant time, but we generally don't care. 21 | return ConstantTimeEqualsBytes( 22 | reinterpret_cast(a.data()), 23 | reinterpret_cast(b.data()), 24 | prefix_size); 25 | } 26 | 27 | // Templatized to work on std::array and std::string. 28 | template 29 | static bool ConstantTimeEquals(const T1& a, const T2& b) { 30 | if (a.size() != b.size()) return false; // not constant time, but we generally don't care. 31 | return ConstantTimeEqualsPrefix(a, b, a.size()); 32 | } 33 | 34 | } // namespace svr2::util 35 | 36 | #endif // __SVR2_UTIL_CONSTANT_H__ 37 | -------------------------------------------------------------------------------- /enclave/util/cpu.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_CPU_H__ 5 | #define __SVR2_UTIL_CPU_H__ 6 | 7 | #include 8 | 9 | namespace svr2::util { 10 | 11 | // `rdtsc` gets the current CPU ticks from the current CPU. 12 | uint64_t asm_rdtsc(); 13 | inline uint64_t asm_rdtsc() { 14 | uint64_t lo, hi; 15 | asm volatile( "rdtsc" : "=a" (lo), "=d" (hi) ); 16 | return lo | ( hi << 32 ); 17 | } 18 | 19 | } // namespace svr2::util 20 | 21 | #endif // __SVR2_UTIL_CPU_H__ 22 | -------------------------------------------------------------------------------- /enclave/util/endian.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_ENDIAN_H__ 5 | #define __SVR2_UTIL_ENDIAN_H__ 6 | 7 | namespace svr2::util { 8 | 9 | inline uint64_t BigEndian64FromBytes(const uint8_t in[8]) { 10 | return ((uint64_t)in[0]) << (8*7) | 11 | ((uint64_t)in[1]) << (8*6) | 12 | ((uint64_t)in[2]) << (8*5) | 13 | ((uint64_t)in[3]) << (8*4) | 14 | ((uint64_t)in[4]) << (8*3) | 15 | ((uint64_t)in[5]) << (8*2) | 16 | ((uint64_t)in[6]) << (8*1) | 17 | ((uint64_t)in[7]) << (8*0); 18 | } 19 | 20 | inline uint32_t BigEndian32FromBytes(const uint8_t in[4]) { 21 | return ((uint32_t)in[0]) << (8*3) | 22 | ((uint32_t)in[1]) << (8*2) | 23 | ((uint32_t)in[2]) << (8*1) | 24 | ((uint32_t)in[3]) << (8*0); 25 | } 26 | 27 | inline uint16_t BigEndian16FromBytes(const uint8_t in[2]) { 28 | return ((uint16_t)in[0]) << (8*1) | 29 | ((uint16_t)in[1]) << (8*0); 30 | } 31 | 32 | inline uint64_t BigEndian64FromBytes(const char* in) { 33 | return BigEndian64FromBytes(reinterpret_cast(in)); 34 | } 35 | 36 | inline void BigEndian64Bytes(uint64_t v, uint8_t out[8]) { 37 | out[0] = v >> (8*7); 38 | out[1] = v >> (8*6); 39 | out[2] = v >> (8*5); 40 | out[3] = v >> (8*4); 41 | out[4] = v >> (8*3); 42 | out[5] = v >> (8*2); 43 | out[6] = v >> (8*1); 44 | out[7] = v >> (8*0); 45 | } 46 | 47 | inline void BigEndian32Bytes(uint32_t v, uint8_t out[4]) { 48 | out[0] = v >> (8*3); 49 | out[1] = v >> (8*2); 50 | out[2] = v >> (8*1); 51 | out[3] = v >> (8*0); 52 | } 53 | 54 | inline void LittleEndian64Bytes(uint64_t v, uint8_t out[8]) { 55 | out[0] = v >> (8*0); 56 | out[1] = v >> (8*1); 57 | out[2] = v >> (8*2); 58 | out[3] = v >> (8*3); 59 | out[4] = v >> (8*4); 60 | out[5] = v >> (8*5); 61 | out[6] = v >> (8*6); 62 | out[7] = v >> (8*7); 63 | } 64 | 65 | } // namespace svr2::util 66 | 67 | #endif // __SVR2_UTIL_ENDIAN_H__ 68 | -------------------------------------------------------------------------------- /enclave/util/hex.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "util/hex.h" 5 | #include "metrics/metrics.h" 6 | 7 | namespace svr2::util { 8 | namespace { 9 | 10 | inline uint8_t HexCharToNibble(char c) { 11 | if (c >= '0' && c <= '9') { 12 | return c - '0'; 13 | } else if (c >= 'a' && c <= 'f') { 14 | return 0xa + (c - 'a'); 15 | } else if (c >= 'A' && c <= 'F') { 16 | return 0xa + (c - 'A'); 17 | } else { 18 | return 0xFF; 19 | } 20 | } 21 | 22 | } // namespace 23 | 24 | std::string BytesToHex(const uint8_t* in, size_t size) { 25 | static const char* nibbles = "0123456789abcdef"; 26 | std::string out(size * 2, ' '); 27 | for (size_t i = 0; i < size; i++) { 28 | out[i*2+0] = nibbles[(in[i] & 0xf0) >> 4]; 29 | out[i*2+1] = nibbles[(in[i] & 0x0f) >> 0]; 30 | } 31 | return out; 32 | } 33 | 34 | std::pair HexToBytes(const char* in, size_t in_size) { 35 | std::string out; 36 | if (in_size % 2 != 0) { 37 | return std::make_pair(std::move(out), COUNTED_ERROR(Util_HexBytesSize)); 38 | } 39 | for (size_t i = 0; i < in_size; i += 2) { 40 | uint8_t n1 = HexCharToNibble(in[i]); 41 | uint8_t n2 = HexCharToNibble(in[i+1]); 42 | if (n1 == 0xFF || n2 == 0xFF) { 43 | return std::make_pair(std::move(out), COUNTED_ERROR(Util_HexCharInvalid)); 44 | } 45 | out.append(1, static_cast((n1 << 4) | n2)); 46 | } 47 | return std::make_pair(std::move(out), error::OK); 48 | } 49 | 50 | } // namespace svr2::util 51 | -------------------------------------------------------------------------------- /enclave/util/hex.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_HEX_H__ 5 | #define __SVR2_UTIL_HEX_H__ 6 | 7 | #include 8 | #include 9 | #include "proto/error.pb.h" 10 | 11 | namespace svr2::util { 12 | 13 | std::string BytesToHex(const uint8_t* in, size_t size); 14 | 15 | // Turns the `s`-byte prefix of `in` into `s*2` hex characters and returns it as a string. 16 | template 17 | std::string PrefixToHex(const T& in, size_t s) { 18 | return BytesToHex(reinterpret_cast(in.data()), std::min(s, in.size())); 19 | } 20 | 21 | // Turns the bytes of `in` into `in.size()*2` hex characters and returns it as a string. 22 | template 23 | std::string ToHex(const T& in) { 24 | return PrefixToHex(in, in.size()); 25 | } 26 | 27 | template 28 | std::string ValueToHex(const T& in) { 29 | return BytesToHex(reinterpret_cast(&in), sizeof(in)); 30 | } 31 | 32 | std::pair HexToBytes(const char* in, size_t in_size); 33 | inline std::pair HexToBytes(const std::string& in) { 34 | return HexToBytes(in.data(), in.size()); 35 | } 36 | 37 | } // namespace svr2::util 38 | 39 | #endif // __SVR2_UTIL_HEX_H__ 40 | -------------------------------------------------------------------------------- /enclave/util/log.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "util/log.h" 5 | #include "env/env.h" 6 | #include "util/macros.h" 7 | #include 8 | #include 9 | 10 | namespace svr2::util { 11 | 12 | ::svr2::enclaveconfig::EnclaveLogLevel log_level_to_write = 13 | #ifdef IS_TEST 14 | enclaveconfig::LOG_LEVEL_MAX; 15 | #else 16 | enclaveconfig::LOG_LEVEL_INFO; 17 | #endif 18 | 19 | std::hash thread_id_hasher; 20 | 21 | Log::Log(::svr2::enclaveconfig::EnclaveLogLevel lvl) : lvl_(lvl) {} 22 | 23 | Log::~Log() { 24 | env::environment->Log(lvl_, ss_.str()); 25 | if (lvl_ == enclaveconfig::LOG_LEVEL_FATAL) { 26 | env::environment->FlushAllLogsIfAble(); 27 | abort(); 28 | } 29 | } 30 | 31 | void SetLogLevel(::svr2::enclaveconfig::EnclaveLogLevel level) { 32 | log_level_to_write = level; 33 | } 34 | 35 | uint64_t TimestampMicros() { 36 | struct timeval tv; 37 | if (0 != gettimeofday(&tv, NULL)) return -1; 38 | return tv.tv_usec + (1000000 * tv.tv_sec); 39 | } 40 | 41 | } // namespace svr2::util 42 | 43 | std::ostream& operator<<(std::ostream& os, ::svr2::error::Error err) { 44 | if (err == ::svr2::error::OK) { 45 | os << "OK"; 46 | } else { 47 | os << "error::" << ::svr2::error::Error_Name(err); 48 | } 49 | return os; 50 | } 51 | -------------------------------------------------------------------------------- /enclave/util/log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_LOG_H__ 5 | #define __SVR2_UTIL_LOG_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include "proto/error.pb.h" 11 | #include "proto/msgs.pb.h" 12 | 13 | std::ostream& operator<<(std::ostream& os, ::svr2::error::Error err); 14 | 15 | namespace svr2::util { 16 | 17 | class Log { 18 | public: 19 | Log(::svr2::enclaveconfig::EnclaveLogLevel lvl); 20 | ~Log(); 21 | 22 | template 23 | std::ostream& operator<<(T x) { 24 | ss_ << x; 25 | return ss_; 26 | } 27 | 28 | private: 29 | ::svr2::enclaveconfig::EnclaveLogLevel lvl_; 30 | std::stringstream ss_; 31 | }; 32 | 33 | extern ::svr2::enclaveconfig::EnclaveLogLevel log_level_to_write; 34 | extern std::hash thread_id_hasher; 35 | 36 | void SetLogLevel(::svr2::enclaveconfig::EnclaveLogLevel level); 37 | 38 | uint64_t TimestampMicros(); 39 | 40 | } // namespace svr2::util 41 | 42 | #define LOG(x) if (::svr2::enclaveconfig::LOG_LEVEL_##x <= ::svr2::util::log_level_to_write) ::svr2::util::Log(::svr2::enclaveconfig::LOG_LEVEL_##x) << #x << "\t" << __FILE__ << ":" << __LINE__ << "(" << __FUNCTION__ << ") @ " << ::svr2::util::TimestampMicros() << " T=" << (::svr2::util::thread_id_hasher(std::this_thread::get_id()) % 10000) << " - " 43 | 44 | #endif // __SVR2_UTIL_LOG_H__ 45 | -------------------------------------------------------------------------------- /enclave/util/macros.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_MACROS_H__ 5 | #define __SVR2_UTIL_MACROS_H__ 6 | 7 | #include 8 | #include "util/log.h" 9 | 10 | // LOG(FATAL) already does an abort(), but we do it again so the compiler really 11 | // knows that this is a sequence of instructions that doesn't continue, for things 12 | // like fallthrough etc. 13 | #define CHECK(x) do { \ 14 | if (!(x)) { \ 15 | LOG(FATAL) << "CHECK FAIL: " << #x; \ 16 | abort(); \ 17 | } \ 18 | } while (0) 19 | 20 | #define RETURN_IF_ERROR(x) do { \ 21 | ::svr2::error::Error _err_ = (x); \ 22 | if (_err_ != ::svr2::error::OK) return _err_; \ 23 | } while (0) 24 | 25 | #define DELETE_COPY_AND_ASSIGN(x) \ 26 | x(x& other) = delete; \ 27 | void operator=(const x &) = delete 28 | #define DELETE_ASSIGN(x) \ 29 | void operator=(const x &) = delete 30 | 31 | #ifdef IS_TEST 32 | #define public_for_test public 33 | #else 34 | #define public_for_test private 35 | #endif 36 | 37 | #endif // __SVR2_UTIL_MACROS_H__ 38 | -------------------------------------------------------------------------------- /enclave/util/mutex.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_MUTEX_H__ 5 | #define __SVR2_UTIL_MUTEX_H__ 6 | 7 | #include 8 | #include "util/threadsafetyannotations.h" 9 | #include "util/macros.h" 10 | 11 | namespace svr2::util { 12 | 13 | // These classes are simple wrappers around equivalent std::xxx classes, 14 | // except they've been augmented with Clang thread safety annotations 15 | // for static analysis of locking. 16 | 17 | class CAPABILITY("mutex") mutex { 18 | public: 19 | DELETE_COPY_AND_ASSIGN(mutex); 20 | mutex() {} 21 | inline void lock() ACQUIRE() { mu_.lock(); } 22 | inline void unlock() RELEASE() { mu_.unlock(); } 23 | inline bool try_lock() TRY_ACQUIRE(true) { return mu_.try_lock(); } 24 | 25 | // For negative thread safety analysis capabilities only. 26 | const mutex& operator!() const { 27 | CHECK(nullptr == "this function should be used only for thread annotations"); 28 | return *this; 29 | } 30 | 31 | private: 32 | std::mutex mu_; 33 | }; 34 | 35 | template 36 | class SCOPED_CAPABILITY unique_lock { 37 | public: 38 | DELETE_COPY_AND_ASSIGN(unique_lock); 39 | unique_lock(T& mu) ACQUIRE(mu) : mu_(mu), locked_(true) { mu_.lock(); } 40 | ~unique_lock() RELEASE() { if (locked_) mu_.unlock(); } 41 | unique_lock(T& mu, std::defer_lock_t d) EXCLUDES(mu) : mu_(mu), locked_(false) { } 42 | inline void lock() ACQUIRE() { mu_.lock(); locked_ = true; } 43 | inline void unlock() RELEASE() { mu_.unlock(); locked_ = false; } 44 | 45 | private: 46 | T& mu_; 47 | bool locked_; 48 | }; 49 | 50 | } // namespace svr2::util 51 | 52 | #endif // __SVR2_UTIL_MUTEX_H__ 53 | -------------------------------------------------------------------------------- /enclave/util/tests/base64.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP proto 6 | //TESTDEP protobuf-lite 7 | //TESTDEP metrics 8 | //TESTDEP util 9 | //TESTDEP env 10 | //TESTDEP env/test 11 | //TESTDEP context 12 | //TESTDEP libsodium 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "util/base64.h" 20 | #include "env/env.h" 21 | 22 | namespace svr2::util { 23 | 24 | class Base64Test : public ::testing::Test { 25 | protected: 26 | static void SetUpTestSuite() { 27 | env::Init(env::SIMULATED); 28 | } 29 | }; 30 | 31 | TEST_F(Base64Test, Decode) { 32 | std::string in("TWFu"); 33 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 34 | ASSERT_EQ("Man", in); 35 | in = "TWE"; 36 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 37 | ASSERT_EQ("Ma", in); 38 | in = "TWE="; 39 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 40 | ASSERT_EQ("Ma", in); 41 | in = "TQ"; 42 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 43 | ASSERT_EQ("M", in); 44 | in = "TQ="; 45 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 46 | ASSERT_EQ("M", in); 47 | in = "TQ=="; 48 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 49 | ASSERT_EQ("M", in); 50 | in = "TQ====="; 51 | ASSERT_EQ(error::OK, B64DecodeInline(&in, B64STD)); 52 | ASSERT_EQ("M", in); 53 | in = "TQ==x"; 54 | ASSERT_EQ(error::Util_Base64InvalidPadding, B64DecodeInline(&in, B64STD)); 55 | } 56 | 57 | TEST_F(Base64Test, Encode) { 58 | EXPECT_EQ(Base64Encode("Man", B64STD, false), "TWFu"); 59 | EXPECT_EQ(Base64Encode("Ma", B64STD, false), "TWE"); 60 | EXPECT_EQ(Base64Encode("M", B64STD, false), "TQ"); 61 | EXPECT_EQ(Base64Encode("Man", B64STD, true), "TWFu"); 62 | EXPECT_EQ(Base64Encode("Ma", B64STD, true), "TWE="); 63 | EXPECT_EQ(Base64Encode("M", B64STD, true), "TQ=="); 64 | } 65 | 66 | } // namespace svr2::util 67 | -------------------------------------------------------------------------------- /enclave/util/tests/constant.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP util 6 | #include 7 | #include "util/constant.h" 8 | #include 9 | #include 10 | 11 | namespace svr2::util { 12 | 13 | class ConstantTest : public ::testing::Test {}; 14 | 15 | TEST_F(ConstantTest, Equality) { 16 | std::string a("abc"); 17 | std::array b{'a', 'b', 'c'}; 18 | EXPECT_TRUE(ConstantTimeEquals(a, a)); 19 | EXPECT_TRUE(ConstantTimeEquals(a, b)); 20 | EXPECT_TRUE(ConstantTimeEquals(b, a)); 21 | std::string c("aBc"); 22 | EXPECT_FALSE(ConstantTimeEquals(a, c)); 23 | EXPECT_FALSE(ConstantTimeEquals(c, a)); 24 | EXPECT_FALSE(ConstantTimeEquals(b, c)); 25 | EXPECT_FALSE(ConstantTimeEquals(c, b)); 26 | } 27 | 28 | } // namespace svr2::util 29 | -------------------------------------------------------------------------------- /enclave/util/tests/endian.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP util 6 | #include 7 | #include "util/endian.h" 8 | #include 9 | #include 10 | 11 | namespace svr2::util { 12 | 13 | class EndianTest : public ::testing::Test {}; 14 | 15 | TEST_F(EndianTest, BigEndian64RoundTrip) { 16 | uint8_t buf[8] = {0}; 17 | BigEndian64Bytes(0xfedcba9876543210ULL, buf); 18 | ASSERT_EQ(BigEndian64FromBytes(buf), 0xfedcba9876543210ULL); 19 | uint8_t expected[8] = {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}; 20 | ASSERT_EQ(0, memcmp(buf, expected, 8)); 21 | } 22 | 23 | TEST_F(EndianTest, BigEndian32RoundTrip) { 24 | uint8_t buf[4] = {0}; 25 | BigEndian32Bytes(0xfedc4321UL, buf); 26 | ASSERT_EQ(BigEndian32FromBytes(buf), 0xfedc4321UL); 27 | uint8_t expected[8] = {0xfe, 0xdc, 0x43, 0x21}; 28 | ASSERT_EQ(0, memcmp(buf, expected, 4)); 29 | } 30 | 31 | } // namespace svr2::util 32 | -------------------------------------------------------------------------------- /enclave/util/tests/hex.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | //TESTDEP gtest 5 | //TESTDEP util 6 | //TESTDEP metrics 7 | //TESTDEP proto 8 | //TESTDEP protobuf-lite 9 | //TESTDEP env 10 | //TESTDEP libsodium 11 | #include 12 | #include "util/hex.h" 13 | #include 14 | 15 | namespace svr2::util { 16 | 17 | class HexTest : public ::testing::Test {}; 18 | 19 | TEST_F(HexTest, ToHex) { 20 | std::string a("\x01\x02\x0a"); 21 | std::array b{4, 0x3b, 0xff}; 22 | EXPECT_EQ("01020a", ToHex(a)); 23 | EXPECT_EQ("043bff", ToHex(b)); 24 | } 25 | 26 | TEST_F(HexTest, PrefixToHex) { 27 | std::array b{4, 0x3b, 0xff}; 28 | EXPECT_EQ("", PrefixToHex(b, 0)); 29 | EXPECT_EQ("043b", PrefixToHex(b, 2)); 30 | EXPECT_EQ("043bff", PrefixToHex(b, 3)); 31 | EXPECT_EQ("043bff", PrefixToHex(b, 4)); 32 | } 33 | 34 | } // namespace svr2::util 35 | -------------------------------------------------------------------------------- /enclave/util/threadsafetyannotations.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_THREADSAFETYANNOTATIONS_H__ 5 | #define __SVR2_UTIL_THREADSAFETYANNOTATIONS_H__ 6 | 7 | // Taken from https://releases.llvm.org/11.0.0/tools/clang/docs/ThreadSafetyAnalysis.html 8 | 9 | // Enable thread safety attributes only with clang. 10 | // The attributes can be safely erased when compiling with other compilers. 11 | #if defined(__clang__) && (!defined(SWIG)) 12 | #define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) 13 | #else 14 | #define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op 15 | #endif 16 | 17 | #define CAPABILITY(x) \ 18 | THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) 19 | 20 | #define SCOPED_CAPABILITY \ 21 | THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) 22 | 23 | #define GUARDED_BY(x) \ 24 | THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) 25 | 26 | #define PT_GUARDED_BY(x) \ 27 | THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) 28 | 29 | #define ACQUIRED_BEFORE(...) \ 30 | THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) 31 | 32 | #define ACQUIRED_AFTER(...) \ 33 | THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) 34 | 35 | #define REQUIRES(...) \ 36 | THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) 37 | 38 | #define REQUIRES_SHARED(...) \ 39 | THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) 40 | 41 | #define ACQUIRE(...) \ 42 | THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) 43 | 44 | #define ACQUIRE_SHARED(...) \ 45 | THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) 46 | 47 | #define RELEASE(...) \ 48 | THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) 49 | 50 | #define RELEASE_SHARED(...) \ 51 | THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) 52 | 53 | #define TRY_ACQUIRE(...) \ 54 | THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) 55 | 56 | #define TRY_ACQUIRE_SHARED(...) \ 57 | THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) 58 | 59 | #define EXCLUDES(...) \ 60 | THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) 61 | 62 | #define ASSERT_CAPABILITY(x) \ 63 | THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) 64 | 65 | #define ASSERT_SHARED_CAPABILITY(x) \ 66 | THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) 67 | 68 | #define RETURN_CAPABILITY(x) \ 69 | THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) 70 | 71 | #define NO_THREAD_SAFETY_ANALYSIS \ 72 | THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) 73 | 74 | #endif // __SVR2_UTIL_THREADSAFETYANNOTATIONS_H__ 75 | -------------------------------------------------------------------------------- /enclave/util/ticks.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "util/ticks.h" 5 | 6 | namespace svr2::util { 7 | const Ticks InvalidTicks = INT64_MAX; 8 | } // namespace svr2::util 9 | -------------------------------------------------------------------------------- /enclave/util/ticks.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #ifndef __SVR2_UTIL_TICKS_H__ 5 | #define __SVR2_UTIL_TICKS_H__ 6 | 7 | #include 8 | #include 9 | 10 | namespace svr2::util { 11 | 12 | typedef int64_t Ticks; 13 | extern const Ticks InvalidTicks; 14 | typedef time_t UnixSecs; 15 | 16 | } // namespace svr2::util 17 | 18 | #endif // __SVR2_UTIL_TICKS_H__ 19 | -------------------------------------------------------------------------------- /host/.gitignore: -------------------------------------------------------------------------------- 1 | *.pb.go 2 | main 3 | enclave/c 4 | enclave/enclave.test 5 | cmd/control/control 6 | cmd/svr2client/svr2client 7 | cmd/svr3client/svr3client 8 | cmd/get_sev_chain/get_sev_chain 9 | cmd/svr3gcp/svr3gcp 10 | miniredis/miniredis 11 | -------------------------------------------------------------------------------- /host/.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.23.5 2 | -------------------------------------------------------------------------------- /host/README.md: -------------------------------------------------------------------------------- 1 | # SVR2 Host Code 2 | 3 | This codebase provides a host-side binary for running and interacting with 4 | an enclave, while also interacting with the outside world (external services, 5 | clients, etc), and acts as a bridge between these two worlds. It follows 6 | typical Go paradigms. 7 | 8 | ## Go Version 9 | 10 | This code was developed on Go 1.19+, so yay, generics are a thing. 11 | 12 | ## Testing 13 | 14 | The usual `go test ./...` won’t necessarily work, because some tests depend on the enclave 15 | and generated code. Run `make -C .. [docker_]host_test` 16 | 17 | ## Formatting 18 | 19 | One `gofmt` to rule them all, except `goimports` for, you know, the imports: 20 | ```shell 21 | go install golang.org/x/tools/cmd/goimports@latest # if needed 22 | goimports -w -local 'github.com/signalapp/svr2' $(find . -name '*.go' -not -path '*.pb.go') 23 | ``` 24 | -------------------------------------------------------------------------------- /host/auth/auth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package auth 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | "time" 10 | 11 | "github.com/signalapp/svr2/util" 12 | ) 13 | 14 | func TestAuthWorks(t *testing.T) { 15 | a := &auth{ 16 | secret: []byte{1, 2, 3, 4, 5}, 17 | clock: util.TestAt(time.Unix(10000, 0)), 18 | expiration: 3600 * time.Second, 19 | } 20 | for _, test := range []struct{ user, pass string }{ 21 | {user: "12345", pass: "10000:8b2df41718f48f312c6d"}, 22 | {user: "12345", pass: "13600:fb1e57e272683fb785b1"}, 23 | {user: "12345", pass: "6400:614c7129a946e79c83ed"}, 24 | {user: "123456", pass: "10000:9a08d531879caa2a81f0"}, 25 | {user: "wizzle", pass: a.PassFor("wizzle")}, 26 | } { 27 | t.Logf("%+v", test) 28 | if err := a.Check(test.user, test.pass); err != nil { 29 | t.Errorf("expected check success, got error: %v", err) 30 | } 31 | } 32 | 33 | validPass := []byte{0x8b, 0x2d, 0xf4, 0x17, 0x18, 0xf4, 0x8f, 0x31, 0x2c, 0x6d} 34 | for i := 0; i < len(validPass); i++ { 35 | for j := 0; j < 8; j++ { 36 | b := make([]byte, len(validPass)) 37 | copy(b, validPass) 38 | b[i] ^= 1 << j 39 | badPass := fmt.Sprintf("10000:%x", b) 40 | if err := a.Check("12345", badPass); err == nil { 41 | t.Errorf("bitflipped pass, want error got success: valid=%x ours=%q", validPass, badPass) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /host/cmd/get_sev_chain/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // During the private preview of GCP Confidential VMs with AMD SEV-SNP 5 | // integration, they're not filling in the VCEK/ASK certificates via 6 | // SNP_SET_EXT_CONFIG. This script pulls them via the `go-sev-guest` 7 | // library and dumps them to a file that's accessible to the enclave 8 | // process. These certs are long-lived, so should only need to be pulled 9 | // once. This avoids us having to do HTTPS GET calls directly from 10 | // the enclave C++ code. 11 | // 12 | // Note: these are pulled from AMD, as described in https://www.amd.com/system/files/TechDocs/57230.pdf 13 | package main 14 | 15 | import ( 16 | "flag" 17 | "log" 18 | "os" 19 | 20 | "github.com/google/go-sev-guest/client" 21 | "github.com/google/go-sev-guest/verify" 22 | "google.golang.org/protobuf/proto" 23 | 24 | pb "github.com/signalapp/svr2/proto" 25 | ) 26 | 27 | var ( 28 | outputFilename = flag.String("out", "endorsements.pb", "File to write endorsements to") 29 | ) 30 | 31 | func main() { 32 | flag.Parse() 33 | dev, err := client.OpenDevice() 34 | if err != nil { 35 | log.Fatalf("OpenDevice: %v", err) 36 | } 37 | report, err := client.GetReport(dev, [64]byte{}) 38 | if err != nil { 39 | log.Fatalf("GetReport: %v", err) 40 | } 41 | attestation, err := verify.GetAttestationFromReport(report, &verify.Options{}) 42 | if err != nil { 43 | log.Fatalf("GetAttestationFromReport: %v", err) 44 | } 45 | out := &pb.SevSnpEndorsements{} 46 | out.VcekDer = attestation.CertificateChain.VcekCert 47 | out.AskDer = attestation.CertificateChain.AskCert 48 | out.ArkDer = attestation.CertificateChain.ArkCert 49 | data, err := proto.Marshal(out) 50 | if err != nil { 51 | log.Fatalf("proto.Marshal: %v", err) 52 | } 53 | if f, err := os.Create(*outputFilename); err != nil { 54 | log.Fatalf("os.Create: %v", err) 55 | } else if _, err := f.Write(data); err != nil { 56 | log.Fatalf("f.Write: %v", err) 57 | } else if err := f.Close(); err != nil { 58 | log.Fatalf("f.Close: %v", err) 59 | } 60 | log.Println("Success") 61 | } 62 | -------------------------------------------------------------------------------- /host/config/config_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package config 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | 10 | "go.uber.org/zap" 11 | ) 12 | 13 | func TestConfig(t *testing.T) { 14 | var yaml = ` 15 | log: 16 | level: info 17 | raft: 18 | tickDuration: 1000ms 19 | metricPollDuration: 2h 20 | ` 21 | conf, err := unmarshal([]byte(yaml)) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if conf.Log.Level.Level() != zap.InfoLevel { 26 | t.Errorf("conf.level=%v, want %v", conf.Log.Level.Level(), zap.InfoLevel) 27 | } 28 | if conf.Log.Encoding != "console" { 29 | t.Errorf("conf.encoding=%v, want %v", conf.Log.Encoding, "console") 30 | } 31 | if conf.Raft.TickDuration != time.Second { 32 | t.Errorf("conf.raft.tickDuration=%v, want %v", conf.Raft.TickDuration, time.Second) 33 | } 34 | if conf.Raft.MetricPollDuration != 2*time.Hour { 35 | t.Errorf("conf.raft.metricPollDuration=%v, want %v", conf.Raft.MetricPollDuration, time.Hour*2) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /host/config/peer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | type PeerConfig struct { 12 | // minimum time to sleep for exponential backoff retries to connect to a peer 13 | MinSleepDuration time.Duration `yaml:"minSleepDuration"` 14 | // maximum time to sleep for exponential backoff retries to connect to a peer 15 | MaxSleepDuration time.Duration `yaml:"maxSleepDuration"` 16 | // maximum time to attempt to connect to a peer before giving up 17 | AbandonDuration time.Duration `yaml:"abandonDuration"` 18 | // maximum number of messages to buffer for sending to a peer 19 | BufferSize int `yaml:"bufferSize"` 20 | } 21 | 22 | func (p *PeerConfig) validate() []string { 23 | var errs []string 24 | if p.BufferSize < 1 { 25 | errs = append(errs, fmt.Sprintf("invalid BufferSize: %v", p.BufferSize)) 26 | } 27 | if p.MinSleepDuration > p.MaxSleepDuration { 28 | errs = append(errs, fmt.Sprintf("MinSleep (%v) must be less than MaxSleep (%v)", p.MinSleepDuration, p.MaxSleepDuration)) 29 | } 30 | return errs 31 | } 32 | -------------------------------------------------------------------------------- /host/config/raft.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | type RaftHostConfig struct { 12 | // how often to update the peerdb to let other peers know we're joinable 13 | RefreshStatusDuration time.Duration `yaml:"refreshStatusDuration"` 14 | // how often to fetch a fresh attestation in the enclave 15 | RefreshAttestationDuration time.Duration `yaml:"refreshAttestationDuration"` 16 | // how often to send a raft tick down to the enclave 17 | TickDuration time.Duration `yaml:"tickDuration"` 18 | // how often to poll metrics from the enclave 19 | MetricPollDuration time.Duration `yaml:"metricPollDuration"` 20 | // how often to update env stats (memory usage, etc) 21 | EnvStatsPollDuration time.Duration `yaml:"envStatsPollDuration"` 22 | // max number of in-flight enclave calls 23 | EnclaveConcurrency int `yaml:"enclaveConcurrency"` 24 | // timeout for intitial raft Joining attempt 25 | InitialJoinDuration time.Duration `yaml:"initialJoinDuration"` 26 | } 27 | 28 | func (r *RaftHostConfig) validate() []string { 29 | var errs []string 30 | if r.EnclaveConcurrency <= 1 { 31 | errs = append(errs, fmt.Sprintf("invalid EnclaveConcurrency: %v", r.EnclaveConcurrency)) 32 | } 33 | if r.TickDuration <= 0 { 34 | errs = append(errs, fmt.Sprintf("invalid TickDuration: %v", r.TickDuration)) 35 | } 36 | if r.RefreshAttestationDuration <= 0 { 37 | errs = append(errs, fmt.Sprintf("invalid RefreshAttestationDuration: %v", r.RefreshAttestationDuration)) 38 | } 39 | return errs 40 | } 41 | -------------------------------------------------------------------------------- /host/config/rate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | type RateLimitConfig struct { 12 | // The maximum size of the leaky bucket. This is the maximum "burst" of requests that will be allowed 13 | BucketSize int `yaml:"bucketSize"` 14 | // The amount of requests that will be added (up to BucketSize) per LeakRateDuration 15 | LeakRateScalar int `yaml:"leakRateScalar"` 16 | // The period at which LeakRateScalar additional requests will be allowed 17 | LeakRateDuration time.Duration `yaml:"leakRateDuration"` 18 | // If true, do no rate limiting. 19 | SkipLimiting bool `yaml:"skipLimiting"` 20 | } 21 | 22 | func (r *RateLimitConfig) validate() []string { 23 | if r.SkipLimiting { 24 | return nil 25 | } 26 | var errs []string 27 | if r.BucketSize < 0 { 28 | errs = append(errs, fmt.Sprintf("invalid BucketSize: %v", r.BucketSize)) 29 | } 30 | if r.LeakRateScalar < 0 { 31 | errs = append(errs, fmt.Sprintf("invalid LeakRateDuration: %v", r.LeakRateScalar)) 32 | } 33 | return errs 34 | } 35 | -------------------------------------------------------------------------------- /host/config/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type RedisConfig struct { 13 | // A seed list of host:port addresses of cluster nodes. 14 | Addrs []string `yaml:"addrs"` 15 | // password for instance (may be blank if protected mode is disabled) 16 | Password string `yaml:"password"` 17 | // a unique name for the deployment 18 | Name string `yaml:"name"` 19 | // minimum time to sleep for exponential backoff retries to redis 20 | MinSleepDuration time.Duration `yaml:"minSleepDuration"` 21 | // maximum time to sleep for exponential backoff retries to redis 22 | MaxSleepDuration time.Duration `yaml:"maxSleepDuration"` 23 | } 24 | 25 | func (r *RedisConfig) validate() []string { 26 | var errs []string 27 | if len(r.Addrs) == 0 { 28 | errs = append(errs, fmt.Sprintf("must provide redis Addrs")) 29 | } 30 | for _, addr := range r.Addrs { 31 | spl := strings.Split(addr, ":") 32 | if len(spl) != 2 { 33 | errs = append(errs, fmt.Sprintf("invalid redis Addr %v", addr)) 34 | } 35 | } 36 | return errs 37 | } 38 | -------------------------------------------------------------------------------- /host/config/request.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package config 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | type RequestConfig struct { 12 | // Timeout to perform websocket handshake over http connection 13 | WebsocketHandshakeTimeout time.Duration `yaml:"socketTimeout"` 14 | 15 | // Timeout for websocket read/write operations 16 | SocketTimeout time.Duration `yaml:"socketTimeout"` 17 | } 18 | 19 | func (r *RequestConfig) validate() []string { 20 | var errs []string 21 | if r.WebsocketHandshakeTimeout <= 0 { 22 | errs = append(errs, fmt.Sprintf("Handshake timeout %v must be >0", r.WebsocketHandshakeTimeout)) 23 | } 24 | if r.SocketTimeout <= 0 { 25 | errs = append(errs, fmt.Sprintf("Socket timeout %v must be >0", r.SocketTimeout)) 26 | } 27 | return errs 28 | } 29 | -------------------------------------------------------------------------------- /host/enclave.config.sample: -------------------------------------------------------------------------------- 1 | enclave_config { 2 | raft { 3 | election_ticks: 30 4 | heartbeat_ticks: 15 5 | replication_chunk_bytes: 1048576 6 | replica_voting_timeout_ticks: 60 7 | replica_membership_timeout_ticks: 300 8 | log_max_bytes: 104857600 9 | replication_pipeline: 32 10 | } 11 | e2e_txn_timeout_ticks: 30 12 | send_timestamp_ticks: 60 13 | } 14 | initial_log_level: LOG_LEVEL_INFO 15 | group_config { 16 | min_voting_replicas: 1 17 | max_voting_replicas: 5 18 | super_majority: 0 19 | db_version: DATABASE_VERSION_SVR2 20 | attestation_timeout: 86400 21 | simulated: true 22 | } 23 | -------------------------------------------------------------------------------- /host/enclave/callback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package enclave 5 | 6 | import ( 7 | "reflect" 8 | "unsafe" 9 | ) 10 | 11 | // #include 12 | import "C" 13 | 14 | //export svr2OutputMessageGoCallback 15 | func svr2OutputMessageGoCallback(size C.size_t, msg *C.uchar) { 16 | var msgSlice []byte 17 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&msgSlice)) 18 | hdr.Len = int(size) 19 | hdr.Cap = int(size) 20 | hdr.Data = uintptr(unsafe.Pointer(msg)) 21 | receiveMessage(msgSlice) 22 | } 23 | -------------------------------------------------------------------------------- /host/enclave/iface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package enclave 5 | 6 | import ( 7 | "github.com/signalapp/svr2/peerid" 8 | pb "github.com/signalapp/svr2/proto" 9 | ) 10 | 11 | type Enclave interface { 12 | PID() peerid.PeerID 13 | OutputMessages() <-chan *pb.EnclaveMessage 14 | SendMessage(msgPB *pb.UntrustedMessage) error 15 | } 16 | -------------------------------------------------------------------------------- /host/enclave/logging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package enclave 5 | 6 | import ( 7 | "unsafe" 8 | 9 | "go.uber.org/zap" 10 | "go.uber.org/zap/zapcore" 11 | ) 12 | 13 | // #include 14 | // #include 15 | // #include 16 | // #include "c/svr2_u.h" 17 | import "C" 18 | 19 | //export svr2LogCallback 20 | func svr2LogCallback( 21 | context unsafe.Pointer, 22 | is_enclave bool, 23 | t *C.struct_tm, 24 | usecs C.long, 25 | level C.oe_log_level_t, 26 | host_thread_id uint64, 27 | msg *C.char) { 28 | 29 | var zapLevel zapcore.Level 30 | switch level { 31 | case C.OE_LOG_LEVEL_NONE: 32 | zapLevel = zapcore.ErrorLevel 33 | case C.OE_LOG_LEVEL_FATAL: 34 | // use error for fatal, fatal in go means panic 35 | zapLevel = zapcore.ErrorLevel 36 | case C.OE_LOG_LEVEL_ERROR: 37 | zapLevel = zapcore.ErrorLevel 38 | case C.OE_LOG_LEVEL_WARNING: 39 | zapLevel = zapcore.WarnLevel 40 | case C.OE_LOG_LEVEL_INFO: 41 | zapLevel = zapcore.InfoLevel 42 | case C.OE_LOG_LEVEL_VERBOSE: 43 | zapLevel = zapcore.DebugLevel 44 | case C.OE_LOG_LEVEL_MAX: 45 | zapLevel = zapcore.DebugLevel 46 | default: 47 | zapLevel = zapcore.ErrorLevel 48 | } 49 | 50 | zap.L().Log(zapLevel, C.GoString(msg), 51 | zap.Uint64("host_thread", host_thread_id)) 52 | } 53 | -------------------------------------------------------------------------------- /host/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/signalapp/svr2 2 | 3 | go 1.23.5 4 | 5 | require ( 6 | github.com/alicebob/miniredis/v2 v2.34.0 7 | github.com/go-redis/redis_rate/v10 v10.0.1 8 | github.com/google/go-sev-guest v0.12.1 9 | github.com/google/go-tpm v0.9.3 10 | github.com/google/go-tpm-tools v0.4.4 11 | github.com/gtank/ristretto255 v0.1.2 12 | github.com/hashicorp/go-metrics v0.5.4 13 | github.com/mdlayher/vsock v1.2.1 14 | github.com/redis/go-redis/v9 v9.7.0 15 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 16 | golang.org/x/sync v0.10.0 17 | ) 18 | 19 | require ( 20 | github.com/DataDog/datadog-go v4.8.3+incompatible // indirect 21 | github.com/Microsoft/go-winio v0.6.2 // indirect 22 | github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect 23 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 24 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 25 | github.com/google/go-configfs-tsm v0.3.2 // indirect 26 | github.com/google/go-tdx-guest v0.3.1 // indirect 27 | github.com/google/logger v1.1.1 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 30 | github.com/hashicorp/golang-lru v1.0.2 // indirect 31 | github.com/mdlayher/socket v0.5.1 // indirect 32 | github.com/yuin/gopher-lua v1.1.1 // indirect 33 | golang.org/x/crypto v0.32.0 // indirect 34 | golang.org/x/net v0.34.0 // indirect 35 | golang.org/x/sys v0.29.0 // indirect 36 | ) 37 | 38 | require ( 39 | github.com/flynn/noise v1.1.0 40 | github.com/google/go-cmp v0.6.0 41 | github.com/gorilla/websocket v1.5.3 42 | go.uber.org/multierr v1.11.0 // indirect 43 | go.uber.org/zap v1.27.0 44 | google.golang.org/protobuf v1.36.3 45 | gopkg.in/yaml.v2 v2.4.0 46 | ) 47 | -------------------------------------------------------------------------------- /host/health/health.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package health 5 | 6 | import ( 7 | "fmt" 8 | "net/http" 9 | "sync" 10 | 11 | "github.com/signalapp/svr2/logger" 12 | ) 13 | 14 | // Health wraps an error (nil means "healthy"), and provides HTTP handling 15 | // logic to serve that error. 16 | type Health struct { 17 | mu sync.Mutex 18 | name string 19 | err error 20 | } 21 | 22 | // New creates a new health object, with initial health set based on the 23 | // 'initial' error (nil==healthy). 24 | func New(name string, initial error) *Health { 25 | return &Health{name: name, err: initial} 26 | } 27 | 28 | // Set sets the underlying error for this Health object; err=nil means "OK" 29 | func (h *Health) Set(err error) { 30 | h.mu.Lock() 31 | oldErr := h.err 32 | h.err = err 33 | h.mu.Unlock() 34 | if (err == nil) != (oldErr == nil) { 35 | logger.Infof("Setting health %q from [%v] -> [%v]", h.name, oldErr, err) 36 | } 37 | } 38 | 39 | // ServeHTTP implements http.Handler. 40 | func (h *Health) ServeHTTP(w http.ResponseWriter, r *http.Request) { 41 | h.mu.Lock() 42 | err := h.err 43 | h.mu.Unlock() 44 | if err == nil { 45 | fmt.Fprintf(w, "ok") 46 | return 47 | } 48 | w.WriteHeader(http.StatusInternalServerError) 49 | fmt.Fprintf(w, "error: %v", err) 50 | } 51 | -------------------------------------------------------------------------------- /host/health/health_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package health 5 | 6 | import ( 7 | "errors" 8 | "net/http" 9 | "net/http/httptest" 10 | "testing" 11 | ) 12 | 13 | func TestServingFromHealthy(t *testing.T) { 14 | h := New("test", nil) 15 | ts := httptest.NewServer(h) 16 | defer ts.Close() 17 | res, err := http.Get(ts.URL) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | if res.StatusCode != http.StatusOK { 22 | t.Errorf("nil error returned status: %v", res.Status) 23 | } 24 | h.Set(errors.New("FUBAR")) 25 | res, err = http.Get(ts.URL) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | if res.StatusCode != http.StatusInternalServerError { 30 | t.Errorf("non-nil error returned status: %v", res.Status) 31 | } 32 | } 33 | 34 | func TestServingFromUnhealthy(t *testing.T) { 35 | h := New("test", errors.New("FUBAR")) 36 | ts := httptest.NewServer(h) 37 | defer ts.Close() 38 | res, err := http.Get(ts.URL) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | if res.StatusCode != http.StatusInternalServerError { 43 | t.Errorf("non-nil error returned status: %v", res.Status) 44 | } 45 | h.Set(nil) 46 | res, err = http.Get(ts.URL) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if res.StatusCode != http.StatusOK { 51 | t.Errorf("nil error returned status: %v", res.Status) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /host/host.config.sample: -------------------------------------------------------------------------------- 1 | clientListenAddr: localhost:8080 2 | controlListenAddr: localhost:8081 3 | peerAddr: localhost:8082 4 | enclaveId: 17e1cb662572d28e0eb5a492ed8df949bc2cfcf3f2098b710e7b637759d6dcb3 5 | 6 | raft: 7 | tickDuration: 1s 8 | metricPollDuration: 10s 9 | refreshStatusDuration: 15s 10 | enclaveConcurrency: 8 11 | 12 | redis: 13 | addrs: [localhost:9999] 14 | -------------------------------------------------------------------------------- /host/integration/testdata/enclave.config: -------------------------------------------------------------------------------- 1 | enclave_config { 2 | raft { 3 | election_ticks: 30 4 | heartbeat_ticks: 15 5 | replication_chunk_bytes: 1048576 6 | replica_voting_timeout_ticks: 60 7 | replica_membership_timeout_ticks: 300 8 | log_max_bytes: 10000000 9 | } 10 | e2e_txn_timeout_ticks: 30 11 | } 12 | initial_log_level: LOG_LEVEL_DEBUG 13 | group_config { 14 | min_voting_replicas: 1 15 | max_voting_replicas: 5 16 | super_majority: 0 17 | db_version: DATABASE_VERSION_SVR2 18 | attestation_timeout: 86400 19 | simulated: true 20 | } 21 | -------------------------------------------------------------------------------- /host/logger/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package logger provides helper functions to configure and use a global logger 5 | package logger 6 | 7 | import ( 8 | "log" 9 | 10 | "go.uber.org/zap" 11 | "go.uber.org/zap/zapcore" 12 | 13 | "github.com/signalapp/svr2/config" 14 | ) 15 | 16 | // init sets up reasonable logging defaults for tests / non-main 17 | func init() { 18 | Init(config.Default()) 19 | } 20 | 21 | // Init configures global loggers accessed with logging functions in this module. 22 | // Optionaly provided fields will bind some key/value pairs to the global logger 23 | func Init(cfg *config.Config) { 24 | z, err := cfg.Log.Build(zap.AddCallerSkip(1)) 25 | if err != nil { 26 | log.Fatalf("zap init: %v", err) 27 | } 28 | zap.ReplaceGlobals(z) 29 | } 30 | 31 | // WithGlobal binds some key/value pairs to the global logger 32 | func WithGlobal(fields ...zapcore.Field) { 33 | zap.ReplaceGlobals(zap.L().With(fields...)) 34 | } 35 | 36 | // With returns a logger with some bound key/value pairs 37 | func With(keysAndValues ...interface{}) *Logger { 38 | return &Logger{zap.S().With(keysAndValues...)} 39 | } 40 | 41 | // Sync flushes any buffered logs. Applications should call Sync before program exit. 42 | func Sync() { 43 | zap.L().Sync() 44 | } 45 | 46 | type Logger struct { 47 | *zap.SugaredLogger 48 | } 49 | 50 | // wrappers around sugared zap logging methods that use the zap global logger 51 | 52 | func Infow(msg string, keysAndValues ...interface{}) { zap.S().Infow(msg, keysAndValues...) } 53 | func Infof(template string, args ...interface{}) { zap.S().Infof(template, args...) } 54 | func Debugw(msg string, keysAndValues ...interface{}) { zap.S().Debugw(msg, keysAndValues...) } 55 | func Debugf(template string, args ...interface{}) { zap.S().Debugf(template, args...) } 56 | func Warnw(msg string, keysAndValues ...interface{}) { zap.S().Warnw(msg, keysAndValues...) } 57 | func Warnf(template string, args ...interface{}) { zap.S().Warnf(template, args...) } 58 | func Errorw(msg string, keysAndValues ...interface{}) { zap.S().Errorw(msg, keysAndValues...) } 59 | func Errorf(template string, args ...interface{}) { zap.S().Errorf(template, args...) } 60 | func Fatalw(msg string, keysAndValues ...interface{}) { zap.S().Fatalw(msg, keysAndValues...) } 61 | func Fatalf(template string, args ...interface{}) { zap.S().Fatalf(template, args...) } 62 | -------------------------------------------------------------------------------- /host/miniredis/miniredis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Binary miniredis sets up a usable Redis port at --addr, good for simple local testing. 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "log" 10 | 11 | "github.com/alicebob/miniredis/v2" 12 | ) 13 | 14 | var ( 15 | addr = flag.String("addr", "localhost:6379", "MiniRedis bind address") 16 | ) 17 | 18 | func main() { 19 | flag.Parse() 20 | r := miniredis.NewMiniRedis() 21 | if err := r.StartAddr(*addr); err != nil { 22 | log.Fatal(err) 23 | } 24 | select {} 25 | } 26 | -------------------------------------------------------------------------------- /host/peer/peer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package peer implements the host to host network protocol for sending messages between enclaves 5 | package peer 6 | 7 | import ( 8 | pb "github.com/signalapp/svr2/proto" 9 | ) 10 | 11 | var ( 12 | maxMessageLength uint64 = 1024 * 1024 * 128 13 | ) 14 | 15 | type EnclaveSender interface { 16 | // Send sends a message to the enclave and potentially waits for a reply 17 | Send(p *pb.UntrustedMessage) (*pb.EnclaveMessage, error) 18 | } 19 | -------------------------------------------------------------------------------- /host/peer/sequence_number.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package peer 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | 10 | pb "github.com/signalapp/svr2/proto" 11 | ) 12 | 13 | type sequenceNumber struct { 14 | epoch uint32 15 | seq uint64 16 | } 17 | 18 | // follows returns true if this seqeunce number directly follows the provided sequence number. 19 | // This is the case if either it's the next in the sequence, or it is from a greater epoch. 20 | func (s sequenceNumber) follows(pred sequenceNumber) bool { 21 | return (s.epoch > pred.epoch && s.seq == 0) || (s.epoch == pred.epoch && s.seq == pred.seq+1) 22 | } 23 | 24 | // next returns the sequenceNumber of the next message within an enclave peer session 25 | func (s sequenceNumber) next() sequenceNumber { 26 | return sequenceNumber{ 27 | epoch: s.epoch, 28 | seq: s.seq + 1, 29 | } 30 | } 31 | 32 | // nextEpoch returns the sequenceNumber of the next epoch, typically denoting the start of a new 33 | // enclave peer session 34 | func (s sequenceNumber) nextEpoch() sequenceNumber { 35 | return sequenceNumber{ 36 | epoch: s.epoch + 1, 37 | seq: 0, 38 | } 39 | } 40 | 41 | // cmp compares two sequence numbers 42 | // 43 | // returns: 44 | // 45 | // < 0 if this is less than the provided sequenceNumber 46 | // == 0 if this is equal to the provided sequenceNumber 47 | // > 0 if this is greater than the provided sequenceNumber 48 | func (s sequenceNumber) cmp(o sequenceNumber) int { 49 | if ecmp := int(s.epoch - o.epoch); ecmp != 0 { 50 | return ecmp 51 | } 52 | return int(s.seq - o.seq) 53 | } 54 | 55 | func (s sequenceNumber) proto() *pb.SequenceNumber { 56 | return &pb.SequenceNumber{ 57 | Epoch: s.epoch, 58 | Seq: s.seq, 59 | } 60 | } 61 | 62 | func makeSeqno(p *pb.SequenceNumber) (sequenceNumber, error) { 63 | if p == nil { 64 | return sequenceNumber{}, errors.New("expected a sequence number present on message") 65 | } 66 | return sequenceNumber{ 67 | epoch: p.Epoch, 68 | seq: p.Seq, 69 | }, nil 70 | } 71 | 72 | func (s sequenceNumber) String() string { 73 | return fmt.Sprintf("%v:%v", s.epoch, s.seq) 74 | } 75 | -------------------------------------------------------------------------------- /host/peerid/peerid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package peerid provides the PeerID type. Enclaves identify remote peers by 5 | // their PeerID, which is a a 256 bit public key that an enclave generates at startup. 6 | // Hosts are responsible for mapping PeerIDs to the actual remote endpoints used for 7 | // network communication. 8 | package peerid 9 | 10 | import ( 11 | "encoding/hex" 12 | "fmt" 13 | ) 14 | 15 | type PeerID [32]byte 16 | 17 | func Make(s []byte) (PeerID, error) { 18 | if len(s) != 32 { 19 | return PeerID{}, fmt.Errorf("incorrect peer id length %v", len(s)) 20 | } 21 | var out PeerID 22 | copy(out[:], s) 23 | return out, nil 24 | } 25 | 26 | // Hex parses a hexidecimal formatted peerID 27 | func FromHex(s string) (PeerID, error) { 28 | if len(s) != 64 { 29 | return PeerID{}, fmt.Errorf("must provide 32-byte value as hex (64 characters)") 30 | } 31 | bs, err := hex.DecodeString(s) 32 | if err != nil { 33 | return PeerID{}, err 34 | } 35 | return Make(bs) 36 | } 37 | 38 | // String returns a hexidecimal formatted peerID (just an 8-char prefix) 39 | func (p PeerID) String() string { 40 | return hex.EncodeToString(p[:4]) 41 | } 42 | 43 | // Set implements flag.Value and sets the PeerID from a hex string. 44 | func (p *PeerID) Set(in string) error { 45 | pid, err := FromHex(in) 46 | if err != nil { 47 | return err 48 | } 49 | *p = pid 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /host/proto/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package proto 5 | 6 | import ( 7 | "fmt" 8 | ) 9 | 10 | // Error implements the `error` interface on the `Error` enum. 11 | func (e Error) Error() string { 12 | if str, ok := Error_name[int32(e)]; ok { 13 | return str 14 | } 15 | return fmt.Sprintf("UnknownErrorEnumValue(%d)", int32(e)) 16 | } 17 | -------------------------------------------------------------------------------- /host/proto/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package proto 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestError(t *testing.T) { 11 | var e error = Error_Core_NoInit 12 | if got, want := e.Error(), "Core_NoInit"; got != want { 13 | t.Errorf("got %q want %q", got, want) 14 | } 15 | 16 | e = Error(-1) 17 | if got, want := e.Error(), "UnknownErrorEnumValue(-1)"; got != want { 18 | t.Errorf("got %q want %q", got, want) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /host/proto/peerdb.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.peerdb; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | 11 | message PeerEntry { 12 | int64 join_ts = 1; 13 | int64 last_update_ts = 2; 14 | string addr = 3; // hostname:port that other nodes can use to access this peer 15 | bool raft_member = 4; // if true, this peer is a raft member 16 | bool leader = 5; // if true, the peer thinks it is the leader 17 | } 18 | 19 | message PeerMap { 20 | message Entry { 21 | bytes id = 1; 22 | PeerEntry entry = 2; 23 | } 24 | repeated Entry entries = 1; 25 | } 26 | -------------------------------------------------------------------------------- /host/raftmanager/raftmanager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package raftmanager 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/signalapp/svr2/config" 11 | "github.com/signalapp/svr2/peerid" 12 | pb "github.com/signalapp/svr2/proto" 13 | ) 14 | 15 | type mockEnclave struct { 16 | msgs []*pb.HostToEnclaveRequest 17 | } 18 | 19 | func (t *mockEnclave) SendTransaction(p *pb.HostToEnclaveRequest) (*pb.HostToEnclaveResponse, error) { 20 | t.msgs = append(t.msgs, p) 21 | return &pb.HostToEnclaveResponse{ 22 | Inner: &pb.HostToEnclaveResponse_Status{ 23 | Status: pb.Error_OK, 24 | }, 25 | }, nil 26 | } 27 | 28 | type mockFinder peerid.PeerID 29 | 30 | func (f mockFinder) FindRaftMember(ctx context.Context, me peerid.PeerID, addr string) (peerid.PeerID, error) { 31 | return peerid.PeerID(f), nil 32 | } 33 | 34 | func TestCreate(t *testing.T) { 35 | peer0 := [32]byte{byte(0)} 36 | mockEnclave := &mockEnclave{} 37 | 38 | raftManager := New(peer0, mockEnclave, mockFinder(peer0), config.Default()) 39 | if err := raftManager.CreateOrJoin(context.Background()); err != nil { 40 | t.Error(err) 41 | } 42 | if len(mockEnclave.msgs) != 2 { 43 | t.Errorf("want %v enclave messages, got %v", 2, len(mockEnclave.msgs)) 44 | } 45 | 46 | if mockEnclave.msgs[0].GetCreateNewRaftGroup() == false { 47 | t.Errorf("got message %v, want create request", mockEnclave.msgs[0]) 48 | } 49 | if mockEnclave.msgs[1].GetRefreshAttestation() == nil { 50 | t.Errorf("got message %v, want refresh request", mockEnclave.msgs[1]) 51 | } 52 | } 53 | 54 | func TestJoin(t *testing.T) { 55 | peer0 := [32]byte{byte(0)} 56 | peer1 := [32]byte{byte(1)} 57 | 58 | mockEnclave := &mockEnclave{} 59 | 60 | raftManager := New(peer0, mockEnclave, mockFinder(peer1), config.Default()) 61 | if err := raftManager.CreateOrJoin(context.Background()); err != nil { 62 | t.Error(err) 63 | } 64 | if len(mockEnclave.msgs) != 2 { 65 | t.Errorf("want %v enclave messages, got %v", 2, len(mockEnclave.msgs)) 66 | } 67 | 68 | if mockEnclave.msgs[0].GetJoinRaft() == nil { 69 | t.Errorf("got message %v, want create request", mockEnclave.msgs[0]) 70 | } 71 | 72 | if mockEnclave.msgs[1].GetRefreshAttestation() == nil { 73 | t.Errorf("got message %v, want create request", mockEnclave.msgs[1]) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /host/rate/rate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package rate provides rate limiters that may be used to limit the 5 | // number of operations performed during some time period. 6 | package rate 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "time" 12 | 13 | "github.com/go-redis/redis_rate/v10" 14 | "github.com/redis/go-redis/v9" 15 | 16 | "github.com/signalapp/svr2/config" 17 | ) 18 | 19 | // ErrLimitExceeded indicates that the requested permit exceeds 20 | // the configured rate limit for the user. After waiting for 21 | // RetryAfter, the same request should succeed 22 | type ErrLimitExceeded struct{ RetryAfter time.Duration } 23 | 24 | func (e ErrLimitExceeded) Error() string { 25 | return fmt.Sprintf("rate limit exceeded, retry after : %v", e.RetryAfter) 26 | } 27 | 28 | type Limiter interface { 29 | // Limit checks and enforces the rate limit. Returns nil if the operation 30 | // may proceed, or ErrLimitExceeded if the operation would exceed the 31 | // rate limit. If the rate limit cannot be checked, a different error 32 | // may be returned. 33 | Limit(ctx context.Context, key string) error 34 | } 35 | 36 | // NewConfiguredLimiter returns a Limiter backed by redis 37 | func NewConfiguredLimiter(cfg *config.Config) Limiter { 38 | if cfg.Limit.SkipLimiting { 39 | return AlwaysAllow 40 | } 41 | return &redisLimiter{ 42 | fmt.Sprintf("%s::leaky_bucket", cfg.Redis.Name), 43 | redis_rate.Limit{ 44 | Rate: cfg.Limit.LeakRateScalar, 45 | Burst: cfg.Limit.BucketSize, 46 | Period: cfg.Limit.LeakRateDuration, 47 | }, 48 | redis_rate.NewLimiter(redis.NewClusterClient(&redis.ClusterOptions{ 49 | Addrs: cfg.Redis.Addrs, 50 | Password: cfg.Redis.Password, 51 | }))} 52 | } 53 | 54 | type redisLimiter struct { 55 | prefix string // prefix for rate limit buckets 56 | limit redis_rate.Limit // configured limit 57 | limiter *redis_rate.Limiter 58 | } 59 | 60 | func (r *redisLimiter) Limit(ctx context.Context, key string) error { 61 | res, err := r.limiter.Allow(ctx, r.redisKey(key), r.limit) 62 | if err != nil { 63 | return err 64 | } 65 | if res.Allowed <= 0 { 66 | return ErrLimitExceeded{res.RetryAfter} 67 | } 68 | return nil 69 | } 70 | 71 | func (r *redisLimiter) redisKey(userKey string) string { 72 | return fmt.Sprintf("%s::%s", r.prefix, userKey) 73 | } 74 | 75 | type alwaysAllow struct{} 76 | 77 | func (r alwaysAllow) Limit(context.Context, string) error { return nil } 78 | 79 | // AlwaysAllow provides a Limiter that will always allow callers through 80 | var AlwaysAllow = Limiter(alwaysAllow{}) 81 | -------------------------------------------------------------------------------- /host/rate/rate_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package rate 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "testing" 10 | "time" 11 | 12 | "github.com/alicebob/miniredis/v2" 13 | 14 | "github.com/signalapp/svr2/config" 15 | ) 16 | 17 | func testLimiter(t *testing.T, limitConfig config.RateLimitConfig) Limiter { 18 | cfg := config.Default() 19 | cfg.Limit = limitConfig 20 | s := miniredis.RunT(t) 21 | cfg.Redis.Addrs = []string{s.Addr()} 22 | return NewConfiguredLimiter(cfg) 23 | } 24 | 25 | func TestRedisLimiter(t *testing.T) { 26 | cfg := config.RateLimitConfig{ 27 | BucketSize: 2, 28 | LeakRateScalar: 1, 29 | LeakRateDuration: 100 * time.Millisecond, 30 | } 31 | limiter := testLimiter(t, cfg) 32 | 33 | var count int 34 | var err error 35 | var exceeded ErrLimitExceeded 36 | 37 | // wait until we get a limit exceeded error 38 | for count = 0; !errors.As(err, &exceeded); count++ { 39 | err = limiter.Limit(context.Background(), "test1") 40 | } 41 | if count < 2 { 42 | t.Errorf("took %v limits, should be at least %v", count, cfg.BucketSize) 43 | } 44 | 45 | start := time.Now() 46 | 47 | // keep trying until we don't get limited 48 | for errors.As(err, &exceeded) { 49 | err = limiter.Limit(context.Background(), "test1") 50 | time.Sleep(cfg.LeakRateDuration / 10) 51 | } 52 | duration := time.Since(start) 53 | 54 | if err != nil { 55 | t.Error(err) 56 | } 57 | if duration > cfg.LeakRateDuration*2 { 58 | t.Errorf("took %v to get a permit, should only need %v", duration, cfg.LeakRateDuration) 59 | } 60 | } 61 | 62 | func TestRedisLimiterExhaust(t *testing.T) { 63 | limiter := testLimiter(t, config.RateLimitConfig{ 64 | BucketSize: 10, 65 | LeakRateScalar: 1, 66 | LeakRateDuration: time.Hour, 67 | }) 68 | for i := 0; i < 10; i++ { 69 | if err := limiter.Limit(context.Background(), "test1"); err != nil { 70 | t.Errorf("iter %v : %v", i, err) 71 | } 72 | } 73 | 74 | // 11th request should get rate limited 75 | var errExceed ErrLimitExceeded 76 | err := limiter.Limit(context.Background(), "test1") 77 | if !errors.As(err, &errExceed) { 78 | t.Fatalf("Limit(11)=%v, want %v", err, "ErrLimitExceeded") 79 | } 80 | if errExceed.RetryAfter < (time.Hour - 5*time.Second) { 81 | t.Fatalf("RetryAfter = %v, should be at least %v", errExceed.RetryAfter, time.Hour-5*time.Second) 82 | } 83 | if errExceed.RetryAfter > time.Hour { 84 | t.Fatalf("RetryAfter = %v, should be at most %v", errExceed.RetryAfter, time.Hour) 85 | } 86 | 87 | // unrelated key should be fine 88 | if err := limiter.Limit(context.Background(), "test2"); err != nil { 89 | t.Error(err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /host/rustclient/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustclient" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | snow = { version = "0.9.6", features = ["hfs", "pqclean_kyber1024"] } 10 | tokio-tungstenite = "0.26.2" 11 | prost = "0.13.1" 12 | simple-error = "0.3.1" 13 | base64 = "0.22.1" 14 | hmac = "0.12.1" 15 | sha2 = "0.10.8" 16 | hex = "0.4.3" 17 | 18 | [build-dependencies] 19 | prost-build = "0.13.1" 20 | -------------------------------------------------------------------------------- /host/rustclient/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | fn main() { 5 | let protos = [ 6 | "proto/msgs.proto", 7 | ]; 8 | prost_build::compile_protos(&protos, &["proto"]).expect("Protobufs in src are valid"); 9 | for proto in &protos { 10 | println!("cargo:rerun-if-changed={}", proto); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /host/rustclient/proto: -------------------------------------------------------------------------------- 1 | ../../shared/proto/ -------------------------------------------------------------------------------- /host/rustclient/target: -------------------------------------------------------------------------------- 1 | ../../.cargotarget/ -------------------------------------------------------------------------------- /host/util/clock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package util 5 | 6 | import ( 7 | "time" 8 | ) 9 | 10 | // Clock provides an interface for accessing the current time. 11 | type Clock interface { 12 | Now() time.Time 13 | } 14 | 15 | type realClock struct{} 16 | 17 | func (r *realClock) Now() time.Time { 18 | return time.Now() 19 | } 20 | 21 | // RealClock returns a clock which uses time.Now. 22 | var RealClock Clock = (*realClock)(nil) 23 | 24 | // TestAt is a Clock that returns a set single point in time. 25 | type TestAt time.Time 26 | 27 | func (t TestAt) Now() time.Time { 28 | return time.Time(t) 29 | } 30 | -------------------------------------------------------------------------------- /host/util/clock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package util 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestTestAt(t *testing.T) { 13 | now := time.Now() 14 | ta := TestAt(now) 15 | if got := ta.Now(); got != now { 16 | t.Errorf("TestAt.Now: got %v want %v", got, now) 17 | } 18 | } 19 | 20 | func TestRealClock(t *testing.T) { 21 | t1 := time.Now() 22 | time.Sleep(time.Millisecond * 10) 23 | v1 := RealClock.Now() 24 | time.Sleep(time.Millisecond * 10) 25 | t2 := time.Now() 26 | time.Sleep(time.Millisecond * 10) 27 | v2 := RealClock.Now() 28 | if !v1.After(t1) { 29 | t.Errorf("v1 (%v) before t1 (%v)", v1, t1) 30 | } 31 | if !t2.After(v1) { 32 | t.Errorf("t2 (%v) before v1 (%v)", t2, v1) 33 | } 34 | if !v2.After(t2) { 35 | t.Errorf("v2 (%v) before t2 (%v)", v2, t2) 36 | } 37 | } 38 | 39 | func ExampleTestAt() { 40 | clock := TestAt(time.Unix(123, 0)) 41 | now := clock.Now() 42 | fmt.Print(now.Unix()) 43 | // Output: 44 | // 123 45 | } 46 | -------------------------------------------------------------------------------- /host/util/txid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package util 5 | 6 | import "sync/atomic" 7 | 8 | // TxGenerator provides unique transaction ids for transactional request/responses 9 | // to the enclave 10 | type TxGenerator struct { 11 | txcounter uint64 12 | } 13 | 14 | // NextId returns a new unique transaction id 15 | func (t *TxGenerator) NextID() uint64 { 16 | return atomic.AddUint64(&t.txcounter, 1) 17 | } 18 | -------------------------------------------------------------------------------- /host/util/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // Package util contains general purpose utilities 5 | package util 6 | 7 | import ( 8 | "context" 9 | "time" 10 | 11 | "golang.org/x/exp/constraints" 12 | ) 13 | 14 | // Min returns the minimum of a and b 15 | func Min[T constraints.Ordered](a, b T) T { 16 | if a < b { 17 | return a 18 | } 19 | return b 20 | } 21 | 22 | // Max returns the maximum of a and b 23 | func Max[T constraints.Ordered](a, b T) T { 24 | if a > b { 25 | return a 26 | } 27 | return b 28 | } 29 | 30 | // Clamp restricts the value to the range [lo, hi] 31 | func Clamp[T constraints.Ordered](v, lo, hi T) T { 32 | return Max(lo, Min(hi, v)) 33 | } 34 | 35 | // RetryWithBackoff repeatedly attempts to call `fun`, increasing the wait between each call 36 | func RetryWithBackoff(ctx context.Context, fun func() error, minSleep time.Duration, maxSleep time.Duration) error { 37 | _, err := RetrySupplierWithBackoff(ctx, func() (interface{}, error) { return nil, fun() }, minSleep, maxSleep) 38 | return err 39 | } 40 | 41 | // RetrySupplierWithBackoff repeatedly attempts to call `fun` to produce a value, increasing the wait between each call 42 | func RetrySupplierWithBackoff[T any](ctx context.Context, fun func() (T, error), minSleep time.Duration, maxSleep time.Duration) (T, error) { 43 | sleepTime := time.Duration(0) 44 | var res T 45 | var err error 46 | for { 47 | select { 48 | case <-ctx.Done(): 49 | return res, ctx.Err() 50 | case <-time.After(sleepTime): 51 | } 52 | res, err = fun() 53 | if err == nil { 54 | return res, nil 55 | } 56 | sleepTime = Clamp(sleepTime*2, minSleep, maxSleep) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /host/web/client/control_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package client 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | 12 | "google.golang.org/protobuf/encoding/protojson" 13 | 14 | pb "github.com/signalapp/svr2/proto" 15 | ) 16 | 17 | type ControlClient struct { 18 | Addr string 19 | } 20 | 21 | func (cc *ControlClient) Do(request *pb.HostToEnclaveRequest) (*pb.HostToEnclaveResponse, error) { 22 | bs, err := protojson.Marshal(request) 23 | if err != nil { 24 | return nil, fmt.Errorf("failed to marshal proto : %w", err) 25 | } 26 | return cc.DoJSON(bs) 27 | } 28 | 29 | func (cc *ControlClient) Peers() (*pb.PeerMap, error) { 30 | url := fmt.Sprintf("http://%v/control/peers", cc.Addr) 31 | resp, err := http.Get(url) 32 | if err != nil { 33 | return nil, fmt.Errorf("requesting peers via GET: %w", err) 34 | } 35 | defer resp.Body.Close() 36 | if resp.StatusCode != http.StatusOK { 37 | return nil, fmt.Errorf("requesting peers via GET: status=%v", resp.StatusCode) 38 | } 39 | body, err := io.ReadAll(resp.Body) 40 | if err != nil { 41 | return nil, fmt.Errorf("reading body: %v", err) 42 | } 43 | pbResponse := pb.PeerMap{} 44 | if err := protojson.Unmarshal(body, &pbResponse); err != nil { 45 | return nil, fmt.Errorf("could not parse server response, body=%s : %w", body, err) 46 | } 47 | return &pbResponse, nil 48 | } 49 | 50 | func (cc *ControlClient) DoJSON(request []byte) (*pb.HostToEnclaveResponse, error) { 51 | url := fmt.Sprintf("http://%v/control", cc.Addr) 52 | req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(request)) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | req.Header.Set("Content-Type", "application/json") 58 | resp, err := http.DefaultClient.Do(req) 59 | if err != nil { 60 | return nil, fmt.Errorf("request failed : %w", err) 61 | } 62 | 63 | defer resp.Body.Close() 64 | body, err := io.ReadAll(resp.Body) 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to read response body : %w", err) 67 | } 68 | 69 | if resp.StatusCode != http.StatusOK { 70 | return nil, fmt.Errorf("request failed, status=%v, body=%s", resp.Status, body) 71 | } 72 | 73 | pbResponse := pb.HostToEnclaveResponse{} 74 | if err := protojson.Unmarshal(body, &pbResponse); err != nil { 75 | return nil, fmt.Errorf("could not parse server response, body=%s : %w", body, err) 76 | } 77 | if status, ok := pbResponse.Inner.(*pb.HostToEnclaveResponse_Status); ok && status.Status != pb.Error_OK { 78 | return nil, status.Status 79 | } 80 | return &pbResponse, nil 81 | } 82 | -------------------------------------------------------------------------------- /host/web/handlers/delete_backup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package handlers 5 | 6 | import ( 7 | "encoding/hex" 8 | "net/http" 9 | 10 | "google.golang.org/protobuf/proto" 11 | 12 | pb "github.com/signalapp/svr2/proto" 13 | ) 14 | 15 | // NewDeleteBackup returns a handler that takes HTTP DELETE requests and notifies 16 | // the enclave to delete the backup associated with the user (provided via basic auth) 17 | func NewDeleteBackup(server EnclaveRequester) http.Handler { 18 | return &deleteBackupHandler{ 19 | enclaveRequester: server, 20 | } 21 | } 22 | 23 | type deleteBackupHandler struct { 24 | enclaveRequester EnclaveRequester 25 | } 26 | 27 | func (d *deleteBackupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 28 | if r.Method != http.MethodDelete { 29 | http.Error(w, "only DELETE allowed", http.StatusMethodNotAllowed) 30 | return 31 | } 32 | user, _, _ := r.BasicAuth() 33 | authID, err := hex.DecodeString(user) 34 | if err != nil { 35 | http.Error(w, err.Error(), http.StatusBadRequest) 36 | return 37 | } else if len(authID) != 16 { 38 | http.Error(w, "auth ID not 16 bytes", http.StatusBadRequest) 39 | return 40 | } 41 | deleteReq := pb.Request{ 42 | Inner: &pb.Request_Delete{Delete: &pb.DeleteRequest{}}, 43 | } 44 | marshalled, err := proto.Marshal(&deleteReq) 45 | if err != nil { 46 | http.Error(w, err.Error(), http.StatusInternalServerError) 47 | return 48 | } 49 | resp, err := d.enclaveRequester.SendTransaction(&pb.HostToEnclaveRequest{ 50 | Inner: &pb.HostToEnclaveRequest_DatabaseRequest{ 51 | DatabaseRequest: &pb.DatabaseRequest{ 52 | Request: marshalled, 53 | AuthenticatedId: authID, 54 | }, 55 | }, 56 | }) 57 | if err != nil { 58 | http.Error(w, err.Error(), http.StatusInternalServerError) 59 | return 60 | } 61 | if err = responseErr(resp); err != nil { 62 | http.Error(w, err.Error(), http.StatusInternalServerError) 63 | return 64 | } 65 | w.WriteHeader(http.StatusOK) 66 | } 67 | -------------------------------------------------------------------------------- /host/web/handlers/handlers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // package handlers provides http handlers for SVR2 endpoints 5 | package handlers 6 | 7 | import ( 8 | "fmt" 9 | 10 | pb "github.com/signalapp/svr2/proto" 11 | ) 12 | 13 | type EnclaveRequester interface { 14 | // SendTransaction sends a request to the enclave and returns the enclave's response. 15 | // Implementations must tag the provided request with a requestID. 16 | SendTransaction(req *pb.HostToEnclaveRequest) (*pb.HostToEnclaveResponse, error) 17 | } 18 | 19 | // responseErr check a response for an enclave error status 20 | func responseErr(r *pb.HostToEnclaveResponse) error { 21 | if e, ok := r.Inner.(*pb.HostToEnclaveResponse_Status); ok && e.Status != pb.Error_OK { 22 | return fmt.Errorf("transaction %d failed with code: %w", r.RequestId, e.Status) 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /host/web/middleware/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package middleware 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/signalapp/svr2/auth" 10 | "github.com/signalapp/svr2/logger" 11 | ) 12 | 13 | // AuthCheck wraps an http.Handler to check the request's BasicAuth using the provided authenticator 14 | func AuthCheck(authenticator auth.Auth, inner http.Handler) http.Handler { 15 | return &authHandler{authenticator, inner} 16 | } 17 | 18 | type authHandler struct { 19 | authenticator auth.Auth 20 | inner http.Handler 21 | } 22 | 23 | func (a *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 24 | user, pass, _ := r.BasicAuth() 25 | if err := a.authenticator.Check(user, pass); err != nil { 26 | logger.Infow("basic auth failed", "err", err) 27 | w.WriteHeader(http.StatusUnauthorized) 28 | return 29 | } 30 | a.inner.ServeHTTP(w, r) 31 | } 32 | -------------------------------------------------------------------------------- /host/web/middleware/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package middleware 5 | 6 | import ( 7 | "bufio" 8 | "errors" 9 | "net" 10 | "net/http" 11 | "strconv" 12 | 13 | "github.com/hashicorp/go-metrics" 14 | 15 | "github.com/signalapp/svr2/util" 16 | ) 17 | 18 | // Instrument wraps an http.Handler and updates metrics with the http response 19 | func Instrument(inner http.Handler) http.Handler { 20 | return &handler{inner: inner} 21 | } 22 | 23 | type handler struct { 24 | inner http.Handler 25 | } 26 | 27 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 28 | ww := &writerWrapper{w: w} 29 | h.inner.ServeHTTP(ww, r) 30 | if ww.recorded { 31 | userAgent := r.UserAgent() 32 | labels := util.ParseTags(userAgent) 33 | labels = append(labels, 34 | metrics.Label{Name: "method", Value: r.Method}, 35 | metrics.Label{Name: "endpoint", Value: r.URL.Path}, 36 | metrics.Label{Name: "status", Value: strconv.Itoa(ww.statusCode)}, 37 | ) 38 | metrics.IncrCounterWithLabels([]string{"http", "response"}, 1, labels) 39 | } 40 | } 41 | 42 | // When a response is written, record the status code so it can be instrumented later 43 | type writerWrapper struct { 44 | w http.ResponseWriter 45 | statusCode int 46 | recorded bool 47 | } 48 | 49 | var _ http.ResponseWriter = (*writerWrapper)(nil) 50 | var _ http.Hijacker = (*writerWrapper)(nil) 51 | 52 | func (ww *writerWrapper) Header() http.Header { 53 | return ww.w.Header() 54 | } 55 | 56 | func (ww *writerWrapper) Write(b []byte) (int, error) { 57 | if !ww.recorded { 58 | ww.recorded = true 59 | ww.statusCode = http.StatusOK 60 | } 61 | return ww.w.Write(b) 62 | } 63 | 64 | func (ww *writerWrapper) WriteHeader(statusCode int) { 65 | ww.recorded = true 66 | ww.statusCode = statusCode 67 | ww.w.WriteHeader(statusCode) 68 | } 69 | 70 | func (ww *writerWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) { 71 | h, ok := ww.w.(http.Hijacker) 72 | if !ok { 73 | return nil, nil, errors.New("hijack not supported") 74 | } 75 | if !ww.recorded { 76 | // If the response handler is switching protocols, (e.x. upgrading 77 | // to a websocket) report StatusSwitchingProtocols 78 | ww.recorded = true 79 | ww.statusCode = http.StatusSwitchingProtocols 80 | } 81 | return h.Hijack() 82 | } 83 | -------------------------------------------------------------------------------- /host/web/middleware/rate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | package middleware 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/hashicorp/go-metrics" 13 | 14 | "github.com/signalapp/svr2/logger" 15 | "github.com/signalapp/svr2/rate" 16 | ) 17 | 18 | // RateLimit wraps a http.Handler and enforces a rate limit on requests going to that handler 19 | func RateLimit(limiter rate.Limiter, next http.Handler) http.Handler { 20 | return &rateLimitHandler{limiter, next} 21 | } 22 | 23 | type rateLimitHandler struct { 24 | limiter rate.Limiter 25 | inner http.Handler 26 | } 27 | 28 | var ( 29 | rateLimitCounter = []string{"request", "rateLimit"} 30 | rateLimitErrCounter = []string{"request", "rateLimitErr"} 31 | ) 32 | 33 | func (rh *rateLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 34 | user, _, _ := r.BasicAuth() 35 | 36 | err := rh.limiter.Limit(r.Context(), user) 37 | var retryErr rate.ErrLimitExceeded 38 | rateLimitExceeded := errors.As(err, &retryErr) 39 | 40 | metrics.IncrCounterWithLabels(rateLimitCounter, 1, []metrics.Label{ 41 | {Name: "exceeded", Value: strconv.FormatBool(rateLimitExceeded)}, 42 | }) 43 | 44 | if rateLimitExceeded { 45 | retryAfterSecs := int64(retryErr.RetryAfter.Seconds()) 46 | w.Header().Set("Retry-After", strconv.FormatInt(retryAfterSecs, 10)) 47 | w.WriteHeader(http.StatusTooManyRequests) 48 | return 49 | } else if errors.Is(err, context.Canceled) { 50 | logger.Infow("context cancelled while updating rate limit", "err", err) 51 | w.WriteHeader(499) 52 | return 53 | } else if err != nil { 54 | // still allow request in the case where we can't access the rate limiter 55 | metrics.IncrCounter(rateLimitErrCounter, 1) 56 | logger.Errorw("could not update rate limit", "err", err) 57 | } 58 | rh.inner.ServeHTTP(w, r) 59 | } 60 | -------------------------------------------------------------------------------- /shared/.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | protoc* 3 | !sha256.* 4 | go* 5 | -------------------------------------------------------------------------------- /shared/proto/attestation.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.attestation; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | import "minimums.proto"; 11 | import "enclaveconfig.proto"; 12 | 13 | message AttestationData { 14 | bytes public_key = 1; 15 | enclaveconfig.RaftGroupConfig group_config = 2; 16 | // This is the set of limits that this replica enforces on its peers. 17 | minimums.MinimumLimits minimum_limits = 3; 18 | // This is the set of values associated with a peer. These are not sent 19 | // over the wire, but are rather added on by env->Attest as part of the 20 | // attestation process. 21 | minimums.MinimumValues minimum_values = 4; 22 | } 23 | -------------------------------------------------------------------------------- /shared/proto/client3.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.client; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | // Client protocol for SVR3. 11 | 12 | message Request3 { 13 | oneof inner { 14 | CreateRequest create = 1; 15 | EvaluateRequest evaluate = 2; 16 | RemoveRequest remove = 3; 17 | QueryRequest query = 4; 18 | } 19 | } 20 | 21 | message Response3 { 22 | oneof inner { 23 | CreateResponse create = 1; 24 | EvaluateResponse evaluate = 2; 25 | RemoveResponse remove = 3; 26 | QueryResponse query = 4; 27 | } 28 | } 29 | 30 | // 31 | // create 32 | // 33 | 34 | message CreateRequest { 35 | uint32 max_tries = 1; 36 | bytes blinded_element = 2; // ristretto255 element, 32 bytes 37 | } 38 | 39 | message CreateResponse { 40 | enum Status { 41 | UNSET = 0; 42 | OK = 1; 43 | INVALID_REQUEST = 2; 44 | ERROR = 3; 45 | } 46 | Status status = 1; 47 | bytes evaluated_element = 2; // ristretto255 element, 32 bytes 48 | } 49 | 50 | // 51 | // evaluate 52 | // 53 | 54 | message EvaluateRequest { 55 | bytes blinded_element = 1; // ristretto255 element, 32 bytes 56 | } 57 | 58 | message EvaluateResponse { 59 | enum Status { 60 | UNSET = 0; 61 | OK = 1; 62 | MISSING = 2; 63 | INVALID_REQUEST = 3; 64 | ERROR = 4; 65 | } 66 | Status status = 1; 67 | bytes evaluated_element = 2; // ristretto255 element, 32 bytes 68 | uint32 tries_remaining = 3; 69 | } 70 | 71 | // 72 | // remove 73 | // 74 | 75 | message RemoveRequest { 76 | } 77 | message RemoveResponse { 78 | } 79 | 80 | // 81 | // query 82 | // 83 | 84 | message QueryRequest { 85 | } 86 | message QueryResponse { 87 | enum Status { 88 | UNSET = 0; 89 | OK = 1; 90 | MISSING = 2; 91 | } 92 | Status status = 1; 93 | uint32 tries_remaining = 2; 94 | } 95 | -------------------------------------------------------------------------------- /shared/proto/metrics.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.metrics; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | message U64PB { 11 | string name = 1; 12 | map tags = 2; 13 | uint64 v = 3; 14 | } 15 | 16 | message MetricsPB { 17 | repeated U64PB counters = 1; 18 | repeated U64PB gauges = 2; 19 | } 20 | -------------------------------------------------------------------------------- /shared/proto/minimums.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.minimums; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | message MinimumLimits { 11 | // Minimums have the following rules: 12 | // - Once a minimum is added to the set, its key cannot be removed 13 | // - A minimum's value can only increase over time 14 | map lim = 1; 15 | } 16 | 17 | message MinimumValues { 18 | map val = 1; 19 | } 20 | -------------------------------------------------------------------------------- /shared/proto/sev.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.attestation.sev; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | message SevSnpEndorsements { 11 | bytes vcek_der = 1; 12 | bytes ask_der = 2; 13 | bytes ark_der = 3; 14 | bytes vlek_der = 4; 15 | bytes crl = 5; 16 | } 17 | -------------------------------------------------------------------------------- /shared/proto/socketmain.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.socketmain; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | import "error.proto"; 10 | import "enclaveconfig.proto"; 11 | 12 | message Log { 13 | enclaveconfig.EnclaveLogLevel level = 1; 14 | string log = 2; 15 | } 16 | 17 | message InboundMessage { 18 | oneof inner { 19 | enclaveconfig.InitConfig init = 1; 20 | MsgCallRequest msg = 2; 21 | } 22 | } 23 | message OutboundMessage { 24 | oneof inner { 25 | InitCallResponse init = 1; 26 | MsgCallResponse msg = 2; 27 | bytes out = 3; 28 | Log log = 4; 29 | } 30 | } 31 | 32 | message InitCallResponse { 33 | // there's no `status` here, because a failure to init will crash. 34 | bytes peer_id = 1; 35 | } 36 | 37 | message MsgCallRequest { 38 | uint64 id = 1; 39 | bytes data = 2; // A serialized UntrustedMessage 40 | } 41 | message MsgCallResponse { 42 | uint64 id = 1; 43 | error.Error status = 2; 44 | } 45 | -------------------------------------------------------------------------------- /shared/proto/tpm2snp.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Signal Messenger, LLC 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | syntax = "proto3"; 5 | 6 | package svr2.attestation.tpm2snp; 7 | option go_package = "github.com/signalapp/svr2/proto"; 8 | option optimize_for = LITE_RUNTIME; 9 | 10 | message TPM2SNPEvidence { 11 | // Serialized AttestationData 12 | bytes attestation_data = 1; 13 | 14 | // TPM2 quote. On Azure: 15 | // tpm2_quote -c 0x81000003 -l sha256:all -q "" -m msg -s sig -o pcrs --pcrs_format values 16 | // On GCP, this information is pulled via the host/cmd/svr3gcp binary. 17 | bytes pcrs = 2; 18 | bytes msg = 3; 19 | bytes sig = 4; 20 | 21 | // SNP report containing hash of runtime_data. On Azure: 22 | // tpm2_nvread -C o 0x01400001 # contains snp_report and runtimedata 23 | // On GCP, this is pulled from the /dev/sev or /dev/sev-guest directly. 24 | bytes snp_report = 5; // contains snp_report and runtimedata 25 | // runtime_data should be in the form used by Azure, and must 26 | // contain at least the following: 27 | // { "keys": [{ 28 | // "kid": "HCLAkPub", 29 | // "kty": "RSA", 30 | // "e": base64(big-endian-bignum(rsa exponent)), 31 | // "n": base64(big-endian-bignum(rsa modulus)), 32 | // ... 33 | // }]} 34 | // In GCP, we generate the above format ourselves. 35 | bytes runtime_data = 6; 36 | // AK certificate, wrapping the TPM2 public key. On Azure: 37 | // tpm2_nvread -C o 0x1C101D0 38 | // On GCP, this information is pulled via the host/cmd/svr3gcp binary. 39 | bytes akcert_der = 7; 40 | } 41 | 42 | message TPM2SNPEndorsements { 43 | // Azure: pulled from https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-faq?tabs=cli%2Cdebianbased#certificates 44 | // GCP: retrieved via host/cmd/svr3gcp. 45 | bytes intermediate_der = 1; 46 | // Azure: http://169.254.169.254/metadata/THIM/amd/certification 47 | // GCP: retrieved via host/cmd/svr3gcp. 48 | bytes vcek_der = 2; 49 | bytes ask_der = 3; 50 | } 51 | -------------------------------------------------------------------------------- /shared/svr2.edl: -------------------------------------------------------------------------------- 1 | enclave { 2 | from "openenclave/edl/syscall.edl" import *; 3 | from "openenclave/edl/logging.edl" import *; 4 | from "platform.edl" import *; 5 | 6 | trusted { 7 | // svr2_init initiates the enclave. 8 | // 9 | // Args: 10 | // enclave_id: Unique identifier for this enclave (will be passed out 11 | // in svr2_output_message calls to differentiate calls from multiple 12 | // enclaves. 13 | // config{,_size}: Serialized EnclaveConfig protobuf. 14 | // peer_id{,32}: The peer_id is the public key of anenclave generated 15 | // key pair and is generated internally 16 | // Returns: error::Error as int. 17 | public int svr2_init( 18 | size_t config_size, 19 | [in, size=config_size] unsigned char* config, 20 | [out, size=32] unsigned char* peer_id); 21 | 22 | // svr2_input_message sends a message from host->enclave. 23 | // Should not be called concurrently. The enclave won't care, but 24 | // the caller won't know which svr2_output_message is associated with 25 | // which input message, as enclave-side locking will be opaque. 26 | // 27 | // Args: 28 | // msg{,_size}: Serialized HostToEnclaveMessage. 29 | // Returns: error::Error as int. 30 | public int svr2_input_message( 31 | size_t msg_size, 32 | [in, size=msg_size] unsigned char* msg); 33 | }; 34 | 35 | untrusted { 36 | // svr2_output_message sends a message from enclave->host. 37 | // It will only be called during a call to svr2_input_message. 38 | // 39 | // Args: 40 | // msg{,_size}: Serialized EnclaveToHostMessage. 41 | void svr2_output_message( 42 | size_t msg_size, 43 | [in, size=msg_size] unsigned char* msg); 44 | }; 45 | }; 46 | 47 | 48 | -------------------------------------------------------------------------------- /trustedimage/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | debian2/id_rsa.pub 3 | azure_config 4 | gcp_config 5 | -------------------------------------------------------------------------------- /trustedimage/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Signal Messenger, LLC 2 | # SPDX-License-Identifier: AGPL-3.0-only 3 | SHELL=/bin/bash -o pipefail # needed for pipefail 4 | 5 | all: build/gcp_version 6 | 7 | build/dir: 8 | mkdir -p build 9 | touch build/dir 10 | 11 | build/debian2.out: debian2.pkr.hcl build/debian1.out debian2/* ../enclave/build/enclave.gcpsnp ../enclave/build/enclave.azuresnp ../host/main 12 | rm -rf $@ 13 | packer build $< 14 | 15 | build/debian1.out: debian1.pkr.hcl debian1/* build/dir 16 | rm -rf $@ 17 | packer build $< 18 | 19 | clean: 20 | rm -rf build 21 | 22 | build/version: build/debian2.out 23 | echo "0.$$(date --utc +%Y%m%d.%H%M%S)" > $@ 24 | 25 | build/azure_version: build/version azure.sh azure_config 26 | ./azure.sh $$(cat build/version) 27 | 28 | build/gcp_version: build/version gcp.sh gcp_config 29 | ./gcp.sh $$(cat build/version) 30 | -------------------------------------------------------------------------------- /trustedimage/README.md: -------------------------------------------------------------------------------- 1 | Generate and build AMD-SEV-SNP attestable VM disk images for GCP or Azure 2 | 3 | ## Dependencies 4 | 5 | To run build and upload disk images, you'll need 6 | - [packer](https://developer.hashicorp.com/packer/tutorials/docker-get-started/get-started-install-cli) 7 | - [gcloud](https://cloud.google.com/sdk/docs/install-sdk) (to make GCP disk images) 8 | - [az](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) (to make Azure disk images) 9 | 10 | Then you'll also need to install the `qemu` plugin for packer, run 11 | ``` 12 | packer init template.pkr.hcl 13 | ``` 14 | 15 | Finally, you'll have to configure credentials and projects for the cloud provider you want to build 16 | disk images on. See azure_config.example or gcp_config.example. 17 | 18 | ## Building 19 | 20 | `make build/gcp_version` will create a GCP disk image 21 | `make build/azure_version` will create an Azure disk image 22 | `make` will default to the GCP version 23 | -------------------------------------------------------------------------------- /trustedimage/azure_config.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | 5 | # Region to put resources in 6 | AZ_LOCATION=eastus 7 | 8 | # Resource group to put resources in 9 | AZ_RESOURCE_GROUP=my_rg 10 | 11 | # Existing shared image gallery to create image versions in 12 | AZ_SHARED_IMAGE_GALLERY=my_sig 13 | 14 | # Existing image definition within shared image gallery to put image versions in 15 | AZ_IMAGE_DEFINITION=my_id 16 | 17 | # AZ Storage account and container into which to put image VHD 18 | # az storage account create 19 | AZ_STORAGE_ACCOUNT=my_sa 20 | # az storage container create 21 | AZ_STORAGE_CONTAINER=my_sc 22 | 23 | # Regions to replicate to, comma-separated. 24 | AZ_TARGET_REGIONS=eastus 25 | 26 | # Optional host to send files to before calling azcopy. azcopy doesn't compress 27 | # stuff while it's sending, and doesn't do any diffing. This can make upload 28 | # of a VHD file take a LONG time. To get around this, if a jumphost is specified, 29 | # this script will: 30 | # rsync (via ssh) the VHD up to the jumphost 31 | # azcopy the file from the jumphost to Azure 32 | # Since rsync does on-the-wire compression and incremental updates, this can save 33 | # a ton of time. You need to have SSH access to this host. You can specify 34 | # a username with user@hostname 35 | AZ_JUMPHOST=foo@bar.example.com 36 | -------------------------------------------------------------------------------- /trustedimage/azure_copy_blob.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | # Most of this pulled from 6 | # * https://learn.microsoft.com/en-us/azure/virtual-machines/linux/create-upload-generic 7 | # * https://learn.microsoft.com/en-us/azure/virtual-machines/linux/disks-upload-vhd-to-managed-disk-cli 8 | # * https://learn.microsoft.com/en-us/azure/virtual-machines/linux/debian-create-upload-vhd 9 | set -euxo pipefail 10 | 11 | # Pull in local configuration information for where to put this in Azure. 12 | # See ./azure_config.example for an example of this. 13 | if [ ! -f ./azure_config ]; then 14 | echo "Must have created file './azure_config' with $AZ_... variables" 1>&2 15 | exit 1 16 | fi 17 | source ./azure_config 18 | 19 | # Get SAS token for uploading blob. 20 | # Note: We upload a storage blob, not a managed disk. Managed disks are 21 | # rejected when we request confidential computing be enabled in the 22 | # image definition, while blobs are accepted. 23 | FILE="$1" 24 | AZ_BLOB="$2" 25 | AZ_CONNECTION_STRING="$(az storage account show-connection-string \ 26 | --name $AZ_STORAGE_ACCOUNT \ 27 | --resource-group $AZ_RESOURCE_GROUP \ 28 | --output tsv)" 29 | AZ_SAS_EXPIRY="$(date -u -d "30 minutes" '+%Y-%m-%dT%H:%MZ')" 30 | AZ_SAS_TOKEN="$(az storage blob generate-sas --account-name $AZ_STORAGE_ACCOUNT --container-name $AZ_STORAGE_ACCOUNT --name $AZ_BLOB --permissions crw --expiry $AZ_SAS_EXPIRY --connection-string "$AZ_CONNECTION_STRING" --output tsv)" 31 | AZ_BLOB_URL="$(az storage blob url --account-name $AZ_STORAGE_ACCOUNT --container-name $AZ_STORAGE_CONTAINER --name $AZ_BLOB --connection-string "$AZ_CONNECTION_STRING" --output tsv)" 32 | 33 | if [ ! -z "$AZ_JUMPHOST" ]; then 34 | # Rsync up to the target host, then run azcopy from the directory 35 | # we just uploaded it to. 36 | rsync --progress --compress --inplace -e ssh -L $(which azcopy) $FILE $AZ_JUMPHOST:./ 1>&2 37 | ssh $AZ_JUMPHOST "./azcopy copy $(basename $FILE) \"${AZ_BLOB_URL}?${AZ_SAS_TOKEN}\"" 1>&2 38 | else 39 | # Just run the azcopy command locally. 40 | azcopy copy $FILE \"${AZ_BLOB_URL}?${AZ_SAS_TOKEN}\" 1>&2 41 | fi 42 | echo $AZ_BLOB_URL 43 | -------------------------------------------------------------------------------- /trustedimage/debian2.pkr.hcl: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Signal Messenger, LLC 2 | # SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | # Do the second phase of image creation, where we actually create the new, 5 | # secure disk image. The additional disk will be set up as our secure one. 6 | # This could be done in 'debian1', but that takes a long time and by 7 | # splitting them up, we can iterate much faster on these scripts. 8 | 9 | packer { 10 | required_plugins { 11 | qemu = { 12 | source = "github.com/hashicorp/qemu" 13 | version = "~> 1" 14 | } 15 | } 16 | } 17 | 18 | source "qemu" "debian2" { 19 | format = "raw" 20 | headless = "true" 21 | communicator = "ssh" 22 | disk_size = "5G" 23 | disk_additional_size = ["3G"] 24 | memory = 4096 25 | 26 | # Use UEFI again, but this time use the vars we got from 'debian1', 27 | # since they tell us how to boot to the OS we just set up. 28 | efi_boot = "true" 29 | efi_firmware_code = "/usr/share/OVMF/OVMF_CODE_4M.ms.fd" 30 | efi_firmware_vars = "build/debian1.out/efivars.fd" 31 | machine_type = "q35" 32 | 33 | accelerator = "kvm" 34 | disk_interface = "virtio" 35 | net_device = "virtio-net" 36 | output_directory = "build/debian2.out" 37 | shutdown_command = "sudo shutdown -h now" 38 | ssh_password = "svr3" # Super secret, don't tell anyone. 39 | ssh_timeout = "5m" 40 | ssh_username = "svr3" 41 | vm_name = "disk.raw" 42 | 43 | # We start from the disk image that 'debian1' created. Because 44 | # we just created it, we ignore checksumming. 45 | iso_checksum = "none" 46 | iso_url = "build/debian1.out/disk.raw" 47 | disk_image = true 48 | } 49 | 50 | build { 51 | sources = ["source.qemu.debian2"] 52 | 53 | # Write up a bunch of scripts for us to use into /dev/shm/debian2/... 54 | # We use /dev/shm since it's accessible even when we `chroot` halfway 55 | # through our install process. 56 | provisioner "file" { 57 | destination = "/dev/shm" 58 | source = "debian2" 59 | } 60 | 61 | # Call the first of our scripts. It does everything else. 62 | provisioner "shell" { 63 | inline = ["sudo /dev/shm/debian2/run.sh"] 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /trustedimage/debian2/azure-provisioning.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Azure Provisioning 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/bin/bash /usr/sbin/azure-provisioning.sh 7 | ExecStart=/bin/bash -c "hostnamectl set-hostname $(curl \ 8 | -H 'metadata: true' \ 9 | 'http://169.254.169.254/metadata/instance/compute/name?api-version=2019-06-01&format=text')" 10 | ExecStart=/usr/bin/systemctl disable azure-provisioning.service 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /trustedimage/debian2/azure-provisioning.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # From https://learn.microsoft.com/en-us/azure/virtual-machines/linux/no-agent 3 | 4 | attempts=1 5 | until [ "$attempts" -gt 5 ] 6 | do 7 | echo "obtaining goal state - attempt $attempts" 8 | goalstate=$(curl --fail -v -X 'GET' -H "x-ms-agent-name: azure-vm-register" \ 9 | -H "Content-Type: text/xml;charset=utf-8" \ 10 | -H "x-ms-version: 2012-11-30" \ 11 | "http://168.63.129.16/machine/?comp=goalstate") 12 | if [ $? -eq 0 ] 13 | then 14 | echo "successfully retrieved goal state" 15 | retrieved_goal_state=true 16 | break 17 | fi 18 | sleep 5 19 | attempts=$((attempts+1)) 20 | done 21 | 22 | if [ "$retrieved_goal_state" != "true" ] 23 | then 24 | echo "failed to obtain goal state - cannot register this VM" 25 | exit 1 26 | fi 27 | 28 | container_id=$(grep ContainerId <<< "$goalstate" | sed 's/\s*<\/*ContainerId>//g' | sed 's/\r$//') 29 | instance_id=$(grep InstanceId <<< "$goalstate" | sed 's/\s*<\/*InstanceId>//g' | sed 's/\r$//') 30 | 31 | ready_doc=$(cat << EOF 32 | 33 | 34 | 1 35 | 36 | $container_id 37 | 38 | 39 | $instance_id 40 | 41 | Ready 42 | 43 | 44 | 45 | 46 | 47 | EOF 48 | ) 49 | 50 | attempts=1 51 | until [ "$attempts" -gt 5 ] 52 | do 53 | echo "registering with Azure - attempt $attempts" 54 | curl --fail -v -X 'POST' -H "x-ms-agent-name: azure-vm-register" \ 55 | -H "Content-Type: text/xml;charset=utf-8" \ 56 | -H "x-ms-version: 2012-11-30" \ 57 | -d "$ready_doc" \ 58 | "http://168.63.129.16/machine?comp=health" 59 | if [ $? -eq 0 ] 60 | then 61 | echo "successfully register with Azure" 62 | break 63 | fi 64 | sleep 5 # sleep to prevent throttling from wire server 65 | done 66 | -------------------------------------------------------------------------------- /trustedimage/debian2/azure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Most of this comes from https://learn.microsoft.com/en-us/azure/virtual-machines/linux/create-upload-generic 4 | 5 | set -euxo pipefail 6 | 7 | # Fix network interface name. 8 | sed -i 's/enp0s2/eth0/g' /etc/network/interfaces 9 | 10 | # Allow dhclient to do some things it only does on Azure. 11 | cat >> /etc/apparmor.d/local/sbin.dhclient <> /etc/initramfs-tools/modules <> /etc/initramfs-tools/modules <upper (via work), then be writable there. 37 | mount -t overlay overlay -o upperdir=/ram_disk/root,workdir=/ram_disk/work,lowerdir=/verity /root || panic "Overlay failed" 38 | -------------------------------------------------------------------------------- /trustedimage/debian2/initramfs_local-top.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | PREREQ="" 5 | prereqs() 6 | { 7 | echo "$PREREQ" 8 | } 9 | 10 | case $1 in 11 | prereqs) 12 | prereqs 13 | exit 0 14 | ;; 15 | esac 16 | 17 | . /scripts/functions 18 | # Begin real processing below this line 19 | 20 | # Set up /dev/mapper/verity to be a dm-verity integrity-checked 21 | # device with the root hash $svr3verity given in the kernel command 22 | # line. Our kernel command line then references /dev/mapper/verity 23 | # as our root partition, so we'll mount it as /root in initramfs. 24 | # We do some other tricks to it in local-bottom before `pivot_root` 25 | # is called to pivot into it. 26 | log_begin_msg "Creating verity device mapping with veritysetup" 27 | 28 | for device in vda vdb sda sdb nvme0n1p; do 29 | if ! [ -e /dev/${device}3 ]; then 30 | continue 31 | fi 32 | if ! /sbin/veritysetup open /dev/${device}2 verity /dev/${device}3 $svr3verity --panic-on-corruption; then 33 | log_failure_msg "veritysetup failed for ${device}" 34 | exit 1 # Halt boot process 35 | else 36 | log_end_msg 37 | exit 0 38 | fi 39 | done 40 | log_failure_msg "device not found in devices `ls /dev/`" 41 | exit 1 42 | -------------------------------------------------------------------------------- /trustedimage/debian2/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | # 5 | # Our goal with this script is to set up the currently unformatted 6 | # /dev/vdb (our `disk_additional_size` disk) to be a dm-verity-protected 7 | # disk that allows our trust chain up to Grub to continue into userspace. 8 | 9 | set -euxo pipefail 10 | 11 | # Make the partitions we need in the new disk for UEFI secure-boot booting. 12 | parted --script /dev/vdb mklabel gpt 13 | parted --script --align=optimal /dev/vdb mkpart ESP fat32 1MB 512MB 14 | parted --script --align=optimal /dev/vdb mkpart ROOT 512MB 3000MB 15 | parted --script --align=optimal /dev/vdb mkpart HASH 3000MB 100% 16 | parted --script /dev/vdb set 1 boot on 17 | 18 | # Make the necessary filesystems. 19 | mkfs.fat -F 32 /dev/vdb1 20 | mkfs.ext2 /dev/vdb2 21 | 22 | # Mount the new filesystems onto directories we can write to. 23 | mkdir -p /mnt/newroot 24 | mount -t ext2 /dev/vdb2 /mnt/newroot 25 | mount --mkdir /dev/vdb1 /mnt/newroot/boot 26 | 27 | # Copy over all the files from this disk to the new one; this disk is bootable 28 | # so soon that one will be too :D 29 | rsync -ax / /mnt/newroot/ 30 | rsync -ax /boot/efi/ /mnt/newroot/boot/ 31 | 32 | # Mount in necessary subsystems so when we chroot we can do whatever we want. 33 | mount -t proc /proc /mnt/newroot/proc 34 | mount --rbind /sys /mnt/newroot/sys 35 | mount --rbind /dev /mnt/newroot/dev 36 | 37 | # Do some stuff from within the new disk 38 | chroot /mnt/newroot /dev/shm/debian2/chroot.sh 39 | 40 | # Now mark the new disk's vdb2 partition as read-only and set up dm-verity 41 | # using vdb3 as a hash partition. 42 | mount -o remount,ro /dev/vdb2 /mnt/newroot 43 | veritysetup format /dev/vdb2 /dev/vdb3 2>&1 | tee /tmp/verity 44 | HASH=`awk '/^Root/ {print $3}' /tmp/verity` 45 | 46 | # /mnt/newroot is read-only, but /mnt/newroot/boot is not, and its grub config 47 | # currently has a placeholder `VERITYHASH` for our root hash. Fill it in. 48 | sed -i "s#VERITYHASH#$HASH#" /mnt/newroot/boot/grub/grub.cfg 49 | -------------------------------------------------------------------------------- /trustedimage/debian2/svr3: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")" 3 | 4 | if dmidecode -s system-product-name | grep -q "Google Compute Engine"; then 5 | exec ./enclave.gcpsnp "$@" 6 | else 7 | exec ./enclave.azuresnp "$@" 8 | fi 9 | -------------------------------------------------------------------------------- /trustedimage/debian2/svr3.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SVR3 3 | Requires=dev-tpmrm0.device 4 | Before=ssh.service 5 | After=network-online.target 6 | Wants=network-online.target 7 | # If we run SVR3, disallow SSH. 8 | #Conflicts=ssh.service 9 | 10 | [Service] 11 | Type=simple 12 | Restart=always 13 | RestartSec=60 14 | User=root 15 | ExecStart=/usr/bin/svr3 --sock_type=af_inet 16 | 17 | [Install] 18 | WantedBy=multi-user.target 19 | -------------------------------------------------------------------------------- /trustedimage/debian2/svr3gcp: -------------------------------------------------------------------------------- 1 | ../../host/cmd/svr3gcp/svr3gcp -------------------------------------------------------------------------------- /trustedimage/debian2/svr3test: -------------------------------------------------------------------------------- 1 | ../../enclave/build/attest.gcpsnp -------------------------------------------------------------------------------- /trustedimage/gcp.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | set -exo pipefail 4 | 5 | if ! source ./gcp_config; then 6 | echo 1>&2 "Must create ./gcp_config file (see ./gcp_config.example)" 7 | exit 1 8 | fi 9 | 10 | GCLOUD="gcloud --project=$GCP_PROJECT" 11 | 12 | FROM=build/debian2.out/disk.raw-1 13 | VERSION="$1" 14 | VERSION_DASH="$(echo $VERSION | sed 's/\./-/g')" 15 | # Note: to give access, create a service account with "Storage Object User" 16 | # and "Storage Insights Collector Service" (for storage.buckets.get) roles. 17 | # Then, go to the "Permissions" tab of that user and add the 18 | # "Service Account Token Creator" role to the user that's running this script. 19 | BLOB=gs://$GCP_BUCKET/image-$VERSION.tar.gz 20 | function rm_blob() { 21 | # Whether we succeed in creating an image or fail in our endeavours, we 22 | # don't need the blob anymore, so try to delete it. But don't worry 23 | # if this attempt fails; it's just a nice-to-have. 24 | $GCLOUD storage rm $BLOB 25 | } 26 | trap rm_blob EXIT 27 | $GCLOUD storage cp ../host/main gs://$GCP_BUCKET/svr3-$VERSION 28 | $GCLOUD storage cp ../host/cmd/control/control gs://$GCP_BUCKET/svr3control-$VERSION 29 | tar --transform="s/$(basename $FROM)/disk.raw/" --format=oldgnu -cvf - -C $(dirname $FROM) $(basename $FROM) | pigz >gcp.tar.gz 30 | if [ -z "$GCP_JUMPHOST" ]; then 31 | $GCLOUD storage cp ./gcp.tar.gz $BLOB 32 | else 33 | rsync -e ssh --progress --compress --inplace ./gcp.tar.gz $GCP_JUMPHOST:./gcp.tar.gz 34 | ACCESS_TOKEN="$($GCLOUD auth print-access-token --lifetime=900 --impersonate-service-account $GCP_SERVICE_ACCOUNT)" 35 | ssh $GCP_JUMPHOST "CLOUDSDK_AUTH_ACCESS_TOKEN=$ACCESS_TOKEN $GCLOUD storage cp gcp.tar.gz $BLOB" 36 | fi 37 | $GCLOUD compute images create svr3-$VERSION_DASH --source-uri $BLOB --guest-os-features=SEV_SNP_CAPABLE,UEFI_COMPATIBLE 38 | mv -v gcp.tar.gz ../enclave/releases/gcpsnp/${VERSION}.tar.gz 39 | echo $VERSION > build/gcp_version 40 | -------------------------------------------------------------------------------- /trustedimage/gcp_config.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2024 Signal Messenger, LLC 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | 5 | # Which GCP Project to put things in 6 | GCP_PROJECT=my-project 7 | 8 | # Which GCP bucket should be used for intermediate storage of disk 9 | # images before they become images proper. 10 | GCP_BUCKET=my-storage-bucket 11 | 12 | # Which GCP service account should be used for upload, if using 13 | # a jumphost. If not using a jumphost, this can be empty. 14 | GCP_SERVICE_ACCOUNT=my-service-account@my-project.iam.gserviceaccount.com 15 | 16 | # Optional host to send files to before copying up to GCP. This can make upload 17 | # of our image faster on slow network links, as the original copy via rsync is 18 | # compressed and incremental. 19 | # rsync (via ssh) the VHD up to the jumphost 20 | # azcopy the file from the jumphost to Azure 21 | # Since rsync does on-the-wire compression and incremental updates, this can save 22 | # a ton of time. You need to have SSH access to this host. You can specify 23 | # a username with user@hostname 24 | GCP_JUMPHOST=foo@example.com 25 | -------------------------------------------------------------------------------- /trustedimage/template.pkr.hcl: -------------------------------------------------------------------------------- 1 | packer { 2 | required_plugins { 3 | qemu = { 4 | version = "~> 1" 5 | source = "github.com/hashicorp/qemu" 6 | } 7 | } 8 | } 9 | --------------------------------------------------------------------------------