1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 18:23:31 +00:00

Merge branch 'master' into feature/flexible-collections

This commit is contained in:
Vincent Salucci
2023-09-14 17:07:54 -05:00
900 changed files with 69193 additions and 19393 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@bitwarden/cli",
"description": "A secure and free password manager for all of your devices.",
"version": "2023.7.0",
"version": "2023.8.2",
"keywords": [
"bitwarden",
"password",
@@ -49,29 +49,29 @@
"dependencies": {
"@koa/multer": "3.0.2",
"@koa/router": "12.0.0",
"argon2": "0.30.3",
"argon2": "0.31.0",
"big-integer": "1.6.51",
"browser-hrtime": "1.1.8",
"chalk": "4.1.2",
"commander": "7.2.0",
"form-data": "4.0.0",
"https-proxy-agent": "5.0.1",
"inquirer": "8.2.5",
"inquirer": "8.2.6",
"jsdom": "22.1.0",
"jszip": "3.10.1",
"koa": "2.14.2",
"koa-bodyparser": "4.4.0",
"koa-bodyparser": "4.4.1",
"koa-json": "2.0.2",
"lowdb": "1.0.0",
"lunr": "2.3.9",
"multer": "1.4.5-lts.1",
"node-fetch": "2.6.11",
"node-fetch": "2.6.12",
"node-forge": "1.3.1",
"open": "8.4.2",
"papaparse": "5.4.1",
"proper-lockfile": "4.1.2",
"rxjs": "7.8.1",
"tldts": "6.0.5",
"tldts": "6.0.14",
"zxcvbn": "4.4.2"
}
}

View File

@@ -1,4 +1,4 @@
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
import { Response } from "../../models/response";
import { MessageResponse } from "../../models/response/message.response";

View File

