1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-24 04:04:34 +00:00

Compare commits

...

64 Commits

Author SHA1 Message Date
Federico Maccaroni
fc2a91c435 PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet 2023-08-07 11:48:21 -03:00
Jacob Fink
4e1361e94a renaming bio key and fix build 2023-08-07 09:59:23 -04:00
Jacob Fink
d6567fe819 Merge branch 'auth/pm-2713/drop-master-key-dependency' into add-bio-key-migration 2023-08-07 09:57:53 -04:00
Federico Maccaroni
bf749d39de PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called 2023-08-04 18:39:16 -03:00
Jacob Fink
2522bbc60f Get UserKey from bio state on unlock 2023-08-04 16:34:27 -04:00
Jacob Fink
b620a9c09f add state for biometric key and accept UserKey instead of string for auto key 2023-08-04 16:20:39 -04:00
Jacob Fink
903f099134 [PM-2713] rename ephemeral pin key 2023-08-04 15:58:04 -04:00
Jacob Fink
fd5ef49811 [PM-2713] add async to key connector service methods 2023-08-04 13:38:04 -04:00
Jacob Fink
78004dbdb9 [PM-2713] clear service cache when adding new account 2023-08-03 16:28:04 -04:00
Jacob Fink
e820408a64 [PM-2713] make method async again
- returning null from a task thats not async throws
2023-08-03 15:44:41 -04:00
Jacob Fink
c595b1626e fix formatting 2023-08-03 14:34:50 -04:00
Jacob Fink
270a395d9f [PM-2713] set enc user key during kc onboarding 2023-08-03 13:41:19 -04:00
Jacob Fink
4fa8d2ba28 [PM-2713] set user key on set password page 2023-08-03 13:30:45 -04:00
Jacob Fink
e076c9fe04 [PM-2713] add auto unlock key to mobile 2023-08-03 10:49:55 -04:00
Jacob Fink
ee0dcd23f5 rename account keys to be more descriptive 2023-08-01 20:08:41 -04:00
Jacob Fink
1e8ed1b5ce [PM-2713] replace generic with inherited class 2023-08-01 10:47:02 -04:00
Jacob Fink
7fb89fa1a5 [PM-2713] consolidate attachment key creation
- also fix ios files missed during symbol rename
2023-08-01 09:30:00 -04:00
Jacob Fink
b1eb263fef [PM-2713] combine makeDataEncKey methods 2023-08-01 08:54:19 -04:00
Jacob Fink
61aac20555 [PM-2713] rename state methods 2023-08-01 08:46:02 -04:00
Jacob Fink
3e87d74061 [PM-2713] revert feedback for build 2023-07-31 17:09:47 -04:00
Jacob Fink
89a9185b20 [PM-2713] rename get pin lock type method 2023-07-31 16:49:41 -04:00
Jacob Fink
e323e196c0 [PM-2713] PR feedback 2023-07-31 15:31:46 -04:00
Jacob Fink
c793260689 [PM-2713] pr feedback 2023-07-31 13:13:29 -04:00
Jacob Fink
c2ddbb7eff [PM-2713] rename toggle method, don't reset enc user key 2023-07-31 12:57:50 -04:00
Jacob Fink
bb5a7383a8 [PM-2713] don't pass user key as param when encrypting 2023-07-31 12:42:56 -04:00
Jacob Fink
de5113ede7 [PM-2713] rename PinLockEnum to PinLockType 2023-07-27 16:26:23 -04:00
Jacob Fink
ba6d260565 [PM-2713] rename PrivateKey methods to UserPrivateKey on crypto service 2023-07-27 16:24:31 -04:00
Jacob Fink
7562c688c5 [PM-2713] deconstruct new key pair 2023-07-27 16:20:33 -04:00
Jacob Fink
10574a7117 [PM-2713] remove broken casting 2023-07-20 16:17:30 -04:00
Jacob Fink
a2f1ca583a [PM-2713] remove extra comment 2023-07-20 16:14:40 -04:00
Jacob Fink
813ac841c6 [PM-2713] fix casting issues and pin 2023-07-20 13:57:32 -04:00
Jacob Fink
0da3d25955 [PM-2713] rename password hash to master key hash 2023-07-20 12:30:02 -04:00
Jacob Fink
f8c9cde2ed [PM-2713] optimize async code in crypto service 2023-07-20 11:17:57 -04:00
Jacob Fink
a3183857b9 [PM-2713] set decrypt and set user key in login helper 2023-07-19 21:23:21 -04:00
Jacob Fink
bedbca841d [PM-2713] remove unused cached values in crypto service 2023-07-19 21:23:21 -04:00
Jacob Fink
0ff314f076 [PM-2713] use new crypto service api in auth service 2023-07-19 21:23:21 -04:00
Jacob Fink
c9a7c29190 [PM-2713] More conversions to crypto api 2023-07-19 21:23:20 -04:00
Jacob Fink
546bf8dcb1 [PM-2713] convert cipher service and others to crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7fdc5597fc [PM-2713] more conversions to new crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7c664f58b3 [PM-2713] add migration for pin on lock screens 2023-07-19 21:23:20 -04:00
Jacob Fink
bdfe806846 [PM-2713] converting calls to new crypto service api 2023-07-19 21:23:19 -04:00
Jacob Fink
5ed567ab90 [PM-2713] add toggle method to crypto service for keys 2023-07-19 21:23:19 -04:00
Jacob Fink
cd4f44e6f6 [PM-2713] use new MakeMasterKey method 2023-07-19 21:23:19 -04:00
Jacob Fink
d58f0b281b [PM-2713] refresh pin key when setting user key 2023-07-19 21:23:19 -04:00
Jacob Fink
5ba3fac0c0 [PM-2713] add make user key method to crypto service 2023-07-19 21:23:19 -04:00
Jacob Fink
1e30524985 [PM-2713] fix signature of GetUserKeyPin 2023-07-19 21:23:18 -04:00
Jacob Fink
515decb4c9 [PM-2713] add new pin methods to state service 2023-07-19 21:23:18 -04:00
Jacob Fink
bf28d373e9 [PM-2713] more updates to crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
69d38d4d75 [PM-2713] continue organizing crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
c1619536aa [PM-2713] rename key hash to password hash & begin add methods to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
079e02e4e5 [PM-271] add UserKey and MasterKey support to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
15d3da607b [PM-2713] add new state for new keys and obsolete old ones
- UserKey
- MasterKey
- UserKeyMasterKey (enc UserKey from User Table)
2023-07-19 21:23:17 -04:00
Jacob Fink
b5cf9fd79d [PM-2731] add user key and master key types 2023-07-19 21:23:17 -04:00
Federico Maccaroni
dd52ff0dcc [PM-2320] Improve Android block Auto-fill URIs (#2616)
* PM-2320 Added new view for block autofill URIs on Android

* PM-2320 Fix formatting

* PM-2320 Improved validations on block autofill uris

* PM-2320 Improved autofill block uris placeholder colors on different themes
2023-07-18 11:25:38 -03:00
github-actions[bot]
c678c17ebc Bumped version to 2023.7.1 (#2625)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-18 14:23:08 +00:00
Federico Maccaroni
cd9e49b13b ac-1425 added main thread invocations when updating the vault properties to fix cases where the screen stays blank and doesn't update (#2604) 2023-07-17 16:53:30 -03:00
Federico Maccaroni
6d7970f767 [AC-762] Configure Crowdin to localize watch app (#2552)
* AC-762 Added localization files to watch project

* AC-762 Added crowdin config for watchOS localizable files
2023-07-17 13:35:54 -03:00
mpbw2
9adc4d3080 Catch additional exception types when validating intents (#2618) 2023-07-17 08:40:35 -04:00
ifernandezdiaz
1f20f70d13 Fixing show value id button (#2620) 2023-07-16 20:13:55 -03:00
Vince Grassia
a25da68437 Fix syntax in Version Auto Bump workflow (#2615) 2023-07-13 11:55:16 -04:00
Vince Grassia
fdc0313d10 Fix Build Workflow (#2613) 2023-07-13 10:05:57 -04:00
github-actions[bot]
f31c87b52e Bumped version to 2023.7.0 (#2612)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-12 17:34:23 +00:00
Cesar Gonzalez
1e79e1182f [PM-1063] Re-prompt for Master Password Can be Bypassed When Using Gboard Inline Autofill (#2593) 2023-07-11 08:15:37 -05:00
github-actions[bot]
11947ce99a Autosync the updated translations (#2603)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-07 05:13:18 +00:00
145 changed files with 3177 additions and 1221 deletions

View File

@@ -71,6 +71,11 @@ jobs:
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
- name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '3.1.x'
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1 uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1

View File

@@ -32,14 +32,10 @@ jobs:
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump: trigger_version_bump:
name: "Version bump" name: Bump version to ${{ needs.setup.outputs.version_number }}
runs-on: ubuntu-22.04 needs: setup
needs: uses: ./.github/workflows/version-bump.yml
- setup secrets:
steps: AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Bump version to ${{ needs.setup.outputs.version_number }} with:
uses: ./.github/workflows/version-bump.yml version_number: ${{ needs.setup.outputs.version_number }}
secrets:
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
with:
version_number: ${{ needs.setup.outputs.version_number }}

View File

@@ -38,3 +38,15 @@ files:
pt-PT: pt-PT pt-PT: pt-PT
en-GB: en-GB en-GB: en-GB
en-IN: en-IN en-IN: en-IN
- source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
update_option: update_as_unapproved
languages_mapping:
two_letters_code:
zh-CN: zh-Hans
zh-TW: zh-Hant
pt-BR: pt-BR
pt-PT: pt-PT
en-GB: en-GB
en-IN: en-IN

View File

@@ -233,6 +233,18 @@
<SubType></SubType> <SubType></SubType>
<Generator></Generator> <Generator></Generator>
</AndroidResource> </AndroidResource>
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" /> <AndroidResource Include="Resources\drawable\splash_screen.xml" />

View File

@@ -156,9 +156,9 @@ namespace Bit.Droid
messagingService, broadcasterService); messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>()); platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage); ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.5.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.7.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />

View File

@@ -1,10 +1,10 @@
using System; using System.ComponentModel;
using Bit.App.Controls;
using System.ComponentModel;
using Xamarin.Forms.Platform.Android;
using Android.Content; using Android.Content;
using Xamarin.Forms; using Android.OS;
using Bit.App.Controls;
using Bit.Droid.Renderers; using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))] [assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Bit.Droid.Renderers namespace Bit.Droid.Renderers
@@ -15,6 +15,19 @@ namespace Bit.Droid.Renderers
: base(context) : base(context)
{ } { }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is CustomLabel label)
{
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
var label = sender as CustomLabel; var label = sender as CustomLabel;
@@ -28,4 +41,3 @@ namespace Bit.Droid.Renderers
} }
} }
} }

View File

@@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="129"
android:viewportHeight="124"
android:width="129dp"
android:height="124dp">
<path
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
android:fillColor="#F0F0F0"
android:strokeColor="#89929F"
android:strokeWidth="3" />
<path
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
android:fillColor="#89929F" />
<path
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
android:fillColor="#89929F" />
<path
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
android:fillColor="#89929F" />
</vector>

View File

@@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="129"
android:viewportHeight="124"
android:width="129dp"
android:height="124dp">
<path
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
android:fillColor="@android:color/transparent"
android:strokeColor="#A3A3A3"
android:strokeWidth="3" />
<path
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
android:fillColor="#A3A3A3" />
<path
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
android:fillColor="#A3A3A3" />
<path
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
android:fillColor="#A3A3A3" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="30dp"
android:paddingRight="30dp">
<TextView
android:id="@+id/lblHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_header_text_size"
android:layout_marginTop="5dp"
android:layout_marginBottom="-3dp"
android:labelFor="@+id/txtValue"/>
<EditText
android:id="@id/txtValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_input_text_size"/>
<TextView
android:id="@+id/lblValueSubinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_sub_value_info_text_size"/>
</LinearLayout>

View File

@@ -2,4 +2,7 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen> <dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen> <dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
<dimen name="dialog_input_text_size">16sp</dimen>
<dimen name="dialog_header_text_size">12sp</dimen>
<dimen name="dialog_sub_value_info_text_size">12sp</dimen>
</resources> </resources>

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.OS; using Android.OS;
using Android.Security.Keystore; using Android.Security.Keystore;
using Bit.App.Services;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Java.Security; using Java.Security;
@@ -9,10 +10,8 @@ using Javax.Crypto;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
public class BiometricService : IBiometricService public class BiometricService : BaseBiometricService
{ {
private readonly IStateService _stateService;
private const string KeyName = "com.8bit.bitwarden.biometric_integrity"; private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
private const string KeyStoreName = "AndroidKeyStore"; private const string KeyStoreName = "AndroidKeyStore";
@@ -24,14 +23,14 @@ namespace Bit.Droid.Services
private readonly KeyStore _keystore; private readonly KeyStore _keystore;
public BiometricService(IStateService stateService) public BiometricService(IStateService stateService, ICryptoService cryptoService)
: base(stateService, cryptoService)
{ {
_stateService = stateService;
_keystore = KeyStore.GetInstance(KeyStoreName); _keystore = KeyStore.GetInstance(KeyStoreName);
_keystore.Load(null); _keystore.Load(null);
} }
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null) public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
{ {
if (Build.VERSION.SdkInt >= BuildVersionCodes.M) if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{ {
@@ -41,7 +40,7 @@ namespace Bit.Droid.Services
return true; return true;
} }
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
{ {
if (Build.VERSION.SdkInt < BuildVersionCodes.M) if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{ {

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Media;
using Android.Nfc; using Android.Nfc;
using Android.OS; using Android.OS;
using Android.Provider; using Android.Provider;
@@ -13,11 +14,17 @@ using Android.Views.InputMethods;
using Android.Widget; using Android.Widget;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.App.Utilities.Prompts;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using Xamarin.Forms.Platform.Android;
using static Android.Icu.Text.CaseMap;
using static Android.Renderscripts.ScriptGroup;
using static Android.Util.EventLogTags;
using static Bit.App.Pages.SettingsPageViewModel; using static Bit.App.Pages.SettingsPageViewModel;
namespace Bit.Droid.Services namespace Bit.Droid.Services
@@ -209,10 +216,7 @@ namespace Bit.Droid.Services
} }
if (numericKeyboard) if (numericKeyboard)
{ {
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned; SetNumericKeyboardTo(input);
#pragma warning disable CS0618 // Type or member is obsolete
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
} }
if (password) if (password)
{ {
@@ -248,6 +252,82 @@ namespace Bit.Droid.Services
return result.Task; return result.Task;
} }
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return Task.FromResult<ValidatablePromptResponse?>(null);
}
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetTitle(config.Title);
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
alertBuilder.SetView(view);
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
if (!string.IsNullOrEmpty(config.ThirdButtonText))
{
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
}
var alert = alertBuilder.Create();
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
lblHeader.Text = config.Subtitle;
lblValueSubinfo.Text = config.ValueSubInfo;
var defaultSubInfoColor = lblValueSubinfo.TextColors;
input.InputType = InputTypes.ClassText;
if (config.NumericKeyboard)
{
SetNumericKeyboardTo(input);
}
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
input.Text = config.Text ?? string.Empty;
input.SetSelection(config.Text?.Length ?? 0);
input.AfterTextChanged += (sender, args) =>
{
if (lblValueSubinfo.Text != config.ValueSubInfo)
{
lblValueSubinfo.Text = config.ValueSubInfo;
lblHeader.SetTextColor(defaultSubInfoColor);
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
}
};
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
alert.Show();
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
positiveButton.Click += (sender, args) =>
{
var error = config.ValidateText(input.Text);
if (error != null)
{
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.Text = error;
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
return;
}
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
alert.Dismiss();
};
return result.Task;
}
public void RateApp() public void RateApp()
{ {
var activity = (MainActivity)CrossCurrentActivity.Current.Activity; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -525,5 +605,13 @@ namespace Bit.Droid.Services
// only used by iOS // only used by iOS
throw new NotImplementedException(); throw new NotImplementedException();
} }
private void SetNumericKeyboardTo(EditText editText)
{
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Java.Lang;
namespace Bit.Droid.Utilities namespace Bit.Droid.Utilities
{ {
@@ -13,7 +14,12 @@ namespace Bit.Droid.Utilities
// Note: getting the bundle like this will cause to call unparcel() internally // Note: getting the bundle like this will cause to call unparcel() internally
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel"); var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
} }
catch (BadParcelableException) catch (Exception ex) when
(
ex is BadParcelableException ||
ex is ClassNotFoundException ||
ex is RuntimeException
)
{ {
intent.ReplaceExtras((Bundle)null); intent.ReplaceExtras((Bundle)null);
} }

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Utilities.Prompts;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
@@ -18,6 +19,7 @@ namespace Bit.App.Abstractions
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null, Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false, string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true, bool password = false); bool autofocus = true, bool password = false);
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config);
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons); Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons); Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);

