@@ -58,6 +58,11 @@ pub enum CertificateVerifierError {
58
58
#[ error( "certificate epoch unmatch error" ) ]
59
59
CertificateEpochUnmatch ,
60
60
61
+ /// Error raised when validating the certificate chain if the current [Certificate]
62
+ /// `epoch` is neither equal to the previous certificate `epoch` nor to the previous certificate `epoch - 1`
63
+ #[ error( "certificate chain missing epoch error" ) ]
64
+ CertificateChainMissingEpoch ,
65
+
61
66
/// Error raised when validating the certificate chain if the chain loops.
62
67
#[ error( "certificate chain infinite loop error" ) ]
63
68
CertificateChainInfiniteLoop ,
@@ -224,6 +229,21 @@ impl MithrilCertificateVerifier {
224
229
225
230
Err ( anyhow ! ( CertificateVerifierError :: CertificateEpochUnmatch ) )
226
231
}
232
+
233
+ fn verify_epoch_chaining (
234
+ & self ,
235
+ certificate : & Certificate ,
236
+ previous_certificate : & Certificate ,
237
+ ) -> StdResult < ( ) > {
238
+ if certificate. epoch . has_gap_with ( & previous_certificate. epoch ) {
239
+ return Err ( anyhow ! (
240
+ CertificateVerifierError :: CertificateChainMissingEpoch
241
+ ) ) ;
242
+ }
243
+
244
+ Ok ( ( ) )
245
+ }
246
+
227
247
fn verify_aggregate_verification_key_chaining (
228
248
& self ,
229
249
certificate : & Certificate ,
@@ -312,6 +332,7 @@ impl CertificateVerifier for MithrilCertificateVerifier {
312
332
& certificate. metadata . protocol_parameters ,
313
333
) ?;
314
334
self . verify_epoch_matches_protocol_message ( certificate) ?;
335
+ self . verify_epoch_chaining ( certificate, previous_certificate) ?;
315
336
self . verify_previous_hash_matches_previous_certificate_hash (
316
337
certificate,
317
338
previous_certificate,
@@ -366,7 +387,9 @@ mod tests {
366
387
367
388
use crate :: certificate_chain:: { CertificateRetrieverError , FakeCertificaterRetriever } ;
368
389
use crate :: crypto_helper:: { tests_setup:: * , ProtocolClerk } ;
369
- use crate :: test_utils:: { MithrilFixtureBuilder , TestLogger } ;
390
+ use crate :: test_utils:: {
391
+ CertificateChainBuilder , CertificateChainBuilderContext , MithrilFixtureBuilder , TestLogger ,
392
+ } ;
370
393
371
394
macro_rules! assert_error_matches {
372
395
( $expected_error: path, $error: expr ) => { {
@@ -693,6 +716,87 @@ mod tests {
693
716
)
694
717
}
695
718
719
+ #[ tokio:: test]
720
+ async fn verify_standard_certificate_fails_if_epoch_unmatch ( ) {
721
+ let ( total_certificates, certificates_per_epoch) = ( 5 , 1 ) ;
722
+ let ( fake_certificates, _) =
723
+ setup_certificate_chain ( total_certificates, certificates_per_epoch) ;
724
+ let verifier = MockDependencyInjector :: new ( ) . build_certificate_verifier ( ) ;
725
+ let mut certificate = fake_certificates[ 0 ] . clone ( ) ;
726
+ certificate. epoch -= 1 ;
727
+ certificate. hash = certificate. compute_hash ( ) ;
728
+ let previous_certificate = fake_certificates[ 1 ] . clone ( ) ;
729
+
730
+ let error = verifier
731
+ . verify_standard_certificate ( & certificate, & previous_certificate)
732
+ . await
733
+ . expect_err ( "verify_standard_certificate should fail" ) ;
734
+
735
+ assert_error_matches ! ( CertificateVerifierError :: CertificateEpochUnmatch , error)
736
+ }
737
+
738
+ #[ tokio:: test]
739
+ async fn verify_standard_certificate_fails_if_has_missing_epoch ( ) {
740
+ fn create_epoch_gap_certificate (
741
+ certificate : Certificate ,
742
+ context : & CertificateChainBuilderContext ,
743
+ ) -> Certificate {
744
+ let fixture = context. fixture ;
745
+ let modified_epoch = certificate. epoch + 1 ;
746
+ let mut protocol_message = certificate. protocol_message . to_owned ( ) ;
747
+ protocol_message. set_message_part (
748
+ ProtocolMessagePartKey :: CurrentEpoch ,
749
+ modified_epoch. to_string ( ) ,
750
+ ) ;
751
+ let signed_message = protocol_message. compute_hash ( ) ;
752
+ let mut modified_certificate = certificate;
753
+ modified_certificate. epoch = modified_epoch;
754
+ modified_certificate. protocol_message = protocol_message;
755
+ modified_certificate. signed_message = signed_message. clone ( ) ;
756
+ let single_signatures = fixture
757
+ . signers_fixture ( )
758
+ . iter ( )
759
+ . filter_map ( |s| s. protocol_signer . sign ( signed_message. as_bytes ( ) ) )
760
+ . collect :: < Vec < _ > > ( ) ;
761
+ let clerk = ProtocolClerk :: from_signer ( & fixture. signers_fixture ( ) [ 0 ] . protocol_signer ) ;
762
+ let modified_multi_signature = clerk
763
+ . aggregate ( & single_signatures, signed_message. as_bytes ( ) )
764
+ . unwrap ( ) ;
765
+ modified_certificate. signature = CertificateSignature :: MultiSignature (
766
+ modified_certificate. signed_entity_type ( ) ,
767
+ modified_multi_signature. into ( ) ,
768
+ ) ;
769
+
770
+ modified_certificate
771
+ }
772
+
773
+ let ( total_certificates, certificates_per_epoch) = ( 5 , 1 ) ;
774
+ let ( fake_certificates, _) = CertificateChainBuilder :: new ( )
775
+ . with_total_certificates ( total_certificates)
776
+ . with_certificates_per_epoch ( certificates_per_epoch)
777
+ . with_standard_certificate_processor ( & |certificate, context| {
778
+ if context. is_last_certificate ( ) {
779
+ create_epoch_gap_certificate ( certificate, context)
780
+ } else {
781
+ certificate
782
+ }
783
+ } )
784
+ . build ( ) ;
785
+ let verifier = MockDependencyInjector :: new ( ) . build_certificate_verifier ( ) ;
786
+ let certificate = fake_certificates[ 0 ] . clone ( ) ;
787
+ let previous_certificate = fake_certificates[ 1 ] . clone ( ) ;
788
+
789
+ let error = verifier
790
+ . verify_standard_certificate ( & certificate, & previous_certificate)
791
+ . await
792
+ . expect_err ( "verify_standard_certificate should fail" ) ;
793
+
794
+ assert_error_matches ! (
795
+ CertificateVerifierError :: CertificateChainMissingEpoch ,
796
+ error
797
+ )
798
+ }
799
+
696
800
#[ tokio:: test]
697
801
async fn verify_standard_certificate_fails_if_certificate_previous_hash_unmatch ( ) {
698
802
let ( total_certificates, certificates_per_epoch) = ( 5 , 1 ) ;
0 commit comments