A library for ATProtocol identities.
at main 5.5 kB view raw
1//! Signature verification for AT Protocol attestations. 2//! 3//! This module provides verification functions for AT Protocol record attestations. 4 5use crate::cid::create_attestation_cid; 6use crate::errors::AttestationError; 7use crate::input::AnyInput; 8use crate::utils::BASE64; 9use atproto_identity::key::{KeyResolver, validate}; 10use atproto_record::lexicon::com::atproto::repo::STRONG_REF_NSID; 11use base64::Engine; 12use serde::Serialize; 13use serde_json::{Value, Map}; 14use std::convert::TryInto; 15 16/// Helper function to extract and validate signatures array from a record 17fn extract_signatures(record_object: &Map<String, Value>) -> Result<Vec<Value>, AttestationError> { 18 match record_object.get("signatures") { 19 Some(value) => value 20 .as_array() 21 .ok_or(AttestationError::SignaturesFieldInvalid) 22 .cloned(), 23 None => Ok(vec![]), 24 } 25} 26 27/// Verify all signatures in a record with flexible input types. 28/// 29/// This is a high-level verification function that accepts records in multiple formats 30/// (String, Json, or TypedLexicon) and verifies all signatures with custom resolvers. 31/// 32/// # Arguments 33/// 34/// * `verify_input` - The record to verify (as AnyInput: String, Json, or TypedLexicon) 35/// * `repository` - The repository DID to validate against (prevents replay attacks) 36/// * `key_resolver` - Resolver for looking up verification keys from DIDs 37/// * `record_resolver` - Resolver for fetching remote attestation proof records 38/// 39/// # Returns 40/// 41/// Returns `Ok(())` if all signatures are valid, or an error if any verification fails. 42/// 43/// # Errors 44/// 45/// Returns an error if: 46/// - The input is not a valid record object 47/// - Any signature verification fails 48/// - Key or record resolution fails 49/// 50/// # Type Parameters 51/// 52/// * `R` - The record type (must implement Serialize + LexiconType + PartialEq + Clone) 53/// * `RR` - The record resolver type (must implement RecordResolver) 54/// * `KR` - The key resolver type (must implement KeyResolver) 55pub async fn verify_record<R, RR, KR>( 56 verify_input: AnyInput<R>, 57 repository: &str, 58 key_resolver: KR, 59 record_resolver: RR, 60) -> Result<(), AttestationError> 61where 62 R: Serialize + Clone, 63 RR: atproto_client::record_resolver::RecordResolver, 64 KR: KeyResolver, 65{ 66 let record_object: Map<String, Value> = verify_input 67 .clone() 68 .try_into() 69 .map_err(|_| AttestationError::RecordMustBeObject)?; 70 71 let signatures = extract_signatures(&record_object)?; 72 73 if signatures.is_empty() { 74 return Ok(()); 75 } 76 77 for signature in signatures { 78 let signature_refernce_type = signature 79 .get("$type") 80 .and_then(Value::as_str) 81 .filter(|value| !value.is_empty()) 82 .ok_or(AttestationError::SigMetadataMissingType)?; 83 84 let metadata = if signature_refernce_type == STRONG_REF_NSID { 85 let aturi = signature 86 .get("uri") 87 .and_then(Value::as_str) 88 .filter(|value| !value.is_empty()) 89 .ok_or(AttestationError::SignatureMissingField { 90 field: "uri".to_string(), 91 })?; 92 93 record_resolver 94 .resolve::<serde_json::Value>(aturi) 95 .await 96 .map_err(|error| AttestationError::RemoteAttestationFetchFailed { 97 uri: aturi.to_string(), 98 error, 99 })? 100 } else { 101 signature.clone() 102 }; 103 104 let computed_cid = create_attestation_cid( 105 verify_input.clone(), 106 AnyInput::Serialize(metadata.clone()), 107 repository, 108 )?; 109 110 if signature_refernce_type == STRONG_REF_NSID { 111 let attestation_cid = metadata 112 .get("cid") 113 .and_then(Value::as_str) 114 .filter(|value| !value.is_empty()) 115 .ok_or(AttestationError::SignatureMissingField { 116 field: "cid".to_string(), 117 })?; 118 119 if computed_cid.to_string() != attestation_cid { 120 return Err(AttestationError::RemoteAttestationCidMismatch { 121 expected: attestation_cid.to_string(), 122 actual: computed_cid.to_string(), 123 }); 124 } 125 continue; 126 } 127 128 let key = metadata 129 .get("key") 130 .and_then(Value::as_str) 131 .filter(|value| !value.is_empty()) 132 .ok_or(AttestationError::SignatureMissingField { 133 field: "key".to_string(), 134 })?; 135 let key_data = key_resolver.resolve(key).await.map_err(|error| { 136 AttestationError::KeyResolutionFailed { 137 key: key.to_string(), 138 error, 139 } 140 })?; 141 142 let signature_bytes = metadata 143 .get("signature") 144 .and_then(Value::as_object) 145 .and_then(|object| object.get("$bytes")) 146 .and_then(Value::as_str) 147 .ok_or(AttestationError::SignatureBytesFormatInvalid)?; 148 149 let signature_bytes = BASE64 150 .decode(signature_bytes) 151 .map_err(|error| AttestationError::SignatureDecodingFailed { error })?; 152 153 let computed_cid_bytes = computed_cid.to_bytes(); 154 155 validate(&key_data, &signature_bytes, &computed_cid_bytes) 156 .map_err(|error| AttestationError::SignatureValidationFailed { error })?; 157 } 158 159 Ok(()) 160}