1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-02 16:43:20 +00:00

Trusted Device Encryption feature (#2656)

* [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added.

* [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account.

* [PM-1208] Add continue button and not you option

* [PM-1379] add DeviceTrustCryptoService with establish trust logic (#2535)

* [PM-1379] add DeviceCryptoService with establish trust logic

* PM-1379 update api location and other minor refactors

* pm-1379 fix encoding

* update trusted device keys api call to Put

* [PM-1379] rename DeviceCryptoService to DeviceTrustCryptoService
- refactors to prevent side effects

* [PM-1379] rearrange methods in DeviceTrustCryptoService

* [PM-1379] rearrange methods in abstraction

* [PM-1379] deconstruct tuples

* [PM-1379] remove extra tasks

* [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609)

* [PM-2287][PM-2289][PM-2293] Approval Options (#2608)

* [PM-2293] Add AuthRequestType to PasswordlessLoginPage.

* [PM-2293] Add Actions to ApproveWithDevicePage

* [PM-2293] Change screen text based on AuthRequestType

* [PM-2293] Refactor AuthRequestType enum. Add label. Remove unnecessary actions.

* [PM-2293] Change boolean variable expression.

* [PM-2293] Trust device after admin request login.

* code format

* [PM-2287] Add trust device to master password unlock. Change trust device method. Remove email from SSO login page.

* [PM-2293] Fix state variable get set.

* [PM-2287][PM-2289][PM-2293] Rename method

* [PM-1201] Change timeout actions available based on hasMasterPassword (#2610)

* [PM-1201] Change timeout actions available based on hasMasterPassword

* [PM-2731] add user key and master key types

* [PM-2713] add new state for new keys and obsolete old ones
- UserKey
- MasterKey
- UserKeyMasterKey (enc UserKey from User Table)

* [PM-271] add UserKey and MasterKey support to crypto service

* [PM-2713] rename key hash to password hash & begin add methods to crypto service

* [PM-2713] continue organizing crypto service

* [PM-2713] more updates to crypto service

* [PM-2713] add new pin methods to state service

* [PM-2713] fix signature of GetUserKeyPin

* [PM-2713] add make user key method to crypto service

* [PM-2713] refresh pin key when setting user key

* [PM-2713] use new MakeMasterKey method

* [PM-2713] add toggle method to crypto service for keys

* [PM-2713] converting calls to new crypto service api

* [PM-2713] add migration for pin on lock screens

* [PM-2713] more conversions to new crypto service api

* [PM-2713] convert cipher service and others to crypto service api

* [PM-2713] More conversions to crypto api

* [PM-2713] use new crypto service api in auth service

* [PM-2713] remove unused cached values in crypto service

* [PM-2713] set decrypt and set user key in login helper

* fix bad merge

* Update crypto service api call to fix build

* [PM-1208] Fix app resource file

* [PM-1208] Fix merge

* [PM-1208] Fix merge

* [PM-2713] optimize async code in crypto service

* [PM-2713] rename password hash to master key hash

* [PM-2713] fix casting issues and pin

* [PM-2713] remove extra comment

* [PM-2713] remove broken casting

* [PM-2297] Login with trusted device (Flow 2) (#2623)

* [PM-2297] Add DecryptUserKeyWithDeviceKey method

* [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model

* [PM-2297] Update account decryption options model

* [PM-2297] Fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id

* [PM-2297] Update navigation to decryption options

* [PM-2297] Add missing action navigations to iOS extensions

* [PM-2297] Fix trust device bug/typo

* [PM-2297] Fix model bug

* [PM-2297] Fix state var crash

* [PM-2297] Add trust device login logic to auth service

* [PM-2297] Refactor auth service key connector code

* [PM-2297] Remove reconciledOptions for deviceKey in state service

* [PM-2297] Remove unnecessary user id params

* [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642)

* [PM-2713] deconstruct new key pair

* [PM-2713] rename PrivateKey methods to UserPrivateKey on crypto service

* [PM-2713] rename PinLockEnum to PinLockType

* [PM-2713] don't pass user key as param when encrypting

* [PM-2713] rename toggle method, don't reset enc user key

* [PM-2713] pr feedback

* [PM-2713] PR feedback

* [PM-2713] rename get pin lock type method

* [PM-2713] revert feedback for build

* [PM-2713] rename state methods

* [PM-2713] combine makeDataEncKey methods

* [PM-2713] consolidate attachment key creation
- also fix ios files missed during symbol rename

* [PM-2713] replace generic with inherited class

* rename account keys to be more descriptive

* [PM-2713] add auto unlock key to mobile

* [PM-1208] Add TDE flows for new users (#2655)

* [PM-1208] Create new user on SSO. Logout if not password is setup or has pending admin auth request.

* [PM-1208] Fix new user UserKey decryption.

* [PM-1208] Add new user continue to vault logic. Auto enrol user on continue.

* [PM-1208] Trust device only if needed

* [PM-1208] Add logic for New User SSO.

* [PM-1208] Add logic for New User SSO (missing file).

* [PM-2713] set user key on set password page

* [PM-2713] set enc user key during kc onboarding

* fix formatting

* [PM-2713] make method async again
- returning null from a task thats not async throws

* [PM-2713] clear service cache when adding new account

* Fix build after merge

* [PM-3313] Fix Android SSO Login (#2663)

* [PM-3313] Catch exception on AuthPendingRequest

* [PM-3313] Fix lock timeout action if user doesn't have a master password.

* code format

* [PM-3313] Null email in Approval Options screen (#2664)

* [PM-3313] Fix null email in approval options screen

* [PM-3320][PM-3321] Fix labels and UI tweaks (#2666)

* [PM-3320] Fix UI copy and remember me default ON.

* [PM-3321] Fix UI on Log in with device screen.

* [PM-3337] Fix admin request deny error (#2669)

* [PM-3342] Not you button logs user out. (#2672)

* [PM-3319] Check for admin request in Lock page (#2668)

* [PM-3319] Ignore admin auth request when choosing mp as decryption option.

* [PM-2289] Change header title based on auth request type (#2670)

* [PM-2289] Change header title based on auth request type

* [PM-3333] Check for purged admin auth requests (#2671)

* [PM-3333] Check for purged admin auth requests

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* [PM-3341] Vault Timeout Action not persisted correctly (#2673)

* [PM-3341] Fix timeout action change when navigating

* [PM-3357] Fix copy for Login Initiated (#2674)

* [PM-3362] Fix auth request approval (#2675)

* [PM-3362] Fix auth request approval

* [PM-3362] Add new exception type

* [PM-3102] Update Master password reprompt to be based on MP instead of Key Connector (#2653)

* PM-3102 Added check to see if a user has master password set replacing previous usage of key connector.

* PM-3102 Fix formatting

* [PM-2713] Final merge from Key Migration branch to TDE Feature branch (#2667)

* [PM-2713] add async to key connector service methods

* [PM-2713] rename ephemeral pin key

* add state for biometric key and accept UserKey instead of string for auto key

* Get UserKey from bio state on unlock

* PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called

* renaming bio key and fix build

* PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet

* revert removal of key connector service from auth service

* PM-2713 set user key when using KC

* clear enc user key after migration

* use is true for nullable bool

* PR feedback, refactor kc service

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* Fix app fresh install user login with master password. (#2676)

* [PM-3303] Fix biometric login after key migration (#2679)

* [PM-3303] Add condition to biometric unlock

* [PM-3381] Fix TDE login 2FA flow (#2678)

* [PM-3381] Check for vault lock on 2FA screen

* [PM-3381] Move logic to ViewModel

* [PM-3381] Fix null vm error

* [PM-3379] Fix key rotation on trusted device. (#2680)

* [PM-3381] Update login flows (#2683)

* [PM-3381] Update login flows

* [PM-3381] Remove _authingWithSso parameter

* PM-3385 Fix MP reprompt item level when no MP hash is stored like logging in with TDE. Also refactor code to be more maintainable (#2687)

* PM-3386 Fix MP reprompt / OTP decision to be also based on the master key hash. (#2688)

* PM-3450 Fix has master password with mp key hash check (#2689)

* [PM-3394] Fix login with device for passwordless approvals (#2686)

* set activeUserId to null when logging in a new account
- Also stop the user key from being set in inactive accounts

* get token for login with device if approving device doesn't have master key

* add comment

* simplify logic

* check for route instead of using isAuthenticated
- we don't clear the user id when logging in new account
- this means we can't trust the state service, so we have to base our logic off the route in login with device

* use authenticated auth request for tde login with device

* [PM-3394] Add authingWithSso parameter to LoginPasswordlessRequestPage.

* pr feedback

* [PM-3394] Refactor condition

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* [PM-3462] Handle force password reset on mobile with TDE (#2694)

* [PM-3462] Handle force password reset on mobile with TDE

* [PM-3462] update references to refactored crypto method
- fix kc bug, we were sending private key instead of user key to server
- rename kc service method to be correct

* [PM-3462] Update TwoFactorPage login logic

* [PM-3462] Added pending admin request check to TwoFactorPage

* [PM-3462] Added new exception types for null keys

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>

* [PM-1029] Fix Async suffix in ApiService. Add UserKeyNullExceptions.

* [PM 3513] Fix passwordless 2fa login with device on mobile (#2700)

* [PM-3513] Fix 2FA for normal login with device with users without mp

* move _userKey

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>

* clear encrypted pin on logout (#2699)

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: Jake Fink <jfink@bitwarden.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
This commit is contained in:
Todd Martin
2023-08-17 15:19:35 -04:00
committed by GitHub
parent a23454bc53
commit bfcfd367dd
90 changed files with 3348 additions and 1365 deletions

View File

@@ -4,6 +4,7 @@ using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
@@ -46,7 +47,7 @@ namespace Bit.Core.Abstractions
Task PutDeleteCipherAsync(string id);
Task<CipherResponse> PutRestoreCipherAsync(string id);
Task RefreshIdentityTokenAsync();
Task<SsoPrevalidateResponse> PreValidateSso(string identifier);
Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
TRequest body, bool authed, bool hasResponse, Action<HttpRequestMessage> alterRequest, bool logoutOnUnauthorized = true);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default);
@@ -70,11 +71,11 @@ namespace Bit.Core.Abstractions
Task<OrganizationAutoEnrollStatusResponse> GetOrganizationAutoEnrollStatusAsync(string identifier);
Task PutOrganizationUserResetPasswordEnrollmentAsync(string orgId, string userId,
OrganizationUserResetPasswordEnrollmentRequest request);
Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl);
Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnector();
Task PostLeaveOrganization(string id);
Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl);
Task PostMasterKeyToKeyConnectorAsync(string keyConnectorUrl, KeyConnectorUserKeyRequest request);
Task PostSetKeyConnectorKeyAsync(SetKeyConnectorKeyRequest request);
Task PostConvertToKeyConnectorAsync();
Task PostLeaveOrganizationAsync(string id);
Task<SendResponse> GetSendAsync(string id);
Task<SendResponse> PostSendAsync(SendRequest request);
@@ -90,9 +91,12 @@ namespace Bit.Core.Abstractions
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
Task<PasswordlessLoginResponse> GetAuthResponseAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest);
Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType);
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<bool> GetDevicesExistenceByTypes(DeviceType[] deviceTypes);
Task<ConfigResponse> GetConfigsAsync();
Task<string> GetFastmailAccountIdAsync(string apiKey);
}

View File

@@ -27,14 +27,18 @@ namespace Bit.Core.Abstractions
Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId);
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
Task<AuthResult> LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered);
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
Task<List<PasswordlessLoginResponse>> GetActivePasswordlessLoginRequestsAsync();
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode);
/// <summary>
/// Gets a passwordless login request by <paramref name="id"/> and <paramref name="accessCode"/>. No authentication required.
/// </summary>
Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode);
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email);
Task<PasswordlessLoginResponse> PasswordlessCreateLoginRequestAsync(string email, AuthRequestType authRequestType);
Task CreateNewSsoUserAsync(string organizationSsoId);
void LogOut(Action callback);
void Init();

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
namespace Bit.Core.Abstractions
{
public interface IDeviceTrustCryptoService
{
Task<SymmetricCryptoKey> GetDeviceKeyAsync();
Task<DeviceResponse> TrustDeviceAsync();
Task<DeviceResponse> TrustDeviceIfNeededAsync();
Task RemoveTrustedDeviceAsync();
Task<bool> GetShouldTrustDeviceAsync();
Task SetShouldTrustDeviceAsync(bool value);
Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey);
Task<bool> IsDeviceTrustedAsync();
}
}

View File

@@ -1,16 +1,17 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
namespace Bit.Core.Abstractions
{
public interface IKeyConnectorService
{
Task SetUsesKeyConnector(bool usesKeyConnector);
Task<bool> GetUsesKeyConnector();
Task<bool> UserNeedsMigration();
Task MigrateUser();
Task GetAndSetKey(string url);
Task<Organization> GetManagingOrganization();
Task SetUsesKeyConnectorAsync(bool usesKeyConnector);
Task<bool> GetUsesKeyConnectorAsync();
Task<bool> UserNeedsMigrationAsync();
Task MigrateUserAsync();
Task SetMasterKeyFromUrlAsync(string url);
Task<Organization> GetManagingOrganizationAsync();
Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse);
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{
public interface IPasswordResetEnrollmentService
{
Task EnrollIfRequiredAsync(string organizationSsoId);
Task EnrollAsync(string organizationId);
}
}

View File

@@ -13,6 +13,14 @@ namespace Bit.Core.Abstractions
public interface IStateService
{
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> GetActiveUserEmailAsync();
Task<T> GetActiveUserCustomDataAsync<T>(Func<Account, T> dataMapper);
@@ -27,6 +35,8 @@ namespace Bit.Core.Abstractions
Task<EnvironmentUrlData> GetPreAuthEnvironmentUrlsAsync();
Task SetPreAuthEnvironmentUrlsAsync(EnvironmentUrlData value);
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 SetBiometricUnlockAsync(bool? value, string userId = null);
Task<bool> GetBiometricLockedAsync(string userId = null);
@@ -36,26 +46,22 @@ namespace Bit.Core.Abstractions
Task<bool> IsAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
Task SetAccountBiometricIntegrityValidAsync(string bioIntegritySrcKey, string userId = null);
Task<bool> CanAccessPremiumAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<string> GetProtectedPinAsync(string userId = null);
Task SetPersonalPremiumAsync(bool value, string userId = null);
Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null);
Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null);
Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null);
Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null);
Task SetProtectedPinAsync(string value, string userId = null);
Task<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<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 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 SetOrgKeysEncryptedAsync(Dictionary<string, string> value, string userId = null);
Task<string> GetPrivateKeyEncryptedAsync(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 SetAutofillBlacklistedUrisAsync(List<string> value, string userId = null);
Task<bool?> GetAutofillTileAddedAsync();
@@ -172,9 +178,33 @@ namespace Bit.Core.Abstractions
Task<string> GetAvatarColorAsync(string userId = null);
Task<string> GetPreLoginEmailAsync();
Task SetPreLoginEmailAsync(string value);
Task<AccountDecryptionOptions> GetAccountDecryptionOptions(string userId = null);
Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null);
Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null);
string GetLocale();
void SetLocale(string locale);
ConfigResponse GetConfigs();
void SetConfigs(ConfigResponse value);
Task<bool> GetShouldTrustDeviceAsync();
Task SetShouldTrustDeviceAsync(bool value);
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetPinProtectedAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task SetPinProtectedAsync(string value, string userId = null);
[Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
Task<EncString> GetPinProtectedKeyAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
Task SetPinProtectedKeyAsync(EncString value, string userId = null);
[Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetEncKeyEncryptedAsync(string userId = null);
[Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task SetEncKeyEncryptedAsync(string value, string userId = null);
[Obsolete("Left for migration purposes")]
Task SetKeyEncryptedAsync(string value, string userId = null);
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
Task<string> GetKeyEncryptedAsync(string userId = null);
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null);
}
}

View File

@@ -6,5 +6,6 @@ namespace Bit.Core.Abstractions
public interface IUserVerificationService
{
Task<bool> VerifyUser(string secret, VerificationType verificationType);
Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false);
}
}

View File

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

View File

@@ -1,4 +1,6 @@
namespace Bit.Core
using System;
namespace Bit.Core
{
public static class Constants
{
@@ -53,6 +55,7 @@
public const string AppLocaleKey = "appLocale";
public const string ClearSensitiveFields = "clearSensitiveFields";
public const string ForceUpdatePassword = "forceUpdatePassword";
public const string ShouldTrustDevice = "shouldTrustDevice";
public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43;
public const int SaveFileRequestCode = 44;
@@ -82,6 +85,9 @@
public static string VaultTimeoutKey(string userId) => $"vaultTimeout_{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 FoldersKey(string userId) => $"folders_{userId}";
public static string CollectionsKey(string userId) => $"collections_{userId}";
@@ -90,12 +96,11 @@
public static string NeverDomainsKey(string userId) => $"neverDomains_{userId}";
public static string SendsKey(string userId) => $"sends_{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 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 PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
public static string PinKeyEncryptedUserKeyKey(string userId) => $"pinKeyEncryptedUserKey_{userId}";
public static string PassGenOptionsKey(string userId) => $"passwordGenerationOptions_{userId}";
public static string PassGenHistoryKey(string userId) => $"generatedPasswordHistory_{userId}";
public static string TwoFactorTokenKey(string email) => $"twoFactorToken_{email}";
@@ -124,5 +129,12 @@
public static string PushCurrentTokenKey(string userId) => $"pushCurrentToken_{userId}";
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
[Obsolete]
public static string KeyKey(string userId) => $"key_{userId}";
[Obsolete]
public static string EncKeyKey(string userId) => $"encKey_{userId}";
[Obsolete]
public static string PinProtectedKey(string userId) => $"pinProtectedKey_{userId}";
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Bit.Core.Enums
{
public enum AuthRequestType : byte
{
AuthenticateAndUnlock = 0,
Unlock = 1,
AdminApproval = 2
}
}

View File

@@ -1,4 +1,7 @@
namespace Bit.Core.Enums
using System.Collections.Generic;
using System.Linq;
namespace Bit.Core.Enums
{
public enum DeviceType : byte
{
@@ -24,4 +27,24 @@
VivaldiExtension = 19,
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();
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Bit.Core.Exceptions
{
public class MasterKeyNullException : Exception
{
public MasterKeyNullException()
: base("MasterKey is null.")
{
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Bit.Core.Exceptions
{
public class UserAndMasterKeysNullException : Exception
{
public UserAndMasterKeysNullException()
: base("UserKey and MasterKey are null.")
{
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Bit.Core.Exceptions
{
public class UserKeyNullException : Exception
{
public UserKeyNullException()
: base("UserKey is null.")
{
}
}
}

View File

@@ -53,6 +53,7 @@ namespace Bit.Core.Models.Domain
HasPremiumPersonally = copy.HasPremiumPersonally;
AvatarColor = copy.AvatarColor;
ForcePasswordResetReason = copy.ForcePasswordResetReason;
UserDecryptionOptions = copy.UserDecryptionOptions;
}
public string UserId;
@@ -68,6 +69,7 @@ namespace Bit.Core.Models.Domain
public bool? EmailVerified;
public bool? HasPremiumPersonally;
public ForcePasswordResetReason? ForcePasswordResetReason;
public AccountDecryptionOptions UserDecryptionOptions;
}
public class AccountTokens
@@ -117,9 +119,14 @@ namespace Bit.Core.Models.Domain
public class AccountVolatileData
{
public SymmetricCryptoKey Key;
public EncString PinProtectedKey;
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;
[Obsolete("Jul 6 2023: PinProtectedKey has been deprecated in favor of UserKeyPinEphemeral. It remains here for migration during app upgrade.")]
public EncString PinProtectedKey;
}
}
}

View File

@@ -0,0 +1,27 @@
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 bool RequireSetPassword => !HasMasterPassword && KeyConnectorOption == null;
}
public class TrustedDeviceOption
{
public bool HasAdminApproval { get; set; }
public bool HasLoginApprovingDevice { get; set; }
public bool HasManageResetPasswordPermission { get; set; }
public string EncryptedPrivateKey { get; set; }
public string EncryptedUserKey { get; set; }
}
public class KeyConnectorOption
{
public string KeyConnectorUrl { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Bit.Core.Enums;
namespace Bit.Core.Models.Domain
@@ -8,6 +9,8 @@ namespace Bit.Core.Models.Domain
public bool TwoFactor { get; set; }
public bool CaptchaNeeded => !string.IsNullOrWhiteSpace(CaptchaSiteKey);
public string CaptchaSiteKey { get; set; }
// TODO: PM-3287 - Remove after 3 releases of backwards compatibility - Target release 2023.12
[Obsolete("Use AccountDecryptionOptions to determine if the user does not have a MP")]
public bool ResetMasterPassword { get; set; }
public bool ForcePasswordReset { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }

View File

@@ -0,0 +1,10 @@
using System;
namespace Bit.Core.Models.Domain
{
public class PendingAdminAuthRequest
{
public string Id { get; set; }
public byte[] PrivateKey { get; set; }
}
}

View File

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

View File

@@ -1,4 +1,6 @@
using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Request
{
public class PasswordlessCreateLoginRequest
@@ -25,10 +27,4 @@ namespace Bit.Core.Models.Request
public string FingerprintPhrase { get; set; }
}
public enum AuthRequestType : byte
{
AuthenticateAndUnlock = 0,
Unlock = 1
}
}

View File

@@ -8,7 +8,7 @@ namespace Bit.Core.Models.Request
bool requestApproved)
{
Key = key ?? throw new ArgumentNullException(nameof(key));
MasterPasswordHash = masterPasswordHash ?? throw new ArgumentNullException(nameof(masterPasswordHash));
MasterPasswordHash = masterPasswordHash;
DeviceIdentifier = deviceIdentifier ?? throw new ArgumentNullException(nameof(deviceIdentifier));
RequestApproved = requestApproved;
}

View 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; }
}
}

View File

@@ -0,0 +1,13 @@
using Bit.Core.Enums;
public class DeviceResponse
{
public string Id { get; set; }
public string 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; }
}

View File

@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Response
public bool ForcePasswordReset { get; set; }
public string KeyConnectorUrl { get; set; }
public MasterPasswordPolicyOptions MasterPasswordPolicy { get; set; }
public AccountDecryptionOptions UserDecryptionOptions { get; set; }
[JsonIgnore]
public KdfConfig KdfConfig => new KdfConfig(Kdf, KdfIterations, KdfMemory, KdfParallelism);
}

View File

@@ -211,12 +211,12 @@ namespace Bit.Core.Services
return SendAsync<DeleteAccountRequest, object>(HttpMethod.Delete, "/accounts", request, true, false);
}
public Task PostConvertToKeyConnector()
public Task PostConvertToKeyConnectorAsync()
{
return SendAsync<object, object>(HttpMethod.Post, "/accounts/convert-to-key-connector", null, true, false);
}
public Task PostSetKeyConnectorKey(SetKeyConnectorKeyRequest request)
public Task PostSetKeyConnectorKeyAsync(SetKeyConnectorKeyRequest request)
{
return SendAsync<SetKeyConnectorKeyRequest>(HttpMethod.Post, "/accounts/set-key-connector-key", request, true);
}
@@ -397,12 +397,38 @@ namespace Bit.Core.Services
#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)
{
return SendAsync<DeviceTokenRequest, object>(
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
#region Event APIs
@@ -460,7 +486,7 @@ namespace Bit.Core.Services
$"/organizations/{identifier}/auto-enroll-status", null, true, true);
}
public Task PostLeaveOrganization(string id)
public Task PostLeaveOrganizationAsync(string id)
{
return SendAsync<object, object>(HttpMethod.Post, $"/organizations/{id}/leave", null, true, false);
}
@@ -485,7 +511,7 @@ namespace Bit.Core.Services
#region Key Connector
public async Task<KeyConnectorUserKeyResponse> GetUserKeyFromKeyConnector(string keyConnectorUrl)
public async Task<KeyConnectorUserKeyResponse> GetMasterKeyFromKeyConnectorAsync(string keyConnectorUrl)
{
using (var requestMessage = new HttpRequestMessage())
{
@@ -515,7 +541,7 @@ namespace Bit.Core.Services
}
}
public async Task PostUserKeyToKeyConnector(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
public async Task PostMasterKeyToKeyConnectorAsync(string keyConnectorUrl, KeyConnectorUserKeyRequest request)
{
using (var requestMessage = new HttpRequestMessage())
{
@@ -565,9 +591,9 @@ namespace Bit.Core.Services
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}/response?code={accessCode}", null, false, true);
}
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest)
public Task<PasswordlessLoginResponse> PostCreateRequestAsync(PasswordlessCreateLoginRequest passwordlessCreateLoginRequest, AuthRequestType authRequestType)
{
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, $"/auth-requests", passwordlessCreateLoginRequest, false, true);
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Post, authRequestType == AuthRequestType.AdminApproval ? "/auth-requests/admin-request" : "/auth-requests", passwordlessCreateLoginRequest, authRequestType == AuthRequestType.AdminApproval, true);
}
public Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string encKey, string encMasterPasswordHash, string deviceIdentifier, bool requestApproved)
@@ -576,15 +602,6 @@ namespace Bit.Core.Services
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
#region Configs
@@ -610,7 +627,7 @@ namespace Bit.Core.Services
return accessToken;
}
public async Task<SsoPrevalidateResponse> PreValidateSso(string identifier)
public async Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier)
{
var path = "/account/prevalidate?domainHint=" + WebUtility.UrlEncode(identifier);
using (var requestMessage = new HttpRequestMessage())

View File

@@ -27,10 +27,13 @@ namespace Bit.Core.Services
private readonly IKeyConnectorService _keyConnectorService;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly IPolicyService _policyService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly IPasswordResetEnrollmentService _passwordResetEnrollmentService;
private readonly bool _setCryptoKeys;
private readonly LazyResolve<IWatchDeviceService> _watchDeviceService = new LazyResolve<IWatchDeviceService>();
private SymmetricCryptoKey _key;
private MasterKey _masterKey;
private UserKey _userKey;
private string _authedUserId;
private MasterPasswordPolicyOptions _masterPasswordPolicy;
@@ -46,10 +49,11 @@ namespace Bit.Core.Services
II18nService i18nService,
IPlatformUtilsService platformUtilsService,
IMessagingService messagingService,
IVaultTimeoutService vaultTimeoutService,
IKeyConnectorService keyConnectorService,
IPasswordGenerationService passwordGenerationService,
IPolicyService policyService,
IDeviceTrustCryptoService deviceTrustCryptoService,
IPasswordResetEnrollmentService passwordResetEnrollmentService,
bool setCryptoKeys = true)
{
_cryptoService = cryptoService;
@@ -64,6 +68,8 @@ namespace Bit.Core.Services
_keyConnectorService = keyConnectorService;
_passwordGenerationService = passwordGenerationService;
_policyService = policyService;
_deviceTrustCryptoService = deviceTrustCryptoService;
_passwordResetEnrollmentService = passwordResetEnrollmentService;
_setCryptoKeys = setCryptoKeys;
TwoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
@@ -145,8 +151,8 @@ namespace Bit.Core.Services
SelectedTwoFactorProviderType = null;
_2faForcePasswordResetReason = null;
var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
var hashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
var result = await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, null, null, null, captchaToken);
if (await RequirePasswordChangeAsync(email, masterPassword))
@@ -195,18 +201,50 @@ namespace Bit.Core.Services
return !await _policyService.EvaluateMasterPassword(strength.Value, masterPassword, _masterPasswordPolicy);
}
public async Task<AuthResult> LogInPasswordlessAsync(string email, string accessCode, string authRequestId, byte[] decryptionKey, string userKeyCiphered, string localHashedPasswordCiphered)
public async Task<AuthResult> LogInPasswordlessAsync(bool authingWithSso, string email, string accessCode, string authRequestId, byte[] decryptionKey, string encryptedAuthRequestKey, string masterKeyHash)
{
var decKey = await _cryptoService.RsaDecryptAsync(userKeyCiphered, decryptionKey);
var decPasswordHash = await _cryptoService.RsaDecryptAsync(localHashedPasswordCiphered, decryptionKey);
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decPasswordHash), null, null, null, new SymmetricCryptoKey(decKey), null, null,
var decryptedKey = await _cryptoService.RsaDecryptAsync(encryptedAuthRequestKey, decryptionKey);
// If the user is already authenticated, we can just set the key
// Note: We can't check for the existance of an access token here because the active user id may not be null
if (authingWithSso)
{
if (string.IsNullOrEmpty(masterKeyHash))
{
await _cryptoService.SetUserKeyAsync(new UserKey(decryptedKey));
}
else
{
var masterKey = new MasterKey(decryptedKey);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await _cryptoService.SetUserKeyAsync(userKey);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
return null;
}
// The approval device may not have a master key hash if it authenticated with a passwordless method
if (string.IsNullOrEmpty(masterKeyHash) && decryptionKey != null)
{
return await LogInHelperAsync(email, accessCode, null, null, null, null, null, null, null, null, null, authRequestId: authRequestId, userKey2FA: new UserKey(decryptedKey));
}
var decKeyHash = await _cryptoService.RsaDecryptAsync(masterKeyHash, decryptionKey);
return await LogInHelperAsync(email, accessCode, Encoding.UTF8.GetString(decKeyHash), null, null, null, new MasterKey(decryptedKey), null, null,
null, null, authRequestId: authRequestId);
}
public async Task<AuthResult> LogInSsoAsync(string code, string codeVerifier, string redirectUrl, string orgId)
{
SelectedTwoFactorProviderType = null;
return await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
var result = await LogInHelperAsync(null, null, null, code, codeVerifier, redirectUrl, null, orgId: orgId);
if (result.ForcePasswordReset)
{
await _stateService.SetForcePasswordResetReasonAsync(ForcePasswordResetReason.AdminForcePasswordReset);
}
return result;
}
public async Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken,
@@ -216,8 +254,8 @@ namespace Bit.Core.Services
{
CaptchaToken = captchaToken;
}
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _key,
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId);
var result = await LogInHelperAsync(Email, MasterPasswordHash, LocalMasterPasswordHash, Code, CodeVerifier, SsoRedirectUrl, _masterKey,
twoFactorProvider, twoFactorToken, remember, CaptchaToken, authRequestId: AuthRequestId, userKey2FA: _userKey);
// If we successfully authenticated and we have a saved _2faForcePasswordResetReason reason from LogInAsync()
if (!string.IsNullOrEmpty(_authedUserId) && _2faForcePasswordResetReason.HasValue)
@@ -236,8 +274,8 @@ namespace Bit.Core.Services
{
SelectedTwoFactorProviderType = null;
var key = await MakePreloginKeyAsync(masterPassword, email);
var hashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashPasswordAsync(masterPassword, key, HashPurpose.LocalAuthorization);
var hashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key);
var localHashedPassword = await _cryptoService.HashMasterKeyAsync(masterPassword, key, HashPurpose.LocalAuthorization);
return await LogInHelperAsync(email, hashedPassword, localHashedPassword, null, null, null, key, twoFactorProvider,
twoFactorToken, remember);
}
@@ -337,7 +375,7 @@ namespace Bit.Core.Services
// Helpers
private async Task<SymmetricCryptoKey> MakePreloginKeyAsync(string masterPassword, string email)
private async Task<MasterKey> MakePreloginKeyAsync(string masterPassword, string email)
{
email = email.Trim().ToLower();
KdfConfig kdfConfig = KdfConfig.Default;
@@ -356,13 +394,13 @@ namespace Bit.Core.Services
throw;
}
}
return await _cryptoService.MakeKeyAsync(masterPassword, email, kdfConfig);
return await _cryptoService.MakeMasterKeyAsync(masterPassword, email, kdfConfig);
}
private async Task<AuthResult> LogInHelperAsync(string email, string hashedPassword, string localHashedPassword,
string code, string codeVerifier, string redirectUrl, SymmetricCryptoKey key,
string code, string codeVerifier, string redirectUrl, MasterKey masterKey,
TwoFactorProviderType? twoFactorProvider = null, string twoFactorToken = null, bool? remember = null,
string captchaToken = null, string orgId = null, string authRequestId = null)
string captchaToken = null, string orgId = null, string authRequestId = null, UserKey userKey2FA = null)
{
var storedTwoFactorToken = await _tokenService.GetTwoFactorTokenAsync(email);
var appId = await _appIdService.GetAppIdAsync();
@@ -426,7 +464,8 @@ namespace Bit.Core.Services
Code = code;
CodeVerifier = codeVerifier;
SsoRedirectUrl = redirectUrl;
_key = _setCryptoKeys ? key : null;
_masterKey = _setCryptoKeys ? masterKey : null;
_userKey = userKey2FA;
TwoFactorProvidersData = response.TwoFactorResponse.TwoFactorProviders2;
result.TwoFactorProviders = response.TwoFactorResponse.TwoFactorProviders2;
CaptchaToken = response.TwoFactorResponse.CaptchaToken;
@@ -459,6 +498,7 @@ namespace Bit.Core.Services
ForcePasswordResetReason = result.ForcePasswordReset
? ForcePasswordResetReason.AdminForcePasswordReset
: (ForcePasswordResetReason?)null,
UserDecryptionOptions = tokenResponse.UserDecryptionOptions,
},
new Account.AccountTokens()
{
@@ -470,24 +510,55 @@ namespace Bit.Core.Services
_messagingService.Send("accountAdded");
if (_setCryptoKeys)
{
if (key != null)
{
await _cryptoService.SetKeyAsync(key);
}
if (localHashedPassword != null)
{
await _cryptoService.SetKeyHashAsync(localHashedPassword);
await _cryptoService.SetMasterKeyHashAsync(localHashedPassword);
await _cryptoService.SetMasterKeyAsync(masterKey);
}
// Trusted Device
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
var hasUserKey = await _cryptoService.HasUserKeyAsync();
if (decryptOptions?.TrustedDeviceOption != null && !hasUserKey &&
decryptOptions.TrustedDeviceOption.EncryptedPrivateKey != null &&
decryptOptions.TrustedDeviceOption.EncryptedUserKey != null)
{
var key = await _deviceTrustCryptoService.DecryptUserKeyWithDeviceKeyAsync(decryptOptions.TrustedDeviceOption.EncryptedPrivateKey,
decryptOptions.TrustedDeviceOption.EncryptedUserKey);
if (key != null)
{
await _cryptoService.SetUserKeyAsync(key);
}
}
if (code == null || tokenResponse.Key != null)
{
if (tokenResponse.KeyConnectorUrl != null)
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(tokenResponse.Key);
// Key Connector
if (!string.IsNullOrEmpty(tokenResponse.KeyConnectorUrl) || !string.IsNullOrEmpty(decryptOptions?.KeyConnectorOption?.KeyConnectorUrl))
{
await _keyConnectorService.GetAndSetKey(tokenResponse.KeyConnectorUrl);
var url = tokenResponse.KeyConnectorUrl ?? decryptOptions.KeyConnectorOption.KeyConnectorUrl;
await _keyConnectorService.SetMasterKeyFromUrlAsync(url);
}
await _cryptoService.SetEncKeyAsync(tokenResponse.Key);
// Login with Device
if (masterKey != null && !string.IsNullOrEmpty(authRequestId))
{
await _cryptoService.SetMasterKeyAsync(masterKey);
}
else if (userKey2FA != null)
{
await _cryptoService.SetUserKeyAsync(userKey2FA);
}
// Decrypt UserKey with MasterKey
masterKey ??= await _stateService.GetMasterKeyAsync();
if (masterKey != null)
{
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.
if (tokenResponse.PrivateKey == null)
@@ -505,40 +576,20 @@ namespace Bit.Core.Services
catch { }
}
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
await _cryptoService.SetUserPrivateKeyAsync(tokenResponse.PrivateKey);
}
else if (tokenResponse.KeyConnectorUrl != null)
{
// SSO Key Connector Onboarding
var password = await _cryptoFunctionService.RandomBytesAsync(64);
var k = await _cryptoService.MakeKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
var keyConnectorRequest = new KeyConnectorUserKeyRequest(k.EncKeyB64);
await _cryptoService.SetKeyAsync(k);
var encKey = await _cryptoService.MakeEncKeyAsync(k);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
var keyPair = await _cryptoService.MakeKeyPairAsync();
try
// New User has tokenResponse.Key == null
if (tokenResponse.Key == null)
{
await _apiService.PostUserKeyToKeyConnector(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
await _keyConnectorService.ConvertNewUserToKeyConnectorAsync(orgId, tokenResponse);
}
catch (Exception e)
else
{
throw new Exception("Unable to reach Key Connector", e);
await _keyConnectorService.SetMasterKeyFromUrlAsync(tokenResponse.KeyConnectorUrl);
}
var keys = new KeysRequest
{
PublicKey = keyPair.Item1,
EncryptedPrivateKey = keyPair.Item2.EncryptedString
};
var setPasswordRequest = new SetKeyConnectorKeyRequest(
encKey.Item2.EncryptedString, keys, tokenResponse.KdfConfig, orgId
);
await _apiService.PostSetKeyConnectorKey(setPasswordRequest);
}
}
_authedUserId = _tokenService.GetUserId();
@@ -549,7 +600,7 @@ namespace Bit.Core.Services
private void ClearState()
{
_key = null;
_masterKey = null;
Email = null;
CaptchaToken = null;
MasterPasswordHash = null;
@@ -575,14 +626,22 @@ namespace Bit.Core.Services
var activeRequests = requests.Where(r => !r.IsAnswered && !r.IsExpired).OrderByDescending(r => r.CreationDate).ToList();
return await PopulateFingerprintPhrasesAsync(activeRequests);
}
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
{
var response = await _apiService.GetAuthRequestAsync(id);
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
try
{
var response = await _apiService.GetAuthRequestAsync(id);
return await PopulateFingerprintPhraseAsync(response, await _stateService.GetEmailAsync());
}
catch (ApiException ex) when (ex.Error?.StatusCode == System.Net.HttpStatusCode.NotFound)
{
// Thrown when request expires and purge job erases it from the db
return null;
}
}
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResponseAsync(string id, string accessCode)
/// <inheritdoc />
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginResquestAsync(string id, string accessCode)
{
return await _apiService.GetAuthResponseAsync(id, accessCode);
}
@@ -590,15 +649,36 @@ namespace Bit.Core.Services
public async Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved)
{
var publicKey = CoreHelpers.Base64UrlDecode(pubKey);
var masterKey = await _cryptoService.GetKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(masterKey.EncKey, publicKey);
var encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(await _stateService.GetKeyHashAsync()), publicKey);
var masterKey = await _cryptoService.GetMasterKeyAsync();
byte[] keyToEncrypt = null;
EncString encryptedMasterPassword = null;
if (masterKey == null)
{
var userKey = await _cryptoService.GetUserKeyAsync();
if (userKey == null)
{
throw new UserAndMasterKeysNullException();
}
keyToEncrypt = userKey.Key;
}
else
{
keyToEncrypt = masterKey.Key;
var keyHash = await _stateService.GetKeyHashAsync();
if (!string.IsNullOrEmpty(keyHash))
{
encryptedMasterPassword = await _cryptoService.RsaEncryptAsync(Encoding.UTF8.GetBytes(keyHash), publicKey);
}
}
var encryptedKey = await _cryptoService.RsaEncryptAsync(keyToEncrypt, publicKey);
var deviceId = await _appIdService.GetAppIdAsync();
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword.EncryptedString, deviceId, requestApproved);
var response = await _apiService.PutAuthRequestAsync(id, encryptedKey.EncryptedString, encryptedMasterPassword?.EncryptedString, deviceId, requestApproved);
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 keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
@@ -606,8 +686,8 @@ namespace Bit.Core.Services
var fingerprintPhrase = string.Join("-", generatedFingerprintPhrase);
var publicB64 = Convert.ToBase64String(keyPair.Item1);
var accessCode = await _passwordGenerationService.GeneratePasswordAsync(PasswordGenerationOptions.CreateDefault.WithLength(25));
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, AuthRequestType.AuthenticateAndUnlock, fingerprintPhrase);
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest);
var passwordlessCreateLoginRequest = new PasswordlessCreateLoginRequest(email, publicB64, deviceId, accessCode, authRequestType, fingerprintPhrase);
var response = await _apiService.PostCreateRequestAsync(passwordlessCreateLoginRequest, authRequestType);
if (response != null)
{
@@ -638,5 +718,22 @@ namespace Bit.Core.Services
passwordlessLogin.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(userEmail, CoreHelpers.Base64UrlDecode(passwordlessLogin.PublicKey)));
return passwordlessLogin;
}
public async Task CreateNewSsoUserAsync(string organizationSsoId)
{
var orgAutoEnrollStatusResponse = await _apiService.GetOrganizationAutoEnrollStatusAsync(organizationSsoId);
var randomBytes = _cryptoFunctionService.RandomBytes(64);
var userKey = new UserKey(randomBytes);
var (userPubKey, userPrivKey) = await _cryptoService.MakeKeyPairAsync(userKey);
await _apiService.PostAccountKeysAsync(new KeysRequest
{
PublicKey = userPubKey,
EncryptedPrivateKey = userPrivKey.EncryptedString
});
await _stateService.SetUserKeyAsync(userKey);
await _stateService.SetPrivateKeyEncryptedAsync(userPrivKey.EncryptedString);
await _passwordResetEnrollmentService.EnrollAsync(orgAutoEnrollStatusResponse.Id);
}
}
}

