use std::fmt;
use crypto_box::aead::{
generic_array::typenum::Unsigned,
AeadCore,
AeadInPlace
};
use crypto_box::{
ChaChaBox,
PublicKey,
SecretKey,
KEY_SIZE as PUBLIC_KEY_SIZE
};
use data_encoding::BASE64;
use serde::{
de::{self, Visitor},
Deserialize, Serialize,
};
use crate::control::KeyArg;
use crate::peer::{
Address,
NodeID
};
use crate::util::{
bincode_deserialize,
bincode_serialize_into
};
const NONCE_SIZE: usize = <ChaChaBox as AeadCore>::NonceSize::USIZE;
const TAG_SIZE: usize = <ChaChaBox as AeadCore>::TagSize::USIZE;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Invitation {
pub from: [u8; PUBLIC_KEY_SIZE],
nonce: [u8; NONCE_SIZE],
tag: [u8; TAG_SIZE],
ciphertext: Vec<u8>
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Content {
pub id: NodeID,
pub name: String,
pub key: KeyArg,
pub addresses: Vec<Address>
}
impl Invitation {
pub fn encrypt(key: &PublicKey, data: &Content) -> Result<Self, String> {
let our_private_key = SecretKey::from_bytes(rand::random());
let our_public_key = our_private_key.public_key();
let cipher = ChaChaBox::new(key, &our_private_key);
let nonce: [u8; NONCE_SIZE] = rand::random();
let mut buf = Vec::with_capacity(128);
bincode_serialize_into(&mut buf, data)
.map_err(|err| err.to_string())?;
let tag = cipher
.encrypt_in_place_detached(&nonce.into(), &[], &mut buf)
.map_err(|err| err.to_string())?
.into();
let from = our_public_key.to_bytes();
let ciphertext = buf;
Ok(Self { from, nonce, tag, ciphertext })
}
pub fn decrypt(mut self, key: &SecretKey) -> Result<Content, String> {
let their_public_key = PublicKey::from_bytes(self.from);
let cipher = ChaChaBox::new(&their_public_key, key);
cipher.decrypt_in_place_detached(
&self.nonce.into(),
&[],
&mut self.ciphertext,
&self.tag.into()
).map_err(|err| err.to_string())?;
bincode_deserialize(&self.ciphertext)
.map_err(|err| err.to_string())
}
pub fn into_bytes(self) -> Vec<u8> {
let mut vec = Vec::new();
vec.extend_from_slice(&self.from);
vec.extend_from_slice(&self.nonce);
vec.extend_from_slice(&self.tag);
vec.extend_from_slice(&self.ciphertext);
vec
}
#[cfg(test)]
pub(crate) fn random() -> Self {
let rcpt_key = PublicKey::from_bytes(rand::random());
Self::encrypt(&rcpt_key, &Content::random()).unwrap()
}
}
impl Content {
#[cfg(test)]
fn random() -> Self {
Self {
id: NodeID::random(),
name: "Test123".to_string(),
key: KeyArg::random(),
addresses: Vec::new()
}
}
}
impl std::str::FromStr for Invitation {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let vec = BASE64.decode(s.as_bytes()).map_err(|err| err.to_string())?;
Self::try_from(vec.as_slice()).map_err(|_| "too short!".to_owned())
}
}
impl fmt::Display for Invitation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut output = String::new();
let encoding = BASE64;
let mut encoder = encoding.new_encoder(&mut output);
encoder.append(&self.from);
encoder.append(&self.nonce);
encoder.append(&self.tag);
encoder.append(&self.ciphertext);
encoder.finalize();
f.write_str(&output)
}
}
pub struct TooShort;
impl TryFrom<&[u8]> for Invitation {
type Error = TooShort;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let (&from, rest) = value.split_first_chunk().ok_or(TooShort)?;
let (&nonce, rest) = rest.split_first_chunk().ok_or(TooShort)?;
let (&tag, rest) = rest.split_first_chunk().ok_or(TooShort)?;
let ciphertext = Vec::from(rest);
Ok(Self {from, nonce, tag, ciphertext})
}
}
impl Serialize for Invitation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
}
else {
let bytes = &[
&self.from[..],
&self.nonce,
&self.tag,
&self.ciphertext
];
let vec: Vec<u8> = bytes.iter()
.copied()
.flatten()
.copied()
.collect();
serializer.serialize_bytes(&vec)
}
}
}
impl<'de> Deserialize<'de> for Invitation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let human_readable = deserializer.is_human_readable();
let visitor = InvitationVisitor { human_readable };
if deserializer.is_human_readable() {
deserializer.deserialize_str(visitor)
}
else {
deserializer.deserialize_byte_buf(visitor)
}
}
}
struct InvitationVisitor {
human_readable: bool,
}
impl Visitor<'_> for InvitationVisitor {
type Value = Invitation;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
if self.human_readable {
formatter.write_str("a base64-encoded invitation")
}
else {
formatter.write_str("an invitation as raw bytes")
}
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
value.parse::<Invitation>().map_err(E::custom)
}
fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
Invitation::try_from(v)
.map_err(|_| E::custom("byte sequence too short"))
}
}
#[test]
fn human_readable() {
use crate::control::Command;
let invitation = Invitation::random();
let serialized = serde_json::to_string(&invitation).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(invitation, deserialized);
let serialized = serde_json::to_value(&invitation).unwrap();
dbg!(&serialized);
assert!(serialized.is_string());
let deserialized = serde_json::from_value(serialized).unwrap();
assert_eq!(invitation, deserialized);
let cmd = Command::Accept(invitation.clone());
let serialized = serde_json::to_string(&cmd).unwrap();
dbg!(&serialized);
let deserialized = serde_json::from_str(&serialized).unwrap();
let Command::Accept(inv_deserialized) = deserialized else {panic!();};
assert_eq!(invitation, inv_deserialized);
let example = serde_json::json!(
"qoNFKuxatD48taSHyuPBYdACi4dtoHUG74yOH2MdvVGFxBH0\
nbc9PD2RBu6+4F0/xhJoVK1+GUNlQjy+2PLXG+WLO0KtNnlU"
);
let _ = serde_json::from_value::<Invitation>(example).unwrap();
const EXAMPLE: &str = "\"qoNFKuxatD48taSHyuPBYdACi4dtoHUG74yOH2MdvVGFxBH0\
nbc9PD2RBu6+4F0/xhJoVK1+GUNlQjy+2PLXG+WLO0KtNnlU\"";
let _ = serde_json::from_str::<Invitation>(EXAMPLE).unwrap();
}
#[test]
fn round_trip() {
use crate::util::bincode_serialize;
let rx_privkey = SecretKey::from_bytes(rand::random());
let rx_pubkey = rx_privkey.public_key();
let content = Content {
id: NodeID::random(),
name: "Test123".to_string(),
key: KeyArg::random(),
addresses: Vec::new()
};
let invitation = Invitation::encrypt(&rx_pubkey, &content).unwrap();
let serialized = serde_json::to_string(&invitation).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(invitation, deserialized);
let serialized = bincode_serialize(&deserialized).unwrap();
let deserialized = bincode_deserialize(&serialized).unwrap();
assert_eq!(invitation, deserialized);
let serialized = serde_json::to_string(&deserialized).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(invitation, deserialized);
let decrypted = deserialized.decrypt(&rx_privkey).unwrap();
assert_eq!(content, decrypted);
}