private async void ButtonGetAssertion_Click(object sender, RoutedEventArgs e) { GetFirstUSBDevice(); var rpid = "test.com"; var challenge = AttestationVerifier.CreateChallenge(); var param = new g.FIDO2.CTAP.CTAPCommandGetAssertionParam(rpid, challenge, creid); param.Option_up = true; var res = await con.GetAssertionAsync(param, "1234"); if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.NotConnected) { // FIDOキーが接続されていない場合 return; } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Timeout) { // FIDOキーのタッチ待ちでTimeoutした場合 return; } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Ok) { string verifyResult = ""; if (res.CTAPResponse.Assertion != null) { // verify var v = new AssertionVerifier(); var verify = v.Verify(rpid, pubkey, challenge, res.CTAPResponse.Assertion); verifyResult = $"- Verify = {verify.IsSuccess}"; } MessageBox.Show($"GetAssertionAsync\r\n- Status = {res.CTAPResponse.Status}\r\n- StatusMsg = {res.CTAPResponse.StatusMsg}\r\n{verifyResult}"); } }
private void ButtonNext_Click(object sender, RoutedEventArgs e) { var challenge = Common.HexStringToBytes(this.TextChallenge.Text); var att_b = Common.HexStringToBytes(this.TextAttestation.Text); var att = Serializer.DeserializeAttestation(att_b); if (att == null) { // Attestaion Deserialize Error return; } // verify var v = new AttestationVerifier(); var verify = v.Verify(rpid, challenge, att); if (verify.IsSuccess) { if (page4 == null) { page4 = new Page4(verify.CredentialID, verify.PublicKeyPem); } this.NavigationService.Navigate(page4); } }
public Page2() { InitializeComponent(); var challenge = AttestationVerifier.CreateChallenge(); this.TextChallenge.Text = Common.BytesToHexString(challenge); }
public Page5(byte[] creid, string pubkey) { InitializeComponent(); var challenge = AttestationVerifier.CreateChallenge(); this.TextChallenge.Text = Common.BytesToHexString(challenge); if (creid != null) { this.TextCredentialID.Text = Common.BytesToHexString(creid); } if (pubkey != null) { this.TextPublickKey.Text = pubkey; } }
private async void ButtonMakeCredential_Click(object sender, RoutedEventArgs e) { GetFirstUSBDevice(); string rpid = "test.com"; var challenge = AttestationVerifier.CreateChallenge(); var param = new g.FIDO2.CTAP.CTAPCommandMakeCredentialParam(rpid, challenge); var res = await con.MakeCredentialAsync(param, "1234"); if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.NotConnected) { // FIDOキーが接続されていない場合 return; } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Timeout) { // FIDOキーのタッチ待ちでTimeoutした場合 return; } else if (res.DeviceStatus == g.FIDO2.CTAP.DeviceStatus.Ok) { string verifyResult = ""; if (res.CTAPResponse.Status == 0) { if (res.CTAPResponse.Attestation != null) { // verify var v = new AttestationVerifier(); var verify = v.Verify(rpid, challenge, res.CTAPResponse.Attestation); verifyResult = $"- Verify = {verify.IsSuccess}\r\n- CredentialID = {Common.BytesToHexString(verify.CredentialID)}\r\n- PublicKey = {verify.PublicKeyPem}"; if (verify.IsSuccess) { // store creid = verify.CredentialID.ToArray(); pubkey = verify.PublicKeyPem; } } } MessageBox.Show($"MakeCredentialAsync\r\n- Status = {res.CTAPResponse.Status}\r\n- StatusMsg = {res.CTAPResponse.StatusMsg}\r\n{verifyResult}"); } }
private async void ButtonMakeCredential_Click(object sender, RoutedEventArgs e) { addLog("<makeCredential>"); var rpid = "BLEtest.com"; var challenge = Encoding.ASCII.GetBytes("this is challenge"); var param = new g.FIDO2.CTAP.CTAPCommandMakeCredentialParam(rpid, challenge, new byte[0]); param.RpName = "BLEtest name"; param.UserName = "******"; param.UserDisplayName = "testUserDisplayName"; param.Option_rk = false; param.Option_uv = true; //param.Extensions = new Dictionary<string, bool> { { "hmac-secret", true } }; string pin = ""; var res = await con.MakeCredentialAsync(param, pin); LogResponse(res.DeviceStatus, res.CTAPResponse); if (res?.CTAPResponse.Status == 0) { if (res.CTAPResponse?.Attestation != null) { //Verify var v = new AttestationVerifier(); var verify = v.Verify(rpid, challenge, res.CTAPResponse.Attestation); addLog($"- Verify = {verify.IsSuccess}\r\n- - PublicKey = {verify.PublicKeyPem}"); var creid = res.CTAPResponse.Attestation.CredentialId.ToHexString(); addLog($"- CredentialID = {creid}"); textBoxCreID.Text = creid; pubkey = verify.PublicKeyPem; } } }
public async Task <AttestationVerificationSuccess> VerifyAsync(CredentialCreateOptions originalOptions, Fido2Configuration config, IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUser, IMetadataService metadataService, byte[] requestTokenBindingId) { // https://www.w3.org/TR/webauthn/#registering-a-new-credential // 1. Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON. // 2. Let C, the client data claimed as collected during the credential creation, be the result of running an implementation-specific JSON parser on JSONtext. // Note: C may be any implementation-specific data structure representation, as long as C’s components are referenceable, as required by this algorithm. // Above handled in base class constructor // 3. Verify that the value of C.type is webauthn.create if (Type != "webauthn.create") { throw new VerificationException("AttestationResponse is not type webauthn.create"); } // 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call. // 5. Verify that the value of C.origin matches the Relying Party's origin. // 6. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over which the assertion was obtained. // If Token Binding was used on that TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection. BaseVerify(config.Origin, originalOptions.Challenge, requestTokenBindingId); if (Raw.Id == null || Raw.Id.Length == 0) { throw new VerificationException("AttestationResponse is missing Id"); } if (Raw.Type != PublicKeyCredentialType.PublicKey) { throw new VerificationException("AttestationResponse is missing type with value 'public-key'"); } var authData = new AuthenticatorData(AttestationObject.AuthData); // 7. Compute the hash of response.clientDataJSON using SHA-256. byte[] clientDataHash, rpIdHash; using (var sha = CryptoUtils.GetHasher(HashAlgorithmName.SHA256)) { clientDataHash = sha.ComputeHash(Raw.Response.ClientDataJson); rpIdHash = sha.ComputeHash(Encoding.UTF8.GetBytes(originalOptions.Rp.Id)); } // 8. Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse structure to obtain the attestation statement format fmt, the authenticator data authData, and the attestation statement attStmt. // Handled in AuthenticatorAttestationResponse::Parse() // 9. Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party if (false == authData.RpIdHash.SequenceEqual(rpIdHash)) { throw new VerificationException("Hash mismatch RPID"); } // 10. Verify that the User Present bit of the flags in authData is set. if (false == authData.UserPresent) { throw new VerificationException("User Present flag not set in authenticator data"); } // 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set. // see authData.UserVerified // TODO: Make this a configurable option and add check to require // 12. Verify that the values of the client extension outputs in clientExtensionResults and the authenticator extension outputs in the extensions in authData are as expected, // considering the client extension input values that were given as the extensions option in the create() call. In particular, any extension identifier values // in the clientExtensionResults and the extensions in authData MUST be also be present as extension identifier values in the extensions member of options, i.e., // no extensions are present that were not requested. In the general case, the meaning of "are as expected" is specific to the Relying Party and which extensions are in use. // TODO?: Implement sort of like this: ClientExtensions.Keys.Any(x => options.extensions.contains(x); if (false == authData.HasAttestedCredentialData) { throw new VerificationException("Attestation flag not set on attestation data"); } // 13. Determine the attestation statement format by performing a USASCII case-sensitive match on fmt against the set of supported WebAuthn Attestation Statement Format Identifier values. // An up-to-date list of registered WebAuthn Attestation Statement Format Identifier values is maintained in the IANA registry of the same name // https://www.w3.org/TR/webauthn/#defined-attestation-formats AttestationVerifier verifier = default; switch (AttestationObject.Fmt) { case "none": verifier = new None(); // https://www.w3.org/TR/webauthn/#none-attestation break; case "tpm": verifier = new Tpm(); // https://www.w3.org/TR/webauthn/#tpm-attestation break; case "android-key": verifier = new AndroidKey(); // https://www.w3.org/TR/webauthn/#android-key-attestation break; case "android-safetynet": verifier = new AndroidSafetyNet(metadataService.TimestampDriftTolerance); // https://www.w3.org/TR/webauthn/#android-safetynet-attestation break; case "fido-u2f": verifier = new FidoU2f(); // https://www.w3.org/TR/webauthn/#fido-u2f-attestation break; case "packed": verifier = new Packed(); // https://www.w3.org/TR/webauthn/#packed-attestation break; case "apple": verifier = new Apple(); // https://www.w3.org/TR/webauthn/#apple-anonymous-attestation break; default: throw new VerificationException("Missing or unknown attestation type"); } ; // 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature, // by using the attestation statement format fmt’s verification procedure given attStmt, authData and the hash of the serialized client data computed in step 7 (var attType, var trustPath) = verifier.Verify(AttestationObject.AttStmt, AttestationObject.AuthData, clientDataHash); // 15. If validation is successful, obtain a list of acceptable trust anchors (attestation root certificates or ECDAA-Issuer public keys) for that attestation type and attestation statement format fmt, from a trusted source or from policy. // For example, the FIDO Metadata Service [FIDOMetadataService] provides one way to obtain such information, using the aaguid in the attestedCredentialData in authData. var entry = metadataService?.GetEntry(authData.AttestedCredentialData.AaGuid); // while conformance testing, we must reject any authenticator that we cannot get metadata for if (metadataService?.ConformanceTesting() == true && null == entry && AttestationType.None != attType && "fido-u2f" != AttestationObject.Fmt) { throw new VerificationException("AAGUID not found in MDS test metadata"); } if (null != trustPath) { // If the authenticator is listed as in the metadata as one that should produce a basic full attestation, build and verify the chain if ((entry?.MetadataStatement?.AttestationTypes.Contains(MetadataAttestationType.ATTESTATION_BASIC_FULL.ToEnumMemberValue()) ?? false) || (entry?.MetadataStatement?.AttestationTypes.Contains(MetadataAttestationType.ATTESTATION_PRIVACY_CA.ToEnumMemberValue()) ?? false)) { var attestationRootCertificates = entry.MetadataStatement.AttestationRootCertificates .Select(x => new X509Certificate2(Convert.FromBase64String(x))) .ToArray(); if (false == CryptoUtils.ValidateTrustChain(trustPath, attestationRootCertificates)) { throw new VerificationException("Invalid certificate chain"); } } // If the authenticator is not listed as one that should produce a basic full attestation, the certificate should be self signed if ((!entry?.MetadataStatement?.AttestationTypes.Contains(MetadataAttestationType.ATTESTATION_BASIC_FULL.ToEnumMemberValue()) ?? false) && (!entry?.MetadataStatement?.AttestationTypes.Contains(MetadataAttestationType.ATTESTATION_PRIVACY_CA.ToEnumMemberValue()) ?? false) && (!entry?.MetadataStatement?.AttestationTypes.Contains(MetadataAttestationType.ATTESTATION_ANONCA.ToEnumMemberValue()) ?? false)) { if (trustPath.FirstOrDefault().Subject != trustPath.FirstOrDefault().Issuer) { throw new VerificationException("Attestation with full attestation from authenticator that does not support full attestation"); } } } // Check status resports for authenticator with undesirable status foreach (var report in entry?.StatusReports ?? Enumerable.Empty <StatusReport>()) { if (true == Enum.IsDefined(typeof(UndesiredAuthenticatorStatus), (UndesiredAuthenticatorStatus)report.Status)) { throw new VerificationException("Authenticator found with undesirable status"); } } // 16. Assess the attestation trustworthiness using the outputs of the verification procedure in step 14, as follows: // If self attestation was used, check if self attestation is acceptable under Relying Party policy. // If ECDAA was used, verify that the identifier of the ECDAA-Issuer public key used is included in the set of acceptable trust anchors obtained in step 15. // Otherwise, use the X.509 certificates returned by the verification procedure to verify that the attestation public key correctly chains up to an acceptable root certificate. // 17. Check that the credentialId is not yet registered to any other user. // If registration is requested for a credential that is already registered to a different user, the Relying Party SHOULD fail this registration ceremony, or it MAY decide to accept the registration, e.g. while deleting the older registration if (false == await isCredentialIdUniqueToUser(new IsCredentialIdUniqueToUserParams(authData.AttestedCredentialData.CredentialID, originalOptions.User))) { throw new VerificationException("CredentialId is not unique to this user"); } // 18. If the attestation statement attStmt verified successfully and is found to be trustworthy, then register the new credential with the account that was denoted in the options.user passed to create(), // by associating it with the credentialId and credentialPublicKey in the attestedCredentialData in authData, as appropriate for the Relying Party's system. var result = new AttestationVerificationSuccess() { CredentialId = authData.AttestedCredentialData.CredentialID, PublicKey = authData.AttestedCredentialData.CredentialPublicKey.GetBytes(), User = originalOptions.User, Counter = authData.SignCount, CredType = AttestationObject.Fmt, Aaguid = authData.AttestedCredentialData.AaGuid, }; return(result); // 19. If the attestation statement attStmt successfully verified but is not trustworthy per step 16 above, the Relying Party SHOULD fail the registration ceremony. // This implementation throws if the outputs are not trustworthy for a particular attestation type. }