Ejemplo n.º 1
0
        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}");
            }
        }
Ejemplo n.º 2
0
        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);
            }
        }
Ejemplo n.º 3
0
        public Page2()
        {
            InitializeComponent();

            var challenge = AttestationVerifier.CreateChallenge();

            this.TextChallenge.Text = Common.BytesToHexString(challenge);
        }
Ejemplo n.º 4
0
        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;
            }
        }
Ejemplo n.º 5
0
        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}");
            }
        }
Ejemplo n.º 6
0
        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.
        }