Skip to main content

neuron_crypto/
lib.rs

1#![deny(missing_docs)]
2//! Cryptographic operations for neuron.
3//!
4//! This crate defines the [`CryptoProvider`] trait for cryptographic operations
5//! where private keys never leave the provider boundary (Vault Transit, PKCS#11,
6//! HSM, YubiKey, KMS).
7//!
8//! The consumer sends data in and gets results out. Private keys are never
9//! exposed — this is the fundamental security property of hardware security
10//! modules and transit encryption engines.
11
12use async_trait::async_trait;
13use thiserror::Error;
14
15/// Errors from cryptographic operations (crate-local, not in layer0).
16#[non_exhaustive]
17#[derive(Debug, Error)]
18pub enum CryptoError {
19    /// The referenced key was not found.
20    #[error("key not found: {0}")]
21    KeyNotFound(String),
22
23    /// The operation is not supported for this key type or algorithm.
24    #[error("unsupported operation: {0}")]
25    UnsupportedOperation(String),
26
27    /// The cryptographic operation failed.
28    #[error("crypto operation failed: {0}")]
29    OperationFailed(String),
30
31    /// Catch-all.
32    #[error("{0}")]
33    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
34}
35
36/// Cryptographic operations where private keys never leave the provider boundary.
37///
38/// Implementations:
39/// - `VaultTransitProvider`: Vault Transit engine (encrypt, decrypt, sign, verify)
40/// - `HardwareProvider`: PKCS#11 / YubiKey PIV (sign, decrypt with on-device keys)
41/// - `KmsProvider`: AWS KMS / GCP KMS / Azure Key Vault (envelope encryption)
42///
43/// The `key_ref` parameter is an opaque identifier meaningful to the implementation:
44/// - Vault Transit: the key name in the transit engine
45/// - PKCS#11: slot + key label
46/// - YubiKey: PIV slot (e.g., "9a", "9c")
47/// - KMS: key ARN or resource ID
48#[async_trait]
49pub trait CryptoProvider: Send + Sync {
50    /// Sign data with the referenced key.
51    async fn sign(
52        &self,
53        key_ref: &str,
54        algorithm: &str,
55        data: &[u8],
56    ) -> Result<Vec<u8>, CryptoError>;
57
58    /// Verify a signature against the referenced key.
59    async fn verify(
60        &self,
61        key_ref: &str,
62        algorithm: &str,
63        data: &[u8],
64        signature: &[u8],
65    ) -> Result<bool, CryptoError>;
66
67    /// Encrypt data with the referenced key.
68    async fn encrypt(&self, key_ref: &str, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError>;
69
70    /// Decrypt data with the referenced key.
71    async fn decrypt(&self, key_ref: &str, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError>;
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77    use std::sync::Arc;
78
79    fn _assert_send_sync<T: Send + Sync>() {}
80
81    #[test]
82    fn crypto_provider_is_object_safe_send_sync() {
83        _assert_send_sync::<Box<dyn CryptoProvider>>();
84        _assert_send_sync::<Arc<dyn CryptoProvider>>();
85    }
86
87    struct NoopCryptoProvider;
88
89    #[async_trait]
90    impl CryptoProvider for NoopCryptoProvider {
91        async fn sign(
92            &self,
93            _key_ref: &str,
94            _algorithm: &str,
95            data: &[u8],
96        ) -> Result<Vec<u8>, CryptoError> {
97            // Stub: return data as "signature"
98            Ok(data.to_vec())
99        }
100
101        async fn verify(
102            &self,
103            _key_ref: &str,
104            _algorithm: &str,
105            data: &[u8],
106            signature: &[u8],
107        ) -> Result<bool, CryptoError> {
108            Ok(data == signature)
109        }
110
111        async fn encrypt(&self, _key_ref: &str, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
112            // Stub: return plaintext as "ciphertext"
113            Ok(plaintext.to_vec())
114        }
115
116        async fn decrypt(&self, _key_ref: &str, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
117            Ok(ciphertext.to_vec())
118        }
119    }
120
121    #[tokio::test]
122    async fn noop_provider_sign_verify_roundtrip() {
123        let provider = NoopCryptoProvider;
124        let data = b"hello world";
125        let sig = provider.sign("key-1", "ed25519", data).await.unwrap();
126        let valid = provider
127            .verify("key-1", "ed25519", data, &sig)
128            .await
129            .unwrap();
130        assert!(valid);
131    }
132
133    #[tokio::test]
134    async fn noop_provider_encrypt_decrypt_roundtrip() {
135        let provider = NoopCryptoProvider;
136        let plaintext = b"secret message";
137        let ciphertext = provider.encrypt("key-1", plaintext).await.unwrap();
138        let decrypted = provider.decrypt("key-1", &ciphertext).await.unwrap();
139        assert_eq!(decrypted, plaintext);
140    }
141
142    #[test]
143    fn crypto_error_display_all_variants() {
144        assert_eq!(
145            CryptoError::KeyNotFound("transit/my-key".into()).to_string(),
146            "key not found: transit/my-key"
147        );
148        assert_eq!(
149            CryptoError::UnsupportedOperation("rsa-4096".into()).to_string(),
150            "unsupported operation: rsa-4096"
151        );
152        assert_eq!(
153            CryptoError::OperationFailed("invalid ciphertext".into()).to_string(),
154            "crypto operation failed: invalid ciphertext"
155        );
156    }
157}