From fd5e2c9466b3546916cc3610999bb85b08fd7109 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 21 Jun 2017 21:46:52 -0400 Subject: [PATCH] stubbing out api setup for u2f --- src/Api/Controllers/TwoFactorController.cs | 19 +++++ src/Core/Enums/TwoFactorProviderType.cs | 2 +- .../Identity/AuthenticatorTokenProvider.cs | 4 +- src/Core/Identity/DuoTokenProvider.cs | 10 +-- src/Core/Identity/DuoWebTokenProvider.cs | 4 +- src/Core/Identity/YubicoOtpTokenProvider.cs | 2 +- .../ResourceOwnerPasswordValidator.cs | 6 +- .../Api/Request/TwoFactorRequestModels.cs | 41 +++++++++-- .../TwoFactorAuthenticatorResponseModel.cs | 2 +- .../TwoFactor/TwoFactorDuoResponseModel.cs | 6 +- .../TwoFactor/TwoFactorEmailResponseModel.cs | 2 +- .../TwoFactor/TwoFactorU2fResponseModel.cs | 43 ++++++++++++ .../TwoFactorYubiKeyResponseModel.cs | 10 +-- src/Core/Models/TwoFactorProvider.cs | 11 ++- .../Services/Implementations/UserService.cs | 2 +- src/Sql/Sql.sqlproj | 70 +++++++++---------- 16 files changed, 170 insertions(+), 64 deletions(-) create mode 100644 src/Core/Models/Api/Response/TwoFactor/TwoFactorU2fResponseModel.cs diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index ac768dd2d8..07bb067801 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -112,6 +112,25 @@ namespace Bit.Api.Controllers return response; } + [HttpPost("get-u2f")] + public async Task GetU2f([FromBody]TwoFactorRequestModel model) + { + var user = await CheckPasswordAsync(model.MasterPasswordHash); + var response = new TwoFactorU2fResponseModel(user); + return response; + } + + [HttpPut("u2f")] + [HttpPost("u2f")] + public async Task PutU2f([FromBody]TwoFactorU2fRequestModel model) + { + var user = await CheckPasswordAsync(model.MasterPasswordHash); + model.ToUser(user); + await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.U2f); + var response = new TwoFactorU2fResponseModel(user); + return response; + } + public async Task ValidateYubiKeyAsync(User user, string name, string value) { if(string.IsNullOrWhiteSpace(value) || value.Length == 12) diff --git a/src/Core/Enums/TwoFactorProviderType.cs b/src/Core/Enums/TwoFactorProviderType.cs index 898855c56a..a23712a912 100644 --- a/src/Core/Enums/TwoFactorProviderType.cs +++ b/src/Core/Enums/TwoFactorProviderType.cs @@ -6,6 +6,6 @@ Email = 1, Duo = 2, YubiKey = 3, - U2F = 4 + U2f = 4 } } diff --git a/src/Core/Identity/AuthenticatorTokenProvider.cs b/src/Core/Identity/AuthenticatorTokenProvider.cs index 0c9241ac3f..d60129c887 100644 --- a/src/Core/Identity/AuthenticatorTokenProvider.cs +++ b/src/Core/Identity/AuthenticatorTokenProvider.cs @@ -14,7 +14,7 @@ namespace Bit.Core.Identity var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator); var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Authenticator) - && !string.IsNullOrWhiteSpace(provider.MetaData["Key"]); + && !string.IsNullOrWhiteSpace((string)provider.MetaData["Key"]); return Task.FromResult(canGenerate); } @@ -27,7 +27,7 @@ namespace Bit.Core.Identity public Task ValidateAsync(string purpose, string token, UserManager manager, User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator); - var otp = new Totp(Base32Encoding.ToBytes(provider.MetaData["Key"])); + var otp = new Totp(Base32Encoding.ToBytes((string)provider.MetaData["Key"])); long timeStepMatched; var valid = otp.VerifyTotp(token, out timeStepMatched, new VerificationWindow(1, 1)); diff --git a/src/Core/Identity/DuoTokenProvider.cs b/src/Core/Identity/DuoTokenProvider.cs index a34259af8c..40bb7f22db 100644 --- a/src/Core/Identity/DuoTokenProvider.cs +++ b/src/Core/Identity/DuoTokenProvider.cs @@ -14,7 +14,7 @@ namespace Bit.Core.Identity { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Duo) - && !string.IsNullOrWhiteSpace(provider?.MetaData["UserId"]); + && !string.IsNullOrWhiteSpace((string)provider?.MetaData["UserId"]); return Task.FromResult(canGenerate); } @@ -23,13 +23,14 @@ namespace Bit.Core.Identity public async Task GenerateAsync(string purpose, UserManager manager, User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var duoClient = new DuoApi(provider.MetaData["IKey"], provider.MetaData["SKey"], provider.MetaData["Host"]); + var duoClient = new DuoApi((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], + (string)provider.MetaData["Host"]); var parts = purpose.Split(':'); var parameters = new Dictionary { ["async"] = "1", - ["user_id"] = provider.MetaData["UserId"], + ["user_id"] = (string)provider.MetaData["UserId"], ["factor"] = parts[0] }; @@ -61,7 +62,8 @@ namespace Bit.Core.Identity public async Task ValidateAsync(string purpose, string token, UserManager manager, User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var duoClient = new DuoApi(provider.MetaData["IKey"], provider.MetaData["SKey"], provider.MetaData["Host"]); + var duoClient = new DuoApi((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], + (string)provider.MetaData["Host"]); var parameters = new Dictionary { diff --git a/src/Core/Identity/DuoWebTokenProvider.cs b/src/Core/Identity/DuoWebTokenProvider.cs index f4e1ca5791..2fcc298ea2 100644 --- a/src/Core/Identity/DuoWebTokenProvider.cs +++ b/src/Core/Identity/DuoWebTokenProvider.cs @@ -32,7 +32,7 @@ namespace Bit.Core.Identity return Task.FromResult(null); } - var signatureRequest = DuoWeb.SignRequest(provider.MetaData["IKey"], provider.MetaData["SKey"], + var signatureRequest = DuoWeb.SignRequest((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], _globalSettings.Duo.AKey, user.Id.ToString()); return Task.FromResult(signatureRequest); } @@ -45,7 +45,7 @@ namespace Bit.Core.Identity return Task.FromResult(false); } - var response = DuoWeb.VerifyResponse(provider.MetaData["IKey"], provider.MetaData["SKey"], + var response = DuoWeb.VerifyResponse((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], _globalSettings.Duo.AKey, token); Guid userId; diff --git a/src/Core/Identity/YubicoOtpTokenProvider.cs b/src/Core/Identity/YubicoOtpTokenProvider.cs index 3c14b694af..07779e1dff 100644 --- a/src/Core/Identity/YubicoOtpTokenProvider.cs +++ b/src/Core/Identity/YubicoOtpTokenProvider.cs @@ -20,7 +20,7 @@ namespace Bit.Core.Identity { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey); var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.YubiKey) - && (provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace(v)) ?? false); + && (provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace((string)v)) ?? false); return Task.FromResult(canGenerate); } diff --git a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs index 34c2c52fb8..98c538f445 100644 --- a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -166,7 +166,7 @@ namespace Bit.Core.IdentityServer case TwoFactorProviderType.Authenticator: case TwoFactorProviderType.Duo: case TwoFactorProviderType.YubiKey: - case TwoFactorProviderType.U2F: + case TwoFactorProviderType.U2f: return await _userManager.VerifyTwoFactorTokenAsync(user, type.ToString(), token); case TwoFactorProviderType.Email: return await _userService.VerifyTwoFactorEmailAsync(user, token); @@ -181,7 +181,7 @@ namespace Bit.Core.IdentityServer switch(type) { case TwoFactorProviderType.Duo: - case TwoFactorProviderType.U2F: + case TwoFactorProviderType.U2f: var token = await _userManager.GenerateTwoFactorTokenAsync(user, type.ToString()); if(type == TwoFactorProviderType.Duo) { @@ -191,7 +191,7 @@ namespace Bit.Core.IdentityServer ["Signature"] = token }; } - else if(type == TwoFactorProviderType.U2F) + else if(type == TwoFactorProviderType.U2f) { // TODO: U2F challenge return new Dictionary { }; diff --git a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs index af34cc5386..c23c259608 100644 --- a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs +++ b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs @@ -30,7 +30,7 @@ namespace Bit.Core.Models.Api providers.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider { - MetaData = new Dictionary { ["Key"] = Key }, + MetaData = new Dictionary { ["Key"] = Key }, Enabled = true }); extistingUser.SetTwoFactorProviders(providers); @@ -64,7 +64,7 @@ namespace Bit.Core.Models.Api providers.Add(TwoFactorProviderType.Duo, new TwoFactorProvider { - MetaData = new Dictionary + MetaData = new Dictionary { ["SKey"] = SecretKey, ["IKey"] = IntegrationKey, @@ -107,7 +107,7 @@ namespace Bit.Core.Models.Api providers.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider { - MetaData = new Dictionary + MetaData = new Dictionary { ["Key1"] = FormatKey(Key1), ["Key2"] = FormatKey(Key2), @@ -187,7 +187,40 @@ namespace Bit.Core.Models.Api providers.Add(TwoFactorProviderType.Email, new TwoFactorProvider { - MetaData = new Dictionary { ["Email"] = Email }, + MetaData = new Dictionary { ["Email"] = Email }, + Enabled = true + }); + extistingUser.SetTwoFactorProviders(providers); + return extistingUser; + } + } + + public class TwoFactorU2fRequestModel : TwoFactorRequestModel + { + [Required] + public string DeviceResponse { get; set; } + + public User ToUser(User extistingUser) + { + var providers = extistingUser.GetTwoFactorProviders(); + if(providers == null) + { + providers = new Dictionary(); + } + else if(providers.ContainsKey(TwoFactorProviderType.U2f)) + { + providers.Remove(TwoFactorProviderType.U2f); + } + + providers.Add(TwoFactorProviderType.U2f, new TwoFactorProvider + { + MetaData = new Dictionary + { + ["Key1"] = new TwoFactorProvider.U2fMetaData + { + // TODO + } + }, Enabled = true }); extistingUser.SetTwoFactorProviders(providers); diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs index 9401d38995..d973e046e5 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs @@ -18,7 +18,7 @@ namespace Bit.Core.Models.Api var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator); if(provider?.MetaData?.ContainsKey("Key") ?? false) { - Key = provider.MetaData["Key"]; + Key = (string)provider.MetaData["Key"]; Enabled = provider.Enabled; } else diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs index 29674330cc..e370f9e5c7 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs @@ -21,15 +21,15 @@ namespace Bit.Core.Models.Api if(provider.MetaData.ContainsKey("Host")) { - Host = provider.MetaData["Host"]; + Host = (string)provider.MetaData["Host"]; } if(provider.MetaData.ContainsKey("SKey")) { - SecretKey = provider.MetaData["SKey"]; + SecretKey = (string)provider.MetaData["SKey"]; } if(provider.MetaData.ContainsKey("IKey")) { - IntegrationKey = provider.MetaData["IKey"]; + IntegrationKey = (string)provider.MetaData["IKey"]; } } else diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorEmailResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorEmailResponseModel.cs index dacccb2672..9c61a26cbe 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorEmailResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorEmailResponseModel.cs @@ -17,7 +17,7 @@ namespace Bit.Core.Models.Api var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); if(provider?.MetaData?.ContainsKey("Email") ?? false) { - Email = provider.MetaData["Email"]; + Email = (string)provider.MetaData["Email"]; Enabled = provider.Enabled; } else diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorU2fResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorU2fResponseModel.cs new file mode 100644 index 0000000000..aa211a830f --- /dev/null +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorU2fResponseModel.cs @@ -0,0 +1,43 @@ +using System; +using Bit.Core.Enums; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api +{ + public class TwoFactorU2fResponseModel : ResponseModel + { + public TwoFactorU2fResponseModel(User user) + : base("twoFactorU2f") + { + if(user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f); + if(provider?.MetaData != null && provider.MetaData.Count > 0) + { + Challenge = new ChallengeModel + { + // TODO + }; + Enabled = provider.Enabled; + } + else + { + Enabled = false; + } + } + + public ChallengeModel Challenge { get; set; } + public bool Enabled { get; set; } + + public class ChallengeModel + { + public string UserId { get; set; } + public string AppId { get; set; } + public string Challenge { get; set; } + public string Version { get; set; } + } + } +} diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs index bd87945c06..8be8654e36 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorYubiKeyResponseModel.cs @@ -21,23 +21,23 @@ namespace Bit.Core.Models.Api if(provider.MetaData.ContainsKey("Key1")) { - Key1 = provider.MetaData["Key1"]; + Key1 = (string)provider.MetaData["Key1"]; } if(provider.MetaData.ContainsKey("Key2")) { - Key2 = provider.MetaData["Key2"]; + Key2 = (string)provider.MetaData["Key2"]; } if(provider.MetaData.ContainsKey("Key3")) { - Key3 = provider.MetaData["Key3"]; + Key3 = (string)provider.MetaData["Key3"]; } if(provider.MetaData.ContainsKey("Key4")) { - Key4 = provider.MetaData["Key4"]; + Key4 = (string)provider.MetaData["Key4"]; } if(provider.MetaData.ContainsKey("Key5")) { - Key5 = provider.MetaData["Key5"]; + Key5 = (string)provider.MetaData["Key5"]; } } else diff --git a/src/Core/Models/TwoFactorProvider.cs b/src/Core/Models/TwoFactorProvider.cs index 5a2628b3d9..e7442350b5 100644 --- a/src/Core/Models/TwoFactorProvider.cs +++ b/src/Core/Models/TwoFactorProvider.cs @@ -5,6 +5,15 @@ namespace Bit.Core.Models public class TwoFactorProvider { public bool Enabled { get; set; } - public Dictionary MetaData { get; set; } = new Dictionary(); + public Dictionary MetaData { get; set; } = new Dictionary(); + + public class U2fMetaData + { + public string KeyHandle { get; set; } + public string PublicKey { get; set; } + public string Certificate { get; set; } + public int Counter { get; set; } + public bool Compromised { get; set; } + } } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 5a52e3a8df..b82c8db2a1 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -192,7 +192,7 @@ namespace Bit.Core.Services var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, "2faEmail:" + provider.MetaData["Email"]); - await _mailService.SendChangeEmailEmailAsync(provider.MetaData["Email"], token); + await _mailService.SendChangeEmailEmailAsync((string)provider.MetaData["Email"], token); } public async Task VerifyTwoFactorEmailAsync(User user, string token) diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index efa8becfe8..645682a1f6 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -68,29 +68,32 @@ - + + - + + - - + + - + + @@ -118,15 +121,23 @@ + + + + + + + + @@ -143,62 +154,51 @@ - - - - - - + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - \ No newline at end of file