1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

78 Commits

Author SHA1 Message Date
Kyle Spearrin
051e15215d bump version 2019-12-26 15:15:15 -05:00
proletarius101
fee8f58c0a Add support for Tor browser (#680) 2019-12-26 07:29:20 -05:00
Kyle Spearrin
cc036cf3c5 Set lock page on resume for android 2019-12-16 09:43:14 -05:00
Kyle Spearrin
4e51517ddb update libs 2019-12-16 09:14:54 -05:00
Kyle Spearrin
3b7454961d New Crowdin translations (#663)
* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Polish)

* New translations copy.resx (Persian)

* New translations copy.resx (Persian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (German)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (English, United Kingdom)

* New translations copy.resx (Dutch)

* New translations copy.resx (Dutch)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Ukrainian)
2019-12-03 11:35:09 -05:00
Kyle Spearrin
88009e1a63 bump versions 2019-12-03 11:11:30 -05:00
Kyle Spearrin
0afca29b0c still load list if there are any ciphers 2019-11-22 09:51:30 -05:00
Kyle Spearrin
46a75a2944 Revert "try new http client handler for icons"
This reverts commit c099f82752.
2019-11-22 08:24:23 -05:00
Kyle Spearrin
c099f82752 try new http client handler for icons 2019-11-20 17:38:05 -05:00
Kyle Spearrin
1da94bd9c8 completed status on track release 2019-11-20 10:39:15 -05:00
Kyle Spearrin
96ce8165e9 upgrade to v3 publisher apis 2019-11-20 09:57:40 -05:00
Kyle Spearrin
f9b617339d try builds with VS 2019 preview 2019-11-20 08:25:52 -05:00
Kyle Spearrin
58084810f3 don't auto-capitalize password field when viewed 2019-11-20 08:24:08 -05:00
Kyle Spearrin
429e62e6b5 Don't index a "never" uri match. 2019-11-19 07:47:01 -05:00
Kyle Spearrin
b0b7f2afdf Merge branch 'master' of github.com:bitwarden/mobile 2019-11-15 08:55:31 -05:00
Kyle Spearrin
55f160d125 Show exception message from Api errors 2019-11-15 08:55:22 -05:00
Wasim Malik
f6352f5392 Updated Incorrect Check Condition (#648)
Issue is fixed No 361
Now tested on android device extra comma is not appended anymore
2019-11-12 16:28:12 -05:00
Kyle Spearrin
ac7e90c0aa blacklist com.android.settings from autofill 2019-11-08 11:57:07 -05:00
Kyle Spearrin
88fccfd6cd try setting http version 1.0 2019-11-05 09:14:55 -05:00
Kyle Spearrin
5fdf8e6045 set fingerprint lock prop on login 2019-11-05 09:03:48 -05:00
Kyle Spearrin
d9907cdbeb style fixes for xamarin forms 4 2019-10-30 09:30:45 -04:00
Kyle Spearrin
d308f1ca3b update libs and to xamarin forms 4 2019-10-30 08:27:40 -04:00
Kyle Spearrin
0cdc138ba3 dont immediatly prompt biometric when locked 2019-10-30 08:08:15 -04:00
Kyle Spearrin
59d5314164 New Crowdin translations (#639)
* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Vietnamese)
2019-10-23 20:55:51 -04:00
Kyle Spearrin
9c08a37772 UseNativeBiometric only for SDK 29 2019-10-23 11:54:53 -04:00
Kyle Spearrin
b13f5356fe FingerprintManager to detect fingerprints on SDK 28 2019-10-23 11:26:00 -04:00
Kyle Spearrin
5f0c9725ce bump version 2019-10-23 09:27:31 -04:00
Kyle Spearrin
f951fea555 use bio strings for native android bio 2019-10-23 09:24:34 -04:00
Kyle Spearrin
4b989b01e9 use native biomatrics on Android 2019-10-23 09:11:48 -04:00
Kyle Spearrin
aed3ec5474 check authed and unlocked before trying to load 2019-10-22 16:42:05 -04:00
Kyle Spearrin
b354986199 null check apiexception error 2019-10-22 16:37:40 -04:00
Kyle Spearrin
e1983a7d66 fix error when login token expires 2019-10-22 16:30:28 -04:00
Kyle Spearrin
0400d79f43 android 10 and bio permission 2019-10-18 20:41:04 -04:00
Kyle Spearrin
c911484632 upgrade builds to vs 2019 2019-10-18 14:21:07 -04:00
Kyle Spearrin
713e441d2e upgrade to android 10 sdk 2019-10-18 14:19:56 -04:00
Kyle Spearrin
d4b577732b npm audit fix 2019-10-17 08:01:50 -04:00
Kyle Spearrin
440a410d7f skip com.treydev.pns 2019-10-17 08:00:58 -04:00
Kyle Spearrin
37a536b138 catch thrown sync errors from ui 2019-10-15 11:05:56 -04:00
Kyle Spearrin
a0aca3e837 add tf browser 2019-10-11 09:29:33 -04:00
Kyle Spearrin
b58c29111a bump version 2019-10-07 09:42:55 -04:00
Kyle Spearrin
b0f86ea161 focus search bar on appear 2019-10-07 09:23:41 -04:00
Kyle Spearrin
93b59a75a4 remove migration code 2019-10-05 21:36:47 -04:00
Kyle Spearrin
54fcabaea6 shorter delays 2019-10-05 21:31:55 -04:00
Kyle Spearrin
0e966c0304 fix min character assignments for pw gen 2019-10-05 20:39:42 -04:00
Kyle Spearrin
a363712127 use black text for search bar on light theme 2019-10-04 09:32:59 -04:00
Kyle Spearrin
4d8c665917 fix light theme 2019-10-04 09:11:14 -04:00
kspearrin
53bdd92e72 support dark theme default on extensions 2019-09-30 21:40:05 -04:00
Kyle Spearrin
e51aa39ede New Crowdin translations (#614)
* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Croatian)

* New translations copy.resx (German)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Dutch)

* New translations copy.resx (Dutch)

* New translations copy.resx (Dutch)
2019-09-30 21:35:24 -04:00
Kyle Spearrin
33c82129ff bump version 2019-09-30 21:19:20 -04:00
kspearrin
7c5b8c0e9f modal full screen 2019-09-30 21:17:53 -04:00
kspearrin
9dc01bca1c detect dark mode theme. set modal to full screen 2019-09-30 20:38:22 -04:00
Kyle Spearrin
3c7920b84c XF 3.6 update 2019-09-30 20:33:54 -04:00
Kyle Spearrin
b92f3abbaf support dark theme logos 2019-09-30 16:52:20 -04:00
Kyle Spearrin
b6747a63ed stub out support for dark theme by default 2019-09-30 16:41:31 -04:00
Kyle Spearrin
41a44548d2 use TextColor for search bar text color 2019-09-30 16:33:53 -04:00
Kyle Spearrin
a79d3a0d7c uisearchbar tint color 2019-09-30 16:28:07 -04:00
kspearrin
f3a17709e5 get proper hex string from nsdata token 2019-09-30 16:24:35 -04:00
Kyle Spearrin
ced9d33d2e memory stored pinProtectedKey 2019-09-20 16:43:03 -04:00
Kyle Spearrin
23b1373f80 add tag to entitlement 2019-09-20 08:17:37 -04:00
Kyle Spearrin
a80eb1f533 bump version 2019-09-20 07:56:49 -04:00
Kyle Spearrin
f657edf195 add support for vivaldi browser. resolves #599 2019-09-10 17:21:11 -04:00
Kyle Spearrin
d34279dca5 more null checks on add/edit save 2019-09-06 10:03:11 -04:00
Kyle Spearrin
954aa1112a added locale names dictionary 2019-09-06 09:44:25 -04:00
Kyle Spearrin
b35a3339cb device type should be int 2019-09-06 09:44:25 -04:00
Kyle Spearrin
b59433debd New Crowdin translations (#594)
* New translations copy.resx (Bulgarian)

* New translations copy.resx (Bulgarian)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Turkish)
2019-09-04 13:10:18 -04:00
Kyle Spearrin
fb2db9c652 version bump 2019-09-04 13:02:00 -04:00
Kyle Spearrin
2507f3301b device user agent 2019-09-04 11:52:32 -04:00
Kyle Spearrin
bdad5e4f0a fixes to opening file types on android 2019-08-30 16:43:58 -04:00
Kyle Spearrin
b5dcdc74d7 dark keyboard on dark themes. resolves #588 2019-08-28 20:27:15 -04:00
Kyle Spearrin
e2d1da02d3 more null checking on save 2019-08-27 15:03:33 -04:00
Kyle Spearrin
8253f18312 null check cipher service 2019-08-27 14:55:15 -04:00
kenjirooo
f4a98a2031 Added support for Sleipnir Mobile (https://play.google.com/store/apps/details?id=jp.co.fenrir.android.sleipnir). (#583) 2019-08-22 08:00:03 -04:00
Kyle Spearrin
224845cfd3 bump version 2019-08-12 09:41:12 -04:00
Kyle Spearrin
fc8c2ad67a add back references for autofill projects 2019-08-12 09:36:10 -04:00
Kyle Spearrin
c9d6f58563 delay for 1s on SyncIfNeeded 2019-08-12 09:35:18 -04:00
Kyle Spearrin
325b557506 null check on SyncIfNeeded 2019-08-12 08:51:49 -04:00
Kyle Spearrin
ce751cfc87 fix unlock logic 2019-08-12 08:05:45 -04:00
Kyle Spearrin
0f451fd4b9 set FingerprintUnlockKey before setKey 2019-08-10 00:19:01 -04:00
139 changed files with 49902 additions and 49951 deletions

View File

@@ -1,5 +1,5 @@
image:
- Visual Studio 2017
- Visual Studio 2019 Preview
- Ubuntu1804
branches:

8
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{
"name": "bitwarden-fdroid",
"name": "bitwarden-mobile",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
@@ -196,9 +196,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"minimatch": {

View File

@@ -63,6 +63,11 @@ namespace Bit.Droid.Accessibility
new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.ecosia.android", "url_bar"),
new Browser("com.qwant.liberty", "url_bar_title"),
new Browser("jp.co.fenrir.android.sleipnir", "url_text"),
new Browser("jp.co.fenrir.android.sleipnir_black", "url_text"),
new Browser("jp.co.fenrir.android.sleipnir_test", "url_text"),
new Browser("com.vivaldi.browser", "url_bar"),
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
}.ToDictionary(n => n.PackageName);
// Known packages to skip
@@ -82,13 +87,18 @@ namespace Bit.Droid.Accessibility
"com.teslacoilsw.launcher.prime",
"is.shortcut",
"me.craftsapp.nlauncher",
"com.ss.squarehome2"
"com.ss.squarehome2",
"com.treydev.pns"
};
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
{
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
foreach(var node in testNodesData)
{
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
}
}
public static string GetUri(AccessibilityNodeInfo root)

View File

@@ -16,7 +16,7 @@
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
@@ -82,19 +82,19 @@
<Version>2.1.0.4</Version>
</PackageReference>
<PackageReference Include="Portable.BouncyCastle">
<Version>1.8.5</Version>
<Version>1.8.5.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Essentials">
<Version>1.1.0</Version>
<Version>1.3.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>60.1142.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.1" />
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.3" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>60.1142.1</Version>
</PackageReference>
@@ -116,7 +116,6 @@
<Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" />
<Compile Include="Migration\AndroidKeyStoreStorageService.cs" />
<Compile Include="Push\FirebaseInstanceIdService.cs" />
<Compile Include="Push\FirebaseMessagingService.cs" />
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />

View File

@@ -60,12 +60,15 @@ namespace Bit.Droid.Autofill
"org.mozilla.fenix.nightly",
"org.mozilla.reference.browser",
"org.mozilla.rocket",
"org.torproject.torbrowser",
"com.vivaldi.browser",
};
// The URLs are blacklisted from autofilling
public static HashSet<string> BlacklistedUris = new HashSet<string>
{
"androidapp://android",
"androidapp://com.android.settings",
"androidapp://com.x8bit.bitwarden",
"androidapp://com.oneplus.applocker",
};

View File

@@ -40,12 +40,8 @@ namespace Bit.Droid
if(ServiceContainer.RegisteredServices.Count == 0)
{
RegisterLocalServices();
ServiceContainer.Init();
if(App.Migration.MigrationHelpers.NeedsMigration())
{
var task = App.Migration.MigrationHelpers.PerformMigrationAsync();
Task.Delay(2000).Wait();
}
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
}
#if !FDROID
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -73,12 +69,6 @@ namespace Bit.Droid
private void RegisterLocalServices()
{
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim());
if(App.Migration.MigrationHelpers.NeedsMigration())
{
ServiceContainer.Register<App.Migration.Abstractions.IOldSecureStorageService>(
"oldSecureStorageService", new Migration.AndroidKeyStoreStorageService());
}
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
// Note: This might cause a race condition. Investigate more.
@@ -155,4 +145,4 @@ namespace Bit.Droid
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
}
}
}
}

