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}