Add support for decrypting autoforwarded messages (#1)

This commit is contained in:
larabr 2020-06-19 16:52:36 +02:00
parent cc79face33
commit d8bdf2bed3
6 changed files with 181 additions and 9 deletions

View File

@ -50,9 +50,9 @@ function buildEcdhParam(public_algo, oid, kdfParams, fingerprint) {
return util.concatUint8Array([
oid.write(),
new Uint8Array([public_algo]),
kdfParams.write(),
kdfParams.replacementKDFParams || kdfParams.write(),
util.stringToUint8Array('Anonymous Sender '),
fingerprint
kdfParams.replacementFingerprint || fingerprint
]);
}

View File

@ -50,6 +50,18 @@ export default {
'brainpoolP512r1': 'brainpoolP512r1'
},
/** KDF parameters flags
* Non-standard extensions (for now) to allow email forwarding
* @enum {Integer}
* @readonly
*/
kdfFlags: {
/** Specify fingerprint to use instead of the recipient's */
replace_fingerprint: 0x01,
/** Specify custom parameters to use in the KDF digest computation */
replace_kdf_params: 0x02
},
/** A string to key specifier type
* @enum {Integer}
* @readonly

View File

@ -26,22 +26,36 @@ import { UnsupportedError } from '../packet/packet';
* Alternative 1) [NIST-SP800-56A] with the KDF hash function that is
* SHA2-256 [FIPS-180-3] or stronger is REQUIRED.
* @module type/kdf_params
* @private
*/
import util from '../util';
import enums from '../enums';
class KDFParams {
/**
* @param {enums.hash} hash - Hash algorithm
* @param {enums.symmetric} cipher - Symmetric algorithm
* @param {Integer} version Version, defaults to 1
* @param {enums.hash} hash Hash algorithm
* @param {enums.symmetric} cipher Symmetric algorithm
* @param {enums.kdfFlags} flags (v2 only) flags
* @param {Uint8Array} replacementFingerprint (v2 only) fingerprint to use instead of recipient one (v5 keys, the 20 leftmost bytes of the fingerprint)
* @param {Uint8Array} replacementKDFParams (v2 only) serialized KDF params to use in KDF digest computation
*/
constructor(data) {
if (data) {
const { hash, cipher } = data;
const { version, hash, cipher, flags, replacementFingerprint, replacementKDFParams } = data;
this.version = version || 1;
this.hash = hash;
this.cipher = cipher;
this.flags = flags;
this.replacementFingerprint = replacementFingerprint;
this.replacementKDFParams = replacementKDFParams;
} else {
this.version = null;
this.hash = null;
this.cipher = null;
this.flags = null;
this.replacementFingerprint = null;
this.replacementKDFParams = null;
}
}
@ -51,12 +65,27 @@ class KDFParams {
* @returns {Number} Number of read bytes.
*/
read(input) {
if (input.length < 4 || input[0] !== 3 || input[1] !== 1) {
if (input.length < 4 || (input[1] !== 1 && input[1] !== 2)) {
throw new UnsupportedError('Cannot read KDFParams');
}
this.version = input[1];
this.hash = input[2];
this.cipher = input[3];
return 4;
let readBytes = 4;
if (this.version === 2) {
this.flags = input[readBytes++];
if (this.flags & enums.kdfFlags.replace_fingerprint) {
this.replacementFingerprint = input.slice(readBytes, readBytes + 20);
readBytes += 20;
}
if (this.flags & enums.kdfFlags.replace_kdf_params) {
const fieldLength = input[readBytes] + 1; // account for length
this.replacementKDFParams = input.slice(readBytes, readBytes + fieldLength);
readBytes += fieldLength;
}
}
return readBytes;
}
/**
@ -64,7 +93,19 @@ class KDFParams {
* @returns {Uint8Array} Array with the KDFParams value
*/
write() {
return new Uint8Array([3, 1, this.hash, this.cipher]);
if (!this.version || this.version === 1) {
return new Uint8Array([3, 1, this.hash, this.cipher]);
}
const v2Fields = util.concatUint8Array([
new Uint8Array([4, 2, this.hash, this.cipher, this.flags]),
this.replacementFingerprint || new Uint8Array(),
this.replacementKDFParams || new Uint8Array()
]);
// update length field
v2Fields[0] = v2Fields.length - 1;
return new Uint8Array(v2Fields);
}
}

View File

@ -362,3 +362,77 @@ export default () => describe('ECDH key exchange @lightweight', function () {
});
});
});
describe('KDF parameters', function () {
const fingerprint = new Uint8Array([
177, 183, 116, 123, 76, 133, 245, 212, 151, 243,
236, 71, 245, 86, 3, 168, 101, 74, 209, 105
]);
it('Valid serialization', async function () {
const cipher = openpgp.enums.symmetric.aes256;
const hash = openpgp.enums.hash.sha256;
const v1 = new KDFParams({ cipher, hash });
const v1Copy = new KDFParams({});
v1Copy.read(v1.write());
expect(v1Copy).to.deep.equal(v1);
const v1Flags0x0 = new KDFParams({
cipher,
hash,
flags: 0x0 // discarded
});
const v1Flags0x0Copy = new KDFParams({});
v1Flags0x0Copy.read(v1Flags0x0.write());
v1Flags0x0.flags = undefined;
expect(v1Flags0x0Copy).to.deep.equal(v1Flags0x0);
const v2Flags0x3 = new KDFParams({
cipher,
hash,
version: 2,
flags: 0x3,
replacementFingerprint: fingerprint,
replacementKDFParams: new Uint8Array([3, 1, cipher, hash])
});
const v2Flags0x3Copy = new KDFParams();
v2Flags0x3Copy.read(v2Flags0x3.write());
expect(v2Flags0x3Copy).to.deep.equal(v2Flags0x3);
const v2Flags0x0 = new KDFParams({
cipher,
hash,
version: 2,
flags: 0x0
});
const v2Flags0x0Copy = new KDFParams({});
v2Flags0x0Copy.read(v2Flags0x0.write());
expect(v2Flags0x0Copy).to.deep.equal(v2Flags0x0);
const v2Flags0x1 = new KDFParams({
cipher,
hash,
version: 2,
flags: 0x1,
replacementFingerprint: fingerprint
});
const v2Flags0x1Copy = new KDFParams();
v2Flags0x1Copy.read(v2Flags0x1.write());
v2Flags0x1.replacementKDFParams = null;
expect(v2Flags0x1Copy).to.deep.equal(v2Flags0x1);
const v2Flags0x2 = new KDFParams({
cipher,
hash,
version: 2,
flags: 0x2,
replacementKDFParams: new Uint8Array([3, 1, cipher, hash])
});
const v2Flags0x2Copy = new KDFParams();
v2Flags0x2Copy.read(v2Flags0x2.write());
v2Flags0x2.replacementFingerprint = null;
expect(v2Flags0x2Copy).to.deep.equal(v2Flags0x2);
});
});