View File

@@ -1,373 +0,0 @@
using Java.Security;
using Javax.Crypto;
using Android.OS;
using Bit.App.Abstractions;
using System;
using Android.Security;
using Javax.Security.Auth.X500;
using Java.Math;
using Android.Security.Keystore;
using Android.App;
using Java.Util;
using Javax.Crypto.Spec;
using Android.Preferences;
using Bit.App.Migration;
using Bit.Core.Utilities;
using Bit.App.Migration.Abstractions;
namespace Bit.Droid.Migration
{
public class AndroidKeyStoreStorageService : IOldSecureStorageService
{
private const string AndroidKeyStore = "AndroidKeyStore";
private const string AesMode = "AES/GCM/NoPadding";
private const string KeyAlias = "bitwardenKey2";
private const string KeyAliasV1 = "bitwardenKey";
private const string SettingsFormat = "ksSecured2:{0}";
private const string SettingsFormatV1 = "ksSecured:{0}";
private const string AesKey = "ksSecured2:aesKeyForService";
private const string AesKeyV1 = "ksSecured:aesKeyForService";
private readonly string _rsaMode;
private readonly bool _oldAndroid;
private readonly SettingsShim _settings;
private readonly KeyStore _keyStore;
public AndroidKeyStoreStorageService()
{
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
_settings = ServiceContainer.Resolve<SettingsShim>("settingsShim");
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
_keyStore.Load(null);
/*
try
{
GenerateStoreKey(true);
}
catch
{
GenerateStoreKey(false);
}
GenerateAesKey();
*/
}
public bool Contains(string key)
{
return _settings.Contains(string.Format(SettingsFormat, key)) ||
_settings.Contains(string.Format(SettingsFormatV1, key));
}
public void Delete(string key)
{
CleanupOld(key);
var formattedKey = string.Format(SettingsFormat, key);
if(_settings.Contains(formattedKey))
{
_settings.Remove(formattedKey);
}
}
public byte[] Retrieve(string key)
{
var formattedKey = string.Format(SettingsFormat, key);
if(!_settings.Contains(formattedKey))
{
return TryGetAndMigrate(key);
}
var cs = _settings.GetValueOrDefault(formattedKey, null);
if(string.IsNullOrWhiteSpace(cs))
{
return null;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return null;
}
try
{
return App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKey);
}
catch
{
Console.WriteLine("Failed to decrypt from secure storage.");
_settings.Remove(formattedKey);
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
return null;
}
}
public void Store(string key, byte[] dataBytes)
{
var formattedKey = string.Format(SettingsFormat, key);
CleanupOld(key);
if(dataBytes == null)
{
_settings.Remove(formattedKey);
return;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return;
}
try
{
var cipherString = App.Migration.Crypto.AesCbcEncrypt(dataBytes, aesKey);
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
}
catch
{
Console.WriteLine("Failed to encrypt to secure storage.");
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
}
}
private void GenerateStoreKey(bool withDate)
{
if(_keyStore.ContainsAlias(KeyAlias))
{
return;
}
ClearSettings();
var end = Calendar.Instance;
end.Add(CalendarField.Year, 99);
if(_oldAndroid)
{
var subject = new X500Principal($"CN={KeyAlias}");
var builder = new KeyPairGeneratorSpec.Builder(Application.Context)
.SetAlias(KeyAlias)
.SetSubject(subject)
.SetSerialNumber(BigInteger.Ten);
if(withDate)
{
builder.SetStartDate(new Date(0)).SetEndDate(end.Time);
}
var spec = builder.Build();
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
gen.Initialize(spec);
gen.GenerateKeyPair();
}
else
{
var builder = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
.SetBlockModes(KeyProperties.BlockModeGcm)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone);
if(withDate)
{
builder.SetKeyValidityStart(new Date(0)).SetKeyValidityEnd(end.Time);
}
var spec = builder.Build();
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
gen.Init(spec);
gen.GenerateKey();
}
}
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
{
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
}
private void GenerateAesKey()
{
if(_settings.Contains(AesKey))
{
return;
}
var key = App.Migration.Crypto.RandomBytes(512 / 8);
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
_settings.AddOrUpdateValue(AesKey, encKey);
}
private App.Migration.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
{
try
{
var aesKey = v1 ? AesKeyV1 : AesKey;
if(!_settings.Contains(aesKey))
{
return null;
}
var encKey = _settings.GetValueOrDefault(aesKey, null);
if(string.IsNullOrWhiteSpace(encKey))
{
return null;
}
if(_oldAndroid || v1)
{
var encKeyBytes = Convert.FromBase64String(encKey);
var key = RsaDecrypt(encKeyBytes, v1);
return new App.Migration.Models.SymmetricCryptoKey(key);
}
else
{
var parts = encKey.Split('|');
if(parts.Length < 2)
{
return null;
}
var ivBytes = Convert.FromBase64String(parts[0]);
var encKeyBytes = Convert.FromBase64String(parts[1]);
var key = AesDecrypt(ivBytes, encKeyBytes);
return new App.Migration.Models.SymmetricCryptoKey(key);
}
}
catch
{
Console.WriteLine("Cannot get AesKey.");
_keyStore.DeleteEntry(KeyAlias);
_settings.Remove(AesKey);
if(!v1)
{
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
}
return null;
}
}
private string AesEncrypt(byte[] input)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
cipher.Init(CipherMode.EncryptMode, entry);
var encBytes = cipher.DoFinal(input);
var ivBytes = cipher.GetIV();
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
}
}
private byte[] AesDecrypt(byte[] iv, byte[] encData)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
var spec = new GCMParameterSpec(128, iv);
cipher.Init(CipherMode.DecryptMode, entry, spec);
var decBytes = cipher.DoFinal(encData);
return decBytes;
}
}
private string RsaEncrypt(byte[] data)
{
using(var entry = GetRsaKeyEntry(KeyAlias))
using(var cipher = Cipher.GetInstance(_rsaMode))
{
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
var cipherText = cipher.DoFinal(data);
return Convert.ToBase64String(cipherText);
}
}
private byte[] RsaDecrypt(byte[] encData, bool v1)
{
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
using(var cipher = Cipher.GetInstance(_rsaMode))
{
if(_oldAndroid)
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
}
else
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
}
var plainText = cipher.DoFinal(encData);
return plainText;
}
}
private byte[] TryGetAndMigrate(string key)
{
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
var aesKeyV1 = GetAesKey(true);
if(aesKeyV1 != null)
{
try
{
var cs = _settings.GetValueOrDefault(formattedKeyV1, null);
var value = App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKeyV1);
Store(key, value);
return value;
}
catch
{
Console.WriteLine("Failed to decrypt v1 from secure storage.");
}
}
_settings.Remove(formattedKeyV1);
}
return null;
}
private void CleanupOld(string key)
{
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
_settings.Remove(formattedKeyV1);
}
}
private void ClearSettings(string format = SettingsFormat)
{
var prefix = string.Format(format, string.Empty);
using(var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Application.Context))
using(var sharedPreferencesEditor = sharedPreferences.Edit())
{
var removed = false;
foreach(var pref in sharedPreferences.All)
{
if(pref.Key.StartsWith(prefix))
{
removed = true;
sharedPreferencesEditor.Remove(pref.Key);
}
}
if(removed)
{
sharedPreferencesEditor.Commit();
}
}
}
}
}

View File

@@ -3,16 +3,17 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="2.2.0"
android:versionName="2.2.8"
package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />

File diff suppressed because it is too large Load Diff

View File

@@ -90,4 +90,7 @@
<compatibility-package
android:name="com.ecosia.android"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.vivaldi.browser"
android:maxLongVersionCode="10000000000"/>
</autofill-service>

View File

@@ -8,9 +8,12 @@ using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.Content.PM;
using Android.Hardware.Biometrics;
using Android.Hardware.Fingerprints;
using Android.Nfc;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Text;
@@ -28,6 +31,7 @@ using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Plugin.CurrentActivity;
using Plugin.Fingerprint;
namespace Bit.Droid.Services
{
@@ -40,6 +44,7 @@ namespace Bit.Droid.Services
private ProgressDialog _progressDialog;
private bool _cameraPermissionsDenied;
private Toast _toast;
private string _userAgent;
public DeviceActionService(
IStorageService storageService,
@@ -61,6 +66,19 @@ namespace Bit.Droid.Services
});
}
public string DeviceUserAgent
{
get
{
if(string.IsNullOrWhiteSpace(_userAgent))
{
_userAgent = $"Bitwarden_Mobile/{Xamarin.Essentials.AppInfo.VersionString} " +
$"(Android {Build.VERSION.Release}; SDK {Build.VERSION.Sdk}; Model {Build.Model})";
}
return _userAgent;
}
}
public DeviceType DeviceType => DeviceType.Android;
public void Toast(string text, bool longDuration = false)
@@ -114,38 +132,14 @@ namespace Bit.Droid.Services
public bool OpenFile(byte[] fileData, string id, string fileName)
{
if(!CanOpenFile(fileName))
{
return false;
}
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if(extension == null)
{
return false;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if(mimeType == null)
{
return false;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if(!file.IsFile)
{
return false;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(fileData, fileName);
if(intent == null)
{
return false;
}
activity.StartActivity(intent);
return true;
}
@@ -154,22 +148,57 @@ namespace Bit.Droid.Services
}
public bool CanOpenFile(string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
if(intent == null)
{
return false;
}
var activities = activity.PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
catch { }
return false;
}
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if(extension == null)
{
return false;
return null;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if(mimeType == null)
{
return false;
return null;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = new Intent(Intent.ActionView);
intent.SetType(mimeType);
var activities = activity.PackageManager.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if(!file.IsFile)
{
return null;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
return intent;
}
catch { }
return null;
}
public async Task ClearCacheAsync()
@@ -185,7 +214,8 @@ namespace Bit.Droid.Services
public Task SelectFileAsync()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage);
var hasStorageWritePermission = !_cameraPermissionsDenied &&
HasPermission(Manifest.Permission.WriteExternalStorage);
var additionalIntents = new List<IParcelable>();
if(activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
{
@@ -309,11 +339,72 @@ namespace Bit.Droid.Services
Application.Context.PackageName, 0).VersionCode.ToString();
}
public bool SupportsFaceId()
public bool SupportsFaceBiometric()
{
return false;
}
public Task<bool> SupportsFaceBiometricAsync()
{
return Task.FromResult(SupportsFaceBiometric());
}
public async Task<bool> BiometricAvailableAsync()
{
if(UseNativeBiometric())
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var manager = activity.GetSystemService(Context.BiometricService) as BiometricManager;
return manager.CanAuthenticate() == BiometricCode.Success;
}
else
{
try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
}
}
public bool UseNativeBiometric()
{
return (int)Build.VERSION.SdkInt >= 29;
}
public Task<bool> AuthenticateBiometricAsync(string text = null)
{
if(string.IsNullOrWhiteSpace(text))
{
text = AppResources.BiometricsDirection;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
using(var builder = new BiometricPrompt.Builder(activity))
{
builder.SetTitle(text);
builder.SetConfirmationRequired(false);
builder.SetNegativeButton(AppResources.Cancel, activity.MainExecutor,
new DialogInterfaceOnClickListener
{
Clicked = () => { }
});
var prompt = builder.Build();
var result = new TaskCompletionSource<bool>();
prompt.Authenticate(new CancellationSignal(), activity.MainExecutor,
new BiometricAuthenticationCallback
{
Success = authResult => result.TrySetResult(true),
Failed = () => result.TrySetResult(false),
Help = (helpCode, helpString) => { }
});
return result.Task;
}
}
public bool SupportsNfc()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -543,7 +634,8 @@ namespace Bit.Droid.Services
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var afm = (AutofillManager)activity.GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager)));
var afm = (AutofillManager)activity.GetSystemService(
Java.Lang.Class.FromType(typeof(AutofillManager)));
return afm.IsEnabled && afm.HasEnabledAutofillServices;
}
catch
@@ -585,6 +677,11 @@ namespace Bit.Droid.Services
}
}
public bool UsingDarkTheme()
{
return false;
}
private bool DeleteDir(Java.IO.File dir)
{
if(dir != null && dir.IsDirectory)
@@ -683,5 +780,41 @@ namespace Bit.Droid.Services
Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.Text = text;
}
private class BiometricAuthenticationCallback : BiometricPrompt.AuthenticationCallback
{
public Action<BiometricPrompt.AuthenticationResult> Success { get; set; }
public Action Failed { get; set; }
public Action<BiometricAcquiredStatus, Java.Lang.ICharSequence> Help { get; set; }
public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult authResult)
{
base.OnAuthenticationSucceeded(authResult);
Success?.Invoke(authResult);
}
public override void OnAuthenticationFailed()
{
base.OnAuthenticationFailed();
Failed?.Invoke();
}
public override void OnAuthenticationHelp([GeneratedEnum] BiometricAcquiredStatus helpCode,
Java.Lang.ICharSequence helpString)
{
base.OnAuthenticationHelp(helpCode, helpString);
Help?.Invoke(helpCode, helpString);
}
}
private class DialogInterfaceOnClickListener : Java.Lang.Object, IDialogInterfaceOnClickListener
{
public Action Clicked { get; set; }
public void OnClick(IDialogInterface dialog, int which)
{
Clicked?.Invoke();
}
}
}
}
}

