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([
|
return util.concatUint8Array([
|
||||||
oid.write(),
|
oid.write(),
|
||||||
new Uint8Array([public_algo]),
|
new Uint8Array([public_algo]),
|
||||||
kdfParams.write(),
|
kdfParams.replacementKDFParams || kdfParams.write(),
|
||||||
util.stringToUint8Array('Anonymous Sender '),
|
util.stringToUint8Array('Anonymous Sender '),
|
||||||
fingerprint
|
kdfParams.replacementFingerprint || fingerprint
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/enums.js
12
src/enums.js
@ -50,6 +50,18 @@ export default {
|
|||||||
'brainpoolP512r1': 'brainpoolP512r1'
|
'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
|
/** A string to key specifier type
|
||||||
* @enum {Integer}
|
* @enum {Integer}
|
||||||
* @readonly
|
* @readonly
|
||||||
|
@ -26,22 +26,36 @@ import { UnsupportedError } from '../packet/packet';
|
|||||||
* Alternative 1) [NIST-SP800-56A] with the KDF hash function that is
|
* Alternative 1) [NIST-SP800-56A] with the KDF hash function that is
|
||||||
* SHA2-256 [FIPS-180-3] or stronger is REQUIRED.
|
* SHA2-256 [FIPS-180-3] or stronger is REQUIRED.
|
||||||
* @module type/kdf_params
|
* @module type/kdf_params
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
|
import util from '../util';
|
||||||
|
import enums from '../enums';
|
||||||
|
|
||||||
class KDFParams {
|
class KDFParams {
|
||||||
/**
|
/**
|
||||||
* @param {enums.hash} hash - Hash algorithm
|
* @param {Integer} version Version, defaults to 1
|
||||||
* @param {enums.symmetric} cipher - Symmetric algorithm
|
* @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) {
|
constructor(data) {
|
||||||
if (data) {
|
if (data) {
|
||||||
const { hash, cipher } = data;
|
const { version, hash, cipher, flags, replacementFingerprint, replacementKDFParams } = data;
|
||||||
|
this.version = version || 1;
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
this.cipher = cipher;
|
this.cipher = cipher;
|
||||||
|
|
||||||
|
this.flags = flags;
|
||||||
|
this.replacementFingerprint = replacementFingerprint;
|
||||||
|
this.replacementKDFParams = replacementKDFParams;
|
||||||
} else {
|
} else {
|
||||||
|
this.version = null;
|
||||||
this.hash = null;
|
this.hash = null;
|
||||||
this.cipher = 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.
|
* @returns {Number} Number of read bytes.
|
||||||
*/
|
*/
|
||||||
read(input) {
|
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');
|
throw new UnsupportedError('Cannot read KDFParams');
|
||||||
}
|
}
|
||||||
|
this.version = input[1];
|
||||||
this.hash = input[2];
|
this.hash = input[2];
|
||||||
this.cipher = input[3];
|
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
|
* @returns {Uint8Array} Array with the KDFParams value
|
||||||
*/
|
*/
|
||||||
write() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 testPacket from './packet.js';
|
||||||
import testSignature from './signature.js';
|
import testSignature from './signature.js';
|
||||||
import testKey from './key.js';
|
import testKey from './key.js';
|
||||||
|
import testForwarding from './forwarding.js';
|
||||||
import testOpenPGP from './openpgp.js';
|
import testOpenPGP from './openpgp.js';
|
||||||
import testConfig from './config.js';
|
import testConfig from './config.js';
|
||||||
import testOID from './oid.js';
|
import testOID from './oid.js';
|
||||||
@ -20,6 +21,7 @@ export default () => describe('General', function () {
|
|||||||
testPacket();
|
testPacket();
|
||||||
testSignature();
|
testSignature();
|
||||||
testKey();
|
testKey();
|
||||||
|
testForwarding();
|
||||||
testOpenPGP();
|
testOpenPGP();
|
||||||
testConfig();
|
testConfig();
|
||||||
testOID();
|
testOID();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user