lazaret/crates/lazaret-core/src/protocol/session.rs

867 lines
33 KiB
Rust
Raw Normal View History

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<ProtocolCodecError> 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<VerifyingKey, ProtocolValidationError> {
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<ProtocolCredential, ProtocolValidationError> {
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<VerifyingKey, 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);
}
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<SessionRequestCapability, ProtocolValidationError> {
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<Self, ProtocolValidationError> {
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::<MailboxRequest>(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<MailboxCancellation, ProtocolValidationError> {
let (cancellation, _) = decode_signed::<MailboxCancellation>(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<MailboxResultAck, ProtocolValidationError> {
let (ack, _) = decode_signed::<MailboxResultAck>(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<DeveloperDecision, ProtocolValidationError> {
let (decision, _) = decode_signed::<DeveloperDecision>(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<MailboxResultChunk, ProtocolValidationError> {
let (chunk, _) = decode_signed::<MailboxResultChunk>(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<MailboxResult, ProtocolValidationError> {
let (result, _) = decode_signed::<MailboxResult>(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<RequestId>,
}
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);
2026-07-15 16:18:38 +09:00
#[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"
);
}
}