Fix various signature verification issues (#1302)

- Throw on signature parsing (e.g. in `openpgp.readSignature`) if the
  creation time subpacket is missing
- `SignaturePacket.verify` now directly checks for signature creation
  and expiration times. This makes it easier to thoroughly check the
  validity of signatures. Also:
  - `openpgp.revokeKey` now takes a `date` to check the provided
    revocation certificate
  - `openpgp.decryptSessionKeys` now takes a `date` to check the
    validity of the provided private keys
  - whenever a `date` is used internally, the function accepts a
    `date` param to allow passing the correct date
- Add tests for all of the above
- Like `openpgp.generateKey`, `openpgp.reformatKey` now also requires
  `options.userIDs`
- Simplify calling `SubKey.isRevoked/update/getExpirationTime` by
  adding the `SubKey.mainKey` field to hold the reference of the
  corresponding `Key`

Breaking changes in low-level functions:
- Added/removed `date` params:
  - `Key.update(key, config)` -> `update(key, date, config)`
  - `Key.applyRevocationCertificate(revocationCertificate, config)` ->
    `applyRevocationCertificate(revocationCertificate, date, config)`
  - `Key.signAllUsers(privateKeys, config)` ->
    `signAllUsers(privateKeys, date, config)`
  - `Key.verifyAllUsers(keys, config)` ->
    `verifyAllUsers(keys, date, config)`
  - `new SignaturePacket(date)` -> `new SignaturePacket()`
  - `SignaturePacket.sign(key, data, detached)` ->
    `sign(key, data, date, detached)`
  - `Message.sign(primaryKey, privateKeys, config)` ->
    `sign(primaryKey, privateKeys, date, config)`
  - `Message.decrypt(privateKeys, passwords, sessionKeys, config)` ->
    `decrypt(privateKeys, passwords, sessionKeys, date, config)`
  - `Message.decryptSessionKeys(privateKeys, passwords, config)` ->
    `decryptSessionKeys(privateKeys, passwords, date, config)`
- Removed `primaryKey` params:
  - `SubKey.isRevoked(primaryKey, signature, key, date, config)` ->
    `isRevoked(signature, key, date, config)`
  - `SubKey.update(subKey, primaryKey, date, config)` ->
    `update(subKey, date, config)`
  - `SubKey.getExpirationTime(primaryKey, date, config)` ->
    `getExpirationTime(date, config)`
This commit is contained in:
larabr 2021-06-08 18:12:48 +02:00 committed by GitHub
parent ab7dedf0a5
commit 0e088aec28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 828 additions and 775 deletions

22
openpgp.d.ts vendored
View File

@ -24,7 +24,6 @@ export function encryptKey(options: { privateKey: PrivateKey; passphrase?: strin
export function reformatKey(options: { privateKey: PrivateKey; userIDs?: UserID|UserID[]; passphrase?: string; keyExpirationTime?: number; config?: PartialConfig }): Promise<KeyPair>; export function reformatKey(options: { privateKey: PrivateKey; userIDs?: UserID|UserID[]; passphrase?: string; keyExpirationTime?: number; config?: PartialConfig }): Promise<KeyPair>;
export abstract class Key { export abstract class Key {
private primaryKey: PublicKeyPacket | SecretKeyPacket;
private keyPacket: PublicKeyPacket | SecretKeyPacket; private keyPacket: PublicKeyPacket | SecretKeyPacket;
public subKeys: SubKey[]; public subKeys: SubKey[];
public users: User[]; public users: User[];
@ -38,12 +37,12 @@ export abstract class Key {
public isPrivate(): boolean; public isPrivate(): boolean;
public isPublic(): boolean; public isPublic(): boolean;
public toPublic(): PublicKey; public toPublic(): PublicKey;
public update(sourceKey: PublicKey, config?: Config): Promise<PublicKey>; public update(sourceKey: PublicKey, date?: Date, config?: Config): Promise<PublicKey>;
public signPrimaryUser(privateKeys: PrivateKey[], date?: Date, userID?: UserID, config?: Config): Promise<PublicKey> public signPrimaryUser(privateKeys: PrivateKey[], date?: Date, userID?: UserID, config?: Config): Promise<PublicKey>
public signAllUsers(privateKeys: PrivateKey[], config?: Config): Promise<PublicKey> public signAllUsers(privateKeys: PrivateKey[], date?: Date, config?: Config): Promise<PublicKey>
public verifyPrimaryKey(date?: Date, userID?: UserID, config?: Config): Promise<void>; // throws on error public verifyPrimaryKey(date?: Date, userID?: UserID, config?: Config): Promise<void>; // throws on error
public verifyPrimaryUser(publicKeys: PublicKey[], date?: Date, userIDs?: UserID, config?: Config): Promise<{ keyID: KeyID, valid: boolean | null }[]>; public verifyPrimaryUser(publicKeys: PublicKey[], date?: Date, userIDs?: UserID, config?: Config): Promise<{ keyID: KeyID, valid: boolean | null }[]>;
public verifyAllUsers(publicKeys: PublicKey[], config?: Config): Promise<{ userID: string, keyID: KeyID, valid: boolean | null }[]>; public verifyAllUsers(publicKeys: PublicKey[], date?: Date, config?: Config): Promise<{ userID: string, keyID: KeyID, valid: boolean | null }[]>;
public isRevoked(signature: SignaturePacket, key?: AnyKeyPacket, date?: Date, config?: Config): Promise<boolean>; public isRevoked(signature: SignaturePacket, key?: AnyKeyPacket, date?: Date, config?: Config): Promise<boolean>;
public getRevocationCertificate(date?: Date, config?: Config): Promise<Stream<string> | string | undefined>; public getRevocationCertificate(date?: Date, config?: Config): Promise<Stream<string> | string | undefined>;
public getEncryptionKey(keyID?: KeyID, date?: Date | null, userID?: UserID, config?: Config): Promise<PublicKey | SubKey>; public getEncryptionKey(keyID?: KeyID, date?: Date | null, userID?: UserID, config?: Config): Promise<PublicKey | SubKey>;
@ -68,16 +67,17 @@ export class PrivateKey extends PublicKey {
public isDecrypted(): boolean; public isDecrypted(): boolean;
public addSubkey(options: SubKeyOptions): Promise<PrivateKey>; public addSubkey(options: SubKeyOptions): Promise<PrivateKey>;
public getDecryptionKeys(keyID?: KeyID, date?: Date | null, userID?: UserID, config?: Config): Promise<PrivateKey | SubKey> public getDecryptionKeys(keyID?: KeyID, date?: Date | null, userID?: UserID, config?: Config): Promise<PrivateKey | SubKey>
public update(sourceKey: PublicKey, config?: Config): Promise<PrivateKey>; public update(sourceKey: PublicKey, date?: Date, config?: Config): Promise<PrivateKey>;
public getKeys(keyID?: KeyID): (PrivateKey | SubKey)[]; public getKeys(keyID?: KeyID): (PrivateKey | SubKey)[];
} }
export class SubKey { export class SubKey {
constructor(subKeyPacket: SecretSubkeyPacket | PublicSubkeyPacket); constructor(subKeyPacket: SecretSubkeyPacket | PublicSubkeyPacket, mainKey: PublicKey);
private keyPacket: SecretSubkeyPacket | PublicSubkeyPacket; private keyPacket: SecretSubkeyPacket | PublicSubkeyPacket;
private mainKey: PublicKey;
public bindingSignatures: SignaturePacket[]; public bindingSignatures: SignaturePacket[];
public revocationSignatures: SignaturePacket[]; public revocationSignatures: SignaturePacket[];
public verify(primaryKey: PublicKeyPacket | SecretKeyPacket, date?: Date, config?: Config): Promise<SignaturePacket>; public verify(date?: Date, config?: Config): Promise<SignaturePacket>;
public isDecrypted(): boolean; public isDecrypted(): boolean;
public getFingerprint(): string; public getFingerprint(): string;
public getCreationTime(): Date; public getCreationTime(): Date;
@ -230,7 +230,7 @@ export class Message<T extends MaybeStream<Data>> {
/** Decrypt the message /** Decrypt the message
@param decryptionKeys array of private keys with decrypted secret data @param decryptionKeys array of private keys with decrypted secret data
*/ */
public decrypt(decryptionKeys?: PrivateKey[], passwords?: string[], sessionKeys?: SessionKey[], config?: Config): Promise<Message<MaybeStream<Data>>>; public decrypt(decryptionKeys?: PrivateKey[], passwords?: string[], sessionKeys?: SessionKey[], date?: Date, config?: Config): Promise<Message<MaybeStream<Data>>>;
/** Encrypt the message /** Encrypt the message
@param encryptionKeys array of public keys, used to encrypt the message @param encryptionKeys array of public keys, used to encrypt the message
@ -444,7 +444,7 @@ export class SignaturePacket extends BasePacket {
public signatureData: null | Uint8Array; public signatureData: null | Uint8Array;
public unhashedSubpackets: null | Uint8Array; public unhashedSubpackets: null | Uint8Array;
public signedHashValue: null | Uint8Array; public signedHashValue: null | Uint8Array;
public created: Date; public created: Date | null;
public signatureExpirationTime: null | number; public signatureExpirationTime: null | number;
public signatureNeverExpires: boolean; public signatureNeverExpires: boolean;
public exportable: null | boolean; public exportable: null | boolean;
@ -480,8 +480,8 @@ export class SignaturePacket extends BasePacket {
public preferredAEADAlgorithms: enums.aead[] | null; public preferredAEADAlgorithms: enums.aead[] | null;
public verified: null | boolean; public verified: null | boolean;
public revoked: null | boolean; public revoked: null | boolean;
public sign(key: AnySecretKeyPacket, data: Uint8Array, detached?: boolean): Promise<void>; public sign(key: AnySecretKeyPacket, data: Uint8Array, date?: Date, detached?: boolean): Promise<void>;
public verify(key: AnyKeyPacket, signatureType: enums.signature, data: Uint8Array, detached?: boolean, config?: Config): Promise<void>; // throws on error public verify(key: AnyKeyPacket, signatureType: enums.signature, data: Uint8Array, date?: Date, detached?: boolean, config?: Config): Promise<void>; // throws on error
public isExpired(date?: Date): boolean; public isExpired(date?: Date): boolean;
public getExpirationTime(): Date | typeof Infinity; public getExpirationTime(): Date | typeof Infinity;
} }

View File

@ -94,7 +94,7 @@ export async function reformat(options, config) {
throw new Error('Cannot reformat a public key'); throw new Error('Cannot reformat a public key');
} }
if (privateKey.primaryKey.isDummy()) { if (privateKey.keyPacket.isDummy()) {
throw new Error('Cannot reformat a gnu-dummy primary key'); throw new Error('Cannot reformat a gnu-dummy primary key');
} }
@ -162,7 +162,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
const dataToSign = {}; const dataToSign = {};
dataToSign.userID = userIDPacket; dataToSign.userID = userIDPacket;
dataToSign.key = secretKeyPacket; dataToSign.key = secretKeyPacket;
const signaturePacket = new SignaturePacket(options.date); const signaturePacket = new SignaturePacket();
signaturePacket.signatureType = enums.signature.certGeneric; signaturePacket.signatureType = enums.signature.certGeneric;
signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm; signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await helper.getPreferredHashAlgo(null, secretKeyPacket, undefined, undefined, config); signaturePacket.hashAlgorithm = await helper.getPreferredHashAlgo(null, secretKeyPacket, undefined, undefined, config);
@ -205,7 +205,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
signaturePacket.keyExpirationTime = options.keyExpirationTime; signaturePacket.keyExpirationTime = options.keyExpirationTime;
signaturePacket.keyNeverExpires = false; signaturePacket.keyNeverExpires = false;
} }
await signaturePacket.sign(secretKeyPacket, dataToSign); await signaturePacket.sign(secretKeyPacket, dataToSign, options.date);
return { userIDPacket, signaturePacket }; return { userIDPacket, signaturePacket };
})).then(list => { })).then(list => {

View File

@ -37,45 +37,42 @@ export async function generateSecretKey(options, config) {
/** /**
* Returns the valid and non-expired 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<SignaturePacket>} signatures - List of signatures * @param {Array<SignaturePacket>} signatures - List of signatures
* @param {PublicKeyPacket|PublicSubkeyPacket} publicKey - Public key packet to verify the signature
* @param {Date} date - Use the given date instead of the current time * @param {Date} date - Use the given date instead of the current time
* @param {Object} config - full configuration * @param {Object} config - full configuration
* @returns {Promise<SignaturePacket>} The latest valid signature. * @returns {Promise<SignaturePacket>} The latest valid signature.
* @async * @async
*/ */
export async function getLatestValidSignature(signatures, primaryKey, signatureType, dataToVerify, date = new Date(), config) { export async function getLatestValidSignature(signatures, publicKey, signatureType, dataToVerify, date = new Date(), config) {
let signature; let latestValid;
let exception; let exception;
for (let i = signatures.length - 1; i >= 0; i--) { for (let i = signatures.length - 1; i >= 0; i--) {
try { try {
if ( if (
(!signature || signatures[i].created >= signature.created) && (!latestValid || signatures[i].created >= latestValid.created)
// check binding signature is not expired (ie, check for V4 expiration time)
!signatures[i].isExpired(date)
) { ) {
// check binding signature is verified await signatures[i].verify(publicKey, signatureType, dataToVerify, date, undefined, config);
signatures[i].verified || await signatures[i].verify(primaryKey, signatureType, dataToVerify, undefined, config); latestValid = signatures[i];
signature = signatures[i];
} }
} catch (e) { } catch (e) {
exception = e; exception = e;
} }
} }
if (!signature) { if (!latestValid) {
throw util.wrapError( throw util.wrapError(
`Could not find valid ${enums.read(enums.signature, signatureType)} signature in key ${primaryKey.getKeyID().toHex()}` `Could not find valid ${enums.read(enums.signature, signatureType)} signature in key ${publicKey.getKeyID().toHex()}`
.replace('certGeneric ', 'self-') .replace('certGeneric ', 'self-')
.replace(/([a-z])([A-Z])/g, (_, $1, $2) => $1 + ' ' + $2.toLowerCase()) .replace(/([a-z])([A-Z])/g, (_, $1, $2) => $1 + ' ' + $2.toLowerCase())
, exception); , exception);
} }
return signature; return latestValid;
} }
export function isDataExpired(keyPacket, signature, date = new Date()) { export function isDataExpired(keyPacket, signature, date = new Date()) {
const normDate = util.normalizeDate(date); const normDate = util.normalizeDate(date);
if (normDate !== null) { if (normDate !== null) {
const expirationTime = getExpirationTime(keyPacket, signature); const expirationTime = getKeyExpirationTime(keyPacket, signature);
return !(keyPacket.created <= normDate && normDate <= expirationTime) || return !(keyPacket.created <= normDate && normDate <= expirationTime);
(signature && signature.isExpired(date));
} }
return false; return false;
} }
@ -91,7 +88,7 @@ export async function createBindingSignature(subkey, primaryKey, options, config
const dataToSign = {}; const dataToSign = {};
dataToSign.key = primaryKey; dataToSign.key = primaryKey;
dataToSign.bind = subkey; dataToSign.bind = subkey;
const subkeySignaturePacket = new SignaturePacket(options.date); const subkeySignaturePacket = new SignaturePacket();
subkeySignaturePacket.signatureType = enums.signature.subkeyBinding; subkeySignaturePacket.signatureType = enums.signature.subkeyBinding;
subkeySignaturePacket.publicKeyAlgorithm = primaryKey.algorithm; subkeySignaturePacket.publicKeyAlgorithm = primaryKey.algorithm;
subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, subkey, undefined, undefined, config); subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, subkey, undefined, undefined, config);
@ -107,7 +104,7 @@ export async function createBindingSignature(subkey, primaryKey, options, config
subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime; subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime;
subkeySignaturePacket.keyNeverExpires = false; subkeySignaturePacket.keyNeverExpires = false;
} }
await subkeySignaturePacket.sign(primaryKey, dataToSign); await subkeySignaturePacket.sign(primaryKey, dataToSign, options.date);
return subkeySignaturePacket; return subkeySignaturePacket;
} }
@ -189,6 +186,7 @@ export async function getPreferredAlgo(type, keys = [], date = new Date(), userI
/** /**
* Create signature packet * Create signature packet
* @param {Object} dataToSign - Contains packets to be signed * @param {Object} dataToSign - Contains packets to be signed
* @param {PrivateKey} privateKey - key to get preferences from
* @param {SecretKeyPacket| * @param {SecretKeyPacket|
* SecretSubkeyPacket} signingKeyPacket secret key packet for signing * SecretSubkeyPacket} signingKeyPacket secret key packet for signing
* @param {Object} [signatureProperties] - Properties to write on the signature packet before signing * @param {Object} [signatureProperties] - Properties to write on the signature packet before signing
@ -205,11 +203,11 @@ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPa
if (!signingKeyPacket.isDecrypted()) { if (!signingKeyPacket.isDecrypted()) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
} }
const signaturePacket = new SignaturePacket(date); const signaturePacket = new SignaturePacket();
Object.assign(signaturePacket, signatureProperties); Object.assign(signaturePacket, signatureProperties);
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userID, config); signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userID, config);
await signaturePacket.sign(signingKeyPacket, dataToSign, detached); await signaturePacket.sign(signingKeyPacket, dataToSign, date, detached);
return signaturePacket; return signaturePacket;
} }
@ -218,16 +216,17 @@ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPa
* @param {Object} source * @param {Object} source
* @param {Object} dest * @param {Object} dest
* @param {String} attr * @param {String} attr
* @param {Function} checkFn - optional, signature only merged if true * @param {Date} [date] - date to use for signature expiration check, instead of the current time
* @param {(SignaturePacket) => Boolean} [checkFn] - signature only merged if true
*/ */
export async function mergeSignatures(source, dest, attr, checkFn) { export async function mergeSignatures(source, dest, attr, date = new Date(), checkFn) {
source = source[attr]; source = source[attr];
if (source) { if (source) {
if (!dest[attr].length) { if (!dest[attr].length) {
dest[attr] = source; dest[attr] = source;
} else { } else {
await Promise.all(source.map(async function(sourceSig) { await Promise.all(source.map(async function(sourceSig) {
if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) && if (!sourceSig.isExpired(date) && (!checkFn || await checkFn(sourceSig)) &&
!dest[attr].some(function(destSig) { !dest[attr].some(function(destSig) {
return util.equalsUint8Array(destSig.writeParams(), sourceSig.writeParams()); return util.equalsUint8Array(destSig.writeParams(), sourceSig.writeParams());
})) { })) {
@ -256,7 +255,6 @@ export async function mergeSignatures(source, dest, attr, checkFn) {
*/ */
export async function isDataRevoked(primaryKey, signatureType, dataToVerify, revocations, signature, key, date = new Date(), config) { export async function isDataRevoked(primaryKey, signatureType, dataToVerify, revocations, signature, key, date = new Date(), config) {
key = key || primaryKey; key = key || primaryKey;
const normDate = util.normalizeDate(date);
const revocationKeyIDs = []; const revocationKeyIDs = [];
await Promise.all(revocations.map(async function(revocationSignature) { await Promise.all(revocations.map(async function(revocationSignature) {
try { try {
@ -269,10 +267,11 @@ export async function isDataRevoked(primaryKey, signatureType, dataToVerify, rev
// third-party revocation signatures here. (It could also be revoking a // third-party revocation signatures here. (It could also be revoking a
// third-party key certification, which should only affect // third-party key certification, which should only affect
// `verifyAllCertifications`.) // `verifyAllCertifications`.)
(!signature || revocationSignature.issuerKeyID.equals(signature.issuerKeyID)) && !signature || revocationSignature.issuerKeyID.equals(signature.issuerKeyID)
!(config.revocationsExpire && revocationSignature.isExpired(normDate))
) { ) {
revocationSignature.verified || await revocationSignature.verify(key, signatureType, dataToVerify, undefined, config); await revocationSignature.verify(
key, signatureType, dataToVerify, config.revocationsExpire ? date : null, false, config
);
// TODO get an identifier of the revoked object instead // TODO get an identifier of the revoked object instead
revocationKeyIDs.push(revocationSignature.issuerKeyID); revocationKeyIDs.push(revocationSignature.issuerKeyID);
@ -288,7 +287,14 @@ export async function isDataRevoked(primaryKey, signatureType, dataToVerify, rev
return revocationKeyIDs.length > 0; return revocationKeyIDs.length > 0;
} }
export function getExpirationTime(keyPacket, signature) { /**
* Returns key expiration time based on the given certification signature.
* The expiration time of the signature is ignored.
* @param {PublicSubkeyPacket|PublicKeyPacket} keyPacket - key to check
* @param {SignaturePacket} signature - signature to process
* @returns {Date|Infinity} expiration time or infinity if the key does not expire
*/
export function getKeyExpirationTime(keyPacket, signature) {
let expirationTime; let expirationTime;
// check V4 expiration time // check V4 expiration time
if (signature.keyNeverExpires === false) { if (signature.keyNeverExpires === false) {
@ -355,10 +361,6 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
} }
export function isValidSigningKeyPacket(keyPacket, signature) { export function isValidSigningKeyPacket(keyPacket, signature) {
if (!signature.verified || signature.revoked !== false) { // Sanity check
throw new Error('Signature not verified');
}
const keyAlgo = enums.write(enums.publicKey, keyPacket.algorithm); const keyAlgo = enums.write(enums.publicKey, keyPacket.algorithm);
return keyAlgo !== enums.publicKey.rsaEncrypt && return keyAlgo !== enums.publicKey.rsaEncrypt &&
keyAlgo !== enums.publicKey.elgamal && keyAlgo !== enums.publicKey.elgamal &&
@ -368,10 +370,6 @@ export function isValidSigningKeyPacket(keyPacket, signature) {
} }
export function isValidEncryptionKeyPacket(keyPacket, signature) { export function isValidEncryptionKeyPacket(keyPacket, signature) {
if (!signature.verified || signature.revoked !== false) { // Sanity check
throw new Error('Signature not verified');
}
const keyAlgo = enums.write(enums.publicKey, keyPacket.algorithm); const keyAlgo = enums.write(enums.publicKey, keyPacket.algorithm);
return keyAlgo !== enums.publicKey.dsa && return keyAlgo !== enums.publicKey.dsa &&
keyAlgo !== enums.publicKey.rsaSign && keyAlgo !== enums.publicKey.rsaSign &&
@ -383,10 +381,6 @@ export function isValidEncryptionKeyPacket(keyPacket, signature) {
} }
export function isValidDecryptionKeyPacket(signature, config) { export function isValidDecryptionKeyPacket(signature, config) {
if (!signature.verified) { // Sanity check
throw new Error('Signature not verified');
}
if (config.allowInsecureDecryptionWithSigningKeys) { if (config.allowInsecureDecryptionWithSigningKeys) {
// This is only relevant for RSA keys, all other signing algorithms cannot decrypt // This is only relevant for RSA keys, all other signing algorithms cannot decrypt
return true; return true;

View File

@ -42,10 +42,6 @@ const allowedRevocationPackets = /*#__PURE__*/ util.constructAllowedPackets([Sig
* @borrows PublicKeyPacket#getCreationTime as Key#getCreationTime * @borrows PublicKeyPacket#getCreationTime as Key#getCreationTime
*/ */
class Key { class Key {
get primaryKey() {
return this.keyPacket;
}
/** /**
* Transforms packetlist to structured key data * Transforms packetlist to structured key data
* @param {PacketList} packetlist - The packets that form a key * @param {PacketList} packetlist - The packets that form a key
@ -80,7 +76,7 @@ class Key {
case enums.packet.publicSubkey: case enums.packet.publicSubkey:
case enums.packet.secretSubkey: case enums.packet.secretSubkey:
user = null; user = null;
subKey = new SubKey(packet); subKey = new SubKey(packet, this);
this.subKeys.push(subKey); this.subKeys.push(subKey);
break; break;
case enums.packet.signature: case enums.packet.signature:
@ -227,10 +223,10 @@ class Key {
/** /**
* Returns last created key or key by given keyID that is available for signing and verification * Returns last created key or key by given keyID that is available for signing and verification
* @param {module:type/keyid~KeyID} keyID, optional * @param {module:type/keyid~KeyID} [keyID] - key ID of a specific key to retrieve
* @param {Date} [date] - Use the given date for verification instead of the current time * @param {Date} [date] - use the fiven date date to to check key validity instead of the current date
* @param {Object} userID, optional user ID * @param {Object} [userID] - filter keys for the given user ID
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Key|SubKey>} signing key * @returns {Promise<Key|SubKey>} signing key
* @throws if no valid signing key was found * @throws if no valid signing key was found
* @async * @async
@ -243,7 +239,7 @@ class Key {
for (const subKey of subKeys) { for (const subKey of subKeys) {
if (!keyID || subKey.getKeyID().equals(keyID)) { if (!keyID || subKey.getKeyID().equals(keyID)) {
try { try {
await subKey.verify(primaryKey, date, config); await subKey.verify(date, config);
const dataToVerify = { key: primaryKey, bind: subKey.keyPacket }; const dataToVerify = { key: primaryKey, bind: subKey.keyPacket };
const bindingSignature = await helper.getLatestValidSignature( const bindingSignature = await helper.getLatestValidSignature(
subKey.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config subKey.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config
@ -281,10 +277,10 @@ class Key {
/** /**
* Returns last created key or key by given keyID that is available for encryption or decryption * Returns last created key or key by given keyID that is available for encryption or decryption
* @param {module:type/keyid~KeyID} keyID, optional * @param {module:type/keyid~KeyID} [keyID] - key ID of a specific key to retrieve
* @param {Date} date, optional * @param {Date} [date] - use the fiven date date to to check key validity instead of the current date
* @param {String} userID, optional * @param {Object} [userID] - filter keys for the given user ID
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Key|SubKey>} encryption key * @returns {Promise<Key|SubKey>} encryption key
* @throws if no valid encryption key was found * @throws if no valid encryption key was found
* @async * @async
@ -298,7 +294,7 @@ class Key {
for (const subKey of subKeys) { for (const subKey of subKeys) {
if (!keyID || subKey.getKeyID().equals(keyID)) { if (!keyID || subKey.getKeyID().equals(keyID)) {
try { try {
await subKey.verify(primaryKey, date, config); await subKey.verify(date, config);
const dataToVerify = { key: primaryKey, bind: subKey.keyPacket }; const dataToVerify = { key: primaryKey, bind: subKey.keyPacket };
const bindingSignature = await helper.getLatestValidSignature(subKey.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config); const bindingSignature = await helper.getLatestValidSignature(subKey.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config);
if (helper.isValidEncryptionKeyPacket(subKey.keyPacket, bindingSignature)) { if (helper.isValidEncryptionKeyPacket(subKey.keyPacket, bindingSignature)) {
@ -332,7 +328,7 @@ class Key {
* SecretSubkeyPacket| * SecretSubkeyPacket|
* PublicKeyPacket| * PublicKeyPacket|
* SecretKeyPacket} key, optional The key to verify the signature * SecretKeyPacket} key, optional The key to verify the signature
* @param {Date} date - Use the given date instead of the current time * @param {Date} [date] - Use the given date for verification, instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Boolean>} True if the certificate is revoked. * @returns {Promise<Boolean>} True if the certificate is revoked.
* @async * @async
@ -360,7 +356,7 @@ class Key {
} }
// check for valid, unrevoked, unexpired self signature // check for valid, unrevoked, unexpired self signature
const { selfCertification } = await this.getPrimaryUser(date, userID, config); const { selfCertification } = await this.getPrimaryUser(date, userID, config);
// check for expiration time // check for expiration time in binding signatures
if (helper.isDataExpired(primaryKey, selfCertification, date)) { if (helper.isDataExpired(primaryKey, selfCertification, date)) {
throw new Error('Primary key is expired'); throw new Error('Primary key is expired');
} }
@ -371,17 +367,17 @@ class Key {
* When `capabilities` is null, defaults to returning the expiry date of the primary key. * 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 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. * Returns Infinity if the key doesn't expire.
* @param {encrypt|sign|encrypt_sign} capabilities, optional * @param {encrypt|sign|encrypt_sign} [capabilities] - capabilities to look up
* @param {module:type/keyid~KeyID} keyID, optional * @param {module:type/keyid~KeyID} [keyID] - key ID of the specific key to check
* @param {Object} userID, optional user ID * @param {Object} [userID] - User ID to consider instead of the primary user
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Date | Infinity | null>} * @returns {Promise<Date | Infinity | null>}
* @async * @async
*/ */
async getExpirationTime(capabilities, keyID, userID, config = defaultConfig) { async getExpirationTime(capabilities, keyID, userID, config = defaultConfig) {
const primaryUser = await this.getPrimaryUser(null, userID, config); const primaryUser = await this.getPrimaryUser(null, userID, config);
const selfCert = primaryUser.selfCertification; const selfCert = primaryUser.selfCertification;
const keyExpiry = helper.getExpirationTime(this.keyPacket, selfCert); const keyExpiry = helper.getKeyExpirationTime(this.keyPacket, selfCert);
const sigExpiry = selfCert.getExpirationTime(); const sigExpiry = selfCert.getExpirationTime();
let expiry = keyExpiry < sigExpiry ? keyExpiry : sigExpiry; let expiry = keyExpiry < sigExpiry ? keyExpiry : sigExpiry;
if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') { if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') {
@ -389,7 +385,7 @@ class Key {
await this.getEncryptionKey(keyID, expiry, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {}) || await this.getEncryptionKey(keyID, expiry, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {}) ||
await this.getEncryptionKey(keyID, null, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {}); await this.getEncryptionKey(keyID, null, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {});
if (!encryptKey) return null; if (!encryptKey) return null;
const encryptExpiry = await encryptKey.getExpirationTime(this.keyPacket, undefined, config); const encryptExpiry = await encryptKey.getExpirationTime(null, config);
if (encryptExpiry < expiry) expiry = encryptExpiry; if (encryptExpiry < expiry) expiry = encryptExpiry;
} }
if (capabilities === 'sign' || capabilities === 'encrypt_sign') { if (capabilities === 'sign' || capabilities === 'encrypt_sign') {
@ -397,7 +393,7 @@ class Key {
await this.getSigningKey(keyID, expiry, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {}) || await this.getSigningKey(keyID, expiry, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {}) ||
await this.getSigningKey(keyID, null, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {}); await this.getSigningKey(keyID, null, userID, { ...config, rejectPublicKeyAlgorithms: new Set(), minRSABits: 0 }).catch(() => {});
if (!signKey) return null; if (!signKey) return null;
const signExpiry = await signKey.getExpirationTime(this.keyPacket, undefined, config); const signExpiry = await signKey.getExpirationTime(null, config);
if (signExpiry < expiry) expiry = signExpiry; if (signExpiry < expiry) expiry = signExpiry;
} }
return expiry; return expiry;
@ -467,11 +463,12 @@ class Key {
* If the source key is a private key and the destination key is public, * If the source key is a private key and the destination key is public,
* a private key is returned. * a private key is returned.
* @param {Key} sourceKey - Source key to merge * @param {Key} sourceKey - Source key to merge
* @param {Date} [date] - Date to verify validity of signatures and keys
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Key>} updated key * @returns {Promise<Key>} updated key
* @async * @async
*/ */
async update(sourceKey, config = defaultConfig) { async update(sourceKey, date = new Date(), config = defaultConfig) {
if (!this.hasSameFingerprintAs(sourceKey)) { if (!this.hasSameFingerprintAs(sourceKey)) {
throw new Error('Primary key fingerprints must be equal to update the key'); throw new Error('Primary key fingerprints must be equal to update the key');
} }
@ -495,11 +492,11 @@ class Key {
// hence we don't need to convert the destination key type // hence we don't need to convert the destination key type
const updatedKey = this.clone(); const updatedKey = this.clone();
// revocation signatures // revocation signatures
await helper.mergeSignatures(sourceKey, updatedKey, 'revocationSignatures', srcRevSig => { await helper.mergeSignatures(sourceKey, updatedKey, 'revocationSignatures', date, srcRevSig => {
return helper.isDataRevoked(updatedKey.keyPacket, enums.signature.keyRevocation, updatedKey, [srcRevSig], null, sourceKey.keyPacket, undefined, config); return helper.isDataRevoked(updatedKey.keyPacket, enums.signature.keyRevocation, updatedKey, [srcRevSig], null, sourceKey.keyPacket, date, config);
}); });
// direct signatures // direct signatures
await helper.mergeSignatures(sourceKey, updatedKey, 'directSignatures'); await helper.mergeSignatures(sourceKey, updatedKey, 'directSignatures', date);
// update users // update users
await Promise.all(sourceKey.users.map(async srcUser => { await Promise.all(sourceKey.users.map(async srcUser => {
// multiple users with the same ID/attribute are not explicitly disallowed by the spec // multiple users with the same ID/attribute are not explicitly disallowed by the spec
@ -510,7 +507,7 @@ class Key {
)); ));
if (usersToUpdate.length > 0) { if (usersToUpdate.length > 0) {
await Promise.all( await Promise.all(
usersToUpdate.map(userToUpdate => userToUpdate.update(srcUser, updatedKey.keyPacket, config)) usersToUpdate.map(userToUpdate => userToUpdate.update(srcUser, updatedKey.keyPacket, date, config))
); );
} else { } else {
updatedKey.users.push(srcUser); updatedKey.users.push(srcUser);
@ -524,7 +521,7 @@ class Key {
)); ));
if (subkeysToUpdate.length > 0) { if (subkeysToUpdate.length > 0) {
await Promise.all( await Promise.all(
subkeysToUpdate.map(subkeyToUpdate => subkeyToUpdate.update(srcSubkey, updatedKey.keyPacket, config)) subkeysToUpdate.map(subkeyToUpdate => subkeyToUpdate.update(srcSubkey, date, config))
); );
} else { } else {
updatedKey.subKeys.push(srcSubkey); updatedKey.subKeys.push(srcSubkey);
@ -555,11 +552,12 @@ class Key {
* This adds the first signature packet in the armored text to the key, * This adds the first signature packet in the armored text to the key,
* if it is a valid revocation signature. * if it is a valid revocation signature.
* @param {String} revocationCertificate - armored revocation certificate * @param {String} revocationCertificate - armored revocation certificate
* @param {Date} [date] - Date to verify the certificate
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Key>} Revoked key. * @returns {Promise<Key>} Revoked key.
* @async * @async
*/ */
async applyRevocationCertificate(revocationCertificate, config = defaultConfig) { async applyRevocationCertificate(revocationCertificate, date = new Date(), config = defaultConfig) {
const input = await unarmor(revocationCertificate, config); const input = await unarmor(revocationCertificate, config);
const packetlist = await PacketList.fromBinary(input.data, allowedRevocationPackets, config); const packetlist = await PacketList.fromBinary(input.data, allowedRevocationPackets, config);
const revocationSignature = packetlist.findPacket(enums.packet.signature); const revocationSignature = packetlist.findPacket(enums.packet.signature);
@ -569,11 +567,8 @@ class Key {
if (!revocationSignature.issuerKeyID.equals(this.getKeyID())) { if (!revocationSignature.issuerKeyID.equals(this.getKeyID())) {
throw new Error('Revocation signature does not match key'); throw new Error('Revocation signature does not match key');
} }
if (revocationSignature.isExpired()) {
throw new Error('Revocation signature is expired');
}
try { try {
await revocationSignature.verify(this.keyPacket, enums.signature.keyRevocation, { key: this.keyPacket }, undefined, config); await revocationSignature.verify(this.keyPacket, enums.signature.keyRevocation, { key: this.keyPacket }, date, undefined, config);
} catch (e) { } catch (e) {
throw util.wrapError('Could not verify revocation signature', e); throw util.wrapError('Could not verify revocation signature', e);
} }
@ -593,7 +588,7 @@ class Key {
*/ */
async signPrimaryUser(privateKeys, date, userID, config = defaultConfig) { async signPrimaryUser(privateKeys, date, userID, config = defaultConfig) {
const { index, user } = await this.getPrimaryUser(date, userID, config); const { index, user } = await this.getPrimaryUser(date, userID, config);
const userSign = await user.sign(this.keyPacket, privateKeys, config); const userSign = await user.sign(this.keyPacket, privateKeys, date, config);
const key = this.clone(); const key = this.clone();
key.users[index] = userSign; key.users[index] = userSign;
return key; return key;
@ -602,15 +597,16 @@ class Key {
/** /**
* Signs all users of key * Signs all users of key
* @param {Array<PrivateKey>} privateKeys - decrypted private keys for signing * @param {Array<PrivateKey>} privateKeys - decrypted private keys for signing
* @param {Date} [date] - Use the given date for signing, instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Key>} Key with new certificate signature. * @returns {Promise<Key>} Key with new certificate signature.
* @async * @async
*/ */
async signAllUsers(privateKeys, config = defaultConfig) { async signAllUsers(privateKeys, date = new Date(), config = defaultConfig) {
const that = this; const that = this;
const key = this.clone(); const key = this.clone();
key.users = await Promise.all(this.users.map(function(user) { key.users = await Promise.all(this.users.map(function(user) {
return user.sign(that.keyPacket, privateKeys, config); return user.sign(that.keyPacket, privateKeys, date, config);
})); }));
return key; return key;
} }
@ -629,11 +625,11 @@ class Key {
* }>>} List of signer's keyID and validity of signature * }>>} List of signer's keyID and validity of signature
* @async * @async
*/ */
async verifyPrimaryUser(keys, date, userID, config = defaultConfig) { async verifyPrimaryUser(keys, date = new Date(), userID, config = defaultConfig) {
const primaryKey = this.keyPacket; const primaryKey = this.keyPacket;
const { user } = await this.getPrimaryUser(date, userID, config); const { user } = await this.getPrimaryUser(date, userID, config);
const results = keys ? await user.verifyAllCertifications(primaryKey, keys, undefined, config) : const results = keys ? await user.verifyAllCertifications(primaryKey, keys, date, config) :
[{ keyID: primaryKey.getKeyID(), valid: await user.verify(primaryKey, undefined, config).catch(() => false) }]; [{ keyID: primaryKey.getKeyID(), valid: await user.verify(primaryKey, date, config).catch(() => false) }];
return results; return results;
} }
@ -642,6 +638,7 @@ class Key {
* - if no arguments are given, verifies the self certificates; * - if no arguments are given, verifies the self certificates;
* - otherwise, verifies all certificates signed with given keys. * - otherwise, verifies all certificates signed with given keys.
* @param {Array<Key>} keys - array of keys to verify certificate signatures * @param {Array<Key>} keys - array of keys to verify certificate signatures
* @param {Date} [date] - Use the given date for verification instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Array<{ * @returns {Promise<Array<{
* userID: String, * userID: String,
@ -650,12 +647,12 @@ class Key {
* }>>} List of userID, signer's keyID and validity of signature * }>>} List of userID, signer's keyID and validity of signature
* @async * @async
*/ */
async verifyAllUsers(keys, config = defaultConfig) { async verifyAllUsers(keys, date = new Date(), config = defaultConfig) {
const results = []; const results = [];
const primaryKey = this.keyPacket; const primaryKey = this.keyPacket;
await Promise.all(this.users.map(async function(user) { await Promise.all(this.users.map(async function(user) {
const signatures = keys ? await user.verifyAllCertifications(primaryKey, keys, undefined, config) : const signatures = keys ? await user.verifyAllCertifications(primaryKey, keys, date, config) :
[{ keyID: primaryKey.getKeyID(), valid: await user.verify(primaryKey, undefined, config).catch(() => false) }]; [{ keyID: primaryKey.getKeyID(), valid: await user.verify(primaryKey, date, config).catch(() => false) }];
signatures.forEach(signature => { signatures.forEach(signature => {
results.push({ results.push({
userID: user.userID.userID, userID: user.userID.userID,

View File

@ -136,8 +136,8 @@ class PrivateKey extends PublicKey {
} }
let signingKeyPacket; let signingKeyPacket;
if (!this.primaryKey.isDummy()) { if (!this.keyPacket.isDummy()) {
signingKeyPacket = this.primaryKey; signingKeyPacket = this.keyPacket;
} else { } else {
/** /**
* It is enough to validate any signing keys * It is enough to validate any signing keys
@ -196,7 +196,7 @@ class PrivateKey extends PublicKey {
throw new Error('Need private key for revoking'); throw new Error('Need private key for revoking');
} }
const dataToSign = { key: this.keyPacket }; const dataToSign = { key: this.keyPacket };
const key = await this.clone(); const key = this.clone();
key.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, this.keyPacket, { key.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, this.keyPacket, {
signatureType: enums.signature.keyRevocation, signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
@ -227,7 +227,7 @@ class PrivateKey extends PublicKey {
if (options.rsaBits < config.minRSABits) { if (options.rsaBits < config.minRSABits) {
throw new Error(`rsaBits should be at least ${config.minRSABits}, got: ${options.rsaBits}`); throw new Error(`rsaBits should be at least ${config.minRSABits}, got: ${options.rsaBits}`);
} }
const secretKeyPacket = this.primaryKey; const secretKeyPacket = this.keyPacket;
if (secretKeyPacket.isDummy()) { if (secretKeyPacket.isDummy()) {
throw new Error("Cannot add subkey to gnu-dummy primary key"); throw new Error("Cannot add subkey to gnu-dummy primary key");
} }

View File

@ -18,10 +18,15 @@ import defaultConfig from '../config';
* @borrows PublicSubkeyPacket#isDecrypted as SubKey#isDecrypted * @borrows PublicSubkeyPacket#isDecrypted as SubKey#isDecrypted
*/ */
class SubKey { class SubKey {
constructor(subKeyPacket) { /**
* @param {SecretSubkeyPacket|PublicSubkeyPacket} subKeyPacket - subkey packet to hold in the Subkey
* @param {Key} mainKey - reference to main Key object, containing the primary key packet corresponding to the subkey
*/
constructor(subKeyPacket, mainKey) {
this.keyPacket = subKeyPacket; this.keyPacket = subKeyPacket;
this.bindingSignatures = []; this.bindingSignatures = [];
this.revocationSignatures = []; this.revocationSignatures = [];
this.mainKey = mainKey;
} }
/** /**
@ -38,19 +43,18 @@ class SubKey {
/** /**
* Checks if a binding signature of a subkey is revoked * Checks if a binding signature of a subkey is revoked
* @param {SecretKeyPacket|
* PublicKeyPacket} primaryKey The primary key packet
* @param {SignaturePacket} signature - The binding signature to verify * @param {SignaturePacket} signature - The binding signature to verify
* @param {PublicSubkeyPacket| * @param {PublicSubkeyPacket|
* SecretSubkeyPacket| * SecretSubkeyPacket|
* PublicKeyPacket| * PublicKeyPacket|
* SecretKeyPacket} key, optional The key to verify the signature * SecretKeyPacket} key, optional The key to verify the signature
* @param {Date} date - Use the given date instead of the current time * @param {Date} [date] - Use the given date for verification instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Boolean>} True if the binding signature is revoked. * @returns {Promise<Boolean>} True if the binding signature is revoked.
* @async * @async
*/ */
async isRevoked(primaryKey, signature, key, date = new Date(), config = defaultConfig) { async isRevoked(signature, key, date = new Date(), config = defaultConfig) {
const primaryKey = this.mainKey.keyPacket;
return helper.isDataRevoked( return helper.isDataRevoked(
primaryKey, enums.signature.subkeyRevocation, { primaryKey, enums.signature.subkeyRevocation, {
key: primaryKey, key: primaryKey,
@ -62,20 +66,19 @@ class SubKey {
/** /**
* Verify subkey. Checks for revocation signatures, expiration time * Verify subkey. Checks for revocation signatures, expiration time
* and valid binding signature. * and valid binding signature.
* @param {SecretKeyPacket|
* PublicKeyPacket} primaryKey The primary key packet
* @param {Date} date - Use the given date instead of the current time * @param {Date} date - Use the given date instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<SignaturePacket>} * @returns {Promise<SignaturePacket>}
* @throws {Error} if the subkey is invalid. * @throws {Error} if the subkey is invalid.
* @async * @async
*/ */
async verify(primaryKey, date = new Date(), config = defaultConfig) { async verify(date = new Date(), config = defaultConfig) {
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = { key: primaryKey, bind: this.keyPacket }; const dataToVerify = { key: primaryKey, bind: this.keyPacket };
// check subkey binding signatures // check subkey binding signatures
const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config); const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config);
// check binding signature is not revoked // check binding signature is not revoked
if (bindingSignature.revoked || await this.isRevoked(primaryKey, bindingSignature, null, date, config)) { if (bindingSignature.revoked || await this.isRevoked(bindingSignature, null, date, config)) {
throw new Error('Subkey is revoked'); throw new Error('Subkey is revoked');
} }
// check for expiration time // check for expiration time
@ -86,16 +89,15 @@ class SubKey {
} }
/** /**
* Returns the expiration time of the subkey or Infinity if key does not expire * Returns the expiration time of the subkey or Infinity if key does not expire.
* Returns null if the subkey is invalid. * Returns null if the subkey is invalid.
* @param {SecretKeyPacket|
* PublicKeyPacket} primaryKey The primary key packet
* @param {Date} date - Use the given date instead of the current time * @param {Date} date - Use the given date instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Date | Infinity | null>} * @returns {Promise<Date | Infinity | null>}
* @async * @async
*/ */
async getExpirationTime(primaryKey, date = new Date(), config = defaultConfig) { async getExpirationTime(date = new Date(), config = defaultConfig) {
const primaryKey = this.mainKey.keyPacket;
const dataToVerify = { key: primaryKey, bind: this.keyPacket }; const dataToVerify = { key: primaryKey, bind: this.keyPacket };
let bindingSignature; let bindingSignature;
try { try {
@ -103,7 +105,7 @@ class SubKey {
} catch (e) { } catch (e) {
return null; return null;
} }
const keyExpiry = helper.getExpirationTime(this.keyPacket, bindingSignature); const keyExpiry = helper.getKeyExpirationTime(this.keyPacket, bindingSignature);
const sigExpiry = bindingSignature.getExpirationTime(); const sigExpiry = bindingSignature.getExpirationTime();
return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; return keyExpiry < sigExpiry ? keyExpiry : sigExpiry;
} }
@ -111,13 +113,13 @@ class SubKey {
/** /**
* Update subkey with new components from specified subkey * Update subkey with new components from specified subkey
* @param {SubKey} subKey - Source subkey to merge * @param {SubKey} subKey - Source subkey to merge
* @param {SecretKeyPacket| * @param {Date} [date] - Date to verify validity of signatures
SecretSubkeyPacket} primaryKey primary key used for validation
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @throws {Error} if update failed * @throws {Error} if update failed
* @async * @async
*/ */
async update(subKey, primaryKey, config = defaultConfig) { async update(subKey, date = new Date(), config = defaultConfig) {
const primaryKey = this.mainKey.keyPacket;
if (!this.hasSameFingerprintAs(subKey)) { if (!this.hasSameFingerprintAs(subKey)) {
throw new Error('SubKey update method: fingerprints of subkeys not equal'); throw new Error('SubKey update method: fingerprints of subkeys not equal');
} }
@ -129,7 +131,7 @@ class SubKey {
// update missing binding signatures // update missing binding signatures
const that = this; const that = this;
const dataToVerify = { key: primaryKey, bind: that.keyPacket }; const dataToVerify = { key: primaryKey, bind: that.keyPacket };
await helper.mergeSignatures(subKey, this, 'bindingSignatures', async function(srcBindSig) { await helper.mergeSignatures(subKey, this, 'bindingSignatures', date, async function(srcBindSig) {
for (let i = 0; i < that.bindingSignatures.length; i++) { for (let i = 0; i < that.bindingSignatures.length; i++) {
if (that.bindingSignatures[i].issuerKeyID.equals(srcBindSig.issuerKeyID)) { if (that.bindingSignatures[i].issuerKeyID.equals(srcBindSig.issuerKeyID)) {
if (srcBindSig.created > that.bindingSignatures[i].created) { if (srcBindSig.created > that.bindingSignatures[i].created) {
@ -139,15 +141,15 @@ class SubKey {
} }
} }
try { try {
srcBindSig.verified || await srcBindSig.verify(primaryKey, enums.signature.subkeyBinding, dataToVerify, undefined, config); await srcBindSig.verify(primaryKey, enums.signature.subkeyBinding, dataToVerify, date, undefined, config);
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
} }
}); });
// revocation signatures // revocation signatures
await helper.mergeSignatures(subKey, this, 'revocationSignatures', function(srcRevSig) { await helper.mergeSignatures(subKey, this, 'revocationSignatures', date, function(srcRevSig) {
return helper.isDataRevoked(primaryKey, enums.signature.subkeyRevocation, dataToVerify, [srcRevSig], undefined, undefined, undefined, config); return helper.isDataRevoked(primaryKey, enums.signature.subkeyRevocation, dataToVerify, [srcRevSig], undefined, undefined, date, config);
}); });
} }
@ -172,13 +174,13 @@ class SubKey {
config = defaultConfig config = defaultConfig
) { ) {
const dataToSign = { key: primaryKey, bind: this.keyPacket }; const dataToSign = { key: primaryKey, bind: this.keyPacket };
const subKey = new SubKey(this.keyPacket); const subKey = new SubKey(this.keyPacket, this.mainKey);
subKey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, primaryKey, { subKey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, primaryKey, {
signatureType: enums.signature.subkeyRevocation, signatureType: enums.signature.subkeyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString reasonForRevocationString
}, date, undefined, undefined, config)); }, date, undefined, false, config));
await subKey.update(this, primaryKey); await subKey.update(this);
return subKey; return subKey;
} }

View File

@ -38,11 +38,12 @@ class User {
* @param {SecretKeyPacket| * @param {SecretKeyPacket|
* PublicKeyPacket} primaryKey The primary key packet * PublicKeyPacket} primaryKey The primary key packet
* @param {Array<Key>} privateKeys - Decrypted private keys for signing * @param {Array<Key>} privateKeys - Decrypted private keys for signing
* @param {Date} date - Date to overwrite creation date of the signature
* @param {Object} config - Full configuration * @param {Object} config - Full configuration
* @returns {Promise<Key>} New user with new certificate signatures. * @returns {Promise<Key>} New user with new certificate signatures.
* @async * @async
*/ */
async sign(primaryKey, privateKeys, config) { async sign(primaryKey, privateKeys, date, config) {
const dataToSign = { const dataToSign = {
userID: this.userID, userID: this.userID,
userAttribute: this.userAttribute, userAttribute: this.userAttribute,
@ -56,14 +57,14 @@ class User {
if (privateKey.hasSameFingerprintAs(primaryKey)) { if (privateKey.hasSameFingerprintAs(primaryKey)) {
throw new Error('Not implemented for self signing'); throw new Error('Not implemented for self signing');
} }
const signingKey = await privateKey.getSigningKey(undefined, undefined, undefined, config); const signingKey = await privateKey.getSigningKey(undefined, date, undefined, config);
return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, { return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, {
// Most OpenPGP implementations use generic certification (0x10) // Most OpenPGP implementations use generic certification (0x10)
signatureType: enums.signature.certGeneric, signatureType: enums.signature.certGeneric,
keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData] keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData]
}, undefined, undefined, undefined, config); }, date, undefined, undefined, config);
})); }));
await user.update(this, primaryKey); await user.update(this, primaryKey, date, config);
return user; return user;
} }
@ -114,18 +115,15 @@ class User {
if (!key.getKeyIDs().some(id => id.equals(keyID))) { if (!key.getKeyIDs().some(id => id.equals(keyID))) {
return null; return null;
} }
const signingKey = await key.getSigningKey(keyID, date, undefined, config); const signingKey = await key.getSigningKey(keyID, certificate.created, undefined, config);
if (certificate.revoked || await that.isRevoked(primaryKey, certificate, signingKey.keyPacket, date, config)) { if (certificate.revoked || await that.isRevoked(primaryKey, certificate, signingKey.keyPacket, date, config)) {
throw new Error('User certificate is revoked'); throw new Error('User certificate is revoked');
} }
try { try {
certificate.verified || await certificate.verify(signingKey.keyPacket, enums.signature.certGeneric, dataToVerify, undefined, config); await certificate.verify(signingKey.keyPacket, enums.signature.certGeneric, dataToVerify, date, undefined, config);
} catch (e) { } catch (e) {
throw util.wrapError('User certificate is invalid', e); throw util.wrapError('User certificate is invalid', e);
} }
if (certificate.isExpired(date)) {
throw new Error('User certificate is expired');
}
return true; return true;
})); }));
return results.find(result => result !== null) || null; return results.find(result => result !== null) || null;
@ -185,13 +183,10 @@ class User {
throw new Error('Self-certification is revoked'); throw new Error('Self-certification is revoked');
} }
try { try {
selfCertification.verified || await selfCertification.verify(primaryKey, enums.signature.certGeneric, dataToVerify, undefined, config); await selfCertification.verify(primaryKey, enums.signature.certGeneric, dataToVerify, date, undefined, config);
} catch (e) { } catch (e) {
throw util.wrapError('Self-certification is invalid', e); throw util.wrapError('Self-certification is invalid', e);
} }
if (selfCertification.isExpired(date)) {
throw new Error('Self-certification is expired');
}
return true; return true;
} catch (e) { } catch (e) {
exception = e; exception = e;
@ -205,30 +200,31 @@ class User {
* @param {User} user - Source user to merge * @param {User} user - Source user to merge
* @param {SecretKeyPacket| * @param {SecretKeyPacket|
* SecretSubkeyPacket} primaryKey primary key used for validation * SecretSubkeyPacket} primaryKey primary key used for validation
* @param {Date} date - Date to verify the validity of signatures
* @param {Object} config - Full configuration * @param {Object} config - Full configuration
* @returns {Promise<undefined>} * @returns {Promise<undefined>}
* @async * @async
*/ */
async update(user, primaryKey, config) { async update(user, primaryKey, date, config) {
const dataToVerify = { const dataToVerify = {
userID: this.userID, userID: this.userID,
userAttribute: this.userAttribute, userAttribute: this.userAttribute,
key: primaryKey key: primaryKey
}; };
// self signatures // self signatures
await mergeSignatures(user, this, 'selfCertifications', async function(srcSelfSig) { await mergeSignatures(user, this, 'selfCertifications', date, async function(srcSelfSig) {
try { try {
srcSelfSig.verified || await srcSelfSig.verify(primaryKey, enums.signature.certGeneric, dataToVerify, undefined, config); await srcSelfSig.verify(primaryKey, enums.signature.certGeneric, dataToVerify, date, false, config);
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
} }
}); });
// other signatures // other signatures
await mergeSignatures(user, this, 'otherCertifications'); await mergeSignatures(user, this, 'otherCertifications', date);
// revocation signatures // revocation signatures
await mergeSignatures(user, this, 'revocationSignatures', function(srcRevSig) { await mergeSignatures(user, this, 'revocationSignatures', date, function(srcRevSig) {
return isDataRevoked(primaryKey, enums.signature.certRevocation, dataToVerify, [srcRevSig], undefined, undefined, undefined, config); return isDataRevoked(primaryKey, enums.signature.certRevocation, dataToVerify, [srcRevSig], undefined, undefined, date, config);
}); });
} }
} }

View File

@ -107,12 +107,13 @@ export class Message {
* @param {Array<PrivateKey>} [decryptionKeys] - Private keys with decrypted secret data * @param {Array<PrivateKey>} [decryptionKeys] - Private keys with decrypted secret data
* @param {Array<String>} [passwords] - Passwords used to decrypt * @param {Array<String>} [passwords] - Passwords used to decrypt
* @param {Array<Object>} [sessionKeys] - Session keys in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] } * @param {Array<Object>} [sessionKeys] - Session keys in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] }
* @param {Date} [date] - Use the given date for key verification instead of the current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Message>} New message with decrypted content. * @returns {Promise<Message>} New message with decrypted content.
* @async * @async
*/ */
async decrypt(decryptionKeys, passwords, sessionKeys, config = defaultConfig) { async decrypt(decryptionKeys, passwords, sessionKeys, date = new Date(), config = defaultConfig) {
const keyObjs = sessionKeys || await this.decryptSessionKeys(decryptionKeys, passwords, config); const keyObjs = sessionKeys || await this.decryptSessionKeys(decryptionKeys, passwords, date, config);
const symEncryptedPacketlist = this.packets.filterByTag( const symEncryptedPacketlist = this.packets.filterByTag(
enums.packet.symmetricallyEncryptedData, enums.packet.symmetricallyEncryptedData,
@ -157,6 +158,7 @@ export class Message {
* Decrypt encrypted session keys either with private keys or passwords. * Decrypt encrypted session keys either with private keys or passwords.
* @param {Array<PrivateKey>} [decryptionKeys] - Private keys with decrypted secret data * @param {Array<PrivateKey>} [decryptionKeys] - Private keys with decrypted secret data
* @param {Array<String>} [passwords] - Passwords used to decrypt * @param {Array<String>} [passwords] - Passwords used to decrypt
* @param {Date} [date] - Use the given date for key verification, instead of current time
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Array<{ * @returns {Promise<Array<{
* data: Uint8Array, * data: Uint8Array,
@ -164,7 +166,7 @@ export class Message {
* }>>} array of object with potential sessionKey, algorithm pairs * }>>} array of object with potential sessionKey, algorithm pairs
* @async * @async
*/ */
async decryptSessionKeys(decryptionKeys, passwords, config = defaultConfig) { async decryptSessionKeys(decryptionKeys, passwords, date = new Date(), config = defaultConfig) {
let keyPackets = []; let keyPackets = [];
let exception; let exception;
@ -203,7 +205,7 @@ export class Message {
enums.symmetric.cast5 // Golang OpenPGP fallback enums.symmetric.cast5 // Golang OpenPGP fallback
]; ];
try { try {
const primaryUser = await decryptionKey.getPrimaryUser(undefined, undefined, config); // TODO: Pass userID from somewhere. const primaryUser = await decryptionKey.getPrimaryUser(date, undefined, config); // TODO: Pass userID from somewhere.
if (primaryUser.selfCertification.preferredSymmetricAlgorithms) { if (primaryUser.selfCertification.preferredSymmetricAlgorithms) {
algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms); algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms);
} }
@ -697,8 +699,7 @@ export async function createSignaturePackets(literalDataPacket, signingKeys, sig
* @param {SignaturePacket} signature - Signature packet * @param {SignaturePacket} signature - Signature packet
* @param {Array<LiteralDataPacket>} literalDataList - Array of literal data packets * @param {Array<LiteralDataPacket>} literalDataList - Array of literal data packets
* @param {Array<PublicKey>} verificationKeys - Array of public keys to verify signatures * @param {Array<PublicKey>} verificationKeys - Array of public keys to verify signatures
* @param {Date} date - Verify the signature against the given date, * @param {Date} [date] - Check signature validity with respect to the given date
* i.e. check signature creation time < date < expiration time
* @param {Boolean} [detached] - Whether to verify detached signature packets * @param {Boolean} [detached] - Whether to verify detached signature packets
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<{ * @returns {Promise<{
@ -711,49 +712,41 @@ export async function createSignaturePackets(literalDataPacket, signingKeys, sig
*/ */
async function createVerificationObject(signature, literalDataList, verificationKeys, date = new Date(), detached = false, config = defaultConfig) { async function createVerificationObject(signature, literalDataList, verificationKeys, date = new Date(), detached = false, config = defaultConfig) {
let primaryKey; let primaryKey;
let signingKey; let unverifiedSigningKey;
let keyError;
for (const key of verificationKeys) { for (const key of verificationKeys) {
const issuerKeys = key.getKeys(signature.issuerKeyID); const issuerKeys = key.getKeys(signature.issuerKeyID);
if (issuerKeys.length > 0) { if (issuerKeys.length > 0) {
primaryKey = key; primaryKey = key;
unverifiedSigningKey = issuerKeys[0];
break; break;
} }
} }
if (!primaryKey) {
keyError = new Error(`Could not find signing key with key ID ${signature.issuerKeyID.toHex()}`); const isOnePassSignature = signature instanceof OnePassSignaturePacket;
} else { const signaturePacketPromise = isOnePassSignature ? signature.correspondingSig : signature;
try {
signingKey = await primaryKey.getSigningKey(signature.issuerKeyID, null, undefined, config);
} catch (e) {
keyError = e;
}
}
const signaturePacket = signature.correspondingSig || signature;
const verifiedSig = { const verifiedSig = {
keyID: signature.issuerKeyID, keyID: signature.issuerKeyID,
verified: (async () => { verified: (async () => {
if (keyError) { if (!unverifiedSigningKey) {
throw keyError; throw new Error(`Could not find signing key with key ID ${signature.issuerKeyID.toHex()}`);
} }
await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0], detached, config);
const sig = await signaturePacket; await signature.verify(unverifiedSigningKey.keyPacket, signature.signatureType, literalDataList[0], date, detached, config);
if (sig.isExpired(date) || !( const signaturePacket = await signaturePacketPromise;
sig.created >= signingKey.getCreationTime() && if (unverifiedSigningKey.getCreationTime() > signaturePacket.created) {
sig.created < await (signingKey === primaryKey ? throw new Error('Key is newer than the signature');
signingKey.getExpirationTime(undefined, undefined, undefined, config) :
signingKey.getExpirationTime(primaryKey, date, undefined, config)
)
)) {
throw new Error('Signature is expired');
} }
// We pass the signature creation time to check whether the key was expired at the time of signing.
// We check this after signature verification because for streamed one-pass signatures, the creation time is not available before
await primaryKey.getSigningKey(unverifiedSigningKey.getKeyID(), signaturePacket.created, undefined, config);
return true; return true;
})(), })(),
signature: (async () => { signature: (async () => {
const sig = await signaturePacket; const signaturePacket = await signaturePacketPromise;
const packetlist = new PacketList(); const packetlist = new PacketList();
sig && packetlist.push(sig); signaturePacket && packetlist.push(signaturePacket);
return new Signature(packetlist); return new Signature(packetlist);
})() })()
}; };

View File

@ -93,6 +93,9 @@ export function generateKey({ userIDs = [], passphrase = "", type = "ecc", rsaBi
export function reformatKey({ privateKey, userIDs = [], passphrase = "", keyExpirationTime = 0, date, config }) { export function reformatKey({ privateKey, userIDs = [], passphrase = "", keyExpirationTime = 0, date, config }) {
config = { ...defaultConfig, ...config }; config = { ...defaultConfig, ...config };
userIDs = toArray(userIDs); userIDs = toArray(userIDs);
if (userIDs.length === 0) {
throw new Error('UserIDs are required for key reformat');
}
const options = { privateKey, userIDs, passphrase, keyExpirationTime, date }; const options = { privateKey, userIDs, passphrase, keyExpirationTime, date };
return reformat(options, config).then(async key => { return reformat(options, config).then(async key => {
@ -119,6 +122,7 @@ export function reformatKey({ privateKey, userIDs = [], passphrase = "", keyExpi
* @param {Object} [options.reasonForRevocation] - Object indicating the reason for revocation * @param {Object} [options.reasonForRevocation] - Object indicating the reason for revocation
* @param {module:enums.reasonForRevocation} [options.reasonForRevocation.flag=[noReason]{@link module:enums.reasonForRevocation}] - Flag indicating the reason for revocation * @param {module:enums.reasonForRevocation} [options.reasonForRevocation.flag=[noReason]{@link module:enums.reasonForRevocation}] - Flag indicating the reason for revocation
* @param {String} [options.reasonForRevocation.string=""] - String explaining the reason for revocation * @param {String} [options.reasonForRevocation.string=""] - String explaining the reason for revocation
* @param {Date} [options.date] - Use the given date instead of the current time to verify validity of revocation certificate (if provided), or as creation time of the revocation signature
* @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config} * @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config}
* @returns {Promise<Object>} The revoked key object in the form: * @returns {Promise<Object>} The revoked key object in the form:
* `{ privateKey:PrivateKey, privateKeyArmored:String, publicKey:PublicKey, publicKeyArmored:String }` * `{ privateKey:PrivateKey, privateKeyArmored:String, publicKey:PublicKey, publicKeyArmored:String }`
@ -126,13 +130,13 @@ export function reformatKey({ privateKey, userIDs = [], passphrase = "", keyExpi
* @async * @async
* @static * @static
*/ */
export function revokeKey({ key, revocationCertificate, reasonForRevocation, config }) { export function revokeKey({ key, revocationCertificate, reasonForRevocation, date = new Date(), config }) {
config = { ...defaultConfig, ...config }; config = { ...defaultConfig, ...config };
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (revocationCertificate) { if (revocationCertificate) {
return key.applyRevocationCertificate(revocationCertificate, config); return key.applyRevocationCertificate(revocationCertificate, date, config);
} else { } else {
return key.revoke(reasonForRevocation, undefined, config); return key.revoke(reasonForRevocation, date, config);
} }
}).then(async key => { }).then(async key => {
if (key.isPrivate()) { if (key.isPrivate()) {
@ -309,7 +313,7 @@ export function decrypt({ message, decryptionKeys, passwords, sessionKeys, verif
config = { ...defaultConfig, ...config }; config = { ...defaultConfig, ...config };
checkMessage(message); verificationKeys = toArray(verificationKeys); decryptionKeys = toArray(decryptionKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys); checkMessage(message); verificationKeys = toArray(verificationKeys); decryptionKeys = toArray(decryptionKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys);
return message.decrypt(decryptionKeys, passwords, sessionKeys, config).then(async function(decrypted) { return message.decrypt(decryptionKeys, passwords, sessionKeys, date, config).then(async function(decrypted) {
if (!verificationKeys) { if (!verificationKeys) {
verificationKeys = []; verificationKeys = [];
} }
@ -515,6 +519,7 @@ export function encryptSessionKey({ data, algorithm, aeadAlgorithm, encryptionKe
* @param {Message} options.message - A message object containing the encrypted session key packets * @param {Message} options.message - A message object containing the encrypted session key packets
* @param {PrivateKey|PrivateKey[]} [options.decryptionKeys] - Private keys with decrypted secret key data * @param {PrivateKey|PrivateKey[]} [options.decryptionKeys] - Private keys with decrypted secret key data
* @param {String|String[]} [options.passwords] - Passwords to decrypt the session key * @param {String|String[]} [options.passwords] - Passwords to decrypt the session key
* @param {Date} [options.date] - Date to use for key verification instead of the current time
* @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config} * @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config}
* @returns {Promise<Object>} Array of decrypted session key, algorithm pairs in the form: * @returns {Promise<Object>} Array of decrypted session key, algorithm pairs in the form:
* { data:Uint8Array, algorithm:String } * { data:Uint8Array, algorithm:String }
@ -522,13 +527,13 @@ export function encryptSessionKey({ data, algorithm, aeadAlgorithm, encryptionKe
* @async * @async
* @static * @static
*/ */
export function decryptSessionKeys({ message, decryptionKeys, passwords, config }) { export function decryptSessionKeys({ message, decryptionKeys, passwords, date = new Date(), config }) {
config = { ...defaultConfig, ...config }; config = { ...defaultConfig, ...config };
checkMessage(message); decryptionKeys = toArray(decryptionKeys); passwords = toArray(passwords); checkMessage(message); decryptionKeys = toArray(decryptionKeys); passwords = toArray(passwords);
return Promise.resolve().then(async function() { return Promise.resolve().then(async function() {
return message.decryptSessionKeys(decryptionKeys, passwords, config); return message.decryptSessionKeys(decryptionKeys, passwords, date, config);
}).catch(onError.bind(null, 'Error decrypting session keys')); }).catch(onError.bind(null, 'Error decrypting session keys'));
} }

View File

@ -23,6 +23,18 @@ import enums from '../enums';
import util from '../util'; import util from '../util';
import defaultConfig from '../config'; import defaultConfig from '../config';
// Symbol to store cryptographic validity of the signature, to avoid recomputing multiple times on verification.
const verified = Symbol('verified');
// GPG puts the Issuer and Signature subpackets in the unhashed area.
// Tampering with those invalidates the signature, so we still trust them and parse them.
// All other unhashed subpackets are ignored.
const allowedUnhashedSubpackets = new Set([
enums.signatureSubpacket.issuer,
enums.signatureSubpacket.issuerFingerprint,
enums.signatureSubpacket.embeddedSignature
]);
/** /**
* Implementation of the Signature Packet (Tag 2) * Implementation of the Signature Packet (Tag 2)
* *
@ -36,11 +48,8 @@ class SignaturePacket {
return enums.packet.signature; return enums.packet.signature;
} }
/** constructor() {
* @param {Date} date - The creation date of the signature this.version = null;
*/
constructor(date = new Date()) {
this.version = 4; // This is set to 5 below if we sign with a V5 key.
this.signatureType = null; this.signatureType = null;
this.hashAlgorithm = null; this.hashAlgorithm = null;
this.publicKeyAlgorithm = null; this.publicKeyAlgorithm = null;
@ -49,7 +58,7 @@ class SignaturePacket {
this.unhashedSubpackets = []; this.unhashedSubpackets = [];
this.signedHashValue = null; this.signedHashValue = null;
this.created = util.normalizeDate(date); this.created = null;
this.signatureExpirationTime = null; this.signatureExpirationTime = null;
this.signatureNeverExpires = true; this.signatureNeverExpires = true;
this.exportable = null; this.exportable = null;
@ -85,8 +94,8 @@ class SignaturePacket {
this.issuerFingerprint = null; this.issuerFingerprint = null;
this.preferredAEADAlgorithms = null; this.preferredAEADAlgorithms = null;
this.verified = null;
this.revoked = null; this.revoked = null;
this[verified] = null;
} }
/** /**
@ -108,6 +117,9 @@ class SignaturePacket {
// hashed subpackets // hashed subpackets
i += this.readSubPackets(bytes.subarray(i, bytes.length), true); i += this.readSubPackets(bytes.subarray(i, bytes.length), true);
if (!this.created) {
throw new Error('Missing signature creation time subpacket.');
}
// A V4 signature hashes the packet body // A V4 signature hashes the packet body
// starting from its first field, the version number, through the end // starting from its first field, the version number, through the end
@ -152,20 +164,24 @@ class SignaturePacket {
* Signs provided data. This needs to be done prior to serialization. * Signs provided data. This needs to be done prior to serialization.
* @param {SecretKeyPacket} key - Private key used to sign the message. * @param {SecretKeyPacket} key - Private key used to sign the message.
* @param {Object} data - Contains packets to be signed. * @param {Object} data - Contains packets to be signed.
* @param {Date} [date] - The signature creation time.
* @param {Boolean} [detached] - Whether to create a detached signature * @param {Boolean} [detached] - Whether to create a detached signature
* @throws {Error} if signing failed * @throws {Error} if signing failed
* @async * @async
*/ */
async sign(key, data, detached = false) { async sign(key, data, date = new Date(), detached = false) {
const signatureType = enums.write(enums.signature, this.signatureType); const signatureType = enums.write(enums.signature, this.signatureType);
const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm);
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
if (key.version === 5) { if (key.version === 5) {
this.version = 5; this.version = 5;
} else {
this.version = 4;
} }
const arr = [new Uint8Array([this.version, signatureType, publicKeyAlgorithm, hashAlgorithm])]; const arr = [new Uint8Array([this.version, signatureType, publicKeyAlgorithm, hashAlgorithm])];
this.created = util.normalizeDate(date);
this.issuerKeyVersion = key.version; this.issuerKeyVersion = key.version;
this.issuerFingerprint = key.getFingerprintBytes(); this.issuerFingerprint = key.getFingerprintBytes();
this.issuerKeyID = key.getKeyID(); this.issuerKeyID = key.getKeyID();
@ -191,7 +207,7 @@ class SignaturePacket {
// getLatestValidSignature(this.revocationSignatures, key, data)` later. // getLatestValidSignature(this.revocationSignatures, key, data)` later.
// Note that this only holds up if the key and data passed to verify are the // Note that this only holds up if the key and data passed to verify are the
// same as the ones passed to sign. // same as the ones passed to sign.
this.verified = true; this[verified] = true;
} }
} }
@ -203,9 +219,10 @@ class SignaturePacket {
const sub = enums.signatureSubpacket; const sub = enums.signatureSubpacket;
const arr = []; const arr = [];
let bytes; let bytes;
if (this.created !== null) { if (this.created === null) {
arr.push(writeSubPacket(sub.signatureCreationTime, util.writeDate(this.created))); throw new Error('Missing signature creation time');
} }
arr.push(writeSubPacket(sub.signatureCreationTime, util.writeDate(this.created)));
if (this.signatureExpirationTime !== null) { if (this.signatureExpirationTime !== null) {
arr.push(writeSubPacket(sub.signatureExpirationTime, util.writeNumber(this.signatureExpirationTime, 4))); arr.push(writeSubPacket(sub.signatureExpirationTime, util.writeNumber(this.signatureExpirationTime, 4)));
} }
@ -331,30 +348,14 @@ class SignaturePacket {
} }
// V4 signature sub packets // V4 signature sub packets
readSubPacket(bytes, hashed = true) {
readSubPacket(bytes, trusted = true) {
let mypos = 0; let mypos = 0;
const readArray = (prop, bytes) => {
this[prop] = [];
for (let i = 0; i < bytes.length; i++) {
this[prop].push(bytes[i]);
}
};
// The leftmost bit denotes a "critical" packet // The leftmost bit denotes a "critical" packet
const critical = bytes[mypos] & 0x80; const critical = bytes[mypos] & 0x80;
const type = bytes[mypos] & 0x7F; const type = bytes[mypos] & 0x7F;
// GPG puts the Issuer and Signature subpackets in the unhashed area. if (!hashed && !allowedUnhashedSubpackets.has(type)) {
// Tampering with those invalidates the signature, so we can trust them.
// Ignore all other unhashed subpackets.
if (!trusted && ![
enums.signatureSubpacket.issuer,
enums.signatureSubpacket.issuerFingerprint,
enums.signatureSubpacket.embeddedSignature
].includes(type)) {
this.unhashedSubpackets.push(bytes.subarray(mypos, bytes.length)); this.unhashedSubpackets.push(bytes.subarray(mypos, bytes.length));
return; return;
} }
@ -363,11 +364,11 @@ class SignaturePacket {
// subpacket type // subpacket type
switch (type) { switch (type) {
case 2: case enums.signatureSubpacket.signatureCreationTime:
// Signature Creation Time // Signature Creation Time
this.created = util.readDate(bytes.subarray(mypos, bytes.length)); this.created = util.readDate(bytes.subarray(mypos, bytes.length));
break; break;
case 3: { case enums.signatureSubpacket.signatureExpirationTime: {
// Signature Expiration Time in seconds // Signature Expiration Time in seconds
const seconds = util.readNumber(bytes.subarray(mypos, bytes.length)); const seconds = util.readNumber(bytes.subarray(mypos, bytes.length));
@ -376,24 +377,24 @@ class SignaturePacket {
break; break;
} }
case 4: case enums.signatureSubpacket.exportableCertification:
// Exportable Certification // Exportable Certification
this.exportable = bytes[mypos++] === 1; this.exportable = bytes[mypos++] === 1;
break; break;
case 5: case enums.signatureSubpacket.trustSignature:
// Trust Signature // Trust Signature
this.trustLevel = bytes[mypos++]; this.trustLevel = bytes[mypos++];
this.trustAmount = bytes[mypos++]; this.trustAmount = bytes[mypos++];
break; break;
case 6: case enums.signatureSubpacket.regularExpression:
// Regular Expression // Regular Expression
this.regularExpression = bytes[mypos]; this.regularExpression = bytes[mypos];
break; break;
case 7: case enums.signatureSubpacket.revocable:
// Revocable // Revocable
this.revocable = bytes[mypos++] === 1; this.revocable = bytes[mypos++] === 1;
break; break;
case 9: { case enums.signatureSubpacket.keyExpirationTime: {
// Key Expiration Time in seconds // Key Expiration Time in seconds
const seconds = util.readNumber(bytes.subarray(mypos, bytes.length)); const seconds = util.readNumber(bytes.subarray(mypos, bytes.length));
@ -402,11 +403,11 @@ class SignaturePacket {
break; break;
} }
case 11: case enums.signatureSubpacket.preferredSymmetricAlgorithms:
// Preferred Symmetric Algorithms // Preferred Symmetric Algorithms
readArray('preferredSymmetricAlgorithms', bytes.subarray(mypos, bytes.length)); this.preferredSymmetricAlgorithms = [...bytes.subarray(mypos, bytes.length)];
break; break;
case 12: case enums.signatureSubpacket.revocationKey:
// Revocation Key // Revocation Key
// (1 octet of class, 1 octet of public-key algorithm ID, 20 // (1 octet of class, 1 octet of public-key algorithm ID, 20
// octets of // octets of
@ -416,12 +417,12 @@ class SignaturePacket {
this.revocationKeyFingerprint = bytes.subarray(mypos, mypos + 20); this.revocationKeyFingerprint = bytes.subarray(mypos, mypos + 20);
break; break;
case 16: case enums.signatureSubpacket.issuer:
// Issuer // Issuer
this.issuerKeyID.read(bytes.subarray(mypos, bytes.length)); this.issuerKeyID.read(bytes.subarray(mypos, bytes.length));
break; break;
case 20: { case enums.signatureSubpacket.notationData: {
// Notation Data // Notation Data
const humanReadable = !!(bytes[mypos] & 0x80); const humanReadable = !!(bytes[mypos] & 0x80);
@ -442,48 +443,48 @@ class SignaturePacket {
} }
break; break;
} }
case 21: case enums.signatureSubpacket.preferredHashAlgorithms:
// Preferred Hash Algorithms // Preferred Hash Algorithms
readArray('preferredHashAlgorithms', bytes.subarray(mypos, bytes.length)); this.preferredHashAlgorithms = [...bytes.subarray(mypos, bytes.length)];
break; break;
case 22: case enums.signatureSubpacket.preferredCompressionAlgorithms:
// Preferred Compression Algorithms // Preferred Compression Algorithms
readArray('preferredCompressionAlgorithms', bytes.subarray(mypos, bytes.length)); this.preferredCompressionAlgorithms = [...bytes.subarray(mypos, bytes.length)];
break; break;
case 23: case enums.signatureSubpacket.keyServerPreferences:
// Key Server Preferences // Key Server Preferences
readArray('keyServerPreferences', bytes.subarray(mypos, bytes.length)); this.keyServerPreferences = [...bytes.subarray(mypos, bytes.length)];
break; break;
case 24: case enums.signatureSubpacket.preferredKeyServer:
// Preferred Key Server // Preferred Key Server
this.preferredKeyServer = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.preferredKeyServer = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length));
break; break;
case 25: case enums.signatureSubpacket.primaryUserID:
// Primary User ID // Primary User ID
this.isPrimaryUserID = bytes[mypos++] !== 0; this.isPrimaryUserID = bytes[mypos++] !== 0;
break; break;
case 26: case enums.signatureSubpacket.policyURI:
// Policy URI // Policy URI
this.policyURI = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.policyURI = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length));
break; break;
case 27: case enums.signatureSubpacket.keyFlags:
// Key Flags // Key Flags
readArray('keyFlags', bytes.subarray(mypos, bytes.length)); this.keyFlags = [...bytes.subarray(mypos, bytes.length)];
break; break;
case 28: case enums.signatureSubpacket.signersUserID:
// Signer's User ID // Signer's User ID
this.signersUserID = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.signersUserID = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length));
break; break;
case 29: case enums.signatureSubpacket.reasonForRevocation:
// Reason for Revocation // Reason for Revocation
this.reasonForRevocationFlag = bytes[mypos++]; this.reasonForRevocationFlag = bytes[mypos++];
this.reasonForRevocationString = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length)); this.reasonForRevocationString = util.uint8ArrayToString(bytes.subarray(mypos, bytes.length));
break; break;
case 30: case enums.signatureSubpacket.features:
// Features // Features
readArray('features', bytes.subarray(mypos, bytes.length)); this.features = [...bytes.subarray(mypos, bytes.length)];
break; break;
case 31: { case enums.signatureSubpacket.signatureTarget: {
// Signature Target // Signature Target
// (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash) // (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash)
this.signatureTargetPublicKeyAlgorithm = bytes[mypos++]; this.signatureTargetPublicKeyAlgorithm = bytes[mypos++];
@ -494,12 +495,12 @@ class SignaturePacket {
this.signatureTargetHash = util.uint8ArrayToString(bytes.subarray(mypos, mypos + len)); this.signatureTargetHash = util.uint8ArrayToString(bytes.subarray(mypos, mypos + len));
break; break;
} }
case 32: case enums.signatureSubpacket.embeddedSignature:
// Embedded Signature // Embedded Signature
this.embeddedSignature = new SignaturePacket(); this.embeddedSignature = new SignaturePacket();
this.embeddedSignature.read(bytes.subarray(mypos, bytes.length)); this.embeddedSignature.read(bytes.subarray(mypos, bytes.length));
break; break;
case 33: case enums.signatureSubpacket.issuerFingerprint:
// Issuer Fingerprint // Issuer Fingerprint
this.issuerKeyVersion = bytes[mypos++]; this.issuerKeyVersion = bytes[mypos++];
this.issuerFingerprint = bytes.subarray(mypos, bytes.length); this.issuerFingerprint = bytes.subarray(mypos, bytes.length);
@ -509,12 +510,12 @@ class SignaturePacket {
this.issuerKeyID.read(this.issuerFingerprint.subarray(-8)); this.issuerKeyID.read(this.issuerFingerprint.subarray(-8));
} }
break; break;
case 34: case enums.signatureSubpacket.preferredAEADAlgorithms:
// Preferred AEAD Algorithms // Preferred AEAD Algorithms
readArray.call(this, 'preferredAEADAlgorithms', bytes.subarray(mypos, bytes.length)); this.preferredAEADAlgorithms = [...bytes.subarray(mypos, bytes.length)];
break; break;
default: { default: {
const err = new Error("Unknown signature subpacket type " + type + " @:" + mypos); const err = new Error(`Unknown signature subpacket type ${type}`);
if (critical) { if (critical) {
throw err; throw err;
} else { } else {
@ -654,41 +655,59 @@ class SignaturePacket {
* SecretSubkeyPacket|SecretKeyPacket} key - the public key to verify the signature * SecretSubkeyPacket|SecretKeyPacket} key - the public key to verify the signature
* @param {module:enums.signature} signatureType - Expected signature type * @param {module:enums.signature} signatureType - Expected signature type
* @param {String|Object} data - Data which on the signature applies * @param {String|Object} data - Data which on the signature applies
* @param {Date} [date] - Use the given date instead of the current time to check for signature validity and expiration
* @param {Boolean} [detached] - Whether to verify a detached signature * @param {Boolean} [detached] - Whether to verify a detached signature
* @param {Object} [config] - Full configuration, defaults to openpgp.config * @param {Object} [config] - Full configuration, defaults to openpgp.config
* @throws {Error} if signature validation failed * @throws {Error} if signature validation failed
* @async * @async
*/ */
async verify(key, signatureType, data, detached = false, config = defaultConfig) { async verify(key, signatureType, data, date = new Date(), detached = false, config = defaultConfig) {
const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm);
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
if (!this.issuerKeyID.equals(key.getKeyID())) {
throw new Error('Signature was not issued by the given public key');
}
if (publicKeyAlgorithm !== enums.write(enums.publicKey, key.algorithm)) { if (publicKeyAlgorithm !== enums.write(enums.publicKey, key.algorithm)) {
throw new Error('Public key algorithm used to sign signature does not match issuer key algorithm.'); throw new Error('Public key algorithm used to sign signature does not match issuer key algorithm.');
} }
let toHash; const isMessageSignature = signatureType === enums.signature.binary || signatureType === enums.signature.text;
let hash; // Cryptographic validity is cached after one successful verification.
if (this.hashed) { // However, for message signatures, we always re-verify, since the passed `data` can change
hash = await this.hashed; const skipVerify = this[verified] && !isMessageSignature;
} else { if (!skipVerify) {
toHash = this.toHash(signatureType, data, detached); let toHash;
hash = await this.hash(signatureType, data, toHash); let hash;
} if (this.hashed) {
hash = await stream.readToEnd(hash); hash = await this.hashed;
if (this.signedHashValue[0] !== hash[0] || } else {
this.signedHashValue[1] !== hash[1]) { toHash = this.toHash(signatureType, data, detached);
throw new Error('Message digest did not match'); hash = await this.hash(signatureType, data, toHash);
}
hash = await stream.readToEnd(hash);
if (this.signedHashValue[0] !== hash[0] ||
this.signedHashValue[1] !== hash[1]) {
throw new Error('Signed digest did not match');
}
this.params = await this.params;
this[verified] = await crypto.signature.verify(
publicKeyAlgorithm, hashAlgorithm, this.params, key.publicParams,
toHash, hash
);
if (!this[verified]) {
throw new Error('Signature verification failed');
}
} }
this.params = await this.params; const normDate = util.normalizeDate(date);
if (normDate && this.created > normDate) {
const verified = await crypto.signature.verify( throw new Error('Signature creation time is in the future');
publicKeyAlgorithm, hashAlgorithm, this.params, key.publicParams, }
toHash, hash if (normDate && normDate > this.getExpirationTime()) {
); throw new Error('Signature is expired');
if (!verified) {
throw new Error('Signature verification failed');
} }
if (config.rejectHashAlgorithms.has(hashAlgorithm)) { if (config.rejectHashAlgorithms.has(hashAlgorithm)) {
throw new Error('Insecure hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); throw new Error('Insecure hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase());
@ -705,7 +724,6 @@ class SignaturePacket {
if (this.revocationKeyClass !== null) { if (this.revocationKeyClass !== null) {
throw new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'); throw new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.');
} }
this.verified = true;
} }
/** /**
@ -716,18 +734,17 @@ class SignaturePacket {
isExpired(date = new Date()) { isExpired(date = new Date()) {
const normDate = util.normalizeDate(date); const normDate = util.normalizeDate(date);
if (normDate !== null) { if (normDate !== null) {
const expirationTime = this.getExpirationTime(); return !(this.created <= normDate && normDate <= this.getExpirationTime());
return !(this.created <= normDate && normDate <= expirationTime);
} }
return false; return false;
} }
/** /**
* Returns the expiration time of the signature or Infinity if signature does not expire * Returns the expiration time of the signature or Infinity if signature does not expire
* @returns {Date} Expiration time. * @returns {Date | Infinity} Expiration time.
*/ */
getExpirationTime() { getExpirationTime() {
return !this.signatureNeverExpires ? new Date(this.created.getTime() + this.signatureExpirationTime * 1000) : Infinity; return this.signatureNeverExpires ? Infinity : new Date(this.created.getTime() + this.signatureExpirationTime * 1000);
} }
} }

View File

@ -231,7 +231,7 @@ module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve
return openpgp.generateKey(options).then(function (key) { return openpgp.generateKey(options).then(function (key) {
expect(key).to.exist; expect(key).to.exist;
expect(key.key).to.exist; expect(key.key).to.exist;
expect(key.key.primaryKey).to.exist; expect(key.key.keyPacket).to.exist;
expect(key.privateKeyArmored).to.exist; expect(key.privateKeyArmored).to.exist;
expect(key.publicKeyArmored).to.exist; expect(key.publicKeyArmored).to.exist;
}); });

View File

@ -2415,7 +2415,7 @@ function versionSpecificTests() {
const actual_delta = (new Date(expiration) - new Date()) / 1000; const actual_delta = (new Date(expiration) - new Date()) / 1000;
expect(Math.abs(actual_delta - expect_delta)).to.be.below(60); expect(Math.abs(actual_delta - expect_delta)).to.be.below(60);
const subKeyExpiration = await key.subKeys[0].getExpirationTime(key.primaryKey); const subKeyExpiration = await key.subKeys[0].getExpirationTime();
expect(subKeyExpiration).to.exist; expect(subKeyExpiration).to.exist;
const actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000; const actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000;
@ -2693,7 +2693,7 @@ function versionSpecificTests() {
// ssb cv25519 2019-03-20 [E] // ssb cv25519 2019-03-20 [E]
// E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965 // E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965
const key = await openpgp.readKey({ armoredKey: v5_sample_key }); const key = await openpgp.readKey({ armoredKey: v5_sample_key });
expect(await key.primaryKey.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54'); expect(await key.keyPacket.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54');
expect(await key.subKeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965'); expect(await key.subKeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965');
await key.verifyPrimaryKey(); await key.verifyPrimaryKey();
}); });
@ -2816,9 +2816,7 @@ module.exports = () => describe('Key', function() {
expect(pubKey.subKeys).to.exist; expect(pubKey.subKeys).to.exist;
expect(pubKey.subKeys).to.have.length(2); expect(pubKey.subKeys).to.have.length(2);
await expect(pubKey.subKeys[0].verify( await expect(pubKey.subKeys[0].verify()).to.be.rejectedWith('Subkey is revoked');
pubKey.primaryKey
)).to.be.rejectedWith('Subkey is revoked');
}); });
it('Verify status of key with non-self revocation signature', async function() { it('Verify status of key with non-self revocation signature', async function() {
@ -2842,19 +2840,24 @@ module.exports = () => describe('Key', function() {
expect(signatures[1].valid).to.be.false; expect(signatures[1].valid).to.be.false;
const { user } = await pubKey.getPrimaryUser(); const { user } = await pubKey.getPrimaryUser();
await expect(user.verifyCertificate(pubKey.primaryKey, user.otherCertifications[0], [certifyingKey], undefined, openpgp.config)).to.be.rejectedWith('User certificate is revoked'); await expect(user.verifyCertificate(pubKey.keyPacket, user.otherCertifications[0], [certifyingKey], undefined, openpgp.config)).to.be.rejectedWith('User certificate is revoked');
} finally { } finally {
openpgp.config.rejectPublicKeyAlgorithms = rejectPublicKeyAlgorithms; openpgp.config.rejectPublicKeyAlgorithms = rejectPublicKeyAlgorithms;
} }
}); });
it('Verify primary key with authorized revocation key in a direct-key signature', async function() {
const pubKey = await openpgp.readKey({ armoredKey: key_with_authorized_revocation_key_in_separate_sig });
await pubKey.verifyPrimaryKey();
});
it('Verify certificate of key with future creation date', async function() { it('Verify certificate of key with future creation date', async function() {
const pubKey = await openpgp.readKey({ armoredKey: key_created_2030 }); const pubKey = await openpgp.readKey({ armoredKey: key_created_2030 });
const user = pubKey.users[0]; const user = pubKey.users[0];
await user.verifyCertificate(pubKey.primaryKey, user.selfCertifications[0], [pubKey], pubKey.primaryKey.created, openpgp.config); await user.verifyCertificate(pubKey.keyPacket, user.selfCertifications[0], [pubKey], pubKey.keyPacket.created, openpgp.config);
const verifyAllResult = await user.verifyAllCertifications(pubKey.primaryKey, [pubKey], pubKey.primaryKey.created); const verifyAllResult = await user.verifyAllCertifications(pubKey.keyPacket, [pubKey], pubKey.keyPacket.created);
expect(verifyAllResult[0].valid).to.be.true; expect(verifyAllResult[0].valid).to.be.true;
await user.verify(pubKey.primaryKey, pubKey.primaryKey.created); await user.verify(pubKey.keyPacket, pubKey.keyPacket.created);
}); });
it('Evaluate key flags to find valid encryption key packet', async function() { it('Evaluate key flags to find valid encryption key packet', async function() {
@ -2908,29 +2911,38 @@ module.exports = () => describe('Key', function() {
const [, pubKey] = await openpgp.readKeys({ armoredKeys: twoKeys }); const [, pubKey] = await openpgp.readKeys({ armoredKeys: twoKeys });
expect(pubKey).to.exist; expect(pubKey).to.exist;
expect(pubKey).to.be.an.instanceof(openpgp.PublicKey); expect(pubKey).to.be.an.instanceof(openpgp.PublicKey);
const expirationTime = await pubKey.subKeys[0].getExpirationTime(pubKey.primaryKey); const expirationTime = await pubKey.subKeys[0].getExpirationTime();
expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z');
}); });
it('Method getExpirationTime V4 Key with capabilities', async function() { it('Method getExpirationTime V4 Key with capabilities', async function() {
const pubKey = await openpgp.readKey({ armoredKey: priv_key_2000_2008 }); const { minRSABits } = openpgp.config;
expect(pubKey).to.exist; try {
expect(pubKey).to.be.an.instanceof(openpgp.PublicKey); openpgp.config.minRSABits = 1024;
pubKey.users[0].selfCertifications[0].keyFlags = [1]; const privKey = await openpgp.readKey({ armoredKey: priv_key_2000_2008 });
const expirationTime = await pubKey.getExpirationTime(); privKey.users[0].selfCertifications[0].keyFlags = [1];
expect(expirationTime).to.equal(Infinity); const expirationTime = await privKey.getExpirationTime();
const encryptExpirationTime = await pubKey.getExpirationTime('encrypt_sign'); expect(expirationTime).to.equal(Infinity);
expect(encryptExpirationTime.toISOString()).to.equal('2008-02-12T17:12:08.000Z'); const encryptExpirationTime = await privKey.getExpirationTime('encrypt_sign');
expect(encryptExpirationTime.toISOString()).to.equal('2008-02-12T17:12:08.000Z');
} finally {
openpgp.config.minRSABits = minRSABits;
}
}); });
it('Method getExpirationTime V4 Key with capabilities - capable primary key', async function() { it('Method getExpirationTime V4 Key with capabilities - capable primary key', async function() {
const pubKey = await openpgp.readKey({ armoredKey: priv_key_2000_2008 }); const { minRSABits } = openpgp.config;
expect(pubKey).to.exist; try {
expect(pubKey).to.be.an.instanceof(openpgp.PublicKey); openpgp.config.minRSABits = 1024;
const expirationTime = await pubKey.getExpirationTime(); const privKey = await openpgp.readKey({ armoredKey: priv_key_2000_2008 });
expect(expirationTime).to.equal(Infinity); const expirationTime = await privKey.getExpirationTime();
const encryptExpirationTime = await pubKey.getExpirationTime('encrypt_sign'); expect(expirationTime).to.equal(Infinity);
expect(encryptExpirationTime).to.equal(Infinity); const encryptExpirationTime = await privKey.getExpirationTime('encrypt_sign');
expect(encryptExpirationTime).to.equal(Infinity);
} finally {
openpgp.config.minRSABits = minRSABits;
}
}); });
it("decryptKey() - throw if key parameters don't correspond", async function() { it("decryptKey() - throw if key parameters don't correspond", async function() {
@ -2985,7 +2997,7 @@ module.exports = () => describe('Key', function() {
it('makeDummy() - the converted key can be parsed', async function() { it('makeDummy() - the converted key can be parsed', async function() {
const { key } = await openpgp.generateKey({ userIDs: { name: 'dummy', email: 'dummy@alice.com' } }); const { key } = await openpgp.generateKey({ userIDs: { name: 'dummy', email: 'dummy@alice.com' } });
key.primaryKey.makeDummy(); key.keyPacket.makeDummy();
const parsedKeys = await openpgp.readKey({ armoredKey: key.armor() }); const parsedKeys = await openpgp.readKey({ armoredKey: key.armor() });
expect(parsedKeys).to.not.be.empty; expect(parsedKeys).to.not.be.empty;
}); });
@ -2993,7 +3005,7 @@ module.exports = () => describe('Key', function() {
it('makeDummy() - the converted key can be encrypted and decrypted', async function() { it('makeDummy() - the converted key can be encrypted and decrypted', async function() {
const { key } = await openpgp.generateKey({ userIDs: { name: 'dummy', email: 'dummy@alice.com' } }); const { key } = await openpgp.generateKey({ userIDs: { name: 'dummy', email: 'dummy@alice.com' } });
const passphrase = 'passphrase'; const passphrase = 'passphrase';
key.primaryKey.makeDummy(); key.keyPacket.makeDummy();
expect(key.isDecrypted()).to.be.true; expect(key.isDecrypted()).to.be.true;
const encryptedKey = await openpgp.encryptKey({ privateKey: key, passphrase }); const encryptedKey = await openpgp.encryptKey({ privateKey: key, passphrase });
expect(encryptedKey.isDecrypted()).to.be.false; expect(encryptedKey.isDecrypted()).to.be.false;
@ -3006,9 +3018,9 @@ module.exports = () => describe('Key', function() {
privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }), privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }),
passphrase: 'hello world' passphrase: 'hello world'
}); });
expect(key.primaryKey.isDummy()).to.be.false; expect(key.keyPacket.isDummy()).to.be.false;
key.primaryKey.makeDummy(); key.keyPacket.makeDummy();
expect(key.primaryKey.isDummy()).to.be.true; expect(key.keyPacket.isDummy()).to.be.true;
await key.validate(); await key.validate();
await expect(openpgp.reformatKey({ privateKey: key, userIDs: { name: 'test', email: 'a@b.com' } })).to.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/); await expect(openpgp.reformatKey({ privateKey: key, userIDs: { name: 'test', email: 'a@b.com' } })).to.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/);
}); });
@ -3018,27 +3030,27 @@ module.exports = () => describe('Key', function() {
privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }), privateKey: await openpgp.readKey({ armoredKey: priv_key_rsa }),
passphrase: 'hello world' passphrase: 'hello world'
}); });
expect(key.primaryKey.isDummy()).to.be.false; expect(key.keyPacket.isDummy()).to.be.false;
key.primaryKey.makeDummy(); key.keyPacket.makeDummy();
expect(key.primaryKey.isDummy()).to.be.true; expect(key.keyPacket.isDummy()).to.be.true;
await expect(openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: [key], config: { minRSABits: 1024 } })).to.be.fulfilled; await expect(openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: [key], config: { minRSABits: 1024 } })).to.be.fulfilled;
}); });
it('makeDummy() - should work for encrypted keys', async function() { it('makeDummy() - should work for encrypted keys', async function() {
const passphrase = 'hello world'; const passphrase = 'hello world';
const key = await openpgp.readKey({ armoredKey: priv_key_rsa }); const key = await openpgp.readKey({ armoredKey: priv_key_rsa });
expect(key.primaryKey.isDummy()).to.be.false; expect(key.keyPacket.isDummy()).to.be.false;
expect(key.primaryKey.makeDummy()).to.not.throw; expect(key.keyPacket.makeDummy()).to.not.throw;
expect(key.primaryKey.isDummy()).to.be.true; expect(key.keyPacket.isDummy()).to.be.true;
// dummy primary key should always be marked as not decrypted // dummy primary key should always be marked as not decrypted
const decryptedKey = await openpgp.decryptKey({ privateKey: key, passphrase }); const decryptedKey = await openpgp.decryptKey({ privateKey: key, passphrase });
expect(decryptedKey.primaryKey.isDummy()).to.be.true; expect(decryptedKey.keyPacket.isDummy()).to.be.true;
expect(decryptedKey.primaryKey.isEncrypted === null); expect(decryptedKey.keyPacket.isEncrypted === null);
expect(decryptedKey.primaryKey.isDecrypted()).to.be.false; expect(decryptedKey.keyPacket.isDecrypted()).to.be.false;
const encryptedKey = await openpgp.encryptKey({ privateKey: decryptedKey, passphrase }); const encryptedKey = await openpgp.encryptKey({ privateKey: decryptedKey, passphrase });
expect(encryptedKey.primaryKey.isDummy()).to.be.true; expect(encryptedKey.keyPacket.isDummy()).to.be.true;
expect(encryptedKey.primaryKey.isEncrypted === null); expect(encryptedKey.keyPacket.isEncrypted === null);
expect(encryptedKey.primaryKey.isDecrypted()).to.be.false; expect(encryptedKey.keyPacket.isDecrypted()).to.be.false;
// confirm that the converted keys can be parsed // confirm that the converted keys can be parsed
await openpgp.readKey({ armoredKey: encryptedKey.armor() }); await openpgp.readKey({ armoredKey: encryptedKey.armor() });
await openpgp.readKey({ armoredKey: decryptedKey.armor() }); await openpgp.readKey({ armoredKey: decryptedKey.armor() });
@ -3061,8 +3073,8 @@ module.exports = () => describe('Key', function() {
const signingKeyPacket = key.subKeys[0].keyPacket; const signingKeyPacket = key.subKeys[0].keyPacket;
const privateParams = signingKeyPacket.privateParams; const privateParams = signingKeyPacket.privateParams;
await key.clearPrivateParams(); await key.clearPrivateParams();
key.primaryKey.isEncrypted = false; key.keyPacket.isEncrypted = false;
key.primaryKey.privateParams = privateParams; key.keyPacket.privateParams = privateParams;
key.subKeys[0].keyPacket.isEncrypted = false; key.subKeys[0].keyPacket.isEncrypted = false;
key.subKeys[0].keyPacket.privateParams = privateParams; key.subKeys[0].keyPacket.privateParams = privateParams;
await expect(key.validate()).to.be.rejectedWith('Key is invalid'); await expect(key.validate()).to.be.rejectedWith('Key is invalid');
@ -3079,8 +3091,8 @@ module.exports = () => describe('Key', function() {
privateParams[name] = value; privateParams[name] = value;
}); });
await key.clearPrivateParams(); await key.clearPrivateParams();
key.primaryKey.isEncrypted = false; key.keyPacket.isEncrypted = false;
key.primaryKey.privateParams = privateParams; key.keyPacket.privateParams = privateParams;
key.subKeys[0].keyPacket.isEncrypted = false; key.subKeys[0].keyPacket.isEncrypted = false;
key.subKeys[0].keyPacket.privateParams = privateParams; key.subKeys[0].keyPacket.privateParams = privateParams;
await expect(key.validate()).to.be.rejectedWith('Key is invalid'); await expect(key.validate()).to.be.rejectedWith('Key is invalid');
@ -3162,11 +3174,11 @@ module.exports = () => describe('Key', function() {
updated.verifyPrimaryKey().then(async result => { updated.verifyPrimaryKey().then(async result => {
await expect(source.verifyPrimaryKey()).to.eventually.equal(result); await expect(source.verifyPrimaryKey()).to.eventually.equal(result);
}), }),
updated.users[0].verify(updated.primaryKey).then(async result => { updated.users[0].verify(updated.keyPacket).then(async result => {
await expect(source.users[0].verify(source.primaryKey)).to.eventually.equal(result); await expect(source.users[0].verify(source.keyPacket)).to.eventually.equal(result);
}), }),
updated.subKeys[0].verify(updated.primaryKey).then(async result => { updated.subKeys[0].verify().then(async result => {
await expect(source.subKeys[0].verify(source.primaryKey)).to.eventually.deep.equal(result); await expect(source.subKeys[0].verify()).to.eventually.deep.equal(result);
}) })
]); ]);
}); });
@ -3182,17 +3194,11 @@ module.exports = () => describe('Key', function() {
const updated = await dest.update(source); const updated = await dest.update(source);
expect(updated.isPrivate()).to.be.true; expect(updated.isPrivate()).to.be.true;
const { selfCertification: destCertification } = await updated.getPrimaryUser(); await updated.verifyPrimaryKey();
const { selfCertification: sourceCertification } = await source.getPrimaryUser(); await source.verifyPrimaryKey();
destCertification.verified = null;
sourceCertification.verified = null;
await updated.verifyPrimaryKey().then(async () => expect(destCertification.verified).to.be.true);
await source.verifyPrimaryKey().then(async () => expect(sourceCertification.verified).to.be.true);
destCertification.verified = null; await updated.users[0].verify(updated.keyPacket);
sourceCertification.verified = null; await source.users[0].verify(source.keyPacket);
await updated.users[0].verify(updated.primaryKey).then(async () => expect(destCertification.verified).to.be.true);
await source.users[0].verify(source.primaryKey).then(async () => expect(sourceCertification.verified).to.be.true);
}); });
it('update() - merge private key into public key - mismatch throws error', async function() { it('update() - merge private key into public key - mismatch throws error', async function() {
@ -3209,11 +3215,14 @@ module.exports = () => describe('Key', function() {
const source = await openpgp.readKey({ armoredKey: pgp_desktop_pub }); const source = await openpgp.readKey({ armoredKey: pgp_desktop_pub });
const dest = await openpgp.readKey({ armoredKey: pgp_desktop_priv }); const dest = await openpgp.readKey({ armoredKey: pgp_desktop_priv });
expect(source.subKeys[0].bindingSignatures[0]).to.exist; expect(source.subKeys[0].bindingSignatures[0]).to.exist;
await source.subKeys[0].verify(source.primaryKey); await source.subKeys[0].verify();
expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist; expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist;
const updated = await dest.update(source); const updated = await dest.update(source);
expect(updated.subKeys[0].bindingSignatures[0]).to.exist; expect(updated.subKeys[0].bindingSignatures[0]).to.exist;
await updated.subKeys[0].verify(source.primaryKey); // the source primary key should still verify the subkey
updated.subKeys[0].mainKey = source;
await updated.subKeys[0].verify();
updated.subKeys[0].mainKey = updated;
}); });
it('update() - merge multiple subkey binding signatures', async function() { it('update() - merge multiple subkey binding signatures', async function() {
@ -3221,12 +3230,12 @@ module.exports = () => describe('Key', function() {
const dest = await openpgp.readKey({ armoredKey: multipleBindingSignatures }); const dest = await openpgp.readKey({ armoredKey: multipleBindingSignatures });
// remove last subkey binding signature of destination subkey // remove last subkey binding signature of destination subkey
dest.subKeys[0].bindingSignatures.length = 1; dest.subKeys[0].bindingSignatures.length = 1;
expect((await source.subKeys[0].getExpirationTime(source.primaryKey)).toISOString()).to.equal('2015-10-18T07:41:30.000Z'); expect((await source.subKeys[0].getExpirationTime()).toISOString()).to.equal('2015-10-18T07:41:30.000Z');
expect((await dest.subKeys[0].getExpirationTime(dest.primaryKey)).toISOString()).to.equal('2018-09-07T06:03:37.000Z'); expect((await dest.subKeys[0].getExpirationTime()).toISOString()).to.equal('2018-09-07T06:03:37.000Z');
return dest.update(source).then(async updated => { return dest.update(source).then(async updated => {
expect(updated.subKeys[0].bindingSignatures.length).to.equal(1); expect(updated.subKeys[0].bindingSignatures.length).to.equal(1);
// destination key gets new expiration date from source key which has newer subkey binding signature // destination key gets new expiration date from source key which has newer subkey binding signature
expect((await updated.subKeys[0].getExpirationTime(updated.primaryKey)).toISOString()).to.equal('2015-10-18T07:41:30.000Z'); expect((await updated.subKeys[0].getExpirationTime()).toISOString()).to.equal('2015-10-18T07:41:30.000Z');
}); });
}); });
@ -3258,7 +3267,7 @@ module.exports = () => describe('Key', function() {
}); });
const subKey = pubKey.subKeys[0]; const subKey = pubKey.subKeys[0];
await subKey.revoke(privKey.primaryKey, { await subKey.revoke(privKey.keyPacket, {
flag: openpgp.enums.reasonForRevocation.keySuperseded flag: openpgp.enums.reasonForRevocation.keySuperseded
}).then(async revKey => { }).then(async revKey => {
expect(revKey.revocationSignatures).to.exist.and.have.length(1); expect(revKey.revocationSignatures).to.exist.and.have.length(1);
@ -3266,8 +3275,8 @@ module.exports = () => describe('Key', function() {
expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.keySuperseded); expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.keySuperseded);
expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal(''); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('');
await subKey.verify(pubKey.primaryKey); await subKey.verify();
await expect(revKey.verify(pubKey.primaryKey)).to.be.rejectedWith('Subkey is revoked'); await expect(revKey.verify()).to.be.rejectedWith('Subkey is revoked');
}); });
}); });
@ -3491,27 +3500,27 @@ VYGdb3eNlV8CfoEC
it('Selects the most recent subkey binding signature', async function() { it('Selects the most recent subkey binding signature', async function() {
const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures }); const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures });
expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2015-10-18T07:41:30.000Z'); expect((await key.subKeys[0].getExpirationTime()).toISOString()).to.equal('2015-10-18T07:41:30.000Z');
}); });
it('Selects the most recent non-expired subkey binding signature', async function() { it('Selects the most recent non-expired subkey binding signature', async function() {
const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures }); const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures });
key.subKeys[0].bindingSignatures[1].signatureNeverExpires = false; key.subKeys[0].bindingSignatures[1].signatureNeverExpires = false;
key.subKeys[0].bindingSignatures[1].signatureExpirationTime = 0; key.subKeys[0].bindingSignatures[1].signatureExpirationTime = 0;
expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2018-09-07T06:03:37.000Z'); expect((await key.subKeys[0].getExpirationTime()).toISOString()).to.equal('2018-09-07T06:03:37.000Z');
}); });
it('Selects the most recent valid subkey binding signature', async function() { it('Selects the most recent valid subkey binding signature', async function() {
const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures }); const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures });
key.subKeys[0].bindingSignatures[1].signatureData[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'); expect((await key.subKeys[0].getExpirationTime()).toISOString()).to.equal('2018-09-07T06:03:37.000Z');
}); });
it('Handles a key with no valid subkey binding signatures gracefully', async function() { it('Handles a key with no valid subkey binding signatures gracefully', async function() {
const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures }); const key = await openpgp.readKey({ armoredKey: multipleBindingSignatures });
key.subKeys[0].bindingSignatures[0].signatureData[0]++; key.subKeys[0].bindingSignatures[0].signatureData[0]++;
key.subKeys[0].bindingSignatures[1].signatureData[0]++; key.subKeys[0].bindingSignatures[1].signatureData[0]++;
expect(await key.subKeys[0].getExpirationTime(key.primaryKey)).to.be.null; expect(await key.subKeys[0].getExpirationTime()).to.be.null;
}); });
it('Reject encryption with revoked primary user', async function() { it('Reject encryption with revoked primary user', async function() {
@ -3590,11 +3599,11 @@ VYGdb3eNlV8CfoEC
expect(subKey).to.exist; expect(subKey).to.exist;
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
const subkeyN = subKey.keyPacket.publicParams.n; const subkeyN = subKey.keyPacket.publicParams.n;
const pkN = privateKey.primaryKey.publicParams.n; const pkN = privateKey.keyPacket.publicParams.n;
expect(subkeyN.length).to.be.equal(pkN.length); expect(subkeyN.length).to.be.equal(pkN.length);
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits); expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits);
await subKey.verify(newPrivateKey.primaryKey); await subKey.verify();
}); });
it('Add a new default subkey to an rsaSign key', async function() { it('Add a new default subkey to an rsaSign key', async function() {
@ -3654,7 +3663,7 @@ VYGdb3eNlV8CfoEC
const subKey = importedPrivateKey.subKeys[total]; const subKey = importedPrivateKey.subKeys[total];
expect(subKey).to.exist; expect(subKey).to.exist;
expect(importedPrivateKey.subKeys.length).to.be.equal(total + 1); expect(importedPrivateKey.subKeys.length).to.be.equal(total + 1);
await subKey.verify(importedPrivateKey.primaryKey); await subKey.verify();
}); });
it('create and add a new ec subkey to a ec key', async function() { it('create and add a new ec subkey to a ec key', async function() {
@ -3677,10 +3686,10 @@ VYGdb3eNlV8CfoEC
expect(subKey2).to.exist; expect(subKey2).to.exist;
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
const subkeyOid = subKey2.keyPacket.publicParams.oid; const subkeyOid = subKey2.keyPacket.publicParams.oid;
const pkOid = privateKey.primaryKey.publicParams.oid; const pkOid = privateKey.keyPacket.publicParams.oid;
expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); expect(subkeyOid.getName()).to.be.equal(pkOid.getName());
expect(subKey2.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); expect(subKey2.getAlgorithmInfo().algorithm).to.be.equal('eddsa');
await subKey2.verify(privateKey.primaryKey); await subKey2.verify();
}); });
it('create and add a new ecdsa subkey to a eddsa key', async function() { it('create and add a new ecdsa subkey to a eddsa key', async function() {
@ -3698,7 +3707,7 @@ VYGdb3eNlV8CfoEC
expect(newPrivateKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); expect(newPrivateKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa');
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdsa'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdsa');
await subKey.verify(privateKey.primaryKey); await subKey.verify();
}); });
it('create and add a new ecc subkey to a rsa key', async function() { it('create and add a new ecc subkey to a rsa key', async function() {
@ -3716,7 +3725,7 @@ VYGdb3eNlV8CfoEC
expect(subKey).to.exist; expect(subKey).to.exist;
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh');
expect(subKey.getAlgorithmInfo().curve).to.be.equal(openpgp.enums.curve.curve25519); expect(subKey.getAlgorithmInfo().curve).to.be.equal(openpgp.enums.curve.curve25519);
await subKey.verify(privateKey.primaryKey); await subKey.verify();
}); });
it('create and add a new rsa subkey to a ecc key', async function() { it('create and add a new rsa subkey to a ecc key', async function() {
@ -3732,7 +3741,7 @@ VYGdb3eNlV8CfoEC
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
expect(subKey.getAlgorithmInfo().bits).to.be.equal(4096); expect(subKey.getAlgorithmInfo().bits).to.be.equal(4096);
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
await subKey.verify(privateKey.primaryKey); await subKey.verify();
}); });
it('create and add a new rsa subkey to a dsa key', async function() { it('create and add a new rsa subkey to a dsa key', async function() {
@ -3745,7 +3754,7 @@ VYGdb3eNlV8CfoEC
expect(subKey).to.exist; expect(subKey).to.exist;
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
expect(subKey.getAlgorithmInfo().bits).to.be.equal(2048); expect(subKey.getAlgorithmInfo().bits).to.be.equal(2048);
await subKey.verify(privateKey.primaryKey); await subKey.verify();
}); });
it('sign/verify data with the new subkey correctly using curve25519', async function() { it('sign/verify data with the new subkey correctly using curve25519', async function() {
@ -3759,10 +3768,10 @@ VYGdb3eNlV8CfoEC
newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey }); newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey });
const subKey = newPrivateKey.subKeys[total]; const subKey = newPrivateKey.subKeys[total];
const subkeyOid = subKey.keyPacket.publicParams.oid; const subkeyOid = subKey.keyPacket.publicParams.oid;
const pkOid = newPrivateKey.primaryKey.publicParams.oid; const pkOid = newPrivateKey.keyPacket.publicParams.oid;
expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); expect(subkeyOid.getName()).to.be.equal(pkOid.getName());
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa');
await subKey.verify(newPrivateKey.primaryKey); await subKey.verify();
expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey);
const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, armor:false }); const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, armor:false });
const message = await openpgp.readMessage({ binaryMessage: signed }); const message = await openpgp.readMessage({ binaryMessage: signed });
@ -3784,7 +3793,7 @@ VYGdb3eNlV8CfoEC
newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey }); newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey });
const subKey = newPrivateKey.subKeys[total]; const subKey = newPrivateKey.subKeys[total];
const publicKey = newPrivateKey.toPublic(); const publicKey = newPrivateKey.toPublic();
await subKey.verify(newPrivateKey.primaryKey); await subKey.verify();
expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey); expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey);
const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: vData }), encryptionKeys: publicKey, armor:false }); const encrypted = await openpgp.encrypt({ message: await openpgp.createMessage({ text: vData }), encryptionKeys: publicKey, armor:false });
expect(encrypted).to.be.exist; expect(encrypted).to.be.exist;
@ -3810,7 +3819,7 @@ VYGdb3eNlV8CfoEC
newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey }); newPrivateKey = await openpgp.readKey({ armoredKey: armoredKey });
const subKey = newPrivateKey.subKeys[total]; const subKey = newPrivateKey.subKeys[total];
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
await subKey.verify(newPrivateKey.primaryKey); await subKey.verify();
expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey);
const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, armor:false }); const signed = await openpgp.sign({ message: await openpgp.createMessage({ text: 'the data to signed' }), signingKeys: newPrivateKey, armor:false });
const message = await openpgp.readMessage({ binaryMessage: signed }); const message = await openpgp.readMessage({ binaryMessage: signed });
@ -3849,12 +3858,12 @@ VYGdb3eNlV8CfoEC
it('Subkey.verify returns the latest valid signature', async function () { it('Subkey.verify returns the latest valid signature', async function () {
const { key: encryptionKey } = await openpgp.generateKey({ userIDs: { name: "purple" } }); const { key: encryptionKey } = await openpgp.generateKey({ userIDs: { name: "purple" } });
const encryptionKeySignature = await encryptionKey.getSubkeys()[0].verify(encryptionKey); const encryptionKeySignature = await encryptionKey.getSubkeys()[0].verify();
expect(encryptionKeySignature instanceof openpgp.SignaturePacket).to.be.true; expect(encryptionKeySignature instanceof openpgp.SignaturePacket).to.be.true;
expect(encryptionKeySignature.keyFlags[0] & openpgp.enums.keyFlags.encryptCommunication).to.be.equals(openpgp.enums.keyFlags.encryptCommunication); expect(encryptionKeySignature.keyFlags[0] & openpgp.enums.keyFlags.encryptCommunication).to.be.equals(openpgp.enums.keyFlags.encryptCommunication);
expect(encryptionKeySignature.keyFlags[0] & openpgp.enums.keyFlags.encryptStorage).to.be.equals(openpgp.enums.keyFlags.encryptStorage); expect(encryptionKeySignature.keyFlags[0] & openpgp.enums.keyFlags.encryptStorage).to.be.equals(openpgp.enums.keyFlags.encryptStorage);
const { key: signingKey } = await openpgp.generateKey({ userIDs: { name: "purple" }, subkeys: [{ sign: true }] }); const { key: signingKey } = await openpgp.generateKey({ userIDs: { name: "purple" }, subkeys: [{ sign: true }] });
const signingKeySignature = await signingKey.getSubkeys()[0].verify(signingKey); const signingKeySignature = await signingKey.getSubkeys()[0].verify();
expect(signingKeySignature instanceof openpgp.SignaturePacket).to.be.true; expect(signingKeySignature instanceof openpgp.SignaturePacket).to.be.true;
expect(signingKeySignature.keyFlags[0] & openpgp.enums.keyFlags.signData).to.be.equals(openpgp.enums.keyFlags.signData); expect(signingKeySignature.keyFlags[0] & openpgp.enums.keyFlags.signData).to.be.equals(openpgp.enums.keyFlags.signData);
}); });

View File

@ -736,6 +736,51 @@ AP9fcXZg/Eo55YB/B5XKLkuzDFwJaTlncrD5jcUgtVXFCg==
=q2yi =q2yi
-----END PGP PRIVATE KEY BLOCK-----`; -----END PGP PRIVATE KEY BLOCK-----`;
const armoredDummyPrivateKey1 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)
lQGqBFERnrMRBADmM0hIfkI3yosjgbWo9v0Lnr3CCE+8KsMszgVS+hBu0XfGraKm
ivcA2aaJimHqVYOP7gEnwFAxHBBpeTJcu5wzCFyJwEYqVeS3nnaIhBPplSF14Duf
i6bB9RV7KxVAg6aunmM2tAutqC+a0y2rDaf7jkJoZ9gWJe2zI+vraD6fiwCgxvHo
3IgULB9RqIqpLoMgXfcjC+cD/1jeJlKRm+n71ryYwT/ECKsspFz7S36z6q3XyS8Q
QfrsUz2p1fbFicvJwIOJ8B20J/N2/nit4P0gBUTUxv3QEa7XCM/56/xrGkyBzscW
AzBoy/AK9K7GN6z13RozuAS60F1xO7MQc6Yi2VU3eASDQEKiyL/Ubf/s/rkZ+sGj
yJizBACtwCbQzA+z9XBZNUat5NPgcZz5Qeh1nwF9Nxnr6pyBv7tkrLh/3gxRGHqG
063dMbUk8pmUcJzBUyRsNiIPDoEUsLjY5zmZZmp/waAhpREsnK29WLCbqLdpUors
c1JJBsObkA1IM8TZY8YUmvsMEvBLCCanuKpclZZXqeRAeOHJ0v4DZQJHTlUBtBZU
ZXN0MiA8dGVzdDJAdGVzdC5jb20+iGIEExECACIFAlERnrMCGwMGCwkIBwMCBhUI
AgkKCwQWAgMBAh4BAheAAAoJEBEnlAPLFp74xc0AoLNZINHe0ytOsNtMCuLvc3Vd
vePUAJ9KX3L5IBqHarsa+aJHX7r796SokZ0BWARREZ6zEAQA2WkxmNbfeMzGUocN
3JEVe0o6rxGt5eGrTSmWisduDP3MURabhUXnf4T8oaeYcbJjkLLxMrJmNq55ln1e
4bSG5mDkh/ryKsV81m3F0DbqO/z/891nRSP5fondFVral4wsMOzBNgs4vVk7V/F2
0MPjR90CIhnVDKPAQbQA+3PjUR8AAwUEALn922AEE+0d7xSMMFpR7ic3Me5QEGnp
cT4ft6oc0UK5kAnvKoksZUc0hpBHjX1w3LTz847/5hRDuuDvwvGMWK8IfsjOF9T7
rK8QtJuBEyJxjoScA/YZP5vX4y0U1reUEa0EdwmVrnZzatMAe2FhlaR9PlHkOcm5
DZwkcExL0dbI/gMDArxZ+5N7kH4zYLtr9glJS/pJ7F0YJqJpNwCbqD8+8DqHD8Uv
MgQ/rtBxBJJOaF+1AjCd123hLgzIkkfdTh8loV9hDXMKeJgmiEkEGBECAAkFAlER
nrMCGwwACgkQESeUA8sWnvhBswCfdXjznvHCc73/6/MhWcv3dbeTT/wAoLyiZg8+
iY3UT9QkV9d0sMgyLkug
=GQsY
-----END PGP PRIVATE KEY BLOCK-----`;
const armoredPublicKey1 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.11 (GNU/Linux)
mQGiBFERlw4RBAD6Bmcf2w1dtUmtCLkdxeqZLArk3vYoQAjdibxA3gXVyur7fsWb
ro0jVbBHqOCtC6jDxE2l52NP9+tTlWeVMaqqNvUE47LSaPq2DGI8Wx1Rj6bF3mTs
obYEwhGbGh/MhJnME9AHODarvk8AZbzo0+k1EwrBWF6dTUBPfqO7rGU2ewCg80WV
x5pt3evj8rRK3jQ8SMKTNRsD/1PhTdxdZTdXARAFzcW1VaaruWW0Rr1+XHKKwDCz
i7HE76SO9qjnQfZCZG75CdQxI0h8GFeN3zsDqmhob2iSz2aJ1krtjM+iZ1FBFd57
OqCV6wmk5IT0RBN12ZzMS19YvzN/ONXHrmTZlKExd9Mh9RKLeVNw+bf6JsKQEzcY
JzFkBACX9X+hDYchO/2hiTwx4iOO9Fhsuh7eIWumB3gt+aUpm1jrSbas/QLTymmk
uZuQVXI4NtnlvzlNgWv4L5s5RU5WqNGG7WSaKNdcrvJZRC2dgbUJt04J5CKrWp6R
aIYal/81Ut1778lU01PEt563TcQnUBlnjU5OR25KhfSeN5CZY7QUVGVzdCA8dGVz
dEB0ZXN0LmNvbT6IYgQTEQIAIgUCURGXDgIbAwYLCQgHAwIGFQgCCQoLBBYCAwEC
HgECF4AACgkQikDlZK/UvLSspgCfcNaOpTg1W2ucR1JwBbBGvaERfuMAnRgt3/rs
EplqEakMckCtikEnpxYe
=b2Ln
-----END PGP PUBLIC KEY BLOCK-----`;
function withCompression(tests) { function withCompression(tests) {
const compressionTypes = Object.keys(openpgp.enums.compression).map(k => openpgp.enums.compression[k]); const compressionTypes = Object.keys(openpgp.enums.compression).map(k => openpgp.enums.compression[k]);
@ -1058,14 +1103,14 @@ module.exports = () => describe('OpenPGP.js public api tests', function() {
}); });
expect(key.isDecrypted()).to.be.true; expect(key.isDecrypted()).to.be.true;
expect(locked.isDecrypted()).to.be.false; expect(locked.isDecrypted()).to.be.false;
expect(locked.primaryKey.isDummy()).to.be.true; expect(locked.keyPacket.isDummy()).to.be.true;
const unlocked = await openpgp.decryptKey({ const unlocked = await openpgp.decryptKey({
privateKey: locked, privateKey: locked,
passphrase: passphrase passphrase: passphrase
}); });
expect(key.isDecrypted()).to.be.true; expect(key.isDecrypted()).to.be.true;
expect(unlocked.isDecrypted()).to.be.true; expect(unlocked.isDecrypted()).to.be.true;
expect(unlocked.primaryKey.isDummy()).to.be.true; expect(unlocked.keyPacket.isDummy()).to.be.true;
}); });
}); });
@ -1246,6 +1291,60 @@ module.exports = () => describe('OpenPGP.js public api tests', function() {
stream.readToEnd(streamedData) stream.readToEnd(streamedData)
).to.be.eventually.rejectedWith(/Could not find signing key/); ).to.be.eventually.rejectedWith(/Could not find signing key/);
}); });
it('Supports decrypting with GnuPG dummy key', async function() {
const { rejectMessageHashAlgorithms } = openpgp.config;
Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) });
try {
const armoredMessage = `-----BEGIN PGP MESSAGE-----
Version: GnuPG v1.4.11 (GNU/Linux)
hQEOA1N4OCSSjECBEAP/diDJCQn4e88193PgqhbfAkohk9RQ0v0MPnXpJbCRTHKO
8r9nxiAr/TQv4ZOingXdAp2JZEoE9pXxZ3r1UWew04czxmgJ8FP1ztZYWVFAWFVi
Tj930TBD7L1fY/MD4fK6xjEG7z5GT8k4tn4mLm/PpWMbarIglfMopTy1M/py2cID
/2Sj7Ikh3UFiG+zm4sViYc5roNbMy8ixeoKixxi99Mx8INa2cxNfqbabjblFyc0Z
BwmbIc+ZiY2meRNI5y/tk0gRD7hT84IXGGl6/mH00bsX/kkWdKGeTvz8s5G8RDHa
Za4HgLbXItkX/QarvRS9kvkD01ujHfj+1ZvgmOBttNfP0p8BQLIICqvg1eYD9aPB
+GtOZ2F3+k5VyBL5yIn/s65SBjNO8Fqs3aL0x+p7s1cfUzx8J8a8nWpqq/qIQIqg
ZJH6MZRKuQwscwH6NWgsSVwcnVCAXnYOpbHxFQ+j7RbF/+uiuqU+DFH/Rd5pik8b
0Dqnp0yfefrkjQ0nuvubgB6Rv89mHpnvuJfFJRInpg4lrHwLvRwdpN2HDozFHcKK
aOU=
=4iGt
-----END PGP MESSAGE-----`;
const passphrase = 'abcd';
// exercises the GnuPG s2k type 1001 extension:
// the secrets on the primary key have been stripped.
const dummyKey = await openpgp.readKey({ armoredKey: armoredDummyPrivateKey1 });
const publicKey = await openpgp.readKey({ armoredKey: armoredPublicKey1 });
const message = await openpgp.readMessage({ armoredMessage });
const primaryKeyPacket = dummyKey.keyPacket.write();
expect(dummyKey.isDecrypted()).to.be.false;
const decryptedDummyKey = await openpgp.decryptKey({ privateKey: dummyKey, passphrase });
expect(decryptedDummyKey.isDecrypted()).to.be.true;
// decrypting with a secret subkey works
const msg = await openpgp.decrypt({
message, decryptionKeys: decryptedDummyKey, verificationKeys: publicKey, config: { rejectPublicKeyAlgorithms: new Set() }
});
expect(msg.signatures).to.exist;
expect(msg.signatures).to.have.length(1);
expect(msg.signatures[0].valid).to.be.true;
expect(msg.signatures[0].signature.packets.length).to.equal(1);
// secret key operations involving the primary key should fail
await expect(openpgp.sign({
message: await openpgp.createMessage({ text: 'test' }), signingKeys: decryptedDummyKey, config: { rejectPublicKeyAlgorithms: new Set() }
})).to.eventually.be.rejectedWith(/Cannot sign with a gnu-dummy key/);
await expect(
openpgp.reformatKey({ userIDs: { name: 'test' }, privateKey: decryptedDummyKey })
).to.eventually.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/);
const encryptedDummyKey = await openpgp.encryptKey({ privateKey: decryptedDummyKey, passphrase });
expect(encryptedDummyKey.isDecrypted()).to.be.false;
const primaryKeyPacket2 = encryptedDummyKey.keyPacket.write();
expect(primaryKeyPacket).to.deep.equal(primaryKeyPacket2);
} finally {
Object.assign(openpgp.config, { rejectMessageHashAlgorithms });
}
});
}); });
describe('verify - unit tests', function() { describe('verify - unit tests', function() {
@ -1262,6 +1361,86 @@ module.exports = () => describe('OpenPGP.js public api tests', function() {
describe('message', function() { describe('message', function() {
verifyTests(false); verifyTests(false);
it('verify should succeed with valid signature (expectSigned=true, with streaming)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await openpgp.createMessage({ text: plaintext }),
signingKeys: privateKey
});
const { data: streamedData, signatures } = await openpgp.verify({
message: await openpgp.readMessage({ armoredMessage: stream.toStream(signed) }),
verificationKeys: publicKey,
expectSigned: true
});
const data = await stream.readToEnd(streamedData);
expect(data).to.equal(plaintext);
expect(await signatures[0].verified).to.be.true;
});
it('verify should throw on missing signature (expectSigned=true, with streaming)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
await expect(openpgp.verify({
message: await openpgp.createMessage({ text: stream.toStream(plaintext) }),
verificationKeys: publicKey,
expectSigned: true
})).to.be.eventually.rejectedWith(/Message is not signed/);
});
it('verify should throw on invalid signature (expectSigned=true, with streaming)', async function () {
const wrongPublicKey = (await openpgp.readKey({ armoredKey: priv_key_2000_2008 })).toPublic();
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await openpgp.createMessage({ text: plaintext }),
signingKeys: privateKey
});
const { data: streamedData } = await openpgp.verify({
message: await openpgp.readMessage({ armoredMessage: stream.toStream(signed) }),
verificationKeys: wrongPublicKey,
expectSigned: true
});
await expect(
stream.readToEnd(streamedData)
).to.be.eventually.rejectedWith(/Could not find signing key/);
});
it('verify should fail if the signature is re-used with a different message', async function () {
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const message = await openpgp.createMessage({ text: 'a message' });
const armoredSignature = await openpgp.sign({
message,
signingKeys: privateKey,
detached: true
});
const { signatures } = await openpgp.verify({
message,
signature: await openpgp.readSignature({ armoredSignature }),
verificationKeys: privateKey.toPublic()
});
expect(await signatures[0].verified).to.be.true;
// pass a different message
await expect(openpgp.verify({
message: await openpgp.createMessage({ text: 'a different message' }),
signature: await openpgp.readSignature({ armoredSignature }),
verificationKeys: privateKey.toPublic(),
expectSigned: true
})).to.be.rejectedWith(/digest did not match/);
});
}); });
describe('cleartext message', function() { describe('cleartext message', function() {
@ -1324,67 +1503,17 @@ module.exports = () => describe('OpenPGP.js public api tests', function() {
expectSigned: true expectSigned: true
})).to.be.eventually.rejectedWith(/Could not find signing key/); })).to.be.eventually.rejectedWith(/Could not find signing key/);
}); });
it('verify should succeed with valid signature (expectSigned=true, with streaming)', async function () {
if (useCleartext) this.skip(); // eslint-disable-line no-invalid-this
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await createMessage({ text }),
signingKeys: privateKey
});
const { data: streamedData, signatures } = await openpgp.verify({
message: await readMessage({ armoredMessage: stream.toStream(signed) }),
verificationKeys: publicKey,
expectSigned: true
});
const data = await stream.readToEnd(streamedData);
expect(data).to.equal(text);
expect(await signatures[0].verified).to.be.true;
});
it('verify should throw on missing signature (expectSigned=true, with streaming)', async function () {
if (useCleartext) this.skip(); // eslint-disable-line no-invalid-this
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
await expect(openpgp.verify({
message: await createMessage({ text: stream.toStream(text) }),
verificationKeys: publicKey,
expectSigned: true
})).to.be.eventually.rejectedWith(/Message is not signed/);
});
it('verify should throw on invalid signature (expectSigned=true, with streaming)', async function () {
if (useCleartext) this.skip(); // eslint-disable-line no-invalid-this
const wrongPublicKey = (await openpgp.readKey({ armoredKey: priv_key_2000_2008 })).toPublic();
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await createMessage({ text }),
signingKeys: privateKey
});
const { data: streamedData } = await openpgp.verify({
message: await readMessage({ armoredMessage: stream.toStream(signed) }),
verificationKeys: wrongPublicKey,
expectSigned: true
});
await expect(
stream.readToEnd(streamedData)
).to.be.eventually.rejectedWith(/Could not find signing key/);
});
} }
}); });
describe('sign - unit tests', function() {
it('Supports signing with GnuPG dummy key', async function() {
const dummyKey = await openpgp.readKey({ armoredKey: gnuDummyKeySigningSubkey });
const sig = await openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), privateKeys: dummyKey, date: new Date('2018-12-17T03:24:00') });
expect(sig).to.match(/-----END PGP MESSAGE-----\n$/);
});
});
describe('encrypt, decrypt, sign, verify - integration tests', function() { describe('encrypt, decrypt, sign, verify - integration tests', function() {
let privateKey_2000_2008; let privateKey_2000_2008;
let publicKey_2000_2008; let publicKey_2000_2008;
@ -3043,7 +3172,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() {
privateKey: await openpgp.readKey({ armoredKey: priv_key_de }), privateKey: await openpgp.readKey({ armoredKey: priv_key_de }),
passphrase passphrase
}); });
return privKeyDE.subKeys[0].revoke(privKeyDE.primaryKey).then(async function(revSubKey) { return privKeyDE.subKeys[0].revoke(privKeyDE.keyPacket).then(async function(revSubKey) {
pubKeyDE.subKeys[0] = revSubKey; pubKeyDE.subKeys[0] = revSubKey;
return openpgp.encrypt({ return openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ text: plaintext }),
@ -3068,7 +3197,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() {
encryptionKeys: pubKeyDE, encryptionKeys: pubKeyDE,
config: { rejectPublicKeyAlgorithms: new Set() } config: { rejectPublicKeyAlgorithms: new Set() }
}); });
privKeyDE.subKeys[0] = await privKeyDE.subKeys[0].revoke(privKeyDE.primaryKey); privKeyDE.subKeys[0] = await privKeyDE.subKeys[0].revoke(privKeyDE.keyPacket);
const decOpt = { const decOpt = {
message: await openpgp.readMessage({ armoredMessage: encrypted }), message: await openpgp.readMessage({ armoredMessage: encrypted }),
decryptionKeys: privKeyDE, decryptionKeys: privKeyDE,
@ -3441,5 +3570,4 @@ bsZgJWVlAa5eil6J9ePX2xbo1vVAkLQdzE9+1jL+l7PRIZuVBQ==
}); });
}); });
}); });
}); });

