Skip to content

Commit

Permalink
Port send jslib to mobile (#1219)
Browse files Browse the repository at this point in the history
* Expand Hkdf crypto functions

* Add tests for hkdf crypto functions

Took the testing infrastructure from bitwarden/server

* Move Hkdf to cryptoFunctionService

* Port changes from bitwarden/jslib#192

* Port changes from bitwarden/jslib#205

* Make Send Expiration Optional implement changes from bitwarden/jslib#242

* Bug fixes found by testing

* Test helpers

* Test conversion between model types

* Test SendService

These are mostly happy-path tests to ensure a reasonably correct
implementation

* Add run tests step to GitHub Actions

* Test send decryption

* Test Request generation from Send

* Constructor dependencies on separate lines

* Remove unused testing infrastructure

* Rename to match class name

* Move fat arrows to previous lines

* Handle exceptions in App layer

* PR review cleanups

* Throw when attempting to save an unkown Send Type

I think it's best to only throw on unknown send types here.
I don't think we want to throw whenever we encounter one since that would
do bad things like lock up Sync if clients get out of date relative to
servers. Instead, keep the client from ruining saved data by complaining
last minute that it doesn't know what it's doing.
  • Loading branch information
MGibson1 authored Jan 25, 2021
1 parent 9b6bf13 commit 8d5614c
Show file tree
Hide file tree
Showing 52 changed files with 2,046 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ jobs:
- name: Restore packages
run: nuget restore

- name: Run Core Tests
run: dotnet test test/Core.Test/Core.Test.csproj

- name: Build Play Store publisher
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release

Expand Down
66 changes: 66 additions & 0 deletions bitwarden-mobile.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Ex
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "test\Core.Test\Core.Test.csproj", "{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
Expand Down Expand Up @@ -351,6 +355,66 @@ Global
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.Build.0 = Release|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|Any CPU.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhone.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhone.Build.0 = Debug|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|Any CPU.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhone.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhone.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|Any CPU.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhone.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhone.Build.0 = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|Any CPU.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhone.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhone.Build.0 = Debug|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|Any CPU.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhone.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhone.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|Any CPU.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhone.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhone.Build.0 = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -366,6 +430,8 @@ Global
{599E0201-420A-4C3E-A7BA-5349F72E0B15} = {D10CA4A9-F866-40E1-B658-F69051236C71}
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {D10CA4A9-F866-40E1-B658-F69051236C71}
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A} = {D10CA4A9-F866-40E1-B658-F69051236C71}
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39} = {8904C536-C67D-420F-9971-51B26574C3AA}
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0} = {8904C536-C67D-420F-9971-51B26574C3AA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
Expand Down
2 changes: 1 addition & 1 deletion src/Android/Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\App\App.csproj">
<Project>{9F1742A7-7D03-4BB3-8FCD-41BC3002B00A}</Project>
<Project>{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}</Project>
<Name>App</Name>
</ProjectReference>
<ProjectReference Include="..\Core\Core.csproj">
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Abstractions/IApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,12 @@ Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFor
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
Task PostEventsCollectAsync(IEnumerable<EventRequest> request);

Task<SendResponse> GetSendAsync(string id);
Task<SendResponse> PostSendAsync(SendRequest request);
Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data);
Task<SendResponse> PutSendAsync(string id, SendRequest request);
Task<SendResponse> PutSendRemovePasswordAsync(string id);
Task DeleteSendAsync(string id);
}
}
6 changes: 6 additions & 0 deletions src/Core/Abstractions/ICryptoFunctionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public interface ICryptoFunctionService
Task<byte[]> Pbkdf2Async(byte[] password, string salt, CryptoHashAlgorithm algorithm, int iterations);
Task<byte[]> Pbkdf2Async(string password, byte[] salt, CryptoHashAlgorithm algorithm, int iterations);
Task<byte[]> Pbkdf2Async(byte[] password, byte[] salt, CryptoHashAlgorithm algorithm, int iterations);
Task<byte[]> HkdfAsync(byte[] ikm, string salt, string info, int outputByteSize, HkdfAlgorithm algorithm);
Task<byte[]> HkdfAsync(byte[] ikm, byte[] salt, string info, int outputByteSize, HkdfAlgorithm algorithm);
Task<byte[]> HkdfAsync(byte[] ikm, string salt, byte[] info, int outputByteSize, HkdfAlgorithm algorithm);
Task<byte[]> HkdfAsync(byte[] ikm, byte[] salt, byte[] info, int outputByteSize, HkdfAlgorithm algorithm);
Task<byte[]> HkdfExpandAsync(byte[] prk, string info, int outputByteSize, HkdfAlgorithm algorithm);
Task<byte[]> HkdfExpandAsync(byte[] prk, byte[] info, int outputByteSize, HkdfAlgorithm algorithm);
Task<byte[]> HashAsync(string value, CryptoHashAlgorithm algorithm);
Task<byte[]> HashAsync(byte[] value, CryptoHashAlgorithm algorithm);
Task<byte[]> HmacAsync(byte[] value, byte[] key, CryptoHashAlgorithm algorithm);
Expand Down
1 change: 1 addition & 0 deletions src/Core/Abstractions/ICryptoService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kd
Task<Tuple<string, CipherString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations);
Task<Tuple<CipherString, SymmetricCryptoKey>> MakeShareKeyAsync();
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
Task<int> RandomNumberAsync(int min, int max);
Task<Tuple<SymmetricCryptoKey, CipherString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
Task<CipherString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
Expand Down
25 changes: 25 additions & 0 deletions src/Core/Abstractions/ISendService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;

