Skip to content

Commit

Permalink
Best effort zeroisation of secret data
Browse files Browse the repository at this point in the history
- ecdsa: intermediate private key encoding
- aarch64::aes: round keys
- aarch64::ghash: hash key powers
- posint: newtype for sensitive ones: use in rsa_priv.
  erase modexp powers.
- x86_64::aes: round keys
- x86_64::ghash: hash key powers
- p256: scalar & shared secret
- p384: scalar & shared secret
- x25519: private key & shared secret
  • Loading branch information
ctz committed Oct 6, 2024
1 parent 2105419 commit 9807b69
Show file tree
Hide file tree
Showing 19 changed files with 392 additions and 19 deletions.
4 changes: 4 additions & 0 deletions graviola/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ serde_json = "1"
[[test]]
name = "wycheproof"
required-features = ["__internal_08eaf2eb"]

[[test]]
name = "zeroing"
required-features = ["__internal_08eaf2eb"]
7 changes: 4 additions & 3 deletions graviola/src/high/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use super::hash::{Hash, HashContext};
use super::hmac_drbg::HmacDrbg;
use super::pkcs8;
use crate::error::{Error, KeyFormatError};
use crate::low::Entry;
use crate::low::{zeroise, Entry};
use crate::mid::rng::{RandomSource, SystemRandom};