View File

@@ -250,10 +250,9 @@ namespace Bit.Core.Services
{
try
{
var hashKey = await _cryptoService.HasKeyAsync();
if (!hashKey)
if (!await _cryptoService.HasUserKeyAsync())
{
throw new Exception("No key.");
throw new UserKeyNullException();
}
var decCiphers = new List<CipherView>();
async Task decryptAndAddCipherAsync(Cipher cipher)
@@ -591,9 +590,9 @@ namespace Bit.Core.Services
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
{
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
var (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(cipher.OrganizationId);
var encFileName = await _cryptoService.EncryptAsync(filename, encKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
CipherResponse response;
@@ -601,7 +600,7 @@ namespace Bit.Core.Services
{
var request = new AttachmentRequest
{
Key = orgEncAttachmentKey.EncryptedString,
Key = protectedAttachmentKey.EncryptedString,
FileName = encFileName.EncryptedString,
FileSize = encFileData.Buffer.Length,
};
@@ -612,7 +611,7 @@ namespace Bit.Core.Services
}
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
{
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, protectedAttachmentKey);
}
var userId = await _stateService.GetActiveUserIdAsync();
@@ -830,6 +829,14 @@ namespace Bit.Core.Services
// 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,
string organizationId)
{
@@ -841,14 +848,16 @@ namespace Bit.Core.Services
var bytes = await attachmentResponse.Content.ReadAsByteArrayAsync();
var decBytes = await _cryptoService.DecryptFromBytesAsync(bytes, null);
var key = await _cryptoService.GetOrgKeyAsync(organizationId);
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, key);
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
var encData = await _cryptoService.EncryptToBytesAsync(decBytes, dataEncKey.Item1);
var (attachmentKey, protectedAttachmentKey, encKey) = await MakeAttachmentKeyAsync(organizationId);
var encFileName = await _cryptoService.EncryptAsync(attachmentView.FileName, encKey);
var encFileData = await _cryptoService.EncryptToBytesAsync(decBytes, attachmentKey);
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
var fd = new MultipartFormDataContent(boundary);
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString);
fd.Add(new StringContent(protectedAttachmentKey.EncryptedString), "key");
fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@

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 RemoveTrustedDeviceAsync()
{
await SetDeviceKeyAsync(null);
}
public async Task<DeviceResponse> TrustDeviceAsync()
{
// Attempt to get user key
var userKey = await _cryptoService.GetUserKeyAsync();
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.Key, 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> GetShouldTrustDeviceAsync()
{
return await _stateService.GetShouldTrustDeviceAsync();
}
public async Task SetShouldTrustDeviceAsync(bool value)
{
await _stateService.SetShouldTrustDeviceAsync(value);
}
public async Task<DeviceResponse> TrustDeviceIfNeededAsync()
{
if (!await GetShouldTrustDeviceAsync())
{
return null;
}
var response = await TrustDeviceAsync();
await SetShouldTrustDeviceAsync(false);
return response;
}
public async Task<bool> IsDeviceTrustedAsync()
{
var existingDeviceKey = await GetDeviceKeyAsync();
return existingDeviceKey != null;
}
public async Task<UserKey> DecryptUserKeyWithDeviceKeyAsync(string encryptedDevicePrivateKey, string encryptedUserKey)
{
var existingDeviceKey = await GetDeviceKeyAsync();
if (existingDeviceKey == null)
{
// User doesn't have a device key anymore so device is untrusted
return null;
}
// Attempt to decrypt encryptedDevicePrivateKey with device key
var devicePrivateKeyBytes = await _cryptoService.DecryptToBytesAsync(
new EncString(encryptedDevicePrivateKey),
existingDeviceKey
);
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey
var userKeyBytes = await _cryptoService.RsaDecryptAsync(encryptedUserKey, devicePrivateKeyBytes);
return new UserKey(userKeyBytes);
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
@@ -77,10 +78,10 @@ namespace Bit.Core.Services
{
return _decryptedFolderCache;
}
var hasKey = await _cryptoService.HasKeyAsync();
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
{
throw new Exception("No key.");
throw new UserKeyNullException();
}
var decFolders = new List<FolderView>();
async Task decryptAndAddFolderAsync(Folder folder)

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Models.Response;
namespace Bit.Core.Services
{
@@ -12,26 +13,28 @@ namespace Bit.Core.Services
private readonly ICryptoService _cryptoService;
private readonly ITokenService _tokenService;
private readonly IApiService _apiService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly IOrganizationService _organizationService;
public KeyConnectorService(IStateService stateService, ICryptoService cryptoService,
ITokenService tokenService, IApiService apiService, OrganizationService organizationService)
ITokenService tokenService, IApiService apiService, ICryptoFunctionService cryptoFunctionService, OrganizationService organizationService)
{
_stateService = stateService;
_cryptoService = cryptoService;
_tokenService = tokenService;
_apiService = apiService;
_cryptoFunctionService = cryptoFunctionService;
_organizationService = organizationService;
}
public async Task GetAndSetKey(string url)
public async Task SetMasterKeyFromUrlAsync(string url)
{
try
{
var userKeyResponse = await _apiService.GetUserKeyFromKeyConnector(url);
var keyArr = Convert.FromBase64String(userKeyResponse.Key);
var k = new SymmetricCryptoKey(keyArr);
await _cryptoService.SetKeyAsync(k);
var masterKeyResponse = await _apiService.GetMasterKeyFromKeyConnectorAsync(url);
var masterKeyBytes = Convert.FromBase64String(masterKeyResponse.Key);
var masterKey = new MasterKey(masterKeyBytes);
await _cryptoService.SetMasterKeyAsync(masterKey);
}
catch (Exception e)
{
@@ -39,17 +42,17 @@ namespace Bit.Core.Services
}
}
public async Task SetUsesKeyConnector(bool usesKeyConnector)
public async Task SetUsesKeyConnectorAsync(bool usesKeyConnector)
{
await _stateService.SetUsesKeyConnectorAsync(usesKeyConnector);
}
public async Task<bool> GetUsesKeyConnector()
public async Task<bool> GetUsesKeyConnectorAsync()
{
return await _stateService.GetUsesKeyConnectorAsync();
}
public async Task<Organization> GetManagingOrganization()
public async Task<Organization> GetManagingOrganizationAsync()
{
var orgs = await _organizationService.GetAllAsync();
return orgs.Find(o =>
@@ -57,31 +60,66 @@ namespace Bit.Core.Services
!o.IsAdmin);
}
public async Task MigrateUser()
public async Task MigrateUserAsync()
{
var organization = await GetManagingOrganization();
var key = await _cryptoService.GetKeyAsync();
var organization = await GetManagingOrganizationAsync();
var masterKey = await _cryptoService.GetMasterKeyAsync();
try
{
var keyConnectorRequest = new KeyConnectorUserKeyRequest(key.EncKeyB64);
await _apiService.PostUserKeyToKeyConnector(organization.KeyConnectorUrl, keyConnectorRequest);
var keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.EncKeyB64);
await _apiService.PostMasterKeyToKeyConnectorAsync(organization.KeyConnectorUrl, keyConnectorRequest);
}
catch (Exception e)
{
throw new Exception("Unable to reach Key Connector", e);
}
await _apiService.PostConvertToKeyConnector();
await _apiService.PostConvertToKeyConnectorAsync();
}
public async Task<bool> UserNeedsMigration()
public async Task<bool> UserNeedsMigrationAsync()
{
var loggedInUsingSso = await _tokenService.GetIsExternal();
var requiredByOrganization = await GetManagingOrganization() != null;
var userIsNotUsingKeyConnector = !await GetUsesKeyConnector();
var requiredByOrganization = await GetManagingOrganizationAsync() != null;
var userIsNotUsingKeyConnector = !await GetUsesKeyConnectorAsync();
return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector;
}
public async Task ConvertNewUserToKeyConnectorAsync(string orgId, IdentityTokenResponse tokenResponse)
{
// SSO Key Connector Onboarding
var password = await _cryptoFunctionService.RandomBytesAsync(64);
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(Convert.ToBase64String(password), _tokenService.GetEmail(), tokenResponse.KdfConfig);
var keyConnectorRequest = new KeyConnectorUserKeyRequest(newMasterKey.EncKeyB64);
await _cryptoService.SetMasterKeyAsync(newMasterKey);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
newMasterKey,
await _cryptoService.MakeUserKeyAsync());
await _cryptoService.SetUserKeyAsync(newUserKey);
try
{
await _apiService.PostMasterKeyToKeyConnectorAsync(tokenResponse.KeyConnectorUrl, keyConnectorRequest);
}
catch (Exception e)
{
throw new Exception("Unable to reach Key Connector", e);
}
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync();
var keys = new KeysRequest
{
PublicKey = newPublicKey,
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
};
var setPasswordRequest = new SetKeyConnectorKeyRequest(
newProtectedUserKey.EncryptedString, keys, tokenResponse.KdfConfig, orgId
);
await _apiService.PostSetKeyConnectorKeyAsync(setPasswordRequest);
}
}
}

