@@ -3,7 +3,9 @@ use std::sync::Arc;
33
44use ctap_types:: serde:: cbor_deserialize;
55use futures:: { SinkExt , StreamExt } ;
6- use p256:: NonZeroScalar ;
6+ use hmac:: { Hmac , Mac } ;
7+ use p256:: { ecdh, NonZeroScalar } ;
8+ use p256:: { PublicKey , SecretKey } ;
79use serde:: Deserialize ;
810use serde_bytes:: ByteBuf ;
911use serde_cbor:: Value ;
@@ -150,6 +152,7 @@ pub fn decode_tunnel_server_domain(encoded: u16) -> Option<String> {
150152 Some ( ret)
151153}
152154
155+ #[ derive( Clone ) ]
153156pub ( crate ) enum CableTunnelConnectionType {
154157 QrCode {
155158 routing_id : String ,
@@ -340,6 +343,7 @@ pub(crate) async fn do_handshake(
340343}
341344
342345pub ( crate ) async fn channel (
346+ connection_type : & CableTunnelConnectionType ,
343347 noise_state : TunnelNoiseState ,
344348 tunnel_domain : & str ,
345349 maybe_known_device_store : & Option < Arc < dyn CableKnownDeviceInfoStore > > ,
@@ -350,8 +354,10 @@ pub(crate) async fn channel(
350354
351355 let tunnel_domain: String = tunnel_domain. to_string ( ) ;
352356 let maybe_known_device_store = maybe_known_device_store. clone ( ) ;
357+ let connection_type = connection_type. to_owned ( ) ;
353358 let handle_connection = task:: spawn ( async move {
354359 connection (
360+ & connection_type,
355361 & tunnel_domain,
356362 & maybe_known_device_store,
357363 ws_stream,
@@ -375,6 +381,7 @@ pub(crate) async fn channel(
375381}
376382
377383async fn connection (
384+ connection_type : & CableTunnelConnectionType ,
378385 tunnel_domain : & str ,
379386 known_device_store : & Option < Arc < dyn CableKnownDeviceInfoStore > > ,
380387 mut ws_stream : WebSocketStream < MaybeTlsStream < TcpStream > > ,
@@ -414,7 +421,7 @@ async fn connection(
414421 Ok ( message) => {
415422 debug!( "Received WSS message" ) ;
416423 trace!( ?message) ;
417- let _ = connection_recv( tunnel_domain, known_device_store, message, & cbor_rx_send, & mut noise_state) . await ;
424+ let _ = connection_recv( connection_type , tunnel_domain, known_device_store, message, & cbor_rx_send, & mut noise_state) . await ;
418425 }
419426 } ;
420427 }
@@ -644,6 +651,7 @@ async fn connection_recv_update(message: &[u8]) -> Result<Option<CableLinkingInf
644651}
645652
646653async fn connection_recv (
654+ connection_type : & CableTunnelConnectionType ,
647655 tunnel_domain : & str ,
648656 known_device_store : & Option < Arc < dyn CableKnownDeviceInfoStore > > ,
649657 message : Message ,
@@ -695,14 +703,23 @@ async fn connection_recv(
695703 return Ok ( ( ) ) ;
696704 } ;
697705
706+ let CableTunnelConnectionType :: QrCode { private_key, .. } = connection_type else {
707+ warn ! ( "Ignoring update message for non-QR code connection" ) ;
708+ return Ok ( ( ) ) ;
709+ } ;
710+
698711 debug ! ( "Received update message with linking info" ) ;
699712 trace ! ( ?linking_info) ;
700713
701714 let device_id: CableKnownDeviceId = ( & linking_info) . into ( ) ;
702715 match known_device_store {
703716 Some ( store) => {
704- match parse_known_device ( tunnel_domain, & device_id, & linking_info, & noise_state)
705- {
717+ match parse_known_device (
718+ private_key,
719+ tunnel_domain,
720+ & linking_info,
721+ & noise_state,
722+ ) {
706723 Ok ( known_device) => {
707724 debug ! ( ?device_id, "Updating known device" ) ;
708725 trace ! ( ?known_device) ;
@@ -728,17 +745,44 @@ async fn connection_recv(
728745 Ok ( ( ) )
729746}
730747
748+ /// Validation requires a shared key computed on the QR code ephemeral identity key (private_key here).
749+ /// We're currently unable to validate the signature on linking information received for state-assisted transactions,
750+ /// so these should be discarded. This is the same Chrome currently does, although it may change in future spec versions.
751+ /// See: https://github.com/chromium/chromium/blob/88e250200e59daf52554bcc74870138143a830c4/device/fido/cable/fido_tunnel_device.cc#L547-L549
731752fn parse_known_device (
753+ private_key : & NonZeroScalar ,
732754 tunnel_domain : & str ,
733- _device_id : & CableKnownDeviceId ,
734755 linking_info : & CableLinkingInfo ,
735- _noise_state : & TunnelNoiseState ,
756+ noise_state : & TunnelNoiseState ,
736757) -> Result < CableKnownDeviceInfo , Error > {
737758 let known_device = CableKnownDeviceInfo :: new ( tunnel_domain, linking_info) ?;
759+ let secret_key = SecretKey :: from ( private_key) ;
738760
739- // TODO: validate the signature
740- // TODO: check public key hasn't changed for previous
761+ let Ok ( authenticator_public_key) =
762+ PublicKey :: from_sec1_bytes ( & linking_info. authenticator_public_key )
763+ else {
764+ error ! ( "Failed to parse public key." ) ;
765+ return Err ( Error :: Transport ( TransportError :: InvalidKey ) ) ;
766+ } ;
767+
768+ let shared_secret: Vec < u8 > = ecdh:: diffie_hellman (
769+ secret_key. to_nonzero_scalar ( ) ,
770+ authenticator_public_key. as_affine ( ) ,
771+ )
772+ . raw_secret_bytes ( )
773+ . to_vec ( ) ;
774+
775+ let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( & shared_secret) . expect ( "Any key size is valid" ) ;
776+ hmac. update ( & noise_state. handshake_hash ) ;
777+ let expected_mac = hmac. finalize ( ) . into_bytes ( ) . to_vec ( ) ;
778+
779+ if expected_mac != linking_info. handshake_signature {
780+ error ! ( "Invalid handshake signature, rejecting update message" ) ;
781+ trace ! ( ?expected_mac, ?linking_info. handshake_signature) ;
782+ return Err ( Error :: Transport ( TransportError :: InvalidSignature ) ) ;
783+ }
741784
785+ debug ! ( "Parsed known device with valid signature" ) ;
742786 Ok ( known_device)
743787}
744788
0 commit comments