23
23
) ]
24
24
25
25
use std:: fmt;
26
+ use std:: fmt:: Display ;
26
27
use std:: str:: FromStr ;
27
28
28
29
use chrono:: { DateTime , Duration , Utc } ;
@@ -196,7 +197,7 @@ impl SecretKey {
196
197
/// Signs some data with the secret key and returns the signature.
197
198
///
198
199
/// This is will sign with the default header.
199
- pub fn sign ( & self , data : & [ u8 ] ) -> String {
200
+ pub fn sign ( & self , data : & [ u8 ] ) -> Signature {
200
201
self . sign_with_header ( data, & SignatureHeader :: default ( ) )
201
202
}
202
203
@@ -205,7 +206,7 @@ impl SecretKey {
205
206
///
206
207
/// The default behavior is to attach the timestamp in the header to the
207
208
/// signature so that old signatures on verification can be rejected.
208
- pub fn sign_with_header ( & self , data : & [ u8 ] , header : & SignatureHeader ) -> String {
209
+ pub fn sign_with_header ( & self , data : & [ u8 ] , header : & SignatureHeader ) -> Signature {
209
210
let mut header =
210
211
serde_json:: to_vec ( & header) . expect ( "attempted to pack non json safe header" ) ;
211
212
let header_encoded = BASE64URL_NOPAD . encode ( & header) ;
@@ -215,11 +216,11 @@ impl SecretKey {
215
216
let mut sig_encoded = BASE64URL_NOPAD . encode ( & sig. to_bytes ( ) ) ;
216
217
sig_encoded. push ( '.' ) ;
217
218
sig_encoded. push_str ( & header_encoded) ;
218
- sig_encoded
219
+ Signature ( sig_encoded)
219
220
}
220
221
221
222
/// Packs some serializable data into JSON and signs it with the default header.
222
- pub fn pack < S : Serialize > ( & self , data : S ) -> ( Vec < u8 > , String ) {
223
+ pub fn pack < S : Serialize > ( & self , data : S ) -> ( Vec < u8 > , Signature ) {
223
224
self . pack_with_header ( data, & SignatureHeader :: default ( ) )
224
225
}
225
226
@@ -228,7 +229,7 @@ impl SecretKey {
228
229
& self ,
229
230
data : S ,
230
231
header : & SignatureHeader ,
231
- ) -> ( Vec < u8 > , String ) {
232
+ ) -> ( Vec < u8 > , Signature ) {
232
233
// this can only fail if we deal with badly formed data. In that case we
233
234
// consider that a panic. Should not happen.
234
235
let json = serde_json:: to_vec ( & data) . expect ( "attempted to pack non json safe data" ) ;
@@ -302,8 +303,8 @@ pub struct PublicKey {
302
303
impl PublicKey {
303
304
/// Verifies the signature and returns the embedded signature
304
305
/// header.
305
- pub fn verify_meta ( & self , data : & [ u8 ] , sig : & str ) -> Option < SignatureHeader > {
306
- let mut iter = sig. splitn ( 2 , '.' ) ;
306
+ pub fn verify_meta ( & self , data : & [ u8 ] , sig : SignatureRef < ' _ > ) -> Option < SignatureHeader > {
307
+ let mut iter = sig. 0 . splitn ( 2 , '.' ) ;
307
308
let sig_bytes = match iter. next ( ) {
308
309
Some ( sig_encoded) => BASE64URL_NOPAD . decode ( sig_encoded. as_bytes ( ) ) . ok ( ) ?,
309
310
None => return None ,
@@ -325,12 +326,17 @@ impl PublicKey {
325
326
}
326
327
327
328
/// Verifies a signature but discards the header.
328
- pub fn verify ( & self , data : & [ u8 ] , sig : & str ) -> bool {
329
+ pub fn verify ( & self , data : & [ u8 ] , sig : SignatureRef < ' _ > ) -> bool {
329
330
self . verify_meta ( data, sig) . is_some ( )
330
331
}
331
332
332
333
/// Verifies a signature and checks the timestamp.
333
- pub fn verify_timestamp ( & self , data : & [ u8 ] , sig : & str , max_age : Option < Duration > ) -> bool {
334
+ pub fn verify_timestamp (
335
+ & self ,
336
+ data : & [ u8 ] ,
337
+ sig : SignatureRef < ' _ > ,
338
+ max_age : Option < Duration > ,
339
+ ) -> bool {
334
340
self . verify_meta ( data, sig)
335
341
. map ( |header| max_age. is_none ( ) || !header. expired ( max_age. unwrap ( ) ) )
336
342
. unwrap_or ( false )
@@ -340,7 +346,7 @@ impl PublicKey {
340
346
pub fn unpack_meta < D : DeserializeOwned > (
341
347
& self ,
342
348
data : & [ u8 ] ,
343
- signature : & str ,
349
+ signature : SignatureRef < ' _ > ,
344
350
) -> Result < ( SignatureHeader , D ) , UnpackError > {
345
351
if let Some ( header) = self . verify_meta ( data, signature) {
346
352
serde_json:: from_slice ( data)
@@ -358,7 +364,7 @@ impl PublicKey {
358
364
pub fn unpack < D : DeserializeOwned > (
359
365
& self ,
360
366
data : & [ u8 ] ,
361
- signature : & str ,
367
+ signature : SignatureRef < ' _ > ,
362
368
max_age : Option < Duration > ,
363
369
) -> Result < D , UnpackError > {
364
370
let ( header, data) = self . unpack_meta ( data, signature) ?;
@@ -575,7 +581,7 @@ impl RegisterRequest {
575
581
/// the data is returned.
576
582
pub fn bootstrap_unpack (
577
583
data : & [ u8 ] ,
578
- signature : & str ,
584
+ signature : SignatureRef < ' _ > ,
579
585
max_age : Option < Duration > ,
580
586
) -> Result < RegisterRequest , UnpackError > {
581
587
let req: RegisterRequest = serde_json:: from_slice ( data) . map_err ( UnpackError :: BadPayload ) ?;
@@ -653,7 +659,7 @@ impl RegisterResponse {
653
659
/// Unpacks the register response and validates signatures.
654
660
pub fn unpack (
655
661
data : & [ u8 ] ,
656
- signature : & str ,
662
+ signature : SignatureRef < ' _ > ,
657
663
secret : & [ u8 ] ,
658
664
max_age : Option < Duration > ,
659
665
) -> Result < ( Self , RegisterState ) , UnpackError > {
@@ -687,6 +693,80 @@ impl RegisterResponse {
687
693
}
688
694
}
689
695
696
+ /// A wrapper around a String that represents a signature.
697
+ #[ derive( Debug , Clone , PartialEq ) ]
698
+ pub struct Signature ( pub String ) ;
699
+
700
+ impl Display for Signature {
701
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
702
+ write ! ( f, "{}" , self . 0 )
703
+ }
704
+ }
705
+
706
+ impl Signature {
707
+ /// Verifies the signature against any of the provided public keys.
708
+ ///
709
+ /// Returns `true` if the signature is valid with one of the given
710
+ /// public keys and satisfies the timestamp constraints defined by `start_time`
711
+ /// and `max_age`.
712
+ pub fn verify_any (
713
+ & self ,
714
+ public_key : & [ PublicKey ] ,
715
+ start_time : DateTime < Utc > ,
716
+ max_age : Duration ,
717
+ ) -> bool {
718
+ public_key
719
+ . iter ( )
720
+ . any ( |p| self . verify ( p, start_time, max_age) )
721
+ }
722
+
723
+ /// Verifies the signature using the specified public key.
724
+ ///
725
+ /// The signature is considered valid if it can be verified using the given
726
+ /// public key and its embedded timestamp falls within the valid time range,
727
+ /// starting from `start_time` and not exceeding `max_age`.
728
+ pub fn verify (
729
+ & self ,
730
+ public_key : & PublicKey ,
731
+ start_time : DateTime < Utc > ,
732
+ max_age : Duration ,
733
+ ) -> bool {
734
+ let Some ( header) = public_key. verify_meta ( & [ ] , self . as_signature_ref ( ) ) else {
735
+ return false ;
736
+ } ;
737
+ let Some ( timestamp) = header. timestamp else {
738
+ return false ;
739
+ } ;
740
+ let elapsed = start_time - timestamp;
741
+ elapsed >= Duration :: zero ( ) && elapsed <= max_age
742
+ }
743
+
744
+ /// Verifies the signature against the given data and public key.
745
+ ///
746
+ /// Returns `true` if the signature is valid for the provided `data`
747
+ /// when verified with the given public key.
748
+ pub fn verify_bytes ( & self , data : & [ u8 ] , public_key : & PublicKey ) -> bool {
749
+ public_key
750
+ . verify_meta ( data, self . as_signature_ref ( ) )
751
+ . is_some ( )
752
+ }
753
+
754
+ /// Returns a borrowed view of the signature as a `SignatureRef`.
755
+ ///
756
+ /// This method provides a lightweight reference wrapper over the internal
757
+ /// signature data.
758
+ pub fn as_signature_ref ( & self ) -> SignatureRef < ' _ > {
759
+ SignatureRef ( self . 0 . as_str ( ) )
760
+ }
761
+ }
762
+
763
+ /// A borrowed reference to a signature string used for validation.
764
+ ///
765
+ /// `SignatureRef` provides a view into the signature data as a string slice,
766
+ /// allowing verification to work with borrowed data without unnecessary allocations.
767
+ /// This type is typically obtained by borrowing from an owned [`Signature`].
768
+ pub struct SignatureRef < ' a > ( pub & ' a str ) ;
769
+
690
770
#[ cfg( test) ]
691
771
mod tests {
692
772
use super :: * ;
@@ -753,10 +833,10 @@ mod tests {
753
833
let data = b"Hello World!" ;
754
834
755
835
let sig = sk. sign ( data) ;
756
- assert ! ( pk. verify( data, & sig) ) ;
836
+ assert ! ( pk. verify( data, sig. as_signature_ref ( ) ) ) ;
757
837
758
838
let bad_sig = "jgubwSf2wb2wuiRpgt2H9_bdDSMr88hXLp5zVuhbr65EGkSxOfT5ILIWr623twLgLd0bDgHg6xzOaUCX7XvUCw" ;
759
- assert ! ( !pk. verify( data, bad_sig) ) ;
839
+ assert ! ( !pk. verify( data, SignatureRef ( bad_sig) ) ) ;
760
840
}
761
841
762
842
#[ test]
@@ -774,8 +854,12 @@ mod tests {
774
854
let ( request_bytes, request_sig) = sk. pack ( request) ;
775
855
776
856
// attempt to get the data through bootstrap unpacking.
777
- let request =
778
- RegisterRequest :: bootstrap_unpack ( & request_bytes, & request_sig, Some ( max_age) ) . unwrap ( ) ;
857
+ let request = RegisterRequest :: bootstrap_unpack (
858
+ & request_bytes,
859
+ request_sig. as_signature_ref ( ) ,
860
+ Some ( max_age) ,
861
+ )
862
+ . unwrap ( ) ;
779
863
assert_eq ! ( request. relay_id( ) , relay_id) ;
780
864
assert_eq ! ( request. public_key( ) , & pk) ;
781
865
@@ -800,7 +884,7 @@ mod tests {
800
884
let ( response_bytes, response_sig) = sk. pack ( response) ;
801
885
let ( response, _) = RegisterResponse :: unpack (
802
886
& response_bytes,
803
- & response_sig,
887
+ response_sig. as_signature_ref ( ) ,
804
888
upstream_secret,
805
889
Some ( max_age) ,
806
890
)
@@ -836,8 +920,12 @@ mod tests {
836
920
println ! ( "REQUEST_SIG = \" {request_sig}\" " ) ;
837
921
838
922
// attempt to get the data through bootstrap unpacking.
839
- let request =
840
- RegisterRequest :: bootstrap_unpack ( & request_bytes, & request_sig, Some ( max_age) ) . unwrap ( ) ;
923
+ let request = RegisterRequest :: bootstrap_unpack (
924
+ & request_bytes,
925
+ request_sig. as_signature_ref ( ) ,
926
+ Some ( max_age) ,
927
+ )
928
+ . unwrap ( ) ;
841
929
842
930
let upstream_secret = b"secret" ;
843
931
@@ -906,4 +994,61 @@ mod tests {
906
994
fn test_relay_version_from_str ( ) {
907
995
assert_eq ! ( RelayVersion :: new( 20 , 7 , 0 ) , "20.7.0" . parse( ) . unwrap( ) ) ;
908
996
}
997
+
998
+ #[ test]
999
+ fn test_verify_any ( ) {
1000
+ let pair1 = generate_key_pair ( ) ;
1001
+ let pair2 = generate_key_pair ( ) ;
1002
+ let pair3 = generate_key_pair ( ) ;
1003
+
1004
+ let signature = pair3. 0 . sign ( & [ ] ) ;
1005
+ assert ! ( signature. verify_any(
1006
+ & [ pair1. 1 , pair2. 1 , pair3. 1 ] ,
1007
+ Utc :: now( ) ,
1008
+ Duration :: seconds( 10 )
1009
+ ) ) ;
1010
+ }
1011
+
1012
+ #[ test]
1013
+ fn test_verify_max_age ( ) {
1014
+ let pair = generate_key_pair ( ) ;
1015
+ let signature = pair. 0 . sign ( & [ ] ) ;
1016
+ let start_time = Utc :: now ( ) ;
1017
+ // The signature is valid in general
1018
+ assert ! ( signature. verify( & pair. 1 , start_time, Duration :: seconds( 10 ) ) ) ;
1019
+ // Signature is no longer valid because too much time elapsed
1020
+ assert ! ( !signature. verify(
1021
+ & pair. 1 ,
1022
+ start_time - Duration :: seconds( 1 ) ,
1023
+ Duration :: milliseconds( 500 )
1024
+ ) )
1025
+ }
1026
+
1027
+ #[ test]
1028
+ fn test_verify_any_max_age ( ) {
1029
+ let start_time = Utc :: now ( ) ;
1030
+ let pair1 = generate_key_pair ( ) ;
1031
+ let pair2 = generate_key_pair ( ) ;
1032
+ let pair3 = generate_key_pair ( ) ;
1033
+
1034
+ let header = SignatureHeader {
1035
+ timestamp : Some ( start_time) ,
1036
+ } ;
1037
+ let signature = pair3. 0 . sign_with_header ( & [ ] , & header) ;
1038
+
1039
+ let public_keys = & [ pair1. 1 , pair2. 1 , pair3. 1 ] ;
1040
+
1041
+ // Signature still valid after 1 second
1042
+ assert ! ( signature. verify_any(
1043
+ public_keys,
1044
+ start_time + Duration :: seconds( 1 ) ,
1045
+ Duration :: seconds( 2 )
1046
+ ) ) ;
1047
+ // Signature is no longer valid because too much time elapsed
1048
+ assert ! ( !signature. verify_any(
1049
+ public_keys,
1050
+ start_time + Duration :: seconds( 3 ) ,
1051
+ Duration :: seconds( 2 )
1052
+ ) )
1053
+ }
909
1054
}
0 commit comments