Skip to main content

identity_kit/sd_jwt_vc/
hash.rs

1//! The hash algorithm of an SD-JWT (`_sd_alg`).
2
3use core::fmt;
4use core::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use sha2::Digest as _;
8
9use super::SdJwtError;
10use crate::jose::base64url;
11
12/// The hash algorithm an SD-JWT uses for its disclosure digests and its `sd_hash`, named as
13/// in the IANA "Named Information Hash Algorithm" registry (`sha-256`, the default and the
14/// one every implementation must support, `sha-384` or `sha-512`).
15///
16/// # Examples
17///
18/// ```
19/// use identity_kit::sd_jwt_vc::HashAlgorithm;
20///
21/// // RFC 9901 ยง4.2.3: the digest of the `family_name` disclosure.
22/// let disclosure = "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0";
23/// assert_eq!(
24///     HashAlgorithm::Sha256.digest(disclosure.as_bytes()),
25///     "X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0"
26/// );
27/// assert_eq!("sha-256".parse::<HashAlgorithm>()?, HashAlgorithm::Sha256);
28/// assert!("md5".parse::<HashAlgorithm>().is_err());
29/// # Ok::<(), identity_kit::sd_jwt_vc::SdJwtError>(())
30/// ```
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
32#[non_exhaustive]
33pub enum HashAlgorithm {
34    /// `sha-256`, the default when `_sd_alg` is absent.
35    #[default]
36    Sha256,
37    /// `sha-384`.
38    Sha384,
39    /// `sha-512`.
40    Sha512,
41}
42
43impl HashAlgorithm {
44    /// The claim carrying the algorithm, `_sd_alg`.
45    pub const CLAIM: &'static str = "_sd_alg";
46
47    /// The IANA hash name string.
48    pub const fn name(self) -> &'static str {
49        match self {
50            Self::Sha256 => "sha-256",
51            Self::Sha384 => "sha-384",
52            Self::Sha512 => "sha-512",
53        }
54    }
55
56    /// The algorithm with the IANA hash name `name`.
57    ///
58    /// # Errors
59    ///
60    /// [`SdJwtError::UnsupportedHashAlgorithm`] for any other name.
61    pub fn parse(name: &str) -> Result<Self, SdJwtError> {
62        match name {
63            "sha-256" => Ok(Self::Sha256),
64            "sha-384" => Ok(Self::Sha384),
65            "sha-512" => Ok(Self::Sha512),
66            _ => Err(SdJwtError::UnsupportedHashAlgorithm(name.to_owned())),
67        }
68    }
69
70    /// The base64url-encoded digest of `data`.
71    pub fn digest(self, data: &[u8]) -> String {
72        let digest = match self {
73            Self::Sha256 => sha2::Sha256::digest(data).to_vec(),
74            Self::Sha384 => sha2::Sha384::digest(data).to_vec(),
75            Self::Sha512 => sha2::Sha512::digest(data).to_vec(),
76        };
77        base64url::encode(&digest)
78    }
79}
80
81impl fmt::Display for HashAlgorithm {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        f.write_str(self.name())
84    }
85}
86
87impl FromStr for HashAlgorithm {
88    type Err = SdJwtError;
89
90    fn from_str(s: &str) -> Result<Self, Self::Err> {
91        Self::parse(s)
92    }
93}
94
95impl Serialize for HashAlgorithm {
96    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
97        serializer.serialize_str(self.name())
98    }
99}
100
101impl<'de> Deserialize<'de> for HashAlgorithm {
102    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
103        let name = String::deserialize(deserializer)?;
104        Self::parse(&name).map_err(serde::de::Error::custom)
105    }
106}