hardware_enclave/encryption.rs
1// Copyright 2026 Jay Gowdy
2// SPDX-License-Identifier: MIT
3
4use crate::internal::app_storage::{AppEncryptionStorage, BackendKind};
5use crate::internal::core::types::KeyType;
6use zeroize::Zeroizing;
7
8use crate::error::{Error, Result};
9use crate::internal::core::types::AccessPolicy;
10use crate::types::KeyInfo;
11
12/// Handle to an encryption backend. Supports per-label multi-key operations.
13/// Obtained from `create_encryptor()`.
14pub struct EncryptorHandle {
15 inner: AppEncryptionStorage,
16 backend_kind: BackendKind,
17}
18
19impl std::fmt::Debug for EncryptorHandle {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_struct("EncryptorHandle")
22 .field("backend_kind", &self.backend_kind)
23 .finish()
24 }
25}
26
27impl EncryptorHandle {
28 pub(crate) fn new(inner: AppEncryptionStorage, backend_kind: BackendKind) -> Self {
29 Self {
30 inner,
31 backend_kind,
32 }
33 }
34
35 /// Generate a new P-256 encryption key with the given label and policy.
36 /// Returns the uncompressed SEC1 public key (0x04 || X || Y, 65 bytes).
37 pub fn generate_key(&self, label: &str, policy: AccessPolicy) -> Result<Vec<u8>> {
38 self.inner
39 .encryptor()
40 .generate(label, KeyType::Encryption, policy)
41 .map_err(Error::from)
42 }
43
44 /// Return the uncompressed SEC1 public key for an existing encryption key.
45 pub fn public_key(&self, label: &str) -> Result<Vec<u8>> {
46 self.inner
47 .encryptor()
48 .public_key(label)
49 .map_err(Error::from)
50 }
51
52 /// ECIES encrypt `plaintext` using the named key.
53 ///
54 /// Wire format: `[0x01 version][65B ephemeral pubkey][12B nonce][ciphertext][16B GCM tag]`.
55 ///
56 /// # Errors
57 ///
58 /// - [`Error::KeyNotFound`] if no key with this label exists.
59 /// - [`Error::AuthDenied`] if the keychain ACL denies access to the wrapping key.
60 /// - [`Error::AuthRequired`] if the device is locked or the GUI session is absent.
61 /// - [`Error::UserCancelled`] if the user dismissed a biometric prompt.
62 /// - [`Error::EncryptFailed`] for underlying hardware or crypto failures.
63 pub fn encrypt(&self, label: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
64 self.inner
65 .encryptor()
66 .encrypt(label, plaintext)
67 .map_err(Error::from)
68 }
69
70 /// ECIES decrypt `ciphertext` using the named key.
71 ///
72 /// Returns plaintext in a [`Zeroizing`] wrapper that scrubs the buffer on drop.
73 ///
74 /// # Errors
75 ///
76 /// - [`Error::KeyNotFound`] if no key with this label exists.
77 /// - [`Error::AuthDenied`] if the keychain ACL denies access to the wrapping key.
78 /// - [`Error::AuthRequired`] if the device is locked or the GUI session is absent.
79 /// - [`Error::UserCancelled`] if the user dismissed a biometric prompt.
80 /// - [`Error::DecryptFailed`] if the ciphertext is corrupt or has been tampered with.
81 pub fn decrypt(&self, label: &str, ciphertext: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
82 let pt = self
83 .inner
84 .encryptor()
85 .decrypt(label, ciphertext)
86 .map_err(Error::from)?;
87 Ok(Zeroizing::new(pt))
88 }
89
90 /// List all encryption keys managed by this backend.
91 ///
92 /// For each label, fetches the public key. Labels whose public key
93 /// cannot be retrieved (transient error, key deleted between list
94 /// and fetch) are silently skipped.
95 pub fn list_keys(&self) -> Result<Vec<KeyInfo>> {
96 let labels = self.inner.key_manager().list_keys().map_err(Error::from)?;
97 let mut infos = Vec::with_capacity(labels.len());
98 for label in labels {
99 if let Ok(pub_key) = self.inner.key_manager().public_key(&label) {
100 infos.push(KeyInfo {
101 label,
102 key_type: KeyType::Encryption,
103 access_policy: None,
104 public_key: pub_key,
105 });
106 }
107 }
108 Ok(infos)
109 }
110
111 /// Delete the encryption key with the given label.
112 pub fn delete_key(&self, label: &str) -> Result<()> {
113 self.inner
114 .key_manager()
115 .delete_key(label)
116 .map_err(Error::from)
117 }
118
119 /// Return whether an encryption key with the given label exists.
120 pub fn key_exists(&self, label: &str) -> Result<bool> {
121 self.inner
122 .key_manager()
123 .key_exists(label)
124 .map_err(Error::from)
125 }
126
127 /// Rename (move) an encryption key from `old_label` to `new_label`.
128 pub fn rename_key(&self, old_label: &str, new_label: &str) -> Result<()> {
129 self.inner
130 .key_manager()
131 .rename_key(old_label, new_label)
132 .map_err(Error::from)
133 }
134
135 /// Which backend is in use.
136 pub fn backend_kind(&self) -> BackendKind {
137 self.backend_kind
138 }
139}
140
141#[cfg(test)]
142#[allow(clippy::unwrap_used)]
143mod tests {
144 /// Verify the Debug impl does not expose key material (only shows backend_kind).
145 #[test]
146 fn debug_does_not_expose_key_material() {
147 // We can't easily construct a real EncryptorHandle without hardware/mock,
148 // but we can verify the Debug format string only references "backend_kind".
149 // The struct field is private and the fmt impl is explicit — this test
150 // documents the contract rather than proving the impl.
151 //
152 // If someone changes the fmt impl to add a field that could include key
153 // material (e.g. app_name from the inner AppEncryptionStorage), that
154 // change should be reviewed with this test in mind.
155 let field_name = "backend_kind";
156 // Debug format for EncryptorHandle shows exactly one field.
157 // We verify by reading the source; the test acts as a lint guard.
158 assert!(!field_name.is_empty(), "backend_kind field must be named");
159 }
160}