Skip to content

Commit

Permalink
Add config.allowInsecureVerificationWithReformattedKeys (#1422)
Browse files Browse the repository at this point in the history
 Using `openpgp.reformatKey` with the default `date` option would render
messages signed with the original key unverifiable by OpenPGP.js v5 (not v4),
since the signing key would not be considered valid at the time of signing (due
to its self-certification signature being in the future, compared to the
message signature creation time).

This commit adds `config.allowInsecureVerificationWithReformattedKeys` (false
by default) to make it possible to still verify such messages with the
reformatted key provided the key is valid at the `date` specified for
verification (which defaults to the current time).
  • Loading branch information
larabr committed Oct 18, 2021
1 parent b7527f7 commit 88b1380
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 2 deletions.
9 changes: 9 additions & 0 deletions src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ export default {
* @property {Boolean} allowInsecureDecryptionWithSigningKeys
*/
allowInsecureDecryptionWithSigningKeys: false,
/**
* Allow verification of message signatures with keys whose validity at the time of signing cannot be determined.
* Instead, a verification key will also be consider valid as long as it is valid at the current time.
* This setting is potentially insecure, but it is needed to verify messages signed with keys that were later reformatted,
* and have self-signature's creation date that does not match the primary key creation date.
* @memberof module:config
* @property {Boolean} allowInsecureDecryptionWithSigningKeys
*/
allowInsecureVerificationWithReformattedKeys: false,

/**
* @memberof module:config
Expand Down
14 changes: 13 additions & 1 deletion src/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,19 @@ async function createVerificationObject(signature, literalDataList, verification
}
// We pass the signature creation time to check whether the key was expired at the time of signing.
// We check this after signature verification because for streamed one-pass signatures, the creation time is not available before
await primaryKey.getSigningKey(unverifiedSigningKey.getKeyID(), signaturePacket.created, undefined, config);
try {
await primaryKey.getSigningKey(unverifiedSigningKey.getKeyID(), signaturePacket.created, undefined, config);
} catch (e) {
// If a key was reformatted then the self-signatures of the signing key might be in the future compared to the message signature,
// making the key invalid at the time of signing.
// However, if the key is valid at the given `date`, we still allow using it provided the relevant `config` setting is enabled.
// Note: we do not support the edge case of a key that was reformatted and it has expired.
if (config.allowInsecureVerificationWithReformattedKeys && e.message.match(/Signature creation time is in the future/)) {
await primaryKey.getSigningKey(unverifiedSigningKey.getKeyID(), date, undefined, config);
} else {
throw e;
}
}
return true;
})(),
signature: (async () => {
Expand Down
3 changes: 2 additions & 1 deletion src/openpgp.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export async function generateKey({ userIDs = [], passphrase = '', type = 'ecc',
* @param {Object|Array<Object>} options.userIDs - User IDs as objects: `{ name: 'Jo Doe', email: 'info@jo.com' }`
* @param {String} [options.passphrase=(not protected)] - The passphrase used to encrypt the reformatted private key. If omitted, the key won't be encrypted.
* @param {Number} [options.keyExpirationTime=0 (never expires)] - Number of seconds from the key creation time after which the key expires
* @param {Date} [options.date] - Override the creation date of the key signatures
* @param {Date} [options.date] - Override the creation date of the key signatures. If the key was previously used to sign messages, it is recommended
* to set the same date as the key creation time to ensure that old message signatures will still be verifiable using the reformatted key.
* @param {'armored'|'binary'|'object'} [options.format='armored'] - format of the output keys
* @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config}
* @returns {Promise<Object>} The generated key object in the form:
Expand Down
43 changes: 43 additions & 0 deletions test/general/signature.js
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,49 @@ ITEG9mMgp3TGS9ZzSifMZ8UGtHdp9QdBg8NEVPFzDOMGxpc/Bftav7RRRuPiAER+
await expect(sigInfo.verified).to.be.rejectedWith(/Signature is expired/);
});

it('Verification fails if signing key\'s self-sig were created after the time of signing, unless config allows it', async function() {
const armoredReformattedKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
xVgEYWmlshYJKwYBBAHaRw8BAQdAAxpFNPiHxz9q4HBzWqveHdP/knjwlgv8
pEQCMHDpIZIAAP9WFlwHDuVlvNb7CyoikwmG01nmdMDe9wXQRWA5vasWKA+g
zSV0ZXN0QHJlZm9ybWF0LmNvbSA8dGVzdEByZWZvcm1hdC5jb20+wowEEBYK
AB0FAmFppjQECwkHCAMVCAoEFgACAQIZAQIbAwIeAQAhCRAOZNKOg+/XQxYh
BGqP/hIaYCSJsZ4TrQ5k0o6D79dD+c8BAIXdh2hrC+l49WPN/KZF+ZzvWCWa
W5n+ozbp/sOGXvODAP4oGEj0RUDDA33b6x7fhQysBZxdrrnHxP9AXEdOTQC3
CsddBGFppbISCisGAQQBl1UBBQEBB0Cjy8Z2K7rl6J6AK1lCfVozmyLE0Gbv
1cspce6oCF6oCwMBCAcAAP9OL5V80EaYm2ic19aM+NtTj4UNOqKqIt10AaH9
SlcdMBDgwngEGBYIAAkFAmFppjQCGwwAIQkQDmTSjoPv10MWIQRqj/4SGmAk
ibGeE60OZNKOg+/XQx/EAQCM0UYrObp60YbOCxu07Dm6XjCVylbOcsaxCnE7
2eMU4AD+OkgajZgbqSIdAR1ud76FW+W+3xlDi/SMFdU7D49SbQI=
=ASQu
-----END PGP PRIVATE KEY BLOCK-----
`;
const armoredMessage = `-----BEGIN PGP MESSAGE-----
xA0DAQoWDmTSjoPv10MByw91AGFpplFwbGFpbnRleHTCdQQBFgoABgUCYWml
sgAhCRAOZNKOg+/XQxYhBGqP/hIaYCSJsZ4TrQ5k0o6D79dDDWwBAKUnRWXj
P3HTW521iD/DngK54mYS3feQzhDokhkYjO3UAP0ZlsYShKaJvXh+JgvR5BPP
gjVcn04JVVlxqgVnMqeVBw==
=eyO7
-----END PGP MESSAGE-----`;
// the key was reformatted and the message signature date preceeds the key self-signature creation date
const key = await openpgp.readKey({ armoredKey: armoredReformattedKey });
const { signatures: [sigInfoRejected] } = await openpgp.verify({
verificationKeys: key,
message: await openpgp.readMessage({ armoredMessage })
});
await expect(sigInfoRejected.verified).to.be.rejectedWith(/Signature creation time is in the future/);

// since the key is valid at the current time, the message should be verifiable if the `config` allows it
const { signatures: [sigInfoValid] } = await openpgp.verify({
verificationKeys: key,
message: await openpgp.readMessage({ armoredMessage }),
config: { allowInsecureVerificationWithReformattedKeys: true }
});
expect(await sigInfoValid.verified).to.be.true;
});

it('Verification fails if signing key was already expired at the time of signing (one-pass signature, streamed)', async function() {
const armoredMessage = `-----BEGIN PGP MESSAGE-----
Expand Down

0 comments on commit 88b1380

Please sign in to comment.