diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 9c7ac367e8..c72f372c54 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -10,9 +10,10 @@ edition = "2021" [features] default = [] -fixtures = ["dep:hex"] +fixtures = ["dep:hex", "dep:tlsn-data-fixtures"] [dependencies] +tlsn-data-fixtures = { workspace = true, optional = true } tlsn-tls-core = { workspace = true, features = ["serde"] } tlsn-utils = { workspace = true } @@ -29,6 +30,7 @@ k256 = { workspace = true } opaque-debug = { workspace = true } p256 = { workspace = true, features = ["serde"] } rand = { workspace = true } +rand_core = { workspace = true } rs_merkle = { workspace = true, features = ["serde"] } rstest = { workspace = true, optional = true } serde = { workspace = true } @@ -41,7 +43,6 @@ webpki-roots = { workspace = true } [dev-dependencies] rstest = { workspace = true } hex = { workspace = true } -rand_core = { workspace = true } rand_chacha = { workspace = true } bincode = { workspace = true } tlsn-data-fixtures = { workspace = true } diff --git a/crates/core/src/attestation/builder.rs b/crates/core/src/attestation/builder.rs index 2c3b43f13f..a2dc674ead 100644 --- a/crates/core/src/attestation/builder.rs +++ b/crates/core/src/attestation/builder.rs @@ -237,3 +237,234 @@ impl std::fmt::Display for AttestationBuilderError { Ok(()) } } + +#[cfg(test)] +mod test { + use rstest::{fixture, rstest}; + use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; + + use crate::{ + connection::{HandshakeData, HandshakeDataV1_2}, + fixtures::{encoder_seed, encoding_provider, ConnectionFixture}, + hash::Blake3, + request::RequestConfig, + transcript::{encoding::EncodingTree, Transcript, TranscriptCommitConfigBuilder}, + }; + + use super::*; + + fn request_and_connection() -> (Request, ConnectionFixture) { + let provider = CryptoProvider::default(); + + let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); + let (sent_len, recv_len) = transcript.len(); + // Plaintext encodings which the Prover obtained from GC evaluation + let encodings_provider = encoding_provider(GET_WITH_HEADER, OK_JSON); + + // At the end of the TLS connection the Prover holds the: + let ConnectionFixture { + server_name, + server_cert_data, + .. + } = ConnectionFixture::tlsnotary(transcript.length()); + + // Prover specifies the ranges it wants to commit to. + let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); + transcript_commitment_builder + .commit_sent(&(0..sent_len)) + .unwrap() + .commit_recv(&(0..recv_len)) + .unwrap(); + + let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); + + // Prover constructs encoding tree. + let encoding_tree = EncodingTree::new( + &Blake3::default(), + transcripts_commitment_config.iter_encoding(), + &encodings_provider, + &transcript.length(), + ) + .unwrap(); + + let request_config = RequestConfig::default(); + let mut request_builder = Request::builder(&request_config); + + request_builder + .server_name(server_name.clone()) + .server_cert_data(server_cert_data) + .transcript(transcript.clone()) + .encoding_tree(encoding_tree); + let (request, _) = request_builder.build(&provider).unwrap(); + + (request, ConnectionFixture::tlsnotary(transcript.length())) + } + + #[fixture] + #[once] + fn default_attestation_config() -> AttestationConfig { + AttestationConfig::builder() + .supported_signature_algs([SignatureAlgId::SECP256K1]) + .build() + .unwrap() + } + + #[fixture] + #[once] + fn crypto_provider() -> CryptoProvider { + let mut provider = CryptoProvider::default(); + provider.signer.set_secp256k1(&[42u8; 32]).unwrap(); + provider + } + + #[rstest] + fn test_attestation_builder_accept_unsupported_signer() { + let (request, _) = request_and_connection(); + let attestation_config = AttestationConfig::builder() + .supported_signature_algs([SignatureAlgId::SECP256R1]) + .build() + .unwrap(); + + let err = Attestation::builder(&attestation_config) + .accept_request(request) + .err() + .unwrap(); + assert!(err.is_request()); + } + + #[rstest] + fn test_attestation_builder_accept_unsupported_hasher() { + let (request, _) = request_and_connection(); + + let attestation_config = AttestationConfig::builder() + .supported_signature_algs([SignatureAlgId::SECP256K1]) + .supported_hash_algs([HashAlgId::KECCAK256]) + .build() + .unwrap(); + + let err = Attestation::builder(&attestation_config) + .accept_request(request) + .err() + .unwrap(); + assert!(err.is_request()); + } + + #[rstest] + fn test_attestation_builder_accept_unsupported_encoding_commitment() { + let (request, _) = request_and_connection(); + + let attestation_config = AttestationConfig::builder() + .supported_signature_algs([SignatureAlgId::SECP256K1]) + .supported_fields([ + FieldKind::ConnectionInfo, + FieldKind::ServerEphemKey, + FieldKind::ServerIdentityCommitment, + ]) + .build() + .unwrap(); + + let err = Attestation::builder(&attestation_config) + .accept_request(request) + .err() + .unwrap(); + assert!(err.is_request()); + } + + #[rstest] + fn test_attestation_builder_sign_missing_signer( + default_attestation_config: &AttestationConfig, + ) { + let (request, _) = request_and_connection(); + + let attestation_builder = Attestation::builder(default_attestation_config) + .accept_request(request.clone()) + .unwrap(); + + let mut provider = CryptoProvider::default(); + provider.signer.set_secp256r1(&[42u8; 32]).unwrap(); + + let err = attestation_builder.build(&provider).err().unwrap(); + assert!(matches!(err.kind, ErrorKind::Config)); + } + + #[rstest] + fn test_attestation_builder_sign_missing_encoding_seed( + default_attestation_config: &AttestationConfig, + crypto_provider: &CryptoProvider, + ) { + let (request, connection) = request_and_connection(); + + let mut attestation_builder = Attestation::builder(default_attestation_config) + .accept_request(request.clone()) + .unwrap(); + + let ConnectionFixture { + connection_info, + server_cert_data, + .. + } = connection; + + let HandshakeData::V1_2(HandshakeDataV1_2 { + server_ephemeral_key, + .. + }) = server_cert_data.handshake.clone(); + + attestation_builder + .connection_info(connection_info.clone()) + .server_ephemeral_key(server_ephemeral_key); + + let err = attestation_builder.build(crypto_provider).err().unwrap(); + assert!(matches!(err.kind, ErrorKind::Field)); + } + + #[rstest] + fn test_attestation_builder_sign_missing_server_ephemeral_key( + default_attestation_config: &AttestationConfig, + crypto_provider: &CryptoProvider, + ) { + let (request, connection) = request_and_connection(); + + let mut attestation_builder = Attestation::builder(default_attestation_config) + .accept_request(request.clone()) + .unwrap(); + + let ConnectionFixture { + connection_info, .. + } = connection; + + attestation_builder + .connection_info(connection_info.clone()) + .encoding_seed(encoder_seed().to_vec()); + + let err = attestation_builder.build(crypto_provider).err().unwrap(); + assert!(matches!(err.kind, ErrorKind::Field)); + } + + #[rstest] + fn test_attestation_builder_sign_missing_connection_info( + default_attestation_config: &AttestationConfig, + crypto_provider: &CryptoProvider, + ) { + let (request, connection) = request_and_connection(); + + let mut attestation_builder = Attestation::builder(default_attestation_config) + .accept_request(request.clone()) + .unwrap(); + + let ConnectionFixture { + server_cert_data, .. + } = connection; + + let HandshakeData::V1_2(HandshakeDataV1_2 { + server_ephemeral_key, + .. + }) = server_cert_data.handshake.clone(); + + attestation_builder + .server_ephemeral_key(server_ephemeral_key) + .encoding_seed(encoder_seed().to_vec()); + + let err = attestation_builder.build(crypto_provider).err().unwrap(); + assert!(matches!(err.kind, ErrorKind::Field)); + } +} diff --git a/crates/core/src/connection.rs b/crates/core/src/connection.rs index b142b3c98c..30a30324b2 100644 --- a/crates/core/src/connection.rs +++ b/crates/core/src/connection.rs @@ -376,3 +376,291 @@ pub enum CertificateVerificationError { #[error("invalid server ephemeral key")] InvalidServerEphemeralKey, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{fixtures::ConnectionFixture, transcript::Transcript}; + + use hex::FromHex; + use rstest::*; + use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; + + #[fixture] + #[once] + fn crypto_provider() -> CryptoProvider { + CryptoProvider::default() + } + + fn tlsnotary() -> ConnectionFixture { + ConnectionFixture::tlsnotary(Transcript::new(GET_WITH_HEADER, OK_JSON).length()) + } + + fn appliedzkp() -> ConnectionFixture { + ConnectionFixture::appliedzkp(Transcript::new(GET_WITH_HEADER, OK_JSON).length()) + } + + /// Expect chain verification to succeed. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_sucess_ca_implicit( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + // Remove the CA cert + data.server_cert_data.certs.pop(); + + assert!(data + .server_cert_data + .verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ) + .is_ok()); + } + + /// Expect chain verification to succeed even when a trusted CA is provided + /// among the intermediate certs. webpki handles such cases properly. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_success_ca_explicit( + crypto_provider: &CryptoProvider, + #[case] data: ConnectionFixture, + ) { + assert!(data + .server_cert_data + .verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ) + .is_ok()); + } + + /// Expect to fail since the end entity cert was not valid at the time. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_fail_bad_time( + crypto_provider: &CryptoProvider, + #[case] data: ConnectionFixture, + ) { + // unix time when the cert chain was NOT valid + let bad_time: u64 = 1571465711; + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + bad_time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidCert + )); + } + + /// Expect to fail when no intermediate cert provided. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_fail_no_interm_cert( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + // Remove the CA cert + data.server_cert_data.certs.pop(); + // Remove the intermediate cert + data.server_cert_data.certs.pop(); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidCert + )); + } + + /// Expect to fail when no intermediate cert provided even if a trusted CA + /// cert is provided. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_fail_no_interm_cert_with_ca_cert( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + // Remove the intermediate cert + data.server_cert_data.certs.remove(1); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidCert + )); + } + + /// Expect to fail because end-entity cert is wrong. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_fail_bad_ee_cert( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + let ee: &[u8] = include_bytes!("./fixtures/data/unknown/ee.der"); + + // Change the end entity cert + data.server_cert_data.certs[0] = Certificate(ee.to_vec()); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidCert + )); + } + + /// Expect sig verification to fail because client_random is wrong. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_sig_ke_params_fail_bad_client_random( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + let HandshakeData::V1_2(HandshakeDataV1_2 { client_random, .. }) = + &mut data.server_cert_data.handshake; + client_random[31] = client_random[31].wrapping_add(1); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidServerSignature + )); + } + + /// Expect sig verification to fail because the sig is wrong. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_sig_ke_params_fail_bad_sig( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + data.server_cert_data.sig.sig[31] = data.server_cert_data.sig.sig[31].wrapping_add(1); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidServerSignature + )); + } + + /// Expect to fail because the dns name is not in the cert. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_check_dns_name_present_in_cert_fail_bad_host( + crypto_provider: &CryptoProvider, + #[case] data: ConnectionFixture, + ) { + let bad_name = ServerName::from("badhost.com"); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &bad_name, + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidCert + )); + } + + /// Expect to fail because the ephemeral key provided is wrong. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_invalid_ephemeral_key( + crypto_provider: &CryptoProvider, + #[case] data: ConnectionFixture, + ) { + let wrong_ephemeral_key = ServerEphemKey { + typ: KeyType::SECP256R1, + key: Vec::::from_hex(include_bytes!("./fixtures/data/unknown/pubkey")).unwrap(), + }; + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + &wrong_ephemeral_key, + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::InvalidServerEphemeralKey + )); + } + + /// Expect to fail when no cert provided. + #[rstest] + #[case::tlsnotary(tlsnotary())] + #[case::appliedzkp(appliedzkp())] + fn test_verify_cert_chain_fail_no_cert( + crypto_provider: &CryptoProvider, + #[case] mut data: ConnectionFixture, + ) { + // Empty certs + data.server_cert_data.certs = Vec::new(); + + let err = data.server_cert_data.verify_with_provider( + crypto_provider, + data.connection_info.time, + data.server_ephemeral_key(), + &ServerName::from(data.server_name.as_ref()), + ); + + assert!(matches!( + err.unwrap_err(), + CertificateVerificationError::MissingCerts + )); + } +} diff --git a/crates/core/src/fixtures.rs b/crates/core/src/fixtures.rs index ba580a80eb..44ab905fd6 100644 --- a/crates/core/src/fixtures.rs +++ b/crates/core/src/fixtures.rs @@ -83,7 +83,7 @@ impl ConnectionFixture { Certificate(include_bytes!("fixtures/data/appliedzkp.org/ca.der").to_vec()), ], sig: ServerSignature { - scheme: SignatureScheme::RSA_PKCS1_SHA256, + scheme: SignatureScheme::ECDSA_NISTP256_SHA256, sig: Vec::::from_hex(include_bytes!( "fixtures/data/appliedzkp.org/signature" )) @@ -109,6 +109,15 @@ impl ConnectionFixture { }, } } + + /// Returns the server_ephemeral_key fixture. + pub fn server_ephemeral_key(&self) -> &ServerEphemKey { + let HandshakeData::V1_2(HandshakeDataV1_2 { + server_ephemeral_key, + .. + }) = &self.server_cert_data.handshake; + server_ephemeral_key + } } /// Returns an encoding provider fixture. diff --git a/crates/core/src/fixtures/data/unknown/pubkey b/crates/core/src/fixtures/data/unknown/pubkey new file mode 100644 index 0000000000..e9166c19fe --- /dev/null +++ b/crates/core/src/fixtures/data/unknown/pubkey @@ -0,0 +1 @@ +14e1f634ecfee5bd4f987f8c571146cb2acb432e400b2fabcbd8ed77f6ef08bd5496cd51d449ce131efd74a24d07b01c38ec794d22d3d43b2b05b907e72797534e \ No newline at end of file diff --git a/crates/core/src/index.rs b/crates/core/src/index.rs index 3187cb8549..6e974f5ef0 100644 --- a/crates/core/src/index.rs +++ b/crates/core/src/index.rs @@ -104,3 +104,74 @@ impl From> for Index { }) } } + +#[cfg(test)] +mod test { + use utils::range::RangeSet; + + use super::*; + + #[derive(PartialEq, Debug, Clone)] + struct Stub { + field_index: FieldId, + index: Idx, + } + + impl From> for Index { + fn from(items: Vec) -> Self { + Self::new(items, |item: &Stub| (&item.field_index, &item.index)) + } + } + + fn stubs() -> Vec { + vec![ + Stub { + field_index: FieldId(1), + index: Idx::new(RangeSet::from([0..1, 18..21])), + }, + Stub { + field_index: FieldId(2), + index: Idx::new(RangeSet::from([1..5, 8..11])), + }, + ] + } + + #[test] + fn test_successful_retrieval() { + let stub_a_index = Idx::new(RangeSet::from([0..4, 7..10])); + let stub_b_field_index = FieldId(8); + + let stubs = vec![ + Stub { + field_index: FieldId(1), + index: stub_a_index.clone(), + }, + Stub { + field_index: stub_b_field_index, + index: Idx::new(RangeSet::from([1..5, 8..11])), + }, + ]; + let stubs_index: Index = stubs.clone().into(); + + assert_eq!( + stubs_index.get_by_field_id(&stub_b_field_index), + Some(&stubs[1]) + ); + assert_eq!( + stubs_index.get_by_transcript_idx(&stub_a_index), + Some(&stubs[0]) + ); + } + + #[test] + fn test_failed_retrieval() { + let stubs = stubs(); + let stubs_index: Index = stubs.clone().into(); + + let wrong_index = Idx::new(RangeSet::from([0..3, 4..5])); + let wrong_field_index = FieldId(200); + + assert_eq!(stubs_index.get_by_field_id(&wrong_field_index), None); + assert_eq!(stubs_index.get_by_transcript_idx(&wrong_index), None); + } +} diff --git a/crates/core/src/request.rs b/crates/core/src/request.rs index c165b3b162..32818ac6d7 100644 --- a/crates/core/src/request.rs +++ b/crates/core/src/request.rs @@ -87,3 +87,171 @@ impl Request { #[derive(Debug, thiserror::Error)] #[error("inconsistent attestation: {0}")] pub struct InconsistentAttestation(String); + +#[cfg(test)] +mod test { + use super::*; + + use crate::{ + attestation::{Attestation, AttestationConfig}, + connection::{HandshakeData, HandshakeDataV1_2, ServerCertOpening, TranscriptLength}, + fixtures::{encoder_seed, encoding_provider, ConnectionFixture}, + hash::{Blake3, Hash, HashAlgId}, + signing::SignatureAlgId, + transcript::{encoding::EncodingTree, Transcript, TranscriptCommitConfigBuilder}, + CryptoProvider, + }; + + use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; + + fn attestation(payload: (Request, ConnectionFixture)) -> Attestation { + let (request, connection) = payload; + + let ConnectionFixture { + connection_info, + server_cert_data, + .. + } = connection; + + let HandshakeData::V1_2(HandshakeDataV1_2 { + server_ephemeral_key, + .. + }) = server_cert_data.handshake.clone(); + + let mut provider = CryptoProvider::default(); + provider.signer.set_secp256k1(&[42u8; 32]).unwrap(); + + let attestation_config = AttestationConfig::builder() + .supported_signature_algs([SignatureAlgId::SECP256K1]) + .build() + .unwrap(); + + let mut attestation_builder = Attestation::builder(&attestation_config) + .accept_request(request.clone()) + .unwrap(); + + attestation_builder + .connection_info(connection_info.clone()) + .server_ephemeral_key(server_ephemeral_key) + .encoding_seed(encoder_seed().to_vec()); + + attestation_builder.build(&provider).unwrap() + } + + fn request_and_connection() -> (Request, ConnectionFixture) { + let provider = CryptoProvider::default(); + + let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); + let (sent_len, recv_len) = transcript.len(); + // Plaintext encodings which the Prover obtained from GC evaluation + let encodings_provider = encoding_provider(GET_WITH_HEADER, OK_JSON); + + // At the end of the TLS connection the Prover holds the: + let ConnectionFixture { + server_name, + server_cert_data, + .. + } = ConnectionFixture::tlsnotary(transcript.length()); + + // Prover specifies the ranges it wants to commit to. + let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); + transcript_commitment_builder + .commit_sent(&(0..sent_len)) + .unwrap() + .commit_recv(&(0..recv_len)) + .unwrap(); + + let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); + + // Prover constructs encoding tree. + let encoding_tree = EncodingTree::new( + &Blake3::default(), + transcripts_commitment_config.iter_encoding(), + &encodings_provider, + &transcript.length(), + ) + .unwrap(); + + let request_config = RequestConfig::default(); + let mut request_builder = Request::builder(&request_config); + + request_builder + .server_name(server_name.clone()) + .server_cert_data(server_cert_data) + .transcript(transcript.clone()) + .encoding_tree(encoding_tree); + let (request, _) = request_builder.build(&provider).unwrap(); + + (request, ConnectionFixture::tlsnotary(transcript.length())) + } + + #[test] + fn test_success() { + let (request, connection) = request_and_connection(); + + let attestation = attestation((request.clone(), connection)); + + assert!(request.validate(&attestation).is_ok()) + } + + #[test] + fn test_wrong_signature_alg() { + let (mut request, connection) = request_and_connection(); + + let attestation = attestation((request.clone(), connection)); + + request.signature_alg = SignatureAlgId::SECP256R1; + + let res = request.validate(&attestation); + assert!(res.is_err()); + } + + #[test] + fn test_wrong_hash_alg() { + let (mut request, connection) = request_and_connection(); + + let attestation = attestation((request.clone(), connection)); + + request.hash_alg = HashAlgId::SHA256; + + let res = request.validate(&attestation); + assert!(res.is_err()) + } + + #[test] + fn test_wrong_server_commitment() { + let (mut request, connection) = request_and_connection(); + + let attestation = attestation((request.clone(), connection)); + + let ConnectionFixture { + server_cert_data, .. + } = ConnectionFixture::appliedzkp(TranscriptLength { + sent: 100, + received: 100, + }); + let opening = ServerCertOpening::new(server_cert_data); + + let crypto_provider = CryptoProvider::default(); + request.server_cert_commitment = + opening.commit(crypto_provider.hash.get(&HashAlgId::BLAKE3).unwrap()); + + let res = request.validate(&attestation); + assert!(res.is_err()) + } + + #[test] + fn test_wrong_encoding_commitment_root() { + let (mut request, connection) = request_and_connection(); + + let attestation = attestation((request.clone(), connection)); + + request.encoding_commitment_root = Some(TypedHash { + alg: HashAlgId::BLAKE3, + value: Hash::default(), + }); + + let res = request.validate(&attestation); + assert!(res.is_err()) + } +} diff --git a/crates/core/src/signing.rs b/crates/core/src/signing.rs index 13edbace37..eb2c902231 100644 --- a/crates/core/src/signing.rs +++ b/crates/core/src/signing.rs @@ -372,3 +372,78 @@ mod secp256r1 { } pub use secp256r1::{Secp256r1Signer, Secp256r1Verifier}; + +#[cfg(test)] +mod test { + use super::*; + use rand_core::OsRng; + use rstest::{fixture, rstest}; + + #[fixture] + #[once] + fn secp256k1_signer() -> Secp256k1Signer { + let signing_key = k256::ecdsa::SigningKey::random(&mut OsRng); + Secp256k1Signer::new(&signing_key.to_bytes()).unwrap() + } + + #[fixture] + #[once] + fn secp256r1_signer() -> Secp256r1Signer { + let signing_key = p256::ecdsa::SigningKey::random(&mut OsRng); + Secp256r1Signer::new(&signing_key.to_bytes()).unwrap() + } + + #[rstest] + fn test_secp256k1_success(secp256k1_signer: &Secp256k1Signer) { + assert_eq!(secp256k1_signer.alg_id(), SignatureAlgId::SECP256K1); + + let msg = "test payload"; + let signature = secp256k1_signer.sign(msg.as_bytes()).unwrap(); + let verifying_key = secp256k1_signer.verifying_key(); + + let verifier = Secp256k1Verifier {}; + assert_eq!(verifier.alg_id(), SignatureAlgId::SECP256K1); + let result = verifier.verify(&verifying_key, msg.as_bytes(), &signature.data); + assert!(result.is_ok()); + } + + #[rstest] + fn test_secp256r1_success(secp256r1_signer: &Secp256r1Signer) { + assert_eq!(secp256r1_signer.alg_id(), SignatureAlgId::SECP256R1); + + let msg = "test payload"; + let signature = secp256r1_signer.sign(msg.as_bytes()).unwrap(); + let verifying_key = secp256r1_signer.verifying_key(); + + let verifier = Secp256r1Verifier {}; + assert_eq!(verifier.alg_id(), SignatureAlgId::SECP256R1); + let result = verifier.verify(&verifying_key, msg.as_bytes(), &signature.data); + assert!(result.is_ok()); + } + + #[rstest] + #[case::wrong_signer(&secp256r1_signer(), false, false)] + #[case::corrupted_signature(&secp256k1_signer(), true, false)] + #[case::wrong_signature(&secp256k1_signer(), false, true)] + fn test_failure( + #[case] signer: &dyn Signer, + #[case] corrupted_signature: bool, + #[case] wrong_signature: bool, + ) { + let msg = "test payload"; + let mut signature = signer.sign(msg.as_bytes()).unwrap(); + let verifying_key = signer.verifying_key(); + + if corrupted_signature { + signature.data.push(0); + } + + if wrong_signature { + signature = signer.sign("different payload".as_bytes()).unwrap(); + } + + let verifier = Secp256k1Verifier {}; + let result = verifier.verify(&verifying_key, msg.as_bytes(), &signature.data); + assert!(result.is_err()); + } +}