Yubico / libfido2

Provides library functionality for FIDO2, including communication with a device over USB or NFC.
Other
590 stars 152 forks source link

Possible compatibility issue between this library and the .NET SDK #733

Closed balazserdeszlogmein closed 1 year ago

balazserdeszlogmein commented 1 year ago

[Note: this has also been posted on the .NET SDK as an issue here.]

Hi,

I have noticed a possible compatibility issue between this library and your .NET SDK.

I am creating (registering) a U2F credential with the libfido2 command line tool like this on a macOS operating system.

bash

% echo credential challenge | openssl sha256 -binary | base64 > cred_param    
% echo relying_party >> cred_param                   
% echo username >> cred_param                       
% dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param  

then bash:

% fido2-cred -M -u -i cred_param ioreg://4295049894 | fido2-cred -V -o cred

which results in a file called "cred" in the following format:

Hy8EHTL3JjA0UpNavz[...]TiLCi/5WeGnvzHyq8bonq7y0YQ==
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj[...]09K4FOmgk3dHXzSgcA==
-----END PUBLIC KEY-----

Then, we also have a credential assertion (authentication) application, written with this .NET SDK, roughly doing the following on a Windows operating system:

  1. Getting two inputs from the user, private key handle and public key as bas64 strings:

C#

var privateKeyHandle = Convert.FromBase64String(s: PrivateKeyHandleBase64);
var publicKey = Convert.FromBase64String(s: PublicKeyBase64);
  1. Authenticating with these credentials

C#

IYubiKeyDevice yubiKey = YubiKeyDevice.FindAll().FirstOrDefault();

if (yubiKey is null) { return null; }

using (var session = new U2fSession(yubiKey: yubiKey))
{
    byte[] hashOrigin = U2fSession.EncodeAndHashString(data: "relying_party");

    var clientData = new FidoU2fClientData
    {
        Challenge = GenerateChallengeDataBase64(), // Not putting the implementation here, it is unnecessary
        Origin = "relying_party"
    };

    string jsonClientData = JsonConvert.SerializeObject(value: clientData);

    byte[] hashClientData = U2fSession.EncodeAndHashString(data: jsonClientData);

    AuthenticationData authenticationData = session.Authenticate(
        applicationId: hashOrigin,
        clientDataHash: hashClientData,
        keyHandle: privateKeyHandle,
        timeout: TimeSpan.FromSeconds(10)
    );
}

When passing the credential id (private key handle) created on macOS with fido2-cred, calling the last session.Authenticate function throws an error, namely new ArgumentException(ExceptionMessages.UnsupportedAlgorithm); in line 401 of the EcdsaVerify.cs file, because at line 368 in the private static ECDsa ConvertPublicKey(ReadOnlyMemory<byte> encodedEccPoint) the (encodedEccPoint.Span[0] == EncodedPointTag) condition is not met.

Based on this, what we realized after a while, is that if we base64 decode the string of the credential id (key handle), prepend a 04 byte, then base64 encode it back to a string, the above will work and the authentication is going to be successful. Why is there a difference between the U2F implementation for LibFido2 and the .NET SDK?

Let me know if anything else is needed from my side to investigate this issue.

Thanks in advance, Balazs

LDVG commented 1 year ago

The fido2-cred output contains a PEM-encoded public key, itself encoded in the SubjectPublicKeyInfo format. Furthermore, libfido2's API allows you to pick a suitable serialization yourself, depending on your needs.

I see @GregDomzalski has got you covered in your other issue, so I'll be closing this.