使用 Windows Hello via c# 的 Webauthn 身份验证

Webauthn authentication with Windows Hello via c#

更新:===========

椭圆曲线的问题与我在 RSA 中的问题非常相似。 VarifyData 总是 returns false。

        byte[] data = new byte[authData.Length + hashValClientData.Length];
        Buffer.BlockCopy(authData, 0, data, 0, authData.Length);
        Buffer.BlockCopy(hashValClientData, 0, data, authData.Length, hashValClientData.Length);

        byte[] sig = Convert.FromBase64String(assertion.Signature);

        if (pubKey.kty == "EC")
        {
            var keyType = new byte[] { 0x45, 0x43, 0x53, 0x31 }; // BCRYPT_ECDSA_PUBLIC_P256_MAGIC {E, C, S, 1}
            var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 }; // Key length 32
            byte[] keyImport = new byte [72];
            Buffer.BlockCopy(keyType, 0, keyImport, 0, 4);
            Buffer.BlockCopy(keyLength, 0, keyImport, 4, 4);
            Buffer.BlockCopy(Convert.FromBase64String(pubKey.x), 0, keyImport, 8, 32);
            Buffer.BlockCopy(Convert.FromBase64String(pubKey.y), 0, keyImport, 40, 32);

            try
            {
                using (ECDsaCng dsa = new ECDsaCng(CngKey.Import(keyImport, CngKeyBlobFormat.EccPublicBlob)))
                {
                    dsa.HashAlgorithm = CngAlgorithm.Sha256;
                    if (dsa.VerifyData(data, sig))
                    {
                        Console.WriteLine("The signature is valid.");
                    }
                    else
                    {
                        Console.WriteLine("The signature is not valid.");
                        return FAIL_STATUS;
                    }
                }
            }
            catch (Exception e)
            {
                return FAIL_STATUS;
            }
        } 

ECDsaCng 对象和 CngKey 有效,但 VerifyData 方法总是 returns 错误。 我的数据有问题吗?

ClientDataJSON 的正斜杠是从 Android EC1 转义的,但不是 Windows RSA

===================

我正在尝试使用 C# 验证从 Javascript navigator.credentials.get() 返回的 FIDO2/WebAuthn 凭据。这里的验证器是 Windows 你好(PIN 码) 无论我做什么,VerifySignature 方法总是返回无效签名。我是否使用了错误的 RSA 算法? Base64 什么时候不应该?有什么想法吗?

Javascript代码:-