View File

@@ -6,6 +6,7 @@ namespace Bit.App.Abstractions
{
public interface IDeviceActionService
{
string DeviceUserAgent { get; }
DeviceType DeviceType { get; }
void Toast(string text, bool longDuration = false);
bool LaunchApp(string appName);
@@ -19,7 +20,11 @@ namespace Bit.App.Abstractions
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true);
void RateApp();
bool SupportsFaceId();
bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync();
Task<bool> BiometricAvailableAsync();
bool UseNativeBiometric();
Task<bool> AuthenticateBiometricAsync(string text = null);
bool SupportsNfc();
bool SupportsCamera();
bool SupportsAutofillService();
@@ -34,5 +39,6 @@ namespace Bit.App.Abstractions
string GetBuildNumber();
void OpenAccessibilitySettings();
void OpenAutofillSettings();
bool UsingDarkTheme();
}
}

View File

@@ -16,9 +16,9 @@
<PackageReference Include="HockeySDK.Xamarin" Version="5.2.0" />
<PackageReference Include="Plugin.Fingerprint" Version="1.4.9" />
<PackageReference Include="Refractored.FloatingActionButtonForms" Version="2.1.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.1.0" />
<PackageReference Include="Xamarin.Essentials" Version="1.3.1" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="3.6.0.344457" />
<PackageReference Include="Xamarin.Forms" Version="4.4.0.991265" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.1.47" />
</ItemGroup>

View File

@@ -97,11 +97,8 @@ namespace Bit.App
}
else if(message.Command == "logout")
{
if(Migration.MigrationHelpers.Migrating)
{
return;
}
Device.BeginInvokeOnMainThread(async () => await LogOutAsync(false));
Device.BeginInvokeOnMainThread(async () =>
await LogOutAsync((message.Data as bool?).GetValueOrDefault()));
}
else if(message.Command == "loggedOut")
{
@@ -212,6 +209,13 @@ namespace Bit.App
SyncIfNeeded();
if(Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage)
{
if(Device.RuntimePlatform == Device.Android)
{
// Workaround for https://github.com/xamarin/Xamarin.Forms/issues/7478
await Task.Delay(100);
Current.MainPage = new NavigationPage(lockPage);
// End workaround
}
await lockPage.PromptFingerprintAfterResumeAsync();
}
}
@@ -239,12 +243,15 @@ namespace Bit.App
_passwordGenerationService.ClearAsync(),
_lockService.ClearAsync(),
_stateService.PurgeAsync());
_lockService.PinLocked = false;
_lockService.FingerprintLocked = true;
_searchService.ClearIndex();
_authService.LogOut(() =>
{
Current.MainPage = new HomePage();
if(expired)
{
_platformUtilsService.ShowToast("warning", null, AppResources.LoginExpired);
}
});
}
@@ -356,10 +363,6 @@ namespace Bit.App
private void SyncIfNeeded()
{
if(Migration.MigrationHelpers.Migrating)
{
return;
}
if(Xamarin.Essentials.Connectivity.NetworkAccess == Xamarin.Essentials.NetworkAccess.None)
{
return;
@@ -367,8 +370,9 @@ namespace Bit.App
Task.Run(async () =>
{
var lastSync = await _syncService.GetLastSyncAsync();
if(DateTime.UtcNow - lastSync > TimeSpan.FromMinutes(30))
if(lastSync == null || ((DateTime.UtcNow - lastSync) > TimeSpan.FromMinutes(30)))
{
await Task.Delay(1000);
await _syncService.FullSyncAsync(false);
}
});
@@ -399,6 +403,11 @@ namespace Bit.App
autoPromptFingerprint = false;
}
}
else if(autoPromptFingerprint && Device.RuntimePlatform == Device.Android &&
_deviceActionService.UseNativeBiometric())
{
autoPromptFingerprint = false;
}
PreviousPageInfo lastPageBeforeLock = null;
if(Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
{

View File

@@ -0,0 +1,21 @@
using Bit.App.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class ExtendedSearchBar : SearchBar
{
public ExtendedSearchBar()
{
if(Device.RuntimePlatform == Device.iOS)
{
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService", true);
if(!deviceActionService?.UsingDarkTheme() ?? false)
{
TextColor = Color.Black;
}
}
}
}
}

View File

@@ -1,10 +0,0 @@
namespace Bit.App.Migration.Abstractions
{
public interface IOldSecureStorageService
{
bool Contains(string key);
void Delete(string key);
byte[] Retrieve(string key);
void Store(string key, byte[] dataBytes);
}
}

View File

@@ -1,199 +0,0 @@
using Bit.App.Migration.Models;
using Bit.Core.Enums;
using PCLCrypto;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Bit.App.Migration
{
public static class Crypto
{
public static CipherString AesCbcEncrypt(byte[] plainBytes, SymmetricCryptoKey key)
{
var parts = AesCbcEncryptToParts(plainBytes, key);
return new CipherString(parts.Item1, Convert.ToBase64String(parts.Item2),
Convert.ToBase64String(parts.Item4), parts.Item3 != null ? Convert.ToBase64String(parts.Item3) : null);
}
public static byte[] AesCbcEncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key)
{
var parts = AesCbcEncryptToParts(plainBytes, key);
var macLength = parts.Item3?.Length ?? 0;
var encBytes = new byte[1 + parts.Item2.Length + macLength + parts.Item4.Length];
encBytes[0] = (byte)parts.Item1;
parts.Item2.CopyTo(encBytes, 1);
if(parts.Item3 != null)
{
parts.Item3.CopyTo(encBytes, 1 + parts.Item2.Length);
}
parts.Item4.CopyTo(encBytes, 1 + parts.Item2.Length + macLength);
return encBytes;
}
private static Tuple<EncryptionType, byte[], byte[], byte[]> AesCbcEncryptToParts(byte[] plainBytes,
SymmetricCryptoKey key)
{
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);
var ct = WinRTCrypto.CryptographicEngine.Encrypt(cryptoKey, plainBytes, iv);
var mac = key.MacKey != null ? ComputeMac(ct, iv, key.MacKey) : null;
return new Tuple<EncryptionType, byte[], byte[], byte[]>(key.EncryptionType, iv, mac, ct);
}
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
{
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)
{
if(key == null)
{
throw new ArgumentNullException(nameof(key));
}
if(ct == null)
{
throw new ArgumentNullException(nameof(ct));
}
if(iv == null)
{
throw new ArgumentNullException(nameof(iv));
}
if(key.MacKey != null && mac == null)
{
throw new ArgumentNullException(nameof(mac));
}
if(key.EncryptionType != type)
{
throw new InvalidOperationException(nameof(type));
}
if(key.MacKey != null && mac != null)
{
var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
if(!MacsEqual(computedMacBytes, mac))
{
throw new InvalidOperationException("MAC failed.");
}
}
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
return decryptedBytes;
}
public static byte[] RandomBytes(int length)
{
return WinRTCrypto.CryptographicBuffer.GenerateRandom(length);
}
public static byte[] ComputeMac(byte[] ctBytes, byte[] ivBytes, byte[] macKey)
{
if(ctBytes == null)
{
throw new ArgumentNullException(nameof(ctBytes));
}
if(ivBytes == null)
{
throw new ArgumentNullException(nameof(ivBytes));
}
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));
}
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(macKey);
hasher.Append(dataBytes.ToArray());
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/
// 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)
{
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha256);
var hasher = algorithm.CreateHash(RandomBytes(32));
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;
}
// 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;
}
}
}

View File

@@ -1,200 +0,0 @@
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using System;
using System.Text;
using System.Threading.Tasks;
namespace Bit.App.Migration
{
public static class MigrationHelpers
{
public static bool Migrating = false;
public static bool NeedsMigration()
{
return ServiceContainer.Resolve<SettingsShim>("settingsShim")
.GetValueOrDefault(Constants.OldUserIdKey, null) != null; ;
}
public static async Task<bool> PerformMigrationAsync()
{
if(!NeedsMigration() || Migrating)
{
return false;
}
Migrating = true;
var settingsShim = ServiceContainer.Resolve<SettingsShim>("settingsShim");
var oldSecureStorageService = ServiceContainer.Resolve<Abstractions.IOldSecureStorageService>(
"oldSecureStorageService");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
var secureStorageService = ServiceContainer.Resolve<IStorageService>("secureStorageService");
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
var tokenService = ServiceContainer.Resolve<ITokenService>("tokenService");
var userService = ServiceContainer.Resolve<IUserService>("userService");
var environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
var passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>(
"passwordGenerationService");
var syncService = ServiceContainer.Resolve<ISyncService>("syncService");
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
// Get old data
var oldTokenBytes = oldSecureStorageService.Retrieve("accessToken");
var oldToken = oldTokenBytes == null ? null : Encoding.UTF8.GetString(
oldTokenBytes, 0, oldTokenBytes.Length);
var oldKeyBytes = oldSecureStorageService.Retrieve("key");
var oldKey = oldKeyBytes == null ? null : new Models.SymmetricCryptoKey(oldKeyBytes);
var oldUserId = settingsShim.GetValueOrDefault("userId", null);
var isAuthenticated = oldKey != null && !string.IsNullOrWhiteSpace(oldToken) &&
!string.IsNullOrWhiteSpace(oldUserId);
if(!isAuthenticated)
{
Migrating = false;
return false;
}
var oldRefreshTokenBytes = oldSecureStorageService.Retrieve("refreshToken");
var oldRefreshToken = oldRefreshTokenBytes == null ? null : Encoding.UTF8.GetString(
oldRefreshTokenBytes, 0, oldRefreshTokenBytes.Length);
var oldPinBytes = oldSecureStorageService.Retrieve("pin");
var oldPin = oldPinBytes == null ? null : Encoding.UTF8.GetString(
oldPinBytes, 0, oldPinBytes.Length);
var oldEncKey = settingsShim.GetValueOrDefault("encKey", null);
var oldEncPrivateKey = settingsShim.GetValueOrDefault("encPrivateKey", null);
var oldEmail = settingsShim.GetValueOrDefault("email", null);
var oldKdf = (KdfType)settingsShim.GetValueOrDefault("kdf", (int)KdfType.PBKDF2_SHA256);
var oldKdfIterations = settingsShim.GetValueOrDefault("kdfIterations", 5000);
var oldTwoFactorTokenBytes = oldSecureStorageService.Retrieve(
string.Format("twoFactorToken_{0}", Convert.ToBase64String(Encoding.UTF8.GetBytes(oldEmail))));
var oldTwoFactorToken = oldTwoFactorTokenBytes == null ? null : Encoding.UTF8.GetString(
oldTwoFactorTokenBytes, 0, oldTwoFactorTokenBytes.Length);
var oldAppIdBytes = oldSecureStorageService.Retrieve("appId");
var oldAppId = oldAppIdBytes == null ? null : new Guid(oldAppIdBytes).ToString();
var oldAnonAppIdBytes = oldSecureStorageService.Retrieve("anonymousAppId");
var oldAnonAppId = oldAnonAppIdBytes == null ? null : new Guid(oldAnonAppIdBytes).ToString();
var oldFingerprint = settingsShim.GetValueOrDefault("setting:fingerprintUnlockOn", false);
// Save settings
await storageService.SaveAsync(Constants.AccessibilityAutofillPersistNotificationKey,
settingsShim.GetValueOrDefault("setting:persistNotification", false));
await storageService.SaveAsync(Constants.AccessibilityAutofillPasswordFieldKey,
settingsShim.GetValueOrDefault("setting:autofillPasswordField", false));
await storageService.SaveAsync(Constants.DisableAutoTotpCopyKey,
settingsShim.GetValueOrDefault("setting:disableAutoCopyTotp", false));
await storageService.SaveAsync(Constants.DisableFaviconKey,
settingsShim.GetValueOrDefault("setting:disableWebsiteIcons", false));
await storageService.SaveAsync(Constants.AddSitePromptShownKey,
settingsShim.GetValueOrDefault("addedSiteAlert", false));
await storageService.SaveAsync(Constants.PushInitialPromptShownKey,
settingsShim.GetValueOrDefault("push:initialPromptShown", false));
await storageService.SaveAsync(Constants.PushCurrentTokenKey,
settingsShim.GetValueOrDefault("push:currentToken", null));
await storageService.SaveAsync(Constants.PushRegisteredTokenKey,
settingsShim.GetValueOrDefault("push:registeredToken", null));
// For some reason "push:lastRegistrationDate" isn't getting pulled from settingsShim correctly.
// We don't really need it anyways.
// var lastReg = settingsShim.GetValueOrDefault("push:lastRegistrationDate", DateTime.MinValue);
// await storageService.SaveAsync(Constants.PushLastRegistrationDateKey, lastReg);
await storageService.SaveAsync("rememberedEmail",
settingsShim.GetValueOrDefault("other:lastLoginEmail", null));
await storageService.SaveAsync("appExtensionStarted",
settingsShim.GetValueOrDefault("extension:started", false));
await storageService.SaveAsync("appExtensionActivated",
settingsShim.GetValueOrDefault("extension:activated", false));
await environmentService.SetUrlsAsync(new Core.Models.Data.EnvironmentUrlData
{
Base = settingsShim.GetValueOrDefault("other:baseUrl", null),
Api = settingsShim.GetValueOrDefault("other:apiUrl", null),
WebVault = settingsShim.GetValueOrDefault("other:webVaultUrl", null),
Identity = settingsShim.GetValueOrDefault("other:identityUrl", null),
Icons = settingsShim.GetValueOrDefault("other:iconsUrl", null)
});
await passwordGenerationService.SaveOptionsAsync(new Core.Models.Domain.PasswordGenerationOptions
{
Ambiguous = settingsShim.GetValueOrDefault("pwGenerator:ambiguous", false),
Length = settingsShim.GetValueOrDefault("pwGenerator:length", 15),
Uppercase = settingsShim.GetValueOrDefault("pwGenerator:uppercase", true),
Lowercase = settingsShim.GetValueOrDefault("pwGenerator:lowercase", true),
Number = settingsShim.GetValueOrDefault("pwGenerator:numbers", true),
MinNumber = settingsShim.GetValueOrDefault("pwGenerator:minNumbers", 0),
Special = settingsShim.GetValueOrDefault("pwGenerator:special", true),
MinSpecial = settingsShim.GetValueOrDefault("pwGenerator:minSpecial", 0),
WordSeparator = "-",
NumWords = 3
});
// Save lock options
int? lockOptionsSeconds = settingsShim.GetValueOrDefault("setting:lockSeconds", -10);
if(lockOptionsSeconds == -10)
{
lockOptionsSeconds = 60 * 15;
}
else if(lockOptionsSeconds == -1)
{
lockOptionsSeconds = null;
}
await storageService.SaveAsync(Constants.LockOptionKey,
lockOptionsSeconds == null ? (int?)null : lockOptionsSeconds.Value / 60);
// Save app ids
await storageService.SaveAsync("appId", oldAppId);
await storageService.SaveAsync("anonymousAppId", oldAnonAppId);
// Save new authed data
await tokenService.SetTwoFactorTokenAsync(oldTwoFactorToken, oldEmail);
await tokenService.SetTokensAsync(oldToken, oldRefreshToken);
await userService.SetInformationAsync(oldUserId, oldEmail, oldKdf, oldKdfIterations);
var newKey = new Core.Models.Domain.SymmetricCryptoKey(oldKey.Key);
await cryptoService.SetKeyAsync(newKey);
// Key hash is unavailable in old version, store old key until we can move it to key hash
await secureStorageService.SaveAsync("oldKey", newKey.KeyB64);
await cryptoService.SetEncKeyAsync(oldEncKey);
await cryptoService.SetEncPrivateKeyAsync(oldEncPrivateKey);
// Save fingerprint/pin
if(oldFingerprint)
{
await storageService.SaveAsync(Constants.FingerprintUnlockKey, true);
}
else if(!string.IsNullOrWhiteSpace(oldPin))
{
var pinKey = await cryptoService.MakePinKeyAysnc(oldPin, oldEmail, oldKdf, oldKdfIterations);
var pinProtectedKey = await cryptoService.EncryptAsync(oldKeyBytes, pinKey);
await storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
}
// Post migration tasks
await cryptoService.ToggleKeyAsync();
await storageService.SaveAsync(Constants.LastActiveKey, DateTime.UtcNow.AddYears(-1));
await lockService.CheckLockAsync();
// Remove "needs migration" flag
settingsShim.Remove(Constants.OldUserIdKey);
await storageService.SaveAsync(Constants.MigratedFromV1, true);
Migrating = false;
messagingService.Send("migrated");
if(Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
var task = Task.Run(() => syncService.FullSyncAsync(true));
}
return true;
}
}
}

