mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
migrate login strategies to new key model
- decrypt and set user symmetric key if Master Key is available - rename keys where applicable - update unit tests
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { AuthRequestPushNotification } from "../../models/response/notification.response";
|
import { AuthRequestPushNotification } from "../../models/response/notification.response";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
import { MasterKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { AuthenticationStatus } from "../enums/authentication-status";
|
import { AuthenticationStatus } from "../enums/authentication-status";
|
||||||
import { AuthResult } from "../models/domain/auth-result";
|
import { AuthResult } from "../models/domain/auth-result";
|
||||||
import {
|
import {
|
||||||
@@ -32,7 +32,7 @@ export abstract class AuthService {
|
|||||||
captchaResponse: string
|
captchaResponse: string
|
||||||
) => Promise<AuthResult>;
|
) => Promise<AuthResult>;
|
||||||
logOut: (callback: () => void) => void;
|
logOut: (callback: () => void) => void;
|
||||||
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
|
makePreloginKey: (masterPassword: string, email: string) => Promise<MasterKey>;
|
||||||
authingWithUserApiKey: () => boolean;
|
authingWithUserApiKey: () => boolean;
|
||||||
authingWithSso: () => boolean;
|
authingWithSso: () => boolean;
|
||||||
authingWithPassword: () => boolean;
|
authingWithPassword: () => boolean;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Organization } from "../../admin-console/models/domain/organization";
|
|||||||
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||||
|
|
||||||
export abstract class KeyConnectorService {
|
export abstract class KeyConnectorService {
|
||||||
getAndSetKey: (url?: string) => Promise<void>;
|
getAndSetMasterKey: (url?: string) => Promise<void>;
|
||||||
getManagingOrganization: () => Promise<Organization>;
|
getManagingOrganization: () => Promise<Organization>;
|
||||||
getUsesKeyConnector: () => Promise<boolean>;
|
getUsesKeyConnector: () => Promise<boolean>;
|
||||||
migrateUser: () => Promise<void>;
|
migrateUser: () => Promise<void>;
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ import {
|
|||||||
PasswordStrengthService,
|
PasswordStrengthService,
|
||||||
PasswordStrengthServiceAbstraction,
|
PasswordStrengthServiceAbstraction,
|
||||||
} from "../../tools/password-strength";
|
} from "../../tools/password-strength";
|
||||||
|
import {
|
||||||
|
MasterKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "../../types/csprng";
|
||||||
import { AuthService } from "../abstractions/auth.service";
|
import { AuthService } from "../abstractions/auth.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
@@ -37,7 +43,7 @@ const masterPassword = "password";
|
|||||||
const deviceId = Utils.newGuid();
|
const deviceId = Utils.newGuid();
|
||||||
const accessToken = "ACCESS_TOKEN";
|
const accessToken = "ACCESS_TOKEN";
|
||||||
const refreshToken = "REFRESH_TOKEN";
|
const refreshToken = "REFRESH_TOKEN";
|
||||||
const encKey = "ENC_KEY";
|
const userSymKey = "USER_SYM_KEY";
|
||||||
const privateKey = "PRIVATE_KEY";
|
const privateKey = "PRIVATE_KEY";
|
||||||
const captchaSiteKey = "CAPTCHA_SITE_KEY";
|
const captchaSiteKey = "CAPTCHA_SITE_KEY";
|
||||||
const kdf = 0;
|
const kdf = 0;
|
||||||
@@ -64,7 +70,7 @@ export function identityTokenResponseFactory(
|
|||||||
ForcePasswordReset: false,
|
ForcePasswordReset: false,
|
||||||
Kdf: kdf,
|
Kdf: kdf,
|
||||||
KdfIterations: kdfIterations,
|
KdfIterations: kdfIterations,
|
||||||
Key: encKey,
|
Key: userSymKey,
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
ResetMasterPassword: false,
|
ResetMasterPassword: false,
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
@@ -129,6 +135,20 @@ describe("LogInStrategy", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("base class", () => {
|
describe("base class", () => {
|
||||||
|
const userSymKeyBytesLength = 64;
|
||||||
|
const masterKeyBytesLength = 64;
|
||||||
|
let userSymKey: UserSymKey;
|
||||||
|
let masterKey: MasterKey;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userSymKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(userSymKeyBytesLength).buffer as CsprngArray
|
||||||
|
) as UserSymKey;
|
||||||
|
masterKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(masterKeyBytesLength).buffer as CsprngArray
|
||||||
|
) as MasterKey;
|
||||||
|
});
|
||||||
|
|
||||||
it("sets the local environment after a successful login", async () => {
|
it("sets the local environment after a successful login", async () => {
|
||||||
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
|
||||||
|
|
||||||
@@ -156,8 +176,6 @@ describe("LogInStrategy", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(cryptoService.setEncKey).toHaveBeenCalledWith(encKey);
|
|
||||||
expect(cryptoService.setEncPrivateKey).toHaveBeenCalledWith(privateKey);
|
|
||||||
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,6 +205,8 @@ describe("LogInStrategy", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
cryptoService.decryptUserSymKeyWithMasterKey.mockResolvedValue(userSymKey);
|
||||||
|
|
||||||
const result = await passwordLogInStrategy.logIn(credentials);
|
const result = await passwordLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
@@ -204,13 +224,15 @@ describe("LogInStrategy", () => {
|
|||||||
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
|
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
cryptoService.decryptUserSymKeyWithMasterKey.mockResolvedValue(userSymKey);
|
||||||
|
|
||||||
await passwordLogInStrategy.logIn(credentials);
|
await passwordLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
// User key must be set before the new RSA keypair is generated, otherwise we can't decrypt the EncKey
|
// User symmetric key must be set before the new RSA keypair is generated
|
||||||
expect(cryptoService.setKey).toHaveBeenCalled();
|
expect(cryptoService.setUserKey).toHaveBeenCalled();
|
||||||
expect(cryptoService.makeKeyPair).toHaveBeenCalled();
|
expect(cryptoService.makeKeyPair).toHaveBeenCalled();
|
||||||
expect(cryptoService.setKey.mock.invocationCallOrder[0]).toBeLessThan(
|
expect(cryptoService.setUserKey.mock.invocationCallOrder[0]).toBeLessThan(
|
||||||
cryptoService.makeKeyPair.mock.invocationCallOrder[0]
|
cryptoService.makeKeyPair.mock.invocationCallOrder[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ export abstract class LogInStrategy {
|
|||||||
| PasswordlessLogInCredentials
|
| PasswordlessLogInCredentials
|
||||||
): Promise<AuthResult>;
|
): Promise<AuthResult>;
|
||||||
|
|
||||||
// The user key comes from different sources depending on the login strategy
|
|
||||||
protected abstract setUserKey(response: IdentityTokenResponse): Promise<void>;
|
|
||||||
|
|
||||||
async logInTwoFactor(
|
async logInTwoFactor(
|
||||||
twoFactor: TokenTwoFactorRequest,
|
twoFactor: TokenTwoFactorRequest,
|
||||||
captchaResponse: string = null
|
captchaResponse: string = null
|
||||||
@@ -141,22 +138,34 @@ export abstract class LogInStrategy {
|
|||||||
await this.tokenService.setTwoFactorToken(response);
|
await this.tokenService.setTwoFactorToken(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.setMasterKey(response);
|
||||||
|
|
||||||
await this.setUserKey(response);
|
await this.setUserKey(response);
|
||||||
|
|
||||||
// Must come after the user Key is set, otherwise createKeyPairForOldAccount will fail
|
await this.setPrivateKey(response);
|
||||||
const newSsoUser = response.key == null;
|
|
||||||
if (!newSsoUser) {
|
|
||||||
await this.cryptoService.setEncKey(response.key);
|
|
||||||
await this.cryptoService.setEncPrivateKey(
|
|
||||||
response.privateKey ?? (await this.createKeyPairForOldAccount())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.messagingService.send("loggedIn");
|
this.messagingService.send("loggedIn");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The keys comes from different sources depending on the login strategy
|
||||||
|
protected abstract setMasterKey(response: IdentityTokenResponse): Promise<void>;
|
||||||
|
|
||||||
|
protected abstract setUserKey(response: IdentityTokenResponse): Promise<void>;
|
||||||
|
|
||||||
|
protected abstract setPrivateKey(response: IdentityTokenResponse): Promise<void>;
|
||||||
|
|
||||||
|
protected async createKeyPairForOldAccount() {
|
||||||
|
try {
|
||||||
|
const [publicKey, privateKey] = await this.cryptoService.makeKeyPair();
|
||||||
|
await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString));
|
||||||
|
return privateKey.encryptedString;
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async processTwoFactorResponse(response: IdentityTwoFactorResponse): Promise<AuthResult> {
|
private async processTwoFactorResponse(response: IdentityTwoFactorResponse): Promise<AuthResult> {
|
||||||
const result = new AuthResult();
|
const result = new AuthResult();
|
||||||
result.twoFactorProviders = response.twoFactorProviders2;
|
result.twoFactorProviders = response.twoFactorProviders2;
|
||||||
@@ -173,14 +182,4 @@ export abstract class LogInStrategy {
|
|||||||
result.captchaSiteKey = response.siteKey;
|
result.captchaSiteKey = response.siteKey;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createKeyPairForOldAccount() {
|
|
||||||
try {
|
|
||||||
const [publicKey, privateKey] = await this.cryptoService.makeKeyPair();
|
|
||||||
await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString));
|
|
||||||
return privateKey.encryptedString;
|
|
||||||
} catch (e) {
|
|
||||||
this.logService.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,17 +10,23 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
|
|||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
|
||||||
import {
|
import {
|
||||||
PasswordStrengthService,
|
PasswordStrengthService,
|
||||||
PasswordStrengthServiceAbstraction,
|
PasswordStrengthServiceAbstraction,
|
||||||
} from "../../tools/password-strength";
|
} from "../../tools/password-strength";
|
||||||
|
import {
|
||||||
|
MasterKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "../../types/csprng";
|
||||||
import { AuthService } from "../abstractions/auth.service";
|
import { AuthService } from "../abstractions/auth.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
|
||||||
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
|
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
|
||||||
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
|
||||||
|
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
|
||||||
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
|
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
|
||||||
|
|
||||||
@@ -31,11 +37,11 @@ const email = "hello@world.com";
|
|||||||
const masterPassword = "password";
|
const masterPassword = "password";
|
||||||
const hashedPassword = "HASHED_PASSWORD";
|
const hashedPassword = "HASHED_PASSWORD";
|
||||||
const localHashedPassword = "LOCAL_HASHED_PASSWORD";
|
const localHashedPassword = "LOCAL_HASHED_PASSWORD";
|
||||||
const preloginKey = new SymmetricCryptoKey(
|
const masterKey = new SymmetricCryptoKey(
|
||||||
Utils.fromB64ToArray(
|
Utils.fromB64ToArray(
|
||||||
"N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg=="
|
"N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg=="
|
||||||
)
|
)
|
||||||
);
|
) as MasterKey;
|
||||||
const deviceId = Utils.newGuid();
|
const deviceId = Utils.newGuid();
|
||||||
const masterPasswordPolicy = new MasterPasswordPolicyResponse({
|
const masterPasswordPolicy = new MasterPasswordPolicyResponse({
|
||||||
EnforceOnLogin: true,
|
EnforceOnLogin: true,
|
||||||
@@ -58,6 +64,7 @@ describe("PasswordLogInStrategy", () => {
|
|||||||
|
|
||||||
let passwordLogInStrategy: PasswordLogInStrategy;
|
let passwordLogInStrategy: PasswordLogInStrategy;
|
||||||
let credentials: PasswordLogInCredentials;
|
let credentials: PasswordLogInCredentials;
|
||||||
|
let tokenResponse: IdentityTokenResponse;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
cryptoService = mock<CryptoService>();
|
cryptoService = mock<CryptoService>();
|
||||||
@@ -76,7 +83,7 @@ describe("PasswordLogInStrategy", () => {
|
|||||||
appIdService.getAppId.mockResolvedValue(deviceId);
|
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||||
tokenService.decodeToken.mockResolvedValue({});
|
tokenService.decodeToken.mockResolvedValue({});
|
||||||
|
|
||||||
authService.makePreloginKey.mockResolvedValue(preloginKey);
|
authService.makePreloginKey.mockResolvedValue(masterKey);
|
||||||
|
|
||||||
cryptoService.hashPassword
|
cryptoService.hashPassword
|
||||||
.calledWith(masterPassword, expect.anything(), undefined)
|
.calledWith(masterPassword, expect.anything(), undefined)
|
||||||
@@ -102,10 +109,9 @@ describe("PasswordLogInStrategy", () => {
|
|||||||
authService
|
authService
|
||||||
);
|
);
|
||||||
credentials = new PasswordLogInCredentials(email, masterPassword);
|
credentials = new PasswordLogInCredentials(email, masterPassword);
|
||||||
|
tokenResponse = identityTokenResponseFactory(masterPasswordPolicy);
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
identityTokenResponseFactory(masterPasswordPolicy)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends master password credentials to the server", async () => {
|
it("sends master password credentials to the server", async () => {
|
||||||
@@ -127,15 +133,25 @@ describe("PasswordLogInStrategy", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sets the local environment after a successful login", async () => {
|
it("sets keys after a successful authentication", async () => {
|
||||||
|
const userSymKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(64).buffer as CsprngArray
|
||||||
|
) as UserSymKey;
|
||||||
|
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
cryptoService.decryptUserSymKeyWithMasterKey.mockResolvedValue(userSymKey);
|
||||||
|
|
||||||
await passwordLogInStrategy.logIn(credentials);
|
await passwordLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
expect(cryptoService.setKey).toHaveBeenCalledWith(preloginKey);
|
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey);
|
||||||
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword);
|
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword);
|
||||||
|
expect(cryptoService.setUserSymKeyMasterKey).toHaveBeenCalledWith(tokenResponse.key);
|
||||||
|
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userSymKey);
|
||||||
|
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not force the user to update their master password when there are no requirements", async () => {
|
it("does not force the user to update their master password when there are no requirements", async () => {
|
||||||
apiService.postIdentityToken.mockResolvedValueOnce(identityTokenResponseFactory(null));
|
apiService.postIdentityToken.mockResolvedValueOnce(identityTokenResponseFactory());
|
||||||
|
|
||||||
const result = await passwordLogInStrategy.logIn(credentials);
|
const result = await passwordLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { LogService } from "../../platform/abstractions/log.service";
|
|||||||
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { PasswordStrengthServiceAbstraction } from "../../tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "../../tools/password-strength";
|
||||||
|
import { MasterKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { AuthService } from "../abstractions/auth.service";
|
import { AuthService } from "../abstractions/auth.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
@@ -36,7 +36,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
|||||||
tokenRequest: PasswordTokenRequest;
|
tokenRequest: PasswordTokenRequest;
|
||||||
|
|
||||||
private localHashedPassword: string;
|
private localHashedPassword: string;
|
||||||
private key: SymmetricCryptoKey;
|
private masterKey: MasterKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options to track if the user needs to update their password due to a password that does not meet an organization's
|
* Options to track if the user needs to update their password due to a password that does not meet an organization's
|
||||||
@@ -71,12 +71,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserKey() {
|
override async logInTwoFactor(
|
||||||
await this.cryptoService.setKey(this.key);
|
|
||||||
await this.cryptoService.setKeyHash(this.localHashedPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
async logInTwoFactor(
|
|
||||||
twoFactor: TokenTwoFactorRequest,
|
twoFactor: TokenTwoFactorRequest,
|
||||||
captchaResponse: string
|
captchaResponse: string
|
||||||
): Promise<AuthResult> {
|
): Promise<AuthResult> {
|
||||||
@@ -96,18 +91,18 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async logIn(credentials: PasswordLogInCredentials) {
|
override async logIn(credentials: PasswordLogInCredentials) {
|
||||||
const { email, masterPassword, captchaToken, twoFactor } = credentials;
|
const { email, masterPassword, captchaToken, twoFactor } = credentials;
|
||||||
|
|
||||||
this.key = await this.authService.makePreloginKey(masterPassword, email);
|
this.masterKey = await this.authService.makePreloginKey(masterPassword, email);
|
||||||
|
|
||||||
// Hash the password early (before authentication) so we don't persist it in memory in plaintext
|
// Hash the password early (before authentication) so we don't persist it in memory in plaintext
|
||||||
this.localHashedPassword = await this.cryptoService.hashPassword(
|
this.localHashedPassword = await this.cryptoService.hashPassword(
|
||||||
masterPassword,
|
masterPassword,
|
||||||
this.key,
|
this.masterKey,
|
||||||
HashPurpose.LocalAuthorization
|
HashPurpose.LocalAuthorization
|
||||||
);
|
);
|
||||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, this.key);
|
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, this.masterKey);
|
||||||
|
|
||||||
this.tokenRequest = new PasswordTokenRequest(
|
this.tokenRequest = new PasswordTokenRequest(
|
||||||
email,
|
email,
|
||||||
@@ -118,6 +113,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [authResult, identityResponse] = await this.startLogIn();
|
const [authResult, identityResponse] = await this.startLogIn();
|
||||||
|
|
||||||
const masterPasswordPolicyOptions =
|
const masterPasswordPolicyOptions =
|
||||||
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
|
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
|
||||||
|
|
||||||
@@ -145,6 +141,27 @@ export class PasswordLogInStrategy extends LogInStrategy {
|
|||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async setMasterKey(response: IdentityTokenResponse) {
|
||||||
|
await this.cryptoService.setMasterKey(this.masterKey);
|
||||||
|
await this.cryptoService.setKeyHash(this.localHashedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
|
||||||
|
await this.cryptoService.setUserSymKeyMasterKey(response.key);
|
||||||
|
|
||||||
|
const masterKey = await this.cryptoService.getMasterKey();
|
||||||
|
if (masterKey) {
|
||||||
|
const userKey = await this.cryptoService.decryptUserSymKeyWithMasterKey(masterKey);
|
||||||
|
await this.cryptoService.setUserKey(userKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setPrivateKey(response: IdentityTokenResponse): Promise<void> {
|
||||||
|
await this.cryptoService.setPrivateKey(
|
||||||
|
response.privateKey ?? (await this.createKeyPairForOldAccount())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private getMasterPasswordPolicyOptionsFromResponse(
|
private getMasterPasswordPolicyOptionsFromResponse(
|
||||||
response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse
|
response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse
|
||||||
): MasterPasswordPolicyOptions {
|
): MasterPasswordPolicyOptions {
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { ApiService } from "../../abstractions/api.service";
|
||||||
|
import { AppIdService } from "../../abstractions/appId.service";
|
||||||
|
import { CryptoService } from "../../abstractions/crypto.service";
|
||||||
|
import { LogService } from "../../abstractions/log.service";
|
||||||
|
import { MessagingService } from "../../abstractions/messaging.service";
|
||||||
|
import { PlatformUtilsService } from "../../abstractions/platformUtils.service";
|
||||||
|
import { StateService } from "../../abstractions/state.service";
|
||||||
|
import { Utils } from "../../misc/utils";
|
||||||
|
import {
|
||||||
|
MasterKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "../../models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "../../types/csprng";
|
||||||
|
import { TokenService } from "../abstractions/token.service";
|
||||||
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
|
import { PasswordlessLogInCredentials } from "../models/domain/log-in-credentials";
|
||||||
|
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||||
|
|
||||||
|
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||||
|
import { PasswordlessLogInStrategy } from "./passwordless-login.strategy";
|
||||||
|
|
||||||
|
describe("SsoLogInStrategy", () => {
|
||||||
|
let cryptoService: MockProxy<CryptoService>;
|
||||||
|
let apiService: MockProxy<ApiService>;
|
||||||
|
let tokenService: MockProxy<TokenService>;
|
||||||
|
let appIdService: MockProxy<AppIdService>;
|
||||||
|
let platformUtilsService: MockProxy<PlatformUtilsService>;
|
||||||
|
let messagingService: MockProxy<MessagingService>;
|
||||||
|
let logService: MockProxy<LogService>;
|
||||||
|
let stateService: MockProxy<StateService>;
|
||||||
|
let twoFactorService: MockProxy<TwoFactorService>;
|
||||||
|
|
||||||
|
let passwordlessLoginStrategy: PasswordlessLogInStrategy;
|
||||||
|
let credentials: PasswordlessLogInCredentials;
|
||||||
|
let tokenResponse: IdentityTokenResponse;
|
||||||
|
|
||||||
|
const deviceId = Utils.newGuid();
|
||||||
|
|
||||||
|
const email = "EMAIL";
|
||||||
|
const accessCode = "ACCESS_CODE";
|
||||||
|
const authRequestId = "AUTH_REQUEST_ID";
|
||||||
|
const decKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||||
|
const localPasswordHash = "LOCAL_PASSWORD_HASH";
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
cryptoService = mock<CryptoService>();
|
||||||
|
apiService = mock<ApiService>();
|
||||||
|
tokenService = mock<TokenService>();
|
||||||
|
appIdService = mock<AppIdService>();
|
||||||
|
platformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
messagingService = mock<MessagingService>();
|
||||||
|
logService = mock<LogService>();
|
||||||
|
stateService = mock<StateService>();
|
||||||
|
twoFactorService = mock<TwoFactorService>();
|
||||||
|
|
||||||
|
tokenService.getTwoFactorToken.mockResolvedValue(null);
|
||||||
|
appIdService.getAppId.mockResolvedValue(deviceId);
|
||||||
|
tokenService.decodeToken.mockResolvedValue({});
|
||||||
|
|
||||||
|
passwordlessLoginStrategy = new PasswordlessLogInStrategy(
|
||||||
|
cryptoService,
|
||||||
|
apiService,
|
||||||
|
tokenService,
|
||||||
|
appIdService,
|
||||||
|
platformUtilsService,
|
||||||
|
messagingService,
|
||||||
|
logService,
|
||||||
|
stateService,
|
||||||
|
twoFactorService
|
||||||
|
);
|
||||||
|
credentials = new PasswordlessLogInCredentials(
|
||||||
|
email,
|
||||||
|
accessCode,
|
||||||
|
authRequestId,
|
||||||
|
decKey,
|
||||||
|
localPasswordHash
|
||||||
|
);
|
||||||
|
|
||||||
|
tokenResponse = identityTokenResponseFactory();
|
||||||
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets keys after a successful authentication", async () => {
|
||||||
|
const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||||
|
const userSymKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(64).buffer as CsprngArray
|
||||||
|
) as UserSymKey;
|
||||||
|
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
cryptoService.decryptUserSymKeyWithMasterKey.mockResolvedValue(userSymKey);
|
||||||
|
|
||||||
|
await passwordlessLoginStrategy.logIn(credentials);
|
||||||
|
|
||||||
|
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey);
|
||||||
|
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localPasswordHash);
|
||||||
|
expect(cryptoService.setUserSymKeyMasterKey).toHaveBeenCalledWith(tokenResponse.key);
|
||||||
|
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userSymKey);
|
||||||
|
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,13 +5,13 @@ import { LogService } from "../../platform/abstractions/log.service";
|
|||||||
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { AuthService } from "../abstractions/auth.service";
|
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
import { AuthResult } from "../models/domain/auth-result";
|
import { AuthResult } from "../models/domain/auth-result";
|
||||||
import { PasswordlessLogInCredentials } from "../models/domain/log-in-credentials";
|
import { PasswordlessLogInCredentials } from "../models/domain/log-in-credentials";
|
||||||
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
|
||||||
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
|
||||||
|
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||||
|
|
||||||
import { LogInStrategy } from "./login.strategy";
|
import { LogInStrategy } from "./login.strategy";
|
||||||
|
|
||||||
@@ -40,8 +40,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
|
|||||||
messagingService: MessagingService,
|
messagingService: MessagingService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService
|
||||||
private authService: AuthService
|
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cryptoService,
|
cryptoService,
|
||||||
@@ -56,20 +55,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserKey() {
|
override async logIn(credentials: PasswordlessLogInCredentials) {
|
||||||
await this.cryptoService.setKey(this.passwordlessCredentials.decKey);
|
|
||||||
await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
async logInTwoFactor(
|
|
||||||
twoFactor: TokenTwoFactorRequest,
|
|
||||||
captchaResponse: string
|
|
||||||
): Promise<AuthResult> {
|
|
||||||
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
|
|
||||||
return super.logInTwoFactor(twoFactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
async logIn(credentials: PasswordlessLogInCredentials) {
|
|
||||||
this.passwordlessCredentials = credentials;
|
this.passwordlessCredentials = credentials;
|
||||||
|
|
||||||
this.tokenRequest = new PasswordTokenRequest(
|
this.tokenRequest = new PasswordTokenRequest(
|
||||||
@@ -84,4 +70,33 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
|
|||||||
const [authResult] = await this.startLogIn();
|
const [authResult] = await this.startLogIn();
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async logInTwoFactor(
|
||||||
|
twoFactor: TokenTwoFactorRequest,
|
||||||
|
captchaResponse: string
|
||||||
|
): Promise<AuthResult> {
|
||||||
|
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
|
||||||
|
return super.logInTwoFactor(twoFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setMasterKey(response: IdentityTokenResponse) {
|
||||||
|
await this.cryptoService.setMasterKey(this.passwordlessCredentials.decKey);
|
||||||
|
await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
|
||||||
|
await this.cryptoService.setUserSymKeyMasterKey(response.key);
|
||||||
|
|
||||||
|
const masterKey = await this.cryptoService.getMasterKey();
|
||||||
|
if (masterKey) {
|
||||||
|
const userKey = await this.cryptoService.decryptUserSymKeyWithMasterKey(masterKey);
|
||||||
|
await this.cryptoService.setUserKey(userKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setPrivateKey(response: IdentityTokenResponse): Promise<void> {
|
||||||
|
await this.cryptoService.setPrivateKey(
|
||||||
|
response.privateKey ?? (await this.createKeyPairForOldAccount())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,17 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
|
|||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
|
import {
|
||||||
|
MasterKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "../../types/csprng";
|
||||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
import { SsoLogInCredentials } from "../models/domain/log-in-credentials";
|
import { SsoLogInCredentials } from "../models/domain/log-in-credentials";
|
||||||
|
import { IdentityTokenResponse } from "../models/response/identity-token.response";
|
||||||
|
|
||||||
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
import { identityTokenResponseFactory } from "./login.strategy.spec";
|
||||||
import { SsoLogInStrategy } from "./sso-login.strategy";
|
import { SsoLogInStrategy } from "./sso-login.strategy";
|
||||||
@@ -98,24 +105,32 @@ describe("SsoLogInStrategy", () => {
|
|||||||
|
|
||||||
await ssoLogInStrategy.logIn(credentials);
|
await ssoLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
expect(cryptoService.setEncPrivateKey).not.toHaveBeenCalled();
|
expect(cryptoService.setMasterKey).not.toHaveBeenCalled();
|
||||||
expect(cryptoService.setEncKey).not.toHaveBeenCalled();
|
expect(cryptoService.setUserKey).not.toHaveBeenCalled();
|
||||||
|
expect(cryptoService.setPrivateKey).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets and sets KeyConnector key for enrolled user", async () => {
|
describe("Key Connector", () => {
|
||||||
const tokenResponse = identityTokenResponseFactory();
|
let tokenResponse: IdentityTokenResponse;
|
||||||
|
beforeEach(() => {
|
||||||
|
tokenResponse = identityTokenResponseFactory();
|
||||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets and sets the master key if Key Connector is enabled", async () => {
|
||||||
|
const masterKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(64).buffer as CsprngArray
|
||||||
|
) as MasterKey;
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
|
||||||
await ssoLogInStrategy.logIn(credentials);
|
await ssoLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
|
expect(keyConnectorService.getAndSetMasterKey).toHaveBeenCalledWith(keyConnectorUrl);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("converts new SSO user to Key Connector on first login", async () => {
|
it("converts new SSO user to Key Connector on first login", async () => {
|
||||||
const tokenResponse = identityTokenResponseFactory();
|
|
||||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
|
||||||
tokenResponse.key = null;
|
tokenResponse.key = null;
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
@@ -127,4 +142,23 @@ describe("SsoLogInStrategy", () => {
|
|||||||
ssoOrgId
|
ssoOrgId
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("decrypts and sets the user symmetric key if Key Connector is enabled", async () => {
|
||||||
|
const userSymKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(64).buffer as CsprngArray
|
||||||
|
) as UserSymKey;
|
||||||
|
const masterKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(64).buffer as CsprngArray
|
||||||
|
) as MasterKey;
|
||||||
|
|
||||||
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
cryptoService.decryptUserSymKeyWithMasterKey.mockResolvedValue(userSymKey);
|
||||||
|
|
||||||
|
await ssoLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
|
expect(cryptoService.decryptUserSymKeyWithMasterKey).toHaveBeenCalledWith(masterKey);
|
||||||
|
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userSymKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,18 +49,6 @@ export class SsoLogInStrategy extends LogInStrategy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserKey(tokenResponse: IdentityTokenResponse) {
|
|
||||||
const newSsoUser = tokenResponse.key == null;
|
|
||||||
|
|
||||||
if (tokenResponse.keyConnectorUrl != null) {
|
|
||||||
if (!newSsoUser) {
|
|
||||||
await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl);
|
|
||||||
} else {
|
|
||||||
await this.keyConnectorService.convertNewSsoUserToKeyConnector(tokenResponse, this.orgId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async logIn(credentials: SsoLogInCredentials) {
|
async logIn(credentials: SsoLogInCredentials) {
|
||||||
this.orgId = credentials.orgId;
|
this.orgId = credentials.orgId;
|
||||||
this.tokenRequest = new SsoTokenRequest(
|
this.tokenRequest = new SsoTokenRequest(
|
||||||
@@ -78,4 +66,43 @@ export class SsoLogInStrategy extends LogInStrategy {
|
|||||||
|
|
||||||
return ssoAuthResult;
|
return ssoAuthResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async setMasterKey(tokenResponse: IdentityTokenResponse) {
|
||||||
|
const newSsoUser = tokenResponse.key == null;
|
||||||
|
|
||||||
|
if (tokenResponse.keyConnectorUrl != null) {
|
||||||
|
if (!newSsoUser) {
|
||||||
|
await this.keyConnectorService.getAndSetMasterKey(tokenResponse.keyConnectorUrl);
|
||||||
|
} else {
|
||||||
|
await this.keyConnectorService.convertNewSsoUserToKeyConnector(tokenResponse, this.orgId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setUserKey(tokenResponse: IdentityTokenResponse): Promise<void> {
|
||||||
|
const newSsoUser = tokenResponse.key == null;
|
||||||
|
|
||||||
|
if (!newSsoUser) {
|
||||||
|
await this.cryptoService.setUserSymKeyMasterKey(tokenResponse.key);
|
||||||
|
|
||||||
|
if (tokenResponse.keyConnectorUrl != null) {
|
||||||
|
const masterKey = await this.cryptoService.getMasterKey();
|
||||||
|
if (!masterKey) {
|
||||||
|
throw new Error("Master key not found");
|
||||||
|
}
|
||||||
|
const userKey = await this.cryptoService.decryptUserSymKeyWithMasterKey(masterKey);
|
||||||
|
await this.cryptoService.setUserKey(userKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setPrivateKey(tokenResponse: IdentityTokenResponse): Promise<void> {
|
||||||
|
const newSsoUser = tokenResponse.key == null;
|
||||||
|
|
||||||
|
if (!newSsoUser) {
|
||||||
|
await this.cryptoService.setPrivateKey(
|
||||||
|
tokenResponse.privateKey ?? (await this.createKeyPairForOldAccount())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
|
|||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
|
import {
|
||||||
|
MasterKey,
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
|
import { CsprngArray } from "../../types/csprng";
|
||||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { TwoFactorService } from "../abstractions/two-factor.service";
|
import { TwoFactorService } from "../abstractions/two-factor.service";
|
||||||
@@ -101,7 +107,18 @@ describe("UserApiLogInStrategy", () => {
|
|||||||
expect(stateService.addAccount).toHaveBeenCalled();
|
expect(stateService.addAccount).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets and sets the Key Connector key from environmentUrl", async () => {
|
it("sets the encrypted user symmetric key and private key from the identity token response", async () => {
|
||||||
|
const tokenResponse = identityTokenResponseFactory();
|
||||||
|
|
||||||
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
|
||||||
|
await apiLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
|
expect(cryptoService.setUserSymKeyMasterKey).toHaveBeenCalledWith(tokenResponse.key);
|
||||||
|
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets and sets the master key if Key Connector is enabled", async () => {
|
||||||
const tokenResponse = identityTokenResponseFactory();
|
const tokenResponse = identityTokenResponseFactory();
|
||||||
tokenResponse.apiUseKeyConnector = true;
|
tokenResponse.apiUseKeyConnector = true;
|
||||||
|
|
||||||
@@ -110,6 +127,26 @@ describe("UserApiLogInStrategy", () => {
|
|||||||
|
|
||||||
await apiLogInStrategy.logIn(credentials);
|
await apiLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
|
expect(keyConnectorService.getAndSetMasterKey).toHaveBeenCalledWith(keyConnectorUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("decrypts and sets the user symmetric key if Key Connector is enabled", async () => {
|
||||||
|
const userSymKey = new SymmetricCryptoKey(
|
||||||
|
new Uint8Array(64).buffer as CsprngArray
|
||||||
|
) as UserSymKey;
|
||||||
|
const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
|
||||||
|
|
||||||
|
const tokenResponse = identityTokenResponseFactory();
|
||||||
|
tokenResponse.apiUseKeyConnector = true;
|
||||||
|
|
||||||
|
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
||||||
|
environmentService.getKeyConnectorUrl.mockReturnValue(keyConnectorUrl);
|
||||||
|
cryptoService.getMasterKey.mockResolvedValue(masterKey);
|
||||||
|
cryptoService.decryptUserSymKeyWithMasterKey.mockResolvedValue(userSymKey);
|
||||||
|
|
||||||
|
await apiLogInStrategy.logIn(credentials);
|
||||||
|
|
||||||
|
expect(cryptoService.decryptUserSymKeyWithMasterKey).toHaveBeenCalledWith(masterKey);
|
||||||
|
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userSymKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,14 +44,7 @@ export class UserApiLogInStrategy extends LogInStrategy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserKey(tokenResponse: IdentityTokenResponse) {
|
override async logIn(credentials: UserApiLogInCredentials) {
|
||||||
if (tokenResponse.apiUseKeyConnector) {
|
|
||||||
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
|
|
||||||
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async logIn(credentials: UserApiLogInCredentials) {
|
|
||||||
this.tokenRequest = new UserApiTokenRequest(
|
this.tokenRequest = new UserApiTokenRequest(
|
||||||
credentials.clientId,
|
credentials.clientId,
|
||||||
credentials.clientSecret,
|
credentials.clientSecret,
|
||||||
@@ -63,6 +56,31 @@ export class UserApiLogInStrategy extends LogInStrategy {
|
|||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override async setMasterKey(response: IdentityTokenResponse) {
|
||||||
|
if (response.apiUseKeyConnector) {
|
||||||
|
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
|
||||||
|
await this.keyConnectorService.getAndSetMasterKey(keyConnectorUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
|
||||||
|
await this.cryptoService.setUserSymKeyMasterKey(response.key);
|
||||||
|
|
||||||
|
if (response.apiUseKeyConnector) {
|
||||||
|
const masterKey = await this.cryptoService.getMasterKey();
|
||||||
|
if (masterKey) {
|
||||||
|
const userKey = await this.cryptoService.decryptUserSymKeyWithMasterKey(masterKey);
|
||||||
|
await this.cryptoService.setUserKey(userKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async setPrivateKey(response: IdentityTokenResponse): Promise<void> {
|
||||||
|
await this.cryptoService.setPrivateKey(
|
||||||
|
response.privateKey ?? (await this.createKeyPairForOldAccount())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
|
||||||
await super.saveAccountInformation(tokenResponse);
|
await super.saveAccountInformation(tokenResponse);
|
||||||
await this.stateService.setApiKeyClientId(this.tokenRequest.clientId);
|
await this.stateService.setApiKeyClientId(this.tokenRequest.clientId);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { MasterKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { AuthenticationType } from "../../enums/authentication-type";
|
import { AuthenticationType } from "../../enums/authentication-type";
|
||||||
import { TokenTwoFactorRequest } from "../request/identity-token/token-two-factor.request";
|
import { TokenTwoFactorRequest } from "../request/identity-token/token-two-factor.request";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export class PasswordlessLogInCredentials {
|
|||||||
public email: string,
|
public email: string,
|
||||||
public accessCode: string,
|
public accessCode: string,
|
||||||
public authRequestId: string,
|
public authRequestId: string,
|
||||||
public decKey: SymmetricCryptoKey,
|
public decKey: MasterKey,
|
||||||
public localPasswordHash: string,
|
public localPasswordHash: string,
|
||||||
public twoFactor?: TokenTwoFactorRequest
|
public twoFactor?: TokenTwoFactorRequest
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
|
|||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
|
||||||
import { PasswordStrengthServiceAbstraction } from "../../tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "../../tools/password-strength";
|
||||||
|
import { MasterKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
|
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
|
||||||
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
import { KeyConnectorService } from "../abstractions/key-connector.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
@@ -262,7 +262,7 @@ export class AuthService implements AuthServiceAbstraction {
|
|||||||
return AuthenticationStatus.Unlocked;
|
return AuthenticationStatus.Unlocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
|
async makePreloginKey(masterPassword: string, email: string): Promise<MasterKey> {
|
||||||
email = email.trim().toLowerCase();
|
email = email.trim().toLowerCase();
|
||||||
let kdf: KdfType = null;
|
let kdf: KdfType = null;
|
||||||
let kdfConfig: KdfConfig = null;
|
let kdfConfig: KdfConfig = null;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { CryptoService } from "../../platform/abstractions/crypto.service";
|
|||||||
import { LogService } from "../../platform/abstractions/log.service";
|
import { LogService } from "../../platform/abstractions/log.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
import { MasterKey, SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
|
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
|
||||||
import { TokenService } from "../abstractions/token.service";
|
import { TokenService } from "../abstractions/token.service";
|
||||||
import { KdfConfig } from "../models/domain/kdf-config";
|
import { KdfConfig } from "../models/domain/kdf-config";
|
||||||
@@ -60,12 +60,13 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
|||||||
await this.apiService.postConvertToKeyConnector();
|
await this.apiService.postConvertToKeyConnector();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAndSetKey(url: string) {
|
// TODO: UserKey should be renamed to MasterKey and typed accordingly
|
||||||
|
async getAndSetMasterKey(url: string) {
|
||||||
try {
|
try {
|
||||||
const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url);
|
const masterKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url);
|
||||||
const keyArr = Utils.fromB64ToArray(userKeyResponse.key);
|
const keyArr = Utils.fromB64ToArray(masterKeyResponse.key);
|
||||||
const k = new SymmetricCryptoKey(keyArr);
|
const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey;
|
||||||
await this.cryptoService.setKey(k);
|
await this.cryptoService.setMasterKey(masterKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.handleKeyConnectorError(e);
|
this.handleKeyConnectorError(e);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user