diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mail/extensions/openpgp/test/unit/rnp/test_secretKeys.js | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/comm/mail/extensions/openpgp/test/unit/rnp/test_secretKeys.js b/comm/mail/extensions/openpgp/test/unit/rnp/test_secretKeys.js new file mode 100644 index 0000000000..772c5caae4 --- /dev/null +++ b/comm/mail/extensions/openpgp/test/unit/rnp/test_secretKeys.js @@ -0,0 +1,384 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests for secret keys. + */ + +"use strict"; + +const { RNP, RnpPrivateKeyUnlockTracker } = ChromeUtils.import( + "chrome://openpgp/content/modules/RNP.jsm" +); +const { OpenPGPMasterpass } = ChromeUtils.import( + "chrome://openpgp/content/modules/masterpass.jsm" +); +const { EnigmailConstants } = ChromeUtils.import( + "chrome://openpgp/content/modules/constants.jsm" +); +const { EnigmailKeyRing } = ChromeUtils.import( + "chrome://openpgp/content/modules/keyRing.jsm" +); +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); +const { OpenPGPTestUtils } = ChromeUtils.import( + "resource://testing-common/mozmill/OpenPGPTestUtils.jsm" +); + +const keyDir = "../../../../../test/browser/openpgp/data/keys"; + +/** + * Initialize OpenPGP add testing keys. + */ +add_setup(async function () { + do_get_profile(); + + await OpenPGPTestUtils.initOpenPGP(); +}); + +add_task(async function testSecretKeys() { + let pass = await OpenPGPMasterpass.retrieveOpenPGPPassword(); + let newKeyId = await RNP.genKey( + "Erin <erin@example.com>", + "ECC", + 0, + 30, + pass + ); + + Assert.ok( + newKeyId != null && typeof newKeyId == "string", + "RNP.genKey() should return a non null string with a key ID" + ); + + let keyObj = EnigmailKeyRing.getKeyById(newKeyId); + Assert.ok( + keyObj && keyObj.secretAvailable, + "EnigmailKeyRing.getKeyById should return an object with a secret key" + ); + + let fpr = keyObj.fpr; + + Assert.ok( + keyObj.iSimpleOneSubkeySameExpiry(), + "check iSimpleOneSubkeySameExpiry should succeed" + ); + + let allFingerprints = [fpr, keyObj.subKeys[0].fpr]; + + let keyTrackers = []; + for (let fp of allFingerprints) { + let tracker = RnpPrivateKeyUnlockTracker.constructFromFingerprint(fp); + await tracker.unlock(); + keyTrackers.push(tracker); + } + + let expiryChanged = await RNP.changeExpirationDate( + allFingerprints, + 100 * 24 * 60 * 60 + ); + Assert.ok(expiryChanged, "changeExpirationDate should return success"); + + for (let t of keyTrackers) { + t.release(); + } + + let backupPassword = "new-password-1234"; + + let backupKeyBlock = await RNP.backupSecretKeys([fpr], backupPassword); + + let expectedString = "END PGP PRIVATE KEY BLOCK"; + + Assert.ok( + backupKeyBlock.includes(expectedString), + "backup of secret key should contain the string: " + expectedString + ); + + await RNP.deleteKey(fpr, true); + + EnigmailKeyRing.clearCache(); + + keyObj = EnigmailKeyRing.getKeyById(newKeyId); + Assert.ok( + !keyObj, + "after deleting the key we should be unable to find it in the keyring" + ); + + let alreadyProvidedWrongPassword = false; + + let getWrongPassword = function (win, keyId, resultFlags) { + if (alreadyProvidedWrongPassword) { + resultFlags.canceled = true; + return ""; + } + + alreadyProvidedWrongPassword = true; + return "wrong-password"; + }; + + let importResult = await RNP.importSecKeyBlockImpl( + null, + getWrongPassword, + false, + backupKeyBlock + ); + + Assert.ok(importResult.exitCode != 0, "import should have failed"); + + let getGoodPassword = function (win, keyId, resultFlags) { + return backupPassword; + }; + + importResult = await RNP.importSecKeyBlockImpl( + null, + getGoodPassword, + false, + backupKeyBlock + ); + + Assert.ok(importResult.exitCode == 0, "import result code should be 0"); + + keyObj = EnigmailKeyRing.getKeyById(newKeyId); + + Assert.ok( + keyObj && keyObj.secretAvailable, + "after import, EnigmailKeyRing.getKeyById should return an object with a secret key" + ); +}); + +add_task(async function testImportSecretKeyIsProtected() { + let carolFile = do_get_file( + `${keyDir}/carol@example.com-0x3099ff1238852b9f-secret.asc` + ); + let carolSec = await IOUtils.readUTF8(carolFile.path); + + // Carol's secret key is protected with password "x". + let getCarolPassword = function (win, keyId, resultFlags) { + return "x"; + }; + + let importResult = await RNP.importSecKeyBlockImpl( + null, + getCarolPassword, + false, + carolSec + ); + + Assert.equal( + importResult.exitCode, + 0, + "Should be able to import Carol's secret key" + ); + + let aliceFile = do_get_file( + `${keyDir}/alice@openpgp.example-0xf231550c4f47e38e-secret.asc` + ); + let aliceSec = await IOUtils.readUTF8(aliceFile.path); + + // Alice's secret key is unprotected. + importResult = await RNP.importSecKeyBlockImpl(null, null, false, aliceSec); + + Assert.equal( + importResult.exitCode, + 0, + "Should be able to import Alice's secret key" + ); + + let [prot, unprot] = OpenPGPTestUtils.getProtectedKeysCount(); + Assert.notEqual(prot, 0, "Should have protected secret keys"); + Assert.equal(unprot, 0, "Should not have any unprotected secret keys"); +}); + +add_task(async function testImportOfflinePrimaryKey() { + let importResult = await OpenPGPTestUtils.importPrivateKey( + null, + do_get_file(`${keyDir}/ofelia-secret-subkeys.asc`) + ); + + Assert.equal( + importResult[0], + "0x97DCDA5E56EBB822", + "expected key id should have been reported" + ); + + let primaryKey = await RNP.findKeyByEmail("<ofelia@openpgp.example>", false); + + let encSubKey = RNP.getSuitableSubkey(primaryKey, "encrypt"); + let keyId = RNP.getKeyIDFromHandle(encSubKey); + Assert.equal( + keyId, + "31C31DF1DFB67601", + "should obtain key ID of encryption subkey" + ); + + let sigSubKey = RNP.getSuitableSubkey(primaryKey, "sign"); + let keyIdSig = RNP.getKeyIDFromHandle(sigSubKey); + Assert.equal( + keyIdSig, + "1BC8F5764D348FE1", + "should obtain key ID of signing subkey" + ); + + // Test that we can sign with a signing subkey + // (this ensures that our code can unlock the secret subkey). + // Ofelia's key has no secret key for the primary key available, + // which further ensures that signing used the subkey. + + let sourceText = "we-sign-this-text"; + let signResult = {}; + + let signArgs = { + aliasKeys: new Map(), + armor: true, + bcc: [], + encrypt: false, + encryptToSender: false, + sender: "0x97DCDA5E56EBB822", + senderKeyIsExternal: false, + sigTypeClear: true, + sigTypeDetached: false, + sign: true, + signatureHash: "SHA256", + to: ["<alice@openpgp.example>"], + }; + + await RNP.encryptAndOrSign(sourceText, signArgs, signResult); + + Assert.ok(!signResult.exitCode, "signing with subkey should work"); +}); + +add_task(async function testSecretForPreferredSignSubkeyIsMissing() { + let secBlock = await IOUtils.readUTF8( + do_get_file( + `${keyDir}/secret-for-preferred-sign-subkey-is-missing--a-without-second-sub--sec.asc` + ).path + ); + + let cancelPassword = function (win, keyId, resultFlags) { + resultFlags.canceled = true; + return ""; + }; + + let importResult = await RNP.importSecKeyBlockImpl( + null, + cancelPassword, + false, + secBlock + ); + + Assert.ok(importResult.exitCode == 0); + + let pubBlock = await IOUtils.readUTF8( + do_get_file( + `${keyDir}/secret-for-preferred-sign-subkey-is-missing--b-with-second-sub--pub.asc` + ).path + ); + + importResult = await RNP.importPubkeyBlockAutoAcceptImpl( + null, + pubBlock, + null // acceptance + ); + + Assert.ok(importResult.exitCode == 0); + + let primaryKey = await RNP.findKeyByEmail( + "<secret-for-preferred-sign-subkey-is-missing@example.com>", + false + ); + + let signSubKey = RNP.getSuitableSubkey(primaryKey, "sign"); + let keyId = RNP.getKeyIDFromHandle(signSubKey); + Assert.equal( + keyId, + "625D4819F02EE727", + "should obtain key ID of older, non-preferred subkey that has the secret key available" + ); +}); + +// If we an existing public key, with multiple subkeys, and then we +// import the secret key, but one of the existing public subkeys is +// missing, test that we don't fail to import (bug 1795698). +add_task(async function testNoSecretForExistingPublicSubkey() { + let pubBlock = await IOUtils.readUTF8( + do_get_file(`${keyDir}/two-enc-subkeys-still-both.pub.asc`).path + ); + + let importResult = await RNP.importPubkeyBlockAutoAcceptImpl( + null, + pubBlock, + null // acceptance + ); + + Assert.ok(importResult.exitCode == 0); + + let secBlock = await IOUtils.readUTF8( + do_get_file(`${keyDir}/two-enc-subkeys-one-deleted.sec.asc`).path + ); + + let cancelPassword = function (win, keyId, resultFlags) { + resultFlags.canceled = true; + return ""; + }; + + importResult = await RNP.importSecKeyBlockImpl( + null, + cancelPassword, + false, + secBlock + ); + + Assert.ok(importResult.exitCode == 0); +}); + +add_task(async function testImportAndBackupUntweakedECCKey() { + const untweakedFile = do_get_file(`${keyDir}/untweaked-secret.asc`); + const untweakedSecKey = await IOUtils.readUTF8(untweakedFile.path); + + const getGoodPasswordForTweaked = function (win, keyId, resultFlags) { + return "pass112233"; + }; + + const importResult = await RNP.importSecKeyBlockImpl( + null, + getGoodPasswordForTweaked, + false, + untweakedSecKey + ); + + Assert.ok(importResult.exitCode == 0); + const fpr = "492965A6F56DAD2423B3506E849F29B0020707F7"; + + const backupPassword = "new-password-1234"; + const backupKeyBlock = await RNP.backupSecretKeys([fpr], backupPassword); + const expectedString = "END PGP PRIVATE KEY BLOCK"; + + Assert.ok( + backupKeyBlock.includes(expectedString), + "backup of secret key should contain the string: " + expectedString + ); + + await RNP.deleteKey(fpr, true); + + EnigmailKeyRing.clearCache(); +}); + +// Sanity check for bug 1790610 and bug 1792450, test that our passphrase +// reading code, which can run through repair code for corrupted profiles, +// will not replace our existing and good data. +// Ideally this test should restart the application, but is is difficult. +// We simulate a restart by erasing the cache and forcing it to read +// data again from disk (which will run the consistency checks and +// could potentially execute the repair code). +add_task(async function testRereadingPassphrase() { + let pass1 = await OpenPGPMasterpass.retrieveOpenPGPPassword(); + OpenPGPMasterpass.cachedPassword = null; + let pass2 = await OpenPGPMasterpass.retrieveOpenPGPPassword(); + Assert.equal( + pass1, + pass2, + "openpgp passphrase should remain the same after cache invalidation" + ); +}); |