Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make PacketList a valid subtype of Array and update Packet.tag type accessor #1289

Merged
merged 8 commits into from Apr 29, 2021
83 changes: 50 additions & 33 deletions openpgp.d.ts
Expand Up @@ -294,7 +294,7 @@ interface PartialConfig extends Partial<Config> {}
/* ############## v5 PACKET #################### */

declare abstract class BasePacket {
public tag: enums.packet;
static readonly tag: enums.packet;
public read(bytes: Uint8Array): void;
public write(): Uint8Array;
}
Expand All @@ -316,14 +316,20 @@ declare abstract class BasePublicKeyPacket extends BasePacket {
public getKeyID(): KeyID;
public isDecrypted(): boolean;
public publicParams: object;
// `isSubkey` is a dummy method to ensure that Subkey packets are not accepted as Key one, and vice versa.
// The key class hierarchy is already modelled to cover this, but the concrete key packet classes
// have compatible structure and TS can't detect the difference.
protected isSubkey(): boolean;
}

export class PublicKeyPacket extends BasePublicKeyPacket {
public tag: enums.packet.publicKey;
static readonly tag: enums.packet.publicKey;
protected isSubkey(): false;
}

export class PublicSubkeyPacket extends BasePublicKeyPacket {
public tag: enums.packet.publicSubkey;
static readonly tag: enums.packet.publicSubkey;
protected isSubkey(): true;
}

