use x25519_dalek::StaticSecret;
use crate::attestation::artifact::Attestation;
use crate::attestation::sign::{sign_attestation, verify_attestation};
use crate::error::{TrustError, TrustResult};
use crate::keys::identity::Ed25519Identity;
use crate::seal::sealed_envelope::{SealedEnvelope, open, seal};
const SEALED_ENVELOPE_CONTEXT_V1: &[u8] = b"lys/sealed-envelope/v1";
fn contextualized_envelope_bytes(envelope: &SealedEnvelope) -> Vec<u8> {
let envelope_bytes = envelope.attestation_bytes();
let mut message = Vec::with_capacity(SEALED_ENVELOPE_CONTEXT_V1.len() + envelope_bytes.len());
message.extend_from_slice(SEALED_ENVELOPE_CONTEXT_V1);
message.extend_from_slice(&envelope_bytes);
message
}
pub fn sign_and_seal(
payload: &[u8],
sender_identity: &Ed25519Identity,
recipient_x25519_public_key: &[u8; 32],
) -> TrustResult<(SealedEnvelope, Attestation)> {
let envelope = seal(payload, recipient_x25519_public_key)?;
let attestation = sign_attestation(&contextualized_envelope_bytes(&envelope), sender_identity);
Ok((envelope, attestation))
}
pub fn open_and_verify(
envelope: &SealedEnvelope,
attestation: &Attestation,
sender_public_key: &[u8; 32],
recipient_x25519_secret: &StaticSecret,
) -> TrustResult<Vec<u8>> {
if attestation.signer_public_key != *sender_public_key {
return Err(TrustError::AttestationFailed);
}
verify_attestation(attestation, &contextualized_envelope_bytes(envelope))
.map_err(|_err| TrustError::AttestationFailed)?;
open(envelope, recipient_x25519_secret)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use tempfile::tempdir;
fn identity() -> Ed25519Identity {
let dir = tempdir().unwrap();
Ed25519Identity::load_or_generate(&dir.path().join("id.key")).unwrap()
}
#[test]
fn sign_and_seal_returns_envelope_and_attestation() {
let sender = identity();
let recipient = identity();
let payload = b"authenticated credential bundle";
let (env, att) = sign_and_seal(payload, &sender, &recipient.x25519_public_key()).unwrap();
assert_eq!(att.signer_public_key, sender.public_key_bytes());
assert_eq!(env.ciphertext.len(), payload.len() + 16);
}
#[test]
fn open_and_verify_returns_payload_for_correct_sender() {
let sender = identity();
let recipient = identity();
let payload = b"audit-bound payload";
let (env, att) = sign_and_seal(payload, &sender, &recipient.x25519_public_key()).unwrap();
let opened = open_and_verify(
&env,
&att,
&sender.public_key_bytes(),
&recipient.x25519_static_secret(),
)
.unwrap();
assert_eq!(opened.as_slice(), payload);
}
#[test]
fn wrong_sender_public_key_returns_attestation_failed_without_unsealing() {
let sender = identity();
let imposter = identity();
let recipient = identity();
let (env, att) =
sign_and_seal(b"payload", &sender, &recipient.x25519_public_key()).unwrap();
let err = open_and_verify(
&env,
&att,
&imposter.public_key_bytes(),
&recipient.x25519_static_secret(),
)
.unwrap_err();
assert!(matches!(err, TrustError::AttestationFailed));
}
#[test]
fn tampered_attestation_signature_returns_attestation_failed() {
let sender = identity();
let recipient = identity();
let (env, mut att) =
sign_and_seal(b"payload", &sender, &recipient.x25519_public_key()).unwrap();
att.signature[0] ^= 0x01;
let err = open_and_verify(
&env,
&att,
&sender.public_key_bytes(),
&recipient.x25519_static_secret(),
)
.unwrap_err();
assert!(matches!(err, TrustError::AttestationFailed));
}
#[test]
fn forged_signer_key_in_attestation_returns_attestation_failed() {
let sender = identity();
let imposter = identity();
let recipient = identity();
let (env, mut att) =
sign_and_seal(b"payload", &sender, &recipient.x25519_public_key()).unwrap();
att.signer_public_key = imposter.public_key_bytes();
let err = open_and_verify(
&env,
&att,
&imposter.public_key_bytes(),
&recipient.x25519_static_secret(),
)
.unwrap_err();
assert!(matches!(err, TrustError::AttestationFailed));
}
#[test]
fn tampered_envelope_after_signing_returns_attestation_failed() {
let sender = identity();
let recipient = identity();
let (mut env, att) =
sign_and_seal(b"payload", &sender, &recipient.x25519_public_key()).unwrap();
env.ciphertext[0] ^= 0x01;
let err = open_and_verify(
&env,
&att,
&sender.public_key_bytes(),
&recipient.x25519_static_secret(),
)
.unwrap_err();
assert!(matches!(err, TrustError::AttestationFailed));
}
#[test]
fn generic_attestation_over_bare_envelope_bytes_is_rejected() {
let sender = identity();
let recipient = identity();
let (env, _att) =
sign_and_seal(b"payload", &sender, &recipient.x25519_public_key()).unwrap();
let generic = crate::attestation::sign::sign_attestation(&env.attestation_bytes(), &sender);
let err = open_and_verify(
&env,
&generic,
&sender.public_key_bytes(),
&recipient.x25519_static_secret(),
)
.unwrap_err();
assert!(matches!(err, TrustError::AttestationFailed));
}
#[test]
fn standalone_seal_and_open_still_work() {
let recipient = identity();
let env = seal(b"anonymous", &recipient.x25519_public_key()).unwrap();
let opened = open(&env, &recipient.x25519_static_secret()).unwrap();
assert_eq!(opened.as_slice(), b"anonymous");
}
}