View File

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

View File

@@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
public class PasswordResetEnrollmentService : IPasswordResetEnrollmentService
{
private readonly IApiService _apiService;
private readonly ICryptoService _cryptoService;
private readonly IOrganizationService _organizationService;
private readonly IStateService _stateService;
public PasswordResetEnrollmentService(IApiService apiService,
ICryptoService cryptoService,
IOrganizationService organizationService,
IStateService stateService)
{
_apiService = apiService;
_cryptoService = cryptoService;
_organizationService = organizationService;
_stateService = stateService;
}
public async Task EnrollIfRequiredAsync(string organizationSsoId)
{
var orgAutoEnrollStatusResponse = await _apiService.GetOrganizationAutoEnrollStatusAsync(organizationSsoId);
if (!orgAutoEnrollStatusResponse?.ResetPasswordEnabled ?? false)
{
await EnrollAsync(orgAutoEnrollStatusResponse.Id);
}
}
public async Task EnrollAsync(string organizationId)
{
var orgKeyResponse = await _apiService.GetOrganizationKeysAsync(organizationId);
if (orgKeyResponse == null)
{
throw new Exception("Organization keys missing");
}
var userId = await _stateService.GetActiveUserIdAsync();
var userKey = await _cryptoService.GetUserKeyAsync();
var orgPublicKey = CoreHelpers.Base64UrlDecode(orgKeyResponse.PublicKey);
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, orgPublicKey);
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.ResetPasswordKey = encryptedKey.EncryptedString;
await _apiService.PutOrganizationUserResetPasswordEnrollmentAsync(
organizationId,
userId,
resetRequest
);
}
}
}

