1- use crate :: components:: CoveredComponent ;
1+ use crate :: components:: { CoveredComponent , DerivedComponent , HTTPField } ;
22use crate :: keyring:: { Algorithm , KeyRing } ;
33use indexmap:: IndexMap ;
44use sfv:: SerializeValue ;
@@ -182,7 +182,11 @@ impl SignatureBase {
182182
183183 for ( component, serialized_value) in self . components {
184184 let sfv_item = match component {
185- CoveredComponent :: HTTP ( http) => sfv:: Item :: try_from ( http) ?,
185+ CoveredComponent :: HTTP ( http) => {
186+ let HTTPField { name, parameters } = http;
187+ let name = name. to_lowercase ( ) ;
188+ sfv:: Item :: try_from ( HTTPField { name, parameters } ) ?
189+ }
186190 CoveredComponent :: Derived ( derived) => sfv:: Item :: try_from ( derived) ?,
187191 } ;
188192
@@ -245,10 +249,12 @@ pub trait SignedMessage {
245249/// Trait that messages seeking signing should implement to generate `Signature-Input`
246250/// and `Signature` header contents.
247251pub trait UnsignedMessage {
248- /// Obtain a list of covered components to be included. HTTP fields must be lowercased before
249- /// emitting. It is NOT RECOMMENDED to include `signature` and `signature-input` fields here.
250- /// If signing a Web Bot Auth message, and `Signature-Agent` header is intended present, you MUST
251- /// include it as a component here for successful verification.
252+ /// Obtain a list of covered components to be included. The `MessageSigner` struct will
253+ /// apply URL normalization to the authority, scheme, path and query derived components,
254+ /// as well as lowercase HTTP field names. It is NOT RECOMMENDED to include `Signature` and
255+ /// `Signature-Input` fields here. If signing a Web Bot Auth message, and `Signature-Agent`
256+ /// header is intended present, you MUST include it as a component here for successful
257+ /// verification.
252258 fn fetch_components_to_cover ( & self ) -> IndexMap < CoveredComponent , String > ;
253259 /// Store the contents of a generated `Signature-Input` and `Signature` header value.
254260 /// It is the responsibility of the application to generate a consistent label for both.
@@ -283,7 +289,7 @@ impl MessageSigner {
283289 algorithm : Algorithm ,
284290 signing_key : & Vec < u8 > ,
285291 ) -> Result < ( ) , ImplementationError > {
286- let components_to_cover = message. fetch_components_to_cover ( ) ;
292+ let mut components_to_cover = message. fetch_components_to_cover ( ) ;
287293 let mut sfv_parameters = sfv:: Parameters :: new ( ) ;
288294
289295 sfv_parameters. insert (
@@ -352,6 +358,44 @@ impl MessageSigner {
352358 sfv:: BareItem :: Integer ( sfv:: Integer :: constant ( expires_as_i64) ) ,
353359 ) ;
354360
361+ // Handle RFC 9421 rules regarding normal form of different components
362+ for ( component, value) in components_to_cover. iter_mut ( ) {
363+ match component {
364+ CoveredComponent :: Derived ( DerivedComponent :: Authority { req : _ } ) => {
365+ // In normal form, default ports are removed. This has edge case when someone is using
366+ // HTTP with port 443 or HTTPS with port 80, but this is very unlikely.
367+ // We need to know the scheme to remove the default ports accurately, but
368+ // this info is not guaranteed to be present in the components to cover.
369+ for suffix in [ ":80" , ":443" , ":" ] {
370+ if let Some ( stripped) = value. strip_suffix ( suffix) {
371+ * value = stripped. to_string ( ) ;
372+ break ;
373+ }
374+ }
375+ value. make_ascii_lowercase ( ) ;
376+ }
377+ CoveredComponent :: Derived ( DerivedComponent :: Scheme { req : _ } ) => {
378+ value. make_ascii_lowercase ( ) ;
379+ }
380+ CoveredComponent :: Derived ( DerivedComponent :: Path { req : _ } ) => {
381+ * value = percent_encoding:: percent_decode ( value. as_bytes ( ) )
382+ . decode_utf8 ( )
383+ . map_err ( |_| ImplementationError :: InvalidUTF8Found ) ?
384+ . to_string ( ) ;
385+ if value. is_empty ( ) {
386+ * value = "/" . to_string ( ) ;
387+ }
388+ }
389+ CoveredComponent :: Derived ( DerivedComponent :: Query { req : _ } ) => {
390+ * value = percent_encoding:: percent_decode ( value. as_bytes ( ) )
391+ . decode_utf8 ( )
392+ . map_err ( |_| ImplementationError :: InvalidUTF8Found ) ?
393+ . to_string ( ) ;
394+ }
395+ _ => { }
396+ }
397+ }
398+
355399 let ( signature_base, signature_params_content) = SignatureBase {
356400 components : components_to_cover,
357401 parameters : sfv_parameters. into ( ) ,
@@ -547,6 +591,18 @@ mod tests {
547591
548592 use super :: * ;
549593
594+ const PRIVATE_KEY : [ u8 ; ed25519_dalek:: SECRET_KEY_LENGTH ] = [
595+ 0x9f , 0x83 , 0x62 , 0xf8 , 0x7a , 0x48 , 0x4a , 0x95 , 0x4e , 0x6e , 0x74 , 0x0c , 0x5b , 0x4c , 0x0e ,
596+ 0x84 , 0x22 , 0x91 , 0x39 , 0xa2 , 0x0a , 0xa8 , 0xab , 0x56 , 0xff , 0x66 , 0x58 , 0x6f , 0x6a , 0x7d ,
597+ 0x29 , 0xc5 ,
598+ ] ;
599+
600+ const PUBLIC_KEY : [ u8 ; ed25519_dalek:: PUBLIC_KEY_LENGTH ] = [
601+ 0x26 , 0xb4 , 0x0b , 0x8f , 0x93 , 0xff , 0xf3 , 0xd8 , 0x97 , 0x11 , 0x2f , 0x7e , 0xbc , 0x58 , 0x2b ,
602+ 0x23 , 0x2d , 0xbd , 0x72 , 0x51 , 0x7d , 0x08 , 0x2f , 0xe8 , 0x3c , 0xfb , 0x30 , 0xdd , 0xce , 0x43 ,
603+ 0xd1 , 0xbb ,
604+ ] ;
605+
550606 struct StandardTestVector { }
551607
552608 impl SignedMessage for StandardTestVector {
@@ -582,16 +638,11 @@ mod tests {
582638 #[ test]
583639 fn test_verifying_as_http_signature ( ) {
584640 let test = StandardTestVector { } ;
585- let public_key: [ u8 ; ed25519_dalek:: PUBLIC_KEY_LENGTH ] = [
586- 0x26 , 0xb4 , 0x0b , 0x8f , 0x93 , 0xff , 0xf3 , 0xd8 , 0x97 , 0x11 , 0x2f , 0x7e , 0xbc , 0x58 ,
587- 0x2b , 0x23 , 0x2d , 0xbd , 0x72 , 0x51 , 0x7d , 0x08 , 0x2f , 0xe8 , 0x3c , 0xfb , 0x30 , 0xdd ,
588- 0xce , 0x43 , 0xd1 , 0xbb ,
589- ] ;
590641 let mut keyring = KeyRing :: default ( ) ;
591642 keyring. import_raw (
592643 "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U" . to_string ( ) ,
593644 Algorithm :: Ed25519 ,
594- public_key . to_vec ( ) ,
645+ PUBLIC_KEY . to_vec ( ) ,
595646 ) ;
596647 let verifier = MessageVerifier :: parse ( & test, |( _, _) | true ) . unwrap ( ) ;
597648 let timing = verifier. verify ( & keyring, None ) . unwrap ( ) ;
@@ -637,12 +688,6 @@ mod tests {
637688 tag : "web-bot-auth" . into ( ) ,
638689 } ;
639690
640- let private_key: [ u8 ; ed25519_dalek:: SECRET_KEY_LENGTH ] = [
641- 0x9f , 0x83 , 0x62 , 0xf8 , 0x7a , 0x48 , 0x4a , 0x95 , 0x4e , 0x6e , 0x74 , 0x0c , 0x5b , 0x4c ,
642- 0x0e , 0x84 , 0x22 , 0x91 , 0x39 , 0xa2 , 0x0a , 0xa8 , 0xab , 0x56 , 0xff , 0x66 , 0x58 , 0x6f ,
643- 0x6a , 0x7d , 0x29 , 0xc5 ,
644- ] ;
645-
646691 let mut test = SigningTest { } ;
647692
648693 assert ! (
@@ -651,7 +696,7 @@ mod tests {
651696 & mut test,
652697 Duration :: from_secs( 10 ) ,
653698 Algorithm :: Ed25519 ,
654- & private_key . to_vec( )
699+ & PRIVATE_KEY . to_vec( )
655700 )
656701 . is_ok( )
657702 ) ;
@@ -694,4 +739,141 @@ mod tests {
694739 let ( base, _) = sigbase. into_ascii ( ) . unwrap ( ) ;
695740 assert_eq ! ( base, expected_base) ;
696741 }
742+
743+ #[ test]
744+ fn test_equivalent_forms_generate_same_signature ( ) {
745+ struct EquivalentForm1 {
746+ signature_input : Option < String > ,
747+ signature_header : Option < String > ,
748+ }
749+
750+ struct EquivalentForm2 {
751+ signature_input : Option < String > ,
752+ signature_header : Option < String > ,
753+ }
754+
755+ impl UnsignedMessage for EquivalentForm1 {
756+ fn fetch_components_to_cover ( & self ) -> IndexMap < CoveredComponent , String > {
757+ IndexMap :: from_iter ( [
758+ (
759+ CoveredComponent :: HTTP ( HTTPField {
760+ name : "content-length" . to_string ( ) ,
761+ parameters : HTTPFieldParametersSet ( vec ! [ ] ) ,
762+ } ) ,
763+ "18" . to_string ( ) ,
764+ ) ,
765+ (
766+ CoveredComponent :: Derived ( DerivedComponent :: Authority { req : false } ) ,
767+ "example.com" . to_string ( ) ,
768+ ) ,
769+ (
770+ CoveredComponent :: Derived ( DerivedComponent :: Scheme { req : false } ) ,
771+ "http" . to_string ( ) ,
772+ ) ,
773+ (
774+ CoveredComponent :: Derived ( DerivedComponent :: Path { req : false } ) ,
775+ "/~smith" . to_string ( ) ,
776+ ) ,
777+ (
778+ CoveredComponent :: Derived ( DerivedComponent :: Query { req : false } ) ,
779+ "?param=value&foo=bar&baz=bat~man" . to_string ( ) ,
780+ ) ,
781+ ] )
782+ }
783+
784+ fn register_header_contents (
785+ & mut self ,
786+ signature_input : String ,
787+ signature_header : String ,
788+ ) {
789+ self . signature_input = Some ( signature_input) ;
790+ self . signature_header = Some ( signature_header) ;
791+ }
792+ }
793+
794+ impl UnsignedMessage for EquivalentForm2 {
795+ fn fetch_components_to_cover ( & self ) -> IndexMap < CoveredComponent , String > {
796+ IndexMap :: from_iter ( [
797+ (
798+ CoveredComponent :: HTTP ( HTTPField {
799+ name : "CONTENT-LENGTH" . to_string ( ) ,
800+ parameters : HTTPFieldParametersSet ( vec ! [ ] ) ,
801+ } ) ,
802+ "18" . to_string ( ) ,
803+ ) ,
804+ (
805+ CoveredComponent :: Derived ( DerivedComponent :: Authority { req : false } ) ,
806+ "example.com:" . to_string ( ) ,
807+ ) ,
808+ (
809+ CoveredComponent :: Derived ( DerivedComponent :: Scheme { req : false } ) ,
810+ "HTTP" . to_string ( ) ,
811+ ) ,
812+ (
813+ CoveredComponent :: Derived ( DerivedComponent :: Path { req : false } ) ,
814+ "/%7Esmith" . to_string ( ) ,
815+ ) ,
816+ (
817+ CoveredComponent :: Derived ( DerivedComponent :: Query { req : false } ) ,
818+ "?param=value&foo=bar&baz=bat%7Eman" . to_string ( ) ,
819+ ) ,
820+ ] )
821+ }
822+
823+ fn register_header_contents (
824+ & mut self ,
825+ signature_input : String ,
826+ signature_header : String ,
827+ ) {
828+ self . signature_input = Some ( signature_input) ;
829+ self . signature_header = Some ( signature_header) ;
830+ }
831+ }
832+
833+ let signer = MessageSigner {
834+ keyid : "test" . into ( ) ,
835+ nonce : "another-test" . into ( ) ,
836+ tag : "web-bot-auth" . into ( ) ,
837+ } ;
838+
839+ let mut equivalent_form_1 = EquivalentForm1 {
840+ signature_input : None ,
841+ signature_header : None ,
842+ } ;
843+ let mut equivalent_form_2 = EquivalentForm2 {
844+ signature_input : None ,
845+ signature_header : None ,
846+ } ;
847+
848+ assert ! (
849+ signer
850+ . generate_signature_headers_content(
851+ & mut equivalent_form_1,
852+ Duration :: from_secs( 10 ) ,
853+ Algorithm :: Ed25519 ,
854+ & PRIVATE_KEY . to_vec( )
855+ )
856+ . is_ok( )
857+ ) ;
858+
859+ assert ! (
860+ signer
861+ . generate_signature_headers_content(
862+ & mut equivalent_form_2,
863+ Duration :: from_secs( 10 ) ,
864+ Algorithm :: Ed25519 ,
865+ & PRIVATE_KEY . to_vec( )
866+ )
867+ . is_ok( )
868+ ) ;
869+
870+ assert_eq ! (
871+ equivalent_form_1. signature_input. unwrap( ) ,
872+ equivalent_form_2. signature_input. unwrap( )
873+ ) ;
874+ assert_eq ! (
875+ equivalent_form_1. signature_header. unwrap( ) ,
876+ equivalent_form_2. signature_header. unwrap( )
877+ ) ;
878+ }
697879}
0 commit comments