1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-28 22:23:35 +00:00

download, decrypt and open attachment files

This commit is contained in:
Kyle Spearrin
2017-07-12 23:09:44 -04:00
parent 0a7ad44d23
commit ac3fdbc2cd
26 changed files with 285 additions and 71 deletions

View File

@@ -1,7 +0,0 @@
namespace Bit.App.Abstractions
{
public interface IClipboardService
{
void CopyToClipboard(string text);
}
}

View File

@@ -19,6 +19,7 @@ namespace Bit.App.Abstractions
void ClearKeys();
string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null);
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt);

View File

@@ -0,0 +1,8 @@
namespace Bit.App.Abstractions
{
public interface IDeviceActionService
{
void CopyToClipboard(string text);
bool OpenFile(byte[] fileData, string id, string fileName);
}
}

View File

@@ -14,5 +14,6 @@ namespace Bit.App.Abstractions
Task<Tuple<IEnumerable<Login>, IEnumerable<Login>>> GetAllAsync(string uriString);
Task<ApiResult<LoginResponse>> SaveAsync(Login login);
Task<ApiResult> DeleteAsync(string id);
Task<byte[]> DownloadAndDecryptAttachmentAsync(SymmetricCryptoKey key, string url);
}
}

View File

@@ -51,7 +51,7 @@
<Compile Include="Abstractions\Services\IAppInfoService.cs" />
<Compile Include="Abstractions\Services\IAppIdService.cs" />
<Compile Include="Abstractions\Services\IAuthService.cs" />
<Compile Include="Abstractions\Services\IClipboardService.cs" />
<Compile Include="Abstractions\Services\IDeviceActionService.cs" />
<Compile Include="Abstractions\Services\IKeyDerivationService.cs" />
<Compile Include="Abstractions\Services\ILogService.cs" />
<Compile Include="Abstractions\Services\IReflectionService.cs" />

View File

@@ -223,7 +223,8 @@ namespace Bit.App.Models.Page
{
Id = attachment.Id,
Name = attachment.FileName?.Decrypt(login.OrganizationId),
Size = attachment.SizeName
Size = attachment.SizeName,
Url = attachment.Url
});
}
Attachments = attachments;
@@ -239,6 +240,7 @@ namespace Bit.App.Models.Page
public string Id { get; set; }
public string Name { get; set; }
public string Size { get; set; }
public string Url { get; set; }
}
}
}

View File

@@ -16,7 +16,7 @@ namespace Bit.App.Pages
private readonly IUserDialogs _userDialogs;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISettings _settings;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _clipboardService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly Action<string> _passwordValueAction;
private readonly bool _fromAutofill;
@@ -26,7 +26,7 @@ namespace Bit.App.Pages
_userDialogs = Resolver.Resolve<IUserDialogs>();
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
_settings = Resolver.Resolve<ISettings>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_passwordValueAction = passwordValueAction;
_fromAutofill = fromAutofill;

View File

