简体   繁体   中英

Is there a way to authenticate YubiKey from stand-alone C# application without internet connection?

I need some way to authenticate a user, or key pair, from YubiKey on an air-gapped PC (no internet connection). Preferably from a C#/.NET application.

Essentially, I need to verify that the inserted YubiKey gives user proper authorization to use my application.

My first idea was to generate a RSA key pair, store private key on YubiKey and public key in my application. I would then verify the key pair using gpg. However, this approach does not work:

C:\\Program Files (x86)\\GnuPG\\bin>gpg --card-status

gpg: selecting openpgp failed: No such device

gpg: OpenPGP card not available: No such device

I then used YubiKey manager to generate key pair and certificate and store it on device. I can see the certificate via Windows CertUtil command but I don't know what arguments to pass to CertUtil -verifykeys [KeyContainerName CACertFile] since I don't know the container name.

After some searching I found this solution based on ( https://www.codeproject.com/Articles/240655/Using-a-Smart-Card-Certificate-with-NET-Security-i )

Step 1: use ykman to set up private/public key pair

ykman piv generate-key -a RSA2048 -F PEM --touch-policy NEVER 9e "c:\\dev\\License\\ykeys\\my_key.pub"

This command creates public/private RSA key pair. Private key is saved on the device in slot 9e and while public key is saved to "my_key.pub" file

Step 2: use ykman to create self-signed certificate

ykman piv generate-certificate -s "my_key_test" -d 365 9e "c:\\dev\\License\\ykeys\\my_key.pub"

This command creates self-signed X.509 certificate and saves it one device.

Step 3: export certificate

ykman piv export-certificate -F PEM 9e "c:\\dev\\License\\ykeys\\my_key_crt.pem"

This command creates a copy of the certificate from step 2 in my_key_crt.pem

Step 4: use C# program to verify public/private key pair

using System;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace TestCSPSmartCard
{
class Program
{
    static unsafe void Main(string[] args)
    {
        //PKI provider name comes from system registry or the output  
        //of "certutil -scinfo" command
        //The container name comes from the output of "certutil -scinfo" command
        const string 
            pkiProvider = "Microsoft Base Smart Card Crypto Provider", 
            container = "b51a653f-f451-c1d4-0841-5ace955fc101";

        try
        {
            //'123456' is the default 
            SecureString smartCardPin;
            char[] scPwd = { '1', '2', '3', '4', '5', '6' };
            fixed(char* pChars = scPwd)       
            {   
                smartCardPin = new SecureString(pChars, scPwd.Length);       
            }

            //Construct CspParameters object. 
            //Omitting last two arguments will cause Windows to display a dialog
            //prompting user for the SmartCard PIN.
            CspParameters csp = 
                new CspParameters(1,
                    pkiProvider,
                    container,
                    new System.Security.AccessControl.CryptoKeySecurity(),
                    smartCardPin);

            byte[] toSign = new byte[20];
            Random rnd = new Random((int)DateTime.Now.Ticks);
            rnd.NextBytes(toSign);

            Console.WriteLine("Data to sign : " + BitConverter.ToString(toSign));

            RSACryptoServiceProvider rsaCsp = new RSACryptoServiceProvider(csp);
            RSAPKCS1SignatureFormatter rsaSign = new RSAPKCS1SignatureFormatter(rsaCsp);
            rsaSign.SetHashAlgorithm("SHA1");
            byte[] signature = rsaSign.CreateSignature(toSign);

            Console.WriteLine();
            Console.WriteLine("Signature: " + BitConverter.ToString(signature));

            RSACryptoServiceProvider rsaCsp2 = FromPublicKey(args.FirstOrDefault());

            RSAPKCS1SignatureDeformatter rsaVerify = new RSAPKCS1SignatureDeformatter(rsaCsp2);
            rsaVerify.SetHashAlgorithm("SHA1");
            bool verified = rsaVerify.VerifySignature(toSign, signature);

            Console.WriteLine();
            Console.WriteLine("Signature verified [{0}]", verified);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Crypto error: " + ex.Message);
        }

        Console.WriteLine("done!");
    }

    private static RSACryptoServiceProvider FromPublicKey(string keyFile = null)
    {
        //Generated from PEM public key file using https://superdry.apphb.com/tools/online-rsa-key-converter
        const string xmlPubKey =
            @"<RSAKeyValue><Modulus>2mdYz5yV59K0PMO6HCxBA7gVWtbmNY+dwYOc14H5DTD7zQ64CHpxAQOAexFx5uQKaxIR8UjZOikOwO+NWMvQ4/DCIHu3WwK2/M07JQ3LYeeJ8L28RSfb9S7CCMvJ7sDOmVMB4otfQwqYvMri9QWYVe/9jWIyp3LezAUyFTGnA2OeMiVaZa2gsI5+v4HkuY3ZD9KIdUgp3Wt0SFTe1jRKAaqJhp8f3Lh0CRaYoukeq0XAhhh9k55o7wLCp0XZgSZzOPeuNL+at20Tx9BRcb/9odlmFoHn/0P0X57a1yKhKRGUIri3gfu2BJ2BnXOUy+iSk1VNWRixuMsxee059Gg7Uw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

        if (keyFile != null)
        {
            FileInfo cerFile = new FileInfo(keyFile);

            if (cerFile.Exists)
            {
                X509Certificate2 cert = new X509Certificate2();

                Console.WriteLine($"Importing public key from {cerFile.FullName}");

                cert.Import(cerFile.FullName);

                return (RSACryptoServiceProvider)cert.PublicKey.Key;
            }
        }
        RSACryptoServiceProvider result = new RSACryptoServiceProvider();
        result.FromXmlString(xmlPubKey);

        return result;
    }
}
}

U2F and Webauthn can be used to authenticate a token completely locally. Yubico has a Java based one on GitHub and while there is no binary download, you can easily compile it by following the description on the page. The server comes with a demo server that allows you to do first tests, especially when it comes to test your client side implementation. For that there are libraries as well that are provided by Yubico that you can also find on Yubico . The nice thing about U2F and Webauthn is that both are supported by many modern browsers out of the box, so by doing some Javascript-magic (esentially navigator.credentials.get(...) to start the token authentication process) you can get things working. The demo server comes with an HTML-page that contains everything needed to do U2F or Webauthn.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM