public static WebAuthnAssertion VerifyAssertionResult(Fido2Configuration fido2Config, string requestJSON, string resultJSON, Func <string, byte[]> getStoredPublicKey)
        {
            var fido2 = new Fido2(fido2Config);

            IsUserHandleOwnerOfCredentialIdAsync callback = (args) =>
            {
                var u  = args.UserHandle;
                var id = args.CredentialId;
                return(Task.FromResult(true));
            };

            var request = Newtonsoft.Json.JsonConvert.DeserializeObject <AssertionOptions>(requestJSON);
            AuthenticatorAssertionRawResponse assertionResponse = Newtonsoft.Json.JsonConvert.DeserializeObject <AuthenticatorAssertionRawResponse>(resultJSON);
            var  credentialId   = System.Convert.ToBase64String(assertionResponse.Id);
            uint storedCounter  = 0; // 0 means always success
            var  publicKey      = getStoredPublicKey(credentialId);
            var  success        = fido2.MakeAssertionAsync(assertionResponse, request, publicKey, storedCounter, callback).Result;
            var  clientDataJson = Newtonsoft.Json.JsonConvert.DeserializeObject <AuthenticatorResponse>(System.Text.UTF8Encoding.UTF8.GetString(assertionResponse.Response.ClientDataJson));
            var  challenge      = clientDataJson.Challenge;

            return(new WebAuthnAssertion
            {
                verified = true,
                challenge = System.Convert.ToBase64String(clientDataJson.Challenge),
                credentialId = credentialId,
                publicKey = System.Convert.ToBase64String(publicKey),
                userHandle = assertionResponse.Response.UserHandle
            });
        }
Example #2
0
        public async Task <JsonResult> MakeAssertionTest([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            // 1. Get the assertion options we sent the client
            var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
            var options     = AssertionOptions.FromJson(jsonOptions);

            // 2. Get registered credential from database
            var creds = DemoStorage.GetCredentialById(clientResponse.Id);

            // 3. Get credential counter from database
            var storedCounter = creds.SignatureCounter;

            // 4. Create callback to check if userhandle owns the credentialId
            IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
            {
                var storedCreds = await DemoStorage.GetCredentialsByUserHandleAsync(args.UserHandle);

                return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
            };

            // 5. Make the assertion
            var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

            // 6. Store the updated counter
            DemoStorage.UpdateCounter(res.CredentialId, res.Counter);

            var testRes = new
            {
                status       = "ok",
                errorMessage = ""
            };

            // 7. return OK to client
            return(Json(testRes));
        }
Example #3
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                //var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var jsonOptions = await _distributedCache.GetStringAsync(UniqueId);

                if (string.IsNullOrEmpty(jsonOptions))
                {
                    throw new Exception("Can't get AssertionOptions from cache");
                }
                var options = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = await _fido2Storage.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await _fido2Storage.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                await _fido2Storage.UpdateCounter(res.CredentialId, res.Counter);

                // complete sign-in
                var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();

                if (user == null)
                {
                    throw new InvalidOperationException($"Unable to load two-factor authentication user.");
                }

                var result = await _signInManager.TwoFactorSignInAsync("FIDO2", string.Empty, false, false);

                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
Example #4
0
        public async Task <IActionResult> Fido2LoginCallback([FromBody] AuthenticatorAssertionRawResponse clientResponse, [FromHeader] string returnUrl)
        {
            var info = await HttpContext.AuthenticateAsync(_fido2AuthenticationScheme);

            var tempUser = info?.Principal;

            if (tempUser == null)
            {
                return(Json(new { success = false }));
            }
            var user = await _users.FindByIdAsync(tempUser.GetSubjectId());

            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = _authenticationContext.FidoLogins.FirstOrDefault(x => x.PublicKeyIdBytes.SequenceEqual(clientResponse.Id));
                //DemoStorage.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                if (creds.UserId != user.Id)
                {
                    throw new Exception("User is not owner of credentials.");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    return(_authenticationContext.FidoLogins.FirstOrDefault(x => x.UserHandle.SequenceEqual(args.UserHandle) && x.PublicKeyIdBytes.SequenceEqual(args.CredentialId)) != null);
                };

                // 5. Make the assertion
                var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);


                // 6. Store the updated counter
                creds.SignatureCounter = res.Counter;
                _authenticationContext.SaveChanges();

                // 7. return OK to client
                return(Json(new { success = true }));
            }
            catch (Exception e)
            {
                return(Json(new { success = false }));
            }
        }
Example #5
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database

                var signInUser = await _signInManager.GetTwoFactorAuthenticationUserAsync();

                var applicationUser = await _userManager.FindByNameAsync(signInUser.UserName);

                var creds = _fido2CredentialService.GetCredentialByUser(applicationUser);



                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await _fido2CredentialService.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => new PublicKeyCredentialDescriptor(c.Descriptor).Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                var code = await _fido2CredentialService.UpdateCounter(res.CredentialId, res.Counter);

                // TODO Fix counter
                // TODO Allow more than one key to work

                // var result = await _signInManager.TwoFactorSignInAsync("Fido2", string.Empty, false, false);
                await _signInManager.SignInAsync(applicationUser, false);

                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
