Skip to content

Commit a5749d8

Browse files
authored
fix(attestation): verify sig during validation (#1037)
1 parent f2e119b commit a5749d8

File tree

5 files changed

+228
-22
lines changed

5 files changed

+228
-22
lines changed

crates/attestation/src/fixtures.rs

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//! Attestation fixtures.
2-
32
use tlsn_core::{
43
connection::{CertBinding, CertBindingV1_2},
54
fixtures::ConnectionFixture,
@@ -13,7 +12,10 @@ use tlsn_core::{
1312
use crate::{
1413
Attestation, AttestationConfig, CryptoProvider, Extension,
1514
request::{Request, RequestConfig},
16-
signing::SignatureAlgId,
15+
signing::{
16+
KeyAlgId, SignatureAlgId, SignatureVerifier, SignatureVerifierProvider, Signer,
17+
SignerProvider,
18+
},
1719
};
1820

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

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

123126
attestation_builder.build(&provider).unwrap()
124127
}
128+
129+
/// Returns a crypto provider which supports only a custom signature alg.
130+
pub fn custom_provider_fixture() -> CryptoProvider {
131+
const CUSTOM_SIG_ALG_ID: SignatureAlgId = SignatureAlgId::new(128);
132+
133+
// A dummy signer.
134+
struct DummySigner {}
135+
impl Signer for DummySigner {
136+
fn alg_id(&self) -> SignatureAlgId {
137+
CUSTOM_SIG_ALG_ID
138+
}
139+
140+
fn sign(
141+
&self,
142+
msg: &[u8],
143+
) -> Result<crate::signing::Signature, crate::signing::SignatureError> {
144+
Ok(crate::signing::Signature {
145+
alg: CUSTOM_SIG_ALG_ID,
146+
data: msg.to_vec(),
147+
})
148+
}
149+
150+
fn verifying_key(&self) -> crate::signing::VerifyingKey {
151+
crate::signing::VerifyingKey {
152+
alg: KeyAlgId::new(128),
153+
data: vec![1, 2, 3, 4],
154+
}
155+
}
156+
}
157+
158+
// A dummy verifier.
159+
struct DummyVerifier {}
160+
impl SignatureVerifier for DummyVerifier {
161+
fn alg_id(&self) -> SignatureAlgId {
162+
CUSTOM_SIG_ALG_ID
163+
}
164+
165+
fn verify(
166+
&self,
167+
_key: &crate::signing::VerifyingKey,
168+
msg: &[u8],
169+
sig: &[u8],
170+
) -> Result<(), crate::signing::SignatureError> {
171+
if msg == sig {
172+
Ok(())
173+
} else {
174+
Err(crate::signing::SignatureError::from_str(
175+
"invalid signature",
176+
))
177+
}
178+
}
179+
}
180+
181+
let mut provider = CryptoProvider::default();
182+
183+
let mut signer_provider = SignerProvider::default();
184+
signer_provider.set_signer(Box::new(DummySigner {}));
185+
provider.signer = signer_provider;
186+
187+
let mut verifier_provider = SignatureVerifierProvider::empty();
188+
verifier_provider.set_verifier(Box::new(DummyVerifier {}));
189+
provider.signature = verifier_provider;
190+
191+
provider
192+
}

crates/attestation/src/request.rs

Lines changed: 136 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ use serde::{Deserialize, Serialize};
2020

2121
use tlsn_core::hash::HashAlgId;
2222

23-
use crate::{Attestation, Extension, connection::ServerCertCommitment, signing::SignatureAlgId};
23+
use crate::{
24+
Attestation, CryptoProvider, Extension, connection::ServerCertCommitment,
25+
serialize::CanonicalSerialize, signing::SignatureAlgId,
26+
};
2427

2528
pub use builder::{RequestBuilder, RequestBuilderError};
2629
pub use config::{RequestConfig, RequestConfigBuilder, RequestConfigBuilderError};
@@ -41,44 +44,102 @@ impl Request {
4144
}
4245

4346
/// Validates the content of the attestation against this request.
44-
pub fn validate(&self, attestation: &Attestation) -> Result<(), InconsistentAttestation> {
47+
pub fn validate(
48+
&self,
49+
attestation: &Attestation,
50+
provider: &CryptoProvider,
51+
) -> Result<(), AttestationValidationError> {
4552
if attestation.signature.alg != self.signature_alg {
46-
return Err(InconsistentAttestation(format!(
53+
return Err(AttestationValidationError::inconsistent(format!(
4754
"signature algorithm: expected {:?}, got {:?}",
4855
self.signature_alg, attestation.signature.alg
4956
)));
5057
}
5158

5259
if attestation.header.root.alg != self.hash_alg {
53-
return Err(InconsistentAttestation(format!(
60+
return Err(AttestationValidationError::inconsistent(format!(
5461
"hash algorithm: expected {:?}, got {:?}",
5562
self.hash_alg, attestation.header.root.alg
5663
)));
5764
}
5865

5966
if attestation.body.cert_commitment() != &self.server_cert_commitment {
60-
return Err(InconsistentAttestation(
61-
"server certificate commitment does not match".to_string(),
67+
return Err(AttestationValidationError::inconsistent(
68+
"server certificate commitment does not match",
6269
));
6370
}
6471

6572
// TODO: improve the O(M*N) complexity of this check.
6673
for extension in &self.extensions {
6774
if !attestation.body.extensions().any(|e| e == extension) {
68-
return Err(InconsistentAttestation(
69-
"extension is missing from the attestation".to_string(),
75+
return Err(AttestationValidationError::inconsistent(
76+
"extension is missing from the attestation",
7077
));
7178
}
7279
}
7380

81+
let verifier = provider
82+
.signature
83+
.get(&attestation.signature.alg)
84+
.map_err(|_| {
85+
AttestationValidationError::provider(format!(
86+
"provider not configured for signature algorithm id {:?}",
87+
attestation.signature.alg,
88+
))
89+
})?;
90+
91+
verifier
92+
.verify(
93+
&attestation.body.verifying_key.data,
94+
&CanonicalSerialize::serialize(&attestation.header),
95+
&attestation.signature.data,
96+
)
97+
.map_err(|_| {
98+
AttestationValidationError::inconsistent("failed to verify the signature")
99+
})?;
100+
74101
Ok(())
75102
}
76103
}
77104

78105
/// Error for [`Request::validate`].
79106
#[derive(Debug, thiserror::Error)]
80-
#[error("inconsistent attestation: {0}")]
81-
pub struct InconsistentAttestation(String);
107+
#[error("attestation validation error: {kind}: {message}")]
108+
pub struct AttestationValidationError {
109+
kind: ErrorKind,
110+
message: String,
111+
}
112+
113+
impl AttestationValidationError {
114+
fn inconsistent(msg: impl Into<String>) -> Self {
115+
Self {
116+
kind: ErrorKind::Inconsistent,
117+
message: msg.into(),
118+
}
119+
}
120+
121+
fn provider(msg: impl Into<String>) -> Self {
122+
Self {
123+
kind: ErrorKind::Provider,
124+
message: msg.into(),
125+
}
126+
}
127+
}
128+
129+
#[derive(Debug)]
130+
enum ErrorKind {
131+
Inconsistent,
132+
Provider,
133+
}
134+
135+
impl std::fmt::Display for ErrorKind {
136+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137+
match self {
138+
ErrorKind::Inconsistent => write!(f, "inconsistent"),
139+
ErrorKind::Provider => write!(f, "provider"),
140+
}
141+
}
142+
}
82143

83144
#[cfg(test)]
84145
mod test {
@@ -93,7 +154,8 @@ mod test {
93154
use crate::{
94155
CryptoProvider,
95156
connection::ServerCertOpening,
96-
fixtures::{RequestFixture, attestation_fixture, request_fixture},
157+
fixtures::{RequestFixture, attestation_fixture, custom_provider_fixture, request_fixture},
158+
request::{AttestationValidationError, ErrorKind},
97159
signing::SignatureAlgId,
98160
};
99161

@@ -113,7 +175,9 @@ mod test {
113175
let attestation =
114176
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
115177

116-
assert!(request.validate(&attestation).is_ok())
178+
let provider = CryptoProvider::default();
179+
180+
assert!(request.validate(&attestation, &provider).is_ok())
117181
}
118182

119183
#[test]
@@ -134,7 +198,9 @@ mod test {
134198

135199
request.signature_alg = SignatureAlgId::SECP256R1;
136200

137-
let res = request.validate(&attestation);
201+
let provider = CryptoProvider::default();
202+
203+
let res = request.validate(&attestation, &provider);
138204
assert!(res.is_err());
139205
}
140206

@@ -156,7 +222,9 @@ mod test {
156222

157223
request.hash_alg = HashAlgId::SHA256;
158224

159-
let res = request.validate(&attestation);
225+
let provider = CryptoProvider::default();
226+
227+
let res = request.validate(&attestation, &provider);
160228
assert!(res.is_err())
161229
}
162230

@@ -184,11 +252,62 @@ mod test {
184252
});
185253
let opening = ServerCertOpening::new(server_cert_data);
186254

187-
let crypto_provider = CryptoProvider::default();
255+
let provider = CryptoProvider::default();
188256
request.server_cert_commitment =
189-
opening.commit(crypto_provider.hash.get(&HashAlgId::BLAKE3).unwrap());
257+
opening.commit(provider.hash.get(&HashAlgId::BLAKE3).unwrap());
190258

191-
let res = request.validate(&attestation);
259+
let res = request.validate(&attestation, &provider);
192260
assert!(res.is_err())
193261
}
262+
263+
#[test]
264+
fn test_wrong_sig() {
265+
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
266+
let connection = ConnectionFixture::tlsnotary(transcript.length());
267+
268+
let RequestFixture { request, .. } = request_fixture(
269+
transcript,
270+
encoding_provider(GET_WITH_HEADER, OK_JSON),
271+
connection.clone(),
272+
Blake3::default(),
273+
Vec::new(),
274+
);
275+
276+
let mut attestation =
277+
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
278+
279+
// Corrupt the signature.
280+
attestation.signature.data[1] = attestation.signature.data[1].wrapping_add(1);
281+
282+
let provider = CryptoProvider::default();
283+
284+
assert!(request.validate(&attestation, &provider).is_err())
285+
}
286+
287+
#[test]
288+
fn test_wrong_provider() {
289+
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
290+
let connection = ConnectionFixture::tlsnotary(transcript.length());
291+
292+
let RequestFixture { request, .. } = request_fixture(
293+
transcript,
294+
encoding_provider(GET_WITH_HEADER, OK_JSON),
295+
connection.clone(),
296+
Blake3::default(),
297+
Vec::new(),
298+
);
299+
300+
let attestation =
301+
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
302+
303+
let provider = custom_provider_fixture();
304+
305+
assert!(matches!(
306+
request.validate(&attestation, &provider),
307+
Err(AttestationValidationError {
308+
kind: ErrorKind::Provider,
309+
..
310+
})
311+
))
312+
}
194313
}

crates/attestation/src/signing.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ impl SignatureVerifierProvider {
202202
.map(|s| &**s)
203203
.ok_or(UnknownSignatureAlgId(*alg))
204204
}
205+
206+
/// Returns am empty provider.
207+
#[cfg(any(test, feature = "fixtures"))]
208+
pub fn empty() -> Self {
209+
Self {
210+
verifiers: HashMap::default(),
211+
}
212+
}
205213
}
206214

207215
/// Signature verifier.
@@ -229,6 +237,14 @@ impl_domain_separator!(VerifyingKey);
229237
#[error("signature verification failed: {0}")]
230238
pub struct SignatureError(String);
231239

240+
impl SignatureError {
241+
/// Creates a new error with the given message.
242+
#[allow(clippy::should_implement_trait)]
243+
pub fn from_str(msg: &str) -> Self {
244+
Self(msg.to_string())
245+
}
246+
}
247+
232248
/// A signature.
233249
#[derive(Debug, Clone, Deserialize, Serialize)]
234250
pub struct Signature {

crates/attestation/tests/api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn test_api() {
101101
let attestation = attestation_builder.build(&provider).unwrap();
102102

103103
// Prover validates the attestation is consistent with its request.
104-
request.validate(&attestation).unwrap();
104+
request.validate(&attestation, &provider).unwrap();
105105

106106
let mut transcript_proof_builder = secrets.transcript_proof_builder();
107107

crates/examples/attestation/prove.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,11 @@ async fn notarize(
297297
.await
298298
.map_err(|err| anyhow!("notary did not respond with attestation: {err}"))?;
299299

300+
// Signature verifier for the signature algorithm in the request.
301+
let provider = CryptoProvider::default();
302+
300303
// Check the attestation is consistent with the Prover's view.
301-
request.validate(&attestation)?;
304+
request.validate(&attestation, &provider)?;
302305

303306
Ok((attestation, secrets))
304307
}

0 commit comments

Comments
 (0)