Skip to main content

affinidi_crypto/
jwk.rs

1//! JWK (JSON Web Key) types per RFC 7517
2
3use affinidi_encoding::{ED25519_PUB, MultiEncoded, P256_PUB, P384_PUB, SECP256K1_PUB, X25519_PUB};
4use serde::{Deserialize, Serialize};
5use zeroize::{Zeroize, ZeroizeOnDrop};
6
7use crate::{CryptoError, KeyType};
8
9/// RFC 7517 JWK Struct
10#[derive(Debug, Serialize, Deserialize, Clone, Zeroize, ZeroizeOnDrop)]
11pub struct JWK {
12    #[serde(rename = "kid")]
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub key_id: Option<String>,
15    #[serde(flatten)]
16    pub params: Params,
17}
18
19impl JWK {
20    /// Returns the KeyType for a JWK
21    pub fn key_type(&self) -> KeyType {
22        match &self.params {
23            Params::EC(params) => match params.curve.as_str() {
24                "P-256" => KeyType::P256,
25                "secp256k1" => KeyType::Secp256k1,
26                "P-384" => KeyType::P384,
27                _ => KeyType::Unknown,
28            },
29            Params::OKP(params) => match params.curve.as_str() {
30                "Ed25519" => KeyType::Ed25519,
31                "X25519" => KeyType::X25519,
32                _ => KeyType::Unknown,
33            },
34        }
35    }
36
37    /// Converts a multikey string into a JWK struct
38    pub fn from_multikey(key: &str) -> Result<Self, CryptoError> {
39        // decode multibase
40        let (_, data) = multibase::decode(key)
41            .map_err(|e| CryptoError::Decoding(format!("Failed to decode multibase: {e}")))?;
42
43        // decode multicodec
44        let decoded = MultiEncoded::new(&data)?;
45
46        match decoded.codec() {
47            #[cfg(feature = "p256")]
48            P256_PUB => crate::p256::public_jwk(decoded.data()),
49            #[cfg(feature = "p384")]
50            P384_PUB => crate::p384::public_jwk(decoded.data()),
51            #[cfg(feature = "k256")]
52            SECP256K1_PUB => crate::secp256k1::public_jwk(decoded.data()),
53            #[cfg(feature = "ed25519")]
54            ED25519_PUB => crate::ed25519::public_jwk(decoded.data()),
55            #[cfg(feature = "ed25519")]
56            X25519_PUB => crate::ed25519::x25519_public_jwk(decoded.data()),
57            codec => Err(CryptoError::UnsupportedKeyType(format!(
58                "Unsupported key type codec (0x{codec:x})"
59            ))),
60        }
61    }
62}
63
64/// JWK Key Types and associated Parameters
65#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Zeroize, ZeroizeOnDrop)]
66#[serde(tag = "kty")]
67pub enum Params {
68    EC(ECParams),
69    OKP(OctectParams),
70}
71
72/// Elliptic Curve parameters (P-256, P-384, secp256k1)
73#[derive(Debug, Serialize, Deserialize, Clone, Zeroize, PartialEq, ZeroizeOnDrop)]
74pub struct ECParams {
75    #[serde(rename = "crv")]
76    pub curve: String,
77    pub x: String,
78    pub y: String,
79    pub d: Option<String>,
80}
81
82/// Octet Key Pair parameters (Ed25519, X25519)
83#[derive(Debug, Serialize, Deserialize, Clone, Zeroize, PartialEq, ZeroizeOnDrop)]
84pub struct OctectParams {
85    #[serde(rename = "crv")]
86    pub curve: String,
87    pub x: String,
88    pub d: Option<String>,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn deserialize_okp_jwk() {
97        let raw = r#"{
98            "crv": "Ed25519",
99            "d": "jybTAuX6NlN7cJLWNCSOLUnJpblpsGr05TTp7scjSvE",
100            "kty": "OKP",
101            "x": "Xx4_L89E6RsyvDTzN9wuN3cDwgifPkXMgFJv_HMIxdk"
102        }"#;
103
104        let jwk: JWK = serde_json::from_str(raw).expect("Couldn't deserialize JWK");
105
106        assert_eq!(
107            jwk.params,
108            Params::OKP(OctectParams {
109                curve: "Ed25519".to_string(),
110                x: "Xx4_L89E6RsyvDTzN9wuN3cDwgifPkXMgFJv_HMIxdk".to_string(),
111                d: Some("jybTAuX6NlN7cJLWNCSOLUnJpblpsGr05TTp7scjSvE".to_string())
112            })
113        );
114    }
115
116    #[test]
117    fn deserialize_ec_jwk() {
118        let raw = r#"{
119            "crv": "P-256",
120            "d": "kQrTUKhBU-6bHbCdiY0dIfg3knd5U2-1FlLGGHSbF6U",
121            "kty": "EC",
122            "x": "sl56LMzaiR5efwwWU1jzC_dfbxQ8gzyLj_N1q2cJmkE",
123            "y": "UnAimUtlHMPj_T_wIDVPoJAolKHy8DoXXTb8wch4hgU"
124        }"#;
125
126        let jwk: JWK = serde_json::from_str(raw).expect("Couldn't deserialize JWK");
127
128        assert_eq!(
129            jwk.params,
130            Params::EC(ECParams {
131                curve: "P-256".to_string(),
132                x: "sl56LMzaiR5efwwWU1jzC_dfbxQ8gzyLj_N1q2cJmkE".to_string(),
133                y: "UnAimUtlHMPj_T_wIDVPoJAolKHy8DoXXTb8wch4hgU".to_string(),
134                d: Some("kQrTUKhBU-6bHbCdiY0dIfg3knd5U2-1FlLGGHSbF6U".to_string())
135            })
136        );
137    }
138
139    #[test]
140    fn from_multikey_secp256k1() {
141        assert!(JWK::from_multikey("zQ3shT2ynSjzY5XoTxhWHvYVZ6GiLWhBVincVekcEpZDRCBHV").is_ok());
142    }
143
144    #[test]
145    fn from_multikey_p256() {
146        assert!(JWK::from_multikey("zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169").is_ok());
147    }
148
149    #[test]
150    fn from_multikey_p384() {
151        assert!(
152            JWK::from_multikey(
153                "z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9"
154            )
155            .is_ok()
156        );
157    }
158
159    #[test]
160    fn from_multikey_ed25519() {
161        assert!(JWK::from_multikey("z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp").is_ok());
162    }
163}