1use rand::rngs::OsRng;
2use thiserror::Error;
3use x25519_dalek::{PublicKey as X25519PublicKey, SharedSecret, StaticSecret as X25519Secret};
4
5use chacha20poly1305::aead::{AeadInPlace, KeyInit};
6use chacha20poly1305::{ChaCha20Poly1305, Key};
7use hkdf::Hkdf;
8use sha2::Sha256;
9
10pub mod identity;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum KeyExchangeAlgorithm {
15 X25519,
16 EcdhP256,
17 None,
18}
19
20#[derive(Debug, Clone)]
22pub struct SessionKeys {
23 pub shared_secret: Vec<u8>,
24 pub control_key: [u8; 32],
25 pub stream_key: [u8; 32],
26}
27
28pub trait KeyExchange {
30 fn algorithm(&self) -> KeyExchangeAlgorithm;
31 fn public_key(&self) -> Vec<u8>;
32 fn derive_keys(&self, peer_public_key: &[u8], salt: &[u8]) -> Result<SessionKeys, CryptoError>;
33}
34
35pub struct X25519KeyExchange {
37 public_key: X25519PublicKey,
38 private_key: X25519Secret,
39}
40
41impl X25519KeyExchange {
42 pub fn new() -> Self {
43 let private_key = X25519Secret::random_from_rng(OsRng);
44 let public_key = X25519PublicKey::from(&private_key);
45 Self {
46 public_key,
47 private_key,
48 }
49 }
50}
51
52impl Default for X25519KeyExchange {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl KeyExchange for X25519KeyExchange {
59 fn algorithm(&self) -> KeyExchangeAlgorithm {
60 KeyExchangeAlgorithm::X25519
61 }
62
63 fn public_key(&self) -> Vec<u8> {
64 self.public_key.to_bytes().to_vec()
65 }
66
67 fn derive_keys(&self, peer_public_key: &[u8], salt: &[u8]) -> Result<SessionKeys, CryptoError> {
68 let peer_bytes: [u8; 32] = peer_public_key
69 .try_into()
70 .map_err(|_| CryptoError::InvalidPeerKey)?;
71 let peer_pk = X25519PublicKey::from(peer_bytes);
72 let shared_secret: SharedSecret = self.private_key.diffie_hellman(&peer_pk);
73 let shared_secret_bytes = shared_secret.as_bytes().to_vec();
74
75 let hkdf = Hkdf::<Sha256>::new(Some(salt), shared_secret.as_bytes());
76 let mut control_key = [0u8; 32];
77 let mut stream_key = [0u8; 32];
78 hkdf.expand(b"alpine-control", &mut control_key)
79 .map_err(|e| CryptoError::Hkdf(format!("{:?}", e)))?;
80 hkdf.expand(b"alpine-stream", &mut stream_key)
81 .map_err(|e| CryptoError::Hkdf(format!("{:?}", e)))?;
82
83 Ok(SessionKeys {
84 shared_secret: shared_secret_bytes,
85 control_key,
86 stream_key,
87 })
88 }
89}
90
91pub trait TlsWrapper {
93 fn wrap_stream(&self, plaintext: &[u8]) -> Vec<u8>;
94 fn unwrap_stream(&self, ciphertext: &[u8]) -> Vec<u8>;
95}
96
97#[derive(Debug, Error)]
99pub enum CryptoError {
100 #[error("invalid peer public key")]
101 InvalidPeerKey,
102 #[error("hkdf expand error: {0}")]
103 Hkdf(String),
104 #[error("aead error: {0}")]
105 Aead(String),
106}
107
108pub fn compute_mac(
110 keys: &SessionKeys,
111 seq: u64,
112 payload: &[u8],
113 aad: &[u8],
114) -> Result<Vec<u8>, CryptoError> {
115 let key = Key::from_slice(&keys.control_key);
116 let cipher = ChaCha20Poly1305::new(key);
117 let mut nonce = [0u8; 12];
118 nonce[..8].copy_from_slice(&seq.to_be_bytes());
119 let mut buffer = payload.to_vec();
120 let tag = cipher
121 .encrypt_in_place_detached(&nonce.into(), aad, &mut buffer)
122 .map_err(|e| CryptoError::Aead(e.to_string()))?;
123 Ok(tag.to_vec())
124}
125
126pub fn verify_mac(keys: &SessionKeys, seq: u64, payload: &[u8], aad: &[u8], mac: &[u8]) -> bool {
128 const CHACHA_TAG_SIZE: usize = 16;
129 if mac.len() != CHACHA_TAG_SIZE {
130 return false;
131 }
132 match compute_mac(keys, seq, payload, aad) {
133 Ok(expected) => expected == mac,
134 Err(_) => false,
135 }
136}