blob: b31bd4bee9c71245d725014f239fe0ad799ea646 [file] [log] [blame]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Functions and drivers for obtaining true entropy.
use crate::hvc;
use core::fmt;
use core::mem::size_of;
use smccc::{self, Hvc};
use zerocopy::AsBytes as _;
type Entropy = [u8; size_of::<u64>() * 3];
/// Error type for rand operations.
pub enum Error {
/// No source of entropy found.
NoEntropySource,
/// Error during architectural SMCCC call.
Smccc(smccc::arch::Error),
/// Error during SMCCC TRNG call.
Trng(hvc::trng::Error),
/// Unsupported SMCCC version.
UnsupportedSmcccVersion(smccc::arch::Version),
/// Unsupported SMCCC TRNG version.
UnsupportedTrngVersion(hvc::trng::Version),
}
impl From<smccc::arch::Error> for Error {
fn from(e: smccc::arch::Error) -> Self {
Self::Smccc(e)
}
}
impl From<hvc::trng::Error> for Error {
fn from(e: hvc::trng::Error) -> Self {
Self::Trng(e)
}
}
/// Result type for rand operations.
pub type Result<T> = core::result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NoEntropySource => write!(f, "No source of entropy available"),
Self::Smccc(e) => write!(f, "Architectural SMCCC error: {e}"),
Self::Trng(e) => write!(f, "SMCCC TRNG error: {e}"),
Self::UnsupportedSmcccVersion(v) => write!(f, "Unsupported SMCCC version {v}"),
Self::UnsupportedTrngVersion(v) => write!(f, "Unsupported SMCCC TRNG version {v}"),
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self}")
}
}
/// Configure the source of entropy.
pub(crate) fn init() -> Result<()> {
// SMCCC TRNG requires SMCCC v1.1.
match smccc::arch::version::<Hvc>()? {
smccc::arch::Version { major: 1, minor } if minor >= 1 => (),
version => return Err(Error::UnsupportedSmcccVersion(version)),
}
// TRNG_RND requires SMCCC TRNG v1.0.
match hvc::trng_version()? {
hvc::trng::Version { major: 1, minor: _ } => (),
version => return Err(Error::UnsupportedTrngVersion(version)),
}
// TRNG_RND64 doesn't define any special capabilities so ignore the successful result.
let _ = hvc::trng_features(hvc::ARM_SMCCC_TRNG_RND64).map_err(|e| {
if e == hvc::trng::Error::NotSupported {
// SMCCC TRNG is currently our only source of entropy.
Error::NoEntropySource
} else {
e.into()
}
})?;
Ok(())
}
/// Fills a slice of bytes with true entropy.
pub fn fill_with_entropy(s: &mut [u8]) -> Result<()> {
const MAX_BYTES_PER_CALL: usize = size_of::<Entropy>();
for chunk in s.chunks_mut(MAX_BYTES_PER_CALL) {
let entropy = repeat_trng_rnd(chunk.len())?;
chunk.clone_from_slice(&entropy[..chunk.len()]);
}
Ok(())
}
/// Returns an array where the first `n_bytes` bytes hold entropy.
///
/// The rest of the array should be ignored.
fn repeat_trng_rnd(n_bytes: usize) -> Result<Entropy> {
loop {
if let Some(entropy) = rnd64(n_bytes)? {
return Ok(entropy);
}
}
}
/// Returns an array where the first `n_bytes` bytes hold entropy, if available.
///
/// The rest of the array should be ignored.
fn rnd64(n_bytes: usize) -> Result<Option<Entropy>> {
let bits = usize::try_from(u8::BITS).unwrap();
let result = hvc::trng_rnd64((n_bytes * bits).try_into().unwrap());
let entropy = if matches!(result, Err(hvc::trng::Error::NoEntropy)) {
None
} else {
let r = result?;
// From the SMCCC TRNG:
//
// A MAX_BITS-bits wide value (Entropy) is returned across X1 to X3.
// The requested conditioned entropy is returned in Entropy[N-1:0].
//
// X1 Entropy[191:128]
// X2 Entropy[127:64]
// X3 Entropy[63:0]
//
// The bits in Entropy[MAX_BITS-1:N] are 0.
let reordered = [r[2].to_le(), r[1].to_le(), r[0].to_le()];
Some(reordered.as_bytes().try_into().unwrap())
};
Ok(entropy)
}
/// Generate an array of fixed-size initialized with true-random bytes.
pub fn random_array<const N: usize>() -> Result<[u8; N]> {
let mut arr = [0; N];
fill_with_entropy(&mut arr)?;
Ok(arr)
}