Merge pull request #647 from KAYLukas/feat/time-param

Add a date parameter to the sign/verify/encrypt/decrypt functions
This commit is contained in:
Bart Butler 2018-02-21 16:58:17 -08:00 committed by GitHub
commit e939d9b1ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 507 additions and 159 deletions

View File

@ -30,7 +30,6 @@ import enums from './enums';
import packet from './packet';
import { Signature } from './signature';
import { createVerificationObjects, createSignaturePackets } from './message';
import { getPreferredHashAlgo } from './key';
/**
* @class
@ -68,44 +67,50 @@ CleartextMessage.prototype.getSigningKeyIds = function() {
/**
* Sign the cleartext message
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature
* @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) {
return new CleartextMessage(this.text, await this.signDetached(privateKeys));
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<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature
* @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) {
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));
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date));
};
/**
* Verify signatures of cleartext signed message
* @param {Array<module:key~Key>} 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<module:key~Key>} 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);
};
/**

View File

@ -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

View File

@ -300,20 +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} 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, allowExpired=false) {
const primaryUser = this.getPrimaryUser(allowExpired);
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, allowExpired)) {
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)) {
if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) {
return this.subKeys[i].subKey;
}
}
@ -323,7 +323,8 @@ Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false) {
return null;
};
function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false) {
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,43 +332,35 @@ 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() &&
// check expiration time of V3 key packet
!(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 &&
Date.now() > (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))));
(!signature.isExpired(normDate) &&
(normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))));
}
function isValidSigningKeyPacket(keyPacket, signature, allowExpired=false) {
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) &&
(allowExpired || (!signature.isExpired() &&
// check expiration time of V3 key packet
!(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 &&
Date.now() > (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))));
(!signature.isExpired(normDate) &&
(normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))));
}
/**
* 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} 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) {
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])) {
if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) {
return this.subKeys[i].subKey;
}
}
@ -375,9 +368,9 @@ Key.prototype.getEncryptionKeyPacket = function(keyId) {
}
}
// 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)) {
isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) {
return this.primaryKey;
}
return null;
@ -448,10 +441,10 @@ 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
* @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(allowExpired=false) {
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() &&
@ -459,10 +452,14 @@ Key.prototype.verifyPrimaryKey = async function(allowExpired=false) {
await this.revocationSignature.verify(this.primaryKey, { key: this.primaryKey }))) {
return enums.keyStatus.revoked;
}
// check V3 expiration time
if (!allowExpired && this.primaryKey.version === 3 && this.primaryKey.expirationTimeV3 !== 0 &&
Date.now() > (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}
@ -471,14 +468,16 @@ Key.prototype.verifyPrimaryKey = async function(allowExpired=false) {
}
// check for valid self signature
await this.verifyPrimaryUser();
const primaryUser = this.getPrimaryUser(allowExpired);
const primaryUser = this.getPrimaryUser(date);
if (!primaryUser) {
return enums.keyStatus.invalid;
}
// check V4 expiration time
if (!allowExpired && this.primaryKey.version === 4 && primaryUser.selfCertificate.keyNeverExpires === false &&
Date.now() > (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;
};
@ -501,7 +500,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,17 +509,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 {Boolean} allowExpired allows signature verification with expired keys
* @param {Date} date use the given date for verification instead of the current time
* @return {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}|null} The primary user and the self signature
*/
Key.prototype.getPrimaryUser = function(allowExpired=false) {
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
@ -531,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(date)) {
continue;
}
primaryUsers.push({ index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j] });
@ -840,17 +839,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<module:key~Key>} keys array of keys to verify certificate signatures
* @param {Boolean} allowExpired allows signature verification with expired keys
* @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, allowExpired=false) {
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, allowExpired);
const keyPacket = key.getSigningKeyPacket(keyid, date);
if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) {
return enums.keyStatus.revoked;
}
@ -959,14 +958,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) {
if (await this.verify(primaryKey) !== enums.keyStatus.valid) {
SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) {
if (await this.verify(primaryKey, date) !== 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;
}
}
@ -976,15 +976,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 {Boolean} allowExpired allows signature verification with expired keys
* @param {Date} date use the given date for verification instead of the current time
* @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, date=new Date()) {
if (await this.verify(primaryKey, date) !== 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], date)) {
return true;
}
}
@ -995,10 +995,10 @@ 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
* @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, allowExpired=false) {
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
@ -1007,16 +1007,20 @@ SubKey.prototype.verify = async function(primaryKey, allowExpired=false) {
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 (!allowExpired && this.subKey.version === 3 && this.subKey.expirationTimeV3 !== 0 &&
Date.now() > (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
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(date)) {
return enums.keyStatus.expired; // last expired binding signature
}
// check binding signature can verify
@ -1025,9 +1029,9 @@ SubKey.prototype.verify = async function(primaryKey, allowExpired=false) {
return enums.keyStatus.invalid; // last invalid binding signature
}
// check V4 expiration time
if (that.subKey.version === 4) {
if (!allowExpired && bindingSignature.keyNeverExpires === false &&
Date.now() > (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
}
}
@ -1380,7 +1384,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:

View File

@ -239,9 +239,10 @@ Message.prototype.getText = function() {
* @param {Array<String>} 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) override the creation date of the literal package
* @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, date=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, date);
if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
@ -296,16 +297,17 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard = fal
* @param {Array<Key>} publicKeys (optional) public key(s) for message encryption
* @param {Array<String>} 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) override the creation date signature
* @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, 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();
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());
}
@ -358,11 +360,12 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi
/**
* Sign the message (the literal data packet of the message)
* @param {Array<module:key~Key>} privateKey private keys with decrypted secret key data for signing
* @param {Array<module:key~Key>} 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
*/
Message.prototype.sign = async function(privateKeys=[], signature=null) {
Message.prototype.sign = async function(privateKeys=[], signature=null, date=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, date);
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, date));
return new Message(packetlist);
};
@ -443,26 +446,28 @@ Message.prototype.compress = function(compression) {
/**
* Create a detached signature for the message (the literal data packet of the message)
* @param {Array<module:key~Key>} privateKey private keys with decrypted secret key data for signing
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature
* @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) {
Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) {
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, date));
};
/**
* Create signature packets for the message
* @param {module:packet/literal} the literal data packet to sign
* @param {Array<module:key~Key>} privateKey private keys with decrypted secret key data for signing
* @param {module:packet/literal} literalDataPacket the literal data packet to sign
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature to append
* @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) {
export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date()) {
const packetlist = new packet.List();
const literalFormat = enums.write(enums.literal, literalDataPacket.format);
@ -474,14 +479,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, 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();
const signaturePacket = new packet.Signature(date);
signaturePacket.signatureType = signatureType;
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey);
@ -501,32 +506,34 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
/**
* Verify message signatures
* @param {Array<module:key~Key>} 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
*/
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<module:key~Key>} keys array of keys to verify signatures
* @param {Signature}
* @param {Signature} signature
* @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) {
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);
};
/**
@ -534,15 +541,16 @@ Message.prototype.verifyDetached = function(signature, keys) {
* @param {Array<module:packet/signature>} signatureList array of signature packets
* @param {Array<module:packet/literal>} literalDataList array of literal data packets
* @param {Array<module:key~Key>} keys array of keys to verify signatures
* @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) {
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;
}
@ -626,11 +634,12 @@ export function readSignedContent(content, detachedSignature) {
* creates new message object from text
* @param {String} text
* @param {String} filename (optional)
* @param {Date} date (optional)
* @return {module:message~Message} new message object
* @static
*/
export function fromText(text, filename) {
const literalDataPacket = new packet.Literal();
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) {
@ -645,15 +654,16 @@ export function fromText(text, filename) {
* creates new message object from binary data
* @param {Uint8Array} bytes
* @param {String} filename (optional)
* @param {Date} date (optional)
* @return {module:message~Message} new message object
* @static
*/
export function fromBinary(bytes, filename) {
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();
const literalDataPacket = new packet.Literal(date);
if (filename) {
literalDataPacket.setFilename(filename);
}

View File

@ -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} date (optional) override the creation date of the message and the message signature
* @return {Promise<Object>} 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, 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 });
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);
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);
const detachedSignature = await message.signDetached(privateKeys, signature, date);
result.signature = armor ? detachedSignature.armor() : detachedSignature;
} else {
message = await message.sign(privateKeys, signature);
message = await message.sign(privateKeys, signature, date);
}
}
message = message.compress(compression);
return message.encrypt(publicKeys, passwords, sessionKey, wildcard);
return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date);
}).then(encrypted => {
if (armor) {
@ -253,15 +254,16 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey,
* @param {Key|Array<Key>} 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) use the given date for verification instead of the current time
* @return {Promise<Object>} 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) {
@ -272,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'));
}
@ -291,20 +293,21 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @param {Key|Array<Key>} 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) override the creation date signature
* @return {Promise<Object>} 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, date=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, date
});
}
@ -313,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);
const signature = await message.signDetached(privateKeys, undefined, date);
result.signature = armor ? signature.armor() : signature;
} else {
message = await message.sign(privateKeys);
message = await message.sign(privateKeys, undefined, date);
if (armor) {
result.data = message.armor();
} else {
@ -332,22 +335,23 @@ export function sign({
* @param {Key|Array<Key>} 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 (optional) use the given date for verification instead of the current time
* @return {Promise<Object>} 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);
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() {
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, date);
return result;
}).catch(onError.bind(null, 'Error verifying cleartext signed message'));
}
@ -493,14 +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} date the creation date of the package
* @return {Message} a message object
*/
function createMessage(data, filename) {
function createMessage(data, filename, date=new Date()) {
let msg;
if (util.isUint8Array(data)) {
msg = messageLib.fromBinary(data, filename);
msg = messageLib.fromBinary(data, filename, date);
} else if (util.isString(data)) {
msg = messageLib.fromText(data, filename);
msg = messageLib.fromText(data, filename, date);
} else {
throw new Error('Data must be of type String or Uint8Array');
}

View File

@ -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)) {

View File

@ -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() {
export default function Literal(date=new Date()) {
this.tag = enums.packet.literal;
this.format = 'utf8'; // default format for literal data packets
this.date = new Date();
this.date = util.normalizeDate(date);
this.data = new Uint8Array(0); // literal data representation
this.filename = 'msg.txt';
}

View File

@ -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)

View File

@ -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() {
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() {
this.unhashedSubpackets = null;
this.signedHashValue = null;
this.created = new 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 () {
if (!this.signatureNeverExpires) {
return Date.now() > (this.created.getTime() + this.signatureExpirationTime*1000);
Signature.prototype.isExpired = function (date=new Date()) {
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);
}
return false;
};

View File

@ -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;

View File

@ -158,6 +158,132 @@ const priv_key_de =
'=kyeP',
'-----END PGP PRIVATE KEY BLOCK-----'].join('\n');
const priv_key_2000_2008 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
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
t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU=
=C0fJ
-----END PGP PRIVATE KEY BLOCK-----`;
const priv_key_2038_2045 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
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
0uyeibPdgJTjARMuQzDFA8bdbM540vBf5Q==
=WLIN
-----END PGP PRIVATE KEY BLOCK-----`;
const priv_key_expires_1337 = `-----BEGIN PGP PRIVATE 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한국어/조선말';
const password1 = 'I am a password';
@ -465,6 +591,12 @@ 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_1337;
let publicKey_1337;
let privateKey;
let publicKey;
let zero_copyVal;
@ -478,6 +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;
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_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;
@ -724,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 () {
@ -1347,25 +1496,197 @@ 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 = openpgp.util.normalizeDate();
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(+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(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.null;
expect(verified.signatures[0].signature.packets.length).to.equal(1);
// 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(+past);
expect(verified.data).to.equal(plaintext);
expect(verified.signatures[0].valid).to.be.true;
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 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([].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;
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,
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,
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);
});
});
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, 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
};
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);
});
});
});

View File

@ -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;