public static void VerifySignature(PublicKeyCredential cred, byte[] authData, byte[] clientData, byte[] signature, string rpid) { if (cred == null) { throw new ArgumentNullException(nameof(cred)); } var payload = authData.Concat(CredentialUtility.Hash(clientData)).ToArray(); var rawSignature = DERSignature.Deserialize(signature); var authDataSpan = authData.AsSpan(); var rpidHash = authDataSpan.Slice(0, 32); var flags = (AuthenticatorDataFlags)authDataSpan[32]; var counterSpan = authDataSpan.Slice(33, 4); counterSpan.Reverse(); uint counter = BitConverter.ToUInt32(counterSpan); if ((flags & AuthenticatorDataFlags.UserPresent) == 0) { throw new Exception("user does not present"); } if (!rpidHash.SequenceEqual(CredentialUtility.Hash(Encoding.UTF8.GetBytes(rpid)))) { throw new Exception("RP ID Hash does not match"); } var publicKey = CBORObject.DecodeFromBytes(cred.PublicKey); var x = publicKey.MapGet(-2).GetByteString(); var y = publicKey.MapGet(-3).GetByteString(); var ecDsa = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { X = x, Y = y } }); var isValid = ecDsa.VerifyData(payload, rawSignature, HashAlgorithmName.SHA256); if (!isValid) { throw new Exception("invalid signature"); } if (cred.SignCounter >= counter) { throw new Exception("invalid signature counter"); } cred.SignCounter = counter; }
public static ClientData ValidateClientData(byte[] clientData, string origin, byte[] challenge) { var data = JsonSerializer.Deserialize <ClientData>(clientData); if (data.Type != "webauthn.create") { throw new Exception($"type does not match: {data.Type}"); } if (data.Origin != origin) { throw new Exception($"origin does not match: {data.Origin}"); } if (!CredentialUtility.Base64UrlDecode(data.Challenge).SequenceEqual(challenge)) { throw new Exception("challenge does not match"); } return(data); }
public static PublicKeyCredential ValidateAttestationData(string name, byte[] bytes, byte[] clientData, string rpid) { var cbor = CBORObject.DecodeFromBytes(bytes); var authDataSpan = cbor["authData"].GetByteString().AsSpan(); var format = cbor["fmt"].AsString(); var attestationStatement = cbor["attStmt"]; var rpidHash = authDataSpan.Slice(0, 32); var flags = (AuthenticatorDataFlags)authDataSpan[32]; var counterSpan = authDataSpan.Slice(33, 4); counterSpan.Reverse(); uint counter = BitConverter.ToUInt32(counterSpan); if ((flags & AuthenticatorDataFlags.UserPresent) == 0) { throw new Exception("user does not present"); } if ((flags & AuthenticatorDataFlags.AttestedCredentialData) == 0) { throw new Exception("credential data does not exist"); } var aaguid = authDataSpan.Slice(37, 16); int credentialIdLength = (authDataSpan[53] << 8) + authDataSpan[54]; var credentialId = authDataSpan.Slice(55, credentialIdLength); if (!rpidHash.SequenceEqual(CredentialUtility.Hash(Encoding.UTF8.GetBytes(rpid)))) { throw new Exception("RP ID Hash does not match"); } var credentialPublicKey = authDataSpan.Slice(55 + credentialIdLength).ToArray(); // validate attestation switch (format) { case "none": break; case "packed": // TODO break; case "fido-u2f": var publicKey = CBORObject.DecodeFromBytes(credentialPublicKey); if (publicKey.MapGet(3).AsInt32() != -7) // ES256 { throw new Exception("invalid signature algorithm"); } var x = publicKey.MapGet(-2).GetByteString(); var y = publicKey.MapGet(-3).GetByteString(); if (x.Length != 32 || y.Length != 32) { throw new Exception("invalid publickey length"); } var publicKeyU2F = new ByteArrayWriter(1 + 32 + 32) .Set(4) .CopyFrom(x) .CopyFrom(y) .ToArray(); var verificationData = new ByteArrayWriter(1 + 32 + 32 + credentialIdLength + (1 + 32 + 32)) .Set(0) .CopyFrom(rpidHash) .CopyFrom(CredentialUtility.Hash(clientData)) .CopyFrom(credentialId) .CopyFrom(publicKeyU2F) .ToArray(); var fidoU2F = FIDOU2FAttestationStatement.Decode(attestationStatement); if (!fidoU2F.VerifyData(verificationData)) { throw new Exception("invalid signature"); } break; default: throw new Exception($"unsupported attestation format: {format}"); } return(new PublicKeyCredential { Name = name, CredentialId = credentialId.ToArray(), PublicKey = credentialPublicKey, }); }