1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-31 07:33:46 +00:00

Port send jslib to mobile (#1219)

* 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.
This commit is contained in:
Matt Gibson
2021-01-25 14:27:38 -06:00
committed by GitHub
parent 9b6bf136f1
commit 8d5614cd7b
52 changed files with 2046 additions and 38 deletions

View File

@@ -2,6 +2,7 @@
using Bit.Core.Enums;
using PCLCrypto;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static PCLCrypto.WinRTCrypto;
@@ -43,6 +44,61 @@ namespace Bit.Core.Services
return Task.FromResult(_cryptoPrimitiveService.Pbkdf2(password, salt, algorithm, iterations));
}
public async Task<byte[]> HkdfAsync(byte[] ikm, string salt, string info, int outputByteSize, HkdfAlgorithm algorithm) =>
await HkdfAsync(ikm, Encoding.UTF8.GetBytes(salt), Encoding.UTF8.GetBytes(info), outputByteSize, algorithm);
public async Task<byte[]> HkdfAsync(byte[] ikm, byte[] salt, string info, int outputByteSize, HkdfAlgorithm algorithm) =>
await HkdfAsync(ikm, salt, Encoding.UTF8.GetBytes(info), outputByteSize, algorithm);
public async Task<byte[]> HkdfAsync(byte[] ikm, string salt, byte[] info, int outputByteSize, HkdfAlgorithm algorithm) =>
await HkdfAsync(ikm, Encoding.UTF8.GetBytes(salt), info, outputByteSize, algorithm);
public async Task<byte[]> HkdfAsync(byte[] ikm, byte[] salt, byte[] info, int outputByteSize, HkdfAlgorithm algorithm)
{
var prk = await HmacAsync(ikm, salt, HkdfAlgorithmToCryptoHashAlgorithm(algorithm));
return await HkdfExpandAsync(prk, info, outputByteSize, algorithm);
}
public async Task<byte[]> HkdfExpandAsync(byte[] prk, string info, int outputByteSize, HkdfAlgorithm algorithm) =>
await HkdfExpandAsync(prk, Encoding.UTF8.GetBytes(info), outputByteSize, algorithm);
// ref: https://tools.ietf.org/html/rfc5869
public async Task<byte[]> HkdfExpandAsync(byte[] prk, byte[] info, int outputByteSize, HkdfAlgorithm algorithm)
{
var hashLen = algorithm == HkdfAlgorithm.Sha256 ? 32 : 64;
var maxOutputByteSize = 255 * hashLen;
if (outputByteSize > maxOutputByteSize)
{
throw new ArgumentException($"{nameof(outputByteSize)} is too large. Max is {maxOutputByteSize}, received {outputByteSize}");
}
if (prk.Length < hashLen)
{
throw new ArgumentException($"{nameof(prk)} length is too small. Must be at least {hashLen} for {algorithm}");
}
var cryptoHashAlgorithm = HkdfAlgorithmToCryptoHashAlgorithm(algorithm);
var previousT = new byte[0];
var runningOkmLength = 0;
var n = (int)Math.Ceiling((double)outputByteSize / hashLen);
var okm = new byte[n * hashLen];
for (var i = 0; i < n; i++)
{
var t = new byte[previousT.Length + info.Length + 1];
previousT.CopyTo(t, 0);
info.CopyTo(t, previousT.Length);
t[t.Length - 1] = (byte)(i + 1);
previousT = await HmacAsync(t, prk, cryptoHashAlgorithm);
previousT.CopyTo(okm, runningOkmLength);
runningOkmLength = previousT.Length;
if (runningOkmLength >= outputByteSize)
{
break;
}
}
return okm.Take(outputByteSize).ToArray();
}
public Task<byte[]> HashAsync(string value, CryptoHashAlgorithm algorithm)
{
return HashAsync(Encoding.UTF8.GetBytes(value), algorithm);
@@ -217,5 +273,18 @@ namespace Bit.Core.Services
.Replace("\n", " ") // New line => space
.Replace(" ", " "); // No-break space (00A0) => space
}
private CryptoHashAlgorithm HkdfAlgorithmToCryptoHashAlgorithm(HkdfAlgorithm hkdfAlgorithm)
{
switch (hkdfAlgorithm)
{
case HkdfAlgorithm.Sha256:
return CryptoHashAlgorithm.Sha256;
case HkdfAlgorithm.Sha512:
return CryptoHashAlgorithm.Sha512;
default:
throw new ArgumentException($"Invalid hkdf algorithm type, {hkdfAlgorithm}");
}
}
}
}