View File

@ -706,15 +706,13 @@ module.exports = () => describe("Packet", function() {
it('Secret key reading with signature verification.', async function() { it('Secret key reading with signature verification.', async function() {
const packets = await openpgp.PacketList.fromBinary((await openpgp.unarmor(armored_key)).data, allAllowedPackets); const packets = await openpgp.PacketList.fromBinary((await openpgp.unarmor(armored_key)).data, allAllowedPackets);
const [keyPacket, userIDPacket, keySigPacket, subkeyPacket, subkeySigPacket] = packets; const [keyPacket, userIDPacket, keySigPacket, subkeyPacket, subkeySigPacket] = packets;
expect(keySigPacket.verified).to.be.null;
expect(subkeySigPacket.verified).to.be.null;
await keySigPacket.verify( await keySigPacket.verify(
keyPacket, openpgp.enums.signature.certGeneric, { userID: userIDPacket, key: keyPacket } keyPacket, openpgp.enums.signature.certGeneric, { userID: userIDPacket, key: keyPacket }
).then(async () => expect(keySigPacket.verified).to.be.true); );
await subkeySigPacket.verify( await subkeySigPacket.verify(
keyPacket, openpgp.enums.signature.keyBinding, { key: keyPacket, bind: subkeyPacket } keyPacket, openpgp.enums.signature.keyBinding, { key: keyPacket, bind: subkeyPacket }
).then(async () => expect(subkeySigPacket.verified).to.be.true); );
}); });
it('Reading a signed, encrypted message.', async function() { it('Reading a signed, encrypted message.', async function() {

View File

@ -41,34 +41,6 @@ module.exports = () => describe("Signature", function() {
'=LSrW', '=LSrW',
'-----END PGP PRIVATE KEY BLOCK-----'].join("\n"); '-----END PGP PRIVATE KEY BLOCK-----'].join("\n");
const priv_key_arm1_stripped =
['-----BEGIN PGP PRIVATE KEY BLOCK-----',
'Version: GnuPG v1.4.11 (GNU/Linux)',
'',
'lQGqBFERnrMRBADmM0hIfkI3yosjgbWo9v0Lnr3CCE+8KsMszgVS+hBu0XfGraKm',
'ivcA2aaJimHqVYOP7gEnwFAxHBBpeTJcu5wzCFyJwEYqVeS3nnaIhBPplSF14Duf',
'i6bB9RV7KxVAg6aunmM2tAutqC+a0y2rDaf7jkJoZ9gWJe2zI+vraD6fiwCgxvHo',
'3IgULB9RqIqpLoMgXfcjC+cD/1jeJlKRm+n71ryYwT/ECKsspFz7S36z6q3XyS8Q',
'QfrsUz2p1fbFicvJwIOJ8B20J/N2/nit4P0gBUTUxv3QEa7XCM/56/xrGkyBzscW',
'AzBoy/AK9K7GN6z13RozuAS60F1xO7MQc6Yi2VU3eASDQEKiyL/Ubf/s/rkZ+sGj',
'yJizBACtwCbQzA+z9XBZNUat5NPgcZz5Qeh1nwF9Nxnr6pyBv7tkrLh/3gxRGHqG',
'063dMbUk8pmUcJzBUyRsNiIPDoEUsLjY5zmZZmp/waAhpREsnK29WLCbqLdpUors',
'c1JJBsObkA1IM8TZY8YUmvsMEvBLCCanuKpclZZXqeRAeOHJ0v4DZQJHTlUBtBZU',
'ZXN0MiA8dGVzdDJAdGVzdC5jb20+iGIEExECACIFAlERnrMCGwMGCwkIBwMCBhUI',
'AgkKCwQWAgMBAh4BAheAAAoJEBEnlAPLFp74xc0AoLNZINHe0ytOsNtMCuLvc3Vd',
'vePUAJ9KX3L5IBqHarsa+aJHX7r796SokZ0BWARREZ6zEAQA2WkxmNbfeMzGUocN',
'3JEVe0o6rxGt5eGrTSmWisduDP3MURabhUXnf4T8oaeYcbJjkLLxMrJmNq55ln1e',
'4bSG5mDkh/ryKsV81m3F0DbqO/z/891nRSP5fondFVral4wsMOzBNgs4vVk7V/F2',
'0MPjR90CIhnVDKPAQbQA+3PjUR8AAwUEALn922AEE+0d7xSMMFpR7ic3Me5QEGnp',
'cT4ft6oc0UK5kAnvKoksZUc0hpBHjX1w3LTz847/5hRDuuDvwvGMWK8IfsjOF9T7',
'rK8QtJuBEyJxjoScA/YZP5vX4y0U1reUEa0EdwmVrnZzatMAe2FhlaR9PlHkOcm5',
'DZwkcExL0dbI/gMDArxZ+5N7kH4zYLtr9glJS/pJ7F0YJqJpNwCbqD8+8DqHD8Uv',
'MgQ/rtBxBJJOaF+1AjCd123hLgzIkkfdTh8loV9hDXMKeJgmiEkEGBECAAkFAlER',
'nrMCGwwACgkQESeUA8sWnvhBswCfdXjznvHCc73/6/MhWcv3dbeTT/wAoLyiZg8+',
'iY3UT9QkV9d0sMgyLkug',
'=GQsY',
'-----END PGP PRIVATE KEY BLOCK-----'].join("\n");
const pub_key_arm1 = const pub_key_arm1 =
['-----BEGIN PGP PUBLIC KEY BLOCK-----', ['-----BEGIN PGP PUBLIC KEY BLOCK-----',
'Version: GnuPG v1.4.11 (GNU/Linux)', 'Version: GnuPG v1.4.11 (GNU/Linux)',
@ -254,61 +226,6 @@ module.exports = () => describe("Signature", function() {
'=ok+o', '=ok+o',
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); '-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
const pub_expired =
['-----BEGIN PGP PUBLIC KEY BLOCK-----',
'Comment: GPGTools - https://gpgtools.org',
'',
'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',
'2g==',
'=X7/F',
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
const pub_latin1_msg = `-----BEGIN PGP PUBLIC KEY BLOCK----- const pub_latin1_msg = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFS6eEEBEAC56tAm82tgg5BJE0dA4c5UNUDQ7SKLIsleh7TrwsKocEp1b34E mQINBFS6eEEBEAC56tAm82tgg5BJE0dA4c5UNUDQ7SKLIsleh7TrwsKocEp1b34E
@ -663,171 +580,56 @@ Blk+CJ7ytHy6En8542bB/yC+Z9/zWbVuhg==
=jmT1 =jmT1
-----END PGP PUBLIC KEY BLOCK-----`; -----END PGP PUBLIC KEY BLOCK-----`;
const msg_sig_expired = [ const keyExpiredBindingSig = `-----BEGIN PGP PUBLIC KEY BLOCK-----
'-----BEGIN PGP MESSAGE-----',
'Comment: GPGTools - https://gpgtools.org',
'',
'owEBWwKk/ZANAwAKAeyAexA3gWZ0AawUYgloZWxsby50eHRaX2WpaGVsbG+JAjME',
'AAEKAB0WIQTxcIn7ZRrhwd51IZbsgHsQN4FmdAUCWl9lqQAKCRDsgHsQN4FmdCln',
'D/44x1bcrOXg+DbRStSrC75wFa+cvPEmaTZyqN6d7qlQCMxOcPlq6lbZ74QWfEq7',
'i1ZYHp4AU8jALw0QqBQQE5FvABleQKpVfY22s83Bqy+P0DB9ntpD+t+oZrxGCLmL',
'MbZJNFnGro48gHt+/OQKLuftiVwE2opHfgogVKNL74FmYA0hMItdzpn4OPNFkP8t',
'Iq/m0hkXlTAKqBPITVLv1FN16v+Sm1iC317eP/HOTYqVZdJN3svVF8ZBfg29a8p6',
'6nl67fZhXgrt0OB6KSNIZEwMTWjFAqi365mtTssqAA0un94+cQ/WvAC5QcMM8g5S',
'i3G7vny9AsXor+GDU1z7UDWs3wBV4mVRdj7bBIS6PK+6oe012aNpRObcI2bU2BT/',
'H/7uHZWfwEmpfvH9RVZgoeETA3vSx7MDrNyDt3gwv2hxOHEd7nnVQ3EKG33173o1',
'/5/oEmn2USujKGhHJ2Zo3aWNRuUWZlvBaYw+PwB2R0UiuJbi0KofNYPssNdpw4sg',
'Qs7Nb2/Ilo1zn5bDh+WDrUrn6zHKAfBytBPpwPFWPZ8W10HUlC5vMZSKH5/UZhj5',
'kLlUC1zKjFPpRhO27ImTJuImil4lR2/CFjB1duG3JGJQaYIq8RFJOjvTVY29wl0i',
'pFy6y1Ofv2lLHB9K7N7dvvee2nvpUMkLEL52oFQ6Jc7sdg==',
'=Q4tk',
'-----END PGP MESSAGE-----'
].join('\n');
const flowcrypt_stripped_key = [ xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv
'-----BEGIN PGP PRIVATE KEY BLOCK-----', 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', hW1Hj9AO8lzggBQ=
'rfSRYjuZpgO3cIJdDvGXv0vnuF2p9Z83pz3FS3dx33Weiss30pBt5pCvZKT8SAQp', =Nt+N
'ityaxxYtDDb1t0fKmd59DByNfLaHl9pOPIs6adYL8ojFA2Qhd4walTl2+nkuWz9A', -----END PGP PUBLIC KEY BLOCK-----`;
'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',
'=NbaL',
'-----END PGP PRIVATE KEY BLOCK-----'
].join("\n");
const signature_with_critical_notation = `-----BEGIN PGP MESSAGE----- const signature_with_critical_notation = `-----BEGIN PGP MESSAGE-----
@ -848,6 +650,24 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw==
-----END PGP SIGNATURE----- -----END PGP SIGNATURE-----
`; `;
it('Throws when reading a signature missing the creation time', async function () {
const armoredSignature = `-----BEGIN PGP SIGNATURE-----
wsDtBAABCAAXFiEE0aZuGiOxgsmYD3iM+/zIKgFeczAACgkQ+/zIKgFeczDjiwv+
LFUWJohCYtauaVDHBDHWF+tojls+ducY6uuU6iUTBb1969okh2sjUmvPwIjrVXuk
cfPl616xRqVWolEU9T5sG6MjRlAaG31Oo/FVAVFXZn30ldEtuDss12+/IhES3Wfx
M1jGdJZ1ZMZJpRsNBJXBegEBFKbPleEd66seuuFfvoIUbgsdj7IT/ZlEMlixelWW
igXPVY1C05oPbkC8oo0lVSxwdq6gDvm8a52k3GCtXJELrYGH29C+eDqmyLP1zJOt
NBoZBAqMd9XYVrJtuip436D9pdo5pbg4zCE6uPf2zzx4taK7jGkk6nn7LqVDxvQm
3dAXUnIxw4V9eL3V8SFAKwmouUmHPRbjfnQ70hxYQxDXUcIwu1aYn13QS1s/F/jf
DVRZWaAhNdL9BfHfhEsRVsmjMhe0zwRpaepvXnERbnA/lAUHEmEvgfPFz/2GsAo/
kCNcH9WI6idSzFjuYegECf+ZA1xOCjS9oLTGbSeT7jNfC8dH5+E92qlBLq4Ctt7k
=lMU7
-----END PGP SIGNATURE-----`;
await expect(openpgp.readSignature({ armoredSignature })).to.be.rejectedWith(/Missing signature creation time/);
});
it('Testing signature checking on CAST5-enciphered message', async function() { it('Testing signature checking on CAST5-enciphered message', async function() {
const publicKey = await openpgp.readKey({ armoredKey: pub_key_arm1 }); const publicKey = await openpgp.readKey({ armoredKey: pub_key_arm1 });
const privateKey = await openpgp.decryptKey({ const privateKey = await openpgp.decryptKey({
@ -865,50 +685,180 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw==
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
it('Supports decrypting with GnuPG dummy key', async function() { it('Signing fails if primary key is expired', async function() {
const { rejectMessageHashAlgorithms } = openpgp.config; const armoredExpiredKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) });
try {
const passphrase = 'abcd';
// exercises the GnuPG s2k type 1001 extension:
// the secrets on the primary key have been stripped.
const dummyKey = await openpgp.readKey({ armoredKey: priv_key_arm1_stripped });
const publicKey = await openpgp.readKey({ armoredKey: pub_key_arm1 });
const message = await openpgp.readMessage({ armoredMessage: msg_arm1 });
const primaryKeyPacket = dummyKey.primaryKey.write();
expect(dummyKey.isDecrypted()).to.be.false;
const decryptedDummyKey = await openpgp.decryptKey({ privateKey: dummyKey, passphrase });
expect(decryptedDummyKey.isDecrypted()).to.be.true;
// decrypting with a secret subkey works
const msg = await openpgp.decrypt({ message, decryptionKeys: decryptedDummyKey, verificationKeys: publicKey, config: { rejectPublicKeyAlgorithms: new Set() } });
expect(msg.signatures).to.exist;
expect(msg.signatures).to.have.length(1);
expect(msg.signatures[0].valid).to.be.true;
expect(msg.signatures[0].signature.packets.length).to.equal(1);
// secret key operations involving the primary key should fail
await expect(openpgp.sign({
message: await openpgp.createMessage({ text: 'test' }), signingKeys: decryptedDummyKey, config: { rejectPublicKeyAlgorithms: new Set() }
})).to.eventually.be.rejectedWith(/Cannot sign with a gnu-dummy key/);
await expect(
openpgp.reformatKey({ userIDs: { name: 'test' }, privateKey: decryptedDummyKey })
).to.eventually.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/);
const encryptedDummyKey = await openpgp.encryptKey({ privateKey: decryptedDummyKey, passphrase }); xVgEYKKPDRYJKwYBBAHaRw8BAQdAwJcSQMkHVnZPesPJP1JaB9ptV+wG8Io1
expect(encryptedDummyKey.isDecrypted()).to.be.false; vxRKvXQe0wMAAP0fdn6gvpVwFUE4bIRcn9hx6eDxSxUu+tg/t959Oo+iahF1
const primaryKeyPacket2 = encryptedDummyKey.primaryKey.write(); zRB0ZXN0IDx0ZXN0QGEuaXQ+wpIEEBYKACMFAmCijw0FCQAAAAEECwkHCAMV
expect(primaryKeyPacket).to.deep.equal(primaryKeyPacket2); CAoEFgACAQIZAQIbAwIeAQAhCRD16pevybCusRYhBHjm9svlAjmgVWL4wvXq
} finally { l6/JsK6xGUQBAPzxKS2Qs+vWGpxPT2N2T+PLHIgCOxVJVngj4fzREFH1AP9t
Object.assign(openpgp.config, { rejectMessageHashAlgorithms }); wP+fn3eSsik+vFGy93REmlD1xdu7nW/sHuxY4roqBcddBGCijw0SCisGAQQB
} l1UBBQEBB0Cl1lr+aHfy6V4ePmZUULK6VKTCTPTMaPpR2TzKNIJQBQMBCAcA
AP9DZWRqQLCIkF38Q0UC/YXLCDdBEQdnlwpHgA0W1bSWmA3uwn4EGBYIAA8F
AmCijw0FCQAAAAECGwwAIQkQ9eqXr8mwrrEWIQR45vbL5QI5oFVi+ML16pev
ybCusYE4AQCYbXw8ZWoMevbOM7lAttkwyrG3V/nTW6BVo7/M9Pr9swEA0mDI
DQmhI0SZoTKy4EGhS0bNJ+g2+dJ8Y22fKzLWXwo=
=qiIN
-----END PGP PRIVATE KEY BLOCK-----`;
const key = await openpgp.readKey({ armoredKey: armoredExpiredKey });
await expect(openpgp.sign({
signingKeys: key,
message: await openpgp.createMessage({ text: 'Hello World' })
})).to.be.rejectedWith(/key is expired/);
}); });
it('Supports signing with GnuPG dummy key', async function() { it('Signing fails if the signing date is before the key creation date', async function() {
const dummyKey = await openpgp.decryptKey({ const key = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: flowcrypt_stripped_key }), privateKey: await openpgp.readKey({ armoredKey: priv_key_arm2 }),
passphrase: 'FlowCrypt' passphrase: 'hello world'
}); });
const sig = await openpgp.sign({ message: await openpgp.createMessage({ text: 'test' }), signingKeys: dummyKey, date: new Date('2018-12-17T03:24:00') }); await expect(openpgp.sign({
expect(sig).to.match(/-----END PGP MESSAGE-----\n$/); signingKeys: key,
date: new Date(key.keyPacket.created - 3600),
message: await openpgp.createMessage({ text: 'Hello World' })
})).to.be.rejectedWith(/Signature creation time is in the future/);
});
it('Verification fails if primary key binding signature is expired', async function() {
const armoredSignature = `-----BEGIN PGP SIGNATURE-----
wsDzBAABCgAGBYJfLAFsACEJEHwvqk35PDeyFiEEHdzhXwkhfO4vOzdgfC+qTfk8
N7KiqwwAts4QGB7v9bABCC2qkTxJhmStC0wQMcHRcjL/qAiVnmasQWmvE9KVsdm3
AaXd8mIx4a37/RRvr9dYrY2eE4uw72cMqPxNja2tvVXkHQvk1oEUqfkvbXs4ypKI
NyeTWjXNOTZEbg0hbm3nMy+Wv7zgB1CEvAsEboLDJlhGqPcD+X8a6CJGrBGUBUrv
KVmZr3U6vEzClz3DBLpoddCQseJRhT4YM1nKmBlZ5quh2LFgTSpajv5OsZheqt9y
EZAPbqmLhDmWRQwGzkWHKceKS7nZ/ox2WK6OS7Ob8ZGZkM64iPo6/EGj5Yc19vQN
AGiIaPEGszBBWlOpHTPhNm0LB0nMWqqaT87oNYwP8CQuuxDb6rKJ2lffCmZH27Lb
UbQZcH8J+0UhpeaiadPZxH5ATJAcenmVtVVMLVOFnm+eIlxzov9ntpgGYt8hLdXB
ITEG9mMgp3TGS9ZzSifMZ8UGtHdp9QdBg8NEVPFzDOMGxpc/Bftav7RRRuPiAER+
7A5CBid5
=aQkm
-----END PGP SIGNATURE-----`;
const key = await openpgp.readKey({ armoredKey: keyExpiredBindingSig });
const signature = await openpgp.readSignature({ armoredSignature });
const { signatures: [sigInfo] } = await openpgp.verify({
verificationKeys: key,
message: await openpgp.createMessage({ text: 'Hello World :)' }),
signature
});
await expect(sigInfo.verified).to.be.rejectedWith(/Signature is expired/);
});
it('Verification fails if signing key was already expired at the time of signing (one-pass signature, streamed)', async function() {
const armoredMessage = `-----BEGIN PGP MESSAGE-----
xA0DAQgB4IT3RGwgLJcByxR1AGCc8BxIZWxsbyBXb3JsZCA6KcKzBAEBCAAG
BQJgnPAcACEJEOCE90RsICyXFiEE19Gx3sbKlGcANEXF4IT3RGwgLJdssAP+
KpyVi30z5iMckULAQ3Q34IB29Gxa1/99ABSld6iIVGRCfmZZlS5UGcxJJGoL
vZ1RAL4YQx/GLV1dBcKWFwzb5G2/ip4coDMCDGTAwnwjcPwjHpfMQ9gX59mG
AkLaG/AkATpuH+DMkYDmKbDLGgD+N4yuxXBJmBfC2IBe4J1S2Gg=
=zvNP
-----END PGP MESSAGE-----`;
const key = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key_arm2 }),
passphrase: 'hello world'
});
const { key: expiredKey } = await openpgp.reformatKey({
privateKey: key,
userIDs: key.users.map(user => user.userID),
keyExpirationTime: 1,
date: key.keyPacket.created
});
await stream.loadStreamsPonyfill();
const { signatures: [sigInfo] } = await openpgp.verify({
verificationKeys: expiredKey,
message: await openpgp.readMessage({ armoredMessage: stream.toStream(armoredMessage) }),
config: { minRSABits: 1024 }
});
await expect(sigInfo.verified).to.be.rejectedWith(/Primary key is expired/);
});
it('Verification fails if signing key was already expired at the time of signing (standard signature)', async function() {
const armoredMessage = `-----BEGIN PGP MESSAGE-----
wrMEAQEIAAYFAmCc8BwAIQkQ4IT3RGwgLJcWIQTX0bHexsqUZwA0RcXghPdE
bCAsl2ywA/4qnJWLfTPmIxyRQsBDdDfggHb0bFrX/30AFKV3qIhUZEJ+ZlmV
LlQZzEkkagu9nVEAvhhDH8YtXV0FwpYXDNvkbb+KnhygMwIMZMDCfCNw/CMe
l8xD2Bfn2YYCQtob8CQBOm4f4MyRgOYpsMsaAP43jK7FcEmYF8LYgF7gnVLY
aMsUdQBgnPAcSGVsbG8gV29ybGQgOik=
=s9xh
-----END PGP MESSAGE-----`;
const key = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key_arm2 }),
passphrase: 'hello world'
});
const { key: expiredKey } = await openpgp.reformatKey({
privateKey: key,
userIDs: key.users.map(user => user.userID),
keyExpirationTime: 1,
date: key.keyPacket.created
});
await stream.loadStreamsPonyfill();
const { signatures: [sigInfo] } = await openpgp.verify({
verificationKeys: expiredKey,
message: await openpgp.readMessage({ armoredMessage: stream.toStream(armoredMessage) }),
config: { minRSABits: 1024 }
});
await expect(sigInfo.verified).to.be.rejectedWith(/Primary key is expired/);
});
it('Verification succeeds if an expired signing key was valid at the time of signing (with streaming)', async function() {
const armoredMessage = `-----BEGIN PGP MESSAGE-----
xA0DAQgB4IT3RGwgLJcByxF1AGCdJvJoZWxsbyB3b3JsZMKzBAEBCAAGBQJS
YS9OACEJEOCE90RsICyXFiEE19Gx3sbKlGcANEXF4IT3RGwgLJcPBQP/csZd
9AhZQ3+dPkwlqlsXqYMlVEagYDavlUDI2CEJ2cn1rqHBuMlRkmYs7UqODku4
FhJ6WvghiEKx8vqghDuaUXmcKuXhYe+PomD4XBmpbURBXCdPnojTINUj7GPK
eSvSZutLuKKbidSYMLhWROPlwKc2GU2ws6PrLZAyCAel/lU=
=xDib
-----END PGP MESSAGE-----`;
const key = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key_arm2 }),
passphrase: 'hello world'
});
const { key: expiredKey } = await openpgp.reformatKey({
privateKey: key,
userIDs: key.users.map(user => user.userID),
keyExpirationTime: 1,
date: key.keyPacket.created
});
await stream.loadStreamsPonyfill();
const { signatures: [sigInfo] } = await openpgp.verify({
verificationKeys: expiredKey,
message: await openpgp.readMessage({ armoredMessage: stream.toStream(armoredMessage) }),
config: { minRSABits: 1024 }
});
expect(await sigInfo.verified).to.be.true;
});
it('Verification succeeds if an expired signing key was valid at the time of signing (without streaming)', async function() {
const armoredMessage = `-----BEGIN PGP MESSAGE-----
xA0DAQgB4IT3RGwgLJcByxF1AGCdJvJoZWxsbyB3b3JsZMKzBAEBCAAGBQJS
YS9OACEJEOCE90RsICyXFiEE19Gx3sbKlGcANEXF4IT3RGwgLJcPBQP/csZd
9AhZQ3+dPkwlqlsXqYMlVEagYDavlUDI2CEJ2cn1rqHBuMlRkmYs7UqODku4
FhJ6WvghiEKx8vqghDuaUXmcKuXhYe+PomD4XBmpbURBXCdPnojTINUj7GPK
eSvSZutLuKKbidSYMLhWROPlwKc2GU2ws6PrLZAyCAel/lU=
=xDib
-----END PGP MESSAGE-----`;
const key = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key_arm2 }),
passphrase: 'hello world'
});
const { key: expiredKey } = await openpgp.reformatKey({
privateKey: key,
userIDs: key.users.map(user => user.userID),
keyExpirationTime: 1,
date: key.keyPacket.created
});
const { signatures: [sigInfo] } = await openpgp.verify({
verificationKeys: expiredKey,
message: await openpgp.readMessage({ armoredMessage }),
config: { minRSABits: 1024 }
});
expect(await sigInfo.verified).to.be.true;
}); });
it('Supports non-human-readable notations', async function() { it('Supports non-human-readable notations', async function() {
@ -1493,46 +1443,21 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA
}); });
}); });
it('Verify test with expired verification public key', async function() {
const pubKey = await openpgp.readKey({ armoredKey: pub_expired });
const message = await openpgp.readMessage({ armoredMessage: msg_sig_expired });
return openpgp.verify({ verificationKeys:[pubKey], message:message }).then(function(verified) {
expect(verified).to.exist;
expect(verified.signatures).to.have.length(1);
expect(verified.signatures[0].valid).to.be.true;
expect(verified.signatures[0].signature.packets.length).to.equal(1);
});
});
it('Verify test with expired verification public key and disable expiration checks using null date', async function() {
const pubKey = await openpgp.readKey({ armoredKey: pub_expired });
const message = await openpgp.readMessage({ armoredMessage: msg_sig_expired });
return openpgp.verify({ verificationKeys:[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;
expect(verified.signatures[0].signature.packets.length).to.equal(1);
});
});
// TODO add test with multiple revocation signatures // TODO add test with multiple revocation signatures
it('Verify primary key revocation signatures', async function() { it('Verify primary key revocation signatures', async function() {
const pubKey = await openpgp.readKey({ armoredKey: pub_revoked }); const pubKey = await openpgp.readKey({ armoredKey: pub_revoked });
const revSig = pubKey.revocationSignatures[0];
revSig.verified = null;
await pubKey.revocationSignatures[0].verify( await pubKey.revocationSignatures[0].verify(
pubKey.primaryKey, openpgp.enums.signature.keyRevocation, { key: pubKey.primaryKey } pubKey.keyPacket, openpgp.enums.signature.keyRevocation, { key: pubKey.keyPacket }
).then(() => expect(revSig.verified).to.be.true); );
}); });
// TODO add test with multiple revocation signatures // TODO add test with multiple revocation signatures
it('Verify subkey revocation signatures', async function() { it('Verify subkey revocation signatures', async function() {
const pubKey = await openpgp.readKey({ armoredKey: pub_revoked }); const pubKey = await openpgp.readKey({ armoredKey: pub_revoked });
const revSig = pubKey.subKeys[0].revocationSignatures[0]; const revSig = pubKey.subKeys[0].revocationSignatures[0];
revSig.verified = null;
await revSig.verify( await revSig.verify(
pubKey.primaryKey, openpgp.enums.signature.subkeyRevocation, { key: pubKey.primaryKey, bind: pubKey.subKeys[0].keyPacket } pubKey.keyPacket, openpgp.enums.signature.subkeyRevocation, { key: pubKey.keyPacket, bind: pubKey.subKeys[0].keyPacket }
).then(() => expect(revSig.verified).to.be.true); );
}); });
it('Verify key expiration date', async function() { it('Verify key expiration date', async function() {
@ -1698,7 +1623,7 @@ iTuGu4fEU1UligAXSrZmCdE=
const key = await openpgp.readKey({ armoredKey: armoredKeyWithPhoto }); const key = await openpgp.readKey({ armoredKey: armoredKeyWithPhoto });
await Promise.all(key.users.map(async user => { await Promise.all(key.users.map(async user => {
await user.verify(key.primaryKey); await user.verify(key.keyPacket);
})); }));
}); });

View File

@ -389,30 +389,27 @@ function omnibus() {
expect(firstKey.privateKeyArmored).to.exist; expect(firstKey.privateKeyArmored).to.exist;
expect(firstKey.publicKeyArmored).to.exist; expect(firstKey.publicKeyArmored).to.exist;
expect(firstKey.key).to.exist; expect(firstKey.key).to.exist;
expect(firstKey.key.primaryKey).to.exist; expect(firstKey.key.keyPacket).to.exist;
expect(firstKey.key.subKeys).to.have.length(1); expect(firstKey.key.subKeys).to.have.length(1);
expect(firstKey.key.subKeys[0].keyPacket).to.exist; expect(firstKey.key.subKeys[0].keyPacket).to.exist;
const hi = firstKey.key; const hi = firstKey.key;
const primaryKey = hi.primaryKey; const primaryKey = hi.keyPacket;
const subKey = hi.subKeys[0]; const subKey = hi.subKeys[0];
expect(hi.getAlgorithmInfo().curve).to.equal('ed25519'); expect(hi.getAlgorithmInfo().curve).to.equal('ed25519');
expect(hi.getAlgorithmInfo().algorithm).to.equal('eddsa'); expect(hi.getAlgorithmInfo().algorithm).to.equal('eddsa');
expect(subKey.getAlgorithmInfo().curve).to.equal('curve25519'); expect(subKey.getAlgorithmInfo().curve).to.equal('curve25519');
expect(subKey.getAlgorithmInfo().algorithm).to.equal('ecdh'); expect(subKey.getAlgorithmInfo().algorithm).to.equal('ecdh');
// Self Certificate is valid // Verify that self Certificate is valid
const user = hi.users[0]; const user = hi.users[0];
const certificate = user.selfCertifications[0]; const certificate = user.selfCertifications[0];
certificate.verified = null;
await certificate.verify( await certificate.verify(
primaryKey, openpgp.enums.signature.certGeneric, { userID: user.userID, key: primaryKey } primaryKey, openpgp.enums.signature.certGeneric, { userID: user.userID, key: primaryKey }
).then(async () => expect(certificate.verified).to.be.true); );
certificate.verified = null;
await user.verifyCertificate( await user.verifyCertificate(
primaryKey, certificate, [hi.toPublic()], undefined, openpgp.config primaryKey, certificate, [hi.toPublic()], undefined, openpgp.config
).then(async () => expect(certificate.verified).to.be.true); );
const options = { const options = {
userIDs: { name: "Bye", email: "bye@good.bye" }, userIDs: { name: "Bye", email: "bye@good.bye" },
@ -425,27 +422,23 @@ function omnibus() {
expect(bye.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519'); expect(bye.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519');
expect(bye.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); expect(bye.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh');
// Self Certificate is valid // Verify that self Certificate is valid
const user = bye.users[0]; const user = bye.users[0];
const certificate = user.selfCertifications[0]; const certificate = user.selfCertifications[0];
certificate.verified = null;
await certificate.verify( await certificate.verify(
bye.primaryKey, openpgp.enums.signature.certGeneric, { userID: user.userID, key: bye.primaryKey } bye.keyPacket, openpgp.enums.signature.certGeneric, { userID: user.userID, key: bye.keyPacket }
).then(async () => expect(certificate.verified).to.be.true); );
certificate.verified = null;
await user.verifyCertificate( await user.verifyCertificate(
bye.primaryKey, user.selfCertifications[0], [bye.toPublic()], undefined, openpgp.config bye.keyPacket, user.selfCertifications[0], [bye.toPublic()], undefined, openpgp.config
).then(async () => expect(certificate.verified).to.be.true); );
return Promise.all([ return Promise.all([
// Hi trusts Bye! // Hi trusts Bye!
bye.toPublic().signPrimaryUser([hi]).then(trustedBye => { bye.toPublic().signPrimaryUser([hi]).then(trustedBye => {
const hiCertificate = trustedBye.users[0].otherCertifications[0]; const hiCertificate = trustedBye.users[0].otherCertifications[0];
expect(hiCertificate.verified).to.be.true;
hiCertificate.verified = null;
return hiCertificate.verify( return hiCertificate.verify(
primaryKey, openpgp.enums.signature.certGeneric, { userID: user.userID, key: bye.toPublic().primaryKey } primaryKey, openpgp.enums.signature.certGeneric, { userID: user.userID, key: bye.toPublic().keyPacket }
).then(async () => expect(hiCertificate.verified).to.be.true); );
}), }),
// Signing message // Signing message
openpgp.sign( openpgp.sign(
@ -466,22 +459,18 @@ function omnibus() {
]); ]);
}), }),
// Encrypting and signing // Encrypting and signing
openpgp.encrypt( openpgp.encrypt({
{ message: await openpgp.createMessage({ text: 'Hi, Hi wrote this but only Bye can read it!' }),
message: await openpgp.createMessage({ text: 'Hi, Hi wrote this but only Bye can read it!' }), encryptionKeys: [bye.toPublic()],
encryptionKeys: [bye.toPublic()], signingKeys: [hi]
signingKeys: [hi] }).then(async encrypted => {
}
).then(async encrypted => {
const msg = await openpgp.readMessage({ armoredMessage: encrypted }); const msg = await openpgp.readMessage({ armoredMessage: encrypted });
// Decrypting and verifying // Decrypting and verifying
return openpgp.decrypt( return openpgp.decrypt({
{ message: msg,
message: msg, decryptionKeys: bye,
decryptionKeys: bye, verificationKeys: [hi.toPublic()]
verificationKeys: [hi.toPublic()] }).then(output => {
}
).then(output => {
expect(output.data).to.equal('Hi, Hi wrote this but only Bye can read it!'); expect(output.data).to.equal('Hi, Hi wrote this but only Bye can read it!');
expect(output.signatures[0].valid).to.be.true; expect(output.signatures[0].valid).to.be.true;
}); });