mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-07-10 15:02:30 +00:00
Add support for decrypting autoforwarded messages (#1)
This commit is contained in:
parent
cc79face33
commit
d8bdf2bed3
@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
|
12
src/enums.js
12
src/enums.js
@ -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
|
||||
|
@ -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,8 +93,20 @@ class KDFParams {
|
||||
* @returns {Uint8Array} Array with the KDFParams value
|
||||
*/
|
||||
write() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export default KDFParams;
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
43
test/general/forwarding.js
Normal file
43
test/general/forwarding.js
Normal 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');
|
||||
});
|
||||
});
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user