namespace Bit.Core.Abstractions
{
public interface ISendService
{
void ClearCache();
Task<(Send send, CipherString encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, string password,
SymmetricCryptoKey key = null);
Task<Send> GetAsync(string id);
Task<List<Send>> GetAllAsync();
Task<List<SendView>> GetAllDecryptedAsync();
Task SaveWithServerAsync(Send sendData, byte[] encryptedFileData);
Task UpsertAsync(params SendData[] send);
Task ReplaceAsync(Dictionary<string, SendData> sends);
Task ClearAsync(string userId);
Task DeleteAsync(params string[] ids);
Task DeleteWithServerAsync(string id);
Task RemovePasswordWithServerAsync(string id);
}
}
8 changes: 8 additions & 0 deletions src/Core/Enums/HdkfAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum HkdfAlgorithm : byte
{
Sha256 = 1,
Sha512 = 2,
}
}
8 changes: 8 additions & 0 deletions src/Core/Enums/SendType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum SendType
{
Text = 0,
File = 1,
}
}
12 changes: 12 additions & 0 deletions src/Core/Models/Api/SendFileApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Bit.Core.Models.Api
{
public class SendFileApi
{
public string Id { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
public string Key { get; set; }
public string Size { get; set; }
public string SizeName { get; set; }
}
}
8 changes: 8 additions & 0 deletions src/Core/Models/Api/SendTextApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Bit.Core.Models.Api
{
public class SendTextApi
{
public string Text { get; set; }
public bool Hidden { get; set; }
}
}
58 changes: 58 additions & 0 deletions src/Core/Models/Data/SendData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using Bit.Core.Enums;
using Bit.Core.Models.Response;

namespace Bit.Core.Models.Data
{
public class SendData : Data
{
public SendData() { }

public SendData(SendResponse response, string userId)
{
Id = response.Id;
AccessId = response.AccessId;
UserId = userId;
Type = response.Type;
Name = response.Name;
Notes = response.Notes;
Key = response.Key;
MaxAccessCount = response.MaxAccessCount;
AccessCount = response.AccessCount;
RevisionDate = response.RevisionDate;
ExpirationDate = response.ExpirationDate;
DeletionDate = response.DeletionDate;
Password = response.Password;
Disabled = response.Disabled;

switch (Type)
{
case SendType.File:
File = new SendFileData(response.File);
break;
case SendType.Text:
Text = new SendTextData(response.Text);
break;
default:
break;
}
}

public string Id { get; set; }
public string AccessId { get; set; }
public string UserId { get; set; }
public SendType Type { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public SendFileData File { get; set; }
public SendTextData Text { get; set; }
public string Key { get; set; }
public int? MaxAccessCount { get; set; }
public int AccessCount { get; set; }
public DateTime RevisionDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public DateTime DeletionDate { get; set; }
public string Password { get; set; }
public bool Disabled { get; set; }
}
}
27 changes: 27 additions & 0 deletions src/Core/Models/Data/SendFileData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Drawing;
using Bit.Core.Models.Api;

namespace Bit.Core.Models.Data
{
public class SendFileData : Data
{
public SendFileData() { }

public SendFileData(SendFileApi data)
{
Id = data.Id;
Url = data.Url;
FileName = data.FileName;
Key = data.Key;
Size = data.Size;
SizeName = data.SizeName;
}

public string Id { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
public string Key { get; set; }
public string Size { get; set; }
public string SizeName { get; set; }
}
}
19 changes: 19 additions & 0 deletions src/Core/Models/Data/SendTextData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Drawing;
using Bit.Core.Models.Api;

namespace Bit.Core.Models.Data
{
public class SendTextData : Data
{
public SendTextData() { }

public SendTextData(SendTextApi data)
{
Text = data.Text;
Hidden = data.Hidden;
}

public string Text { get; set; }
public bool Hidden { get; set; }
}
}
9 changes: 6 additions & 3 deletions src/Core/Models/Domain/CipherString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public CipherString(string encryptedString)
public string Data { get; private set; }
public string Mac { get; private set; }

public async Task<string> DecryptAsync(string orgId = null)
public async Task<string> DecryptAsync(string orgId = null, SymmetricCryptoKey key = null)
{
if (_decryptedValue != null)
{
Expand All @@ -109,8 +109,11 @@ public async Task<string> DecryptAsync(string orgId = null)
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
try
{
var orgKey = await cryptoService.GetOrgKeyAsync(orgId);
_decryptedValue = await cryptoService.DecryptToUtf8Async(this, orgKey);
if (key == null)
{
key = await cryptoService.GetOrgKeyAsync(orgId);
}
_decryptedValue = await cryptoService.DecryptToUtf8Async(this, key);
}
catch
{
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/Domain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected void BuildDataModel<D, O>(D domain, O dataObj, HashSet<string> map,
}
}

protected async Task<V> DecryptObjAsync<V, D>(V viewModel, D domain, HashSet<string> map, string orgId)
protected async Task<V> DecryptObjAsync<V, D>(V viewModel, D domain, HashSet<string> map, string orgId, SymmetricCryptoKey key = null)
where V : View.View
{
var viewModelType = viewModel.GetType();
Expand All @@ -64,7 +64,7 @@ async Task decCsAndSetDec(string propName)
string val = null;
if (domainPropInfo.GetValue(domain) is CipherString domainProp)
{
val = await domainProp.DecryptAsync(orgId);
val = await domainProp.DecryptAsync(orgId, key);
}
var viewModelPropInfo = viewModelType.GetProperty(propName);
viewModelPropInfo.SetValue(viewModel, val, null);
Expand Down
Loading

0 comments on commit 8d5614c

Please sign in to comment.