View File

@@ -1,117 +0,0 @@
using System;
using Bit.Core.Enums;
namespace Bit.App.Migration.Models
{
public class CipherString
{
private string _decryptedValue;
public CipherString(string encryptedString)
{
if(string.IsNullOrWhiteSpace(encryptedString))
{
throw new ArgumentException(nameof(encryptedString));
}
var headerPieces = encryptedString.Split('.');
string[] encPieces;
EncryptionType encType;
if(headerPieces.Length == 2 && Enum.TryParse(headerPieces[0], out encType))
{
EncryptionType = encType;
encPieces = headerPieces[1].Split('|');
}
else if(headerPieces.Length == 1)
{
encPieces = headerPieces[0].Split('|');
EncryptionType = encPieces.Length == 3 ? EncryptionType.AesCbc128_HmacSha256_B64 :
EncryptionType.AesCbc256_B64;
}
else
{
throw new ArgumentException("Malformed header.");
}
switch(EncryptionType)
{
case EncryptionType.AesCbc256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
break;
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if(encPieces.Length != 3)
{
throw new ArgumentException("Malformed encPieces.");
}
InitializationVector = encPieces[0];
CipherText = encPieces[1];
Mac = encPieces[2];
break;
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_B64:
if(encPieces.Length != 1)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
break;
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
Mac = encPieces[1];
break;
default:
throw new ArgumentException("Unknown encType.");
}
EncryptedString = encryptedString;
}
public CipherString(EncryptionType encryptionType, string initializationVector, string cipherText,
string mac = null)
{
if(string.IsNullOrWhiteSpace(initializationVector))
{
throw new ArgumentNullException(nameof(initializationVector));
}
if(string.IsNullOrWhiteSpace(cipherText))
{
throw new ArgumentNullException(nameof(cipherText));
}
EncryptionType = encryptionType;
EncryptedString = string.Format("{0}.{1}|{2}", (byte)EncryptionType, initializationVector, cipherText);
if(!string.IsNullOrWhiteSpace(mac))
{
EncryptedString = string.Format("{0}|{1}", EncryptedString, mac);
}
CipherText = cipherText;
InitializationVector = initializationVector;
Mac = mac;
}
public EncryptionType EncryptionType { get; private set; }
public string EncryptedString { get; private set; }
public string InitializationVector { get; private set; }
public string CipherText { get; private set; }
public string Mac { get; private set; }
public byte[] InitializationVectorBytes => string.IsNullOrWhiteSpace(InitializationVector) ?
null : Convert.FromBase64String(InitializationVector);
public byte[] CipherTextBytes => Convert.FromBase64String(CipherText);
public byte[] MacBytes => Mac == null ? null : Convert.FromBase64String(Mac);
}
}

View File