declare abstract class BaseSecretKeyPacket extends BasePublicKeyPacket {
Expand All @@ -336,56 +342,78 @@ declare abstract class BaseSecretKeyPacket extends BasePublicKeyPacket {
}

export class SecretKeyPacket extends BaseSecretKeyPacket {
public tag: enums.packet.secretKey;
static readonly tag: enums.packet.secretKey;
protected isSubkey(): false;
}

export class SecretSubkeyPacket extends BaseSecretKeyPacket {
public tag: enums.packet.secretSubkey;
static readonly tag: enums.packet.secretSubkey;
larabr marked this conversation as resolved.
Show resolved Hide resolved
protected isSubkey(): true;
}

export class CompressedDataPacket extends BasePacket {
public tag: enums.packet.compressedData;
static readonly tag: enums.packet.compressedData;
private compress(): void;
private decompress(): void;
}

export class SymEncryptedIntegrityProtectedDataPacket extends BasePacket {
public tag: enums.packet.symEncryptedIntegrityProtectedData;
static readonly tag: enums.packet.symEncryptedIntegrityProtectedData;
}

export class AEADEncryptedDataPacket extends BasePacket {
public tag: enums.packet.aeadEncryptedData;
static readonly tag: enums.packet.aeadEncryptedData;
private decrypt(sessionKeyAlgorithm: string, sessionKey: Uint8Array): void;
private encrypt(sessionKeyAlgorithm: string, sessionKey: Uint8Array, config?: Config): void;
private crypt(fn: Function, sessionKey: Uint8Array, data: MaybeStream<Uint8Array>): MaybeStream<Uint8Array>
}

export class PublicKeyEncryptedSessionKeyPaclet extends BasePacket {
public tag: enums.packet.publicKeyEncryptedSessionKey;
static readonly tag: enums.packet.publicKeyEncryptedSessionKey;
private decrypt(keyPacket: SecretKeyPacket): Promise<true>; // throws on error
private encrypt(keyPacket: PublicKeyPacket): Promise<true>; // throws on error
}

export class SymEncryptedSessionKey extends BasePacket {
public tag: enums.packet.symEncryptedSessionKey;
static readonly tag: enums.packet.symEncryptedSessionKey;
private decrypt(passphrase: string): Promise<void>;
private encrypt(passphrase: string, config?: Config): Promise<void>;
}

export class LiteralDataPacket extends BasePacket {
public tag: enums.packet.literalData;
static readonly tag: enums.packet.literalData;
private getText(clone?: boolean): MaybeStream<string>;
private getBytes(clone?: boolean): MaybeStream<Uint8Array>;
private setText(text: MaybeStream<string>, format?: DataPacketType);
private setBytes(bytes: MaybeStream<Uint8Array>, format?: DataPacketType);
private setFilename(filename: string);
private getFilename(): string;
private writeHeader(): Uint8Array;
}

export class SymmetricallyEncryptedDataPacket extends BasePacket {
public tag: enums.packet.symmetricallyEncryptedData;
static readonly tag: enums.packet.symmetricallyEncryptedData;
private decrypt(sessionKeyAlgorithm: enums.symmetric, sessionKey: Uint8Array, config?: Config): void;
private encrypt(sessionKeyAlgorithm: enums.symmetric, sessionKey: Uint8Array, config?: Config): void;
}

export class MarkerPacket extends BasePacket {
public tag: enums.packet.marker;
static readonly tag: enums.packet.marker;
}

export class UserAttributePacket extends BasePacket {
public tag: enums.packet.userAttribute;
static readonly tag: enums.packet.userAttribute;
private equals(packet: UserAttributePacket): boolean;
}

export class OnePassSignaturePacket extends BasePacket {
public tag: enums.packet.onePassSignature;
static readonly tag: enums.packet.onePassSignature;
public correspondingSig?: Promise<SignaturePacket>;
private verify: SignaturePacket['verify'];
}

export class UserIDPacket extends BasePacket {
public readonly tag: enums.packet.userID;
static readonly tag: enums.packet.userID;
public readonly name: string;
public readonly comment: string;
public readonly email: string;
Expand All @@ -394,7 +422,7 @@ export class UserIDPacket extends BasePacket {
}

export class SignaturePacket extends BasePacket {
public tag: enums.packet.signature;
static readonly tag: enums.packet.signature;
public version: number;
public signatureType: enums.signature | null;
public hashAlgorithm: enums.hash | null;
Expand Down Expand Up @@ -445,7 +473,7 @@ export class SignaturePacket extends BasePacket {
}

export class TrustPacket extends BasePacket {
public tag: enums.packet.trust;
static readonly tag: enums.packet.trust;
}

export type AnyPacket = BasePacket;
Expand All @@ -455,24 +483,13 @@ export type AnyKeyPacket = BasePublicKeyPacket;
type DataPacketType = 'utf8' | 'binary' | 'text' | 'mime';


export class PacketList<PACKET_TYPE> extends Array<PACKET_TYPE> {
[index: number]: PACKET_TYPE;
export class PacketList<T extends AnyPacket> extends Array<T> {
public length: number;
public read(bytes: Uint8Array, allowedPackets?: object, config?: Config): void;
public write(): Uint8Array;
public push(...packet: PACKET_TYPE[]): number;
public pop(): PACKET_TYPE;
public filter(callback: (packet: PACKET_TYPE, i: number, self: PacketList<PACKET_TYPE>) => void): PacketList<PACKET_TYPE>;
public filterByTag(...args: enums.packet[]): PacketList<PACKET_TYPE>;
public forEach(callback: (packet: PACKET_TYPE, i: number, self: PacketList<PACKET_TYPE>) => void): void;
public map<RETURN_TYPE>(callback: (packet: PACKET_TYPE, i: number, self: PacketList<PACKET_TYPE>) => RETURN_TYPE): PacketList<RETURN_TYPE>;
// some()
// every()
// findPacket()
// indexOfTag()
// slice()
// concat()
// fromStructuredClone()
public filterByTag(...args: enums.packet[]): PacketList<T>;
public indexOfTag(...tags: enums.packet[]): number[];
public findPacket(tag: enums.packet): T | undefined;
}

/* ############## v5 STREAM #################### */
Expand Down
8 changes: 4 additions & 4 deletions src/key/key.js
Expand Up @@ -150,10 +150,10 @@ class Key {
toPacketlist() {
const packetlist = new PacketList();
packetlist.push(this.keyPacket);
packetlist.concat(this.revocationSignatures);
packetlist.concat(this.directSignatures);
this.users.map(user => packetlist.concat(user.toPacketlist()));
this.subKeys.map(subKey => packetlist.concat(subKey.toPacketlist()));
packetlist.push(...this.revocationSignatures);
packetlist.push(...this.directSignatures);
this.users.map(user => packetlist.push(...user.toPacketlist()));
this.subKeys.map(subKey => packetlist.push(...subKey.toPacketlist()));
return packetlist;
}

Expand Down
4 changes: 2 additions & 2 deletions src/key/subkey.js
Expand Up @@ -34,8 +34,8 @@ class SubKey {
toPacketlist() {
const packetlist = new PacketList();
packetlist.push(this.keyPacket);
packetlist.concat(this.revocationSignatures);
packetlist.concat(this.bindingSignatures);
packetlist.push(...this.revocationSignatures);
packetlist.push(...this.bindingSignatures);
return packetlist;
}

Expand Down
6 changes: 3 additions & 3 deletions src/key/user.js
Expand Up @@ -30,9 +30,9 @@ class User {
toPacketlist() {
const packetlist = new PacketList();
packetlist.push(this.userID || this.userAttribute);
packetlist.concat(this.revocationSignatures);
packetlist.concat(this.selfCertifications);
packetlist.concat(this.otherCertifications);
packetlist.push(...this.revocationSignatures);
packetlist.push(...this.selfCertifications);
packetlist.push(...this.otherCertifications);
return packetlist;
}

Expand Down
16 changes: 8 additions & 8 deletions src/message.js
Expand Up @@ -170,7 +170,7 @@ export class Message {
let exception;
if (passwords) {
const symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
if (!symESKeyPacketlist) {
if (symESKeyPacketlist.length === 0) {
throw new Error('No symmetrically encrypted session key packet found.');
}
await Promise.all(passwords.map(async function(password, i) {
Expand All @@ -192,7 +192,7 @@ export class Message {
}));
} else if (privateKeys) {
const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
if (!pkESKeyPacketlist) {
if (pkESKeyPacketlist.length === 0) {
throw new Error('No public key encrypted session key packet found.');
}
await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) {
Expand Down Expand Up @@ -385,7 +385,7 @@ export class Message {
delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption
return pkESKeyPacket;
}));
packetlist.concat(results);
packetlist.push(...results);
}
if (passwords) {
const testDecrypt = async function(keyPacket, password) {
Expand Down Expand Up @@ -420,7 +420,7 @@ export class Message {
};

const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, algorithm, aeadAlgorithm, pwd)));
packetlist.concat(results);
packetlist.push(...results);
}

return new Message(packetlist);
Expand Down Expand Up @@ -487,7 +487,7 @@ export class Message {
});

packetlist.push(literalDataPacket);
packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, false, config));
packetlist.push(...(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, false, config)));

return new Message(packetlist);
}
Expand Down Expand Up @@ -551,7 +551,7 @@ export class Message {
throw new Error('Can only verify message with one literal data packet.');
}
if (stream.isArrayStream(msg.packets.stream)) {
msg.packets.concat(await stream.readToEnd(msg.packets.stream, _ => _));
msg.packets.push(...await stream.readToEnd(msg.packets.stream, _ => _ || []));
}
const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse();
const signatureList = msg.packets.filterByTag(enums.packet.signature);
Expand Down Expand Up @@ -686,7 +686,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig

if (signature) {
const existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
packetlist.concat(existingSigPacketlist);
packetlist.push(...existingSigPacketlist);
}
return packetlist;
}
Expand Down Expand Up @@ -752,7 +752,7 @@ async function createVerificationObject(signature, literalDataList, keys, date =
signature: (async () => {
const sig = await signaturePacket;
const packetlist = new PacketList();
packetlist.push(sig);
sig && packetlist.push(sig);
return new Signature(packetlist);
})()
};
Expand Down
2 changes: 2 additions & 0 deletions src/packet/aead_encrypted_data.js
Expand Up @@ -25,6 +25,7 @@ import LiteralDataPacket from './literal_data';
import CompressedDataPacket from './compressed_data';
import OnePassSignaturePacket from './one_pass_signature';
import SignaturePacket from './signature';
import PacketList from './packetlist';

// An AEAD-encrypted Data packet can contain the following packet types
const allowedPackets = /*#__PURE__*/ util.constructAllowedPackets([
Expand Down Expand Up @@ -93,6 +94,7 @@ class AEADEncryptedDataPacket {
* @async
*/
async decrypt(sessionKeyAlgorithm, key) {
this.packets = new PacketList();
await this.packets.read(await this.crypt('decrypt', key, stream.clone(this.encrypted)), allowedPackets);
}

Expand Down
2 changes: 2 additions & 0 deletions src/packet/compressed_data.js
Expand Up @@ -27,6 +27,7 @@ import defaultConfig from '../config';
import LiteralDataPacket from './literal_data';
import OnePassSignaturePacket from './one_pass_signature';
import SignaturePacket from './signature';
import PacketList from './packetlist';

// A Compressed Data packet can contain the following packet types
const allowedPackets = /*#__PURE__*/ util.constructAllowedPackets([
Expand Down Expand Up @@ -116,6 +117,7 @@ class CompressedDataPacket {
throw new Error(this.algorithm + ' decompression not supported');
}

this.packets = new PacketList();
await this.packets.read(decompress_fns[this.algorithm](this.compressed), allowedPackets);
}

Expand Down
26 changes: 15 additions & 11 deletions src/packet/marker.js
Expand Up @@ -28,33 +28,37 @@ import enums from '../enums';
* tag. With PGP 5.x, this packet has been reassigned and is reserved for use as
* the Marker packet.
*
* Such a packet MUST be ignored when received.
* The body of this packet consists of:
* The three octets 0x50, 0x47, 0x50 (which spell "PGP" in UTF-8).
*
* Such a packet MUST be ignored when received. It may be placed at the
* beginning of a message that uses features not available in PGP
* version 2.6 in order to cause that version to report that newer
* software is necessary to process the message.
*/
class MarkerPacket {
static get tag() {
return enums.packet.marker;
}

/**
* Parsing function for a literal data packet (tag 10).
*
* @param {String} input - Payload of a tag 10 packet
* @param {Integer} position
* Position to start reading from the input string
* @param {Integer} len
* Length of the packet or the remaining length of
* input at position
* @returns {MarkerPacket} Object representation.
* Parsing function for a marker data packet (tag 10).
* @param {Uint8Array} bytes - Payload of a tag 10 packet
* @returns {Boolean} whether the packet payload contains "PGP"
*/
read(bytes) {
if (bytes[0] === 0x50 && // P
bytes[1] === 0x47 && // G
bytes[2] === 0x50) { // P
return true;
}
// marker packet does not contain "PGP"
return false;
}

// eslint-disable-next-line class-methods-use-this
write() {
return new Uint8Array([0x50, 0x47, 0x50]);
}
}

export default MarkerPacket;