diff options
Diffstat (limited to 'src/rust/external/crypto_digest.rs')
-rw-r--r-- | src/rust/external/crypto_digest.rs | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/src/rust/external/crypto_digest.rs b/src/rust/external/crypto_digest.rs new file mode 100644 index 0000000000..3e8801f203 --- /dev/null +++ b/src/rust/external/crypto_digest.rs @@ -0,0 +1,406 @@ +// Copyright (c) 2018, The Tor Project, Inc. +// Copyright (c) 2018, isis agora lovecruft +// See LICENSE for licensing information + +//! Bindings to external digest and XOF functions which live within +//! src/common/crypto_digest.[ch]. +//! +//! We wrap our C implementations in src/common/crypto_digest.[ch] with more +//! Rusty types and interfaces in src/rust/crypto/digest/. + +use std::process::abort; + +use libc::c_char; +use libc::c_int; +use libc::size_t; +use libc::uint8_t; + +use smartlist::Stringlist; + +/// Length of the output of our message digest. +pub const DIGEST_LEN: usize = 20; + +/// Length of the output of our second (improved) message digests. (For now +/// this is just sha256, but it could be any other 256-bit digest.) +pub const DIGEST256_LEN: usize = 32; + +/// Length of the output of our 64-bit optimized message digests (SHA512). +pub const DIGEST512_LEN: usize = 64; + +/// Length of a sha1 message digest when encoded in base32 with trailing = signs +/// removed. +pub const BASE32_DIGEST_LEN: usize = 32; + +/// Length of a sha1 message digest when encoded in base64 with trailing = signs +/// removed. +pub const BASE64_DIGEST_LEN: usize = 27; + +/// Length of a sha256 message digest when encoded in base64 with trailing = +/// signs removed. +pub const BASE64_DIGEST256_LEN: usize = 43; + +/// Length of a sha512 message digest when encoded in base64 with trailing = +/// signs removed. +pub const BASE64_DIGEST512_LEN: usize = 86; + +/// Length of hex encoding of SHA1 digest, not including final NUL. +pub const HEX_DIGEST_LEN: usize = 40; + +/// Length of hex encoding of SHA256 digest, not including final NUL. +pub const HEX_DIGEST256_LEN: usize = 64; + +/// Length of hex encoding of SHA512 digest, not including final NUL. +pub const HEX_DIGEST512_LEN: usize = 128; + +/// Our C code uses an enum to declare the digest algorithm types which we know +/// about. However, because enums are implementation-defined in C, we can +/// neither work with them directly nor translate them into Rust enums. +/// Instead, we represent them as a u8 (under the assumption that we'll never +/// support more than 256 hash functions). +#[allow(non_camel_case_types)] +type digest_algorithm_t = u8; + +const DIGEST_SHA1: digest_algorithm_t = 0; +const DIGEST_SHA256: digest_algorithm_t = 1; +const DIGEST_SHA512: digest_algorithm_t = 2; +const DIGEST_SHA3_256: digest_algorithm_t = 3; +const DIGEST_SHA3_512: digest_algorithm_t = 4; + +/// The number of hash digests we produce for a `common_digests_t`. +/// +/// We can't access these from Rust, because their definitions in C require +/// introspecting the `digest_algorithm_t` typedef, which is an enum, so we have +/// to redefine them here. +const N_COMMON_DIGEST_ALGORITHMS: usize = DIGEST_SHA256 as usize + 1; + +/// A digest function. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +struct crypto_digest_t { + // This private, zero-length field forces the struct to be treated the same + // as its opaque C couterpart. + _unused: [u8; 0], +} + +/// An eXtendible Output Function (XOF). +#[repr(C)] +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +struct crypto_xof_t { + // This private, zero-length field forces the struct to be treated the same + // as its opaque C couterpart. + _unused: [u8; 0], +} + +/// A set of all the digests we commonly compute, taken on a single +/// string. Any digests that are shorter than 512 bits are right-padded +/// with 0 bits. +/// +/// Note that this representation wastes 44 bytes for the SHA1 case, so +/// don't use it for anything where we need to allocate a whole bunch at +/// once. +#[repr(C)] +#[derive(Debug, Copy, Clone)] +#[allow(non_camel_case_types)] +struct common_digests_t { + pub d: [[c_char; N_COMMON_DIGEST_ALGORITHMS]; DIGEST256_LEN], +} + +/// A `smartlist_t` is just an alias for the `#[repr(C)]` type `Stringlist`, to +/// make it more clear that we're working with a smartlist which is owned by C. +#[allow(non_camel_case_types)] +// BINDGEN_GENERATED: This type isn't actually bindgen generated, but the code +// below it which uses it is. As such, this comes up as "dead code" as well. +#[allow(dead_code)] +type smartlist_t = Stringlist; + +/// All of the external functions from `src/common/crypto_digest.h`. +/// +/// These are kept private because they should be wrapped with Rust to make their usage safer. +// +// BINDGEN_GENERATED: These definitions were generated with bindgen and cleaned +// up manually. As such, there are more bindings than are likely necessary or +// which are in use. +#[allow(dead_code)] +extern "C" { + fn crypto_digest(digest: *mut c_char, m: *const c_char, len: size_t) -> c_int; + fn crypto_digest256(digest: *mut c_char, m: *const c_char, len: size_t, + algorithm: digest_algorithm_t) -> c_int; + fn crypto_digest512(digest: *mut c_char, m: *const c_char, len: size_t, + algorithm: digest_algorithm_t) -> c_int; + fn crypto_common_digests(ds_out: *mut common_digests_t, m: *const c_char, len: size_t) -> c_int; + fn crypto_digest_smartlist_prefix(digest_out: *mut c_char, len_out: size_t, prepend: *const c_char, + lst: *const smartlist_t, append: *const c_char, alg: digest_algorithm_t); + fn crypto_digest_smartlist(digest_out: *mut c_char, len_out: size_t, + lst: *const smartlist_t, append: *const c_char, alg: digest_algorithm_t); + fn crypto_digest_algorithm_get_name(alg: digest_algorithm_t) -> *const c_char; + fn crypto_digest_algorithm_get_length(alg: digest_algorithm_t) -> size_t; + fn crypto_digest_algorithm_parse_name(name: *const c_char) -> c_int; + fn crypto_digest_new() -> *mut crypto_digest_t; + fn crypto_digest256_new(algorithm: digest_algorithm_t) -> *mut crypto_digest_t; + fn crypto_digest512_new(algorithm: digest_algorithm_t) -> *mut crypto_digest_t; + fn crypto_digest_free_(digest: *mut crypto_digest_t); + fn crypto_digest_add_bytes(digest: *mut crypto_digest_t, data: *const c_char, len: size_t); + fn crypto_digest_get_digest(digest: *mut crypto_digest_t, out: *mut c_char, out_len: size_t); + fn crypto_digest_dup(digest: *const crypto_digest_t) -> *mut crypto_digest_t; + fn crypto_digest_assign(into: *mut crypto_digest_t, from: *const crypto_digest_t); + fn crypto_hmac_sha256(hmac_out: *mut c_char, key: *const c_char, key_len: size_t, + msg: *const c_char, msg_len: size_t); + fn crypto_mac_sha3_256(mac_out: *mut uint8_t, len_out: size_t, + key: *const uint8_t, key_len: size_t, + msg: *const uint8_t, msg_len: size_t); + fn crypto_xof_new() -> *mut crypto_xof_t; + fn crypto_xof_add_bytes(xof: *mut crypto_xof_t, data: *const uint8_t, len: size_t); + fn crypto_xof_squeeze_bytes(xof: *mut crypto_xof_t, out: *mut uint8_t, len: size_t); + fn crypto_xof_free(xof: *mut crypto_xof_t); +} + +/// A wrapper around a `digest_algorithm_t`. +pub enum DigestAlgorithm { + SHA2_256, + SHA2_512, + SHA3_256, + SHA3_512, +} + +impl From<DigestAlgorithm> for digest_algorithm_t { + fn from(digest: DigestAlgorithm) -> digest_algorithm_t { + match digest { + DigestAlgorithm::SHA2_256 => DIGEST_SHA256, + DigestAlgorithm::SHA2_512 => DIGEST_SHA512, + DigestAlgorithm::SHA3_256 => DIGEST_SHA3_256, + DigestAlgorithm::SHA3_512 => DIGEST_SHA3_512, + } + } +} + +/// A wrapper around a mutable pointer to a `crypto_digest_t`. +pub struct CryptoDigest(*mut crypto_digest_t); + +/// Explicitly copy the state of a `CryptoDigest` hash digest context. +/// +/// # C_RUST_COUPLED +/// +/// * `crypto_digest_dup` +impl Clone for CryptoDigest { + fn clone(&self) -> CryptoDigest { + let digest: *mut crypto_digest_t; + + unsafe { + digest = crypto_digest_dup(self.0 as *const crypto_digest_t); + } + + // See the note in the implementation of CryptoDigest for the + // reasoning for `abort()` here. + if digest.is_null() { + abort(); + } + + CryptoDigest(digest) + } +} + +impl CryptoDigest { + /// A wrapper to call one of the C functions `crypto_digest_new`, + /// `crypto_digest256_new`, or `crypto_digest512_new`. + /// + /// # Warnings + /// + /// This function will `abort()` the entire process in an "abnormal" fashion, + /// i.e. not unwinding this or any other thread's stack, running any + /// destructors, or calling any panic/exit hooks) if `tor_malloc()` (called in + /// `crypto_digest256_new()`) is unable to allocate memory. + /// + /// # Returns + /// + /// A new `CryptoDigest`, which is a wrapper around a opaque representation + /// of a `crypto_digest_t`. The underlying `crypto_digest_t` _MUST_ only + /// ever be handled via a raw pointer, and never introspected. + /// + /// # C_RUST_COUPLED + /// + /// * `crypto_digest_new` + /// * `crypto_digest256_new` + /// * `crypto_digest512_new` + /// * `tor_malloc` (called by `crypto_digest256_new`, but we make + /// assumptions about its behvaiour and return values here) + pub fn new(algorithm: Option<DigestAlgorithm>) -> CryptoDigest { + let digest: *mut crypto_digest_t; + + if algorithm.is_none() { + unsafe { + digest = crypto_digest_new(); + } + } else { + let algo: digest_algorithm_t = algorithm.unwrap().into(); // can't fail because it's Some + + unsafe { + // XXX This is a pretty awkward API to use from Rust... + digest = match algo { + DIGEST_SHA1 => crypto_digest_new(), + DIGEST_SHA256 => crypto_digest256_new(DIGEST_SHA256), + DIGEST_SHA3_256 => crypto_digest256_new(DIGEST_SHA3_256), + DIGEST_SHA512 => crypto_digest512_new(DIGEST_SHA512), + DIGEST_SHA3_512 => crypto_digest512_new(DIGEST_SHA3_512), + _ => abort(), + } + } + } + + // In our C code, `crypto_digest*_new()` allocates memory with + // `tor_malloc()`. In `tor_malloc()`, if the underlying malloc + // implementation fails to allocate the requested memory and returns a + // NULL pointer, we call `exit(1)`. In the case that this `exit(1)` is + // called within a worker, be that a process or a thread, the inline + // comments within `tor_malloc()` mention "that's ok, since the parent + // will run out of memory soon anyway". However, if it takes long + // enough for the worker to die, and it manages to return a NULL pointer + // to our Rust code, our Rust is now in an irreparably broken state and + // may exhibit undefined behaviour. An even worse scenario, if/when we + // have parent/child processes/threads controlled by Rust, would be that + // the UB contagion in Rust manages to spread to other children before + // the entire process (hopefully terminates). + // + // However, following the assumptions made in `tor_malloc()` that + // calling `exit(1)` in a child is okay because the parent will + // eventually run into the same errors, and also to stymie any UB + // contagion in the meantime, we call abort!() here to terminate the + // entire program immediately. + if digest.is_null() { + abort(); + } + + CryptoDigest(digest) + } + + /// A wrapper to call the C function `crypto_digest_add_bytes`. + /// + /// # Inputs + /// + /// * `bytes`: a byte slice of bytes to be added into this digest. + /// + /// # C_RUST_COUPLED + /// + /// * `crypto_digest_add_bytes` + pub fn add_bytes(&self, bytes: &[u8]) { + unsafe { + crypto_digest_add_bytes(self.0 as *mut crypto_digest_t, + bytes.as_ptr() as *const c_char, + bytes.len() as size_t) + } + } +} + +impl Drop for CryptoDigest { + fn drop(&mut self) { + unsafe { + crypto_digest_free_(self.0 as *mut crypto_digest_t); + } + } +} + +/// Get the 256-bit digest output of a `crypto_digest_t`. +/// +/// # Inputs +/// +/// * `digest`: A `CryptoDigest` which wraps either a `DIGEST_SHA256` or a +/// `DIGEST_SHA3_256`. +/// +/// # Warning +/// +/// Calling this function with a `CryptoDigest` which is neither SHA2-256 or +/// SHA3-256 is a programming error. Since we cannot introspect the opaque +/// struct from Rust, however, there is no way for us to check that the correct +/// one is being passed in. That is up to you, dear programmer. If you mess +/// up, you will get a incorrectly-sized hash digest in return, and it will be +/// your fault. Don't do that. +/// +/// # Returns +/// +/// A 256-bit hash digest, as a `[u8; 32]`. +/// +/// # C_RUST_COUPLED +/// +/// * `crypto_digest_get_digest` +/// * `DIGEST256_LEN` +// +// FIXME: Once const generics land in Rust, we should genericise calling +// crypto_digest_get_digest w.r.t. output array size. +pub fn get_256_bit_digest(digest: CryptoDigest) -> [u8; DIGEST256_LEN] { + let mut buffer: [u8; DIGEST256_LEN] = [0u8; DIGEST256_LEN]; + + unsafe { + crypto_digest_get_digest(digest.0, + buffer.as_mut_ptr() as *mut c_char, + DIGEST256_LEN as size_t); + + if buffer.as_ptr().is_null() { + abort(); + } + } + buffer +} + +/// Get the 512-bit digest output of a `crypto_digest_t`. +/// +/// # Inputs +/// +/// * `digest`: A `CryptoDigest` which wraps either a `DIGEST_SHA512` or a +/// `DIGEST_SHA3_512`. +/// +/// # Warning +/// +/// Calling this function with a `CryptoDigest` which is neither SHA2-512 or +/// SHA3-512 is a programming error. Since we cannot introspect the opaque +/// struct from Rust, however, there is no way for us to check that the correct +/// one is being passed in. That is up to you, dear programmer. If you mess +/// up, you will get a incorrectly-sized hash digest in return, and it will be +/// your fault. Don't do that. +/// +/// # Returns +/// +/// A 512-bit hash digest, as a `[u8; 64]`. +/// +/// # C_RUST_COUPLED +/// +/// * `crypto_digest_get_digest` +/// * `DIGEST512_LEN` +// +// FIXME: Once const generics land in Rust, we should genericise calling +// crypto_digest_get_digest w.r.t. output array size. +pub fn get_512_bit_digest(digest: CryptoDigest) -> [u8; DIGEST512_LEN] { + let mut buffer: [u8; DIGEST512_LEN] = [0u8; DIGEST512_LEN]; + + unsafe { + crypto_digest_get_digest(digest.0, + buffer.as_mut_ptr() as *mut c_char, + DIGEST512_LEN as size_t); + + if buffer.as_ptr().is_null() { + abort(); + } + } + buffer +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_layout_common_digests_t() { + assert_eq!(::std::mem::size_of::<common_digests_t>(), 64usize, + concat!("Size of: ", stringify!(common_digests_t))); + assert_eq!(::std::mem::align_of::<common_digests_t>(), 1usize, + concat!("Alignment of ", stringify!(common_digests_t))); + } + + #[test] + fn test_layout_crypto_digest_t() { + assert_eq!(::std::mem::size_of::<crypto_digest_t>(), 0usize, + concat!("Size of: ", stringify!(crypto_digest_t))); + assert_eq!(::std::mem::align_of::<crypto_digest_t>(), 1usize, + concat!("Alignment of ", stringify!(crypto_digest_t))); + } +} |