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()`.
This commit is contained in:
parent
a1ef5f509f
commit
d89cc48bf3
46
openpgp.d.ts
vendored
46
openpgp.d.ts
vendored
|
@ -7,6 +7,8 @@
|
||||||
* - Errietta Kostala <https://github.com/errietta>
|
* - Errietta Kostala <https://github.com/errietta>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { WebStream as GenericWebStream, NodeStream as GenericNodeStream } from '@openpgp/web-stream-tools';
|
||||||
|
|
||||||
/* ############## v5 KEY #################### */
|
/* ############## 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 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.
|
// 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 #################### */
|
/* ############## v5 MSG #################### */
|
||||||
export function generateSessionKey(options: { encryptionKeys: MaybeArray<PublicKey>, date?: Date, encryptionUserIDs?: MaybeArray<UserID>, config?: PartialConfig }): Promise<SessionKey>;
|
export function generateSessionKey(options: { encryptionKeys: MaybeArray<PublicKey>, date?: Date, encryptionUserIDs?: MaybeArray<UserID>, config?: PartialConfig }): Promise<SessionKey>;
|
||||||
export function encryptSessionKey(options: SessionKey & {
|
export function encryptSessionKey(options: EncryptSessionKeyOptions & { format?: 'armored' }): Promise<string>;
|
||||||
encryptionKeys?: MaybeArray<PublicKey>, passwords?: MaybeArray<string>, format?: 'armored', wildcard?: boolean, encryptionKeyIDs?: MaybeArray<KeyID>, date?: Date, encryptionUserIDs?: MaybeArray<UserID>, config?: PartialConfig
|
export function encryptSessionKey(options: EncryptSessionKeyOptions & { format: 'binary' }): Promise<Uint8Array>;
|
||||||
}) : Promise<string>;
|
export function encryptSessionKey(options: EncryptSessionKeyOptions & { format: 'object' }): Promise<Message<Data>>;
|
||||||
export function encryptSessionKey(options: SessionKey & {
|
|
||||||
encryptionKeys?: MaybeArray<PublicKey>, passwords?: MaybeArray<string>, format: 'binary', wildcard?: boolean, encryptionKeyIDs?: MaybeArray<KeyID>, date?: Date, encryptionUserIDs?: MaybeArray<UserID>, config?: PartialConfig
|
|
||||||
}) : Promise<Uint8Array>;
|
|
||||||
export function encryptSessionKey(options: SessionKey & {
|
|
||||||
encryptionKeys?: MaybeArray<PublicKey>, passwords?: MaybeArray<string>, format: 'object', wildcard?: boolean, encryptionKeyIDs?: MaybeArray<KeyID>, date?: Date, encryptionUserIDs?: MaybeArray<UserID>, config?: PartialConfig
|
|
||||||
}) : Promise<Message<Data>>;
|
|
||||||
export function decryptSessionKeys<T extends MaybeStream<Data>>(options: { message: Message<T>, decryptionKeys?: MaybeArray<PrivateKey>, passwords?: MaybeArray<string>, date?: Date, config?: PartialConfig }): Promise<SessionKey[]>;
|
export function decryptSessionKeys<T extends MaybeStream<Data>>(options: { message: Message<T>, decryptionKeys?: MaybeArray<PrivateKey>, passwords?: MaybeArray<string>, date?: Date, config?: PartialConfig }): Promise<SessionKey[]>;
|
||||||
|
|
||||||
export function readMessage<T extends MaybeStream<string>>(options: { armoredMessage: T, config?: PartialConfig }): Promise<Message<T>>;
|
export function readMessage<T extends MaybeStream<string>>(options: { armoredMessage: T, config?: PartialConfig }): Promise<Message<T>>;
|
||||||
|
@ -271,7 +267,7 @@ export class Message<T extends MaybeStream<Data>> {
|
||||||
|
|
||||||
/** Get literal data that is the body of the message
|
/** Get literal data that is the body of the message
|
||||||
*/
|
*/
|
||||||
public getLiteralData(): MaybeStream<Uint8Array> | null;
|
public getLiteralData(): (T extends Stream<Data> ? WebStream<Uint8Array> : Uint8Array) | null;
|
||||||
|
|
||||||
/** Returns the key IDs of the keys that signed the message
|
/** Returns the key IDs of the keys that signed the message
|
||||||
*/
|
*/
|
||||||
|
@ -279,7 +275,7 @@ export class Message<T extends MaybeStream<Data>> {
|
||||||
|
|
||||||
/** Get literal data as text
|
/** Get literal data as text
|
||||||
*/
|
*/
|
||||||
public getText(): MaybeStream<string> | null;
|
public getText(): (T extends Stream<Data> ? WebStream<string> : string) | null;
|
||||||
|
|
||||||
public getFilename(): string | null;
|
public getFilename(): string | null;
|
||||||
|
|
||||||
|
@ -548,18 +544,10 @@ export class PacketList<T extends AnyPacket> extends Array<T> {
|
||||||
/* ############## v5 STREAM #################### */
|
/* ############## v5 STREAM #################### */
|
||||||
|
|
||||||
type Data = Uint8Array | string;
|
type Data = Uint8Array | string;
|
||||||
interface BaseStream<T extends Data> extends AsyncIterable<T> { }
|
export interface WebStream<T extends Data> extends GenericWebStream<T> {}
|
||||||
interface WebStream<T extends Data> extends BaseStream<T> { // copied+simplified version of ReadableStream from lib.dom.d.ts
|
export interface NodeStream<T extends Data> extends GenericNodeStream<T> {}
|
||||||
readonly locked: boolean; getReader: Function; pipeThrough: Function; pipeTo: Function; tee: Function;
|
export type Stream<T extends Data> = WebStream<T> | NodeStream<T>;
|
||||||
cancel(reason?: any): Promise<void>;
|
export type MaybeStream<T extends Data> = T | Stream<T>;
|
||||||
}
|
|
||||||
interface NodeStream<T extends Data> extends BaseStream<T> { // 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<T extends Data> = WebStream<T> | NodeStream<T>;
|
|
||||||
type MaybeStream<T extends Data> = T | Stream<T>;
|
|
||||||
|
|
||||||
/* ############## v5 GENERAL #################### */
|
/* ############## v5 GENERAL #################### */
|
||||||
type MaybeArray<T> = T | Array<T>;
|
type MaybeArray<T> = T | Array<T>;
|
||||||
|
@ -627,7 +615,7 @@ interface DecryptOptions {
|
||||||
|
|
||||||
interface SignOptions {
|
interface SignOptions {
|
||||||
message: CleartextMessage | Message<MaybeStream<Data>>;
|
message: CleartextMessage | Message<MaybeStream<Data>>;
|
||||||
signingKeys?: MaybeArray<PrivateKey>;
|
signingKeys: MaybeArray<PrivateKey>;
|
||||||
format?: 'armored' | 'binary' | 'object';
|
format?: 'armored' | 'binary' | 'object';
|
||||||
detached?: boolean;
|
detached?: boolean;
|
||||||
signingKeyIDs?: MaybeArray<KeyID>;
|
signingKeyIDs?: MaybeArray<KeyID>;
|
||||||
|
@ -652,6 +640,16 @@ interface VerifyOptions {
|
||||||
config?: PartialConfig;
|
config?: PartialConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EncryptSessionKeyOptions extends SessionKey {
|
||||||
|
encryptionKeys?: MaybeArray<PublicKey>,
|
||||||
|
passwords?: MaybeArray<string>,
|
||||||
|
format?: 'armored' | 'binary' | 'object',
|
||||||
|
date?: Date,
|
||||||
|
wildcard?: boolean,
|
||||||
|
encryptionKeyIDs?: MaybeArray<KeyID>,
|
||||||
|
encryptionUserIDs?: MaybeArray<UserID>,
|
||||||
|
config?: PartialConfig
|
||||||
|
}
|
||||||
|
|
||||||
interface SerializedKeyPair<T extends string|Uint8Array> {
|
interface SerializedKeyPair<T extends string|Uint8Array> {
|
||||||
privateKey: T;
|
privateKey: T;
|
||||||
|
|
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -267,13 +267,21 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@openpgp/web-stream-tools": {
|
"@openpgp/web-stream-tools": {
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@openpgp/web-stream-tools/-/web-stream-tools-0.0.10.tgz",
|
||||||
"integrity": "sha512-GEKuXpQRshUqgKH4sMcwYbHolxaUSHIowcIMd02EsnLv4q5acP0z9pRUy3kjV0ZyRPgyD0vXAy60Rf0MPKoCMw==",
|
"integrity": "sha512-1ONZADML0fb0RJR5UiGYPnRf9VaYBYUBc1gF9jyq57sHkr58cp5/BQHS+ivrqbRw21Sb70FKTssmJbRe71V+kw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@mattiasbuelens/web-streams-adapter": "~0.1.0",
|
"@mattiasbuelens/web-streams-adapter": "~0.1.0",
|
||||||
"web-streams-polyfill": "~3.0.3"
|
"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": {
|
"@rollup/plugin-commonjs": {
|
||||||
|
@ -5401,9 +5409,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"web-streams-polyfill": {
|
"web-streams-polyfill": {
|
||||||
"version": "3.0.3",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
|
||||||
"integrity": "sha512-d2H/t0eqRNM4w2WvmTdoeIvzAUSpK7JmATB8Nr2lb7nQ9BTIJVjbQ/TRFVEh2gUH1HwclPdoPtfMoFfetXaZnA==",
|
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"which": {
|
"which": {
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
"@openpgp/pako": "^1.0.12",
|
"@openpgp/pako": "^1.0.12",
|
||||||
"@openpgp/seek-bzip": "^1.0.5-git",
|
"@openpgp/seek-bzip": "^1.0.5-git",
|
||||||
"@openpgp/tweetnacl": "^1.0.3",
|
"@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-commonjs": "^11.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
"@rollup/plugin-node-resolve": "^7.1.3",
|
||||||
"@rollup/plugin-replace": "^2.3.2",
|
"@rollup/plugin-replace": "^2.3.2",
|
||||||
|
@ -87,7 +87,8 @@
|
||||||
"rollup": "^2.38.5",
|
"rollup": "^2.38.5",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"sinon": "^4.3.0",
|
"sinon": "^4.3.0",
|
||||||
"typescript": "^4.1.2"
|
"typescript": "^4.1.2",
|
||||||
|
"web-streams-polyfill": "^3.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asn1.js": "^5.0.0"
|
"asn1.js": "^5.0.0"
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
* - if it fails to build, edit the file to match type definitions
|
* - 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.
|
* - 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 { expect } from 'chai';
|
||||||
import {
|
import {
|
||||||
|
@ -12,7 +14,8 @@ import {
|
||||||
readMessage, createMessage, Message, createCleartextMessage,
|
readMessage, createMessage, Message, createCleartextMessage,
|
||||||
encrypt, decrypt, sign, verify, config, enums,
|
encrypt, decrypt, sign, verify, config, enums,
|
||||||
generateSessionKey, encryptSessionKey, decryptSessionKeys,
|
generateSessionKey, encryptSessionKey, decryptSessionKeys,
|
||||||
LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage
|
LiteralDataPacket, PacketList, CompressedDataPacket, PublicKeyPacket, PublicSubkeyPacket, SecretKeyPacket, SecretSubkeyPacket, CleartextMessage,
|
||||||
|
WebStream, NodeStream,
|
||||||
} from '../..';
|
} from '../..';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -100,8 +103,10 @@ import {
|
||||||
// Get session keys from encrypted message
|
// Get session keys from encrypted message
|
||||||
const sessionKeys = await decryptSessionKeys({ message: await readMessage({ binaryMessage: encryptedBinary }), decryptionKeys: privateKeys });
|
const sessionKeys = await decryptSessionKeys({ message: await readMessage({ binaryMessage: encryptedBinary }), decryptionKeys: privateKeys });
|
||||||
expect(sessionKeys).to.have.length(1);
|
expect(sessionKeys).to.have.length(1);
|
||||||
const encryptedSessionKeys: string = await encryptSessionKey({ ...sessionKeys[0], passwords: 'pass', algorithm: 'aes128', aeadAlgorithm: 'eax' });
|
const armoredEncryptedSessionKeys: string = await encryptSessionKey({ ...sessionKeys[0], passwords: 'pass', algorithm: 'aes128', aeadAlgorithm: 'eax' });
|
||||||
expect(encryptedSessionKeys).to.include('-----BEGIN PGP MESSAGE-----');
|
expect(armoredEncryptedSessionKeys).to.include('-----BEGIN PGP MESSAGE-----');
|
||||||
|
const encryptedSessionKeys: Message<any> = 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() });
|
const newSessionKey = await generateSessionKey({ encryptionKeys: privateKey.toPublic() });
|
||||||
expect(newSessionKey.data).to.exist;
|
expect(newSessionKey.data).to.exist;
|
||||||
expect(newSessionKey.algorithm).to.exist;
|
expect(newSessionKey.algorithm).to.exist;
|
||||||
|
@ -180,19 +185,34 @@ import {
|
||||||
// const signed = await sign({ privateKeys, message, detached: true, format: 'binary' });
|
// const signed = await sign({ privateKeys, message, detached: true, format: 'binary' });
|
||||||
// console.log(signed); // Uint8Array
|
// console.log(signed); // Uint8Array
|
||||||
|
|
||||||
// // Streaming - encrypt text message on Node.js (armored)
|
// @ts-expect-error for passing text stream as binary data
|
||||||
// const data = fs.createReadStream(filename, { encoding: 'utf8' });
|
await createMessage({ binary: new WebReadableStream<string>() });
|
||||||
// const message = await createMessage({ text: data });
|
// @ts-expect-error for passing binary stream as text data
|
||||||
// const encrypted = await encrypt({ publicKeys, message });
|
await createMessage({ text: new WebReadableStream<Uint8Array>() });
|
||||||
// encrypted.on('data', chunk => {
|
|
||||||
// console.log(chunk); // String
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Streaming - encrypt binary message on Node.js (unarmored)
|
// Streaming - encrypt text message (armored output)
|
||||||
// const data = fs.createReadStream(filename);
|
try {
|
||||||
// const message = await createMessage({ binary: data });
|
const nodeTextStream = createReadStream('non-existent-file', { encoding: 'utf8' });
|
||||||
// const encrypted = await encrypt({ publicKeys, message, format: 'binary' });
|
const messageFromNodeTextStream = await createMessage({ text: nodeTextStream });
|
||||||
// encrypted.pipe(targetStream);
|
(await encrypt({ message: messageFromNodeTextStream, passwords: 'password', format: 'armored' })) as NodeStream<string>;
|
||||||
|
} catch (err) {}
|
||||||
|
const webTextStream = new WebReadableStream<string>();
|
||||||
|
const messageFromWebTextStream = await createMessage({ text: webTextStream });
|
||||||
|
(await encrypt({ message: messageFromWebTextStream, passwords: 'password', format: 'armored' })) as WebStream<string>;
|
||||||
|
messageFromWebTextStream.getText() as WebStream<string>;
|
||||||
|
messageFromWebTextStream.getLiteralData() as WebStream<Uint8Array>;
|
||||||
|
|
||||||
|
// 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<Uint8Array>;
|
||||||
|
} catch (err) {}
|
||||||
|
const webBinaryStream = new WebReadableStream<Uint8Array>();
|
||||||
|
const messageFromWebBinaryStream = await createMessage({ binary: webBinaryStream });
|
||||||
|
(await encrypt({ message: messageFromWebBinaryStream, passwords: 'password', format: 'binary' })) as WebStream<Uint8Array>;
|
||||||
|
messageFromWebBinaryStream.getText() as WebStream<string>;
|
||||||
|
messageFromWebBinaryStream.getLiteralData() as WebStream<Uint8Array>;
|
||||||
|
|
||||||
console.log('TypeScript definitions are correct');
|
console.log('TypeScript definitions are correct');
|
||||||
})().catch(e => {
|
})().catch(e => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user