Cryptographic Secret type (#615)

* Adds a type for cryptographic secrets

* default implementations and zeroize documentation

* removes whitespace
This commit is contained in:
kaczmarczyck
2023-04-19 18:02:48 +02:00
committed by GitHub
parent 3091b5a29d
commit 5f7eb3177b
36 changed files with 582 additions and 254 deletions

View File

@@ -20,6 +20,7 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
serde_json = { version = "=1.0.69", optional = true }
regex = { version = "1", optional = true }
rand_core = "0.6.4"
zeroize = { version = "1.5.7", features = ["derive"] }
[features]
std = ["hex", "ring", "untrusted", "serde", "serde_json", "regex", "rand_core/getrandom"]

View File

@@ -12,18 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! A portable and naive textbook implementation of AES-256
use super::util::{xor_block_16, Block16};
use arrayref::{array_mut_ref, array_ref};
use zeroize::Zeroize;
/** A portable and naive textbook implementation of AES-256 **/
type Word = [u8; 4];
/** This structure caches the round keys, to avoid re-computing the key schedule for each block. **/
/// Encryption key for AES256.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Zeroize)]
pub struct EncryptionKey {
// This structure caches the round keys, to avoid re-computing the key schedule for each block.
enc_round_keys: [Block16; 15],
}
/// Decryption key for AES256.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Zeroize)]
pub struct DecryptionKey {
// This structure caches the round keys, to avoid re-computing the key schedule for each block.
dec_round_keys: [Block16; 15],
}

View File

@@ -16,9 +16,12 @@ use super::int256::{Digit, Int256};
use core::ops::Mul;
use rand_core::RngCore;
use subtle::{self, Choice, ConditionallySelectable, CtOption};
use zeroize::Zeroize;
// An exponent on the elliptic curve, that is an element modulo the curve order N.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// An exponent on the elliptic curve, that is an element modulo the curve order N.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Zeroize)]
// TODO: remove this Default once https://github.com/dalek-cryptography/subtle/issues/63 is
// resolved.
#[derive(Default)]
@@ -90,8 +93,10 @@ impl Mul for &ExponentP256 {
}
}
// A non-zero exponent on the elliptic curve.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
/// A non-zero exponent on the elliptic curve.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Zeroize)]
// TODO: remove this Default once https://github.com/dalek-cryptography/subtle/issues/63 is
// resolved.
#[derive(Default)]

View File

@@ -15,12 +15,16 @@
use super::int256::{Digit, Int256};
use core::ops::Mul;
use subtle::Choice;
use zeroize::Zeroize;
// A field element on the elliptic curve, that is an element modulo the prime P.
// This is the format used to serialize coordinates of points on the curve.
// This implements enough methods to validate points and to convert them to/from the Montgomery
// form, which is more convenient to operate on.
#[derive(Clone, Copy, PartialEq, Eq)]
/// A field element on the elliptic curve, that is an element modulo the prime P.
///
/// This is the format used to serialize coordinates of points on the curve.
/// This implements enough methods to validate points and to convert them to/from the Montgomery
/// form, which is more convenient to operate on.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, PartialEq, Eq, Zeroize)]
pub struct GFP256 {
int: Int256,
}

View File

@@ -19,6 +19,7 @@ use byteorder::{BigEndian, ByteOrder};
use core::ops::{Add, AddAssign, Sub, SubAssign};
use rand_core::RngCore;
use subtle::{self, Choice, ConditionallySelectable, ConstantTimeEq};
use zeroize::Zeroize;
const BITS_PER_DIGIT: usize = 32;
const BYTES_PER_DIGIT: usize = BITS_PER_DIGIT >> 3;
@@ -29,7 +30,10 @@ pub type Digit = u32;
type DoubleDigit = u64;
type SignedDoubleDigit = i64;
#[derive(Clone, Copy, PartialEq, Eq)]
/// Big integer implementation with 256 bits.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, PartialEq, Eq, Zeroize)]
// TODO: remove this Default once https://github.com/dalek-cryptography/subtle/issues/63 is
// resolved.
#[derive(Default)]

View File

