Drop support for native Node Readable stream: require passing Node Web Streams (#1716)

Breaking change: all functions taking streams as inputs will now require passing Web Streams in Node.js . If given a native `stream.Readable` input, they will throw. The browser build is unaffected by this change.

Utils to convert from and to Web Streams in Node are available from v17,
see https://nodejs.org/api/stream.html#streamreadabletowebstreamreadable-options .
Previously, we automatically converted between Node native streams and custom, Web-like Readable streams.
This led to occasional issues.
This commit is contained in:
larabr 2024-01-26 17:52:29 +01:00 committed by GitHub
parent 591b9399a8
commit 99899d1d5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 155 additions and 175 deletions

View File

@ -20,6 +20,8 @@ jobs:
ref: main
path: main
- uses: actions/setup-node@v3
with:
node-version: '>=20.6.0'
- name: Run pull request time benchmark
run: cd pr && npm install && npm run --silent benchmark-time > benchmarks.txt && cat benchmarks.txt

75
openpgp.d.ts vendored
View File

@ -7,9 +7,19 @@
* - Errietta Kostala <https://github.com/errietta>
*/
import type { WebStream as GenericWebStream, NodeStream as GenericNodeStream } from '@openpgp/web-stream-tools';
import type { WebStream as GenericWebStream, NodeWebStream as GenericNodeWebStream } from '@openpgp/web-stream-tools';
/* ############## v5 KEY #################### */
/* ############## STREAM #################### */
type Data = Uint8Array | string;
// web-stream-tools might end up supporting additional data types, so we re-declare the types
// to enforce the type contraint that we need.
export type WebStream<T extends Data> = GenericWebStream<T>;
export type NodeWebStream<T extends Data> = GenericNodeWebStream<T>;
export type Stream<T extends Data> = WebStream<T> | NodeWebStream<T>;
export type MaybeStream<T extends Data> = T | Stream<T>;
type MaybeArray<T> = T | Array<T>;
/* ############## KEY #################### */
// The Key and PublicKey types can be used interchangably since TS cannot detect the difference, as they have the same class properties.
// The declared readKey(s) return type is Key instead of a PublicKey since it seems more obvious that a Key can be cast to a PrivateKey.
export function readKey(options: { armoredKey: string, config?: PartialConfig }): Promise<Key>;
@ -118,13 +128,13 @@ export interface PrimaryUser {
selfCertification: SignaturePacket;
}
type AlgorithmInfo = {
export type AlgorithmInfo = {
algorithm: enums.publicKeyNames;
bits?: number;
curve?: EllipticCurveName;
};
/* ############## v5 SIG #################### */
/* ############## SIG #################### */
export function readSignature(options: { armoredSignature: string, config?: PartialConfig }): Promise<Signature>;
export function readSignature(options: { binarySignature: Uint8Array, config?: PartialConfig }): Promise<Signature>;
@ -143,7 +153,7 @@ interface VerificationResult {
signature: Promise<Signature>;
}
/* ############## v5 CLEARTEXT #################### */
/* ############## CLEARTEXT #################### */
export function readCleartextMessage(options: { cleartextMessage: string, config?: PartialConfig }): Promise<CleartextMessage>;
@ -176,7 +186,7 @@ export class CleartextMessage {
verify(keys: PublicKey[], date?: Date, config?: Config): Promise<VerificationResult[]>;
}
/* ############## v5 MSG #################### */
/* ############## MSG #################### */
export function generateSessionKey(options: { encryptionKeys: MaybeArray<PublicKey>, date?: Date, encryptionUserIDs?: MaybeArray<UserID>, config?: PartialConfig }): Promise<SessionKey>;
export function encryptSessionKey(options: EncryptSessionKeyOptions & { format?: 'armored' }): Promise<string>;
export function encryptSessionKey(options: EncryptSessionKeyOptions & { format: 'binary' }): Promise<Uint8Array>;
@ -191,24 +201,24 @@ export function createMessage<T extends MaybeStream<Uint8Array>>(options: { bina
export function encrypt<T extends MaybeStream<Data>>(options: EncryptOptions & { message: Message<T>, format?: 'armored' }): Promise<
T extends WebStream<infer X> ? WebStream<string> :
T extends NodeStream<infer X> ? NodeStream<string> :
T extends NodeWebStream<infer X> ? NodeWebStream<string> :
string
>;
export function encrypt<T extends MaybeStream<Data>>(options: EncryptOptions & { message: Message<T>, format: 'binary' }): Promise<
T extends WebStream<infer X> ? WebStream<Uint8Array> :
T extends NodeStream<infer X> ? NodeStream<Uint8Array> :
T extends NodeWebStream<infer X> ? NodeWebStream<Uint8Array> :
Uint8Array
>;
export function encrypt<T extends MaybeStream<Data>>(options: EncryptOptions & { message: Message<T>, format: 'object' }): Promise<Message<T>>;
export function sign<T extends MaybeStream<Data>>(options: SignOptions & { message: Message<T>, format?: 'armored' }): Promise<
T extends WebStream<infer X> ? WebStream<string> :
T extends NodeStream<infer X> ? NodeStream<string> :
T extends NodeWebStream<infer X> ? NodeWebStream<string> :
string
>;
export function sign<T extends MaybeStream<Data>>(options: SignOptions & { message: Message<T>, format: 'binary' }): Promise<
T extends WebStream<infer X> ? WebStream<Uint8Array> :
T extends NodeStream<infer X> ? NodeStream<Uint8Array> :
T extends NodeWebStream<infer X> ? NodeWebStream<Uint8Array> :
Uint8Array
>;
export function sign<T extends MaybeStream<Data>>(options: SignOptions & { message: Message<T>, format: 'object' }): Promise<Message<T>>;
@ -218,25 +228,25 @@ export function sign(options: SignOptions & { message: CleartextMessage, format:
export function decrypt<T extends MaybeStream<Data>>(options: DecryptOptions & { message: Message<T>, format: 'binary' }): Promise<DecryptMessageResult & {
data:
T extends WebStream<infer X> ? WebStream<Uint8Array> :
T extends NodeStream<infer X> ? NodeStream<Uint8Array> :
T extends NodeWebStream<infer X> ? NodeWebStream<Uint8Array> :
Uint8Array
}>;
export function decrypt<T extends MaybeStream<Data>>(options: DecryptOptions & { message: Message<T> }): Promise<DecryptMessageResult & {
data:
T extends WebStream<infer X> ? WebStream<string> :
T extends NodeStream<infer X> ? NodeStream<string> :
T extends NodeWebStream<infer X> ? NodeWebStream<string> :
string
}>;
export function verify(options: VerifyOptions & { message: CleartextMessage, format?: 'utf8' }): Promise<VerifyMessageResult<string>>;
export function verify<T extends MaybeStream<Data>>(options: VerifyOptions & { message: Message<T>, format: 'binary' }): Promise<VerifyMessageResult<
T extends WebStream<infer X> ? WebStream<Uint8Array> :
T extends NodeStream<infer X> ? NodeStream<Uint8Array> :
T extends NodeWebStream<infer X> ? NodeWebStream<Uint8Array> :
Uint8Array
>>;
export function verify<T extends MaybeStream<Data>>(options: VerifyOptions & { message: Message<T> }): Promise<VerifyMessageResult<
T extends WebStream<infer X> ? WebStream<string> :
T extends NodeStream<infer X> ? NodeStream<string> :
T extends NodeWebStream<infer X> ? NodeWebStream<string> :
string
>>;
@ -305,7 +315,7 @@ export class Message<T extends MaybeStream<Data>> {
}
/* ############## v5 CONFIG #################### */
/* ############## CONFIG #################### */
interface Config {
preferredHashAlgorithm: enums.hash;
@ -347,11 +357,11 @@ export var config: Config;
// PartialConfig has the same properties as Config, but declared as optional.
// This interface is relevant for top-level functions, which accept a subset of configuration options
interface PartialConfig extends Partial<Config> {}
export interface PartialConfig extends Partial<Config> {}
/* ############## v5 PACKET #################### */
/* ############## PACKET #################### */
declare abstract class BasePacket {
export declare abstract class BasePacket {
static readonly tag: enums.packet;
public read(bytes: Uint8Array): void;
public write(): Uint8Array;
@ -561,16 +571,7 @@ export class PacketList<T extends AnyPacket> extends Array<T> {
public findPacket(tag: enums.packet): T | undefined;
}
/* ############## v5 STREAM #################### */
type Data = Uint8Array | string;
export interface WebStream<T extends Data> extends GenericWebStream<T> {}
export interface NodeStream<T extends Data> extends GenericNodeStream<T> {}
export type Stream<T extends Data> = WebStream<T> | NodeStream<T>;
export type MaybeStream<T extends Data> = T | Stream<T>;
/* ############## v5 GENERAL #################### */
type MaybeArray<T> = T | Array<T>;
/* ############## GENERAL #################### */
export interface UserID { name?: string; email?: string; comment?: string; }
export interface SessionKey {
@ -586,7 +587,7 @@ export interface DecryptedSessionKey {
export interface ReasonForRevocation { flag?: enums.reasonForRevocation, string?: string }
interface EncryptOptions {
export interface EncryptOptions {
/** message to be encrypted as created by createMessage */
message: Message<MaybeStream<Data>>;
/** (optional) array of keys or single key, used to encrypt the message */
@ -618,7 +619,7 @@ interface EncryptOptions {
config?: PartialConfig;
}
interface DecryptOptions {
export interface DecryptOptions {
/** the message object with the encrypted data */
message: Message<MaybeStream<Data>>;
/** (optional) private keys with decrypted secret key data or session key */
@ -640,7 +641,7 @@ interface DecryptOptions {
config?: PartialConfig;
}
interface SignOptions {
export interface SignOptions {
message: CleartextMessage | Message<MaybeStream<Data>>;
signingKeys: MaybeArray<PrivateKey>;
format?: 'armored' | 'binary' | 'object';
@ -652,7 +653,7 @@ interface SignOptions {
config?: PartialConfig;
}
interface VerifyOptions {
export interface VerifyOptions {
/** (cleartext) message object with signatures */
message: CleartextMessage | Message<MaybeStream<Data>>;
/** array of publicKeys or single key, to verify signatures */
@ -668,7 +669,7 @@ interface VerifyOptions {
config?: PartialConfig;
}
interface EncryptSessionKeyOptions extends SessionKey {
export interface EncryptSessionKeyOptions extends SessionKey {
encryptionKeys?: MaybeArray<PublicKey>,
passwords?: MaybeArray<string>,
format?: 'armored' | 'binary' | 'object',
@ -704,7 +705,7 @@ interface GenerateKeyOptions {
}
export type KeyOptions = GenerateKeyOptions;
interface SubkeyOptions {
export interface SubkeyOptions {
type?: 'ecc' | 'rsa';
curve?: EllipticCurveName;
rsaBits?: number;
@ -714,20 +715,20 @@ interface SubkeyOptions {
config?: PartialConfig;
}
declare class KeyID {
export declare class KeyID {
bytes: string;
equals(keyID: KeyID, matchWildcard?: boolean): boolean;
toHex(): string;
static fromID(hex: string): KeyID;
}
interface DecryptMessageResult {
export interface DecryptMessageResult {
data: MaybeStream<Data>;
signatures: VerificationResult[];
filename: string;
}
interface VerifyMessageResult<T extends MaybeStream<Data> = MaybeStream<Data>> {
export interface VerifyMessageResult<T extends MaybeStream<Data> = MaybeStream<Data>> {
data: T;
signatures: VerificationResult[];
}

97
package-lock.json generated
View File

@ -18,10 +18,10 @@
"@openpgp/noble-hashes": "^1.3.3-0",
"@openpgp/seek-bzip": "^1.0.5-git",
"@openpgp/tweetnacl": "^1.0.4-1",
"@openpgp/web-stream-tools": "^0.0.14",
"@openpgp/web-stream-tools": "~0.1.1",
"@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-wasm": "^6.1.2",
@ -52,11 +52,11 @@
"rollup": "^3.29.4",
"sinon": "^15.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.1.2",
"typescript": "^5.3.3",
"web-streams-polyfill": "^3.2.0"
},
"engines": {
"node": ">= 16.0.0"
"node": ">= 16.5.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@ -630,10 +630,13 @@
"dev": true
},
"node_modules/@openpgp/web-stream-tools": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.14.tgz",
"integrity": "sha512-6btCNVf6YSsmlyIS7yw+IbzXeXCEcJxeSpxvSxkDuZj9B/ekt4fXkZj4oOaIxG4SKTftIK1svnlVroJ1cCMT4g==",
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.1.1.tgz",
"integrity": "sha512-1WkV+z78S8DJNlUiCPxSjsna6gfAGCVuepL2KUDlBztXzhvplwWr4lAvsWcYzFkHykaFoCSOV9ssiRRq7QzydQ==",
"dev": true,
"engines": {
"node": ">= 16.5.0"
},
"peerDependencies": {
"typescript": ">=4.2"
},
@ -729,9 +732,9 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz",
"integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==",
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
@ -745,7 +748,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0"
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@ -1004,10 +1007,13 @@
"dev": true
},
"node_modules/@types/node": {
"version": "13.13.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz",
"integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==",
"dev": true
"version": "20.11.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz",
"integrity": "sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.3",
@ -3145,9 +3151,9 @@
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
@ -6486,16 +6492,16 @@
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
"node": ">=14.17"
}
},
"node_modules/ua-parser-js": {
@ -6544,6 +6550,12 @@
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
"dev": true
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
@ -7247,9 +7259,9 @@
"dev": true
},
"@openpgp/web-stream-tools": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.14.tgz",
"integrity": "sha512-6btCNVf6YSsmlyIS7yw+IbzXeXCEcJxeSpxvSxkDuZj9B/ekt4fXkZj4oOaIxG4SKTftIK1svnlVroJ1cCMT4g==",
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.1.1.tgz",
"integrity": "sha512-1WkV+z78S8DJNlUiCPxSjsna6gfAGCVuepL2KUDlBztXzhvplwWr4lAvsWcYzFkHykaFoCSOV9ssiRRq7QzydQ==",
"dev": true,
"requires": {}
},
@ -7310,9 +7322,9 @@
}
},
"@rollup/plugin-node-resolve": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz",
"integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==",
"version": "15.2.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
"integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^5.0.1",
@ -7531,10 +7543,13 @@
"dev": true
},
"@types/node": {
"version": "13.13.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz",
"integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==",
"dev": true
"version": "20.11.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz",
"integrity": "sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g==",
"dev": true,
"requires": {
"undici-types": "~5.26.4"
}
},
"@types/normalize-package-data": {
"version": "2.4.3",
@ -9186,9 +9201,9 @@
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"optional": true
},
@ -11718,9 +11733,9 @@
}
},
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"dev": true
},
"ua-parser-js": {
@ -11753,6 +11768,12 @@
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==",
"dev": true
},
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"union": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",

View File

@ -5,7 +5,7 @@
"license": "LGPL-3.0+",
"homepage": "https://openpgpjs.org/",
"engines": {
"node": ">= 16.0.0"
"node": ">= 16.5.0"
},
"keywords": [
"crypto",
@ -68,10 +68,10 @@
"@openpgp/noble-hashes": "^1.3.3-0",
"@openpgp/seek-bzip": "^1.0.5-git",
"@openpgp/tweetnacl": "^1.0.4-1",
"@openpgp/web-stream-tools": "^0.0.14",
"@openpgp/web-stream-tools": "~0.1.1",
"@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@rollup/plugin-wasm": "^6.1.2",
@ -102,7 +102,7 @@
"rollup": "^3.29.4",
"sinon": "^15.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.1.2",
"typescript": "^5.3.3",
"web-streams-polyfill": "^3.2.0"
},
"dependencies": {

View File

@ -287,7 +287,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords,
if (!signingKeys) {
signingKeys = [];
}
const streaming = message.fromStream;
try {
if (signingKeys.length || signature) { // sign the message only if signing keys or signature is specified
message = await message.sign(signingKeys, signature, signingKeyIDs, date, signingUserIDs, signatureNotations, config);
@ -301,7 +301,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords,
// serialize data
const armor = format === 'armored';
const data = armor ? message.armor(config) : message.write();
return convertStream(data, streaming, armor ? 'utf8' : 'binary');
return convertStream(data);
} catch (err) {
throw util.wrapError('Error encrypting message', err);
}
@ -372,7 +372,7 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys,
})
]);
}
result.data = await convertStream(result.data, message.fromStream, format);
result.data = await convertStream(result.data);
return result;
} catch (err) {
throw util.wrapError('Error decrypting message', err);
@ -438,7 +438,7 @@ export async function sign({ message, signingKeys, format = 'armored', detached
]);
});
}
return convertStream(signature, message.fromStream, armor ? 'utf8' : 'binary');
return convertStream(signature);
} catch (err) {
throw util.wrapError('Error signing message', err);
}
@ -501,7 +501,7 @@ export async function verify({ message, verificationKeys, expectSigned = false,
})
]);
}
result.data = await convertStream(result.data, message.fromStream, format);
result.data = await convertStream(result.data);
return result;
} catch (err) {
throw util.wrapError('Error verifying signed message', err);
@ -672,22 +672,15 @@ function toArray(param) {
/**
* Convert data to or from Stream
* @param {Object} data - the data to convert
* @param {'web'|'node'|false} streaming - Whether to return a ReadableStream, and of what type
* @param {'utf8'|'binary'} [encoding] - How to return data in Node Readable streams
* @returns {Promise<Object>} The data in the respective format.
* @async
* @private
*/
async function convertStream(data, streaming, encoding = 'utf8') {
async function convertStream(data) {
const streamType = util.isStream(data);
if (streamType === 'array') {
return stream.readToEnd(data);
}
if (streaming === 'node') {
data = stream.webToNode(data);
if (encoding !== 'binary') data.setEncoding(encoding);
return data;
}
return data;
}

View File

@ -143,29 +143,10 @@ export default CompressedDataPacket;
// //
//////////////////////////
const nodeZlib = util.getNodeZlib();
function uncompressed(data) {
return data;
}
function node_zlib(func, create, options = {}) {
return function (data) {
if (!util.isStream(data) || stream.isArrayStream(data)) {
return stream.fromAsync(() => stream.readToEnd(data).then(data => {
return new Promise((resolve, reject) => {
func(data, options, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
}));
}
return stream.nodeToWeb(stream.webToNode(data).pipe(create(options)));
};
}
function fflate_zlib(ZlibStreamedConstructor, options) {
return data => {
if (!util.isStream(data) || stream.isArrayStream(data)) {
@ -216,20 +197,12 @@ function bzip2(func) {
};
}
const compress_fns = nodeZlib ? {
zip: /*#__PURE__*/ (compressed, level) => node_zlib(nodeZlib.deflateRaw, nodeZlib.createDeflateRaw, { level })(compressed),
zlib: /*#__PURE__*/ (compressed, level) => node_zlib(nodeZlib.deflate, nodeZlib.createDeflate, { level })(compressed)
} : {
const compress_fns = {
zip: /*#__PURE__*/ (compressed, level) => fflate_zlib(Deflate, { level })(compressed),
zlib: /*#__PURE__*/ (compressed, level) => fflate_zlib(Zlib, { level })(compressed)
};
const decompress_fns = nodeZlib ? {
uncompressed: uncompressed,
zip: /*#__PURE__*/ node_zlib(nodeZlib.inflateRaw, nodeZlib.createInflateRaw),
zlib: /*#__PURE__*/ node_zlib(nodeZlib.inflate, nodeZlib.createInflate),
bzip2: /*#__PURE__*/ bzip2(BunzipDecode)
} : {
const decompress_fns = {
uncompressed: uncompressed,
zip: /*#__PURE__*/ fflate_zlib(Inflate),
zlib: /*#__PURE__*/ fflate_zlib(Unzlib),

View File

@ -173,7 +173,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 }));
const plaintextMessage = await openpgp.createMessage({ binary: inputStream });
assert(plaintextMessage.fromStream);
@ -183,10 +183,8 @@ class MemoryBenchamrkSuite {
assert.ok(encryptedMessage.packets[1].version === 1);
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
suite.add('openpgp.encrypt/decrypt (CFB, text, with streaming)', async () => {
@ -199,7 +197,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 }));
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
assert(plaintextMessage.fromStream);
@ -209,10 +207,8 @@ class MemoryBenchamrkSuite {
assert.ok(encryptedMessage.packets[1].version === 1);
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
suite.add('openpgp.encrypt/decrypt (AEAD, binary, with streaming)', async () => {
@ -225,7 +221,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: new Uint8Array(ONE_MEGABYTE), numberOfChunks: 1 }));
const plaintextMessage = await openpgp.createMessage({ binary:inputStream });
assert(plaintextMessage.fromStream);
@ -235,10 +231,8 @@ class MemoryBenchamrkSuite {
assert.ok(encryptedMessage.packets[1].version === 2);
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
suite.add('openpgp.encrypt/decrypt (AEAD, text, with streaming)', async () => {
@ -251,7 +245,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 1 }));
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
assert(plaintextMessage.fromStream);
@ -261,10 +255,8 @@ class MemoryBenchamrkSuite {
assert.ok(encryptedMessage.packets[1].version === 2);
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with streaming)', async () => {
@ -277,7 +269,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
assert(plaintextMessage.fromStream);
@ -287,10 +279,8 @@ class MemoryBenchamrkSuite {
assert.ok(encryptedMessage.packets[1].version === 1);
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
suite.add('openpgp.encrypt/decrypt (CFB, text @ 10MB, with unauthenticated streaming)', async () => {
@ -303,7 +293,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: false, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
assert(plaintextMessage.fromStream);
@ -317,10 +307,8 @@ class MemoryBenchamrkSuite {
config: { ...config, allowUnauthenticatedStream: true }
});
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
suite.add('openpgp.encrypt/decrypt (AEAD, text @ 10MB, with streaming)', async () => {
@ -333,7 +321,7 @@ class MemoryBenchamrkSuite {
const passwords = 'password';
const config = { aeadProtect: true, preferredCompressionAlgorithm: openpgp.enums.compression.uncompressed };
const inputStream = require('stream').Readable.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
const inputStream = require('stream/web').ReadableStream.from(largeDataGenerator({ chunk: 'a'.repeat(ONE_MEGABYTE / 2), numberOfChunks: 20 }));
const plaintextMessage = await openpgp.createMessage({ text: inputStream });
assert(plaintextMessage.fromStream);
@ -343,10 +331,8 @@ class MemoryBenchamrkSuite {
assert.ok(encryptedMessage.packets[1].version === 2);
const { data: decryptedData } = await openpgp.decrypt({ message: encryptedMessage, passwords, config });
// read out output stream to trigger decryption
await new Promise(resolve => {
decryptedData.pipe(require('fs').createWriteStream('/dev/null'));
decryptedData.on('end', resolve);
});
const sink = require('stream').Writable.toWeb(require('fs').createWriteStream('/dev/null'));
await decryptedData.pipeTo(sink);
});
const stats = await suite.run();

View File

@ -3840,9 +3840,7 @@ XfA3pqV4mTzF
packets.push(message.packets.findPacket(openpgp.enums.packet.signature));
packets.push(message.packets.findPacket(openpgp.enums.packet.literalData));
verifyOpt.message = await openpgp.readMessage({
binaryMessage: stream[
globalThis.ReadableStream ? 'toStream' : 'webToNode'
](packets.write())
binaryMessage: stream.toStream(packets.write())
});
return openpgp.verify(verifyOpt);
}).then(async function (verified) {

View File

@ -416,7 +416,7 @@ function tests() {
expect(stream.isStream(encrypted)).to.equal(expectedType);
const message = await openpgp.readMessage({
armoredMessage: stream[expectedType === 'node' ? 'webToNode' : 'toStream'](stream.transform(encrypted, value => {
armoredMessage: stream.toStream(stream.transform(encrypted, value => {
value += '';
const newlineIndex = value.indexOf('\n', 500);
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex);
@ -453,7 +453,7 @@ function tests() {
expect(stream.isStream(encrypted)).to.equal(expectedType);
const message = await openpgp.readMessage({
armoredMessage: stream[expectedType === 'node' ? 'webToNode' : 'toStream'](stream.transform(encrypted, value => {
armoredMessage: stream.toStream(stream.transform(encrypted, value => {
value += '';
const newlineIndex = value.indexOf('\n', 500);
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex);
@ -486,7 +486,7 @@ function tests() {
expect(stream.isStream(signed)).to.equal(expectedType);
const message = await openpgp.readMessage({
armoredMessage: stream[expectedType === 'node' ? 'webToNode' : 'toStream'](stream.transform(signed, value => {
armoredMessage: stream.toStream(stream.transform(signed, value => {
value += '';
const newlineIndex = value.indexOf('\n', 500);
if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex);
@ -872,6 +872,7 @@ function tests() {
export default () => describe('Streaming', function() {
let currentTest = 0;
const needsStreamPolyfills = !globalThis.ReadableStream;
before(async function() {
pubKey = await openpgp.readKey({ armoredKey: pub_key });
@ -916,40 +917,41 @@ export default () => describe('Streaming', function() {
tests();
if (detectNode()) {
if (detectNode() && !needsStreamPolyfills) { // ReadableStream polyfills interfere with these tests
const fs = util.nodeRequire('fs');
const { Readable: NodeReadableStream } = util.nodeRequire('stream');
const { fileURLToPath } = util.nodeRequire('url');
const __filename = fileURLToPath(import.meta.url);
it('Node: Encrypt and decrypt text message roundtrip', async function() {
dataArrived(); // Do not wait until data arrived.
const plaintext = fs.readFileSync(__filename.replace('streaming.js', 'openpgp.js'), 'utf8'); // eslint-disable-line no-sync
const data = fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'), { encoding: 'utf8' });
const data = NodeReadableStream.toWeb(fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'), { encoding: 'utf8' }));
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: data }),
passwords: ['test']
});
expect(stream.isStream(encrypted)).to.equal('node');
expect(stream.isStream(encrypted)).to.equal('web');
const message = await openpgp.readMessage({ armoredMessage: encrypted });
const decrypted = await openpgp.decrypt({
passwords: ['test'],
message
});
expect(stream.isStream(decrypted.data)).to.equal('node');
expect(stream.isStream(decrypted.data)).to.equal('web');
expect(await stream.readToEnd(decrypted.data)).to.equal(plaintext);
});
it('Node: Encrypt and decrypt binary message roundtrip', async function() {
dataArrived(); // Do not wait until data arrived.
const plaintext = fs.readFileSync(__filename.replace('streaming.js', 'openpgp.js')); // eslint-disable-line no-sync
const data = fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'));
const data = NodeReadableStream.toWeb(fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js')));
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ binary: data }),
passwords: ['test'],
format: 'binary'
});
expect(stream.isStream(encrypted)).to.equal('node');
expect(stream.isStream(encrypted)).to.equal('web');
const message = await openpgp.readMessage({ binaryMessage: encrypted });
const decrypted = await openpgp.decrypt({
@ -957,7 +959,7 @@ export default () => describe('Streaming', function() {
message,
format: 'binary'
});
expect(stream.isStream(decrypted.data)).to.equal('node');
expect(stream.isStream(decrypted.data)).to.equal('web');
expect(await stream.readToEnd(decrypted.data)).to.deep.equal(plaintext);
});
}

View File

@ -7,6 +7,7 @@
*/
import { ReadableStream as WebReadableStream } from 'web-streams-polyfill';
import { createReadStream } from 'fs';
import { Readable as NodeNativeReadableStream } from 'stream';
import { expect } from 'chai';
import {
@ -15,7 +16,7 @@ import {
encrypt, decrypt, sign, verify, config, enums,
generateSessionKey, encryptSessionKey, decryptSessionKeys,
LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage,
WebStream, NodeStream,
WebStream, NodeWebStream,
} from 'openpgp';
(async () => {
@ -207,9 +208,9 @@ import {
// Streaming - encrypt text message (armored output)
try {
const nodeTextStream = createReadStream('non-existent-file', { encoding: 'utf8' });
const nodeTextStream = NodeNativeReadableStream.toWeb(createReadStream('non-existent-file', { encoding: 'utf8' }));
const messageFromNodeTextStream = await createMessage({ text: nodeTextStream });
(await encrypt({ message: messageFromNodeTextStream, passwords: 'password', format: 'armored' })) as NodeStream<string>;
(await encrypt({ message: messageFromNodeTextStream, passwords: 'password', format: 'armored' })) as NodeWebStream<string>;
} catch (err) {}
const webTextStream = new WebReadableStream<string>();
const messageFromWebTextStream = await createMessage({ text: webTextStream });
@ -219,9 +220,9 @@ import {
// Streaming - encrypt binary message (binary output)
try {
const nodeBinaryStream = createReadStream('non-existent-file');
const nodeBinaryStream = NodeNativeReadableStream.toWeb(createReadStream('non-existent-file'));
const messageFromNodeBinaryStream = await createMessage({ binary: nodeBinaryStream });
(await encrypt({ message: messageFromNodeBinaryStream, passwords: 'password', format: 'binary' })) as NodeStream<Uint8Array>;
(await encrypt({ message: messageFromNodeBinaryStream, passwords: 'password', format: 'binary' })) as NodeWebStream<Uint8Array>;
} catch (err) {}
const webBinaryStream = new WebReadableStream<Uint8Array>();
const messageFromWebBinaryStream = await createMessage({ binary: webBinaryStream });

View File

@ -27,7 +27,10 @@ globalThis.tryTests = function(name, tests, options) {
};
globalThis.loadStreamsPolyfill = function() {
return import('web-streams-polyfill');
// do not polyfill Node
const detectNodeWebStreams = () => typeof globalThis.process === 'object' && typeof globalThis.process.versions === 'object' && globalThis.ReadableStream;
return detectNodeWebStreams() || import('web-streams-polyfill');
};
import runWorkerTests from './worker';