Yubico / Yubico.NET.SDK

A YubiKey SDK for .NET developers
Apache License 2.0
96 stars 48 forks source link

Unable to use OathSession GetCredentials/CalculateAllCredentials #35

Closed jraats closed 1 year ago

jraats commented 1 year ago

Dear community, I'm playing around with the SDK, because I want to use the SDK to generate Oauth tokens with my Yubikey, but I get my MVP not working, so I thought maybe one of you developers can help what is the problem.

There is my small example code:

using Yubico.YubiKey;
using Yubico.YubiKey.Oath;
using Yubico.YubiKey.Oath.Commands;
using Yubico.YubiKey.Otp;
using Yubico.YubiKey.Piv;
using Yubico.YubiKey.U2f;

internal class Program
{
    // Chooses the first YubiKey found on the computer.
    private static IYubiKeyDevice? SampleChooseYubiKey()
    {
        IEnumerable<IYubiKeyDevice> list = YubiKeyDevice.FindByTransport(Transport.SmartCard);
        if (list.Count() == 0 ) {
            return null;
        }

        return list.First();
    }

    private static bool KeyCollectorDelegate(KeyEntryData keyEntryData)
    {
        switch (keyEntryData.Request) {
            case KeyEntryRequest.Release:
                // Do release work.
                return true;            
            case KeyEntryRequest.VerifyOathPassword:
                // Oauth password

                // removed my password so this part is not required anymore
                //byte[] password = new byte[]{ (byte)'-' };
                //keyEntryData.SubmitValue((Span<byte>)password);
                return true;
            default:
                // Not supported
                return false;
        }
    }

    private static void Main(string[] args)
    {
        IYubiKeyDevice? yubiKeyToUse = SampleChooseYubiKey();
        if (yubiKeyToUse == null) {
            Console.WriteLine("No key inserted");
            return;
        }

        Console.WriteLine("Found a Yubikey!");
        Console.WriteLine("Serial number {0:D}", yubiKeyToUse.SerialNumber);
        Console.WriteLine("Firmware version {0:D}", yubiKeyToUse.FirmwareVersion);        

        OathSession session = new OathSession(yubiKeyToUse);

        if (session.IsPasswordProtected) {
            Console.WriteLine("Yubikey is protected by a password");
        }
        else {
            Console.WriteLine("Yubikey is not protected by a password");
        }

        session.KeyCollector = KeyCollectorDelegate;
        // Removed my Yubikey password so this part is not required anymore
        //Console.WriteLine("Password is correct: {0:D}", session.TryVerifyPassword());

        IList<Credential> credentials = session.GetCredentials();
        foreach (Credential credential in credentials) {
            Console.WriteLine(credential.Name);
        }

        Console.ReadLine();        
    }
}

The example above will output this in my console:

Found a Yubikey!
Serial number XXXXXXXX
Firmware version 5.4.3
Yubikey is not protected by a password

Every call which requires access to the Yubikey e.g. GetCredentals() will raise an exception: "System.InvalidOperationException: 'Instruction value either not supported or invalid.'".

I've tried to downgrade the .Net SDK version, but the error remains. I've also tried to use the list command, but I get the same error:

var listCommand = new ListCommand();
ListResponse response = session.Connection.SendCommand(listCommand);

Console.WriteLine(response.Status);
Console.WriteLine(response.StatusMessage);

Output: Failed Instruction value either not supported or invalid..

I've tried with and without password, but I thought without password is better to debug (there are less edge cases) then with password.

OS: Windows 11 Platform: x64 Target framework: .NET 6.0 AND .NET 7.0 (tested both)

Ran the example in administer mode and regualr user mode.

jraats commented 1 year ago

I've tested it with 2 Yubikeys. Both gave the same result. The yubico authenticator app (I know that is written in python) works as intended and I use that daily.

Device type is : YubiKey 5 NFC.

GregDomzalski commented 1 year ago

Thanks for the detailed bug report! Would you be able to share the stack trace for the exception that you're hitting?

In the meantime, we will try out your example and see what happens on our side.

jraats commented 1 year ago

Thanks for looking into this issue. I don't really know how to print a stack trace which is thrown in a DLL, because I added the dependency via NuGet. I've selected 'Show External Code' and then I see:

Yubico.YubiKey.dll!Yubico.YubiKey.Oath.OathSession.GetCredentials()
TestYubikey.dll!Program.Main(string[] args) Line 70
    at C:\Users\jraats\source\repos\TestYubikey\TestYubikey\Program.cs(70)

I think the exception is thrown here: https://github.com/Yubico/Yubico.NET.SDK/blob/da4ded8c11962a7c200350b9ab38b40676573ebf/Yubico.YubiKey/src/Yubico/YubiKey/Oath/OathSession.Credential.cs#L53, which I've validated via my ListCommand (the status isn't Success, but Failed).

I have updated my 'app.manifest' to include: <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />. Is there anything else required to run the SDK besides Administrator rights?

I double checked, the SDK I'm using is version 1.5.1 (latest).

jraats commented 1 year ago

So instead of using the DLL I've used the latest (develop) code and added my project to the solution. With this I could use the debugger to find out what happend with my Yubikey.

So the first part is I think ok:

Yubico.YubiKey.Pipelines.CommandChainingTransform -> Invoke
Command:
Cla 0
Data: empty
Ins 161
Nc 0
Ne 0
P1 0
P2 0
_ne 0

Which runs _pipeline.Invoke (without while loop)
-----
Which runs _smartCardConnection.Transmit (SmartCardTransform)
-----
Which runs SCardTransmit
_activeProtocol: T1
-----
Yubico.Core.Iso7816.CommandApdu.AsByteArray returned [0, 161, 0, 0]

So SCardTransmit is called with the following command: 0, 161, 0, 0 which is imo Ok.

SCardTransmit returned 0 -> outputBuffer has size 258

Here are the responseApdu's: The first one just after the _pipeline.Invoke:

SW: 25002
SW1: 97
SW2: 170
Data size: 256

Which is according to the doc OK: 0x61 == More data available.

Then after the second _pipeline.Invoke call: SCardTransmit is called with the following command: 0, 192, 0, 0

SW: 27904
SW1: 109
SW2: 0
Data size: 0

This is I think not ok. 0x6D is not listed as valid response status.

But the status is Failed so the exception is thrown in OauthSession:IList.

jraats commented 1 year ago

Hi @GregDomzalski do you have any updates? I've ran the sample code and I have the same issue. Can I help by sending some additional information?

GregDomzalski commented 1 year ago

We're looking to issue a patch release either later this week or early next. It contains a fix for this issue and a few others that we found while further exercising corner cases in this code.

GregDomzalski commented 1 year ago

The issue, btw, is that the OATH application uses a different response chaining command than the rest of the applications. So while the SDK is supposed to automatically handle the "more data" cases automatically, it was unable to do so for OATH since it required the use of a different instruction.

I'll check in with my teammates to see where the other fixes are at and let you know when I have a more accurate ETA.

jraats commented 1 year ago

Thanks for letting me know. I'm glad that it was not something that I did wrong. Strange that OAUTH uses not the regular instructions. Take you time to fix this bug I will see when it is available. 😄

GregDomzalski commented 1 year ago

Hi @jraats - the fix for this issue has been released in version 1.6.1. Thank you for being patient.

I'm resolving this issue, but if you continue to run into problems, feel free to re-open or to open a new issue.

jraats commented 1 year ago

Thanks @GregDomzalski and @coonsd! The fix works as expected. I also found the fix in the code and found the 'SEND REMAINING' command in OAUTH section. Something which I read over. Thanks for this wonderfull SDK.