use bherror::Error;
use crate::error::FormatError;
pub(crate) const SD_JWT_DELIMITER: &str = "~";
#[derive(Debug)]
pub(crate) struct SdJwt {
pub(crate) jwt: String,
pub(crate) disclosures: Vec<String>,
}
#[derive(Debug)]
pub struct SdJwtKB {
pub(crate) sd_jwt: SdJwt,
pub(crate) key_binding_jwt: String,
}
impl SdJwt {
pub(crate) fn new(jwt: String, disclosures: Vec<String>) -> Self {
Self { jwt, disclosures }
}
}
impl SdJwtKB {
pub(crate) fn new(
sd_jwt: SdJwt,
key_binding_jwt: String,
) -> Result<Self, bherror::Error<FormatError>> {
if key_binding_jwt.is_empty() {
return Err(Error::root(FormatError::InvalidSdJwtFormat));
}
Ok(Self {
sd_jwt,
key_binding_jwt,
})
}
}
impl std::str::FromStr for SdJwt {
type Err = bherror::Error<FormatError>;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if !value.ends_with('~') {
return Err(Error::root(FormatError::InvalidSdJwtFormat));
}
let sd_jwt_parts: Vec<&str> = value.split(SD_JWT_DELIMITER).collect();
debug_assert!(sd_jwt_parts.last().unwrap().is_empty());
sd_jwt_from_parts(&sd_jwt_parts[0..sd_jwt_parts.len() - 1])
}
}
impl std::str::FromStr for SdJwtKB {
type Err = bherror::Error<FormatError>;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let sd_jwt_parts: Vec<&str> = value.split(SD_JWT_DELIMITER).collect();
let parts_len = sd_jwt_parts.len();
let sd_jwt = sd_jwt_from_parts(&sd_jwt_parts[0..parts_len - 1])?;
let key_binding_jwt = sd_jwt_parts[parts_len - 1];
Self::new(sd_jwt, key_binding_jwt.to_owned())
}
}
impl std::fmt::Display for SdJwt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.jwt, SD_JWT_DELIMITER)?;
for disclosure in &self.disclosures {
write!(f, "{}{}", disclosure, SD_JWT_DELIMITER)?;
}
Ok(())
}
}
impl std::fmt::Display for SdJwtKB {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}{}", self.sd_jwt, self.key_binding_jwt)?;
Ok(())
}
}
fn sd_jwt_from_parts(sd_jwt_parts: &[&str]) -> Result<SdJwt, bherror::Error<FormatError>> {
let sd_jwt_parts = sd_jwt_parts.split_first();
let Some((jwt, disclosures)) = sd_jwt_parts else {
return Err(Error::root(FormatError::InvalidSdJwtFormat));
};
let disclosures: Vec<String> = disclosures.iter().map(|&s| s.to_owned()).collect();
Ok(SdJwt::new(jwt.to_string(), disclosures))
}
#[cfg(test)]
mod test {
use super::*;
const JWT: &str = "\
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb\
IkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZ\
akg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBL\
dVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1\
SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tB\
TmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2\
Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFr\
b2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpn\
bGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu\
Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjog\
InVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15\
VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1\
ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjog\
InNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0y\
NTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VH\
ZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlG\
MkhaUSJ9fX0.7oEYwv1H4rBa54xAhDH19DEIy-RRSTdwyJvhbjOKVFyQeM0-gcgpwCq-\
yFCbWj9THEjD9M4yYkAeaWXfuvBS-Q";
const DISCLOSURE_1: &str = "WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd";
const DISCLOSURE_2: &str = "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0";
const KEY_BINDING_JWT: &str = "\
eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9.eyJub25jZSI6ICIxMjM0NTY\
3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaWVyLmV4YW1wbGUub3JnIiwgImlhdCI\
6IDE3MDIzMTYwMTUsICJzZF9oYXNoIjogIm5ZY09YeVA0M3Y5c3pLcnluX2tfNEdrUnJ\
fajNTVEhoTlNTLWkxRHVhdW8ifQ.12Qymun2geGbkYOwiV-DUVfS-zBBKqNe83yNbxM4\
5J93bno-oM7mph3L1-rPa4lFKQ04wB-T9rU3uAZnBAan5g";
#[test]
fn test_from_str_without_disclosures_without_kb_jwt() {
let sd_jwt_presentation = format!("{JWT}~");
let sd_jwt: SdJwt = sd_jwt_presentation.parse().unwrap();
assert!(sd_jwt.disclosures.is_empty());
assert_eq!(sd_jwt.jwt, JWT);
let error: Result<SdJwtKB, Error<FormatError>> = sd_jwt_presentation.parse();
assert_eq!(error.unwrap_err().error, FormatError::InvalidSdJwtFormat);
}
#[test]
fn test_from_str_without_disclosures_with_kb_jwt() {
let sd_jwt_kb_presentation = format!("{JWT}~{KEY_BINDING_JWT}");
let sd_jwt_kb: SdJwtKB = sd_jwt_kb_presentation.parse().unwrap();
assert!(sd_jwt_kb.sd_jwt.disclosures.is_empty());
assert_eq!(sd_jwt_kb.sd_jwt.jwt, JWT);
assert_eq!(sd_jwt_kb.key_binding_jwt, KEY_BINDING_JWT);
let error: Result<SdJwt, Error<FormatError>> = sd_jwt_kb_presentation.parse();
assert_eq!(error.unwrap_err().error, FormatError::InvalidSdJwtFormat);
}
#[test]
fn test_from_str_with_disclosures_without_kb_jwt() {
let sd_jwt_presentation = format!("{JWT}~{DISCLOSURE_1}~{DISCLOSURE_2}~");
let sd_jwt: SdJwt = sd_jwt_presentation.parse().unwrap();
assert_eq!(sd_jwt.disclosures.len(), 2);
assert_eq!(sd_jwt.jwt, JWT);
assert_eq!(sd_jwt.disclosures, &[DISCLOSURE_1, DISCLOSURE_2]);
let error: Result<SdJwtKB, Error<FormatError>> = sd_jwt_presentation.parse();
assert_eq!(error.unwrap_err().error, FormatError::InvalidSdJwtFormat);
}
#[test]
fn test_from_str_with_disclosures_with_kb_jwt() {
let sd_jwt_kb_presentation =
format!("{JWT}~{DISCLOSURE_1}~{DISCLOSURE_2}~{KEY_BINDING_JWT}");
let sd_jwt_kb: SdJwtKB = sd_jwt_kb_presentation.parse().unwrap();
assert_eq!(sd_jwt_kb.sd_jwt.disclosures.len(), 2);
assert_eq!(sd_jwt_kb.sd_jwt.jwt, JWT);
assert_eq!(sd_jwt_kb.sd_jwt.disclosures, &[DISCLOSURE_1, DISCLOSURE_2]);
assert_eq!(sd_jwt_kb.key_binding_jwt, KEY_BINDING_JWT);
let error: Result<SdJwt, Error<FormatError>> = sd_jwt_kb_presentation.parse();
assert_eq!(error.unwrap_err().error, FormatError::InvalidSdJwtFormat);
}
#[test]
fn test_from_str_invalid_format() {
let result = JWT.parse::<SdJwtKB>();
assert!(result.is_err());
let result = JWT.parse::<SdJwt>();
assert!(result.is_err());
}
#[test]
fn test_from_str_sd_jwt_kb_empty_key_binding() {
let sd_jwt_kb_with_empty_key_binding_presentation =
format!("{JWT}~{DISCLOSURE_1}~{DISCLOSURE_2}~");
let error: Result<SdJwtKB, Error<FormatError>> =
sd_jwt_kb_with_empty_key_binding_presentation.parse();
assert_eq!(error.unwrap_err().error, FormatError::InvalidSdJwtFormat);
}
#[test]
fn test_from_str_empty() {
let result = "".parse::<SdJwtKB>();
assert!(result.is_err());
let result = "".parse::<SdJwt>();
assert!(result.is_err());
}
#[test]
fn test_display_without_disclosures_without_kb_jwt() {
let sd_jwt = SdJwt::new(JWT.to_owned(), Vec::new());
let expected_presentation = format!("{JWT}~");
assert_eq!(sd_jwt.to_string(), expected_presentation);
}
#[test]
fn test_display_without_disclosures_with_kb_jwt() {
let sd_jwt = SdJwt {
jwt: JWT.to_owned(),
disclosures: Vec::new(),
};
let sd_jwt_kb = SdJwtKB::new(sd_jwt, KEY_BINDING_JWT.to_owned());
let expected_presentation = format!("{JWT}~{KEY_BINDING_JWT}");
assert_eq!(sd_jwt_kb.unwrap().to_string(), expected_presentation);
}
#[test]
fn test_display_with_disclosures_without_kb_jwt() {
let sd_jwt = SdJwt::new(
JWT.to_owned(),
vec![DISCLOSURE_1.to_owned(), DISCLOSURE_2.to_owned()],
);
let expected_presentation = format!("{JWT}~{DISCLOSURE_1}~{DISCLOSURE_2}~");
assert_eq!(sd_jwt.to_string(), expected_presentation);
}
#[test]
fn test_display_with_disclosures_with_kb_jwt() {
let sd_jwt = SdJwt {
jwt: JWT.to_owned(),
disclosures: vec![DISCLOSURE_1.to_owned(), DISCLOSURE_2.to_owned()],
};
let sd_jwt_kb = SdJwtKB::new(sd_jwt, KEY_BINDING_JWT.to_owned());
let expected_presentation =
format!("{JWT}~{DISCLOSURE_1}~{DISCLOSURE_2}~{KEY_BINDING_JWT}");
assert_eq!(sd_jwt_kb.unwrap().to_string(), expected_presentation);
}
}