Yubico / Yubico.NET.SDK

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

Signing and verifying arbitrary data with PIV #52

Closed SCLDGit closed 1 year ago

SCLDGit commented 1 year ago

Hello,

Please tell me if I'm completely misunderstanding this functionality, but I'm having a hell of a time figuring out how to sign the hash of some arbitrary data and then later validate that hash using a distributed public key. Our goal is to use the PIV module's signing capability to sign the SHA256 hash of a file, and then distribute that file to customers at which point they will use one of our applications to import that file, which will have its signature validated against the one generated on our end using an embedded public key.

I've figured out how to sign the hash using code like the following:

var fileBytes = Encoding.UTF8.GetBytes(myFileContents);

var hash = SHA256.HashData(fileBytes);

var validYubikey = GetValidYubikey();

using var pivSession = new PivSession(validYubikey);

var keyCollector = new KeyCollector(p_window);

pivSession.KeyCollector = keyCollector.KeyCollectorDelegate;

try
{
    var signature = pivSession.Sign(PivSlot.Signing, hash);

    // Do something here to test the validation?
}

My signature is generated as expected, but I can't for the life of me figure out how I can then retrieve the public key of the cert in the signing slot and use it to validate that signature. Am I missing something obvious?

I've tried various permutations of the following without much luck, VerifyHash and VerifyData always fail:

            var publicKey = pivSession.GetMetadata(PivSlot.Signing).PublicKey as PivEccPublicKey;

            var eccCurve = ECCurve.CreateFromValue("1.2.840.10045.3.1.7");

            var eccParams = new ECParameters
                            {
                                Curve = eccCurve
                            };
            // In PIV, a public point is represented as
            // 04 || x-coordinate || y-coordinate
            // For the C# class we need to break it into the two coordinates.
            var coordLength = (publicKey.PublicPoint.Length - 1) / 2;
            eccParams.Q.X = publicKey.PublicPoint.Slice(1, coordLength).ToArray();
            eccParams.Q.Y = publicKey.PublicPoint.Slice(1 + coordLength, coordLength).ToArray();

            // Build the EC object that will be able to create the
            // SubjectPublicKeyInfo.
            using var ecdsa = ECDsa.Create(eccParams);

            var test = ecdsa.VerifyHash(hash, signature);
SCLDGit commented 1 year ago

For anyone struggling with this in the future, the solution was to manually specify the DSASignatureFormat in the VerifyHash method, like so:

ecdsa.VerifyHash(hash, signature, DSASignatureFormat.Rfc3279DerSequence);

Cheers!