@@ -17,13 +17,16 @@ use super::int256::Int256;
use super::precomputed;
use core::ops::{Add, Mul, Sub};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use zeroize::Zeroize;
pub const NLIMBS: usize = 9;
pub const BOTTOM_28_BITS: u32 = 0x0fff_ffff;
pub const BOTTOM_29_BITS: u32 = 0x1fff_ffff;
/** Field element on the secp256r1 curve, represented in Montgomery form **/
#[derive(Clone, Copy)]
/// Field element on the secp256r1 curve, represented in Montgomery form.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, Zeroize)]
pub struct Montgomery {
// The 9 limbs use 28 or 29 bits, alternatively: even limbs use 29 bits, odd limbs use 28 bits.
// The Montgomery form stores a field element x as (x * 2^257) mod P.

View File

@@ -21,11 +21,15 @@ use arrayref::array_mut_ref;
use arrayref::array_ref;
use core::ops::Add;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use zeroize::Zeroize;
// A point on the elliptic curve is represented by two field elements.
// The "direct" representation with GFP256 (integer modulo p) is used for serialization of public
// keys.
#[derive(Clone, Copy)]
/// A point on the elliptic curve, represented by two field elements.
///
/// The "direct" representation with GFP256 (integer modulo p) is used for serialization of public
/// keys.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, Zeroize)]
pub struct PointP256 {
x: GFP256,
y: GFP256,
@@ -128,12 +132,15 @@ impl PointP256 {
}
}
// A point on the elliptic curve in projective form.
// This uses Montgomery representation for field elements.
// This is in projective coordinates, i.e. it represents the point { x: x / z, y: y / z }.
// This representation is more convenient to implement complete formulas for elliptic curve
// arithmetic.
#[derive(Clone, Copy)]
/// A point on the elliptic curve in projective form.
///
/// This uses Montgomery representation for field elements.
/// This is in projective coordinates, i.e. it represents the point { x: x / z, y: y / z }.
/// This representation is more convenient to implement complete formulas for elliptic curve
/// arithmetic.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, Zeroize)]
pub struct PointProjective {
x: Montgomery,
y: Montgomery,
@@ -150,8 +157,10 @@ impl ConditionallySelectable for PointProjective {
}
}
// Equivalent to PointProjective { x, y, z: 1 }
#[derive(Clone, Copy)]
/// Equivalent to PointProjective { x, y, z: 1 }
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Copy, Zeroize)]
pub struct PointAffine {
x: Montgomery,
y: Montgomery,

View File

@@ -17,14 +17,22 @@ use super::ec::int256;
use super::ec::int256::Int256;
use super::ec::point::PointP256;
use rand_core::RngCore;
use zeroize::Zeroize;
pub const NBYTES: usize = int256::NBYTES;
/// A private key for ECDH.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Zeroize)]
pub struct SecKey {
a: NonZeroExponentP256,
}
#[derive(Clone, Debug, PartialEq)]
/// A public key for ECDH.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Debug, PartialEq, Zeroize)]
pub struct PubKey {
p: PointP256,
}

View File

@@ -16,7 +16,6 @@ use super::ec::exponent256::{ExponentP256, NonZeroExponentP256};
use super::ec::int256;
use super::ec::int256::Int256;
use super::ec::point::PointP256;
use super::hmac::hmac_256;
use super::Hash256;
use alloc::vec;
use alloc::vec::Vec;
@@ -25,20 +24,31 @@ use arrayref::array_mut_ref;
use arrayref::{array_ref, mut_array_refs};
use core::marker::PhantomData;
use rand_core::RngCore;
use zeroize::Zeroize;
pub const NBYTES: usize = int256::NBYTES;
#[derive(Clone, Debug, PartialEq, Eq)]
/// A private key for ECDSA.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Debug, PartialEq, Eq, Zeroize)]
pub struct SecKey {
k: NonZeroExponentP256,
}
/// An ECDSA signature.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Zeroize)]
pub struct Signature {
r: NonZeroExponentP256,
s: NonZeroExponentP256,
}
#[derive(Clone)]
/// A public key for ECDSA.
///
/// Never call zeroize explicitly, to not invalidate any invariants.
#[derive(Clone, Zeroize)]
pub struct PubKey {
p: PointP256,
}
@@ -310,15 +320,15 @@ where
Int256::to_bin(&sk.k.to_int(), contents_k);
Int256::to_bin(&Int256::from_bin(&h1).modd(&Int256::N), contents_h1);
let k = hmac_256::<H>(&k, &contents);
let v = hmac_256::<H>(&k, &v);
let k = H::hmac(&k, &contents);
let v = H::hmac(&k, &v);
let (contents_v, marker, _) = mut_array_refs![&mut contents, 32, 1, 64];
contents_v.copy_from_slice(&v);
marker[0] = 0x01;
let k = hmac_256::<H>(&k, &contents);
let v = hmac_256::<H>(&k, &v);
let k = H::hmac(&k, &contents);
let v = H::hmac(&k, &v);
Rfc6979 {
k,
@@ -330,14 +340,14 @@ where
fn next(&mut self) -> Int256 {
// Note: at this step, the logic from RFC 6979 is simplified, because the HMAC produces 256
// bits and we need 256 bits.
let t = hmac_256::<H>(&self.k, &self.v);
let t = H::hmac(&self.k, &self.v);
let result = Int256::from_bin(&t);
let mut v1 = [0; 33];
v1[..32].copy_from_slice(&self.v);
v1[32] = 0x00;
self.k = hmac_256::<H>(&self.k, &v1);
self.v = hmac_256::<H>(&self.k, &self.v);
self.k = H::hmac(&self.k, &v1);
self.v = H::hmac(&self.k, &self.v);
result
}

View File

@@ -27,15 +27,15 @@ const HASH_SIZE: usize = 32;
///
/// This implementation is equivalent to a standard HKD, with `salt` fixed at a length of
/// 32 byte and the output length l as 32.
pub fn hkdf_256<H>(ikm: &[u8], salt: &[u8; HASH_SIZE], info: &[u8]) -> [u8; HASH_SIZE]
pub fn hkdf_256<H>(ikm: &[u8], salt: &[u8; HASH_SIZE], info: &[u8], okm: &mut [u8; HASH_SIZE])
where
H: Hash256,
{
let prk = hmac_256::<H>(salt, ikm);
let prk = H::hmac(salt, ikm);
// l is implicitly the block size, so we iterate exactly once.
let mut t = info.to_vec();
t.push(1);
hmac_256::<H>(&prk, t.as_slice())
hmac_256::<H>(&prk, t.as_slice(), okm);
}
/// Computes the HKDF with empty salt and 256 bit (one block) output.
@@ -47,12 +47,12 @@ where
///
/// This implementation is equivalent to the below hkdf, with `salt` set to the
/// default block of zeros and the output length l as 32.
pub fn hkdf_empty_salt_256<H>(ikm: &[u8], info: &[u8]) -> [u8; HASH_SIZE]
pub fn hkdf_empty_salt_256<H>(ikm: &[u8], info: &[u8], okm: &mut [u8; HASH_SIZE])
where
H: Hash256,
{
// Salt is a zero block here.
hkdf_256::<H>(ikm, &[0; HASH_SIZE], info)
hkdf_256::<H>(ikm, &[0; HASH_SIZE], info, okm);
}
#[cfg(test)]
@@ -81,10 +81,9 @@ mod test {
// Byte i.
let info = [i as u8];
let okm = hex::decode(okm).unwrap();
assert_eq!(
&hkdf_empty_salt_256::<Sha256>(&ikm.as_bytes(), &info[..]),
array_ref!(okm, 0, 32)
);
let mut output = [0; HASH_SIZE];
hkdf_empty_salt_256::<Sha256>(&ikm.as_bytes(), &info[..], &mut output);
assert_eq!(&output, array_ref!(okm, 0, HASH_SIZE));
}
}
@@ -109,10 +108,9 @@ mod test {
// Byte i.
let info = [i as u8];
let okm = hex::decode(okm).unwrap();
assert_eq!(
&hkdf_256::<Sha256>(&ikm.as_bytes(), &salt, &info[..]),
array_ref!(okm, 0, 32)
);
let mut output = [0; HASH_SIZE];
hkdf_256::<Sha256>(&ikm.as_bytes(), &salt, &info[..], &mut output);
assert_eq!(&output, array_ref!(okm, 0, HASH_SIZE));
}
}
}