@@ -406,15 +406,15 @@ export class LoginCommand {
}
try {
const { newPasswordHash, newEncKey, hint } = await this.collectNewMasterPasswordDetails(
const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails(
"Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now."
);
const request = new PasswordRequest();
request.masterPasswordHash = await this.cryptoService.hashPassword(currentPassword, null);
request.masterPasswordHash = await this.cryptoService.hashMasterKey(currentPassword, null);
request.masterPasswordHint = hint;
request.newMasterPasswordHash = newPasswordHash;
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
await this.apiService.postPassword(request);
@@ -444,12 +444,12 @@ export class LoginCommand {
}
try {
const { newPasswordHash, newEncKey, hint } = await this.collectNewMasterPasswordDetails(
const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails(
"An organization administrator recently changed your master password. In order to access the vault, you must update your master password now."
);
const request = new UpdateTempPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
request.masterPasswordHint = hint;
@@ -467,8 +467,8 @@ export class LoginCommand {
/**
* Collect new master password and hint from the CLI. The collected password
* is validated against any applicable master password policies and a new encryption
* key is generated
* is validated against any applicable master password policies, a new master
* key is generated, and we use it to re-encrypt the user key
* @param prompt - Message that is displayed during the initial prompt
* @param error
*/
@@ -477,7 +477,7 @@ export class LoginCommand {
error?: string
): Promise<{
newPasswordHash: string;
newEncKey: [SymmetricCryptoKey, EncString];
newUserKey: [SymmetricCryptoKey, EncString];
hint?: string;
}> {
if (this.email == null || this.email === "undefined") {
@@ -559,21 +559,24 @@ export class LoginCommand {
const kdfConfig = await this.stateService.getKdfConfig();
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword,
this.email.trim().toLowerCase(),
kdf,
kdfConfig
);
const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
const newPasswordHash = await this.cryptoService.hashMasterKey(masterPassword, newMasterKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Grab user key
const userKey = await this.cryptoService.getUserKey();
if (!userKey) {
throw new Error("User key not found.");
}
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Re-encrypt user key with new master key
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey, userKey);
return { newPasswordHash, newEncKey, hint: masterPasswordHint };
return { newPasswordHash, newUserKey: newUserKey, hint: masterPasswordHint };
}
private async handleCaptchaRequired(

View File

@@ -44,17 +44,17 @@ export class UnlockCommand {
const email = await this.stateService.getEmail();
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const key = await this.cryptoService.makeKey(password, email, kdf, kdfConfig);
const storedKeyHash = await this.cryptoService.getKeyHash();
const masterKey = await this.cryptoService.makeMasterKey(password, email, kdf, kdfConfig);
const storedKeyHash = await this.cryptoService.getMasterKeyHash();
let passwordValid = false;
if (key != null) {
if (masterKey != null) {
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key);
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, masterKey);
} else {
const serverKeyHash = await this.cryptoService.hashPassword(
const serverKeyHash = await this.cryptoService.hashMasterKey(
password,
key,
masterKey,
HashPurpose.ServerAuthorization
);
const request = new SecretVerificationRequest();
@@ -62,12 +62,12 @@ export class UnlockCommand {
try {
await this.apiService.postAccountVerifyPassword(request);
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(
const localKeyHash = await this.cryptoService.hashMasterKey(
password,
key,
masterKey,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
await this.cryptoService.setMasterKeyHash(localKeyHash);
} catch {
// Ignore
}
@@ -75,7 +75,9 @@ export class UnlockCommand {
}
if (passwordValid) {
await this.cryptoService.setKey(key);
await this.cryptoService.setMasterKey(masterKey);
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
if (await this.keyConnectorService.getConvertAccountRequired()) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(

View File

@@ -12,7 +12,13 @@ import { OrganizationService } from "@bitwarden/common/admin-console/services/or
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AuthRequestCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-crypto.service.abstraction";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { AuthRequestCryptoServiceImplementation } from "@bitwarden/common/auth/services/auth-request-crypto.service.implementation";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device-trust-crypto.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
@@ -31,15 +37,14 @@ import { EnvironmentService } from "@bitwarden/common/platform/services/environm
import { FileUploadService } from "@bitwarden/common/platform/services/file-upload/file-upload.service";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
import { StateMigrationService } from "@bitwarden/common/platform/services/state-migration.service";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { AuditService } from "@bitwarden/common/services/audit.service";
import { OrganizationUserServiceImplementation } from "@bitwarden/common/services/organization-user/organization-user.service.implementation";
import { SearchService } from "@bitwarden/common/services/search.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
import { TotpService } from "@bitwarden/common/services/totp.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vaultTimeout/vaultTimeout.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vaultTimeout/vaultTimeoutSettings.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import {
PasswordGenerationService,
PasswordGenerationServiceAbstraction,
@@ -130,7 +135,6 @@ export class Main {
keyConnectorService: KeyConnectorService;
userVerificationService: UserVerificationService;
stateService: StateService;
stateMigrationService: StateMigrationService;
organizationService: OrganizationService;
providerService: ProviderService;
twoFactorService: TwoFactorService;
@@ -140,6 +144,9 @@ export class Main {
organizationApiService: OrganizationApiServiceAbstraction;
syncNotifierService: SyncNotifierService;
sendApiService: SendApiService;
devicesApiService: DevicesApiServiceAbstraction;
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction;
authRequestCryptoService: AuthRequestCryptoServiceAbstraction;
constructor() {
let p = null;
@@ -179,18 +186,11 @@ export class Main {
this.memoryStorageService = new MemoryStorageService();
this.stateMigrationService = new StateMigrationService(
this.storageService,
this.secureStorageService,
new StateFactory(GlobalState, Account)
);
this.stateService = new StateService(
this.storageService,
this.secureStorageService,
this.memoryStorageService,
this.logService,
this.stateMigrationService,
new StateFactory(GlobalState, Account)
);
@@ -315,6 +315,20 @@ export class Main {
this.stateService
);
this.devicesApiService = new DevicesApiServiceImplementation(this.apiService);
this.deviceTrustCryptoService = new DeviceTrustCryptoService(
this.cryptoFunctionService,
this.cryptoService,
this.encryptService,
this.stateService,
this.appIdService,
this.devicesApiService,
this.i18nService,
this.platformUtilsService
);
this.authRequestCryptoService = new AuthRequestCryptoServiceImplementation(this.cryptoService);
this.authService = new AuthService(
this.cryptoService,
this.apiService,
@@ -330,17 +344,27 @@ export class Main {
this.i18nService,
this.encryptService,
this.passwordStrengthService,
this.policyService
this.policyService,
this.deviceTrustCryptoService,
this.authRequestCryptoService
);
const lockedCallback = async () =>
await this.cryptoService.clearStoredKey(KeySuffixOptions.Auto);
const lockedCallback = async (userId?: string) =>
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
this.userVerificationService = new UserVerificationService(
this.stateService,
this.cryptoService,
this.i18nService,
this.userVerificationApiService
);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
this.cryptoService,
this.tokenService,
this.policyService,
this.stateService
this.stateService,
this.userVerificationService
);
this.vaultTimeoutService = new VaultTimeoutService(
@@ -351,7 +375,6 @@ export class Main {
this.platformUtilsService,
this.messagingService,
this.searchService,
this.keyConnectorService,
this.stateService,
this.authService,
this.vaultTimeoutSettingsService,
@@ -406,12 +429,6 @@ export class Main {
this.sendProgram = new SendProgram(this);
this.userVerificationApiService = new UserVerificationApiService(this.apiService);
this.userVerificationService = new UserVerificationService(
this.cryptoService,
this.i18nService,
this.userVerificationApiService
);
}
async run() {

View File

@@ -5,7 +5,6 @@ import * as koa from "koa";
import * as koaBodyParser from "koa-bodyparser";
import * as koaJson from "koa-json";
import { KeySuffixOptions } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ConfirmCommand } from "../admin-console/commands/confirm.command";
@@ -425,11 +424,7 @@ export class ServeCommand {
this.processResponse(res, Response.error("You are not logged in."));
return true;
}
if (await this.main.cryptoService.hasKeyInMemory()) {
return false;
} else if (await this.main.cryptoService.hasKeyStored(KeySuffixOptions.Auto)) {
// load key into memory
await this.main.cryptoService.getKey();
if (await this.main.cryptoService.hasUserKey()) {
return false;
}
this.processResponse(res, Response.error("Vault is locked."));

View File

@@ -46,5 +46,8 @@
},
"ssoKeyConnectorError": {
"message": "Key Connector error: make sure Key Connector is available and working correctly."
},
"unsupportedEncryptedImport": {
"message": "Importing encrypted files is currently not supported."
}
}

View File

@@ -2,7 +2,6 @@ import * as chalk from "chalk";
import * as program from "commander";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { KeySuffixOptions } from "@bitwarden/common/enums";
import { LockCommand } from "./auth/commands/lock.command";
import { LoginCommand } from "./auth/commands/login.command";
@@ -299,9 +298,12 @@ export class Program {
.option("-p, --passphrase", "Generate a passphrase.")
.option("--length <length>", "Length of the password.")
.option("--words <words>", "Number of words.")
.option("--minNumber <count>", "Minimum number of numeric characters.")
.option("--minSpecial <count>", "Minimum number of special characters.")
.option("--separator <separator>", "Word separator.")
.option("-c, --capitalize", "Title case passphrase.")
.option("--includeNumber", "Passphrase includes number.")
.option("--ambiguous", "Avoid ambiguous characters.")
.on("--help", () => {
writeLn("\n Notes:");
writeLn("");
@@ -597,11 +599,8 @@ export class Program {
protected async exitIfLocked() {
await this.exitIfNotAuthed();
if (await this.main.cryptoService.hasKeyInMemory()) {
if (await this.main.cryptoService.hasUserKey()) {
return;
} else if (await this.main.cryptoService.hasKeyStored(KeySuffixOptions.Auto)) {
// load key into memory
await this.main.cryptoService.getKey();
} else if (process.env.BW_NOINTERACTION !== "true") {
// must unlock
if (await this.main.keyConnectorService.getUsesKeyConnector()) {

View File

@@ -1,5 +1,6 @@
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { PasswordGeneratorOptions } from "@bitwarden/common/tools/generator/password/password-generator-options";
import { Response } from "../models/response";
import { StringResponse } from "../models/response/string.response";
@@ -13,7 +14,7 @@ export class GenerateCommand {
async run(cmdOptions: Record<string, any>): Promise<Response> {
const normalizedOptions = new Options(cmdOptions);
const options = {
const options: PasswordGeneratorOptions = {
uppercase: normalizedOptions.uppercase,
lowercase: normalizedOptions.lowercase,
number: normalizedOptions.number,
@@ -24,6 +25,9 @@ export class GenerateCommand {
numWords: normalizedOptions.words,
capitalize: normalizedOptions.capitalize,
includeNumber: normalizedOptions.includeNumber,
minNumber: normalizedOptions.minNumber,
minSpecial: normalizedOptions.minSpecial,
ambiguous: normalizedOptions.ambiguous,
};
const enforcedOptions = (await this.stateService.getIsAuthenticated())
@@ -47,6 +51,9 @@ class Options {
words: number;
capitalize: boolean;
includeNumber: boolean;
minNumber: number;
minSpecial: number;
ambiguous: boolean;
constructor(passedOptions: Record<string, any>) {
this.uppercase = CliUtils.convertBooleanOption(passedOptions?.uppercase);
@@ -55,10 +62,13 @@ class Options {
this.special = CliUtils.convertBooleanOption(passedOptions?.special);
this.capitalize = CliUtils.convertBooleanOption(passedOptions?.capitalize);
this.includeNumber = CliUtils.convertBooleanOption(passedOptions?.includeNumber);
this.length = passedOptions?.length != null ? parseInt(passedOptions?.length, null) : 14;
this.ambiguous = CliUtils.convertBooleanOption(passedOptions?.ambiguous);
this.length = CliUtils.convertNumberOption(passedOptions?.length, 14);
this.type = passedOptions?.passphrase ? "passphrase" : "password";
this.separator = passedOptions?.separator == null ? "-" : passedOptions.separator + "";
this.words = passedOptions?.words != null ? parseInt(passedOptions.words, null) : 3;
this.separator = CliUtils.convertStringOption(passedOptions?.separator, "-");
this.words = CliUtils.convertNumberOption(passedOptions?.words, 3);
this.minNumber = CliUtils.convertNumberOption(passedOptions?.minNumber, 1);
this.minSpecial = CliUtils.convertNumberOption(passedOptions?.minSpecial, 1);
if (!this.uppercase && !this.lowercase && !this.special && !this.number) {
this.lowercase = true;

View File

@@ -67,7 +67,9 @@ export class ImportCommand {
try {
let contents;
if (format === "1password1pux") {
contents = await CliUtils.extract1PuxContent(filepath);
contents = await CliUtils.extractZipContent(filepath, "export.data");
} else if (format === "protonpass" && filepath.endsWith(".zip")) {
contents = await CliUtils.extractZipContent(filepath, "Proton Pass/data.json");
} else {
contents = await CliUtils.readFile(filepath);
}

View File

@@ -46,7 +46,7 @@ export class CliUtils {
});
}
static extract1PuxContent(input: string): Promise<string> {
static extractZipContent(input: string, filepath: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
let p: string = null;
if (input != null && input !== "") {
@@ -65,7 +65,7 @@ export class CliUtils {
}
JSZip.loadAsync(data).then(
(zip) => {
resolve(zip.file("export.data").async("string"));
resolve(zip.file(filepath).async("string"));
},
(reason) => {
reject(reason);
@@ -74,6 +74,7 @@ export class CliUtils {
});
});
}
/**
* Save the given data to a file and determine the target file if necessary.
* If output is non-empty, it is used as target filename. Otherwise the target filename is
@@ -252,4 +253,20 @@ export class CliUtils {
static convertBooleanOption(optionValue: any) {
return optionValue || optionValue === "" ? true : false;
}
static convertNumberOption(optionValue: any, defaultValue: number) {
try {
if (optionValue != null) {
const numVal = parseInt(optionValue);
return !Number.isNaN(numVal) ? numVal : defaultValue;
}
return defaultValue;
} catch {
return defaultValue;
}
}
static convertStringOption(optionValue: any, defaultValue: string) {
return optionValue != null ? String(optionValue) : defaultValue;
}
}

View File

@@ -126,8 +126,8 @@ export class CreateCommand {
return Response.error("Premium status is required to use this feature.");
}
const encKey = await this.cryptoService.getEncKey();
if (encKey == null) {
const userKey = await this.cryptoService.getUserKey();
if (userKey == null) {
return Response.error(
"You must update your encryption key before you can use this feature. " +
"See https://help.bitwarden.com/article/update-encryption-key/"