mirror of
https://github.com/bitwarden/mobile.git
synced 2024-12-26 16:57:59 +01:00
set org keys on login and decrypt org ciphers
This commit is contained in:
parent
490d1775a2
commit
18b2b6f447
@ -8,6 +8,7 @@ namespace Bit.App.Abstractions
|
||||
{
|
||||
Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj);
|
||||
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj);
|
||||
Task<ApiResult<DateTime?>> GetAccountRevisionDate();
|
||||
Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync();
|
||||
Task<ApiResult<ProfileResponse>> GetProfileAsync();
|
||||
}
|
||||
}
|
@ -10,14 +10,16 @@ namespace Bit.App.Abstractions
|
||||
CryptoKey PreviousKey { get; }
|
||||
bool KeyChanged { get; }
|
||||
byte[] PrivateKey { get; }
|
||||
IDictionary<Guid, CryptoKey> OrgKeys { get; set; }
|
||||
IDictionary<string, CryptoKey> OrgKeys { get; set; }
|
||||
|
||||
void SetPrivateKey(CipherString privateKeyEnc, CryptoKey key);
|
||||
CryptoKey GetOrgKey(Guid orgId);
|
||||
void ClearOrgKey(Guid orgId);
|
||||
CryptoKey GetOrgKey(string orgId);
|
||||
void ClearOrgKey(string orgId);
|
||||
void ClearKeys();
|
||||
CryptoKey AddOrgKey(Guid orgId, CipherString encOrgKey, byte[] privateKey);
|
||||
CryptoKey AddOrgKey(string orgId, CipherString encOrgKey, byte[] privateKey);
|
||||
string Decrypt(CipherString encyptedValue, CryptoKey key = null);
|
||||
byte[] DecryptToBytes(CipherString encyptedValue, CryptoKey key = null);
|
||||
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
|
||||
CipherString Encrypt(string plaintextValue, CryptoKey key = null);
|
||||
CryptoKey MakeKeyFromPassword(string password, string salt);
|
||||
string MakeKeyFromPasswordBase64(string password, string salt);
|
||||
|
@ -81,8 +81,10 @@
|
||||
<Compile Include="Controls\PinControl.cs" />
|
||||
<Compile Include="Controls\VaultListViewCell.cs" />
|
||||
<Compile Include="Enums\EncryptionType.cs" />
|
||||
<Compile Include="Enums\OrganizationUserType.cs" />
|
||||
<Compile Include="Enums\LockType.cs" />
|
||||
<Compile Include="Enums\CipherType.cs" />
|
||||
<Compile Include="Enums\OrganizationUserStatusType.cs" />
|
||||
<Compile Include="Enums\PushType.cs" />
|
||||
<Compile Include="Enums\ReturnType.cs" />
|
||||
<Compile Include="Abstractions\Services\ILocalizeService.cs" />
|
||||
@ -104,6 +106,7 @@
|
||||
<Compile Include="Models\Api\Response\ListResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\DeviceResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\LoginResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\ProfileOrganizationResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\TokenResponse.cs" />
|
||||
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
|
||||
<Compile Include="Models\Api\LoginDataModel.cs" />
|
||||
|
9
src/App/Enums/OrganizationUserStatusType.cs
Normal file
9
src/App/Enums/OrganizationUserStatusType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Bit.App.Enums
|
||||
{
|
||||
public enum OrganizationUserStatusType : byte
|
||||
{
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2
|
||||
}
|
||||
}
|
9
src/App/Enums/OrganizationUserType.cs
Normal file
9
src/App/Enums/OrganizationUserType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Bit.App.Enums
|
||||
{
|
||||
public enum OrganizationUserType : byte
|
||||
{
|
||||
Owner = 0,
|
||||
Admin = 1,
|
||||
User = 2
|
||||
}
|
||||
}
|
14
src/App/Models/Api/Response/ProfileOrganizationResponse.cs
Normal file
14
src/App/Models/Api/Response/ProfileOrganizationResponse.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class ProfileOrganizationResponseModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Key { get; set; }
|
||||
public OrganizationUserStatusType Status { get; set; }
|
||||
public OrganizationUserType Type { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
namespace Bit.App.Models.Api
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.App.Models.Api
|
||||
{
|
||||
public class ProfileResponse
|
||||
{
|
||||
@ -8,5 +10,6 @@
|
||||
public string MasterPasswordHint { get; set; }
|
||||
public string Culture { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
public IEnumerable<ProfileOrganizationResponseModel> Organizations { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -102,12 +102,19 @@ namespace Bit.App.Models
|
||||
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
|
||||
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
|
||||
|
||||
public string Decrypt()
|
||||
public string Decrypt(string orgId = null)
|
||||
{
|
||||
if(_decryptedValue == null)
|
||||
{
|
||||
var cryptoService = Resolver.Resolve<ICryptoService>();
|
||||
_decryptedValue = cryptoService.Decrypt(this);
|
||||
if(!string.IsNullOrWhiteSpace(orgId))
|
||||
{
|
||||
_decryptedValue = cryptoService.Decrypt(this, cryptoService.GetOrgKey(orgId));
|
||||
}
|
||||
else
|
||||
{
|
||||
_decryptedValue = cryptoService.Decrypt(this);
|
||||
}
|
||||
}
|
||||
|
||||
return _decryptedValue;
|
||||
|
@ -12,10 +12,10 @@ namespace Bit.App.Models.Page
|
||||
{
|
||||
Id = login.Id;
|
||||
FolderId = login.FolderId;
|
||||
Name = login.Name?.Decrypt();
|
||||
Username = login.Username?.Decrypt() ?? " ";
|
||||
Password = new Lazy<string>(() => login.Password?.Decrypt());
|
||||
Uri = new Lazy<string>(() => login.Uri?.Decrypt());
|
||||
Name = login.Name?.Decrypt(login.OrganizationId);
|
||||
Username = login.Username?.Decrypt(login.OrganizationId) ?? " ";
|
||||
Password = new Lazy<string>(() => login.Password?.Decrypt(login.OrganizationId));
|
||||
Uri = new Lazy<string>(() => login.Uri?.Decrypt(login.OrganizationId));
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
@ -171,11 +171,11 @@ namespace Bit.App.Models.Page
|
||||
|
||||
public void Update(Login login)
|
||||
{
|
||||
Name = login.Name?.Decrypt();
|
||||
Username = login.Username?.Decrypt();
|
||||
Password = login.Password?.Decrypt();
|
||||
Uri = login.Uri?.Decrypt();
|
||||
Notes = login.Notes?.Decrypt();
|
||||
Name = login.Name?.Decrypt(login.OrganizationId);
|
||||
Username = login.Username?.Decrypt(login.OrganizationId);
|
||||
Password = login.Password?.Decrypt(login.OrganizationId);
|
||||
Uri = login.Uri?.Decrypt(login.OrganizationId);
|
||||
Notes = login.Notes?.Decrypt(login.OrganizationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,25 +51,25 @@ namespace Bit.App.Pages
|
||||
}
|
||||
|
||||
NotesCell = new FormEditorCell(height: 90);
|
||||
NotesCell.Editor.Text = login.Notes?.Decrypt();
|
||||
NotesCell.Editor.Text = login.Notes?.Decrypt(login.OrganizationId);
|
||||
|
||||
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
|
||||
useButton: true);
|
||||
PasswordCell.Entry.Text = login.Password?.Decrypt();
|
||||
PasswordCell.Entry.Text = login.Password?.Decrypt(login.OrganizationId);
|
||||
PasswordCell.Button.Image = "eye";
|
||||
PasswordCell.Entry.DisableAutocapitalize = true;
|
||||
PasswordCell.Entry.Autocorrect = false;
|
||||
PasswordCell.Entry.FontFamily = Device.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
|
||||
|
||||
UsernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry);
|
||||
UsernameCell.Entry.Text = login.Username?.Decrypt();
|
||||
UsernameCell.Entry.Text = login.Username?.Decrypt(login.OrganizationId);
|
||||
UsernameCell.Entry.DisableAutocapitalize = true;
|
||||
UsernameCell.Entry.Autocorrect = false;
|
||||
|
||||
UriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: UsernameCell.Entry);
|
||||
UriCell.Entry.Text = login.Uri?.Decrypt();
|
||||
UriCell.Entry.Text = login.Uri?.Decrypt(login.OrganizationId);
|
||||
NameCell = new FormEntryCell(AppResources.Name, nextElement: UriCell.Entry);
|
||||
NameCell.Entry.Text = login.Name?.Decrypt();
|
||||
NameCell.Entry.Text = login.Name?.Decrypt(login.OrganizationId);
|
||||
|
||||
GenerateCell = new ExtendedTextCell
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models.Api;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.App.Repositories
|
||||
{
|
||||
@ -84,7 +85,7 @@ namespace Bit.App.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<ApiResult<DateTime?>> GetAccountRevisionDate()
|
||||
public virtual async Task<ApiResult<DateTime?>> GetAccountRevisionDateAsync()
|
||||
{
|
||||
if(!Connectivity.IsConnected)
|
||||
{
|
||||
@ -132,5 +133,45 @@ namespace Bit.App.Repositories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<ApiResult<ProfileResponse>> GetProfileAsync()
|
||||
{
|
||||
if(!Connectivity.IsConnected)
|
||||
{
|
||||
return HandledNotConnected<ProfileResponse>();
|
||||
}
|
||||
|
||||
var tokenStateResponse = await HandleTokenStateAsync<ProfileResponse>();
|
||||
if(!tokenStateResponse.Succeeded)
|
||||
{
|
||||
return tokenStateResponse;
|
||||
}
|
||||
|
||||
using(var client = HttpService.Client)
|
||||
{
|
||||
var requestMessage = new TokenHttpRequestMessage()
|
||||
{
|
||||
Method = HttpMethod.Get,
|
||||
RequestUri = new Uri(client.BaseAddress, string.Concat(ApiRoute, "/profile")),
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.SendAsync(requestMessage).ConfigureAwait(false);
|
||||
if(!response.IsSuccessStatusCode)
|
||||
{
|
||||
return await HandleErrorAsync<ProfileResponse>(response).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
var responseObj = JsonConvert.DeserializeObject<ProfileResponse>(responseContent);
|
||||
return ApiResult<ProfileResponse>.Success(responseObj, response.StatusCode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return HandledWebException<ProfileResponse>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ using Bit.App.Models.Api;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
@ -21,6 +23,7 @@ namespace Bit.App.Services
|
||||
private readonly ISettings _settings;
|
||||
private readonly ICryptoService _cryptoService;
|
||||
private readonly IConnectApiRepository _connectApiRepository;
|
||||
private readonly IAccountsApiRepository _accountsApiRepository;
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IDeviceInfoService _deviceInfoService;
|
||||
|
||||
@ -35,6 +38,7 @@ namespace Bit.App.Services
|
||||
ISettings settings,
|
||||
ICryptoService cryptoService,
|
||||
IConnectApiRepository connectApiRepository,
|
||||
IAccountsApiRepository accountsApiRepository,
|
||||
IAppIdService appIdService,
|
||||
IDeviceInfoService deviceInfoService)
|
||||
{
|
||||
@ -43,6 +47,7 @@ namespace Bit.App.Services
|
||||
_settings = settings;
|
||||
_cryptoService = cryptoService;
|
||||
_connectApiRepository = connectApiRepository;
|
||||
_accountsApiRepository = accountsApiRepository;
|
||||
_appIdService = appIdService;
|
||||
_deviceInfoService = deviceInfoService;
|
||||
}
|
||||
@ -230,7 +235,7 @@ namespace Bit.App.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
ProcessLoginSuccess(key, response.Result);
|
||||
await ProcessLoginSuccessAsync(key, response.Result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -257,11 +262,11 @@ namespace Bit.App.Services
|
||||
}
|
||||
|
||||
result.Success = true;
|
||||
ProcessLoginSuccess(key, response.Result);
|
||||
await ProcessLoginSuccessAsync(key, response.Result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ProcessLoginSuccess(CryptoKey key, TokenResponse response)
|
||||
private async Task ProcessLoginSuccessAsync(CryptoKey key, TokenResponse response)
|
||||
{
|
||||
if(response.PrivateKey != null)
|
||||
{
|
||||
@ -274,6 +279,30 @@ namespace Bit.App.Services
|
||||
UserId = _tokenService.TokenUserId;
|
||||
Email = _tokenService.TokenEmail;
|
||||
_settings.AddOrUpdateValue(Constants.LastLoginEmail, Email);
|
||||
|
||||
if(response.PrivateKey != null)
|
||||
{
|
||||
var profile = await _accountsApiRepository.GetProfileAsync();
|
||||
var orgKeysDict = new Dictionary<string, CryptoKey>();
|
||||
|
||||
if(profile.Succeeded && (profile.Result.Organizations?.Any() ?? false))
|
||||
{
|
||||
foreach(var org in profile.Result.Organizations)
|
||||
{
|
||||
try
|
||||
{
|
||||
var decBytes = _cryptoService.RsaDecryptToBytes(new CipherString(org.Key), null);
|
||||
orgKeysDict.Add(org.Id, new CryptoKey(decBytes));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.WriteLine($"Cannot set org key {org.Id}. Decryption failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cryptoService.OrgKeys = orgKeysDict;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace Bit.App.Services
|
||||
private CryptoKey _key;
|
||||
private CryptoKey _legacyEtmKey;
|
||||
private CryptoKey _previousKey;
|
||||
private IDictionary<Guid, CryptoKey> _orgKeys;
|
||||
private IDictionary<string, CryptoKey> _orgKeys;
|
||||
private byte[] _privateKey;
|
||||
|
||||
public CryptoService(
|
||||
@ -127,7 +127,7 @@ namespace Bit.App.Services
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<Guid, CryptoKey> OrgKeys
|
||||
public IDictionary<string, CryptoKey> OrgKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -139,8 +139,8 @@ namespace Bit.App.Services
|
||||
var orgKeysDictJson = Encoding.UTF8.GetString(orgKeysDictBytes, 0, orgKeysDictBytes.Length);
|
||||
if(!string.IsNullOrWhiteSpace(orgKeysDictJson))
|
||||
{
|
||||
_orgKeys = new Dictionary<Guid, CryptoKey>();
|
||||
var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<Guid, byte[]>>(orgKeysDictJson);
|
||||
_orgKeys = new Dictionary<string, CryptoKey>();
|
||||
var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<string, byte[]>>(orgKeysDictJson);
|
||||
foreach(var item in orgKeysDict)
|
||||
{
|
||||
_orgKeys.Add(item.Key, new CryptoKey(item.Value));
|
||||
@ -155,7 +155,7 @@ namespace Bit.App.Services
|
||||
{
|
||||
if(value != null && value.Any())
|
||||
{
|
||||
var dict = new Dictionary<Guid, byte[]>();
|
||||
var dict = new Dictionary<string, byte[]>();
|
||||
foreach(var item in value)
|
||||
{
|
||||
dict.Add(item.Key, item.Value.Key);
|
||||
@ -180,7 +180,7 @@ namespace Bit.App.Services
|
||||
PrivateKey = bytes;
|
||||
}
|
||||
|
||||
public CryptoKey GetOrgKey(Guid orgId)
|
||||
public CryptoKey GetOrgKey(string orgId)
|
||||
{
|
||||
if(OrgKeys == null || !OrgKeys.ContainsKey(orgId))
|
||||
{
|
||||
@ -190,7 +190,7 @@ namespace Bit.App.Services
|
||||
return OrgKeys[orgId];
|
||||
}
|
||||
|
||||
public void ClearOrgKey(Guid orgId)
|
||||
public void ClearOrgKey(string orgId)
|
||||
{
|
||||
var localOrgKeys = OrgKeys;
|
||||
if(localOrgKeys == null || !localOrgKeys.ContainsKey(orgId))
|
||||
@ -210,7 +210,7 @@ namespace Bit.App.Services
|
||||
PrivateKey = null;
|
||||
}
|
||||
|
||||
public CryptoKey AddOrgKey(Guid orgId, CipherString encOrgKey, byte[] privateKey)
|
||||
public CryptoKey AddOrgKey(string orgId, CipherString encOrgKey, byte[] privateKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -125,7 +125,7 @@ namespace Bit.App.Services
|
||||
continue;
|
||||
}
|
||||
|
||||
var loginUriString = new CipherString(login.Uri).Decrypt();
|
||||
var loginUriString = new CipherString(login.Uri).Decrypt(login.OrganizationId);
|
||||
if(string.IsNullOrWhiteSpace(loginUriString))
|
||||
{
|
||||
continue;
|
||||
|
@ -217,7 +217,7 @@ namespace Bit.App.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDate();
|
||||
var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDateAsync();
|
||||
if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue &&
|
||||
accountRevisionDate.Result.Value > lastSync)
|
||||
{
|
||||
|
@ -7,10 +7,10 @@ namespace Bit.iOS.Extension.Models
|
||||
public LoginViewModel(Login login)
|
||||
{
|
||||
Id = login.Id;
|
||||
Name = login.Name?.Decrypt();
|
||||
Username = login.Username?.Decrypt();
|
||||
Password = login.Password?.Decrypt();
|
||||
Uri = login.Uri?.Decrypt();
|
||||
Name = login.Name?.Decrypt(login.OrganizationId);
|
||||
Username = login.Username?.Decrypt(login.OrganizationId);
|
||||
Password = login.Password?.Decrypt(login.OrganizationId);
|
||||
Uri = login.Uri?.Decrypt(login.OrganizationId);
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
Loading…
Reference in New Issue
Block a user