public static WebAuthnAssertion VerifyAttestationResult(Fido2Configuration fido2Config, string requestJSON, string resultJSON) { var fido2 = new Fido2(fido2Config); IsCredentialIdUniqueToUserAsyncDelegate callback = (IsCredentialIdUniqueToUserParams args) => { var id = args.CredentialId; // generated ID by authenticator(should be always unique for each authenticator, equivalent to client cert) var u = args.User; // user info, kind of useless as this can be changed by js at client side return(Task.FromResult(true)); }; var request = Newtonsoft.Json.JsonConvert.DeserializeObject <CredentialCreateOptions>(requestJSON); AuthenticatorAttestationRawResponse regResponse = Newtonsoft.Json.JsonConvert.DeserializeObject <AuthenticatorAttestationRawResponse>(resultJSON); var success = fido2.MakeNewCredentialAsync(regResponse, request, callback).Result; var clientDataJson = Newtonsoft.Json.JsonConvert.DeserializeObject <AuthenticatorResponse>(System.Text.UTF8Encoding.UTF8.GetString(regResponse.Response.ClientDataJson)); var challenge = System.Convert.ToBase64String(clientDataJson.Challenge); var credentialId = System.Convert.ToBase64String(success.Result.CredentialId); var publicKey = System.Convert.ToBase64String(success.Result.PublicKey); var signingCounter = success.Result.Counter; // designed for replay attact prevention but useless for a multiple node situation var user = success.Result.User; var aaguid = success.Result.Aaguid; return(new WebAuthnAssertion { verified = true, challenge = challenge, credentialId = credentialId, publicKey = publicKey, signingCounter = signingCounter, aaguid = aaguid, userHandle = request.User.Id }); }
public EnrollAuthenticatorDeviceCommand(string name, AuthenticatorAttestationRawResponse authenticatorAttestationRawResponse, CredentialCreateOptions credentialCreateOptions) { this.AuthenticatorAttestationRawResponse = authenticatorAttestationRawResponse; this.CredentialCreateOptions = credentialCreateOptions; this.Name = name; }
public async Task <JsonResult> MakeCredentialResultTest([FromBody] AuthenticatorAttestationRawResponse attestationResponse) { // 1. get the options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var users = await DemoStorage.GetUsersByCredentialIdAsync(args.CredentialId); if (users.Count > 0) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await _lib.MakeNewCredentialAsync(attestationResponse, options, callback); // 3. Store the credentials in db DemoStorage.AddCredentialToUser(options.User, new StoredCredential { Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter }); // 4. return "ok" to the client return(Json(success)); }
public void Constructor_GiveValidArguments_PropertiesAreSet() { var authenticatorAttestationRawResponse = new AuthenticatorAttestationRawResponse(); var credentialCreateOptions = new CredentialCreateOptions(); var command = new EnrollAuthenticatorDeviceCommand("name", authenticatorAttestationRawResponse, credentialCreateOptions); Assert.Equal("name", command.Name); Assert.Equal(authenticatorAttestationRawResponse, command.AuthenticatorAttestationRawResponse); Assert.Equal(credentialCreateOptions, command.CredentialCreateOptions); }
public async Task <JsonResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse, CancellationToken cancellationToken) { try { // 1. get the options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = static async(args, cancellationToken) =>
public async Task <CacheItem> ExecuteAsync(string fido2Payload, CacheItem relatedItem) { var request = OwnIdSerializer.Deserialize <Fido2RegisterRequest>(fido2Payload); if (string.IsNullOrWhiteSpace(request.AttestationObject)) { throw new CommandValidationException("Incorrect Fido2 register request: AttestationObject is missing"); } if (string.IsNullOrWhiteSpace(request.ClientDataJson)) { throw new CommandValidationException("Incorrect Fido2 register request: ClientDataJson is missing"); } var fido2Response = new AuthenticatorAttestationRawResponse { Id = _encodingService.Base64UrlDecode(NewUserId), RawId = _encodingService.Base64UrlDecode(NewUserId), Type = PublicKeyCredentialType.PublicKey, Response = new AuthenticatorAttestationRawResponse.ResponseData { AttestationObject = _encodingService.Base64UrlDecode(request.AttestationObject), ClientDataJson = _encodingService.Base64UrlDecode(request.ClientDataJson) } }; var options = new CredentialCreateOptions { Challenge = _encodingService.ASCIIDecode(relatedItem.Context), Rp = new PublicKeyCredentialRpEntity( _configuration.Fido2.RelyingPartyId, _configuration.Fido2.RelyingPartyName, null), User = new Fido2User { DisplayName = _configuration.Fido2.UserDisplayName, Name = _configuration.Fido2.UserName, Id = _encodingService.Base64UrlDecode(NewUserId) } }; var result = await _fido2.MakeNewCredentialAsync(fido2Response, options, args => Task.FromResult(true)); if (result == null) { throw new InternalLogicException("Cannot verify fido2 register request"); } var publicKey = _encodingService.Base64UrlEncode(result.Result.PublicKey); return(await ProcessFido2RegisterResponseAsync(relatedItem, publicKey, result.Result.Counter, _encodingService.Base64UrlEncode(result.Result.CredentialId))); }
public async Task <ActionResult <CredentialMakeResult> > MakeCredential(AuthenticatorAttestationRawResponse attestationResponse) { try { // 1. get the options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var credentialIdString = Base64Url.Encode(args.CredentialId); var cred = await context.StoredCredentials.Where(x => x.DescriptorJson.Contains(credentialIdString)).FirstOrDefaultAsync(); if (cred == null) { return(true); } var users = await context.Members.Where(x => x.UserId == cred.UserId).Select(u => new Fido2User { DisplayName = u.DisplayName, Name = u.UserName, Id = u.UserId }).ToListAsync(); return(users.Count == 0); }; // 2. Verify and make the credentials var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); context.StoredCredentials.Add(new StoredCredential { Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), UserId = options.User.Id, PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }); await context.SaveChangesAsync(); return(Ok(success)); } catch (Exception e) { return(BadRequest(new CredentialMakeResult { Status = "error", ErrorMessage = FormatException(e) })); } }
public async Task <IActionResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse) { var o = new { challenge = string.Empty, origin = string.Empty, type = string.Empty }; var username = string.Empty; try { o = JsonConvert.DeserializeAnonymousType((Encoding.UTF8.GetString(attestationResponse.Response.ClientDataJson)), o); var jsonOptions = _memoryCache.Get <string>(o.challenge); var options = CredentialCreateOptions.FromJson(jsonOptions); username = options.User.Name; async Task <bool> Callback(IsCredentialIdUniqueToUserParams args) { var users = await _dataStore.GetUsersByCredentialIdAsync(args.CredentialId); return(users.Count <= 0); } var success = await _lib.MakeNewCredentialAsync(attestationResponse, options, Callback); _dataStore.AddCredentialToUser(options.User, new StoredCredential { Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }); var ev = new Event(username, "Successfully logged the person in", nameof(RegistrationController), nameof(MakeCredential)); await _elasticClient.IndexAsync(ev, i => i.Index(GetIndexName(nameof(Ok)))); return(Ok(success)); } catch (Exception e) { var errorEvent = new ErrorEvent(e, username, nameof(RegistrationController), nameof(MakeCredential)); await _elasticClient.IndexAsync(errorEvent, i => i.Index(GetIndexName(nameof(Exception)))); return(Ok(new Fido2.CredentialMakeResult { Status = "error", ErrorMessage = FormatException(e) + $"ClientDataJson = {Encoding.UTF8.GetString(attestationResponse.Response.ClientDataJson)}" })); } }
public void TestAuthenticatorAttestationResponseNull() { var rawResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = null, }; var ex = Assert.Throws <Fido2VerificationException>(() => AuthenticatorAttestationResponse.Parse(rawResponse)); Assert.Equal("Expected rawResponse, got null", ex.Message); }
public void TestAuthenticatorAttestationReponseAttestationObjectNull(byte[] value) { var rawResponse = new AuthenticatorAttestationRawResponse { Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = value, } }; var ex = Assert.Throws <Fido2VerificationException>(() => AuthenticatorAttestationResponse.Parse(rawResponse)); Assert.Equal("Missing AttestationObject", ex.Message); }
public void TestAuthenticatorAttestationRawResponse() { var challenge = RandomGenerator.Default.GenerateBytes(128); var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { Type = "webauthn.create", Challenge = challenge, Origin = "https://www.passwordless.dev", }); var rawResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = new CborMap().Encode(), ClientDataJson = clientDataJson }, Extensions = new AuthenticationExtensionsClientOutputs() { AppID = true, AuthenticatorSelection = true, Extensions = new string[] { "foo", "bar" }, Example = "test", UserVerificationMethod = new ulong[][] { new ulong[] { 4 // USER_VERIFY_PASSCODE_INTERNAL }, }, } }; Assert.Equal(PublicKeyCredentialType.PublicKey, rawResponse.Type); Assert.True(rawResponse.Id.SequenceEqual(new byte[] { 0xf1, 0xd0 })); Assert.True(rawResponse.RawId.SequenceEqual(new byte[] { 0xf1, 0xd0 })); Assert.True(rawResponse.Response.AttestationObject.SequenceEqual(new byte[] { 0xa0 })); Assert.True(rawResponse.Response.ClientDataJson.SequenceEqual(clientDataJson)); Assert.True(rawResponse.Extensions.AppID); Assert.True(rawResponse.Extensions.AuthenticatorSelection); Assert.Equal(rawResponse.Extensions.Extensions, new string[] { "foo", "bar" }); Assert.Equal("test", rawResponse.Extensions.Example); Assert.Equal((ulong)4, rawResponse.Extensions.UserVerificationMethod[0][0]); }
public async Task <ActionResult> MakeCredential( [FromBody] AuthenticatorAttestationRawResponse attestationResponse) { try { // 1. get the options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var users = await fidoStore.GetUsersByCredentialIdAsync(args.CredentialId); if (users.Count > 0) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await fido2.MakeNewCredentialAsync(attestationResponse, options, callback); // 3. Store the credentials in db fidoStore.AddCredentialToUser(options.User, new StoredCredential { Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }); // 4. return "ok" to the client return(Ok(success)); } catch (Exception e) { return(BadRequest(new Fido2.CredentialMakeResult { Status = "error", ErrorMessage = FormatException(e) })); } }
public void TestAuthenticatorAttestationObjectBadCBOR(byte[] value) { var rawResponse = new AuthenticatorAttestationRawResponse { Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = value, } }; var ex = Assert.Throws <Fido2VerificationException>(() => AuthenticatorAttestationResponse.Parse(rawResponse)); Assert.Equal("AttestationObject invalid CBOR", ex.Message); var innerEx = (CborContentException)ex.InnerException; Assert.Equal("Declared definite length of CBOR data item exceeds available buffer size.", innerEx.Message); }
public async Task <JsonResult> SaveCredentials([FromBody] AuthenticatorAttestationRawResponse attestationResponse) { try { var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); var fidoCredentials = await fido2.MakeNewCredentialAsync(attestationResponse, options, IsCredentialUnique); var storedCredential = new StoredCredential { Descriptor = new PublicKeyCredentialDescriptor(fidoCredentials.Result.CredentialId), PublicKey = fidoCredentials.Result.PublicKey, UserHandle = fidoCredentials.Result.User.Id, SignatureCounter = fidoCredentials.Result.Counter, CredType = fidoCredentials.Result.CredType, RegDate = DateTime.Now, AaGuid = fidoCredentials.Result.Aaguid }; var names = options.User.DisplayName.Split(' '); var result = await oktaClient.Users.CreateUserAsync(new CreateUserWithoutCredentialsOptions { Profile = new UserProfile { Login = options.User.Name, Email = options.User.Name, DisplayName = options.User.DisplayName, FirstName = names[0], LastName = names[1], ["CredentialId"] = Convert.ToBase64String(fidoCredentials.Result.CredentialId), ["PasswordlessPublicKey"] = JsonConvert.SerializeObject(storedCredential) } }); return(Json(fidoCredentials)); } catch (Exception e) { return(Json(new Fido2.CredentialMakeResult { Status = "error", ErrorMessage = e.Message })); } }
public async Task <JsonResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse, [FromQuery] CredentialCreateOptions options) { var user = await GetCurrentLocalUserAsync(); try { // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var users = await _auth.GetUserIdsByCredentialIdAsync(args.CredentialId); if (users.Count > 0) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); // 3. Store the credentials in db await _auth.AddCredential(user, new StoredCredential { Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserId = BitConverter.GetBytes(user.Id), UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }); // 4. return "ok" to the client return(Json(success)); } catch (Exception e) { return(Json(new { Status = "error", ErrorMessage = e.Message })); } }
public async Task <bool> CompleteCreation(string userId, string name, AuthenticatorAttestationRawResponse attestationResponse) { await using var dbContext = _contextFactory.CreateContext(); var user = await dbContext.Users.Include(applicationUser => applicationUser.Fido2Credentials) .FirstOrDefaultAsync(applicationUser => applicationUser.Id == userId); if (user == null || !CreationStore.TryGetValue(userId, out var options)) { return(false); } // 2. Verify and make the credentials var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, args => Task.FromResult(true)); // 3. Store the credentials in db var newCredential = new Fido2Credential() { Name = name, ApplicationUserId = userId }; newCredential.SetBlob(new Fido2CredentialBlob() { Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, AaGuid = success.Result.Aaguid.ToString(), }); await dbContext.Fido2Credentials.AddAsync(newCredential); await dbContext.SaveChangesAsync(); CreationStore.Remove(userId, out _); return(true); }
public async Task <Result <Fido2.CredentialMakeResult> > MakeCredentialAsync(AuthenticatorAttestationRawResponse attestationResponse, int userId, long loginId, string deviceName) { try { // 1. get the options we sent the client var cacheKey = $"CredentialOptions:{loginId}"; if (!_cache.TryGetValue(cacheKey, out CredentialCreateOptions options)) { _logger.LogError($"Failed to find credential options for user {userId} login {loginId}"); return(Result.Failure <Fido2.CredentialMakeResult>("No challenge found")); } _cache.Remove(cacheKey); // 2. Verify and make the credentials var cmr = await _fido2.MakeNewCredentialAsync(attestationResponse, options, x => Task.FromResult(true)); // 3. Store the credentials in db _db.DeviceAuthorizations.Add(new DeviceAuthorization { UserId = userId, PublicKey = cmr.Result.PublicKey, CredentialId = cmr.Result.CredentialId, SignatureCounter = cmr.Result.Counter, DeviceName = deviceName }); await _db.SaveChangesAsync(); // 4. return "ok" to the client _logger.LogInformation($"Created credential for user {userId} login {loginId}"); return(Result.Success(cmr)); } catch (Exception e) { _logger.LogError(e, $"Failed to make credential for user {userId} login {loginId}"); return(Result.Failure <Fido2.CredentialMakeResult>("Error making credential")); } }
public async Task <JsonResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse) { try { // 1. get the options we sent the client //var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var jsonOptions = await _distributedCache.GetStringAsync(UniqueId); if (string.IsNullOrEmpty(jsonOptions)) { throw new Exception("Can't get CredentialOptions from cache"); } var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var users = await _fido2Storage.GetUsersByCredentialIdAsync(args.CredentialId); if (users.Count > 0) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await _lib.MakeNewCredentialAsync(attestationResponse, options, callback); // 3. Store the credentials in db await _fido2Storage.AddCredentialToUser(options.User, new FidoStoredCredential { Username = options.User.Name, Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }); // 4. return "ok" to the client var user = await _userManager.GetUserAsync(User); if (user == null) { return(Json(new CredentialMakeResult { Status = "error", ErrorMessage = $"Unable to load user with ID '{_userManager.GetUserId(User)}'." })); } await _userManager.SetTwoFactorEnabledAsync(user, true); if (await _userManager.CountRecoveryCodesAsync(user) == 0) { var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); RecoveryCodes = recoveryCodes.ToArray(); } return(Json(success)); } catch (Exception e) { return(Json(new CredentialMakeResult { Status = "error", ErrorMessage = FormatException(e) })); } }
public async Task <JsonResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse attestationResponse) { try { // 1. get the options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var users = await _fido2Storage.GetUsersByCredentialIdAsync(args.CredentialId); if (users.Count > 0) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await _lib.MakeNewCredentialAsync(attestationResponse, options, callback); // 3. Store the credentials in db await _fido2Storage.AddCredentialToUser(options.User, new FidoStoredCredential { Username = options.User.Name, Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }); // 4. return "ok" to the client var user = await _userManager.GetUserAsync(User); if (user == null) { return(Json(new CredentialMakeResult { Status = "error", ErrorMessage = _sharedLocalizer["FIDO2_USER_NOTFOUND", _userManager.GetUserId(User)] })); } await _userManager.SetTwoFactorEnabledAsync(user, true); var userId = await _userManager.GetUserIdAsync(user); return(Json(success)); } catch (Exception e) { return(Json(new CredentialMakeResult { Status = "error", ErrorMessage = FormatException(e) })); } }
public async Task TestAuthenticatorOrigins(string origin, string expectedOrigin) { var challenge = RandomGenerator.Default.GenerateBytes(128); var rp = origin; var acd = new AttestedCredentialData(("00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-40-FE-6A-32-63-BE-37-D1-01-B1-2E-57-CA-96-6C-00-22-93-E4-19-C8-CD-01-06-23-0B-C6-92-E8-CC-77-12-21-F1-DB-11-5D-41-0F-82-6B-DB-98-AC-64-2E-B1-AE-B5-A8-03-D1-DB-C1-47-EF-37-1C-FD-B1-CE-B0-48-CB-2C-A5-01-02-03-26-20-01-21-58-20-A6-D1-09-38-5A-C7-8E-5B-F0-3D-1C-2E-08-74-BE-6D-BB-A4-0B-4F-2A-5F-2F-11-82-45-65-65-53-4F-67-28-22-58-20-43-E1-08-2A-F3-13-5B-40-60-93-79-AC-47-42-58-AA-B3-97-B8-86-1D-E4-41-B4-4E-83-08-5D-1C-6B-E0-D0").Split('-').Select(c => Convert.ToByte(c, 16)).ToArray()); var authData = new AuthenticatorData( SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(origin)), AuthenticatorFlags.UP | AuthenticatorFlags.AT, 0, acd, null ).ToByteArray(); var clientDataJson = Encoding.UTF8.GetBytes( JsonConvert.SerializeObject ( new { Type = "webauthn.create", Challenge = challenge, Origin = rp, } ) ); var rawResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = CBORObject.NewMap().Add("fmt", "none").Add("attStmt", CBORObject.NewMap()).Add("authData", authData).EncodeToBytes(), ClientDataJson = clientDataJson }, }; var origChallenge = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection { AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, RequireResidentKey = true, UserVerification = UserVerificationRequirement.Required, }, Challenge = challenge, ErrorMessage = "", PubKeyCredParams = new List <PubKeyCredParam>() { new PubKeyCredParam { Alg = COSE.Algorithm.ES256, Type = PublicKeyCredentialType.PublicKey, } }, Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), Status = "ok", User = new Fido2User { Name = "testuser", Id = Encoding.UTF8.GetBytes("testuser"), DisplayName = "Test User", }, Timeout = 60000, }; IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => { return(Task.FromResult(true)); }; var lib = new Fido2(new Fido2Configuration() { ServerDomain = rp, ServerName = rp, Origin = expectedOrigin, }); var result = await lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback); }
public void TestAuthenticatorAttestationResponseNotUserPresent() { var challenge = RandomGenerator.Default.GenerateBytes(128); var rp = "https://www.passwordless.dev"; var authData = new AuthenticatorData( SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.UV, 0, null, null ).ToByteArray(); var clientDataJson = Encoding.UTF8.GetBytes( JsonConvert.SerializeObject ( new { Type = "webauthn.create", Challenge = challenge, Origin = rp, } ) ); var rawResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = CBORObject.NewMap().Add("fmt", "testing").Add("attStmt", CBORObject.NewMap()).Add("authData", authData).EncodeToBytes(), ClientDataJson = clientDataJson }, }; var origChallenge = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection { AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, RequireResidentKey = true, UserVerification = UserVerificationRequirement.Required, }, Challenge = challenge, ErrorMessage = "", PubKeyCredParams = new List <PubKeyCredParam>() { new PubKeyCredParam { Alg = COSE.Algorithm.ES256, Type = PublicKeyCredentialType.PublicKey, } }, Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), Status = "ok", User = new Fido2User { Name = "testuser", Id = Encoding.UTF8.GetBytes("testuser"), DisplayName = "Test User", }, Timeout = 60000, }; IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => { return(Task.FromResult(true)); }; var lib = new Fido2(new Fido2Configuration() { ServerDomain = rp, ServerName = rp, Origin = rp, }); var ex = Assert.ThrowsAsync <Fido2VerificationException>(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); Assert.Equal("User Present flag not set in authenticator data", ex.Result.Message); }
/// <summary> /// 注册:验证用户凭证 /// <para>当客户端返回响应时,我们验证并注册凭据。</para> /// </summary> /// <param name="attestationResponse"></param> public async Task <CredentialMakeResult> RegisterCredentials(User user, string fido2Name, AuthenticatorAttestationRawResponse attestationResponse) { // 1. get the options we sent the client var jsonOptions = distributedCache.GetString(user.UserId.ToString() + "attestationOptions"); if (string.IsNullOrEmpty(jsonOptions)) { return(null); } var options = CredentialCreateOptions.FromJson(jsonOptions); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { //var users = await DemoStorage.GetUsersByCredentialIdAsync(args.CredentialId); //if (users.Count > 0) // return false; var argUserId = args.CredentialId; if (this.dataContext.FIDO2Repository.Where(b => b.CredentialId.Equals(argUserId)).Any()) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); // 3. Store the credentials in db //var user = dataContext.User.Where(b => b.UserId == userId).FirstOrDefault(); if (user == null) { return(null); } var fido2 = new FIDO2Item() { Id = IdGenerator.NextId(), UserId = user.UserId, FIDO2Name = fido2Name, CredentialId = success.Result.CredentialId, PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }; dataContext.FIDO2Repository.Add(fido2); dataContext.SaveChanges(); return(success); }
public async Task <IActionResult> RegisterCallback([FromBody] AuthenticatorAttestationRawResponse model) { var sub = HttpContext.User.Claims.FirstOrDefault(x => x.Type == "sub")?.Value; if (string.IsNullOrEmpty(sub)) { return(RedirectToAction("Index", "Home")); } var user = await _users.FindByIdAsync(sub); if (user == null) { return(RedirectToAction("Index", "Home")); } try { // 1. get the options we sent the client var jsonOptions = HttpContext.Session.GetString("fido2.attestationOptions"); var options = CredentialCreateOptions.FromJson(jsonOptions); var authenticatorName = HttpContext.Session.GetString("fido2.attestationOptions.authenticatorType"); // 2. Create callback so that lib can verify credential id is unique to this user IsCredentialIdUniqueToUserAsyncDelegate callback = async(IsCredentialIdUniqueToUserParams args) => { var users = _authContext.FidoLogins.Where(l => l.PublicKeyIdBytes.SequenceEqual(args.CredentialId)); if (users.Count() > 0) { return(false); } return(true); }; // 2. Verify and make the credentials var success = await _lib.MakeNewCredentialAsync(model, options, callback); var parsedResponse = AuthenticatorAttestationResponse.Parse(model);; var authData = new AuthenticatorData(parsedResponse.AttestationObject.AuthData); var dbUser = _authContext.Users.First(x => x.Id == user.Id); dbUser.TwoFactorEnabled = true; var login = new FidoLogin() { PublicKeyIdBytes = success.Result.CredentialId, PublicKeyId = Fido2NetLib.Base64Url.Encode(success.Result.CredentialId), AaGuid = success.Result.Aaguid.ToString(), PublicKey = success.Result.PublicKey, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegistrationDate = DateTime.Now, User = dbUser, UserHandle = success.Result.User.Id, AuthenticatorName = authenticatorName }; _authContext.FidoLogins.Add(login); _authContext.SaveChanges(); // 4. return "ok" to the client return(Json(new { success = true })); } catch (Exception e) { return(Json(new { error = true })); } }
public async Task <Fido2.CredentialMakeResult> MakeAttestationResponse() { _attestationObject.Set("authData", _authData); var attestationResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = _attestationObject.EncodeToBytes(), ClientDataJson = _clientDataJson, }, Extensions = new AuthenticationExtensionsClientOutputs() { AppID = true, AuthenticatorSelection = true, BiometricAuthenticatorPerformanceBounds = true, GenericTransactionAuthorization = new byte[] { 0xf1, 0xd0 }, SimpleTransactionAuthorization = "test", Extensions = new string[] { "foo", "bar" }, Example = "test", Location = new GeoCoordinatePortable.GeoCoordinate(42.523714, -71.040860), UserVerificationIndex = new byte[] { 0xf1, 0xd0 }, UserVerificationMethod = new ulong[][] { new ulong[] { 4 // USER_VERIFY_PASSCODE_INTERNAL }, }, } }; var origChallenge = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection { AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, RequireResidentKey = true, UserVerification = UserVerificationRequirement.Required, }, Challenge = _challenge, ErrorMessage = "", PubKeyCredParams = new List <PubKeyCredParam>() { new PubKeyCredParam { Alg = COSE.Algorithm.ES256, Type = PublicKeyCredentialType.PublicKey, } }, Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), Status = "ok", User = new Fido2User { Name = "testuser", Id = Encoding.UTF8.GetBytes("testuser"), DisplayName = "Test User", }, Timeout = 60000, }; IsCredentialIdUniqueToUserAsyncDelegate callback = (args) => { return(Task.FromResult(true)); }; var lib = new Fido2(new Fido2Configuration() { ServerDomain = rp, ServerName = rp, Origin = rp, }); var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); return(credentialMakeResult); }
public async Task <bool> CompleteWebAuthRegistrationAsync(User user, int id, string name, AuthenticatorAttestationRawResponse attestationResponse) { var keyId = $"Key{id}"; var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn); if (!provider?.MetaData?.ContainsKey("pending") ?? true) { return(false); } var options = CredentialCreateOptions.FromJson((string)provider.MetaData["pending"]); // Callback to ensure credential id is unique. Always return true since we don't care if another // account uses the same 2fa key. IsCredentialIdUniqueToUserAsyncDelegate callback = args => Task.FromResult(true); var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); provider.MetaData.Remove("pending"); provider.MetaData[keyId] = new TwoFactorProvider.WebAuthnData { Name = name, Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), PublicKey = success.Result.PublicKey, UserHandle = success.Result.User.Id, SignatureCounter = success.Result.Counter, CredType = success.Result.CredType, RegDate = DateTime.Now, AaGuid = success.Result.Aaguid }; var providers = user.GetTwoFactorProviders(); providers[TwoFactorProviderType.WebAuthn] = provider; user.SetTwoFactorProviders(providers); await UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn); return(true); }
public async Task <JsonResult> MakeCredential([FromBody] AuthenticatorAttestationRawResponse authenticatorAttestationRawResponse) { try { var user = await _userManager.GetUserAsync(User); if (user == null) { throw new Exception("Unable to retrieve user."); } var jsonOptions = await _distributedCache.GetStringAsync(UniqueId); if (string.IsNullOrEmpty(jsonOptions)) { throw new Exception("Cant get Credential options from cache."); } var options = CredentialCreateOptions.FromJson(jsonOptions); IsCredentialIdUniqueToUserAsyncDelegate isCredentialIdUniqueToUserAsyncDelegate = async(IsCredentialIdUniqueToUserParams isCredentialIdUniqueToUserParams) => { var fido2Users = await _fido2Service.GetFido2UsersByCredentialIdAsync(isCredentialIdUniqueToUserParams.CredentialId); if (fido2Users.Count > 0) { return(false); } return(true); }; var result = await _fido2.MakeNewCredentialAsync(authenticatorAttestationRawResponse, options, isCredentialIdUniqueToUserAsyncDelegate); if (result.Status != "ok") { throw new Exception("Unable to create credential."); } var newFido2StoredCredential = new Fido2StoredCredential { }; newFido2StoredCredential.UserName = options.User.Name; newFido2StoredCredential.UserId = options.User.Id; newFido2StoredCredential.PublicKey = result.Result.PublicKey; newFido2StoredCredential.UserHandle = result.Result.User.Id; newFido2StoredCredential.SignatureCounter = result.Result.Counter; newFido2StoredCredential.CredType = result.Result.CredType; newFido2StoredCredential.RegDate = DateTime.Now; newFido2StoredCredential.AaGuid = Guid.NewGuid(); newFido2StoredCredential.Descriptor = new PublicKeyCredentialDescriptor(result.Result.CredentialId); _fido2Service.AddFido2StoredCredential(newFido2StoredCredential); return(Json(result)); } catch (Exception exception) { return(Json(new CredentialCreateOptions() { Status = "error", ErrorMessage = CommonFunctions.FormatException(exception) })); } }
public async Task TestApplePublicKeyMismatch() { var cpkBytes = new byte[] { 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x79, 0xfe, 0x59, 0x08, 0xbb, 0x51, 0x29, 0xc8, 0x09, 0x38, 0xb7, 0x54, 0xc0, 0x4d, 0x2b, 0x34, 0x0e, 0xfa, 0x66, 0x15, 0xb9, 0x87, 0x69, 0x8b, 0xf5, 0x9d, 0xa4, 0xe5, 0x3e, 0xa3, 0xe6, 0xfe, 0x22, 0x58, 0x20, 0xfb, 0x03, 0xda, 0xa1, 0x27, 0x0d, 0x58, 0x04, 0xe8, 0xab, 0x61, 0xc1, 0x5a, 0xac, 0xa2, 0x43, 0x5c, 0x7d, 0xbf, 0x36, 0x9d, 0x71, 0xca, 0x15, 0xc5, 0x23, 0xb0, 0x00, 0x4a, 0x1b, 0x75, 0xb7 }; _credentialPublicKey = new CredentialPublicKey(cpkBytes); var authData = new AuthenticatorData(_rpIdHash, _flags, _signCount, _acd, _exts).ToByteArray(); _attestationObject.Set("authData", new CborByteString(authData)); var clientData = new { type = "webauthn.create", challenge = _challenge, origin = "https://www.passwordless.dev", }; var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(clientData); var invalidX5cStrings = StackAllocSha256(authData, clientDataJson); var trustPath = invalidX5cStrings .Select(x => new X509Certificate2(Convert.FromBase64String(x))) .ToArray(); var X5c = new CborArray { { trustPath[0].RawData }, { trustPath[1].RawData } }; ((CborMap)_attestationObject["attStmt"]).Set("x5c", X5c); var attestationResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = _attestationObject.Encode(), ClientDataJson = clientDataJson, } }; var origChallenge = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection { AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, RequireResidentKey = true, UserVerification = UserVerificationRequirement.Discouraged, }, Challenge = _challenge, ErrorMessage = "", PubKeyCredParams = new List <PubKeyCredParam>() { new PubKeyCredParam(COSE.Algorithm.ES256) }, Rp = new PublicKeyCredentialRpEntity("https://www.passwordless.dev", "6cc3c9e7967a.ngrok.io", ""), Status = "ok", User = new Fido2User { Name = "testuser", Id = Encoding.UTF8.GetBytes("testuser"), DisplayName = "Test User", }, Timeout = 60000, }; IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => { return(Task.FromResult(true)); }; IFido2 lib = new Fido2(new Fido2Configuration() { ServerDomain = "6cc3c9e7967a.ngrok.io", ServerName = "6cc3c9e7967a.ngrok.io", Origins = new HashSet <string> { "https://www.passwordless.dev" }, }); var credentialMakeResult = await lib.MakeNewCredentialAsync(attestationResponse, origChallenge, callback); }
static void Main(string[] args) { Console.WriteLine($"webauthn.dll API version: {WebAuthnApi.GetApiVersionNumber()}"); WebAuthnApi.IsUserVerifyingPlatformAuthenticatorAvailable(out var vpa); Console.WriteLine($"Verifying Platform Authenticator available: {vpa}"); var config = new Fido2.Configuration { ChallengeSize = 32, Origin = "https://foo.net/bar", ServerDomain = "foo.net", ServerName = "Foo Server" }; var user1 = new User { Name = "Testuser1", Id = new byte[32] }; new Random().NextBytes(user1.Id); var fido2NetLib = new Fido2(config); var excl = new List <F2.PublicKeyCredentialDescriptor>(); var selection = new AuthenticatorSelection { AuthenticatorAttachment = F2.AuthenticatorAttachment.CrossPlatform, RequireResidentKey = false, UserVerification = F2.UserVerificationRequirement.Discouraged }; //generate valid Fido2 request with Fido2Net var f2req = fido2NetLib.RequestNewCredential(user1, excl, selection, F2.AttestationConveyancePreference.Direct); //translate objects into our own types var rp = new RelayingPartyInfo { Id = f2req.Rp.Id, Name = f2req.Rp.Name }; var user = new UserInfo { UserId = f2req.User.Id, Name = f2req.User.Name, DisplayName = f2req.User.DisplayName }; var json = $"{{\r\n\t\"type\" : \"webauthn.create\",\r\n\t\"challenge\" : \"{Convert.ToBase64String(f2req.Challenge)}\",\r\n\t\"origin\" : \"{config.Origin}\"\r\n}}"; var clientData = new ClientData { ClientDataJSON = Encoding.UTF8.GetBytes(json), HashAlgorithm = HashAlgorithm.Sha256 }; var coseParams = f2req.PubKeyCredParams.Select(p => new CoseCredentialParameter((CoseAlgorithm)p.Alg)).ToList(); var makeOptions = new AuthenticatorMakeCredentialOptions { TimeoutMilliseconds = (int)f2req.Timeout, UserVerificationRequirement = (UserVerificationRequirement)f2req.AuthenticatorSelection.UserVerification + 1, AuthenticatorAttachment = ((AuthenticatorAttachment?)f2req.AuthenticatorSelection.AuthenticatorAttachment + 1) ?? AuthenticatorAttachment.Any, RequireResidentKey = f2req.AuthenticatorSelection.RequireResidentKey, AttestationConveyancePreference = (AttestationConveyancePreference)f2req.Attestation + 1, ExcludeCredentialsEx = f2req.ExcludeCredentials .Select(ec => new CredentialEx(ec.Id, ec.Transports.Aggregate(CtapTransport.NoRestrictions, (acc, n) => acc | (CtapTransport)(1 << (int)n)))).ToList() }; //call WinAPI var windowHandle = Process.GetCurrentProcess().MainWindowHandle; var res = WebAuthnApi.AuthenticatorMakeCredential(windowHandle, rp, user, coseParams, clientData, makeOptions, out var credential); if (res != WebAuthnResult.Ok) { Console.WriteLine(WebAuthnApi.GetErrorName(res)); return; } //translate back and verify var resp = new AuthenticatorAttestationRawResponse { Id = credential.CredentialId, RawId = credential.CredentialId, Response = new AuthenticatorAttestationRawResponse.ResponseData { AttestationObject = credential.AttestationObject, ClientDataJson = Encoding.UTF8.GetBytes(json) }, Type = F2.PublicKeyCredentialType.PublicKey }; var makeRes = fido2NetLib.MakeNewCredentialAsync(resp, f2req, _ => Task.FromResult(true)).Result; Console.WriteLine($"Register Result: {makeRes.Result.Status}"); Console.WriteLine($"Attestation CN: {credential.CommonAttestation.Certificates.FirstOrDefault()?.Subject}"); for (; ;) { //--------------------- Assertion ---------------- var allowed = new List <F2.PublicKeyCredentialDescriptor> { new F2.PublicKeyCredentialDescriptor(credential.CredentialId) }; //generate valid Fido2 request with Fido2Net var f2reqA = fido2NetLib.GetAssertionOptions(allowed, F2.UserVerificationRequirement.Discouraged); //translate objects into our own types var jsonA = $"{{\r\n\t\"type\" : \"webauthn.get\",\r\n\t\"challenge\" : \"{Convert.ToBase64String(f2reqA.Challenge)}\",\r\n\t\"origin\" : \"{config.Origin}\"\r\n}}"; var clientDataA = new ClientData { ClientDataJSON = Encoding.UTF8.GetBytes(jsonA), HashAlgorithm = HashAlgorithm.Sha256 }; WebAuthnApi.GetCancellationId(out var cancelId); var getOptions = new AuthenticatorGetAssertionOptions() { CancellationId = cancelId, TimeoutMilliseconds = (int)f2reqA.Timeout, UserVerificationRequirement = (UserVerificationRequirement)f2reqA.UserVerification + 1, AllowedCredentialsEx = f2reqA.AllowCredentials .Select(ec => new CredentialEx(ec.Id, ec.Transports?.Aggregate(CtapTransport.NoRestrictions, (acc, n) => acc | (CtapTransport)(1 << (int)n)) ?? CtapTransport.NoRestrictions)).ToList() }; //call WinAPI Console.WriteLine("Canceling Assertion in 5sec..."); Task.Run(async() => { await Task.Delay(5000); WebAuthnApi.CancelCurrentOperation(cancelId); }); res = WebAuthnApi.AuthenticatorGetAssertion(windowHandle, config.ServerDomain, clientDataA, getOptions, out var assertion); if (res != WebAuthnResult.Ok) { if (res == WebAuthnResult.Canceled) { Console.WriteLine("Canceled"); Console.ReadKey(); continue; } Console.WriteLine($"0x{(int)res:X} " + WebAuthnApi.GetErrorName(res)); break; } //translate back and verify var respA = new AuthenticatorAssertionRawResponse() { Id = assertion.Credential.CredentialId, RawId = assertion.Credential.CredentialId, Type = F2.PublicKeyCredentialType.PublicKey, Response = new AuthenticatorAssertionRawResponse.AssertionResponse() { AuthenticatorData = assertion.AuthenticatorData, Signature = assertion.Signature, UserHandle = assertion.UserId, ClientDataJson = Encoding.UTF8.GetBytes(jsonA) }, }; var getResult = fido2NetLib.MakeAssertionAsync(respA, f2reqA, makeRes.Result.PublicKey, makeRes.Result.Counter, _ => Task.FromResult(true)).Result; Console.WriteLine($"Assertion Result: {getResult.Status}"); Console.WriteLine("Press any key to test assertion again"); Console.ReadKey(); } Console.ReadKey(); }
public void TestAuthenticatorAttestationResponseUVRequired() { var challenge = RandomGenerator.Default.GenerateBytes(128); var rp = "https://www.passwordless.dev"; var acd = new AttestedCredentialData(("00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-40-FE-6A-32-63-BE-37-D1-01-B1-2E-57-CA-96-6C-00-22-93-E4-19-C8-CD-01-06-23-0B-C6-92-E8-CC-77-12-21-F1-DB-11-5D-41-0F-82-6B-DB-98-AC-64-2E-B1-AE-B5-A8-03-D1-DB-C1-47-EF-37-1C-FD-B1-CE-B0-48-CB-2C-A5-01-02-03-26-20-01-21-58-20-A6-D1-09-38-5A-C7-8E-5B-F0-3D-1C-2E-08-74-BE-6D-BB-A4-0B-4F-2A-5F-2F-11-82-45-65-65-53-4F-67-28-22-58-20-43-E1-08-2A-F3-13-5B-40-60-93-79-AC-47-42-58-AA-B3-97-B8-86-1D-E4-41-B4-4E-83-08-5D-1C-6B-E0-D0").Split('-').Select(c => Convert.ToByte(c, 16)).ToArray()); var authData = new AuthenticatorData( SHA256.HashData(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.AT | AuthenticatorFlags.UP, 0, acd ).ToByteArray(); var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { type = "webauthn.create", challenge = challenge, origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = new CborMap { { "fmt", "none" }, { "attStmt", new CborMap() }, { "authData", authData } }.Encode(), ClientDataJson = clientDataJson }, }; var origChallenge = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection { AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, RequireResidentKey = true, UserVerification = UserVerificationRequirement.Required, }, Challenge = challenge, ErrorMessage = "", PubKeyCredParams = new List <PubKeyCredParam>() { new PubKeyCredParam(COSE.Algorithm.ES256) }, Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), Status = "ok", User = new Fido2User { Name = "testuser", Id = Encoding.UTF8.GetBytes("testuser"), DisplayName = "Test User", }, Timeout = 60000, }; IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => { return(Task.FromResult(true)); }; IFido2 lib = new Fido2(new Fido2Configuration() { ServerDomain = rp, ServerName = rp, Origins = new HashSet <string> { rp }, }); var ex = Assert.ThrowsAsync <Fido2VerificationException>(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); Assert.Equal("User Verified flag not set in authenticator data and user verification was required", ex.Result.Message); }
public void TestAuthenticatorAttestationResponseNoAttestedCredentialData() { var challenge = RandomGenerator.Default.GenerateBytes(128); var rp = "https://www.passwordless.dev"; var authData = new AuthenticatorData( SHA256.HashData(Encoding.UTF8.GetBytes(rp)), AuthenticatorFlags.UP | AuthenticatorFlags.UV, 0, null ).ToByteArray(); var clientDataJson = JsonSerializer.SerializeToUtf8Bytes(new { type = "webauthn.create", challenge = challenge, origin = rp, }); var rawResponse = new AuthenticatorAttestationRawResponse { Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, Response = new AuthenticatorAttestationRawResponse.ResponseData() { AttestationObject = new CborMap { { "fmt", "testing" }, { "attStmt", new CborMap() }, { "authData", authData } }.Encode(), ClientDataJson = clientDataJson }, }; var origChallenge = new CredentialCreateOptions { Attestation = AttestationConveyancePreference.Direct, AuthenticatorSelection = new AuthenticatorSelection { AuthenticatorAttachment = AuthenticatorAttachment.CrossPlatform, RequireResidentKey = true, UserVerification = UserVerificationRequirement.Discouraged, }, Challenge = challenge, ErrorMessage = "", PubKeyCredParams = new List <PubKeyCredParam>() { new PubKeyCredParam(COSE.Algorithm.ES256) }, Rp = new PublicKeyCredentialRpEntity(rp, rp, ""), Status = "ok", User = new Fido2User { Name = "testuser", Id = Encoding.UTF8.GetBytes("testuser"), DisplayName = "Test User", }, Timeout = 60000, }; IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => { return(Task.FromResult(true)); }; IFido2 lib = new Fido2(new Fido2Configuration() { ServerDomain = rp, ServerName = rp, Origins = new HashSet <string> { rp }, }); var ex = Assert.ThrowsAsync <Fido2VerificationException>(() => lib.MakeNewCredentialAsync(rawResponse, origChallenge, callback)); Assert.Equal("Attestation flag not set on attestation data", ex.Result.Message); }