| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
use curve25519_dalek::montgomery::MontgomeryPoint; |
| 13 |
use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, CHACHA20_POLY1305}; |
| 14 |
use ring::hkdf; |
| 15 |
use ring::rand::{SecureRandom, SystemRandom}; |
| 16 |
|
| 17 |
|
| 18 |
const INFO: &[u8] = b"oxygenhub alert v1"; |
| 19 |
|
| 20 |
|
| 21 |
pub fn new_secret() -> [u8; 32] { |
| 22 |
let mut s = [0u8; 32]; |
| 23 |
SystemRandom::new().fill(&mut s).expect("the system's random source"); |
| 24 |
s |
| 25 |
} |
| 26 |
|
| 27 |
|
| 28 |
pub fn public_key(secret: &[u8; 32]) -> [u8; 32] { |
| 29 |
MontgomeryPoint::mul_base_clamped(*secret).to_bytes() |
| 30 |
} |
| 31 |
|
| 32 |
fn key(shared: &MontgomeryPoint, ephemeral: &[u8; 32], recipient: &[u8; 32]) -> Option<LessSafeKey> { |
| 33 |
|
| 34 |
if shared.to_bytes() == [0u8; 32] { |
| 35 |
return None; |
| 36 |
} |
| 37 |
let mut salt = [0u8; 64]; |
| 38 |
salt[..32].copy_from_slice(ephemeral); |
| 39 |
salt[32..].copy_from_slice(recipient); |
| 40 |
let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, &salt).extract(shared.as_bytes()); |
| 41 |
let okm = prk.expand(&[INFO], &CHACHA20_POLY1305).ok()?; |
| 42 |
let mut bytes = [0u8; 32]; |
| 43 |
okm.fill(&mut bytes).ok()?; |
| 44 |
Some(LessSafeKey::new(UnboundKey::new(&CHACHA20_POLY1305, &bytes).ok()?)) |
| 45 |
} |
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
pub fn seal(recipient: &[u8; 32], message: &[u8]) -> Option<Vec<u8>> { |
| 50 |
let e = new_secret(); |
| 51 |
let ephemeral = public_key(&e); |
| 52 |
let shared = MontgomeryPoint(*recipient).mul_clamped(e); |
| 53 |
let key = key(&shared, &ephemeral, recipient)?; |
| 54 |
let mut sealed = message.to_vec(); |
| 55 |
key.seal_in_place_append_tag(Nonce::assume_unique_for_key([0u8; 12]), Aad::empty(), &mut sealed).ok()?; |
| 56 |
let mut out = ephemeral.to_vec(); |
| 57 |
out.extend(sealed); |
| 58 |
Some(out) |
| 59 |
} |
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
pub fn open(secret: &[u8; 32], sealed: &[u8]) -> Option<Vec<u8>> { |
| 64 |
if sealed.len() < 32 + CHACHA20_POLY1305.tag_len() { |
| 65 |
return None; |
| 66 |
} |
| 67 |
let ephemeral: [u8; 32] = sealed[..32].try_into().ok()?; |
| 68 |
let shared = MontgomeryPoint(ephemeral).mul_clamped(*secret); |
| 69 |
let key = key(&shared, &ephemeral, &public_key(secret))?; |
| 70 |
let mut body = sealed[32..].to_vec(); |
| 71 |
let plain = key.open_in_place(Nonce::assume_unique_for_key([0u8; 12]), Aad::empty(), &mut body).ok()?; |
| 72 |
Some(plain.to_vec()) |
| 73 |
} |
| 74 |
|
| 75 |
#[cfg(test)] |
| 76 |
mod tests { |
| 77 |
use super::*; |
| 78 |
|
| 79 |
#[test] |
| 80 |
fn a_sealed_alert_opens_only_with_its_phones_key() { |
| 81 |
let phone = new_secret(); |
| 82 |
let sealed = seal(&public_key(&phone), b"run #5: test passed").unwrap(); |
| 83 |
assert_eq!(open(&phone, &sealed).as_deref(), Some(&b"run #5: test passed"[..])); |
| 84 |
|
| 85 |
assert_ne!(seal(&public_key(&phone), b"run #5: test passed").unwrap(), sealed); |
| 86 |
assert!(open(&new_secret(), &sealed).is_none(), "another phone"); |
| 87 |
let mut changed = sealed.clone(); |
| 88 |
*changed.last_mut().unwrap() ^= 1; |
| 89 |
assert!(open(&phone, &changed).is_none(), "changed on the way"); |
| 90 |
assert!(open(&phone, &sealed[..40]).is_none(), "cut short"); |
| 91 |
|
| 92 |
assert!(seal(&[0u8; 32], b"x").is_none()); |
| 93 |
} |
| 94 |
} |