View File

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

View File

@@ -241,6 +241,19 @@ namespace Bit.Core.Services
))?.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)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -302,12 +315,70 @@ namespace Bit.Core.Services
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)
{
if (userId == null)
{
userId = await GetActiveUserIdAsync();
}
if (!await IsAuthenticatedAsync(userId))
{
return false;
@@ -353,36 +424,36 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.ProtectedPinKey(reconciledOptions.UserId), value, reconciledOptions);
}
public async Task<string> GetPinProtectedAsync(string userId = null)
public async Task<EncString> GetPinKeyEncryptedUserKeyAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
var key = await _storageMediatorService.GetAsync<string>(
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), false);
return key != null ? new EncString(key) : null;
}
public async Task SetPinProtectedAsync(string value, string userId = null)
public async Task SetPinKeyEncryptedUserKeyAsync(EncString value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
await _storageMediatorService.SaveAsync(
await ComposeKeyAsync(Constants.PinKeyEncryptedUserKeyKey, userId), value?.EncryptedString, false);
}
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
public async Task<EncString> GetPinKeyEncryptedUserKeyEphemeralAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.PinProtectedKey;
))?.VolatileData?.PinKeyEncryptedUserKeyEphemeral;
}
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
public async Task SetPinKeyEncryptedUserKeyEphemeralAsync(EncString value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.PinProtectedKey = value;
account.VolatileData.PinKeyEncryptedUserKeyEphemeral = value;
await SaveAccountAsync(account, reconciledOptions);
}
public async Task SetKdfConfigurationAsync(KdfConfig config, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -395,35 +466,6 @@ namespace Bit.Core.Services
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)
{
@@ -439,19 +481,6 @@ namespace Bit.Core.Services
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)
{
@@ -482,6 +511,25 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.EncPrivateKeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
public async Task<SymmetricCryptoKey> GetDeviceKeyAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
var deviceKeyB64 = await _storageMediatorService.GetAsync<string>(Constants.DeviceKeyKey(reconciledOptions.UserId), true);
if (string.IsNullOrEmpty(deviceKeyB64))
{
return null;
}
return new SymmetricCryptoKey(Convert.FromBase64String(deviceKeyB64));
}
public async Task SetDeviceKeyAsync(SymmetricCryptoKey value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await _storageMediatorService.SaveAsync(Constants.DeviceKeyKey(reconciledOptions.UserId), value?.KeyB64, true);
}
public async Task<List<string>> GetAutofillBlacklistedUrisAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
@@ -1280,6 +1328,42 @@ namespace Bit.Core.Services
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> GetShouldTrustDeviceAsync()
{
return await _storageMediatorService.GetAsync<bool>(Constants.ShouldTrustDevice);
}
public async Task SetShouldTrustDeviceAsync(bool value)
{
await _storageMediatorService.SaveAsync(Constants.ShouldTrustDevice, value);
}
public async Task<PendingAdminAuthRequest> GetPendingAdminAuthRequestAsync(string userId = null)
{
try
{
// GetAsync will throw an ArgumentException exception if there isn't a value to deserialize
return await _storageMediatorService.GetAsync<PendingAdminAuthRequest>(await ComposeKeyAsync(Constants.PendingAdminAuthRequest, userId), true);
}
catch (ArgumentException)
{
return null;
}
}
public async Task SetPendingAdminAuthRequestAsync(PendingAdminAuthRequest value, string userId = null)
{
await _storageMediatorService.SaveAsync(await ComposeKeyAsync(Constants.PendingAdminAuthRequest, userId), value, true);
}
public ConfigResponse GetConfigs()
{
return _storageMediatorService.Get<ConfigResponse>(Constants.ConfigsKey);
@@ -1447,28 +1531,32 @@ namespace Bit.Core.Services
}
// Non-state storage
await SetProtectedPinAsync(null, userId);
await SetPinProtectedAsync(null, userId);
await SetKeyEncryptedAsync(null, userId);
await SetKeyHashAsync(null, userId);
await SetEncKeyEncryptedAsync(null, userId);
await SetOrgKeysEncryptedAsync(null, userId);
await SetPrivateKeyEncryptedAsync(null, userId);
await SetLastActiveTimeAsync(null, userId);
await SetPreviousPageInfoAsync(null, userId);
await SetInvalidUnlockAttemptsAsync(null, userId);
await SetLocalDataAsync(null, userId);
await SetEncryptedCiphersAsync(null, userId);
await SetEncryptedCollectionsAsync(null, userId);
await SetLastSyncAsync(null, userId);
await SetEncryptedFoldersAsync(null, userId);
await SetEncryptedPoliciesAsync(null, userId);
await SetUsesKeyConnectorAsync(null, userId);
await SetOrganizationsAsync(null, userId);
await SetEncryptedPasswordGenerationHistoryAsync(null, userId);
await SetEncryptedSendsAsync(null, userId);
await SetSettingsAsync(null, userId);
await SetApprovePasswordlessLoginsAsync(null, userId);
await Task.WhenAll(
SetUserKeyAutoUnlockAsync(null, userId),
SetUserKeyBiometricUnlockAsync(null, userId),
SetProtectedPinAsync(null, userId),
SetPinKeyEncryptedUserKeyAsync(null, userId),
SetKeyHashAsync(null, userId),
SetOrgKeysEncryptedAsync(null, userId),
SetPrivateKeyEncryptedAsync(null, userId),
SetLastActiveTimeAsync(null, userId),
SetPreviousPageInfoAsync(null, userId),
SetInvalidUnlockAttemptsAsync(null, userId),
SetLocalDataAsync(null, userId),
SetEncryptedCiphersAsync(null, userId),
SetEncryptedCollectionsAsync(null, userId),
SetLastSyncAsync(null, userId),
SetEncryptedFoldersAsync(null, userId),
SetEncryptedPoliciesAsync(null, userId),
SetUsesKeyConnectorAsync(null, userId),
SetOrganizationsAsync(null, userId),
SetEncryptedPasswordGenerationHistoryAsync(null, userId),
SetEncryptedSendsAsync(null, userId),
SetSettingsAsync(null, userId),
SetApprovePasswordlessLoginsAsync(null, userId),
SetEncKeyEncryptedAsync(null, userId),
SetKeyEncryptedAsync(null, userId),
SetPinProtectedAsync(null, userId));
}
private async Task ScaffoldNewAccountAsync(Account account)
@@ -1656,5 +1744,79 @@ namespace Bit.Core.Services
await SetValueAsync(Constants.LastUserShouldConnectToWatchKey,
shouldConnect ?? await GetShouldConnectToWatchAsync(), await GetDefaultStorageOptionsAsync());
}
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
public async Task<string> GetPinProtectedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await GetValueAsync<string>(Constants.PinProtectedKey(reconciledOptions.UserId), reconciledOptions);
}
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead")]
public async Task SetPinProtectedAsync(string value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await SetValueAsync(Constants.PinProtectedKey(reconciledOptions.UserId), value, reconciledOptions);
}
[Obsolete("Use GetPinKeyEncryptedUserKeyEphemeralAsync instead, left for migration purposes")]
public async Task<EncString> GetPinProtectedKeyAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.PinProtectedKey;
}
[Obsolete("Use SetPinKeyEncryptedUserKeyEphemeralAsync instead")]
public async Task SetPinProtectedKeyAsync(EncString value, string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultInMemoryOptionsAsync());
var account = await GetAccountAsync(reconciledOptions);
account.VolatileData.PinProtectedKey = value;
await SaveAccountAsync(account, reconciledOptions);
}
[Obsolete("Use GetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
public async Task<string> GetEncKeyEncryptedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
return await GetValueAsync<string>(Constants.EncKeyKey(reconciledOptions.UserId), reconciledOptions);
}
[Obsolete("Use SetMasterKeyEncryptedUserKeyAsync instead, left for migration purposes")]
public async Task SetEncKeyEncryptedAsync(string value, string userId)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultStorageOptionsAsync());
await SetValueAsync(Constants.EncKeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
[Obsolete("Left for migration purposes")]
public async Task SetKeyEncryptedAsync(string value, string userId)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultSecureStorageOptionsAsync());
await SetValueAsync(Constants.KeyKey(reconciledOptions.UserId), value, reconciledOptions);
}
[Obsolete("Use GetUserKeyAutoUnlock instead, left for migration purposes")]
public async Task<string> GetKeyEncryptedAsync(string userId = null)
{
var reconciledOptions = ReconcileOptions(new StorageOptions { UserId = userId },
await GetDefaultSecureStorageOptionsAsync());
return await GetValueAsync<string>(Constants.KeyKey(reconciledOptions.UserId), reconciledOptions);
}
[Obsolete("Use GetMasterKeyAsync instead, left for migration purposes")]
public async Task<SymmetricCryptoKey> GetKeyDecryptedAsync(string userId = null)
{
return (await GetAccountAsync(
ReconcileOptions(new StorageOptions { UserId = userId }, await GetDefaultInMemoryOptionsAsync())
))?.VolatileData?.Key;
}
}
}