Example #6
0
        // Only API call
        public async Task <AuthorizationResponse> SignInAsync(SecurityKeySignInModel parameters)
        {
            var assertionRawResponse = parameters.AuthenticatorAssertionRawResponse;

            // Get the assertion options we sent the client
            var jsonOptions = _memoryCache.Get <string>(Convert.ToBase64String(assertionRawResponse.Id));
            var options     = AssertionOptions.FromJson(jsonOptions);

            // Get registered credential from database
            var creds = await GetCredentialById(assertionRawResponse.Id);

            if (creds == null)
            {
                throw new Exception("Unknown credentials");
            }

            // Get credential counter from database
            var storedCounter = creds.SignatureCounter;

            // Create callback to check if userhandle owns the credentialId
            IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
            {
                var storedCreds = await GetCredentialsByUserHandleAsync(args.UserHandle);

                return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
            };

            // Make the assertion
            var res = await _lib.MakeAssertionAsync(assertionRawResponse, options, creds.PublicKey, storedCounter, callback);

            // Store the updated counter
            await UpdateCounter(res.CredentialId, res.Counter);

            // Get authenticator flags
            var authData = new AuthenticatorData(assertionRawResponse.Response.AuthenticatorData);

            if (authData.UserPresent && authData.UserVerified)
            {
                var user = await _userManager.FindByNameAsync(creds.Username);

                if (user == null)
                {
                    throw new HESException(HESCode.UserNotFound);
                }

                await _signInManager.SignInAsync(user, parameters.RememberMe);

                return(AuthorizationResponse.Success(user));
            }

            return(AuthorizationResponse.Error(HESCode.AuthenticatorNotFIDO2));
        }
Example #7
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = await _fido2Storage.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await _fido2Storage.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                await _fido2Storage.UpdateCounter(res.CredentialId, res.Counter);

                var identityUser = await _userManager.FindByNameAsync(creds.Username);

                if (identityUser == null)
                {
                    throw new InvalidOperationException($"Unable to load user.");
                }

                await _signInManager.SignInAsync(identityUser, isPersistent : false);

                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
Example #8
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = DemoStorage.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await DemoStorage.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                DemoStorage.UpdateCounter(res.CredentialId, res.Counter);

                // Begin customization - Krishna
                // 7. At this point, the user has been authenticated successfully.
                // Set auth cookie through standard ASP.NET core's identity methods, using creds.userHandle as the UPN claim.
                var userHandleStr = Encoding.UTF8.GetString(creds.UserHandle);
                await SetClaimsPrincipalAsync(userHandleStr);

                // End customization - Krishna

                // 8. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse authenticatorAssertionRawResponse)
        {
            try
            {
                var jsonOptions = await _distributedCache.GetStringAsync(UniqueId);

                if (string.IsNullOrEmpty(jsonOptions))
                {
                    throw new Exception("Cant get Credential options from cache.");
                }
                var assertionOptions     = AssertionOptions.FromJson(jsonOptions);
                var fidoStoredCredential = await _fido2Service.GetFido2StoredCredentialsByCredentialIdAsync(authenticatorAssertionRawResponse.Id);

                if (fidoStoredCredential == null)
                {
                    throw new Exception("Unkown credentials.");
                }

                IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdAsync = async(args) =>
                {
                    var storedCreds = await _fido2Service.GetFido2StoredCredentialsByUserHandleAsync(args.UserHandle);

                    var storedCredExsist = storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId));
                    return(storedCredExsist);
                };

                var response = await _fido2.MakeAssertionAsync(authenticatorAssertionRawResponse, assertionOptions, fidoStoredCredential.PublicKey, fidoStoredCredential.SignatureCounter, isUserHandleOwnerOfCredentialIdAsync);

                if (response.Status != "ok")
                {
                    throw new Exception("Failed to assertion.");
                }

                await _fido2Service.UpdateFido2StoredCredentialCounter(response.CredentialId, response.Counter);

                var identityUser = _applicationDbContext.Users.Where(c => c.UserName == fidoStoredCredential.UserName).FirstOrDefault();
                if (identityUser == null)
                {
                    throw new Exception($"Unable to load two factor authentication user.");
                }
                await _signInManager.SignInAsync(identityUser, true);

                return(Json(response));
            }
            catch (Exception exception)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = CommonFunctions.FormatException(exception)
                }));
            }
        }
Example #10
0
        public async Task <bool> ValidateAsync(string purpose, string token, UserManager <User> manager, User user)
        {
            var userService = _serviceProvider.GetRequiredService <IUserService>();

            if (!(await userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token))
            {
                return(false);
            }

            var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
            var keys     = LoadKeys(provider);

            if (!provider.MetaData.ContainsKey("login"))
            {
                return(false);
            }

            var clientResponse = JsonSerializer.Deserialize <AuthenticatorAssertionRawResponse>(token,
                                                                                                new JsonSerializerOptions {
                PropertyNameCaseInsensitive = true
            });

            var jsonOptions = provider.MetaData["login"].ToString();
            var options     = AssertionOptions.FromJson(jsonOptions);

            var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));

            if (webAuthCred == null)
            {
                return(false);
            }

            IsUserHandleOwnerOfCredentialIdAsync callback = (args) => Task.FromResult(true);

            var res = await _fido2.MakeAssertionAsync(clientResponse, options, webAuthCred.Item2.PublicKey, webAuthCred.Item2.SignatureCounter, callback);

            provider.MetaData.Remove("login");

            // Update SignatureCounter
            webAuthCred.Item2.SignatureCounter = res.Counter;

            var providers = user.GetTwoFactorProviders();

            providers[TwoFactorProviderType.WebAuthn].MetaData[webAuthCred.Item1] = webAuthCred.Item2;
            user.SetTwoFactorProviders(providers);
            await userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn, logEvent : false);

            return(res.Status == "ok");
        }
