use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use sha2::{Digest, Sha256};
use super::error::ReplicationError;
use super::types::{
ArtifactKind, MemoryMutationEnvelopeV1, DIGEST_DOMAIN_TAG, SIGNATURE_DOMAIN_TAG,
};
pub fn canonical_digest(value: &MemoryMutationEnvelopeV1) -> Result<[u8; 32], ReplicationError> {
let preimage = value.signing_preimage()?;
let mut hasher = Sha256::new();
if preimage.len() < SIGNATURE_DOMAIN_TAG.len() {
return Err(ReplicationError::DigestComputation(
"preimage shorter than signature domain tag".into(),
));
}
hasher.update(DIGEST_DOMAIN_TAG);
hasher.update(&preimage[SIGNATURE_DOMAIN_TAG.len()..]);
Ok(hasher.finalize().into())
}
pub fn validate_envelope(value: &MemoryMutationEnvelopeV1) -> Result<(), ReplicationError> {
if value.protocol_version != 1 {
return Err(ReplicationError::UnsupportedProtocolVersion(
value.protocol_version,
));
}
match ArtifactKind::from_u8(value.artifact_kind.to_u8()) {
Some(_) => {}
None => {
return Err(ReplicationError::UnknownArtifactKind(
value.artifact_kind.to_u8(),
));
}
}
match crate::replication::SignerRole::from_u8(value.signer_role as u8) {
Some(_) => {}
None => {
return Err(ReplicationError::UnknownSignerRole(value.signer_role as u8));
}
}
for (name, field) in [
("operation_id", value.operation_id.as_str()),
("idempotency_key", value.idempotency_key.as_str()),
("home_device_id", value.home_device_id.as_str()),
("store_id", value.store_id.as_str()),
("actor_id", value.actor_id.as_str()),
("namespace", value.namespace.as_str()),
("fencing_token", value.fencing_token.as_str()),
("operation_kind", value.operation_kind.as_str()),
("signer_principal_id", value.signer_principal_id.as_str()),
] {
if field.trim().is_empty() {
return Err(ReplicationError::EmptyField(name));
}
}
if value.canonical_payload.is_empty() {
return Err(ReplicationError::EmptyPayload);
}
for (name, field) in [
("operation_id", value.operation_id.as_str()),
("idempotency_key", value.idempotency_key.as_str()),
("home_device_id", value.home_device_id.as_str()),
("store_id", value.store_id.as_str()),
("actor_id", value.actor_id.as_str()),
("namespace", value.namespace.as_str()),
("fencing_token", value.fencing_token.as_str()),
("operation_kind", value.operation_kind.as_str()),
("signer_principal_id", value.signer_principal_id.as_str()),
] {
if let Some((pos, byte)) = field
.bytes()
.enumerate()
.find(|(_, b)| *b < 0x20 || *b == 0x7f)
{
return Err(ReplicationError::ControlCharacterInField {
field: name,
byte,
pos,
});
}
}
const MAX_OPERATION_ID_LEN: usize = 128;
const MAX_IDEMPOTENCY_KEY_LEN: usize = 128;
const MAX_DEVICE_STORE_ACTOR_LEN: usize = 64;
const MAX_NAMESPACE_LEN: usize = 256;
const MAX_FENCING_TOKEN_LEN: usize = 128;
const MAX_OPERATION_KIND_LEN: usize = 64;
const MAX_PRINCIPAL_LEN: usize = 128;
const MAX_PAYLOAD_LEN: usize = 1_048_576;
let byte_checks: [(&str, &str, usize); 9] = [
("operation_id", &value.operation_id, MAX_OPERATION_ID_LEN),
(
"idempotency_key",
&value.idempotency_key,
MAX_IDEMPOTENCY_KEY_LEN,
),
(
"home_device_id",
&value.home_device_id,
MAX_DEVICE_STORE_ACTOR_LEN,
),
("store_id", &value.store_id, MAX_DEVICE_STORE_ACTOR_LEN),
("actor_id", &value.actor_id, MAX_DEVICE_STORE_ACTOR_LEN),
("namespace", &value.namespace, MAX_NAMESPACE_LEN),
("fencing_token", &value.fencing_token, MAX_FENCING_TOKEN_LEN),
(
"operation_kind",
&value.operation_kind,
MAX_OPERATION_KIND_LEN,
),
(
"signer_principal_id",
&value.signer_principal_id,
MAX_PRINCIPAL_LEN,
),
];
for (name, field, max) in &byte_checks {
if field.len() > *max {
return Err(ReplicationError::FieldExceedsMaxLength {
field: name,
len: field.len(),
max: *max,
});
}
}
if value.canonical_payload.len() > MAX_PAYLOAD_LEN {
return Err(ReplicationError::FieldExceedsMaxLength {
field: "canonical_payload",
len: value.canonical_payload.len(),
max: MAX_PAYLOAD_LEN,
});
}
if value.valid_from > value.valid_to {
return Err(ReplicationError::InvalidTemporalInterval {
valid_from: value.valid_from,
valid_to: value.valid_to,
});
}
let actual_len = value.canonical_payload.len() as u64;
if value.payload_length != actual_len {
return Err(ReplicationError::PayloadLengthMismatch {
declared: value.payload_length,
actual: actual_len,
});
}
let computed_digest = Sha256::digest(&value.canonical_payload);
if computed_digest.as_slice() != value.payload_digest.as_slice() {
return Err(ReplicationError::PayloadDigestMismatch);
}
if value.authorization_snapshot_id.len() != 16 {
return Err(ReplicationError::WrongSnapshotIdLength(
value.authorization_snapshot_id.len(),
));
}
if value.signer_public_key.len() != 32 {
return Err(ReplicationError::WrongPublicKeyLength(
value.signer_public_key.len(),
));
}
if value.signature.len() != 64 {
return Err(ReplicationError::WrongSignatureLength(
value.signature.len(),
));
}
if !value.signer_role.may_sign(value.artifact_kind) {
return Err(ReplicationError::RoleArtifactMismatch {
role: value.signer_role,
artifact: value.artifact_kind,
});
}
let key = VerifyingKey::from_bytes(&value.signer_public_key)
.map_err(|e| ReplicationError::InvalidPublicKey(e.to_string()))?;
let preimage = value.signing_preimage()?;
key.verify(&preimage, &Signature::from_bytes(&value.signature))
.map_err(|e| ReplicationError::SignatureVerification(e.to_string()))?;
Ok(())
}