akv_cli/jose/
mod.rs

1// Copyright 2025 Heath Stewart.
2// Licensed under the MIT License. See LICENSE.txt in the project root for license information.
3
4#![allow(non_camel_case_types)]
5
6mod jwe;
7
8use std::fmt;
9
10use crate::Result;
11use azure_core::{
12    base64,
13    json::{from_json, to_json},
14};
15use clap::ValueEnum;
16pub use jwe::{Jwe, JweEncryptor, WrapKeyResult};
17use serde::{de::DeserializeOwned, Deserialize, Serialize};
18
19pub trait Encode
20where
21    Self: Sized,
22{
23    fn decode(value: &str) -> Result<Self>;
24    fn encode(&self) -> Result<String>;
25}
26
27impl<T: DeserializeOwned + Serialize> Encode for T {
28    fn decode(value: &str) -> Result<Self> {
29        let buf = base64::decode_url_safe(value)?;
30        Ok(from_json(buf)?)
31    }
32
33    fn encode(&self) -> Result<String> {
34        let buf = to_json(self)?;
35        Ok(base64::encode_url_safe(buf))
36    }
37}
38
39/// A JSON web algorithm header that precedes a JWE or JWS.
40#[derive(Debug, Serialize, Deserialize)]
41pub struct Header {
42    /// For JWEs, the algorithm used to encrypt the content encryption key (CEK).
43    ///
44    /// Only algorithms supported fully on Key Vault are supported.
45    #[serde(rename = "alg")]
46    pub alg: Algorithm,
47
48    /// For JWEs, the algorithm used to encrypt content with the content encryption key (CEK).
49    #[serde(rename = "enc", skip_serializing_if = "Option::is_none")]
50    pub enc: Option<EncryptionAlgorithm>,
51
52    /// The ID of the key in Key Vault used for [`Header::alg`].
53    #[serde(rename = "kid", skip_serializing_if = "Option::is_none")]
54    pub kid: Option<String>,
55
56    /// The type of the payload following this header.
57    #[serde(rename = "typ")]
58    pub typ: Type,
59}
60
61#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "UPPERCASE")]
63pub enum Type {
64    JWE,
65    #[serde(untagged)]
66    Other(String),
67}
68
69impl fmt::Display for Type {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::JWE => f.write_str("JWE"),
73            Self::Other(s) => f.write_str(s),
74        }
75    }
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ValueEnum)]
79#[serde(rename_all = "UPPERCASE")]
80pub enum Algorithm {
81    // JWS algorithms
82    // RS256,
83    // RS384,
84    // RS512,
85    // ES256,
86    // ES384,
87    // ES512,
88    // PS256,
89    // PS384,
90    // PS512,
91
92    // JWE algorithms
93    #[serde(rename = "RSA1_5")]
94    RSA1_5,
95    #[serde(rename = "RSA-OAEP")]
96    RSA_OAEP,
97    #[serde(rename = "RSA-OAEP-256")]
98    RSA_OAEP_256,
99
100    #[serde(untagged)]
101    #[value(skip)]
102    Other(String),
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ValueEnum)]
106#[serde(rename_all = "UPPERCASE")]
107pub enum EncryptionAlgorithm {
108    // A128CBC_HS256,
109    // A192CBC_HS384,
110    // A256CBC_HS512,
111    A128GCM,
112    A192GCM,
113    A256GCM,
114
115    #[serde(untagged)]
116    #[value(skip)]
117    Other(String),
118}
119
120impl fmt::Display for EncryptionAlgorithm {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::A128GCM => f.write_str("A128GCM"),
124            Self::A192GCM => f.write_str("A192GCM"),
125            Self::A256GCM => f.write_str("A256GCM"),
126            Self::Other(s) => f.write_str(s),
127        }
128    }
129}
130
131#[derive(Debug)]
132#[doc(hidden)]
133pub enum Set {}
134
135#[derive(Debug)]
136#[doc(hidden)]
137pub enum Unset {}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use serde_json::json;
143
144    #[test]
145    fn header_encode_decode() {
146        let header = Header {
147            alg: Algorithm::RSA_OAEP_256,
148            enc: Some(EncryptionAlgorithm::A256GCM),
149            kid: Some("https://myvault.vault.azure.net/keys/mykey/1234567890abcdef".into()),
150            typ: Type::JWE,
151        };
152
153        let expected_json = json!({
154            "alg": "RSA-OAEP-256",
155            "enc": "A256GCM",
156            "kid": "https://myvault.vault.azure.net/keys/mykey/1234567890abcdef",
157            "typ": "JWE"
158        });
159        let expected_bytes = serde_json::to_vec(&expected_json).unwrap();
160        let expected_b64 = azure_core::base64::encode_url_safe(expected_bytes);
161
162        // Encode
163        let encoded = header.encode().unwrap();
164        assert_eq!(encoded, expected_b64);
165
166        // Decode
167        let decoded = Header::decode(&encoded).unwrap();
168        assert_eq!(decoded.alg, Algorithm::RSA_OAEP_256);
169        assert_eq!(decoded.enc, Some(EncryptionAlgorithm::A256GCM));
170        assert_eq!(
171            decoded.kid,
172            Some("https://myvault.vault.azure.net/keys/mykey/1234567890abcdef".into()),
173        );
174        assert_eq!(decoded.typ, Type::JWE);
175    }
176}