Skip to main content

scll_core/backend/
key.rs

1//! `KeyBackend` (+ optional `ExportableKeyBackend`) and opaque key handles — §3.3.
2//!
3//! `no_std`: `random_bytes` fills a caller-supplied buffer (no alloc, no forced
4//! capacity — `n` is `out.len()`); `export_key_dangerous` returns an
5//! [`ExportedKey`] — a fixed-capacity, drop-zeroizing secret-bytes type. (We do
6//! not use `Zeroizing<heapless::Vec<..>>`: heapless 0.8 has no `zeroize`
7//! feature, so `heapless::Vec` is not `Zeroize`; the dedicated type keeps us on
8//! heapless 0.8 / MSRV 1.81.)
9
10use zeroize::Zeroize;
11
12use crate::error::BackendError;
13use crate::limits::KEY_BYTES_MAX;
14
15/// Backend-defined opaque key reference. With the software backend (and the
16/// recommended embedded pattern) this is an **index into a fixed key-slot table
17/// the backend owns**; another backend may reinterpret the value. Opaque to
18/// callers — no key material crosses this boundary except via
19/// [`ExportableKeyBackend`]. The `new`/`index` accessors are backend-facing.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct KeyHandle(u16);
22
23impl KeyHandle {
24    /// Construct a handle from a backend slot index.
25    #[must_use]
26    pub const fn new(index: u16) -> Self {
27        Self(index)
28    }
29    /// The backend slot index this handle refers to.
30    #[must_use]
31    pub const fn index(self) -> u16 {
32        self.0
33    }
34}
35
36/// Algorithm/length class for an imported or generated key.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum KeyKind {
40    Aes128,
41    Aes192,
42    Aes256,
43    TripleDesDouble, // 3DES double-length (SCP02)
44}
45
46impl KeyKind {
47    /// The plaintext key length in bytes: AES-128/192/256 → 16/24/32; two-key
48    /// 3DES → 16. This is the value carried in the SCP03 AES PUT KEY block's
49    /// clear-key-length byte (Amendment D §7.2), which is distinct from the
50    /// *encrypted* length — a 24-byte AES-192 key is padded to 32 ciphertext
51    /// bytes, so the two only coincide for AES-128 and AES-256.
52    #[must_use]
53    pub const fn clear_len(self) -> usize {
54        match self {
55            KeyKind::Aes192 => 24,
56            KeyKind::Aes256 => 32,
57            KeyKind::Aes128 | KeyKind::TripleDesDouble => 16,
58        }
59    }
60}
61
62/// Key handles, randomness, KCV, constant-time compare. Required by every backend.
63pub trait KeyBackend: Send + Sync + 'static {
64    /// Import raw key `bytes` of `kind`, returning an opaque [`KeyHandle`].
65    ///
66    /// # Errors
67    /// Returns [`BackendError::KeyImport`] if `bytes` does not match `kind`, or
68    /// if the backend's key-slot table is full.
69    fn import_key(&self, kind: KeyKind, bytes: &[u8]) -> Result<KeyHandle, BackendError>;
70    /// Generate a fresh key of `kind`, returning an opaque [`KeyHandle`].
71    ///
72    /// # Errors
73    /// Returns [`BackendError::KeyGen`] (or [`BackendError::Rng`]) if generation
74    /// fails or no slot is free.
75    fn generate_key(&self, kind: KeyKind) -> Result<KeyHandle, BackendError>;
76    /// Compute the GP Key Check Value (3 bytes) for the referenced key.
77    ///
78    /// # Errors
79    /// Returns [`BackendError::Crypto`] if `h` does not refer to a live key, or
80    /// the KCV computation fails.
81    fn compute_kcv(&self, h: &KeyHandle) -> Result<[u8; 3], BackendError>;
82    /// Fill `out` with cryptographically secure random bytes. The count is
83    /// `out.len()` (was the alloc-returning `random_bytes(n) -> Vec<u8>`).
84    ///
85    /// # Errors
86    /// Returns [`BackendError::Rng`] if the underlying CSPRNG fails.
87    fn random_bytes(&self, out: &mut [u8]) -> Result<(), BackendError>;
88    fn ct_eq(&self, a: &[u8], b: &[u8]) -> bool;
89}
90
91/// Plaintext key material returned by [`ExportableKeyBackend::export_key_dangerous`]
92/// (software backends only). Holds up to [`KEY_BYTES_MAX`] bytes and **zeroizes
93/// the buffer on drop**. Dangerous by construction: do not copy the bytes into
94/// an unprotected location. Replaces `Zeroizing<heapless::Vec<..>>` so the core
95/// stays on heapless 0.8 (which has no `zeroize` feature).
96pub struct ExportedKey {
97    bytes: [u8; KEY_BYTES_MAX],
98    len: usize,
99}
100
101impl ExportedKey {
102    /// Build from a key slice. Returns `None` if `src` exceeds [`KEY_BYTES_MAX`].
103    #[must_use]
104    pub fn from_slice(src: &[u8]) -> Option<Self> {
105        if src.len() > KEY_BYTES_MAX {
106            return None;
107        }
108        let mut bytes = [0u8; KEY_BYTES_MAX];
109        bytes[..src.len()].copy_from_slice(src);
110        Some(Self {
111            bytes,
112            len: src.len(),
113        })
114    }
115
116    /// The meaningful key bytes (length matches the key type, e.g. 16/24/32).
117    #[must_use]
118    pub fn as_bytes(&self) -> &[u8] {
119        &self.bytes[..self.len]
120    }
121}
122
123impl Zeroize for ExportedKey {
124    fn zeroize(&mut self) {
125        self.bytes.zeroize();
126        self.len = 0;
127    }
128}
129
130impl Drop for ExportedKey {
131    fn drop(&mut self) {
132        self.zeroize();
133    }
134}
135
136impl core::ops::Deref for ExportedKey {
137    type Target = [u8];
138    fn deref(&self) -> &[u8] {
139        self.as_bytes()
140    }
141}
142
143/// OPTIONAL. Plaintext export of a key the backend holds. A software backend
144/// (`RustCrypto`) implements this; an HSM/PKCS#11 backend does NOT — so "keys
145/// never leave the token" is enforced by the type system, not a runtime error.
146pub trait ExportableKeyBackend: KeyBackend {
147    /// Export the plaintext bytes of the key referenced by `h` (software
148    /// backends only).
149    ///
150    /// # Errors
151    /// Returns [`BackendError::Crypto`] if `h` does not refer to a live,
152    /// exportable key, or [`BackendError::Unsupported`] if export is refused.
153    fn export_key_dangerous(&self, h: &KeyHandle) -> Result<ExportedKey, BackendError>;
154}