View File

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

View File

@@ -11,14 +11,18 @@ namespace Bit.Core.Services
private readonly IPlatformUtilsService _platformUtilsService;
private readonly II18nService _i18nService;
private readonly ICryptoService _cryptoService;
private readonly IStateService _stateService;
private readonly IKeyConnectorService _keyConnectorService;
public UserVerificationService(IApiService apiService, IPlatformUtilsService platformUtilsService,
II18nService i18nService, ICryptoService cryptoService)
II18nService i18nService, ICryptoService cryptoService, IStateService stateService, IKeyConnectorService keyConnectorService)
{
_apiService = apiService;
_platformUtilsService = platformUtilsService;
_i18nService = i18nService;
_cryptoService = cryptoService;
_stateService = stateService;
_keyConnectorService = keyConnectorService;
}
async public Task<bool> VerifyUser(string secret, VerificationType verificationType)
@@ -63,5 +67,21 @@ namespace Bit.Core.Services
await _platformUtilsService.ShowDialogAsync(errorMessage);
}
public async Task<bool> HasMasterPasswordAsync(bool checkMasterKeyHash = false)
{
async Task<bool> CheckMasterKeyHashAsync()
{
return !checkMasterKeyHash || await _cryptoService.GetMasterKeyHashAsync() != null;
};
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
if (decryptOptions != null)
{
return decryptOptions.HasMasterPassword && await CheckMasterKeyHashAsync();
}
return !await _keyConnectorService.GetUsesKeyConnectorAsync() && await CheckMasterKeyHashAsync();
}
}
}