Example #11
0
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = PasswordlessStore.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await PasswordlessStore.GetCredentialsByUserHandleAsync(args.UserHandle);

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter,
                                                          callback);

                // 6. Store the updated counter
                PasswordlessStore.UpdateCounter(res.CredentialId, res.Counter);

                if (res.Status == "ok")
                {
                    var username = System.Text.Encoding.UTF8.GetString(creds.UserId);
                    await SignInOidc(username);
                }
                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
        public async Task <IActionResult> MakeAssertion(AuthenticatorAssertionRawResponse clientResponse)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var credentialIdString = Base64Url.Encode(clientResponse.Id);
                var cred = await context.StoredCredentials.Where(x => x.DescriptorJson.Contains(credentialIdString)).FirstOrDefaultAsync();

                if (cred == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = cred.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await context.StoredCredentials.Where(x => x.UserHandle == args.UserHandle).ToListAsync();

                    return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, cred.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                cred.SignatureCounter = res.Counter;
                await context.SaveChangesAsync();

                // 7. return OK to client
                return(Ok(res));
            }
            catch (Exception e)
            {
                return(BadRequest(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
Example #13
0
        /// <summary>
        /// Verifies the assertion response from the browser/authr to assert existing credentials and authenticate a user.
        /// </summary>
        /// <returns></returns>
        public async Task <AssertionVerificationResult> MakeAssertionAsync(
            AuthenticatorAssertionRawResponse assertionResponse,
            AssertionOptions originalOptions,
            byte[] storedPublicKey,
            uint storedSignatureCounter,
            IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredentialIdCallback,
            byte[] requestTokenBindingId = null)
        {
            var parsedResponse = AuthenticatorAssertionResponse.Parse(assertionResponse);

            var result = await parsedResponse.VerifyAsync(originalOptions,
                                                          _config.Origin,
                                                          storedPublicKey,
                                                          storedSignatureCounter,
                                                          isUserHandleOwnerOfCredentialIdCallback,
                                                          requestTokenBindingId);

            return(result);
        }
Example #14
0
        /// <summary>
        /// 断言:验证断言响应
        /// <para>当客户端返回响应时,我们对其进行验证并接受登录。</para>
        /// </summary>
        public async Task <AssertionVerificationResult> MakeAssertionAsync(User user, AuthenticatorAssertionRawResponse clientRespons)
        {
            if (user == null)
            {
                return(new AssertionVerificationResult()
                {
                    Status = "bad"
                });
            }
            // 1. Get the assertion options we sent the client
            var jsonOptions = distributedCache.GetString(user.UserId.ToString() + "assertionOptions");

            if (string.IsNullOrEmpty(jsonOptions))
            {
                return(new AssertionVerificationResult()
                {
                    Status = "fail",
                    ErrorMessage = "time out"
                });
            }
            var options = AssertionOptions.FromJson(jsonOptions);
            // 2. Get registered credential from database
            var storedCredential = this.GetFIDO2ItemByUserId(user.UserId);
            // 3. Get credential counter from database

            var creds = user.FIDO2Items.Where(b => b.CredentialId.SequenceEqual(clientRespons.Id)).FirstOrDefault();

            var storedCounter = creds.SignatureCounter;
            // 4. Create callback to check if userhandle owns the credentialId

            IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
            {
                return(storedCredential.Exists(c => c.CredentialId.SequenceEqual(args.CredentialId)));
            };
            // 5. Make the assertion
            var res = await _fido2.MakeAssertionAsync(clientRespons, options, creds.PublicKey, storedCounter, callback);

            // 6. Store the updated counter
            creds.SignatureCounter = res.Counter;
            dataContext.SaveChanges();
            return(res);
        }
        public async Task <JsonResult> MakeAssertion([FromBody] AuthenticatorAssertionRawResponse clientResponse, [FromQuery] AssertionOptions options)
        {
            try
            {
                // 2. Get registered credential from database
                var creds = await _auth.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    throw new Exception("Unknown credentials");
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = await _auth.GetCredentialsForUser(BitConverter.ToInt32(args.UserHandle));

                    return(storedCreds.Exists(c => c.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _fido2.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                // 6. Store the updated counter
                await _auth.UpdateCredential(BitConverter.ToInt32(res.CredentialId), res.Counter);

                // 7. return OK to client
                return(Json(res));
            }
            catch (Exception e)
            {
                return(Json(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = e.Message
                }));
            }
        }
        /// <summary>
        /// Implements alghoritm from https://www.w3.org/TR/webauthn/#verifying-assertion
        /// </summary>
        /// <param name="options">The assertionoptions that was sent to the client</param>
        /// <param name="expectedOrigin">The expected server origin, used to verify that the signature is sent to the expected server</param>
        /// <param name="storedSignatureCounter">The stored counter value for this CredentialId</param>
        public async Task <AssertionVerificationResult> VerifyAsync(AssertionOptions options, string expectedOrigin, byte[] storedPublicKey, uint storedSignatureCounter, IsUserHandleOwnerOfCredentialIdAsync isUserHandleOwnerOfCredId, byte[] requestTokenBindingId)
        {
            BaseVerify(expectedOrigin, options.Challenge, requestTokenBindingId);

            if (Raw.Type != PublicKeyCredentialType.PublicKey)
            {
                throw new Fido2VerificationException("AssertionResponse Type is not set to public-key");
            }

            if (Raw.Id == null)
            {
                throw new Fido2VerificationException("Id is missing");
            }
            if (Raw.RawId == null)
            {
                throw new Fido2VerificationException("RawId is missing");
            }

            // 1. If the allowCredentials option was given when this authentication ceremony was initiated, verify that credential.id identifies one of the public key credentials that were listed in allowCredentials.
            if (options.AllowCredentials != null && options.AllowCredentials.Count() > 0)
            {
                // might need to transform x.Id and raw.id as described in https://www.w3.org/TR/webauthn/#publickeycredential
                if (!options.AllowCredentials.Any(x => x.Id.SequenceEqual(Raw.Id)))
                {
                    throw new Fido2VerificationException("Invalid");
                }
            }

            // 2. If credential.response.userHandle is present, verify that the user identified by this value is the owner of the public key credential identified by credential.id.
            if (UserHandle != null)
            {
                if (UserHandle.Length == 0)
                {
                    throw new Fido2VerificationException("Userhandle was empty DOMString. It should either be null or have a value.");
                }
                if (false == await isUserHandleOwnerOfCredId(new IsUserHandleOwnerOfCredentialIdParams(Raw.Id, UserHandle)))
                {
                    throw new Fido2VerificationException("User is not owner of the public key identitief by the credential id");
                }
            }

            // 3. Using credential’s id attribute(or the corresponding rawId, if base64url encoding is inappropriate for your use case), look up the corresponding credential public key.
            // public key inserted via parameter.

            // 4. Let cData, authData and sig denote the value of credential’s response's clientDataJSON, authenticatorData, and signature respectively.
            //var cData = Raw.Response.ClientDataJson;
            var authData = new AuthenticatorData(Raw.Response.AuthenticatorData);

            //var sig = Raw.Response.Signature;

            // 5. Let JSONtext be the result of running UTF-8 decode on the value of cData.
            //var JSONtext = Encoding.UTF8.GetBytes(cData.ToString());


            // 7. Verify that the value of C.type is the string webauthn.get.
            if (Type != "webauthn.get")
            {
                throw new Fido2VerificationException();
            }

            // 8. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the PublicKeyCredentialRequestOptions passed to the get() call.
            // 9. Verify that the value of C.origin matches the Relying Party's origin.
            // done in base class

            //10. Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over which the attestation 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.
            // Validated in BaseVerify.
            // todo: Needs testing

            // 11. Verify that the rpIdHash in aData is the SHA - 256 hash of the RP ID expected by the Relying Party.

            byte[] hashedClientDataJson;
            byte[] hashedRpId;
            using (var sha = SHA256.Create())
            {
                // 11
                hashedRpId = sha.ComputeHash(Encoding.UTF8.GetBytes(options.RpId));
                // 15
                hashedClientDataJson = sha.ComputeHash(Raw.Response.ClientDataJson);
            }

            if (false == authData.RpIdHash.SequenceEqual(hashedRpId))
            {
                throw new Fido2VerificationException("Hash mismatch RPID");
            }
            // 12. Verify that the User Present bit of the flags in authData is set.
            // UNLESS...userVerification is set to preferred or discouraged?
            // See Server-ServerAuthenticatorAssertionResponse-Resp3 Test server processing authenticatorData
            // P-5 Send a valid ServerAuthenticatorAssertionResponse both authenticatorData.flags.UV and authenticatorData.flags.UP are not set, for userVerification set to "preferred", and check that server succeeds
            // P-8 Send a valid ServerAuthenticatorAssertionResponse both authenticatorData.flags.UV and authenticatorData.flags.UP are not set, for userVerification set to "discouraged", and check that server succeeds
            //if ((false == authData.UserPresent) && (options.UserVerification != UserVerificationRequirement.Discouraged && options.UserVerification != UserVerificationRequirement.Preferred)) throw new Fido2VerificationException("User Present flag not set in authenticator data");

            // 13 If user verification is required for this assertion, verify that the User Verified bit of the flags in aData is set.
            // UNLESS...userPresent is true?
            // see ee Server-ServerAuthenticatorAssertionResponse-Resp3 Test server processing authenticatorData
            // P-8 Send a valid ServerAuthenticatorAssertionResponse both authenticatorData.flags.UV and authenticatorData.flags.UP are not set, for userVerification set to "discouraged", and check that server succeeds
            if (UserVerificationRequirement.Required == options.UserVerification && false == authData.UserVerified && false == authData.UserPresent)
            {
                throw new Fido2VerificationException("User verification is required");
            }

            // 14. 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 get() 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: Verify this (and implement extensions on options)
            if ((true == authData.ExtensionsPresent) && ((null == authData.Extensions) || (0 == authData.Extensions.Length)))
            {
                throw new Fido2VerificationException("Extensions flag present, malformed extensions detected");
            }
            if ((false == authData.ExtensionsPresent) && (null != authData.Extensions))
            {
                throw new Fido2VerificationException("Extensions flag not present, but extensions detected");
            }

            // 15.
            // Done earlier, hashedClientDataJson

            // 16. Using the credential public key looked up in step 3, verify that sig is a valid signature over the binary concatenation of aData and hash.
            byte[] data = new byte[Raw.Response.AuthenticatorData.Length + hashedClientDataJson.Length];
            Buffer.BlockCopy(Raw.Response.AuthenticatorData, 0, data, 0, Raw.Response.AuthenticatorData.Length);
            Buffer.BlockCopy(hashedClientDataJson, 0, data, Raw.Response.AuthenticatorData.Length, hashedClientDataJson.Length);

            if (null == storedPublicKey || 0 == storedPublicKey.Length)
            {
                throw new Fido2VerificationException("Stored public key is null or empty");
            }
            var coseKey = PeterO.Cbor.CBORObject.DecodeFromBytes(storedPublicKey);

            if (true != CryptoUtils.VerifySigWithCoseKey(data, coseKey, Signature))
            {
                throw new Fido2VerificationException("Signature did not match");
            }

            // 17.
            var counter = BitConverter.ToUInt32(authData.SignCount.ToArray().Reverse().ToArray(), 0);

            if (counter > 0 && counter <= storedSignatureCounter)
            {
                throw new Fido2VerificationException("SignatureCounter was not greater than stored SignatureCounter");
            }

            return(new AssertionVerificationResult()
            {
                Status = "ok",
                ErrorMessage = string.Empty,
                CredentialId = Raw.Id,
                Counter = counter
            });
        }
Example #17
0
        internal async void MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256)
        {
            const string rp = "fido2.azurewebsites.net";

            byte[]       rpId         = Encoding.UTF8.GetBytes(rp);
            var          rpIdHash     = SHA256.Create().ComputeHash(rpId);
            var          flags        = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV;
            const ushort signCount    = 0xf1d0;
            var          aaguid       = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0");
            var          credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, };

            CredentialPublicKey cpk = null;
            ECDsa ecdsa             = null;
            RSA   rsa = null;

            byte[] expandedPrivateKey = null;
            switch (kty)
            {
            case COSE.KeyType.EC2:
            {
                ecdsa = MakeECDsa(alg, crv);
                var ecparams = ecdsa.ExportParameters(true);
                cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y);
                break;
            }

            case COSE.KeyType.RSA:
            {
                rsa = RSA.Create();
                var rsaparams = rsa.ExportParameters(true);
                cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent);
                break;
            }

            case COSE.KeyType.OKP:
            {
                MakeEdDSA(out var privateKeySeed, out var publicKey, out expandedPrivateKey);
                cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey);
                break;
            }
                throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}");
            }

            var acd      = new AttestedCredentialData(aaguid, credentialID, cpk);
            var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes();
            var exts     = new Extensions(extBytes);

            var ad       = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts);
            var authData = ad.ToByteArray();

            var challenge = new byte[128];
            var rng       = RandomNumberGenerator.Create();

            rng.GetBytes(challenge);


            var clientData = new
            {
                Type      = "webauthn.get",
                Challenge = challenge,
                Origin    = rp,
            };
            var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData));

            var sha = SHA256.Create();
            var hashedClientDataJson = sha.ComputeHash(clientDataJson);

            byte[] data = new byte[authData.Length + hashedClientDataJson.Length];
            Buffer.BlockCopy(authData, 0, data, 0, authData.Length);
            Buffer.BlockCopy(hashedClientDataJson, 0, data, authData.Length, hashedClientDataJson.Length);
            byte[] signature = null;
            switch (kty)
            {
            case COSE.KeyType.EC2:
            {
                signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]);
                break;
            }

            case COSE.KeyType.RSA:
            {
                RSASignaturePadding padding;
                switch (alg)         // https://www.iana.org/assignments/cose/cose.xhtml#algorithms
                {
                case COSE.Algorithm.PS256:
                case COSE.Algorithm.PS384:
                case COSE.Algorithm.PS512:
                    padding = RSASignaturePadding.Pss;
                    break;

                case COSE.Algorithm.RS1:
                case COSE.Algorithm.RS256:
                case COSE.Algorithm.RS384:
                case COSE.Algorithm.RS512:
                    padding = RSASignaturePadding.Pkcs1;
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(alg), $"Missing or unknown alg {alg}");
                }
                signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding);
                break;
            }

            case COSE.KeyType.OKP:
            {
                signature = Ed25519.Sign(data, expandedPrivateKey);
                break;
            }

            default:
                throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}");
            }

            if (kty == COSE.KeyType.EC2)
            {
                signature = EcDsaSigFromSig(signature, ecdsa.KeySize);
            }

            var userHandle = new byte[16];

            rng.GetBytes(userHandle);

            var assertion = new AuthenticatorAssertionRawResponse.AssertionResponse()
            {
                AuthenticatorData = authData,
                Signature         = signature,
                ClientDataJson    = clientDataJson,
                UserHandle        = userHandle,
            };

            var lib = new Fido2(new Fido2Configuration()
            {
                ServerDomain = rp,
                ServerName   = rp,
                Origin       = rp,
            });
            var existingCredentials = new List <PublicKeyCredentialDescriptor>();
            var cred = new PublicKeyCredentialDescriptor
            {
                Type = PublicKeyCredentialType.PublicKey,
                Id   = new byte[] { 0xf1, 0xd0 }
            };

            existingCredentials.Add(cred);

            var options = lib.GetAssertionOptions(existingCredentials, null, null);

            options.Challenge = challenge;
            var response = new AuthenticatorAssertionRawResponse()
            {
                Response = assertion,
                Type     = PublicKeyCredentialType.PublicKey,
                Id       = new byte[] { 0xf1, 0xd0 },
                RawId    = new byte[] { 0xf1, 0xd0 },
            };
            IsUserHandleOwnerOfCredentialIdAsync callback = (args) =>
            {
                return(Task.FromResult(true));
            };
            var res = await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount - 1, callback);
        }
