mirror of
https://github.com/signalapp/libsignal.git
synced 2024-09-20 03:52:17 +02:00
Client-side rust-only HSM enclave library.
This commit is contained in:
parent
0d5d70038d
commit
9aa79c0c59
5
.github/workflows/build_and_test.yml
vendored
5
.github/workflows/build_and_test.yml
vendored
@ -131,10 +131,7 @@ jobs:
|
||||
target: i686-unknown-linux-gnu
|
||||
|
||||
- name: Check for duplicate dependencies
|
||||
run: |
|
||||
DUPS="$(cargo tree -d -e normal --workspace)"
|
||||
echo "$DUPS"
|
||||
test -z "$DUPS"
|
||||
run: ./bin/verify_duplicate_crates
|
||||
|
||||
- name: Rustfmt check
|
||||
run: cargo fmt --all -- --check
|
||||
|
173
Cargo.lock
generated
173
Cargo.lock
generated
@ -39,6 +39,20 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm-siv"
|
||||
version = "0.10.1"
|
||||
@ -135,6 +149,17 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174"
|
||||
dependencies = [
|
||||
"crypto-mac 0.8.0",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
@ -197,7 +222,7 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"rustc_version 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -224,6 +249,31 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea8756167ea0aca10e066cdbe7813bd71d2f24e69b0bc7b50509590cef2ce0b9"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6547abe025f4027edacd9edaa357aded014eecec42a5070d9b885c3c334aba2"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
@ -365,6 +415,16 @@ dependencies = [
|
||||
"loom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.9.1"
|
||||
@ -415,7 +475,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.0.0"
|
||||
source = "git+https://github.com/signalapp/curve25519-dalek?branch=3.0.0-lizard2#2694ad3b789635f90f941648ae952f58d59ffc73"
|
||||
source = "git+https://github.com/signalapp/curve25519-dalek.git?branch=3.0.0-lizard2#2694ad3b789635f90f941648ae952f58d59ffc73"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest",
|
||||
@ -626,10 +686,21 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"crypto-mac 0.9.1",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hsm-enclave"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"rand_core 0.6.2",
|
||||
"sha2",
|
||||
"snow",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.1"
|
||||
@ -920,29 +991,29 @@ checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333"
|
||||
|
||||
[[package]]
|
||||
name = "neon"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "158d44fdd9cc93d5051c15e970727e94e4440c3c585c77b2f482df397a661fcf"
|
||||
checksum = "5e85820b585bf3360bf158ac87a75764c48e361c91bbeb69873e6613cc78c023"
|
||||
dependencies = [
|
||||
"cslice",
|
||||
"neon-build",
|
||||
"neon-macros",
|
||||
"neon-runtime",
|
||||
"semver",
|
||||
"semver 0.9.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neon-build"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "067f30e1bb8bec9a0f2be115fd54430dd66472872c7c1491473618a405c1daac"
|
||||
checksum = "ad9febc63f515156d4311a0c43899d3ace46352ecdd591c21b98ca3974f2a0d0"
|
||||
|
||||
[[package]]
|
||||
name = "neon-macros"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72adff7fd8a79dc3a18e565086179fa7608c555dd0e553e4eafebf9033b6a01c"
|
||||
checksum = "987f12c91eb6ce0b67819f7c5fb4d391de64cf411c605ed027f03507a33943b2"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@ -950,9 +1021,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "neon-runtime"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be066c504047007b93709dc164c42f9f19c2ff365e7bf71240c8f88c92f2e687"
|
||||
checksum = "02662cd2e62b131937bdef85d0918b05bc3c204daf4c64af62845403eccb60f3"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libloading",
|
||||
@ -1137,6 +1208,15 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.5.1"
|
||||
@ -1261,6 +1341,17 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fcffab1f78ebbdf4b93b68c1ffebc24037eedf271edaca795732b24e5e4e349"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.5.1"
|
||||
@ -1554,7 +1645,16 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"semver 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||
dependencies = [
|
||||
"semver 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1596,7 +1696,16 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
"semver-parser 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||
dependencies = [
|
||||
"semver-parser 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1605,6 +1714,15 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.123"
|
||||
@ -1752,6 +1870,23 @@ version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "snow"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"blake2",
|
||||
"chacha20poly1305",
|
||||
"rand 0.8.3",
|
||||
"rand_core 0.6.2",
|
||||
"rustc_version 0.3.3",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
@ -1891,6 +2026,12 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
@ -2084,9 +2225,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
|
||||
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"rust/crypto",
|
||||
"rust/device-transfer",
|
||||
"rust/hsm-enclave",
|
||||
"rust/poksho",
|
||||
"rust/protocol",
|
||||
"rust/bridge/ffi",
|
||||
@ -14,6 +15,7 @@ default-members = [
|
||||
"rust/poksho",
|
||||
"rust/protocol",
|
||||
]
|
||||
resolver = "2" # so that our dev-dependency features don't leak into products
|
||||
|
||||
[patch.crates-io]
|
||||
curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', branch = '3.0.0-lizard2' }
|
||||
|
32
bin/verify_duplicate_crates
Executable file
32
bin/verify_duplicate_crates
Executable file
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Copyright 2021 Signal Messenger, LLC.
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
#
|
||||
|
||||
# For now, these dependencies have conflicting requirements.
|
||||
# You can use the `cargo tree` command below to see where they come from,
|
||||
# and then document them here.
|
||||
#
|
||||
# getrandom + rand_core:
|
||||
# curve25519-dalek + x25519-dalek use rand_core 0.5, snow uses rand_core 0.6
|
||||
# serde:
|
||||
# num_enum_derive indirectly uses serde in a proc-macro;
|
||||
# unfortunately that shows up as a repeat here
|
||||
EXPECTED="\
|
||||
getrandom v0.1.16
|
||||
|
||||
getrandom v0.2.2
|
||||
|
||||
rand_core v0.5.1
|
||||
|
||||
rand_core v0.6.2
|
||||
|
||||
serde v1.0.123
|
||||
|
||||
serde v1.0.123"
|
||||
|
||||
if [[ $(cargo tree -d -e normal --workspace --depth 0) != "${EXPECTED}" ]]; then
|
||||
cargo tree -d -e normal --workspace
|
||||
fi
|
@ -47,3 +47,4 @@ extra_bindings = ["libsignal-bridge"]
|
||||
|
||||
[parse.expand]
|
||||
crates = ["libsignal-ffi", "libsignal-bridge"]
|
||||
features = ["libsignal-bridge/ffi"]
|
||||
|
@ -21,3 +21,4 @@ extra_bindings = ["libsignal-bridge"]
|
||||
|
||||
[parse.expand]
|
||||
crates = ["libsignal-jni", "libsignal-bridge"]
|
||||
features = ["libsignal-bridge/jni"]
|
||||
|
15
rust/hsm-enclave/Cargo.toml
Normal file
15
rust/hsm-enclave/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "hsm-enclave"
|
||||
version = "0.1.0"
|
||||
authors = ["Graeme Connell <gram@signal.org>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
snow = { version = "0.8.0", default-features = false }
|
||||
aes-gcm = "0.9"
|
||||
rand_core = { version = "0.6", features = ["getrandom"] }
|
||||
sha2 = "0.9"
|
||||
x25519-dalek = "1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
snow = { version = "0.8.0", features = ["default-resolver"] }
|
160
rust/hsm-enclave/src/lib.rs
Normal file
160
rust/hsm-enclave/src/lib.rs
Normal file
@ -0,0 +1,160 @@
|
||||
//
|
||||
// Copyright 2021 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
//! Support logic for Signal's device-to-device transfer feature.
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::convert::From;
|
||||
use std::fmt;
|
||||
|
||||
mod snow_resolver;
|
||||
|
||||
/// Error types for device transfer.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Failure to connect to a trusted HSM.
|
||||
HSMCommunicationError(snow::Error),
|
||||
/// Failure to connect to trusted code on the given HSM.
|
||||
TrustedCodeError,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::HSMCommunicationError(n) => write!(f, "Error in Noise protocol ({})", n),
|
||||
Error::TrustedCodeError => {
|
||||
write!(f, "Trusted HSM process does not match trusted code hash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<snow::Error> for Error {
|
||||
fn from(e: snow::Error) -> Self {
|
||||
Error::HSMCommunicationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a connection handshake to an HSM-resident enclave.
|
||||
///
|
||||
/// ```pseudocode
|
||||
/// let mut client_conn_establishment = ClientConnectionEstablishment::new(...)?;
|
||||
/// let websocket = ... open websocket ...
|
||||
/// websocket.send(client_conn_establishment.initial_request());
|
||||
/// let initial_response = websocket.recv(...);
|
||||
/// let conn = client_conn_establishment.complete(initial_response)?;
|
||||
/// ```
|
||||
pub struct ClientConnectionEstablishment {
|
||||
hs: snow::HandshakeState,
|
||||
initial_message: Vec<u8>,
|
||||
trusted_code_hashes: Vec<[u8; CODE_HASH_SIZE]>,
|
||||
}
|
||||
|
||||
const NOISE_PATTERN: &str = "Noise_NK_25519_AESGCM_SHA256";
|
||||
const NOISE_HANDSHAKE_OVERHEAD: usize = 64; // TODO: this could be more exact
|
||||
|
||||
/// The size in bytes of a code hash.
|
||||
pub const CODE_HASH_SIZE: usize = 32;
|
||||
/// The size in bytes of a public key.
|
||||
pub const PUB_KEY_SIZE: usize = 32;
|
||||
|
||||
impl ClientConnectionEstablishment {
|
||||
/// Creates a new client connection establishment.
|
||||
pub fn new(
|
||||
trusted_public_key: [u8; PUB_KEY_SIZE],
|
||||
trusted_code_hashes: Vec<[u8; CODE_HASH_SIZE]>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut hs = snow::Builder::with_resolver(
|
||||
NOISE_PATTERN.parse().expect("valid"),
|
||||
Box::new(snow_resolver::Resolver),
|
||||
)
|
||||
.remote_public_key(&trusted_public_key[..])
|
||||
.build_initiator()?;
|
||||
let payload = trusted_code_hashes.concat();
|
||||
let mut initial_message = vec![0u8; NOISE_HANDSHAKE_OVERHEAD + payload.len()];
|
||||
let size = hs.write_message(&payload, &mut initial_message)?;
|
||||
initial_message.truncate(size);
|
||||
Ok(Self {
|
||||
hs,
|
||||
initial_message,
|
||||
trusted_code_hashes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Initial message to send to server to establish connection.
|
||||
pub fn initial_request(&self) -> &[u8] {
|
||||
&self.initial_message
|
||||
}
|
||||
|
||||
/// Completes client connection initiation, returns a valid client connection.
|
||||
pub fn complete(mut self, initial_received: &[u8]) -> Result<ClientConnection, Error> {
|
||||
let mut received_hash = [0u8; CODE_HASH_SIZE];
|
||||
let size = self.hs.read_message(initial_received, &mut received_hash)?;
|
||||
if size != received_hash.len() {
|
||||
return Err(Error::TrustedCodeError);
|
||||
}
|
||||
if !self.trusted_code_hashes.contains(&received_hash) {
|
||||
return Err(Error::TrustedCodeError);
|
||||
}
|
||||
let transport = self.hs.into_transport_mode()?;
|
||||
Ok(ClientConnection { transport })
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps an established connection to an HSM-resident enclave.
|
||||
///
|
||||
/// ```pseudocode
|
||||
/// let conn = client_connection_establishment.complete(...)?;
|
||||
///
|
||||
/// // any number of sends:
|
||||
/// let plaintext_to_send: &[u8] = ...;
|
||||
/// let encrypted_to_send: Vec<u8> = conn.send(plaintext_to_send)?;
|
||||
/// websocket.send(&encrypted_to_send)?;
|
||||
///
|
||||
/// // and receives:
|
||||
/// let encrypted_received = websocket.recv(...)?;
|
||||
/// let plaintext_received: Vec<u8> = conn.recv(encrypted_received)?;
|
||||
/// ```
|
||||
pub struct ClientConnection {
|
||||
transport: snow::TransportState,
|
||||
}
|
||||
|
||||
const NOISE_TRANSPORT_PER_PACKET_MAX: usize = 65535;
|
||||
const NOISE_TRANSPORT_PER_PAYLOAD_OVERHEAD: usize = 16;
|
||||
const NOISE_TRANSPORT_PER_PAYLOAD_MAX: usize =
|
||||
NOISE_TRANSPORT_PER_PACKET_MAX - NOISE_TRANSPORT_PER_PAYLOAD_OVERHEAD;
|
||||
|
||||
impl ClientConnection {
|
||||
/// Wrap a plaintext message to be sent, returning the ciphertext.
|
||||
pub fn send(&mut self, plaintext_to_send: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let max_ciphertext_size = plaintext_to_send.len()
|
||||
+ (1 + plaintext_to_send.len() / NOISE_TRANSPORT_PER_PAYLOAD_MAX)
|
||||
* NOISE_HANDSHAKE_OVERHEAD;
|
||||
let mut ciphertext = vec![0u8; max_ciphertext_size];
|
||||
let mut total_size = 0;
|
||||
for chunk in plaintext_to_send.chunks(NOISE_TRANSPORT_PER_PAYLOAD_MAX) {
|
||||
total_size += self
|
||||
.transport
|
||||
.write_message(chunk, &mut ciphertext[total_size..])?;
|
||||
}
|
||||
ciphertext.truncate(total_size);
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
/// Unwrap a ciphertext message that's been received, returning the plaintext.
|
||||
pub fn recv(&mut self, received_ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let mut received_plaintext: Vec<u8> = vec![0u8; received_ciphertext.len()];
|
||||
let mut total_size = 0;
|
||||
for chunk in received_ciphertext.chunks(NOISE_TRANSPORT_PER_PACKET_MAX) {
|
||||
total_size += self
|
||||
.transport
|
||||
.read_message(chunk, &mut received_plaintext[total_size..])?;
|
||||
}
|
||||
received_plaintext.truncate(total_size);
|
||||
Ok(received_plaintext)
|
||||
}
|
||||
}
|
199
rust/hsm-enclave/src/snow_resolver.rs
Normal file
199
rust/hsm-enclave/src/snow_resolver.rs
Normal file
@ -0,0 +1,199 @@
|
||||
//
|
||||
// Copyright 2021 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
use aes_gcm::aead::generic_array::typenum::Unsigned;
|
||||
use aes_gcm::{AeadCore, AeadInPlace, NewAead};
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use snow::params::{CipherChoice, DHChoice, HashChoice};
|
||||
use snow::resolvers::CryptoResolver;
|
||||
use snow::types::{Cipher, Dh, Hash, Random};
|
||||
use x25519_dalek as x25519;
|
||||
|
||||
struct Rng<T>(T);
|
||||
impl<T: RngCore> RngCore for Rng<T> {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.0.next_u64()
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
self.0.fill_bytes(dest)
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||
self.0.try_fill_bytes(dest)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CryptoRng> CryptoRng for Rng<T> {}
|
||||
impl<T: RngCore + CryptoRng + Send + Sync> Random for Rng<T> {}
|
||||
|
||||
// From snow's resolvers/default.rs
|
||||
#[derive(Default)]
|
||||
struct Dh25519 {
|
||||
privkey: [u8; 32],
|
||||
pubkey: [u8; 32],
|
||||
}
|
||||
impl Dh for Dh25519 {
|
||||
fn name(&self) -> &'static str {
|
||||
"25519"
|
||||
}
|
||||
|
||||
fn pub_len(&self) -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn priv_len(&self) -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn set(&mut self, privkey: &[u8]) {
|
||||
self.privkey.copy_from_slice(privkey);
|
||||
self.pubkey = x25519::x25519(self.privkey, x25519::X25519_BASEPOINT_BYTES);
|
||||
}
|
||||
|
||||
fn generate(&mut self, rng: &mut dyn Random) {
|
||||
rng.fill_bytes(&mut self.privkey);
|
||||
self.pubkey = x25519::x25519(self.privkey, x25519::X25519_BASEPOINT_BYTES);
|
||||
}
|
||||
|
||||
fn pubkey(&self) -> &[u8] {
|
||||
&self.pubkey
|
||||
}
|
||||
|
||||
fn privkey(&self) -> &[u8] {
|
||||
&self.privkey
|
||||
}
|
||||
|
||||
fn dh(&self, pubkey: &[u8], out: &mut [u8]) -> Result<(), ()> {
|
||||
let result = x25519::x25519(self.privkey, pubkey[..self.pub_len()].try_into().unwrap());
|
||||
out[..result.len()].copy_from_slice(&result);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Based on snow's resolvers/default.rs
|
||||
#[derive(Default)]
|
||||
struct HashSHA256 {
|
||||
hasher: Sha256,
|
||||
}
|
||||
impl Hash for HashSHA256 {
|
||||
fn name(&self) -> &'static str {
|
||||
"sha256"
|
||||
}
|
||||
|
||||
fn block_len(&self) -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn hash_len(&self) -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.hasher = Sha256::default();
|
||||
}
|
||||
|
||||
fn input(&mut self, data: &[u8]) {
|
||||
self.hasher.update(data);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
let hash = self.hasher.finalize_reset();
|
||||
out[..hash.len()].copy_from_slice(&hash);
|
||||
}
|
||||
}
|
||||
|
||||
// Based on snow's resolvers/default.rs
|
||||
#[derive(Default)]
|
||||
struct CipherAesGcm {
|
||||
key: [u8; 32],
|
||||
}
|
||||
|
||||
impl Cipher for CipherAesGcm {
|
||||
fn name(&self) -> &'static str {
|
||||
"AESGCM"
|
||||
}
|
||||
|
||||
fn set(&mut self, key: &[u8]) {
|
||||
self.key.copy_from_slice(key);
|
||||
}
|
||||
|
||||
fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize {
|
||||
let aead = aes_gcm::Aes256Gcm::new(&self.key.into());
|
||||
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
nonce_bytes[4..].copy_from_slice(&nonce.to_be_bytes());
|
||||
|
||||
let (ciphertext, rest_of_out) = out.split_at_mut(plaintext.len());
|
||||
ciphertext.copy_from_slice(plaintext);
|
||||
|
||||
let tag = aead
|
||||
.encrypt_in_place_detached(&nonce_bytes.into(), authtext, ciphertext)
|
||||
.expect("Encryption failed!");
|
||||
|
||||
rest_of_out[..tag.len()].copy_from_slice(&tag);
|
||||
|
||||
plaintext.len() + <aes_gcm::Aes256Gcm as AeadCore>::TagSize::USIZE
|
||||
}
|
||||
|
||||
fn decrypt(
|
||||
&self,
|
||||
nonce: u64,
|
||||
authtext: &[u8],
|
||||
ciphertext: &[u8],
|
||||
out: &mut [u8],
|
||||
) -> Result<usize, ()> {
|
||||
let aead = aes_gcm::Aes256Gcm::new(&self.key.into());
|
||||
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
nonce_bytes[4..].copy_from_slice(&nonce.to_be_bytes());
|
||||
|
||||
let (ciphertext, tag) = ciphertext
|
||||
.split_at(ciphertext.len() - <aes_gcm::Aes256Gcm as AeadCore>::TagSize::USIZE);
|
||||
|
||||
let plaintext = &mut out[..ciphertext.len()];
|
||||
plaintext.copy_from_slice(ciphertext);
|
||||
|
||||
aead.decrypt_in_place_detached(&nonce_bytes.into(), authtext, plaintext, tag.into())
|
||||
.map(|_| plaintext.len())
|
||||
.map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Resolver;
|
||||
|
||||
impl CryptoResolver for Resolver {
|
||||
fn resolve_rng(&self) -> Option<Box<dyn Random>> {
|
||||
Some(Box::new(Rng(rand_core::OsRng)))
|
||||
}
|
||||
|
||||
fn resolve_dh(&self, choice: &DHChoice) -> Option<Box<dyn Dh>> {
|
||||
match choice {
|
||||
DHChoice::Curve25519 => Some(Box::new(Dh25519::default())),
|
||||
_ => panic!("{:?} not supported", choice),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_hash(&self, choice: &HashChoice) -> Option<Box<dyn Hash>> {
|
||||
match choice {
|
||||
HashChoice::SHA256 => Some(Box::new(HashSHA256::default())),
|
||||
_ => panic!("{:?} not supported", choice),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_cipher(&self, choice: &CipherChoice) -> Option<Box<dyn Cipher>> {
|
||||
match choice {
|
||||
CipherChoice::AESGCM => Some(Box::new(CipherAesGcm::default())),
|
||||
_ => panic!("{:?} not supported", choice),
|
||||
}
|
||||
}
|
||||
}
|
85
rust/hsm-enclave/tests/tests.rs
Normal file
85
rust/hsm-enclave/tests/tests.rs
Normal file
@ -0,0 +1,85 @@
|
||||
//
|
||||
// Copyright 2021 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use hsm_enclave::*;
|
||||
const NOISE_PATTERN: &str = "Noise_NK_25519_AESGCM_SHA256";
|
||||
|
||||
#[test]
|
||||
fn test_hsm_enclave_happy_path() -> Result<(), Error> {
|
||||
// Spin up a handshake for the server-side.
|
||||
let keypair = snow::Builder::new(NOISE_PATTERN.parse()?).generate_keypair()?;
|
||||
let mut server_hs = snow::Builder::new(NOISE_PATTERN.parse()?)
|
||||
.local_private_key(&keypair.private)
|
||||
.build_responder()?;
|
||||
|
||||
// Spin up the client connection establishment.
|
||||
let mut public_key = [0u8; 32];
|
||||
public_key.copy_from_slice(&keypair.public);
|
||||
let establishment = ClientConnectionEstablishment::new(public_key, vec![[1u8; 32]])?;
|
||||
|
||||
// Give the establishment message to the server.
|
||||
let mut payload = vec![0u8; 32];
|
||||
let read_size = server_hs.read_message(establishment.initial_request(), &mut payload)?;
|
||||
assert_eq!(read_size, 32);
|
||||
assert_eq!(payload, [1u8; 32]);
|
||||
|
||||
// Send message back to client, finish handshake.
|
||||
let mut message = vec![0u8; 80];
|
||||
let write_size = server_hs.write_message(&payload, &mut message)?;
|
||||
assert_eq!(write_size, 80);
|
||||
assert!(server_hs.is_handshake_finished());
|
||||
let mut server_transport = server_hs.into_transport_mode()?;
|
||||
|
||||
// This should complete our connection establishment, now.
|
||||
let mut conn = establishment.complete(&message)?;
|
||||
|
||||
// Send message server to client.
|
||||
let mut svr_cli_message = vec![0u8; 19]; // size=3 + overhead=16
|
||||
let svr_cli_write_size = server_transport.write_message(&[7, 8, 9], &mut svr_cli_message)?;
|
||||
assert_eq!(svr_cli_write_size, 19);
|
||||
assert_eq!([7, 8, 9], conn.recv(&svr_cli_message)?.as_slice());
|
||||
|
||||
// Send message client to server.
|
||||
let cli_svr_message = conn.send(&[0xa, 0xb, 0xc])?;
|
||||
let mut cli_svr_payload = vec![0u8; 3];
|
||||
let cli_svr_read_size =
|
||||
server_transport.read_message(&cli_svr_message, &mut cli_svr_payload)?;
|
||||
assert_eq!(cli_svr_read_size, 3);
|
||||
assert_eq!([0xAu8, 0xBu8, 0xCu8], cli_svr_payload.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hsm_enclave_codehash_mismatch() -> Result<(), Error> {
|
||||
// Spin up a handshake for the server-side.
|
||||
let keypair = snow::Builder::new(NOISE_PATTERN.parse()?).generate_keypair()?;
|
||||
let mut server_hs = snow::Builder::new(NOISE_PATTERN.parse()?)
|
||||
.local_private_key(&keypair.private)
|
||||
.build_responder()?;
|
||||
|
||||
// Spin up the client connection establishment.
|
||||
let mut public_key = [0u8; 32];
|
||||
public_key.copy_from_slice(&keypair.public);
|
||||
let establishment = ClientConnectionEstablishment::new(public_key, vec![[1u8; 32]])?;
|
||||
|
||||
// Give the establishment message to the server.
|
||||
let mut payload = vec![0u8; 32];
|
||||
let read_size = server_hs.read_message(establishment.initial_request(), &mut payload)?;
|
||||
assert_eq!(read_size, 32);
|
||||
assert_eq!(payload, [1u8; 32]);
|
||||
|
||||
// Send message back to client, finish handshake.
|
||||
let mismatched_payload = vec![2u8; 32];
|
||||
let mut message = vec![0u8; 80];
|
||||
let write_size = server_hs.write_message(&mismatched_payload, &mut message)?;
|
||||
assert_eq!(write_size, 80);
|
||||
|
||||
// This should complete our connection establishment, now.
|
||||
let out = establishment.complete(message.as_slice());
|
||||
assert!(out.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user