View File

@@ -146,6 +146,7 @@
<Folder Include="Controls\IconLabelButton\" /> <Folder Include="Controls\IconLabelButton\" />
<Folder Include="Controls\PasswordStrengthProgressBar\" /> <Folder Include="Controls\PasswordStrengthProgressBar\" />
<Folder Include="Utilities\Automation\" /> <Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -442,5 +443,6 @@
<None Remove="MessagePack.MSBuild.Tasks" /> <None Remove="MessagePack.MSBuild.Tasks" />
<None Remove="Controls\PasswordStrengthProgressBar\" /> <None Remove="Controls\PasswordStrengthProgressBar\" />
<None Remove="Utilities\Automation\" /> <None Remove="Utilities\Automation\" />
<None Remove="Utilities\Prompts\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,4 @@
using System; using Xamarin.Forms;
using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
@@ -8,6 +7,7 @@ namespace Bit.App.Controls
public CustomLabel() public CustomLabel()
{ {
} }
public int? FontWeight { get; set; }
} }
} }

View File

@@ -46,7 +46,7 @@
<StackLayout StyleClass="box"> <StackLayout StyleClass="box">
<Grid <Grid
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding PinLock}" IsVisible="{Binding PinEnabled}"
Padding="0, 10, 0, 0"> Padding="0, 10, 0, 0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -89,7 +89,7 @@
<Grid <Grid
x:Name="_passwordGrid" x:Name="_passwordGrid"
StyleClass="box-row" StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}" IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0"> Padding="0, 10, 0, 0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />

View File

@@ -44,7 +44,7 @@ namespace Bit.App.Pages
{ {
get get
{ {
if (_vm?.PinLock ?? false) if (_vm?.PinEnabled ?? false)
{ {
return _pin; return _pin;
} }
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
public async Task PromptBiometricAfterResumeAsync() public async Task PromptBiometricAfterResumeAsync()
{ {
if (_vm.BiometricLock) if (_vm.BiometricEnabled)
{ {
await Task.Delay(500); await Task.Delay(500);
if (!_promptedAfterResume) if (!_promptedAfterResume)
@@ -91,13 +91,13 @@ namespace Bit.App.Pages
_vm.FocusSecretEntry += PerformFocusSecretEntry; _vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricLock) if (!_vm.BiometricEnabled)
{ {
RequestFocus(SecretEntry); RequestFocus(SecretEntry);
} }
else else
{ {
if (_vm.UsingKeyConnector && !_vm.PinLock) if (_vm.UsingKeyConnector && !_vm.PinEnabled)
{ {
_passwordGrid.IsVisible = false; _passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false; _unlockButton.IsVisible = false;

View File

@@ -38,16 +38,15 @@ namespace Bit.App.Pages
private string _masterPassword; private string _masterPassword;
private string _pin; private string _pin;
private bool _showPassword; private bool _showPassword;
private bool _pinLock; private PinLockType _pinStatus;
private bool _biometricLock; private bool _pinEnabled;
private bool _biometricEnabled;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _biometricButtonVisible; private bool _biometricButtonVisible;
private bool _usingKeyConnector; private bool _usingKeyConnector;
private string _biometricButtonText; private string _biometricButtonText;
private string _loggedInAsText; private string _loggedInAsText;
private string _lockedVerifyText; private string _lockedVerifyText;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel() public LockPageViewModel()
{ {
@@ -100,10 +99,10 @@ namespace Bit.App.Pages
}); });
} }
public bool PinLock public bool PinEnabled
{ {
get => _pinLock; get => _pinEnabled;
set => SetProperty(ref _pinLock, value); set => SetProperty(ref _pinEnabled, value);
} }
public bool UsingKeyConnector public bool UsingKeyConnector
@@ -111,10 +110,10 @@ namespace Bit.App.Pages
get => _usingKeyConnector; get => _usingKeyConnector;
} }
public bool BiometricLock public bool BiometricEnabled
{ {
get => _biometricLock; get => _biometricEnabled;
set => SetProperty(ref _biometricLock, value); set => SetProperty(ref _biometricEnabled, value);
} }
public bool BiometricIntegrityValid public bool BiometricIntegrityValid
@@ -162,14 +161,18 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync(); ?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _biometricService.CanUseBiometricsUnlockAsync();
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usingKeyConnector = await _keyConnectorService.GetUsesKeyConnectorAsync();
if (_usingKeyConnector && !(BiometricLock || PinLock)) if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
{ {
await _vaultTimeoutService.LogOutAsync(); await _vaultTimeoutService.LogOutAsync();
return; return;
@@ -188,7 +191,7 @@ namespace Bit.App.Pages
} }
var webVaultHostname = CoreHelpers.GetHostname(webVault); var webVaultHostname = CoreHelpers.GetHostname(webVault);
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname); LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
if (PinLock) if (PinEnabled)
{ {
PageTitle = AppResources.VerifyPIN; PageTitle = AppResources.VerifyPIN;
LockedVerifyText = AppResources.VaultLockedPIN; LockedVerifyText = AppResources.VaultLockedPIN;
@@ -207,7 +210,7 @@ namespace Bit.App.Pages
} }
} }
if (BiometricLock) if (BiometricEnabled)
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
@@ -229,14 +232,14 @@ namespace Bit.App.Pages
public async Task SubmitAsync() public async Task SubmitAsync()
{ {
if (PinLock && string.IsNullOrWhiteSpace(Pin)) if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN), string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
AppResources.Ok); AppResources.Ok);
return; return;
} }
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword)) if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{ {
await Page.DisplayAlert(AppResources.AnErrorHasOccurred, await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword), string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
@@ -247,34 +250,54 @@ namespace Bit.App.Pages
ShowPassword = false; ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinLock) if (PinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockType.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockType.Transient)
{
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockType.Transient,
Pin,
_email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
} }
else else
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig); userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
failed = false; Pin,
_email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != Pin;
if (!failed)
{
Pin = string.Empty; Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key); await SetUserKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -295,19 +318,21 @@ namespace Bit.App.Pages
} }
else else
{ {
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
var passwordValid = false; var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null; MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null) if (storedKeyHash != null)
{ {
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key); // Offline unlock possible
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
} }
else else
{ {
// Online unlock required
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest(); var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash; request.MasterPasswordHash = keyHash;
@@ -316,8 +341,8 @@ namespace Bit.App.Pages
var response = await _apiService.PostAccountVerifyPasswordAsync(request); var response = await _apiService.PostAccountVerifyPasswordAsync(request);
enforcedMasterPasswordOptions = response.MasterPasswordPolicy; enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
passwordValid = true; passwordValid = true;
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -327,15 +352,6 @@ namespace Bit.App.Pages
} }
if (passwordValid) if (passwordValid)
{ {
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
}
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions)) if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
{ {
// Save the ForcePasswordResetReason to force a password reset after unlock // Save the ForcePasswordResetReason to force a password reset after unlock
@@ -345,10 +361,13 @@ namespace Bit.App.Pages
MasterPassword = string.Empty; MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetUserKeyAndContinueAsync(userKey);
// Re-enable biometrics // Re-enable biometrics
if (BiometricLock & !BiometricIntegrityValid) if (BiometricEnabled & !BiometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(); await _biometricService.SetupBiometricAsync();
} }
@@ -425,7 +444,7 @@ namespace Bit.App.Pages
public void TogglePassword() public void TogglePassword()
{ {
ShowPassword = !ShowPassword; ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword; var secret = PinEnabled ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry)); _secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
} }
@@ -433,26 +452,27 @@ namespace Bit.App.Pages
{ {
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync(); BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid; BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricLock || !BiometricIntegrityValid) if (!BiometricEnabled || !BiometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword, PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry))); () => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
{ {
await DoContinueAsync(); var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
await SetUserKeyAndContinueAsync(userKey);
} }
} }
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key) private async Task SetUserKeyAndContinueAsync(UserKey key)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(key);
} }
await DoContinueAsync(); await DoContinueAsync();
} }

View File

@@ -177,25 +177,25 @@ namespace Bit.App.Pages
Name = string.IsNullOrWhiteSpace(Name) ? null : Name; Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
Email = Email.Trim().ToLower(); Email = Email.Trim().ToLower();
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
var encKey = await _cryptoService.MakeEncKeyAsync(key); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key); var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey);
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new RegisterRequest var request = new RegisterRequest
{ {
Email = Email, Email = Email,
Name = Name, Name = Name,
MasterPasswordHash = hashedPassword, MasterPasswordHash = hashedPassword,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Key = encKey.Item2.EncryptedString, Key = newProtectedUserKey.EncryptedString,
Kdf = kdfConfig.Type, Kdf = kdfConfig.Type,
KdfIterations = kdfConfig.Iterations, KdfIterations = kdfConfig.Iterations,
KdfMemory = kdfConfig.Memory, KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism, KdfParallelism = kdfConfig.Parallelism,
Keys = new KeysRequest Keys = new KeysRequest
{ {
PublicKey = keys.Item1, PublicKey = newPublicKey,
EncryptedPrivateKey = keys.Item2.EncryptedString EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
}, },
CaptchaResponse = _captchaToken, CaptchaResponse = _captchaToken,
}; };

View File

@@ -30,14 +30,14 @@ namespace Bit.App.Pages
public async Task Init() public async Task Init()
{ {
Organization = await _keyConnectorService.GetManagingOrganization(); Organization = await _keyConnectorService.GetManagingOrganizationAsync();
} }
public async Task MigrateAccount() public async Task MigrateAccount()
{ {
await _deviceActionService.ShowLoadingAsync(AppResources.Loading); await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _keyConnectorService.MigrateUser(); await _keyConnectorService.MigrateUserAsync();
await _syncService.FullSyncAsync(true); await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync(); await _deviceActionService.HideLoadingAsync();

View File

@@ -165,26 +165,17 @@ namespace Bit.App.Pages
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null); var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization); var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization); var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
Tuple<SymmetricCryptoKey, EncString> encKey; var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
var existingEncKey = await _cryptoService.GetEncKeyAsync();
if (existingEncKey == null)
{
encKey = await _cryptoService.MakeEncKeyAsync(key);
}
else
{
encKey = await _cryptoService.RemakeEncKeyAsync(key);
}
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1); var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new SetPasswordRequest var request = new SetPasswordRequest
{ {
MasterPasswordHash = masterPasswordHash, MasterPasswordHash = masterPasswordHash,
Key = encKey.Item2.EncryptedString, Key = newProtectedUserKey.EncryptedString,
MasterPasswordHint = Hint, MasterPasswordHint = Hint,
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256), Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations), KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
@@ -193,8 +184,8 @@ namespace Bit.App.Pages
OrgIdentifier = OrgIdentifier, OrgIdentifier = OrgIdentifier,
Keys = new KeysRequest Keys = new KeysRequest
{ {
PublicKey = keys.Item1, PublicKey = newPublicKey,
EncryptedPrivateKey = keys.Item2.EncryptedString EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
} }
}; };
@@ -204,19 +195,20 @@ namespace Bit.App.Pages
// Set Password and relevant information // Set Password and relevant information
await _apiService.SetPasswordAsync(request); await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfConfigurationAsync(kdfConfig); await _stateService.SetKdfConfigurationAsync(kdfConfig);
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(newUserKey);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash); await _cryptoService.SetMasterKeyAsync(newMasterKey);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString); await _cryptoService.SetMasterKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
await _cryptoService.SetUserPrivateKeyAsync(newProtectedPrivateKey.EncryptedString);
if (ResetPasswordAutoEnroll) if (ResetPasswordAutoEnroll)
{ {
// Grab Organization Keys // Grab Organization Keys
var response = await _apiService.GetOrganizationKeysAsync(OrgId); var response = await _apiService.GetOrganizationKeysAsync(OrgId);
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey); var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
// Grab user's Encryption Key and encrypt with Org Public Key // Grab User Key and encrypt with Org Public Key
var userEncKey = await _cryptoService.GetEncKeyAsync(); var userKey = await _cryptoService.GetUserKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey); var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
// Request // Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{ {

View File

