Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions crates/attestation/src/fixtures.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! Attestation fixtures.

use tlsn_core::{
connection::{CertBinding, CertBindingV1_2},
fixtures::ConnectionFixture,
Expand All @@ -13,7 +12,10 @@ use tlsn_core::{
use crate::{
Attestation, AttestationConfig, CryptoProvider, Extension,
request::{Request, RequestConfig},
signing::SignatureAlgId,
signing::{
KeyAlgId, SignatureAlgId, SignatureVerifier, SignatureVerifierProvider, Signer,
SignerProvider,
},
};

/// A Request fixture used for testing.
Expand Down Expand Up @@ -102,7 +104,8 @@ pub fn attestation_fixture(
let mut provider = CryptoProvider::default();
match signature_alg {
SignatureAlgId::SECP256K1 => provider.signer.set_secp256k1(&[42u8; 32]).unwrap(),
SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[42u8; 32]).unwrap(),
SignatureAlgId::SECP256K1ETH => provider.signer.set_secp256k1eth(&[43u8; 32]).unwrap(),
SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[44u8; 32]).unwrap(),
_ => unimplemented!(),
};

Expand All @@ -122,3 +125,68 @@ pub fn attestation_fixture(

attestation_builder.build(&provider).unwrap()
}

/// Returns a crypto provider which supports only a custom signature alg.
pub fn custom_provider_fixture() -> CryptoProvider {
const CUSTOM_SIG_ALG_ID: SignatureAlgId = SignatureAlgId::new(128);

// A dummy signer.
struct DummySigner {}
impl Signer for DummySigner {
fn alg_id(&self) -> SignatureAlgId {
CUSTOM_SIG_ALG_ID
}

fn sign(
&self,
msg: &[u8],
) -> Result<crate::signing::Signature, crate::signing::SignatureError> {
Ok(crate::signing::Signature {
alg: CUSTOM_SIG_ALG_ID,
data: msg.to_vec(),
})
}

fn verifying_key(&self) -> crate::signing::VerifyingKey {
crate::signing::VerifyingKey {
alg: KeyAlgId::new(128),
data: vec![1, 2, 3, 4],
}
}
}

// A dummy verifier.
struct DummyVerifier {}
impl SignatureVerifier for DummyVerifier {
fn alg_id(&self) -> SignatureAlgId {
CUSTOM_SIG_ALG_ID
}

fn verify(
&self,
_key: &crate::signing::VerifyingKey,
msg: &[u8],
sig: &[u8],
) -> Result<(), crate::signing::SignatureError> {
if msg == sig {
Ok(())
} else {
Err(crate::signing::SignatureError(
"invalid signature".to_string(),
))
}
}
}

let mut provider = CryptoProvider::default();

let mut signer_provider = SignerProvider::default();
signer_provider.set_signer(Box::new(DummySigner {}));
provider.signer = signer_provider;

let mut verifier_provider = SignatureVerifierProvider::empty();
verifier_provider.set_verifier(Box::new(DummyVerifier {}));
provider.signature = verifier_provider;

provider
}
152 changes: 136 additions & 16 deletions crates/attestation/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ use serde::{Deserialize, Serialize};

use tlsn_core::hash::HashAlgId;

use crate::{Attestation, Extension, connection::ServerCertCommitment, signing::SignatureAlgId};
use crate::{
Attestation, Extension,
connection::ServerCertCommitment,
serialize::CanonicalSerialize,
signing::{SignatureAlgId, SignatureVerifierProvider},
};

pub use builder::{RequestBuilder, RequestBuilderError};
pub use config::{RequestConfig, RequestConfigBuilder, RequestConfigBuilderError};
Expand All @@ -41,44 +46,99 @@ impl Request {
}

/// Validates the content of the attestation against this request.
pub fn validate(&self, attestation: &Attestation) -> Result<(), InconsistentAttestation> {
pub fn validate(
&self,
attestation: &Attestation,
provider: &SignatureVerifierProvider,
) -> Result<(), AttestationValidationError> {
if attestation.signature.alg != self.signature_alg {
return Err(InconsistentAttestation(format!(
return Err(AttestationValidationError::inconsistent(format!(
"signature algorithm: expected {:?}, got {:?}",
self.signature_alg, attestation.signature.alg
)));
}

if attestation.header.root.alg != self.hash_alg {
return Err(InconsistentAttestation(format!(
return Err(AttestationValidationError::inconsistent(format!(
"hash algorithm: expected {:?}, got {:?}",
self.hash_alg, attestation.header.root.alg
)));
}

if attestation.body.cert_commitment() != &self.server_cert_commitment {
return Err(InconsistentAttestation(
"server certificate commitment does not match".to_string(),
return Err(AttestationValidationError::inconsistent(
"server certificate commitment does not match",
));
}

// TODO: improve the O(M*N) complexity of this check.
for extension in &self.extensions {
if !attestation.body.extensions().any(|e| e == extension) {
return Err(InconsistentAttestation(
"extension is missing from the attestation".to_string(),
return Err(AttestationValidationError::inconsistent(
"extension is missing from the attestation",
));
}
}

let verifier = provider.get(&attestation.signature.alg).map_err(|_| {
AttestationValidationError::provider(format!(
"provider not configured for signature algorithm id {:?}",
attestation.signature.alg,
))
})?;

verifier
.verify(
&attestation.body.verifying_key.data,
&CanonicalSerialize::serialize(&attestation.header),
&attestation.signature.data,
)
.map_err(|_| {
AttestationValidationError::inconsistent("failed to verify the signature")
})?;

Ok(())
}
}

/// Error for [`Request::validate`].
#[derive(Debug, thiserror::Error)]
#[error("inconsistent attestation: {0}")]
pub struct InconsistentAttestation(String);
#[error("attestation validation error: {kind}: {message}")]
pub struct AttestationValidationError {
kind: ErrorKind,
message: String,
}

impl AttestationValidationError {
fn inconsistent(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Inconsistent,
message: msg.into(),
}
}

fn provider(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Provider,
message: msg.into(),
}
}
}

#[derive(Debug)]
enum ErrorKind {
Inconsistent,
Provider,
}

impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Inconsistent => write!(f, "inconsistent"),
ErrorKind::Provider => write!(f, "provider"),
}
}
}

#[cfg(test)]
mod test {
Expand All @@ -93,8 +153,9 @@ mod test {
use crate::{
CryptoProvider,
connection::ServerCertOpening,
fixtures::{RequestFixture, attestation_fixture, request_fixture},
signing::SignatureAlgId,
fixtures::{RequestFixture, attestation_fixture, custom_provider_fixture, request_fixture},
request::{AttestationValidationError, ErrorKind},
signing::{SignatureAlgId, SignatureVerifierProvider},
};

#[test]
Expand All @@ -113,7 +174,9 @@ mod test {
let attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);

assert!(request.validate(&attestation).is_ok())
let provider = SignatureVerifierProvider::default();

assert!(request.validate(&attestation, &provider).is_ok())
}

#[test]
Expand All @@ -134,7 +197,9 @@ mod test {

request.signature_alg = SignatureAlgId::SECP256R1;

let res = request.validate(&attestation);
let provider = SignatureVerifierProvider::default();

let res = request.validate(&attestation, &provider);
assert!(res.is_err());
}

Expand All @@ -156,7 +221,9 @@ mod test {

request.hash_alg = HashAlgId::SHA256;

let res = request.validate(&attestation);
let provider = SignatureVerifierProvider::default();

let res = request.validate(&attestation, &provider);
assert!(res.is_err())
}

Expand Down Expand Up @@ -188,7 +255,60 @@ mod test {
request.server_cert_commitment =
opening.commit(crypto_provider.hash.get(&HashAlgId::BLAKE3).unwrap());

let res = request.validate(&attestation);
let provider = SignatureVerifierProvider::default();

let res = request.validate(&attestation, &provider);
assert!(res.is_err())
}

#[test]
fn test_wrong_sig() {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
let connection = ConnectionFixture::tlsnotary(transcript.length());

let RequestFixture { request, .. } = request_fixture(
transcript,
encoding_provider(GET_WITH_HEADER, OK_JSON),
connection.clone(),
Blake3::default(),
Vec::new(),
);

let mut attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);

// Corrupt the signature.
attestation.signature.data[1] = attestation.signature.data[1].wrapping_add(1);

let provider = SignatureVerifierProvider::default();

assert!(request.validate(&attestation, &provider).is_err())
}

#[test]
fn test_wrong_provider() {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
let connection = ConnectionFixture::tlsnotary(transcript.length());

let RequestFixture { request, .. } = request_fixture(
transcript,
encoding_provider(GET_WITH_HEADER, OK_JSON),
connection.clone(),
Blake3::default(),
Vec::new(),
);

let attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);

let provider = custom_provider_fixture();

assert!(matches!(
request.validate(&attestation, &provider.signature),
Err(AttestationValidationError {
kind: ErrorKind::Provider,
..
})
))
}
}
10 changes: 9 additions & 1 deletion crates/attestation/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,14 @@ impl SignatureVerifierProvider {
.map(|s| &**s)
.ok_or(UnknownSignatureAlgId(*alg))
}

/// Returns am empty provider.
#[cfg(any(test, feature = "fixtures"))]
pub fn empty() -> Self {
Self {
verifiers: HashMap::default(),
}
}
}

/// Signature verifier.
Expand All @@ -227,7 +235,7 @@ impl_domain_separator!(VerifyingKey);
/// Error that can occur while verifying a signature.
#[derive(Debug, thiserror::Error)]
#[error("signature verification failed: {0}")]
pub struct SignatureError(String);
pub struct SignatureError(pub String);

/// A signature.
#[derive(Debug, Clone, Deserialize, Serialize)]
Expand Down
2 changes: 1 addition & 1 deletion crates/attestation/tests/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fn test_api() {
let attestation = attestation_builder.build(&provider).unwrap();

// Prover validates the attestation is consistent with its request.
request.validate(&attestation).unwrap();
request.validate(&attestation, &provider.signature).unwrap();

let mut transcript_proof_builder = secrets.transcript_proof_builder();

Expand Down
7 changes: 5 additions & 2 deletions crates/examples/attestation/prove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use tracing::info;
use tlsn::{
attestation::{
request::{Request as AttestationRequest, RequestConfig},
signing::Secp256k1Signer,
signing::{Secp256k1Signer, SignatureVerifierProvider},
Attestation, AttestationConfig, CryptoProvider, Secrets,
},
config::{CertificateDer, PrivateKeyDer, ProtocolConfig, RootCertStore},
Expand Down Expand Up @@ -290,8 +290,11 @@ async fn notarize(
.await
.map_err(|err| format!("notary did not respond with attestation: {err}"))?;

// Signature verifier for the signature algorithm in the request.
let provider = SignatureVerifierProvider::default();

// Check the attestation is consistent with the Prover's view.
request.validate(&attestation)?;
request.validate(&attestation, &provider)?;

Ok((attestation, secrets))
}
Expand Down
Loading