use std::collections::BTreeSet; use std::fmt; use ed25519_dalek::{SigningKey, VerifyingKey}; use serde::Serialize; use crate::identity::{ CapabilityId, CertificateDigest, ProtocolCredentialId, RequestDigest, RequestId, TrustManifestDigest, }; use crate::records::{ DeveloperDecision, GenerationIdentity, MailboxCancellation, MailboxRequest, MailboxResult, MailboxResultAck, MailboxResultChunk, ProtocolCredential, ProtocolCredentialRole, ProtocolSubjectId, ProtocolVersion, SessionBinding, SessionRequestCapability, TlsCredential, TlsRole, TlsSubjectId, TrustManifest, }; use super::signing::{ ProtocolCodecError, ProtocolRecordKind, decode_signed, encode_payload, sha256, sign_payload, trust_manifest_digest, verify_payload, }; use super::window::MAX_UNACKNOWLEDGED_CHUNKS; use super::{MAX_RESULT_CHUNK_BYTES, request_digest}; #[derive(Debug)] pub enum ProtocolValidationError { Codec(ProtocolCodecError), InvalidPublicKey, ManifestDigestMismatch, CertificateDigestMismatch, CredentialDigestMismatch, CapabilityDigestMismatch, GenerationMismatch, RoleMismatch, SubjectMismatch, SessionBindingMismatch, CredentialMismatch, CapabilityMismatch, RequestMismatch, RequestReplay, ChunkTooLarge { length: usize }, } impl fmt::Display for ProtocolValidationError { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Codec(error) => error.fmt(formatter), Self::InvalidPublicKey => formatter.write_str("protocol public key is invalid"), Self::ManifestDigestMismatch => formatter.write_str("Trust Manifest digest mismatch"), Self::CertificateDigestMismatch => { formatter.write_str("TLS certificate digest mismatch") } Self::CredentialDigestMismatch => { formatter.write_str("protocol credential digest mismatch") } Self::CapabilityDigestMismatch => { formatter.write_str("Session Request Capability digest mismatch") } Self::GenerationMismatch => { formatter.write_str("protocol generation identity mismatch") } Self::RoleMismatch => formatter.write_str("protocol credential role mismatch"), Self::SubjectMismatch => formatter.write_str("protocol subject identity mismatch"), Self::SessionBindingMismatch => formatter.write_str("session binding mismatch"), Self::CredentialMismatch => formatter.write_str("protocol credential mismatch"), Self::CapabilityMismatch => formatter.write_str("Session Request Capability mismatch"), Self::RequestMismatch => formatter.write_str("escalation request correlation mismatch"), Self::RequestReplay => { formatter.write_str("escalation request ID was already admitted") } Self::ChunkTooLarge { length } => write!( formatter, "result chunk length {length} exceeds {MAX_RESULT_CHUNK_BYTES}" ), } } } impl std::error::Error for ProtocolValidationError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Codec(error) => Some(error), _ => None, } } } impl From for ProtocolValidationError { fn from(error: ProtocolCodecError) -> Self { Self::Codec(error) } } #[derive(Serialize)] struct TrustManifestClaims<'a> { protocol_version: ProtocolVersion, generation_identity: &'a GenerationIdentity, tls_ca_sha256: CertificateDigest, protocol_issuer_public_key: &'a [u8], } #[derive(Serialize)] struct ProtocolCredentialClaims<'a> { protocol_version: ProtocolVersion, generation_identity: &'a GenerationIdentity, role: ProtocolCredentialRole, subject_id: ProtocolSubjectId, public_key: &'a [u8], } #[derive(Serialize)] struct SessionCapabilityClaims<'a> { protocol_version: ProtocolVersion, session_binding: &'a SessionBinding, coordinator_credential_id: ProtocolCredentialId, sandbox_credential_id: ProtocolCredentialId, } /// Creates a self-verifying manifest whose digest must still arrive through a trusted path. /// /// # Errors /// /// Returns an error when a claim cannot be encoded. pub fn issue_trust_manifest( generation_identity: GenerationIdentity, tls_ca_sha256: CertificateDigest, protocol_issuer: &SigningKey, ) -> Result<(TrustManifest, TrustManifestDigest), ProtocolValidationError> { let public_key = protocol_issuer.verifying_key().to_bytes().to_vec(); let claims = TrustManifestClaims { protocol_version: ProtocolVersion::V1, generation_identity: &generation_identity, tls_ca_sha256, protocol_issuer_public_key: &public_key, }; let claims = encode_payload(&claims)?; let signature = sign_payload(ProtocolRecordKind::TrustManifest, &claims, protocol_issuer)?; let manifest = TrustManifest { protocol_version: ProtocolVersion::V1, generation_identity, tls_ca_sha256, protocol_issuer_public_key: public_key, issuer_signature: signature.to_vec(), }; let encoded = encode_payload(&manifest)?; let digest = trust_manifest_digest(&encoded); Ok((manifest, digest)) } /// Validates a pinned Trust Manifest and returns its protocol issuer key. /// /// # Errors /// /// Returns an error for a stale tuple, digest mismatch, malformed key, or invalid signature. pub fn verify_trust_manifest( manifest: &TrustManifest, trusted_digest: TrustManifestDigest, expected_generation: &GenerationIdentity, expected_tls_ca_sha256: CertificateDigest, ) -> Result { if &manifest.generation_identity != expected_generation { return Err(ProtocolValidationError::GenerationMismatch); } if manifest.tls_ca_sha256 != expected_tls_ca_sha256 { return Err(ProtocolValidationError::CertificateDigestMismatch); } let encoded = encode_payload(manifest)?; if trust_manifest_digest(&encoded) != trusted_digest { return Err(ProtocolValidationError::ManifestDigestMismatch); } let key_bytes: [u8; 32] = manifest .protocol_issuer_public_key .as_slice() .try_into() .map_err(|_| ProtocolValidationError::InvalidPublicKey)?; let key = VerifyingKey::from_bytes(&key_bytes) .map_err(|_| ProtocolValidationError::InvalidPublicKey)?; let claims = TrustManifestClaims { protocol_version: manifest.protocol_version, generation_identity: &manifest.generation_identity, tls_ca_sha256: manifest.tls_ca_sha256, protocol_issuer_public_key: &manifest.protocol_issuer_public_key, }; verify_payload( ProtocolRecordKind::TrustManifest, &encode_payload(&claims)?, &manifest.issuer_signature, &key, )?; Ok(key) } /// Issues one generation- and role-bound protocol credential. /// /// # Errors /// /// Returns an error when a claim cannot be encoded. pub fn issue_protocol_credential( generation_identity: GenerationIdentity, role: ProtocolCredentialRole, subject_id: ProtocolSubjectId, subject_key: &VerifyingKey, protocol_issuer: &SigningKey, ) -> Result { let public_key = subject_key.to_bytes().to_vec(); let claims = ProtocolCredentialClaims { protocol_version: ProtocolVersion::V1, generation_identity: &generation_identity, role, subject_id, public_key: &public_key, }; let claims = encode_payload(&claims)?; let credential_id = ProtocolCredentialId::new(sha256(&claims)); let signature = sign_payload( ProtocolRecordKind::ProtocolCredential, &claims, protocol_issuer, )?; let credential = match (role, subject_id) { ( ProtocolCredentialRole::SessionCoordinator, ProtocolSubjectId::SessionCoordinator(subject_id), ) => ProtocolCredential::session_coordinator( credential_id, generation_identity, subject_id, public_key, signature.to_vec(), ), (ProtocolCredentialRole::AgentSandbox, ProtocolSubjectId::AgentSandbox(subject_id)) => { ProtocolCredential::agent_sandbox( credential_id, generation_identity, subject_id, public_key, signature.to_vec(), ) } _ => return Err(ProtocolValidationError::RoleMismatch), }; Ok(credential) } /// Validates one protocol credential against its issuer and exact authority tuple. /// /// # Errors /// /// Returns an error for key, role, subject, generation, digest, or signature mismatch. pub fn verify_protocol_credential( credential: &ProtocolCredential, protocol_issuer: &VerifyingKey, expected_generation: &GenerationIdentity, expected_role: ProtocolCredentialRole, expected_subject: ProtocolSubjectId, ) -> Result { if &credential.generation_identity != expected_generation { return Err(ProtocolValidationError::GenerationMismatch); } if credential.role() != expected_role { return Err(ProtocolValidationError::RoleMismatch); } if credential.subject_id() != expected_subject { return Err(ProtocolValidationError::SubjectMismatch); } let claims = ProtocolCredentialClaims { protocol_version: credential.protocol_version, generation_identity: &credential.generation_identity, role: credential.role(), subject_id: credential.subject_id(), public_key: &credential.public_key, }; let claims = encode_payload(&claims)?; if credential.credential_id != ProtocolCredentialId::new(sha256(&claims)) { return Err(ProtocolValidationError::CredentialDigestMismatch); } verify_payload( ProtocolRecordKind::ProtocolCredential, &claims, &credential.issuer_signature, protocol_issuer, )?; let key_bytes: [u8; 32] = credential .public_key .as_slice() .try_into() .map_err(|_| ProtocolValidationError::InvalidPublicKey)?; VerifyingKey::from_bytes(&key_bytes).map_err(|_| ProtocolValidationError::InvalidPublicKey) } /// Issues one capability for an exact live session and credential pair. /// /// # Errors /// /// Returns an error when its claims cannot be encoded. pub fn issue_session_capability( session_binding: SessionBinding, coordinator_credential_id: ProtocolCredentialId, sandbox_credential_id: ProtocolCredentialId, coordinator_key: &SigningKey, ) -> Result { let claims = SessionCapabilityClaims { protocol_version: ProtocolVersion::V1, session_binding: &session_binding, coordinator_credential_id, sandbox_credential_id, }; let claims = encode_payload(&claims)?; let capability_id = CapabilityId::new(sha256(&claims)); let signature = sign_payload( ProtocolRecordKind::SessionRequestCapability, &claims, coordinator_key, )?; Ok(SessionRequestCapability { protocol_version: ProtocolVersion::V1, capability_id, session_binding, coordinator_credential_id, sandbox_credential_id, coordinator_signature: signature.to_vec(), }) } fn verify_session_capability( capability: &SessionRequestCapability, session_binding: &SessionBinding, coordinator_credential_id: ProtocolCredentialId, sandbox_credential_id: ProtocolCredentialId, coordinator_key: &VerifyingKey, ) -> Result<(), ProtocolValidationError> { if &capability.session_binding != session_binding { return Err(ProtocolValidationError::SessionBindingMismatch); } if capability.coordinator_credential_id != coordinator_credential_id || capability.sandbox_credential_id != sandbox_credential_id { return Err(ProtocolValidationError::CredentialMismatch); } let claims = SessionCapabilityClaims { protocol_version: capability.protocol_version, session_binding: &capability.session_binding, coordinator_credential_id: capability.coordinator_credential_id, sandbox_credential_id: capability.sandbox_credential_id, }; let claims = encode_payload(&claims)?; if capability.capability_id != CapabilityId::new(sha256(&claims)) { return Err(ProtocolValidationError::CapabilityDigestMismatch); } verify_payload( ProtocolRecordKind::SessionRequestCapability, &claims, &capability.coordinator_signature, coordinator_key, )?; Ok(()) } /// Checks the certificate bytes and exact TLS identity tuple carried by a TLS credential. /// /// # Errors /// /// Returns an error for a stale tuple, role/subject mismatch, or certificate digest mismatch. pub fn verify_tls_credential( credential: &TlsCredential, expected_generation: &GenerationIdentity, expected_role: TlsRole, expected_subject: TlsSubjectId, ) -> Result<(), ProtocolValidationError> { if &credential.generation_identity != expected_generation { return Err(ProtocolValidationError::GenerationMismatch); } if credential.role() != expected_role { return Err(ProtocolValidationError::RoleMismatch); } if credential.subject_id() != expected_subject { return Err(ProtocolValidationError::SubjectMismatch); } if credential.certificate_sha256 != CertificateDigest::new(sha256(&credential.certificate_der)) { return Err(ProtocolValidationError::CertificateDigestMismatch); } Ok(()) } #[derive(Clone, Debug)] pub struct VerifiedSession { session_binding: SessionBinding, capability: SessionRequestCapability, sandbox_credential_id: ProtocolCredentialId, coordinator_credential_id: ProtocolCredentialId, sandbox_key: VerifyingKey, coordinator_key: VerifyingKey, session_anchor_key: VerifyingKey, } impl VerifiedSession { /// Constructs an authority only after validating its manifest, credentials, and capability. /// /// # Errors /// /// Returns an error when any key or identity tuple fails validation. pub fn new( trusted_manifest_digest: TrustManifestDigest, tls_ca_sha256: CertificateDigest, manifest: &TrustManifest, session_binding: SessionBinding, coordinator_credential: &ProtocolCredential, sandbox_credential: &ProtocolCredential, capability: SessionRequestCapability, ) -> Result { let generation = &session_binding.generation_identity; let issuer = verify_trust_manifest(manifest, trusted_manifest_digest, generation, tls_ca_sha256)?; let coordinator_key = verify_protocol_credential( coordinator_credential, &issuer, generation, ProtocolCredentialRole::SessionCoordinator, ProtocolSubjectId::SessionCoordinator(generation.coordinator_id), )?; let sandbox_key = verify_protocol_credential( sandbox_credential, &issuer, generation, ProtocolCredentialRole::AgentSandbox, ProtocolSubjectId::AgentSandbox(generation.sandbox_id), )?; verify_session_capability( &capability, &session_binding, coordinator_credential.credential_id, sandbox_credential.credential_id, &coordinator_key, )?; let anchor_bytes: [u8; 32] = session_binding .session_anchor_public_key .as_slice() .try_into() .map_err(|_| ProtocolValidationError::InvalidPublicKey)?; let session_anchor_key = VerifyingKey::from_bytes(&anchor_bytes) .map_err(|_| ProtocolValidationError::InvalidPublicKey)?; Ok(Self { session_binding, capability, sandbox_credential_id: sandbox_credential.credential_id, coordinator_credential_id: coordinator_credential.credential_id, sandbox_key, coordinator_key, session_anchor_key, }) } fn verify_sandbox_tuple( &self, session_binding: &SessionBinding, capability: &SessionRequestCapability, credential_id: ProtocolCredentialId, ) -> Result<(), ProtocolValidationError> { if session_binding != &self.session_binding { return Err(ProtocolValidationError::SessionBindingMismatch); } if capability != &self.capability { return Err(ProtocolValidationError::CapabilityMismatch); } if credential_id != self.sandbox_credential_id { return Err(ProtocolValidationError::CredentialMismatch); } Ok(()) } /// Verifies an exact signed request and returns its digest over accepted payload bytes. /// /// # Errors /// /// Returns an error for signature, encoding, or tuple mismatch. pub fn verify_request( &self, encoded: &[u8], ) -> Result<(MailboxRequest, RequestDigest), ProtocolValidationError> { let (request, payload_bytes) = decode_signed::(encoded, &self.sandbox_key)?; self.verify_sandbox_tuple( request.session_binding(), request.session_capability(), request.sandbox_credential_id(), )?; Ok((request, request_digest(&payload_bytes))) } /// Verifies a signed cancellation for the exact request. /// /// # Errors /// /// Returns an error for signature, encoding, tuple, or request mismatch. pub fn verify_cancellation( &self, encoded: &[u8], request_id: RequestId, ) -> Result { let (cancellation, _) = decode_signed::(encoded, &self.sandbox_key)?; self.verify_sandbox_tuple( &cancellation.session_binding, &cancellation.session_capability, cancellation.sandbox_credential_id, )?; if cancellation.request_id != request_id { return Err(ProtocolValidationError::RequestMismatch); } Ok(cancellation) } /// Verifies a signed highest-contiguous acknowledgement for the exact request. /// /// # Errors /// /// Returns an error for signature, encoding, tuple, or request mismatch. pub fn verify_result_ack( &self, encoded: &[u8], request_id: RequestId, digest: RequestDigest, ) -> Result { let (ack, _) = decode_signed::(encoded, &self.sandbox_key)?; self.verify_sandbox_tuple( &ack.session_binding, &ack.session_capability, ack.sandbox_credential_id, )?; if ack.request_id != request_id || ack.request_sha256 != digest { return Err(ProtocolValidationError::RequestMismatch); } Ok(ack) } /// Verifies one host-only Session Anchor decision for the exact request. /// /// # Errors /// /// Returns an error for signature, encoding, tuple, or request mismatch. pub fn verify_developer_decision( &self, encoded: &[u8], request_id: RequestId, digest: RequestDigest, ) -> Result { let (decision, _) = decode_signed::(encoded, &self.session_anchor_key)?; if decision.session_binding != self.session_binding { return Err(ProtocolValidationError::SessionBindingMismatch); } if decision.request_id != request_id || decision.request_sha256 != digest { return Err(ProtocolValidationError::RequestMismatch); } Ok(decision) } /// Verifies one coordinator result chunk for the exact request. /// /// # Errors /// /// Returns an error for signature, encoding, tuple, request, or size mismatch. pub fn verify_result_chunk( &self, encoded: &[u8], request_id: RequestId, digest: RequestDigest, ) -> Result { let (chunk, _) = decode_signed::(encoded, &self.coordinator_key)?; if chunk.bytes.len() > MAX_RESULT_CHUNK_BYTES { return Err(ProtocolValidationError::ChunkTooLarge { length: chunk.bytes.len(), }); } self.verify_coordinator_tuple( &chunk.session_binding, chunk.request_id, chunk.request_sha256, chunk.coordinator_credential_id, request_id, digest, )?; Ok(chunk) } /// Verifies one coordinator terminal result for the exact request. /// /// # Errors /// /// Returns an error for signature, encoding, tuple, or request mismatch. pub fn verify_result( &self, encoded: &[u8], request_id: RequestId, digest: RequestDigest, ) -> Result { let (result, _) = decode_signed::(encoded, &self.coordinator_key)?; self.verify_coordinator_tuple( &result.session_binding, result.request_id, result.request_sha256, result.coordinator_credential_id, request_id, digest, )?; Ok(result) } fn verify_coordinator_tuple( &self, session_binding: &SessionBinding, actual_request_id: RequestId, actual_digest: RequestDigest, credential_id: ProtocolCredentialId, expected_request_id: RequestId, expected_digest: RequestDigest, ) -> Result<(), ProtocolValidationError> { if session_binding != &self.session_binding { return Err(ProtocolValidationError::SessionBindingMismatch); } if credential_id != self.coordinator_credential_id { return Err(ProtocolValidationError::CredentialMismatch); } if actual_request_id != expected_request_id || actual_digest != expected_digest { return Err(ProtocolValidationError::RequestMismatch); } Ok(()) } } #[derive(Debug, Default)] pub struct RequestReplayGuard { admitted: BTreeSet, } impl RequestReplayGuard { /// Verifies and records a request ID exactly once for this generation. /// /// # Errors /// /// Returns an error when validation fails or the request ID was previously admitted. pub fn admit( &mut self, session: &VerifiedSession, encoded: &[u8], ) -> Result<(MailboxRequest, RequestDigest), ProtocolValidationError> { let (request, digest) = session.verify_request(encoded)?; if !self.admitted.insert(request.request_id()) { return Err(ProtocolValidationError::RequestReplay); } Ok((request, digest)) } #[must_use] pub fn len(&self) -> usize { self.admitted.len() } #[must_use] pub fn is_empty(&self) -> bool { self.admitted.is_empty() } } const _: () = assert!(MAX_UNACKNOWLEDGED_CHUNKS == 8); #[cfg(test)] mod tests { use ed25519_dalek::SigningKey; use crate::identity::{ CertificateDigest, ClientId, ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, ProtocolCredentialId, RequestId, SandboxId, SessionId, }; use crate::records::{ GenerationIdentity, MailboxRequest, ProtocolCredentialRole, ProtocolSubjectId, SessionBinding, SignedProtocolRecord, }; use super::{ ProtocolCredentialClaims, SessionCapabilityClaims, TrustManifestClaims, issue_protocol_credential, issue_session_capability, issue_trust_manifest, }; use crate::protocol::signing::{ ProtocolRecordKind, decode_payload, encode_payload, encode_signed, request_digest, }; fn repeated(value: u8) -> [u8; 32] { [value; 32] } fn key(value: u8) -> SigningKey { SigningKey::from_bytes(&repeated(value)) } fn generation() -> GenerationIdentity { GenerationIdentity { agent_profile: "a".into(), domain_id: DomainId::new(repeated(1)), generation_id: GenerationId::new(2), configuration_fingerprint: ConfigurationFingerprint::new(repeated(3)), coordinator_id: CoordinatorId::new(repeated(4)), sandbox_id: SandboxId::new(repeated(5)), } } fn hex(bytes: &[u8]) -> String { use std::fmt::Write; let mut encoded = String::with_capacity(bytes.len() * 2); for byte in bytes { write!(&mut encoded, "{byte:02x}").unwrap(); } encoded } fn assert_hex(actual: &[u8], expected: &str) { assert_eq!(hex(actual), expected); } #[test] fn trust_manifest_claim_has_fixed_bytes_digest_and_signature() { let generation = generation(); let issuer = key(6); let tls_ca = CertificateDigest::new(repeated(7)); let (manifest, digest) = issue_trust_manifest(generation.clone(), tls_ca, &issuer).unwrap(); let claims = encode_payload(&TrustManifestClaims { protocol_version: manifest.protocol_version, generation_identity: &generation, tls_ca_sha256: tls_ca, protocol_issuer_public_key: &manifest.protocol_issuer_public_key, }) .unwrap(); assert_hex( &claims, "0001610101010101010101010101010101010101010101010101010101010101010101020303030303030303030303030303030303030303030303030303030303030303040404040404040404040404040404040404040404040404040404040404040405050505050505050505050505050505050505050505050505050505050505050707070707070707070707070707070707070707070707070707070707070707208a875fff1eb38451577acd5afee405456568dd7c89e090863a0557bc7af49f17", ); assert_hex( digest.as_bytes(), "6123f09e099b74151640a51172be4a76a5fe55fcbb6602e1c1851cf5d63e63f3", ); assert_hex( &manifest.issuer_signature, "3e3bad1fd1adb95dabe7e3624e61fa8e07c114c015a5e84dc280bfff6fe34bc2fcfe619d4552ed13edbccc46bf2b10486309143504676dd29a0d9db6a8db1b05", ); } #[test] fn protocol_credential_claim_has_fixed_bytes_id_and_signature() { let generation = generation(); let issuer = key(6); let subject_key = key(8).verifying_key(); let credential = issue_protocol_credential( generation.clone(), ProtocolCredentialRole::SessionCoordinator, ProtocolSubjectId::SessionCoordinator(generation.coordinator_id), &subject_key, &issuer, ) .unwrap(); let claims = encode_payload(&ProtocolCredentialClaims { protocol_version: credential.protocol_version, generation_identity: &generation, role: credential.role(), subject_id: credential.subject_id(), public_key: &credential.public_key, }) .unwrap(); assert_hex( &claims, "00016101010101010101010101010101010101010101010101010101010101010101010203030303030303030303030303030303030303030303030303030303030303030404040404040404040404040404040404040404040404040404040404040404050505050505050505050505050505050505050505050505050505050505050500000404040404040404040404040404040404040404040404040404040404040404201398f62c6d1a457c51ba6a4b5f3dbd2f69fca93216218dc8997e416bd17d93ca", ); assert_hex( credential.credential_id.as_bytes(), "ec6f0029ae2cd0dfaceca450b0a449407e572bbb896dec3278991a0df482f0e4", ); assert_hex( &credential.issuer_signature, "2e58ff820e1753377f6b03a5a9aa91398f761ac0ffec2bb97f485af1790601fa79774dc1084060be9d1f87c59f50be29e459a7beec0812ef514495c2d858c30c", ); } #[test] fn session_capability_claim_has_fixed_bytes_id_and_signature() { let coordinator_key = key(8); let binding = SessionBinding { generation_identity: generation(), cli_id: ClientId::new(repeated(9)), session_id: SessionId::new(repeated(10)), session_anchor_public_key: key(11).verifying_key().to_bytes().to_vec(), }; let coordinator_id = ProtocolCredentialId::new(repeated(12)); let sandbox_id = ProtocolCredentialId::new(repeated(13)); let capability = issue_session_capability( binding.clone(), coordinator_id, sandbox_id, &coordinator_key, ) .unwrap(); let claims = encode_payload(&SessionCapabilityClaims { protocol_version: capability.protocol_version, session_binding: &binding, coordinator_credential_id: coordinator_id, sandbox_credential_id: sandbox_id, }) .unwrap(); assert_hex( &claims, "00016101010101010101010101010101010101010101010101010101010101010101010203030303030303030303030303030303030303030303030303030303030303030404040404040404040404040404040404040404040404040404040404040404050505050505050505050505050505050505050505050505050505050505050509090909090909090909090909090909090909090909090909090909090909090a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a2066be7e332c7a453332bd9d0a7f7db055f5c5ef1a06ada66d98b39fb6810c473a0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d", ); assert_hex( capability.capability_id.as_bytes(), "8cfff31df03359ac89574621aa139363b71d7f07f1e89ddaf158724d0d7a2a63", ); assert_hex( &capability.coordinator_signature, "17c2d8532df692e1124af8d6c5c313489e19257bafd2bc9b1db8403f90db811da253af5229582c19ec36375118336150b8f90057fe6f932a07ce53cb5699970a", ); } #[test] fn mailbox_request_has_fixed_payload_digest_and_signature() { let coordinator_key = key(8); let sandbox_key = key(14); let binding = SessionBinding { generation_identity: generation(), cli_id: ClientId::new(repeated(9)), session_id: SessionId::new(repeated(10)), session_anchor_public_key: key(11).verifying_key().to_bytes().to_vec(), }; let sandbox_id = ProtocolCredentialId::new(repeated(13)); let capability = issue_session_capability( binding.clone(), ProtocolCredentialId::new(repeated(12)), sandbox_id, &coordinator_key, ) .unwrap(); let request = MailboxRequest::new( binding, RequestId::new(repeated(15)), capability, sandbox_id, vec![b"/x".to_vec(), b"a".to_vec()], "j".into(), ) .unwrap(); let payload = encode_payload(&request).unwrap(); assert_hex( &payload, "00016101010101010101010101010101010101010101010101010101010101010101010203030303030303030303030303030303030303030303030303030303030303030404040404040404040404040404040404040404040404040404040404040404050505050505050505050505050505050505050505050505050505050505050509090909090909090909090909090909090909090909090909090909090909090a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a2066be7e332c7a453332bd9d0a7f7db055f5c5ef1a06ada66d98b39fb6810c473a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f008cfff31df03359ac89574621aa139363b71d7f07f1e89ddaf158724d0d7a2a63016101010101010101010101010101010101010101010101010101010101010101010203030303030303030303030303030303030303030303030303030303030303030404040404040404040404040404040404040404040404040404040404040404050505050505050505050505050505050505050505050505050505050505050509090909090909090909090909090909090909090909090909090909090909090a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a2066be7e332c7a453332bd9d0a7f7db055f5c5ef1a06ada66d98b39fb6810c473a0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d4017c2d8532df692e1124af8d6c5c313489e19257bafd2bc9b1db8403f90db811da253af5229582c19ec36375118336150b8f90057fe6f932a07ce53cb5699970a0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d02022f780161016a", ); assert_hex( request_digest(&payload).as_bytes(), "824946c9c105603af1358fa321ce11a0bdd9db05d6de9b6c62008f87b7afbf54", ); let encoded = encode_signed(&request, &sandbox_key).unwrap(); let record: SignedProtocolRecord = decode_payload(&encoded).unwrap(); assert_hex( &record.signature, "6b617a235797c2c6992e74bad70d267fc0ff251a08f0cd934cfab6b897a4ab2b408f685e72d384b5ee8467f36bbd523ecc5ffd534f34998bca20b6a668e38d0c", ); assert_eq!( ProtocolRecordKind::MailboxRequest.signing_context(), b"lazaret/v1/mailbox-request" ); } }