1
0
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:
Kyle Spearrin 2017-04-20 11:23:30 -04:00
parent 490d1775a2
commit 18b2b6f447
17 changed files with 158 additions and 40 deletions

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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" />

View File

@ -0,0 +1,9 @@
namespace Bit.App.Enums
{
public enum OrganizationUserStatusType : byte
{
Invited = 0,
Accepted = 1,
Confirmed = 2
}
}

View File

@ -0,0 +1,9 @@
namespace Bit.App.Enums
{
public enum OrganizationUserType : byte
{
Owner = 0,
Admin = 1,
User = 2
}
}

View 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; }
}
}

View File

@ -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; }
}
}

View File

@ -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;

View File

@ -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; }

View File

@ -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);
}
}
}

View File

@ -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
{

View File

@ -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>();
}
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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
{

View File

@ -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;

View File

@ -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)
{

View File

@ -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; }