From d89cc48bf33f7f9b9bff9f87e0da0f04724d700c Mon Sep 17 00:00:00 2001 From: larabr Date: Wed, 9 Mar 2022 17:00:12 +0100 Subject: TypeScript: rely on new `web-stream-tools` types, fix `SignOptions` (#1502) The updated stream types improve type inference and checks, in particular when using ReadableStreams. Also: - add `EncryptSessionKeyOptions` to make it easier to declare wrapper functions of `encryptSessionKey`; - tighter output type inference in `Message.getText()` and `.getLiteralData()`. --- openpgp.d.ts | 46 ++++++++++++++++++------------------- package-lock.json | 20 +++++++++++----- package.json | 5 ++-- test/typescript/definitions.ts | 52 +++++++++++++++++++++++++++++------------- 4 files changed, 75 insertions(+), 48 deletions(-) diff --git a/openpgp.d.ts b/openpgp.d.ts index 911a78fc..a30cbdc6 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -7,6 +7,8 @@ * - Errietta Kostala */ +import type { WebStream as GenericWebStream, NodeStream as GenericNodeStream } from '@openpgp/web-stream-tools'; + /* ############## v5 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. @@ -171,15 +173,9 @@ export class CleartextMessage { /* ############## v5 MSG #################### */ export function generateSessionKey(options: { encryptionKeys: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig }): Promise; -export function encryptSessionKey(options: SessionKey & { - encryptionKeys?: MaybeArray, passwords?: MaybeArray, format?: 'armored', wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig -}) : Promise; -export function encryptSessionKey(options: SessionKey & { - encryptionKeys?: MaybeArray, passwords?: MaybeArray, format: 'binary', wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig -}) : Promise; -export function encryptSessionKey(options: SessionKey & { - encryptionKeys?: MaybeArray, passwords?: MaybeArray, format: 'object', wildcard?: boolean, encryptionKeyIDs?: MaybeArray, date?: Date, encryptionUserIDs?: MaybeArray, config?: PartialConfig -}) : Promise>; +export function encryptSessionKey(options: EncryptSessionKeyOptions & { format?: 'armored' }): Promise; +export function encryptSessionKey(options: EncryptSessionKeyOptions & { format: 'binary' }): Promise; +export function encryptSessionKey(options: EncryptSessionKeyOptions & { format: 'object' }): Promise>; export function decryptSessionKeys>(options: { message: Message, decryptionKeys?: MaybeArray, passwords?: MaybeArray, date?: Date, config?: PartialConfig }): Promise; export function readMessage>(options: { armoredMessage: T, config?: PartialConfig }): Promise>; @@ -271,7 +267,7 @@ export class Message> { /** Get literal data that is the body of the message */ - public getLiteralData(): MaybeStream | null; + public getLiteralData(): (T extends Stream ? WebStream : Uint8Array) | null; /** Returns the key IDs of the keys that signed the message */ @@ -279,7 +275,7 @@ export class Message> { /** Get literal data as text */ - public getText(): MaybeStream | null; + public getText(): (T extends Stream ? WebStream : string) | null; public getFilename(): string | null; @@ -548,18 +544,10 @@ export class PacketList extends Array { /* ############## v5 STREAM #################### */ type Data = Uint8Array | string; -interface BaseStream extends AsyncIterable { } -interface WebStream extends BaseStream { // copied+simplified version of ReadableStream from lib.dom.d.ts - readonly locked: boolean; getReader: Function; pipeThrough: Function; pipeTo: Function; tee: Function; - cancel(reason?: any): Promise; -} -interface NodeStream extends BaseStream { // copied+simplified version of ReadableStream from @types/node/index.d.ts - readable: boolean; pipe: Function; unpipe: Function; wrap: Function; - read(size?: number): string | Uint8Array; setEncoding(encoding: string): this; pause(): this; resume(): this; - isPaused(): boolean; unshift(chunk: string | Uint8Array): void; -} -type Stream = WebStream | NodeStream; -type MaybeStream = T | Stream; +export interface WebStream extends GenericWebStream {} +export interface NodeStream extends GenericNodeStream {} +export type Stream = WebStream | NodeStream; +export type MaybeStream = T | Stream; /* ############## v5 GENERAL #################### */ type MaybeArray = T | Array; @@ -627,7 +615,7 @@ interface DecryptOptions { interface SignOptions { message: CleartextMessage | Message>; - signingKeys?: MaybeArray; + signingKeys: MaybeArray; format?: 'armored' | 'binary' | 'object'; detached?: boolean; signingKeyIDs?: MaybeArray; @@ -652,6 +640,16 @@ interface VerifyOptions { config?: PartialConfig; } +interface EncryptSessionKeyOptions extends SessionKey { + encryptionKeys?: MaybeArray, + passwords?: MaybeArray, + format?: 'armored' | 'binary' | 'object', + date?: Date, + wildcard?: boolean, + encryptionKeyIDs?: MaybeArray, + encryptionUserIDs?: MaybeArray, + config?: PartialConfig +} interface SerializedKeyPair { privateKey: T; diff --git a/package-lock.json b/package-lock.json index e768a9a1..2c59caa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -267,13 +267,21 @@ "dev": true }, "@openpgp/web-stream-tools": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.9.tgz", - "integrity": "sha512-GEKuXpQRshUqgKH4sMcwYbHolxaUSHIowcIMd02EsnLv4q5acP0z9pRUy3kjV0ZyRPgyD0vXAy60Rf0MPKoCMw==", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.10.tgz", + "integrity": "sha512-1ONZADML0fb0RJR5UiGYPnRf9VaYBYUBc1gF9jyq57sHkr58cp5/BQHS+ivrqbRw21Sb70FKTssmJbRe71V+kw==", "dev": true, "requires": { "@mattiasbuelens/web-streams-adapter": "~0.1.0", "web-streams-polyfill": "~3.0.3" + }, + "dependencies": { + "web-streams-polyfill": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.0.3.tgz", + "integrity": "sha512-d2H/t0eqRNM4w2WvmTdoeIvzAUSpK7JmATB8Nr2lb7nQ9BTIJVjbQ/TRFVEh2gUH1HwclPdoPtfMoFfetXaZnA==", + "dev": true + } } }, "@rollup/plugin-commonjs": { @@ -5401,9 +5409,9 @@ "dev": true }, "web-streams-polyfill": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.0.3.tgz", - "integrity": "sha512-d2H/t0eqRNM4w2WvmTdoeIvzAUSpK7JmATB8Nr2lb7nQ9BTIJVjbQ/TRFVEh2gUH1HwclPdoPtfMoFfetXaZnA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz", + "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==", "dev": true }, "which": { diff --git a/package.json b/package.json index a19b1ba4..a5ccc3ee 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@openpgp/pako": "^1.0.12", "@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/tweetnacl": "^1.0.3", - "@openpgp/web-stream-tools": "0.0.9", + "@openpgp/web-stream-tools": "^0.0.10", "@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-replace": "^2.3.2", @@ -87,7 +87,8 @@ "rollup": "^2.38.5", "rollup-plugin-terser": "^7.0.2", "sinon": "^4.3.0", - "typescript": "^4.1.2" + "typescript": "^4.1.2", + "web-streams-polyfill": "^3.2.0" }, "dependencies": { "asn1.js": "^5.0.0" diff --git a/test/typescript/definitions.ts b/test/typescript/definitions.ts index deeedba0..3bc2f547 100644 --- a/test/typescript/definitions.ts +++ b/test/typescript/definitions.ts @@ -5,6 +5,8 @@ * - if it fails to build, edit the file to match type definitions * - if it fails to run, edit this file to match the actual library API, then edit the definitions file (openpgp.d.ts) accordingly. */ +import { ReadableStream as WebReadableStream } from 'web-streams-polyfill'; +import { createReadStream } from 'fs'; import { expect } from 'chai'; import { @@ -12,7 +14,8 @@ import { readMessage, createMessage, Message, createCleartextMessage, encrypt, decrypt, sign, verify, config, enums, generateSessionKey, encryptSessionKey, decryptSessionKeys, - LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage + LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage, + WebStream, NodeStream, } from '../..'; (async () => { @@ -100,8 +103,10 @@ import { // Get session keys from encrypted message const sessionKeys = await decryptSessionKeys({ message: await readMessage({ binaryMessage: encryptedBinary }), decryptionKeys: privateKeys }); expect(sessionKeys).to.have.length(1); - const encryptedSessionKeys: string = await encryptSessionKey({ ...sessionKeys[0], passwords: 'pass', algorithm: 'aes128', aeadAlgorithm: 'eax' }); - expect(encryptedSessionKeys).to.include('-----BEGIN PGP MESSAGE-----'); + const armoredEncryptedSessionKeys: string = await encryptSessionKey({ ...sessionKeys[0], passwords: 'pass', algorithm: 'aes128', aeadAlgorithm: 'eax' }); + expect(armoredEncryptedSessionKeys).to.include('-----BEGIN PGP MESSAGE-----'); + const encryptedSessionKeys: Message = await encryptSessionKey({ ...sessionKeys[0], passwords: 'pass', algorithm: 'aes128', aeadAlgorithm: 'eax', format: 'object' }); + expect(encryptedSessionKeys).to.be.instanceOf(Message); const newSessionKey = await generateSessionKey({ encryptionKeys: privateKey.toPublic() }); expect(newSessionKey.data).to.exist; expect(newSessionKey.algorithm).to.exist; @@ -180,19 +185,34 @@ import { // const signed = await sign({ privateKeys, message, detached: true, format: 'binary' }); // console.log(signed); // Uint8Array - // // Streaming - encrypt text message on Node.js (armored) - // const data = fs.createReadStream(filename, { encoding: 'utf8' }); - // const message = await createMessage({ text: data }); - // const encrypted = await encrypt({ publicKeys, message }); - // encrypted.on('data', chunk => { - // console.log(chunk); // String - // }); - - // // Streaming - encrypt binary message on Node.js (unarmored) - // const data = fs.createReadStream(filename); - // const message = await createMessage({ binary: data }); - // const encrypted = await encrypt({ publicKeys, message, format: 'binary' }); - // encrypted.pipe(targetStream); + // @ts-expect-error for passing text stream as binary data + await createMessage({ binary: new WebReadableStream() }); + // @ts-expect-error for passing binary stream as text data + await createMessage({ text: new WebReadableStream() }); + + // Streaming - encrypt text message (armored output) + try { + const nodeTextStream = createReadStream('non-existent-file', { encoding: 'utf8' }); + const messageFromNodeTextStream = await createMessage({ text: nodeTextStream }); + (await encrypt({ message: messageFromNodeTextStream, passwords: 'password', format: 'armored' })) as NodeStream; + } catch (err) {} + const webTextStream = new WebReadableStream(); + const messageFromWebTextStream = await createMessage({ text: webTextStream }); + (await encrypt({ message: messageFromWebTextStream, passwords: 'password', format: 'armored' })) as WebStream; + messageFromWebTextStream.getText() as WebStream; + messageFromWebTextStream.getLiteralData() as WebStream; + + // Streaming - encrypt binary message (binary output) + try { + const nodeBinaryStream = createReadStream('non-existent-file'); + const messageFromNodeBinaryStream = await createMessage({ binary: nodeBinaryStream }); + (await encrypt({ message: messageFromNodeBinaryStream, passwords: 'password', format: 'binary' })) as NodeStream; + } catch (err) {} + const webBinaryStream = new WebReadableStream(); + const messageFromWebBinaryStream = await createMessage({ binary: webBinaryStream }); + (await encrypt({ message: messageFromWebBinaryStream, passwords: 'password', format: 'binary' })) as WebStream; + messageFromWebBinaryStream.getText() as WebStream; + messageFromWebBinaryStream.getLiteralData() as WebStream; console.log('TypeScript definitions are correct'); })().catch(e => { -- cgit v1.2.3