0
0
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:
Graeme Connell 2021-07-15 15:04:30 -06:00 committed by gram-signal
parent 0d5d70038d
commit 9aa79c0c59
10 changed files with 653 additions and 20 deletions

View File

@ -131,10 +131,7 @@ jobs:
target: i686-unknown-linux-gnu target: i686-unknown-linux-gnu
- name: Check for duplicate dependencies - name: Check for duplicate dependencies
run: | run: ./bin/verify_duplicate_crates
DUPS="$(cargo tree -d -e normal --workspace)"
echo "$DUPS"
test -z "$DUPS"
- name: Rustfmt check - name: Rustfmt check
run: cargo fmt --all -- --check run: cargo fmt --all -- --check

173
Cargo.lock generated
View File

@ -39,6 +39,20 @@ dependencies = [
"opaque-debug", "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]] [[package]]
name = "aes-gcm-siv" name = "aes-gcm-siv"
version = "0.10.1" version = "0.10.1"
@ -135,6 +149,17 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.9.0" version = "0.9.0"
@ -197,7 +222,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
dependencies = [ dependencies = [
"rustc_version", "rustc_version 0.2.3",
] ]
[[package]] [[package]]
@ -224,6 +249,31 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.19"
@ -365,6 +415,16 @@ dependencies = [
"loom", "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]] [[package]]
name = "crypto-mac" name = "crypto-mac"
version = "0.9.1" version = "0.9.1"
@ -415,7 +475,7 @@ dependencies = [
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "3.0.0" 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 = [ dependencies = [
"byteorder", "byteorder",
"digest", "digest",
@ -626,10 +686,21 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff"
dependencies = [ dependencies = [
"crypto-mac", "crypto-mac 0.9.1",
"digest", "digest",
] ]
[[package]]
name = "hsm-enclave"
version = "0.1.0"
dependencies = [
"aes-gcm",
"rand_core 0.6.2",
"sha2",
"snow",
"x25519-dalek",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.6.1" version = "1.6.1"
@ -920,29 +991,29 @@ checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333"
[[package]] [[package]]
name = "neon" name = "neon"
version = "0.9.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "158d44fdd9cc93d5051c15e970727e94e4440c3c585c77b2f482df397a661fcf" checksum = "5e85820b585bf3360bf158ac87a75764c48e361c91bbeb69873e6613cc78c023"
dependencies = [ dependencies = [
"cslice", "cslice",
"neon-build", "neon-build",
"neon-macros", "neon-macros",
"neon-runtime", "neon-runtime",
"semver", "semver 0.9.0",
"smallvec", "smallvec",
] ]
[[package]] [[package]]
name = "neon-build" name = "neon-build"
version = "0.9.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "067f30e1bb8bec9a0f2be115fd54430dd66472872c7c1491473618a405c1daac" checksum = "ad9febc63f515156d4311a0c43899d3ace46352ecdd591c21b98ca3974f2a0d0"
[[package]] [[package]]
name = "neon-macros" name = "neon-macros"
version = "0.9.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72adff7fd8a79dc3a18e565086179fa7608c555dd0e553e4eafebf9033b6a01c" checksum = "987f12c91eb6ce0b67819f7c5fb4d391de64cf411c605ed027f03507a33943b2"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
@ -950,9 +1021,9 @@ dependencies = [
[[package]] [[package]]
name = "neon-runtime" name = "neon-runtime"
version = "0.9.0" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be066c504047007b93709dc164c42f9f19c2ff365e7bf71240c8f88c92f2e687" checksum = "02662cd2e62b131937bdef85d0918b05bc3c204daf4c64af62845403eccb60f3"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libloading", "libloading",
@ -1137,6 +1208,15 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]] [[package]]
name = "petgraph" name = "petgraph"
version = "0.5.1" version = "0.5.1"
@ -1261,6 +1341,17 @@ dependencies = [
"sha2", "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]] [[package]]
name = "polyval" name = "polyval"
version = "0.5.1" version = "0.5.1"
@ -1554,7 +1645,16 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [ 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]] [[package]]
@ -1596,7 +1696,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [ 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]] [[package]]
@ -1605,6 +1714,15 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 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]] [[package]]
name = "serde" name = "serde"
version = "1.0.123" version = "1.0.123"
@ -1752,6 +1870,23 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 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]] [[package]]
name = "spin" name = "spin"
version = "0.5.2" version = "0.5.2"
@ -1891,6 +2026,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.7.1" version = "1.7.1"
@ -2084,9 +2225,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.2.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36" checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [ dependencies = [
"zeroize_derive", "zeroize_derive",
] ]

View File

@ -2,6 +2,7 @@
members = [ members = [
"rust/crypto", "rust/crypto",
"rust/device-transfer", "rust/device-transfer",
"rust/hsm-enclave",
"rust/poksho", "rust/poksho",
"rust/protocol", "rust/protocol",
"rust/bridge/ffi", "rust/bridge/ffi",
@ -14,6 +15,7 @@ default-members = [
"rust/poksho", "rust/poksho",
"rust/protocol", "rust/protocol",
] ]
resolver = "2" # so that our dev-dependency features don't leak into products
[patch.crates-io] [patch.crates-io]
curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', branch = '3.0.0-lizard2' } curve25519-dalek = { git = 'https://github.com/signalapp/curve25519-dalek', branch = '3.0.0-lizard2' }

32
bin/verify_duplicate_crates Executable file
View 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

View File

@ -47,3 +47,4 @@ extra_bindings = ["libsignal-bridge"]
[parse.expand] [parse.expand]
crates = ["libsignal-ffi", "libsignal-bridge"] crates = ["libsignal-ffi", "libsignal-bridge"]
features = ["libsignal-bridge/ffi"]

View File

@ -21,3 +21,4 @@ extra_bindings = ["libsignal-bridge"]
[parse.expand] [parse.expand]
crates = ["libsignal-jni", "libsignal-bridge"] crates = ["libsignal-jni", "libsignal-bridge"]
features = ["libsignal-bridge/jni"]

View 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
View 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)
}
}

View 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),
}
}
}

View 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(())
}