View File

@@ -1,12 +1,17 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
namespace Bit.Core.Services
{
public enum PinLockType
{
Disabled,
Persistent,
Transient
}
public class VaultTimeoutService : IVaultTimeoutService
{
private readonly ICryptoService _cryptoService;
@@ -18,7 +23,7 @@ namespace Bit.Core.Services
private readonly ISearchService _searchService;
private readonly IMessagingService _messagingService;
private readonly ITokenService _tokenService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private readonly Func<Tuple<string, bool>, Task> _lockedCallback;
private readonly Func<Tuple<string, bool, bool>, Task> _loggedOutCallback;
@@ -32,7 +37,7 @@ namespace Bit.Core.Services
ISearchService searchService,
IMessagingService messagingService,
ITokenService tokenService,
IKeyConnectorService keyConnectorService,
IUserVerificationService userVerificationService,
Func<Tuple<string, bool>, Task> lockedCallback,
Func<Tuple<string, bool, bool>, Task> loggedOutCallback)
{
@@ -45,7 +50,7 @@ namespace Bit.Core.Services
_searchService = searchService;
_messagingService = messagingService;
_tokenService = tokenService;
_keyConnectorService = keyConnectorService;
_userVerificationService = userVerificationService;
_lockedCallback = lockedCallback;
_loggedOutCallback = loggedOutCallback;
}
@@ -54,15 +59,30 @@ namespace Bit.Core.Services
public async Task<bool> IsLockedAsync(string userId = null)
{
var hasKey = await _cryptoService.HasKeyAsync(userId);
if (hasKey)
// When checking if we need to lock inactive account, we don't want to
// set the key, so we only check if the account has an auto unlock key
if (userId != null && await _stateService.GetActiveUserIdAsync() != userId)
{
var biometricSet = await IsBiometricLockSetAsync(userId);
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
return await _cryptoService.HasAutoUnlockKeyAsync(userId);
}
var biometricSet = await IsBiometricLockSetAsync(userId);
if (biometricSet && await _stateService.GetBiometricLockedAsync(userId))
{
return true;
}
if (!await _cryptoService.HasUserKeyAsync(userId))
{
if (!await _cryptoService.HasAutoUnlockKeyAsync(userId))
{
return true;
}
await _cryptoService.SetUserKeyAsync(await _cryptoService.GetAutoUnlockKeyAsync(userId), userId);
}
// Check again to verify auto key was set
var hasKey = await _cryptoService.HasUserKeyAsync(userId);
return !hasKey;
}
@@ -163,13 +183,15 @@ namespace Bit.Core.Services
userId = await _stateService.GetActiveUserIdAsync();
}
if (await _keyConnectorService.GetUsesKeyConnector())
if (!await _userVerificationService.HasMasterPasswordAsync())
{
var (isPinProtected, isPinProtectedWithKey) = await IsPinLockSetAsync(userId);
var pinLock = (isPinProtected && await _stateService.GetPinProtectedKeyAsync(userId) != null) ||
isPinProtectedWithKey;
var pinStatus = await GetPinLockTypeAsync(userId);
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
?? await _stateService.GetPinProtectedKeyAsync();
var pinEnabled = (pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
pinStatus == PinLockType.Persistent;
if (!pinLock && !await IsBiometricLockSetAsync())
if (!pinEnabled && !await IsBiometricLockSetAsync())
{
await LogOutAsync(userInitiated, userId);
return;
@@ -187,10 +209,11 @@ namespace Bit.Core.Services
}
}
await Task.WhenAll(
_cryptoService.ClearKeyAsync(userId),
_cryptoService.ClearUserKeyAsync(userId),
_cryptoService.ClearMasterKeyAsync(userId),
_stateService.SetUserKeyAutoUnlockAsync(null, userId),
_cryptoService.ClearOrgKeysAsync(true, userId),
_cryptoService.ClearKeyPairAsync(true, userId),
_cryptoService.ClearEncKeyAsync(true, userId));
_cryptoService.ClearKeyPairAsync(true, userId));
if (isActiveAccount)
{
@@ -214,15 +237,27 @@ namespace Bit.Core.Services
{
await _stateService.SetVaultTimeoutAsync(timeout);
await _stateService.SetVaultTimeoutActionAsync(action);
await _cryptoService.ToggleKeyAsync();
await _cryptoService.RefreshKeysAsync();
await _tokenService.ToggleTokensAsync();
}
public async Task<Tuple<bool, bool>> IsPinLockSetAsync(string userId = null)
public async Task<PinLockType> GetPinLockTypeAsync(string userId = null)
{
var protectedPin = await _stateService.GetProtectedPinAsync(userId);
var pinProtectedKey = await _stateService.GetPinProtectedAsync(userId);
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null);
// we can't depend on only the protected pin being set because old
// versions only used it for MP on Restart
var isPinEnabled = await _stateService.GetProtectedPinAsync(userId) != null;
var hasUserKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync(userId) != null;
var hasOldUserKeyPin = await _stateService.GetPinProtectedAsync(userId) != null;
if (hasUserKeyPin || hasOldUserKeyPin)
{
return PinLockType.Persistent;
}
else if (isPinEnabled && !hasUserKeyPin && !hasOldUserKeyPin)
{
return PinLockType.Transient;
}
return PinLockType.Disabled;
}
public async Task<bool> IsBiometricLockSetAsync(string userId = null)
@@ -233,8 +268,7 @@ namespace Bit.Core.Services
public async Task ClearAsync(string userId = null)
{
await _stateService.SetPinProtectedKeyAsync(null, userId);
await _stateService.SetProtectedPinAsync(null, userId);
await _cryptoService.ClearPinKeysAsync(userId);
}
public async Task<int?> GetVaultTimeout(string userId = null)

