jwk-simple 0.5.0

JWK/JWKS for Rust and WASM (RFC 7517, 7518, 7638, 8037, 9864) with jwt-simple integration
Documentation
//! Symmetric key parameters as defined in RFC 7518 Section 6.4.
//!
//! This module contains the [`SymmetricParams`] type which holds symmetric
//! key material for algorithms like HMAC and AES.

use std::fmt::{self, Debug};

use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::encoding::Base64UrlBytes;
use crate::error::{IncompatibleKeyError, InvalidKeyError, Result};

/// Symmetric key parameters (RFC 7518 Section 6.4).
///
/// Contains the raw symmetric key value `k`.
///
/// # Security Note
///
/// The key material is automatically zeroed from memory when this type is dropped.
/// For secret comparisons, prefer [`SymmetricParams::ct_eq`] over [`PartialEq`].
///
/// # Examples
///
/// ```
/// use jwk_simple::SymmetricParams;
///
/// // Parse from JSON
/// let json = r#"{"k": "GawgguFyGrWKav7AX4VKUg"}"#;
///
/// let params: SymmetricParams = serde_json::from_str(json).unwrap();
/// assert_eq!(params.key_size_bits(), 128);
/// ```
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)]
#[non_exhaustive]
pub struct SymmetricParams {
    /// The symmetric key value.
    pub k: Base64UrlBytes,
}

impl SymmetricParams {
    /// Creates new symmetric key parameters.
    ///
    /// # Examples
    ///
    /// ```
    /// use jwk_simple::SymmetricParams;
    /// use jwk_simple::encoding::Base64UrlBytes;
    ///
    /// // Create a 256-bit key
    /// let key_bytes = vec![0u8; 32];
    /// let params = SymmetricParams::new(Base64UrlBytes::new(key_bytes));
    /// assert_eq!(params.key_size_bits(), 256);
    /// ```
    #[must_use]
    pub fn new(k: Base64UrlBytes) -> Self {
        Self { k }
    }

    /// Returns `false` because symmetric keys are always considered "private".
    ///
    /// Symmetric keys don't have separate public and private components -
    /// the key material itself is always secret.
    pub fn is_public_key_only(&self) -> bool {
        false
    }

    /// Returns `true` because symmetric keys always contain the secret key material.
    pub fn has_private_key(&self) -> bool {
        true
    }

    /// Returns the key size in bits.
    pub fn key_size_bits(&self) -> usize {
        self.k.len() * 8
    }

    /// Validates the symmetric key parameters.
    ///
    /// # Errors
    ///
    /// Returns an error if the key is empty.
    ///
    /// # Examples
    ///
    /// ```
    /// use jwk_simple::SymmetricParams;
    /// use jwk_simple::encoding::Base64UrlBytes;
    ///
    /// let valid_params = SymmetricParams::new(Base64UrlBytes::new(vec![1, 2, 3]));
    /// assert!(valid_params.validate().is_ok());
    ///
    /// let invalid_params = SymmetricParams::new(Base64UrlBytes::new(vec![]));
    /// assert!(invalid_params.validate().is_err());
    /// ```
    pub fn validate(&self) -> Result<()> {
        if self.k.is_empty() {
            return Err(InvalidKeyError::MissingParameter("k").into());
        }
        Ok(())
    }

    /// Validates that the key meets the minimum size requirement.
    ///
    /// # Errors
    ///
    /// Returns an error if the key is smaller than required.
    pub fn validate_min_size(&self, min_bits: usize, context: &'static str) -> Result<()> {
        self.validate()?;

        let actual_bits = self.key_size_bits();
        if actual_bits < min_bits {
            return Err(IncompatibleKeyError::InsufficientKeyStrength {
                minimum_bits: min_bits,
                actual_bits,
                context,
            }
            .into());
        }
        Ok(())
    }

    /// Validates that the key has exactly the required size.
    ///
    /// # Errors
    ///
    /// Returns an error if the key size differs from the required size.
    pub fn validate_exact_size(&self, exact_bits: usize, context: &'static str) -> Result<()> {
        self.validate()?;

        let actual_bits = self.key_size_bits();
        if actual_bits != exact_bits {
            return Err(IncompatibleKeyError::KeySizeMismatch {
                required_bits: exact_bits,
                actual_bits,
                context,
            }
            .into());
        }

        Ok(())
    }

    /// Performs a constant-time equality comparison of the key material.
    #[inline]
    pub fn ct_eq(&self, other: &Self) -> bool {
        self.k.ct_eq(&other.k)
    }
}

impl Debug for SymmetricParams {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Never show the actual key value in debug output
        f.debug_struct("SymmetricParams")
            .field("key_size_bits", &self.key_size_bits())
            .finish()
    }
}

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

    #[test]
    fn test_key_size() {
        let params = SymmetricParams::new(Base64UrlBytes::new(vec![0; 32]));
        assert_eq!(params.key_size_bits(), 256);
    }

    #[test]
    fn test_validate_empty() {
        let params = SymmetricParams::new(Base64UrlBytes::new(vec![]));
        assert!(params.validate().is_err());
    }

    #[test]
    fn test_validate_min_size() {
        let small_key = SymmetricParams::new(Base64UrlBytes::new(vec![0; 16]));
        assert!(small_key.validate_min_size(128, "test").is_ok());
        assert!(small_key.validate_min_size(256, "test").is_err());
    }

    #[test]
    fn test_validate_exact_size() {
        let key_128 = SymmetricParams::new(Base64UrlBytes::new(vec![0; 16]));
        assert!(key_128.validate_exact_size(128, "AES-128").is_ok());
        assert!(key_128.validate_exact_size(256, "AES-256").is_err());
    }

    #[test]
    fn test_json_roundtrip() {
        let original = SymmetricParams::new(Base64UrlBytes::new(vec![1, 2, 3, 4, 5]));
        let json = serde_json::to_string(&original).unwrap();
        let decoded: SymmetricParams = serde_json::from_str(&json).unwrap();
        assert_eq!(original, decoded);
    }

    #[test]
    fn test_constant_time_equality() {
        let a = SymmetricParams::new(Base64UrlBytes::new(vec![0; 32]));
        let b = SymmetricParams::new(Base64UrlBytes::new(vec![0; 32]));
        let mut different = vec![0; 32];
        different[31] = 1;
        let c = SymmetricParams::new(Base64UrlBytes::new(different));

        assert!(a.ct_eq(&b));
        assert!(!a.ct_eq(&c));
    }
}