View File

@ -0,0 +1,43 @@
import { expect } from 'chai';
import openpgp from '../initOpenpgp.js';
const charlieKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.10.4
Comment: https://openpgpjs.org
xVgEXqG7KRYJKwYBBAHaRw8BAQdA/q4cs9Pwms3R4trjUd7YyrsRYdQHC9wI
MqLdefob4KUAAQDfy9e8qleM+a1EnPCjDpm69FIY769mo/dpwYlkuI2T/RQt
zSlCb2IgKEZvcndhcmRlZCB0byBDaGFybGllKSA8aW5mb0Bib2IuY29tPsJ4
BBAWCgAgBQJeobspBgsJBwgDAgQVCAoCBBYCAQACGQECGwMCHgEACgkQN2cz
+W7U/RnS8AEArtRly8vW6uUSng9EJ0iuIwJpwgZfykSLl/t4u3HTBZ4BALzY
3XsnvKtZZVvaKvFvCUu/2NvC/1yw2wJk9wGbCwEOx3YEXqG7KRIKKwYBBAGX
VQEFAQEHQCGxSJahhDUdTKnlqT3UIn3rXn5i47I4MsG4kSWfTwcOHAIIBwPe
7fJ+kOrMea9aIUeYtGpUzABa9gMBCAcAAP95QjbjU7kyugp39vhi60YW5T8p
Me0kKFCWzmSYzstgGBBbwmEEGBYIAAkFAl6huykCGwwACgkQN2cz+W7U/RkP
WQD+KcU1HKn6PkVJKxg6RS0Q7RcCZwaQ1DyEyjUoneMCRAgA/jUl9uvPAoCS
3+4Wqg9Q//zOwXNImimIPIdpWNXYZJID
=FVvG
-----END PGP PRIVATE KEY BLOCK-----`;
const fwdCiphertextArmored = `-----BEGIN PGP MESSAGE-----
Version: OpenPGP.js v4.10.4
Comment: https://openpgpjs.org
wV4Dog8LAQLriGUSAQdA/I6k0IvGxyNG2SdSDHrv3bZQDWH18OhTWkcmSF0M
Bxcw3w8KMjr2v69ro5cyZztymEXi5RemRx+oPZGKIZ9N5T+26TaOltH7h8eR
Mu4H03Lp0k4BRsjpFNUBL3HsAuMIemNf4369g+szlpuzjNE1KQhQzZbh87AU
T7KAKygwz0EpOWpx2RHtshDy/bZ1EC8Ia4qDAebameIqCU929OmY1uI=
=3iIr
-----END PGP MESSAGE-----`;
export default () => describe('Forwarding', function() {
it('can decrypt forwarded ciphertext', async function() {
const charlieKey = await openpgp.readKey({ armoredKey: charlieKeyArmored });
const msg = await openpgp.readMessage({ armoredMessage: fwdCiphertextArmored });
const result = await openpgp.decrypt({ decryptionKeys: charlieKey, message: msg });
expect(result).to.exist;
expect(result.data).to.equal('Hello Bob, hello world');
});
});

View File

@ -4,6 +4,7 @@ import testArmor from './armor.js';
import testPacket from './packet.js';
import testSignature from './signature.js';
import testKey from './key.js';
import testForwarding from './forwarding.js';
import testOpenPGP from './openpgp.js';
import testConfig from './config.js';
import testOID from './oid.js';
@ -20,6 +21,7 @@ export default () => describe('General', function () {
testPacket();
testSignature();
testKey();
testForwarding();
testOpenPGP();
testConfig();
testOID();