use crate::*;
pub const KEYBYTES: usize =
libsodium_sys::crypto_secretstream_xchacha20poly1305_KEYBYTES as usize;
pub const ABYTES: usize =
libsodium_sys::crypto_secretstream_xchacha20poly1305_ABYTES as usize;
pub const HEADERBYTES: usize =
libsodium_sys::crypto_secretstream_xchacha20poly1305_HEADERBYTES as usize;
#[repr(u8)]
#[derive(Debug, PartialEq, Eq)]
pub enum Tag {
Message =
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_MESSAGE as u8,
Push = libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_PUSH as u8,
Rekey =
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_REKEY as u8,
Final =
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_FINAL as u8,
}
pub struct State(libsodium_sys::crypto_secretstream_xchacha20poly1305_state);
impl Default for State {
fn default() -> Self {
Self(libsodium_sys::crypto_secretstream_xchacha20poly1305_state {
k: Default::default(),
nonce: Default::default(),
_pad: Default::default(),
})
}
}
pub fn init_push(
state: &mut State,
header: &mut [u8; HEADERBYTES],
key: &[u8; KEYBYTES],
) -> Result<()> {
crate::sodium_init();
let res = unsafe {
libsodium_sys::crypto_secretstream_xchacha20poly1305_init_push(
&mut state.0,
raw_ptr_char!(header),
raw_ptr_char_immut!(key),
)
};
if res == 0_i32 {
Ok(())
} else {
Err(Error::other("internal"))
}
}
pub fn push(
state: &mut State,
cipher: &mut [u8],
message: &[u8],
adata: Option<&[u8]>,
tag: Tag,
) -> Result<()> {
let (adata_len, adata) = match adata {
Some(adata) => {
(adata.len() as libc::c_ulonglong, raw_ptr_char_immut!(adata))
}
None => (0, std::ptr::null()),
};
if cipher.len() != message.len() + ABYTES {
return Err(Error::other("invalid cipher len"));
}
crate::sodium_init();
let res = unsafe {
let message_len = message.len() as libc::c_ulonglong;
libsodium_sys::crypto_secretstream_xchacha20poly1305_push(
&mut state.0,
raw_ptr_char!(cipher),
std::ptr::null_mut(),
raw_ptr_char_immut!(message),
message_len,
adata,
adata_len,
tag as u8,
)
};
if res == 0_i32 {
Ok(())
} else {
Err(Error::other("internal"))
}
}
pub fn init_pull(
state: &mut State,
header: &[u8; HEADERBYTES],
key: &[u8; KEYBYTES],
) -> Result<()> {
crate::sodium_init();
let res = unsafe {
libsodium_sys::crypto_secretstream_xchacha20poly1305_init_pull(
&mut state.0,
raw_ptr_char_immut!(header),
raw_ptr_char_immut!(key),
)
};
if res == 0_i32 {
Ok(())
} else {
Err(Error::other("internal"))
}
}
pub fn pull(
state: &mut State,
message: &mut [u8],
cipher: &[u8],
adata: Option<&[u8]>,
) -> Result<Tag> {
if message.len() != cipher.len() - ABYTES {
return Err(Error::other("invalid message len"));
}
let mut tag = 0;
let (adata_len, adata) = match adata {
Some(adata) => {
(adata.len() as libc::c_ulonglong, raw_ptr_char_immut!(adata))
}
None => (0, std::ptr::null()),
};
crate::sodium_init();
let res = unsafe {
let cipher_len = cipher.len() as libc::c_ulonglong;
libsodium_sys::crypto_secretstream_xchacha20poly1305_pull(
&mut state.0,
raw_ptr_char!(message),
std::ptr::null_mut(),
&mut tag,
raw_ptr_char_immut!(cipher),
cipher_len,
adata,
adata_len,
)
};
if res == 0_i32 {
let tag = match tag as u32 {
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
=> Tag::Message,
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_PUSH
=> Tag::Push,
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_REKEY
=> Tag::Rekey,
libsodium_sys::crypto_secretstream_xchacha20poly1305_TAG_FINAL
=> Tag::Final,
_ => return Err(Error::other("recv invalid tag")),
};
Ok(tag)
} else {
Err(Error::other("internal"))
}
}
pub fn rekey(state: &mut State) {
crate::sodium_init();
unsafe {
libsodium_sys::crypto_secretstream_xchacha20poly1305_rekey(
&mut state.0,
);
}
}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn secretstream() {
let mut p1 = [0; sign::PUBLICKEYBYTES];
let mut s1 = SizedLockedArray::new().unwrap();
sign::keypair(&mut p1, &mut s1.lock()).unwrap();
let mut xp1 = [0; kx::PUBLICKEYBYTES];
let mut xs1 = SizedLockedArray::new().unwrap();
sign::pk_to_curve25519(&mut xp1, &p1).unwrap();
sign::sk_to_curve25519(&mut xs1.lock(), &s1.lock()).unwrap();
let mut p2 = [0; sign::PUBLICKEYBYTES];
let mut s2 = SizedLockedArray::new().unwrap();
sign::keypair(&mut p2, &mut s2.lock()).unwrap();
let mut xp2 = [0; kx::PUBLICKEYBYTES];
let mut xs2 = SizedLockedArray::new().unwrap();
sign::pk_to_curve25519(&mut xp2, &p2).unwrap();
sign::sk_to_curve25519(&mut xs2.lock(), &s2.lock()).unwrap();
let mut srx = SizedLockedArray::new().unwrap();
let mut stx = SizedLockedArray::new().unwrap();
kx::server_session_keys(
&mut srx.lock(),
&mut stx.lock(),
&xp1,
&xs1.lock(),
&xp2,
)
.unwrap();
let mut crx = SizedLockedArray::new().unwrap();
let mut ctx = SizedLockedArray::new().unwrap();
kx::client_session_keys(
&mut crx.lock(),
&mut ctx.lock(),
&xp2,
&xs2.lock(),
&xp1,
)
.unwrap();
assert_eq!(*srx.lock(), *ctx.lock());
assert_eq!(*stx.lock(), *crx.lock());
let mut sstate = secretstream::State::default();
let mut header = [0; secretstream::HEADERBYTES];
secretstream::init_push(&mut sstate, &mut header, &mut stx.lock())
.unwrap();
let mut cstate = secretstream::State::default();
secretstream::init_pull(&mut cstate, &header, &mut crx.lock()).unwrap();
let message = b"hello";
let mut cipher = vec![0; secretstream::ABYTES + message.len()];
secretstream::push(
&mut sstate,
&mut cipher,
message,
None,
secretstream::Tag::Message,
)
.unwrap();
let mut rmsg = vec![0; cipher.len() - secretstream::ABYTES];
let rtag =
secretstream::pull(&mut cstate, &mut rmsg, &cipher, None).unwrap();
assert_eq!(secretstream::Tag::Message, rtag);
assert_eq!(&rmsg, b"hello");
}
}