@@ -32,6 +32,8 @@ use data_url::DataUrl;
3232use keyring:: { Algorithm , JSONWebKeySet , KeyRing } ;
3333use std:: time:: SystemTimeError ;
3434
35+ use crate :: components:: HTTPField ;
36+
3537/// Errors that may be thrown by this module.
3638#[ derive( Debug ) ]
3739pub enum ImplementationError {
@@ -91,16 +93,6 @@ pub enum WebBotAuthError {
9193 /// and `creates` method.
9294 SignatureIsExpired ,
9395}
94- /// A trait that messages wishing to be verified as a `web-bot-auth` method specifically
95- /// must implement.
96- pub trait WebBotAuthSignedMessage : SignedMessage {
97- /// Obtain every `Signature-Agent` header in the message. Despite the name, you can omit
98- /// `Signature-Agents` that are known to be invalid ahead of time. However, each `Signature-Agent`
99- /// header must be unparsed and a be a valid sfv::Item::String value (meaning it should be encased
100- /// in double quotes). You should separately implement looking this up in `SignedMessage::lookup_component`
101- /// as an HTTP header with multiple values.
102- fn fetch_all_signature_agents ( & self ) -> Vec < String > ;
103- }
10496
10597/// A verifier for Web Bot Auth messages specifically.
10698#[ derive( Clone , Debug ) ]
@@ -127,10 +119,14 @@ impl WebBotAuthVerifier {
127119 /// # Errors
128120 ///
129121 /// Returns `ImplementationErrors` relevant to verifying and parsing.
130- pub fn parse ( message : & impl WebBotAuthSignedMessage ) -> Result < Self , ImplementationError > {
131- let signature_agents = message. fetch_all_signature_agents ( ) ;
132- let web_bot_auth_verifier = Self {
133- message_verifier : MessageVerifier :: parse ( message, |( _, innerlist) | {
122+ pub fn parse ( message : & impl SignedMessage ) -> Result < Self , ImplementationError > {
123+ let signature_agents = message. lookup_component ( & CoveredComponent :: HTTP ( HTTPField {
124+ name : "signature-agent" . to_string ( ) ,
125+ parameters : components:: HTTPFieldParametersSet ( vec ! [ ] ) ,
126+ } ) ) ;
127+
128+ let message_verifier =
129+ MessageVerifier :: parse ( message, |( _, innerlist) | {
134130 innerlist. params . contains_key ( "keyid" )
135131 && innerlist. params . contains_key ( "tag" )
136132 && innerlist. params . contains_key ( "expires" )
@@ -140,18 +136,22 @@ impl WebBotAuthVerifier {
140136 . get ( "tag" )
141137 . and_then ( |tag| tag. as_string ( ) )
142138 . is_some_and ( |tag| tag. as_str ( ) == "web-bot-auth" )
143- && innerlist
144- . items
145- . iter ( )
146- . any ( |item| * item == sfv:: Item :: new ( sfv:: StringRef :: constant ( "@authority" ) ) )
139+ && ( innerlist. items . iter ( ) . any ( |item| {
140+ * item == sfv:: Item :: new ( sfv:: StringRef :: constant ( "@authority" ) )
141+ } ) || innerlist. items . iter ( ) . any ( |item| {
142+ * item == sfv:: Item :: new ( sfv:: StringRef :: constant ( "@target-uri" ) )
143+ } ) )
147144 && ( if !signature_agents. is_empty ( ) {
148145 innerlist. items . iter ( ) . any ( |item| {
149146 * item == sfv:: Item :: new ( sfv:: StringRef :: constant ( "signature-agent" ) )
150147 } )
151148 } else {
152149 true
153150 } )
154- } ) ?,
151+ } ) ?;
152+
153+ let web_bot_auth_verifier = Self {
154+ message_verifier,
155155 parsed_directories : signature_agents
156156 . iter ( )
157157 . map ( |header| {
@@ -230,28 +230,26 @@ mod tests {
230230 struct StandardTestVector { }
231231
232232 impl SignedMessage for StandardTestVector {
233- fn fetch_all_signature_headers ( & self ) -> Vec < String > {
234- vec ! [ "sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:" . to_owned( ) ]
235- }
236- fn fetch_all_signature_inputs ( & self ) -> Vec < String > {
237- vec ! [ r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="web-bot-auth""# . to_owned( ) ]
238- }
239- fn lookup_component ( & self , name : & CoveredComponent ) -> Option < String > {
240- match * name {
233+ fn lookup_component ( & self , name : & CoveredComponent ) -> Vec < String > {
234+ match name {
235+ CoveredComponent :: HTTP ( HTTPField { name, .. } ) => {
236+ if name == "signature" {
237+ return vec ! [ "sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:" . to_owned( ) ] ;
238+ }
239+
240+ if name == "signature-input" {
241+ return vec ! [ r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="web-bot-auth""# . to_owned( ) ] ;
242+ }
243+ vec ! [ ]
244+ }
241245 CoveredComponent :: Derived ( DerivedComponent :: Authority { .. } ) => {
242- Some ( "example.com" . to_string ( ) )
246+ vec ! [ "example.com" . to_string( ) ]
243247 }
244- _ => None ,
248+ _ => vec ! [ ] ,
245249 }
246250 }
247251 }
248252
249- impl WebBotAuthSignedMessage for StandardTestVector {
250- fn fetch_all_signature_agents ( & self ) -> Vec < String > {
251- vec ! [ ]
252- }
253- }
254-
255253 #[ test]
256254 fn test_verifying_as_web_bot_auth ( ) {
257255 let test = StandardTestVector { } ;
@@ -307,28 +305,26 @@ mod tests {
307305 }
308306
309307 impl SignedMessage for MyTest {
310- fn fetch_all_signature_headers ( & self ) -> Vec < String > {
311- vec ! [ self . signature_header. clone( ) ]
312- }
313- fn fetch_all_signature_inputs ( & self ) -> Vec < String > {
314- vec ! [ self . signature_input. clone( ) ]
315- }
316- fn lookup_component ( & self , name : & CoveredComponent ) -> Option < String > {
317- match * name {
308+ fn lookup_component ( & self , name : & CoveredComponent ) -> Vec < String > {
309+ match name {
310+ CoveredComponent :: HTTP ( HTTPField { name, .. } ) => {
311+ if name == "signature" {
312+ return vec ! [ self . signature_header. clone( ) ] ;
313+ }
314+
315+ if name == "signature-input" {
316+ return vec ! [ self . signature_input. clone( ) ] ;
317+ }
318+ vec ! [ ]
319+ }
318320 CoveredComponent :: Derived ( DerivedComponent :: Authority { .. } ) => {
319- Some ( "example.com" . to_string ( ) )
321+ vec ! [ "example.com" . to_string( ) ]
320322 }
321- _ => None ,
323+ _ => vec ! [ ] ,
322324 }
323325 }
324326 }
325327
326- impl WebBotAuthSignedMessage for MyTest {
327- fn fetch_all_signature_agents ( & self ) -> Vec < String > {
328- vec ! [ ]
329- }
330- }
331-
332328 let public_key: [ u8 ; ed25519_dalek:: PUBLIC_KEY_LENGTH ] = [
333329 0x26 , 0xb4 , 0x0b , 0x8f , 0x93 , 0xff , 0xf3 , 0xd8 , 0x97 , 0x11 , 0x2f , 0x7e , 0xbc , 0x58 ,
334330 0x2b , 0x23 , 0x2d , 0xbd , 0x72 , 0x51 , 0x7d , 0x08 , 0x2f , 0xe8 , 0x3c , 0xfb , 0x30 , 0xdd ,
@@ -388,30 +384,28 @@ mod tests {
388384 struct MissingParametersTestVector { }
389385
390386 impl SignedMessage for MissingParametersTestVector {
391- fn fetch_all_signature_headers ( & self ) -> Vec < String > {
392- vec ! [
393- "sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:" . to_owned( )
394- ]
395- }
396- fn fetch_all_signature_inputs ( & self ) -> Vec < String > {
397- vec ! [ r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="not-web-bot-auth""# . to_owned( ) ]
398- }
399- fn lookup_component ( & self , name : & CoveredComponent ) -> Option < String > {
400- match * name {
387+ fn lookup_component ( & self , name : & CoveredComponent ) -> Vec < String > {
388+ match name {
389+ CoveredComponent :: HTTP ( HTTPField { name, .. } ) => {
390+ if name == "signature" {
391+ return vec ! [
392+ "sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:" . to_owned( )
393+ ] ;
394+ }
395+
396+ if name == "signature-input" {
397+ return vec ! [ r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="not-web-bot-auth""# . to_owned( ) ] ;
398+ }
399+ vec ! [ ]
400+ }
401401 CoveredComponent :: Derived ( DerivedComponent :: Authority { .. } ) => {
402- Some ( "example.com" . to_string ( ) )
402+ vec ! [ "example.com" . to_string( ) ]
403403 }
404- _ => None ,
404+ _ => vec ! [ ] ,
405405 }
406406 }
407407 }
408408
409- impl WebBotAuthSignedMessage for MissingParametersTestVector {
410- fn fetch_all_signature_agents ( & self ) -> Vec < String > {
411- vec ! [ ]
412- }
413- }
414-
415409 let test = MissingParametersTestVector { } ;
416410 WebBotAuthVerifier :: parse ( & test) . expect_err ( "This should not have parsed" ) ;
417411 }
@@ -421,28 +415,30 @@ mod tests {
421415 struct MissingParametersTestVector { }
422416
423417 impl SignedMessage for MissingParametersTestVector {
424- fn fetch_all_signature_headers ( & self ) -> Vec < String > {
425- vec ! [ "sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:" . to_owned( ) ]
426- }
427- fn fetch_all_signature_inputs ( & self ) -> Vec < String > {
428- vec ! [ r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="web-bot-auth""# . to_owned( ) ]
429- }
430- fn lookup_component ( & self , name : & CoveredComponent ) -> Option < String > {
431- match * name {
418+ fn lookup_component ( & self , name : & CoveredComponent ) -> Vec < String > {
419+ match name {
420+ CoveredComponent :: HTTP ( HTTPField { name, .. } ) => {
421+ if name == "signature" {
422+ return vec ! [ "sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:" . to_owned( ) ] ;
423+ }
424+
425+ if name == "signature-input" {
426+ return vec ! [ r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="web-bot-auth""# . to_owned( ) ] ;
427+ }
428+
429+ if name == "signature-agent" {
430+ return vec ! [ String :: from( "\" https://myexample.com\" " ) ] ;
431+ }
432+ vec ! [ ]
433+ }
432434 CoveredComponent :: Derived ( DerivedComponent :: Authority { .. } ) => {
433- Some ( "example.com" . to_string ( ) )
435+ vec ! [ "example.com" . to_string( ) ]
434436 }
435- _ => None ,
437+ _ => vec ! [ ] ,
436438 }
437439 }
438440 }
439441
440- impl WebBotAuthSignedMessage for MissingParametersTestVector {
441- fn fetch_all_signature_agents ( & self ) -> Vec < String > {
442- vec ! [ String :: from( "\" https://myexample.com\" " ) ]
443- }
444- }
445-
446442 let test = MissingParametersTestVector { } ;
447443 WebBotAuthVerifier :: parse ( & test) . expect_err ( "This should not have parsed" ) ;
448444 }
@@ -452,34 +448,30 @@ mod tests {
452448 struct StandardTestVector { }
453449
454450 impl SignedMessage for StandardTestVector {
455- fn fetch_all_signature_headers ( & self ) -> Vec < String > {
456- vec ! [ "sig1=:3q7S1TtbrFhQhpcZ1gZwHPCFHTvdKXNY1xngkp6lyaqqqv3QZupwpu/wQG5a7qybnrj2vZYMeVKuWepm+rNkDw==:" . to_owned( ) ]
457- }
458- fn fetch_all_signature_inputs ( & self ) -> Vec < String > {
459- vec ! [ r#"sig1=("@authority" "signature-agent");alg="ed25519";keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";nonce="ZO3/XMEZjrvSnLtAP9M7jK0WGQf3J+pbmQRUpKDhF9/jsNCWqUh2sq+TH4WTX3/GpNoSZUa8eNWMKqxWp2/c2g==";tag="web-bot-auth";created=1749331474;expires=1749331484"# . to_owned( ) ]
460- }
461- fn lookup_component ( & self , name : & CoveredComponent ) -> Option < String > {
451+ fn lookup_component ( & self , name : & CoveredComponent ) -> Vec < String > {
462452 match name {
463- CoveredComponent :: Derived ( DerivedComponent :: Authority { .. } ) => {
464- Some ( "example.com" . to_string ( ) )
465- }
466- CoveredComponent :: HTTP ( components:: HTTPField { name, .. } ) => {
453+ CoveredComponent :: HTTP ( HTTPField { name, .. } ) => {
454+ if name == "signature" {
455+ return vec ! [ "sig1=:3q7S1TtbrFhQhpcZ1gZwHPCFHTvdKXNY1xngkp6lyaqqqv3QZupwpu/wQG5a7qybnrj2vZYMeVKuWepm+rNkDw==:" . to_owned( ) ] ;
456+ }
457+
458+ if name == "signature-input" {
459+ return vec ! [ r#"sig1=("@authority" "signature-agent");alg="ed25519";keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";nonce="ZO3/XMEZjrvSnLtAP9M7jK0WGQf3J+pbmQRUpKDhF9/jsNCWqUh2sq+TH4WTX3/GpNoSZUa8eNWMKqxWp2/c2g==";tag="web-bot-auth";created=1749331474;expires=1749331484"# . to_owned( ) ] ;
460+ }
461+
467462 if name == "signature-agent" {
468- return Some ( String :: from ( "\" https://myexample.com\" " ) ) ;
463+ return vec ! [ String :: from( "\" https://myexample.com\" " ) ] ;
469464 }
470- None
465+ vec ! [ ]
466+ }
467+ CoveredComponent :: Derived ( DerivedComponent :: Authority { .. } ) => {
468+ vec ! [ "example.com" . to_string( ) ]
471469 }
472- _ => None ,
470+ _ => vec ! [ ] ,
473471 }
474472 }
475473 }
476474
477- impl WebBotAuthSignedMessage for StandardTestVector {
478- fn fetch_all_signature_agents ( & self ) -> Vec < String > {
479- vec ! [ String :: from( "\" https://myexample.com\" " ) ]
480- }
481- }
482-
483475 let public_key: [ u8 ; ed25519_dalek:: PUBLIC_KEY_LENGTH ] = [
484476 0x26 , 0xb4 , 0x0b , 0x8f , 0x93 , 0xff , 0xf3 , 0xd8 , 0x97 , 0x11 , 0x2f , 0x7e , 0xbc , 0x58 ,
485477 0x2b , 0x23 , 0x2d , 0xbd , 0x72 , 0x51 , 0x7d , 0x08 , 0x2f , 0xe8 , 0x3c , 0xfb , 0x30 , 0xdd ,
0 commit comments