Yubico / Yubico.NET.SDK

A YubiKey SDK for .NET developers
Apache License 2.0
99 stars 47 forks source link

"Warning, state of non-volatile memory is unchanged." when trying to configure static password or delete slot. #48

Closed MartinTeichler closed 4 months ago

MartinTeichler commented 1 year ago

Hello,

I get the above error when trying to configure a static password or delete a slot. This error happens only when I proceed in a specific way:

  1. Configure a static password using the last example from this PDF (version 2.2):

    // Clear current settings.
    yk.ykClear();
    
    // Use selected config.
    yk.ykFlagProperty(ykFlagProperty.SECOND_CONFIG) = (slot == 2);
    
    // Don't append enter at the end.
    yk.ykFlagProperty(ykFlagProperty.APPEND_CR) = false;
    
    // Use static password mode => output scancodes (as opposed to modhex).
    yk.ykFlagProperty(ykFlagProperty.STATIC_TICKET) = false;
    
    // Use short ticket.
    yk.ykFlagProperty(ykFlagProperty.SHORT_TICKET) = true;
    
    // Encode plain text into keyboard scan codes.
    var scanCodes = text2scancode(input);
    
    // Split key to fit in various yubikey properties.
    var key1 = scanCodes.substr(0, FIXED_SIZE * 2);
    var key2 = scanCodes.substr(FIXED_SIZE * 2, UID_SIZE * 2);
    var key3 = scanCodes.substr(FIXED_SIZE * 2 + UID_SIZE * 2);
    
    // Set key parts.
    yk.ykStaticId = key1;
    yk.ykUID = key2;
    yk.ykKey = key3;
    
    // Write config to YubiKey.
    var programResult = yk.ykProgram;
  2. Without removing the Yubikey, use the current .Net SDK to configure a static password:

    public void SendStaticPasswordToYubikey(IYubiKeyDevice yubikey, Memory<char> payload, int slot)
    {
        if (slot != 1 && slot != 2)
            throw new ArgumentException($"{nameof(slot)} has to be either 1 or 2.");
    
        using (var otp = new OtpSession(yubikey))
        {
            var castSlot = (Slot)slot;
    
            if (castSlot == Slot.LongPress && otp.IsLongPressConfigured)
                otp.DeleteSlot(Slot.LongPress);
            else if(castSlot == Slot.ShortPress && otp.IsShortPressConfigured)
                otp.DeleteSlot(Slot.ShortPress);
    
            otp.ConfigureStaticPassword((Slot)slot)
                .WithKeyboard(KeyboardLayout.en_US)
                .SetPassword(payload)
                .Execute();
        }
    }

The call to Execute will throw the above error. Is there any way to "fix" this? I tried deleting the slot, but I get the same error. The only way to make it go away is to delete the slot (which leads to the above error) and restart the program. After that, it will write to the key successfully.

GregDomzalski commented 1 year ago

And you say this only happens if you provision with the COM code first?

As in: If you did not use COM, but configured the slot using the .NET SDK first and then try to re-run it on the same YubiKey, would that work fine?

How quickly is the .NET code running after the COM code is run?

Lastly, what version of the .NET SDK are you using?

MartinTeichler commented 1 year ago

And you say this only happens if you provision with the COM code first?

As in: If you did not use COM, but configured the slot using the .NET SDK first and then try to re-run it on the same YubiKey, would that work fine?

Yes and yes. What I noticed too was that the serial # was not displayed after I wrote a static password using COM api (it was there before). It doesn't really matter whether I wait a few hours or try to send a password immediately, the result is still the same.

I did a bit of messing around and found out that it has to do with the transport I am using. According to the docs, HID keyboard should support most operations. However, sometimes I also get SmartCard and this is where the problems start. On a VM, I only get SmartCard transport and it works when sending passwords through SmartCard when on a VM. BUT when I am on a host machine, this will not work and I have to use HID keyboard transport (I get SmartCard and HID transport on a host machine). However, due the missing serial number, I have no way to associate the different IYubiKey instances even though they (may or may not) belong to the same physical key. This is a problem because many of our users are still using the COM api and are also using VMs.

