Skip to content

Commit

Permalink
Make PacketList a valid subtype of Array and update Packet.tag
Browse files Browse the repository at this point in the history
…types (#1289)

Changes:
- Implementation:
  - Remove `PacketList.prototype.concat` and `push`
    (we solely rely on `Array.push` instead)
  - Fix #907 by
    correctly handling result of `filterByTag`
  - Implement `write()` method for `Trust` and `Marker` packets,
    to make them compatible with the `BasePacket` interface
- Types:
  - Simplify and updated `PacketList` type definitions
  - Fix types for `Packet.tag`, which is `static` since
    #1268
  - Prevent passing SubkeyPackets where KeyPackets are expected,
    and vice versa
  • Loading branch information
larabr committed Apr 29, 2021
1 parent 2d07c43 commit aeddac4
Show file tree
Hide file tree
Showing 17 changed files with 185 additions and 150 deletions.
83 changes: 50 additions & 33 deletions openpgp.d.ts
Expand Up @@ -292,7 +292,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 @@ -314,14 +314,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 @@ -334,56 +340,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;
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 @@ -392,7 +420,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 @@ -443,7 +471,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 @@ -453,24 +481,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;

0 comments on commit aeddac4

Please sign in to comment.