Skip to main content

rialo_tee_secret_sharing/
lib.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Secret sharing key management and wrapping for TEEs
5//!
6//! This crate provides secure key management and secret sharing capabilities
7//! for TEEs. It includes key generation,
8//! cryptographic operations, and wrapping mechanisms for secure communication.
9
10pub mod key;
11pub mod traits;
12pub mod types;
13pub mod wrapping;
14
15pub mod constants;
16mod error;
17pub mod hw_rng;
18#[cfg(test)]
19pub mod tests;
20
21pub use rialo_types::PublicKey;
22
23pub use self::{
24    constants::{SECRET_SHARING_HPKE_INFO, USER_SECRET_AAD},
25    error::SecretSharingError,
26    hw_rng::{HwRng, HwRngError},
27    key::SecretSharingKey,
28    traits::SecretSharingEngine,
29    types::{Aad, KeyId, WrappedKeyEnvelope},
30    wrapping::{aad_for_dkg, unwrap_scalar, wrap_scalar_for_recipient, HpkeX25519ChaChaEngine},
31};
32
33/// Client-side HPKE encryption function for user payloads.
34/// This is a convenience wrapper around the engine's encrypt function that:
35/// - Provides a stable public API independent of engine implementation
36/// - Allows future engine replacement without breaking client code  
37/// - Maintains consistent error handling and parameter validation
38///
39/// # Arguments
40/// * `sk_pub` - The SecretSharingKey public key bytes (from oracle-registry)
41/// * `payload` - The plaintext payload to encrypt
42/// * `aad` - Associated authenticated data for binding (e.g. USER_SECRET_AAD || creator_pubkey(32 bytes))
43///
44/// # Returns
45/// * `Ok(Vec<u8>)` - Encrypted payload in HPKE format: [enc (32 bytes)] || [ciphertext + tag]
46/// * `Err(SecretSharingError)` - If encryption fails
47///
48/// # Example
49/// ```ignore
50/// use rialo_tee_secret_sharing::{encrypt_user_payload, USER_SECRET_AAD};
51///
52/// let sk_pub = get_secret_sharing_pubkey(); // From oracle registry
53/// let payload = b"secret data";
54/// let aad = USER_SECRET_AAD || creator_pubkey_bytes;
55/// let ciphertext = encrypt_user_payload(&sk_pub, payload, &aad)?;
56/// ```
57pub fn encrypt_user_payload(
58    sk_pub: &PublicKey,
59    payload: &[u8],
60    aad: &[u8],
61) -> Result<Vec<u8>, SecretSharingError> {
62    HpkeX25519ChaChaEngine::hpke_encrypt_user_payload(sk_pub, payload, aad)
63}
64
65/// Default engine = HPKE X25519 + HKDF-SHA256 + ChaCha20-Poly1305
66pub type DefaultEngine = HpkeX25519ChaChaEngine;
67
68use std::sync::OnceLock;
69
70/// Global storage for the secret sharing key in this TEE instance.
71/// This key is used to decrypt user-submitted secret payloads.
72/// The key never leaves this module boundary for security.
73static SECRET_KEY: OnceLock<SecretSharingKey> = OnceLock::new();
74
75/// Initialize the secret sharing key for this TEE instance.
76/// This should be called once during TEE startup after key generation or distribution.
77/// The key remains isolated within this module for security.
78///
79/// Returns an error if the key is already initialized.
80pub fn initialize_secret_key(sk: SecretSharingKey) -> Result<(), SecretSharingError> {
81    SECRET_KEY
82        .set(sk)
83        .map_err(|_| SecretSharingError::UnwrapFailed("Secret key already initialized".into()))
84}
85
86/// Returns `true` if a secret key has been initialized in this TEE instance.
87pub fn has_secret_key() -> bool {
88    SECRET_KEY.get().is_some()
89}
90
91/// Common helper function to decrypt user ciphertext using HPKE.
92/// This function uses the stored secret sharing key to perform HPKE decryption with the
93/// user provided AAD for authenticated encryption.
94///
95/// # Arguments
96/// * `ciphertext` - The encrypted data in HPKE format
97/// * `aad` - The associated authenticated data used during encryption
98///
99/// # Returns
100/// * `Ok(Zeroizing<Vec<u8>>)` - The decrypted plaintext bytes
101/// * `Err(SecretSharingError)` - If decryption fails or key not initialized
102pub fn decrypt_user_ciphertext(
103    ciphertext: &[u8],
104    aad: &[u8],
105) -> Result<zeroize::Zeroizing<Vec<u8>>, SecretSharingError> {
106    let sk = SECRET_KEY
107        .get()
108        .ok_or_else(|| SecretSharingError::UnwrapFailed("Secret key not initialized".into()))?;
109
110    // For user payloads, we expect the ciphertext to be in a format similar to HPKE:
111    // [enc (32 bytes)] || [ciphertext + tag (variable length)]
112    if ciphertext.len() < 32 {
113        return Err(SecretSharingError::EnvelopeInvalid(
114            "Ciphertext too short for HPKE format".into(),
115        ));
116    }
117
118    let (enc_bytes, ct_with_tag) = ciphertext.split_at(32);
119    let mut enc_array = [0u8; 32];
120    enc_array.copy_from_slice(enc_bytes);
121
122    // Use the existing HPKE implementation from wrapping module with the caller-bound AAD
123    let plaintext = wrapping::HpkeX25519ChaChaEngine::hpke_decrypt_user_payload(
124        &sk.private_key().to_bytes(),
125        &enc_array,
126        ct_with_tag,
127        aad,
128    )?;
129
130    Ok(plaintext)
131}
132
133/// Decrypt a user-submitted encrypted message using the stored SecretSharingKey.
134///
135/// This function takes encrypted user data (containing a plain text message) and decrypts it
136/// inside the TEE using the module's stored SK_priv. The decryption uses HPKE in base mode
137/// with the caller-bound AAD for authenticated encryption.
138/// The AAD is derived from the USER_SECRET_AAD and the creator's public key.
139/// # Arguments
140/// * `ciphertext` - The encrypted message from the user in HPKE format
141/// * `creator_pubkey` - The 32-byte public key of the oracle creator who created the message
142///
143/// # Returns
144/// * `Ok(String)` - The decrypted plain text message
145/// * `Err(SecretSharingError)` - If decryption fails, key not initialized, or invalid UTF-8
146///
147/// # Security Notes
148/// - The encrypted message is decrypted inside the TEE using the stored secret key
149/// - The SecretSharingKey never leaves this module boundary for security
150/// - The decrypted content is validated as UTF-8 before returning
151/// - Only the creator's matching public key can decrypt the message
152pub fn decrypt_user_message(
153    ciphertext: &[u8],
154    creator_pubkey: &PublicKey,
155) -> Result<String, SecretSharingError> {
156    let aad = [USER_SECRET_AAD, &creator_pubkey.to_bytes()].concat();
157    let plaintext = decrypt_user_ciphertext(ciphertext, &aad)?;
158
159    // Convert bytes to string using std::str::from_utf8
160    let s = std::str::from_utf8(plaintext.as_ref()).map_err(|err| {
161        SecretSharingError::UnwrapFailed(format!(
162            "Failed to parse decrypted message as utf8: {}",
163            err
164        ))
165    })?;
166    Ok(s.to_string())
167}
168
169#[cfg(test)]
170mod secret_sharing_tests {
171    use assert_matches::assert_matches;
172
173    use super::*;
174
175    fn create_test_secret_key() -> SecretSharingKey {
176        SecretSharingKey::from_test_seed(b"test-secret-key")
177    }
178
179    #[test]
180    fn test_initialize_secret_key_double_initialization() {
181        let sk1 = create_test_secret_key();
182        let sk2 = create_test_secret_key();
183
184        // First initialization
185        let _ = initialize_secret_key(sk1);
186
187        // Second initialization should fail
188        let result = initialize_secret_key(sk2);
189        assert_matches!(
190            result,
191            Err(SecretSharingError::UnwrapFailed(msg)) if msg.contains("already initialized")
192        );
193    }
194
195    #[test]
196    fn test_decrypt_user_message_ciphertext_too_short() {
197        // Ensure key is initialized
198        let sk = create_test_secret_key();
199        let _ = initialize_secret_key(sk);
200
201        // Try with ciphertext shorter than 32 bytes
202        let short_ciphertext = vec![0u8; 16];
203
204        let creator_pubkey = PublicKey::from_bytes([2u8; 32]);
205        let result = decrypt_user_message(&short_ciphertext, &creator_pubkey);
206
207        assert_matches!(
208            result,
209            Err(SecretSharingError::EnvelopeInvalid(msg)) if msg.contains("too short")
210        );
211    }
212
213    #[test]
214    fn test_decrypt_user_message_invalid_utf8() {
215        // Initialize key if not already done (safe to call multiple times)
216        let sk = create_test_secret_key();
217        let _ = initialize_secret_key(sk);
218
219        // Create ciphertext that will decrypt to invalid UTF-8
220        let mut invalid_ciphertext = vec![0u8; 32]; // 32-byte "enc"
221        invalid_ciphertext.extend_from_slice(b"invalid data that will cause decryption to fail");
222
223        let creator_pubkey = PublicKey::from_bytes([3u8; 32]);
224        let result = decrypt_user_message(&invalid_ciphertext, &creator_pubkey);
225
226        // This will likely fail during HPKE decryption since we're using dummy data
227        // The exact error depends on the internal HPKE implementation
228        assert!(result.is_err());
229    }
230}