Search code examples
one-time-passwordyubicoyubikey

Yubikey OTP Programming Issue with AccessCode


I am building a web application in C# to program Slot1 on a YubiKey for OTP. I am successfully able to program the key unless the configuration has been protected. The serial number was used to protect Slot1's configuration, but whenever I try to convert the serial number into the necessary Memory get 7 bytes and the .Execute() command complains about the max length accepted being 6 bytes... The serial number is 7 digits.

The error message is - {"Access code must be 6 or fewer bytes."}

I can't find any helpful information in the Yubico API documentation and was hoping someone here could assist. I need to be able to use the current access code (always the S/N) to overwrite Slot1 if its protected.

try
{
    OtpSession otp_session = new OtpSession(_yubiKey);

    Memory<byte> hmac_key = Generate_Seed();
    Memory<byte> serial_number = new Memory<byte>();

    serial_number = Encoding.ASCII.GetBytes(_yubiKey.SerialNumber.ToString());

    SlotAccessCode access_code = new SlotAccessCode(serial_number);

    using (OtpSession otp = new OtpSession(_yubiKey))
    {
        otp.ConfigureHotp(Slot.ShortPress)
            .UseCurrentAccessCode(access_code)
            .AppendCarriageReturn(true)
            .UseNumericKeypad(true)
            .UseKey(hmac_key)         
            .Execute();
    }
}
catch (Exception ex)
{
    if (ex.ToString() == "{YubiKey Operation Failed [Warning, state of non-volatile memory is 
        unchanged.]}")
        lblMessage.Text = "YubiKey could not be configured. Please use the proper access 
        code.";
}        

Solution

  • The ASCII encoding of the serial number cannot be used as an access code, simply because such ASCII strings can exceed the SlotAccessCode.MaxAccessCodeLength value, which is 6.

    However, as the SerialNumber is an int (4 bytes), simply use the binary representation of the serial number as the access code:

    int serial = yubikey.SerialNumber;
    byte[] bytes = new byte[4];
    bytes[0] = (byte)(serial >> 24);
    bytes[1] = (byte)(serial >> 16);
    bytes[2] = (byte)(serial >> 8);
    bytes[3] = (byte)serial;
    
    SlotAccessCode access_code = new SlotAccessCode(bytes);
    

    On recent .NET version, you can probably also use class BinaryPrimitives , but you need to take endianness into account to make your code portable.

    As a side note: why do you want to use the serial as an access code? Access codes usually need to be secret information.

    Update 1: Reading your question more carefully, I understand your access codes are using Binary Coded Decimal (BCD) encoding.

    There is probably a library somewhere that does that for you, but otherwise you can use something like this for the encoding:

    serial = 7112345; // example serial number
    byte[] bcdBytes = new byte[6];
    for (int i = 5; i >= 0; i--)
    {
        int right = serial % 10;
        serial /= 10;
        int left = serial % 10;
        serial /= 10;
        bcdBytes[i] = (byte) (left << 4 | right);
    }
    SlotAccessCode access_code = new SlotAccessCode(bcdBytes);
    

    Update 2: For a big-endian hex encoding:

    serial = 7112345;
    byte[] serialBytes = new byte[4];
    BinaryPrimitives.TryWriteInt32BigEndian(serialBytes,serial);
    byte[] hexBytes = new byte[6];
    Array.Copy(serialBytes,0,hexBytes,2,4);
    SlotAccessCode access_code = new SlotAccessCode(hexBytes);
    

    This would result in the byte array 00 00 00 6c 86 99, where 0x6c8699 == 7112345