@@ -93,12 +93,12 @@ namespace Bit.App.Pages
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
// Create new key and hash new password // Create new master key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key); var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey);
// Create new encKey for the User // Encrypt user key with new master key
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key); var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
// Initiate API action // Initiate API action
try try
@@ -108,10 +108,10 @@ namespace Bit.App.Pages
switch (_reason) switch (_reason)
{ {
case ForcePasswordResetReason.AdminForcePasswordReset: case ForcePasswordResetReason.AdminForcePasswordReset:
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString); await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break; break;
case ForcePasswordResetReason.WeakMasterPasswordOnLogin: case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString); await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
@@ -155,7 +155,7 @@ namespace Bit.App.Pages
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey) private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
{ {
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null); var currentPasswordHash = await _cryptoService.HashMasterKeyAsync(CurrentMasterPassword, null);
var request = new PasswordRequest var request = new PasswordRequest
{ {

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.BlockAutofillUrisPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:BlockAutofillUrisPageViewModel"
Title="{u:I18n BlockAutoFill}">
<ContentPage.BindingContext>
<pages:BlockAutofillUrisPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Orientation="Vertical">
<Image
x:Name="_emptyUrisPlaceholder"
HorizontalOptions="Center"
WidthRequest="120"
HeightRequest="120"
Margin="0,100,0,0"
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ThereAreNoBlockedURIs}" />
<controls:CustomLabel
StyleClass="box-label-regular"
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
FontWeight="500"
HorizontalTextAlignment="Center"
Margin="14,10,14,0"/>
<controls:ExtendedCollectionView
ItemsSource="{Binding BlockedUris}"
IsVisible="{Binding ShowList}"
VerticalOptions="FillAndExpand"
Margin="0,5,0,0"
SelectionMode="None"
StyleClass="list, list-platform"
ExtraDataForLogging="Blocked Autofill Uris"
AutomationId="BlockedUrisCellList">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="pages:BlockAutofillUriItemViewModel">
<StackLayout
Orientation="Vertical"
AutomationId="BlockedUriCell">
<StackLayout
Orientation="Horizontal">
<controls:CustomLabel
VerticalOptions="Center"
StyleClass="box-label-regular"
Text="{Binding Uri}"
MaxLines="2"
LineBreakMode="TailTruncation"
FontWeight="500"
Margin="15,0,0,0"
HorizontalOptions="StartAndExpand"/>
<controls:IconButton
StyleClass="box-row-button-muted, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.PencilSquare}}"
Command="{Binding EditUriCommand}"
Margin="5,0,15,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n EditURI}"
AutomationId="EditUriButton" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
<Button
Text="{u:I18n NewBlockedURI}"
Command="{Binding AddUriCommand}"
VerticalOptions="End"
HeightRequest="40"
Opacity="0.8"
Margin="14,5,14,10"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NewBlockedURI}"
AutomationId="NewBlockedUriButton" />
</StackLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,44 @@
using System.Threading.Tasks;
using Bit.App.Styles;
using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class BlockAutofillUrisPage : BaseContentPage, IThemeDirtablePage
{
private readonly BlockAutofillUrisPageViewModel _vm;
public BlockAutofillUrisPage()
{
InitializeComponent();
_vm = BindingContext as BlockAutofillUrisPageViewModel;
_vm.Page = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.InitAsync().FireAndForget(_ => Navigation.PopAsync());
UpdatePlaceholder();
}
public override async Task UpdateOnThemeChanged()
{
await base.UpdateOnThemeChanged();
UpdatePlaceholder();
}
private void UpdatePlaceholder()
{
MainThread.BeginInvokeOnMainThread(() =>
_emptyUrisPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_uris_placeholder" : "empty_uris_placeholder_dark"));
}
}
}

View File

@@ -0,0 +1,186 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class BlockAutofillUrisPageViewModel : BaseViewModel
{
private const char URI_SEPARARTOR = ',';
private const string URI_FORMAT = "https://domain.com";
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
public BlockAutofillUrisPageViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
AddUriCommand = new AsyncCommand(AddUriAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
public ObservableRangeCollection<BlockAutofillUriItemViewModel> BlockedUris { get; set; } = new ObservableRangeCollection<BlockAutofillUriItemViewModel>();
public bool ShowList => BlockedUris.Any();
public ICommand AddUriCommand { get; }
public ICommand EditUriCommand { get; }
public async Task InitAsync()
{
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
if (blockedUrisList?.Any() != true)
{
return;
}
await MainThread.InvokeOnMainThreadAsync(() =>
{
BlockedUris.AddRange(blockedUrisList.OrderBy(uri => uri).Select(u => new BlockAutofillUriItemViewModel(u, EditUriCommand)).ToList());
TriggerPropertyChanged(nameof(ShowList));
});
}
private async Task AddUriAsync()
{
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
{
Title = AppResources.NewUri,
Subtitle = AppResources.EnterURI,
ValueSubInfo = string.Format(AppResources.FormatXSeparateMultipleURIsWithAComma, URI_FORMAT),
OkButtonText = AppResources.Save,
ValidateText = text => ValidateUris(text, true)
});
if (response?.Text is null)
{
return;
}
await MainThread.InvokeOnMainThreadAsync(() =>
{
foreach (var uri in response.Value.Text.Split(URI_SEPARARTOR).Where(s => !string.IsNullOrEmpty(s)))
{
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
}
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
TriggerPropertyChanged(nameof(BlockedUris));
TriggerPropertyChanged(nameof(ShowList));
});
await UpdateAutofillBlacklistedUrisAsync();
_deviceActionService.Toast(AppResources.URISaved);
}
private async Task EditUriAsync(BlockAutofillUriItemViewModel uriItemViewModel)
{
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
{
Title = AppResources.EditURI,
Subtitle = AppResources.EnterURI,
Text = uriItemViewModel.Uri,
ValueSubInfo = string.Format(AppResources.FormatX, URI_FORMAT),
OkButtonText = AppResources.Save,
ThirdButtonText = AppResources.Remove,
ValidateText = text => ValidateUris(text, false)
});
if (response is null)
{
return;
}
if (response.Value.ExecuteThirdAction)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
BlockedUris.Remove(uriItemViewModel);
TriggerPropertyChanged(nameof(ShowList));
});
await UpdateAutofillBlacklistedUrisAsync();
_deviceActionService.Toast(AppResources.URIRemoved);
return;
}
var cleanedUri = response.Value.Text.Replace(Environment.NewLine, string.Empty).Trim();
await MainThread.InvokeOnMainThreadAsync(() =>
{
BlockedUris.Remove(uriItemViewModel);
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
TriggerPropertyChanged(nameof(BlockedUris));
TriggerPropertyChanged(nameof(ShowList));
});
await UpdateAutofillBlacklistedUrisAsync();
_deviceActionService.Toast(AppResources.URISaved);
}
private string ValidateUris(string uris, bool allowMultipleUris)
{
if (string.IsNullOrWhiteSpace(uris))
{
return string.Format(AppResources.FormatX, URI_FORMAT);
}
if (!allowMultipleUris && uris.Contains(URI_SEPARARTOR))
{
return AppResources.CannotEditMultipleURIsAtOnce;
}
foreach (var uri in uris.Split(URI_SEPARARTOR).Where(u => !string.IsNullOrWhiteSpace(u)))
{
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
{
return AppResources.InvalidFormatUseHttpsHttpOrAndroidApp;
}
if (!Uri.TryCreate(cleanedUri, UriKind.Absolute, out var _))
{
return AppResources.InvalidURI;
}
if (BlockedUris.Any(uriItem => uriItem.Uri == cleanedUri))
{
return string.Format(AppResources.TheURIXIsAlreadyBlocked, cleanedUri);
}
}
return null;
}
private async Task UpdateAutofillBlacklistedUrisAsync()
{
await _stateService.SetAutofillBlacklistedUrisAsync(BlockedUris.Any() ? BlockedUris.Select(bu => bu.Uri).ToList() : null);
}
}
public class BlockAutofillUriItemViewModel : ExtendedViewModel
{
public BlockAutofillUriItemViewModel(string uri, ICommand editUriCommand)
{
Uri = uri;
EditUriCommand = new Command(() => editUriCommand.Execute(this));
}
public string Uri { get; }
public ICommand EditUriCommand { get; }
}
}

View File

@@ -67,7 +67,7 @@ namespace Bit.App.Pages
_initialized = true; _initialized = true;
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json"); FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport); DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector(); UseOTPVerification = await _keyConnectorService.GetUsesKeyConnectorAsync();
if (UseOTPVerification) if (UseOTPVerification)
{ {
@@ -165,7 +165,7 @@ namespace Bit.App.Pages
return; return;
} }
var verificationType = await _keyConnectorService.GetUsesKeyConnector() var verificationType = await _keyConnectorService.GetUsesKeyConnectorAsync()
? VerificationType.OTP ? VerificationType.OTP
: VerificationType.MasterPassword; : VerificationType.MasterPassword;
if (!await _userVerificationService.VerifyUser(Secret, verificationType)) if (!await _userVerificationService.VerifyUser(Secret, verificationType))

View File

@@ -153,22 +153,14 @@
StyleClass="box-footer-label, box-footer-label-switch" /> StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout> </StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}"> <StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
<StackLayout StyleClass="box-row, box-row-input"> <StackLayout.GestureRecognizers>
<Label <TapGestureRecognizer Command="{Binding GoToBlockAutofillUrisCommand}" />
Text="{u:I18n AutofillBlockedUris}" </StackLayout.GestureRecognizers>
StyleClass="box-label" />
<Editor
x:Name="_autofillBlockedUrisEditor"
Text="{Binding AutofillBlockedUris}"
StyleClass="box-value"
AutoSize="TextChanges"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
Keyboard="Url"
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
</StackLayout>
<Label <Label
Text="{u:I18n AutofillBlockedUrisDescription}" Text="{u:I18n BlockAutoFill}"
StyleClass="box-label-regular" />
<Label
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
StyleClass="box-footer-label" /> StyleClass="box-footer-label" />
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>

View File

@@ -1,6 +1,4 @@
using Bit.App.Abstractions; using Bit.Core.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration; using Xamarin.Forms.PlatformConfiguration;
@@ -44,17 +42,6 @@ namespace Bit.App.Pages
await _vm.InitAsync(); await _vm.InitAsync();
} }
protected async override void OnDisappearing()
{
base.OnDisappearing();
await _vm.UpdateAutofillBlockedUris();
}
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
{
await _vm.UpdateAutofillBlockedUris();
}
private async void Close_Clicked(object sender, System.EventArgs e) private async void Close_Clicked(object sender, System.EventArgs e)
{ {
if (DoOnce()) if (DoOnce())

View File

@@ -1,12 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.App.Pages namespace Bit.App.Pages
@@ -19,7 +20,6 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService; private readonly IPlatformUtilsService _platformUtilsService;
private bool _autofillSavePrompt; private bool _autofillSavePrompt;
private string _autofillBlockedUris;
private bool _favicon; private bool _favicon;
private bool _autoTotpCopy; private bool _autoTotpCopy;
private int _clearClipboardSelectedIndex; private int _clearClipboardSelectedIndex;
@@ -84,6 +84,10 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>(null, AppResources.DefaultSystem) new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
}; };
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList()); LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
GoToBlockAutofillUrisCommand = new AsyncCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
} }
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; } public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
@@ -192,25 +196,18 @@ namespace Bit.App.Pages
} }
} }
public string AutofillBlockedUris
{
get => _autofillBlockedUris;
set => SetProperty(ref _autofillBlockedUris, value);
}
public bool ShowAndroidAutofillSettings public bool ShowAndroidAutofillSettings
{ {
get => _showAndroidAutofillSettings; get => _showAndroidAutofillSettings;
set => SetProperty(ref _showAndroidAutofillSettings, value); set => SetProperty(ref _showAndroidAutofillSettings, value);
} }
public ICommand GoToBlockAutofillUrisCommand { get; }
public async Task InitAsync() public async Task InitAsync()
{ {
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault(); AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false); AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault(); Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
@@ -288,41 +285,6 @@ namespace Bit.App.Pages
} }
} }
public async Task UpdateAutofillBlockedUris()
{
if (_inited)
{
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
{
await _stateService.SetAutofillBlacklistedUrisAsync(null);
AutofillBlockedUris = null;
return;
}
try
{
var csv = AutofillBlockedUris;
var urisList = new List<string>();
foreach (var uri in csv.Split(','))
{
if (string.IsNullOrWhiteSpace(uri))
{
continue;
}
var cleanedUri = uri.Replace(System.Environment.NewLine, string.Empty).Trim();
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
{
continue;
}
urisList.Add(cleanedUri);
}
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
AutofillBlockedUris = string.Join(", ", urisList);
}
catch { }
}
}
private async Task UpdateCurrentLocaleAsync() private async Task UpdateCurrentLocaleAsync()
{ {
if (!_inited) if (!_inited)

View File

@@ -138,8 +138,8 @@ namespace Bit.App.Pages
t.Value != null).ToList(); t.Value != null).ToList();
} }
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); var pinSet = await _vaultTimeoutService.GetPinLockTypeAsync();
_pin = pinSet.Item1 || pinSet.Item2; _pin = pinSet != PinLockType.Disabled;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync(); _biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync(); _screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
@@ -149,7 +149,7 @@ namespace Bit.App.Pages
} }
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() && _showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
!await _keyConnectorService.GetUsesKeyConnector(); !await _keyConnectorService.GetUsesKeyConnectorAsync();
_reportLoggingEnabled = await _loggerService.IsEnabled(); _reportLoggingEnabled = await _loggerService.IsEnabled();
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync(); _approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync(); _shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
@@ -323,6 +323,7 @@ namespace Bit.App.Pages
} }
if (oldTimeout != newTimeout) if (oldTimeout != newTimeout)
{ {
await _cryptoService.RefreshKeysAsync();
await Device.InvokeOnMainThreadAsync(BuildList); await Device.InvokeOnMainThreadAsync(BuildList);
} }
} }
@@ -428,7 +429,7 @@ namespace Bit.App.Pages
if (!string.IsNullOrWhiteSpace(pin)) if (!string.IsNullOrWhiteSpace(pin))
{ {
var masterPassOnRestart = false; var masterPassOnRestart = false;
if (!await _keyConnectorService.GetUsesKeyConnector()) if (!await _keyConnectorService.GetUsesKeyConnectorAsync())
{ {
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync( masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN, AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
@@ -437,19 +438,20 @@ namespace Bit.App.Pages
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync(); var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig); var pinKey = await _cryptoService.MakePinKeyAsync(pin, email, kdfConfig);
var key = await _cryptoService.GetKeyAsync(); var userKey = await _cryptoService.GetUserKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey); var protectedPinKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
var encPin = await _cryptoService.EncryptAsync(pin);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
if (masterPassOnRestart) if (masterPassOnRestart)
{ {
var encPin = await _cryptoService.EncryptAsync(pin); await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(protectedPinKey);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
} }
else else
{ {
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString); await _stateService.SetPinKeyEncryptedUserKeyAsync(protectedPinKey);
} }
} }
else else
@@ -459,7 +461,6 @@ namespace Bit.App.Pages
} }
if (!_pin) if (!_pin)
{ {
await _cryptoService.ClearPinProtectedKeyAsync();
await _vaultTimeoutService.ClearAsync(); await _vaultTimeoutService.ClearAsync();
} }
BuildList(); BuildList();
@@ -491,7 +492,7 @@ namespace Bit.App.Pages
await _stateService.SetBiometricUnlockAsync(null); await _stateService.SetBiometricUnlockAsync(null);
} }
await _stateService.SetBiometricLockedAsync(false); await _stateService.SetBiometricLockedAsync(false);
await _cryptoService.ToggleKeyAsync(); await _cryptoService.RefreshKeysAsync();
BuildList(); BuildList();
} }

View File

@@ -94,7 +94,7 @@ namespace Bit.App.Pages
} }
}); });
await UpdateVaultButtonTitleAsync(); await UpdateVaultButtonTitleAsync();
if (await _keyConnectorService.UserNeedsMigration()) if (await _keyConnectorService.UserNeedsMigrationAsync())
{ {
_messagingService.Send("convertAccountToKeyConnector"); _messagingService.Send("convertAccountToKeyConnector");
} }

View File

@@ -74,7 +74,7 @@ namespace Bit.App.Pages
_cipherDomain = await _cipherService.GetAsync(CipherId); _cipherDomain = await _cipherService.GetAsync(CipherId);
Cipher = await _cipherDomain.DecryptAsync(); Cipher = await _cipherDomain.DecryptAsync();
LoadAttachments(); LoadAttachments();
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync(); _hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync(); var canAccessPremium = await _stateService.CanAccessPremiumAsync();
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null; _canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
if (!_canAccessAttachments) if (!_canAccessAttachments)

View File