View File

@@ -24,7 +24,8 @@ pub fn verify_hmac_256<H>(key: &[u8; KEY_SIZE], contents: &[u8], mac: &[u8; HASH
where
H: Hash256,
{
let expected_mac = hmac_256::<H>(key, contents);
let mut expected_mac = [0; HASH_SIZE];
hmac_256::<H>(key, contents, &mut expected_mac);
bool::from(expected_mac.ct_eq(mac))
}
@@ -38,19 +39,23 @@ pub fn verify_hmac_256_first_128bits<H>(
where
H: Hash256,
{
let expected_mac = hmac_256::<H>(key, contents);
let mut expected_mac = [0; HASH_SIZE];
hmac_256::<H>(key, contents, &mut expected_mac);
bool::from(array_ref![expected_mac, 0, 16].ct_eq(pin))
}
pub fn hmac_256<H>(key: &[u8; KEY_SIZE], contents: &[u8]) -> [u8; HASH_SIZE]
pub fn hmac_256<H>(key: &[u8; KEY_SIZE], contents: &[u8], output: &mut [u8; HASH_SIZE])
where
H: Hash256,
{
H::hmac(key, contents)
H::hmac_mut(key, contents, output)
}
pub(crate) fn software_hmac_256<H>(key: &[u8; KEY_SIZE], contents: &[u8]) -> [u8; HASH_SIZE]
where
pub(crate) fn software_hmac_256<H>(
key: &[u8; KEY_SIZE],
contents: &[u8],
output: &mut [u8; HASH_SIZE],
) where
H: Hash256,
{
let mut ipad: [u8; BLOCK_SIZE] = [0x36; BLOCK_SIZE];
@@ -64,13 +69,13 @@ where
let mut ihasher = H::new();
ihasher.update(&ipad);
ihasher.update(contents);
let ihash = ihasher.finalize();
let mut ihash = [0; HASH_SIZE];
ihasher.finalize(&mut ihash);
let mut ohasher = H::new();
ohasher.update(&opad);
ohasher.update(&ihash);
ohasher.finalize()
ohasher.finalize(output);
}
fn xor_pads(ipad: &mut [u8; BLOCK_SIZE], opad: &mut [u8; BLOCK_SIZE], key: &[u8; KEY_SIZE]) {
@@ -91,7 +96,8 @@ mod test {
for len in 0..128 {
let key = [0; KEY_SIZE];
let contents = vec![0; len];
let mac = hmac_256::<Sha256>(&key, &contents);
let mut mac = [0; HASH_SIZE];
hmac_256::<Sha256>(&key, &contents, &mut mac);
assert!(verify_hmac_256::<Sha256>(&key, &contents, &mac));
}
}
@@ -102,7 +108,8 @@ mod test {
for len in 0..128 {
let key = [0; KEY_SIZE];
let contents = vec![0; len];
let mac = hmac_256::<Sha256>(&key, &contents);
let mut mac = [0; HASH_SIZE];
hmac_256::<Sha256>(&key, &contents, &mut mac);
// Check that invalid MACs don't verify, by changing any byte of the valid MAC.
for i in 0..HASH_SIZE {
@@ -116,14 +123,21 @@ mod test {
#[test]
fn test_hmac_sha256_examples() {
let key = [0; KEY_SIZE];
let mut mac = [0; HASH_SIZE];
hmac_256::<Sha256>(&key, &[], &mut mac);
assert_eq!(
hmac_256::<Sha256>(&key, &[]),
mac,
hex::decode("b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad")
.unwrap()
.as_slice()
);
hmac_256::<Sha256>(
&key,
b"The quick brown fox jumps over the lazy dog",
&mut mac,
);
assert_eq!(
hmac_256::<Sha256>(&key, b"The quick brown fox jumps over the lazy dog"),
mac,
hex::decode("fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dced19a416")
.unwrap()
.as_slice()
@@ -274,11 +288,10 @@ mod test {
let mut input = Vec::new();
let key = [b'A'; KEY_SIZE];
let mut mac = [0; HASH_SIZE];
for i in 0..128 {
assert_eq!(
hmac_256::<Sha256>(&key, &input),
hex::decode(hashes[i] as &[u8]).unwrap().as_slice()
);
hmac_256::<Sha256>(&key, &input, &mut mac);
assert_eq!(mac, hex::decode(hashes[i] as &[u8]).unwrap().as_slice());
input.push(b'A');
}
}

View File

@@ -27,26 +27,39 @@ pub mod hmac;
pub mod sha256;
pub mod util;
// Trait for hash functions that returns a 256-bit hash.
// The type must be Sized (size known at compile time) so that we can instanciate one on the stack
// in the hash() method.
/// Trait for hash functions that returns a 256-bit hash.
///
/// When you implement this trait, make sure to implement `hash_mut` and `hmac_mut` first, because
/// the default implementations of `hash` and `hmac` rely on it.
pub trait Hash256: Sized {
fn new() -> Self;
fn update(&mut self, contents: &[u8]);
fn finalize(self) -> [u8; 32];
fn finalize(self, output: &mut [u8; 32]);
fn hash(contents: &[u8]) -> [u8; 32] {
let mut output = [0; 32];
Self::hash_mut(contents, &mut output);
output
}
fn hash_mut(contents: &[u8], output: &mut [u8; 32]) {
let mut h = Self::new();
h.update(contents);
h.finalize()
h.finalize(output)
}
fn hmac(key: &[u8; 32], contents: &[u8]) -> [u8; 32] {
hmac::software_hmac_256::<Self>(key, contents)
let mut output = [0; 32];
Self::hmac_mut(key, contents, &mut output);
output
}
fn hmac_mut(key: &[u8; 32], contents: &[u8], output: &mut [u8; 32]) {
hmac::software_hmac_256::<Self>(key, contents, output);
}
}
// Trait for hash functions that operate on 64-byte input blocks.
/// Trait for hash functions that operate on 64-byte input blocks.
pub trait HashBlockSize64Bytes {
type State;

View File

@@ -17,6 +17,7 @@ use arrayref::{array_mut_ref, array_ref};
use byteorder::{BigEndian, ByteOrder};
use core::cell::Cell;
use core::num::Wrapping;
use zeroize::Zeroize;
const BLOCK_SIZE: usize = 64;
@@ -32,6 +33,17 @@ pub struct Sha256 {
total_len: usize,
}
impl Drop for Sha256 {
// TODO derive Zeroize instead when we upgrade the toolchain
fn drop(&mut self) {
for s in self.state.iter_mut() {
s.0.zeroize();
}
self.block.zeroize();
self.total_len.zeroize();
}
}
impl Hash256 for Sha256 {
fn new() -> Self {
assert!(!BUSY.replace(true));
@@ -72,7 +84,7 @@ impl Hash256 for Sha256 {
}
}
fn finalize(mut self) -> [u8; 32] {
fn finalize(mut self, output: &mut [u8; 32]) {
// Last block and padding.
let cursor_in_block = self.total_len % BLOCK_SIZE;
self.block[cursor_in_block] = 0x80;
@@ -97,12 +109,10 @@ impl Hash256 for Sha256 {
Sha256::hash_block(&mut self.state, &self.block);
// Encode the state's 32-bit words into bytes, using big-endian.
let mut result: [u8; 32] = [0; 32];
for i in 0..8 {
BigEndian::write_u32(array_mut_ref![result, 4 * i, 4], self.state[i].0);
BigEndian::write_u32(array_mut_ref![output, 4 * i, 4], self.state[i].0);
}
BUSY.set(false);
result
}
}
@@ -272,7 +282,9 @@ mod test {
h.update(&input[..i]);
h.update(&input[i..j]);
h.update(&input[j..]);
assert_eq!(h.finalize(), hash.as_slice());
let mut digest = [0; 32];
h.finalize(&mut digest);
assert_eq!(digest, hash.as_slice());
}
}
}