451 lines
14 KiB
Diff
451 lines
14 KiB
Diff
# HG changeset patch
|
|
# User Kai Engert <kaie@kuix.de>
|
|
# Date 1666897160 -7200
|
|
# Thu Oct 27 20:59:20 2022 +0200
|
|
# Node ID af0b1f5e4c7710f824c6141103e516ca60bc78aa
|
|
# Parent adfbf6378df82c8b2e087427a48ddc5cbe13aadd
|
|
Bug 1791195 - Add RNP security rules to obsolete our patches to RNP. r=mkmelin,o.nickolay
|
|
|
|
diff --git a/comm/mail/extensions/openpgp/content/modules/RNP.jsm b/comm/mail/extensions/openpgp/content/modules/RNP.jsm
|
|
--- a/comm/mail/extensions/openpgp/content/modules/RNP.jsm
|
|
+++ b/comm/mail/extensions/openpgp/content/modules/RNP.jsm
|
|
@@ -1863,12 +1863,12 @@ var RNP = {
|
|
|
|
if (keyBlockStr.length > RNP.maxImportKeyBlockSize) {
|
|
throw new Error("rejecting big keyblock");
|
|
}
|
|
|
|
- let tempFFI = new RNPLib.rnp_ffi_t();
|
|
- if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
|
|
+ let tempFFI = RNPLib.prepare_ffi();
|
|
+ if (!tempFFI) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
let pubKey;
|
|
if (!this.importToFFI(tempFFI, keyBlockStr, true, false, permissive)) {
|
|
@@ -1892,12 +1892,12 @@ var RNP = {
|
|
|
|
if (keyBlockStr.length > RNP.maxImportKeyBlockSize) {
|
|
throw new Error("rejecting big keyblock");
|
|
}
|
|
|
|
- let tempFFI = new RNPLib.rnp_ffi_t();
|
|
- if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
|
|
+ let tempFFI = RNPLib.prepare_ffi();
|
|
+ if (!tempFFI) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
let keyList = null;
|
|
if (!this.importToFFI(tempFFI, keyBlockStr, pubkey, seckey, permissive)) {
|
|
@@ -1929,12 +1929,12 @@ var RNP = {
|
|
async mergePublicKeyBlocks(fingerprint, ...keyBlocks) {
|
|
if (keyBlocks.some(b => b.length > RNP.maxImportKeyBlockSize)) {
|
|
throw new Error("keyBlock too big");
|
|
}
|
|
|
|
- let tempFFI = new RNPLib.rnp_ffi_t();
|
|
- if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
|
|
+ let tempFFI = RNPLib.prepare_ffi();
|
|
+ if (!tempFFI) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
const pubkey = true;
|
|
const seckey = false;
|
|
@@ -2067,12 +2067,12 @@ var RNP = {
|
|
let result = {};
|
|
result.exitCode = -1;
|
|
result.importedKeys = [];
|
|
result.errorMsg = "";
|
|
|
|
- let tempFFI = new RNPLib.rnp_ffi_t();
|
|
- if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
|
|
+ let tempFFI = RNPLib.prepare_ffi();
|
|
+ if (!tempFFI) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
// TODO: check result
|
|
if (this.importToFFI(tempFFI, keyBlockStr, pubkey, seckey, permissive)) {
|
|
@@ -3115,12 +3115,12 @@ var RNP = {
|
|
*
|
|
*/
|
|
export_pubkey_strip_sigs_uids(expKey, keepUserIDs, out_binary) {
|
|
let expKeyId = this.getKeyIDFromHandle(expKey);
|
|
|
|
- let tempFFI = new RNPLib.rnp_ffi_t();
|
|
- if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
|
|
+ let tempFFI = RNPLib.prepare_ffi();
|
|
+ if (!tempFFI) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
let exportFlags =
|
|
RNPLib.RNP_KEY_EXPORT_SUBKEYS | RNPLib.RNP_KEY_EXPORT_PUBLIC;
|
|
@@ -3399,12 +3399,12 @@ var RNP = {
|
|
))
|
|
) {
|
|
throw new Error("rnp_output_to_armor failed:" + rv);
|
|
}
|
|
|
|
- let tempFFI = new RNPLib.rnp_ffi_t();
|
|
- if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
|
|
+ let tempFFI = RNPLib.prepare_ffi();
|
|
+ if (!tempFFI) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
let internalPassword = await OpenPGPMasterpass.retrieveOpenPGPPassword();
|
|
|
|
diff --git a/comm/mail/extensions/openpgp/content/modules/RNPLib.jsm b/mail/extensions/openpgp/content/modules/RNPLib/comm.jsm
|
|
--- a/comm/mail/extensions/openpgp/content/modules/RNPLib.jsm
|
|
+++ b/comm/mail/extensions/openpgp/content/modules/RNPLib.jsm
|
|
@@ -13,11 +13,11 @@ XPCOMUtils.defineLazyModuleGetters(this,
|
|
OpenPGPMasterpass: "chrome://openpgp/content/modules/masterpass.jsm",
|
|
Services: "resource://gre/modules/Services.jsm",
|
|
setTimeout: "resource://gre/modules/Timer.jsm",
|
|
});
|
|
|
|
-const MIN_RNP_VERSION = [0, 16, 0];
|
|
+const MIN_RNP_VERSION = [0, 16, 2];
|
|
|
|
var systemOS = Services.appinfo.OS.toLowerCase();
|
|
var abi = ctypes.default_abi;
|
|
|
|
// Open librnp. Determine the path to the chrome directory and look for it
|
|
@@ -149,10 +149,12 @@ function enableRNPLibJS() {
|
|
// this must be delayed until after "librnp" is initialized
|
|
|
|
RNPLib = {
|
|
path: librnpPath,
|
|
|
|
+ // Handle to the RNP library and primary key data store.
|
|
+ // Kept at null if init fails.
|
|
ffi: null,
|
|
|
|
// returns rnp_input_t, destroy using rnp_input_destroy
|
|
async createInputFromPath(path) {
|
|
// IOUtils.read always returns an array.
|
|
@@ -265,13 +267,204 @@ function enableRNPLibJS() {
|
|
const min_version = this.rnp_version_for(...MIN_RNP_VERSION);
|
|
const this_version = this.rnp_version();
|
|
return Boolean(this_version >= min_version);
|
|
},
|
|
|
|
+ /**
|
|
+ * Prepare an RNP library handle, and in addition set all the
|
|
+ * application's preferences for library behavior.
|
|
+ *
|
|
+ * Other application code should NOT call rnp_ffi_create directly,
|
|
+ * but obtain an RNP library handle from this function.
|
|
+ */
|
|
+ prepare_ffi() {
|
|
+ let ffi = new rnp_ffi_t();
|
|
+ if (this._rnp_ffi_create(ffi.address(), "GPG", "GPG")) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ // Treat MD5 as insecure.
|
|
+ if (
|
|
+ this.rnp_add_security_rule(
|
|
+ ffi,
|
|
+ this.RNP_FEATURE_HASH_ALG,
|
|
+ this.RNP_ALGNAME_MD5,
|
|
+ this.RNP_SECURITY_OVERRIDE,
|
|
+ 0,
|
|
+ this.RNP_SECURITY_INSECURE
|
|
+ )
|
|
+ ) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ // Use RNP's default rule for SHA1 used with data signatures,
|
|
+ // and use our override to allow it for key signatures.
|
|
+ if (
|
|
+ this.rnp_add_security_rule(
|
|
+ ffi,
|
|
+ this.RNP_FEATURE_HASH_ALG,
|
|
+ this.RNP_ALGNAME_SHA1,
|
|
+ this.RNP_SECURITY_VERIFY_KEY | this.RNP_SECURITY_OVERRIDE,
|
|
+ 0,
|
|
+ this.RNP_SECURITY_DEFAULT
|
|
+ )
|
|
+ ) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ // Security rules API does not yet support PK and SYMM algs.
|
|
+ //
|
|
+ // If a hash algorithm is already disabled at build time,
|
|
+ // and an attempt is made to set a security rule for that
|
|
+ // algorithm, then RNP returns a failure.
|
|
+ //
|
|
+ // Ideally, RNP should allow these calls (regardless of build time
|
|
+ // settings) to define an application security rule, that is
|
|
+ // independent of the configuration used for building the
|
|
+ // RNP library.
|
|
+
|
|
+ if (
|
|
+ this.rnp_add_security_rule(
|
|
+ ffi,
|
|
+ this.RNP_FEATURE_HASH_ALG,
|
|
+ this.RNP_ALGNAME_SM3,
|
|
+ this.RNP_SECURITY_OVERRIDE,
|
|
+ 0,
|
|
+ this.RNP_SECURITY_PROHIBITED
|
|
+ )
|
|
+ ) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ if (
|
|
+ this.rnp_add_security_rule(
|
|
+ ffi,
|
|
+ this.RNP_FEATURE_PK_ALG,
|
|
+ this.RNP_ALGNAME_SM2,
|
|
+ this.RNP_SECURITY_OVERRIDE,
|
|
+ 0,
|
|
+ this.RNP_SECURITY_PROHIBITED
|
|
+ )
|
|
+ ) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ if (
|
|
+ this.rnp_add_security_rule(
|
|
+ ffi,
|
|
+ this.RNP_FEATURE_SYMM_ALG,
|
|
+ this.RNP_ALGNAME_SM4,
|
|
+ this.RNP_SECURITY_OVERRIDE,
|
|
+ 0,
|
|
+ this.RNP_SECURITY_PROHIBITED
|
|
+ )
|
|
+ ) {
|
|
+ return null;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ return ffi;
|
|
+ },
|
|
+
|
|
+ /**
|
|
+ * Test the correctness of security rules, in particular, test
|
|
+ * if the given hash algorithm is allowed at the given time.
|
|
+ *
|
|
+ * This is an application consistency test. If the behavior isn't
|
|
+ * according to the expectation, the function throws an error.
|
|
+ *
|
|
+ * @param {string} hashAlg - Test this hash algorithm
|
|
+ * @param {time_t} time - Test status at this timestamp
|
|
+ * @param {boolean} keySigAllowed - Test if using the hash algorithm
|
|
+ * is allowed for signatures found inside OpenPGP keys.
|
|
+ * @param {boolean} dataSigAllowed - Test if using the hash algorithm
|
|
+ * is allowed for signatures on data.
|
|
+ */
|
|
+ _confirmSecurityRule(hashAlg, time, keySigAllowed, dataSigAllowed) {
|
|
+ let level = new ctypes.uint32_t();
|
|
+ let flag = new ctypes.uint32_t();
|
|
+
|
|
+ flag.value = this.RNP_SECURITY_VERIFY_DATA;
|
|
+ let testDataSuccess = false;
|
|
+ if (
|
|
+ !RNPLib.rnp_get_security_rule(
|
|
+ this.ffi,
|
|
+ this.RNP_FEATURE_HASH_ALG,
|
|
+ hashAlg,
|
|
+ time,
|
|
+ flag.address(),
|
|
+ null,
|
|
+ level.address()
|
|
+ )
|
|
+ ) {
|
|
+ if (dataSigAllowed) {
|
|
+ testDataSuccess = level.value == RNPLib.RNP_SECURITY_DEFAULT;
|
|
+ } else {
|
|
+ testDataSuccess = level.value < RNPLib.RNP_SECURITY_DEFAULT;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!testDataSuccess) {
|
|
+ throw new Error("security configuration for data signatures failed");
|
|
+ }
|
|
+
|
|
+ flag.value = this.RNP_SECURITY_VERIFY_KEY;
|
|
+ let testKeySuccess = false;
|
|
+ if (
|
|
+ !RNPLib.rnp_get_security_rule(
|
|
+ this.ffi,
|
|
+ this.RNP_FEATURE_HASH_ALG,
|
|
+ hashAlg,
|
|
+ time,
|
|
+ flag.address(),
|
|
+ null,
|
|
+ level.address()
|
|
+ )
|
|
+ ) {
|
|
+ if (keySigAllowed) {
|
|
+ testKeySuccess = level.value == RNPLib.RNP_SECURITY_DEFAULT;
|
|
+ } else {
|
|
+ testKeySuccess = level.value < RNPLib.RNP_SECURITY_DEFAULT;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (!testKeySuccess) {
|
|
+ throw new Error("security configuration for key signatures failed");
|
|
+ }
|
|
+ },
|
|
+
|
|
+ /**
|
|
+ * Perform tests that the RNP library behaves according to the
|
|
+ * defined security rules.
|
|
+ * If a problem is found, the function throws an error.
|
|
+ */
|
|
+ _sanityCheckSecurityRules() {
|
|
+ let time_t_now = Math.round(Date.now() / 1000);
|
|
+ let ten_years_in_seconds = 10 * 365 * 24 * 60 * 60;
|
|
+ let ten_years_future = time_t_now + ten_years_in_seconds;
|
|
+
|
|
+ this._confirmSecurityRule(this.RNP_ALGNAME_MD5, time_t_now, false, false);
|
|
+ this._confirmSecurityRule(
|
|
+ this.RNP_ALGNAME_MD5,
|
|
+ ten_years_future,
|
|
+ false,
|
|
+ false
|
|
+ );
|
|
+
|
|
+ this._confirmSecurityRule(this.RNP_ALGNAME_SHA1, time_t_now, true, false);
|
|
+ this._confirmSecurityRule(
|
|
+ this.RNP_ALGNAME_SHA1,
|
|
+ ten_years_future,
|
|
+ true,
|
|
+ false
|
|
+ );
|
|
+ },
|
|
+
|
|
async init() {
|
|
- this.ffi = new rnp_ffi_t();
|
|
- if (this.rnp_ffi_create(this.ffi.address(), "GPG", "GPG")) {
|
|
+ this.ffi = this.prepare_ffi();
|
|
+ if (!this.ffi) {
|
|
throw new Error("Couldn't initialize librnp.");
|
|
}
|
|
|
|
this.rnp_ffi_set_log_fd(this.ffi, 2); // stderr
|
|
|
|
@@ -286,10 +479,18 @@ function enableRNPLibJS() {
|
|
null
|
|
);
|
|
|
|
let { pubRingPath, secRingPath } = this.getFilenames();
|
|
|
|
+ try {
|
|
+ this._sanityCheckSecurityRules();
|
|
+ } catch (e) {
|
|
+ // Disable all RNP operation
|
|
+ this.ffi = null;
|
|
+ throw e;
|
|
+ }
|
|
+
|
|
await this.loadWithFallback(pubRingPath, this.RNP_LOAD_SAVE_PUBLIC_KEYS);
|
|
await this.loadWithFallback(secRingPath, this.RNP_LOAD_SAVE_SECRET_KEYS);
|
|
|
|
let pubnum = new ctypes.size_t();
|
|
this.rnp_get_public_key_count(this.ffi, pubnum.address());
|
|
@@ -481,10 +682,14 @@ function enableRNPLibJS() {
|
|
* @param {string} path - The file path to save to.
|
|
* @param {number} keyRingFlag - RNP_LOAD_SAVE_PUBLIC_KEYS or
|
|
* RNP_LOAD_SAVE_SECRET_KEYS.
|
|
*/
|
|
async saveKeyRing(path, keyRingFlag) {
|
|
+ if (!this.ffi) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
let oldPath = path + ".old";
|
|
|
|
// Ignore failure, oldPath might not exist yet.
|
|
await IOUtils.copy(path, oldPath).catch(() => {});
|
|
|
|
@@ -540,10 +745,13 @@ function enableRNPLibJS() {
|
|
tmpPath: path + ".tmp-new",
|
|
});
|
|
},
|
|
|
|
async saveKeys() {
|
|
+ if (!this.ffi) {
|
|
+ return;
|
|
+ }
|
|
let { pubRingPath, secRingPath } = this.getFilenames();
|
|
|
|
let saveThem = async () => {
|
|
await this.saveKeyRing(pubRingPath, this.RNP_LOAD_SAVE_PUBLIC_KEYS);
|
|
await this.saveKeyRing(secRingPath, this.RNP_LOAD_SAVE_SECRET_KEYS);
|
|
@@ -600,11 +808,13 @@ function enableRNPLibJS() {
|
|
abi,
|
|
ctypes.char.ptr
|
|
),
|
|
|
|
// Get a RNP library handle.
|
|
- rnp_ffi_create: librnp.declare(
|
|
+ // Mark with leading underscore, to clarify that this function
|
|
+ // shouldn't be called directly - you should call prepare_ffi().
|
|
+ _rnp_ffi_create: librnp.declare(
|
|
"rnp_ffi_create",
|
|
abi,
|
|
rnp_result_t,
|
|
rnp_ffi_t.ptr,
|
|
ctypes.char.ptr,
|
|
@@ -1713,10 +1923,22 @@ function enableRNPLibJS() {
|
|
ctypes.uint32_t.ptr,
|
|
ctypes.uint64_t.ptr,
|
|
ctypes.uint32_t.ptr
|
|
),
|
|
|
|
+ rnp_add_security_rule: librnp.declare(
|
|
+ "rnp_add_security_rule",
|
|
+ abi,
|
|
+ rnp_result_t,
|
|
+ rnp_ffi_t,
|
|
+ ctypes.char.ptr,
|
|
+ ctypes.char.ptr,
|
|
+ ctypes.uint32_t,
|
|
+ ctypes.uint64_t,
|
|
+ ctypes.uint32_t
|
|
+ ),
|
|
+
|
|
rnp_result_t,
|
|
rnp_ffi_t,
|
|
rnp_password_cb_t,
|
|
rnp_input_t,
|
|
rnp_output_t,
|
|
@@ -1748,11 +1970,26 @@ function enableRNPLibJS() {
|
|
|
|
RNP_KEY_SIGNATURE_NON_SELF_SIG: 4,
|
|
|
|
RNP_SUCCESS: 0x00000000,
|
|
|
|
+ RNP_FEATURE_SYMM_ALG: "symmetric algorithm",
|
|
RNP_FEATURE_HASH_ALG: "hash algorithm",
|
|
+ RNP_FEATURE_PK_ALG: "public key algorithm",
|
|
+ RNP_ALGNAME_MD5: "MD5",
|
|
+ RNP_ALGNAME_SHA1: "SHA1",
|
|
+ RNP_ALGNAME_SM2: "SM2",
|
|
+ RNP_ALGNAME_SM3: "SM3",
|
|
+ RNP_ALGNAME_SM4: "SM4",
|
|
+
|
|
+ RNP_SECURITY_OVERRIDE: 1,
|
|
+ RNP_SECURITY_VERIFY_KEY: 2,
|
|
+ RNP_SECURITY_VERIFY_DATA: 4,
|
|
+ RNP_SECURITY_REMOVE_ALL: 65536,
|
|
+
|
|
+ RNP_SECURITY_PROHIBITED: 0,
|
|
+ RNP_SECURITY_INSECURE: 1,
|
|
RNP_SECURITY_DEFAULT: 2,
|
|
|
|
/* Common error codes */
|
|
RNP_ERROR_GENERIC: 0x10000000, // 268435456
|
|
RNP_ERROR_BAD_FORMAT: 0x10000001, // 268435457
|