Skip to content

Commit ef76ca9

Browse files
authored
initial import of S/MIME key (#3938)
* initial import of S/MIME key * non-async SmimeKey.asPublicKey * Added warning when importing S/MIME key as default * Added AddKey test for unprotected S/MIME private key
1 parent b37f73c commit ef76ca9

File tree

8 files changed

+52
-24
lines changed

8 files changed

+52
-24
lines changed

extension/chrome/settings/index.htm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ <h1 class="text-center">FlowCrypt Settings</h1>
269269
<script src="/lib/jquery.min.js"></script>
270270
<script src="/lib/sweetalert2.js"></script>
271271
<script src="/lib/openpgp.js"></script>
272+
<script src="/lib/forge.js"></script>
272273
<script src="/lib/bootstrap/bootstrap.min.js"></script>
273274
<script src="index.js" type="module"></script>
274275

extension/chrome/settings/modules/my_key.htm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<script src="/lib/jquery.min.js"></script>
5858
<script src="/lib/sweetalert2.js"></script>
5959
<script src="/lib/openpgp.js"></script>
60+
<script src="/lib/forge.js"></script>
6061
<script src="/lib/clipboard.js"></script>
6162
<script src="my_key.js" type="module"></script>
6263
</body>

extension/chrome/settings/setup.htm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ <h1>Set Up FlowCrypt</h1>
327327
<script src="/lib/sweetalert2.js"></script>
328328
<script src="/lib/zxcvbn.js"></script>
329329
<script src="/lib/openpgp.js"></script>
330+
<script src="/lib/forge.js"></script>
330331
<script src="setup.js" type="module"></script>
331332
<script src="../settings/index.js" type="module"></script>
332333

extension/chrome/settings/setup/setup-import-key.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export class SetupImportKeyModule {
3030
};
3131
try {
3232
const checked = await this.view.keyImportUi.checkPrv(this.view.acctEmail, String($('#step_2b_manual_enter .input_private_key').val()), options.passphrase);
33+
if (checked.decrypted.type === 'x509') {
34+
if (!await Ui.modal.confirm('Using S/MIME as the only key on account is experimental. '
35+
+ 'You should instead import an OpenPGP key here, and then add S/MIME keys as additional keys in FlowCrypt Settings.' +
36+
'\n\nContinue anyway? (not recommented).')) {
37+
return;
38+
}
39+
}
3340
Xss.sanitizeRender('#step_2b_manual_enter .action_add_private_key', Ui.spinner('white'));
3441
await this.view.saveKeysAndPassPhrase([checked.encrypted], options);
3542
await this.view.preFinalizeSetup(options);

extension/js/common/core/crypto/key.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,13 @@ export class KeyUtil {
220220
return `[-] ${String(value)}`;
221221
}
222222

223-
public static asPublicKey = async (pubkey: Key): Promise<Key> => {
224-
// TODO: Delegate to appropriate key type
225-
if (pubkey.type === 'openpgp') {
226-
return await OpenPGPKey.asPublicKey(pubkey);
223+
public static asPublicKey = async (key: Key): Promise<Key> => {
224+
if (key.type === 'openpgp') {
225+
return await OpenPGPKey.asPublicKey(key);
226+
} else if (key.type === 'x509') {
227+
return SmimeKey.asPublicKey(key);
227228
}
228-
// TODO: Assuming S/MIME keys are already public: this should be fixed.
229-
return pubkey;
229+
throw new UnexpectedKeyTypeError(`Key type is ${key.type}, expecting OpenPGP or x509 S/MIME`);
230230
}
231231

232232
public static expired = (key: Key): boolean => {

extension/js/common/core/crypto/smime/smime-key.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
22
import * as forge from 'node-forge';
3-
import { Key } from '../key.js';
3+
import { Key, UnexpectedKeyTypeError } from '../key.js';
44
import { Str } from '../../common.js';
55
import { UnreportableError } from '../../../platform/catch.js';
66
import { PgpArmor } from '../pgp/pgp-armor.js';
@@ -129,6 +129,16 @@ export class SmimeKey {
129129
key.fullyEncrypted = true;
130130
}
131131

132+
public static asPublicKey = (key: Key): Key => {
133+
if (key.type !== 'x509') {
134+
throw new UnexpectedKeyTypeError(`Key type is ${key.type}, expecting x509 S/MIME`);
135+
}
136+
if (key.isPrivate) {
137+
return SmimeKey.getKeyFromCertificate(SmimeKey.getArmoredCertificate(key), undefined);
138+
}
139+
return key;
140+
}
141+
132142
private static getLeafCertificates = (msgBlocks: MsgBlock[]): { pem: string, certificate: forge.pki.Certificate }[] => {
133143
const parsed = msgBlocks.map(cert => { return { pem: cert.content as string, certificate: forge.pki.certificateFromPem(cert.content as string) }; });
134144
// Note: no signature check is performed.

test/source/tests/settings.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,12 @@ export let defineSettingsTests = (testVariant: TestVariant, testWithBrowser: Tes
472472
{ isSavePassphraseChecked: true, isSavePassphraseHidden: false });
473473
}));
474474

475+
ava.default('settings - add unprotected s/mime key', testWithBrowser('ci.tests.gmail', async (t, browser) => {
476+
const unprotectedPrvKey = fs.readFileSync('test/samples/smime/human-unprotected-pem.txt', 'utf8');
477+
await SettingsPageRecipe.addKeyTest(t, browser, 'ci.tests.gmail@flowcrypt.test', unprotectedPrvKey, 'this is a new passphrase to protect previously unprotected key',
478+
{ isSavePassphraseChecked: true, isSavePassphraseHidden: false });
479+
}));
480+
475481
ava.default('settings - error modal when page parameter invalid', testWithBrowser('ci.tests.gmail', async (t, browser) => {
476482
const invalidParamModalPage = await browser.newPage(t, TestUrls.extension(`chrome/settings/index.htm?acctEmail=ci.tests.gmail@gmail.com&page=invalid`));
477483
await Util.sleep(3);

test/source/tests/setup.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -722,23 +722,25 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
722722
}
723723
}));
724724

725-
// todo - change to an "add key" instead of initial import test
726-
// todo - disable initial import of s/mime key
727-
// todo - disable import of encrypted s/mime key, require decrypted?
728-
// ava.default.only(
729-
// 'setup - s/mime private key',
730-
// testWithBrowser(undefined, async (t, browser) => {
731-
// const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, 'flowcrypt.test.key.imported@gmail.com');
732-
// const key = {
733-
// title: 's/mime pkcs12 encrypted key',
734-
// filePath: 'test/samples/smime/human-unprotected-PKCS12.p12',
735-
// armored: null,
736-
// passphrase: 'test pp to encrypt unprotected key',
737-
// longid: null
738-
// };
739-
// await SetupPageRecipe.manualEnter(settingsPage, key.title, { submitPubkey: false, usedPgpBefore: false, key });
740-
// })
741-
// );
725+
ava.default(
726+
'setup - s/mime private key',
727+
testWithBrowser(undefined, async (t, browser) => {
728+
const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, 'flowcrypt.test.key.imported@gmail.com');
729+
const key = {
730+
title: 's/mime pkcs12 unprotected key',
731+
filePath: 'test/samples/smime/human-unprotected-PKCS12.p12',
732+
armored: null, // tslint:disable-line:no-null-keyword
733+
passphrase: 'test pp to encrypt unprotected key',
734+
longid: null // tslint:disable-line:no-null-keyword
735+
};
736+
await SetupPageRecipe.manualEnter(settingsPage, key.title, { fillOnly: true, submitPubkey: false, usedPgpBefore: false, key });
737+
await settingsPage.waitAndClick('@input-step2bmanualenter-save', { delay: 1 });
738+
await Util.sleep(1);
739+
await settingsPage.waitAndRespondToModal('confirm', 'confirm', 'Using S/MIME as the only key on account is experimental.');
740+
await settingsPage.waitAndClick('@action-step4done-account-settings', { delay: 1 });
741+
await SettingsPageRecipe.ready(settingsPage);
742+
})
743+
);
742744

743745
}
744746

0 commit comments

Comments
 (0)