From 7e66ea20dbfe5edd54cf070446b67a26ec7689e5 Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Thu, 15 Feb 2018 12:51:41 +0100 Subject: [PATCH 1/7] Add timeparameter to signing and encryption --- src/cleartext.js | 12 +- src/key.js | 30 +-- src/message.js | 49 ++-- src/openpgp.js | 29 ++- src/packet/literal.js | 4 +- src/packet/signature.js | 8 +- test/general/openpgp.js | 521 ++++++++++++++++++++++++++++++++++++++-- 7 files changed, 577 insertions(+), 76 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index a3ce9895..9e4da8ed 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -68,22 +68,26 @@ CleartextMessage.prototype.getSigningKeyIds = function() { /** * Sign the cleartext message * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature + * @param {Date} creationDate The creation time of the signature that should be created * @return {module:message~CleartextMessage} new cleartext message with signed content */ -CleartextMessage.prototype.sign = async function(privateKeys) { - return new CleartextMessage(this.text, await this.signDetached(privateKeys)); +CleartextMessage.prototype.sign = async function(privateKeys, signature = null, creationDate = new Date()) { + return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, creationDate)); }; /** * Sign the cleartext message * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature + * @param {Date} creationDate The creation time of the signature that should be created * @return {module:signature~Signature} new detached signature of message content */ -CleartextMessage.prototype.signDetached = async function(privateKeys) { +CleartextMessage.prototype.signDetached = async function(privateKeys, signature = null, creationDate = new Date()) { const literalDataPacket = new packet.Literal(); literalDataPacket.setText(this.text); - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys)); + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, creationDate)); }; /** diff --git a/src/key.js b/src/key.js index ff2185fe..38b8e3c9 100644 --- a/src/key.js +++ b/src/key.js @@ -301,19 +301,20 @@ Key.prototype.armor = function() { * Returns first key packet or key packet by given keyId that is available for signing or signature verification * @param {module:type/keyid} keyId, optional * @param {Boolean} allowExpired allows signature verification with expired keys + * @param {Date} currentDate the current date * @return {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found */ -Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false) { +Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false, currentDate = new Date()) { const primaryUser = this.getPrimaryUser(allowExpired); if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, allowExpired)) { + isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, allowExpired, currentDate)) { return this.primaryKey; } if (this.subKeys) { for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { - if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], allowExpired)) { + if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], allowExpired, currentDate)) { return this.subKeys[i].subKey; } } @@ -323,7 +324,7 @@ Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false) { return null; }; -function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false) { +function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false, currentDate = new Date()) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_sign) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdsa) && @@ -331,43 +332,44 @@ function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false) { (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && - (allowExpired || (!signature.isExpired() && + (allowExpired || (!signature.isExpired(currentDate) && // check expiration time of V3 key packet !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && - Date.now() > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + +currentDate > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && // check expiration time of V4 key packet !(keyPacket.version === 4 && signature.keyNeverExpires === false && - Date.now() > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); + +currentDate > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); } -function isValidSigningKeyPacket(keyPacket, signature, allowExpired=false) { +function isValidSigningKeyPacket(keyPacket, signature, allowExpired=false, currentDate = new Date()) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && - (allowExpired || (!signature.isExpired() && + (allowExpired || (!signature.isExpired(currentDate) && // check expiration time of V3 key packet !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && - Date.now() > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + +currentDate > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && // check expiration time of V4 key packet !(keyPacket.version === 4 && signature.keyNeverExpires === false && - Date.now() > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); + +currentDate > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); } /** * Returns first key packet or key packet by given keyId that is available for encryption or decryption * @param {module:type/keyid} keyId, optional + * @param {Date} currentDate optional * @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found */ -Key.prototype.getEncryptionKeyPacket = function(keyId) { +Key.prototype.getEncryptionKeyPacket = function(keyId, currentDate = new Date()) { // V4: by convention subkeys are preferred for encryption service // V3: keys MUST NOT have subkeys if (this.subKeys) { for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { - if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j])) { + if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], false, currentDate)) { return this.subKeys[i].subKey; } } @@ -377,7 +379,7 @@ Key.prototype.getEncryptionKeyPacket = function(keyId) { // if no valid subkey for encryption, evaluate primary key const primaryUser = this.getPrimaryUser(); if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate)) { + isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, false, currentDate)) { return this.primaryKey; } return null; diff --git a/src/message.js b/src/message.js index 01838921..2ec07f92 100644 --- a/src/message.js +++ b/src/message.js @@ -239,9 +239,10 @@ Message.prototype.getText = function() { * @param {Array} passwords (optional) password(s) for message encryption * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs + * @param {Date} creationDate (optional) the current date of encryption * @return {Message} new message with encrypted content */ -Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = false) { +Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = false, creationDate = new Date()) { let symAlgo; let msg; let symEncryptedPacket; @@ -264,7 +265,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = fal sessionKey = crypto.generateSessionKey(symAlgo); } - msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard); + msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, creationDate); if (config.aead_protect) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); @@ -296,16 +297,17 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = fal * @param {Array} publicKeys (optional) public key(s) for message encryption * @param {Array} passwords (optional) for message encryption * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs + * @param {Date} creationDate (optional) the date used to encrypt * @return {Message} new message with encrypted content */ -export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false) { +export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, creationDate = new Date()) { const packetlist = new packet.List(); return Promise.resolve().then(async () => { if (publicKeys) { const results = await Promise.all(publicKeys.map(async function(key) { await key.verifyPrimaryUser(); - const encryptionKeyPacket = key.getEncryptionKeyPacket(); + const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, creationDate); if (!encryptionKeyPacket) { throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); } @@ -358,11 +360,12 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi /** * Sign the message (the literal data packet of the message) - * @param {Array} privateKey private keys with decrypted secret key data for signing + * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to add to the message + * @param {Date} creationDate} (optional) the creation date of the signature used for creating a new signature * @return {module:message~Message} new message with signed content */ -Message.prototype.sign = async function(privateKeys=[], signature=null) { +Message.prototype.sign = async function(privateKeys=[], signature=null, creationDate = new Date()) { const packetlist = new packet.List(); const literalDataPacket = this.packets.findPacket(enums.packet.literal); @@ -397,7 +400,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null) { throw new Error('Need private key for signing'); } await privateKey.verifyPrimaryUser(); - const signingKeyPacket = privateKey.getSigningKeyPacket(); + const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, undefined, creationDate); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); @@ -416,7 +419,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null) { }); packetlist.push(literalDataPacket); - packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature)); + packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, creationDate)); return new Message(packetlist); }; @@ -443,26 +446,30 @@ Message.prototype.compress = function(compression) { /** * Create a detached signature for the message (the literal data packet of the message) - * @param {Array} privateKey private keys with decrypted secret key data for signing + * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature + * @param {Date} creationDate (optional) the creation date to sign the message with * @return {module:signature~Signature} new detached signature of message content */ -Message.prototype.signDetached = async function(privateKeys=[], signature=null) { +Message.prototype.signDetached = async function(privateKeys=[], signature=null, creationDate = new Date()) { + const packetlist = new packet.List(); + const literalDataPacket = this.packets.findPacket(enums.packet.literal); if (!literalDataPacket) { throw new Error('No literal data packet to sign.'); } - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature)); + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, creationDate)); }; /** * Create signature packets for the message - * @param {module:packet/literal} the literal data packet to sign - * @param {Array} privateKey private keys with decrypted secret key data for signing + * @param {module:packet/literal} literalDataPacket the literal data packet to sign + * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to append + * @param {Date} creationDate (optional) the creation date to sign the message with * @return {module:packet/packetlist} list of signature packets */ -export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null) { +export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, creationDate = new Date()) { const packetlist = new packet.List(); const literalFormat = enums.write(enums.literal, literalDataPacket.format); @@ -474,14 +481,14 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig throw new Error('Need private key for signing'); } await privateKey.verifyPrimaryUser(); - const signingKeyPacket = privateKey.getSigningKeyPacket(); + const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, undefined, creationDate); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); } if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - const signaturePacket = new packet.Signature(); + const signaturePacket = new packet.Signature(creationDate); signaturePacket.signatureType = signatureType; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); @@ -626,11 +633,12 @@ export function readSignedContent(content, detachedSignature) { * creates new message object from text * @param {String} text * @param {String} filename (optional) + * @param {Date} creationDate (optional) * @return {module:message~Message} new message object * @static */ -export function fromText(text, filename) { - const literalDataPacket = new packet.Literal(); +export function fromText(text, filename, creationDate = new Date()) { + const literalDataPacket = new packet.Literal(creationDate); // text will be converted to UTF8 literalDataPacket.setText(text); if (filename !== undefined) { @@ -645,15 +653,16 @@ export function fromText(text, filename) { * creates new message object from binary data * @param {Uint8Array} bytes * @param {String} filename (optional) + * @param {Date} creationDate (optional) * @return {module:message~Message} new message object * @static */ -export function fromBinary(bytes, filename) { +export function fromBinary(bytes, filename, creationDate = new Date()) { if (!util.isUint8Array(bytes)) { throw new Error('Data must be in the form of a Uint8Array'); } - const literalDataPacket = new packet.Literal(); + const literalDataPacket = new packet.Literal(creationDate); if (filename) { literalDataPacket.setFilename(filename); } diff --git a/src/openpgp.js b/src/openpgp.js index 193434ce..9a7ed8be 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -202,33 +202,34 @@ export function decryptKey({ privateKey, passphrase }) { * @param {Signature} signature (optional) a detached signature to add to the encrypted message * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs + * @param {Date} creationDate (optional) the creation date used to encrypt and sign the message * @return {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, compression = config.compression, armor = true, detached = false, signature = null, returnSessionKey = false, wildcard = false}) { +export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, compression = config.compression, armor = true, detached = false, signature = null, returnSessionKey = false, wildcard = false, creationDate = new Date()}) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey, wildcard }); + return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey, wildcard, creationDate }); } const result = {}; return Promise.resolve().then(async function() { - let message = createMessage(data, filename); + let message = createMessage(data, filename, creationDate); if (!privateKeys) { privateKeys = []; } if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (detached) { - const detachedSignature = await message.signDetached(privateKeys, signature); + const detachedSignature = await message.signDetached(privateKeys, signature, creationDate); result.signature = armor ? detachedSignature.armor() : detachedSignature; } else { - message = await message.sign(privateKeys, signature); + message = await message.sign(privateKeys, signature, creationDate); } } message = message.compress(compression); - return message.encrypt(publicKeys, passwords, sessionKey, wildcard); + return message.encrypt(publicKeys, passwords, sessionKey, wildcard, creationDate); }).then(encrypted => { if (armor) { @@ -291,20 +292,21 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign cleartext * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object * @param {Boolean} detached (optional) if the return value should contain a detached signature + * @param {Date} creationDate (optional) the creation date used to sign the message * @return {Promise} signed cleartext in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ export function sign({ - data, privateKeys, armor=true, detached=false + data, privateKeys, armor=true, detached=false, creationDate = new Date() }) { checkData(data); privateKeys = toArray(privateKeys); if (asyncProxy) { // use web worker if available return asyncProxy.delegate('sign', { - data, privateKeys, armor, detached + data, privateKeys, armor, detached, creationDate }); } @@ -313,10 +315,10 @@ export function sign({ let message = util.isString(data) ? new CleartextMessage(data) : messageLib.fromBinary(data); if (detached) { - const signature = await message.signDetached(privateKeys); + const signature = await message.signDetached(privateKeys, undefined, creationDate); result.signature = armor ? signature.armor() : signature; } else { - message = await message.sign(privateKeys); + message = await message.sign(privateKeys, undefined, creationDate); if (armor) { result.data = message.armor(); } else { @@ -493,14 +495,15 @@ function toArray(param) { * Creates a message obejct either from a Uint8Array or a string. * @param {String|Uint8Array} data the payload for the message * @param {String} filename the literal data packet's filename + * @param {Date} creationDate the creation date of the package * @return {Message} a message object */ -function createMessage(data, filename) { +function createMessage(data, filename, creationDate = new Date()) { let msg; if (util.isUint8Array(data)) { - msg = messageLib.fromBinary(data, filename); + msg = messageLib.fromBinary(data, filename, creationDate); } else if (util.isString(data)) { - msg = messageLib.fromText(data, filename); + msg = messageLib.fromText(data, filename, creationDate); } else { throw new Error('Data must be of type String or Uint8Array'); } diff --git a/src/packet/literal.js b/src/packet/literal.js index 73838812..ae27a996 100644 --- a/src/packet/literal.js +++ b/src/packet/literal.js @@ -31,10 +31,10 @@ import enums from '../enums.js'; /** * @constructor */ -export default function Literal() { +export default function Literal(creationDate = new Date()) { this.tag = enums.packet.literal; this.format = 'utf8'; // default format for literal data packets - this.date = new Date(); + this.date = creationDate; this.data = new Uint8Array(0); // literal data representation this.filename = 'msg.txt'; } diff --git a/src/packet/signature.js b/src/packet/signature.js index 803a4f5f..11c6cae4 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -41,7 +41,7 @@ import type_keyid from '../type/keyid.js'; /** * @constructor */ -export default function Signature() { +export default function Signature(creationDate = new Date()) { this.tag = enums.packet.signature; this.version = 4; this.signatureType = null; @@ -52,7 +52,7 @@ export default function Signature() { this.unhashedSubpackets = null; this.signedHashValue = null; - this.created = new Date(); + this.created = creationDate; this.signatureExpirationTime = null; this.signatureNeverExpires = true; this.exportable = null; @@ -663,9 +663,9 @@ Signature.prototype.verify = async function (key, data) { * Verifies signature expiration date * @return {Boolean} true if expired */ -Signature.prototype.isExpired = function () { +Signature.prototype.isExpired = function (currentDate = new Date()) { if (!this.signatureNeverExpires) { - return Date.now() > (this.created.getTime() + this.signatureExpirationTime*1000); + return +currentDate > (this.created.getTime() + this.signatureExpirationTime*1000); } return false; }; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 9ba49199..bc07f2f9 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -158,6 +158,182 @@ const priv_key_de = '=kyeP', '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); +const priv_key_2000_2008 = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xcEYBDioN2gBBACy5VEu8/dlQHOd12v8tNY2Aic+C+k6yyKe7eHRf1Pqwd0d +OdMk+0EvMi1Z+i0x/cQj89te81F7TCmVd+qrIWR6rKc/6WQzg9FQ0h1WQKxD +YizEIyia0ZNEuYd7F1H6ycx352tymepAth05i6t1LxI5jExFDq+d8z8L5ezq ++/6BZQARAQABAAP5AY01ySGNEQKq2LY0WyaqCqG1+5azW72aIS+WKztpO9VE +HhuGXmD+gFK1VtKHFKgAjOucc2RKszYmey56ftL6kdvBs404GEFGCtZOkr4a +PcnSBM7SNZrUlOIBN9u6U4McnNYdEhyARIf+Qm9NGTbzZCoZ13f40/QjX2TG +2T6cTwECAOeTJBaIinz+OInqPzWbEndnbWKIXbPhPtpvU/D2OyLquwMmma8r +khX78V9ZduLVwtzP2DyGnQ+yHBmLCgjxEQECAMXDxAlcx3LbAGew6OA2u938 +Cf+O0fJWid3/e0gNppvnbayTtisXF0uENX4pJv82S02QgqxFL3FYrdON5KVW +zGUB/3rtIzMQJaSYZAJFc4SDOn1RNkl4nUroPf1IbB17nDX/GcB6acquJxQq +0q5FtJCrnNR2K25u6t2AGDcZLleSaFSamc0TdGVzdCA8dGVzdEBleGFtcGxl +PsKtBBMBCgAXBQI4qDdoAhsvAwsJBwMVCggCHgECF4AACgkQXPAg04i7hHT2 +rwQAip3cACXdbShpxvKEsQs0oBN1H5PAx1BAGXanw+jxDFUkrDk1DOSrZFnM +aohuoJrYyoE/RkLz061g8tFc/KETmnyJAcXL/PPic3tPCCs1cphVAsAjELsY +wPL4UQpFnRU2e+phgzX9M/G78wvqiOGcM/K0SZTnyRvYaAHHuLFE2xnHwRgE +OKg3aAEEALOt5AUdDf7fz0DwOkIokGj4zeiFuphsTPwpRAS6c1o9xAzS/C8h +LFShhTKL4Z9znYkdaMHuFIs7AJ3P5tKlvG0/cZAl3u286lz0aTtQluHMCKNy +UyhuZ0K1VgZOj+HcDKo8jQ+aejcwjHDg02yPvfzrXHBjWAJMjglV4W+YPFYj +ABEBAAEAA/9FbqPXagPXgssG8A3DNQOg3MxM1yhk8CzLoHKdVSNwMsAIqJs0 +5x/HUGc1QiKcyEOPEaNClWqw5sr1MLqkmdD2y9xU6Ys1VyJY92GKQyVAgLej +tAvgeUb7NoHKU7b8F/oDfZezY8rs5fBRNVO5hHd+aAD4gcAAfIeAmy7AHRU9 +wQIA7UPEpAI/lil5fDByHz7wyo1k/7yLqY18tHEAcUbPwUWvYCuvv3ASts78 +0kQETsqn0bZZuuiR+IRdFxZzsElLAwIAwd4M85ewucF2tsyJYWJq4A+dETJC +WJfcSboagENXUYjOsLgtU/H8b9JD9CWpsd0DkcPshKAjuum6c3cUaTROYQIA +lp2kWrnzdLZxXELA2RDTaqsp/M+XhwKhChuG53FH+AKMVrwDImG7qVVL07gI +Rv+gGkG79PGvej7YZLZvHIq/+qTWwsCDBBgBCgAPBQI4qDdoBQkPCZwAAhsu +AKgJEFzwINOIu4R0nSAEGQEKAAYFAjioN2gACgkQ4fPj4++ExKB1EQP+Ppm5 +hmv2c04836wMXHjjCIX1fsBhJNSeWNZljxPOcPgb0kAd2hY1S/Vn9ZDogeYm +DBUQ/JHj42Edda2IYax/74dAwUTV2KnDsdBT8Tb9ljHnY3GM7JqEKi/u09u7 +Zfwq3auRDH8RW/hRHQ058dfkSoorpN5iCUfzYJemM4ZmA7NPCwP+PsQ63uIP +mDB49M2sQwV1GsBc+YB+aD3hggsRv7UHh4gvr2GCcukRlHDi/pOEO/ZTaoyS +un3m7b2M4n31bEj1lknZBtMZLo0uWww6YpAQEwFFXhVcAOYQqOb2KfF1rJGB +6w10tmpXdNWm5JPANu6RqaXIzkuMcRUqlYcNLfz6SUHHwRgEOKg3aAEEALfQ +/ENJxzybgdKLQBhF8RN3xb1V8DiYFtfgDkboavjiSD7PVEDNO286cLoe/uAk +E+Dgm2oEFmZ/IJShX+BL1JkHreNKuWTW0Gz0jkqYbE44Kssy5ywCXc0ItW4y +rMtabXPI5zqXzePd9Fwp7ZOt8QN/jU+TUfGUMwEv2tDKq/+7ABEBAAEAA/4l +tAGSQbdSqKj7ySE3+Vyl/Bq8p7xyt0t0Mxpqk/ChJTThYUBsXExVF70YiBQK +YIwNQ7TNDZKUqn3BzsnuJU+xTHKx8/mg7cGo+EzBstLMz7tGQJ9GN2LwrTZj +/yA2JZk3t54Ip/eNCkg7j5OaJG9l3RaW3DKPskRFY63gnitC8QIA745VRJmw +FwmHQ0H4ZoggO26+Q77ViYn84s8gio7AWkrFlt5sWhSdkrGcy/IIeSqzq0ZU +2p7zsXR8qz85+RyTcQIAxG8mwRGHkboHVa6qKt+lAxpqCuxe/buniw0LZuzu +wJQU+E6Y0oybSAcOjleIMkxULljc3Us7a5/HDKdQi4mX6wH/bVPlW8koygus +mDVIPSP2rmjBA9YVLn5CBPG+u0oGAMY9tfJ848V22S/ZPYNZe9ksFSjEuFDL +Xnmz/O1jI3Xht6IGwsCDBBgBCgAPBQI4qDdoBQkPCZwAAhsuAKgJEFzwINOI +u4R0nSAEGQEKAAYFAjioN2gACgkQJVG+vfNJQKhK6gP+LB5qXTJKCduuqZm7 +VhFvPeOu4W0pyORo29zZI0owKZnD2ZKZrZhKXZC/1+xKXi8aX4V2ygRth2P1 +tGFLJRqRiA3C20NVewdI4tQtEqWWSlfNFDz4EsbNspyodQ4jPsKPk2R8pFjA +wmpXLizPg2UyPKUJ/2GnNWjleP0UNyUXgD1MkgP+IkxXTYgDF5/LrOlrq7Th +WqFqQ/prQCBy7xxNLjpVKLDxGYbXVER6p0pkD6DXlaOgSB3i32dQJnU96l44 +TlUyaUK/dJP7JPbVUOFq/awSxJiCxFxF6Oarc10qQ+OG5ESdJAjpCMHGCzlb +t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU= +=C0fJ +-----END PGP PRIVATE KEY BLOCK-----`; + +const pub_key_2000_2008 = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xo0EOKg3aAEEALLlUS7z92VAc53Xa/y01jYCJz4L6TrLIp7t4dF/U+rB3R050yT7 +QS8yLVn6LTH9xCPz217zUXtMKZV36qshZHqspz/pZDOD0VDSHVZArENiLMQjKJrR +k0S5h3sXUfrJzHfna3KZ6kC2HTmLq3UvEjmMTEUOr53zPwvl7Or7/oFlABEBAAHN +E3Rlc3QgPHRlc3RAZXhhbXBsZT7CrQQTAQoAFwUCOKg3aAIbLwMLCQcDFQoIAh4B +AheAAAoJEFzwINOIu4R09q8EAIqd3AAl3W0oacbyhLELNKATdR+TwMdQQBl2p8Po +8QxVJKw5NQzkq2RZzGqIbqCa2MqBP0ZC89OtYPLRXPyhE5p8iQHFy/zz4nN7Twgr +NXKYVQLAIxC7GMDy+FEKRZ0VNnvqYYM1/TPxu/ML6ojhnDPytEmU58kb2GgBx7ix +RNsZzo0EOKg3aAEEALOt5AUdDf7fz0DwOkIokGj4zeiFuphsTPwpRAS6c1o9xAzS +/C8hLFShhTKL4Z9znYkdaMHuFIs7AJ3P5tKlvG0/cZAl3u286lz0aTtQluHMCKNy +UyhuZ0K1VgZOj+HcDKo8jQ+aejcwjHDg02yPvfzrXHBjWAJMjglV4W+YPFYjABEB +AAHCwIMEGAEKAA8FAjioN2gFCQ8JnAACGy4AqAkQXPAg04i7hHSdIAQZAQoABgUC +OKg3aAAKCRDh8+Pj74TEoHURA/4+mbmGa/ZzTjzfrAxceOMIhfV+wGEk1J5Y1mWP +E85w+BvSQB3aFjVL9Wf1kOiB5iYMFRD8kePjYR11rYhhrH/vh0DBRNXYqcOx0FPx +Nv2WMedjcYzsmoQqL+7T27tl/Crdq5EMfxFb+FEdDTnx1+RKiiuk3mIJR/Ngl6Yz +hmYDs08LA/4+xDre4g+YMHj0zaxDBXUawFz5gH5oPeGCCxG/tQeHiC+vYYJy6RGU +cOL+k4Q79lNqjJK6febtvYziffVsSPWWSdkG0xkujS5bDDpikBATAUVeFVwA5hCo +5vYp8XWskYHrDXS2ald01abkk8A27pGppcjOS4xxFSqVhw0t/PpJQc6NBDioN2gB +BAC30PxDScc8m4HSi0AYRfETd8W9VfA4mBbX4A5G6Gr44kg+z1RAzTtvOnC6Hv7g +JBPg4JtqBBZmfyCUoV/gS9SZB63jSrlk1tBs9I5KmGxOOCrLMucsAl3NCLVuMqzL +Wm1zyOc6l83j3fRcKe2TrfEDf41Pk1HxlDMBL9rQyqv/uwARAQABwsCDBBgBCgAP +BQI4qDdoBQkPCZwAAhsuAKgJEFzwINOIu4R0nSAEGQEKAAYFAjioN2gACgkQJVG+ +vfNJQKhK6gP+LB5qXTJKCduuqZm7VhFvPeOu4W0pyORo29zZI0owKZnD2ZKZrZhK +XZC/1+xKXi8aX4V2ygRth2P1tGFLJRqRiA3C20NVewdI4tQtEqWWSlfNFDz4EsbN +spyodQ4jPsKPk2R8pFjAwmpXLizPg2UyPKUJ/2GnNWjleP0UNyUXgD1MkgP+IkxX +TYgDF5/LrOlrq7ThWqFqQ/prQCBy7xxNLjpVKLDxGYbXVER6p0pkD6DXlaOgSB3i +32dQJnU96l44TlUyaUK/dJP7JPbVUOFq/awSxJiCxFxF6Oarc10qQ+OG5ESdJAjp +CMHGCzlbt/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU= +=kwK1 +-----END PGP PUBLIC KEY BLOCK-----`; + +const priv_key_2038_2045 = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xcEYBH/oGU8BBACilkYen6vxr1LAhqWc0HaS+zMkjeND/P9ENePoNRVo3Bq8 +KLacq1pQFitJVcUaz6D5lk0wtijSWb/uUSh6IW6ldVYvsjHdTpGYqH3vLJsp +YXzBzT6sXqht+ceQPi5pIpL/X5240WeaQQtD0arecVAtmtgrN5wJ/3So8llq +mf8q0QARAQABAAP9FZXBxWW0BtLHN7bTMdhzMDGX/phfvbJO6W1beS6Noxg6 +7Gld+mVoCLiIvU8HwKF5YOlVYiGCQJBDF46VbcbBJjwUMCmLBF7eCO1tls6G +JPhG0EcVenx2f/V12cq9O+mKIXkfqnc9n9Wd8uVwav6HQsBFcPcmqj/Y5EAw +Yv8D6qkCANL1ABYZoXn/Bo1SfkOGWFGMS0xb/ISEIgEaQuAt7RFThx3BR7TG +cIkUfG10tm0aRz4LJ74jgfEf+34RZVAzlJsCAMVNWQaSQ2zGmMB+CM73BCXb +JPIh0mB6W0XFWl/a0tex+VkmdnCtvnbtA9MjDs1v3WR2+8SRvDe+k/Yx1w2H +lwMB/2pxnIOH7yrCMPDK14Yfay3EOWzTs17FF1sm8HUSR17qwpBEcH2a6TRd +msr2TvmaCI/uSVtX+h7swnBlhC/+p5ugUc0WZXhhbXBsZSA8dGVzdEBleGFt +cGxlPsKtBBMBCgAXBQJ/6BlPAhsvAwsJBwMVCggCHgECF4AACgkQdKKYGB48 +OusrOgP/Z7+F/BP4rn0CDyPgXmXvj+EAYF2bRWFbxWGPs8KOua9XvuAO1XJQ +CC7Mgx/D8t/7LfLYn4kTzEbKFT/3ZtNzl74Pl/QlDZqodmT8gFESDd01LsL5 +9mI0O9zw7gP7RZkftiFveOGvT4Os/SvOzdpXGGWAfytHtoxmxDq66gzuZUPH +wRcEf+gZTwEEAK0pLhDM5pDxWVfuVFssIdbWhClxlN9ZGhjGM27vf5QE0YAl +uhlv5BTtLU3pYtQYScJksNAFYmENtufWU+c4fv4HHSTGXsW5baw8Ix1vFasr +Aa9atZWBZklQVt3Bsxu9+jOYxGJDjkzyhpLOZgJSYFK36l8dATPF5t1eGy40 +5i0nABEBAAEAA/dvmxsVuPricKwlAHdeTBODZL/J9mr9iXBIh3afCb4wqOpe +rfJEctmOo0+P59zK1tyzbjKH4PCHnU9GHd32KXOvNtmFs4BeuJTFMnQd5YdE +45/7UD29fYtv6cqnn4oigIijuwDFL6qBzEfAjgxl9+MbZz2Gkh6zOtwwDlxv +hOjJAgDhktuQCWfZ8oLoHAHYMR2Fn8n16qUhAqZEDOCF4vjiCOp3za/whtMl +bQMngnA9MioHRQ5vsI5ksUgvzE+9hSzlAgDEhH0b68DTJRDZHFeOIltZhcgC +s5VA6rspabCQ2ETthgLmj4UJbloNCr5z/5IOiAeoWWaw98oSw6yVaHta6p0b +Af4mD95MipQfWvHldxAKeTZRkB9wG68KfzJOmmWoQS+JqYGGwjYZV97KG6ai +7N4xGRiiwfaU0oSIcoDhO0kn5VPMokXCwIMEGAEKAA8FAn/oGU8FCQ8JnAAC +Gy4AqAkQdKKYGB48OuudIAQZAQoABgUCf+gZTwAKCRDuSkIwkyAjaKEqA/9X +S9AgN4nV9on6GsuK1ZpQpqcKAf4SZaF3rDXqpYfM+LDpqaIl8LZKzK7EyW2p +VNV9PwnYtMXwQ7A3KAu2audWxSawHNyvgez1Ujl0J7TfKwJyVBrCDjZCJrr+ +joPU0To95jJivSrnCYC3l1ngoMIZycfaU6FhYwHd2XJe2kbzc8JPA/9aCPIa +hfTEDEH/giKdtzlLbkri2UYGCJqcoNl0Maz6LVUI3NCo3O77zi2v7gLtu+9h +gfWa8dTxCOszDbNTknb8XXCK74FxwIBgr4gHlvK+xh38RI+8eC2y0qONraQ/ +qACJ+UGh1/4smKasSlBi7hZOvNmOxqm4iQ5hve4uWsSlIsfBGAR/6BlPAQQA +w4p7hPgd9QdoQsbEXDYq7hxBfUOub1lAtMN9mvUnLMoohEqocCILNC/xMno5 +5+IwEFZZoHySS1CIIBoy1xgRhe0O7+Ls8R/eyXgvjghVdm9ESMlH9+0p94v/ +gfwS6dudEWy3zeYziQLVaZ2wSUiw46Vs8wumAV4lFzEa0nRBMFsAEQEAAQAD ++gOnmEdpRm0sMO+Okief8OLNEp4NoHM34LhjvTN4OmiL5dX2ss87DIxWCtTo +d3dDXaYpaMb8cJv7Tjqu7VYbYmMXwnPxD6XxOtqAmmL89KmtNAY77B3OQ+dD +LHzkFDjzB4Lzh9/WHwGeDKAlsuYO7KhVwqZ+J67QeQpXBH4ddgwBAgD9xDfI +r+JQzQEsfThwiPt/+XXd3HvpUOubhkGrNTNjy3J0RKOOIz4WVLWL83Y8he31 +ghF6DA2QXEf9zz5aMQS7AgDFQxJmBzSGFCkbHbSphT37SnohLONdxyvmZqj5 +sKIA01fs5gO/+AK2/qpLb1BAXFhi8H6RPVNyOho98VVFx5jhAfwIoivqrLBK +GzFJxS+KxUZgAUwj2ifZ2G3xTAmzZK6ZCPf4giwn4KsC1jVF0TO6zp02RcmZ +wavObOiYwaRyhz9bnvvCwIMEGAEKAA8FAn/oGU8FCQ8JnAACGy4AqAkQdKKY +GB48OuudIAQZAQoABgUCf+gZTwAKCRAowa+OShndpzKyA/0Wi6Vlg76uZDCP +JgTuFn3u/+B3NZvpJw76bwmbfRDQn24o1MrA6VM6Ho2tvSrS3VTZqkn/9JBX +TPGZCZZ/Vrmk1HQp2GIPcnTb7eHAuXl1KhjOQ3MD1fOCDVwJtIMX92Asf7HW +J4wE4f3U5NnR+W6uranaXA2ghVyUsk0lJtnM400nA/45gAq9EBZUSL+DWdYZ ++/RgXpw4/7pwDbq/G4k+4YWn/tvCUnwAsCTo2xD6qN+icY5WwBTphdA/0O3U ++8ujuk61ln9b01u49FoVbuwHoS1gVySj2RyRgldlwg6l99MI8eYmuHf4baPX +0uyeibPdgJTjARMuQzDFA8bdbM540vBf5Q== +=WLIN +-----END PGP PRIVATE KEY BLOCK-----`; + +const pub_key_2038_2045 = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xo0Ef+gZTwEEAKKWRh6fq/GvUsCGpZzQdpL7MySN40P8/0Q14+g1FWjcGrwotpyr +WlAWK0lVxRrPoPmWTTC2KNJZv+5RKHohbqV1Vi+yMd1OkZiofe8smylhfMHNPqxe +qG35x5A+Lmkikv9fnbjRZ5pBC0PRqt5xUC2a2Cs3nAn/dKjyWWqZ/yrRABEBAAHN +FmV4YW1wbGUgPHRlc3RAZXhhbXBsZT7CrQQTAQoAFwUCf+gZTwIbLwMLCQcDFQoI +Ah4BAheAAAoJEHSimBgePDrrKzoD/2e/hfwT+K59Ag8j4F5l74/hAGBdm0VhW8Vh +j7PCjrmvV77gDtVyUAguzIMfw/Lf+y3y2J+JE8xGyhU/92bTc5e+D5f0JQ2aqHZk +/IBREg3dNS7C+fZiNDvc8O4D+0WZH7Yhb3jhr0+DrP0rzs3aVxhlgH8rR7aMZsQ6 +uuoM7mVDzo0Ef+gZTwEEAK0pLhDM5pDxWVfuVFssIdbWhClxlN9ZGhjGM27vf5QE +0YAluhlv5BTtLU3pYtQYScJksNAFYmENtufWU+c4fv4HHSTGXsW5baw8Ix1vFasr +Aa9atZWBZklQVt3Bsxu9+jOYxGJDjkzyhpLOZgJSYFK36l8dATPF5t1eGy405i0n +ABEBAAHCwIMEGAEKAA8FAn/oGU8FCQ8JnAACGy4AqAkQdKKYGB48OuudIAQZAQoA +BgUCf+gZTwAKCRDuSkIwkyAjaKEqA/9XS9AgN4nV9on6GsuK1ZpQpqcKAf4SZaF3 +rDXqpYfM+LDpqaIl8LZKzK7EyW2pVNV9PwnYtMXwQ7A3KAu2audWxSawHNyvgez1 +Ujl0J7TfKwJyVBrCDjZCJrr+joPU0To95jJivSrnCYC3l1ngoMIZycfaU6FhYwHd +2XJe2kbzc8JPA/9aCPIahfTEDEH/giKdtzlLbkri2UYGCJqcoNl0Maz6LVUI3NCo +3O77zi2v7gLtu+9hgfWa8dTxCOszDbNTknb8XXCK74FxwIBgr4gHlvK+xh38RI+8 +eC2y0qONraQ/qACJ+UGh1/4smKasSlBi7hZOvNmOxqm4iQ5hve4uWsSlIs6NBH/o +GU8BBADDinuE+B31B2hCxsRcNiruHEF9Q65vWUC0w32a9ScsyiiESqhwIgs0L/Ey +ejnn4jAQVlmgfJJLUIggGjLXGBGF7Q7v4uzxH97JeC+OCFV2b0RIyUf37Sn3i/+B +/BLp250RbLfN5jOJAtVpnbBJSLDjpWzzC6YBXiUXMRrSdEEwWwARAQABwsCDBBgB +CgAPBQJ/6BlPBQkPCZwAAhsuAKgJEHSimBgePDrrnSAEGQEKAAYFAn/oGU8ACgkQ +KMGvjkoZ3acysgP9FoulZYO+rmQwjyYE7hZ97v/gdzWb6ScO+m8Jm30Q0J9uKNTK +wOlTOh6Nrb0q0t1U2apJ//SQV0zxmQmWf1a5pNR0KdhiD3J02+3hwLl5dSoYzkNz +A9Xzgg1cCbSDF/dgLH+x1ieMBOH91OTZ0flurq2p2lwNoIVclLJNJSbZzONNJwP+ +OYAKvRAWVEi/g1nWGfv0YF6cOP+6cA26vxuJPuGFp/7bwlJ8ALAk6NsQ+qjfonGO +VsAU6YXQP9Dt1PvLo7pOtZZ/W9NbuPRaFW7sB6EtYFcko9kckYJXZcIOpffTCPHm +Jrh3+G2j19Lsnomz3YCU4wETLkMwxQPG3WzOeNLwX+U= +=yV+n +-----END PGP PUBLIC KEY BLOCK-----`; + const passphrase = 'hello world'; const plaintext = 'short message\nnext line\n한국어/조선말'; const password1 = 'I am a password'; @@ -465,6 +641,10 @@ describe('OpenPGP.js public api tests', function() { }); describe('encrypt, decrypt, sign, verify - integration tests', function() { + let privateKey_2000_2008; + let publicKey_2000_2008; + let privateKey_2038_2045; + let publicKey_2038_2045; let privateKey; let publicKey; let zero_copyVal; @@ -478,6 +658,18 @@ describe('OpenPGP.js public api tests', function() { privateKey = openpgp.key.readArmored(priv_key); expect(privateKey.keys).to.have.length(1); expect(privateKey.err).to.not.exist; + publicKey_2000_2008 = openpgp.key.readArmored(pub_key_2000_2008); + expect(publicKey_2000_2008.keys).to.have.length(1); + expect(publicKey_2000_2008.err).to.not.exist; + privateKey_2000_2008 = openpgp.key.readArmored(priv_key_2000_2008); + expect(privateKey_2000_2008.keys).to.have.length(1); + expect(privateKey_2000_2008.err).to.not.exist; + publicKey_2038_2045 = openpgp.key.readArmored(pub_key_2038_2045); + expect(publicKey_2038_2045.keys).to.have.length(1); + expect(publicKey_2038_2045.err).to.not.exist; + privateKey_2038_2045 = openpgp.key.readArmored(priv_key_2038_2045); + expect(privateKey_2038_2045.keys).to.have.length(1); + expect(privateKey_2038_2045.err).to.not.exist; zero_copyVal = openpgp.config.zero_copy; use_nativeVal = openpgp.config.use_native; aead_protectVal = openpgp.config.aead_protect; @@ -1347,26 +1539,317 @@ describe('OpenPGP.js public api tests', function() { }); it('should sign and verify cleartext data and not armor with detached signatures', function () { - const signOpt = { - data: plaintext, - privateKeys: privateKey.keys, - detached: true, - armor: false - }; - const verifyOpt = { - publicKeys: publicKey.keys - }; - return openpgp.sign(signOpt).then(function (signed) { - verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); - verifyOpt.signature = signed.signature; - return openpgp.verify(verifyOpt); - }).then(function (verified) { - expect(verified.data).to.equal(plaintext); - expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); - expect(verified.signatures[0].signature.packets.length).to.equal(1); - }); + const start = Date.now(); + const signOpt = { + data: plaintext, + privateKeys: privateKey.keys, + detached: true, + armor: false + }; + const verifyOpt = { + publicKeys: publicKey.keys + }; + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt); + }).then(function (verified) { + expect(verified.data).to.equal(plaintext); + expect(+verified.signatures[0].signature.packets[0].created).to.be.lte(Date.now()); + expect(+verified.signatures[0].signature.packets[0].created).to.be.gte(start); + expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); }); + + it('should sign and verify cleartext data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const signOpt = { + data: plaintext, + privateKeys: privateKey_2000_2008.keys, + detached: true, + creationDate: past, + armor: false + }; + const verifyOpt = { + publicKeys: publicKey_2000_2008.keys + }; + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt); + }).then(function (verified) { + expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); + expect(verified.data).to.equal(plaintext); + expect(verified.signatures[0].valid).to.be.true; + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, undefined, past)) + .to.be.not.a('null'); + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); + }); + + + it('should sign and verify binary data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const signOpt = { + data, + privateKeys: privateKey_2038_2045.keys, + detached: true, + creationDate: future, + armor: false + }; + const verifyOpt = { + publicKeys: publicKey_2038_2045.keys + }; + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = openpgp.message.fromBinary(data); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt); + }).then(function (verified) { + expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); + expect(verified.data).to.equal(data); + expect(verified.signatures[0].valid).to.be.true; + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, undefined, future)) + .to.be.not.a('null'); + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); + }); + + it('should encrypt and decrypt cleartext data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const encryptOpt = { + data: plaintext, + publicKeys: publicKey_2038_2045.keys, + creationDate: future, + armor: false + }; + const decryptOpt = { + privateKeys: privateKey_2038_2045.keys + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+future); + expect(packets.getText()).to.equal(plaintext); + }); + }); + + + it('should encrypt and decrypt binary data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const encryptOpt = { + data, + publicKeys: publicKey_2000_2008.keys, + creationDate: past, + armor: false + }; + const decryptOpt = { + privateKeys: privateKey_2000_2008.keys + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+past); + expect(packets.getLiteralData()).to.deep.equal(data); + }); + }); + + it('should sign, encrypt and decrypt, verify cleartext data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const encryptOpt = { + data: plaintext, + publicKeys: publicKey_2000_2008.keys, + privateKeys: privateKey_2000_2008.keys, + creationDate: past, + armor: false + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+past); + expect(packets.getText()).to.equal(plaintext); + return packets.verify(encryptOpt.publicKeys); + }).then(function (signatures) { + expect(+signatures[0].signature.packets[0].created).to.equal(+past); + expect(signatures[0].valid).to.be.true; + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, past)) + .to.be.not.a('null'); + expect(signatures[0].signature.packets.length).to.equal(1); + }); + }); + + + it('should sign, encrypt and decrypt, verify binary data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const encryptOpt = { + data, + publicKeys: publicKey_2038_2045.keys, + privateKeys: privateKey_2038_2045.keys, + creationDate: future, + armor: false + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+future); + expect(packets.getLiteralData()).to.deep.equal(data); + return packets.verify(encryptOpt.publicKeys); + }).then(function (signatures) { + expect(+signatures[0].signature.packets[0].created).to.equal(+future); + expect(signatures[0].valid).to.be.true; + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, future)) + .to.be.not.a('null'); + expect(signatures[0].signature.packets.length).to.equal(1); + }); + }); + + it('should sign and verify cleartext data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const signOpt = { + data: plaintext, + privateKeys: privateKey_2000_2008.keys, + detached: true, + creationDate: past, + armor: false + }; + const verifyOpt = { + publicKeys: publicKey_2000_2008.keys + }; + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt); + }).then(function (verified) { + expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); + expect(verified.data).to.equal(plaintext); + expect(verified.signatures[0].valid).to.be.true; + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, undefined, past)) + .to.be.not.a('null'); + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); + }); + + it('should encrypt and decrypt cleartext data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const encryptOpt = { + data: plaintext, + publicKeys: publicKey_2038_2045.keys, + creationDate: future, + armor: false + }; + const decryptOpt = { + privateKeys: privateKey_2038_2045.keys + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+future); + expect(packets.getText()).to.equal(plaintext); + }); + }); + + + it('should encrypt and decrypt binary data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const encryptOpt = { + data, + publicKeys: publicKey_2000_2008.keys, + creationDate: past, + armor: false + }; + const decryptOpt = { + privateKeys: privateKey_2000_2008.keys + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+past); + expect(packets.getLiteralData()).to.deep.equal(data); + }); + }); + + it('should sign, encrypt and decrypt, verify cleartext data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const encryptOpt = { + data: plaintext, + publicKeys: publicKey_2000_2008.keys, + privateKeys: privateKey_2000_2008.keys, + creationDate: past, + armor: false + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+past); + expect(packets.getText()).to.equal(plaintext); + return packets.verify(encryptOpt.publicKeys); + }).then(function (signatures) { + expect(+signatures[0].signature.packets[0].created).to.equal(+past); + expect(signatures[0].valid).to.be.true; + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, past)) + .to.be.not.a('null'); + expect(signatures[0].signature.packets.length).to.equal(1); + }); + }); + + + it('should sign, encrypt and decrypt, verify binary data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const encryptOpt = { + data, + publicKeys: publicKey_2038_2045.keys, + privateKeys: privateKey_2038_2045.keys, + creationDate: future, + armor: false + }; + + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+future); + expect(packets.getLiteralData()).to.deep.equal(data); + return packets.verify(encryptOpt.publicKeys); + }).then(function (signatures) { + expect(+signatures[0].signature.packets[0].created).to.equal(+future); + expect(signatures[0].valid).to.be.true; + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, future)) + .to.be.not.a('null'); + expect(signatures[0].signature.packets.length).to.equal(1); + }); + }); }); describe('ELG / DSA encrypt, decrypt, sign, verify', function() { From 6ca8bc21808488008d8c0ccfd7e8243ad4294a22 Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Thu, 15 Feb 2018 18:32:46 +0100 Subject: [PATCH 2/7] Add timeparameter for verification and remove verify_expired_keys --- src/cleartext.js | 12 +++---- src/config/config.js | 2 -- src/key.js | 70 +++++++++++++++++++---------------------- src/message.js | 63 +++++++++++++++++++------------------ src/openpgp.js | 44 +++++++++++++------------- src/packet/literal.js | 4 +-- src/packet/signature.js | 8 ++--- test/general/openpgp.js | 15 +++++---- 8 files changed, 107 insertions(+), 111 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index 9e4da8ed..b9cd8d6c 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -69,25 +69,25 @@ CleartextMessage.prototype.getSigningKeyIds = function() { * Sign the cleartext message * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature - * @param {Date} creationDate The creation time of the signature that should be created + * @param {Date} date The creation time of the signature that should be created * @return {module:message~CleartextMessage} new cleartext message with signed content */ -CleartextMessage.prototype.sign = async function(privateKeys, signature = null, creationDate = new Date()) { - return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, creationDate)); +CleartextMessage.prototype.sign = async function(privateKeys, signature = null, date = new Date()) { + return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date)); }; /** * Sign the cleartext message * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature - * @param {Date} creationDate The creation time of the signature that should be created + * @param {Date} date The creation time of the signature that should be created * @return {module:signature~Signature} new detached signature of message content */ -CleartextMessage.prototype.signDetached = async function(privateKeys, signature = null, creationDate = new Date()) { +CleartextMessage.prototype.signDetached = async function(privateKeys, signature = null, date = new Date()) { const literalDataPacket = new packet.Literal(); literalDataPacket.setText(this.text); - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, creationDate)); + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date)); }; /** diff --git a/src/config/config.js b/src/config/config.js index 5a0a675d..97ede95e 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -46,8 +46,6 @@ export default { ignore_mdc_error: false, /** @property {Boolean} checksum_required Do not throw error when armor is missing a checksum */ checksum_required: false, - /** @property {Boolean} verify_expired_keys Allow signature verification with expired keys */ - verify_expired_keys: true, /** @property {Boolean} rsa_blinding */ rsa_blinding: true, /** Work-around for rare GPG decryption bug when encrypting with multiple passwords diff --git a/src/key.js b/src/key.js index 38b8e3c9..39efd5b0 100644 --- a/src/key.js +++ b/src/key.js @@ -300,21 +300,20 @@ Key.prototype.armor = function() { /** * Returns first key packet or key packet by given keyId that is available for signing or signature verification * @param {module:type/keyid} keyId, optional - * @param {Boolean} allowExpired allows signature verification with expired keys - * @param {Date} currentDate the current date + * @param {Date} date the current date * @return {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found */ -Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false, currentDate = new Date()) { - const primaryUser = this.getPrimaryUser(allowExpired); +Key.prototype.getSigningKeyPacket = function (keyId = null, date = new Date()) { + const primaryUser = this.getPrimaryUser(); if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, allowExpired, currentDate)) { + isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; } if (this.subKeys) { for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { - if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], allowExpired, currentDate)) { + if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { return this.subKeys[i].subKey; } } @@ -324,7 +323,7 @@ Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false, currentD return null; }; -function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false, currentDate = new Date()) { +function isValidEncryptionKeyPacket(keyPacket, signature, date = new Date()) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_sign) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdsa) && @@ -332,44 +331,44 @@ function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false, cu (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && - (allowExpired || (!signature.isExpired(currentDate) && + (!signature.isExpired(date) && // check expiration time of V3 key packet !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && - +currentDate > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + +date > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && // check expiration time of V4 key packet !(keyPacket.version === 4 && signature.keyNeverExpires === false && - +currentDate > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); + +date > (keyPacket.created.getTime() + signature.keyExpirationTime*1000))); } -function isValidSigningKeyPacket(keyPacket, signature, allowExpired=false, currentDate = new Date()) { +function isValidSigningKeyPacket(keyPacket, signature, date = new Date()) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && - (allowExpired || (!signature.isExpired(currentDate) && + (!signature.isExpired(date) && // check expiration time of V3 key packet !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && - +currentDate > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + +date > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && // check expiration time of V4 key packet !(keyPacket.version === 4 && signature.keyNeverExpires === false && - +currentDate > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); + +date > (keyPacket.created.getTime() + signature.keyExpirationTime*1000))); } /** * Returns first key packet or key packet by given keyId that is available for encryption or decryption * @param {module:type/keyid} keyId, optional - * @param {Date} currentDate optional + * @param {Date} date optional * @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found */ -Key.prototype.getEncryptionKeyPacket = function(keyId, currentDate = new Date()) { +Key.prototype.getEncryptionKeyPacket = function(keyId, date = new Date()) { // V4: by convention subkeys are preferred for encryption service // V3: keys MUST NOT have subkeys if (this.subKeys) { for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { - if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], false, currentDate)) { + if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { return this.subKeys[i].subKey; } } @@ -379,7 +378,7 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, currentDate = new Date()) // if no valid subkey for encryption, evaluate primary key const primaryUser = this.getPrimaryUser(); if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, false, currentDate)) { + isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; } return null; @@ -450,10 +449,9 @@ Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { /** * Verify primary key. Checks for revocation signatures, expiration time * and valid self signature - * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} The status of the primary key */ -Key.prototype.verifyPrimaryKey = async function(allowExpired=false) { +Key.prototype.verifyPrimaryKey = async function() { // TODO clarify OpenPGP's behavior given an expired revocation signature // check revocation signature if (this.revocationSignature && !this.revocationSignature.isExpired() && @@ -462,7 +460,7 @@ Key.prototype.verifyPrimaryKey = async function(allowExpired=false) { return enums.keyStatus.revoked; } // check V3 expiration time - if (!allowExpired && this.primaryKey.version === 3 && this.primaryKey.expirationTimeV3 !== 0 && + if (this.primaryKey.version === 3 && this.primaryKey.expirationTimeV3 !== 0 && Date.now() > (this.primaryKey.created.getTime() + this.primaryKey.expirationTimeV3*24*3600*1000)) { return enums.keyStatus.expired; } @@ -473,12 +471,12 @@ Key.prototype.verifyPrimaryKey = async function(allowExpired=false) { } // check for valid self signature await this.verifyPrimaryUser(); - const primaryUser = this.getPrimaryUser(allowExpired); + const primaryUser = this.getPrimaryUser(); if (!primaryUser) { return enums.keyStatus.invalid; } // check V4 expiration time - if (!allowExpired && this.primaryKey.version === 4 && primaryUser.selfCertificate.keyNeverExpires === false && + if (this.primaryKey.version === 4 && primaryUser.selfCertificate.keyNeverExpires === false && Date.now() > (this.primaryKey.created.getTime() + primaryUser.selfCertificate.keyExpirationTime*1000)) { return enums.keyStatus.expired; } @@ -519,10 +517,9 @@ function getExpirationTime(keyPacket, selfCertificate) { * Returns primary user and most significant (latest valid) self signature * - if multiple users are marked as primary users returns the one with the latest self signature * - if no primary user is found returns the user with the latest self signature - * @param {Boolean} allowExpired allows signature verification with expired keys * @return {{user: Array, selfCertificate: Array}|null} The primary user and the self signature */ -Key.prototype.getPrimaryUser = function(allowExpired=false) { +Key.prototype.getPrimaryUser = function() { let primaryUsers = []; for (let i = 0; i < this.users.length; i++) { // here we only check the primary user ID, ignoring the primary user attribute @@ -533,7 +530,7 @@ Key.prototype.getPrimaryUser = function(allowExpired=false) { // only consider already validated certificates if (!this.users[i].selfCertifications[j].verified || this.users[i].selfCertifications[j].revoked || - (this.users[i].selfCertifications[j].isExpired() && !allowExpired)) { + (this.users[i].selfCertifications[j].isExpired())) { continue; } primaryUsers.push({ index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j] }); @@ -842,17 +839,16 @@ User.prototype.sign = async function(primaryKey, privateKeys) { * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/signature} certificate A certificate of this user * @param {Array} keys array of keys to verify certificate signatures - * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} status of the certificate */ -User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, allowExpired=false) { +User.prototype.verifyCertificate = async function(primaryKey, certificate, keys) { const that = this; const keyid = certificate.issuerKeyId; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const results = await Promise.all(keys.map(async function(key) { if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } await key.verifyPrimaryUser(); - const keyPacket = key.getSigningKeyPacket(keyid, allowExpired); + const keyPacket = key.getSigningKeyPacket(keyid); if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { return enums.keyStatus.revoked; } @@ -978,15 +974,14 @@ SubKey.prototype.isValidEncryptionKey = async function(primaryKey) { /** * Returns true if the subkey can be used for signing of data * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Boolean} allowExpired allows signature verification with expired keys * @return {Boolean} */ -SubKey.prototype.isValidSigningKey = async function(primaryKey, allowExpired=false) { - if (await this.verify(primaryKey, allowExpired) !== enums.keyStatus.valid) { +SubKey.prototype.isValidSigningKey = async function(primaryKey) { + if (await this.verify(primaryKey) !== enums.keyStatus.valid) { return false; } for (let i = 0; i < this.bindingSignatures.length; i++) { - if (isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i], allowExpired)) { + if (isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i])) { return true; } } @@ -997,10 +992,9 @@ SubKey.prototype.isValidSigningKey = async function(primaryKey, allowExpired=fal * Verify subkey. Checks for revocation signatures, expiration time * and valid binding signature * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} The status of the subkey */ -SubKey.prototype.verify = async function(primaryKey, allowExpired=false) { +SubKey.prototype.verify = async function(primaryKey) { const that = this; // TODO clarify OpenPGP's behavior given an expired revocation signature // check subkey revocation signature @@ -1010,7 +1004,7 @@ SubKey.prototype.verify = async function(primaryKey, allowExpired=false) { return enums.keyStatus.revoked; } // check V3 expiration time - if (!allowExpired && this.subKey.version === 3 && this.subKey.expirationTimeV3 !== 0 && + if (this.subKey.version === 3 && this.subKey.expirationTimeV3 !== 0 && Date.now() > (this.subKey.created.getTime() + this.subKey.expirationTimeV3*24*3600*1000)) { return enums.keyStatus.expired; } @@ -1018,7 +1012,7 @@ SubKey.prototype.verify = async function(primaryKey, allowExpired=false) { // TODO replace when Promise.some or Promise.any are implemented const results = [enums.keyStatus.invalid].concat(await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { // check binding signature is not expired - if (!allowExpired && bindingSignature.isExpired()) { + if (bindingSignature.isExpired()) { return enums.keyStatus.expired; // last expired binding signature } // check binding signature can verify @@ -1028,7 +1022,7 @@ SubKey.prototype.verify = async function(primaryKey, allowExpired=false) { } // check V4 expiration time if (that.subKey.version === 4) { - if (!allowExpired && bindingSignature.keyNeverExpires === false && + if (bindingSignature.keyNeverExpires === false && Date.now() > (that.subKey.created.getTime() + bindingSignature.keyExpirationTime*1000)) { return enums.keyStatus.expired; // last V4 expired binding signature } diff --git a/src/message.js b/src/message.js index 2ec07f92..e2f990f8 100644 --- a/src/message.js +++ b/src/message.js @@ -239,10 +239,10 @@ Message.prototype.getText = function() { * @param {Array} passwords (optional) password(s) for message encryption * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} creationDate (optional) the current date of encryption + * @param {Date} date (optional) the current date of encryption * @return {Message} new message with encrypted content */ -Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = false, creationDate = new Date()) { +Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = false, date = new Date()) { let symAlgo; let msg; let symEncryptedPacket; @@ -265,7 +265,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = fal sessionKey = crypto.generateSessionKey(symAlgo); } - msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, creationDate); + msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, date); if (config.aead_protect) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); @@ -297,17 +297,17 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = fal * @param {Array} publicKeys (optional) public key(s) for message encryption * @param {Array} passwords (optional) for message encryption * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} creationDate (optional) the date used to encrypt + * @param {Date} date (optional) the date used to encrypt * @return {Message} new message with encrypted content */ -export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, creationDate = new Date()) { +export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, date = new Date()) { const packetlist = new packet.List(); return Promise.resolve().then(async () => { if (publicKeys) { const results = await Promise.all(publicKeys.map(async function(key) { await key.verifyPrimaryUser(); - const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, creationDate); + const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); if (!encryptionKeyPacket) { throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); } @@ -362,10 +362,10 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi * Sign the message (the literal data packet of the message) * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to add to the message - * @param {Date} creationDate} (optional) the creation date of the signature used for creating a new signature + * @param {Date} date} (optional) the creation date of the signature used for creating a new signature * @return {module:message~Message} new message with signed content */ -Message.prototype.sign = async function(privateKeys=[], signature=null, creationDate = new Date()) { +Message.prototype.sign = async function(privateKeys=[], signature=null, date = new Date()) { const packetlist = new packet.List(); const literalDataPacket = this.packets.findPacket(enums.packet.literal); @@ -400,7 +400,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, creation throw new Error('Need private key for signing'); } await privateKey.verifyPrimaryUser(); - const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, undefined, creationDate); + const signingKeyPacket = privateKey.getSigningKeyPacket(null, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); @@ -419,7 +419,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, creation }); packetlist.push(literalDataPacket); - packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, creationDate)); + packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, date)); return new Message(packetlist); }; @@ -448,17 +448,17 @@ Message.prototype.compress = function(compression) { * Create a detached signature for the message (the literal data packet of the message) * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature - * @param {Date} creationDate (optional) the creation date to sign the message with + * @param {Date} date (optional) the creation date to sign the message with * @return {module:signature~Signature} new detached signature of message content */ -Message.prototype.signDetached = async function(privateKeys=[], signature=null, creationDate = new Date()) { +Message.prototype.signDetached = async function(privateKeys=[], signature=null, date = new Date()) { const packetlist = new packet.List(); const literalDataPacket = this.packets.findPacket(enums.packet.literal); if (!literalDataPacket) { throw new Error('No literal data packet to sign.'); } - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, creationDate)); + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date)); }; /** @@ -466,10 +466,10 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null, * @param {module:packet/literal} literalDataPacket the literal data packet to sign * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to append - * @param {Date} creationDate (optional) the creation date to sign the message with + * @param {Date} date (optional) the creation date to sign the message with * @return {module:packet/packetlist} list of signature packets */ -export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, creationDate = new Date()) { +export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date = new Date()) { const packetlist = new packet.List(); const literalFormat = enums.write(enums.literal, literalDataPacket.format); @@ -481,14 +481,14 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig throw new Error('Need private key for signing'); } await privateKey.verifyPrimaryUser(); - const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, undefined, creationDate); + const signingKeyPacket = privateKey.getSigningKeyPacket(null, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); } if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - const signaturePacket = new packet.Signature(creationDate); + const signaturePacket = new packet.Signature(date); signaturePacket.signatureType = signatureType; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); @@ -508,32 +508,34 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig /** * Verify message signatures * @param {Array} keys array of keys to verify signatures + * @param {Date} date the current date * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Message.prototype.verify = function(keys) { +Message.prototype.verify = function(keys, date = new Date()) { const msg = this.unwrapCompressed(); const literalDataList = msg.packets.filterByTag(enums.packet.literal); if (literalDataList.length !== 1) { throw new Error('Can only verify message with one literal data packet.'); } const signatureList = msg.packets.filterByTag(enums.packet.signature); - return createVerificationObjects(signatureList, literalDataList, keys); + return createVerificationObjects(signatureList, literalDataList, keys, date); }; /** * Verify detached message signature * @param {Array} keys array of keys to verify signatures - * @param {Signature} + * @param {Signature} signature + * @param {Date} date the current date * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Message.prototype.verifyDetached = function(signature, keys) { +Message.prototype.verifyDetached = function(signature, keys, date = new Date()) { const msg = this.unwrapCompressed(); const literalDataList = msg.packets.filterByTag(enums.packet.literal); if (literalDataList.length !== 1) { throw new Error('Can only verify message with one literal data packet.'); } const signatureList = signature.packets; - return createVerificationObjects(signatureList, literalDataList, keys); + return createVerificationObjects(signatureList, literalDataList, keys, date); }; /** @@ -541,15 +543,16 @@ Message.prototype.verifyDetached = function(signature, keys) { * @param {Array} signatureList array of signature packets * @param {Array} literalDataList array of literal data packets * @param {Array} keys array of keys to verify signatures + * @param {Date} date the current date * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -export async function createVerificationObjects(signatureList, literalDataList, keys) { +export async function createVerificationObjects(signatureList, literalDataList, keys, date = new Date()) { return Promise.all(signatureList.map(async function(signature) { let keyPacket = null; await Promise.all(keys.map(async function(key) { await key.verifyPrimaryUser(); // Look for the unique key packet that matches issuerKeyId of signature - const result = key.getSigningKeyPacket(signature.issuerKeyId, config.verify_expired_keys); + const result = key.getSigningKeyPacket(signature.issuerKeyId, date); if (result) { keyPacket = result; } @@ -633,12 +636,12 @@ export function readSignedContent(content, detachedSignature) { * creates new message object from text * @param {String} text * @param {String} filename (optional) - * @param {Date} creationDate (optional) + * @param {Date} date (optional) * @return {module:message~Message} new message object * @static */ -export function fromText(text, filename, creationDate = new Date()) { - const literalDataPacket = new packet.Literal(creationDate); +export function fromText(text, filename, date = new Date()) { + const literalDataPacket = new packet.Literal(date); // text will be converted to UTF8 literalDataPacket.setText(text); if (filename !== undefined) { @@ -653,16 +656,16 @@ export function fromText(text, filename, creationDate = new Date()) { * creates new message object from binary data * @param {Uint8Array} bytes * @param {String} filename (optional) - * @param {Date} creationDate (optional) + * @param {Date} date (optional) * @return {module:message~Message} new message object * @static */ -export function fromBinary(bytes, filename, creationDate = new Date()) { +export function fromBinary(bytes, filename, date = new Date()) { if (!util.isUint8Array(bytes)) { throw new Error('Data must be in the form of a Uint8Array'); } - const literalDataPacket = new packet.Literal(creationDate); + const literalDataPacket = new packet.Literal(date); if (filename) { literalDataPacket.setFilename(filename); } diff --git a/src/openpgp.js b/src/openpgp.js index 9a7ed8be..70784c91 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -202,34 +202,34 @@ export function decryptKey({ privateKey, passphrase }) { * @param {Signature} signature (optional) a detached signature to add to the encrypted message * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} creationDate (optional) the creation date used to encrypt and sign the message + * @param {Date} date (optional) the date used to encrypt and sign the message * @return {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, compression = config.compression, armor = true, detached = false, signature = null, returnSessionKey = false, wildcard = false, creationDate = new Date()}) { +export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, compression = config.compression, armor = true, detached = false, signature = null, returnSessionKey = false, wildcard = false, date = new Date()}) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey, wildcard, creationDate }); + return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey, wildcard, date }); } const result = {}; return Promise.resolve().then(async function() { - let message = createMessage(data, filename, creationDate); + let message = createMessage(data, filename, date); if (!privateKeys) { privateKeys = []; } if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (detached) { - const detachedSignature = await message.signDetached(privateKeys, signature, creationDate); + const detachedSignature = await message.signDetached(privateKeys, signature, date); result.signature = armor ? detachedSignature.armor() : detachedSignature; } else { - message = await message.sign(privateKeys, signature, creationDate); + message = await message.sign(privateKeys, signature, date); } } message = message.compress(compression); - return message.encrypt(publicKeys, passwords, sessionKey, wildcard, creationDate); + return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date); }).then(encrypted => { if (armor) { @@ -254,15 +254,16 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {Signature} signature (optional) detached signature for verification + * @param {Date} date (optional) the current date * @return {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @static */ -export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null }) { +export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format='utf8', signature=null, date=new Date() }) { checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported - return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, signature }); + return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, signature, date }); } return message.decrypt(privateKeys, passwords, sessionKeys).then(async function(message) { @@ -273,7 +274,7 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe publicKeys = []; } - result.signatures = signature ? await message.verifyDetached(signature, publicKeys) : await message.verify(publicKeys); + result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date); return result; }).catch(onError.bind(null, 'Error decrypting message')); } @@ -292,21 +293,21 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign cleartext * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object * @param {Boolean} detached (optional) if the return value should contain a detached signature - * @param {Date} creationDate (optional) the creation date used to sign the message + * @param {Date} date (optional) the creation date used to sign the message * @return {Promise} signed cleartext in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ export function sign({ - data, privateKeys, armor=true, detached=false, creationDate = new Date() + data, privateKeys, armor=true, detached=false, date = new Date() }) { checkData(data); privateKeys = toArray(privateKeys); if (asyncProxy) { // use web worker if available return asyncProxy.delegate('sign', { - data, privateKeys, armor, detached, creationDate + data, privateKeys, armor, detached, date }); } @@ -315,10 +316,10 @@ export function sign({ let message = util.isString(data) ? new CleartextMessage(data) : messageLib.fromBinary(data); if (detached) { - const signature = await message.signDetached(privateKeys, undefined, creationDate); + const signature = await message.signDetached(privateKeys, undefined, date); result.signature = armor ? signature.armor() : signature; } else { - message = await message.sign(privateKeys, undefined, creationDate); + message = await message.sign(privateKeys, undefined, date); if (armor) { result.data = message.armor(); } else { @@ -334,11 +335,12 @@ export function sign({ * @param {Key|Array} publicKeys array of publicKeys or single key, to verify signatures * @param {CleartextMessage} message cleartext message object with signatures * @param {Signature} signature (optional) detached signature for verification + * @param {Date} date * @return {Promise} cleartext with status of verified signatures in the form of: * { data:String, signatures: [{ keyid:String, valid:Boolean }] } * @static */ -export function verify({ message, publicKeys, signature=null }) { +export function verify({ message, publicKeys, signature=null, date = new Date() }) { checkCleartextOrMessage(message); publicKeys = toArray(publicKeys); @@ -349,7 +351,7 @@ export function verify({ message, publicKeys, signature=null }) { return Promise.resolve().then(async function() { const result = {}; result.data = CleartextMessage.prototype.isPrototypeOf(message) ? message.getText() : message.getLiteralData(); - result.signatures = signature ? await message.verifyDetached(signature, publicKeys) : await message.verify(publicKeys); + result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys); return result; }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } @@ -495,15 +497,15 @@ function toArray(param) { * Creates a message obejct either from a Uint8Array or a string. * @param {String|Uint8Array} data the payload for the message * @param {String} filename the literal data packet's filename - * @param {Date} creationDate the creation date of the package + * @param {Date} date the creation date of the package * @return {Message} a message object */ -function createMessage(data, filename, creationDate = new Date()) { +function createMessage(data, filename, date = new Date()) { let msg; if (util.isUint8Array(data)) { - msg = messageLib.fromBinary(data, filename, creationDate); + msg = messageLib.fromBinary(data, filename, date); } else if (util.isString(data)) { - msg = messageLib.fromText(data, filename, creationDate); + msg = messageLib.fromText(data, filename, date); } else { throw new Error('Data must be of type String or Uint8Array'); } diff --git a/src/packet/literal.js b/src/packet/literal.js index ae27a996..810d4004 100644 --- a/src/packet/literal.js +++ b/src/packet/literal.js @@ -31,10 +31,10 @@ import enums from '../enums.js'; /** * @constructor */ -export default function Literal(creationDate = new Date()) { +export default function Literal(date = new Date()) { this.tag = enums.packet.literal; this.format = 'utf8'; // default format for literal data packets - this.date = creationDate; + this.date = date; this.data = new Uint8Array(0); // literal data representation this.filename = 'msg.txt'; } diff --git a/src/packet/signature.js b/src/packet/signature.js index 11c6cae4..7a9c557b 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -41,7 +41,7 @@ import type_keyid from '../type/keyid.js'; /** * @constructor */ -export default function Signature(creationDate = new Date()) { +export default function Signature(date = new Date()) { this.tag = enums.packet.signature; this.version = 4; this.signatureType = null; @@ -52,7 +52,7 @@ export default function Signature(creationDate = new Date()) { this.unhashedSubpackets = null; this.signedHashValue = null; - this.created = creationDate; + this.created = date; this.signatureExpirationTime = null; this.signatureNeverExpires = true; this.exportable = null; @@ -663,9 +663,9 @@ Signature.prototype.verify = async function (key, data) { * Verifies signature expiration date * @return {Boolean} true if expired */ -Signature.prototype.isExpired = function (currentDate = new Date()) { +Signature.prototype.isExpired = function (date = new Date()) { if (!this.signatureNeverExpires) { - return +currentDate > (this.created.getTime() + this.signatureExpirationTime*1000); + return +date > (this.created.getTime() + this.signatureExpirationTime*1000); } return false; }; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index bc07f2f9..d668f8db 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1583,7 +1583,7 @@ describe('OpenPGP.js public api tests', function() { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, undefined, past)) + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, past)) .to.be.not.a('null'); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1611,7 +1611,7 @@ describe('OpenPGP.js public api tests', function() { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); expect(verified.data).to.equal(data); expect(verified.signatures[0].valid).to.be.true; - expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, undefined, future)) + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, future)) .to.be.not.a('null'); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1686,7 +1686,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function (signatures) { expect(+signatures[0].signature.packets[0].created).to.equal(+past); expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, past)) + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, past)) .to.be.not.a('null'); expect(signatures[0].signature.packets.length).to.equal(1); }); @@ -1715,7 +1715,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function (signatures) { expect(+signatures[0].signature.packets[0].created).to.equal(+future); expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, future)) + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, future)) .to.be.not.a('null'); expect(signatures[0].signature.packets.length).to.equal(1); }); @@ -1741,7 +1741,7 @@ describe('OpenPGP.js public api tests', function() { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, undefined, past)) + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, past)) .to.be.not.a('null'); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -1816,7 +1816,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function (signatures) { expect(+signatures[0].signature.packets[0].created).to.equal(+past); expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, past)) + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, past)) .to.be.not.a('null'); expect(signatures[0].signature.packets.length).to.equal(1); }); @@ -1845,7 +1845,7 @@ describe('OpenPGP.js public api tests', function() { }).then(function (signatures) { expect(+signatures[0].signature.packets[0].created).to.equal(+future); expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, undefined, future)) + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, future)) .to.be.not.a('null'); expect(signatures[0].signature.packets.length).to.equal(1); }); @@ -2072,7 +2072,6 @@ describe('OpenPGP.js public api tests', function() { }); }); - } }); From 071fc35f384eee1e7b9f2e8019a8d8fe8358dde0 Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Fri, 16 Feb 2018 17:18:57 +0100 Subject: [PATCH 3/7] Check created time to be valid and discard milliseconds from date objects --- src/cleartext.js | 20 +- src/key.js | 90 +++---- src/message.js | 40 +-- src/openpgp.js | 18 +- src/packet/literal.js | 5 +- src/packet/public_key.js | 2 +- src/packet/signature.js | 14 +- src/util.js | 9 +- test/general/openpgp.js | 512 +++++++++++++------------------------- test/general/signature.js | 16 +- 10 files changed, 283 insertions(+), 443 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index b9cd8d6c..562d1527 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -24,13 +24,13 @@ * @module cleartext */ +import util from './util.js'; import config from './config'; import armor from './encoding/armor'; import enums from './enums'; import packet from './packet'; import { Signature } from './signature'; import { createVerificationObjects, createSignaturePackets } from './message'; -import { getPreferredHashAlgo } from './key'; /** * @class @@ -69,10 +69,10 @@ CleartextMessage.prototype.getSigningKeyIds = function() { * Sign the cleartext message * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date The creation time of the signature that should be created + * @param {Date} date (optional) The creation time of the signature that should be created * @return {module:message~CleartextMessage} new cleartext message with signed content */ -CleartextMessage.prototype.sign = async function(privateKeys, signature = null, date = new Date()) { +CleartextMessage.prototype.sign = async function(privateKeys, signature = null, date=new Date()) { return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date)); }; @@ -80,10 +80,10 @@ CleartextMessage.prototype.sign = async function(privateKeys, signature = null, * Sign the cleartext message * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date The creation time of the signature that should be created + * @param {Date} date (optional) The creation time of the signature that should be created * @return {module:signature~Signature} new detached signature of message content */ -CleartextMessage.prototype.signDetached = async function(privateKeys, signature = null, date = new Date()) { +CleartextMessage.prototype.signDetached = async function(privateKeys, signature=null, date=new Date()) { const literalDataPacket = new packet.Literal(); literalDataPacket.setText(this.text); @@ -93,23 +93,25 @@ CleartextMessage.prototype.signDetached = async function(privateKeys, signature /** * Verify signatures of cleartext signed message * @param {Array} keys array of keys to verify signatures + * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature */ -CleartextMessage.prototype.verify = function(keys) { - return this.verifyDetached(this.signature, keys); +CleartextMessage.prototype.verify = function(keys, date=new Date()) { + return this.verifyDetached(this.signature, keys, date); }; /** * Verify signatures of cleartext signed message * @param {Array} keys array of keys to verify signatures + * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature */ -CleartextMessage.prototype.verifyDetached = function(signature, keys) { +CleartextMessage.prototype.verifyDetached = function(signature, keys, date=new Date()) { const signatureList = signature.packets; const literalDataPacket = new packet.Literal(); // we assume that cleartext signature is generated based on UTF8 cleartext literalDataPacket.setText(this.text); - return createVerificationObjects(signatureList, [literalDataPacket], keys); + return createVerificationObjects(signatureList, [literalDataPacket], keys, date); }; /** diff --git a/src/key.js b/src/key.js index 39efd5b0..105ff69c 100644 --- a/src/key.js +++ b/src/key.js @@ -300,11 +300,11 @@ Key.prototype.armor = function() { /** * Returns first key packet or key packet by given keyId that is available for signing or signature verification * @param {module:type/keyid} keyId, optional - * @param {Date} date the current date + * @param {Date} date use the given date for verification instead of the current time * @return {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found */ -Key.prototype.getSigningKeyPacket = function (keyId = null, date = new Date()) { - const primaryUser = this.getPrimaryUser(); +Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { + const primaryUser = this.getPrimaryUser(date); if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; @@ -323,7 +323,8 @@ Key.prototype.getSigningKeyPacket = function (keyId = null, date = new Date()) { return null; }; -function isValidEncryptionKeyPacket(keyPacket, signature, date = new Date()) { +function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { + const normDate = util.normalizeDate(date); return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_sign) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdsa) && @@ -331,28 +332,19 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date = new Date()) { (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && - (!signature.isExpired(date) && - // check expiration time of V3 key packet - !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && - +date > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && - // check expiration time of V4 key packet - !(keyPacket.version === 4 && signature.keyNeverExpires === false && - +date > (keyPacket.created.getTime() + signature.keyExpirationTime*1000))); + (!signature.isExpired(normDate) && + (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)))); } -function isValidSigningKeyPacket(keyPacket, signature, date = new Date()) { +function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) { + const normDate = util.normalizeDate(date); return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && - (!signature.isExpired(date) && - // check expiration time of V3 key packet - !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && - +date > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && - // check expiration time of V4 key packet - !(keyPacket.version === 4 && signature.keyNeverExpires === false && - +date > (keyPacket.created.getTime() + signature.keyExpirationTime*1000))); + (!signature.isExpired(normDate) && + (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)))); } /** @@ -361,7 +353,7 @@ function isValidSigningKeyPacket(keyPacket, signature, date = new Date()) { * @param {Date} date optional * @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found */ -Key.prototype.getEncryptionKeyPacket = function(keyId, date = new Date()) { +Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { // V4: by convention subkeys are preferred for encryption service // V3: keys MUST NOT have subkeys if (this.subKeys) { @@ -376,7 +368,7 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date = new Date()) { } } // if no valid subkey for encryption, evaluate primary key - const primaryUser = this.getPrimaryUser(); + const primaryUser = this.getPrimaryUser(date); if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; @@ -449,9 +441,10 @@ Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { /** * Verify primary key. Checks for revocation signatures, expiration time * and valid self signature + * @param {Date} date (optional) use the given date for verification instead of the current time * @return {module:enums.keyStatus} The status of the primary key */ -Key.prototype.verifyPrimaryKey = async function() { +Key.prototype.verifyPrimaryKey = async function(date=new Date()) { // TODO clarify OpenPGP's behavior given an expired revocation signature // check revocation signature if (this.revocationSignature && !this.revocationSignature.isExpired() && @@ -460,8 +453,8 @@ Key.prototype.verifyPrimaryKey = async function() { return enums.keyStatus.revoked; } // check V3 expiration time - if (this.primaryKey.version === 3 && this.primaryKey.expirationTimeV3 !== 0 && - Date.now() > (this.primaryKey.created.getTime() + this.primaryKey.expirationTimeV3*24*3600*1000)) { + if (date !== null && this.primaryKey.version === 3 && this.primaryKey.expirationTimeV3 !== 0 && + util.normalizeDate(date) > (this.primaryKey.created.getTime() + this.primaryKey.expirationTimeV3*24*3600*1000)) { return enums.keyStatus.expired; } // check for at least one self signature. Self signature of user ID not mandatory @@ -471,13 +464,13 @@ Key.prototype.verifyPrimaryKey = async function() { } // check for valid self signature await this.verifyPrimaryUser(); - const primaryUser = this.getPrimaryUser(); + const primaryUser = this.getPrimaryUser(date); if (!primaryUser) { return enums.keyStatus.invalid; } // check V4 expiration time - if (this.primaryKey.version === 4 && primaryUser.selfCertificate.keyNeverExpires === false && - Date.now() > (this.primaryKey.created.getTime() + primaryUser.selfCertificate.keyExpirationTime*1000)) { + if (date !== null && this.primaryKey.version === 4 && primaryUser.selfCertificate.keyNeverExpires === false && + util.normalizeDate(date) > (this.primaryKey.created.getTime() + primaryUser.selfCertificate.keyExpirationTime*1000)) { return enums.keyStatus.expired; } return enums.keyStatus.valid; @@ -501,7 +494,7 @@ Key.prototype.getExpirationTime = function() { }; -function getExpirationTime(keyPacket, selfCertificate) { +function getExpirationTime(keyPacket, selfCertificate, defaultValue=null) { // check V3 expiration time if (keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0) { return new Date(keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000); @@ -510,16 +503,17 @@ function getExpirationTime(keyPacket, selfCertificate) { if (keyPacket.version === 4 && selfCertificate.keyNeverExpires === false) { return new Date(keyPacket.created.getTime() + selfCertificate.keyExpirationTime*1000); } - return null; + return defaultValue; } /** * Returns primary user and most significant (latest valid) self signature * - if multiple users are marked as primary users returns the one with the latest self signature * - if no primary user is found returns the user with the latest self signature + * @param {Date} date use the given date for verification instead of the current time * @return {{user: Array, selfCertificate: Array}|null} The primary user and the self signature */ -Key.prototype.getPrimaryUser = function() { +Key.prototype.getPrimaryUser = function(date=new Date()) { let primaryUsers = []; for (let i = 0; i < this.users.length; i++) { // here we only check the primary user ID, ignoring the primary user attribute @@ -530,7 +524,7 @@ Key.prototype.getPrimaryUser = function() { // only consider already validated certificates if (!this.users[i].selfCertifications[j].verified || this.users[i].selfCertifications[j].revoked || - (this.users[i].selfCertifications[j].isExpired())) { + this.users[i].selfCertifications[j].isExpired(date)) { continue; } primaryUsers.push({ index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j] }); @@ -683,6 +677,7 @@ Key.prototype.signAllUsers = async function(privateKeys) { * - if no arguments are given, verifies the self certificates; * - otherwise, verifies all certificates signed with given keys. * @param {Array} keys array of keys to verify certificate signatures + * @param {Date} date (optional) use the given date for verification instead of the current time * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ Key.prototype.verifyPrimaryUser = async function(keys) { @@ -839,16 +834,17 @@ User.prototype.sign = async function(primaryKey, privateKeys) { * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/signature} certificate A certificate of this user * @param {Array} keys array of keys to verify certificate signatures + * @param {Date} date use the given date for verification instead of the current time * @return {module:enums.keyStatus} status of the certificate */ -User.prototype.verifyCertificate = async function(primaryKey, certificate, keys) { +User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) { const that = this; const keyid = certificate.issuerKeyId; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const results = await Promise.all(keys.map(async function(key) { if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } await key.verifyPrimaryUser(); - const keyPacket = key.getSigningKeyPacket(keyid); + const keyPacket = key.getSigningKeyPacket(keyid, date); if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { return enums.keyStatus.revoked; } @@ -957,14 +953,15 @@ SubKey.prototype.toPacketlist = function() { /** * Returns true if the subkey can be used for encryption * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet + * @param {Date} date use the given date for verification instead of the current time * @return {Boolean} */ -SubKey.prototype.isValidEncryptionKey = async function(primaryKey) { +SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) { if (await this.verify(primaryKey) !== enums.keyStatus.valid) { return false; } for (let i = 0; i < this.bindingSignatures.length; i++) { - if (isValidEncryptionKeyPacket(this.subKey, this.bindingSignatures[i])) { + if (isValidEncryptionKeyPacket(this.subKey, this.bindingSignatures[i], date)) { return true; } } @@ -974,14 +971,15 @@ SubKey.prototype.isValidEncryptionKey = async function(primaryKey) { /** * Returns true if the subkey can be used for signing of data * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet + * @param {Date} date use the given date for verification instead of the current time * @return {Boolean} */ -SubKey.prototype.isValidSigningKey = async function(primaryKey) { +SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) { if (await this.verify(primaryKey) !== enums.keyStatus.valid) { return false; } for (let i = 0; i < this.bindingSignatures.length; i++) { - if (isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i])) { + if (isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i], date)) { return true; } } @@ -992,9 +990,10 @@ SubKey.prototype.isValidSigningKey = async function(primaryKey) { * Verify subkey. Checks for revocation signatures, expiration time * and valid binding signature * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet + * @param {Date} date use the given date for verification instead of the current time * @return {module:enums.keyStatus} The status of the subkey */ -SubKey.prototype.verify = async function(primaryKey) { +SubKey.prototype.verify = async function(primaryKey, date=new Date()) { const that = this; // TODO clarify OpenPGP's behavior given an expired revocation signature // check subkey revocation signature @@ -1004,15 +1003,15 @@ SubKey.prototype.verify = async function(primaryKey) { return enums.keyStatus.revoked; } // check V3 expiration time - if (this.subKey.version === 3 && this.subKey.expirationTimeV3 !== 0 && - Date.now() > (this.subKey.created.getTime() + this.subKey.expirationTimeV3*24*3600*1000)) { + if (date !== null && this.subKey.version === 3 && this.subKey.expirationTimeV3 !== 0 && + util.normalizeDate(date) > (this.subKey.created.getTime() + this.subKey.expirationTimeV3*24*3600*1000)) { return enums.keyStatus.expired; } // check subkey binding signatures (at least one valid binding sig needed) // TODO replace when Promise.some or Promise.any are implemented const results = [enums.keyStatus.invalid].concat(await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { // check binding signature is not expired - if (bindingSignature.isExpired()) { + if (bindingSignature.isExpired(date)) { return enums.keyStatus.expired; // last expired binding signature } // check binding signature can verify @@ -1022,8 +1021,8 @@ SubKey.prototype.verify = async function(primaryKey) { } // check V4 expiration time if (that.subKey.version === 4) { - if (bindingSignature.keyNeverExpires === false && - Date.now() > (that.subKey.created.getTime() + bindingSignature.keyExpirationTime*1000)) { + if (date !== null && bindingSignature.keyNeverExpires === false && + util.normalizeDate(date) > (that.subKey.created.getTime() + bindingSignature.keyExpirationTime*1000)) { return enums.keyStatus.expired; // last V4 expired binding signature } } @@ -1291,7 +1290,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { const dataToSign = {}; dataToSign.userid = userIdPacket; dataToSign.key = secretKeyPacket; - const signaturePacket = new packet.Signature(); + const signaturePacket = new packet.Signature(new Date(1000)); signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = options.keyType; signaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket); @@ -1376,7 +1375,8 @@ export function getPreferredHashAlgo(key) { hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? pref_algo : hash_algo; } - key = key.getSigningKeyPacket(); + // disable expiration checks + key = key.getSigningKeyPacket(undefined, null); } switch (Object.getPrototypeOf(key)) { case packet.SecretKey.prototype: diff --git a/src/message.js b/src/message.js index e2f990f8..ddc34746 100644 --- a/src/message.js +++ b/src/message.js @@ -239,10 +239,10 @@ Message.prototype.getText = function() { * @param {Array} passwords (optional) password(s) for message encryption * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} date (optional) the current date of encryption + * @param {Date} date (optional) override the creation date of the literal package * @return {Message} new message with encrypted content */ -Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = false, date = new Date()) { +Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard=false, date=new Date()) { let symAlgo; let msg; let symEncryptedPacket; @@ -297,10 +297,10 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = fal * @param {Array} publicKeys (optional) public key(s) for message encryption * @param {Array} passwords (optional) for message encryption * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} date (optional) the date used to encrypt + * @param {Date} date (optional) override the creation date signature * @return {Message} new message with encrypted content */ -export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, date = new Date()) { +export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, date=new Date()) { const packetlist = new packet.List(); return Promise.resolve().then(async () => { @@ -362,10 +362,10 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi * Sign the message (the literal data packet of the message) * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to add to the message - * @param {Date} date} (optional) the creation date of the signature used for creating a new signature + * @param {Date} date} (optional) override the creation time of the signature * @return {module:message~Message} new message with signed content */ -Message.prototype.sign = async function(privateKeys=[], signature=null, date = new Date()) { +Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date()) { const packetlist = new packet.List(); const literalDataPacket = this.packets.findPacket(enums.packet.literal); @@ -400,7 +400,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date = n throw new Error('Need private key for signing'); } await privateKey.verifyPrimaryUser(); - const signingKeyPacket = privateKey.getSigningKeyPacket(null, date); + const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); @@ -448,10 +448,10 @@ Message.prototype.compress = function(compression) { * Create a detached signature for the message (the literal data packet of the message) * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date (optional) the creation date to sign the message with + * @param {Date} date (optional) override the creation time of the signature * @return {module:signature~Signature} new detached signature of message content */ -Message.prototype.signDetached = async function(privateKeys=[], signature=null, date = new Date()) { +Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) { const packetlist = new packet.List(); const literalDataPacket = this.packets.findPacket(enums.packet.literal); @@ -466,10 +466,10 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null, * @param {module:packet/literal} literalDataPacket the literal data packet to sign * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to append - * @param {Date} date (optional) the creation date to sign the message with + * @param {Date} date (optional) override the creationtime of the signature * @return {module:packet/packetlist} list of signature packets */ -export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date = new Date()) { +export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date()) { const packetlist = new packet.List(); const literalFormat = enums.write(enums.literal, literalDataPacket.format); @@ -481,7 +481,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig throw new Error('Need private key for signing'); } await privateKey.verifyPrimaryUser(); - const signingKeyPacket = privateKey.getSigningKeyPacket(null, date); + const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); } @@ -508,10 +508,10 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig /** * Verify message signatures * @param {Array} keys array of keys to verify signatures - * @param {Date} date the current date + * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Message.prototype.verify = function(keys, date = new Date()) { +Message.prototype.verify = function(keys, date=new Date()) { const msg = this.unwrapCompressed(); const literalDataList = msg.packets.filterByTag(enums.packet.literal); if (literalDataList.length !== 1) { @@ -525,10 +525,10 @@ Message.prototype.verify = function(keys, date = new Date()) { * Verify detached message signature * @param {Array} keys array of keys to verify signatures * @param {Signature} signature - * @param {Date} date the current date + * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Message.prototype.verifyDetached = function(signature, keys, date = new Date()) { +Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { const msg = this.unwrapCompressed(); const literalDataList = msg.packets.filterByTag(enums.packet.literal); if (literalDataList.length !== 1) { @@ -543,10 +543,10 @@ Message.prototype.verifyDetached = function(signature, keys, date = new Date()) * @param {Array} signatureList array of signature packets * @param {Array} literalDataList array of literal data packets * @param {Array} keys array of keys to verify signatures - * @param {Date} date the current date + * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -export async function createVerificationObjects(signatureList, literalDataList, keys, date = new Date()) { +export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) { return Promise.all(signatureList.map(async function(signature) { let keyPacket = null; await Promise.all(keys.map(async function(key) { @@ -640,7 +640,7 @@ export function readSignedContent(content, detachedSignature) { * @return {module:message~Message} new message object * @static */ -export function fromText(text, filename, date = new Date()) { +export function fromText(text, filename, date=new Date()) { const literalDataPacket = new packet.Literal(date); // text will be converted to UTF8 literalDataPacket.setText(text); @@ -660,7 +660,7 @@ export function fromText(text, filename, date = new Date()) { * @return {module:message~Message} new message object * @static */ -export function fromBinary(bytes, filename, date = new Date()) { +export function fromBinary(bytes, filename, date=new Date()) { if (!util.isUint8Array(bytes)) { throw new Error('Data must be in the form of a Uint8Array'); } diff --git a/src/openpgp.js b/src/openpgp.js index 70784c91..8085ab16 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -202,13 +202,13 @@ export function decryptKey({ privateKey, passphrase }) { * @param {Signature} signature (optional) a detached signature to add to the encrypted message * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} date (optional) the date used to encrypt and sign the message + * @param {Date} date (optional) override the creation date of the message and the message signature * @return {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ -export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, compression = config.compression, armor = true, detached = false, signature = null, returnSessionKey = false, wildcard = false, date = new Date()}) { +export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, filename, compression=config.compression, armor=true, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date()}) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported @@ -254,7 +254,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {Signature} signature (optional) detached signature for verification - * @param {Date} date (optional) the current date + * @param {Date} date (optional) use the given date for verification instead of the current time * @return {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @static @@ -293,14 +293,14 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign cleartext * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object * @param {Boolean} detached (optional) if the return value should contain a detached signature - * @param {Date} date (optional) the creation date used to sign the message + * @param {Date} date (optional) override the creation date signature * @return {Promise} signed cleartext in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static */ export function sign({ - data, privateKeys, armor=true, detached=false, date = new Date() + data, privateKeys, armor=true, detached=false, date=new Date() }) { checkData(data); privateKeys = toArray(privateKeys); @@ -335,12 +335,12 @@ export function sign({ * @param {Key|Array} publicKeys array of publicKeys or single key, to verify signatures * @param {CleartextMessage} message cleartext message object with signatures * @param {Signature} signature (optional) detached signature for verification - * @param {Date} date + * @param {Date} date (optional) use the given date for verification instead of the current time * @return {Promise} cleartext with status of verified signatures in the form of: * { data:String, signatures: [{ keyid:String, valid:Boolean }] } * @static */ -export function verify({ message, publicKeys, signature=null, date = new Date() }) { +export function verify({ message, publicKeys, signature=null, date=new Date() }) { checkCleartextOrMessage(message); publicKeys = toArray(publicKeys); @@ -351,7 +351,7 @@ export function verify({ message, publicKeys, signature=null, date = new Date() return Promise.resolve().then(async function() { const result = {}; result.data = CleartextMessage.prototype.isPrototypeOf(message) ? message.getText() : message.getLiteralData(); - result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys); + result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date); return result; }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } @@ -500,7 +500,7 @@ function toArray(param) { * @param {Date} date the creation date of the package * @return {Message} a message object */ -function createMessage(data, filename, date = new Date()) { +function createMessage(data, filename, date=new Date()) { let msg; if (util.isUint8Array(data)) { msg = messageLib.fromBinary(data, filename, date); diff --git a/src/packet/literal.js b/src/packet/literal.js index 810d4004..5ff1bf64 100644 --- a/src/packet/literal.js +++ b/src/packet/literal.js @@ -29,12 +29,13 @@ import util from '../util.js'; import enums from '../enums.js'; /** + * @param {Date} date the creation date of the literal package * @constructor */ -export default function Literal(date = new Date()) { +export default function Literal(date=new Date()) { this.tag = enums.packet.literal; this.format = 'utf8'; // default format for literal data packets - this.date = date; + this.date = util.normalizeDate(date); this.data = new Uint8Array(0); // literal data representation this.filename = 'msg.txt'; } diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 18ae7873..58f037d9 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -43,7 +43,7 @@ export default function PublicKey() { this.version = 4; /** Key creation date. * @type {Date} */ - this.created = new Date(); + this.created = util.normalizeDate(); /* Algorithm specific params */ this.params = []; // time in days (V3 only) diff --git a/src/packet/signature.js b/src/packet/signature.js index 7a9c557b..f1150f9c 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -40,8 +40,9 @@ import type_keyid from '../type/keyid.js'; /** * @constructor + * @param {Date} date the creation date of the signature */ -export default function Signature(date = new Date()) { +export default function Signature(date=new Date()) { this.tag = enums.packet.signature; this.version = 4; this.signatureType = null; @@ -52,7 +53,7 @@ export default function Signature(date = new Date()) { this.unhashedSubpackets = null; this.signedHashValue = null; - this.created = date; + this.created = util.normalizeDate(date); this.signatureExpirationTime = null; this.signatureNeverExpires = true; this.exportable = null; @@ -661,11 +662,14 @@ Signature.prototype.verify = async function (key, data) { /** * Verifies signature expiration date + * @param {Date} date (optional) use the given date for verification instead of the current time * @return {Boolean} true if expired */ -Signature.prototype.isExpired = function (date = new Date()) { - if (!this.signatureNeverExpires) { - return +date > (this.created.getTime() + this.signatureExpirationTime*1000); +Signature.prototype.isExpired = function (date=new Date()) { + if (!this.signatureNeverExpires && date !== null) { + const expirationTime = this.created.getTime() + this.signatureExpirationTime*1000; + const normDate = util.normalizeDate(date); + return !(this.created <= normDate && normDate < expirationTime); } return false; }; diff --git a/src/util.js b/src/util.js index 58a6d0a2..12fc4e69 100644 --- a/src/util.js +++ b/src/util.js @@ -100,17 +100,20 @@ export default { readDate: function (bytes) { const n = this.readNumber(bytes); - const d = new Date(); - d.setTime(n * 1000); + const d = new Date(n * 1000); return d; }, writeDate: function (time) { - const numeric = Math.round(time.getTime() / 1000); + const numeric = Math.floor(time.getTime() / 1000); return this.writeNumber(numeric, 4); }, + normalizeDate: function (time = Date.now()) { + return time === null ? time : new Date(Math.floor(+time / 1000) * 1000); + }, + hexdump: function (str) { const r = []; const e = str.length; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index d668f8db..809fa079 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -214,38 +214,6 @@ t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU= =C0fJ -----END PGP PRIVATE KEY BLOCK-----`; -const pub_key_2000_2008 = `-----BEGIN PGP PUBLIC KEY BLOCK----- - -xo0EOKg3aAEEALLlUS7z92VAc53Xa/y01jYCJz4L6TrLIp7t4dF/U+rB3R050yT7 -QS8yLVn6LTH9xCPz217zUXtMKZV36qshZHqspz/pZDOD0VDSHVZArENiLMQjKJrR -k0S5h3sXUfrJzHfna3KZ6kC2HTmLq3UvEjmMTEUOr53zPwvl7Or7/oFlABEBAAHN -E3Rlc3QgPHRlc3RAZXhhbXBsZT7CrQQTAQoAFwUCOKg3aAIbLwMLCQcDFQoIAh4B -AheAAAoJEFzwINOIu4R09q8EAIqd3AAl3W0oacbyhLELNKATdR+TwMdQQBl2p8Po -8QxVJKw5NQzkq2RZzGqIbqCa2MqBP0ZC89OtYPLRXPyhE5p8iQHFy/zz4nN7Twgr -NXKYVQLAIxC7GMDy+FEKRZ0VNnvqYYM1/TPxu/ML6ojhnDPytEmU58kb2GgBx7ix -RNsZzo0EOKg3aAEEALOt5AUdDf7fz0DwOkIokGj4zeiFuphsTPwpRAS6c1o9xAzS -/C8hLFShhTKL4Z9znYkdaMHuFIs7AJ3P5tKlvG0/cZAl3u286lz0aTtQluHMCKNy -UyhuZ0K1VgZOj+HcDKo8jQ+aejcwjHDg02yPvfzrXHBjWAJMjglV4W+YPFYjABEB -AAHCwIMEGAEKAA8FAjioN2gFCQ8JnAACGy4AqAkQXPAg04i7hHSdIAQZAQoABgUC -OKg3aAAKCRDh8+Pj74TEoHURA/4+mbmGa/ZzTjzfrAxceOMIhfV+wGEk1J5Y1mWP -E85w+BvSQB3aFjVL9Wf1kOiB5iYMFRD8kePjYR11rYhhrH/vh0DBRNXYqcOx0FPx -Nv2WMedjcYzsmoQqL+7T27tl/Crdq5EMfxFb+FEdDTnx1+RKiiuk3mIJR/Ngl6Yz -hmYDs08LA/4+xDre4g+YMHj0zaxDBXUawFz5gH5oPeGCCxG/tQeHiC+vYYJy6RGU -cOL+k4Q79lNqjJK6febtvYziffVsSPWWSdkG0xkujS5bDDpikBATAUVeFVwA5hCo -5vYp8XWskYHrDXS2ald01abkk8A27pGppcjOS4xxFSqVhw0t/PpJQc6NBDioN2gB -BAC30PxDScc8m4HSi0AYRfETd8W9VfA4mBbX4A5G6Gr44kg+z1RAzTtvOnC6Hv7g -JBPg4JtqBBZmfyCUoV/gS9SZB63jSrlk1tBs9I5KmGxOOCrLMucsAl3NCLVuMqzL -Wm1zyOc6l83j3fRcKe2TrfEDf41Pk1HxlDMBL9rQyqv/uwARAQABwsCDBBgBCgAP -BQI4qDdoBQkPCZwAAhsuAKgJEFzwINOIu4R0nSAEGQEKAAYFAjioN2gACgkQJVG+ -vfNJQKhK6gP+LB5qXTJKCduuqZm7VhFvPeOu4W0pyORo29zZI0owKZnD2ZKZrZhK -XZC/1+xKXi8aX4V2ygRth2P1tGFLJRqRiA3C20NVewdI4tQtEqWWSlfNFDz4EsbN -spyodQ4jPsKPk2R8pFjAwmpXLizPg2UyPKUJ/2GnNWjleP0UNyUXgD1MkgP+IkxX -TYgDF5/LrOlrq7ThWqFqQ/prQCBy7xxNLjpVKLDxGYbXVER6p0pkD6DXlaOgSB3i -32dQJnU96l44TlUyaUK/dJP7JPbVUOFq/awSxJiCxFxF6Oarc10qQ+OG5ESdJAjp -CMHGCzlbt/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU= -=kwK1 ------END PGP PUBLIC KEY BLOCK-----`; - const priv_key_2038_2045 = `-----BEGIN PGP PRIVATE KEY BLOCK----- xcEYBH/oGU8BBACilkYen6vxr1LAhqWc0HaS+zMkjeND/P9ENePoNRVo3Bq8 @@ -302,37 +270,19 @@ J4wE4f3U5NnR+W6uranaXA2ghVyUsk0lJtnM400nA/45gAq9EBZUSL+DWdYZ =WLIN -----END PGP PRIVATE KEY BLOCK-----`; -const pub_key_2038_2045 = `-----BEGIN PGP PUBLIC KEY BLOCK----- +const priv_key_expires_1337 = `-----BEGIN PGP PRIVATE KEY BLOCK----- -xo0Ef+gZTwEEAKKWRh6fq/GvUsCGpZzQdpL7MySN40P8/0Q14+g1FWjcGrwotpyr -WlAWK0lVxRrPoPmWTTC2KNJZv+5RKHohbqV1Vi+yMd1OkZiofe8smylhfMHNPqxe -qG35x5A+Lmkikv9fnbjRZ5pBC0PRqt5xUC2a2Cs3nAn/dKjyWWqZ/yrRABEBAAHN -FmV4YW1wbGUgPHRlc3RAZXhhbXBsZT7CrQQTAQoAFwUCf+gZTwIbLwMLCQcDFQoI -Ah4BAheAAAoJEHSimBgePDrrKzoD/2e/hfwT+K59Ag8j4F5l74/hAGBdm0VhW8Vh -j7PCjrmvV77gDtVyUAguzIMfw/Lf+y3y2J+JE8xGyhU/92bTc5e+D5f0JQ2aqHZk -/IBREg3dNS7C+fZiNDvc8O4D+0WZH7Yhb3jhr0+DrP0rzs3aVxhlgH8rR7aMZsQ6 -uuoM7mVDzo0Ef+gZTwEEAK0pLhDM5pDxWVfuVFssIdbWhClxlN9ZGhjGM27vf5QE -0YAluhlv5BTtLU3pYtQYScJksNAFYmENtufWU+c4fv4HHSTGXsW5baw8Ix1vFasr -Aa9atZWBZklQVt3Bsxu9+jOYxGJDjkzyhpLOZgJSYFK36l8dATPF5t1eGy405i0n -ABEBAAHCwIMEGAEKAA8FAn/oGU8FCQ8JnAACGy4AqAkQdKKYGB48OuudIAQZAQoA -BgUCf+gZTwAKCRDuSkIwkyAjaKEqA/9XS9AgN4nV9on6GsuK1ZpQpqcKAf4SZaF3 -rDXqpYfM+LDpqaIl8LZKzK7EyW2pVNV9PwnYtMXwQ7A3KAu2audWxSawHNyvgez1 -Ujl0J7TfKwJyVBrCDjZCJrr+joPU0To95jJivSrnCYC3l1ngoMIZycfaU6FhYwHd -2XJe2kbzc8JPA/9aCPIahfTEDEH/giKdtzlLbkri2UYGCJqcoNl0Maz6LVUI3NCo -3O77zi2v7gLtu+9hgfWa8dTxCOszDbNTknb8XXCK74FxwIBgr4gHlvK+xh38RI+8 -eC2y0qONraQ/qACJ+UGh1/4smKasSlBi7hZOvNmOxqm4iQ5hve4uWsSlIs6NBH/o -GU8BBADDinuE+B31B2hCxsRcNiruHEF9Q65vWUC0w32a9ScsyiiESqhwIgs0L/Ey -ejnn4jAQVlmgfJJLUIggGjLXGBGF7Q7v4uzxH97JeC+OCFV2b0RIyUf37Sn3i/+B -/BLp250RbLfN5jOJAtVpnbBJSLDjpWzzC6YBXiUXMRrSdEEwWwARAQABwsCDBBgB -CgAPBQJ/6BlPBQkPCZwAAhsuAKgJEHSimBgePDrrnSAEGQEKAAYFAn/oGU8ACgkQ -KMGvjkoZ3acysgP9FoulZYO+rmQwjyYE7hZ97v/gdzWb6ScO+m8Jm30Q0J9uKNTK -wOlTOh6Nrb0q0t1U2apJ//SQV0zxmQmWf1a5pNR0KdhiD3J02+3hwLl5dSoYzkNz -A9Xzgg1cCbSDF/dgLH+x1ieMBOH91OTZ0flurq2p2lwNoIVclLJNJSbZzONNJwP+ -OYAKvRAWVEi/g1nWGfv0YF6cOP+6cA26vxuJPuGFp/7bwlJ8ALAk6NsQ+qjfonGO -VsAU6YXQP9Dt1PvLo7pOtZZ/W9NbuPRaFW7sB6EtYFcko9kckYJXZcIOpffTCPHm -Jrh3+G2j19Lsnomz3YCU4wETLkMwxQPG3WzOeNLwX+U= -=yV+n ------END PGP PUBLIC KEY BLOCK-----`; +xcA4BAAAAAEBAgCgONc0J8rfO6cJw5YTP38x1ze2tAYIO7EcmRCNYwMkXngb +0Qdzg34Q5RW0rNiR56VB6KElPUhePRPVklLFiIvHABEBAAEAAf9qabYMzsz/ +/LeRVZSsTgTljmJTdzd2ambUbpi+vt8MXJsbaWh71vjoLMWSXajaKSPDjVU5 +waFNt9kLqwGGGLqpAQD5ZdMH2XzTq6GU9Ka69iZs6Pbnzwdz59Vc3i8hXlUj +zQEApHargCTsrtvSrm+hK/pN51/BHAy9lxCAw9f2etx+AeMA/RGrijkFZtYt +jeWdv/usXL3mgHvEcJv63N5zcEvDX5X4W1bND3Rlc3QxIDxhQGIuY29tPsJ7 +BBABCAAvBQIAAAABBQMAAAU5BgsJBwgDAgkQzcF99nGrkAkEFQgKAgMWAgEC +GQECGwMCHgEAABAlAfwPehmLZs+gOhOTTaSslqQ50bl/REjmv42Nyr1ZBlQS +DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ +=/7PI +-----END PGP PRIVATE KEY BLOCK-----`; const passphrase = 'hello world'; const plaintext = 'short message\nnext line\n한국어/조선말'; @@ -645,6 +595,8 @@ describe('OpenPGP.js public api tests', function() { let publicKey_2000_2008; let privateKey_2038_2045; let publicKey_2038_2045; + let privateKey_1337; + let publicKey_1337; let privateKey; let publicKey; let zero_copyVal; @@ -658,18 +610,18 @@ describe('OpenPGP.js public api tests', function() { privateKey = openpgp.key.readArmored(priv_key); expect(privateKey.keys).to.have.length(1); expect(privateKey.err).to.not.exist; - publicKey_2000_2008 = openpgp.key.readArmored(pub_key_2000_2008); - expect(publicKey_2000_2008.keys).to.have.length(1); - expect(publicKey_2000_2008.err).to.not.exist; privateKey_2000_2008 = openpgp.key.readArmored(priv_key_2000_2008); expect(privateKey_2000_2008.keys).to.have.length(1); expect(privateKey_2000_2008.err).to.not.exist; - publicKey_2038_2045 = openpgp.key.readArmored(pub_key_2038_2045); - expect(publicKey_2038_2045.keys).to.have.length(1); - expect(publicKey_2038_2045.err).to.not.exist; + publicKey_2000_2008 = { keys: [ privateKey_2000_2008.keys[0].toPublic() ] }; privateKey_2038_2045 = openpgp.key.readArmored(priv_key_2038_2045); expect(privateKey_2038_2045.keys).to.have.length(1); expect(privateKey_2038_2045.err).to.not.exist; + publicKey_2038_2045 = { keys: [ privateKey_2038_2045.keys[0].toPublic() ] }; + privateKey_1337 = openpgp.key.readArmored(priv_key_expires_1337); + expect(privateKey_1337.keys).to.have.length(1); + expect(privateKey_1337.err).to.not.exist; + publicKey_1337 = { keys: [ privateKey_1337.keys[0].toPublic() ] }; zero_copyVal = openpgp.config.zero_copy; use_nativeVal = openpgp.config.use_native; aead_protectVal = openpgp.config.aead_protect; @@ -1539,7 +1491,7 @@ describe('OpenPGP.js public api tests', function() { }); it('should sign and verify cleartext data and not armor with detached signatures', function () { - const start = Date.now(); + const start = openpgp.util.normalizeDate(); const signOpt = { data: plaintext, privateKeys: privateKey.keys, @@ -1555,301 +1507,182 @@ describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(function (verified) { expect(verified.data).to.equal(plaintext); - expect(+verified.signatures[0].signature.packets[0].created).to.be.lte(Date.now()); - expect(+verified.signatures[0].signature.packets[0].created).to.be.gte(start); + expect(+verified.signatures[0].signature.packets[0].created).to.be.lte(+openpgp.util.normalizeDate()); + expect(+verified.signatures[0].signature.packets[0].created).to.be.gte(+start); expect(verified.signatures[0].valid).to.be.true; expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and verify cleartext data with a date in the past', function () { - const past = new Date(2005, 5, 5, 5, 5, 5, 0); - const signOpt = { - data: plaintext, - privateKeys: privateKey_2000_2008.keys, - detached: true, - creationDate: past, - armor: false - }; - const verifyOpt = { - publicKeys: publicKey_2000_2008.keys - }; - return openpgp.sign(signOpt).then(function (signed) { - verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); - verifyOpt.signature = signed.signature; - return openpgp.verify(verifyOpt); - }).then(function (verified) { + it('should sign and verify cleartext data with a date in the past', function () { + const past = new Date(2000); + const signOpt = { + data: plaintext, + privateKeys: privateKey_1337.keys, + detached: true, + date: past, + armor: false + }; + const verifyOpt = { + publicKeys: publicKey_1337.keys, + date: past + }; + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt).then(function (verified) { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, past)) - .to.be.not.a('null'); + .to.be.not.null; expect(verified.signatures[0].signature.packets.length).to.equal(1); - }); - }); - - - it('should sign and verify binary data with a date in the future', function () { - const future = new Date(2040, 5, 5, 5, 5, 5, 0); - const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); - const signOpt = { - data, - privateKeys: privateKey_2038_2045.keys, - detached: true, - creationDate: future, - armor: false - }; - const verifyOpt = { - publicKeys: publicKey_2038_2045.keys - }; - return openpgp.sign(signOpt).then(function (signed) { - verifyOpt.message = openpgp.message.fromBinary(data); - verifyOpt.signature = signed.signature; + // now check with expiration checking disabled + verifyOpt.date = null; return openpgp.verify(verifyOpt); - }).then(function (verified) { - expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); - expect(verified.data).to.equal(data); - expect(verified.signatures[0].valid).to.be.true; - expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, future)) - .to.be.not.a('null'); - expect(verified.signatures[0].signature.packets.length).to.equal(1); - }); - }); - - it('should encrypt and decrypt cleartext data with a date in the future', function () { - const future = new Date(2040, 5, 5, 5, 5, 5, 0); - const encryptOpt = { - data: plaintext, - publicKeys: publicKey_2038_2045.keys, - creationDate: future, - armor: false - }; - const decryptOpt = { - privateKeys: privateKey_2038_2045.keys - }; - - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - decryptOpt.message = encrypted.message; - return encrypted.message.decrypt(decryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+future); - expect(packets.getText()).to.equal(plaintext); - }); - }); - - - it('should encrypt and decrypt binary data with a date in the past', function () { - const past = new Date(2005, 5, 5, 5, 5, 5, 0); - const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); - const encryptOpt = { - data, - publicKeys: publicKey_2000_2008.keys, - creationDate: past, - armor: false - }; - const decryptOpt = { - privateKeys: privateKey_2000_2008.keys - }; - - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - decryptOpt.message = encrypted.message; - return encrypted.message.decrypt(decryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+past); - expect(packets.getLiteralData()).to.deep.equal(data); - }); - }); - - it('should sign, encrypt and decrypt, verify cleartext data with a date in the past', function () { - const past = new Date(2005, 5, 5, 5, 5, 5, 0); - const encryptOpt = { - data: plaintext, - publicKeys: publicKey_2000_2008.keys, - privateKeys: privateKey_2000_2008.keys, - creationDate: past, - armor: false - }; - - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - return encrypted.message.decrypt(encryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+past); - expect(packets.getText()).to.equal(plaintext); - return packets.verify(encryptOpt.publicKeys); - }).then(function (signatures) { - expect(+signatures[0].signature.packets[0].created).to.equal(+past); - expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, past)) - .to.be.not.a('null'); - expect(signatures[0].signature.packets.length).to.equal(1); - }); - }); - - - it('should sign, encrypt and decrypt, verify binary data with a date in the future', function () { - const future = new Date(2040, 5, 5, 5, 5, 5, 0); - const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); - const encryptOpt = { - data, - publicKeys: publicKey_2038_2045.keys, - privateKeys: privateKey_2038_2045.keys, - creationDate: future, - armor: false - }; - - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - return encrypted.message.decrypt(encryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+future); - expect(packets.getLiteralData()).to.deep.equal(data); - return packets.verify(encryptOpt.publicKeys); - }).then(function (signatures) { - expect(+signatures[0].signature.packets[0].created).to.equal(+future); - expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, future)) - .to.be.not.a('null'); - expect(signatures[0].signature.packets.length).to.equal(1); - }); - }); - - it('should sign and verify cleartext data with a date in the past', function () { - const past = new Date(2005, 5, 5, 5, 5, 5, 0); - const signOpt = { - data: plaintext, - privateKeys: privateKey_2000_2008.keys, - detached: true, - creationDate: past, - armor: false - }; - const verifyOpt = { - publicKeys: publicKey_2000_2008.keys - }; - return openpgp.sign(signOpt).then(function (signed) { - verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); - verifyOpt.signature = signed.signature; - return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(function (verified) { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, past)) - .to.be.not.a('null'); + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, null)) + .to.be.not.null; expect(verified.signatures[0].signature.packets.length).to.equal(1); - }); - }); + }); + }); + }); - it('should encrypt and decrypt cleartext data with a date in the future', function () { - const future = new Date(2040, 5, 5, 5, 5, 5, 0); - const encryptOpt = { - data: plaintext, - publicKeys: publicKey_2038_2045.keys, - creationDate: future, - armor: false - }; - const decryptOpt = { - privateKeys: privateKey_2038_2045.keys - }; + it('should sign and verify binary data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const signOpt = { + data, + privateKeys: privateKey_2038_2045.keys, + detached: true, + date: future, + armor: false + }; + const verifyOpt = { + publicKeys: publicKey_2038_2045.keys, + date: future + }; + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = openpgp.message.fromBinary(data); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt); + }).then(function (verified) { + expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); + expect(verified.data).to.equal(data); + expect(verified.signatures[0].valid).to.be.true; + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, future)) + .to.be.not.null; + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); + }); - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - decryptOpt.message = encrypted.message; - return encrypted.message.decrypt(decryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+future); - expect(packets.getText()).to.equal(plaintext); - }); - }); + it('should encrypt and decrypt cleartext data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const encryptOpt = { + data: plaintext, + publicKeys: publicKey_2038_2045.keys, + date: future, + armor: false + }; + const decryptOpt = { + privateKeys: privateKey_2038_2045.keys, + date: future + }; + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+future); + expect(packets.getText()).to.equal(plaintext); + }); + }); - it('should encrypt and decrypt binary data with a date in the past', function () { - const past = new Date(2005, 5, 5, 5, 5, 5, 0); - const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); - const encryptOpt = { - data, - publicKeys: publicKey_2000_2008.keys, - creationDate: past, - armor: false - }; - const decryptOpt = { - privateKeys: privateKey_2000_2008.keys - }; + it('should encrypt and decrypt binary data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const encryptOpt = { + data, + publicKeys: publicKey_2000_2008.keys, + date: past, + armor: false + }; + const decryptOpt = { + privateKeys: privateKey_2000_2008.keys, + date: past + }; - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - decryptOpt.message = encrypted.message; - return encrypted.message.decrypt(decryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+past); - expect(packets.getLiteralData()).to.deep.equal(data); - }); - }); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+past); + expect(packets.getLiteralData()).to.deep.equal(data); + }); + }); - it('should sign, encrypt and decrypt, verify cleartext data with a date in the past', function () { - const past = new Date(2005, 5, 5, 5, 5, 5, 0); - const encryptOpt = { - data: plaintext, - publicKeys: publicKey_2000_2008.keys, - privateKeys: privateKey_2000_2008.keys, - creationDate: past, - armor: false - }; + it('should sign, encrypt and decrypt, verify cleartext data with a date in the past', function () { + const past = new Date(2005, 5, 5, 5, 5, 5, 0); + const encryptOpt = { + data: plaintext, + publicKeys: publicKey_2000_2008.keys, + privateKeys: privateKey_2000_2008.keys, + date: past, + armor: false + }; - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - return encrypted.message.decrypt(encryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+past); - expect(packets.getText()).to.equal(plaintext); - return packets.verify(encryptOpt.publicKeys); - }).then(function (signatures) { - expect(+signatures[0].signature.packets[0].created).to.equal(+past); - expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, past)) - .to.be.not.a('null'); - expect(signatures[0].signature.packets.length).to.equal(1); - }); - }); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+past); + expect(packets.getText()).to.equal(plaintext); + return packets.verify(encryptOpt.publicKeys, past); + }).then(function (signatures) { + expect(+signatures[0].signature.packets[0].created).to.equal(+past); + expect(signatures[0].valid).to.be.true; + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, past)) + .to.be.not.null; + expect(signatures[0].signature.packets.length).to.equal(1); + }); + }); + it('should sign, encrypt and decrypt, verify binary data with a date in the future', function () { + const future = new Date(2040, 5, 5, 5, 5, 5, 0); + const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); + const encryptOpt = { + data, + publicKeys: publicKey_2038_2045.keys, + privateKeys: privateKey_2038_2045.keys, + date: future, + armor: false + }; - it('should sign, encrypt and decrypt, verify binary data with a date in the future', function () { - const future = new Date(2040, 5, 5, 5, 5, 5, 0); - const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); - const encryptOpt = { - data, - publicKeys: publicKey_2038_2045.keys, - privateKeys: privateKey_2038_2045.keys, - creationDate: future, - armor: false - }; - - return openpgp.encrypt(encryptOpt).then(function (encrypted) { - return encrypted.message.decrypt(encryptOpt.privateKeys); - }).then(function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); - expect(literals.length).to.equal(1); - expect(+literals[0].date).to.equal(+future); - expect(packets.getLiteralData()).to.deep.equal(data); - return packets.verify(encryptOpt.publicKeys); - }).then(function (signatures) { - expect(+signatures[0].signature.packets[0].created).to.equal(+future); - expect(signatures[0].valid).to.be.true; - expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, future)) - .to.be.not.a('null'); - expect(signatures[0].signature.packets.length).to.equal(1); - }); - }); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); + expect(literals.length).to.equal(1); + expect(+literals[0].date).to.equal(+future); + expect(packets.getLiteralData()).to.deep.equal(data); + return packets.verify(encryptOpt.publicKeys, future); + }).then(function (signatures) { + expect(+signatures[0].signature.packets[0].created).to.equal(+future); + expect(signatures[0].valid).to.be.true; + expect(encryptOpt.privateKeys[0].getSigningKeyPacket(signatures[0].keyid, future)) + .to.be.not.null; + expect(signatures[0].signature.packets.length).to.equal(1); + }); + }); }); describe('ELG / DSA encrypt, decrypt, sign, verify', function() { @@ -2072,6 +1905,7 @@ describe('OpenPGP.js public api tests', function() { }); }); + } }); diff --git a/test/general/signature.js b/test/general/signature.js index 08439af8..d61e3023 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -642,8 +642,7 @@ describe("Signature", function() { }); }); - it('Verify test with expired verification public key and verify_expired_keys set to false', function() { - openpgp.config.verify_expired_keys = false; + it('Verify test with expired verification public key', function() { const pubKey = openpgp.key.readArmored(pub_expired).keys[0]; const message = openpgp.message.readArmored(msg_sig_expired); return openpgp.verify({ publicKeys:[pubKey], message:message }).then(function(verified) { @@ -655,11 +654,10 @@ describe("Signature", function() { }); - it('Verify test with expired verification public key and verify_expired_keys set to true', function() { - openpgp.config.verify_expired_keys = true; + it('Verify test with expired verification public key and disable expiration checks using null date', function() { const pubKey = openpgp.key.readArmored(pub_expired).keys[0]; const message = openpgp.message.readArmored(msg_sig_expired); - return openpgp.verify({ publicKeys:[pubKey], message:message }).then(function(verified) { + return openpgp.verify({ publicKeys:[pubKey], message:message, date: null }).then(function(verified) { expect(verified).to.exist; expect(verified.signatures).to.have.length(1); expect(verified.signatures[0].valid).to.be.true; @@ -668,8 +666,7 @@ describe("Signature", function() { }); - it('Verify test with expired verification public key and verify_expired_keys set to false', function() { - openpgp.config.verify_expired_keys = false; + it('Verify test with expired verification public key', function() { const pubKey = openpgp.key.readArmored(pub_expired).keys[0]; const message = openpgp.message.readArmored(msg_sig_expired); return openpgp.verify({ publicKeys:[pubKey], message:message }).then(function(verified) { @@ -681,11 +678,10 @@ describe("Signature", function() { }); - it('Verify test with expired verification public key and verify_expired_keys set to true', function() { - openpgp.config.verify_expired_keys = true; + it('Verify test with expired verification public key and disable expiration checks using null date', function() { const pubKey = openpgp.key.readArmored(pub_expired).keys[0]; const message = openpgp.message.readArmored(msg_sig_expired); - return openpgp.verify({ publicKeys:[pubKey], message:message }).then(function(verified) { + return openpgp.verify({ publicKeys:[pubKey], message:message, date: null }).then(function(verified) { expect(verified).to.exist; expect(verified.signatures).to.have.length(1); expect(verified.signatures[0].valid).to.be.true; From 454ca1d8797bef1c0a1582c090f43fe7b144918e Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Sat, 17 Feb 2018 11:32:45 +0100 Subject: [PATCH 4/7] Address comments --- src/cleartext.js | 3 +-- src/key.js | 7 +++---- src/message.js | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index 562d1527..36bf6f6b 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -24,7 +24,6 @@ * @module cleartext */ -import util from './util.js'; import config from './config'; import armor from './encoding/armor'; import enums from './enums'; @@ -72,7 +71,7 @@ CleartextMessage.prototype.getSigningKeyIds = function() { * @param {Date} date (optional) The creation time of the signature that should be created * @return {module:message~CleartextMessage} new cleartext message with signed content */ -CleartextMessage.prototype.sign = async function(privateKeys, signature = null, date=new Date()) { +CleartextMessage.prototype.sign = async function(privateKeys, signature=null, date=new Date()) { return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date)); }; diff --git a/src/key.js b/src/key.js index 105ff69c..857f0fde 100644 --- a/src/key.js +++ b/src/key.js @@ -677,7 +677,6 @@ Key.prototype.signAllUsers = async function(privateKeys) { * - if no arguments are given, verifies the self certificates; * - otherwise, verifies all certificates signed with given keys. * @param {Array} keys array of keys to verify certificate signatures - * @param {Date} date (optional) use the given date for verification instead of the current time * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ Key.prototype.verifyPrimaryUser = async function(keys) { @@ -957,7 +956,7 @@ SubKey.prototype.toPacketlist = function() { * @return {Boolean} */ SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) { - if (await this.verify(primaryKey) !== enums.keyStatus.valid) { + if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { return false; } for (let i = 0; i < this.bindingSignatures.length; i++) { @@ -975,7 +974,7 @@ SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date * @return {Boolean} */ SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) { - if (await this.verify(primaryKey) !== enums.keyStatus.valid) { + if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { return false; } for (let i = 0; i < this.bindingSignatures.length; i++) { @@ -1290,7 +1289,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { const dataToSign = {}; dataToSign.userid = userIdPacket; dataToSign.key = secretKeyPacket; - const signaturePacket = new packet.Signature(new Date(1000)); + const signaturePacket = new packet.Signature(); signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = options.keyType; signaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket); diff --git a/src/message.js b/src/message.js index ddc34746..612a6417 100644 --- a/src/message.js +++ b/src/message.js @@ -452,8 +452,6 @@ Message.prototype.compress = function(compression) { * @return {module:signature~Signature} new detached signature of message content */ Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) { - const packetlist = new packet.List(); - const literalDataPacket = this.packets.findPacket(enums.packet.literal); if (!literalDataPacket) { throw new Error('No literal data packet to sign.'); From 6b4d44dbb17d434f6d2e5fc196580e39e87a5f44 Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Mon, 19 Feb 2018 15:57:32 +0100 Subject: [PATCH 5/7] Fix browser tests --- src/openpgp.js | 2 +- src/packet/clone.js | 2 +- test/general/openpgp.js | 39 ++++++++++++++++++++++----------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/openpgp.js b/src/openpgp.js index 8085ab16..5142b520 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -345,7 +345,7 @@ export function verify({ message, publicKeys, signature=null, date=new Date() }) publicKeys = toArray(publicKeys); if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('verify', { message, publicKeys, signature }); + return asyncProxy.delegate('verify', { message, publicKeys, signature, date }); } return Promise.resolve().then(async function() { diff --git a/src/packet/clone.js b/src/packet/clone.js index ff120950..b6969003 100644 --- a/src/packet/clone.js +++ b/src/packet/clone.js @@ -60,7 +60,7 @@ export function clonePackets(options) { if (options.message instanceof Message) { options.message = options.message.packets; } else if (options.message instanceof CleartextMessage) { - options.message.signature = options.message.signature.packets; + options.message = { text: options.message.text, signature: options.message.signature.packets }; } } if (options.signature && (options.signature instanceof Signature)) { diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 809fa079..c0bd1e1b 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -868,7 +868,12 @@ describe('OpenPGP.js public api tests', function() { beforeEach(function (done) { expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; - privateKey.keys[0].verifyPrimaryUser().then(() => done()); + Promise.all([ + privateKey.keys[0].verifyPrimaryUser(), + privateKey_2000_2008.keys[0].verifyPrimaryUser(), + privateKey_1337.keys[0].verifyPrimaryUser(), + privateKey_2038_2045.keys[0].verifyPrimaryUser() + ]).then(() => done()); }); it('should encrypt then decrypt', function () { @@ -1556,27 +1561,27 @@ describe('OpenPGP.js public api tests', function() { const future = new Date(2040, 5, 5, 5, 5, 5, 0); const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const signOpt = { - data, - privateKeys: privateKey_2038_2045.keys, - detached: true, - date: future, - armor: false + data, + privateKeys: privateKey_2038_2045.keys, + detached: true, + date: future, + armor: false }; const verifyOpt = { - publicKeys: publicKey_2038_2045.keys, - date: future + publicKeys: publicKey_2038_2045.keys, + date: future }; return openpgp.sign(signOpt).then(function (signed) { - verifyOpt.message = openpgp.message.fromBinary(data); - verifyOpt.signature = signed.signature; - return openpgp.verify(verifyOpt); + verifyOpt.message = openpgp.message.fromBinary(data); + verifyOpt.signature = signed.signature; + return openpgp.verify(verifyOpt); }).then(function (verified) { - expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); - expect(verified.data).to.equal(data); - expect(verified.signatures[0].valid).to.be.true; - expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, future)) - .to.be.not.null; - expect(verified.signatures[0].signature.packets.length).to.equal(1); + expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); + expect(Array.from(verified.data)).to.deep.equal(Array.from(data)); + expect(verified.signatures[0].valid).to.be.true; + expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, future)) + .to.be.not.null; + expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); From 56ad9a00e0df5a65dae73c23b2a1134cfef7aed7 Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Wed, 21 Feb 2018 22:32:09 +0100 Subject: [PATCH 6/7] Fix testcases --- test/general/openpgp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/general/openpgp.js b/test/general/openpgp.js index c0bd1e1b..477f1291 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1577,7 +1577,7 @@ describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(function (verified) { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); - expect(Array.from(verified.data)).to.deep.equal(Array.from(data)); + expect([].slice.call(verified.data)).to.deep.equal([].slice.call(data)); expect(verified.signatures[0].valid).to.be.true; expect(signOpt.privateKeys[0].getSigningKeyPacket(verified.signatures[0].keyid, future)) .to.be.not.null; From a5b30468efd581c8671c1e6b4fe856ab2e850a87 Mon Sep 17 00:00:00 2001 From: KAYLukas Date: Thu, 22 Feb 2018 00:41:12 +0100 Subject: [PATCH 7/7] Check creation time in expiration checks --- src/key.js | 36 +++++++++++++++++++++++------------- src/message.js | 2 +- src/packet/signature.js | 4 ++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/key.js b/src/key.js index 857f0fde..1f163f74 100644 --- a/src/key.js +++ b/src/key.js @@ -452,10 +452,14 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { await this.revocationSignature.verify(this.primaryKey, { key: this.primaryKey }))) { return enums.keyStatus.revoked; } - // check V3 expiration time - if (date !== null && this.primaryKey.version === 3 && this.primaryKey.expirationTimeV3 !== 0 && - util.normalizeDate(date) > (this.primaryKey.created.getTime() + this.primaryKey.expirationTimeV3*24*3600*1000)) { - return enums.keyStatus.expired; + const creationTime = this.primaryKey.created.getTime(); + const currentTime = util.normalizeDate(date); + // check V3 expiration time + if (date !== null && this.primaryKey.version === 3) { + const expirationTimeV3 = creationTime + (this.primaryKey.expirationTimeV3*24*3600*1000 || Infinity); + if (!(creationTime <= currentTime && currentTime < expirationTimeV3)) { + return enums.keyStatus.expired; + } } // check for at least one self signature. Self signature of user ID not mandatory // See {@link https://tools.ietf.org/html/rfc4880#section-11.1} @@ -469,9 +473,11 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { return enums.keyStatus.invalid; } // check V4 expiration time - if (date !== null && this.primaryKey.version === 4 && primaryUser.selfCertificate.keyNeverExpires === false && - util.normalizeDate(date) > (this.primaryKey.created.getTime() + primaryUser.selfCertificate.keyExpirationTime*1000)) { - return enums.keyStatus.expired; + if (date !== null && this.primaryKey.version === 4) { + const expirationTime = primaryUser.selfCertificate.keyNeverExpires === false ? creationTime + primaryUser.selfCertificate.keyExpirationTime*1000 : Infinity; + if (!(creationTime <= currentTime && currentTime < expirationTime)) { + return enums.keyStatus.expired; + } } return enums.keyStatus.valid; }; @@ -1001,10 +1007,14 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { await this.revocationSignature.verify(primaryKey, { key: primaryKey, bind: this.subKey }))) { return enums.keyStatus.revoked; } + const creationTime = this.subKey.created.getTime(); + const currentTime = util.normalizeDate(date); // check V3 expiration time - if (date !== null && this.subKey.version === 3 && this.subKey.expirationTimeV3 !== 0 && - util.normalizeDate(date) > (this.subKey.created.getTime() + this.subKey.expirationTimeV3*24*3600*1000)) { - return enums.keyStatus.expired; + if (currentTime !== null && this.subKey.version === 3) { + const expirationTime = creationTime + (this.subKey.expirationTimeV3*24*3600*1000 || Infinity); + if (!(creationTime <= currentTime && currentTime < expirationTime)) { + return enums.keyStatus.expired; + } } // check subkey binding signatures (at least one valid binding sig needed) // TODO replace when Promise.some or Promise.any are implemented @@ -1019,9 +1029,9 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { return enums.keyStatus.invalid; // last invalid binding signature } // check V4 expiration time - if (that.subKey.version === 4) { - if (date !== null && bindingSignature.keyNeverExpires === false && - util.normalizeDate(date) > (that.subKey.created.getTime() + bindingSignature.keyExpirationTime*1000)) { + if (that.subKey.version === 4 && currentTime !== null) { + const expirationTime = bindingSignature.keyNeverExpires === false ? (creationTime + bindingSignature.keyExpirationTime*1000) : Infinity; + if (!(creationTime <= currentTime && currentTime < expirationTime)) { return enums.keyStatus.expired; // last V4 expired binding signature } } diff --git a/src/message.js b/src/message.js index 612a6417..57320ecb 100644 --- a/src/message.js +++ b/src/message.js @@ -360,7 +360,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi /** * Sign the message (the literal data packet of the message) - * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to add to the message * @param {Date} date} (optional) override the creation time of the signature * @return {module:message~Message} new message with signed content diff --git a/src/packet/signature.js b/src/packet/signature.js index f1150f9c..d1fa0f5b 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -666,8 +666,8 @@ Signature.prototype.verify = async function (key, data) { * @return {Boolean} true if expired */ Signature.prototype.isExpired = function (date=new Date()) { - if (!this.signatureNeverExpires && date !== null) { - const expirationTime = this.created.getTime() + this.signatureExpirationTime*1000; + if (date !== null) { + const expirationTime = !this.signatureNeverExpires ? this.created.getTime() + this.signatureExpirationTime*1000 : Infinity; const normDate = util.normalizeDate(date); return !(this.created <= normDate && normDate < expirationTime); }