return navigator.credentials.get({
        publicKey: getAssertionOptions
    }).then(rawAssertion => {
        var assertion = {
            id: base64encode(rawAssertion.rawId),
            clientDataJSON: arrayBufferToString(rawAssertion.response.clientDataJSON),
            userHandle: base64encode(rawAssertion.response.userHandle),
            signature: base64encode(rawAssertion.response.signature),
            authenticatorData: base64encode(rawAssertion.response.authenticatorData)
        };

C#代码:-

creds.Id = tempDB.Id;
creds.PublicKeyJwk = tempDB.PublicKeyJwk;

byte[] hashValClientData = _hash.ComputeHash(Encoding.Latin1.GetBytes(assertion.ClientDataJSON));

RSA rsa = RSA.Create();
PublicKey pubKey;
try
{
    pubKey = JsonConvert.DeserializeObject<PublicKey>(creds.PublicKeyJwk);
}
catch (Exception ex)
{
    return FAIL_STATUS;
}

RSAParameters keyInfo = new RSAParameters();
keyInfo.Modulus = Encoding.Latin1.GetBytes(Base64Decode(pubKey.n));
keyInfo.Exponent = Encoding.Latin1.GetBytes(Base64Decode(pubKey.e));
rsa.ImportParameters(keyInfo);
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
rsaDeformatter.SetHashAlgorithm("SHA256");
byte[] sig = Encoding.Latin1.GetBytes(Base64Decode(assertion.Signature));
if (rsaDeformatter.VerifySignature(hashValClientData, sig))
{
    Console.WriteLine("The signature is valid.");
}
else
{
    Console.WriteLine("The signature is not valid.");
}

        public static string Base64Encode(string plainText)
        {
            var plainTextBytes = Encoding.Latin1.GetBytes(plainText);
            return Convert.ToBase64String(plainTextBytes);
        }
        public static string Base64Decode(string base64EncodedData)
        {
            string paddedString = base64EncodedData;
            int padding = base64EncodedData.Length % 4;
            if (padding > 0 && padding < 3)
            {
                paddedString += "==".Substring(0, padding);
            }
            var base64EncodedBytes = Convert.FromBase64String(paddedString);
            return Encoding.Latin1.GetString(base64EncodedBytes);
        }

浏览器控制台输出:-

=== Assertion response === test.html:211 id (base64): gtCDzIXzuh0ZlblqiyMFf7f0/TS2m2a8sLvbj3CtERo= test.html:211 clientDataJSON: {"type":"webauthn.get","challenge":"ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmxlSEFpT2pFMk1UUTFNRFV6TURJc0ltbHpjeUk2SWxSbGMzUXVZMjl0SWl3aVlYVmtJam9pVkdWemRDNWpiMjBpZlEuUXdYVUdob3FQM1RGckhGV2pOOHNyZWVadFpMM2gtaUVpZk9jTWlzbHQxVQ","origin":"https://localhost:44362","crossOrigin":false,"other_keys_can_be_added_here":"do not compare clientDataJSON against a template. "} test.html:211 userHandle (base64): c29tZS51c2VyLmlk test.html:211 signature (base64): Gd0x/28tLTKba9/LRa+7riJ4XygPgfAjwdVw3h/fxisWSU8OLbcfqu6K5bIFspnPrsTyA6xD9I+5Sq/BAOalcAJCy46/39swTPF6+76F8Hx5GFNcXusMZw6PQZpEqALZkifF936hTBXCoVrYcl9NZ5/jjd9zpFhSN90Ht/WEAl4DrvgnZ/NQa2klCpm4anDaZoYDcv9SykqtUv/CHNAtpSYgcfA8XVcDGG3ieefw1rii6g6chgTNfwhctIiqSkCBrLECavVUrbT6UpF+R2nIgexCyT8dKe8gVxvNaUeFnltSSkleOo/GUHzisseFjTow+R9yo4og/tuuS9PSWTR8WA== test.html:211 authenticatorData (base64): SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ==

可能还有其他问题,但最大的问题是您正在尝试根据 clientDataJSON 的哈希值验证签名。它实际上应该是 authenticatorData 和散列的 clientDataJSON 的二进制连接。

来自 https://www.w3.org/TR/webauthn/#sctn-verifying-assertion,第 20 步:

“使用 credentialPublicKey,验证 sig 是对 authData 和 hash 的二进制串联的有效签名。”

鉴于你所拥有的,类似这样的东西应该可以工作:

    var authData = Convert.FromBase64String(assertion.authenticatorData);
    byte[] hashValClientData = _hash.ComputeHash(assertion.ClientDataJSON);

    byte[] data = new byte[authData.Length + hashValClientData.Length];
    Buffer.BlockCopy(authData, 0, data, 0, authData.Length);
    Buffer.BlockCopy(hashValClientData, 0, data, authData.Length, hashValClientData.Length);

    var rsa = RSA.Create();
    rsa.ImportParameters(
        new RSAParameters()
        {
            Modulus = Convert.FromBase64String(pubKey.n),
            Exponent = Convert.FromBase64String(pubKey.e),
        }
    );

    byte[] sig = Convert.FromBase64String(assertion.Signature);
    if (rsa.VerifyData(data, sig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
    {
        Console.WriteLine("The signature is valid.");
    }
    else
    {
        Console.WriteLine("The signature is not valid.");
    }

这个项目可能有一些您感兴趣的代码:https://github.com/abergs/fido2-net-lib