Example #18
0
        public async Task <ActionResult> MakeAssertion([ModelBinder(typeof(NewtonsoftJsonAdapter.Binder))] MakeAssertionRequest request)
        {
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = TestUsers.FidoAttestationOptions[request.SessionId];
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = TestUsers.FidoCredentials.Where(c => c.Descriptor.Id.SequenceEqual(request.RawResponse.Id)).FirstOrDefault();
                if (creds == null)
                {
                    return(BadRequest("unknown credentials"));
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    var storedCreds = TestUsers.FidoCredentials.Where(c => c.UserHandle.SequenceEqual(args.UserHandle));
                    return(storedCreds.Any(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                };

                // 5. Make the assertion
                var res = await _lib.MakeAssertionAsync(
                    assertionResponse : request.RawResponse,
                    originalOptions : options,
                    storedPublicKey : creds.PublicKey,
                    storedSignatureCounter : storedCounter,
                    isUserHandleOwnerOfCredentialIdCallback : callback);

                // 6. Store the updated counter
                TestUsers.FidoCredentials.Where(c => c.Descriptor.Id.SequenceEqual(res.CredentialId)).FirstOrDefault().SignatureCounter = res.Counter;

                // 7. return OK to client
                if (string.Equals(res.Status, "ok", StringComparison.InvariantCultureIgnoreCase))
                {
                    //actually loging the user in
                    var context = await _interaction.GetAuthorizationContextAsync(request.ReturnUrl);

                    var dbUser = TestUsers.Users.FirstOrDefault(u => string.Equals(u.SubjectId, creds.SubjectId, StringComparison.InvariantCultureIgnoreCase));
                    AuthenticationProperties props = null;
                    if (AccountOptions.AllowRememberLogin && request.RememberLogin)
                    {
                        props = new AuthenticationProperties
                        {
                            IsPersistent = true,
                            ExpiresUtc   = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
                        };
                    }
                    ;
                    var isuser = new IdentityServerUser(dbUser.SubjectId)
                    {
                        DisplayName = dbUser.Username
                    };
                    await HttpContext.SignInAsync(isuser, props);

                    if (context != null)
                    {
                        // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
                        return(this.NewtonsoftJsonResult(new MakeAssertionResponse {
                            Status = "ok", Response = res, ReturnUrl = request.ReturnUrl
                        }));
                    }

                    // request for a local page
                    if (Url.IsLocalUrl(request.ReturnUrl))
                    {
                        return(this.NewtonsoftJsonResult(new MakeAssertionResponse {
                            Status = "ok", Response = res, ReturnUrl = request.ReturnUrl
                        }));
                    }
                    else if (string.IsNullOrEmpty(request.ReturnUrl))
                    {
                        return(this.NewtonsoftJsonResult(new MakeAssertionResponse {
                            Status = "ok", Response = res, ReturnUrl = "~/"
                        }));
                    }
                    else
                    {
                        // user might have clicked on a malicious link - should be logged
                        throw new Exception("invalid return URL");
                    }
                }
                else
                {
                    return(BadRequest("not ok"));
                }
            }
            catch (Exception e)
            {
                return(BadRequest(new AssertionVerificationResult {
                    Status = "error", ErrorMessage = FormatException(e)
                }));
            }
        }
        /// <summary>
        /// SetLoginAssertionResult method implementation
        /// </summary>
        private int SetLoginAssertionResult(AuthenticationContext ctx, string jsonResponse, out string error)
        {
            bool isDeserialized = false;

            try
            {
                string jsonOptions = ctx.AssertionOptions;
                if (string.IsNullOrEmpty(jsonOptions))
                {
                    throw new ArgumentNullException(jsonOptions);
                }
                if (string.IsNullOrEmpty(jsonResponse))
                {
                    throw new ArgumentNullException(jsonResponse);
                }

                MFAWebAuthNUser user = RuntimeRepository.GetUser(Config, ctx.UPN);
                if (user != null)
                {
                    AssertionOptions options = AssertionOptions.FromJson(jsonOptions);
                    try
                    {
                        AuthenticatorAssertionRawResponse clientResponse = JsonConvert.DeserializeObject <AuthenticatorAssertionRawResponse>(jsonResponse);
                        isDeserialized = true;

                        MFAUserCredential creds = RuntimeRepository.GetCredentialById(Config, user, clientResponse.Id);
                        if (creds == null)
                        {
                            throw new Exception("Unknown credentials");
                        }

                        AuthenticatorData authData = new AuthenticatorData(clientResponse.Response.AuthenticatorData);
                        bool isnocount             = Utilities.IsNoCounterDevice(this.Config.WebAuthNProvider.Configuration, ctx);
                        uint authCounter           = 0;
                        uint storedCounter         = 0;

                        if (!isnocount)
                        {
                            authCounter   = authData.SignCount;
                            storedCounter = creds.SignatureCounter;
                        }
                        else
                        if ((authCounter > 0) && (authCounter <= storedCounter))
                        {
                            ResourcesLocale Resources = new ResourcesLocale(ctx.Lcid);
                            throw new Exception(Resources.GetString(ResourcesLocaleKind.FIDOHtml, "BIOERRORAUTHREPLAY"));
                        }

#pragma warning disable CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone
                        IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
#pragma warning restore CS1998 // Cette méthode async n'a pas d'opérateur 'await' et elle s'exécutera de façon synchrone
                        {
                            var storedCreds = RuntimeRepository.GetCredentialsByUserHandle(Config, user, args.UserHandle);
                            return(storedCreds.Exists(c => c.Descriptor.Id.SequenceEqual(args.CredentialId)));
                        };

                        // Apple counter always 0
                        AssertionVerificationResult res = _webathn.SetAssertionResult(clientResponse, options, creds.PublicKey, storedCounter, callback).Result;
                        if (!isnocount)
                        {
                            RuntimeRepository.UpdateCounter(Config, user, res.CredentialId, res.Counter);
                        }
                        else
                        {
                            RuntimeRepository.UpdateCounter(Config, user, res.CredentialId, 0);
                        }

                        if (!authData.UserPresent || !authData.UserVerified)
                        {
                            switch (creds.CredType)
                            {
                            case "none":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.None));
                                break;

                            case "android-key":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.AndroidKey));
                                break;

                            case "android-safetynet":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.AndroidSafetyNet));
                                break;

                            case "fido-u2f":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.Fido2U2f));
                                break;

                            case "packed":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.Packed));
                                break;

                            case "tpm":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.TPM));
                                break;

                            case "apple":
                                ctx.PinRequirements = (this.PinRequirements.HasFlag(WebAuthNPinRequirements.Apple));
                                break;

                            default:
                                ctx.PinRequirements = false;
                                break;
                            }
                        }
                        else
                        {
                            ctx.PinRequirements = false;
                        }
                        error = string.Empty;
                        return((int)AuthenticationResponseKind.Biometrics);
                    }
                    catch (Exception ex)
                    {
                        if (isDeserialized)
                        {
                            Log.WriteEntry(string.Format("{0}\r\n{1}", ctx.UPN, ex.Message), EventLogEntryType.Error, 5000);
                        }
                        else
                        {
                            Log.WriteEntry(string.Format("{0}\r\n{1}", ctx.UPN, jsonResponse), EventLogEntryType.Error, 5000);
                        }
                        error = ex.Message;
                        return((int)AuthenticationResponseKind.Error);
                    }
                }
                else
                {
                    Log.WriteEntry(string.Format("{0}\r\n{1}", ctx.UPN, "User does not exists !"), EventLogEntryType.Error, 5000);
                    error = string.Format("{0}\r\n{1}", ctx.UPN, "User does not exists !");
                    return((int)AuthenticationResponseKind.Error);
                }
            }
            catch (Exception e)
            {
                Log.WriteEntry(string.Format("{0}\r\n{1}", ctx.UPN, e.Message), EventLogEntryType.Error, 5000);
                error = e.Message;
                return((int)AuthenticationResponseKind.Error);
            }
        }
        internal static async Task <AssertionVerificationResult> MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256, CredentialPublicKey cpk = null, ushort signCount = 0, ECDsa ecdsa = null, RSA rsa = null, byte[] expandedPrivateKey = null)
        {
            const string rp = "https://www.passwordless.dev";

            byte[] rpId         = Encoding.UTF8.GetBytes(rp);
            var    rpIdHash     = SHA256.Create().ComputeHash(rpId);
            var    flags        = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV;
            var    aaguid       = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0");
            var    credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, };

            if (cpk == null)
            {
                switch (kty)
                {
                case COSE.KeyType.EC2:
                {
                    if (ecdsa == null)
                    {
                        ecdsa = MakeECDsa(alg, crv);
                    }
                    var ecparams = ecdsa.ExportParameters(true);
                    cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y);
                    break;
                }

                case COSE.KeyType.RSA:
                {
                    if (rsa == null)
                    {
                        rsa = RSA.Create();
                    }
                    var rsaparams = rsa.ExportParameters(true);
                    cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent);
                    break;
                }

                case COSE.KeyType.OKP:
                {
                    byte[] publicKey = null;
                    if (expandedPrivateKey == null)
                    {
                        MakeEdDSA(out var privateKeySeed, out publicKey, out expandedPrivateKey);
                    }

                    cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey);
                    break;
                }
                    throw new ArgumentOutOfRangeException(nameof(kty), $"Missing or unknown kty {kty}");
                }
            }
            var acd      = new AttestedCredentialData(aaguid, credentialID, cpk);
            var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes();
            var exts     = new Extensions(extBytes);

            var ad       = new AuthenticatorData(rpIdHash, flags, (uint)(signCount + 1), acd, exts);
            var authData = ad.ToByteArray();

            var challenge = new byte[128];
            var rng       = RandomNumberGenerator.Create();

            rng.GetBytes(challenge);

            var clientData = new
            {
                Type      = "webauthn.get",
                Challenge = challenge,
                Origin    = rp,
            };
            var clientDataJson = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData));

            var sha = SHA256.Create();
            var hashedClientDataJson = sha.ComputeHash(clientDataJson);

            byte[] data = new byte[authData.Length + hashedClientDataJson.Length];
            Buffer.BlockCopy(authData, 0, data, 0, authData.Length);
            Buffer.BlockCopy(hashedClientDataJson, 0, data, authData.Length, hashedClientDataJson.Length);
            byte[] signature = SignData(kty, alg, data, ecdsa, rsa, expandedPrivateKey);

            var userHandle = new byte[16];

            rng.GetBytes(userHandle);

            var assertion = new AuthenticatorAssertionRawResponse.AssertionResponse()
            {
                AuthenticatorData = authData,
                Signature         = signature,
                ClientDataJson    = clientDataJson,
                UserHandle        = userHandle,
            };

            var lib = new Fido2(new Fido2Configuration()
            {
                ServerDomain = rp,
                ServerName   = rp,
                Origin       = rp,
            });
            var existingCredentials = new List <PublicKeyCredentialDescriptor>();
            var cred = new PublicKeyCredentialDescriptor
            {
                Type = PublicKeyCredentialType.PublicKey,
                Id   = new byte[] { 0xf1, 0xd0 }
            };

            existingCredentials.Add(cred);

            var options = lib.GetAssertionOptions(existingCredentials, null, null);

            options.Challenge = challenge;
            var response = new AuthenticatorAssertionRawResponse()
            {
                Response = assertion,
                Type     = PublicKeyCredentialType.PublicKey,
                Id       = new byte[] { 0xf1, 0xd0 },
                RawId    = new byte[] { 0xf1, 0xd0 },
            };
            IsUserHandleOwnerOfCredentialIdAsync callback = (args) =>
            {
                return(Task.FromResult(true));
            };

            return(await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount, callback));
        }