The question is, why is the serial number gone after we write to the key using COM api? I think this is the root of all my problems.

I am using the latest .Net SDK version.

GregDomzalski commented 1 year ago

Huh, OK - thanks for the additional information.

It may be a few days for me to get a repro environment properly set up. Most of my Windows development happens inside of a VM. I'm usually able to pass all three USB interfaces through, so hopefully this won't affect the repro.

My first instinct says that this is related to the YubiKey's USB interface reclaim timeout - a 3 second delay that is required when switching between the 3 USB interfaces. But if you are saying that this happens even when the SDK runs more than 3 seconds after your COM program, then I'm not sure how it could be this.

Could there be any background or long running processes that are attempting to communicate with the YubiKey?

In the meantime, could you try enabling logging in your .NET application? Perhaps the logs may help illuminate the culprit.

This example uses Serilog, but you can use any logger that supports the Microsoft.Extensions.Logging interface.

You'll need Serilog, Serilog.Sinks.Console, Serilog.Extensions.Logging packages and the following code snippet:

            using var log = new LoggerConfiguration()
                .Enrich.With(new ThreadIdEnricher())
                .WriteTo.Console(
                    outputTemplate: "[{Level}] ({ThreadId})  {Message}{NewLine}{Exception}")
                .CreateLogger();

            // This is the line that actually enables the logger for the SDK. So long as LoggerFactory
            // is set to something that properly implements the interface, we should be good to go.
            Core.Logging.Log.LoggerFactory = LoggerFactory.Create(
                builder => builder
                    .AddSerilog(log)
                    .AddFilter(level => level >= LogLevel.Information));
MartinTeichler commented 1 year ago

Could there be any background or long running processes that are attempting to communicate with the YubiKey?

I am not really sure. I am working on a customer laptop and they got all different kinds of programs and background processes running, they basically have a 3rd party app for everything.

I am not sure that the .Net SDK is even the problem here, I think it's rather the COM api because even when we open Yubikey Manager, the serial numbers are not there after we write a static password to the key. The call to yk.ykProgram seems to be the point of entry for this problem. As you can see in my example above, we are not really doing anything out of the ordinary.

Anyway, I added Serilog to my application. It's a WinForms app so I had to make a few adjustments. However, I don't get any output from your SDK. I can inject an ILogger to my app and log with that, so the logging seems to be working.

 var builder = new HostBuilder()
    .ConfigureServices((hostContext, services) =>
    {
        services.AddScoped<Form1>();

        //Add Serilog
        var serilogLogger = new LoggerConfiguration()
            .WriteTo.File("log.log")
            .CreateLogger();
        services.AddLogging(x =>
        {
            x.SetMinimumLevel(LogLevel.Information);
            x.AddSerilog(logger: serilogLogger, dispose: true);
            x.AddFilter(level => level >= LogLevel.Information);
        });
    });

var host = builder.Build();
ApplicationConfiguration.Initialize();
Application.Run(host.Services.GetRequiredService<Form1>());
GregDomzalski commented 1 year ago

Yes, any kind of logger will do.

You say you're still not getting any output from the SDK? I assume based on your code snippet above that this is run prior to any SDK functionality being invoked?

MartinTeichler commented 1 year ago

Yes, any kind of logger will do.

You say you're still not getting any output from the SDK? I assume based on your code snippet above that this is run prior to any SDK functionality being invoked?

Yes, that is correct. I only use the Yubikey stuff inside Form1 and that is injected deeper in the pipeline so it should work.

DennisDyallo commented 5 months ago

Hey @MartinTeichler ! Are you still experiencing this issue? If not, we'll close the issue. Thanks.

DennisDyallo commented 4 months ago

Closing old issues. Feel free to reopen if this is still important to you.