View File

@@ -53,11 +53,13 @@ namespace Bit.Core.Utilities
cryptoFunctionService);
searchService = new SearchService(cipherService, sendService);
var policyService = new PolicyService(stateService, organizationService);
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService,
var keyConnectorService = new KeyConnectorService(stateService, cryptoService, tokenService, apiService, cryptoFunctionService,
organizationService);
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
cryptoService, stateService, keyConnectorService);
var vaultTimeoutService = new VaultTimeoutService(cryptoService, stateService, platformUtilsService,
folderService, cipherService, collectionService, searchService, messagingService, tokenService,
keyConnectorService,
userVerificationService,
(extras) =>
{
messagingService.Send("locked", extras);
@@ -77,15 +79,15 @@ namespace Bit.Core.Utilities
});
var passwordGenerationService = new PasswordGenerationService(cryptoService, stateService, cryptoFunctionService, policyService);
var totpService = new TotpService(cryptoFunctionService);
var deviceTrustCryptoService = new DeviceTrustCryptoService(apiService, appIdService, cryptoFunctionService, cryptoService, stateService);
var passwordResetEnrollmentService = new PasswordResetEnrollmentService(apiService, cryptoService, organizationService, stateService);
var authService = new AuthService(cryptoService, cryptoFunctionService, apiService, stateService,
tokenService, appIdService, i18nService, platformUtilsService, messagingService, vaultTimeoutService,
keyConnectorService, passwordGenerationService, policyService);
tokenService, appIdService, i18nService, platformUtilsService, messagingService,
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService);
var exportService = new ExportService(folderService, cipherService, cryptoService);
var auditService = new AuditService(cryptoFunctionService, apiService);
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
var eventService = new EventService(apiService, stateService, organizationService, cipherService);
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
cryptoService);
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
var configService = new ConfigService(apiService, stateService, logger);
@@ -102,6 +104,8 @@ namespace Bit.Core.Utilities
Register<ISearchService>("searchService", searchService);
Register<IPolicyService>("policyService", policyService);
Register<ISyncService>("syncService", syncService);
Register<IKeyConnectorService>("keyConnectorService", keyConnectorService);
Register<IUserVerificationService>(userVerificationService);
Register<IVaultTimeoutService>("vaultTimeoutService", vaultTimeoutService);
Register<IPasswordGenerationService>("passwordGenerationService", passwordGenerationService);
Register<ITotpService>("totpService", totpService);
@@ -110,10 +114,10 @@ namespace Bit.Core.Utilities
Register<IAuditService>("auditService", auditService);
Register<IEnvironmentService>("environmentService", environmentService);
Register<IEventService>("eventService", eventService);
Register<IKeyConnectorService>("keyConnectorService", keyConnectorService);
Register<IUserVerificationService>("userVerificationService", userVerificationService);
Register<IUsernameGenerationService>(usernameGenerationService);
Register<IConfigService>(configService);
Register<IDeviceTrustCryptoService>(deviceTrustCryptoService);
Register<IPasswordResetEnrollmentService>(passwordResetEnrollmentService);
}
public static void Register<T>(string serviceName, T obj)