@@ -1,62 +0,0 @@
using Bit.Core.Enums;
using System;
using System.Linq;
namespace Bit.App.Migration.Models
{
public class SymmetricCryptoKey
{
public SymmetricCryptoKey(byte[] rawBytes, EncryptionType? encType = null)
{
if(rawBytes == null || rawBytes.Length == 0)
{
throw new Exception("Must provide keyBytes.");
}
if(encType == null)
{
if(rawBytes.Length == 32)
{
encType = EncryptionType.AesCbc256_B64;
}
else if(rawBytes.Length == 64)
{
encType = EncryptionType.AesCbc256_HmacSha256_B64;
}
else
{
throw new Exception("Unable to determine encType.");
}
}
EncryptionType = encType.Value;
Key = rawBytes;
if(EncryptionType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
{
EncKey = Key;
MacKey = null;
}
else if(EncryptionType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
{
EncKey = Key.Take(16).ToArray();
MacKey = Key.Skip(16).Take(16).ToArray();
}
else if(EncryptionType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
{
EncKey = Key.Take(32).ToArray();
MacKey = Key.Skip(32).Take(32).ToArray();
}
else
{
throw new Exception("Unsupported encType/key length.");
}
}
public byte[] Key { get; set; }
public string B64Key => Convert.ToBase64String(Key);
public byte[] EncKey { get; set; }
public byte[] MacKey { get; set; }
public EncryptionType EncryptionType { get; set; }
}
}

View File

@@ -1,122 +0,0 @@
using System;
namespace Bit.App.Migration
{
public class SettingsShim
{
private readonly string _sharedName;
public SettingsShim(string sharedName = null)
{
_sharedName = sharedName;
}
public bool Contains(string key)
{
return _sharedName != null ? Xamarin.Essentials.Preferences.ContainsKey(key, _sharedName) :
Xamarin.Essentials.Preferences.ContainsKey(key);
}
public string GetValueOrDefault(string key, string defaultValue)
{
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public DateTime GetValueOrDefault(string key, DateTime defaultValue)
{
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public bool GetValueOrDefault(string key, bool defaultValue)
{
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public int GetValueOrDefault(string key, int defaultValue)
{
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public long GetValueOrDefault(string key, long defaultValue)
{
return _sharedName != null ? Xamarin.Essentials.Preferences.Get(key, defaultValue, _sharedName) :
Xamarin.Essentials.Preferences.Get(key, defaultValue);
}
public void AddOrUpdateValue(string key, string value)
{
if(_sharedName != null)
{
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
}
else
{
Xamarin.Essentials.Preferences.Set(key, value);
}
}
public void AddOrUpdateValue(string key, DateTime value)
{
if(_sharedName != null)
{
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
}
else
{
Xamarin.Essentials.Preferences.Set(key, value);
}
}
public void AddOrUpdateValue(string key, bool value)
{
if(_sharedName != null)
{
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
}
else
{
Xamarin.Essentials.Preferences.Set(key, value);
}
}
public void AddOrUpdateValue(string key, long value)
{
if(_sharedName != null)
{
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
}
else
{
Xamarin.Essentials.Preferences.Set(key, value);
}
}
public void AddOrUpdateValue(string key, int value)
{
if(_sharedName != null)
{
Xamarin.Essentials.Preferences.Set(key, value, _sharedName);
}
else
{
Xamarin.Essentials.Preferences.Set(key, value);
}
}
public void Remove(string key)
{
if(_sharedName != null)
{
Xamarin.Essentials.Preferences.Remove(key, _sharedName);
}
else
{
Xamarin.Essentials.Preferences.Remove(key);
}
}
}
}

View File

@@ -60,7 +60,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
}

View File

@@ -16,9 +16,7 @@ namespace Bit.App.Pages
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_messagingService.Send("showStatusBar", false);
InitializeComponent();
var theme = ThemeManager.GetTheme(Device.RuntimePlatform == Device.Android);
var darkbasedTheme = theme == "dark" || theme == "black" || theme == "nord";
_logo.Source = darkbasedTheme ? "logo_white.png" : "logo.png";
_logo.Source = !ThemeManager.UsingLightTheme ? "logo_white.png" : "logo.png";
}
public async Task DismissRegisterPageAndLogInAsync(string email)

View File

@@ -25,7 +25,6 @@ namespace Bit.App.Pages
private readonly IEnvironmentService _environmentService;
private readonly IStateService _stateService;
private bool _hasKey;
private string _email;
private bool _showPassword;
private bool _pinLock;
@@ -104,8 +103,7 @@ namespace Bit.App.Pages
public async Task InitAsync(bool autoPromptFingerprint)
{
_pinSet = await _lockService.IsPinLockSetAsync();
_hasKey = await _cryptoService.HasKeyAsync();
PinLock = (_pinSet.Item1 && _hasKey) || _pinSet.Item2;
PinLock = (_pinSet.Item1 && _lockService.PinProtectedKey != null) || _pinSet.Item2;
FingerprintLock = await _lockService.IsFingerprintLockSetAsync();
_email = await _userService.GetEmailAsync();
var webVault = _environmentService.GetWebVaultUrl();
@@ -128,8 +126,19 @@ namespace Bit.App.Pages
if(FingerprintLock)
{
FingerprintButtonText = _deviceActionService.SupportsFaceId() ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock;
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
if(Device.RuntimePlatform == Device.iOS && supportsFace)
{
FingerprintButtonText = AppResources.UseFaceIDToUnlock;
}
else if(Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
FingerprintButtonText = AppResources.UseBiometricsToUnlock;
}
else
{
FingerprintButtonText = AppResources.UseFingerprintToUnlock;
}
if(autoPromptFingerprint)
{
var tasks = Task.Run(async () =>
@@ -169,14 +178,17 @@ namespace Bit.App.Pages
{
if(_pinSet.Item1)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
_lockService.PinProtectedKey);
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin));
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
failed = decPin != Pin;
_lockService.PinLocked = failed;
if(!failed)
{
Pin = string.Empty;
await DoContinueAsync();
await SetKeyAndContinueAsync(key);
}
}
else
@@ -221,6 +233,15 @@ namespace Bit.App.Pages
}
if(storedKeyHash != null && keyHash != null && storedKeyHash == keyHash)
{
if(_pinSet.Item1)
{
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
_lockService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
}
MasterPassword = string.Empty;
await SetKeyAndContinueAsync(key);
}
@@ -256,7 +277,7 @@ namespace Bit.App.Pages
{
return;
}
var success = await _platformUtilsService.AuthenticateFingerprintAsync(null,
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
{
var page = Page as LockPage;
@@ -278,7 +299,8 @@ namespace Bit.App.Pages
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
{
if(!_hasKey)
var hasKey = await _cryptoService.HasKeyAsync();
if(!hasKey)
{
await _cryptoService.SetKeyAsync(key);
}
@@ -287,6 +309,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync()
{
_lockService.FingerprintLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
_messagingService.Send("unlocked");

View File

@@ -135,7 +135,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -135,7 +135,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -213,8 +213,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -4,6 +4,8 @@ using Bit.Core.Utilities;
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Bit.App.Pages
{
@@ -14,6 +16,14 @@ namespace Bit.App.Pages
protected int ShowModalAnimationDelay = 400;
protected int ShowPageAnimationDelay = 100;
public BaseContentPage()
{
if (Device.RuntimePlatform == Device.iOS)
{
On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.FullScreen);
}
}
public DateTime? LastPageAction { get; set; }
protected override void OnAppearing()

View File

@@ -93,7 +93,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -128,7 +132,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -142,8 +142,12 @@ namespace Bit.App.Pages
var fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
fingerprintName = _deviceActionService.SupportsFaceId() ?
AppResources.FaceID : AppResources.TouchID;
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
fingerprintName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
}
else if(Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
fingerprintName = AppResources.Biometrics;
}
if(item.Name == string.Format(AppResources.UnlockWith, fingerprintName))
{

View File

@@ -62,7 +62,7 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
_supportsFingerprint = await _platformUtilsService.SupportsFingerprintAsync();
_supportsFingerprint = await _platformUtilsService.SupportsBiometricAsync();
var lastSync = await _syncService.GetLastSyncAsync();
if(lastSync != null)
{
@@ -214,21 +214,24 @@ namespace Bit.App.Pages
var masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
AppResources.Yes, AppResources.No);
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
if(masterPassOnRestart)
{
var encPin = await _cryptoService.EncryptAsync(pin);
await _storageService.SaveAsync(Constants.ProtectedPin, encPin.EncryptedString);
_lockService.PinProtectedKey = pinProtectedKey;
}
else
{
var kdf = await _userService.GetKdfAsync();
var kdfIterations = await _userService.GetKdfIterationsAsync();
var email = await _userService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
await _storageService.SaveAsync(Constants.PinProtectedKey, pinProtectedKey.EncryptedString);
}
}
@@ -239,8 +242,8 @@ namespace Bit.App.Pages
}
if(!_pin)
{
await _storageService.RemoveAsync(Constants.PinProtectedKey);
await _storageService.RemoveAsync(Constants.ProtectedPin);
await _cryptoService.ClearPinProtectedKeyAsync();
await _lockService.ClearAsync();
}
BuildList();
}
@@ -252,9 +255,9 @@ namespace Bit.App.Pages
{
_fingerprint = false;
}
else if(await _platformUtilsService.SupportsFingerprintAsync())
else if(await _platformUtilsService.SupportsBiometricAsync())
{
_fingerprint = await _platformUtilsService.AuthenticateFingerprintAsync(null,
_fingerprint = await _platformUtilsService.AuthenticateBiometricAsync(null,
_deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null);
}
if(_fingerprint == current)
@@ -325,8 +328,12 @@ namespace Bit.App.Pages
var fingerprintName = AppResources.Fingerprint;
if(Device.RuntimePlatform == Device.iOS)
{
fingerprintName = _deviceActionService.SupportsFaceId() ?
AppResources.FaceID : AppResources.TouchID;
fingerprintName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID :
AppResources.TouchID;
}
else if(Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
fingerprintName = AppResources.Biometrics;
}
var item = new SettingsPageListItem
{

View File

@@ -70,7 +70,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
}

View File

@@ -563,7 +563,15 @@
IsVisible="{Binding IsHiddenType}"
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False" />
IsTextPredictionEnabled="False">
<Entry.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>None</KeyboardFlags>
</x:Arguments>
</Keyboard>
</Entry.Keyboard>
</controls:MonoEntry>
<Switch
IsToggled="{Binding BooleanValue}"
Grid.Row="0"

View File

@@ -73,6 +73,8 @@ namespace Bit.App.Pages
_folderPicker.ItemDisplayBinding = new Binding("Key");
_ownershipPicker.ItemDisplayBinding = new Binding("Key");
_loginPasswordEntry.Keyboard = Keyboard.Create(KeyboardFlags.None);
_nameEntry.ReturnType = ReturnType.Next;
_nameEntry.ReturnCommand = new Command(() =>
{

View File

@@ -401,12 +401,13 @@ namespace Bit.App.Pages
return false;
}
Cipher.Fields = Fields != null && Fields.Any() ? Fields.Select(f => f.Field).ToList() : null;
Cipher.Fields = Fields != null && Fields.Any() ?
Fields.Where(f => f != null).Select(f => f.Field).ToList() : null;
if(Cipher.Login != null)
{
Cipher.Login.Uris = Uris?.ToList();
if(!EditMode && Cipher.Type == CipherType.Login && (Cipher.Login.Uris?.Count ?? 0) == 1 &&
string.IsNullOrWhiteSpace(Cipher.Login.Uris.First().Uri))
if(!EditMode && Cipher.Type == CipherType.Login && Cipher.Login.Uris != null &&
Cipher.Login.Uris.Count == 1 && string.IsNullOrWhiteSpace(Cipher.Login.Uris[0].Uri))
{
Cipher.Login.Uris = null;
}
@@ -414,7 +415,7 @@ namespace Bit.App.Pages
if(!EditMode && Cipher.OrganizationId != null)
{
if(!Collections?.Any(c => c.Checked) ?? true)
if(Collections == null || !Collections.Any(c => c != null && c.Checked))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.SelectOneCollection,
AppResources.Ok);
@@ -422,7 +423,8 @@ namespace Bit.App.Pages
}
Cipher.CollectionIds = Collections.Any() ?
new HashSet<string>(Collections.Where(c => c.Checked).Select(c => c.Collection.Id)) : null;
new HashSet<string>(Collections.Where(c => c != null && c.Checked && c.Collection?.Id != null)
.Select(c => c.Collection.Id)) : null;
}
var cipher = await _cipherService.EncryptAsync(Cipher);
@@ -454,7 +456,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -485,7 +491,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -124,7 +124,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}
@@ -164,7 +168,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}

View File

@@ -39,7 +39,7 @@ namespace Bit.App.Pages
}
catch(Exception e) when(e.Message.Contains("No key."))
{
await Task.Delay(5000);
await Task.Delay(1000);
await _vm.LoadAsync();
}
}, _mainContent);

View File

@@ -157,8 +157,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(),
AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
if(autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)

View File

@@ -34,7 +34,7 @@
VerticalOptions="CenterAndExpand"
Clicked="BackButton_Clicked"
x:Name="_backButton" />
<SearchBar
<controls:ExtendedSearchBar
x:Name="_searchBar"
HorizontalOptions="FillAndExpand"
TextChanged="SearchBar_TextChanged"

View File

@@ -168,8 +168,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(),
AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
}
if(_deviceActionService.SystemMajorVersion() < 21)

View File

@@ -85,7 +85,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -59,7 +59,7 @@
</controls:FaLabel>
<Label Text="{Binding Name, Mode=OneWay}"
LineBreakMode="TailTruncation"
HorizontalOptions="StartAndExpand"
HorizontalOptions="FillAndExpand"
VerticalOptions="CenterAndExpand"
StyleClass="list-title"/>
<Label Text="{Binding ItemCount, Mode=OneWay}"

View File

@@ -7,6 +7,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;
@@ -19,6 +20,7 @@ namespace Bit.App.Pages
private readonly IPushNotificationService _pushNotificationService;
private readonly IStorageService _storageService;
private readonly ILockService _lockService;
private readonly ICipherService _cipherService;
private readonly IDeviceActionService _deviceActionService;
private readonly GroupingsPageViewModel _vm;
private readonly string _pageName;
@@ -37,6 +39,7 @@ namespace Bit.App.Pages
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_vm = BindingContext as GroupingsPageViewModel;
_vm.Page = this;
@@ -97,7 +100,7 @@ namespace Bit.App.Pages
var migratedFromV1 = await _storageService.GetAsync<bool?>(Constants.MigratedFromV1);
await LoadOnAppearedAsync(_mainLayout, false, async () =>
{
if(!_syncService.SyncInProgress)
if(!_syncService.SyncInProgress || (await _cipherService.GetAllAsync()).Any())
{
try
{
@@ -105,7 +108,7 @@ namespace Bit.App.Pages
}
catch(Exception e) when(e.Message.Contains("No key."))
{
await Task.Delay(5000);
await Task.Delay(1000);
await _vm.LoadAsync();
}
}

View File

@@ -37,6 +37,8 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService;
private readonly ICollectionService _collectionService;
private readonly ISyncService _syncService;
private readonly IUserService _userService;
private readonly ILockService _lockService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
@@ -48,6 +50,8 @@ namespace Bit.App.Pages
_folderService = ServiceContainer.Resolve<IFolderService>("folderService");
_collectionService = ServiceContainer.Resolve<ICollectionService>("collectionService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_userService = ServiceContainer.Resolve<IUserService>("userService");
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
@@ -134,6 +138,15 @@ namespace Bit.App.Pages
{
return;
}
var authed = await _userService.IsAuthenticatedAsync();
if(!authed)
{
return;
}
if(await _lockService.IsLockedAsync())
{
return;
}
_doingLoad = true;
LoadedOnce = true;
ShowNoData = false;
@@ -305,9 +318,17 @@ namespace Bit.App.Pages
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
await _syncService.FullSyncAsync(false);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.SyncingComplete);
try
{
await _syncService.FullSyncAsync(false, true);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.SyncingComplete);
}
catch
{
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("error", null, AppResources.SyncingFailed);
}
}
private async Task LoadDataAsync()

View File

@@ -116,12 +116,19 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
catch(System.Exception e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Message, AppResources.Ok);
if(e.Message != null)
{
await _platformUtilsService.ShowDialogAsync(e.Message, AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -298,7 +298,11 @@ namespace Bit.App.Pages
catch(ApiException e)
{
await _deviceActionService.HideLoadingAsync();
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, e.Error.GetSingleMessage(), AppResources.Ok);
if(e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
return false;
}

View File

@@ -537,6 +537,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Biometrics.
/// </summary>
public static string Biometrics {
get {
return ResourceManager.GetString("Biometrics", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use biometrics to verify..
/// </summary>
public static string BiometricsDirection {
get {
return ResourceManager.GetString("BiometricsDirection", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bitwarden.
/// </summary>
@@ -2274,6 +2292,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your login session has expired..
/// </summary>
public static string LoginExpired {
get {
return ResourceManager.GetString("LoginExpired", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login.
/// </summary>
@@ -3813,6 +3840,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Use Biometrics To Unlock.
/// </summary>
public static string UseBiometricsToUnlock {
get {
return ResourceManager.GetString("UseBiometricsToUnlock", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use Face ID To Unlock.
/// </summary>

View File

@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1305,33 +1305,33 @@
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
</data>
<data name="InvalidEmail" xml:space="preserve">
<value>Invalid email address.</value>
<value>Alamat email tidak valid.</value>
</data>
<data name="Cards" xml:space="preserve">
<value>Cards</value>
<value>Kartu</value>
</data>
<data name="Identities" xml:space="preserve">
<value>Identities</value>
<value>Identitas</value>
</data>
<data name="Logins" xml:space="preserve">
<value>Logins</value>
<value>Info Masuk</value>
</data>
<data name="SecureNotes" xml:space="preserve">
<value>Secure Notes</value>
<value>Catatan Aman</value>
</data>
<data name="AllItems" xml:space="preserve">
<value>All Items</value>
<value>Semua Item</value>
</data>
<data name="URIs" xml:space="preserve">
<value>URIs</value>
<value>URI</value>
<comment>Plural form of a URI</comment>
</data>
<data name="CheckingPassword" xml:space="preserve">
<value>Checking password...</value>
<value>Memeriksa kata sandi...</value>
<comment>A loading message when doing an exposed password check.</comment>
</data>
<data name="CheckPassword" xml:space="preserve">
<value>Check if password has been exposed.</value>
<value>Periksalah jika kata sandi telah terpapar.</value>
</data>
<data name="PasswordExposed" xml:space="preserve">
<value>This password has been exposed {0} time(s) in data breaches. You should change it.</value>
@@ -1449,10 +1449,10 @@
<value>Unlock</value>
</data>
<data name="LockOption30Minutes" xml:space="preserve">
<value>30 minutes</value>
<value>30 menit</value>
</data>
<data name="LockOption5Minutes" xml:space="preserve">
<value>5 minutes</value>
<value>5 menit</value>
</data>
<data name="SetPINDescription" xml:space="preserve">
<value>Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.</value>
@@ -1476,22 +1476,22 @@
<comment>A light color</comment>
</data>
<data name="FiveMinutes" xml:space="preserve">
<value>5 minutes</value>
<value>5 menit</value>
</data>
<data name="OneMinute" xml:space="preserve">
<value>1 minute</value>
<value>1 menit</value>
</data>
<data name="TenSeconds" xml:space="preserve">
<value>10 seconds</value>
<value>10 detik</value>
</data>
<data name="ThirtySeconds" xml:space="preserve">
<value>30 seconds</value>
<value>30 detik</value>
</data>
<data name="TwentySeconds" xml:space="preserve">
<value>20 seconds</value>
<value>20 detik</value>
</data>
<data name="TwoMinutes" xml:space="preserve">
<value>2 minutes</value>
<value>2 menit</value>
</data>
<data name="ClearClipboard" xml:space="preserve">
<value>Clear Clipboard</value>
@@ -1509,70 +1509,82 @@
<value>Choose the default way that URI match detection is handled for logins when performing actions such as auto-fill.</value>
</data>
<data name="Theme" xml:space="preserve">
<value>Theme</value>
<value>Tema</value>
<comment>Color theme</comment>
</data>
<data name="ThemeDescription" xml:space="preserve">
<value>Change the application's color theme.</value>
<value>Ubah warna tema aplikasi.</value>
</data>
<data name="RestartIsRequired" xml:space="preserve">
<value>Restart is required.</value>
<value>Dibutuhkan pemulaian ulang.</value>
<comment>Referring to restarting the application.</comment>
</data>
<data name="Restarting" xml:space="preserve">
<value>Restarting...</value>
<value>Memulai ulang...</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Copy Notes</value>
<value>Catatan Salinan</value>
</data>
<data name="Exit" xml:space="preserve">
<value>Exit</value>
<value>Keluar</value>
</data>
<data name="ExitConfirmation" xml:space="preserve">
<value>Are you sure you want to exit Bitwarden?</value>
<value>Apakah anda yakin ingin keluar dari Bitwarden?</value>
</data>
<data name="PINRequireMasterPasswordRestart" xml:space="preserve">
<value>You you want to require unlocking with your master password when the application is restarted?</value>
</data>
<data name="Black" xml:space="preserve">
<value>Black</value>
<value>Hitam</value>
<comment>The color black</comment>
</data>
<data name="BlacklistedUris" xml:space="preserve">
<value>Blacklisted URIs</value>
<value>Daftar hitam URI</value>
</data>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>URIs that are blacklisted will not offer auto-fill. The list of apps should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Disable Save Prompt</value>
<value>Nonaktifkan Saran Simpan</value>
</data>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value>
<value>"Saran Simpan" secara otomatis meminta anda untuk menyimpan item baru ke brankas anda setiap kali anda memasukkannya untuk pertama kali.</value>
</data>
<data name="LockOptionOnRestart" xml:space="preserve">
<value>On App Restart</value>
<value>Saat Aplikasi Memulai Ulang</value>
</data>
<data name="AutofillServiceNotEnabled" xml:space="preserve">
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
<value>Isi-otomatis memudahkan untuk mengakses brankas Bitwarden anda dengan aman dari situs web dan aplikasi lain. Sepertinya anda belum mengaktifkan layanan pengisian-otomatis untuk Bitwarden. Aktifkan pengisian-otomatis untuk Bitwarden dari layar "Pengaturan".</value>
</data>
<data name="ThemeAppliedOnRestart" xml:space="preserve">
<value>Your theme changes will apply when the app is restarted.</value>
<value>Perubahan tema anda akan diterapkan setelah aplikasi dimulai ulang.</value>
</data>
<data name="Capitalize" xml:space="preserve">
<value>Capitalize</value>
<value>Jadikan Huruf Besar</value>
<comment>ex. Uppercase the first character of a word.</comment>
</data>
<data name="IncludeNumber" xml:space="preserve">
<value>Include Number</value>
<value>Sertakan Digit</value>
</data>
<data name="Download" xml:space="preserve">
<value>Download</value>
<value>Unduh</value>
</data>
<data name="Shared" xml:space="preserve">
<value>Shared</value>
<value>Dibagikan</value>
</data>
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
<value>Alihkan Visibilitas</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1308,22 +1308,22 @@
<value>잘못된 이메일 주소입니다.</value>
</data>
<data name="Cards" xml:space="preserve">
<value>Cards</value>
<value>카드</value>
</data>
<data name="Identities" xml:space="preserve">
<value>Identities</value>
<value>신원</value>
</data>
<data name="Logins" xml:space="preserve">
<value>Logins</value>
<value>로그인</value>
</data>
<data name="SecureNotes" xml:space="preserve">
<value>Secure Notes</value>
<value>보안 메모</value>
</data>
<data name="AllItems" xml:space="preserve">
<value>All Items</value>
<value>모든 항목</value>
</data>
<data name="URIs" xml:space="preserve">
<value>URIs</value>
<value>URI</value>
<comment>Plural form of a URI</comment>
</data>
<data name="CheckingPassword" xml:space="preserve">
@@ -1334,25 +1334,25 @@
<value>비밀번호가 노출되었는지 확인합니다.</value>
</data>
<data name="PasswordExposed" xml:space="preserve">
<value>This password has been exposed {0} time(s) in data breaches. You should change it.</value>
<value>이 비밀번호는 데이터 유출에 {0}회 노출되었습니다. 비밀번호를 변경하는 것이 좋습니다.</value>
</data>
<data name="PasswordSafe" xml:space="preserve">
<value>This password was not found in any known data breaches. It should be safe to use.</value>
<value>이 비밀번호는 데이터 유출 목록에 없습니다. 사용하기에 안전한 비밀번호입니다.</value>
</data>
<data name="IdentityName" xml:space="preserve">
<value>Identity Name</value>
<value>ID 이름</value>
</data>
<data name="Value" xml:space="preserve">
<value>값</value>
</data>
<data name="PasswordHistory" xml:space="preserve">
<value>Password History</value>
<value>비밀번호 변경 기록</value>
</data>
<data name="Types" xml:space="preserve">
<value>유형</value>
</data>
<data name="NoPasswordsToList" xml:space="preserve">
<value>No passwords to list.</value>
<value>비밀번호가 없습니다.</value>
</data>
<data name="NoItemsToList" xml:space="preserve">
<value>항목이 없습니다.</value>
@@ -1364,66 +1364,66 @@
<value>폴더 검색</value>
</data>
<data name="SearchType" xml:space="preserve">
<value>Search type</value>
<value>검색 유형</value>
</data>
<data name="Type" xml:space="preserve">
<value>유형</value>
</data>
<data name="MoveDown" xml:space="preserve">
<value>Move Down</value>
<value>아래로 옮기기</value>
</data>
<data name="MoveUp" xml:space="preserve">
<value>Move Up</value>
<value>위로 옮기기</value>
</data>
<data name="Miscellaneous" xml:space="preserve">
<value>Miscellaneous</value>
<value>기타</value>
</data>
<data name="Ownership" xml:space="preserve">
<value>Ownership</value>
<value>소유자</value>
</data>
<data name="WhoOwnsThisItem" xml:space="preserve">
<value>Who owns this item?</value>
<value>이 항목의 소유자는 누구입니까?</value>
</data>
<data name="NoCollectionsToList" xml:space="preserve">
<value>There are no collections to list.</value>
<value>컬렉션이 없습니다.</value>
</data>
<data name="ItemShared" xml:space="preserve">
<value>Item has been shared.</value>
<value>항목이 공유되었습니다.</value>
</data>
<data name="SelectOneCollection" xml:space="preserve">
<value>You must select at least one collection.</value>
</data>
<data name="Share" xml:space="preserve">
<value>Share</value>
<value>공유</value>
</data>
<data name="ShareItem" xml:space="preserve">
<value>Share Item</value>
<value>항목 공유</value>
</data>
<data name="NoOrgsToList" xml:space="preserve">
<value>No organizations to list.</value>
<value>조직이 없습니다.</value>
</data>
<data name="ShareDesc" xml:space="preserve">
<value>Choose an organization that you wish to share this item with. Sharing transfers ownership of the item to the organization. You will no longer be the direct owner of this item once it has been shared.</value>
<value>이 항목을 공유할 조직을 선택하세요. 항목을 공유하면 소유권이 조직으로 이전됩니다. 일단 항목을 공유하면, 항목의 직접적인 소유자가 아니게 됩니다.</value>
</data>
<data name="NumberOfWords" xml:space="preserve">
<value>Number of Words</value>
<value>단어 수</value>
</data>
<data name="Passphrase" xml:space="preserve">
<value>Passphrase</value>
<value>패스프레이즈</value>
</data>
<data name="WordSeparator" xml:space="preserve">
<value>Word Separator</value>
<value>구분 기호</value>
</data>
<data name="Clear" xml:space="preserve">
<value>Clear</value>
<value>삭제</value>
<comment>To clear something out. example: To clear browser history.</comment>
</data>
<data name="Generator" xml:space="preserve">
<value>Generator</value>
<value>생성기</value>
<comment>Short for "Password Generator"</comment>
</data>
<data name="NoFoldersToList" xml:space="preserve">
<value>There are no folders to list.</value>
<value>폴더가 없습니다.</value>
</data>
<data name="FingerprintPhrase" xml:space="preserve">
<value>Fingerprint Phrase</value>
@@ -1437,22 +1437,22 @@
<value>Bitwarden allows you to share your vault with others by using an organization account. Would you like to visit the bitwarden.com website to learn more?</value>
</data>
<data name="ExportVault" xml:space="preserve">
<value>Export Vault</value>
<value>보관함 내보내기</value>
</data>
<data name="LockNow" xml:space="preserve">
<value>Lock Now</value>
<value>지금 잠그기</value>
</data>
<data name="PIN" xml:space="preserve">
<value>PIN</value>
</data>
<data name="Unlock" xml:space="preserve">
<value>Unlock</value>
<value>잠금 해제</value>
</data>
<data name="LockOption30Minutes" xml:space="preserve">
<value>30 minutes</value>
<value>30</value>
</data>
<data name="LockOption5Minutes" xml:space="preserve">
<value>5 minutes</value>
<value>5</value>
</data>
<data name="SetPINDescription" xml:space="preserve">
<value>Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application.</value>
@@ -1468,52 +1468,52 @@
<value>Your vault is locked. Verify your PIN code to continue.</value>
</data>
<data name="Dark" xml:space="preserve">
<value>Dark</value>
<value>어두운 테마</value>
<comment>A dark color</comment>
</data>
<data name="Light" xml:space="preserve">
<value>Light</value>
<value>밝은 테마</value>
<comment>A light color</comment>
</data>
<data name="FiveMinutes" xml:space="preserve">
<value>5 minutes</value>
<value>5</value>
</data>
<data name="OneMinute" xml:space="preserve">
<value>1 minute</value>
<value>1</value>
</data>
<data name="TenSeconds" xml:space="preserve">
<value>10 seconds</value>
<value>10</value>
</data>
<data name="ThirtySeconds" xml:space="preserve">
<value>30 seconds</value>
<value>30</value>
</data>
<data name="TwentySeconds" xml:space="preserve">
<value>20 seconds</value>
<value>20</value>
</data>
<data name="TwoMinutes" xml:space="preserve">
<value>2 minutes</value>
<value>2</value>
</data>
<data name="ClearClipboard" xml:space="preserve">
<value>Clear Clipboard</value>
<value>클립보드 비우기</value>
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
</data>
<data name="ClearClipboardDescription" xml:space="preserve">
<value>Automatically clear copied values from your clipboard.</value>
<value>클립보드에 복사된 값을 자동으로 제거합니다.</value>
<comment>Clipboard is the operating system thing where you copy/paste data to on your device.</comment>
</data>
<data name="DefaultUriMatchDetection" xml:space="preserve">
<value>Default URI Match Detection</value>
<value>기본 URI 일치 인식</value>
<comment>Default URI match detection for auto-fill.</comment>
</data>
<data name="DefaultUriMatchDetectionDescription" xml:space="preserve">
<value>Choose the default way that URI match detection is handled for logins when performing actions such as auto-fill.</value>
</data>
<data name="Theme" xml:space="preserve">
<value>Theme</value>
<value>테마</value>
<comment>Color theme</comment>
</data>
<data name="ThemeDescription" xml:space="preserve">
<value>Change the application's color theme.</value>
<value>애플리케이션의 색상 테마를 변경합니다.</value>
</data>
<data name="RestartIsRequired" xml:space="preserve">
<value>Restart is required.</value>
@@ -1523,10 +1523,10 @@
<value>Restarting...</value>
</data>
<data name="CopyNotes" xml:space="preserve">
<value>Copy Notes</value>
<value>메모 복사</value>
</data>
<data name="Exit" xml:space="preserve">
<value>Exit</value>
<value>끝내기</value>
</data>
<data name="ExitConfirmation" xml:space="preserve">
<value>Are you sure you want to exit Bitwarden?</value>
@@ -1535,7 +1535,7 @@
<value>You you want to require unlocking with your master password when the application is restarted?</value>
</data>
<data name="Black" xml:space="preserve">
<value>Black</value>
<value>검은 테마</value>
<comment>The color black</comment>
</data>
<data name="BlacklistedUris" xml:space="preserve">
@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

View File

@@ -1557,22 +1557,34 @@
<value>Auto-utfylling gjør det lett å få sikker tilgang til Bitwarden-hvelvet ditt fra andre nettsteder og apper. Det ser ut til at du ikke har skrudd på noen auto-utfyllingstjenester for Bitwarden. Du kan skru på auto-utfylling for Bitwarden i «Innstillinger»-menyen.</value>
</data>
<data name="ThemeAppliedOnRestart" xml:space="preserve">
<value>Your theme changes will apply when the app is restarted.</value>
<value>Temaendringene dine vil gjelde når appen startes på nytt.</value>
</data>
<data name="Capitalize" xml:space="preserve">
<value>Capitalize</value>
<value>Stor forbokstav</value>
<comment>ex. Uppercase the first character of a word.</comment>
</data>
<data name="IncludeNumber" xml:space="preserve">
<value>Include Number</value>
<value>Inkluder nummer</value>
</data>
<data name="Download" xml:space="preserve">
<value>Download</value>
<value>Last ned</value>
</data>
<data name="Shared" xml:space="preserve">
<value>Shared</value>
<value>Delt</value>
</data>
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visibility</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -412,7 +412,7 @@
<value>bitwarden rozšírenie aplikácie</value>
</data>
<data name="BitwardenAppExtensionAlert2" xml:space="preserve">
<value>The easiest way to add new logins to your vault is from the Bitwarden App Extension. Learn more about using the Bitwarden App Extension by navigating to the "Settings" screen.</value>
<value>Najjednoduchší spôsob, ako pridať nové prihlasovacie údaje do trezora, je z Bitwarden rozšírenia aplikácie. Ďalšie informácie o používaní Bitwarden rozšírenia aplikácie získate prechodom do obrazovky Nastavení.</value>
</data>
<data name="BitwardenAppExtensionDescription" xml:space="preserve">
<value>Použiť bitwarden v Safari a iných aplikáciach pre automatické vypĺňanie prihlasovacích údajov.</value>
@@ -753,7 +753,7 @@
<value>Stav</value>
</data>
<data name="BitwardenAutofillServiceAlert2" xml:space="preserve">
<value>The easiest way to add new logins to your vault is from the Bitwarden Auto-fill Service. Learn more about using the Bitwarden Auto-fill Service by navigating to the "Settings" screen.</value>
<value>Najjednoduchší spôsob, ako pridať nové prihlasovacie údaje do trezora, je z bitwarden služby automatického vypĺňania. Ďalšie informácie o používaní bitwarden automatického vypĺňania získate prechodom do obrazovky Nastavení.</value>
</data>
<data name="Autofill" xml:space="preserve">
<value>Automatické dopĺňanie</value>
@@ -1302,7 +1302,7 @@
<value>Automatické vypĺňanie hesiel</value>
</data>
<data name="BitwardenAutofillAlert2" xml:space="preserve">
<value>The easiest way to add new logins to your vault is by using the Bitwarden Password AutoFill extension. Learn more about using the Bitwarden Password AutoFill extension by navigating to the "Settings" screen.</value>
<value>Najjednoduchší spôsob, ako pridať nové prihlasovacie údaje do trezora, je z Bitwarden rozšírenia pre automatické vypĺňanie. Ďalšie informácie o používaní rozšírenia pre automatické dopĺňanie získate prechodom na obrazovku Nastavení.</value>
</data>
<data name="InvalidEmail" xml:space="preserve">
<value>Neplatná emailová adresa.</value>
@@ -1352,7 +1352,7 @@
<value>Typy</value>
</data>
<data name="NoPasswordsToList" xml:space="preserve">
<value>No passwords to list.</value>
<value>Nenašli sa žiadne heslá.</value>
</data>
<data name="NoItemsToList" xml:space="preserve">
<value>Neexistujú žiadne položky na zobrazenie.</value>
@@ -1388,7 +1388,7 @@
<value>Neexistujú žiadne zbierky na zobrazenie.</value>
</data>
<data name="ItemShared" xml:space="preserve">
<value>Item has been shared.</value>
<value>Položka bola zdieľaná.</value>
</data>
<data name="SelectOneCollection" xml:space="preserve">
<value>Musíte vybrať aspoň jednu zbierku.</value>
@@ -1400,7 +1400,7 @@
<value>Zdieľať položku</value>
</data>
<data name="NoOrgsToList" xml:space="preserve">
<value>No organizations to list.</value>
<value>Nenašli sa žiadne organizácie.</value>
</data>
<data name="ShareDesc" xml:space="preserve">
<value>Vyberte organizáciu s ktorou chcete zdieľať túto položku. Zdieľanie presunie vlastníctvo položky na organizáciu. Po zdieľaní už nebudete priamy vlastník položky.</value>
@@ -1458,7 +1458,7 @@
<value>Nastaviť kód PIN na odomknutie Bitwarden. Nastavenie PIN sa vynuluje, ak úplne odhlásite z aplikácie.</value>
</data>
<data name="LoggedInAsOn" xml:space="preserve">
<value>Logged in as {0} on {1}.</value>
<value>Prihlásený ako {0} na {1}.</value>
<comment>ex: Logged in as user@example.com on bitwarden.com.</comment>
</data>
<data name="VaultLockedMasterPassword" xml:space="preserve">
@@ -1539,40 +1539,52 @@
<comment>The color black</comment>
</data>
<data name="BlacklistedUris" xml:space="preserve">
<value>Blacklisted URIs</value>
<value>Zakázané URI</value>
</data>
<data name="BlacklistedUrisDescription" xml:space="preserve">
<value>URIs that are blacklisted will not offer auto-fill. The list of apps should be comma separated. Ex: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="DisableSavePrompt" xml:space="preserve">
<value>Disable Save Prompt</value>
<value>Nepýtať sa na ukládanie</value>
</data>
<data name="DisableSavePromptDescription" xml:space="preserve">
<value>The "Save Prompt" automatically prompts you to save new items to your vault whenever you enter them for the first time.</value>
<value>"Notifikácia Pridať prihlasovacie údaje" vás automaticky upozorní na uloženie nových údajov do vášho trezoru vždy, keď sa s nimi prihlasujete po prvý krát.</value>
</data>
<data name="LockOptionOnRestart" xml:space="preserve">
<value>On App Restart</value>
<value>Po reštarte aplikácie</value>
</data>
<data name="AutofillServiceNotEnabled" xml:space="preserve">
<value>Auto-fill makes it easy to securely access your Bitwarden vault from other websites and apps. It looks like you have not enabled an auto-fill service for Bitwarden. Enable auto-fill for Bitwarden from the "Settings" screen.</value>
<value>Automatické dopĺňanie umožňuje jednoducho pristupovať k vašemu trezoru z iných stránok a plaikácii. Vyzerá že ste službu automatického dopĺňania pomocou Bitwarden nezapli. Zapnúť ju môžete na obrazove "Nastavenia".</value>
</data>
<data name="ThemeAppliedOnRestart" xml:space="preserve">
<value>Your theme changes will apply when the app is restarted.</value>
<value>Zmena nastavení vzhľadu sa prejaví po opätovnom štarte aplikácie.</value>
</data>
<data name="Capitalize" xml:space="preserve">
<value>Capitalize</value>
<value>Prvé písmeno veľkým</value>
<comment>ex. Uppercase the first character of a word.</comment>
</data>
<data name="IncludeNumber" xml:space="preserve">
<value>Include Number</value>
<value>Zahrnúť číslo</value>
</data>
<data name="Download" xml:space="preserve">
<value>Download</value>
<value>Stiahnuť</value>
</data>
<data name="Shared" xml:space="preserve">
<value>Shared</value>
<value>Zdieľané</value>
</data>
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

View File

@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

View File

@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1575,4 +1575,16 @@
<data name="ToggleVisibility" xml:space="preserve">
<value>Toggle Visiblity</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>Your login session has expired.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value>
</data>
<data name="Biometrics" xml:space="preserve">
<value>Biometrics</value>
</data>
<data name="UseBiometricsToUnlock" xml:space="preserve">
<value>Use Biometrics To Unlock</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
using Bit.App.Resources;
using Bit.Core.Abstractions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Resources;
@@ -18,6 +19,7 @@ namespace Bit.App.Services
private readonly CultureInfo _defaultCulture = new CultureInfo("en-US");
private bool _inited;
private StringComparer _stringComparer;
private Dictionary<string, string> _localeNames;
public MobileI18nService(CultureInfo systemCulture)
{
@@ -36,6 +38,57 @@ namespace Bit.App.Services
return _stringComparer;
}
}
public Dictionary<string, string> LocaleNames
{
get
{
if(_localeNames == null)
{
_localeNames = new Dictionary<string, string>
{
["af"] = "Afrikaans",
["bg"] = "български",
["ca"] = "català",
["cs"] = "čeština",
["da"] = "dansk",
["de"] = "Deutsch",
["el"] = "Ελληνικά",
["en"] = "English",
["en-GB"] = "English (British)",
["eo"] = "Esperanto",
["es"] = "español",
["et"] = "eesti",
["fa"] = "فارسی",
["fi"] = "suomi",
["fr"] = "français",
["he"] = "עברית",
["hi"] = "हिन्दी",
["hr"] = "hrvatski",
["hu"] = "magyar",
["id"] = "Bahasa Indonesia",
["it"] = "italiano",
["ja"] = "日本語",
["ko"] = "한국어",
["nb"] = "norsk (bokmål)",
["nl"] = "Nederlands",
["pl"] = "polski",
["pt-BR"] = "português do Brasil",
["pt-PT"] = "português",
["ro"] = "română",
["ru"] = "русский",
["sk"] = "slovenčina",
["sv"] = "svenska",
["th"] = "ไทย",
["tr"] = "Türkçe",
["uk"] = "українська",
["vi"] = "Tiếng Việt",
["zh-CN"] = "中文(中国大陆)",
["zh-TW"] = "中文(台灣)"
};
}
return _localeNames;
}
}
public void Init(CultureInfo culture = null)
{

View File

@@ -197,45 +197,45 @@ namespace Bit.App.Services
return await Clipboard.GetTextAsync();
}
public async Task<bool> SupportsFingerprintAsync()
public async Task<bool> SupportsBiometricAsync()
{
try
return await _deviceActionService.BiometricAvailableAsync();
}
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
Action fallback = null)
{
if(_deviceActionService.UseNativeBiometric())
{
return await CrossFingerprint.Current.IsAvailableAsync();
return await _deviceActionService.AuthenticateBiometricAsync(text);
}
catch
else
{
try
{
if(text == null)
{
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
}
var fingerprintRequest = new AuthenticationRequestConfiguration(text)
{
CancelTitle = AppResources.Cancel,
FallbackTitle = fallbackText
};
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest);
if(result.Authenticated)
{
return true;
}
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
{
fallback?.Invoke();
}
}
catch { }
return false;
}
}
public async Task<bool> AuthenticateFingerprintAsync(string text = null, string fallbackText = null,
Action fallback = null)
{
try
{
if(text == null)
{
text = _deviceActionService.SupportsFaceId() ? AppResources.FaceIDDirection :
AppResources.FingerprintDirection;
}
var fingerprintRequest = new AuthenticationRequestConfiguration(text)
{
CancelTitle = AppResources.Cancel,
FallbackTitle = fallbackText
};
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest);
if(result.Authenticated)
{
return true;
}
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
{
fallback?.Invoke();
}
}
catch { }
return false;
}
}
}

View File

@@ -22,7 +22,8 @@
<Setter Property="Margin"
Value="-4, 0, -4, -4" />
</Style>
<Style TargetType="SearchBar">
<Style TargetType="SearchBar"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="Transparent" />
<Setter Property="TextColor"
@@ -111,7 +112,7 @@
Class="list-icon-platform"
ApplyToDerivedTypes="True">
<Setter Property="FontSize"
Value="10" />
Value="23" />
</Style>
<Style TargetType="Button"
ApplyToDerivedTypes="True"
@@ -138,6 +139,8 @@
Class="box-row-button-platform">
<Setter Property="WidthRequest"
Value="37" />
<Setter Property="HeightRequest"
Value="45" />
<Setter Property="FontSize"
Value="25" />
</Style>

View File

@@ -126,9 +126,9 @@
<Style TargetType="Label"
Class="list-header">
<Setter Property="HorizontalOptions"
Value="StartAndExpand" />
Value="FillAndExpand" />
<Setter Property="VerticalOptions"
Value="CenterAndExpand" />
Value="FillAndExpand" />
<Setter Property="VerticalTextAlignment"
Value="Center" />
</Style>

View File

@@ -35,11 +35,12 @@
<Setter Property="OnColor"
Value="{StaticResource SwitchOnColor}" />
</Style>
<Style TargetType="SearchBar">
<Style TargetType="SearchBar"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{StaticResource ListHeaderBackgroundColor}" />
<Setter Property="TextColor"
Value="Black" />
Value="{StaticResource TextColor}" />
<Setter Property="CancelButtonColor"
Value="{StaticResource PrimaryColor}" />
<Setter Property="PlaceholderColor"

View File

@@ -124,23 +124,20 @@ namespace Bit.App.Utilities
{
var currentBuild = deviceActionService.GetBuildNumber();
var lastBuild = await storageService.GetAsync<string>(Constants.LastBuildKey);
if(!Migration.MigrationHelpers.NeedsMigration())
if(lastBuild == null)
{
if(lastBuild == null)
// Installed
var currentLock = await storageService.GetAsync<int?>(Constants.LockOptionKey);
if(currentLock == null)
{
// Installed
var currentLock = await storageService.GetAsync<int?>(Constants.LockOptionKey);
if(currentLock == null)
{
await storageService.SaveAsync(Constants.LockOptionKey, 15);
}
}
else if(lastBuild != currentBuild)
{
// Updated
var tasks = Task.Run(() => syncService.FullSyncAsync(true));
await storageService.SaveAsync(Constants.LockOptionKey, 15);
}
}
else if(lastBuild != currentBuild)
{
// Updated
var tasks = Task.Run(() => syncService.FullSyncAsync(true));
}
if(lastBuild != currentBuild)
{
await storageService.SaveAsync(Constants.LastBuildKey, currentBuild);

View File

@@ -1,12 +1,16 @@
using Bit.App.Services;
using Bit.App.Abstractions;
using Bit.App.Services;
using Bit.App.Styles;
using Bit.Core;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Utilities
{
public static class ThemeManager
{
public static bool UsingLightTheme = true;
public static void SetThemeStyle(string name)
{
// Reset styles
@@ -20,18 +24,36 @@ namespace Bit.App.Utilities
if(name == "dark")
{
Application.Current.Resources.MergedDictionaries.Add(new Dark());
UsingLightTheme = false;
}
else if(name == "black")
{
Application.Current.Resources.MergedDictionaries.Add(new Black());
UsingLightTheme = false;
}
else if(name == "nord")
{
Application.Current.Resources.MergedDictionaries.Add(new Nord());
UsingLightTheme = false;
}
else if(name == "light")
{
Application.Current.Resources.MergedDictionaries.Add(new Light());
UsingLightTheme = true;
}
else
{
Application.Current.Resources.MergedDictionaries.Add(new Light());
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService", true);
if(deviceActionService?.UsingDarkTheme() ?? false)
{
Application.Current.Resources.MergedDictionaries.Add(new Dark());
UsingLightTheme = false;
}
else
{
Application.Current.Resources.MergedDictionaries.Add(new Light());
UsingLightTheme = true;
}
}
// Base styles

View File

@@ -22,7 +22,7 @@ namespace Bit.Core.Abstractions
Task<CipherString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<CipherString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
Task<byte[]> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetEncKeyAsync();
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
Task<SymmetricCryptoKey> GetKeyAsync();
Task<string> GetKeyHashAsync();
@@ -35,7 +35,8 @@ namespace Bit.Core.Abstractions
Task<bool> HasKeyAsync();
Task<Tuple<SymmetricCryptoKey, CipherString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations,
CipherString protectedKeyCs = null);
Task<Tuple<string, CipherString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations);
Task<Tuple<CipherString, SymmetricCryptoKey>> MakeShareKeyAsync();
@@ -49,4 +50,4 @@ namespace Bit.Core.Abstractions
Task SetOrgKeysAsync(IEnumerable<ProfileOrganizationResponse> orgs);
Task ToggleKeyAsync();
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Bit.Core.Abstractions
@@ -7,7 +8,8 @@ namespace Bit.Core.Abstractions
{
CultureInfo Culture { get; set; }
StringComparer StringComparer { get; }
Dictionary<string, string> LocaleNames { get; }
string T(string id, string p1 = null, string p2 = null, string p3 = null);
string Translate(string id, string p1 = null, string p2 = null, string p3 = null);
}
}
}

View File

@@ -1,11 +1,12 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
namespace Bit.Core.Abstractions
{
public interface ILockService
{
bool PinLocked { get; set; }
CipherString PinProtectedKey { get; set; }
bool FingerprintLocked { get; set; }
Task CheckLockAsync();
@@ -16,4 +17,4 @@ namespace Bit.Core.Abstractions
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false);
Task SetLockOptionAsync(int? lockOption);
}
}
}

View File

@@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
bool SupportsU2f();
bool SupportsDuo();
Task<bool> SupportsFingerprintAsync();
Task<bool> AuthenticateFingerprintAsync(string text = null, string fallbackText = null, Action fallback = null);
Task<bool> SupportsBiometricAsync();
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
}
}

View File

@@ -8,7 +8,7 @@ namespace Bit.Core.Abstractions
{
bool SyncInProgress { get; set; }
Task<bool> FullSyncAsync(bool forceSync);
Task<bool> FullSyncAsync(bool forceSync, bool allowThrowOnError = false);
Task<DateTime?> GetLastSyncAsync();
Task SetLastSyncAsync(DateTime date);
Task<bool> SyncDeleteCipherAsync(SyncCipherNotification notification);
@@ -16,4 +16,4 @@ namespace Bit.Core.Abstractions
Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
}
}
}

View File

@@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Include="LiteDB" Version="4.1.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="PCLCrypto" Version="2.0.147" />
</ItemGroup>

View File

@@ -48,7 +48,7 @@ namespace Bit.Core.Models.View
_subTitle = Brand;
if(Number != null && Number.Length >= 4)
{
if(string.IsNullOrWhiteSpace(_subTitle))
if(!string.IsNullOrWhiteSpace(_subTitle))
{
_subTitle += ", ";
}

View File

@@ -25,18 +25,22 @@ namespace Bit.Core.Services
private readonly ITokenService _tokenService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly Func<bool, Task> _logoutCallbackAsync;
private string _deviceType;
public ApiService(
ITokenService tokenService,
IPlatformUtilsService platformUtilsService,
Func<bool, Task> logoutCallbackAsync)
Func<bool, Task> logoutCallbackAsync,
string customUserAgent = null)
{
_tokenService = tokenService;
_platformUtilsService = platformUtilsService;
_logoutCallbackAsync = logoutCallbackAsync;
var device = _platformUtilsService.GetDevice();
_deviceType = device.ToString();
var device = (int)_platformUtilsService.GetDevice();
_httpClient.DefaultRequestHeaders.Add("Device-Type", device.ToString());
if(!string.IsNullOrWhiteSpace(customUserAgent))
{
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(customUserAgent);
}
}
public bool UrlsSet { get; private set; }
@@ -81,21 +85,21 @@ namespace Bit.Core.Services
{
var requestMessage = new HttpRequestMessage
{
Version = new Version(1, 0),
RequestUri = new Uri(string.Concat(IdentityBaseUrl, "/connect/token")),
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(request.ToIdentityToken(_platformUtilsService.IdentityClientId))
};
requestMessage.Headers.Add("Accept", "application/json");
requestMessage.Headers.Add("Device-Type", _deviceType);
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(requestMessage);
}
catch
catch (Exception e)
{
throw new ApiException(HandleWebError());
throw new ApiException(HandleWebError(e));
}
JObject responseJObject = null;
if(IsJsonResponse(response))
@@ -302,9 +306,9 @@ namespace Bit.Core.Services
{
using(var requestMessage = new HttpRequestMessage())
{
requestMessage.Version = new Version(1, 0);
requestMessage.Method = HttpMethod.Post;
requestMessage.RequestUri = new Uri(string.Concat(EventsBaseUrl, "/collect"));
requestMessage.Headers.Add("Device-Type", _deviceType);
var authHeader = await GetActiveBearerTokenAsync();
requestMessage.Headers.Add("Authorization", string.Concat("Bearer ", authHeader));
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request, _jsonSettings),
@@ -314,9 +318,9 @@ namespace Bit.Core.Services
{
response = await _httpClient.SendAsync(requestMessage);
}
catch
catch (Exception e)
{
throw new ApiException(HandleWebError());
throw new ApiException(HandleWebError(e));
}
if(!response.IsSuccessStatusCode)
{
@@ -356,6 +360,7 @@ namespace Bit.Core.Services
{
using(var requestMessage = new HttpRequestMessage())
{
requestMessage.Version = new Version(1, 0);
requestMessage.Method = method;
requestMessage.RequestUri = new Uri(string.Concat(ApiBaseUrl, path));
if(body != null)
@@ -377,7 +382,6 @@ namespace Bit.Core.Services
}
}
requestMessage.Headers.Add("Device-Type", _deviceType);
if(authed)
{
var authHeader = await GetActiveBearerTokenAsync();
@@ -393,9 +397,9 @@ namespace Bit.Core.Services
{
response = await _httpClient.SendAsync(requestMessage);
}
catch
catch (Exception e)
{
throw new ApiException(HandleWebError());
throw new ApiException(HandleWebError(e));
}
if(hasResponse && response.IsSuccessStatusCode)
{
@@ -422,6 +426,7 @@ namespace Bit.Core.Services
var decodedToken = _tokenService.DecodeToken();
var requestMessage = new HttpRequestMessage
{
Version = new Version(1, 0),
RequestUri = new Uri(string.Concat(IdentityBaseUrl, "/connect/token")),
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(new Dictionary<string, string>
@@ -432,16 +437,15 @@ namespace Bit.Core.Services
})
};
requestMessage.Headers.Add("Accept", "application/json");
requestMessage.Headers.Add("Device-Type", _deviceType);
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(requestMessage);
}
catch
catch (Exception e)
{
throw new ApiException(HandleWebError());
throw new ApiException(HandleWebError(e));
}
if(response.IsSuccessStatusCode)
{
@@ -457,12 +461,12 @@ namespace Bit.Core.Services
}
}
private ErrorResponse HandleWebError()
private ErrorResponse HandleWebError(Exception e)
{
return new ErrorResponse
{
StatusCode = HttpStatusCode.BadGateway,
Message = "There is a problem connecting to the server."
Message = "Exception message: " + e.Message
};
}

View File

@@ -19,6 +19,7 @@ namespace Bit.Core.Services
private readonly II18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IMessagingService _messagingService;
private readonly ILockService _lockService;
private readonly bool _setCryptoKeys;
private SymmetricCryptoKey _key;
@@ -34,6 +35,7 @@ namespace Bit.Core.Services
II18nService i18nService,
IPlatformUtilsService platformUtilsService,
IMessagingService messagingService,
ILockService lockService,
bool setCryptoKeys = true)
{
_cryptoService = cryptoService;
@@ -44,6 +46,7 @@ namespace Bit.Core.Services
_i18nService = i18nService;
_platformUtilsService = platformUtilsService;
_messagingService = messagingService;
_lockService = lockService;
_setCryptoKeys = setCryptoKeys;
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
@@ -312,6 +315,7 @@ namespace Bit.Core.Services
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
}
_lockService.FingerprintLocked = false;
_messagingService.Send("loggedIn");
return result;
}

View File

@@ -118,7 +118,7 @@ namespace Bit.Core.Services
return _keyHash;
}
public Task<SymmetricCryptoKey> GetEncKeyAsync()
public Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null)
{
if(_encKey != null)
{
@@ -138,7 +138,10 @@ namespace Bit.Core.Services
return null;
}
var key = await GetKeyAsync();
if(key == null)
{
key = await GetKeyAsync();
}
if(key == null)
{
return null;
@@ -386,14 +389,17 @@ namespace Bit.Core.Services
}
public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt,
KdfType kdf, int kdfIterations)
KdfType kdf, int kdfIterations, CipherString protectedKeyCs = null)
{
var pinProtectedKey = await _storageService.GetAsync<string>(Constants.PinProtectedKey);
if(pinProtectedKey == null)
if(protectedKeyCs == null)
{
throw new Exception("No PIN protected key found.");
var pinProtectedKey = await _storageService.GetAsync<string>(Constants.PinProtectedKey);
if(pinProtectedKey == null)
{
throw new Exception("No PIN protected key found.");
}
protectedKeyCs = new CipherString(pinProtectedKey);
}
var protectedKeyCs = new CipherString(pinProtectedKey);
var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations);
var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey);
return new SymmetricCryptoKey(decKey);

Some files were not shown because too many files have changed in this diff Show More