akv-cli 0.10.1

The Azure Key Vault CLI (unofficial) can read secrets from Key Vault, securely pass secrets to other commands or inject them into configuration files, encrypt and decrypt secrets, and managed keys and secrets in Key Vault.
Documentation
// Copyright 2025 Heath Stewart.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

#![allow(non_camel_case_types)]

//! Basic JSON Web Encryption support.

mod jwe;

use std::fmt;

use crate::Result;
use azure_core::{
    base64,
    json::{from_json, to_json},
};
use clap::ValueEnum;
pub use jwe::{Jwe, JweEncryptor, WrapKeyResult};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

/// Implemented by types that can encode and decode data.
pub trait Encode
where
    Self: Sized,
{
    /// Decode data from a string.
    fn decode(value: &str) -> Result<Self>;

    /// Encode data to a string.
    fn encode(&self) -> Result<String>;
}

impl<T: DeserializeOwned + Serialize> Encode for T {
    fn decode(value: &str) -> Result<Self> {
        let buf = base64::decode_url_safe(value)?;
        Ok(from_json(buf)?)
    }

    fn encode(&self) -> Result<String> {
        let buf = to_json(self)?;
        Ok(base64::encode_url_safe(buf))
    }
}

/// A JSON web algorithm header that precedes a JWE or JWS.
#[derive(Debug, Serialize, Deserialize)]
pub struct Header {
    /// For JWEs, the algorithm used to encrypt the content encryption key (CEK).
    ///
    /// Only algorithms supported fully on Key Vault are supported.
    #[serde(rename = "alg")]
    pub alg: Algorithm,

    /// For JWEs, the algorithm used to encrypt content with the content encryption key (CEK).
    #[serde(rename = "enc", skip_serializing_if = "Option::is_none")]
    pub enc: Option<EncryptionAlgorithm>,

    /// The ID of the key in Key Vault used for [`Header::alg`].
    #[serde(rename = "kid", skip_serializing_if = "Option::is_none")]
    pub kid: Option<String>,

    /// The type of the payload following this header.
    #[serde(rename = "typ")]
    pub typ: Type,
}

/// The type of JWE.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum Type {
    /// JSON Web Encryption
    JWE,

    /// Other type
    #[serde(untagged)]
    Other(String),
}

impl fmt::Display for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::JWE => f.write_str("JWE"),
            Self::Other(s) => f.write_str(s),
        }
    }
}

/// Supported JWE algorithms
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ValueEnum)]
#[serde(rename_all = "UPPERCASE")]
pub enum Algorithm {
    // JWS algorithms
    // RS256,
    // RS384,
    // RS512,
    // ES256,
    // ES384,
    // ES512,
    // PS256,
    // PS384,
    // PS512,

    // JWE algorithms
    /// RSA1_5
    #[serde(rename = "RSA1_5")]
    RSA1_5,

    /// RSA-OAEP
    #[serde(rename = "RSA-OAEP")]
    RSA_OAEP,

    /// RSA-OAEP-256
    #[serde(rename = "RSA-OAEP-256")]
    RSA_OAEP_256,

    /// Other algorithm
    #[serde(untagged)]
    #[value(skip)]
    Other(String),
}

/// Supported JWE encryption algorithms.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, ValueEnum)]
#[serde(rename_all = "UPPERCASE")]
pub enum EncryptionAlgorithm {
    // A128CBC_HS256,
    // A192CBC_HS384,
    // A256CBC_HS512,
    /// A128GCM
    A128GCM,

    /// A192GCM
    A192GCM,

    /// A256GCM
    A256GCM,

    /// Other JWE encryption algorithm
    #[serde(untagged)]
    #[value(skip)]
    Other(String),
}

impl fmt::Display for EncryptionAlgorithm {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::A128GCM => f.write_str("A128GCM"),
            Self::A192GCM => f.write_str("A192GCM"),
            Self::A256GCM => f.write_str("A256GCM"),
            Self::Other(s) => f.write_str(s),
        }
    }
}

#[derive(Debug)]
#[doc(hidden)]
pub enum Set {}

#[derive(Debug)]
#[doc(hidden)]
pub enum Unset {}

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

    #[test]
    fn header_encode_decode() {
        let header = Header {
            alg: Algorithm::RSA_OAEP_256,
            enc: Some(EncryptionAlgorithm::A256GCM),
            kid: Some("https://myvault.vault.azure.net/keys/mykey/1234567890abcdef".into()),
            typ: Type::JWE,
        };

        let expected_json = json!({
            "alg": "RSA-OAEP-256",
            "enc": "A256GCM",
            "kid": "https://myvault.vault.azure.net/keys/mykey/1234567890abcdef",
            "typ": "JWE"
        });
        let expected_bytes = serde_json::to_vec(&expected_json).unwrap();
        let expected_b64 = azure_core::base64::encode_url_safe(expected_bytes);

        // Encode
        let encoded = header.encode().unwrap();
        assert_eq!(encoded, expected_b64);

        // Decode
        let decoded = Header::decode(&encoded).unwrap();
        assert_eq!(decoded.alg, Algorithm::RSA_OAEP_256);
        assert_eq!(decoded.enc, Some(EncryptionAlgorithm::A256GCM));
        assert_eq!(
            decoded.kid,
            Some("https://myvault.vault.azure.net/keys/mykey/1234567890abcdef".into()),
        );
        assert_eq!(decoded.typ, Type::JWE);
    }
}