@@ -176,7 +176,7 @@ namespace Bit.App.Pages
} }
}); });
// Hide password reprompt option if using key connector // Hide password reprompt option if using key connector
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector(); _passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnectorAsync();
} }
protected override void OnDisappearing() protected override void OnDisappearing()

View File

@@ -179,7 +179,7 @@
AutomationProperties.Name="{u:I18n ToggleVisibility}" AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}" IsVisible="{Binding Cipher.ViewPassword}"
AutomationId="ViewValueButton" /> AutomationId="ShowValueButton" />
<controls:IconButton <controls:IconButton
StyleClass="box-row-button, box-row-button-platform" StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}" Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"

View File

@@ -208,6 +208,11 @@ namespace Bit.App.Pages
} }
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave) else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
{ {
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}
if (selection == AppResources.AutofillAndSave) if (selection == AppResources.AutofillAndSave)
{ {
var uris = cipher.Login?.Uris?.ToList(); var uris = cipher.Login?.Uris?.ToList();

View File

@@ -330,13 +330,16 @@ namespace Bit.App.Pages
items.AddRange(itemGroup); items.AddRange(itemGroup);
} }
if (Device.RuntimePlatform == Device.iOS) Device.BeginInvokeOnMainThread(() =>
{ {
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info if (Device.RuntimePlatform == Device.iOS)
// because of update to XF v5.0.0.2401 {
GroupedItems.Clear(); // HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
} // because of update to XF v5.0.0.2401
GroupedItems.ReplaceRange(items); GroupedItems.Clear();
}
GroupedItems.ReplaceRange(items);
});
} }
else else
{ {
@@ -356,21 +359,24 @@ namespace Bit.App.Pages
items.AddRange(itemGroup); items.AddRange(itemGroup);
} }
if (groupedItems.Any()) Device.BeginInvokeOnMainThread(() =>
{ {
if (Device.RuntimePlatform == Device.iOS) if (groupedItems.Any())
{
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
else
{ {
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear(); GroupedItems.Clear();
} }
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) }); });
GroupedItems.AddRange(items);
}
else
{
GroupedItems.Clear();
}
} }
} }
finally finally
@@ -378,9 +384,12 @@ namespace Bit.App.Pages
_doingLoad = false; _doingLoad = false;
Loaded = true; Loaded = true;
Loading = false; Loading = false;
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any(); Device.BeginInvokeOnMainThread(() =>
ShowList = !ShowNoData; {
DisableRefreshing(); ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
ShowList = !ShowNoData;
DisableRefreshing();
});
} }
} }

View File

