iosswiftauthenticationwebauthnfido

What information does FIDO2 url contain and how can we decode it in Swift?


In WWDC 2022 Apple launched GA for Passkeys which will enable in FIDO2 authentication, the next gen open standards based authentication mechanism to replace passwords.

On a Relying Party (RP) server supporting FIDO2 when a user registration is initiated, the browser generates a QR code to register a phone as platform authenticator.

I am trying to build an app which opens up a QR scanner view and I want to register for the FIDO credential by scanning the QR code generated by the browser. The parsed string is of the format - FIDO:/090409094349049349.......

What information does this FIDO:/090409094349049349....... url protocol contain relating to the RP? Also, is there a way to decode this in Swift to get that information in json or string format?

Since the camera app on iPhone is able to scan the QR and generate information like RP domain name and user being registered, I believe there should be a way to do this from a QR scanner inside an app as well. Or are these APIs private in nature only for usage of Camera app?


Solution

  • This is a Client Authenticator Protocol (CTAP) v2.2 hybrid flow link which is meant to bootstrap a Bluetooth connection between the device reading the QR code and the host computer which shows it. This has formerly been known as "cloud assisted bluetooth low energy" binding, or cable in short.

    To decode one of these, lets take an example I created from the webauthn demo page [DEMO] when clicking on Register.

    FIDO:/13086400838107303667332719012595115747821895775708323189557153075146383351399743589971313508078026948312026786722471666005727649643501784024544726574771401798171307406596245
    

    The FIDO:/ URI-scheme is registered with IANA at [IANA].

    Next comes a base10-encoded string, which means that 7 bytes get encoded into 17 digits. It can be decoded for instance with

         let x = input
             .chars()
             .collect::<Vec<char>>()
             .chunks(17)
             .flat_map(|c| {
                 let s = c.iter().collect::<String>();
                 let n = match s.len() {
                     3 => 1,
                     5 => 2,
                     8 => 3,
                     10 => 4,
                     13 => 5,
                     15 => 6,
                     17 => 7,
                     _ => panic!(),
                 };
                 s.parse::<u64>().unwrap().to_le_bytes().into_iter().take(n)
             })
             .collect::<Vec<u8>>();
    

    This yields the byte string

    a7 00 58 21 03 7e 2e f7 c6 5f a6 8e 15 ed 9b 9a 4c b7 22 34 cc ca 8f 9b 84 c7 62 52 d5 27 96 50 9b 31 88 ab 07 01 50 49 aa 2e f2 de 61 7e 4c
    ba 2a e2 68 3d 3c ed 08 02 02 03 1a 64 05 fb 8e 04 f5 05 62 6d 63 06 f5
    

    which is in fact a CBOR [CBOR, CBOR2] value that looks like

    {
      0: h'037E2EF7C65FA68E15ED9B9A4CB72234CCCA8F9B84C76252D52796509B3188AB07', # compressed public key
      1: h'49AA2EF2DE617E4CBA2AE2683D3CED08',                                   # qr secret
      2: 2,                                                                     # num_known_domains
      3: 1678113678,                                                            # Timestamp: Mon Mar 06 2023 14:41:18 GMT+0000
      4: true,                                                                  # supports_linking 
      5: "mc",                                                                  # request_type -> CableRequestType::kMakeCredential
      6: true                                                                   # kWebAuthnNonDiscoverableMakeCredentialQRFlag enabled
    }
    

    To understand this map, we need to know the significance of the map keys, for which I couldn't obtain a normative source. Thus, the truth lies in the code [CHROME].

    Key 0 contains the compressed public key [BTC] of the relying party, in our case https://webauthn.io.

    Key 1 contains the shared secret with which the device will proof that it read the QR code.

    Key 2 contains the number of registered tunnel server domains.

    Key 3 is the host unix time upon creation of the request, in our case Mon Mar 06 2023 14:41:18 GMT+0000

    Key 4 tells us if the host computer showing the QR code supports device linking.

    Key 5 is the request type of the operation, in this case mc which stands for Make Credential, i.e. we want to perform a device registration. Note that this parameter is just a hint to the mobile device and not strictly necessary, as the QR code itself doesn't perform any Webauthn/CTAP operation, it merely helps to avoid the Bluetooth Pairing of the two devices.

    Key 6 declares that the call should be non-discoverable.

    Further reading for WebAuthn [WEBAUTHN] and Client to Authenticator Protocol [CTAP].

    --

    [IANA] https://www.iana.org/assignments/uri-schemes/prov/fido

    [DEMO] https://webauthn.io/

    [CHROME] https://github.com/chromium/chromium/blob/50479218fc94681552b7ba2c526069141182a143/device/fido/cable/v2_handshake.cc#L441-L469 and https://github.com/chromium/chromium/blob/50479218fc94681552b7ba2c526069141182a143/device/fido/cable/v2_handshake.h#L107-L127

    [CBOR] https://www.rfc-editor.org/rfc/rfc8949.txt

    [CBOR2] https://cbor.me/

    [CTAP] https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html Note that this is only version 2.1. Our QR code will only be specified in version 2.2 which is unfortunately not yet publicly available.

    [WEBAUTHN] https://www.w3.org/TR/webauthn-2/

    [BTC] https://btcinformation.org/en/developer-guide#public-key-formats