mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
19 Commits
add-bio-ke
...
feature/pm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2014d7f562 | ||
|
|
8a399235f4 | ||
|
|
ce55750e60 | ||
|
|
a15269bafe | ||
|
|
8a59e17fc9 | ||
|
|
548bd12a8e | ||
|
|
58542fd255 | ||
|
|
fc300f3e3f | ||
|
|
800b4c71de | ||
|
|
7bcf1c377f | ||
|
|
3053eaa036 | ||
|
|
109a84607a | ||
|
|
d2b6c73a75 | ||
|
|
9dc6a725cf | ||
|
|
6268f0776b | ||
|
|
cbbc41be67 | ||
|
|
e164fb9823 | ||
|
|
87866304a6 | ||
|
|
84a82f0876 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -71,11 +71,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
18
.github/workflows/version-auto-bump.yml
vendored
18
.github/workflows/version-auto-bump.yml
vendored
@@ -32,10 +32,14 @@ jobs:
|
|||||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
trigger_version_bump:
|
trigger_version_bump:
|
||||||
name: Bump version to ${{ needs.setup.outputs.version_number }}
|
name: "Version bump"
|
||||||
needs: setup
|
runs-on: ubuntu-22.04
|
||||||
uses: ./.github/workflows/version-bump.yml
|
needs:
|
||||||
secrets:
|
- setup
|
||||||
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
steps:
|
||||||
with:
|
- name: Bump version to ${{ needs.setup.outputs.version_number }}
|
||||||
version_number: ${{ needs.setup.outputs.version_number }}
|
uses: ./.github/workflows/version-bump.yml
|
||||||
|
secrets:
|
||||||
|
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
with:
|
||||||
|
version_number: ${{ needs.setup.outputs.version_number }}
|
||||||
|
|||||||
12
crowdin.yml
12
crowdin.yml
@@ -38,15 +38,3 @@ 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
|
|
||||||
|
|||||||
@@ -233,18 +233,6 @@
|
|||||||
<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" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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.7.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.5.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" />
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System.ComponentModel;
|
using System;
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.Droid.Renderers;
|
using System.ComponentModel;
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using Android.Content;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Bit.Droid.Renderers;
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
|
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
|
||||||
namespace Bit.Droid.Renderers
|
namespace Bit.Droid.Renderers
|
||||||
@@ -15,19 +15,6 @@ 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;
|
||||||
@@ -41,3 +28,4 @@ namespace Bit.Droid.Renderers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
|
||||||
@@ -2,7 +2,4 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
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;
|
||||||
@@ -10,8 +9,10 @@ using Javax.Crypto;
|
|||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
public class BiometricService : BaseBiometricService
|
public class BiometricService : IBiometricService
|
||||||
{
|
{
|
||||||
|
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";
|
||||||
@@ -23,14 +24,14 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
private readonly KeyStore _keystore;
|
private readonly KeyStore _keystore;
|
||||||
|
|
||||||
public BiometricService(IStateService stateService, ICryptoService cryptoService)
|
public BiometricService(IStateService stateService)
|
||||||
: base(stateService, cryptoService)
|
|
||||||
{
|
{
|
||||||
|
_stateService = stateService;
|
||||||
_keystore = KeyStore.GetInstance(KeyStoreName);
|
_keystore = KeyStore.GetInstance(KeyStoreName);
|
||||||
_keystore.Load(null);
|
_keystore.Load(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||||
{
|
{
|
||||||
@@ -40,7 +41,7 @@ namespace Bit.Droid.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||||
{
|
{
|
||||||
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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;
|
||||||
@@ -14,17 +13,11 @@ 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
|
||||||
@@ -216,7 +209,10 @@ namespace Bit.Droid.Services
|
|||||||
}
|
}
|
||||||
if (numericKeyboard)
|
if (numericKeyboard)
|
||||||
{
|
{
|
||||||
SetNumericKeyboardTo(input);
|
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
||||||
|
#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)
|
||||||
{
|
{
|
||||||
@@ -252,82 +248,6 @@ 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;
|
||||||
@@ -605,13 +525,5 @@ 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Java.Lang;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Utilities
|
namespace Bit.Droid.Utilities
|
||||||
{
|
{
|
||||||
@@ -14,12 +13,7 @@ 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 (Exception ex) when
|
catch (BadParcelableException)
|
||||||
(
|
|
||||||
ex is BadParcelableException ||
|
|
||||||
ex is ClassNotFoundException ||
|
|
||||||
ex is RuntimeException
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
intent.ReplaceExtras((Bundle)null);
|
intent.ReplaceExtras((Bundle)null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ 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);
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,6 @@
|
|||||||
<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>
|
||||||
@@ -443,6 +442,5 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Xamarin.Forms;
|
using System;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Controls
|
namespace Bit.App.Controls
|
||||||
{
|
{
|
||||||
@@ -7,7 +8,6 @@ namespace Bit.App.Controls
|
|||||||
public CustomLabel()
|
public CustomLabel()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? FontWeight { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<StackLayout StyleClass="box">
|
<StackLayout StyleClass="box">
|
||||||
<Grid
|
<Grid
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
IsVisible="{Binding PinEnabled}"
|
IsVisible="{Binding PinLock}"
|
||||||
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 PinEnabled, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
|
||||||
Padding="0, 10, 0, 0">
|
Padding="0, 10, 0, 0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_vm?.PinEnabled ?? false)
|
if (_vm?.PinLock ?? false)
|
||||||
{
|
{
|
||||||
return _pin;
|
return _pin;
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task PromptBiometricAfterResumeAsync()
|
public async Task PromptBiometricAfterResumeAsync()
|
||||||
{
|
{
|
||||||
if (_vm.BiometricEnabled)
|
if (_vm.BiometricLock)
|
||||||
{
|
{
|
||||||
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.BiometricEnabled)
|
if (!_vm.BiometricLock)
|
||||||
{
|
{
|
||||||
RequestFocus(SecretEntry);
|
RequestFocus(SecretEntry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_vm.UsingKeyConnector && !_vm.PinEnabled)
|
if (_vm.UsingKeyConnector && !_vm.PinLock)
|
||||||
{
|
{
|
||||||
_passwordGrid.IsVisible = false;
|
_passwordGrid.IsVisible = false;
|
||||||
_unlockButton.IsVisible = false;
|
_unlockButton.IsVisible = false;
|
||||||
|
|||||||
@@ -33,20 +33,21 @@ namespace Bit.App.Pages
|
|||||||
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IPasswordGenerationService _passwordGenerationService;
|
private readonly IPasswordGenerationService _passwordGenerationService;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
private string _email;
|
private string _email;
|
||||||
private string _masterPassword;
|
private string _masterPassword;
|
||||||
private string _pin;
|
private string _pin;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private PinLockType _pinStatus;
|
private bool _pinLock;
|
||||||
private bool _pinEnabled;
|
private bool _biometricLock;
|
||||||
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()
|
||||||
{
|
{
|
||||||
@@ -64,6 +65,7 @@ namespace Bit.App.Pages
|
|||||||
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
|
||||||
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
_policyService = ServiceContainer.Resolve<IPolicyService>();
|
||||||
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.VerifyMasterPassword;
|
PageTitle = AppResources.VerifyMasterPassword;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
@@ -99,10 +101,10 @@ namespace Bit.App.Pages
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool PinEnabled
|
public bool PinLock
|
||||||
{
|
{
|
||||||
get => _pinEnabled;
|
get => _pinLock;
|
||||||
set => SetProperty(ref _pinEnabled, value);
|
set => SetProperty(ref _pinLock, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UsingKeyConnector
|
public bool UsingKeyConnector
|
||||||
@@ -110,10 +112,10 @@ namespace Bit.App.Pages
|
|||||||
get => _usingKeyConnector;
|
get => _usingKeyConnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BiometricEnabled
|
public bool BiometricLock
|
||||||
{
|
{
|
||||||
get => _biometricEnabled;
|
get => _biometricLock;
|
||||||
set => SetProperty(ref _biometricEnabled, value);
|
set => SetProperty(ref _biometricLock, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool BiometricIntegrityValid
|
public bool BiometricIntegrityValid
|
||||||
@@ -161,18 +163,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
|
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
_isPinProtectedWithKey;
|
||||||
?? await _stateService.GetPinProtectedKeyAsync();
|
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
|
||||||
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.GetUsesKeyConnectorAsync();
|
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
|
if (_usingKeyConnector && !(BiometricLock || PinLock))
|
||||||
{
|
{
|
||||||
await _vaultTimeoutService.LogOutAsync();
|
await _vaultTimeoutService.LogOutAsync();
|
||||||
return;
|
return;
|
||||||
@@ -191,7 +189,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 (PinEnabled)
|
if (PinLock)
|
||||||
{
|
{
|
||||||
PageTitle = AppResources.VerifyPIN;
|
PageTitle = AppResources.VerifyPIN;
|
||||||
LockedVerifyText = AppResources.VaultLockedPIN;
|
LockedVerifyText = AppResources.VaultLockedPIN;
|
||||||
@@ -210,7 +208,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BiometricEnabled)
|
if (BiometricLock)
|
||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
if (!_biometricIntegrityValid)
|
if (!_biometricIntegrityValid)
|
||||||
@@ -232,14 +230,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SubmitAsync()
|
public async Task SubmitAsync()
|
||||||
{
|
{
|
||||||
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
|
if (PinLock && 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 (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
|
if (!PinLock && 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),
|
||||||
@@ -250,54 +248,34 @@ 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 (PinEnabled)
|
if (PinLock)
|
||||||
{
|
{
|
||||||
var failed = true;
|
var failed = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EncString userKeyPin = null;
|
if (_isPinProtected)
|
||||||
EncString oldPinProtected = null;
|
|
||||||
if (_pinStatus == PinLockType.Persistent)
|
|
||||||
{
|
{
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
|
||||||
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,
|
||||||
oldPinProtected
|
await _stateService.GetPinProtectedKeyAsync());
|
||||||
);
|
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
|
||||||
{
|
{
|
||||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
|
||||||
Pin,
|
failed = false;
|
||||||
_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 SetUserKeyAndContinueAsync(userKey);
|
await SetKeyAndContinueAsync(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -318,21 +296,19 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
|
||||||
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
var passwordValid = false;
|
var passwordValid = false;
|
||||||
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
|
||||||
|
|
||||||
if (storedKeyHash != null)
|
if (storedKeyHash != null)
|
||||||
{
|
{
|
||||||
// Offline unlock possible
|
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
|
||||||
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.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
|
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||||
var request = new PasswordVerificationRequest();
|
var request = new PasswordVerificationRequest();
|
||||||
request.MasterPasswordHash = keyHash;
|
request.MasterPasswordHash = keyHash;
|
||||||
|
|
||||||
@@ -341,8 +317,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.HashMasterKeyAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
|
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
|
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -352,6 +328,15 @@ 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
|
||||||
@@ -361,13 +346,10 @@ 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 (BiometricEnabled & !BiometricIntegrityValid)
|
if (BiometricLock & !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
await _biometricService.SetupBiometricAsync();
|
await _biometricService.SetupBiometricAsync();
|
||||||
}
|
}
|
||||||
@@ -444,7 +426,7 @@ namespace Bit.App.Pages
|
|||||||
public void TogglePassword()
|
public void TogglePassword()
|
||||||
{
|
{
|
||||||
ShowPassword = !ShowPassword;
|
ShowPassword = !ShowPassword;
|
||||||
var secret = PinEnabled ? Pin : MasterPassword;
|
var secret = PinLock ? Pin : MasterPassword;
|
||||||
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,27 +434,31 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
|
||||||
BiometricButtonVisible = BiometricIntegrityValid;
|
BiometricButtonVisible = BiometricIntegrityValid;
|
||||||
if (!BiometricEnabled || !BiometricIntegrityValid)
|
if (!BiometricLock || !BiometricIntegrityValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
PinLock ? 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)
|
||||||
{
|
{
|
||||||
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
|
await DoContinueAsync();
|
||||||
await SetUserKeyAndContinueAsync(userKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetUserKeyAndContinueAsync(UserKey key)
|
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
|
||||||
{
|
{
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetUserKeyAsync(key);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
|
}
|
||||||
|
if (await _deviceTrustCryptoService.GetUserTrustDeviceChoiceForDecryptionAsync())
|
||||||
|
{
|
||||||
|
await _deviceTrustCryptoService.TrustDeviceAsync();
|
||||||
|
await _deviceTrustCryptoService.SetUserTrustDeviceChoiceForDecryptionAsync(false);
|
||||||
}
|
}
|
||||||
await DoContinueAsync();
|
await DoContinueAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
76
src/App/Pages/Accounts/LoginApproveDevicePage.xaml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?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.LoginApproveDevicePage"
|
||||||
|
xmlns:pages="clr-namespace:Bit.App.Pages"
|
||||||
|
xmlns:u="clr-namespace:Bit.App.Utilities"
|
||||||
|
x:DataType="pages:LoginApproveDeviceViewModel"
|
||||||
|
x:Name="_page"
|
||||||
|
Title="{Binding PageTitle}">
|
||||||
|
|
||||||
|
<ContentPage.BindingContext>
|
||||||
|
<pages:LoginApproveDeviceViewModel />
|
||||||
|
</ContentPage.BindingContext>
|
||||||
|
|
||||||
|
<StackLayout Padding="10, 10">
|
||||||
|
<StackLayout Padding="5, 10" Orientation="Horizontal">
|
||||||
|
<StackLayout HorizontalOptions="FillAndExpand">
|
||||||
|
<Label
|
||||||
|
StyleClass="text-md"
|
||||||
|
Text="{u:I18n RememberThisDevice}"/>
|
||||||
|
<Label
|
||||||
|
StyleClass="box-sub-label"
|
||||||
|
Text="{u:I18n TurnOffUsingPublicDevice}"/>
|
||||||
|
</StackLayout>
|
||||||
|
<Switch
|
||||||
|
Scale="0.8"
|
||||||
|
IsToggled="{Binding RememberThisDevice}"
|
||||||
|
VerticalOptions="Center"/>
|
||||||
|
</StackLayout>
|
||||||
|
<StackLayout Margin="0, 20, 0, 0">
|
||||||
|
<Button
|
||||||
|
x:Name="_continue"
|
||||||
|
Text="{u:I18n Continue}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding ContinueCommand}"
|
||||||
|
IsVisible="{Binding ContinueEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_approveWithMyOtherDevice"
|
||||||
|
Text="{u:I18n ApproveWithMyOtherDevice}"
|
||||||
|
StyleClass="btn-primary"
|
||||||
|
Command="{Binding ApproveWithMyOtherDeviceCommand}"
|
||||||
|
IsVisible="{Binding ApproveWithMyOtherDeviceEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_requestAdminApproval"
|
||||||
|
Text="{u:I18n RequestAdminApproval}"
|
||||||
|
StyleClass="box-button-row"
|
||||||
|
Command="{Binding RequestAdminApprovalCommand}"
|
||||||
|
IsVisible="{Binding RequestAdminApprovalEnabled}"/>
|
||||||
|
<Button
|
||||||
|
x:Name="_approveWithMasterPassword"
|
||||||
|
Text="{u:I18n ApproveWithMasterPassword}"
|
||||||
|
StyleClass="box-button-row"
|
||||||
|
Command="{Binding ApproveWithMasterPasswordCommand}"
|
||||||
|
IsVisible="{Binding ApproveWithMasterPasswordEnabled}"/>
|
||||||
|
<Label
|
||||||
|
Text="{Binding LoggingInAsText}"
|
||||||
|
StyleClass="text-sm"
|
||||||
|
Margin="0,40,0,0"
|
||||||
|
AutomationId="LoggingInAsLabel"
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n NotYou}"
|
||||||
|
StyleClass="text-md"
|
||||||
|
HorizontalOptions="Start"
|
||||||
|
TextColor="{DynamicResource HyperlinkColor}"
|
||||||
|
AutomationId="NotYouLabel">
|
||||||
|
<Label.GestureRecognizers>
|
||||||
|
<TapGestureRecognizer Tapped="Cancel_Clicked" />
|
||||||
|
</Label.GestureRecognizers>
|
||||||
|
</Label>
|
||||||
|
</StackLayout>
|
||||||
|
</StackLayout>
|
||||||
|
</pages:BaseContentPage>
|
||||||
|
|
||||||
|
|
||||||
62
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
62
src/App/Pages/Accounts/LoginApproveDevicePage.xaml.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public partial class LoginApproveDevicePage : BaseContentPage
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly LoginApproveDeviceViewModel _vm;
|
||||||
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
|
public LoginApproveDevicePage(AppOptions appOptions = null)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = BindingContext as LoginApproveDeviceViewModel;
|
||||||
|
_vm.LogInWithMasterPassword = () => StartLogInWithMasterPassword().FireAndForget();
|
||||||
|
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
|
||||||
|
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
|
||||||
|
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
|
||||||
|
_vm.Page = this;
|
||||||
|
_appOptions = appOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearing()
|
||||||
|
{
|
||||||
|
_vm.InitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cancel_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
_vm.CloseAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartLogInWithMasterPassword()
|
||||||
|
{
|
||||||
|
var page = new LockPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartLoginWithDeviceAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RequestAdminApprovalAsync()
|
||||||
|
{
|
||||||
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
140
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
140
src/App/Pages/Accounts/LoginApproveDeviceViewModel.cs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Resources;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
|
using Xamarin.Essentials;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
|
||||||
|
namespace Bit.App.Pages
|
||||||
|
{
|
||||||
|
public class LoginApproveDeviceViewModel : BaseViewModel
|
||||||
|
{
|
||||||
|
private bool _rememberThisDevice;
|
||||||
|
private bool _approveWithMyOtherDeviceEnabled;
|
||||||
|
private bool _requestAdminApprovalEnabled;
|
||||||
|
private bool _approveWithMasterPasswordEnabled;
|
||||||
|
private bool _continueEnabled;
|
||||||
|
private string _email;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
|
||||||
|
public ICommand ApproveWithMyOtherDeviceCommand { get; }
|
||||||
|
public ICommand RequestAdminApprovalCommand { get; }
|
||||||
|
public ICommand ApproveWithMasterPasswordCommand { get; }
|
||||||
|
public ICommand ContinueCommand { get; }
|
||||||
|
|
||||||
|
public Action LogInWithMasterPassword { get; set; }
|
||||||
|
public Action LogInWithDeviceAction { get; set; }
|
||||||
|
public Action RequestAdminApprovalAction { get; set; }
|
||||||
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
|
public LoginApproveDeviceViewModel()
|
||||||
|
{
|
||||||
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
|
_apiService = ServiceContainer.Resolve<IApiService>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
|
PageTitle = AppResources.LoggedIn;
|
||||||
|
|
||||||
|
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => CheckDeviceTrustAndInvoke(LogInWithDeviceAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
RequestAdminApprovalCommand = new AsyncCommand(() => CheckDeviceTrustAndInvoke(RequestAdminApprovalAction),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
ApproveWithMasterPasswordCommand = new AsyncCommand(() => CheckDeviceTrustAndInvoke(LogInWithMasterPassword),
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
|
||||||
|
ContinueCommand = new AsyncCommand(InitAsync,
|
||||||
|
onException: ex => HandleException(ex),
|
||||||
|
allowsMultipleExecutions: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
||||||
|
|
||||||
|
public bool RememberThisDevice
|
||||||
|
{
|
||||||
|
get => _rememberThisDevice;
|
||||||
|
set => SetProperty(ref _rememberThisDevice, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApproveWithMyOtherDeviceEnabled
|
||||||
|
{
|
||||||
|
get => _approveWithMyOtherDeviceEnabled;
|
||||||
|
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequestAdminApprovalEnabled
|
||||||
|
{
|
||||||
|
get => _requestAdminApprovalEnabled;
|
||||||
|
set => SetProperty(ref _requestAdminApprovalEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApproveWithMasterPasswordEnabled
|
||||||
|
{
|
||||||
|
get => _approveWithMasterPasswordEnabled;
|
||||||
|
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContinueEnabled
|
||||||
|
{
|
||||||
|
get => _continueEnabled;
|
||||||
|
set => SetProperty(ref _continueEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Email
|
||||||
|
{
|
||||||
|
get => _email;
|
||||||
|
set => SetProperty(ref _email, value, additionalPropertyNames:
|
||||||
|
new string[] {
|
||||||
|
nameof(LoggingInAsText)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Email = await _stateService.GetRememberedEmailAsync();
|
||||||
|
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
|
||||||
|
RequestAdminApprovalEnabled = decryptOptions != null && decryptOptions.TrustedDeviceOption != null && decryptOptions.TrustedDeviceOption.HasAdminApproval;
|
||||||
|
ApproveWithMasterPasswordEnabled = decryptOptions != null && decryptOptions.HasMasterPassword;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ApproveWithMyOtherDeviceEnabled = await _apiService.GetDevicesExistenceByTypes(DeviceTypeExtensions.GetDesktopAndMobileTypes().ToArray());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
|
||||||
|
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckDeviceTrustAndInvoke(Action action)
|
||||||
|
{
|
||||||
|
await _deviceTrustCryptoService.SetUserTrustDeviceChoiceForDecryptionAsync(RememberThisDevice);
|
||||||
|
await Device.InvokeOnMainThreadAsync(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ using Bit.App.Models;
|
|||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -135,7 +136,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task StartLoginWithDeviceAsync()
|
private async Task StartLoginWithDeviceAsync()
|
||||||
{
|
{
|
||||||
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
|
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,17 +21,17 @@
|
|||||||
<StackLayout
|
<StackLayout
|
||||||
Padding="7, 0, 7, 20">
|
Padding="7, 0, 7, 20">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n LogInInitiated}"
|
Text="{Binding Tittle}"
|
||||||
FontSize="Title"
|
FontSize="Title"
|
||||||
FontAttributes="Bold"
|
FontAttributes="Bold"
|
||||||
Margin="0,14,0,21"
|
Margin="0,14,0,21"
|
||||||
AutomationId="LogInInitiatedLabel" />
|
AutomationId="LogInInitiatedLabel" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
|
Text="{Binding SubTittle}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,10"/>
|
Margin="0,0,0,10"/>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
|
Text="{Binding Description}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
Margin="0,0,0,24"/>
|
Margin="0,0,0,24"/>
|
||||||
<Label
|
<Label
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
AutomationId="FingerprintPhraseValue" />
|
AutomationId="FingerprintPhraseValue" />
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n ResendNotification}"
|
Text="{u:I18n ResendNotification}"
|
||||||
|
IsVisible="{Binding ResendNotificationVisible}"
|
||||||
StyleClass="text-md"
|
StyleClass="text-md"
|
||||||
HorizontalOptions="Start"
|
HorizontalOptions="Start"
|
||||||
Margin="0,40,0,0"
|
Margin="0,40,0,0"
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Margin="0,30,0,0">
|
Margin="0,30,0,0">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n NeedAnotherOption}"
|
Text="{Binding OtherOptions}"
|
||||||
FontSize="Small"
|
FontSize="Small"
|
||||||
VerticalTextAlignment="End"/>
|
VerticalTextAlignment="End"/>
|
||||||
<Label
|
<Label
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Pages
|
namespace Bit.App.Pages
|
||||||
@@ -12,13 +13,14 @@ namespace Bit.App.Pages
|
|||||||
private LoginPasswordlessRequestViewModel _vm;
|
private LoginPasswordlessRequestViewModel _vm;
|
||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
|
|
||||||
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
|
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
_vm = BindingContext as LoginPasswordlessRequestViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.Email = email;
|
_vm.Email = email;
|
||||||
|
_vm.AuthRequestType = authRequestType;
|
||||||
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
|
||||||
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
|
||||||
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -32,6 +33,7 @@ namespace Bit.App.Pages
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IEnvironmentService _environmentService;
|
private IEnvironmentService _environmentService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
private IDeviceTrustCryptoService _deviceTrustCryptoService;
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
@@ -44,6 +46,7 @@ namespace Bit.App.Pages
|
|||||||
private string _email;
|
private string _email;
|
||||||
private string _requestId;
|
private string _requestId;
|
||||||
private string _requestAccessCode;
|
private string _requestAccessCode;
|
||||||
|
private AuthRequestType _authRequestType;
|
||||||
// Item1 publicKey, Item2 privateKey
|
// Item1 publicKey, Item2 privateKey
|
||||||
private Tuple<byte[], byte[]> _requestKeyPair;
|
private Tuple<byte[], byte[]> _requestKeyPair;
|
||||||
|
|
||||||
@@ -57,6 +60,7 @@ namespace Bit.App.Pages
|
|||||||
_i18nService = ServiceContainer.Resolve<II18nService>();
|
_i18nService = ServiceContainer.Resolve<II18nService>();
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>();
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>();
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
|
||||||
|
|
||||||
PageTitle = AppResources.LogInWithAnotherDevice;
|
PageTitle = AppResources.LogInWithAnotherDevice;
|
||||||
|
|
||||||
@@ -77,6 +81,70 @@ namespace Bit.App.Pages
|
|||||||
public ICommand CreatePasswordlessLoginCommand { get; }
|
public ICommand CreatePasswordlessLoginCommand { get; }
|
||||||
public ICommand CloseCommand { get; }
|
public ICommand CloseCommand { get; }
|
||||||
|
|
||||||
|
public string Tittle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.LogInInitiated;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.AdminApprovalRequested;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SubTittle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.ANotificationHasBeenSentToYourDevice;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.YourRequestHasBeenSentToYourAdmin;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.YouWillBeNotifiedOnceApproved;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string OtherOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (_authRequestType)
|
||||||
|
{
|
||||||
|
case AuthRequestType.AuthenticateAndUnlock:
|
||||||
|
return AppResources.NeedAnotherOption;
|
||||||
|
case AuthRequestType.AdminApproval:
|
||||||
|
return AppResources.TroubleLoggingIn;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string FingerprintPhrase
|
public string FingerprintPhrase
|
||||||
{
|
{
|
||||||
get => _fingerprintPhrase;
|
get => _fingerprintPhrase;
|
||||||
@@ -89,6 +157,21 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _email, value);
|
set => SetProperty(ref _email, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AuthRequestType AuthRequestType
|
||||||
|
{
|
||||||
|
get => _authRequestType;
|
||||||
|
set => SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
|
||||||
|
{
|
||||||
|
nameof(Tittle),
|
||||||
|
nameof(SubTittle),
|
||||||
|
nameof(Description),
|
||||||
|
nameof(OtherOptions),
|
||||||
|
nameof(ResendNotificationVisible)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
|
||||||
|
|
||||||
public void StartCheckLoginRequestStatus()
|
public void StartCheckLoginRequestStatus()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -154,6 +237,11 @@ namespace Bit.App.Pages
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_syncService.FullSyncAsync(true).FireAndForget();
|
_syncService.FullSyncAsync(true).FireAndForget();
|
||||||
|
if (await _deviceTrustCryptoService.GetUserTrustDeviceChoiceForDecryptionAsync())
|
||||||
|
{
|
||||||
|
await _deviceTrustCryptoService.TrustDeviceAsync();
|
||||||
|
await _deviceTrustCryptoService.SetUserTrustDeviceChoiceForDecryptionAsync(false);
|
||||||
|
}
|
||||||
LogInSuccessAction?.Invoke();
|
LogInSuccessAction?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +256,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
|
||||||
|
|
||||||
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
|
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
|
||||||
if (response != null)
|
if (response != null)
|
||||||
{
|
{
|
||||||
FingerprintPhrase = response.FingerprintPhrase;
|
FingerprintPhrase = response.FingerprintPhrase;
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
RestoreAppOptionsFromCopy();
|
RestoreAppOptionsFromCopy();
|
||||||
await AppHelpers.ClearPreviousPage();
|
await AppHelpers.ClearPreviousPage();
|
||||||
|
|
||||||
|
// Just for testing the screen
|
||||||
|
Application.Current.MainPage = new NavigationPage(new LoginApproveDevicePage(_appOptions));
|
||||||
|
return;
|
||||||
|
|
||||||
if (await _vaultTimeoutService.IsLockedAsync())
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
|
||||||
|
|||||||
@@ -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 newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
|
||||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
|
var encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||||
var hashedPassword = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey);
|
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||||
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 = newProtectedUserKey.EncryptedString,
|
Key = encKey.Item2.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 = newPublicKey,
|
PublicKey = keys.Item1,
|
||||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||||
},
|
},
|
||||||
CaptchaResponse = _captchaToken,
|
CaptchaResponse = _captchaToken,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task Init()
|
public async Task Init()
|
||||||
{
|
{
|
||||||
Organization = await _keyConnectorService.GetManagingOrganizationAsync();
|
Organization = await _keyConnectorService.GetManagingOrganization();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task MigrateAccount()
|
public async Task MigrateAccount()
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
|
||||||
await _keyConnectorService.MigrateUserAsync();
|
await _keyConnectorService.MigrateUser();
|
||||||
await _syncService.FullSyncAsync(true);
|
await _syncService.FullSyncAsync(true);
|
||||||
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|||||||
@@ -165,17 +165,26 @@ 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 newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||||
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
|
||||||
var localMasterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
|
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
|
||||||
|
|
||||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
|
Tuple<SymmetricCryptoKey, EncString> encKey;
|
||||||
|
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
|
if (existingEncKey == null)
|
||||||
|
{
|
||||||
|
encKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||||
|
}
|
||||||
|
|
||||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
|
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
|
||||||
var request = new SetPasswordRequest
|
var request = new SetPasswordRequest
|
||||||
{
|
{
|
||||||
MasterPasswordHash = masterPasswordHash,
|
MasterPasswordHash = masterPasswordHash,
|
||||||
Key = newProtectedUserKey.EncryptedString,
|
Key = encKey.Item2.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),
|
||||||
@@ -184,8 +193,8 @@ namespace Bit.App.Pages
|
|||||||
OrgIdentifier = OrgIdentifier,
|
OrgIdentifier = OrgIdentifier,
|
||||||
Keys = new KeysRequest
|
Keys = new KeysRequest
|
||||||
{
|
{
|
||||||
PublicKey = newPublicKey,
|
PublicKey = keys.Item1,
|
||||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
EncryptedPrivateKey = keys.Item2.EncryptedString
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,20 +204,19 @@ 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.SetUserKeyAsync(newUserKey);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localMasterPasswordHash);
|
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
|
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.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 Key and encrypt with Org Public Key
|
// Grab user's Encryption Key and encrypt with Org Public Key
|
||||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
var userEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
|
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
|
||||||
// Request
|
// Request
|
||||||
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 master key and hash new password
|
// Create new key and hash new password
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
|
||||||
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
|
|
||||||
// Encrypt user key with new master key
|
// Create new encKey for the User
|
||||||
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
|
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
|
||||||
|
|
||||||
// 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, newProtectedUserKey.EncryptedString);
|
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
|
||||||
break;
|
break;
|
||||||
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
|
||||||
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
|
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.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.HashMasterKeyAsync(CurrentMasterPassword, null);
|
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null);
|
||||||
|
|
||||||
var request = new PasswordRequest
|
var request = new PasswordRequest
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.GetUsesKeyConnectorAsync();
|
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
|
|
||||||
if (UseOTPVerification)
|
if (UseOTPVerification)
|
||||||
{
|
{
|
||||||
@@ -165,7 +165,7 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var verificationType = await _keyConnectorService.GetUsesKeyConnectorAsync()
|
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||||
? VerificationType.OTP
|
? VerificationType.OTP
|
||||||
: VerificationType.MasterPassword;
|
: VerificationType.MasterPassword;
|
||||||
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
|
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
|
||||||
|
|||||||
@@ -153,14 +153,22 @@
|
|||||||
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.GestureRecognizers>
|
<StackLayout StyleClass="box-row, box-row-input">
|
||||||
<TapGestureRecognizer Command="{Binding GoToBlockAutofillUrisCommand}" />
|
<Label
|
||||||
</StackLayout.GestureRecognizers>
|
Text="{u:I18n AutofillBlockedUris}"
|
||||||
|
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 BlockAutoFill}"
|
Text="{u:I18n AutofillBlockedUrisDescription}"
|
||||||
StyleClass="box-label-regular" />
|
|
||||||
<Label
|
|
||||||
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
|
|
||||||
StyleClass="box-footer-label" />
|
StyleClass="box-footer-label" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.App.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;
|
||||||
@@ -42,6 +44,17 @@ 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())
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
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
|
||||||
@@ -20,6 +19,7 @@ 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,10 +84,6 @@ 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; }
|
||||||
@@ -196,18 +192,25 @@ 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();
|
||||||
@@ -285,6 +288,41 @@ 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)
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ namespace Bit.App.Pages
|
|||||||
t.Value != null).ToList();
|
t.Value != null).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var pinSet = await _vaultTimeoutService.GetPinLockTypeAsync();
|
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
_pin = pinSet != PinLockType.Disabled;
|
_pin = pinSet.Item1 || pinSet.Item2;
|
||||||
_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.GetUsesKeyConnectorAsync();
|
!await _keyConnectorService.GetUsesKeyConnector();
|
||||||
_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,7 +323,6 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (oldTimeout != newTimeout)
|
if (oldTimeout != newTimeout)
|
||||||
{
|
{
|
||||||
await _cryptoService.RefreshKeysAsync();
|
|
||||||
await Device.InvokeOnMainThreadAsync(BuildList);
|
await Device.InvokeOnMainThreadAsync(BuildList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +428,7 @@ namespace Bit.App.Pages
|
|||||||
if (!string.IsNullOrWhiteSpace(pin))
|
if (!string.IsNullOrWhiteSpace(pin))
|
||||||
{
|
{
|
||||||
var masterPassOnRestart = false;
|
var masterPassOnRestart = false;
|
||||||
if (!await _keyConnectorService.GetUsesKeyConnectorAsync())
|
if (!await _keyConnectorService.GetUsesKeyConnector())
|
||||||
{
|
{
|
||||||
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
|
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
|
||||||
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
|
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
|
||||||
@@ -438,20 +437,19 @@ 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.MakePinKeyAsync(pin, email, kdfConfig);
|
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
|
||||||
var userKey = await _cryptoService.GetUserKeyAsync();
|
var key = await _cryptoService.GetKeyAsync();
|
||||||
var protectedPinKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
|
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
|
||||||
|
|
||||||
var encPin = await _cryptoService.EncryptAsync(pin);
|
|
||||||
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
|
||||||
|
|
||||||
if (masterPassOnRestart)
|
if (masterPassOnRestart)
|
||||||
{
|
{
|
||||||
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(protectedPinKey);
|
var encPin = await _cryptoService.EncryptAsync(pin);
|
||||||
|
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
|
||||||
|
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _stateService.SetPinKeyEncryptedUserKeyAsync(protectedPinKey);
|
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -461,6 +459,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
if (!_pin)
|
if (!_pin)
|
||||||
{
|
{
|
||||||
|
await _cryptoService.ClearPinProtectedKeyAsync();
|
||||||
await _vaultTimeoutService.ClearAsync();
|
await _vaultTimeoutService.ClearAsync();
|
||||||
}
|
}
|
||||||
BuildList();
|
BuildList();
|
||||||
@@ -492,7 +491,7 @@ namespace Bit.App.Pages
|
|||||||
await _stateService.SetBiometricUnlockAsync(null);
|
await _stateService.SetBiometricUnlockAsync(null);
|
||||||
}
|
}
|
||||||
await _stateService.SetBiometricLockedAsync(false);
|
await _stateService.SetBiometricLockedAsync(false);
|
||||||
await _cryptoService.RefreshKeysAsync();
|
await _cryptoService.ToggleKeyAsync();
|
||||||
BuildList();
|
BuildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
await UpdateVaultButtonTitleAsync();
|
await UpdateVaultButtonTitleAsync();
|
||||||
if (await _keyConnectorService.UserNeedsMigrationAsync())
|
if (await _keyConnectorService.UserNeedsMigration())
|
||||||
{
|
{
|
||||||
_messagingService.Send("convertAccountToKeyConnector");
|
_messagingService.Send("convertAccountToKeyConnector");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.HasUserKeyAsync();
|
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
|
||||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||||
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
|
||||||
if (!_canAccessAttachments)
|
if (!_canAccessAttachments)
|
||||||
|
|||||||
@@ -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.GetUsesKeyConnectorAsync();
|
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
|
|||||||
@@ -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="ShowValueButton" />
|
AutomationId="ViewValueButton" />
|
||||||
<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}}"
|
||||||
|
|||||||
@@ -208,11 +208,6 @@ 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();
|
||||||
|
|||||||
@@ -330,16 +330,13 @@ namespace Bit.App.Pages
|
|||||||
items.AddRange(itemGroup);
|
items.AddRange(itemGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
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
|
||||||
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
|
GroupedItems.Clear();
|
||||||
// because of update to XF v5.0.0.2401
|
}
|
||||||
GroupedItems.Clear();
|
GroupedItems.ReplaceRange(items);
|
||||||
}
|
|
||||||
GroupedItems.ReplaceRange(items);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -359,24 +356,21 @@ namespace Bit.App.Pages
|
|||||||
items.AddRange(itemGroup);
|
items.AddRange(itemGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
if (groupedItems.Any())
|
||||||
{
|
{
|
||||||
if (groupedItems.Any())
|
if (Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
|
||||||
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
|
||||||
@@ -384,12 +378,9 @@ namespace Bit.App.Pages
|
|||||||
_doingLoad = false;
|
_doingLoad = false;
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
Loading = false;
|
Loading = false;
|
||||||
Device.BeginInvokeOnMainThread(() =>
|
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
|
||||||
{
|
ShowList = !ShowNoData;
|
||||||
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
|
DisableRefreshing();
|
||||||
ShowList = !ShowNoData;
|
|
||||||
DisableRefreshing();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
234
src/App/Resources/AppResources.Designer.cs
generated
234
src/App/Resources/AppResources.Designer.cs
generated
@@ -418,6 +418,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Admin approval requested.
|
||||||
|
/// </summary>
|
||||||
|
public static string AdminApprovalRequested {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AdminApprovalRequested", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to All.
|
/// Looks up a localized string similar to All.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -562,6 +571,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Approve with master password.
|
||||||
|
/// </summary>
|
||||||
|
public static string ApproveWithMasterPassword {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ApproveWithMasterPassword", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Approve with my other device.
|
||||||
|
/// </summary>
|
||||||
|
public static string ApproveWithMyOtherDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ApproveWithMyOtherDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to April.
|
/// Looks up a localized string similar to April.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -796,6 +823,15 @@ 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: "https://twitter.com, androidapp://com.twitter.android"..
|
||||||
|
/// </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>
|
||||||
@@ -931,15 +967,6 @@ 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,15 +1255,6 @@ 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>
|
||||||
@@ -1273,15 +1291,6 @@ 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 "{0}"..
|
/// Looks up a localized string similar to Cannot open the app "{0}"..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2164,15 +2173,6 @@ 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>
|
||||||
@@ -2326,15 +2326,6 @@ 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>
|
||||||
@@ -2983,24 +2974,6 @@ 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>
|
||||||
@@ -3343,15 +3316,6 @@ 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>
|
||||||
@@ -3370,15 +3334,6 @@ 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>
|
||||||
@@ -3658,6 +3613,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logged in!.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoggedIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoggedIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
/// Looks up a localized string similar to Logged in as {0} on {1}..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3676,6 +3640,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Logging in as {0}.
|
||||||
|
/// </summary>
|
||||||
|
public static string LoggingInAsX {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
/// Looks up a localized string similar to Logging in as {0} on {1}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -4290,15 +4263,6 @@ 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>
|
||||||
@@ -5273,6 +5237,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Remember this device.
|
||||||
|
/// </summary>
|
||||||
|
public static string RememberThisDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RememberThisDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Remove.
|
/// Looks up a localized string similar to Remove.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -5345,6 +5318,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Request admin approval.
|
||||||
|
/// </summary>
|
||||||
|
public static string RequestAdminApproval {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RequestAdminApproval", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Request one-time password.
|
/// Looks up a localized string similar to Request one-time password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6200,15 +6182,6 @@ 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 "{0}".
|
/// Looks up a localized string similar to There are no items in your vault that match "{0}".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6227,15 +6200,6 @@ 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>
|
||||||
@@ -6353,6 +6317,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Trouble logging in?.
|
||||||
|
/// </summary>
|
||||||
|
public static string TroubleLoggingIn {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TroubleLoggingIn", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Try again.
|
/// Looks up a localized string similar to Try again.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6362,6 +6335,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Turn off using a public device.
|
||||||
|
/// </summary>
|
||||||
|
public static string TurnOffUsingPublicDevice {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("TurnOffUsingPublicDevice", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to 20 seconds.
|
/// Looks up a localized string similar to 20 seconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -6686,15 +6668,6 @@ 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>
|
||||||
@@ -6704,15 +6677,6 @@ 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>
|
||||||
@@ -7262,6 +7226,24 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Your request has been sent to your admin..
|
||||||
|
/// </summary>
|
||||||
|
public static string YourRequestHasBeenSentToYourAdmin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YourRequestHasBeenSentToYourAdmin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to You will be notified once approved. .
|
||||||
|
/// </summary>
|
||||||
|
public static string YouWillBeNotifiedOnceApproved {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("YouWillBeNotifiedOnceApproved", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device's USB port, then touch its button..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1585,6 +1585,9 @@ 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>
|
||||||
@@ -2628,6 +2631,24 @@ Do you want to switch to this account?</value>
|
|||||||
<data name="CurrentMasterPassword" xml:space="preserve">
|
<data name="CurrentMasterPassword" xml:space="preserve">
|
||||||
<value>Current master password</value>
|
<value>Current master password</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LoggedIn" xml:space="preserve">
|
||||||
|
<value>Logged in!</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
|
||||||
|
<value>Approve with my other device</value>
|
||||||
|
</data>
|
||||||
|
<data name="RequestAdminApproval" xml:space="preserve">
|
||||||
|
<value>Request admin approval</value>
|
||||||
|
</data>
|
||||||
|
<data name="ApproveWithMasterPassword" xml:space="preserve">
|
||||||
|
<value>Approve with master password</value>
|
||||||
|
</data>
|
||||||
|
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
|
||||||
|
<value>Turn off using a public device</value>
|
||||||
|
</data>
|
||||||
|
<data name="RememberThisDevice" xml:space="preserve">
|
||||||
|
<value>Remember this device</value>
|
||||||
|
</data>
|
||||||
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
|
||||||
<value>Master password re-prompt help</value>
|
<value>Master password re-prompt help</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -2640,50 +2661,19 @@ 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">
|
<data name="AdminApprovalRequested" xml:space="preserve">
|
||||||
<value>Block auto-fill</value>
|
<value>Admin approval requested</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
|
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
|
||||||
<value>Auto-fill will not be offered for these URIs.</value>
|
<value>Your request has been sent to your admin.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NewBlockedURI" xml:space="preserve">
|
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
|
||||||
<value>New blocked URI</value>
|
<value>You will be notified once approved. </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="URISaved" xml:space="preserve">
|
<data name="TroubleLoggingIn" xml:space="preserve">
|
||||||
<value>URI saved</value>
|
<value>Trouble logging in?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
|
<data name="LoggingInAsX" xml:space="preserve">
|
||||||
<value>Invalid format. Use https://, http://, or androidapp://</value>
|
<value>Logging in as {0}</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>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.GetUsesKeyConnectorAsync();
|
return !await keyConnectorService.GetUsesKeyConnector();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,22 +428,6 @@
|
|||||||
<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">
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ 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;
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -107,7 +107,7 @@ namespace Bit.App.Utilities
|
|||||||
|
|
||||||
public async Task ValidateAndExecuteAsync()
|
public async Task ValidateAndExecuteAsync()
|
||||||
{
|
{
|
||||||
var verificationType = await _keyConnectorService.GetUsesKeyConnectorAsync()
|
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
|
||||||
? 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.HashMasterKeyAsync(password, null);
|
parameters.Secret = await _cryptoService.HashPasswordAsync(password, null);
|
||||||
parameters.VerificationType = VerificationType.MasterPassword;
|
parameters.VerificationType = VerificationType.MasterPassword;
|
||||||
await ExecuteAsync(parameters);
|
await ExecuteAsync(parameters);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Net;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
using Bit.Core.Models.Response;
|
using Bit.Core.Models.Response;
|
||||||
@@ -70,7 +71,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> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
|
Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(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();
|
||||||
@@ -92,7 +93,10 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||||
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
|
||||||
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier);
|
||||||
|
Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier);
|
||||||
|
Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest deviceRequest);
|
||||||
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
Task<OrganizationDomainSsoDetailsResponse> GetOrgDomainSsoDetailsAsync(string email);
|
||||||
|
Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
|
||||||
Task<ConfigResponse> GetConfigsAsync();
|
Task<ConfigResponse> GetConfigsAsync();
|
||||||
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
Task<string> GetFastmailAccountIdAsync(string apiKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email);
|
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
|
||||||
|
|
||||||
void LogOut(Action callback);
|
void LogOut(Action callback);
|
||||||
void Init();
|
void Init();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,56 +9,49 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface ICryptoService
|
public interface ICryptoService
|
||||||
{
|
{
|
||||||
void ClearCache();
|
Task ClearEncKeyAsync(bool memoryOnly = false, string userId = null);
|
||||||
Task RefreshKeysAsync();
|
Task ClearKeyAsync(string userId = null);
|
||||||
Task SetUserKeyAsync(UserKey userKey, string userId = null);
|
Task ClearKeyHashAsync(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 ClearKeyPairAsync(bool memoryOnly = false, string userId = null);
|
||||||
Task<PinKey> MakePinKeyAsync(string pin, string salt, KdfConfig config);
|
Task ClearKeysAsync(string userId = null);
|
||||||
Task ClearPinKeysAsync(string userId = null);
|
Task ClearOrgKeysAsync(bool memoryOnly = false, string userId = null);
|
||||||
Task<UserKey> DecryptUserKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedUserKey = null);
|
Task ClearPinProtectedKeyAsync(string userId = null);
|
||||||
Task<MasterKey> DecryptMasterKeyWithPinAsync(string pin, string salt, KdfConfig kdfConfig, EncString pinProtectedMasterKey = null);
|
void ClearCache();
|
||||||
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<UserKey> DecryptAndMigrateOldPinKeyAsync(bool masterPasswordOnRestart, string pin, string email, KdfConfig kdfConfig, EncString oldPinKey);
|
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
13
src/Core/Abstractions/IDeviceTrustCryptoService.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IDeviceTrustCryptoService
|
||||||
|
{
|
||||||
|
Task<SymmetricCryptoKey> GetDeviceKeyAsync();
|
||||||
|
Task<DeviceResponse> TrustDeviceAsync();
|
||||||
|
Task<bool> GetUserTrustDeviceChoiceForDecryptionAsync();
|
||||||
|
Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IKeyConnectorService
|
public interface IKeyConnectorService
|
||||||
{
|
{
|
||||||
Task SetUsesKeyConnectorAsync(bool usesKeyConnector);
|
Task SetUsesKeyConnector(bool usesKeyConnector);
|
||||||
Task<bool> GetUsesKeyConnectorAsync();
|
Task<bool> GetUsesKeyConnector();
|
||||||
Task<bool> UserNeedsMigrationAsync();
|
Task<bool> UserNeedsMigration();
|
||||||
Task MigrateUserAsync();
|
Task MigrateUser();
|
||||||
Task GetAndSetMasterKeyAsync(string url);
|
Task GetAndSetKey(string url);
|
||||||
Task<Organization> GetManagingOrganizationAsync();
|
Task<Organization> GetManagingOrganization();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ 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);
|
||||||
@@ -35,8 +27,6 @@ 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);
|
||||||
@@ -46,20 +36,28 @@ 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<string> GetProtectedPinAsync(string userId = null);
|
|
||||||
Task SetPersonalPremiumAsync(bool value, string userId = null);
|
Task SetPersonalPremiumAsync(bool value, string userId = null);
|
||||||
Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null);
|
Task<string> GetProtectedPinAsync(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);
|
||||||
Task SetPrivateKeyEncryptedAsync(string value, string userId = null);
|
Task SetPrivateKeyEncryptedAsync(string value, string userId = null);
|
||||||
|
Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null);
|
||||||
|
Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null);
|
||||||
Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
|
Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null);
|
||||||
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
|
Task SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
|
||||||
Task<bool?> GetAutofillTileAddedAsync();
|
Task<bool?> GetAutofillTileAddedAsync();
|
||||||
@@ -176,28 +174,12 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<string> GetAvatarColorAsync(string userId = null);
|
Task<string> GetAvatarColorAsync(string userId = null);
|
||||||
Task<string> GetPreLoginEmailAsync();
|
Task<string> GetPreLoginEmailAsync();
|
||||||
Task SetPreLoginEmailAsync(string value);
|
Task SetPreLoginEmailAsync(string value);
|
||||||
|
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
|
||||||
string GetLocale();
|
string GetLocale();
|
||||||
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<bool> GetUserTrustDeviceChoiceForDecryptionAsync();
|
||||||
Task<string> GetPinProtectedAsync(string userId = null);
|
Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value);
|
||||||
[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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -17,7 +16,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<PinLockType> GetPinLockTypeAsync(string userId = null);
|
Task<Tuple<bool, bool>> IsPinLockSetAsync(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);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace Bit.Core
|
||||||
|
|
||||||
namespace Bit.Core
|
|
||||||
{
|
{
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
@@ -55,6 +53,7 @@ namespace Bit.Core
|
|||||||
public const string AppLocaleKey = "appLocale";
|
public const string AppLocaleKey = "appLocale";
|
||||||
public const string ClearSensitiveFields = "clearSensitiveFields";
|
public const string ClearSensitiveFields = "clearSensitiveFields";
|
||||||
public const string ForceUpdatePassword = "forceUpdatePassword";
|
public const string ForceUpdatePassword = "forceUpdatePassword";
|
||||||
|
public const string RememberDeviceTde = "rememberDeviceTde";
|
||||||
public const int SelectFileRequestCode = 42;
|
public const int SelectFileRequestCode = 42;
|
||||||
public const int SelectFilePermissionRequestCode = 43;
|
public const int SelectFilePermissionRequestCode = 43;
|
||||||
public const int SaveFileRequestCode = 44;
|
public const int SaveFileRequestCode = 44;
|
||||||
@@ -81,9 +80,6 @@ namespace Bit.Core
|
|||||||
|
|
||||||
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}";
|
||||||
@@ -92,10 +88,13 @@ namespace Bit.Core
|
|||||||
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 DeviceKeyKey(string userId) => $"deviceKey_{userId}";
|
||||||
public static string KeyHashKey(string userId) => $"keyHash_{userId}";
|
public static string KeyHashKey(string userId) => $"keyHash_{userId}";
|
||||||
public static string PinKeyEncryptedUserKeyKey(string userId) => $"pinKeyEncryptedUserKey_{userId}";
|
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{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}";
|
||||||
@@ -124,11 +123,5 @@ namespace Bit.Core
|
|||||||
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}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/Core/Enums/AuthRequestType.cs
Normal file
11
src/Core/Enums/AuthRequestType.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum AuthRequestType : byte
|
||||||
|
{
|
||||||
|
AuthenticateAndUnlock = 0,
|
||||||
|
Unlock = 1,
|
||||||
|
AdminApproval = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
namespace Bit.Core.Enums
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Bit.Core.Enums
|
||||||
{
|
{
|
||||||
public enum DeviceType : byte
|
public enum DeviceType : byte
|
||||||
{
|
{
|
||||||
@@ -24,4 +27,24 @@
|
|||||||
VivaldiExtension = 19,
|
VivaldiExtension = 19,
|
||||||
SafariExtension = 20
|
SafariExtension = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DeviceTypeExtensions
|
||||||
|
{
|
||||||
|
public static List<DeviceType> GetMobileTypes() => new List<DeviceType>
|
||||||
|
{
|
||||||
|
DeviceType.Android,
|
||||||
|
DeviceType.AndroidAmazon,
|
||||||
|
DeviceType.iOS
|
||||||
|
};
|
||||||
|
|
||||||
|
public static List<DeviceType> GetDesktopTypes() => new List<DeviceType>
|
||||||
|
{
|
||||||
|
DeviceType.WindowsDesktop,
|
||||||
|
DeviceType.MacOsDesktop,
|
||||||
|
DeviceType.LinuxDesktop,
|
||||||
|
DeviceType.UWP,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static List<DeviceType> GetDesktopAndMobileTypes() => GetMobileTypes().Concat(GetDesktopTypes()).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
HasPremiumPersonally = copy.HasPremiumPersonally;
|
HasPremiumPersonally = copy.HasPremiumPersonally;
|
||||||
AvatarColor = copy.AvatarColor;
|
AvatarColor = copy.AvatarColor;
|
||||||
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
ForcePasswordResetReason = copy.ForcePasswordResetReason;
|
||||||
|
UserDecryptionOptions = copy.UserDecryptionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string UserId;
|
public string UserId;
|
||||||
@@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
public bool? EmailVerified;
|
public bool? EmailVerified;
|
||||||
public bool? HasPremiumPersonally;
|
public bool? HasPremiumPersonally;
|
||||||
public ForcePasswordResetReason? ForcePasswordResetReason;
|
public ForcePasswordResetReason? ForcePasswordResetReason;
|
||||||
|
public AccountDecryptionOptions UserDecryptionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AccountTokens
|
public class AccountTokens
|
||||||
@@ -117,14 +119,9 @@ namespace Bit.Core.Models.Domain
|
|||||||
|
|
||||||
public class AccountVolatileData
|
public class AccountVolatileData
|
||||||
{
|
{
|
||||||
public UserKey UserKey;
|
|
||||||
public MasterKey MasterKey;
|
|
||||||
public EncString PinKeyEncryptedUserKeyEphemeral;
|
|
||||||
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;
|
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;
|
public EncString PinProtectedKey;
|
||||||
|
public bool? BiometricLocked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
21
src/Core/Models/Domain/AccountDecryptionOptions.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.Core.Models.Domain
|
||||||
|
{
|
||||||
|
public class AccountDecryptionOptions
|
||||||
|
{
|
||||||
|
public bool HasMasterPassword { get; set; }
|
||||||
|
public TrustedDeviceOption TrustedDeviceOption { get; set; }
|
||||||
|
public KeyConnectorOption KeyConnectorOption { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrustedDeviceOption
|
||||||
|
{
|
||||||
|
public bool HasAdminApproval { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KeyConnectorOption
|
||||||
|
{
|
||||||
|
public bool KeyConnectorUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -74,32 +74,4 @@ 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)
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Request
|
namespace Bit.Core.Models.Request
|
||||||
{
|
{
|
||||||
public class PasswordlessCreateLoginRequest
|
public class PasswordlessCreateLoginRequest
|
||||||
@@ -25,10 +27,4 @@ namespace Bit.Core.Models.Request
|
|||||||
|
|
||||||
public string FingerprintPhrase { get; set; }
|
public string FingerprintPhrase { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AuthRequestType : byte
|
|
||||||
{
|
|
||||||
AuthenticateAndUnlock = 0,
|
|
||||||
Unlock = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/Core/Models/Request/TrustedDeviceKeysRequest.cs
Normal file
10
src/Core/Models/Request/TrustedDeviceKeysRequest.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
namespace Bit.Core.Models.Request
|
||||||
|
{
|
||||||
|
public class TrustedDeviceKeysRequest
|
||||||
|
{
|
||||||
|
public string EncryptedUserKey { get; set; }
|
||||||
|
public string EncryptedPublicKey { get; set; }
|
||||||
|
public string EncryptedPrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Core/Models/Response/DeviceResponse.cs
Normal file
13
src/Core/Models/Response/DeviceResponse.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
public class DeviceResponse
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public int Name { get; set; }
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public DeviceType Type { get; set; }
|
||||||
|
public string CreationDate { get; set; }
|
||||||
|
public string EncryptedUserKey { get; set; }
|
||||||
|
public string EncryptedPublicKey { get; set; }
|
||||||
|
public string EncryptedPrivateKey { get; set; }
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Response
|
|||||||
public bool ForcePasswordReset { get; set; }
|
public bool ForcePasswordReset { get; set; }
|
||||||
public string KeyConnectorUrl { get; set; }
|
public string KeyConnectorUrl { get; set; }
|
||||||
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
|
||||||
|
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,12 +397,38 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
#region Device APIs
|
#region Device APIs
|
||||||
|
|
||||||
|
|
||||||
|
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
|
||||||
|
{
|
||||||
|
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
|
||||||
|
{
|
||||||
|
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
|
||||||
|
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
|
public Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request)
|
||||||
{
|
{
|
||||||
return SendAsync<DeviceTokenRequest, object>(
|
return SendAsync<DeviceTokenRequest, object>(
|
||||||
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false);
|
HttpMethod.Put, $"/devices/identifier/{identifier}/token", request, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes)
|
||||||
|
{
|
||||||
|
return SendAsync<DeviceType[], bool>(
|
||||||
|
HttpMethod.Post, "/devices/exist-by-types", deviceTypes, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DeviceResponse> GetDeviceByIdentifierAsync(string deviceIdentifier)
|
||||||
|
{
|
||||||
|
return SendAsync<object, DeviceResponse>(HttpMethod.Get, $"/devices/identifier/{deviceIdentifier}", null, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DeviceResponse> UpdateTrustedDeviceKeysAsync(string deviceIdentifier, TrustedDeviceKeysRequest trustedDeviceKeysRequest)
|
||||||
|
{
|
||||||
|
return SendAsync<TrustedDeviceKeysRequest, DeviceResponse>(HttpMethod.Put, $"/devices/{deviceIdentifier}/keys", trustedDeviceKeysRequest, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Event APIs
|
#region Event APIs
|
||||||
@@ -485,7 +511,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
#region Key Connector
|
#region Key Connector
|
||||||
|
|
||||||
public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl)
|
public async Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl)
|
||||||
{
|
{
|
||||||
using (var requestMessage = new HttpRequestMessage())
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
{
|
{
|
||||||
@@ -576,15 +602,6 @@ namespace Bit.Core.Services
|
|||||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
|
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Put, $"/auth-requests/{id}", request, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> GetKnownDeviceAsync(string email, string deviceIdentifier)
|
|
||||||
{
|
|
||||||
return SendAsync<object, bool>(HttpMethod.Get, "/devices/knowndevice", null, false, true, (message) =>
|
|
||||||
{
|
|
||||||
message.Headers.Add("X-Device-Identifier", deviceIdentifier);
|
|
||||||
message.Headers.Add("X-Request-Email", CoreHelpers.Base64UrlEncode(Encoding.UTF8.GetBytes(email)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Configs
|
#region Configs
|
||||||
|
|||||||
@@ -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 MasterKey _masterKey;
|
private SymmetricCryptoKey _key;
|
||||||
|
|
||||||
private string _authedUserId;
|
private string _authedUserId;
|
||||||
private MasterPasswordPolicyOptions _masterPasswordPolicy;
|
private MasterPasswordPolicyOptions _masterPasswordPolicy;
|
||||||
@@ -46,6 +46,8 @@ 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)
|
||||||
@@ -59,6 +61,7 @@ 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;
|
||||||
@@ -142,8 +145,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.HashMasterKeyAsync(masterPassword, key);
|
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||||
var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
var localHashedPassword = await _cryptoService.HashPasswordAsync(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))
|
||||||
@@ -196,7 +199,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 MasterKey(decKey), null, null,
|
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new SymmetricCryptoKey(decKey), null, null,
|
||||||
null, null, authRequestId: authRequestId);
|
null, null, authRequestId: authRequestId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +216,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
CaptchaToken = captchaToken;
|
CaptchaToken = captchaToken;
|
||||||
}
|
}
|
||||||
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _masterKey,
|
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
|
||||||
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()
|
||||||
@@ -233,8 +236,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.HashMasterKeyAsync(masterPassword, key);
|
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
|
||||||
var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
|
var localHashedPassword = await _cryptoService.HashPasswordAsync(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);
|
||||||
}
|
}
|
||||||
@@ -334,7 +337,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
private async Task<MasterKey> MakePreloginKeyAsync(string masterPassword, string email)
|
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
|
||||||
{
|
{
|
||||||
email = email.Trim().ToLower();
|
email = email.Trim().ToLower();
|
||||||
KdfConfig kdfConfig = KdfConfig.Default;
|
KdfConfig kdfConfig = KdfConfig.Default;
|
||||||
@@ -353,11 +356,11 @@ namespace Bit.Core.Services
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await _cryptoService.MakeMasterKeyAsync(masterPassword, email, kdfConfig);
|
return await _cryptoService.MakeKeyAsync(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, MasterKey masterKey,
|
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -423,7 +426,7 @@ namespace Bit.Core.Services
|
|||||||
Code = code;
|
Code = code;
|
||||||
CodeVerifier = codeVerifier;
|
CodeVerifier = codeVerifier;
|
||||||
SsoRedirectUrl = redirectUrl;
|
SsoRedirectUrl = redirectUrl;
|
||||||
_masterKey = _setCryptoKeys ? masterKey : null;
|
_key = _setCryptoKeys ? key : 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;
|
||||||
@@ -456,6 +459,7 @@ namespace Bit.Core.Services
|
|||||||
ForcePasswordResetReason = result.ForcePasswordReset
|
ForcePasswordResetReason = result.ForcePasswordReset
|
||||||
? ForcePasswordResetReason.AdminForcePasswordReset
|
? ForcePasswordResetReason.AdminForcePasswordReset
|
||||||
: (ForcePasswordResetReason?)null,
|
: (ForcePasswordResetReason?)null,
|
||||||
|
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
|
||||||
},
|
},
|
||||||
new Account.AccountTokens()
|
new Account.AccountTokens()
|
||||||
{
|
{
|
||||||
@@ -467,27 +471,24 @@ 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.SetMasterKeyHashAsync(localHashedPassword);
|
await _cryptoService.SetKeyHashAsync(localHashedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code == null || tokenResponse.Key != null)
|
if (code == null || tokenResponse.Key != null)
|
||||||
{
|
{
|
||||||
if (tokenResponse.KeyConnectorUrl != null)
|
if (tokenResponse.KeyConnectorUrl != null)
|
||||||
{
|
{
|
||||||
await _keyConnectorService.GetAndSetMasterKeyAsync(tokenResponse.KeyConnectorUrl);
|
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
|
await _cryptoService.SetEncKeyAsync(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,26 +506,19 @@ namespace Bit.Core.Services
|
|||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cryptoService.SetUserPrivateKeyAsync(tokenResponse.PrivateKey);
|
await _cryptoService.SetEncPrivateKeyAsync(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 newMasterKey = await _cryptoService.MakeMasterKeyAsync(
|
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
|
||||||
Convert.ToBase64String(password),
|
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64);
|
||||||
_tokenService.GetEmail(),
|
await _cryptoService.SetKeyAsync(k);
|
||||||
tokenResponse.KdfConfig);
|
|
||||||
|
|
||||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
|
var encKey = await _cryptoService.MakeEncKeyAsync(k);
|
||||||
|
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
|
||||||
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey);
|
var keyPair = await _cryptoService.MakeKeyPairAsync();
|
||||||
|
|
||||||
await _cryptoService.SetUserKeyAsync(newUserKey);
|
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
|
|
||||||
await _cryptoService.SetMasterKeyAsync(newMasterKey);
|
|
||||||
|
|
||||||
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -537,11 +531,11 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
var keys = new KeysRequest
|
var keys = new KeysRequest
|
||||||
{
|
{
|
||||||
PublicKey = newPublicKey,
|
PublicKey = keyPair.Item1,
|
||||||
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
|
EncryptedPrivateKey = keyPair.Item2.EncryptedString
|
||||||
};
|
};
|
||||||
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
var setPasswordRequest = new SetKeyConnectorKeyRequest(
|
||||||
newProtectedPrivateKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
|
encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId
|
||||||
);
|
);
|
||||||
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
|
||||||
}
|
}
|
||||||
@@ -556,7 +550,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
private void ClearState()
|
private void ClearState()
|
||||||
{
|
{
|
||||||
_masterKey = null;
|
_key = null;
|
||||||
Email = null;
|
Email = null;
|
||||||
CaptchaToken = null;
|
CaptchaToken = null;
|
||||||
MasterPasswordHash = null;
|
MasterPasswordHash = null;
|
||||||
@@ -597,7 +591,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.GetMasterKeyAsync();
|
var masterKey = await _cryptoService.GetKeyAsync();
|
||||||
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();
|
||||||
@@ -605,7 +599,7 @@ namespace Bit.Core.Services
|
|||||||
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email)
|
public async Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType)
|
||||||
{
|
{
|
||||||
var deviceId = await _appIdService.GetAppIdAsync();
|
var deviceId = await _appIdService.GetAppIdAsync();
|
||||||
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||||
@@ -613,7 +607,7 @@ namespace Bit.Core.Services
|
|||||||
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
|
||||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||||
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
|
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
|
||||||
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase);
|
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, authRequestType, fingerprintPhrase);
|
||||||
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
|
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
|
||||||
|
|
||||||
if (response != null)
|
if (response != null)
|
||||||
|
|||||||
@@ -249,7 +249,8 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!await _cryptoService.HasUserKeyAsync())
|
var hashKey = await _cryptoService.HasKeyAsync();
|
||||||
|
if (!hashKey)
|
||||||
{
|
{
|
||||||
throw new Exception("No key.");
|
throw new Exception("No key.");
|
||||||
}
|
}
|
||||||
@@ -556,9 +557,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 (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(cipher.OrganizationId);
|
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||||
|
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
|
||||||
var encFileName = await _cryptoService.EncryptAsync(filename, encKey);
|
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
|
||||||
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
||||||
|
|
||||||
CipherResponse response;
|
CipherResponse response;
|
||||||
@@ -566,7 +567,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var request = new AttachmentRequest
|
var request = new AttachmentRequest
|
||||||
{
|
{
|
||||||
Key = protectedAttachmentKey.EncryptedString,
|
Key = orgEncAttachmentKey.EncryptedString,
|
||||||
FileName = encFileName.EncryptedString,
|
FileName = encFileName.EncryptedString,
|
||||||
FileSize = encFileData.Buffer.Length,
|
FileSize = encFileData.Buffer.Length,
|
||||||
};
|
};
|
||||||
@@ -577,7 +578,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, protectedAttachmentKey);
|
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
var userId = await _stateService.GetActiveUserIdAsync();
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
@@ -795,14 +796,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -814,16 +807,14 @@ 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 (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(organizationId);
|
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, key);
|
||||||
|
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
|
||||||
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, encKey);
|
var encData = await _cryptoService.EncryptToBytesAsync(decBytes, dataEncKey.Item1);
|
||||||
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(protectedAttachmentKey.EncryptedString), "key");
|
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
||||||
fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
|
fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString);
|
||||||
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
|
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
return _decryptedCollectionCache;
|
return _decryptedCollectionCache;
|
||||||
}
|
}
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
throw new Exception("No key.");
|
throw new Exception("No key.");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
91
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
91
src/Core/Services/DeviceTrustCryptoService.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class DeviceTrustCryptoService : IDeviceTrustCryptoService
|
||||||
|
{
|
||||||
|
private readonly IApiService _apiService;
|
||||||
|
private readonly IAppIdService _appIdService;
|
||||||
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
|
private readonly ICryptoService _cryptoService;
|
||||||
|
private readonly IStateService _stateService;
|
||||||
|
|
||||||
|
private const int DEVICE_KEY_SIZE = 64;
|
||||||
|
|
||||||
|
public DeviceTrustCryptoService(
|
||||||
|
IApiService apiService,
|
||||||
|
IAppIdService appIdService,
|
||||||
|
ICryptoFunctionService cryptoFunctionService,
|
||||||
|
ICryptoService cryptoService,
|
||||||
|
IStateService stateService)
|
||||||
|
{
|
||||||
|
_apiService = apiService;
|
||||||
|
_appIdService = appIdService;
|
||||||
|
_cryptoFunctionService = cryptoFunctionService;
|
||||||
|
_cryptoService = cryptoService;
|
||||||
|
_stateService = stateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync()
|
||||||
|
{
|
||||||
|
return await _stateService.GetDeviceKeyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetDeviceKeyAsync(SymmetricCryptoKey deviceKey)
|
||||||
|
{
|
||||||
|
await _stateService.SetDeviceKeyAsync(deviceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DeviceResponse> TrustDeviceAsync()
|
||||||
|
{
|
||||||
|
// Attempt to get user key
|
||||||
|
var userKey = await _cryptoService.GetEncKeyAsync();
|
||||||
|
if (userKey == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Generate deviceKey
|
||||||
|
var deviceKey = await MakeDeviceKeyAsync();
|
||||||
|
|
||||||
|
// Generate asymmetric RSA key pair: devicePrivateKey, devicePublicKey
|
||||||
|
var (devicePublicKey, devicePrivateKey) = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||||
|
|
||||||
|
// Send encrypted keys to server
|
||||||
|
var deviceIdentifier = await _appIdService.GetAppIdAsync();
|
||||||
|
var deviceRequest = new TrustedDeviceKeysRequest
|
||||||
|
{
|
||||||
|
EncryptedUserKey = (await _cryptoService.RsaEncryptAsync(userKey.EncKey, devicePublicKey)).EncryptedString,
|
||||||
|
EncryptedPublicKey = (await _cryptoService.EncryptAsync(devicePublicKey, userKey)).EncryptedString,
|
||||||
|
EncryptedPrivateKey = (await _cryptoService.EncryptAsync(devicePrivateKey, deviceKey)).EncryptedString,
|
||||||
|
};
|
||||||
|
|
||||||
|
var deviceResponse = await _apiService.UpdateTrustedDeviceKeysAsync(deviceIdentifier, deviceRequest);
|
||||||
|
|
||||||
|
// Store device key if successful
|
||||||
|
await SetDeviceKeyAsync(deviceKey);
|
||||||
|
return deviceResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SymmetricCryptoKey> MakeDeviceKeyAsync()
|
||||||
|
{
|
||||||
|
// Create 512-bit device key
|
||||||
|
var randomBytes = await _cryptoFunctionService.RandomBytesAsync(DEVICE_KEY_SIZE);
|
||||||
|
return new SymmetricCryptoKey(randomBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetUserTrustDeviceChoiceForDecryptionAsync()
|
||||||
|
{
|
||||||
|
return await _stateService.GetUserTrustDeviceChoiceForDecryptionAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value)
|
||||||
|
{
|
||||||
|
await _stateService.SetUserTrustDeviceChoiceForDecryptionAsync(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,7 +77,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
return _decryptedFolderCache;
|
return _decryptedFolderCache;
|
||||||
}
|
}
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
throw new Exception("No key.");
|
throw new Exception("No key.");
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ namespace Bit.Core.Services
|
|||||||
_organizationService = organizationService;
|
_organizationService = organizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task GetAndSetMasterKeyAsync(string url)
|
public async Task GetAndSetKey(string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnectorAsync(url);
|
var userKeyResponse = await _apiService.GetUserKeyFromKeyConnector(url);
|
||||||
var masterKeyArr = Convert.FromBase64String(masterKeyResponse.Key);
|
var keyArr = Convert.FromBase64String(userKeyResponse.Key);
|
||||||
var masterKey = new MasterKey(masterKeyArr);
|
var k = new SymmetricCryptoKey(keyArr);
|
||||||
await _cryptoService.SetMasterKeyAsync(masterKey);
|
await _cryptoService.SetKeyAsync(k);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -39,17 +39,17 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetUsesKeyConnectorAsync(bool usesKeyConnector)
|
public async Task SetUsesKeyConnector(bool usesKeyConnector)
|
||||||
{
|
{
|
||||||
await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector);
|
await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GetUsesKeyConnectorAsync()
|
public async Task<bool> GetUsesKeyConnector()
|
||||||
{
|
{
|
||||||
return await _stateService.GetUsesKeyConnectorAsync();
|
return await _stateService.GetUsesKeyConnectorAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Organization> GetManagingOrganizationAsync()
|
public async Task<Organization> GetManagingOrganization()
|
||||||
{
|
{
|
||||||
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 MigrateUserAsync()
|
public async Task MigrateUser()
|
||||||
{
|
{
|
||||||
var organization = await GetManagingOrganizationAsync();
|
var organization = await GetManagingOrganization();
|
||||||
var masterKey = await _cryptoService.GetMasterKeyAsync();
|
var key = await _cryptoService.GetKeyAsync();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
|
var keyConnectorRequest = new KeyConnectorUserKeyRequest(key.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> UserNeedsMigrationAsync()
|
public async Task<bool> UserNeedsMigration()
|
||||||
{
|
{
|
||||||
var loggedInUsingSso = await _tokenService.GetIsExternal();
|
var loggedInUsingSso = await _tokenService.GetIsExternal();
|
||||||
var requiredByOrganization = await GetManagingOrganizationAsync() != null;
|
var requiredByOrganization = await GetManagingOrganization() != null;
|
||||||
var userIsNotUsingKeyConnector = !await GetUsesKeyConnectorAsync();
|
var userIsNotUsingKeyConnector = !await GetUsesKeyConnector();
|
||||||
|
|
||||||
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync()
|
public async Task<List<GeneratedPasswordHistory>> GetHistoryAsync()
|
||||||
{
|
{
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
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.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ namespace Bit.Core.Services
|
|||||||
return _decryptedSendsCache;
|
return _decryptedSendsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
throw new Exception("No Key.");
|
throw new Exception("No Key.");
|
||||||
|
|||||||
@@ -241,19 +241,6 @@ 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 },
|
||||||
@@ -315,70 +302,12 @@ 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;
|
||||||
@@ -424,36 +353,36 @@ namespace Bit.Core.Services
|
|||||||
await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions);
|
await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null)
|
public async Task<string> GetPinProtectedAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var key = await _storageMediatorService.GetAsync<string>(
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), false);
|
await GetDefaultStorageOptionsAsync());
|
||||||
return key != null ? new EncString(key) : null;
|
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null)
|
public async Task SetPinProtectedAsync(string value, string userId = null)
|
||||||
{
|
{
|
||||||
await _storageMediatorService.SaveAsync(
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), value?.EncryptedString, false);
|
await GetDefaultStorageOptionsAsync());
|
||||||
|
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null)
|
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
|
||||||
{
|
{
|
||||||
return (await GetAccountAsync(
|
return (await GetAccountAsync(
|
||||||
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
|
||||||
))?.VolatileData?.PinKeyEncryptedUserKeyEphemeral;
|
))?.VolatileData?.PinProtectedKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null)
|
public async Task SetPinProtectedKeyAsync(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.PinKeyEncryptedUserKeyEphemeral = value;
|
account.VolatileData.PinProtectedKey = 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 },
|
||||||
@@ -466,6 +395,35 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -481,6 +439,19 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -511,6 +482,17 @@ namespace Bit.Core.Services
|
|||||||
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null)
|
||||||
|
{
|
||||||
|
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(userId), true);
|
||||||
|
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
|
||||||
|
{
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(userId), value.KeyB64, true);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
|
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
|
||||||
@@ -1309,6 +1291,23 @@ namespace Bit.Core.Services
|
|||||||
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
|
await SetValueAsync(Constants.PreLoginEmailKey, value, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null)
|
||||||
|
{
|
||||||
|
return (await GetAccountAsync(
|
||||||
|
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultStorageOptionsAsync())
|
||||||
|
))?.Profile?.UserDecryptionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetUserTrustDeviceChoiceForDecryptionAsync()
|
||||||
|
{
|
||||||
|
return await _storageMediatorService.GetAsync<bool>(Constants.RememberDeviceTde);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetUserTrustDeviceChoiceForDecryptionAsync(bool value)
|
||||||
|
{
|
||||||
|
await _storageMediatorService.SaveAsync(Constants.RememberDeviceTde, value);
|
||||||
|
}
|
||||||
|
|
||||||
public ConfigResponse GetConfigs()
|
public ConfigResponse GetConfigs()
|
||||||
{
|
{
|
||||||
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
|
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
|
||||||
@@ -1476,31 +1475,28 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Non-state storage
|
// Non-state storage
|
||||||
await Task.WhenAll(
|
await SetProtectedPinAsync(null, userId);
|
||||||
SetUserKeyAutoUnlockAsync(null, userId),
|
await SetPinProtectedAsync(null, userId);
|
||||||
SetUserKeyBiometricUnlockAsync(null, userId),
|
await SetKeyEncryptedAsync(null, userId);
|
||||||
SetProtectedPinAsync(null, userId),
|
await SetKeyHashAsync(null, userId);
|
||||||
SetKeyHashAsync(null, userId),
|
await SetEncKeyEncryptedAsync(null, userId);
|
||||||
SetOrgKeysEncryptedAsync(null, userId),
|
await SetOrgKeysEncryptedAsync(null, userId);
|
||||||
SetPrivateKeyEncryptedAsync(null, userId),
|
await SetPrivateKeyEncryptedAsync(null, userId);
|
||||||
SetLastActiveTimeAsync(null, userId),
|
await SetLastActiveTimeAsync(null, userId);
|
||||||
SetPreviousPageInfoAsync(null, userId),
|
await SetPreviousPageInfoAsync(null, userId);
|
||||||
SetInvalidUnlockAttemptsAsync(null, userId),
|
await SetInvalidUnlockAttemptsAsync(null, userId);
|
||||||
SetLocalDataAsync(null, userId),
|
await SetLocalDataAsync(null, userId);
|
||||||
SetEncryptedCiphersAsync(null, userId),
|
await SetEncryptedCiphersAsync(null, userId);
|
||||||
SetEncryptedCollectionsAsync(null, userId),
|
await SetEncryptedCollectionsAsync(null, userId);
|
||||||
SetLastSyncAsync(null, userId),
|
await SetLastSyncAsync(null, userId);
|
||||||
SetEncryptedFoldersAsync(null, userId),
|
await SetEncryptedFoldersAsync(null, userId);
|
||||||
SetEncryptedPoliciesAsync(null, userId),
|
await SetEncryptedPoliciesAsync(null, userId);
|
||||||
SetUsesKeyConnectorAsync(null, userId),
|
await SetUsesKeyConnectorAsync(null, userId);
|
||||||
SetOrganizationsAsync(null, userId),
|
await SetOrganizationsAsync(null, userId);
|
||||||
SetEncryptedPasswordGenerationHistoryAsync(null, userId),
|
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
|
||||||
SetEncryptedSendsAsync(null, userId),
|
await SetEncryptedSendsAsync(null, userId);
|
||||||
SetSettingsAsync(null, userId),
|
await SetSettingsAsync(null, userId);
|
||||||
SetApprovePasswordlessLoginsAsync(null, userId),
|
await SetApprovePasswordlessLoginsAsync(null, userId);
|
||||||
SetEncKeyEncryptedAsync(null, userId),
|
|
||||||
SetKeyEncryptedAsync(null, userId),
|
|
||||||
SetPinProtectedAsync(null, userId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ScaffoldNewAccountAsync(Account account)
|
private async Task ScaffoldNewAccountAsync(Account account)
|
||||||
@@ -1688,79 +1684,5 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -327,8 +327,8 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(response.Key);
|
await _cryptoService.SetEncKeyAsync(response.Key);
|
||||||
await _cryptoService.SetUserPrivateKeyAsync(response.PrivateKey);
|
await _cryptoService.SetEncPrivateKeyAsync(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.SetUsesKeyConnectorAsync(response.UsesKeyConnector);
|
await _keyConnectorService.SetUsesKeyConnector(response.UsesKeyConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
|
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
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;
|
||||||
@@ -59,26 +54,15 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<bool> IsLockedAsync(string userId = null)
|
public async Task<bool> IsLockedAsync(string userId = null)
|
||||||
{
|
{
|
||||||
var biometricSet = await IsBiometricLockSetAsync(userId);
|
var hasKey = await _cryptoService.HasKeyAsync(userId);
|
||||||
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
|
if (hasKey)
|
||||||
{
|
{
|
||||||
return true;
|
var biometricSet = await IsBiometricLockSetAsync(userId);
|
||||||
}
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,15 +163,13 @@ namespace Bit.Core.Services
|
|||||||
userId = await _stateService.GetActiveUserIdAsync();
|
userId = await _stateService.GetActiveUserIdAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await _keyConnectorService.GetUsesKeyConnectorAsync())
|
if (await _keyConnectorService.GetUsesKeyConnector())
|
||||||
{
|
{
|
||||||
var pinStatus = await GetPinLockTypeAsync(userId);
|
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId);
|
||||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) ||
|
||||||
?? await _stateService.GetPinProtectedKeyAsync();
|
isPinProtectedWithKey;
|
||||||
var pinEnabled = (pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
|
||||||
pinStatus == PinLockType.Persistent;
|
|
||||||
|
|
||||||
if (!pinEnabled && !await IsBiometricLockSetAsync())
|
if (!pinLock && !await IsBiometricLockSetAsync())
|
||||||
{
|
{
|
||||||
await LogOutAsync(userInitiated, userId);
|
await LogOutAsync(userInitiated, userId);
|
||||||
return;
|
return;
|
||||||
@@ -205,11 +187,10 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
_cryptoService.ClearUserKeyAsync(userId),
|
_cryptoService.ClearKeyAsync(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)
|
||||||
{
|
{
|
||||||
@@ -233,27 +214,15 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
await _stateService.SetVaultTimeoutAsync(timeout);
|
await _stateService.SetVaultTimeoutAsync(timeout);
|
||||||
await _stateService.SetVaultTimeoutActionAsync(action);
|
await _stateService.SetVaultTimeoutActionAsync(action);
|
||||||
await _cryptoService.RefreshKeysAsync();
|
await _cryptoService.ToggleKeyAsync();
|
||||||
await _tokenService.ToggleTokensAsync();
|
await _tokenService.ToggleTokensAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PinLockType> GetPinLockTypeAsync(string userId = null)
|
public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null)
|
||||||
{
|
{
|
||||||
// we can't depend on only the protected pin being set because old
|
var protectedPin = await _stateService.GetProtectedPinAsync(userId);
|
||||||
// versions only used it for MP on Restart
|
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId);
|
||||||
var isPinEnabled = await _stateService.GetProtectedPinAsync(userId) != null;
|
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != 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)
|
||||||
@@ -264,7 +233,8 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task ClearAsync(string userId = null)
|
public async Task ClearAsync(string userId = null)
|
||||||
{
|
{
|
||||||
await _cryptoService.ClearPinKeysAsync(userId);
|
await _stateService.SetPinProtectedKeyAsync(null, userId);
|
||||||
|
await _stateService.SetProtectedPinAsync(null, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int?> GetVaultTimeout(string userId = null)
|
public async Task<int?> GetVaultTimeout(string userId = null)
|
||||||
|
|||||||
@@ -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,
|
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
|
||||||
passwordGenerationService, policyService);
|
keyConnectorService, 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);
|
||||||
@@ -88,6 +88,7 @@ namespace Bit.Core.Utilities
|
|||||||
cryptoService);
|
cryptoService);
|
||||||
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
||||||
var configService = new ConfigService(apiService, stateService, logger);
|
var configService = new ConfigService(apiService, stateService, logger);
|
||||||
|
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
|
||||||
|
|
||||||
Register<IConditionedAwaiterManager>(conditionedRunner);
|
Register<IConditionedAwaiterManager>(conditionedRunner);
|
||||||
Register<ITokenService>("tokenService", tokenService);
|
Register<ITokenService>("tokenService", tokenService);
|
||||||
@@ -114,6 +115,7 @@ namespace Bit.Core.Utilities
|
|||||||
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
||||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||||
Register<IConfigService>(configService);
|
Register<IConfigService>(configService);
|
||||||
|
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Register<T>(string serviceName, T obj)
|
public static void Register<T>(string serviceName, T obj)
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
var appOptions = new AppOptions { IosExtension = true };
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
var app = new App.App(appOptions);
|
var app = new App.App(appOptions);
|
||||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||||
|
|||||||
@@ -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.7.1</string>
|
<string>2023.5.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ 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
|
||||||
@@ -30,9 +28,10 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
private IBiometricService _biometricService;
|
private IBiometricService _biometricService;
|
||||||
private IKeyConnectorService _keyConnectorService;
|
private IKeyConnectorService _keyConnectorService;
|
||||||
private IAccountsManager _accountManager;
|
private IAccountsManager _accountManager;
|
||||||
private PinLockType _pinStatus;
|
private bool _isPinProtected;
|
||||||
private bool _pinEnabled;
|
private bool _isPinProtectedWithKey;
|
||||||
private bool _biometricEnabled;
|
private bool _pinLock;
|
||||||
|
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;
|
||||||
@@ -86,7 +85,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");
|
||||||
@@ -104,28 +103,25 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
||||||
{
|
{
|
||||||
_passwordReprompt = true;
|
_passwordReprompt = true;
|
||||||
_pinStatus = PinLockType.Disabled;
|
_isPinProtected = false;
|
||||||
_pinEnabled = false;
|
_isPinProtectedWithKey = false;
|
||||||
_biometricEnabled = false;
|
_pinLock = false;
|
||||||
|
_biometricLock = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
|
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
_isPinProtectedWithKey;
|
||||||
?? await _stateService.GetPinProtectedKeyAsync();
|
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||||
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
await _cryptoService.HasKeyAsync();
|
||||||
_pinStatus == PinLockType.Persistent;
|
|
||||||
|
|
||||||
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
|
|
||||||
&& await _cryptoService.HasEncryptedUserKeyAsync();
|
|
||||||
_biometricIntegrityValid =
|
_biometricIntegrityValid =
|
||||||
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
||||||
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnectorAsync();
|
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
_biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
|
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pinEnabled)
|
if (_pinLock)
|
||||||
{
|
{
|
||||||
BaseNavItem.Title = AppResources.VerifyPIN;
|
BaseNavItem.Title = AppResources.VerifyPIN;
|
||||||
}
|
}
|
||||||
@@ -154,7 +150,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
if (!_biometricUnlockOnly)
|
if (!_biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
|
MasterPasswordCell.Label.Text = _pinLock ? 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) =>
|
||||||
@@ -162,7 +158,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
CheckPasswordAsync().FireAndForget();
|
CheckPasswordAsync().FireAndForget();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (_pinEnabled)
|
if (_pinLock)
|
||||||
{
|
{
|
||||||
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
||||||
}
|
}
|
||||||
@@ -181,7 +177,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
if (_biometricEnabled)
|
if (_biometricLock)
|
||||||
{
|
{
|
||||||
if (!_biometricIntegrityValid)
|
if (!_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
@@ -202,18 +198,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 (!(_pinEnabled || _biometricEnabled) ||
|
if (!(_pinLock || _biometricLock) ||
|
||||||
(_biometricEnabled && !_biometricIntegrityValid))
|
(_biometricLock && !_biometricIntegrityValid))
|
||||||
{
|
{
|
||||||
PromptSSO();
|
PromptSSO();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!_biometricEnabled || !_biometricIntegrityValid)
|
else if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
MasterPasswordCell.TextField.BecomeFirstResponder();
|
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task CheckPasswordAsync()
|
protected async Task CheckPasswordAsync()
|
||||||
{
|
{
|
||||||
if (_checkingPassword)
|
if (_checkingPassword)
|
||||||
@@ -228,7 +224,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,
|
||||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
|
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
|
||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
PresentViewController(alert, true, null);
|
PresentViewController(alert, true, null);
|
||||||
return;
|
return;
|
||||||
@@ -250,53 +246,33 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pinEnabled)
|
if (_pinLock)
|
||||||
{
|
{
|
||||||
var failed = true;
|
var failed = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EncString userKeyPin = null;
|
if (_isPinProtected)
|
||||||
EncString oldPinProtected = null;
|
|
||||||
if (_pinStatus == PinLockType.Persistent)
|
|
||||||
{
|
{
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||||
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,
|
||||||
oldPinProtected
|
await _stateService.GetPinProtectedKeyAsync());
|
||||||
);
|
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
|
||||||
{
|
{
|
||||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||||
inputtedValue,
|
kdfConfig);
|
||||||
email,
|
failed = false;
|
||||||
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(userKey);
|
await SetKeyAndContinueAsync(key2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -310,27 +286,33 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
|
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
|
||||||
|
|
||||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
if (storedPasswordHash == null)
|
if (storedKeyHash == null)
|
||||||
{
|
{
|
||||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||||
if (masterKey.KeyB64 == oldKey)
|
if (key2.KeyB64 == oldKey)
|
||||||
{
|
{
|
||||||
var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
|
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
|
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
|
||||||
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
|
||||||
{
|
{
|
||||||
@@ -357,12 +339,12 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
if (success)
|
if (success)
|
||||||
@@ -389,12 +371,12 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
PresentViewController(loginController, true, null);
|
PresentViewController(loginController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
|
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
|
||||||
{
|
{
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetUserKeyAsync(userKey);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
}
|
}
|
||||||
DoContinue(masterPassword);
|
DoContinue(masterPassword);
|
||||||
}
|
}
|
||||||
@@ -414,7 +396,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 (_biometricEnabled & !_biometricIntegrityValid)
|
if (_biometricLock & !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||||
}
|
}
|
||||||
@@ -423,7 +405,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, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||||
AppResources.Ok, (a) =>
|
AppResources.Ok, (a) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -508,7 +490,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (!controller._biometricUnlockOnly && controller._biometricEnabled) ||
|
return (!controller._biometricUnlockOnly && controller._biometricLock) ||
|
||||||
controller._passwordReprompt
|
controller._passwordReprompt
|
||||||
? 2
|
? 2
|
||||||
: 1;
|
: 1;
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
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 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 Bit.App.Utilities;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.App.Pages;
|
||||||
|
using Bit.App.Models;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.iOS.Core.Controllers
|
namespace Bit.iOS.Core.Controllers
|
||||||
@@ -30,9 +29,10 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private IBiometricService _biometricService;
|
private IBiometricService _biometricService;
|
||||||
private IKeyConnectorService _keyConnectorService;
|
private IKeyConnectorService _keyConnectorService;
|
||||||
private PinLockType _pinStatus;
|
private bool _isPinProtected;
|
||||||
private bool _pinEnabled;
|
private bool _isPinProtectedWithKey;
|
||||||
private bool _biometricEnabled;
|
private bool _pinLock;
|
||||||
|
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,28 +96,25 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
if (autofillExtension && await _stateService.GetPasswordRepromptAutofillAsync())
|
||||||
{
|
{
|
||||||
_passwordReprompt = true;
|
_passwordReprompt = true;
|
||||||
_pinStatus = PinLockType.Disabled;
|
_isPinProtected = false;
|
||||||
_pinEnabled = false;
|
_isPinProtectedWithKey = false;
|
||||||
_biometricEnabled = false;
|
_pinLock = false;
|
||||||
|
_biometricLock = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
|
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
|
||||||
|
_pinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
|
||||||
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
|
_isPinProtectedWithKey;
|
||||||
?? await _stateService.GetPinProtectedKeyAsync();
|
_biometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() &&
|
||||||
_pinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
|
await _cryptoService.HasKeyAsync();
|
||||||
_pinStatus == PinLockType.Persistent;
|
|
||||||
|
|
||||||
_biometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync()
|
|
||||||
&& await _cryptoService.HasEncryptedUserKeyAsync();
|
|
||||||
_biometricIntegrityValid =
|
_biometricIntegrityValid =
|
||||||
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
await _platformUtilsService.IsBiometricIntegrityValidAsync(BiometricIntegritySourceKey);
|
||||||
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnectorAsync();
|
_usesKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
|
||||||
_biometricUnlockOnly = _usesKeyConnector && _biometricEnabled && !_pinEnabled;
|
_biometricUnlockOnly = _usesKeyConnector && _biometricLock && !_pinLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_pinEnabled)
|
if (_pinLock)
|
||||||
{
|
{
|
||||||
BaseNavItem.Title = AppResources.VerifyPIN;
|
BaseNavItem.Title = AppResources.VerifyPIN;
|
||||||
}
|
}
|
||||||
@@ -129,7 +126,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)
|
||||||
@@ -146,7 +143,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
if (!_biometricUnlockOnly)
|
if (!_biometricUnlockOnly)
|
||||||
{
|
{
|
||||||
MasterPasswordCell.Label.Text = _pinEnabled ? AppResources.PIN : AppResources.MasterPassword;
|
MasterPasswordCell.Label.Text = _pinLock ? 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) =>
|
||||||
@@ -154,7 +151,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
CheckPasswordAsync().GetAwaiter().GetResult();
|
CheckPasswordAsync().GetAwaiter().GetResult();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (_pinEnabled)
|
if (_pinLock)
|
||||||
{
|
{
|
||||||
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
MasterPasswordCell.TextField.KeyboardType = UIKeyboardType.NumberPad;
|
||||||
}
|
}
|
||||||
@@ -168,7 +165,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
if (_biometricEnabled)
|
if (_biometricLock)
|
||||||
{
|
{
|
||||||
if (!_biometricIntegrityValid)
|
if (!_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
@@ -189,13 +186,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 (!(_pinEnabled || _biometricEnabled) ||
|
if (!(_pinLock || _biometricLock) ||
|
||||||
(_biometricEnabled && !_biometricIntegrityValid))
|
(_biometricLock && !_biometricIntegrityValid))
|
||||||
{
|
{
|
||||||
PromptSSO();
|
PromptSSO();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!_biometricEnabled || !_biometricIntegrityValid)
|
else if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
MasterPasswordCell.TextField.BecomeFirstResponder();
|
MasterPasswordCell.TextField.BecomeFirstResponder();
|
||||||
}
|
}
|
||||||
@@ -207,7 +204,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,
|
||||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword),
|
_pinLock ? AppResources.PIN : AppResources.MasterPassword),
|
||||||
AppResources.Ok);
|
AppResources.Ok);
|
||||||
PresentViewController(alert, true, null);
|
PresentViewController(alert, true, null);
|
||||||
return;
|
return;
|
||||||
@@ -217,53 +214,33 @@ 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 (_pinEnabled)
|
if (_pinLock)
|
||||||
{
|
{
|
||||||
var failed = true;
|
var failed = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EncString userKeyPin = null;
|
if (_isPinProtected)
|
||||||
EncString oldPinProtected = null;
|
|
||||||
if (_pinStatus == PinLockType.Persistent)
|
|
||||||
{
|
{
|
||||||
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
|
var key = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||||
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,
|
||||||
oldPinProtected
|
await _stateService.GetPinProtectedKeyAsync());
|
||||||
);
|
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
|
||||||
{
|
{
|
||||||
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
|
var key2 = await _cryptoService.MakeKeyFromPinAsync(inputtedValue, email,
|
||||||
inputtedValue,
|
kdfConfig);
|
||||||
email,
|
failed = false;
|
||||||
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(userKey);
|
await SetKeyAndContinueAsync(key2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -283,27 +260,33 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var masterKey = await _cryptoService.MakeMasterKeyAsync(inputtedValue, email, kdfConfig);
|
var key2 = await _cryptoService.MakeKeyAsync(inputtedValue, email, kdfConfig);
|
||||||
|
|
||||||
var storedPasswordHash = await _cryptoService.GetMasterKeyHashAsync();
|
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
|
||||||
if (storedPasswordHash == null)
|
if (storedKeyHash == null)
|
||||||
{
|
{
|
||||||
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
var oldKey = await _secureStorageService.GetAsync<string>("oldKey");
|
||||||
if (masterKey.KeyB64 == oldKey)
|
if (key2.KeyB64 == oldKey)
|
||||||
{
|
{
|
||||||
var localPasswordHash = await _cryptoService.HashMasterKeyAsync(inputtedValue, masterKey, HashPurpose.LocalAuthorization);
|
var localKeyHash = await _cryptoService.HashPasswordAsync(inputtedValue, key2, HashPurpose.LocalAuthorization);
|
||||||
await _secureStorageService.RemoveAsync("oldKey");
|
await _secureStorageService.RemoveAsync("oldKey");
|
||||||
await _cryptoService.SetMasterKeyHashAsync(localPasswordHash);
|
await _cryptoService.SetKeyHashAsync(localKeyHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, masterKey);
|
var passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(inputtedValue, key2);
|
||||||
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
|
||||||
{
|
{
|
||||||
@@ -320,12 +303,12 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public async Task PromptBiometricAsync()
|
public async Task PromptBiometricAsync()
|
||||||
{
|
{
|
||||||
if (!_biometricEnabled || !_biometricIntegrityValid)
|
if (!_biometricLock || !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||||
_pinEnabled ? AppResources.PIN : AppResources.MasterPassword,
|
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||||
await _stateService.SetBiometricLockedAsync(!success);
|
await _stateService.SetBiometricLockedAsync(!success);
|
||||||
if (success)
|
if (success)
|
||||||
@@ -352,12 +335,12 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
PresentViewController(loginController, true, null);
|
PresentViewController(loginController, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetKeyAndContinueAsync(UserKey userKey, bool masterPassword = false)
|
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key, bool masterPassword = false)
|
||||||
{
|
{
|
||||||
var hasKey = await _cryptoService.HasUserKeyAsync();
|
var hasKey = await _cryptoService.HasKeyAsync();
|
||||||
if (!hasKey)
|
if (!hasKey)
|
||||||
{
|
{
|
||||||
await _cryptoService.SetUserKeyAsync(userKey);
|
await _cryptoService.SetKeyAsync(key);
|
||||||
}
|
}
|
||||||
DoContinue(masterPassword);
|
DoContinue(masterPassword);
|
||||||
}
|
}
|
||||||
@@ -377,7 +360,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 (_biometricEnabled & !_biometricIntegrityValid)
|
if (_biometricLock & !_biometricIntegrityValid)
|
||||||
{
|
{
|
||||||
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
await _biometricService.SetupBiometricAsync(BiometricIntegritySourceKey);
|
||||||
}
|
}
|
||||||
@@ -386,7 +369,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, _pinEnabled ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
string.Format(null, _pinLock ? AppResources.PIN : AppResources.InvalidMasterPassword),
|
||||||
AppResources.Ok, (a) =>
|
AppResources.Ok, (a) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -395,7 +378,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());
|
||||||
@@ -461,7 +444,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
|
|
||||||
public override nint NumberOfSections(UITableView tableView)
|
public override nint NumberOfSections(UITableView tableView)
|
||||||
{
|
{
|
||||||
return (!_controller._biometricUnlockOnly && _controller._biometricEnabled) ||
|
return (!_controller._biometricUnlockOnly && _controller._biometricLock) ||
|
||||||
_controller._passwordReprompt
|
_controller._passwordReprompt
|
||||||
? 2
|
? 2
|
||||||
: 1;
|
: 1;
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
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 : BaseBiometricService
|
public class BiometricService : IBiometricService
|
||||||
{
|
{
|
||||||
public BiometricService(IStateService stateService, ICryptoService cryptoService)
|
private IStateService _stateService;
|
||||||
: base(stateService, cryptoService)
|
|
||||||
|
public BiometricService(IStateService stateService)
|
||||||
{
|
{
|
||||||
|
_stateService = stateService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||||
{
|
{
|
||||||
if (bioIntegritySrcKey == null)
|
if (bioIntegritySrcKey == null)
|
||||||
{
|
{
|
||||||
@@ -29,7 +30,7 @@ namespace Bit.iOS.Core.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||||
{
|
{
|
||||||
var state = GetState();
|
var state = GetState();
|
||||||
if (state == null)
|
if (state == null)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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;
|
||||||
@@ -148,11 +147,6 @@ 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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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.7.1</string>
|
<string>2023.5.1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ using Bit.App.Pages;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.iOS.Core.Views;
|
using Bit.iOS.Core.Views;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.iOS.Extension
|
namespace Bit.iOS.Extension
|
||||||
{
|
{
|
||||||
@@ -536,7 +537,7 @@ namespace Bit.iOS.Extension
|
|||||||
{
|
{
|
||||||
var appOptions = new AppOptions { IosExtension = true };
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
var app = new App.App(appOptions);
|
var app = new App.App(appOptions);
|
||||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, appOptions);
|
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
ThemeManager.ApplyResourcesTo(loginWithDevicePage);
|
||||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2023.7.1</string>
|
<string>2023.5.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
|
|
||||||
private void LaunchLoginWithDevice(string email = null)
|
private void LaunchLoginWithDevice(string email = null)
|
||||||
{
|
{
|
||||||
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, _appOptions.Value);
|
var loginWithDevicePage = new LoginPasswordlessRequestPage(email, AuthRequestType.AuthenticateAndUnlock, _appOptions.Value);
|
||||||
SetupAppAndApplyResources(loginWithDevicePage);
|
SetupAppAndApplyResources(loginWithDevicePage);
|
||||||
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
if (loginWithDevicePage.BindingContext is LoginPasswordlessRequestViewModel vm)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.7.1</string>
|
<string>2023.5.1</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
"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";
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"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
Reference in New Issue
Block a user