pub struct SigningKey<C: Curve> {
Expand Down Expand Up @@ -121,8 +121,8 @@ impl<C: Curve> SigningKey<C> {
}
let hash = ctx.finish();

let mut encoded_private_key = [0u8; MAX_SCALAR_LEN];
let encoded_private_key = self.private_key.encode(&mut encoded_private_key)?;
let mut encoded_private_key_buf = [0u8; MAX_SCALAR_LEN];
let encoded_private_key = self.private_key.encode(&mut encoded_private_key_buf)?;

let e = hash_to_scalar::<C>(hash.as_ref())?;
let mut e_bytes = [0u8; MAX_SCALAR_LEN];
Expand All @@ -132,6 +132,7 @@ impl<C: Curve> SigningKey<C> {
&e_bytes[..C::Scalar::LEN_BYTES],
random,
);
zeroise(&mut encoded_private_key_buf);

let (k, r) = loop {
let k = C::generate_random_key(&mut rng)?;
Expand Down
10 changes: 5 additions & 5 deletions graviola/src/high/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ impl RsaPrivateSigningKey {
.map_err(Error::Asn1Error)?;
let e = e.try_into().map_err(|_| Error::OutOfRange)?;

let p = PosInt::from_bytes(decoded.prime1.as_ref())?;
let q = PosInt::from_bytes(decoded.prime2.as_ref())?;
let dp = PosInt::from_bytes(decoded.exponent1.as_ref())?;
let dq = PosInt::from_bytes(decoded.exponent2.as_ref())?;
let iqmp = PosInt::from_bytes(decoded.coefficient.as_ref())?;
let p = PosInt::from_bytes(decoded.prime1.as_ref())?.into();
let q = PosInt::from_bytes(decoded.prime2.as_ref())?.into();
let dp = PosInt::from_bytes(decoded.exponent1.as_ref())?.into();
let dq = PosInt::from_bytes(decoded.exponent2.as_ref())?.into();
let iqmp = PosInt::from_bytes(decoded.coefficient.as_ref())?.into();

let priv_key = rsa_priv::RsaPrivateKey::new(p, q, dp, dq, iqmp, n, e)?;
Ok(Self(priv_key))
Expand Down
13 changes: 13 additions & 0 deletions graviola/src/low/aarch64/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//
// cf. the x86_64 version, on which this one is based.

use crate::low;
use core::arch::aarch64::*;

pub(crate) enum AesKey {
Expand Down Expand Up @@ -88,6 +89,12 @@ impl AesKey128 {
}
}

impl Drop for AesKey128 {
fn drop(&mut self) {
low::zeroise(&mut self.round_keys);
}
}

pub(crate) struct AesKey256 {
round_keys: [uint8x16_t; 14 + 1],
}
Expand Down Expand Up @@ -131,6 +138,12 @@ impl AesKey256 {
}
}

impl Drop for AesKey256 {
fn drop(&mut self) {
low::zeroise(&mut self.round_keys);
}
}

fn zero() -> uint8x16_t {
unsafe { vdupq_n_u8(0) }
}
Expand Down
28 changes: 28 additions & 0 deletions graviola/src/low/aarch64/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,34 @@ pub(crate) fn leave_cpu_state(old: u32) {
dit::maybe_disable(old);
}

/// Effectively memset(ptr, 0, len), but not visible to optimiser
pub(crate) fn zero_bytes(ptr: *mut u8, len: usize) {
unsafe {
core::arch::asm!(
" eor {zero}.16b, {zero}.16b, {zero}.16b",
// by-16 loop
" 2: cmp {len}, #16",
" blt 3f",
" st1 {{{zero}.16b}}, [{ptr}]",
" add {ptr}, {ptr}, #16",
" sub {len}, {len}, #16",
" b 2b",
// by-1 loop
" 3: subs {len}, {len}, #1",
" blt 4f",
" strb wzr, [{ptr}], #1",
" b 3b",
" 4: ",

ptr = inout(reg) ptr => _,
len = inout(reg) len => _,

// clobbers
zero = out(vreg) _,
)
}
}

pub(crate) fn verify_cpu_features() {
assert!(
is_aarch64_feature_detected!("neon"),
Expand Down
8 changes: 8 additions & 0 deletions graviola/src/low/aarch64/ghash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//!
//! Based on the implementation in low/x86_64/ghash.rs

use crate::low;
use core::arch::aarch64::*;
use core::mem;

Expand Down Expand Up @@ -42,6 +43,13 @@ impl GhashTable {
}
}

impl Drop for GhashTable {
fn drop(&mut self) {
low::zeroise(&mut self.powers);
low::zeroise(&mut self.powers_xor);
}
}

pub(crate) struct Ghash<'a> {
table: &'a GhashTable,
current: uint64x2_t,
Expand Down
31 changes: 31 additions & 0 deletions graviola/src/low/generic/zeroise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Written for Graviola by Joe Birr-Pixton, 2024.
// SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT-0

use crate::low::zero_bytes;

/// Writes zeroes over the whole of the `v` slice.
pub(crate) fn zeroise<T: Zeroable>(v: &mut [T]) {
zero_bytes(v.as_mut_ptr().cast(), size_of_val(v));
}

/// Writes zeroes over the whole of the `v` value.
pub(crate) fn zeroise_value<T: Zeroable>(v: &mut T) {
zero_bytes(v as *mut T as *mut _, size_of::<T>());
}

/// Marker trait for types who have valid all-bits-zero values.
pub(crate) trait Zeroable {}

impl Zeroable for u8 {}
impl Zeroable for u64 {}
impl Zeroable for usize {}

#[cfg(target_arch = "x86_64")]
impl Zeroable for core::arch::x86_64::__m256i {}
#[cfg(target_arch = "x86_64")]
impl Zeroable for core::arch::x86_64::__m128i {}

#[cfg(target_arch = "aarch64")]
impl Zeroable for core::arch::aarch64::uint8x16_t {}
#[cfg(target_arch = "aarch64")]
impl Zeroable for core::arch::aarch64::uint64x2_t {}
8 changes: 5 additions & 3 deletions graviola/src/low/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod generic {
pub(crate) mod poly1305;
pub(super) mod sha256;
pub(super) mod sha512;
pub(super) mod zeroise;
}

mod entry;
Expand All @@ -27,7 +28,8 @@ pub(crate) use entry::Entry;
pub(crate) use generic::blockwise::Blockwise;
pub(crate) use generic::ct_equal::ct_equal;
pub(crate) use generic::poly1305;
pub(crate) use posint::PosInt;
pub(crate) use generic::zeroise::{zeroise, zeroise_value};
pub(crate) use posint::{PosInt, SecretPosInt};

#[cfg(test)]
mod tests;
Expand All @@ -36,7 +38,7 @@ cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
mod x86_64;

pub(crate) use x86_64::cpu::{enter_cpu_state, leave_cpu_state, verify_cpu_features};
pub(crate) use x86_64::cpu::{enter_cpu_state, zero_bytes, leave_cpu_state, verify_cpu_features};
pub(crate) use x86_64::chacha20;
pub(crate) use x86_64::aes::AesKey;
pub(crate) use x86_64::aes_gcm;
Expand Down Expand Up @@ -95,7 +97,7 @@ cfg_if::cfg_if! {
} else if #[cfg(target_arch = "aarch64")] {
mod aarch64;

pub(crate) use aarch64::cpu::{enter_cpu_state, leave_cpu_state, verify_cpu_features};
pub(crate) use aarch64::cpu::{enter_cpu_state, zero_bytes, leave_cpu_state, verify_cpu_features};
pub(crate) use aarch64::aes::AesKey;
pub(crate) use aarch64::bignum_add::bignum_add;
pub(crate) use aarch64::bignum_add_p256::bignum_add_p256;
Expand Down
35 changes: 35 additions & 0 deletions graviola/src/low/posint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
use crate::low;
use crate::Error;

use core::ops::{Deref, DerefMut};

#[derive(Clone, Debug)]
pub(crate) struct PosInt<const N: usize> {
words: [u64; N],
Expand Down Expand Up @@ -452,6 +454,7 @@ impl<const N: usize> PosInt<N> {
}
}

low::zeroise(&mut table);
accum.from_montgomery(n)
}

Expand Down Expand Up @@ -489,6 +492,38 @@ impl<const N: usize> PosInt<N> {
}
}

/// A `SecretPosInt` is a `PosInt` containing long-term key material.
///
/// It is zeroed on drop.
pub(crate) struct SecretPosInt<const N: usize>(PosInt<N>);

impl<const N: usize> From<PosInt<N>> for SecretPosInt<N> {
fn from(pi: PosInt<N>) -> Self {
Self(pi)
}
}

impl<const N: usize> Deref for SecretPosInt<N> {
type Target = PosInt<N>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<const N: usize> DerefMut for SecretPosInt<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<const N: usize> Drop for SecretPosInt<N> {
fn drop(&mut self) {
low::zeroise(self.as_mut_words());
low::zeroise_value(&mut self.used);
}
}

#[derive(Debug)]
struct BitsMsbFirstIter<'a> {
words: &'a [u64],
Expand Down
14 changes: 14 additions & 0 deletions graviola/src/low/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ fn bignum_mux() {
bignum_mux_equiv(u64::MAX, &[1; 1], &[0; 1]);
}

#[test]
fn zeroise() {
for n in 0..1024 {
zeroise_equiv(n);
}
}

fn zeroise_equiv(len: usize) {
let expect = vec![0x00u8; len];
let mut bytes = vec![0xffu8; len];
super::zeroise(&mut bytes);
assert_eq!(expect, bytes);
}

mod model {
pub fn bignum_mux(p: u64, z: &mut [u64], x_if_p: &[u64], y_if_not_p: &[u64]) {
if p > 0 {
Expand Down
14 changes: 14 additions & 0 deletions graviola/src/low/x86_64/aes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

use core::arch::x86_64::*;

use crate::low;

pub(crate) enum AesKey {
Aes128(AesKey128),
Aes256(AesKey256),
Expand Down Expand Up @@ -70,6 +72,12 @@ impl AesKey128 {
}
}

impl Drop for AesKey128 {
fn drop(&mut self) {
low::zeroise(&mut self.round_keys);
}
}

fn zero() -> __m128i {
unsafe { _mm_setzero_si128() }
}
Expand All @@ -94,6 +102,12 @@ impl AesKey256 {
}
}

impl Drop for AesKey256 {
fn drop(&mut self) {
low::zeroise(&mut self.round_keys);
}
}

macro_rules! expand_128 {
($rcon:literal, $t1:ident, $out:expr) => {
// with [X3, _, X1, _] = t1
Expand Down
32 changes: 32 additions & 0 deletions graviola/src/low/x86_64/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ pub(crate) fn leave_cpu_state(_old: u32) {
}
}

/// Effectively memset(ptr, 0, len), but not visible to optimiser
pub(crate) fn zero_bytes(ptr: *mut u8, len: usize) {
unsafe { _zero_bytes(ptr, len) }
}

#[target_feature(enable = "avx")]
unsafe fn _zero_bytes(ptr: *mut u8, len: usize) {
core::arch::asm!(
" vpxor {zero}, {zero}, {zero}",
// by-32 loop
" 2: cmp {len}, 32",
" jl 3f",
" vmovdqu [{ptr}], {zero}",
" add {ptr}, 32",
" sub {len}, 32",
" jmp 2b",
// by-1 loop
" 3: sub {len}, 1",
" jl 4f",
" mov byte ptr [{ptr}], 0",
" add {ptr}, 1",
" jmp 3b",
" 4: ",

ptr = inout(reg) ptr => _,
len = inout(reg) len => _,

// clobbers
zero = out(ymm_reg) _,
)
}

/// This macro interdicts is_x86_feature_detected to
/// allow testability.
macro_rules! have_cpu_feature {
Expand Down
11 changes: 10 additions & 1 deletion graviola/src/low/x86_64/ghash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
use core::arch::x86_64::*;
use core::mem;

use crate::low;

pub(crate) struct GhashTable {
/// H, H^2, H^3, H^4, ... H^7
/// H, H^2, H^3, H^4, ... H^8
powers: [__m128i; 8],

/// `powers_xor[i]` is `powers[i].lo64 ^ powers[i].hi64`
Expand Down Expand Up @@ -41,6 +43,13 @@ impl GhashTable {
}
}

impl Drop for GhashTable {
fn drop(&mut self) {
low::zeroise(&mut self.powers);
low::zeroise(&mut self.powers_xor);
}
}

pub(crate) struct Ghash<'a> {
pub(crate) table: &'a GhashTable,
pub(crate) current: __m128i,
Expand Down
Loading

0 comments on commit 9807b69

Please sign in to comment.