Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,9 @@ export class ComposeDraftModule extends ViewModule<ComposeView> {
} else {
Xss.sanitizeRender(this.view.S.cached('prompt'), `${promptText}<br><br><a href="#" class="action_close">close</a>`).css({ display: 'block', height: '100%' });
}
this.view.S.cached('prompt').find('a.action_open_passphrase_dialog').click(this.view.setHandler(() => {
BrowserMsg.send.passphraseDialog(this.view.parentTabId, { type: 'draft', longids: ['primary'] });
this.view.S.cached('prompt').find('a.action_open_passphrase_dialog').click(this.view.setHandler(async () => {
const primaryKi = await KeyStore.getFirst(this.view.acctEmail);
BrowserMsg.send.passphraseDialog(this.view.parentTabId, { type: 'draft', longids: [primaryKi.longid] });
}));
this.view.S.cached('prompt').find('a.action_close').click(this.view.setHandler(() => this.view.renderModule.closeMsg()));
await this.view.storageModule.whenMasterPassphraseEntered();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export class ComposeRenderModule extends ViewModule<ComposeView> {
<br><br>I was not able to read your encrypted message because it was encrypted for a wrong key.
<br><br>My current public key is attached below. Please update your records and send me a new encrypted message.
<br><br>Thank you</div>`);
const [primaryKi] = await KeyStore.get(this.view.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.view.acctEmail);
const att = Att.keyinfoAsPubkeyAtt(primaryKi);
this.view.attsModule.attach.addFile(new File([att.getData()], att.name));
this.view.sendBtnModule.popover.toggleItemTick($('.action-toggle-encrypt-sending-option'), 'encrypt', false); // don't encrypt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class ComposeStorageModule extends ViewModule<ComposeView> {
let result = await this.view.myPubkeyModule.chooseMyPublicKeyBySenderEmail(keys, senderEmail);
if (!result) {
this.view.errModule.debug(`ComposerStorage.getKey: could not find key based on senderEmail: ${senderEmail}, using primary instead`);
result = keys.find(ki => ki.primary);
result = keys[0];
Assert.abortAndRenderErrorIfKeyinfoEmpty(result);
} else {
this.view.errModule.debug(`ComposerStorage.getKey: found key based on senderEmail: ${senderEmail}`);
Expand Down Expand Up @@ -100,7 +100,7 @@ export class ComposeStorageModule extends ViewModule<ComposeView> {

public passphraseGet = async (senderKi?: KeyInfo) => {
if (!senderKi) {
[senderKi] = await KeyStore.get(this.view.acctEmail, ['primary']);
senderKi = await KeyStore.getFirst(this.view.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(senderKi);
}
return await PassphraseStore.get(this.view.acctEmail, senderKi.fingerprint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BaseMailFormatter } from './base-mail-formatter.js';
import { ComposerResetBtnTrigger } from '../compose-err-module.js';
import { Mime, SendableMsgBody } from '../../../../js/common/core/mime.js';
import { NewMsgData } from '../compose-types.js';
import { Str, Value } from '../../../../js/common/core/common.js';
import { Str, Url, Value } from '../../../../js/common/core/common.js';
import { ApiErr } from '../../../../js/common/api/shared/api-error.js';
import { Att } from '../../../../js/common/core/att.js';
import { Buf } from '../../../../js/common/core/buf.js';
Expand Down Expand Up @@ -143,8 +143,12 @@ export class EncryptedMsgMailFormatter extends BaseMailFormatter {
return undefined;
}
for (const myKey of pubs.filter(ap => ap.isMine)) {
if (await myKey.pubkey.usableButExpired) {
const path = chrome.runtime.getURL(`chrome/settings/index.htm?acctEmail=${encodeURIComponent(myKey.email)}&page=%2Fchrome%2Fsettings%2Fmodules%2Fmy_key_update.htm`);
if (myKey.pubkey.usableButExpired) {
const path = Url.create(chrome.runtime.getURL('chrome/settings/index.htm'), {
acctEmail: myKey.email,
page: '/chrome/settings/modules/my_key_update.htm',
pageUrlParams: JSON.stringify({ fingerprint: myKey.pubkey.id }),
});
const errModalLines = [
'This message could not be encrypted because your own Private Key is expired.',
'',
Expand Down
14 changes: 7 additions & 7 deletions extension/chrome/elements/passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ View.run(class PassphraseView extends View {
private readonly parentTabId: string;
private readonly longids: string[];
private readonly type: string;
private myPrivateKeys: KeyInfo[] | undefined;
private keysWeNeedPassPhraseFor: KeyInfo[] | undefined;

constructor() {
super();
Expand All @@ -35,7 +35,7 @@ View.run(class PassphraseView extends View {
Ui.event.protect();
await initPassphraseToggle(['passphrase']);
const allPrivateKeys = await KeyStore.get(this.acctEmail);
this.myPrivateKeys = allPrivateKeys.filter(ki => this.longids.includes(ki.longid) || (ki.primary && this.longids.includes('primary')));
this.keysWeNeedPassPhraseFor = allPrivateKeys.filter(ki => this.longids.includes(ki.longid));
if (this.type === 'embedded') {
$('h1').parent().css('display', 'none');
$('div.separator').css('display', 'none');
Expand All @@ -55,12 +55,12 @@ View.run(class PassphraseView extends View {
$('#passphrase').focus();
if (allPrivateKeys.length > 1) {
let html: string;
if (this.myPrivateKeys.length === 1) {
html = `For key Fingerprint: <span class="good">${Xss.escape(Str.spaced(this.myPrivateKeys[0].fingerprint || ''))}</span>`;
if (this.keysWeNeedPassPhraseFor.length === 1) {
html = `For key Fingerprint: <span class="good">${Xss.escape(Str.spaced(this.keysWeNeedPassPhraseFor[0].fingerprint || ''))}</span>`;
} else {
html = 'Pass phrase needed for any of the following keys:';
for (const i of this.myPrivateKeys.keys()) {
html += `<div>Fingerprint ${String(i + 1)}: <span class="good">${Xss.escape(Str.spaced(this.myPrivateKeys[i].fingerprint) || '')}</span></div>`;
for (const i of this.keysWeNeedPassPhraseFor.keys()) {
html += `<div>Fingerprint ${String(i + 1)}: <span class="good">${Xss.escape(Str.spaced(this.keysWeNeedPassPhraseFor[i].fingerprint) || '')}</span></div>`;
}
}
Xss.sanitizeRender('.which_key', html);
Expand Down Expand Up @@ -106,7 +106,7 @@ View.run(class PassphraseView extends View {
const pass = String($('#passphrase').val());
const storageType: StorageType = $('.forget').prop('checked') ? 'session' : 'local';
let atLeastOneMatched = false;
for (const keyinfo of this.myPrivateKeys!) { // if passphrase matches more keys, it will save the pass phrase for all keys
for (const keyinfo of this.keysWeNeedPassPhraseFor!) { // if passphrase matches more keys, it will save the pass phrase for all keys
const prv = await KeyUtil.parse(keyinfo.private);
try {
if (await KeyUtil.decrypt(prv, pass) === true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class PgpBlockViewDecryptModule {
this.view.renderModule.renderText('Decrypting...');
await this.decryptAndRender(encryptedData, optionalPwd);
} else {
const [primaryKi] = await KeyStore.get(this.view.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.view.acctEmail);
if (!result.longids.chosen && !primaryKi) {
await this.view.errorModule.renderErr(Lang.pgpBlock.notProperlySetUp + this.view.errorModule.btnHtml('FlowCrypt settings', 'green settings'), undefined);
} else if (result.error.type === DecryptErrTypes.keyMismatch) {
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/index.htm
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ <h1 class="text-center">FlowCrypt Settings</h1>

<div class="row settings-icons-rows">
<div class="col-sm-2 col-xs-6">
<a href="#" class="show_settings_page" page="modules/my_key.htm" data-test="action-open-pubkey-page">
<a href="#" class="action_open_public_key_page" data-test="action-open-pubkey-page">
<span class="box">
<img src="/img/svgs/pub-key-icon.svg" class="pub-key-icon" alt="Your Public Keys">
</span>
Expand Down
15 changes: 9 additions & 6 deletions extension/chrome/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ View.run(class SettingsView extends View {
Catch.report(`Unknown target page in element: ${target.outerHTML}`);
}
}));
$('.action_open_public_key_page').click(this.setHandler(async target => {
const ki = await KeyStore.getFirst(this.acctEmail!);
const escapedFp = Xss.escape(ki.fingerprint);
await Settings.renderSubPage(this.acctEmail!, this.tabId, 'modules/my_key.htm', `&fingerprint=${escapedFp}`);
}));
$('.action_show_encrypted_inbox').click(this.setHandler(target => {
window.location.href = Url.create('/chrome/settings/inbox/inbox.htm', { acctEmail: this.acctEmail! });
}));
Expand Down Expand Up @@ -425,18 +430,16 @@ View.run(class SettingsView extends View {
const created = new Date(prv.created);
const date = Str.monthName(created.getMonth()) + ' ' + created.getDate() + ', ' + created.getFullYear();
const escapedFp = Xss.escape(ki.fingerprint);
let escapedPrimaryOrRm = '';
if (ki.primary) {
escapedPrimaryOrRm = '(primary)';
} else if (canRemoveKey) {
escapedPrimaryOrRm = `(<a href="#" class="action_remove_key" data-test="action-remove-key" fingerprint="${escapedFp}">remove</a>)`;
let removeKeyBtn = '';
if (canRemoveKey && privateKeys.length > 1) {
removeKeyBtn = `(<a href="#" class="action_remove_key" data-test="action-remove-key" fingerprint="${escapedFp}">remove</a>)`;
}
const escapedEmail = Xss.escape(prv.emails[0] || '');
const escapedLink = `<a href="#" data-test="action-show-key-${i}" class="action_show_key" page="modules/my_key.htm" addurltext="&fingerprint=${escapedFp}">${escapedEmail}</a>`;
const fpHtml = `fingerprint:&nbsp;<span class="good">${Str.spaced(escapedFp)}</span>`;
const space = `&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`;
html += `<div class="row key-content-row key_${escapedFp}">`;
html += ` <div class="col-sm-12">${escapedLink} from ${Xss.escape(date)}${space}${fpHtml}${space}${escapedPrimaryOrRm}</div>`;
html += ` <div class="col-sm-12">${escapedLink} from ${Xss.escape(date)}${space}${fpHtml}${space}${removeKeyBtn}</div>`;
html += `</div>`;
}
Xss.sanitizeAppend('.key_list', html);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class BackupAutomaticModule extends ViewModule<BackupView> {
}

private setupCreateSimpleAutomaticInboxBackup = async () => {
const [primaryKi] = await KeyStore.get(this.view.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.view.acctEmail);
if (!(await KeyUtil.parse(primaryKi.private)).fullyEncrypted) {
await Ui.modal.warning('Key not protected with a pass phrase, skipping');
throw new UnreportableError('Key not protected with a pass phrase, skipping');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class BackupManualActionModule extends ViewModule<BackupView> {

private actionManualBackupHandler = async () => {
const selected = $('input[type=radio][name=input_backup_choice]:checked').val();
const [primaryKi] = await KeyStore.get(this.view.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.view.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(primaryKi);
if (! await this.isPrivateKeyEncrypted(primaryKi)) {
await Ui.modal.error('Sorry, cannot back up private key because it\'s not protected with a pass phrase.');
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/modules/change_passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ View.run(class ChangePassPhraseView extends View {
$('#step_0_enter_current #current_pass_phrase').attr('placeholder', 'Current primary key pass phrase');
$('#step_1_enter_new #new_pass_phrase').attr('placeholder', 'Enter a new primary key pass phrase');
}
const [primaryKi] = await KeyStore.get(this.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.acctEmail);
this.primaryKi = primaryKi;
Assert.abortAndRenderErrorIfKeyinfoEmpty(this.primaryKi);
const storedOrSessionPp = await PassphraseStore.get(this.acctEmail, this.primaryKi.fingerprint);
Expand Down
1 change: 0 additions & 1 deletion extension/chrome/settings/modules/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ View.run(class ExperimentalView extends View {
for (const keyinfo of keyinfos) {
text.push('');
text.push('key_longid: ' + keyinfo.longid);
text.push('key_primary: ' + keyinfo.primary);
text.push(keyinfo.private);
}
text.push('');
Expand Down
4 changes: 2 additions & 2 deletions extension/chrome/settings/modules/keyserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ View.run(class KeyserverView extends View {
return await Ui.modal.error('Disallowed by your organisation rules');
}
Xss.sanitizeRender(target, Ui.spinner('white'));
const [primaryKi] = await KeyStore.get(this.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(primaryKi);
try {
await this.pubLookup.attester.initialLegacySubmit(String($(target).attr('email')), primaryKi.public);
Expand All @@ -97,7 +97,7 @@ View.run(class KeyserverView extends View {
return await Ui.modal.error('Disallowed by your organisation rules');
}
Xss.sanitizeRender(target, Ui.spinner('white'));
const [primaryKi] = await KeyStore.get(this.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(primaryKi);
try {
const responseText = await this.pubLookup.attester.replacePubkey(String($(target).attr('email')), primaryKi.public);
Expand Down
4 changes: 2 additions & 2 deletions extension/chrome/settings/modules/my_key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ View.run(class MyKeyView extends View {
super();
const uncheckedUrlParams = Url.parse(['acctEmail', 'fingerprint', 'parentTabId']);
this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
this.fingerprint = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'fingerprint') || 'primary';
this.fingerprint = Assert.urlParamRequire.string(uncheckedUrlParams, 'fingerprint');
this.myKeyUserIdsUrl = Url.create('my_key_user_ids.htm', uncheckedUrlParams);
this.myKeyUpdateUrl = Url.create('my_key_update.htm', uncheckedUrlParams);
}
Expand All @@ -45,8 +45,8 @@ View.run(class MyKeyView extends View {
this.orgRules = await OrgRules.newInstance(this.acctEmail);
this.pubLookup = new PubLookup(this.orgRules);
[this.keyInfo] = await KeyStore.get(this.acctEmail, [this.fingerprint]);
this.pubKey = await KeyUtil.parse(this.keyInfo.public);
Assert.abortAndRenderErrorIfKeyinfoEmpty(this.keyInfo);
this.pubKey = await KeyUtil.parse(this.keyInfo.public);
$('.action_view_user_ids').attr('href', this.myKeyUserIdsUrl);
$('.action_view_update').attr('href', this.myKeyUpdateUrl);
$('.fingerprint').text(Str.spaced(this.keyInfo.fingerprint));
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/modules/my_key_update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ View.run(class MyKeyUpdateView extends View {
super();
const uncheckedUrlParams = Url.parse(['acctEmail', 'fingerprint', 'parentTabId']);
this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
this.fingerprint = Assert.urlParamRequire.optionalString(uncheckedUrlParams, 'fingerprint') || 'primary';
this.fingerprint = Assert.urlParamRequire.string(uncheckedUrlParams, 'fingerprint');
this.showKeyUrl = Url.create('my_key.htm', uncheckedUrlParams);
}

Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/modules/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ View.run(class SecurityView extends View {

public render = async () => {
await initPassphraseToggle(['passphrase_entry']);
[this.primaryKi] = await KeyStore.get(this.acctEmail, ['primary']);
this.primaryKi = await KeyStore.getFirst(this.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(this.primaryKi);
this.authInfo = await AcctStore.authInfo(this.acctEmail);
const storage = await AcctStore.get(this.acctEmail, ['hide_message_password', 'outgoing_language']);
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/modules/test_passphrase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ View.run(class TestPassphrase extends View {
}

public render = async () => {
const [keyInfo] = await KeyStore.get(this.acctEmail, ['primary']);
const keyInfo = await KeyStore.getFirst(this.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(keyInfo);
await initPassphraseToggle(['password']);
this.primaryKey = await KeyUtil.parse(keyInfo.private);
Expand Down
2 changes: 1 addition & 1 deletion extension/chrome/settings/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class SetupView extends View {
}

public submitPublicKeysAndFinalizeSetup = async ({ submit_main, submit_all }: { submit_main: boolean, submit_all: boolean }): Promise<void> => {
const [primaryKi] = await KeyStore.get(this.acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(this.acctEmail);
Assert.abortAndRenderErrorIfKeyinfoEmpty(primaryKi);
try {
await this.submitPublicKeyIfNeeded(primaryKi.public, { submit_main, submit_all });
Expand Down
2 changes: 1 addition & 1 deletion extension/js/common/api/email-provider/sendable-msg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class SendableMsg {
public sign?: (signable: string) => Promise<string>;

public static create = async (acctEmail: string, { from, recipients, subject, body, atts, thread, type, isDraft }: SendableMsgDefinition): Promise<SendableMsg> => {
const [primaryKi] = await KeyStore.get(acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(acctEmail);
const headers: Dict<string> = primaryKi ? { OpenPGP: `id=${primaryKi.longid}` } : {}; // todo - use autocrypt format
return new SendableMsg(
acctEmail,
Expand Down
2 changes: 1 addition & 1 deletion extension/js/common/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class Assert {

public static abortAndRenderErrOnUnprotectedKey = async (acctEmail?: string, tabId?: string) => {
if (acctEmail) {
const [primaryKi] = await KeyStore.get(acctEmail, ['primary']);
const primaryKi = await KeyStore.getFirst(acctEmail);
const { setup_done } = await AcctStore.get(acctEmail, ['setup_done']);
if (setup_done && primaryKi && !(await KeyUtil.parse(primaryKi.private)).fullyEncrypted) {
if (window.location.pathname === '/chrome/settings/index.htm') {
Expand Down
1 change: 0 additions & 1 deletion extension/js/common/core/crypto/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export interface KeyInfo extends PrvKeyInfo {
// this cannot be Pubkey has it's being passed to localstorage
public: string;
fingerprint: string;
primary: boolean;
}

export type PrvPacket = (OpenPGP.packet.SecretKey | OpenPGP.packet.SecretSubkey);
Expand Down
24 changes: 11 additions & 13 deletions extension/js/common/platform/store/key-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ export class KeyStore extends AbstractStore {
if (!fingerprints) {
return keys;
}
return keys.filter(ki => {
if (fingerprints.includes('primary') && ki.primary) {
return true;
}
if (fingerprints.includes(ki.fingerprint)) {
return true;
}
return false;
});
return keys.filter(ki => fingerprints.includes(ki.fingerprint));
}

public static getFirst = async (acctEmail: string): Promise<KeyInfo> => {
const stored = await AcctStore.get(acctEmail, ['keys']);
const keys: KeyInfo[] = stored.keys || [];
return keys[0];
}

public static getAllWithPp = async (acctEmail: string): Promise<KeyInfo[]> => {
Expand All @@ -45,12 +43,12 @@ export class KeyStore extends AbstractStore {
}
for (const i in keyinfos) {
if (prv.id === keyinfos[i].fingerprint) { // replacing a key
keyinfos[i] = await KeyStore.keyInfoObj(prv, keyinfos[i].primary);
keyinfos[i] = await KeyStore.keyInfoObj(prv);
updated = true;
}
}
if (!updated) {
keyinfos.push(await KeyStore.keyInfoObj(prv, keyinfos.length === 0));
keyinfos.push(await KeyStore.keyInfoObj(prv));
}
await AcctStore.set(acctEmail, { keys: keyinfos });
}
Expand All @@ -75,10 +73,10 @@ export class KeyStore extends AbstractStore {
return result;
}

public static keyInfoObj = async (prv: Key, primary = false): Promise<KeyInfo> => {
public static keyInfoObj = async (prv: Key): Promise<KeyInfo> => {
const pubArmor = KeyUtil.armor(await KeyUtil.asPublicKey(prv));
const longid = OpenPGPKey.fingerprintToLongid(prv.id);
return { private: KeyUtil.armor(prv), public: pubArmor, primary, longid, fingerprint: prv.id };
return { private: KeyUtil.armor(prv), public: pubArmor, longid, fingerprint: prv.id };
}

}
15 changes: 0 additions & 15 deletions test/source/platform/store/key-store.ts

This file was deleted.