1#![deny(missing_docs)]
2use async_trait::async_trait;
13use thiserror::Error;
14
15#[non_exhaustive]
17#[derive(Debug, Error)]
18pub enum CryptoError {
19 #[error("key not found: {0}")]
21 KeyNotFound(String),
22
23 #[error("unsupported operation: {0}")]
25 UnsupportedOperation(String),
26
27 #[error("crypto operation failed: {0}")]
29 OperationFailed(String),
30
31 #[error("{0}")]
33 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
34}
35
36#[async_trait]
49pub trait CryptoProvider: Send + Sync {
50 async fn sign(
52 &self,
53 key_ref: &str,
54 algorithm: &str,
55 data: &[u8],
56 ) -> Result<Vec<u8>, CryptoError>;
57
58 async fn verify(
60 &self,
61 key_ref: &str,
62 algorithm: &str,
63 data: &[u8],
64 signature: &[u8],
65 ) -> Result<bool, CryptoError>;
66
67 async fn encrypt(&self, key_ref: &str, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError>;
69
70 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 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 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}