2017-07-13 06:09:44 +03:00
|
|
|
|
using Bit.App.Enums;
|
|
|
|
|
using Bit.App.Models;
|
2017-06-06 04:04:19 +03:00
|
|
|
|
using PCLCrypto;
|
|
|
|
|
using System;
|
2017-06-19 18:57:37 +03:00
|
|
|
|
using System.Collections.Generic;
|
2017-06-06 04:04:19 +03:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace Bit.App.Utilities
|
|
|
|
|
{
|
|
|
|
|
public static class Crypto
|
|
|
|
|
{
|
2018-11-30 01:10:51 +03:00
|
|
|
|
private static string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
|
|
|
|
|
2017-06-06 04:04:19 +03:00
|
|
|
|
public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
|
2017-07-22 22:38:08 +03:00
|
|
|
|
{
|
|
|
|
|
var parts = AesCbcEncryptToParts(plainBytes, key);
|
|
|
|
|
return new CipherString(parts.Item1, Convert.ToBase64String(parts.Item2),
|
2017-07-23 07:10:32 +03:00
|
|
|
|
Convert.ToBase64String(parts.Item4), parts.Item3 != null ? Convert.ToBase64String(parts.Item3) : null);
|
2017-07-22 22:38:08 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] AesCbcEncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key)
|
|
|
|
|
{
|
|
|
|
|
var parts = AesCbcEncryptToParts(plainBytes, key);
|
2017-07-23 06:22:21 +03:00
|
|
|
|
var macLength = parts.Item3?.Length ?? 0;
|
2017-07-22 22:38:08 +03:00
|
|
|
|
|
2017-07-23 06:22:21 +03:00
|
|
|
|
var encBytes = new byte[1 + parts.Item2.Length + macLength + parts.Item4.Length];
|
2017-07-22 22:38:08 +03:00
|
|
|
|
encBytes[0] = (byte)parts.Item1;
|
|
|
|
|
parts.Item2.CopyTo(encBytes, 1);
|
2017-07-23 06:22:21 +03:00
|
|
|
|
if(parts.Item3 != null)
|
|
|
|
|
{
|
|
|
|
|
parts.Item3.CopyTo(encBytes, 1 + parts.Item2.Length);
|
|
|
|
|
}
|
|
|
|
|
parts.Item4.CopyTo(encBytes, 1 + parts.Item2.Length + macLength);
|
2017-07-22 22:38:08 +03:00
|
|
|
|
return encBytes;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-23 06:22:21 +03:00
|
|
|
|
private static Tuple<EncryptionType, byte[], byte[], byte[]> AesCbcEncryptToParts(byte[] plainBytes,
|
2017-07-22 22:38:08 +03:00
|
|
|
|
SymmetricCryptoKey key)
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
|
|
|
|
if(key == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(key));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(plainBytes == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(plainBytes));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
|
|
|
|
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
|
|
|
|
|
var iv = RandomBytes(provider.BlockLength);
|
2017-07-22 22:38:08 +03:00
|
|
|
|
var ct = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
|
|
|
|
|
var mac = key.MacKey != null ? ComputeMac(ct, iv, key.MacKey) : null;
|
2017-06-06 04:04:19 +03:00
|
|
|
|
|
2017-07-22 22:38:08 +03:00
|
|
|
|
return new Tuple<EncryptionType, byte[], byte[], byte[]>(key.EncryptionType, iv, mac, ct);
|
2017-06-06 04:04:19 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
|
2017-07-13 06:09:44 +03:00
|
|
|
|
{
|
|
|
|
|
if(encyptedValue == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(encyptedValue));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AesCbcDecrypt(encyptedValue.EncryptionType, encyptedValue.CipherTextBytes,
|
|
|
|
|
encyptedValue.InitializationVectorBytes, encyptedValue.MacBytes, key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] AesCbcDecrypt(EncryptionType type, byte[] ct, byte[] iv, byte[] mac, SymmetricCryptoKey key)
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
|
|
|
|
if(key == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(key));
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 06:09:44 +03:00
|
|
|
|
if(ct == null)
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
2017-07-13 06:09:44 +03:00
|
|
|
|
throw new ArgumentNullException(nameof(ct));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(iv == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(iv));
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-18 16:39:34 +03:00
|
|
|
|
if(key.MacKey != null && mac == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(mac));
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 06:09:44 +03:00
|
|
|
|
if(key.EncryptionType != type)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException(nameof(type));
|
2017-06-06 04:04:19 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 06:09:44 +03:00
|
|
|
|
if(key.MacKey != null && mac != null)
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
2017-07-13 06:09:44 +03:00
|
|
|
|
var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
|
2018-01-18 17:31:22 +03:00
|
|
|
|
if(!MacsEqual(computedMacBytes, mac))
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("MAC failed.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
|
|
|
|
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
|
2017-07-13 06:09:44 +03:00
|
|
|
|
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
|
2017-06-06 04:04:19 +03:00
|
|
|
|
return decryptedBytes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] RandomBytes(int length)
|
|
|
|
|
{
|
|
|
|
|
return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-19 18:57:37 +03:00
|
|
|
|
public static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
|
|
|
|
if(ctBytes == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(ctBytes));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(ivBytes == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(ivBytes));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-19 18:57:37 +03:00
|
|
|
|
return ComputeMac(ivBytes.Concat(ctBytes), macKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] ComputeMac(IEnumerable<byte> dataBytes, byte[] macKey)
|
|
|
|
|
{
|
|
|
|
|
if(macKey == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(macKey));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(dataBytes == null)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentNullException(nameof(dataBytes));
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-06 04:04:19 +03:00
|
|
|
|
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
|
|
|
|
|
var hasher = algorithm.CreateHash(macKey);
|
2017-06-19 18:57:37 +03:00
|
|
|
|
hasher.Append(dataBytes.ToArray());
|
2017-06-06 04:04:19 +03:00
|
|
|
|
var mac = hasher.GetValueAndReset();
|
|
|
|
|
return mac;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
|
|
|
|
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
2018-01-18 17:31:22 +03:00
|
|
|
|
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
|
|
|
|
public static bool MacsEqual(byte[] mac1, byte[] mac2)
|
2017-06-06 04:04:19 +03:00
|
|
|
|
{
|
|
|
|
|
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
|
2018-01-18 17:31:22 +03:00
|
|
|
|
var hasher = algorithm.CreateHash(RandomBytes(32));
|
2017-06-06 04:04:19 +03:00
|
|
|
|
|
|
|
|
|
hasher.Append(mac1);
|
|
|
|
|
mac1 = hasher.GetValueAndReset();
|
|
|
|
|
|
|
|
|
|
hasher.Append(mac2);
|
|
|
|
|
mac2 = hasher.GetValueAndReset();
|
|
|
|
|
|
|
|
|
|
if(mac1.Length != mac2.Length)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < mac2.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if(mac1[i] != mac2[i])
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2017-07-13 21:44:02 +03:00
|
|
|
|
|
|
|
|
|
// ref: https://github.com/mirthas/totp-net/blob/master/TOTP/Totp.cs
|
2018-07-31 19:34:07 +03:00
|
|
|
|
public static string Totp(string key)
|
2017-07-13 21:44:02 +03:00
|
|
|
|
{
|
2018-07-31 19:34:07 +03:00
|
|
|
|
var otpParams = new OtpAuth(key);
|
|
|
|
|
var b32Key = Base32.FromBase32(otpParams.Secret);
|
|
|
|
|
if(b32Key == null || b32Key.Length == 0)
|
2017-07-13 21:44:02 +03:00
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var now = Helpers.EpocUtcNow() / 1000;
|
2018-07-31 19:34:07 +03:00
|
|
|
|
var sec = now / otpParams.Period;
|
2017-07-13 21:44:02 +03:00
|
|
|
|
|
|
|
|
|
var secBytes = BitConverter.GetBytes(sec);
|
|
|
|
|
if(BitConverter.IsLittleEndian)
|
|
|
|
|
{
|
|
|
|
|
Array.Reverse(secBytes, 0, secBytes.Length);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-31 19:34:07 +03:00
|
|
|
|
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(otpParams.Algorithm);
|
|
|
|
|
var hasher = algorithm.CreateHash(b32Key);
|
2017-07-13 21:44:02 +03:00
|
|
|
|
hasher.Append(secBytes);
|
|
|
|
|
var hash = hasher.GetValueAndReset();
|
|
|
|
|
|
|
|
|
|
var offset = (hash[hash.Length - 1] & 0xf);
|
2018-07-31 19:34:07 +03:00
|
|
|
|
var binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) |
|
2017-07-13 21:44:02 +03:00
|
|
|
|
((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
|
|
|
|
|
2018-11-30 01:10:51 +03:00
|
|
|
|
string otp = string.Empty;
|
|
|
|
|
if(otpParams.Steam)
|
|
|
|
|
{
|
|
|
|
|
var fullCode = binary & 0x7fffffff;
|
|
|
|
|
for(var i = 0; i < otpParams.Digits; i++)
|
|
|
|
|
{
|
|
|
|
|
otp += SteamChars[fullCode % SteamChars.Length];
|
|
|
|
|
fullCode = (int)Math.Truncate(fullCode / (double)SteamChars.Length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var rawOtp = binary % (int)Math.Pow(10, otpParams.Digits);
|
|
|
|
|
otp = rawOtp.ToString().PadLeft(otpParams.Digits, '0');
|
|
|
|
|
}
|
|
|
|
|
return otp;
|
2017-07-13 21:44:02 +03:00
|
|
|
|
}
|
2018-06-14 05:41:18 +03:00
|
|
|
|
|
|
|
|
|
// ref: https://tools.ietf.org/html/rfc5869
|
|
|
|
|
public static byte[] HkdfExpand(byte[] prk, byte[] info, int size)
|
|
|
|
|
{
|
|
|
|
|
var hashLen = 32; // sha256
|
|
|
|
|
var okm = new byte[size];
|
|
|
|
|
var previousT = new byte[0];
|
|
|
|
|
var n = (int)Math.Ceiling((double)size / hashLen);
|
|
|
|
|
for(int 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 = ComputeMac(t, prk);
|
|
|
|
|
previousT.CopyTo(okm, i * hashLen);
|
|
|
|
|
}
|
|
|
|
|
return okm;
|
|
|
|
|
}
|
2017-06-06 04:04:19 +03:00
|
|
|
|
}
|
|
|
|
|
}
|