A library for ATProtocol identities.
1//! ECDSA signature normalization. 2//! 3//! This module handles signature normalization to the low-S form required by 4//! the AT Protocol attestation specification, preventing signature malleability attacks. 5 6use crate::errors::AttestationError; 7use atproto_identity::key::KeyType; 8use k256::ecdsa::Signature as K256Signature; 9use p256::ecdsa::Signature as P256Signature; 10 11/// Normalize raw signature bytes to the required low-S form. 12/// 13/// This helper ensures signatures produced by signing APIs comply with the 14/// specification requirements before embedding them in attestation objects. 15/// 16/// # Arguments 17/// 18/// * `signature` - The raw signature bytes to normalize 19/// * `key_type` - The type of key used to create the signature 20/// 21/// # Returns 22/// 23/// The normalized signature bytes in low-S form 24/// 25/// # Errors 26/// 27/// Returns an error if: 28/// - The signature length is invalid for the key type 29/// - The key type is not supported 30pub fn normalize_signature( 31 signature: Vec<u8>, 32 key_type: &KeyType, 33) -> Result<Vec<u8>, AttestationError> { 34 match key_type { 35 KeyType::P256Private | KeyType::P256Public => normalize_p256(signature), 36 KeyType::K256Private | KeyType::K256Public => normalize_k256(signature), 37 other => Err(AttestationError::UnsupportedKeyType { 38 key_type: (*other).clone(), 39 }), 40 } 41} 42 43/// Normalize a P-256 signature to low-S form. 44fn normalize_p256(signature: Vec<u8>) -> Result<Vec<u8>, AttestationError> { 45 if signature.len() != 64 { 46 return Err(AttestationError::SignatureLengthInvalid { 47 expected: 64, 48 actual: signature.len(), 49 }); 50 } 51 52 let parsed = P256Signature::from_slice(&signature).map_err(|_| { 53 AttestationError::SignatureLengthInvalid { 54 expected: 64, 55 actual: signature.len(), 56 } 57 })?; 58 59 let normalized = parsed.normalize_s().unwrap_or(parsed); 60 61 Ok(normalized.to_vec()) 62} 63 64/// Normalize a K-256 signature to low-S form. 65fn normalize_k256(signature: Vec<u8>) -> Result<Vec<u8>, AttestationError> { 66 if signature.len() != 64 { 67 return Err(AttestationError::SignatureLengthInvalid { 68 expected: 64, 69 actual: signature.len(), 70 }); 71 } 72 73 let parsed = K256Signature::from_slice(&signature).map_err(|_| { 74 AttestationError::SignatureLengthInvalid { 75 expected: 64, 76 actual: signature.len(), 77 } 78 })?; 79 80 let normalized = parsed.normalize_s().unwrap_or(parsed); 81 82 Ok(normalized.to_vec()) 83} 84 85#[cfg(test)] 86mod tests { 87 use super::*; 88 89 #[test] 90 fn reject_invalid_signature_length() { 91 let short_signature = vec![0u8; 32]; 92 let result = normalize_p256(short_signature); 93 assert!(matches!( 94 result, 95 Err(AttestationError::SignatureLengthInvalid { expected: 64, .. }) 96 )); 97 } 98}