Example #21
0
        public async Task <IActionResult> AssertDigitalSignatureResult([FromBody] AuthenticatorAssertionRawResponse clientResponse)
        {
            var sub = HttpContext.User.Claims.FirstOrDefault(x => x.Type == "sub")?.Value;

            if (string.IsNullOrEmpty(sub))
            {
                return(Json(new { success = false }));
            }
            var user = await _users.FindByIdAsync(sub);

            if (user == null)
            {
                return(Json(new { success = false }));
            }
            try
            {
                // 1. Get the assertion options we sent the client
                var jsonOptions = HttpContext.Session.GetString("fido2.assertionOptions");
                var options     = AssertionOptions.FromJson(jsonOptions);

                // 2. Get registered credential from database
                var creds = _authContext.FidoLogins.FirstOrDefault(x => x.PublicKeyIdBytes.SequenceEqual(clientResponse.Id) && x.UserId == user.Id);
                //DemoStorage.GetCredentialById(clientResponse.Id);

                if (creds == null)
                {
                    return(Json(new { success = false }));
                }

                // 3. Get credential counter from database
                var storedCounter = creds.SignatureCounter;

                // 4. Create callback to check if userhandle owns the credentialId
                IsUserHandleOwnerOfCredentialIdAsync callback = async(args) =>
                {
                    return(_authContext.FidoLogins.FirstOrDefault(x => x.UserHandle.SequenceEqual(args.UserHandle) && x.PublicKeyIdBytes.SequenceEqual(args.CredentialId)) != null);
                };

                // 5. Make the assertion
                var res = await _lib.MakeAssertionAsync(clientResponse, options, creds.PublicKey, storedCounter, callback);

                if (!string.IsNullOrEmpty(res.ErrorMessage))
                {
                    return(Json(new { success = false, error = res.ErrorMessage }));
                }

                var paymentId = HttpContext.Session.GetString("fido2.paymentId");
                var payment   = _authContext.Payments.First(x => x.Id == paymentId);
                var signature = Fido2NetLib.Base64Url.Encode(clientResponse.Response.Signature);

                var paymentAuthorization = new PaymentAuthorization()
                {
                    Payment               = payment,
                    PublicKeyId           = creds.PublicKeyId,
                    Signature             = signature,
                    Type                  = (int)DeviceType.FIDO2,
                    AuthorizationDateTime = DateTime.Now,
                    ClientData            = Fido2NetLib.Base64Url.Encode(clientResponse.Response.ClientDataJson),
                    AuthenticatorData     = Fido2NetLib.Base64Url.Encode(clientResponse.Response.AuthenticatorData)
                };

                _authContext.PaymentAuthorizations.Add(paymentAuthorization);
                payment.Status = "authorized";

                // 6. Store the updated counter
                creds.SignatureCounter = res.Counter;
                _authContext.SaveChanges();

                // 7. return OK to client
                return(Json(new { signature, success = true }));
            }
            catch (Exception e)
            {
                return(Json(new { success = false }));
            }
        }