sdjwt 0.8.1

SD-JWT support for Issuers, Holders, and Verifiers
Documentation
use crate::algorithm::{base64_hash, generate_salt, HashAlgorithm};
use crate::error::Error;
use base64::Engine;
use serde::{Deserialize, Serialize};
use serde_json::Value;

const ARRAY_DISCLOSURE_LEN: usize = 2;
const OBJECT_DISCLOSURE_LEN: usize = 3;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Disclosure {
    disclosure: String,
    digest: String,
    key: Option<String>,
    value: Value,
    salt_len: usize,
    algorithm: HashAlgorithm,
}

impl Disclosure {
    const DEFAULT_SALT_LEN: usize = 16;
    const DEFAULT_ALGORITHM: HashAlgorithm = HashAlgorithm::SHA256;

    pub fn new(key: Option<String>, value: Value) -> Self {
        Disclosure {
            disclosure: "".to_string(),
            digest: String::new(),
            key,
            value,
            salt_len: Disclosure::DEFAULT_SALT_LEN,
            algorithm: Disclosure::DEFAULT_ALGORITHM,
        }
    }

    pub fn salt_len(mut self, salt_len: usize) -> Self {
        self.salt_len = salt_len;
        self
    }

    pub fn algorithm(mut self, algorithm: HashAlgorithm) -> Self {
        self.algorithm = algorithm;
        self
    }

    pub fn get_algorithm(&self) -> HashAlgorithm {
        self.algorithm
    }

    pub fn build(self) -> Result<Disclosure, Error> {
        let mut parts: Vec<Value> = Vec::with_capacity(3);
        let salt = generate_salt(self.salt_len);
        parts.push(salt.into());

        match self.key.as_deref() {
            Some("_sd") | Some("...") => {
                return Err(Error::InvalidDisclosureKey(self.key.unwrap()));
            }
            Some(k) => parts.push(k.into()),
            None => {}
        }

        parts.push(self.value.clone());
        let disclosure =
            base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(serde_json::to_vec(&parts)?);
        let digest = base64_hash(self.algorithm, &disclosure);
        Ok(Disclosure {
            disclosure,
            digest,
            key: self.key,
            value: self.value,
            salt_len: self.salt_len,
            algorithm: self.algorithm,
        })
    }

    pub fn disclosure(&self) -> &str {
        &self.disclosure
    }

    pub fn digest(&self) -> &String {
        &self.digest
    }

    pub fn key(&self) -> &Option<String> {
        &self.key
    }

    pub fn value(&self) -> &Value {
        &self.value
    }

    pub fn from_base64(disclosure: &str, algorithm: HashAlgorithm) -> Result<Disclosure, Error> {
        let decoded_disclosure =
            base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(disclosure)?;
        let decoded_disclosure = String::from_utf8(decoded_disclosure)?;

        let disclosure_json: Value = serde_json::from_str(decoded_disclosure.as_str())?;
        let disclosure_array = disclosure_json
            .as_array()
            .ok_or(Error::InvalidDisclosureFormat(disclosure.to_string()))?;
        match disclosure_array.len() {
            ARRAY_DISCLOSURE_LEN | OBJECT_DISCLOSURE_LEN => {
                reconstruct_disclosure(disclosure, algorithm, disclosure_array.as_slice())
            }
            _ => Err(Error::InvalidDisclosureFormat(disclosure.to_string())),
        }
    }
}

pub fn reconstruct_disclosure(
    disclosure: &str,
    algorithm: HashAlgorithm,
    disclosure_array: &[Value],
) -> Result<Disclosure, Error> {
    let digest = base64_hash(algorithm, disclosure);
    let key = if disclosure_array.len() == OBJECT_DISCLOSURE_LEN {
        Some(disclosure_array[1].as_str().unwrap_or_default().to_string())
    } else {
        None
    };
    let value = disclosure_array.last().unwrap().clone();

    Ok(Disclosure {
        disclosure: disclosure.to_string(),
        digest,
        key,
        value,
        salt_len: 0,
        algorithm,
    })
}

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

    #[test]
    fn test_new_disclosure() {
        let value = serde_json::json!({"test": "value"});
        let disclosure = Disclosure::new(Some("key".to_string()), value.clone());
        assert_eq!(disclosure.salt_len, Disclosure::DEFAULT_SALT_LEN);
        assert_eq!(disclosure.algorithm, Disclosure::DEFAULT_ALGORITHM);
        assert_eq!(disclosure.key, Some("key".to_string()));
        assert_eq!(disclosure.value, value);
    }

    #[test]
    fn test_salt_len_setter() {
        let salt_length = 32;
        let disclosure =
            Disclosure::new(Some("key".to_string()), serde_json::Value::Null).salt_len(salt_length);
        assert_eq!(disclosure.salt_len, salt_length);
    }

    #[test]
    fn test_algorithm_setter() {
        let algorithm = HashAlgorithm::SHA512;
        let disclosure =
            Disclosure::new(Some("key".to_string()), serde_json::Value::Null).algorithm(algorithm);
        assert_eq!(disclosure.algorithm, algorithm);
    }

    #[test]
    fn test_build_success() {
        let disclosure = Disclosure::new(Some("key".to_string()), serde_json::Value::Null).build();
        assert!(disclosure.is_ok());
    }

    #[test]
    fn test_build_invalid_key_error() {
        let disclosure = Disclosure::new(Some("_sd".to_string()), serde_json::Value::Null).build();
        assert!(disclosure.is_err());
    }

    #[test]
    fn test_disclosure_and_digest() {
        let disclosure = Disclosure::new(Some("".to_string()), serde_json::Value::Null)
            .build()
            .unwrap();
        assert!(!disclosure.disclosure().is_empty());
        assert!(!disclosure.digest().is_empty());
    }

    #[test]
    fn test_from_base64_for_object() {
        let disclosure = Disclosure::new(Some("key".to_string()), serde_json::json!("some value"))
            .build()
            .unwrap();
        println!("disclosure: {}", disclosure.disclosure());
        let disclosure_from_base64 =
            Disclosure::from_base64(disclosure.disclosure(), disclosure.algorithm).unwrap();
        assert_eq!(disclosure_from_base64.disclosure(), disclosure.disclosure());
        assert_eq!(disclosure_from_base64.digest(), disclosure.digest());
        assert_eq!(disclosure_from_base64.key, disclosure.key);
        assert_eq!(disclosure_from_base64.value, disclosure.value);
        assert_eq!(disclosure_from_base64.algorithm, disclosure.algorithm);
    }

    #[test]
    fn test_from_base64_for_array() {
        let disclosure = Disclosure::new(None, serde_json::json!("some value"))
            .build()
            .unwrap();
        println!("disclosure: {}", disclosure.disclosure());
        let disclosure_from_base64 =
            Disclosure::from_base64(disclosure.disclosure(), disclosure.algorithm).unwrap();
        assert_eq!(disclosure_from_base64.disclosure(), disclosure.disclosure());
        assert_eq!(disclosure_from_base64.digest(), disclosure.digest());
        assert_eq!(disclosure_from_base64.key, disclosure.key);
        assert_eq!(disclosure_from_base64.value, disclosure.value);
        assert_eq!(disclosure_from_base64.algorithm, disclosure.algorithm);
    }
}