@@ -796,15 +796,6 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: &quot;https://twitter.com, androidapp://com.twitter.android&quot;..
/// </summary>
public static string AutofillBlockedUrisDescription {
get {
return ResourceManager.GetString("AutofillBlockedUrisDescription", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Do you want to auto-fill or view this item?. /// Looks up a localized string similar to Do you want to auto-fill or view this item?.
/// </summary> /// </summary>
@@ -940,6 +931,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Auto-fill will not be offered for these URIs..
/// </summary>
public static string AutoFillWillNotBeOfferedForTheseURIs {
get {
return ResourceManager.GetString("AutoFillWillNotBeOfferedForTheseURIs", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Auto-fill with Bitwarden. /// Looks up a localized string similar to Auto-fill with Bitwarden.
/// </summary> /// </summary>
@@ -1228,6 +1228,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Block auto-fill.
/// </summary>
public static string BlockAutoFill {
get {
return ResourceManager.GetString("BlockAutoFill", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Brand. /// Looks up a localized string similar to Brand.
/// </summary> /// </summary>
@@ -1264,6 +1273,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Cannot edit multiple URIs at once.
/// </summary>
public static string CannotEditMultipleURIsAtOnce {
get {
return ResourceManager.GetString("CannotEditMultipleURIsAtOnce", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Cannot open the app &quot;{0}&quot;.. /// Looks up a localized string similar to Cannot open the app &quot;{0}&quot;..
/// </summary> /// </summary>
@@ -2146,6 +2164,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Edit URI.
/// </summary>
public static string EditURI {
get {
return ResourceManager.GetString("EditURI", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Email. /// Looks up a localized string similar to Email.
/// </summary> /// </summary>
@@ -2299,6 +2326,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Enter URI.
/// </summary>
public static string EnterURI {
get {
return ResourceManager.GetString("EnterURI", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app.. /// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app..
/// </summary> /// </summary>
@@ -2947,6 +2983,24 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Format: {0}.
/// </summary>
public static string FormatX {
get {
return ResourceManager.GetString("FormatX", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Format: {0}. Separate multiple URIs with a comma..
/// </summary>
public static string FormatXSeparateMultipleURIsWithAComma {
get {
return ResourceManager.GetString("FormatXSeparateMultipleURIsWithAComma", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Forwarded email alias. /// Looks up a localized string similar to Forwarded email alias.
/// </summary> /// </summary>
@@ -3289,6 +3343,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Invalid format. Use https://, http://, or androidapp://.
/// </summary>
public static string InvalidFormatUseHttpsHttpOrAndroidApp {
get {
return ResourceManager.GetString("InvalidFormatUseHttpsHttpOrAndroidApp", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Invalid master password. Try again.. /// Looks up a localized string similar to Invalid master password. Try again..
/// </summary> /// </summary>
@@ -3307,6 +3370,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Invalid URI.
/// </summary>
public static string InvalidURI {
get {
return ResourceManager.GetString("InvalidURI", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Invalid verification code. /// Looks up a localized string similar to Invalid verification code.
/// </summary> /// </summary>
@@ -4218,6 +4290,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to New blocked URI.
/// </summary>
public static string NewBlockedURI {
get {
return ResourceManager.GetString("NewBlockedURI", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to New custom field. /// Looks up a localized string similar to New custom field.
/// </summary> /// </summary>
@@ -6119,6 +6200,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to There are no blocked URIs.
/// </summary>
public static string ThereAreNoBlockedURIs {
get {
return ResourceManager.GetString("ThereAreNoBlockedURIs", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to There are no items in your vault that match &quot;{0}&quot;. /// Looks up a localized string similar to There are no items in your vault that match &quot;{0}&quot;.
/// </summary> /// </summary>
@@ -6137,6 +6227,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to The URI {0} is already blocked.
/// </summary>
public static string TheURIXIsAlreadyBlocked {
get {
return ResourceManager.GetString("TheURIXIsAlreadyBlocked", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to 30 days. /// Looks up a localized string similar to 30 days.
/// </summary> /// </summary>
@@ -6587,6 +6686,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to URI removed.
/// </summary>
public static string URIRemoved {
get {
return ResourceManager.GetString("URIRemoved", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to URIs. /// Looks up a localized string similar to URIs.
/// </summary> /// </summary>
@@ -6596,6 +6704,15 @@ namespace Bit.App.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to URI saved.
/// </summary>
public static string URISaved {
get {
return ResourceManager.GetString("URISaved", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to US. /// Looks up a localized string similar to US.
/// </summary> /// </summary>

View File

@@ -2638,9 +2638,9 @@
<value>باز کردن قفل ممکن است به دلیل حافظه ناکافی انجام شود. تنظیمات حافظه KDF خود را کاهش دهید تا حل شود.</value> <value>باز کردن قفل ممکن است به دلیل حافظه ناکافی انجام شود. تنظیمات حافظه KDF خود را کاهش دهید تا حل شود.</value>
</data> </data>
<data name="InvalidAPIKey" xml:space="preserve"> <data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value> <value>کلید API نامعتبر</value>
</data> </data>
<data name="InvalidAPIToken" xml:space="preserve"> <data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value> <value>کلید توکن API نامعتبر</value>
</data> </data>
</root> </root>

View File

@@ -1098,7 +1098,7 @@ A leitura será efetuada automaticamente.</value>
<value>Sr.</value> <value>Sr.</value>
</data> </data>
<data name="Mrs" xml:space="preserve"> <data name="Mrs" xml:space="preserve">
<value>Sra.</value> <value>Sr.ª</value>
</data> </data>
<data name="Ms" xml:space="preserve"> <data name="Ms" xml:space="preserve">
<value>Menina</value> <value>Menina</value>
@@ -1579,7 +1579,7 @@ A leitura será efetuada automaticamente.</value>
<comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment> <comment>'Nord' is the name of a specific color scheme. It should not be translated.</comment>
</data> </data>
<data name="SolarizedDark" xml:space="preserve"> <data name="SolarizedDark" xml:space="preserve">
<value>Solarized Dark</value> <value>Solarized (escuro)</value>
<comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment> <comment>'Solarized Dark' is the name of a specific color scheme. It should not be translated.</comment>
</data> </data>
<data name="AutofillBlockedUris" xml:space="preserve"> <data name="AutofillBlockedUris" xml:space="preserve">
@@ -2233,10 +2233,10 @@ A leitura será efetuada automaticamente.</value>
<value>Ajude o Bitwarden a melhorar a estabilidade da aplicação ao enviar relatórios de falhas.</value> <value>Ajude o Bitwarden a melhorar a estabilidade da aplicação ao enviar relatórios de falhas.</value>
</data> </data>
<data name="OptionsExpanded" xml:space="preserve"> <data name="OptionsExpanded" xml:space="preserve">
<value>Options are expanded, tap to collapse.</value> <value>As opções foram expandidas, clique para fechar.</value>
</data> </data>
<data name="OptionsCollapsed" xml:space="preserve"> <data name="OptionsCollapsed" xml:space="preserve">
<value>Options are collapsed, tap to expand.</value> <value>As opções foram fechadas, clique para expandir.</value>
</data> </data>
<data name="UppercaseAtoZ" xml:space="preserve"> <data name="UppercaseAtoZ" xml:space="preserve">
<value>Maiúsculas (A-Z)</value> <value>Maiúsculas (A-Z)</value>
@@ -2392,10 +2392,10 @@ A leitura será efetuada automaticamente.</value>
<value>Tipo de nome de utilizador</value> <value>Tipo de nome de utilizador</value>
</data> </data>
<data name="PlusAddressedEmail" xml:space="preserve"> <data name="PlusAddressedEmail" xml:space="preserve">
<value>Plus addressed email</value> <value>E-mail com subendereço</value>
</data> </data>
<data name="CatchAllEmail" xml:space="preserve"> <data name="CatchAllEmail" xml:space="preserve">
<value>Catch-all email</value> <value>E-mail de captura geral</value>
</data> </data>
<data name="ForwardedEmailAlias" xml:space="preserve"> <data name="ForwardedEmailAlias" xml:space="preserve">
<value>Alias de e-mail reencaminhado</value> <value>Alias de e-mail reencaminhado</value>
@@ -2454,10 +2454,10 @@ A leitura será efetuada automaticamente.</value>
<value>Ocorreu um erro desconhecido ({0}).</value> <value>Ocorreu um erro desconhecido ({0}).</value>
</data> </data>
<data name="PlusAddressedEmailDescription" xml:space="preserve"> <data name="PlusAddressedEmailDescription" xml:space="preserve">
<value>Use your email provider's subaddress capabilities</value> <value>Utilize as capacidades de subendereçamento do seu fornecedor de e-mail.</value>
</data> </data>
<data name="CatchAllEmailDescription" xml:space="preserve"> <data name="CatchAllEmailDescription" xml:space="preserve">
<value>Use your domain's configured catch-all inbox.</value> <value>Utilize a caixa de entrada de captura geral configurada para o seu domínio.</value>
</data> </data>
<data name="ForwardedEmailDescription" xml:space="preserve"> <data name="ForwardedEmailDescription" xml:space="preserve">
<value>Gerar um alias de e-mail com um serviço de reencaminhamento externo.</value> <value>Gerar um alias de e-mail com um serviço de reencaminhamento externo.</value>

View File

@@ -1585,9 +1585,6 @@ Scanning will happen automatically.</value>
<data name="AutofillBlockedUris" xml:space="preserve"> <data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-fill blocked URIs</value> <value>Auto-fill blocked URIs</value>
</data> </data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve"> <data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value> <value>Ask to add login</value>
</data> </data>
@@ -2643,4 +2640,50 @@ Do you want to switch to this account?</value>
<data name="InvalidAPIToken" xml:space="preserve"> <data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value> <value>Invalid API token</value>
</data> </data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-fill will not be offered for these URIs.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>New blocked URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Invalid format. Use https://, http://, or androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Enter URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separate multiple URIs with a comma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Invalid URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI removed</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>There are no blocked URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>The URI {0} is already blocked</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
</root> </root>

View File

@@ -375,7 +375,7 @@
<comment>Validation message for when a form field is left blank and is required to be entered.</comment> <comment>Validation message for when a form field is left blank and is required to be entered.</comment>
</data> </data>
<data name="ValueHasBeenCopied" xml:space="preserve"> <data name="ValueHasBeenCopied" xml:space="preserve">
<value>{0} je bilo kopirano.</value> <value>{0} kopirana</value>
<comment>Confirmation message after successfully copying a value to the clipboard.</comment> <comment>Confirmation message after successfully copying a value to the clipboard.</comment>
</data> </data>
<data name="VerifyFingerprint" xml:space="preserve"> <data name="VerifyFingerprint" xml:space="preserve">

View File

@@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Bit.Core.Abstractions;
namespace Bit.App.Services
{
public abstract class BaseBiometricService : IBiometricService
{
protected readonly IStateService _stateService;
protected readonly ICryptoService _cryptoService;
protected BaseBiometricService(IStateService stateService, ICryptoService cryptoService)
{
_stateService = stateService;
_cryptoService = cryptoService;
}
public async Task<bool> CanUseBiometricsUnlockAsync()
{
return await _cryptoService.HasEncryptedUserKeyAsync() || await _stateService.GetKeyEncryptedAsync() != null;
}
public abstract Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
public abstract Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
}
}

View File

@@ -44,7 +44,7 @@ namespace Bit.App.Services
public async Task<bool> Enabled() public async Task<bool> Enabled()
{ {
var keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"); var keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
return !await keyConnectorService.GetUsesKeyConnector(); return !await keyConnectorService.GetUsesKeyConnectorAsync();
} }
} }
} }

View File

@@ -428,6 +428,22 @@
<Setter Property="VerticalOptions" <Setter Property="VerticalOptions"
Value="CenterAndExpand" /> Value="CenterAndExpand" />
</Style> </Style>
<Style TargetType="Button"
ApplyToDerivedTypes="True"
Class="box-row-button-muted">
<Setter Property="BackgroundColor"
Value="Transparent" />
<Setter Property="BorderWidth"
Value="0" />
<Setter Property="Padding"
Value="0" />
<Setter Property="TextColor"
Value="{DynamicResource MutedColor}" />
<Setter Property="HorizontalOptions"
Value="End" />
<Setter Property="VerticalOptions"
Value="CenterAndExpand" />
</Style>
<Style TargetType="Button" <Style TargetType="Button"
ApplyToDerivedTypes="True" ApplyToDerivedTypes="True"
Class="box-overlay"> Class="box-overlay">

View File

@@ -206,6 +206,7 @@ namespace Bit.App.Utilities.AccountManagement
private async Task AddAccountAsync() private async Task AddAccountAsync()
{ {
await AppHelpers.ClearServiceCacheAsync();
await Device.InvokeOnMainThreadAsync(() => await Device.InvokeOnMainThreadAsync(() =>
{ {
Options.HideAccountSwitcher = false; Options.HideAccountSwitcher = false;

View File

@@ -0,0 +1,29 @@
using System;
namespace Bit.App.Utilities.Prompts
{
public class ValidatablePromptConfig
{
public string Title { get; set; }
public string Subtitle { get; set; }
public string Text { get; set; }
public Func<string, string> ValidateText { get; set; }
public string ValueSubInfo { get; set; }
public string OkButtonText { get; set; }
public string CancelButtonText { get; set; }
public string ThirdButtonText { get; set; }
public bool NumericKeyboard { get; set; }
}
public struct ValidatablePromptResponse
{
public ValidatablePromptResponse(string text, bool executeThirdAction)
{
Text = text;
ExecuteThirdAction = executeThirdAction;
}
public string Text { get; set; }
public bool ExecuteThirdAction { get; set; }
}
}

View File

@@ -107,7 +107,7 @@ namespace Bit.App.Utilities
public async Task ValidateAndExecuteAsync() public async Task ValidateAndExecuteAsync()
{ {
var verificationType = await _keyConnectorService.GetUsesKeyConnector() var verificationType = await _keyConnectorService.GetUsesKeyConnectorAsync()
? VerificationType.OTP ? VerificationType.OTP
: VerificationType.MasterPassword; : VerificationType.MasterPassword;
@@ -121,7 +121,7 @@ namespace Bit.App.Utilities
} }
var parameters = GetParameters(); var parameters = GetParameters();
parameters.Secret = await _cryptoService.HashPasswordAsync(password, null); parameters.Secret = await _cryptoService.HashMasterKeyAsync(password, null);
parameters.VerificationType = VerificationType.MasterPassword; parameters.VerificationType = VerificationType.MasterPassword;
await ExecuteAsync(parameters); await ExecuteAsync(parameters);
break; break;

View File

@@ -70,7 +70,7 @@ namespace Bit.Core.Abstractions
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier); Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId, Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
OrganizationUserResetPasswordEnrollmentRequest request); OrganizationUserResetPasswordEnrollmentRequest request);
Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl); Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request); Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request); Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnector(); Task PostConvertToKeyConnector();

View File

@@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
{ {
public interface IBiometricService public interface IBiometricService
{ {
Task<bool> CanUseBiometricsUnlockAsync();
Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null); Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null);
Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null); Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null);
} }

View File

@@ -9,49 +9,56 @@ namespace Bit.Core.Abstractions
{ {
public interface ICryptoService public interface ICryptoService
{ {
Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null);
Task ClearKeyAsync(string userId = null);
Task ClearKeyHashAsync(string userId = null);
Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null);
Task ClearKeysAsync(string userId = null);
Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null);
Task ClearPinProtectedKeyAsync(string userId = null);
void ClearCache(); void ClearCache();
Task RefreshKeysAsync();
Task SetUserKeyAsync(UserKey userKey, string userId = null);
Task<UserKey> GetUserKeyAsync(string userId = null);
Task<UserKey> GetUserKeyWithLegacySupportAsync(string userId = null);
Task<bool> HasUserKeyAsync(string userId = null);
Task<bool> HasEncryptedUserKeyAsync(string userId = null);
Task<UserKey> MakeUserKeyAsync();
Task ClearUserKeyAsync(string userId = null);
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
Task<UserKey> GetAutoUnlockKeyAsync(string userId = null);
Task<bool> HasAutoUnlockKeyAsync(string userId = null);
Task<UserKey> GetBiometricUnlockKeyAsync(string userId = null);
Task SetMasterKeyAsync(MasterKey masterKey, string userId = null);
Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task<MasterKey> MakeMasterKeyAsync(string password, string email, KdfConfig kdfConfig);
Task ClearMasterKeyAsync(string userId = null);
Task<Tuple<UserKey, EncString>> EncryptUserKeyWithMasterKeyAsync(MasterKey masterKey);
Task<UserKey> DecryptUserKeyWithMasterKeyAsync(MasterKey masterKey, EncString encUserKey = null, string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeDataEncKeyAsync(SymmetricCryptoKey key);
Task<string> HashMasterKeyAsync(string password, MasterKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task SetMasterKeyHashAsync(string keyHash);
Task<string> GetMasterKeyHashAsync();
Task ClearMasterKeyHashAsync(string userId = null);
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, MasterKey key);
Task SetOrgKeysAsync(IEnumerable<ProfileOrganizationResponse> orgs);
Task<OrgKey> GetOrgKeyAsync(string orgId);
Task<Dictionary<string, OrgKey>> GetOrgKeysAsync();
Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null);
Task<byte[]> GetUserPublicKeyAsync();
Task SetUserPrivateKeyAsync(string encPrivateKey);
Task<byte[]> GetUserPrivateKeyAsync();
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task ClearKeyPairAsync(bool memoryOnly = false, string userId = null);
Task<PinKey> MakePinKeyAsync(string pin, string salt, KdfConfig config);
Task ClearPinKeysAsync(string userId = null);
Task<UserKey> DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null);
Task<MasterKey> DecryptMasterKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedMasterKey = null);
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
Task<int> RandomNumberAsync(int min, int max);
Task<string> RandomStringAsync(int length);
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key); Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null); Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null); Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null); Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null); Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null); Task<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
Task<SymmetricCryptoKey> GetKeyAsync(string userId = null);
Task<string> GetKeyHashAsync();
Task<SymmetricCryptoKey> GetOrgKeyAsync(string orgId);
Task<Dictionary<string, SymmetricCryptoKey>> GetOrgKeysAsync();
Task<byte[]> GetPrivateKeyAsync();
Task<byte[]> GetPublicKeyAsync();
Task<bool> CompareAndUpdateKeyHashAsync(string masterPassword, SymmetricCryptoKey key);
Task<bool> HasEncKeyAsync();
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key, HashPurpose hashPurpose = HashPurpose.ServerAuthorization);
Task<bool> HasKeyAsync(string userId = null);
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfConfig config);
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfConfig config, EncString protectedKeyEs = null);
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfConfig config);
Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
Task<int> RandomNumberAsync(int min, int max);
Task<string> RandomStringAsync(int length);
Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
Task<byte[]> RsaDecryptAsync(string encValue, byte[] privateKey = null);
Task SetEncKeyAsync(string encKey);
Task SetEncPrivateKeyAsync(string encPrivateKey);
Task SetKeyAsync(SymmetricCryptoKey key);
Task SetKeyHashAsync(string keyHash);
Task SetOrgKeysAsync(IEnumerable<ProfileOrganizationResponse> orgs);
Task ToggleKeyAsync();
} }
} }

View File

@@ -6,11 +6,11 @@ namespace Bit.Core.Abstractions
{ {
public interface IKeyConnectorService public interface IKeyConnectorService
{ {
Task SetUsesKeyConnector(bool usesKeyConnector); Task SetUsesKeyConnectorAsync(bool usesKeyConnector);
Task<bool> GetUsesKeyConnector(); Task<bool> GetUsesKeyConnectorAsync();
Task<bool> UserNeedsMigration(); Task<bool> UserNeedsMigrationAsync();
Task MigrateUser(); Task MigrateUserAsync();
Task GetAndSetKey(string url); Task GetAndSetMasterKeyAsync(string url);
Task<Organization> GetManagingOrganization(); Task<Organization> GetManagingOrganizationAsync();
} }
} }

View File

@@ -13,6 +13,14 @@ namespace Bit.Core.Abstractions
public interface IStateService public interface IStateService
{ {
List<AccountView> AccountViews { get; } List<AccountView> AccountViews { get; }
Task<UserKey> GetUserKeyAsync(string userId = null);
Task SetUserKeyAsync(UserKey value, string userId = null);
Task<MasterKey> GetMasterKeyAsync(string userId = null);
Task SetMasterKeyAsync(MasterKey value, string userId = null);
Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null);
Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null);
Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null);
Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null);
Task<string> GetActiveUserIdAsync(); Task<string> GetActiveUserIdAsync();
Task<string> GetActiveUserEmailAsync(); Task<string> GetActiveUserEmailAsync();
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper); Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
@@ -27,6 +35,8 @@ namespace Bit.Core.Abstractions
Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync(); Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync();
Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value); Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value);
Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null); Task<EnvironmentUrlData> GetEnvironmentUrlsAsync(string userId = null);
Task<UserKey> GetUserKeyBiometricUnlockAsync(string userId = null);
Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null);
Task<bool?> GetBiometricUnlockAsync(string userId = null); Task<bool?> GetBiometricUnlockAsync(string userId = null);
Task SetBiometricUnlockAsync(bool? value, string userId = null); Task SetBiometricUnlockAsync(bool? value, string userId = null);
Task<bool> GetBiometricLockedAsync(string userId = null); Task<bool> GetBiometricLockedAsync(string userId = null);
@@ -36,22 +46,16 @@ namespace Bit.Core.Abstractions
Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null); Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null); Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
Task<bool> CanAccessPremiumAsync(string userId = null); Task<bool> CanAccessPremiumAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<string> GetProtectedPinAsync(string userId = null); Task<string> GetProtectedPinAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null);
Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null);
Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null);
Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null);
Task SetProtectedPinAsync(string value, string userId = null); Task SetProtectedPinAsync(string value, string userId = null);
Task<string> GetPinProtectedAsync(string userId = null);
Task SetPinProtectedAsync(string value, string userId = null);
Task<EncString> GetPinProtectedKeyAsync(string userId = null);
Task SetPinProtectedKeyAsync(EncString value, string userId = null);
Task SetKdfConfigurationAsync(KdfConfig config, string userId = null); Task SetKdfConfigurationAsync(KdfConfig config, string userId = null);
Task<string> GetKeyEncryptedAsync(string userId = null);
Task SetKeyEncryptedAsync(string value, string userId = null);
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null);
Task<string> GetKeyHashAsync(string userId = null); Task<string> GetKeyHashAsync(string userId = null);
Task SetKeyHashAsync(string value, string userId = null); Task SetKeyHashAsync(string value, string userId = null);
Task<string> GetEncKeyEncryptedAsync(string userId = null);
Task SetEncKeyEncryptedAsync(string value, string userId = null);
Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null); Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null);
Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null); Task SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
Task<string> GetPrivateKeyEncryptedAsync(string userId = null); Task<string> GetPrivateKeyEncryptedAsync(string userId = null);
@@ -176,5 +180,24 @@ namespace Bit.Core.Abstractions
void SetLocale(string locale); void SetLocale(string locale);
ConfigResponse GetConfigs(); ConfigResponse GetConfigs();
void SetConfigs(ConfigResponse value); void SetConfigs(ConfigResponse value);
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetPinProtectedAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task SetPinProtectedAsync(string value, string userId = null);
[Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
Task<EncString> GetPinProtectedKeyAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
Task SetPinProtectedKeyAsync(EncString value, string userId = null);
[Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetEncKeyEncryptedAsync(string userId = null);
[Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task SetEncKeyEncryptedAsync(string value, string userId = null);
[Obsolete("Left for migration purposes")]
Task SetKeyEncryptedAsync(string value, string userId = null);
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
Task<string> GetKeyEncryptedAsync(string userId = null);
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Services;
namespace Bit.Core.Abstractions namespace Bit.Core.Abstractions
{ {
@@ -16,7 +17,7 @@ namespace Bit.Core.Abstractions
Task<bool> ShouldLockAsync(string userId = null); Task<bool> ShouldLockAsync(string userId = null);
Task<bool> IsLoggedOutByTimeoutAsync(string userId = null); Task<bool> IsLoggedOutByTimeoutAsync(string userId = null);
Task<bool> ShouldLogOutByTimeoutAsync(string userId = null); Task<bool> ShouldLogOutByTimeoutAsync(string userId = null);
Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null); Task<PinLockType> GetPinLockTypeAsync(string userId = null);
Task<bool> IsBiometricLockSetAsync(string userId = null); Task<bool> IsBiometricLockSetAsync(string userId = null);
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false, string userId = null);
Task LogOutAsync(bool userInitiated = true, string userId = null); Task LogOutAsync(bool userInitiated = true, string userId = null);

View File

@@ -1,4 +1,6 @@
namespace Bit.Core using System;
namespace Bit.Core
{ {
public static class Constants public static class Constants
{ {
@@ -79,6 +81,9 @@
public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}"; public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{userId}";
public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}"; public static string VaultTimeoutActionKey(string userId) => $"vaultTimeoutAction_{userId}";
public static string MasterKeyEncryptedUserKeyKey(string userId) => $"masterKeyEncryptedUserKey_{userId}";
public static string UserKeyAutoUnlockKey(string userId) => $"autoUnlock_{userId}";
public static string UserKeyBiometricUnlockKey(string userId) => $"biometricUnlock_{userId}";
public static string CiphersKey(string userId) => $"ciphers_{userId}"; public static string CiphersKey(string userId) => $"ciphers_{userId}";
public static string FoldersKey(string userId) => $"folders_{userId}"; public static string FoldersKey(string userId) => $"folders_{userId}";
public static string CollectionsKey(string userId) => $"collections_{userId}"; public static string CollectionsKey(string userId) => $"collections_{userId}";
@@ -87,12 +92,10 @@
public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}"; public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}";
public static string SendsKey(string userId) => $"sends_{userId}"; public static string SendsKey(string userId) => $"sends_{userId}";
public static string PoliciesKey(string userId) => $"policies_{userId}"; public static string PoliciesKey(string userId) => $"policies_{userId}";
public static string KeyKey(string userId) => $"key_{userId}";
public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}"; public static string EncOrgKeysKey(string userId) => $"encOrgKeys_{userId}";
public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}"; public static string EncPrivateKeyKey(string userId) => $"encPrivateKey_{userId}";
public static string EncKeyKey(string userId) => $"encKey_{userId}";
public static string KeyHashKey(string userId) => $"keyHash_{userId}"; public static string KeyHashKey(string userId) => $"keyHash_{userId}";
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}"; public static string PinKeyEncryptedUserKeyKey(string userId) => $"pinKeyEncryptedUserKey_{userId}";
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}"; public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}"; public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}";
public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}"; public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}";
@@ -121,5 +124,11 @@
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}"; public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}"; public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}"; public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
[Obsolete]
public static string KeyKey(string userId) => $"key_{userId}";
[Obsolete]
public static string EncKeyKey(string userId) => $"encKey_{userId}";
[Obsolete]
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
} }
} }

View File

@@ -117,9 +117,14 @@ namespace Bit.Core.Models.Domain
public class AccountVolatileData public class AccountVolatileData
{ {
public SymmetricCryptoKey Key; public UserKey UserKey;
public EncString PinProtectedKey; public MasterKey MasterKey;
public EncString PinKeyEncryptedUserKeyEphemeral;
public bool? BiometricLocked; public bool? BiometricLocked;
[Obsolete("Jul 6 2023: Key has been deprecated. We will use the User Key in the future. It remains here for migration during app upgrade.")]
public SymmetricCryptoKey Key;
[Obsolete("Jul 6 2023: PinProtectedKey has been deprecated in favor of UserKeyPinEphemeral. It remains here for migration during app upgrade.")]
public EncString PinProtectedKey;
} }
} }
} }

View File

