Merge pull request #772 from twiss/getLatestValidSignature

Check validity and keyid of signatures before using them
This commit is contained in:
Sanjana Rajan 2018-09-22 16:27:07 -07:00 committed by GitHub
commit a35b4d28e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 278 additions and 227 deletions

View File

@ -261,32 +261,28 @@ Key.prototype.armor = function() {
};
/**
* Returns the signature that has the latest creation date, while ignoring signatures created in the future.
* Returns the valid and non-expired signature that has the latest creation date, while ignoring signatures created in the future.
* @param {Array<module:packet.Signature>} signatures List of signatures
* @param {Date} date Use the given date instead of the current time
* @returns {module:packet.Signature} The latest signature
* @returns {Promise<module:packet.Signature>} The latest valid signature
* @async
*/
function getLatestSignature(signatures, date=new Date()) {
let signature = signatures[0];
for (let i = 1; i < signatures.length; i++) {
if (signatures[i].created >= signature.created &&
(signatures[i].created <= date || date === null)) {
async function getLatestValidSignature(signatures, primaryKey, dataToVerify, date=new Date()) {
let signature;
for (let i = signatures.length - 1; i >= 0; i--) {
if (
(!signature || signatures[i].created >= signature.created) &&
// check binding signature is not expired (ie, check for V4 expiration time)
!signatures[i].isExpired(date) &&
// check binding signature is verified
(signatures[i].verified || await signatures[i].verify(primaryKey, dataToVerify))
) {
signature = signatures[i];
}
}
return signature;
}
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) &&
signature.verified && !signature.revoked && !signature.isExpired(date) &&
!isDataExpired(keyPacket, signature, date);
}
/**
* Returns last created key or key by given keyId that is available for signing and verification
* @param {module:type/keyid} keyId, optional
@ -302,8 +298,9 @@ Key.prototype.getSigningKey = async function (keyId=null, date=new Date(), userI
for (let i = 0; i < subKeys.length; i++) {
if (!keyId || subKeys[i].getKeyId().equals(keyId)) {
if (await subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) {
const bindingSignature = getLatestSignature(subKeys[i].bindingSignatures, date);
if (isValidSigningKeyPacket(subKeys[i].keyPacket, bindingSignature, date)) {
const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket };
const bindingSignature = await getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, dataToVerify, date);
if (bindingSignature && isValidSigningKeyPacket(subKeys[i].keyPacket, bindingSignature)) {
return subKeys[i];
}
}
@ -311,24 +308,23 @@ Key.prototype.getSigningKey = async function (keyId=null, date=new Date(), userI
}
const primaryUser = await this.getPrimaryUser(date, userId);
if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) &&
isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification, date)) {
isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification)) {
return this;
}
}
return null;
};
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) &&
keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.eddsa) &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 ||
(signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) &&
signature.verified && !signature.revoked && !signature.isExpired(date) &&
!isDataExpired(keyPacket, signature, date);
function isValidSigningKeyPacket(keyPacket, signature) {
if (!signature.verified || signature.revoked !== false) { // Sanity check
throw new Error('Signature not verified');
}
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);
}
};
/**
* Returns last created key or key by given keyId that is available for encryption or decryption
@ -346,8 +342,9 @@ Key.prototype.getEncryptionKey = async function(keyId, date=new Date(), userId={
for (let i = 0; i < subKeys.length; i++) {
if (!keyId || subKeys[i].getKeyId().equals(keyId)) {
if (await subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) {
const bindingSignature = getLatestSignature(subKeys[i].bindingSignatures, date);
if (isValidEncryptionKeyPacket(subKeys[i].keyPacket, bindingSignature, date)) {
const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket };
const bindingSignature = await getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, dataToVerify, date);
if (bindingSignature && isValidEncryptionKeyPacket(subKeys[i].keyPacket, bindingSignature)) {
return subKeys[i];
}
}
@ -356,11 +353,24 @@ Key.prototype.getEncryptionKey = async function(keyId, date=new Date(), userId={
// if no valid subkey for encryption, evaluate primary key
const primaryUser = await this.getPrimaryUser(date, userId);
if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) &&
isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification, date)) {
isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification)) {
return this;
}
}
return null;
function isValidEncryptionKeyPacket(keyPacket, signature) {
if (!signature.verified || signature.revoked !== false) { // Sanity check
throw new Error('Signature not verified');
}
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) &&
keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.eddsa) &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 ||
(signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0);
}
};
/**
@ -473,11 +483,12 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date(), userId={}) {
/**
* Returns the latest date when the key can be used for encrypting, signing, or both, depending on the `capabilities` paramater.
* When `capabilities` is null, defaults to returning the expiry date of the primary key.
* Returns null if `capabilities` is passed and the key does not have the specified capabilities or is revoked or invalid.
* Returns Infinity if the key doesn't expire.
* @param {encrypt|sign|encrypt_sign} capabilities, optional
* @param {module:type/keyid} keyId, optional
* @param {Object} userId, optional user ID
* @returns {Promise<Date>}
* @returns {Promise<Date | Infinity | null>}
* @async
*/
Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) {
@ -492,13 +503,13 @@ Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) {
if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') {
const encryptKey = await this.getEncryptionKey(keyId, null, userId);
if (!encryptKey) return null;
const encryptExpiry = encryptKey.getExpirationTime();
const encryptExpiry = await encryptKey.getExpirationTime(this.keyPacket);
if (encryptExpiry < expiry) expiry = encryptExpiry;
}
if (capabilities === 'sign' || capabilities === 'encrypt_sign') {
const signKey = await this.getSigningKey(keyId, null, userId);
if (!signKey) return null;
const signExpiry = signKey.getExpirationTime();
const signExpiry = await signKey.getExpirationTime(this.keyPacket);
if (signExpiry < expiry) expiry = signExpiry;
}
return expiry;
@ -515,16 +526,20 @@ Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) {
* @async
*/
Key.prototype.getPrimaryUser = async function(date=new Date(), userId={}) {
const users = this.users.map(function(user, index) {
const selfCertification = getLatestSignature(user.selfCertifications, date);
return { index, user, selfCertification };
}).filter(({ user, selfCertification }) => {
return user.userId && selfCertification && (
const primaryKey = this.keyPacket;
const users = [];
for (let i = 0; i < this.users.length; i++) {
const user = this.users[i];
if (!user.userId || !(
(userId.name === undefined || user.userId.name === userId.name) &&
(userId.email === undefined || user.userId.email === userId.email) &&
(userId.comment === undefined || user.userId.comment === userId.comment)
);
});
)) continue;
const dataToVerify = { userId: user.userId, key: primaryKey };
const selfCertification = await getLatestValidSignature(user.selfCertifications, primaryKey, dataToVerify, date);
if (!selfCertification) continue;
users.push({ index: i, user, selfCertification });
}
if (!users.length) {
if (userId.name !== undefined || userId.email !== undefined ||
userId.comment !== undefined) {
@ -539,18 +554,9 @@ Key.prototype.getPrimaryUser = async function(date=new Date(), userId={}) {
return A.isPrimaryUserID - B.isPrimaryUserID || A.created - B.created;
}).pop();
const { user, selfCertification: cert } = primaryUser;
const primaryKey = this.keyPacket;
const dataToVerify = { userId: user.userId, key: primaryKey };
// skip if certificates is invalid, revoked, or expired
if (!(cert.verified || await cert.verify(primaryKey, dataToVerify))) {
return null;
}
if (cert.revoked || await user.isRevoked(primaryKey, cert, null, date)) {
return null;
}
if (cert.isExpired(date)) {
return null;
}
return primaryUser;
};
@ -678,12 +684,15 @@ Key.prototype.revoke = async function({
/**
* Get revocation certificate from a revoked key.
* (To get a revocation certificate for an unrevoked key, call revoke() first.)
* @returns {String} armored revocation certificate
* @returns {Promise<String>} armored revocation certificate
* @async
*/
Key.prototype.getRevocationCertificate = function() {
if (this.revocationSignatures.length) {
Key.prototype.getRevocationCertificate = async function() {
const dataToVerify = { key: this.keyPacket };
const revocationSignature = await getLatestValidSignature(this.revocationSignatures, this.keyPacket, dataToVerify);
if (revocationSignature) {
const packetlist = new packet.List();
packetlist.push(getLatestSignature(this.revocationSignatures));
packetlist.push(revocationSignature);
return armor.encode(enums.armor.public_key, packetlist.write(), null, null, 'This is a revocation certificate');
}
};
@ -1090,17 +1099,17 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) {
const that = this;
const dataToVerify = { key: primaryKey, bind: this.keyPacket };
// check subkey binding signatures
const bindingSignature = getLatestSignature(this.bindingSignatures, date);
const bindingSignature = await getLatestValidSignature(this.bindingSignatures, primaryKey, dataToVerify, date);
// check binding signature is verified
if (!(bindingSignature.verified || await bindingSignature.verify(primaryKey, dataToVerify))) {
if (!bindingSignature) {
return enums.keyStatus.invalid;
}
// check binding signature is not revoked
if (bindingSignature.revoked || await that.isRevoked(primaryKey, bindingSignature, null, date)) {
return enums.keyStatus.revoked;
}
// check binding signature is not expired (ie, check for V4 expiration time)
if (bindingSignature.isExpired(date)) {
// check for expiration time
if (isDataExpired(this.keyPacket, bindingSignature, date)) {
return enums.keyStatus.expired;
}
return enums.keyStatus.valid; // binding signature passed all checks
@ -1108,11 +1117,17 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) {
/**
* Returns the expiration time of the subkey or Infinity if key does not expire
* Returns null if the subkey is invalid.
* @param {module:packet.SecretKey|
* module:packet.PublicKey} primaryKey The primary key packet
* @param {Date} date Use the given date instead of the current time
* @returns {Date}
* @returns {Promise<Date | Infinity | null>}
* @async
*/
SubKey.prototype.getExpirationTime = function(date=new Date()) {
const bindingSignature = getLatestSignature(this.bindingSignatures, date);
SubKey.prototype.getExpirationTime = async function(primaryKey, date=new Date()) {
const dataToVerify = { key: primaryKey, bind: this.keyPacket };
const bindingSignature = await getLatestValidSignature(this.bindingSignatures, primaryKey, dataToVerify, date);
if (!bindingSignature) return null;
const keyExpiry = getExpirationTime(this.keyPacket, bindingSignature);
const sigExpiry = bindingSignature.getExpirationTime();
return keyExpiry < sigExpiry ? keyExpiry : sigExpiry;
@ -1186,8 +1201,6 @@ SubKey.prototype.revoke = async function(primaryKey, {
return subKey;
};
/**
*/
['getKeyId', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'isDecrypted'].forEach(name => {
Key.prototype[name] =
SubKey.prototype[name] =
@ -1207,9 +1220,16 @@ SubKey.prototype.revoke = async function(primaryKey, {
export async function read(data) {
const result = {};
result.keys = [];
const err = [];
try {
const packetlist = new packet.List();
await packetlist.read(data);
if (packetlist.filterByTag(enums.packet.signature).some(
signature => signature.revocationKeyClass !== null
)) {
// Indicate an error, but still parse the key.
err.push(new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'));
}
const keyIndex = packetlist.indexOfTag(enums.packet.publicKey, enums.packet.secretKey);
if (keyIndex.length === 0) {
throw new Error('No key packet found');
@ -1220,13 +1240,14 @@ export async function read(data) {
const newKey = new Key(oneKeyList);
result.keys.push(newKey);
} catch (e) {
result.err = result.err || [];
result.err.push(e);
err.push(e);
}
}
} catch (e) {
result.err = result.err || [];
result.err.push(e);
err.push(e);
}
if (err.length) {
result.err = err;
}
return result;
}
@ -1545,8 +1566,19 @@ async function isDataRevoked(primaryKey, dataToVerify, revocations, signature, k
const normDate = util.normalizeDate(date);
const revocationKeyIds = [];
await Promise.all(revocations.map(async function(revocationSignature) {
if (!(config.revocations_expire && revocationSignature.isExpired(normDate)) &&
(revocationSignature.verified || await revocationSignature.verify(key, dataToVerify))) {
if (
// Note: a third-party revocation signature could legitimately revoke a
// self-signature if the signature has an authorized revocation key.
// However, we don't support passing authorized revocation keys, nor
// verifying such revocation signatures. Instead, we indicate an error
// when parsing a key with an authorized revocation key, and ignore
// third-party revocation signatures here. (It could also be revoking a
// third-party key certification, which should only affect
// `verifyAllCertifications`.)
(!signature || revocationSignature.issuerKeyId.equals(signature.issuerKeyId)) &&
!(config.revocations_expire && revocationSignature.isExpired(normDate)) &&
(revocationSignature.verified || await revocationSignature.verify(key, dataToVerify))
) {
// TODO get an identifier of the revoked object instead
revocationKeyIds.push(revocationSignature.issuerKeyId);
return true;
@ -1556,7 +1588,7 @@ async function isDataRevoked(primaryKey, dataToVerify, revocations, signature, k
// TODO further verify that this is the signature that should be revoked
if (signature) {
signature.revoked = revocationKeyIds.some(keyId => keyId.equals(signature.issuerKeyId)) ? true :
signature.revoked;
signature.revoked || false;
return signature.revoked;
}
return revocationKeyIds.length > 0;

View File

@ -124,7 +124,7 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
}
return generate(options).then(async key => {
const revocationCertificate = key.getRevocationCertificate();
const revocationCertificate = await key.getRevocationCertificate();
key.revocationSignatures = [];
return convertStreams({
@ -159,8 +159,8 @@ export function reformatKey({privateKey, userIds=[], passphrase="", keyExpiratio
options.revoked = options.revocationCertificate;
return reformat(options).then(key => {
const revocationCertificate = key.getRevocationCertificate();
return reformat(options).then(async key => {
const revocationCertificate = await key.getRevocationCertificate();
key.revocationSignatures = [];
return {
@ -344,7 +344,7 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
* @param {String|Array<String>} passwords (optional) passwords to decrypt the message
* @param {Object|Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String }
* @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 {'utf8'|'binary'} format (optional) whether to return data as a string(Stream) or Uint8Array(Stream). If 'utf8' (the default), also normalize newlines.
* @param {'web'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any.
* @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time

View File

@ -21,6 +21,7 @@ import util from '../util';
* are stored as numerical indices.
* @memberof module:packet
* @constructor
* @extends Array
*/
function List() {
/**
@ -31,6 +32,8 @@ function List() {
this.length = 0;
}
List.prototype = [];
/**
* Reads a stream of binary data and interprents it as a list of packets.
* @param {Uint8Array | ReadableStream<Uint8Array>} A Uint8Array of bytes.
@ -145,37 +148,6 @@ List.prototype.push = function (packet) {
this.length++;
};
/**
* Remove a packet from the list and return it.
* @returns {Object} The packet that was removed
*/
List.prototype.pop = function() {
if (this.length === 0) {
return;
}
const packet = this[this.length - 1];
delete this[this.length - 1];
this.length--;
return packet;
};
/**
* Creates a new PacketList with all packets that pass the test implemented by the provided function.
*/
List.prototype.filter = function (callback) {
const filtered = new List();
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) {
filtered.push(this[i]);
}
}
return filtered;
};
/**
* Creates a new PacketList with all packets from the given types
*/
@ -193,58 +165,6 @@ List.prototype.filterByTag = function (...args) {
return filtered;
};
/**
* Executes the provided callback once for each element
*/
List.prototype.forEach = function (callback) {
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
};
/**
* Returns an array containing return values of callback
* on each element
*/
List.prototype.map = function (callback) {
const packetArray = [];
for (let i = 0; i < this.length; i++) {
packetArray.push(callback(this[i], i, this));
}
return packetArray;
};
/**
* Executes the callback function once for each element
* until it finds one where callback returns a truthy value
* @param {Function} callback
* @returns {Promise<Boolean>}
* @async
*/
List.prototype.some = async function (callback) {
for (let i = 0; i < this.length; i++) {
if (await callback(this[i], i, this)) {
return true;
}
}
return false;
};
/**
* Executes the callback function once for each element,
* returns true if all callbacks returns a truthy value
*/
List.prototype.every = function (callback) {
for (let i = 0; i < this.length; i++) {
if (!callback(this[i], i, this)) {
return false;
}
}
return true;
};
/**
* Traverses packet tree and returns first matching packet
* @param {module:enums.packet} type The packet type
@ -285,20 +205,6 @@ List.prototype.indexOfTag = function (...args) {
return tagIndex;
};
/**
* Returns slice of packetlist
*/
List.prototype.slice = function (begin, end) {
if (!end) {
end = this.length;
}
const part = new List();
for (let i = begin; i < end; i++) {
part.push(this[i]);
}
return part;
};
/**
* Concatenates packetlist or array of packets
*/

View File

@ -201,6 +201,12 @@ Signature.prototype.sign = async function (key, data) {
this.signature = stream.fromAsync(async () => crypto.signature.sign(
publicKeyAlgorithm, hashAlgorithm, params, toHash, await stream.readToEnd(hash)
));
// Store the fact that this signature is valid, e.g. for when we call `await
// getLatestValidSignature(this.revocationSignatures, key, data)` later. Note
// that this only holds up if the key and data passed to verify are the same
// as the ones passed to sign.
this.verified = true;
return true;
};

View File

@ -1223,6 +1223,111 @@ t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU=
=C0fJ
-----END PGP PRIVATE KEY BLOCK-----`;
const key_with_authorized_revocation_key = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js VERSION
Comment: https://openpgpjs.org
xsBNBFujnwwBCADK1xX03tSCmktPDS9Ncij3O5wG+F5/5Zm7QJDc39Wt1t/K
szCSobWtm/UObQVZjsTGwg0ZUPgepgWGGDBL0dlc1NObwUiOhGYnJnd4V25P
iU5Mg3+DhRq+LzNK+oGlpVPDpwQ48S8HOphbswKUpuaDcEQ2f+NKIc0eXe5m
ut5x9uoVj8jneUNsHYq6FIlxh4knzpJWFj5+LNi7plMCwKip6srVNf8He/q0
0xA/4vjSIOfGIE7TCBH33CbHEr98p81Cf4g0E+kswEz5iWE2SDCyYgQkMrkz
H9mtVqk3nFT8NR0USxKqH9bGhaTx1AWq/HDgsphayPEWQ0usjQDrbQUnABEB
AAHNDnRlc3QgPGFAYi5jb20+wsCNBBABCABBBQJbo58MBgsJBwgDAhcMgAH0
cOUNyxrV8eZOCGRKY2E6TW5AlAkQWICi/ReDcrkEFQgKAgMWAgECGQECGwMC
HgEAADgHB/0WIHh6maX2LZ0u5ujk1tZxWMrCycccopdQNKN0RGD98X4fyY6J
wfmKb107gcidJBFct0sVWFW8GU42w9pVMU5qWD6kyFkgcmov319UL+7aZ19b
HOWVKUTb6rFG8/qAbq3BF7YB/cZIBWMFKAS3BRJ4Kz23GheAB2A9oVLmuq5o
gW5c2R1YC0T0XyXEFiw9uZ+AS6kEZymFPRQfPUIbJs1ct/lAN+mC9Qp0Y6CL
60Hd6jlKUb6TgljaQ6CtLfT9v72GeKznapKr9IEtsgYv69j0c/MRM2nmu50c
g+fICiiHrTbNS6jkUz0pZLe7hdhWHeEiqcA9+GC1DxOQCRCS/YNfzsBNBFuj
nwwBCADK1xX03tSCmktPDS9Ncij3O5wG+F5/5Zm7QJDc39Wt1t/KszCSobWt
m/UObQVZjsTGwg0ZUPgepgWGGDBL0dlc1NObwUiOhGYnJnd4V25PiU5Mg3+D
hRq+LzNK+oGlpVPDpwQ48S8HOphbswKUpuaDcEQ2f+NKIc0eXe5mut5x9uoV
j8jneUNsHYq6FIlxh4knzpJWFj5+LNi7plMCwKip6srVNf8He/q00xA/4vjS
IOfGIE7TCBH33CbHEr98p81Cf4g0E+kswEz5iWE2SDCyYgQkMrkzH9mtVqk3
nFT8NR0USxKqH9bGhaTx1AWq/HDgsphayPEWQ0usjQDrbQUnABEBAAHCwF8E
GAEIABMFAlujnwwJEFiAov0Xg3K5AhsMAACI/QgArvTcutod+7n1D8wCwM50
jo3x4KPuQw+NwbOnMbFwv0R8i8NqtSFf2bYkkZ7RLViTmphvSon4h2WgfczL
SBulZ1QZF7zCKXmXDg8/HZgRUflC1XMixpB8Hqouin5AVgMbsbHg30V2uPco
V3DeFQ8HWxQC9symaMW/20MkqNXgCjM0us7kVwTEJQqZ6KYrFVjKyprSQRyP
rfckEBuZnj91OS+kAHlZ+ScZIuV4QVF0e2U6oEuB+qFXppR030PJoO6WDOzm
67hkzfrc3VpKw/I+vVJnZb4GOhNTSpa+p8i4gTyWmrOZ0G85QoldHWlWaRBg
lbjwPj3QUTbLFvHisYzXEQ==
=aT8U
-----END PGP PUBLIC KEY BLOCK-----
`;
const key_with_revoked_third_party_cert = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
mQENBFS2KSEBCACzz8KtwE5ualmgF+rKo8aPQ9inTQWCNzCuTs3HaSe0D5heGoSh
mJWl9B5zvXN78L3yzmtWQV92CXOkCRWezIY8y+aN+aJZ6PzPE5Yy74404v3yG9ZK
jGlAWC7Wgkx+YR2vbzj7hDqi5e6TpDGsFkH3OsI3nY7FIvXWbz9Ih4/s/nBPuF0v
sBZ0n97ItszhnrXvvrF1fQvEviB0+xF5DfUURWP45EA+NWnBl7HFzY4FeN5ImYZK
Nt6A88i9SIB3MiwRSUy1UwJjL2L8l+rLbr20JbnIUuJN3h/dY10igxyOh5gsXtr1
fabsm6s2AacrCjQqLkXSnB8Ucu+Enz5R1s0dABEBAAG0KVBhc3N3b3J0ICDDpMOE
Pz9eXjEyIDIgwrUgIDxwd2RAdGVzdC5jb20+iFoEMBEKABoFAlYvilYTHSBJY2gg
d2VpcyBlcyBuaWNodAAKCRDheQpvjBNXTQ5jAKC0AMb5Ivoy0DKNI8Hjus72ob3u
TACg32AGuCernx1Wt7/5oi4KdjxjjxeJATIEEAEIACYFAlS2KSIGCwkIBwMCCRBj
ZJ9T2gveXAQVCAIKAxYCAQIbAwIeAQAA/c4H/i/NgI36q/2lwcRkt5rsVBUlx+Ho
+iKIEh1+XKfDq4A8DTjeYCCOg/k3hDm2LpmmclwRc2X9CMwraSoFTEN6Em78Kd5a
DFaNPbWGP0RCW5zqPGAoZSvOlZYsLMaswFMBD93wf3XwHK8HxTJhTmQC1kGSplO1
GMWkTh6B3tqiy/Jk7Hp5mPASQBid+E9rjr8CgOPF26hjTw+UUBs2ZWO9U9PyhBYH
xLIwtUDoZvqhTdXD0aV7vhRQw6p3UEzxa8t/1iGogHe2CfcMgq5jYmHLTZN3VGE3
djwLQIikRRig7dTBD9BgeG6a+22XRbvpOsHBzsAH4UC97nS+wzkgkyvqfOKIRgQQ
EQoABgUCVi+JYAAKCRDheQpvjBNXTTNIAJwJacb4mKUPrRnnNcGXC6hjizuJOACg
zVFVnWA/A+GrHBogUD780vcJwMG5AQ0EVLYpIQEIANNCJ5sUKv6YDWRToF/tG6ik
LjTAcNelr5LCXLT3Y7CAmk7y88vzCaTLZZUWgwyK8lYGZ3x2icoc4fJeo5BhHNJz
TSL239cTsAugNoVMJFG2xm1TEzBsBCNPOOVpS5cArt6mmhxozwkafawtgA+5z0zB
vQm0AHPudSAJp3Gx69meRzAJgdFVgljZVyCUmQizJqJ1dQPPgarpJKJy3f0+g0ec
yx+gTA4nj+zfqjrXM4O1Ok/Di+8mneA3bhadiYU1VjkqY+1UqkQOU0UFdDlBppRj
xr6h00xECoayyPXr/U+gFSgZHO1mKk3meCyNVKLGAajQxWVWfBwoPixfqOXlBh0A
EQEAAYkBHwQYAQgAEwUCVLYpIwkQY2SfU9oL3lwCGwwAAMkDB/9QeLRyEOX2LWdZ
UkxSldMklAvMBjqY27cC1Qn8wiWzHNJKVEnQ9MiKn0mVRohkRKgsiWfSugDVyVov
eTM7PSjDlAYALCSxSYStykordUSf/5WYb9Mmea4J/WXBQCvwJKFU47ZDl2Tg+HdS
SVznLTt/ASxd2Nap2mUveC4ivGdSo1KOq3+t95xGC7dh4U3JwPanBZ6cfBJbnSEs
QWLUAPnlfn37Ff14haRETX3ND82bkXKEGEk6O5HJ0PWuUtl+TFIkYUGh4zFOZZWq
VHwaffAHDrmTZt/pOXg9l/VFnzfxrId33Tog3Zvejm2+8d2LhSCtfdrdJ/Dx2CZM
Yzxp9Mp9
=uVX6
-----END PGP PUBLIC KEY BLOCK-----
`;
const certifying_key = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org
mQGiBEOd+/gRBACfqCfgQCmUzOr7iA1CerGVmFm8HcN+NVGSpkwF6pmPJh1XVGEA
Nz9Aok6Vx4MQ+QCKo9dTXMZWDE4W/vzaKaEmsirsxGgn7JhK0t/9VeXXieWiJirA
5iTQMsRjfnS6MLLUr56E7HmDZftiOcpJu81S943r+oeINhq37SlJM7Q47wCg8miR
egss26IZfW3RvBuNW1KEDh0D/195DH6sl+/qmgUAj3M7ai1iKOqkILdNuIkYRc18
bsBYIAOjY81guhlEabYEqv8FUKPh2A7dAe/4og89HrUsVxOKJ9EGyrcqyJj676gK
BL383t1dQvyJyWfV5z4umUMaF/xE46go3Trdyu86aJDe57B74RYbWL2aaGLtkPJ2
rHOnBACG5PmLM6EXWJQSfbRqJHlRysc3MOAXMnxu8DVz+Tm0nPcArUG+Mf3GmxT+
537nHqF2xEgVvwfkvGB51ideRRQMhHUzy583zkuPYV1cJ6ykfeA6EHzVbT4vRzO8
AW9ELBKTK9F4N4gGTOdAATcaMC0gwzCz+fofJEJqC/9CS2pYvrQlVGhvbWFzIE9i
ZXJuZMO2cmZlciA8dG9iZXJuZG9AZ214LmRlPohdBBMRAgAdBQJDnfv4BgsJCAcD
AgQVAggDBBYCAwECHgECF4AACgkQ4XkKb4wTV02nkACfWvWnRawPI9AmgXpg6jg1
/22exKkAoMJ+yFhjtuGobOrIAPcEYrwlTQXBiGsEEBECACsFAksJKNQFgwHihQAe
Gmh0dHA6Ly93d3cuY2FjZXJ0Lm9yZy9jcHMucGhwAAoJENK7DQFl0P1Y4VoAmgMc
2qWuBtOBb6Uj6DskTtXORlPgAKCB3Hqp8dJ3dbQh5m0Ebf8F3P71WrkCDQRDnfw1
EAgAkp+0jMpfKKF6Gxy63fP3tCFzn9vq3GBtbNnvp8b0dx6nf+ZxELt/q9K4Yz9g
+sXq0RFQGV+YwS2BGoogzRcT4PHmUBcEAbjZIs9lZdZDEF0/68d+32mHSkLZJxGI
ezXJK3+MpGPnCMbQ63UYpcY1BvL7Vbj6P4X75dJJReGIHQMBA0FEYB5AVm6HrWU5
eDvOZ2w8QAAUluFnD9/xNRqBpcwm5uoox7zq60W5coK6p6WX8t5+WMMrRKF2A1Ru
aTxYQKo3f8XQA4e6tEcdGFlk1K9W8Ov1xVRQa6EqQYZFesbuoo8HHuSNsJ7PQrP+
vyYcafohlO/q4QtJXoUimsrEywADBQf7BQWrEx9YlNNsUD2yuov8pYCymxfUVTzK
huxGHmNj1htXfWWScA2uqD97HOdFu5nvoL2tdaO1RQR/OXKRBcUg6FhOQqqxQSxi
Vcsoy3aofGi3CWVXgn7KlSopkhlb4ELjzt5H+BMneXdgowO4MimXAfivI7OZl2fN
ut7emyN9qaeY/e25UKmCYhmhE5hM2+lV8wEmmu/qTCPiZ2u0zH/PE9AAwRz/6X+p
gsW0WIQpI6iQSSq4KyJxebtJFmCSTFawuXB6rCGovDXo/BkRsDEj1rpZnkwKJPa0
dEhKK4EzNrUzpWHeE3gKPjFXVmcjIPWVAC3BJoJRHOHg8wqLKcX5MYhGBBgRAgAG
BQJDnfw1AAoJEOF5Cm+ME1dNChoAoMKa/qx/RKlu3iQPtN6p4NlhRA9IAJ94F/7l
cKFQz1DDfFCfVpSIJRGozQ==
=EYzO
-----END PGP PUBLIC KEY BLOCK-----
`;
function versionSpecificTests() {
it('Preferences of generated key', function() {
const testPref = function(key) {
@ -1389,7 +1494,7 @@ function versionSpecificTests() {
const actual_delta = (new Date(expiration) - new Date()) / 1000;
expect(Math.abs(actual_delta - expect_delta)).to.be.below(60);
const subKeyExpiration = await key.subKeys[0].getExpirationTime();
const subKeyExpiration = await key.subKeys[0].getExpirationTime(key.primaryKey);
expect(subKeyExpiration).to.exist;
const actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000;
@ -1688,6 +1793,15 @@ describe('Key', function() {
expect(pubKeys.keys[1].getKeyId().toHex()).to.equal('dbf223e870534df4');
});
it('Parsing armored key with an authorized revocation key', async function() {
const pubKeys = await openpgp.key.readArmored(key_with_authorized_revocation_key);
expect(pubKeys).to.exist;
expect(pubKeys.err).to.exist.and.have.length(1);
expect(pubKeys.err[0].message).to.equal('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.');
expect(pubKeys.keys).to.have.length(1);
expect(pubKeys.keys[0].getKeyId().toHex()).to.equal('5880a2fd178372b9');
});
it('Parsing V5 public key packet', async function() {
// Manually modified from https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b2092/back.mkd#sample-eddsa-key
let packetBytes = openpgp.util.hex_to_Uint8Array(`
@ -1765,6 +1879,26 @@ describe('Key', function() {
)).to.eventually.equal(openpgp.enums.keyStatus.revoked);
});
it('Verify status of key with non-self revocation signature', async function() {
const { keys: [pubKey] } = await openpgp.key.readArmored(key_with_revoked_third_party_cert);
const [selfCertification] = await pubKey.verifyPrimaryUser();
const publicSigningKey = await pubKey.getSigningKey();
expect(selfCertification.keyid.toHex()).to.equal(publicSigningKey.getKeyId().toHex());
expect(selfCertification.valid).to.be.true;
const { keys: [certifyingKey] } = await openpgp.key.readArmored(certifying_key);
const certifyingSigningKey = await certifyingKey.getSigningKey();
const signatures = await pubKey.verifyPrimaryUser([certifyingKey]);
expect(signatures.length).to.equal(2);
expect(signatures[0].keyid.toHex()).to.equal(publicSigningKey.getKeyId().toHex());
expect(signatures[0].valid).to.be.null;
expect(signatures[1].keyid.toHex()).to.equal(certifyingSigningKey.getKeyId().toHex());
expect(signatures[1].valid).to.be.false;
const { user } = await pubKey.getPrimaryUser();
expect(await user.verifyCertificate(pubKey.primaryKey, user.otherCertifications[0], [certifyingKey])).to.equal(openpgp.enums.keyStatus.revoked);
});
it('Evaluate key flags to find valid encryption key packet', async function() {
const pubKeys = await openpgp.key.readArmored(pub_sig_test);
expect(pubKeys).to.exist;
@ -1799,7 +1933,7 @@ describe('Key', function() {
const pubKey = (await openpgp.key.readArmored(twoKeys)).keys[1];
expect(pubKey).to.exist;
expect(pubKey).to.be.an.instanceof(openpgp.key.Key);
const expirationTime = await pubKey.subKeys[0].getExpirationTime();
const expirationTime = await pubKey.subKeys[0].getExpirationTime(pubKey.primaryKey);
expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z');
});
@ -1990,7 +2124,7 @@ describe('Key', function() {
it('getRevocationCertificate() should produce the same revocation certificate as GnuPG', async function() {
const revKey = (await openpgp.key.readArmored(revoked_key_arm4)).keys[0];
const revocationCertificate = revKey.getRevocationCertificate();
const revocationCertificate = await revKey.getRevocationCertificate();
const input = await openpgp.armor.decode(revocation_certificate_arm4);
const packetlist = new openpgp.packet.List();
@ -2002,7 +2136,7 @@ describe('Key', function() {
it('getRevocationCertificate() should have an appropriate comment', async function() {
const revKey = (await openpgp.key.readArmored(revoked_key_arm4)).keys[0];
const revocationCertificate = revKey.getRevocationCertificate();
const revocationCertificate = await revKey.getRevocationCertificate();
expect(revocationCertificate).to.match(/Comment: This is a revocation certificate/);
expect(revKey.armor()).not.to.match(/Comment: This is a revocation certificate/);
@ -2170,7 +2304,27 @@ VYGdb3eNlV8CfoEC
it('Selects the most recent subkey binding signature', async function() {
const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0];
expect(key.subKeys[0].getExpirationTime().toISOString()).to.equal('2015-10-18T07:41:30.000Z');
expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2015-10-18T07:41:30.000Z');
});
it('Selects the most recent non-expired subkey binding signature', async function() {
const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0];
key.subKeys[0].bindingSignatures[1].signatureNeverExpires = false;
key.subKeys[0].bindingSignatures[1].signatureExpirationTime = 0;
expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2018-09-07T06:03:37.000Z');
});
it('Selects the most recent valid subkey binding signature', async function() {
const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0];
key.subKeys[0].bindingSignatures[1].signatureData[0]++;
expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2018-09-07T06:03:37.000Z');
});
it('Handles a key with no valid subkey binding signatures gracefully', async function() {
const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0];
key.subKeys[0].bindingSignatures[0].signatureData[0]++;
key.subKeys[0].bindingSignatures[1].signatureData[0]++;
expect(await key.subKeys[0].getExpirationTime(key.primaryKey)).to.be.null;
});
it('Reject encryption with revoked subkey', async function() {

View File

@ -1,47 +0,0 @@
// addEventListener('fetch', event => {
// console.log(event);
// const url = new URL(event.request.url);
// console.log(url);
// if (url.pathname === '/test/somedata') {
// let plaintext = [];
// let i = 0;
// let canceled = false;
// const data = new ReadableStream({
// /*start(_controller) {
// controller = _controller;
// },*/
// async pull(controller) {
// await new Promise(resolve => setTimeout(resolve, 1000));
// console.log(i);
// if (i++ < 10) {
// let randomBytes = new Uint8Array(1000);
// randomBytes.fill(i);
// controller.enqueue(randomBytes);
// plaintext.push(randomBytes);
// } else {
// controller.close();
// }
// },
// cancel() {
// console.log('canceled!');
// }
// });
// const response = new Response(data, {
// headers: {
// 'Content-Type': 'application/octet-stream; charset=utf-8',
// 'Content-Disposition': 'Content-Disposition: attachment; filename=data.bin;'
// }
// });
// event.respondWith(response);
// }
// });