otplus-core 0.0.3

Core cryptographic library for OTPlus - A secure one-time password and key derivation system
Documentation
use chacha20poly1305::{aead::{KeyInit, OsRng}, Key, ChaCha20Poly1305};
use zeroize::Zeroize;
use serde::{Serialize, Serializer, Deserialize, Deserializer};
use serde::de::Error;
use base64::{engine::general_purpose::STANDARD, Engine};
use serde::ser::SerializeStruct;
use chrono::Utc;

#[derive(Debug, Clone)]
pub struct RootKeyMetadata {
    pub created_at: i64,
    pub key_length: u32,
}

impl Serialize for RootKeyMetadata {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("RootKeyMetadata", 2)?;
        state.serialize_field("created_at", &self.created_at)?;
        state.serialize_field("key_length", &self.key_length)?;
        state.end()
    }
}

impl<'de> Deserialize<'de> for RootKeyMetadata {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct RootKeyMetadataHelper {
            created_at: i64,
            key_length: u32,
        }

        let helper = RootKeyMetadataHelper::deserialize(deserializer)?;
        Ok(Self { created_at: helper.created_at, key_length: helper.key_length })
    }
}

#[derive(Debug, Clone)]
pub struct RootKey {
    pub value: Key,
    pub metadata: RootKeyMetadata,
}

impl Serialize for RootKey {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("RootKey", 2)?;
        state.serialize_field("value", &STANDARD.encode(&self.value.as_slice()))?;
        state.serialize_field("metadata", &self.metadata)?;
        state.end()
    }
}

impl<'de> Deserialize<'de> for RootKey {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        struct RootKeyHelper {
            value: String,
            metadata: RootKeyMetadata,
        }

        let helper = RootKeyHelper::deserialize(deserializer)?;
        let key = STANDARD.decode(&helper.value)
            .map_err(|e| Error::custom(e.to_string()))?;
        
        if key.len() != 32 {
            return Err(Error::custom("Invalid key length"));
        }

        Ok(Self { value: Key::from_slice(&key).clone(), metadata: helper.metadata })
    }
}

impl Drop for RootKey {
    fn drop(&mut self) {
        self.value.as_mut_slice().zeroize();
    }
}

impl Default for RootKey {
    fn default() -> Self {
        let mut os_rng = OsRng::default();
        let value = ChaCha20Poly1305::generate_key(&mut os_rng);
        Self { value, metadata: RootKeyMetadata { created_at: Utc::now().timestamp_millis(), key_length: 32 } }
    }
}

#[cfg(test)]
mod root_key_tests {
    use super::*;

    #[test]
    fn test_data_key_default() {
        let dek = RootKey::default();
        assert_eq!(dek.value.as_slice().len(), 32);
        assert_eq!(dek.metadata.key_length, 32);
    }

    #[test]
    fn test_data_key_serialize_deserialize() {
        let dek = RootKey::default();
        let serialized = serde_json::to_string(&dek).unwrap();
        println!("Serialized: {}", serialized);
        let deserialized: RootKey = serde_json::from_str(&serialized).unwrap();
        println!("Deserialized: {:?}", deserialized);
    }
}