@@ -74,4 +74,32 @@ namespace Bit.Core.Models.Domain
public string EncKeyB64 { get; set; } public string EncKeyB64 { get; set; }
public string MacKeyB64 { get; set; } public string MacKeyB64 { get; set; }
} }
public class UserKey : SymmetricCryptoKey
{
public UserKey(byte[] key, EncryptionType? encType = null)
: base(key, encType)
{ }
}
public class MasterKey : SymmetricCryptoKey
{
public MasterKey(byte[] key, EncryptionType? encType = null)
: base(key, encType)
{ }
}
public class PinKey : SymmetricCryptoKey
{
public PinKey(byte[] key, EncryptionType? encType = null)
: base(key, encType)
{ }
}
public class OrgKey : SymmetricCryptoKey
{
public OrgKey(byte[] key, EncryptionType? encType = null)
: base(key, encType)
{ }
}
} }

View File

@@ -485,7 +485,7 @@ namespace Bit.Core.Services
#region Key Connector #region Key Connector
public async Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl) public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl)
{ {
using (var requestMessage = new HttpRequestMessage()) using (var requestMessage = new HttpRequestMessage())
{ {

View File

@@ -30,7 +30,7 @@ namespace Bit.Core.Services
private readonly bool _setCryptoKeys; private readonly bool _setCryptoKeys;
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>(); private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
private SymmetricCryptoKey _key; private MasterKey _masterKey;
private string _authedUserId; private string _authedUserId;
private MasterPasswordPolicyOptions _masterPasswordPolicy; private MasterPasswordPolicyOptions _masterPasswordPolicy;
@@ -46,8 +46,6 @@ namespace Bit.Core.Services
II18nService i18nService, II18nService i18nService,
IPlatformUtilsService platformUtilsService, IPlatformUtilsService platformUtilsService,
IMessagingService messagingService, IMessagingService messagingService,
IVaultTimeoutService vaultTimeoutService,
IKeyConnectorService keyConnectorService,
IPasswordGenerationService passwordGenerationService, IPasswordGenerationService passwordGenerationService,
IPolicyService policyService, IPolicyService policyService,
bool setCryptoKeys = true) bool setCryptoKeys = true)
@@ -61,7 +59,6 @@ namespace Bit.Core.Services
_i18nService = i18nService; _i18nService = i18nService;
_platformUtilsService = platformUtilsService; _platformUtilsService = platformUtilsService;
_messagingService = messagingService; _messagingService = messagingService;
_keyConnectorService = keyConnectorService;
_passwordGenerationService = passwordGenerationService; _passwordGenerationService = passwordGenerationService;
_policyService = policyService; _policyService = policyService;
_setCryptoKeys = setCryptoKeys; _setCryptoKeys = setCryptoKeys;
@@ -145,8 +142,8 @@ namespace Bit.Core.Services
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
_2faForcePasswordResetReason = null; _2faForcePasswordResetReason = null;
var key = await MakePreloginKeyAsync(masterPassword, email); var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key); var hashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization); var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken); var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken);
if (await RequirePasswordChangeAsync(email, masterPassword)) if (await RequirePasswordChangeAsync(email, masterPassword))
@@ -199,7 +196,7 @@ namespace Bit.Core.Services
{ {
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey); var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey);
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey); var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new SymmetricCryptoKey(decKey), null, null, return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new MasterKey(decKey), null, null,
null, null, authRequestId: authRequestId); null, null, authRequestId: authRequestId);
} }
@@ -216,7 +213,7 @@ namespace Bit.Core.Services
{ {
CaptchaToken = captchaToken; CaptchaToken = captchaToken;
} }
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key, var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _masterKey,
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId); twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId);
// If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync() // If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync()
@@ -236,8 +233,8 @@ namespace Bit.Core.Services
{ {
SelectedTwoFactorProviderType = null; SelectedTwoFactorProviderType = null;
var key = await MakePreloginKeyAsync(masterPassword, email); var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key); var hashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization); var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, twoFactorProvider, return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, twoFactorProvider,
twoFactorToken, remember); twoFactorToken, remember);
} }
@@ -337,7 +334,7 @@ namespace Bit.Core.Services
// Helpers // Helpers
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email) private async Task<MasterKey> MakePreloginKeyAsync(string masterPassword, string email)
{ {
email = email.Trim().ToLower(); email = email.Trim().ToLower();
KdfConfig kdfConfig = KdfConfig.Default; KdfConfig kdfConfig = KdfConfig.Default;
@@ -356,11 +353,11 @@ namespace Bit.Core.Services
throw; throw;
} }
} }
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdfConfig); return await _cryptoService.MakeMasterKeyAsync(masterPassword, email, kdfConfig);
} }
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword, private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key, string code, string codeVerifier, string redirectUrl, MasterKey masterKey,
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null, TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null,
string captchaToken = null, string orgId = null, string authRequestId = null) string captchaToken = null, string orgId = null, string authRequestId = null)
{ {
@@ -426,7 +423,7 @@ namespace Bit.Core.Services
Code = code; Code = code;
CodeVerifier = codeVerifier; CodeVerifier = codeVerifier;
SsoRedirectUrl = redirectUrl; SsoRedirectUrl = redirectUrl;
_key = _setCryptoKeys ? key : null; _masterKey = _setCryptoKeys ? masterKey : null;
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2; TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2; result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
CaptchaToken = response.TwoFactorResponse.CaptchaToken; CaptchaToken = response.TwoFactorResponse.CaptchaToken;
@@ -470,24 +467,27 @@ namespace Bit.Core.Services
_messagingService.Send("accountAdded"); _messagingService.Send("accountAdded");
if (_setCryptoKeys) if (_setCryptoKeys)
{ {
if (key != null)
{
await _cryptoService.SetKeyAsync(key);
}
if (localHashedPassword != null) if (localHashedPassword != null)
{ {
await _cryptoService.SetKeyHashAsync(localHashedPassword); await _cryptoService.SetMasterKeyHashAsync(localHashedPassword);
} }
if (code == null || tokenResponse.Key != null) if (code == null || tokenResponse.Key != null)
{ {
if (tokenResponse.KeyConnectorUrl != null) if (tokenResponse.KeyConnectorUrl != null)
{ {
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl); await _keyConnectorService.GetAndSetMasterKeyAsync(tokenResponse.KeyConnectorUrl);
} }
await _cryptoService.SetEncKeyAsync(tokenResponse.Key); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
if (masterKey != null)
{
await _cryptoService.SetMasterKeyAsync(masterKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
}
// User doesn't have a key pair yet (old account), let's generate one for them. // User doesn't have a key pair yet (old account), let's generate one for them.
if (tokenResponse.PrivateKey == null) if (tokenResponse.PrivateKey == null)
@@ -505,19 +505,26 @@ namespace Bit.Core.Services
catch { } catch { }
} }
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey); await _cryptoService.SetUserPrivateKeyAsync(tokenResponse.PrivateKey);
} }
else if (tokenResponse.KeyConnectorUrl != null) else if (tokenResponse.KeyConnectorUrl != null)
{ {
// SSO Key Connector Onboarding // SSO Key Connector Onboarding
var password = await _cryptoFunctionService.RandomBytesAsync(64); var password = await _cryptoFunctionService.RandomBytesAsync(64);
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig); var newMasterKey = await _cryptoService.MakeMasterKeyAsync(
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64); Convert.ToBase64String(password),
await _cryptoService.SetKeyAsync(k); _tokenService.GetEmail(),
tokenResponse.KdfConfig);
var encKey = await _cryptoService.MakeEncKeyAsync(k); var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
var keyPair = await _cryptoService.MakeKeyPairAsync(); var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
await _cryptoService.SetUserKeyAsync(newUserKey);
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
await _cryptoService.SetMasterKeyAsync(newMasterKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
try try
{ {
@@ -530,11 +537,11 @@ namespace Bit.Core.Services
var keys = new KeysRequest var keys = new KeysRequest
{ {
PublicKey = keyPair.Item1, PublicKey = newPublicKey,
EncryptedPrivateKey = keyPair.Item2.EncryptedString EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
}; };
var setPasswordRequest = new SetKeyConnectorKeyRequest( var setPasswordRequest = new SetKeyConnectorKeyRequest(
encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
); );
await _apiService.PostSetKeyConnectorKey(setPasswordRequest); await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
} }
@@ -549,7 +556,7 @@ namespace Bit.Core.Services
private void ClearState() private void ClearState()
{ {
_key = null; _masterKey = null;
Email = null; Email = null;
CaptchaToken = null; CaptchaToken = null;
MasterPasswordHash = null; MasterPasswordHash = null;
@@ -590,7 +597,7 @@ namespace Bit.Core.Services
public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved) public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved)
{ {
var publicKey = CoreHelpers.Base64UrlDecode(pubKey); var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
var masterKey = await _cryptoService.GetKeyAsync(); var masterKey = await _cryptoService.GetMasterKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey); var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey); var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey);
var deviceId = await _appIdService.GetAppIdAsync(); var deviceId = await _appIdService.GetAppIdAsync();

View File

@@ -249,8 +249,7 @@ namespace Bit.Core.Services
{ {
try try
{ {
var hashKey = await _cryptoService.HasKeyAsync(); if (!await _cryptoService.HasUserKeyAsync())
if (!hashKey)
{ {
throw new Exception("No key."); throw new Exception("No key.");
} }
@@ -557,9 +556,9 @@ namespace Bit.Core.Services
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data) public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
{ {
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId); var (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(cipher.OrganizationId);
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey); var encFileName = await _cryptoService.EncryptAsync(filename, encKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey); var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
CipherResponse response; CipherResponse response;
@@ -567,7 +566,7 @@ namespace Bit.Core.Services
{ {
var request = new AttachmentRequest var request = new AttachmentRequest
{ {
Key = orgEncAttachmentKey.EncryptedString, Key = protectedAttachmentKey.EncryptedString,
FileName = encFileName.EncryptedString, FileName = encFileName.EncryptedString,
FileSize = encFileData.Buffer.Length, FileSize = encFileData.Buffer.Length,
}; };
@@ -578,7 +577,7 @@ namespace Bit.Core.Services
} }
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
{ {
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey); response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, protectedAttachmentKey);
} }
var userId = await _stateService.GetActiveUserIdAsync(); var userId = await _stateService.GetActiveUserIdAsync();
@@ -796,6 +795,14 @@ namespace Bit.Core.Services
// Helpers // Helpers
private async Task<Tuple<SymmetricCryptoKey, EncString, SymmetricCryptoKey>> MakeAttachmentKeyAsync(string organizationId)
{
var encryptionKey = await _cryptoService.GetOrgKeyAsync(organizationId)
?? (SymmetricCryptoKey)await _cryptoService.GetUserKeyWithLegacySupportAsync();
var (attachmentKey, protectedAttachmentKey) = await _cryptoService.MakeDataEncKeyAsync(encryptionKey);
return new Tuple<SymmetricCryptoKey, EncString, SymmetricCryptoKey>(attachmentKey, protectedAttachmentKey, encryptionKey);
}
private async Task ShareAttachmentWithServerAsync(AttachmentView attachmentView, string cipherId, private async Task ShareAttachmentWithServerAsync(AttachmentView attachmentView, string cipherId,
string organizationId) string organizationId)
{ {
@@ -807,14 +814,16 @@ namespace Bit.Core.Services
var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync(); var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync();
var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null); var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null);
var key = await _cryptoService.GetOrgKeyAsync(organizationId);
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, key); var (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(organizationId);
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
var encData = await _cryptoService.EncryptToBytesAsync(decBytes, dataEncKey.Item1); var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, encKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(decBytes, attachmentKey);
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks); var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
var fd = new MultipartFormDataContent(boundary); var fd = new MultipartFormDataContent(boundary);
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key"); fd.Add(new StringContent(protectedAttachmentKey.EncryptedString), "key");
fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString); fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId); await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
} }

View File

@@ -101,7 +101,7 @@ namespace Bit.Core.Services
{ {
return _decryptedCollectionCache; return _decryptedCollectionCache;
} }
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
throw new Exception("No key."); throw new Exception("No key.");

File diff suppressed because it is too large Load Diff

View File

@@ -77,7 +77,7 @@ namespace Bit.Core.Services
{ {
return _decryptedFolderCache; return _decryptedFolderCache;
} }
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
throw new Exception("No key."); throw new Exception("No key.");

View File

@@ -24,14 +24,14 @@ namespace Bit.Core.Services
_organizationService = organizationService; _organizationService = organizationService;
} }
public async Task GetAndSetKey(string url) public async Task GetAndSetMasterKeyAsync(string url)
{ {
try try
{ {
var userKeyResponse = await _apiService.GetUserKeyFromKeyConnector(url); var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnectorAsync(url);
var keyArr = Convert.FromBase64String(userKeyResponse.Key); var masterKeyArr = Convert.FromBase64String(masterKeyResponse.Key);
var k = new SymmetricCryptoKey(keyArr); var masterKey = new MasterKey(masterKeyArr);
await _cryptoService.SetKeyAsync(k); await _cryptoService.SetMasterKeyAsync(masterKey);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -39,17 +39,17 @@ namespace Bit.Core.Services
} }
} }
public async Task SetUsesKeyConnector(bool usesKeyConnector) public async Task SetUsesKeyConnectorAsync(bool usesKeyConnector)
{ {
await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector); await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector);
} }
public async Task<bool> GetUsesKeyConnector() public async Task<bool> GetUsesKeyConnectorAsync()
{ {
return await _stateService.GetUsesKeyConnectorAsync(); return await _stateService.GetUsesKeyConnectorAsync();
} }
public async Task<Organization> GetManagingOrganization() public async Task<Organization> GetManagingOrganizationAsync()
{ {
var orgs = await _organizationService.GetAllAsync(); var orgs = await _organizationService.GetAllAsync();
return orgs.Find(o => return orgs.Find(o =>
@@ -57,14 +57,14 @@ namespace Bit.Core.Services
!o.IsAdmin); !o.IsAdmin);
} }
public async Task MigrateUser() public async Task MigrateUserAsync()
{ {
var organization = await GetManagingOrganization(); var organization = await GetManagingOrganizationAsync();
var key = await _cryptoService.GetKeyAsync(); var masterKey = await _cryptoService.GetMasterKeyAsync();
try try
{ {
var keyConnectorRequest = new KeyConnectorUserKeyRequest(key.EncKeyB64); var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest); await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
} }
catch (Exception e) catch (Exception e)
@@ -75,11 +75,11 @@ namespace Bit.Core.Services
await _apiService.PostConvertToKeyConnector(); await _apiService.PostConvertToKeyConnector();
} }
public async Task<bool> UserNeedsMigration() public async Task<bool> UserNeedsMigrationAsync()
{ {
var loggedInUsingSso = await _tokenService.GetIsExternal(); var loggedInUsingSso = await _tokenService.GetIsExternal();
var requiredByOrganization = await GetManagingOrganization() != null; var requiredByOrganization = await GetManagingOrganizationAsync() != null;
var userIsNotUsingKeyConnector = !await GetUsesKeyConnector(); var userIsNotUsingKeyConnector = !await GetUsesKeyConnectorAsync();
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
} }

View File

@@ -247,7 +247,7 @@ namespace Bit.Core.Services
public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync() public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync()
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
return new List<GeneratedPasswordHistory>(); return new List<GeneratedPasswordHistory>();
@@ -262,7 +262,7 @@ namespace Bit.Core.Services
public async Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken)) public async Task AddHistoryAsync(string password, CancellationToken token = default(CancellationToken))
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
return; return;

View File

@@ -143,7 +143,7 @@ namespace Bit.Core.Services
return _decryptedSendsCache; return _decryptedSendsCache;
} }
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
throw new Exception("No Key."); throw new Exception("No Key.");

