@@ -107,6 +111,7 @@
+
diff --git a/extension/chrome/settings/modules/backup.ts b/extension/chrome/settings/modules/backup.ts
index b24e76ca1bb..439caae2393 100644
--- a/extension/chrome/settings/modules/backup.ts
+++ b/extension/chrome/settings/modules/backup.ts
@@ -3,7 +3,7 @@
'use strict';
import { BrowserMsg } from '../../../js/common/browser/browser-msg.js';
-import { Url } from '../../../js/common/core/common.js';
+import { Str, Url } from '../../../js/common/core/common.js';
import { Assert } from '../../../js/common/assert.js';
import { Gmail } from '../../../js/common/api/email-provider/gmail/gmail.js';
import { OrgRules } from '../../../js/common/org-rules.js';
@@ -15,6 +15,9 @@ import { BackupManualActionModule as BackupManualModule } from './backup-manual-
import { BackupAutomaticModule } from './backup-automatic-module.js';
import { Lang } from '../../../js/common/lang.js';
import { AcctStore, EmailProvider } from '../../../js/common/platform/store/acct-store.js';
+import { KeyStore } from '../../../js/common/platform/store/key-store.js';
+import { KeyIdentity, KeyUtil, TypedKeyInfo } from '../../../js/common/core/crypto/key.js';
+import { Settings } from '../../../js/common/settings.js';
export class BackupView extends View {
@@ -22,7 +25,8 @@ export class BackupView extends View {
public readonly idToken: string | undefined;
public readonly action: 'setup_automatic' | 'setup_manual' | 'backup_manual' | undefined;
public readonly gmail: Gmail;
- public readonly parentTabId: string | undefined;
+ public readonly parentTabId: string | undefined; // the master page to interact (settings/index.htm)
+ public readonly keyIdentity: KeyIdentity | undefined; // the key identity supplied with URL params
public readonly statusModule: BackupStatusModule;
public readonly manualModule: BackupManualModule;
@@ -31,19 +35,29 @@ export class BackupView extends View {
public emailProvider: EmailProvider = 'gmail';
public orgRules!: OrgRules;
public tabId!: string;
+ public prvKeysToManuallyBackup: KeyIdentity[] = [];
private readonly blocks = ['loading', 'module_status', 'module_manual'];
constructor() {
super();
- const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId', 'action', 'idToken']);
+ const uncheckedUrlParams = Url.parse(['acctEmail', 'parentTabId', 'action', 'idToken', 'id', 'type']);
this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
this.action = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'action', ['setup_automatic', 'setup_manual', 'backup_manual', undefined]);
if (this.action !== 'setup_automatic' && this.action !== 'setup_manual') {
this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId');
} else {
+ // when sourced from setup.htm page, passphrase-related message handlers are not wired,
+ // so we have limited functionality further down the road
this.idToken = Assert.urlParamRequire.string(uncheckedUrlParams, 'idToken');
}
+ {
+ const id = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'id');
+ const type = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'type');
+ if (id && type === 'openpgp') {
+ this.keyIdentity = { id, type };
+ }
+ }
this.gmail = new Gmail(this.acctEmail);
this.statusModule = new BackupStatusModule(this);
this.manualModule = new BackupManualModule(this);
@@ -62,29 +76,35 @@ export class BackupView extends View {
if (this.action === 'setup_automatic') {
$('#button-go-back').css('display', 'none');
await this.automaticModule.simpleSetupAutoBackupRetryUntilSuccessful();
- } else if (this.action === 'setup_manual') {
- $('#button-go-back').css('display', 'none');
- this.displayBlock('module_manual');
- $('h1').text('Back up your private key');
- } else if (this.action === 'backup_manual') {
- this.displayBlock('module_manual');
- $('h1').text('Back up your private key');
- } else { // action = view status
- $('.hide_if_backup_done').css('display', 'none');
- $('h1').text('Key Backups');
- this.displayBlock('loading');
- await this.statusModule.checkAndRenderBackupStatus();
+ } else {
+ await this.preparePrvKeysBackupSelection();
+ if (this.action === 'setup_manual') {
+ $('#button-go-back').css('display', 'none');
+ this.displayBlock('module_manual');
+ $('h1').text('Back up your private key');
+ } else if (this.action === 'backup_manual') {
+ this.displayBlock('module_manual');
+ $('h1').text('Back up your private key');
+ } else { // action = view status
+ $('.hide_if_backup_done').css('display', 'none');
+ $('h1').text('Key Backups');
+ this.displayBlock('loading');
+ await this.statusModule.checkAndRenderBackupStatus();
+ }
}
}
- public renderBackupDone = async (backedUp = true) => {
+ public renderBackupDone = async (backedUpCount: number) => {
if (this.action === 'setup_automatic' || this.action === 'setup_manual') {
window.location.href = Url.create('/chrome/settings/setup.htm', { acctEmail: this.acctEmail, action: 'finalize', idToken: this.idToken });
- } else if (backedUp) {
- await Ui.modal.info('Your private key has been successfully backed up');
- BrowserMsg.send.closePage(this.parentTabId as string);
- } else {
- window.location.href = Url.create('/chrome/settings/modules/backup.htm', { acctEmail: this.acctEmail, parentTabId: this.parentTabId as string });
+ } else if (backedUpCount > 0) {
+ const pluralOrSingle = backedUpCount > 1 ? "keys have" : "key has";
+ await Ui.modal.info(`Your private ${pluralOrSingle} been successfully backed up`);
+ if (this.parentTabId) {
+ BrowserMsg.send.closePage(this.parentTabId);
+ }
+ } else if (this.parentTabId) { // should be always true as setup_manual is excluded by this point
+ Settings.redirectSubPage(this.acctEmail, this.parentTabId, '/chrome/settings/modules/backup.htm');
}
}
@@ -100,6 +120,62 @@ export class BackupView extends View {
this.manualModule.setHandlers();
}
+ private addKeyToBackup = (keyIdentity: KeyIdentity) => {
+ if (!this.prvKeysToManuallyBackup.some(prvIdentity => KeyUtil.identityEquals(prvIdentity, keyIdentity))) {
+ this.prvKeysToManuallyBackup.push(keyIdentity);
+ }
+ }
+
+ private removeKeyToBackup = (keyIdentity: KeyIdentity) => {
+ this.prvKeysToManuallyBackup.splice(this.prvKeysToManuallyBackup.findIndex(prvIdentity => KeyUtil.identityEquals(prvIdentity, keyIdentity)), 1);
+ }
+
+ private preparePrvKeysBackupSelection = async () => {
+ const kinfos = await KeyStore.getTypedKeyInfos(this.acctEmail);
+ if (this.keyIdentity && this.keyIdentity.type === 'openpgp' && kinfos.some(ki => KeyUtil.identityEquals(ki, this.keyIdentity!))) {
+ // todo: error if not found ?
+ this.addKeyToBackup({ id: this.keyIdentity.id, type: this.keyIdentity.type });
+ } else if (kinfos.length > 1) {
+ await this.renderPrvKeysBackupSelection(kinfos);
+ } else if (kinfos.length === 1 && kinfos[0].type === 'openpgp') {
+ this.addKeyToBackup({ id: kinfos[0].id, type: kinfos[0].type });
+ }
+ }
+
+ private renderPrvKeysBackupSelection = async (kinfos: TypedKeyInfo[]) => {
+ for (const ki of kinfos) {
+ const email = Xss.escape(String(ki.emails![0]));
+ const dom = `
+
+
+
+
+
+ `.trim();
+ $('.key_backup_selection').append(dom); // xss-escaped
+ if (ki.type === 'openpgp') {
+ this.addKeyToBackup({ type: ki.type, id: ki.id });
+ }
+ }
+ $('.input_prvkey_backup_checkbox').click(Ui.event.handle((target) => {
+ const type = $(target).data('type') as string;
+ if (type === 'openpgp') {
+ const id = $(target).data('id') as string;
+ if ($(target).prop('checked')) {
+ this.addKeyToBackup({ type, id });
+ } else {
+ this.removeKeyToBackup({ type, id });
+ }
+ }
+ }));
+ $('#key_backup_selection_container').show();
+ }
}
View.run(BackupView);
diff --git a/extension/chrome/settings/setup/setup-create-key.ts b/extension/chrome/settings/setup/setup-create-key.ts
index 7e78c158667..bd685320357 100644
--- a/extension/chrome/settings/setup/setup-create-key.ts
+++ b/extension/chrome/settings/setup/setup-create-key.ts
@@ -5,7 +5,7 @@
import { SetupOptions, SetupView } from '../setup.js';
import { Catch } from '../../../js/common/platform/catch.js';
import { Lang } from '../../../js/common/lang.js';
-import { KeyAlgo, KeyUtil } from '../../../js/common/core/crypto/key.js';
+import { KeyAlgo, KeyIdentity, KeyUtil } from '../../../js/common/core/crypto/key.js';
import { Settings } from '../../../js/common/settings.js';
import { Ui } from '../../../js/common/browser/ui.js';
import { Url } from '../../../js/common/core/common.js';
@@ -34,15 +34,17 @@ export class SetupCreateKeyModule {
recovered: false,
};
const keyAlgo = this.view.orgRules.getEnforcedKeygenAlgo() || $('#step_2a_manual_create .key_type').val() as KeyAlgo;
- await this.createSaveKeyPair(opts, keyAlgo);
- if (this.view.orgRules.canBackupKeys()) {
- await this.view.preFinalizeSetup(opts);
- const action = $('#step_2a_manual_create .input_backup_inbox').prop('checked') ? 'setup_automatic' : 'setup_manual';
- // only finalize after backup is done. backup.htm will redirect back to this page with ?action=finalize
- window.location.href = Url.create('modules/backup.htm', { action, acctEmail: this.view.acctEmail, idToken: this.view.idToken });
- } else {
- await this.view.submitPublicKeysAndFinalizeSetup(opts);
- await this.view.setupRender.renderSetupDone();
+ const keyIdentity = await this.createSaveKeyPair(opts, keyAlgo);
+ if (keyIdentity) {
+ if (this.view.orgRules.canBackupKeys()) {
+ await this.view.preFinalizeSetup(opts);
+ const action = $('#step_2a_manual_create .input_backup_inbox').prop('checked') ? 'setup_automatic' : 'setup_manual';
+ // only finalize after backup is done. backup.htm will redirect back to this page with ?action=finalize
+ window.location.href = Url.create('modules/backup.htm', { action, acctEmail: this.view.acctEmail, idToken: this.view.idToken, id: keyIdentity.id, type: keyIdentity.type });
+ } else {
+ await this.view.submitPublicKeysAndFinalizeSetup(opts);
+ await this.view.setupRender.renderSetupDone();
+ }
}
} catch (e) {
Catch.reportErr(e);
@@ -65,18 +67,22 @@ export class SetupCreateKeyModule {
}
}
- public createSaveKeyPair = async (options: SetupOptions, keyAlgo: KeyAlgo) => {
+ public createSaveKeyPair = async (options: SetupOptions, keyAlgo: KeyAlgo): Promise
=> {
await Settings.forbidAndRefreshPageIfCannot('CREATE_KEYS', this.view.orgRules);
const { full_name } = await AcctStore.get(this.view.acctEmail, ['full_name']);
const pgpUids = [{ name: full_name || '', email: this.view.acctEmail }]; // todo - add all addresses?
const expireMonths = this.view.orgRules.getEnforcedKeygenExpirationMonths();
+ let keyIdentity: KeyIdentity | undefined;
try {
const key = await OpenPGPKey.create(pgpUids, keyAlgo, options.passphrase, expireMonths);
const prv = await KeyUtil.parse(key.private);
+ keyIdentity = { id: prv.id, type: prv.type };
await this.view.saveKeysAndPassPhrase([prv], options);
+ return keyIdentity;
} catch (e) {
Catch.reportErr(e);
Xss.sanitizeRender('#step_2_easy_generating, #step_2a_manual_create', Lang.setup.fcDidntSetUpProperly);
+ return keyIdentity; // todo: return undefined ?
}
}
}
diff --git a/extension/css/cryptup.css b/extension/css/cryptup.css
index 1a0d11c2785..a6c3fd12ccb 100644
--- a/extension/css/cryptup.css
+++ b/extension/css/cryptup.css
@@ -44,6 +44,7 @@ button.swal2-styled:focus,
text-decoration: none;
}
+.prv_fingerprint span,
a { color: #337ab7; }
.line.red,
@@ -835,6 +836,11 @@ body#settings > div#content.backup div#module_manual table.backup_options label
}
body#settings .password_feedback { display: none; }
+div#module_manual .key_backup_selection {
+ max-height: 300px;
+ overflow-y: scroll;
+}
+
body#settings > div#content.setup div.manual .password_feedback {
display: none;
position: absolute;
@@ -1086,6 +1092,7 @@ body#settings > div#content.storage .pre {
body#settings > div#content .list_table { display: none; }
body#settings > div#content .list_table table img { width: 15px; }
+.prv_fingerprint span,
body#settings > div#content .list_table table {
width: 100%;
font-size: 14px;
@@ -2811,11 +2818,16 @@ body#select_account .action_add_account::before {
padding-top: 0;
margin-top: 0;
}
+
+.m-0 { margin: 0 !important; }
.mt-20 { margin-top: 20px; }
.mt-40 { margin-top: 40px; }
+.mb-20 { margin-bottom: 20px; }
.display_none { display: none; }
+.display_inline_block { display: inline-block; }
+
code {
padding: 0.2em 0.4em;
margin: 0;
diff --git a/extension/js/common/core/crypto/key.ts b/extension/js/common/core/crypto/key.ts
index d19d0f24f51..a861aef7244 100644
--- a/extension/js/common/core/crypto/key.ts
+++ b/extension/js/common/core/crypto/key.ts
@@ -19,9 +19,7 @@ import { PubkeyInfo } from '../../platform/store/contact-store.js';
* all dates are expressed as number of milliseconds since Unix Epoch.
* This is what `Date.now()` returns and `new Date(x)` accepts.
*/
-export interface Key {
- type: 'openpgp' | 'x509';
- id: string; // a fingerprint of the primary key in OpenPGP, and similarly a fingerprint of the actual cryptographic key (eg RSA fingerprint) in S/MIME
+export interface Key extends KeyIdentity {
allIds: string[]; // a list of fingerprints, including those for subkeys
created: number;
revoked: boolean;
@@ -70,11 +68,18 @@ export interface KeyInfo {
emails?: string[]; // todo - used to be missing - but migration was supposed to add it? setting back to optional for now
}
-export interface ExtendedKeyInfo extends KeyInfo {
- passphrase?: string;
+export interface KeyIdentity {
+ id: string, // a fingerprint of the primary key in OpenPGP, and similarly a fingerprint of the actual cryptographic key (eg RSA fingerprint) in S/MIME
type: 'openpgp' | 'x509'
}
+export interface TypedKeyInfo extends KeyInfo, KeyIdentity {
+}
+
+export interface ExtendedKeyInfo extends TypedKeyInfo {
+ passphrase?: string;
+}
+
export type KeyAlgo = 'curve25519' | 'rsa2048' | 'rsa3072' | 'rsa4096';
export type PrvPacket = (OpenPGP.packet.SecretKey | OpenPGP.packet.SecretSubkey);
@@ -83,6 +88,14 @@ export class UnexpectedKeyTypeError extends Error { }
export class KeyUtil {
+ public static identityEquals = (keyIdentity1: KeyIdentity, keyIdentity2: KeyIdentity) => {
+ return keyIdentity1.id === keyIdentity2.id && keyIdentity1.type === keyIdentity2.type;
+ }
+
+ public static filterKeys(kis: T[], ids: KeyIdentity[]): T[] {
+ return kis.filter(ki => ids.some(i => KeyUtil.identityEquals(i, ki)));
+ }
+
public static isWithoutSelfCertifications = async (key: Key) => {
// all non-OpenPGP keys are automatically considered to be not
// "without self certifications"
@@ -361,6 +374,10 @@ export class KeyUtil {
};
}
+ public static typedKeyInfoObj = async (prv: Key): Promise => {
+ return { ...await KeyUtil.keyInfoObj(prv), id: prv.id, type: prv.type };
+ }
+
public static getPubkeyLongids = (pubkey: Key): string[] => {
if (pubkey.type !== 'x509') {
return pubkey.allIds.map(id => OpenPGPKey.fingerprintToLongid(id));
diff --git a/extension/js/common/platform/store/key-store.ts b/extension/js/common/platform/store/key-store.ts
index d190067b3e6..da15a8805c0 100644
--- a/extension/js/common/platform/store/key-store.ts
+++ b/extension/js/common/platform/store/key-store.ts
@@ -1,6 +1,6 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
-import { KeyInfo, ExtendedKeyInfo, KeyUtil, Key } from '../../core/crypto/key.js';
+import { KeyInfo, TypedKeyInfo, ExtendedKeyInfo, KeyUtil, Key } from '../../core/crypto/key.js';
import { AcctStore } from './acct-store.js';
import { PassphraseStore } from './passphrase-store.js';
import { AbstractStore } from './abstract-store.js';
@@ -23,8 +23,7 @@ export class KeyStore extends AbstractStore {
}
public static getFirstOptional = async (acctEmail: string): Promise => {
- const stored = await AcctStore.get(acctEmail, ['keys']);
- const keys: KeyInfo[] = stored.keys || [];
+ const keys = await KeyStore.get(acctEmail);
return keys[0];
}
@@ -34,16 +33,23 @@ export class KeyStore extends AbstractStore {
return key as KeyInfo;
}
- public static getAllWithOptionalPassPhrase = async (acctEmail: string): Promise => {
+ public static getTypedKeyInfos = async (acctEmail: string): Promise => {
const keys = await KeyStore.get(acctEmail);
- const withPp: ExtendedKeyInfo[] = [];
+ const kis: TypedKeyInfo[] = [];
for (const ki of keys) {
const type = KeyUtil.getKeyType(ki.private);
- if (type === 'openpgp' || type === 'x509') {
- withPp.push({ ...ki, type, passphrase: await PassphraseStore.get(acctEmail, ki.fingerprints[0]) });
+ const id = ki.fingerprints[0];
+ if (type !== 'openpgp' && type !== 'x509') {
+ continue;
}
+ kis.push({ ...ki, type, id });
}
- return withPp;
+ return kis;
+ }
+
+ public static getAllWithOptionalPassPhrase = async (acctEmail: string): Promise => {
+ const keys = await KeyStore.getTypedKeyInfos(acctEmail);
+ return await Promise.all(keys.map(async (ki) => { return { ...ki, passphrase: await PassphraseStore.getByKeyIdentity(acctEmail, ki) }; }));
}
public static add = async (acctEmail: string, newKey: string | Key) => {
diff --git a/extension/js/common/platform/store/passphrase-store.ts b/extension/js/common/platform/store/passphrase-store.ts
index 509b282d04e..09db2b03de0 100644
--- a/extension/js/common/platform/store/passphrase-store.ts
+++ b/extension/js/common/platform/store/passphrase-store.ts
@@ -6,6 +6,7 @@ import { PromiseCancellation, Dict } from '../../core/common.js';
import { Ui } from '../../browser/ui.js';
import { OpenPGPKey } from '../../core/crypto/pgp/openpgp-key.js';
import { InMemoryStore } from './in-memory-store.js';
+import { KeyIdentity } from '../../core/crypto/key.js';
/**
* Local or session store of pass phrases
@@ -13,7 +14,7 @@ import { InMemoryStore } from './in-memory-store.js';
export class PassphraseStore extends AbstractStore {
/**
- * todo - make it only accept fingerprints
+ * todo - phase out in favour of KeyIdentity
*/
public static set = async (storageType: StorageType, acctEmail: string, fingerprintOrLongid: string, passphrase: string | undefined) => {
const longid = fingerprintOrLongid.length === 40 ? OpenPGPKey.fingerprintToLongid(fingerprintOrLongid) : fingerprintOrLongid;
@@ -32,7 +33,7 @@ export class PassphraseStore extends AbstractStore {
}
/**
- * todo - make it only accept fingerprints
+ * todo - phase out in favour of KeyIdentity
*/
public static get = async (acctEmail: string, fingerprintOrLongid: string, ignoreSession: boolean = false): Promise => {
const longid = fingerprintOrLongid.length === 40 ? OpenPGPKey.fingerprintToLongid(fingerprintOrLongid) : fingerprintOrLongid;
@@ -48,8 +49,13 @@ export class PassphraseStore extends AbstractStore {
return await InMemoryStore.get(acctEmail, storageIndex) ?? undefined;
}
+ public static getByKeyIdentity = async (acctEmail: string, keyIdentity: KeyIdentity, ignoreSession: boolean = false): Promise => {
+ // todo -- consider x509/openpgp ambiguity
+ return await PassphraseStore.get(acctEmail, keyIdentity.id, ignoreSession);
+ }
+
/**
- * todo - make it only accept fingerprints
+ * todo - phase out in favour of KeyIdentity
*/
public static waitUntilPassphraseChanged = async (
acctEmail: string, missingOrWrongPpKeyLongids: string[], interval = 1000, cancellation: PromiseCancellation = { cancel: false }
diff --git a/extension/js/common/view.ts b/extension/js/common/view.ts
index daefc9aa263..92a0bec3935 100644
--- a/extension/js/common/view.ts
+++ b/extension/js/common/view.ts
@@ -14,7 +14,7 @@ export abstract class View {
const view = new viewClass();
(async () => {
await view.render();
- view.setHandlers();
+ await Promise.resolve(view.setHandlers()); // allow both sync and async
View.setTestViewStateLoaded();
})().catch(View.reportAndRenderErr);
} catch (e) {
@@ -42,7 +42,7 @@ export abstract class View {
public abstract render(): Promise;
- public abstract setHandlers(): void;
+ public abstract setHandlers(): void | Promise;
public setHandler = (cb: (e: HTMLElement, event: JQuery.Event) => void | Promise, errHandlers?: BrowserEventErrHandler) => {
return Ui.event.handle(cb, errHandlers, this);
diff --git a/test/source/mock/google/google-endpoints.ts b/test/source/mock/google/google-endpoints.ts
index a7ed8fa1263..c61fab9df64 100644
--- a/test/source/mock/google/google-endpoints.ts
+++ b/test/source/mock/google/google-endpoints.ts
@@ -15,7 +15,8 @@ type DraftSaveModel = { message: { raw: string, threadId: string } };
const allowedRecipients: Array = ['flowcrypt.compatibility@gmail.com', 'human+manualcopypgp@flowcrypt.com',
'censored@email.com', 'test@email.com', 'human@flowcrypt.com', 'human+nopgp@flowcrypt.com', 'expired.on.attester@domain.com',
'ci.tests.gmail@flowcrypt.test', 'smime1@recipient.com', 'smime2@recipient.com', 'smime@recipient.com',
- 'smime.attachment@recipient.com', 'auto.refresh.expired.key@recipient.com', 'to@example.com', 'cc@example.com', 'bcc@example.com'];
+ 'smime.attachment@recipient.com', 'auto.refresh.expired.key@recipient.com', 'to@example.com', 'cc@example.com', 'bcc@example.com',
+ 'flowcrypt.test.key.multiple.inbox1@gmail.com', 'flowcrypt.test.key.multiple.inbox2@gmail.com'];
export const mockGoogleEndpoints: HandlersDefinition = {
'/o/oauth2/auth': async ({ query: { client_id, response_type, access_type, state, redirect_uri, scope, login_hint, proceed } }, req) => {
diff --git a/test/source/mock/google/strategies/send-message-strategy.ts b/test/source/mock/google/strategies/send-message-strategy.ts
index bd8386727f3..2eda9549aac 100644
--- a/test/source/mock/google/strategies/send-message-strategy.ts
+++ b/test/source/mock/google/strategies/send-message-strategy.ts
@@ -11,6 +11,7 @@ import { HttpClientErr } from '../../lib/api';
import { MsgUtil } from '../../../core/crypto/pgp/msg-util';
import { parsedMailAddressObjectAsArray } from '../google-endpoints.js';
import { Str } from '../../../core/common.js';
+import { GMAIL_RECOVERY_EMAIL_SUBJECTS } from '../../../core/const.js';
// TODO: Make a better structure of ITestMsgStrategy. Because this class doesn't test anything, it only saves message in the Mock
class SaveMessageInStorageStrategy implements ITestMsgStrategy {
@@ -232,6 +233,8 @@ export class TestBySubjectStrategyContext {
this.strategy = new SmimeEncryptedMessageStrategy();
} else if (subject.includes('send signed S/MIME without attachment')) {
this.strategy = new SmimeSignedMessageStrategy();
+ } else if (GMAIL_RECOVERY_EMAIL_SUBJECTS.includes(subject)) {
+ this.strategy = new SaveMessageInStorageStrategy();
} else {
throw new UnsuportableStrategyError(`There isn't any strategy for this subject: ${subject}`);
}
diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts
index 31a7eca21f4..559854735b6 100644
--- a/test/source/tests/settings.ts
+++ b/test/source/tests/settings.ts
@@ -18,7 +18,10 @@ import { testConstants } from './tooling/consts';
import { PageRecipe } from './page-recipe/abstract-page-recipe';
import { OauthPageRecipe } from './page-recipe/oauth-page-recipe';
import { Pubkey } from '../platform/store/contact-store';
-import { KeyInfo } from '../core/crypto/key';
+import { KeyInfo, KeyUtil } from '../core/crypto/key';
+import { Buf } from '../core/buf';
+import { GoogleData } from '../mock/google/google-data';
+import Parse from './../util/parse';
// tslint:disable:no-blank-lines-func
@@ -519,6 +522,293 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
await settingsPage.close();
}));
+ ava.default('settings - manual backup several keys to file with the same pass phrase', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage1 = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage1, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage1.close();
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultiple98acfa1eadab5b92, '1234',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultipleSmimeCEA2D53BB9D24871, '1234',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultipleSmimeA35068FD4E037879, '1234',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ // opening backup.htm independently of settings/index.htm page limits functionality but sufficient for this test
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}&action=backup_manual&parentTabId=1%3A0`));
+ // OpenPGP keys are checked, x509 keys are unchecked
+ expect(await backupPage.isChecked('[data-id="47FB03183E03A8ED44E3BBFCCEA2D53BB9D24871"]')).to.equal(false);
+ expect(await backupPage.isChecked('[data-id="5A08466253C956E9C76C2E95A35068FD4E037879"]')).to.equal(false);
+ expect(await backupPage.isChecked('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(true);
+ expect(await backupPage.isChecked('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(true);
+ // OpenPGP keys are enabled, x509 keys are disabled
+ expect(await backupPage.isDisabled('[data-id="47FB03183E03A8ED44E3BBFCCEA2D53BB9D24871"]')).to.equal(true);
+ expect(await backupPage.isDisabled('[data-id="5A08466253C956E9C76C2E95A35068FD4E037879"]')).to.equal(true);
+ expect(await backupPage.isDisabled('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(false);
+ expect(await backupPage.isDisabled('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(false);
+ await backupPage.waitAndClick('@input-backup-step3manual-file');
+ await backupPage.waitAndClick('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]'); // uncheck
+ // backing up to file when only one key is checked
+ const backupFileRawData1 = await backupPage.awaitDownloadTriggeredByClicking('@action-backup-step3manual-continue');
+ const { keys: keys1 } = await KeyUtil.readMany(Buf.fromUtfStr(backupFileRawData1.toString()));
+ expect(keys1.length).to.equal(1);
+ expect(keys1[0].id).to.equal("515431151DDD3EA232B37A4C98ACFA1EADAB5B92");
+ await backupPage.waitAndRespondToModal('info', 'confirm', 'Downloading private key backup file');
+ await backupPage.waitAndRespondToModal('info', 'confirm', 'Your private key has been successfully backed up');
+ await backupPage.waitAndClick('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]'); // check
+ // backing up to file when two keys are checked
+ const backupFileRawData2 = await backupPage.awaitDownloadTriggeredByClicking('@action-backup-step3manual-continue');
+ const { keys: keys2 } = await KeyUtil.readMany(Buf.fromUtfStr(backupFileRawData2.toString()));
+ expect(keys2.length).to.equal(2);
+ await backupPage.close();
+ }));
+
+ ava.default('settings - manual backup several keys to inbox with the same strong pass phrase', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple.inbox2@gmail.com';
+ const passphrase = 'strong enough passphrase for all keys';
+ const settingsPage1 = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ const key1b383d0334e38b28 = await KeyUtil.parse(testConstants.testKeyMultiple1b383d0334e38b28);
+ expect(await KeyUtil.decrypt(key1b383d0334e38b28, '1234')).to.equal(true);
+ await KeyUtil.encrypt(key1b383d0334e38b28, passphrase);
+ const key98acfa1eadab5b92 = await KeyUtil.parse(testConstants.testKeyMultiple98acfa1eadab5b92);
+ expect(await KeyUtil.decrypt(key98acfa1eadab5b92, '1234')).to.equal(true);
+ await SetupPageRecipe.manualEnter(settingsPage1, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: { title: '?', armored: KeyUtil.armor(key1b383d0334e38b28), passphrase, longid: '1b383d0334e38b28' }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage1.close();
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, KeyUtil.armor(key98acfa1eadab5b92), passphrase,
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ // opening backup.htm independently of settings/index.htm page limits functionality but sufficient for this test
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}&action=backup_manual&parentTabId=1%3A0`));
+ expect(await backupPage.isChecked('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(true);
+ expect(await backupPage.isChecked('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(true);
+ expect(await backupPage.isDisabled('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(false);
+ expect(await backupPage.isDisabled('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(false);
+ await backupPage.waitAndClick('@action-backup-step3manual-continue');
+ await backupPage.waitAndRespondToModal('info', 'confirm', 'Your private keys have been successfully backed up');
+ const sentMsg = (await GoogleData.withInitializedData(acctEmail)).getMessageBySubject('Your FlowCrypt Backup')!;
+ const mimeMsg = await Parse.convertBase64ToMimeMsg(sentMsg.raw!);
+ const { keys } = await KeyUtil.readMany(new Buf(mimeMsg.attachments[0]!.content!));
+ expect(keys.length).to.equal(2);
+ await backupPage.close();
+ }));
+
+ ava.default('settings - manual backup several keys to file with a missing but guessed pass phrase', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage.close();
+ const inboxPage = await browser.newPage(t, TestUrls.extension(`chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`));
+ await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultiple98acfa1eadab5b92, '1234',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ // opening backup.htm independently of settings/index.htm page limits functionality but sufficient for this test
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}` +
+ `&action=backup_manual&parentTabId=1%3A0`));
+ expect(await backupPage.isChecked('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(true);
+ expect(await backupPage.isChecked('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(true);
+ expect(await backupPage.isDisabled('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(false);
+ expect(await backupPage.isDisabled('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(false);
+ await backupPage.waitAndClick('@input-backup-step3manual-file');
+ // one passphrase is not known but successfully guessed
+ const backupFileRawData = await backupPage.awaitDownloadTriggeredByClicking('@action-backup-step3manual-continue');
+ const { keys } = await KeyUtil.readMany(Buf.fromUtfStr(backupFileRawData.toString()));
+ expect(keys.length).to.equal(2);
+ await backupPage.close();
+ }));
+
+ ava.default('settings - manual backup several keys to inbox with a missing pass phrase', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ const inboxPage = await browser.newPage(t, TestUrls.extension(`chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`));
+ await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await inboxPage.close();
+ const key98acfa1eadab5b92 = await KeyUtil.parse(testConstants.testKeyMultiple98acfa1eadab5b92);
+ expect(await KeyUtil.decrypt(key98acfa1eadab5b92, '1234')).to.equal(true);
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, KeyUtil.armor(key98acfa1eadab5b92), 'new passphrase strong enough',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ await settingsPage.waitAndClick('@action-open-backup-page');
+ const backupFrame = await settingsPage.getFrame(['backup.htm']);
+ await backupFrame.waitAndClick('@action-go-manual');
+ expect(await backupFrame.isChecked('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(true);
+ expect(await backupFrame.isChecked('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(true);
+ expect(await backupFrame.isDisabled('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(false);
+ expect(await backupFrame.isDisabled('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(false);
+ await backupFrame.waitAndClick('@action-backup-step3manual-continue');
+ await backupFrame.waitAndRespondToModal('error', 'confirm', 'Your keys are protected with different pass phrases');
+ await settingsPage.close();
+ }));
+
+ ava.default('settings - manual backup a key with a missing pass phrase', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ const key = { title: '?', armored: testConstants.testKeyMultiple1b383d0334e38b28, passphrase: '1234', longid: '1b383d0334e38b28' };
+ await SetupPageRecipe.manualEnter(settingsPage, 'unused', { submitPubkey: false, usedPgpBefore: false, key },
+ { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ const inboxPage = await browser.newPage(t, TestUrls.extension(`chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`));
+ await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await inboxPage.close();
+ await settingsPage.waitAndClick('@action-open-backup-page');
+ const backupFrame = await settingsPage.getFrame(['backup.htm']);
+ await backupFrame.waitAndClick('@action-go-manual');
+ await backupFrame.waitAndClick('@action-backup-step3manual-continue');
+ const ppFrame = await settingsPage.getFrame(['passphrase.htm']);
+ await ppFrame.waitAndType('@input-pass-phrase', key.passphrase);
+ await ppFrame.waitAndClick('@action-confirm-pass-phrase-entry');
+ await Util.sleep(2);
+ expect(ppFrame.frame.isDetached()).to.equal(true);
+ // todo: #4059 we would expect further iteraction with backupFrame here but it is actually wiped out
+ await settingsPage.close();
+ }));
+
+ ava.default('settings - manual backup several keys to file with different pass phrases', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage1 = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage1, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage1.close();
+ const key98acfa1eadab5b92 = await KeyUtil.parse(testConstants.testKeyMultiple98acfa1eadab5b92);
+ expect(await KeyUtil.decrypt(key98acfa1eadab5b92, '1234')).to.equal(true);
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, KeyUtil.armor(key98acfa1eadab5b92), 'new passphrase strong enough',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ // opening backup.htm independently of settings/index.htm page limits functionality but sufficient for this test
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}&action=backup_manual&parentTabId=1%3A0`));
+ expect(await backupPage.isChecked('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(true);
+ expect(await backupPage.isChecked('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(true);
+ expect(await backupPage.isDisabled('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(false);
+ expect(await backupPage.isDisabled('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(false);
+ await backupPage.waitAndClick('@input-backup-step3manual-file');
+ await backupPage.waitAndClick('@action-backup-step3manual-continue');
+ await backupPage.waitAndRespondToModal('error', 'confirm', 'Your keys are protected with different pass phrases');
+ await backupPage.close();
+ }));
+
+ ava.default('settings - setup_manual only backs up supplied key to file', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage1 = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage1, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage1.close();
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultiple98acfa1eadab5b92, '1234',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}&action=setup_manual` +
+ '&type=openpgp&id=515431151DDD3EA232B37A4C98ACFA1EADAB5B92&idToken=fakeheader.01'));
+ await backupPage.waitAndClick('@input-backup-step3manual-file');
+ const backupFileRawData = await backupPage.awaitDownloadTriggeredByClicking('@action-backup-step3manual-continue');
+ const { keys } = await KeyUtil.readMany(Buf.fromUtfStr(backupFileRawData.toString()));
+ expect(keys.length).to.equal(1);
+ expect(keys[0].id).to.equal("515431151DDD3EA232B37A4C98ACFA1EADAB5B92");
+ await backupPage.waitAndRespondToModal('info', 'confirm', 'Downloading private key backup file');
+ await backupPage.waitAll('@action-step4done-account-settings');
+ await backupPage.close();
+ }));
+
+ ava.default('settings - setup_manual only backs up supplied key to inbox', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple.inbox1@gmail.com';
+ const settingsPage1 = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage1, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage1.close();
+ const passphrase = 'strong enough passphrase for inbox backup';
+ const key98acfa1eadab5b92 = await KeyUtil.parse(testConstants.testKeyMultiple98acfa1eadab5b92);
+ expect(await KeyUtil.decrypt(key98acfa1eadab5b92, '1234')).to.equal(true);
+ await KeyUtil.encrypt(key98acfa1eadab5b92, passphrase);
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, KeyUtil.armor(key98acfa1eadab5b92), passphrase,
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}&action=setup_manual` +
+ '&type=openpgp&id=515431151DDD3EA232B37A4C98ACFA1EADAB5B92&idToken=fakeheader.01'));
+ await backupPage.waitAndClick('@action-backup-step3manual-continue');
+ await backupPage.waitAll('@action-step4done-account-settings');
+ const sentMsg = (await GoogleData.withInitializedData(acctEmail)).getMessageBySubject('Your FlowCrypt Backup')!;
+ const mimeMsg = await Parse.convertBase64ToMimeMsg(sentMsg.raw!);
+ const { keys } = await KeyUtil.readMany(new Buf(mimeMsg.attachments[0]!.content!));
+ expect(keys.length).to.equal(1);
+ expect(KeyUtil.identityEquals(keys[0], { id: '515431151DDD3EA232B37A4C98ACFA1EADAB5B92', type: 'openpgp' })).to.equal(true);
+ await backupPage.close();
+ }));
+
+ ava.default('settings - manual backup to inbox keys with weak pass phrases results in error', testWithBrowser(undefined, async (t, browser) => {
+ const acctEmail = 'flowcrypt.test.key.multiple@gmail.com';
+ const settingsPage1 = await BrowserRecipe.openSettingsLoginApprove(t, browser, acctEmail);
+ await SetupPageRecipe.manualEnter(settingsPage1, 'unused', {
+ submitPubkey: false,
+ usedPgpBefore: false,
+ key: {
+ title: '?',
+ armored: testConstants.testKeyMultiple1b383d0334e38b28,
+ passphrase: '1234',
+ longid: '1b383d0334e38b28',
+ }
+ }, { isSavePassphraseChecked: false, isSavePassphraseHidden: false });
+ await settingsPage1.close();
+ await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultiple98acfa1eadab5b92, '1234',
+ { isSavePassphraseChecked: true, isSavePassphraseHidden: false });
+ // opening backup.htm independently of settings/index.htm page limits functionality but sufficient for this test
+ const backupPage = await browser.newPage(t, TestUrls.extension(`/chrome/settings/modules/backup.htm?acctEmail=${acctEmail}&action=backup_manual&parentTabId=1%3A0`));
+ expect(await backupPage.isChecked('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(true);
+ expect(await backupPage.isChecked('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(true);
+ expect(await backupPage.isDisabled('[data-id="CB0485FE44FC22FF09AF0DB31B383D0334E38B28"]')).to.equal(false);
+ expect(await backupPage.isDisabled('[data-id="515431151DDD3EA232B37A4C98ACFA1EADAB5B92"]')).to.equal(false);
+ await backupPage.waitAndClick('@action-backup-step3manual-continue');
+ await backupPage.waitAndRespondToModal('warning', 'confirm', `It's too weak for this backup method`);
+ await Util.sleep(2);
+ await backupPage.waitAny('@action-show-confirm-new-pp');
+ await backupPage.close();
+ }));
+
ava.default('settings - manual enter and key update honor FORBID_STORING_PASS_PHRASE OrgRule', testWithBrowser(undefined, async (t, browser) => {
const { settingsPage, passphrase } = await BrowserRecipe.setUpFcForbidPpStoringAcct(t, browser);
const { cryptup_userforbidstoringpassphraseorgruleflowcrypttest_passphrase_B8F687BCDE14435A: savedPassphrase1,
diff --git a/test/source/tests/tooling/consts.ts b/test/source/tests/tooling/consts.ts
index ed093edc7dd..1d941df8278 100644
--- a/test/source/tests/tooling/consts.ts
+++ b/test/source/tests/tooling/consts.ts
@@ -279,6 +279,112 @@ Qhb2hMOaD7jK2qcP3mTFIBuYt0bPEZLhyMssa9ssK0dcY+JWs6LOeMPzCC1+bz7jCHiBCG5DBG8j
h6VN43eiceJMKCGcndDbIkmHT/JiAtUj2VdKXAldLPIuECUzXtTGL8LdqgSYvxo0b0RiIv6pBNI+
=Iqrh
-----END PGP PRIVATE KEY BLOCK-----`,
+ testKeyMultipleSmimeCEA2D53BB9D24871: `-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA2mG28OIpKeGji8ZrFL1adAQ+one3cSv2W3Qu658stV/55EQB
+xH98a4z2ZOCjz8vBRkoKf4yC9SPrxIJXBD2wZutWSnWBclbcvZJlmtG+mJH/Lqyk
+dIQH0gmYXhObmtDkOnH5nTiGSiVUSy4wEKpdzARpeGlqp+vOrIKImsyMW4CfBz4u
+iPRj0pN0Ugx6pLF6kFGKDS+VIItlhmIQcn/gQebEurwNE4A/hZEZxRguemPdFpJ3
+t0KH2T+f1TwXmFe258ZCQJeCOcgzTjCq5wSC+qSgVKqJY1+gEVBSwlhfshtazFAY
+BoE9Rg8dktfn4KUsUQaWJnsX7HrEoQNJyct9/QIDAQABAoIBAEWBklOK+CUPXxlu
+1sgsGG0SqMjXJ7jKkEe9a+2spPM5j/S6PIHJPdWRcR7cbOcHaAWyLTM5irjxbOoH
+viBobgvj3XwneELnKKzhupsTJQG0fi7h3hoDp+WPDkLrJLyavfloK1WCFyPkS7b+
+BWhYgTAdO2CK1NqLcCVyCn6Smh3I9LsA0z9VsMdPW4pKtQxNEuLMy3zb5Avy9RWH
++/K3ysm/HHZi7yT84VKKzJEdCBqyEqIDEPibulbEZkQuBrvfrOWzz1lqI63ZDFkm
+by8XtyDh3Tox228Es4gdxmk4QCwEOU/51CuSCMvWAaUWd0ob+VS4mISCijLkbBCf
+nT41nQECgYEA9K+ln0pMBTgHD4hUhA3tIr1X8YtSl08FbZYg/dCPhq4/S0CnJJ0G
+h9G4V7yyNyYu0yZj+FK9ZC46j8oTKTFt6vB+gQLHG/HowE0df4MbIGCA/vpbsZdO
+/+2WUtfJ/ecKx9/89wHQkQIX95LWLu4P9+xncq3kp+YbVpBkXq2yPL0CgYEA5Hqz
+qtHokU2PGHecKTbm6dkwYNqg2wO4JuN3CwPIDUUJDbonTHBKJOOfoFhGbza5vc75
+k8HiMWUz29cFZv8d0wSpTI94yESAS5GqEp38+E/HI/jozGv0UUnd7Z4QP7IlXDIn
+2l7n0p6tPDmdQITfUK+PjI/6ow//1kX8uL54ekECgYA5um8S486HtK5Fxd5awYZf
+GdjzzfEQbb5UGoVyHJCgL+AS+w/0HW/6/0nEuWo6hLGrc3VGkw7H4fhDEGPw5g6O
+zWqrOWKMf0hwhkEdYms1+k602ZBkl9Q1oXJD3VF0q9vpSDlAdZclfx70dBpAPD5c
+OdQ2RDycODfe9nYXuFdC6QKBgGlebR2KUMcyDZwrDUm0mCmgkD8alLRssrC8llnu
+FISKIfLCtvz5jDgXAbbg/xqrEl4G5fLOp5JAKfpo34TwgsCelbVJNVqmvMQdGWIc
+ml2p7R0Z3cLoxnP2rPK4wqWdcr1iJB8BRchMhXgQ2gS3QoXjcUBR5jTtW/9lp3dL
+4CQBAoGAJPZ8pb1wmMohs4ZvcnbH0Y+uoMdoxQ+FLD4PzaPlRvZUQ8/PIBYyJ/f+
+lpiLc4Tbuv6u4a6thk6X8i/hFQQs9Uo6cil5Onj7llOh9YrmAqPAeiZTZLwHhd1L
+6VL6r+WR5hdFzNldGFQdZXJwUXArSmUHebw8QESneljBahWgKC0=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIEUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADB0MRMwEQYDVQQIDApTb21l
+LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFzAVBgNV
+BAMMDlNvbWUgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJhdXRob3JpdHlAdGVz
+dC5jb20wIBcNMjEwOTI2MTAzOTUwWhgPMjEyMTA5MjYxMDM5NTBaMDAxLjAsBgNV
+BAMTJWZsb3djcnlwdC50ZXN0LmtleS5tdWx0aXBsZUBnbWFpbC5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaYbbw4ikp4aOLxmsUvVp0BD6id7dx
+K/ZbdC7rnyy1X/nkRAHEf3xrjPZk4KPPy8FGSgp/jIL1I+vEglcEPbBm61ZKdYFy
+Vty9kmWa0b6Ykf8urKR0hAfSCZheE5ua0OQ6cfmdOIZKJVRLLjAQql3MBGl4aWqn
+686sgoiazIxbgJ8HPi6I9GPSk3RSDHqksXqQUYoNL5Ugi2WGYhByf+BB5sS6vA0T
+gD+FkRnFGC56Y90Wkne3QofZP5/VPBeYV7bnxkJAl4I5yDNOMKrnBIL6pKBUqolj
+X6ARUFLCWF+yG1rMUBgGgT1GDx2S1+fgpSxRBpYmexfsesShA0nJy339AgMBAAGj
+MjAwMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgL0MBMGA1UdJQQMMAoGCCsGAQUF
+BwMEMA0GCSqGSIb3DQEBBQUAA4ICAQDbBf3xb7esrnxEjwL/2dvnHAqgUTVaf6O4
+hW9mUp4c727fbDqat9OlilI/fLcwBN/NgEf+9jSWq9uHvjklCIXNTaK8zTkuQdtX
+FXZjT7N9EXWO93wUFtBY/fUIhJAEaL7Z6rmWUkK6UW/mtV7I6gxiZ2GAAE2wFKyS
+atb/Q+sgyeSOZjxux9NRMh8ZXmHTBQ/RUkzyLFM4Kgn5qfbM2vpkVuwjc4aH6r6F
+qeRKf1HULybJn/aHQ6zxVqNYodekUHe55DIAA71X2PVEaltN7szE4K+obdcE4y2I
+OW0w2H/SvBgngkn1aIvHIOltSBczvnSqKyQnLae4FW8PJJyk/X1HkAZPlMIRXXl9
+eUsv81oSiSK7YmRGuObgldBeDYmmECXnknsOPekK+mBaBbNGZNFMS6iJZV4anl4n
+UGgG49YEW97yFhJ4r7CxLhP2Eo7Fbfax4wUDPfaNLv9OK3zn27sjA/QSI3q7eBvi
+vn9dCnAk/hIBj/FsiyZstcczmvnHE9w9NDdCc0bbYcfwHAAs7XNejjDcenA+9ngq
+4hQ6ofK/4WIVekDJLRvoxNY94qjErT6iXL5aduYu8BVri2Q3Jqrp9YILLVH83rkl
+W8ZiWSb2P6bWZG3sxVNxS5iM6v39NbubnqK5UxE6+/GCeo+lHRNzZhhR3VRpKVw2
+xSncWuKSTg==
+-----END CERTIFICATE-----`,
+ testKeyMultipleSmimeA35068FD4E037879: `-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAuhfYg465kFwSBiy2l8HPsLIhqub64Hnf4cQW0j2tK5vmnP1k
+QR3QbiGj3e9fPItetecNmDRvqLp3VibqPEzsaDJEJK2S+FGKb6asde2pTg4zfkrf
+VoMIr8gnMgnrmbY8p98T1ob+CzFMvGLxAjdDC0Jz4FTHC/WRmb++kE6KgE2ekFXe
+8hjS4I2RYq2glOY8SGIGhVQq+/Y2GIU6GT2OS+pK8jbkYbzYd6SMT6J0qW7H5M1e
+5y8bg98dbBmhUGWb4HXA1HhwgfPfEh5JfM0rANS1wMMJaelayZqmHXU2/6gBYlv9
+yyM32Jfpf/eGLlfats59BQVHqcSZaxxeme2f5QIDAQABAoIBAQCLj+KjF2D3cPVb
+iCEQQxv9yjoRr9SqkPxkluj+l57Nu8gCKM8NPszYK4Z3pVPpViSbi0LYyUqMYfup
+stmsi4wowLn2P+6fKJgIEBn6/1gf3+qiZoiEmxTir9slXedBijiHNXftRaQ0xK5m
+AlX5cXRod1fr/+ifgXXGttS16RFaEpQlWl5XHZzMiWfPFcAfuLomoV6bl97f45Jy
+I9LcZIv/0/USwzP56ZLodxZOV0D5pOtJmRfJZ3Zz4Hkh5Y7X1H5gDqNzZCvl+DAo
+UMLHCdbjfucIDesLAwizAYtrz/ozer1sKSyTYu0CF9t1jZrBqTeZAtfgQPPlzb1Y
+8orstFvpAoGBAOknVdklo3ju4qqOPhP2wiSmCcTzggfA6yC62SQVjEGpIzIbZHyF
+aE5VZKKl37JSP7Sv5ADhYqKlmQDpKh2JupmrBJag+OxacZZiAHaU/FO2+XNQGSeO
+61ZPbwZ3yc6Vsr80EwkVn8c//vvTjm2WFtXIPKd0Vj7fRwVx3BTz9RILAoGBAMxT
+/x4yu9AgCHICkJ4EXAW4WR/thR8jUj2pJINEisO2L1tj44G4chlZBdKySMSmArQr
+O20H9X7R4hTKXKvtPEtyzcknx+XbAV8tAzvWgoE8g2YnwoX7dkDIFBwqfZfrLVmP
+/oLzlMBAEYTcGtdd4xzxLptZSATZcOJOe/0bgrvPAoGAagJOkEsCxuum9/Fw3Fbn
+8w3jhz9IsgnPXrRWIogBm6ExtYiq6csmn+dHuIQ+769h89+9hYD0/grZRIa/dWq4
+A0K09V//jkeZOc0ZdNFM4StixEF5Dnl3G2lGi+RPR7Tc7v7YYNsKKOg6IXoRWlZR
+z6YZnfHRfQi8HAenD4fUt/0CgYBmtt7rnlNdl4zHS1TJqqVb1sUu0Kb39TiVeJ67
+QkyDf+Ukiv1GK6Nn7KGJvkfoWw/G5Gi2MacOfpGpI+UcJGEICIk8UrwhZ0u0PZt/
+jCEEuJfRVbFqyvMyM6IwWofSQ84DVOh7idlAH7Tu1frEQxU1amSkgWg7z9b4XL3l
+idvZbwKBgQCj4FU18598duZgT3tSeHYBJ3Anctus6JfqrPBMEpKjuuI8nDW1KWPZ
+P6fMYW/HwX2zpr9nhbZPZrZohDJjAx2S4NhTpXzLFpSdQK5xsTrcSBQNsKMalSFU
++75KlSfsvSpauGW8+S4yT+rBe/ZCQfXioAGnI3dsV9+kopkhf/32ng==
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIEUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADB0MRMwEQYDVQQIDApTb21l
+LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFzAVBgNV
+BAMMDlNvbWUgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJhdXRob3JpdHlAdGVz
+dC5jb20wIBcNMjEwOTI2MTA0NTM5WhgPMjEyMTA5MjYxMDQ1MzlaMDAxLjAsBgNV
+BAMTJWZsb3djcnlwdC50ZXN0LmtleS5tdWx0aXBsZUBnbWFpbC5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6F9iDjrmQXBIGLLaXwc+wsiGq5vrg
+ed/hxBbSPa0rm+ac/WRBHdBuIaPd7188i1615w2YNG+oundWJuo8TOxoMkQkrZL4
+UYpvpqx17alODjN+St9WgwivyCcyCeuZtjyn3xPWhv4LMUy8YvECN0MLQnPgVMcL
+9ZGZv76QToqATZ6QVd7yGNLgjZFiraCU5jxIYgaFVCr79jYYhToZPY5L6kryNuRh
+vNh3pIxPonSpbsfkzV7nLxuD3x1sGaFQZZvgdcDUeHCB898SHkl8zSsA1LXAwwlp
+6VrJmqYddTb/qAFiW/3LIzfYl+l/94YuV9q2zn0FBUepxJlrHF6Z7Z/lAgMBAAGj
+MjAwMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgL0MBMGA1UdJQQMMAoGCCsGAQUF
+BwMEMA0GCSqGSIb3DQEBBQUAA4ICAQDgqD8lwAQ7vUPUdgPsX8ZJrCbLAAebPUMO
+JllYuSJxWnHoNhiIzQV81Cb+6JfxXXcM3XPc9VCtU3Jz3b7tS+/uxtzZnQ4vU9SC
+aEApX4kiwPfeAaHDXe21hHjEEiIEY65ethodILqtXZ9JVnzFbGyZl8ZzOyn8BGQ3
+wVygQmj7vagGc41mFKTBPQvaY5mkx9YyfUfemZ2hsJ9LSXj9nZurISleLY6hgBYG
+cyH7hq+bQZzfC0F2MDq4Hv4n/KUQzNNd0FS45/98g9jIuXP/I2TonYjqUf0EckCd
+ayQs6QK9vZ+Mj/aC6e2K4FBhJEp0+Jm7b8r1FWuQoigGuXKGbY9atR0JaL5r77d/
+fFjOk8dPQgY8qJBntcGVFsjA63MbAuM0rKOFeWiXc1tR2qyE89Ovrcrj84d+AOg0
+zyIy1KhxeIunpI+H/9MJTv/1bInUU9TYypGtmFYW//3g2oyRQu08T9+R81sSQCFp
+5v0mtEb1Xd+8+FRUN7NXeT+9wZ29Zr5rTuUWt+RIqEc/TN30qdOj7rZiFq1GiFHW
+BAOvkzvTSeZKYEZdNEC0yicS5O3dO84HaZ4wBB42XvtFMYEPU6EHPn/RjsUjDGpj
+RiNuW9jHvl7KOpFRmx3OQR/B+AdzjTRqsN5PzLMygb9nx2BG7xV0sg14mkwWhsXk
+Z0Mdtqg+Vg==
+-----END CERTIFICATE-----`,
rsa1024subkeyOnly: `-----BEGIN PGP PRIVATE KEY BLOCK-----
xcLYBGANjIkBCADQU/PgpIj3pe0/A0HZS6r4DbwIb1Rr6sEGVE/juigYZJr8UYKGAA47rHWLiDW7
diff --git a/test/source/tests/unit-node.ts b/test/source/tests/unit-node.ts
index 83575eb551d..1f5d304345b 100644
--- a/test/source/tests/unit-node.ts
+++ b/test/source/tests/unit-node.ts
@@ -849,8 +849,8 @@ jLwe8W9IMt765T5x5oux9MmPDXF05xHfm4qfH/BMO3a802x5u2gJjJjuknrFdgXY
const parsed1 = await KeyUtil.parse(key1.private);
const parsed2 = await KeyUtil.parse(key2.private);
const kisWithPp: ExtendedKeyInfo[] = [ // supply both key1 and key2 for decrypt
- { ... await KeyUtil.keyInfoObj(parsed1), type: parsed1.type, passphrase },
- { ... await KeyUtil.keyInfoObj(parsed2), type: parsed2.type, passphrase },
+ { ... await KeyUtil.typedKeyInfoObj(parsed1), passphrase },
+ { ... await KeyUtil.typedKeyInfoObj(parsed2), passphrase },
];
// we are testing a private method here because the outcome of this method is not directly testable from the
// public method that uses it. It only makes the public method faster, which is hard to test.
@@ -936,7 +936,7 @@ jLwe8W9IMt765T5x5oux9MmPDXF05xHfm4qfH/BMO3a802x5u2gJjJjuknrFdgXY
const pubkeys = [await KeyUtil.parse(justPrimaryPub)];
const encrypted = await MsgUtil.encryptMessage({ pubkeys, data, armor: true }) as PgpMsgMethod.EncryptPgpArmorResult;
const parsed = await KeyUtil.parse(prvEncryptForSubkeyOnlyProtected);
- const kisWithPp: ExtendedKeyInfo[] = [{ ... await KeyUtil.keyInfoObj(parsed), type: parsed.type, passphrase }];
+ const kisWithPp: ExtendedKeyInfo[] = [{ ... await KeyUtil.typedKeyInfoObj(parsed), type: parsed.type, passphrase }];
const decrypted = await MsgUtil.decryptMessage({ kisWithPp, encryptedData: encrypted.data });
// todo - later we'll have an org rule for ignoring this, and then it will be expected to pass as follows:
// expect(decrypted.success).to.equal(true);
diff --git a/test/source/util/index.ts b/test/source/util/index.ts
index a1419fa4163..3b8eb1b2633 100644
--- a/test/source/util/index.ts
+++ b/test/source/util/index.ts
@@ -80,7 +80,7 @@ export class Config {
return await Promise.all(Config._secrets.keys
.filter(key => key.armored && titles.includes(key.title)).map(async key => {
const parsed = await KeyUtil.parse(key.armored!);
- return { ...await KeyUtil.keyInfoObj(parsed), type: parsed.type, passphrase: key.passphrase };
+ return { ...await KeyUtil.typedKeyInfoObj(parsed), passphrase: key.passphrase };
}));
}