@@ -19,7 +19,7 @@ namespace Bit.App.Pages
{
private readonly ILoginService _loginService;
private readonly IDeviceInfoService _deviceInfoService;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _clipboardService;
private readonly ISettingsService _settingsService;
private CancellationTokenSource _filterResultsCancellationTokenSource;
private readonly string _name;
@@ -47,7 +47,7 @@ namespace Bit.App.Pages
_loginService = Resolver.Resolve<ILoginService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_settingsService = Resolver.Resolve<ISettingsService>();
UserDialogs = Resolver.Resolve<IUserDialogs>();
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();

View File

@@ -24,7 +24,7 @@ namespace Bit.App.Pages
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _clipboardService;
private readonly ISyncService _syncService;
private readonly IPushNotification _pushNotification;
private readonly IDeviceInfoService _deviceInfoService;
@@ -41,7 +41,7 @@ namespace Bit.App.Pages
_loginService = Resolver.Resolve<ILoginService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_syncService = Resolver.Resolve<ISyncService>();
_pushNotification = Resolver.Resolve<IPushNotification>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();

View File

@@ -16,14 +16,14 @@ namespace Bit.App.Pages
private readonly string _loginId;
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _deviceActionService;
public VaultViewLoginPage(string loginId)
{
_loginId = loginId;
_loginService = Resolver.Resolve<ILoginService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
Init();
}
@@ -194,9 +194,9 @@ namespace Bit.App.Pages
AttachmentsSection = new TableSection(AppResources.Attachments);
foreach(var attachment in Model.Attachments)
{
AttachmentsSection.Add(new AttachmentViewCell(attachment, () =>
AttachmentsSection.Add(new AttachmentViewCell(attachment, async () =>
{
await SaveAttachmentAsync(attachment);
}));
}
Table.Root.Add(AttachmentsSection);
@@ -211,6 +211,23 @@ namespace Bit.App.Pages
EditItem.Dispose();
}
private async Task SaveAttachmentAsync(VaultViewLoginPageModel.Attachment attachment)
{
var data = await _loginService.DownloadAndDecryptAttachmentAsync(null, attachment.Url);
if(data == null)
{
await _userDialogs.AlertAsync(AppResources.UnableToDownloadFile, null, AppResources.Ok);
return;
}
var opened = _deviceActionService.OpenFile(data, attachment.Id, attachment.Name);
if(!opened)
{
await _userDialogs.AlertAsync(AppResources.UnableToOpenFile, null, AppResources.Ok);
return;
}
}
private void NotesCell_Tapped(object sender, EventArgs e)
{
Copy(Model.Notes, AppResources.Notes);
@@ -218,7 +235,7 @@ namespace Bit.App.Pages
private void Copy(string copyText, string alertLabel)
{
_clipboardService.CopyToClipboard(copyText);
_deviceActionService.CopyToClipboard(copyText);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}

View File

@@ -1987,6 +1987,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Unable to download file..
/// </summary>
public static string UnableToDownloadFile {
get {
return ResourceManager.GetString("UnableToDownloadFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to open this type of file on your device..
/// </summary>
public static string UnableToOpenFile {
get {
return ResourceManager.GetString("UnableToOpenFile", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unlock with {0}.
/// </summary>

View File

@@ -908,4 +908,10 @@
<data name="Attachments" xml:space="preserve">
<value>Attachments</value>
</data>
<data name="UnableToDownloadFile" xml:space="preserve">
<value>Unable to download file.</value>
</data>
<data name="UnableToOpenFile" xml:space="preserve">
<value>Unable to open this type of file on your device.</value>
</data>
</root>

View File

@@ -297,18 +297,61 @@ namespace Bit.App.Services
// Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
{
_legacyEtmKey = new SymmetricCryptoKey(key.Key, Enums.EncryptionType.AesCbc128_HmacSha256_B64);
_legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64);
}
key = _legacyEtmKey;
}
if(encyptedValue.EncryptionType != key.EncryptionType)
return Crypto.AesCbcDecrypt(encyptedValue, key);
}
public byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null)
{
if(key == null)
{
throw new ArgumentException("encType unavailable.");
key = EncKey ?? Key;
}
return Crypto.AesCbcDecrypt(encyptedValue, key);
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null || encyptedValue.Length == 0)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
byte[] ct, iv, mac = null;
var encType = (EncryptionType)encyptedValue[0];
switch(encType)
{
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encyptedValue.Length <= 49)
{
throw new InvalidOperationException("Invalid value length.");
}
iv = new ArraySegment<byte>(encyptedValue, 1, 16).ToArray();
mac = new ArraySegment<byte>(encyptedValue, 17, 32).ToArray();
ct = new ArraySegment<byte>(encyptedValue, 49, encyptedValue.Length - 49).ToArray();
break;
case EncryptionType.AesCbc256_B64:
if(encyptedValue.Length <= 17)
{
throw new InvalidOperationException("Invalid value length.");
}
iv = new ArraySegment<byte>(encyptedValue, 1, 16).ToArray();
ct = new ArraySegment<byte>(encyptedValue, 17, encyptedValue.Length - 17).ToArray();
break;
default:
throw new InvalidOperationException("Invalid encryption type.");
}
return Crypto.AesCbcDecrypt(encType, ct, iv, mac, key);
}
public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey)

View File

@@ -7,6 +7,7 @@ using Bit.App.Models;
using Bit.App.Models.Api;
using Bit.App.Models.Data;
using Xamarin.Forms;
using System.Net.Http;
namespace Bit.App.Services
{
@@ -17,19 +18,22 @@ namespace Bit.App.Services
private readonly IAuthService _authService;
private readonly ILoginApiRepository _loginApiRepository;
private readonly ISettingsService _settingsService;
private readonly ICryptoService _cryptoService;
public LoginService(
ILoginRepository loginRepository,
IAttachmentRepository attachmentRepository,
IAuthService authService,
ILoginApiRepository loginApiRepository,
ISettingsService settingsService)
ISettingsService settingsService,
ICryptoService cryptoService)
{
_loginRepository = loginRepository;
_attachmentRepository = attachmentRepository;
_authService = authService;
_loginApiRepository = loginApiRepository;
_settingsService = settingsService;
_cryptoService = cryptoService;
}
public async Task<Login> GetByIdAsync(string id)
@@ -217,6 +221,33 @@ namespace Bit.App.Services
return response;
}
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(SymmetricCryptoKey key, string url)
{
using(var client = new HttpClient())
{
try
{
var response = await client.GetAsync(new Uri(url)).ConfigureAwait(false);
if(!response.IsSuccessStatusCode)
{
return null;
}
var data = await response.Content.ReadAsByteArrayAsync();
if(data == null)
{
return null;
}
return _cryptoService.DecryptToBytes(data, key);
}
catch
{
return null;
}
}
}
private string WebUriFromAndroidAppUri(string androidAppUriString)
{
if(!UriIsAndroidApp(androidAppUriString))

View File

@@ -1,4 +1,5 @@
using Bit.App.Models;
using Bit.App.Enums;
using Bit.App.Models;
using PCLCrypto;
using System;
using System.Collections.Generic;
@@ -32,21 +33,41 @@ namespace Bit.App.Utilities
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
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)
{
if(key == null)
{
var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes, key.MacKey);
if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes))
throw new ArgumentNullException(nameof(key));
}
if(ct == null)
{
throw new ArgumentNullException(nameof(ct));
}
if(iv == null)
{
throw new ArgumentNullException(nameof(iv));
}
if(key.EncryptionType != type)
{
throw new InvalidOperationException(nameof(type));
}
if(key.MacKey != null && mac != null)
{
var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
if(!MacsEqual(key.MacKey, computedMacBytes, mac))
{
throw new InvalidOperationException("MAC failed.");
}
@@ -54,8 +75,7 @@ namespace Bit.App.Utilities
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
encyptedValue.InitializationVectorBytes);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
return decryptedBytes;
}