View File

@@ -241,6 +241,19 @@ namespace Bit.Core.Services
))?.Settings?.EnvironmentUrls; ))?.Settings?.EnvironmentUrls;
} }
public async Task<UserKey> GetUserKeyBiometricUnlockAsync(string userId = null)
{
var keyB64 = await _storageMediatorService.GetAsync<string>(
await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), true);
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
}
public async Task SetUserKeyBiometricUnlockAsync(UserKey value, string userId = null)
{
await _storageMediatorService.SaveAsync(
await ComposeKeyAsync(Constants.UserKeyBiometricUnlockKey, userId), value?.KeyB64, true);
}
public async Task<bool?> GetBiometricUnlockAsync(string userId = null) public async Task<bool?> GetBiometricUnlockAsync(string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -302,12 +315,70 @@ namespace Bit.Core.Services
true, reconciledOptions); true, reconciledOptions);
} }
public async Task<UserKey> GetUserKeyAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.UserKey;
}
public async Task SetUserKeyAsync(UserKey value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.UserKey = value;
await SaveAccountAsync(account, reconciledOptions);
}
public async Task<MasterKey> GetMasterKeyAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.MasterKey;
}
public async Task SetMasterKeyAsync(MasterKey value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.MasterKey = value;
await SaveAccountAsync(account, reconciledOptions);
}
public async Task<string> GetMasterKeyEncryptedUserKeyAsync(string userId = null)
{
return await _storageMediatorService.GetAsync<string>(
await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), false);
}
public async Task SetMasterKeyEncryptedUserKeyAsync(string value, string userId = null)
{
await _storageMediatorService.SaveAsync(
await ComposeKeyAsync(Constants.MasterKeyEncryptedUserKeyKey, userId), value, false);
}
public async Task<UserKey> GetUserKeyAutoUnlockAsync(string userId = null)
{
var keyB64 = await _storageMediatorService.GetAsync<string>(
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), true);
return keyB64 == null ? null : new UserKey(Convert.FromBase64String(keyB64));
}
public async Task SetUserKeyAutoUnlockAsync(UserKey value, string userId = null)
{
await _storageMediatorService.SaveAsync(
await ComposeKeyAsync(Constants.UserKeyAutoUnlockKey, userId), value?.KeyB64, true);
}
public async Task<bool> CanAccessPremiumAsync(string userId = null) public async Task<bool> CanAccessPremiumAsync(string userId = null)
{ {
if (userId == null) if (userId == null)
{ {
userId = await GetActiveUserIdAsync(); userId = await GetActiveUserIdAsync();
} }
if (!await IsAuthenticatedAsync(userId)) if (!await IsAuthenticatedAsync(userId))
{ {
return false; return false;
@@ -353,36 +424,36 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions); await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions);
} }
public async Task<string> GetPinProtectedAsync(string userId = null) public async Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var key = await _storageMediatorService.GetAsync<string>(
await GetDefaultStorageOptionsAsync()); await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), false);
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions); return key != null ? new EncString(key) : null;
} }
public async Task SetPinProtectedAsync(string value, string userId = null) public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, await _storageMediatorService.SaveAsync(
await GetDefaultStorageOptionsAsync()); await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), value?.EncryptedString, false);
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
} }
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null) public async Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null)
{ {
return (await GetAccountAsync( return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync()) ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.PinProtectedKey; ))?.VolatileData?.PinKeyEncryptedUserKeyEphemeral;
} }
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null) public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync()); await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions); var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.PinProtectedKey = value; account.VolatileData.PinKeyEncryptedUserKeyEphemeral = value;
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }
public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null) public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null)
{ {
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId }, var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -395,35 +466,6 @@ namespace Bit.Core.Services
await SaveAccountAsync(account, reconciledOptions); await SaveAccountAsync(account, reconciledOptions);
} }
public async Task<string> GetKeyEncryptedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultSecureStorageOptionsAsync());
return await GetValueAsync<string>(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions);
}
public async Task SetKeyEncryptedAsync(string value, string userId)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultSecureStorageOptionsAsync());
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.Key;
}
public async Task SetKeyDecryptedAsync(SymmetricCryptoKey value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.Key = value;
await SaveAccountAsync(account, reconciledOptions);
}
public async Task<string> GetKeyHashAsync(string userId = null) public async Task<string> GetKeyHashAsync(string userId = null)
{ {
@@ -439,19 +481,6 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.KeyHashKey(reconciledOptions.UserId), value, reconciledOptions); await SetValueAsync(Constants.KeyHashKey(reconciledOptions.UserId), value, reconciledOptions);
} }
public async Task<string> GetEncKeyEncryptedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await GetValueAsync<string>(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions);
}
public async Task SetEncKeyEncryptedAsync(string value, string userId)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await SetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
public async Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null) public async Task<Dictionary<string, string>> GetOrgKeysEncryptedAsync(string userId = null)
{ {
@@ -1447,28 +1476,31 @@ namespace Bit.Core.Services
} }
// Non-state storage // Non-state storage
await SetProtectedPinAsync(null, userId); await Task.WhenAll(
await SetPinProtectedAsync(null, userId); SetUserKeyAutoUnlockAsync(null, userId),
await SetKeyEncryptedAsync(null, userId); SetUserKeyBiometricUnlockAsync(null, userId),
await SetKeyHashAsync(null, userId); SetProtectedPinAsync(null, userId),
await SetEncKeyEncryptedAsync(null, userId); SetKeyHashAsync(null, userId),
await SetOrgKeysEncryptedAsync(null, userId); SetOrgKeysEncryptedAsync(null, userId),
await SetPrivateKeyEncryptedAsync(null, userId); SetPrivateKeyEncryptedAsync(null, userId),
await SetLastActiveTimeAsync(null, userId); SetLastActiveTimeAsync(null, userId),
await SetPreviousPageInfoAsync(null, userId); SetPreviousPageInfoAsync(null, userId),
await SetInvalidUnlockAttemptsAsync(null, userId); SetInvalidUnlockAttemptsAsync(null, userId),
await SetLocalDataAsync(null, userId); SetLocalDataAsync(null, userId),
await SetEncryptedCiphersAsync(null, userId); SetEncryptedCiphersAsync(null, userId),
await SetEncryptedCollectionsAsync(null, userId); SetEncryptedCollectionsAsync(null, userId),
await SetLastSyncAsync(null, userId); SetLastSyncAsync(null, userId),
await SetEncryptedFoldersAsync(null, userId); SetEncryptedFoldersAsync(null, userId),
await SetEncryptedPoliciesAsync(null, userId); SetEncryptedPoliciesAsync(null, userId),
await SetUsesKeyConnectorAsync(null, userId); SetUsesKeyConnectorAsync(null, userId),
await SetOrganizationsAsync(null, userId); SetOrganizationsAsync(null, userId),
await SetEncryptedPasswordGenerationHistoryAsync(null, userId); SetEncryptedPasswordGenerationHistoryAsync(null, userId),
await SetEncryptedSendsAsync(null, userId); SetEncryptedSendsAsync(null, userId),
await SetSettingsAsync(null, userId); SetSettingsAsync(null, userId),
await SetApprovePasswordlessLoginsAsync(null, userId); SetApprovePasswordlessLoginsAsync(null, userId),
SetEncKeyEncryptedAsync(null, userId),
SetKeyEncryptedAsync(null, userId),
SetPinProtectedAsync(null, userId));
} }
private async Task ScaffoldNewAccountAsync(Account account) private async Task ScaffoldNewAccountAsync(Account account)
@@ -1656,5 +1688,79 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.LastUserShouldConnectToWatchKey, await SetValueAsync(Constants.LastUserShouldConnectToWatchKey,
shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync()); shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync());
} }
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
public async Task<string> GetPinProtectedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
}
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead")]
public async Task SetPinProtectedAsync(string value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
}
[Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.PinProtectedKey;
}
[Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead")]
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.PinProtectedKey = value;
await SaveAccountAsync(account, reconciledOptions);
}
[Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
public async Task<string> GetEncKeyEncryptedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await GetValueAsync<string>(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions);
}
[Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
public async Task SetEncKeyEncryptedAsync(string value, string userId)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await SetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
[Obsolete("Left for migration purposes")]
public async Task SetKeyEncryptedAsync(string value, string userId)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultSecureStorageOptionsAsync());
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
public async Task<string> GetKeyEncryptedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultSecureStorageOptionsAsync());
return await GetValueAsync<string>(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions);
}
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.Key;
}
} }
} }

View File

@@ -327,8 +327,8 @@ namespace Bit.Core.Services
} }
return; return;
} }
await _cryptoService.SetEncKeyAsync(response.Key); await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(response.Key);
await _cryptoService.SetEncPrivateKeyAsync(response.PrivateKey); await _cryptoService.SetUserPrivateKeyAsync(response.PrivateKey);
await _cryptoService.SetOrgKeysAsync(response.Organizations); await _cryptoService.SetOrgKeysAsync(response.Organizations);
await _stateService.SetSecurityStampAsync(response.SecurityStamp); await _stateService.SetSecurityStampAsync(response.SecurityStamp);
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o)); var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
@@ -337,7 +337,7 @@ namespace Bit.Core.Services
await _stateService.SetNameAsync(response.Name); await _stateService.SetNameAsync(response.Name);
await _stateService.SetPersonalPremiumAsync(response.Premium); await _stateService.SetPersonalPremiumAsync(response.Premium);
await _stateService.SetAvatarColorAsync(response.AvatarColor); await _stateService.SetAvatarColorAsync(response.AvatarColor);
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector); await _keyConnectorService.SetUsesKeyConnectorAsync(response.UsesKeyConnector);
} }
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response) private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)

View File

@@ -1,12 +1,17 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public enum PinLockType
{
Disabled,
Persistent,
Transient
}
public class VaultTimeoutService : IVaultTimeoutService public class VaultTimeoutService : IVaultTimeoutService
{ {
private readonly ICryptoService _cryptoService; private readonly ICryptoService _cryptoService;
@@ -54,15 +59,26 @@ namespace Bit.Core.Services
public async Task<bool> IsLockedAsync(string userId = null) public async Task<bool> IsLockedAsync(string userId = null)
{ {
var hasKey = await _cryptoService.HasKeyAsync(userId); var biometricSet = await IsBiometricLockSetAsync(userId);
if (hasKey) if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
{ {
var biometricSet = await IsBiometricLockSetAsync(userId); return true;
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId)) }
if (!await _cryptoService.HasUserKeyAsync(userId))
{
if (await _cryptoService.HasAutoUnlockKeyAsync(userId))
{
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId));
}
else
{ {
return true; return true;
} }
} }
// Check again to verify auto key was set
var hasKey = await _cryptoService.HasUserKeyAsync(userId);
return !hasKey; return !hasKey;
} }
@@ -163,13 +179,15 @@ namespace Bit.Core.Services
userId = await _stateService.GetActiveUserIdAsync(); userId = await _stateService.GetActiveUserIdAsync();
} }
if (await _keyConnectorService.GetUsesKeyConnector()) if (await _keyConnectorService.GetUsesKeyConnectorAsync())
{ {
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId); var pinStatus = await GetPinLockTypeAsync(userId);
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) || var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
isPinProtectedWithKey; ?? await _stateService.GetPinProtectedKeyAsync();
var pinEnabled = (pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
pinStatus == PinLockType.Persistent;
if (!pinLock && !await IsBiometricLockSetAsync()) if (!pinEnabled && !await IsBiometricLockSetAsync())
{ {
await LogOutAsync(userInitiated, userId); await LogOutAsync(userInitiated, userId);
return; return;
@@ -187,10 +205,11 @@ namespace Bit.Core.Services
} }
} }
await Task.WhenAll( await Task.WhenAll(
_cryptoService.ClearKeyAsync(userId), _cryptoService.ClearUserKeyAsync(userId),
_cryptoService.ClearMasterKeyAsync(userId),
_stateService.SetUserKeyAutoUnlockAsync(null, userId),
_cryptoService.ClearOrgKeysAsync(true, userId), _cryptoService.ClearOrgKeysAsync(true, userId),
_cryptoService.ClearKeyPairAsync(true, userId), _cryptoService.ClearKeyPairAsync(true, userId));
_cryptoService.ClearEncKeyAsync(true, userId));
if (isActiveAccount) if (isActiveAccount)
{ {
@@ -214,15 +233,27 @@ namespace Bit.Core.Services
{ {
await _stateService.SetVaultTimeoutAsync(timeout); await _stateService.SetVaultTimeoutAsync(timeout);
await _stateService.SetVaultTimeoutActionAsync(action); await _stateService.SetVaultTimeoutActionAsync(action);
await _cryptoService.ToggleKeyAsync(); await _cryptoService.RefreshKeysAsync();
await _tokenService.ToggleTokensAsync(); await _tokenService.ToggleTokensAsync();
} }
public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null) public async Task<PinLockType> GetPinLockTypeAsync(string userId = null)
{ {
var protectedPin = await _stateService.GetProtectedPinAsync(userId); // we can't depend on only the protected pin being set because old
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId); // versions only used it for MP on Restart
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null); var isPinEnabled = await _stateService.GetProtectedPinAsync(userId) != null;
var hasUserKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(userId) != null;
var hasOldUserKeyPin = await _stateService.GetPinProtectedAsync(userId) != null;
if (hasUserKeyPin || hasOldUserKeyPin)
{
return PinLockType.Persistent;
}
else if (isPinEnabled && !hasUserKeyPin && !hasOldUserKeyPin)
{
return PinLockType.Transient;
}
return PinLockType.Disabled;
} }
public async Task<bool> IsBiometricLockSetAsync(string userId = null) public async Task<bool> IsBiometricLockSetAsync(string userId = null)
@@ -233,8 +264,7 @@ namespace Bit.Core.Services
public async Task ClearAsync(string userId = null) public async Task ClearAsync(string userId = null)
{ {
await _stateService.SetPinProtectedKeyAsync(null, userId); await _cryptoService.ClearPinKeysAsync(userId);
await _stateService.SetProtectedPinAsync(null, userId);
} }
public async Task<int?> GetVaultTimeout(string userId = null) public async Task<int?> GetVaultTimeout(string userId = null)

View File

@@ -78,8 +78,8 @@ namespace Bit.Core.Utilities
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService); var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
var totpService = new TotpService(cryptoFunctionService); var totpService = new TotpService(cryptoFunctionService);
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService, var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService, tokenService, appIdService, i18nService, platformUtilsService, messagingService,
keyConnectorService, passwordGenerationService, policyService); passwordGenerationService, policyService);
var exportService = new ExportService(folderService, cipherService, cryptoService); var exportService = new ExportService(folderService, cipherService, cryptoService);
var auditService = new AuditService(cryptoFunctionService, apiService); var auditService = new AuditService(cryptoFunctionService, apiService);
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner); var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.autofill</string> <string>com.8bit.bitwarden.autofill</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2023.5.1</string> <string>2023.7.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>

View File

