mirror of
https://github.com/0glabs/0g-storage-node.git
synced 2024-12-27 08:45:19 +00:00
568 lines
17 KiB
Rust
568 lines
17 KiB
Rust
//! Improvement version of [`ring::digest::Context`].
|
|
//!
|
|
//! [`Context.finish`] now has `(&mut self)` instead of `(mut self)`.
|
|
//! [`Context`] acquired state `reset` thing.
|
|
|
|
#![cfg(feature = "crypto_bench")]
|
|
#![allow(dead_code)]
|
|
|
|
extern crate rand;
|
|
extern crate ring;
|
|
extern crate test;
|
|
|
|
mod init;
|
|
|
|
use std::fmt;
|
|
|
|
// XXX: Replace with `const fn` when `const fn` is stable:
|
|
// https://github.com/rust-lang/rust/issues/24111
|
|
#[cfg(target_endian = "little")]
|
|
macro_rules! u32x2 {
|
|
( $first:expr, $second:expr ) => {
|
|
((($second as u64) << 32) | ($first as u64))
|
|
};
|
|
}
|
|
|
|
/// A context for multi-step (Init-Update-Finish) digest calculations.
|
|
///
|
|
/// C analog: `EVP_MD_CTX`.
|
|
pub struct Context {
|
|
state: State,
|
|
|
|
// Note that SHA-512 has a 128-bit input bit counter, but this
|
|
// implementation only supports up to 2^64-1 input bits for all algorithms,
|
|
// so a 64-bit counter is more than sufficient.
|
|
completed_data_blocks: u64,
|
|
|
|
// TODO: More explicitly force 64-bit alignment for |pending|.
|
|
pending: [u8; MAX_BLOCK_LEN],
|
|
num_pending: usize,
|
|
|
|
/// The context's algorithm.
|
|
pub algorithm: &'static Algorithm,
|
|
}
|
|
|
|
impl Context {
|
|
/// Constructs a new context.
|
|
///
|
|
/// C analogs: `EVP_DigestInit`, `EVP_DigestInit_ex`
|
|
pub fn new(algorithm: &'static Algorithm) -> Context {
|
|
init::init_once();
|
|
|
|
Context {
|
|
algorithm,
|
|
state: algorithm.initial_state,
|
|
completed_data_blocks: 0,
|
|
pending: [0u8; MAX_BLOCK_LEN],
|
|
num_pending: 0,
|
|
}
|
|
}
|
|
|
|
/// Updates the digest with all the data in `data`. `update` may be called
|
|
/// zero or more times until `finish` is called. It must not be called
|
|
/// after `finish` has been called.
|
|
///
|
|
/// C analog: `EVP_DigestUpdate`
|
|
pub fn update(&mut self, data: &[u8]) {
|
|
if data.len() < self.algorithm.block_len - self.num_pending {
|
|
self.pending[self.num_pending..(self.num_pending + data.len())].copy_from_slice(data);
|
|
self.num_pending += data.len();
|
|
return;
|
|
}
|
|
|
|
let mut remaining = data;
|
|
if self.num_pending > 0 {
|
|
let to_copy = self.algorithm.block_len - self.num_pending;
|
|
self.pending[self.num_pending..self.algorithm.block_len]
|
|
.copy_from_slice(&data[..to_copy]);
|
|
|
|
unsafe {
|
|
(self.algorithm.block_data_order)(&mut self.state, self.pending.as_ptr(), 1);
|
|
}
|
|
self.completed_data_blocks = self.completed_data_blocks.checked_add(1).unwrap();
|
|
|
|
remaining = &remaining[to_copy..];
|
|
self.num_pending = 0;
|
|
}
|
|
|
|
let num_blocks = remaining.len() / self.algorithm.block_len;
|
|
let num_to_save_for_later = remaining.len() % self.algorithm.block_len;
|
|
if num_blocks > 0 {
|
|
unsafe {
|
|
(self.algorithm.block_data_order)(&mut self.state, remaining.as_ptr(), num_blocks);
|
|
}
|
|
self.completed_data_blocks = self
|
|
.completed_data_blocks
|
|
.checked_add(polyfill::slice::u64_from_usize(num_blocks))
|
|
.unwrap();
|
|
}
|
|
if num_to_save_for_later > 0 {
|
|
self.pending[..num_to_save_for_later]
|
|
.copy_from_slice(&remaining[(remaining.len() - num_to_save_for_later)..]);
|
|
self.num_pending = num_to_save_for_later;
|
|
}
|
|
}
|
|
|
|
/// Finalizes the digest calculation and returns the digest value. `finish`
|
|
/// consumes the context so it cannot be (mis-)used after `finish` has been
|
|
/// called.
|
|
///
|
|
/// C analogs: `EVP_DigestFinal`, `EVP_DigestFinal_ex`
|
|
pub fn finish(&mut self) -> Digest {
|
|
// We know |num_pending < self.algorithm.block_len|, because we would
|
|
// have processed the block otherwise.
|
|
|
|
let mut padding_pos = self.num_pending;
|
|
self.pending[padding_pos] = 0x80;
|
|
padding_pos += 1;
|
|
|
|
if padding_pos > self.algorithm.block_len - self.algorithm.len_len {
|
|
polyfill::slice::fill(&mut self.pending[padding_pos..self.algorithm.block_len], 0);
|
|
unsafe {
|
|
(self.algorithm.block_data_order)(&mut self.state, self.pending.as_ptr(), 1);
|
|
}
|
|
// We don't increase |self.completed_data_blocks| because the
|
|
// padding isn't data, and so it isn't included in the data length.
|
|
padding_pos = 0;
|
|
}
|
|
|
|
polyfill::slice::fill(
|
|
&mut self.pending[padding_pos..(self.algorithm.block_len - 8)],
|
|
0,
|
|
);
|
|
|
|
// Output the length, in bits, in big endian order.
|
|
let mut completed_data_bits: u64 = self
|
|
.completed_data_blocks
|
|
.checked_mul(polyfill::slice::u64_from_usize(self.algorithm.block_len))
|
|
.unwrap()
|
|
.checked_add(polyfill::slice::u64_from_usize(self.num_pending))
|
|
.unwrap()
|
|
.checked_mul(8)
|
|
.unwrap();
|
|
|
|
for b in (&mut self.pending[(self.algorithm.block_len - 8)..self.algorithm.block_len])
|
|
.into_iter()
|
|
.rev()
|
|
{
|
|
*b = completed_data_bits as u8;
|
|
completed_data_bits /= 0x100;
|
|
}
|
|
unsafe {
|
|
(self.algorithm.block_data_order)(&mut self.state, self.pending.as_ptr(), 1);
|
|
}
|
|
|
|
Digest {
|
|
algorithm: self.algorithm,
|
|
value: (self.algorithm.format_output)(&self.state),
|
|
}
|
|
}
|
|
|
|
/// The algorithm that this context is using.
|
|
#[inline(always)]
|
|
pub fn algorithm(&self) -> &'static Algorithm {
|
|
self.algorithm
|
|
}
|
|
|
|
/// Reset context state.
|
|
pub fn reset(&mut self) {
|
|
self.state = self.algorithm.initial_state;
|
|
self.pending = [0u8; MAX_BLOCK_LEN];
|
|
self.completed_data_blocks = 0;
|
|
self.num_pending = 0;
|
|
}
|
|
}
|
|
|
|
// XXX: This should just be `#[derive(Clone)]` but that doesn't work because
|
|
// `[u8; 128]` doesn't implement `Clone`.
|
|
impl Clone for Context {
|
|
fn clone(&self) -> Context {
|
|
Context {
|
|
state: self.state,
|
|
pending: self.pending,
|
|
completed_data_blocks: self.completed_data_blocks,
|
|
num_pending: self.num_pending,
|
|
algorithm: self.algorithm,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the digest of `data` using the given digest algorithm.
|
|
///
|
|
/// C analog: `EVP_Digest`
|
|
///
|
|
/// # Examples:
|
|
///
|
|
/// ```
|
|
/// # #[cfg(feature = "use_heap")]
|
|
/// # fn main() {
|
|
/// use ring::{digest, test};
|
|
///
|
|
/// let expected_hex =
|
|
/// "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b";
|
|
/// let expected: Vec<u8> = test::from_hex(expected_hex).unwrap();
|
|
/// let actual = digest::digest(&digest::SHA256, b"hello, world");
|
|
///
|
|
/// assert_eq!(&expected, &actual.as_ref());
|
|
/// # }
|
|
///
|
|
/// # #[cfg(not(feature = "use_heap"))]
|
|
/// # fn main() { }
|
|
/// ```
|
|
pub fn digest(algorithm: &'static Algorithm, data: &[u8]) -> Digest {
|
|
let mut ctx = Context::new(algorithm);
|
|
ctx.update(data);
|
|
ctx.finish()
|
|
}
|
|
|
|
/// A calculated digest value.
|
|
///
|
|
/// Use `as_ref` to get the value as a `&[u8]`.
|
|
#[derive(Clone, Copy)]
|
|
pub struct Digest {
|
|
value: Output,
|
|
algorithm: &'static Algorithm,
|
|
}
|
|
|
|
impl Digest {
|
|
/// The algorithm that was used to calculate the digest value.
|
|
#[inline(always)]
|
|
pub fn algorithm(&self) -> &'static Algorithm {
|
|
self.algorithm
|
|
}
|
|
}
|
|
|
|
impl AsRef<[u8]> for Digest {
|
|
#[inline(always)]
|
|
fn as_ref(&self) -> &[u8] {
|
|
&(polyfill::slice::u64_as_u8(&self.value))[..self.algorithm.output_len]
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Digest {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(fmt, "{:?}:", self.algorithm)?;
|
|
for byte in self.as_ref() {
|
|
write!(fmt, "{:02x}", byte)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A digest algorithm.
|
|
///
|
|
/// C analog: `EVP_MD`
|
|
pub struct Algorithm {
|
|
/// C analog: `EVP_MD_size`
|
|
pub output_len: usize,
|
|
|
|
/// The size of the chaining value of the digest function, in bytes. For
|
|
/// non-truncated algorithms (SHA-1, SHA-256, SHA-512), this is equal to
|
|
/// `output_len`. For truncated algorithms (e.g. SHA-384, SHA-512/256),
|
|
/// this is equal to the length before truncation. This is mostly helpful
|
|
/// for determining the size of an HMAC key that is appropriate for the
|
|
/// digest algorithm.
|
|
pub chaining_len: usize,
|
|
|
|
/// C analog: `EVP_MD_block_size`
|
|
pub block_len: usize,
|
|
|
|
/// The length of the length in the padding.
|
|
pub len_len: usize,
|
|
|
|
pub block_data_order: unsafe extern "C" fn(state: &mut State, data: *const u8, num: usize),
|
|
pub format_output: fn(input: &State) -> Output,
|
|
|
|
pub initial_state: State,
|
|
|
|
pub id: AlgorithmID,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
#[allow(non_camel_case_types)]
|
|
pub enum AlgorithmID {
|
|
SHA256,
|
|
SHA512,
|
|
SHA512_256,
|
|
}
|
|
|
|
impl PartialEq for Algorithm {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.id == other.id
|
|
}
|
|
}
|
|
|
|
impl Eq for Algorithm {}
|
|
|
|
impl fmt::Debug for Algorithm {
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
// This would have to change if/when we add other algorithms with the
|
|
// same lengths.
|
|
let (n, suffix) =
|
|
if self.output_len == SHA512_256_OUTPUT_LEN && self.block_len == SHA512_BLOCK_LEN {
|
|
(512, "_256")
|
|
} else if self.output_len == 20 {
|
|
(1, "")
|
|
} else {
|
|
(self.output_len * 8, "")
|
|
};
|
|
write!(fmt, "SHA{}{}", n, suffix)
|
|
}
|
|
}
|
|
|
|
/// SHA-256 as specified in [FIPS 180-4].
|
|
///
|
|
/// [FIPS 180-4]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
|
|
pub static SHA256: Algorithm = Algorithm {
|
|
output_len: SHA256_OUTPUT_LEN,
|
|
chaining_len: SHA256_OUTPUT_LEN,
|
|
block_len: 512 / 8,
|
|
len_len: 64 / 8,
|
|
block_data_order: GFp_sha256_block_data_order,
|
|
format_output: sha256_format_output,
|
|
initial_state: [
|
|
u32x2!(0x6a09e667u32, 0xbb67ae85u32),
|
|
u32x2!(0x3c6ef372u32, 0xa54ff53au32),
|
|
u32x2!(0x510e527fu32, 0x9b05688cu32),
|
|
u32x2!(0x1f83d9abu32, 0x5be0cd19u32),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
],
|
|
id: AlgorithmID::SHA256,
|
|
};
|
|
|
|
/// SHA-512 as specified in [FIPS 180-4].
|
|
///
|
|
/// [FIPS 180-4]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
|
|
pub static SHA512: Algorithm = Algorithm {
|
|
output_len: SHA512_OUTPUT_LEN,
|
|
chaining_len: SHA512_OUTPUT_LEN,
|
|
block_len: SHA512_BLOCK_LEN,
|
|
len_len: SHA512_LEN_LEN,
|
|
block_data_order: GFp_sha512_block_data_order,
|
|
format_output: sha512_format_output,
|
|
initial_state: [
|
|
0x6a09e667f3bcc908,
|
|
0xbb67ae8584caa73b,
|
|
0x3c6ef372fe94f82b,
|
|
0xa54ff53a5f1d36f1,
|
|
0x510e527fade682d1,
|
|
0x9b05688c2b3e6c1f,
|
|
0x1f83d9abfb41bd6b,
|
|
0x5be0cd19137e2179,
|
|
],
|
|
id: AlgorithmID::SHA512,
|
|
};
|
|
|
|
/// SHA-512/256 as specified in [FIPS 180-4].
|
|
///
|
|
/// This is *not* the same as just truncating the output of SHA-512, as
|
|
/// SHA-512/256 has its own initial state distinct from SHA-512's initial
|
|
/// state.
|
|
///
|
|
/// [FIPS 180-4]: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
|
|
pub static SHA512_256: Algorithm = Algorithm {
|
|
output_len: SHA512_256_OUTPUT_LEN,
|
|
chaining_len: SHA512_OUTPUT_LEN,
|
|
block_len: SHA512_BLOCK_LEN,
|
|
len_len: SHA512_LEN_LEN,
|
|
block_data_order: GFp_sha512_block_data_order,
|
|
format_output: sha512_format_output,
|
|
initial_state: [
|
|
0x22312194fc2bf72c,
|
|
0x9f555fa3c84c64c2,
|
|
0x2393b86b6f53b151,
|
|
0x963877195940eabd,
|
|
0x96283ee2a88effe3,
|
|
0xbe5e1e2553863992,
|
|
0x2b0199fc2c85b8aa,
|
|
0x0eb72ddc81c52ca2,
|
|
],
|
|
id: AlgorithmID::SHA512_256,
|
|
};
|
|
|
|
// We use u64 to try to ensure 64-bit alignment/padding.
|
|
pub type State = [u64; MAX_CHAINING_LEN / 8];
|
|
|
|
pub type Output = [u64; MAX_OUTPUT_LEN / 8];
|
|
|
|
/// The maximum block length (`Algorithm::block_len`) of all the algorithms in
|
|
/// this module.
|
|
pub const MAX_BLOCK_LEN: usize = 1024 / 8;
|
|
|
|
/// The maximum output length (`Algorithm::output_len`) of all the algorithms
|
|
/// in this module.
|
|
pub const MAX_OUTPUT_LEN: usize = 512 / 8;
|
|
|
|
/// The maximum chaining length (`Algorithm::chaining_len`) of all the
|
|
/// algorithms in this module.
|
|
pub const MAX_CHAINING_LEN: usize = MAX_OUTPUT_LEN;
|
|
|
|
mod polyfill {
|
|
pub mod slice {
|
|
use std::slice::from_raw_parts;
|
|
|
|
// https://internals.rust-lang.org/t/
|
|
// safe-trasnsmute-for-slices-e-g-u64-u32-particularly-simd-types/2871
|
|
#[inline(always)]
|
|
pub fn u64_as_u32(src: &[u64]) -> &[u32] {
|
|
unsafe { from_raw_parts(src.as_ptr() as *const u32, src.len() * 2) }
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn u64_from_usize(x: usize) -> u64 {
|
|
x as u64
|
|
}
|
|
|
|
// https://internals.rust-lang.org/t/
|
|
// stabilizing-basic-functions-on-arrays-and-slices/2868
|
|
#[inline(always)]
|
|
pub fn fill(dest: &mut [u8], value: u8) {
|
|
for d in dest {
|
|
*d = value;
|
|
}
|
|
}
|
|
|
|
// https://internals.rust-lang.org/t/
|
|
// safe-trasnsmute-for-slices-e-g-u64-u32-particularly-simd-types/2871
|
|
#[inline(always)]
|
|
pub fn u64_as_u8(src: &[u64]) -> &[u8] {
|
|
unsafe { from_raw_parts(src.as_ptr() as *const u8, src.len() * 8) }
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn sha256_format_output(input: &State) -> Output {
|
|
let input = &polyfill::slice::u64_as_u32(input)[..8];
|
|
[
|
|
u32x2!(input[0].to_be(), input[1].to_be()),
|
|
u32x2!(input[2].to_be(), input[3].to_be()),
|
|
u32x2!(input[4].to_be(), input[5].to_be()),
|
|
u32x2!(input[6].to_be(), input[7].to_be()),
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
]
|
|
}
|
|
|
|
pub fn sha512_format_output(input: &State) -> Output {
|
|
[
|
|
input[0].to_be(),
|
|
input[1].to_be(),
|
|
input[2].to_be(),
|
|
input[3].to_be(),
|
|
input[4].to_be(),
|
|
input[5].to_be(),
|
|
input[6].to_be(),
|
|
input[7].to_be(),
|
|
]
|
|
}
|
|
|
|
/// The length of the output of SHA-256, in bytes.
|
|
pub const SHA256_OUTPUT_LEN: usize = 256 / 8;
|
|
|
|
/// The length of the output of SHA-512, in bytes.
|
|
pub const SHA512_OUTPUT_LEN: usize = 512 / 8;
|
|
|
|
/// The length of the output of SHA-512/256, in bytes.
|
|
pub const SHA512_256_OUTPUT_LEN: usize = 256 / 8;
|
|
|
|
/// The length of a block for SHA-512-based algorithms, in bytes.
|
|
const SHA512_BLOCK_LEN: usize = 1024 / 8;
|
|
|
|
/// The length of the length field for SHA-512-based algorithms, in bytes.
|
|
const SHA512_LEN_LEN: usize = 128 / 8;
|
|
|
|
extern "C" {
|
|
fn GFp_sha256_block_data_order(state: &mut State, data: *const u8, num: usize);
|
|
fn GFp_sha512_block_data_order(state: &mut State, data: *const u8, num: usize);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test_util {
|
|
use super::*;
|
|
|
|
pub static ALL_ALGORITHMS: [&'static Algorithm; 3] = [&SHA256, &SHA512, &SHA512_256];
|
|
}
|
|
|
|
/*
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
mod max_input {
|
|
use super::super::super::digest;
|
|
|
|
macro_rules! max_input_tests {
|
|
( $algorithm_name:ident ) => {
|
|
#[allow(non_snake_case)]
|
|
mod $algorithm_name {
|
|
use super::super::super::super::digest;
|
|
|
|
#[test]
|
|
fn max_input_test() {
|
|
super::max_input_test(&digest::$algorithm_name);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn too_long_input_test_block() {
|
|
super::too_long_input_test_block(
|
|
&digest::$algorithm_name);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn too_long_input_test_byte() {
|
|
super::too_long_input_test_byte(
|
|
&digest::$algorithm_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn max_input_test(alg: &'static digest::Algorithm) {
|
|
let mut context = nearly_full_context(alg);
|
|
let next_input = vec![0u8; alg.block_len - 1];
|
|
context.update(&next_input);
|
|
let _ = context.finish(); // no panic
|
|
}
|
|
|
|
fn too_long_input_test_block(alg: &'static digest::Algorithm) {
|
|
let mut context = nearly_full_context(alg);
|
|
let next_input = vec![0u8; alg.block_len];
|
|
context.update(&next_input);
|
|
let _ = context.finish(); // should panic
|
|
}
|
|
|
|
fn too_long_input_test_byte(alg: &'static digest::Algorithm) {
|
|
let mut context = nearly_full_context(alg);
|
|
let next_input = vec![0u8; alg.block_len - 1];
|
|
context.update(&next_input); // no panic
|
|
context.update(&[0]);
|
|
let _ = context.finish(); // should panic
|
|
}
|
|
|
|
fn nearly_full_context(alg: &'static digest::Algorithm)
|
|
-> digest::Context {
|
|
// All implementations currently support up to 2^64-1 bits
|
|
// of input; according to the spec, SHA-384 and SHA-512
|
|
// support up to 2^128-1, but that's not implemented yet.
|
|
let max_bytes = 1u64 << (64 - 3);
|
|
let max_blocks = max_bytes / (alg.block_len as u64);
|
|
digest::Context {
|
|
algorithm: alg,
|
|
state: alg.initial_state,
|
|
completed_data_blocks: max_blocks - 1,
|
|
pending: [0u8; digest::MAX_BLOCK_LEN],
|
|
num_pending: 0,
|
|
}
|
|
}
|
|
|
|
max_input_tests!(SHA256);
|
|
max_input_tests!(SHA512);
|
|
}
|
|
}*/
|