使用 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
更新:===========
椭圆曲线的问题与我在 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