1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-10 13:23:39 +00: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> PostRegisterAsync(RegisterRequest requestObj);
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest 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; } CryptoKey PreviousKey { get; }
bool KeyChanged { get; } bool KeyChanged { get; }
byte[] PrivateKey { get; } byte[] PrivateKey { get; }
IDictionary<Guid, CryptoKey> OrgKeys { get; set; } IDictionary<string, CryptoKey> OrgKeys { get; set; }
void SetPrivateKey(CipherString privateKeyEnc, CryptoKey key); void SetPrivateKey(CipherString privateKeyEnc, CryptoKey key);
CryptoKey GetOrgKey(Guid orgId); CryptoKey GetOrgKey(string orgId);
void ClearOrgKey(Guid orgId); void ClearOrgKey(string orgId);
void ClearKeys(); 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); 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); CipherString Encrypt(string plaintextValue, CryptoKey key = null);
CryptoKey MakeKeyFromPassword(string password, string salt); CryptoKey MakeKeyFromPassword(string password, string salt);
string MakeKeyFromPasswordBase64(string password, string salt); string MakeKeyFromPasswordBase64(string password, string salt);

View File

@@ -81,8 +81,10 @@
<Compile Include="Controls\PinControl.cs" /> <Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultListViewCell.cs" /> <Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\EncryptionType.cs" /> <Compile Include="Enums\EncryptionType.cs" />
<Compile Include="Enums\OrganizationUserType.cs" />
<Compile Include="Enums\LockType.cs" /> <Compile Include="Enums\LockType.cs" />
<Compile Include="Enums\CipherType.cs" /> <Compile Include="Enums\CipherType.cs" />
<Compile Include="Enums\OrganizationUserStatusType.cs" />
<Compile Include="Enums\PushType.cs" /> <Compile Include="Enums\PushType.cs" />
<Compile Include="Enums\ReturnType.cs" /> <Compile Include="Enums\ReturnType.cs" />
<Compile Include="Abstractions\Services\ILocalizeService.cs" /> <Compile Include="Abstractions\Services\ILocalizeService.cs" />
@@ -104,6 +106,7 @@
<Compile Include="Models\Api\Response\ListResponse.cs" /> <Compile Include="Models\Api\Response\ListResponse.cs" />
<Compile Include="Models\Api\Response\DeviceResponse.cs" /> <Compile Include="Models\Api\Response\DeviceResponse.cs" />
<Compile Include="Models\Api\Response\LoginResponse.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\TokenResponse.cs" />
<Compile Include="Models\Api\Response\ProfileResponse.cs" /> <Compile Include="Models\Api\Response\ProfileResponse.cs" />
<Compile Include="Models\Api\LoginDataModel.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 public class ProfileResponse
{ {
@@ -8,5 +10,6 @@
public string MasterPasswordHint { get; set; } public string MasterPasswordHint { get; set; }
public string Culture { get; set; } public string Culture { get; set; }
public bool TwoFactorEnabled { 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[] CipherTextBytes => Convert.FromBase64String(CipherText);
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac); public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
public string Decrypt() public string Decrypt(string orgId = null)
{ {
if(_decryptedValue == null) if(_decryptedValue == null)
{ {
var cryptoService = Resolver.Resolve<ICryptoService>(); 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; return _decryptedValue;

View File

@@ -12,10 +12,10 @@ namespace Bit.App.Models.Page
{ {
Id = login.Id; Id = login.Id;
FolderId = login.FolderId; FolderId = login.FolderId;
Name = login.Name?.Decrypt(); Name = login.Name?.Decrypt(login.OrganizationId);
Username = login.Username?.Decrypt() ?? " "; Username = login.Username?.Decrypt(login.OrganizationId) ?? " ";
Password = new Lazy<string>(() => login.Password?.Decrypt()); Password = new Lazy<string>(() => login.Password?.Decrypt(login.OrganizationId));
Uri = new Lazy<string>(() => login.Uri?.Decrypt()); Uri = new Lazy<string>(() => login.Uri?.Decrypt(login.OrganizationId));
} }
public string Id { get; set; } public string Id { get; set; }

View File

@@ -171,11 +171,11 @@ namespace Bit.App.Models.Page
public void Update(Login login) public void Update(Login login)
{ {
Name = login.Name?.Decrypt(); Name = login.Name?.Decrypt(login.OrganizationId);
Username = login.Username?.Decrypt(); Username = login.Username?.Decrypt(login.OrganizationId);
Password = login.Password?.Decrypt(); Password = login.Password?.Decrypt(login.OrganizationId);
Uri = login.Uri?.Decrypt(); Uri = login.Uri?.Decrypt(login.OrganizationId);
Notes = login.Notes?.Decrypt(); Notes = login.Notes?.Decrypt(login.OrganizationId);
} }
} }
} }

View File

@@ -51,25 +51,25 @@ namespace Bit.App.Pages
} }
NotesCell = new FormEditorCell(height: 90); 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, PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
useButton: true); useButton: true);
PasswordCell.Entry.Text = login.Password?.Decrypt(); PasswordCell.Entry.Text = login.Password?.Decrypt(login.OrganizationId);
PasswordCell.Button.Image = "eye"; PasswordCell.Button.Image = "eye";
PasswordCell.Entry.DisableAutocapitalize = true; PasswordCell.Entry.DisableAutocapitalize = true;
PasswordCell.Entry.Autocorrect = false; PasswordCell.Entry.Autocorrect = false;
PasswordCell.Entry.FontFamily = Device.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier"); PasswordCell.Entry.FontFamily = Device.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
UsernameCell = new FormEntryCell(AppResources.Username, nextElement: PasswordCell.Entry); 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.DisableAutocapitalize = true;
UsernameCell.Entry.Autocorrect = false; UsernameCell.Entry.Autocorrect = false;
UriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: UsernameCell.Entry); 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 = new FormEntryCell(AppResources.Name, nextElement: UriCell.Entry);
NameCell.Entry.Text = login.Name?.Decrypt(); NameCell.Entry.Text = login.Name?.Decrypt(login.OrganizationId);
GenerateCell = new ExtendedTextCell GenerateCell = new ExtendedTextCell
{ {

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models.Api; using Bit.App.Models.Api;
using Plugin.Connectivity.Abstractions; using Plugin.Connectivity.Abstractions;
using Newtonsoft.Json;
namespace Bit.App.Repositories 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) 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 Plugin.Settings.Abstractions;
using Bit.App.Models; using Bit.App.Models;
using System.Linq; using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
namespace Bit.App.Services namespace Bit.App.Services
{ {
@@ -21,6 +23,7 @@ namespace Bit.App.Services
private readonly ISettings _settings; private readonly ISettings _settings;
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
private readonly IConnectApiRepository _connectApiRepository; private readonly IConnectApiRepository _connectApiRepository;
private readonly IAccountsApiRepository _accountsApiRepository;
private readonly IAppIdService _appIdService; private readonly IAppIdService _appIdService;
private readonly IDeviceInfoService _deviceInfoService; private readonly IDeviceInfoService _deviceInfoService;
@@ -35,6 +38,7 @@ namespace Bit.App.Services
ISettings settings, ISettings settings,
ICryptoService cryptoService, ICryptoService cryptoService,
IConnectApiRepository connectApiRepository, IConnectApiRepository connectApiRepository,
IAccountsApiRepository accountsApiRepository,
IAppIdService appIdService, IAppIdService appIdService,
IDeviceInfoService deviceInfoService) IDeviceInfoService deviceInfoService)
{ {
@@ -43,6 +47,7 @@ namespace Bit.App.Services
_settings = settings; _settings = settings;
_cryptoService = cryptoService; _cryptoService = cryptoService;
_connectApiRepository = connectApiRepository; _connectApiRepository = connectApiRepository;
_accountsApiRepository = accountsApiRepository;
_appIdService = appIdService; _appIdService = appIdService;
_deviceInfoService = deviceInfoService; _deviceInfoService = deviceInfoService;
} }
@@ -230,7 +235,7 @@ namespace Bit.App.Services
return result; return result;
} }
ProcessLoginSuccess(key, response.Result); await ProcessLoginSuccessAsync(key, response.Result);
return result; return result;
} }
@@ -257,11 +262,11 @@ namespace Bit.App.Services
} }
result.Success = true; result.Success = true;
ProcessLoginSuccess(key, response.Result); await ProcessLoginSuccessAsync(key, response.Result);
return result; return result;
} }
private void ProcessLoginSuccess(CryptoKey key, TokenResponse response) private async Task ProcessLoginSuccessAsync(CryptoKey key, TokenResponse response)
{ {
if(response.PrivateKey != null) if(response.PrivateKey != null)
{ {
@@ -274,6 +279,30 @@ namespace Bit.App.Services
UserId = _tokenService.TokenUserId; UserId = _tokenService.TokenUserId;
Email = _tokenService.TokenEmail; Email = _tokenService.TokenEmail;
_settings.AddOrUpdateValue(Constants.LastLoginEmail, Email); _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 _key;
private CryptoKey _legacyEtmKey; private CryptoKey _legacyEtmKey;
private CryptoKey _previousKey; private CryptoKey _previousKey;
private IDictionary<Guid, CryptoKey> _orgKeys; private IDictionary<string, CryptoKey> _orgKeys;
private byte[] _privateKey; private byte[] _privateKey;
public CryptoService( public CryptoService(
@@ -127,7 +127,7 @@ namespace Bit.App.Services
} }
} }
public IDictionary<Guid, CryptoKey> OrgKeys public IDictionary<string, CryptoKey> OrgKeys
{ {
get get
{ {
@@ -139,8 +139,8 @@ namespace Bit.App.Services
var orgKeysDictJson = Encoding.UTF8.GetString(orgKeysDictBytes, 0, orgKeysDictBytes.Length); var orgKeysDictJson = Encoding.UTF8.GetString(orgKeysDictBytes, 0, orgKeysDictBytes.Length);
if(!string.IsNullOrWhiteSpace(orgKeysDictJson)) if(!string.IsNullOrWhiteSpace(orgKeysDictJson))
{ {
_orgKeys = new Dictionary<Guid, CryptoKey>(); _orgKeys = new Dictionary<string, CryptoKey>();
var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<Guid, byte[]>>(orgKeysDictJson); var orgKeysDict = JsonConvert.DeserializeObject<IDictionary<string, byte[]>>(orgKeysDictJson);
foreach(var item in orgKeysDict) foreach(var item in orgKeysDict)
{ {
_orgKeys.Add(item.Key, new CryptoKey(item.Value)); _orgKeys.Add(item.Key, new CryptoKey(item.Value));
@@ -155,7 +155,7 @@ namespace Bit.App.Services
{ {
if(value != null && value.Any()) if(value != null && value.Any())
{ {
var dict = new Dictionary<Guid, byte[]>(); var dict = new Dictionary<string, byte[]>();
foreach(var item in value) foreach(var item in value)
{ {
dict.Add(item.Key, item.Value.Key); dict.Add(item.Key, item.Value.Key);
@@ -180,7 +180,7 @@ namespace Bit.App.Services
PrivateKey = bytes; PrivateKey = bytes;
} }
public CryptoKey GetOrgKey(Guid orgId) public CryptoKey GetOrgKey(string orgId)
{ {
if(OrgKeys == null || !OrgKeys.ContainsKey(orgId)) if(OrgKeys == null || !OrgKeys.ContainsKey(orgId))
{ {
@@ -190,7 +190,7 @@ namespace Bit.App.Services
return OrgKeys[orgId]; return OrgKeys[orgId];
} }
public void ClearOrgKey(Guid orgId) public void ClearOrgKey(string orgId)
{ {
var localOrgKeys = OrgKeys; var localOrgKeys = OrgKeys;
if(localOrgKeys == null || !localOrgKeys.ContainsKey(orgId)) if(localOrgKeys == null || !localOrgKeys.ContainsKey(orgId))
@@ -210,7 +210,7 @@ namespace Bit.App.Services
PrivateKey = null; PrivateKey = null;
} }
public CryptoKey AddOrgKey(Guid orgId, CipherString encOrgKey, byte[] privateKey) public CryptoKey AddOrgKey(string orgId, CipherString encOrgKey, byte[] privateKey)
{ {
try try
{ {

View File

@@ -125,7 +125,7 @@ namespace Bit.App.Services
continue; continue;
} }
var loginUriString = new CipherString(login.Uri).Decrypt(); var loginUriString = new CipherString(login.Uri).Decrypt(login.OrganizationId);
if(string.IsNullOrWhiteSpace(loginUriString)) if(string.IsNullOrWhiteSpace(loginUriString))
{ {
continue; continue;

View File

@@ -217,7 +217,7 @@ namespace Bit.App.Services
return true; return true;
} }
var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDate(); var accountRevisionDate = await _accountsApiRepository.GetAccountRevisionDateAsync();
if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue && if(accountRevisionDate.Succeeded && accountRevisionDate.Result.HasValue &&
accountRevisionDate.Result.Value > lastSync) accountRevisionDate.Result.Value > lastSync)
{ {

View File

@@ -7,10 +7,10 @@ namespace Bit.iOS.Extension.Models
public LoginViewModel(Login login) public LoginViewModel(Login login)
{ {
Id = login.Id; Id = login.Id;
Name = login.Name?.Decrypt(); Name = login.Name?.Decrypt(login.OrganizationId);
Username = login.Username?.Decrypt(); Username = login.Username?.Decrypt(login.OrganizationId);
Password = login.Password?.Decrypt(); Password = login.Password?.Decrypt(login.OrganizationId);
Uri = login.Uri?.Decrypt(); Uri = login.Uri?.Decrypt(login.OrganizationId);
} }
public string Id { get; set; } public string Id { get; set; }