Pin protocol claim and signing vectors
This commit is contained in:
parent
af8669c165
commit
d274e1835d
3 changed files with 273 additions and 7 deletions
|
|
@ -661,3 +661,207 @@ impl RequestReplayGuard {
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: () = assert!(MAX_UNACKNOWLEDGED_CHUNKS == 8);
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,73 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_signing_context_has_an_exact_literal() {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::mem::discriminant;
|
||||||
|
|
||||||
|
let expected = [
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::TrustManifest,
|
||||||
|
b"lazaret/v1/trust-manifest".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::ProtocolCredential,
|
||||||
|
b"lazaret/v1/protocol-credential".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::SessionRequestCapability,
|
||||||
|
b"lazaret/v1/session-request-capability".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::MailboxRequest,
|
||||||
|
b"lazaret/v1/mailbox-request".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::MailboxCancellation,
|
||||||
|
b"lazaret/v1/mailbox-cancellation".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::MailboxResultAck,
|
||||||
|
b"lazaret/v1/mailbox-result-ack".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::DeveloperDecision,
|
||||||
|
b"lazaret/v1/developer-decision".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::MailboxResult,
|
||||||
|
b"lazaret/v1/mailbox-result".as_slice(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ProtocolRecordKind::MailboxResultChunk,
|
||||||
|
b"lazaret/v1/mailbox-result-chunk".as_slice(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
assert_eq!(
|
||||||
|
expected
|
||||||
|
.iter()
|
||||||
|
.map(|(kind, _)| discriminant(kind))
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
.len(),
|
||||||
|
expected.len()
|
||||||
|
);
|
||||||
|
for (kind, context) in expected {
|
||||||
|
assert_eq!(kind.signing_context(), context);
|
||||||
|
match kind {
|
||||||
|
ProtocolRecordKind::TrustManifest
|
||||||
|
| ProtocolRecordKind::ProtocolCredential
|
||||||
|
| ProtocolRecordKind::SessionRequestCapability
|
||||||
|
| ProtocolRecordKind::MailboxRequest
|
||||||
|
| ProtocolRecordKind::MailboxCancellation
|
||||||
|
| ProtocolRecordKind::MailboxResultAck
|
||||||
|
| ProtocolRecordKind::DeveloperDecision
|
||||||
|
| ProtocolRecordKind::MailboxResult
|
||||||
|
| ProtocolRecordKind::MailboxResultChunk => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_signature_does_not_authorize_an_unknown_payload_version() {
|
fn valid_signature_does_not_authorize_an_unknown_payload_version() {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ use lazaret_core::protocol::{
|
||||||
MAX_RESULT_CHUNK_BYTES, MAX_UNACKNOWLEDGED_CHUNKS, ProtocolCodecError, ProtocolValidationError,
|
MAX_RESULT_CHUNK_BYTES, MAX_UNACKNOWLEDGED_CHUNKS, ProtocolCodecError, ProtocolValidationError,
|
||||||
RequestReplayGuard, ResultCollector, ResultSenderWindow, ResultWindowError, VerifiedSession,
|
RequestReplayGuard, ResultCollector, ResultSenderWindow, ResultWindowError, VerifiedSession,
|
||||||
encode_signed, issue_protocol_credential, issue_session_capability, issue_trust_manifest,
|
encode_signed, issue_protocol_credential, issue_session_capability, issue_trust_manifest,
|
||||||
request_digest, stream_digest, verify_protocol_credential, verify_tls_credential,
|
stream_digest, verify_protocol_credential, verify_tls_credential, verify_trust_manifest,
|
||||||
verify_trust_manifest,
|
|
||||||
};
|
};
|
||||||
use lazaret_core::records::{
|
use lazaret_core::records::{
|
||||||
DeveloperDecision, DeveloperDecisionKind, EscalationOutcome, GenerationIdentity,
|
DeveloperDecision, DeveloperDecisionKind, EscalationOutcome, GenerationIdentity,
|
||||||
|
|
@ -134,12 +133,8 @@ fn exact_session_tuple_is_authenticated_and_request_ids_do_not_replay() {
|
||||||
let request = request(&fixture, 30);
|
let request = request(&fixture, 30);
|
||||||
let encoded = encode_signed(&request, &fixture.sandbox_key).unwrap();
|
let encoded = encode_signed(&request, &fixture.sandbox_key).unwrap();
|
||||||
let mut replay = RequestReplayGuard::default();
|
let mut replay = RequestReplayGuard::default();
|
||||||
let (accepted, digest) = replay.admit(&fixture.session, &encoded).unwrap();
|
let (accepted, _) = replay.admit(&fixture.session, &encoded).unwrap();
|
||||||
assert_eq!(accepted, request);
|
assert_eq!(accepted, request);
|
||||||
assert_eq!(
|
|
||||||
digest,
|
|
||||||
request_digest(&postcard::to_stdvec(&request).unwrap())
|
|
||||||
);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
replay.admit(&fixture.session, &encoded),
|
replay.admit(&fixture.session, &encoded),
|
||||||
Err(ProtocolValidationError::RequestReplay)
|
Err(ProtocolValidationError::RequestReplay)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue