kassandra_shared/
ratls.rs

1//! A highly simplified version of RA-TLS. This performs a Diffie-Hellman
2//! key exchange using a hardcoded cryptographic suits as well as remote
3//! attestation. If successful, a single encrypted message containing an
4//! FMD key is sent and the connection is terminated. This means that
5//! we do not need to maintain a list of active sessions or session ids.
6
7use alloc::vec::Vec;
8
9use chacha20poly1305::aead::Aead;
10use chacha20poly1305::{AeadCore, ChaCha20Poly1305, Key, KeyInit, Nonce};
11use fmd::DetectionKey;
12use rand_core::{CryptoRng, RngCore};
13use serde::de::DeserializeOwned;
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15use thiserror::Error;
16
17use crate::db::EncKey;
18use crate::{ClientMsg, MsgToHost};
19
20#[derive(Error, Debug)]
21pub enum RatlsError {
22    #[error("Cannot perform Diffie-Hellman on a connection that is already initialized")]
23    AlreadyInitialized,
24    #[error("Shared Secret was non-contributory. This suggests a man-in-the-middle attack.")]
25    NonContributory,
26    #[error("Cannot encrypt to a non-initialized channel")]
27    NotInitialized,
28    #[error("Could not decrypt message")]
29    Decryption,
30    #[error("Failed to deserialize message with: {0}")]
31    Deserialize(serde_cbor::Error),
32}
33
34/// A ChaCha20 encrypted payload with nonce
35#[derive(Debug, Clone)]
36pub struct TlsCiphertext {
37    payload: Vec<u8>,
38    nonce: Nonce,
39}
40
41/// The data needed to register a user's key with the
42/// Kassandra service.
43#[derive(Deserialize, Serialize)]
44pub struct FmdKeyRegistration {
45    /// The secret detection key for FMD
46    pub fmd_key: DetectionKey,
47    /// A symmetric encryption key for storing encrypted results for users
48    /// in a transparent database
49    pub enc_key: EncKey,
50    /// An optional block height to start detecting from
51    pub birthday: Option<u64>,
52}
53
54impl Serialize for TlsCiphertext {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: Serializer,
58    {
59        #[derive(Serialize)]
60        struct SimplifiedCiphertext {
61            payload: Vec<u8>,
62            nonce: Vec<u8>,
63        }
64        let simplified = SimplifiedCiphertext {
65            payload: self.payload.clone(),
66            nonce: self.nonce.as_slice().to_vec(),
67        };
68        simplified.serialize(serializer)
69    }
70}
71
72impl<'de> Deserialize<'de> for TlsCiphertext {
73    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
74    where
75        D: Deserializer<'de>,
76    {
77        #[derive(Deserialize)]
78        struct SimplifiedCiphertext {
79            payload: Vec<u8>,
80            nonce: Vec<u8>,
81        }
82        let simplified = SimplifiedCiphertext::deserialize(deserializer)?;
83        Ok(Self {
84            payload: simplified.payload,
85            nonce: *Nonce::from_slice(&simplified.nonce),
86        })
87    }
88}
89
90/// A simplified, bespoke RA-TLS connection
91/// It can be in two possible states:
92///
93///  * `Handshake` - initializing session between two parties
94///  * `Initialized` - ready for communicating messages securely
95pub enum Connection {
96    Handshake {
97        ephemeral_key: x25519_dalek::EphemeralSecret,
98    },
99    Initialized {
100        shared_key: ChaCha20Poly1305,
101    },
102}
103
104impl Connection {
105    /// Create a new connection, which creates and ephemeral key for
106    /// Diffie-Hellman
107    pub fn new(rng: impl CryptoRng + RngCore) -> Self {
108        Self::Handshake {
109            ephemeral_key: x25519_dalek::EphemeralSecret::random_from_rng(rng),
110        }
111    }
112
113    /// The client side sends its ephemeral public key
114    pub fn client_send(&self, nonce: u64) -> Result<ClientMsg, RatlsError> {
115        match &self {
116            Self::Handshake { ephemeral_key } => Ok(ClientMsg::RegisterKey {
117                nonce,
118                pk: x25519_dalek::PublicKey::from(ephemeral_key)
119                    .to_bytes()
120                    .into(),
121            }),
122            Self::Initialized { .. } => Err(RatlsError::AlreadyInitialized),
123        }
124    }
125
126    /// The enclave replies with its Attestation report, which contains
127    /// its ephemeral public key and a session id.
128    pub fn enclave_reply(&self, report: Vec<u8>) -> Result<MsgToHost, RatlsError> {
129        match &self {
130            Self::Handshake { .. } => Ok(MsgToHost::RATLS { report }),
131            Self::Initialized { .. } => Err(RatlsError::AlreadyInitialized),
132        }
133    }
134
135    /// Compute the shared ChaCha20 public key for the connection.
136    pub fn initialize(self, pk: x25519_dalek::PublicKey) -> Result<Self, RatlsError> {
137        let Self::Handshake { ephemeral_key } = self else {
138            return Err(RatlsError::AlreadyInitialized);
139        };
140        let shared_secret = ephemeral_key.diffie_hellman(&pk);
141        let shared_key = if shared_secret.was_contributory() {
142            ChaCha20Poly1305::new(Key::from_slice(shared_secret.as_bytes()))
143        } else {
144            return Err(RatlsError::NonContributory);
145        };
146        Ok(Self::Initialized { shared_key })
147    }
148
149    /// Encrypt a message with the session key
150    pub fn encrypt_msg<T: CryptoRng + RngCore>(
151        &self,
152        payload: &[u8],
153        rng: &mut T,
154    ) -> Result<TlsCiphertext, RatlsError> {
155        if let Self::Initialized { shared_key } = &self {
156            let nonce = ChaCha20Poly1305::generate_nonce(rng);
157            Ok(TlsCiphertext {
158                payload: shared_key.encrypt(&nonce, payload).unwrap(),
159                nonce,
160            })
161        } else {
162            Err(RatlsError::NotInitialized)
163        }
164    }
165
166    /// Decrypt and deserialize  message
167    pub fn decrypt_msg<T: DeserializeOwned>(&self, msg: &TlsCiphertext) -> Result<T, RatlsError> {
168        if let Self::Initialized { shared_key } = &self {
169            shared_key
170                .decrypt(&msg.nonce, &*msg.payload)
171                .or(Err(RatlsError::Decryption))
172                .and_then(|p| serde_cbor::from_slice(p.as_slice()).map_err(RatlsError::Deserialize))
173        } else {
174            Err(RatlsError::NotInitialized)
175        }
176    }
177}