@@ -8,11 +8,13 @@ using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
using Foundation; using Foundation;
using UIKit; using UIKit;
using Xamarin.Essentials;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
@@ -28,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IBiometricService _biometricService; private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService; private IKeyConnectorService _keyConnectorService;
private IAccountsManager _accountManager; private IAccountsManager _accountManager;
private bool _isPinProtected; private PinLockType _pinStatus;
private bool _isPinProtectedWithKey; private bool _pinEnabled;
private bool _pinLock; private bool _biometricEnabled;
private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false; private bool _passwordReprompt = false;
private bool _usesKeyConnector; private bool _usesKeyConnector;
@@ -85,7 +86,7 @@ namespace Bit.iOS.Core.Controllers
} }
public abstract UITableView TableView { get; } public abstract UITableView TableView { get; }
public override async void ViewDidLoad() public override async void ViewDidLoad()
{ {
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); _vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
@@ -103,25 +104,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{ {
_passwordReprompt = true; _passwordReprompt = true;
_isPinProtected = false; _pinStatus = PinLockType.Disabled;
_isPinProtectedWithKey = false; _pinEnabled = false;
_pinLock = false; _biometricEnabled = false;
_biometricLock = false;
} }
else else
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && ?? await _stateService.GetPinProtectedKeyAsync();
await _cryptoService.HasKeyAsync(); _pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid = _biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnectorAsync();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
} }
if (_pinLock) if (_pinEnabled)
{ {
BaseNavItem.Title = AppResources.VerifyPIN; BaseNavItem.Title = AppResources.VerifyPIN;
} }
@@ -150,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly) if (!_biometricUnlockOnly)
{ {
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -158,7 +162,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().FireAndForget(); CheckPasswordAsync().FireAndForget();
return true; return true;
}; };
if (_pinLock) if (_pinEnabled)
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
@@ -177,7 +181,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad(); base.ViewDidLoad();
if (_biometricLock) if (_biometricEnabled)
{ {
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
{ {
@@ -198,18 +202,18 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector) if (_usesKeyConnector)
{ {
if (!(_pinLock || _biometricLock) || if (!(_pinEnabled || _biometricEnabled) ||
(_biometricLock && !_biometricIntegrityValid)) (_biometricEnabled && !_biometricIntegrityValid))
{ {
PromptSSO(); PromptSSO();
} }
} }
else if (!_biometricLock || !_biometricIntegrityValid) else if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
MasterPasswordCell.TextField.BecomeFirstResponder(); MasterPasswordCell.TextField.BecomeFirstResponder();
} }
} }
protected async Task CheckPasswordAsync() protected async Task CheckPasswordAsync()
{ {
if (_checkingPassword) if (_checkingPassword)
@@ -224,7 +228,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword), _pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok); AppResources.Ok);
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
return; return;
@@ -246,33 +250,53 @@ namespace Bit.iOS.Core.Controllers
return; return;
} }
if (_pinLock) if (_pinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockType.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockType.Transient)
{
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockType.Transient,
inputtedValue,
email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
kdfConfig); inputtedValue,
failed = false; email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -286,33 +310,27 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
if (storedKeyHash == null) if (storedPasswordHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey) if (masterKey.KeyB64 == oldKey)
{ {
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization); var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
if (passwordValid) if (passwordValid)
{ {
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
} }
else else
{ {
@@ -339,12 +357,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{ {
if (!_biometricLock || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -371,12 +389,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null); PresentViewController(loginController, true, null);
} }
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false) private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(userKey);
} }
DoContinue(masterPassword); DoContinue(masterPassword);
} }
@@ -396,7 +414,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded() private async Task EnableBiometricsIfNeeded()
{ {
// Re-enable biometrics if initial use // Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid) if (_biometricEnabled & !_biometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
} }
@@ -405,7 +423,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword), string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) => AppResources.Ok, (a) =>
{ {
@@ -490,7 +508,7 @@ namespace Bit.iOS.Core.Controllers
return 0; return 0;
} }
return (!controller._biometricUnlockOnly && controller._biometricLock) || return (!controller._biometricUnlockOnly && controller._biometricEnabled) ||
controller._passwordReprompt controller._passwordReprompt
? 2 ? 2
: 1; : 1;

View File

@@ -1,18 +1,19 @@
using System; using System;
using UIKit;
using Foundation;
using Bit.iOS.Core.Views;
using Bit.App.Resources;
using Bit.iOS.Core.Utilities;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Utilities; using Bit.App.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Enums;
using Bit.App.Pages;
using Bit.App.Models; using Bit.App.Models;
using Bit.App.Pages;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using Foundation;
using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
namespace Bit.iOS.Core.Controllers namespace Bit.iOS.Core.Controllers
@@ -29,10 +30,9 @@ namespace Bit.iOS.Core.Controllers
private IPlatformUtilsService _platformUtilsService; private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService; private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService; private IKeyConnectorService _keyConnectorService;
private bool _isPinProtected; private PinLockType _pinStatus;
private bool _isPinProtectedWithKey; private bool _pinEnabled;
private bool _pinLock; private bool _biometricEnabled;
private bool _biometricLock;
private bool _biometricIntegrityValid = true; private bool _biometricIntegrityValid = true;
private bool _passwordReprompt = false; private bool _passwordReprompt = false;
private bool _usesKeyConnector; private bool _usesKeyConnector;
@@ -96,25 +96,28 @@ namespace Bit.iOS.Core.Controllers
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync()) if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
{ {
_passwordReprompt = true; _passwordReprompt = true;
_isPinProtected = false; _pinStatus = PinLockType.Disabled;
_isPinProtectedWithKey = false; _pinEnabled = false;
_pinLock = false; _biometricEnabled = false;
_biometricLock = false;
} }
else else
{ {
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync(); _pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey; var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && ?? await _stateService.GetPinProtectedKeyAsync();
await _cryptoService.HasKeyAsync(); _pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent;
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
&& await _cryptoService.HasEncryptedUserKeyAsync();
_biometricIntegrityValid = _biometricIntegrityValid =
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey); await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector(); _usesKeyConnector = await _keyConnectorService.GetUsesKeyConnectorAsync();
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock; _biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
} }
if (_pinLock) if (_pinEnabled)
{ {
BaseNavItem.Title = AppResources.VerifyPIN; BaseNavItem.Title = AppResources.VerifyPIN;
} }
@@ -126,7 +129,7 @@ namespace Bit.iOS.Core.Controllers
{ {
BaseNavItem.Title = AppResources.VerifyMasterPassword; BaseNavItem.Title = AppResources.VerifyMasterPassword;
} }
BaseCancelButton.Title = AppResources.Cancel; BaseCancelButton.Title = AppResources.Cancel;
if (_biometricUnlockOnly) if (_biometricUnlockOnly)
@@ -143,7 +146,7 @@ namespace Bit.iOS.Core.Controllers
if (!_biometricUnlockOnly) if (!_biometricUnlockOnly)
{ {
MasterPasswordCell.Label.Text = _pinLock ? AppResources.PIN : AppResources.MasterPassword; MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
MasterPasswordCell.TextField.SecureTextEntry = true; MasterPasswordCell.TextField.SecureTextEntry = true;
MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go; MasterPasswordCell.TextField.ReturnKeyType = UIReturnKeyType.Go;
MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) => MasterPasswordCell.TextField.ShouldReturn += (UITextField tf) =>
@@ -151,7 +154,7 @@ namespace Bit.iOS.Core.Controllers
CheckPasswordAsync().GetAwaiter().GetResult(); CheckPasswordAsync().GetAwaiter().GetResult();
return true; return true;
}; };
if (_pinLock) if (_pinEnabled)
{ {
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad; MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
} }
@@ -165,7 +168,7 @@ namespace Bit.iOS.Core.Controllers
base.ViewDidLoad(); base.ViewDidLoad();
if (_biometricLock) if (_biometricEnabled)
{ {
if (!_biometricIntegrityValid) if (!_biometricIntegrityValid)
{ {
@@ -186,13 +189,13 @@ namespace Bit.iOS.Core.Controllers
// Users with key connector and without biometric or pin has no MP to unlock with // Users with key connector and without biometric or pin has no MP to unlock with
if (_usesKeyConnector) if (_usesKeyConnector)
{ {
if (!(_pinLock || _biometricLock) || if (!(_pinEnabled || _biometricEnabled) ||
(_biometricLock && !_biometricIntegrityValid)) (_biometricEnabled && !_biometricIntegrityValid))
{ {
PromptSSO(); PromptSSO();
} }
} }
else if (!_biometricLock || !_biometricIntegrityValid) else if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
MasterPasswordCell.TextField.BecomeFirstResponder(); MasterPasswordCell.TextField.BecomeFirstResponder();
} }
@@ -204,7 +207,7 @@ namespace Bit.iOS.Core.Controllers
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, string.Format(AppResources.ValidationFieldRequired,
_pinLock ? AppResources.PIN : AppResources.MasterPassword), _pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
AppResources.Ok); AppResources.Ok);
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
return; return;
@@ -214,33 +217,53 @@ namespace Bit.iOS.Core.Controllers
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile)); var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var inputtedValue = MasterPasswordCell.TextField.Text; var inputtedValue = MasterPasswordCell.TextField.Text;
if (_pinLock) if (_pinEnabled)
{ {
var failed = true; var failed = true;
try try
{ {
if (_isPinProtected) EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockType.Persistent)
{ {
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockType.Transient)
{
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockType.Transient,
inputtedValue,
email,
kdfConfig, kdfConfig,
await _stateService.GetPinProtectedKeyAsync()); oldPinProtected
var encKey = await _cryptoService.GetEncKeyAsync(key); );
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email, userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
kdfConfig); inputtedValue,
failed = false; email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != inputtedValue;
if (!failed)
{
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2); await SetKeyAndContinueAsync(userKey);
} }
} }
catch catch
@@ -260,33 +283,27 @@ namespace Bit.iOS.Core.Controllers
} }
else else
{ {
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig); var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync(); var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
if (storedKeyHash == null) if (storedPasswordHash == null)
{ {
var oldKey = await _secureStorageService.GetAsync<string>("oldKey"); var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
if (key2.KeyB64 == oldKey) if (masterKey.KeyB64 == oldKey)
{ {
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization); var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
await _secureStorageService.RemoveAsync("oldKey"); await _secureStorageService.RemoveAsync("oldKey");
await _cryptoService.SetKeyHashAsync(localKeyHash); await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
} }
} }
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2); var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
if (passwordValid) if (passwordValid)
{ {
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key2);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key2.Key, pinKey));
}
await AppHelpers.ResetInvalidUnlockAttemptsAsync(); await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key2, true);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey, true);
} }
else else
{ {
@@ -303,12 +320,12 @@ namespace Bit.iOS.Core.Controllers
public async Task PromptBiometricAsync() public async Task PromptBiometricAsync()
{ {
if (!_biometricLock || !_biometricIntegrityValid) if (!_biometricEnabled || !_biometricIntegrityValid)
{ {
return; return;
} }
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
await _stateService.SetBiometricLockedAsync(!success); await _stateService.SetBiometricLockedAsync(!success);
if (success) if (success)
@@ -335,12 +352,12 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(loginController, true, null); PresentViewController(loginController, true, null);
} }
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false) private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey) if (!hasKey)
{ {
await _cryptoService.SetKeyAsync(key); await _cryptoService.SetUserKeyAsync(userKey);
} }
DoContinue(masterPassword); DoContinue(masterPassword);
} }
@@ -360,7 +377,7 @@ namespace Bit.iOS.Core.Controllers
private async Task EnableBiometricsIfNeeded() private async Task EnableBiometricsIfNeeded()
{ {
// Re-enable biometrics if initial use // Re-enable biometrics if initial use
if (_biometricLock & !_biometricIntegrityValid) if (_biometricEnabled & !_biometricIntegrityValid)
{ {
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey); await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
} }
@@ -369,7 +386,7 @@ namespace Bit.iOS.Core.Controllers
private void InvalidValue() private void InvalidValue()
{ {
var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred, var alert = Dialogs.CreateAlert(AppResources.AnErrorHasOccurred,
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword), string.Format(null, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
AppResources.Ok, (a) => AppResources.Ok, (a) =>
{ {
@@ -378,7 +395,7 @@ namespace Bit.iOS.Core.Controllers
}); });
PresentViewController(alert, true, null); PresentViewController(alert, true, null);
} }
private async Task LogOutAsync() private async Task LogOutAsync()
{ {
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync()); await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
@@ -444,7 +461,7 @@ namespace Bit.iOS.Core.Controllers
public override nint NumberOfSections(UITableView tableView) public override nint NumberOfSections(UITableView tableView)
{ {
return (!_controller._biometricUnlockOnly && _controller._biometricLock) || return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) ||
_controller._passwordReprompt _controller._passwordReprompt
? 2 ? 2
: 1; : 1;

View File

@@ -1,20 +1,19 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Services;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Foundation; using Foundation;
using LocalAuthentication; using LocalAuthentication;
namespace Bit.iOS.Core.Services namespace Bit.iOS.Core.Services
{ {
public class BiometricService : IBiometricService public class BiometricService : BaseBiometricService
{ {
private IStateService _stateService; public BiometricService(IStateService stateService, ICryptoService cryptoService)
: base(stateService, cryptoService)
public BiometricService(IStateService stateService)
{ {
_stateService = stateService;
} }
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null) public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
{ {
if (bioIntegritySrcKey == null) if (bioIntegritySrcKey == null)
{ {
@@ -30,7 +29,7 @@ namespace Bit.iOS.Core.Services
return true; return true;
} }
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
{ {
var state = GetState(); var state = GetState();
if (state == null) if (state == null)

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities.Prompts;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.iOS.Core.Utilities; using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views; using Bit.iOS.Core.Views;
@@ -147,6 +148,11 @@ namespace Bit.iOS.Core.Services
return result.Task; return result.Task;
} }
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
throw new NotImplementedException();
}
public void RateApp() public void RateApp()
{ {
string uri = null; string uri = null;

View File

@@ -112,9 +112,9 @@ namespace Bit.iOS.Core.Utilities
var clipboardService = new ClipboardService(stateService); var clipboardService = new ClipboardService(stateService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService, var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService); messagingService, broadcasterService);
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var biometricService = new BiometricService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage); ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string> <string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2023.5.1</string> <string>2023.7.1</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>
<array> <array>
<string>en</string> <string>en</string>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>XPC!</string> <string>XPC!</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2023.5.1</string> <string>2023.7.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string> <string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2023.5.1</string> <string>2023.7.1</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleIconName</key> <key>CFBundleIconName</key>

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

View File

@@ -0,0 +1,10 @@
"ThereAreNoItemsToList"="There are no items to list";
"ToViewVerificationCodesUpgradeToPremium"="To view verification codes, upgrade to premium";
"Add2FactorAutenticationToAnItemToViewVerificationCodes"="Add 2 factor authentication to an item to view the verification codes";
"LogInToBitwardenOnYourIPhoneToViewVerificationCodes"="Log in to Bitwarden on your iPhone to view verification codes";
"SyncingItemsContainingVerificationCodes"="Syncing items containing verification codes";
"UnlockBitwardenOnYourIPhoneToViewVerificationCodes"="Unlock Bitwarden on your iPhone to view verification codes";
"SetUpBitwardenToViewItemsContainingVerificationCodes"="Set up Bitwarden to view items containing verification codes";
"Search"="Search";
"NoItemsFound"="No items found";
"SetUpAppleWatchPasscodeInOrderToUseBitwarden"="Set up Apple Watch passcode in order to use Bitwarden";

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