@consento/crypto
Advanced tools
Comparing version 0.6.0 to 0.7.0
import { IEncryptedBlob, IEncryptedBlobJSON } from '../types'; | ||
import { IEncodable } from '../util/types'; | ||
import { INamedCodec } from '@consento/codecs'; | ||
export declare function isEncryptedBlob(input: any): input is IEncryptedBlob; | ||
export declare function toEncryptedBlob(input: string | Uint8Array | IEncryptedBlob | IEncryptedBlobJSON): IEncryptedBlob; | ||
export declare function encryptBlob(encodable: IEncodable): { | ||
export declare function encryptBlob<TType = any>(encodable: TType, codec?: INamedCodec<string, TType>): { | ||
blob: IEncryptedBlob; | ||
encrypted: Uint8Array; | ||
}; | ||
export declare function decryptBlob(secretKey: Uint8Array, encrypted: Uint8Array): IEncodable; | ||
export declare function decryptBlob<TType = any>(secretKey: Uint8Array, encrypted: Uint8Array, codec?: INamedCodec<string, TType>): TType; |
@@ -21,2 +21,5 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -28,2 +31,3 @@ exports.decryptBlob = exports.encryptBlob = exports.toEncryptedBlob = exports.isEncryptedBlob = void 0; | ||
const sodium = __importStar(require("sodium-universal")); | ||
const codecs_1 = __importDefault(require("@consento/codecs")); | ||
const { crypto_kdf_CONTEXTBYTES: CRYPTO_KDF_CONTEXTBYTES, crypto_kdf_BYTES_MAX: CRYPTO_KDF_BYTES_MAX, crypto_kdf_derive_from_key: kdfDeriveFromKey, sodium_malloc: malloc } = sodium.default; | ||
@@ -99,6 +103,6 @@ const deriveContext = types_1.Buffer.from('conotify'); | ||
exports.toEncryptedBlob = toEncryptedBlob; | ||
function encryptBlob(encodable) { | ||
function encryptBlob(encodable, codec = codecs_1.default.msgpack) { | ||
const secretKey = secretbox_1.createSecret(); | ||
const path = pathForSecretKey(secretKey); | ||
const encrypted = secretbox_1.encrypt(secretKey, encodable); | ||
const encrypted = secretbox_1.encrypt(secretKey, codec.encode(encodable)); | ||
return { | ||
@@ -110,6 +114,6 @@ blob: newBlob(secretKey, path, encrypted.length), | ||
exports.encryptBlob = encryptBlob; | ||
function decryptBlob(secretKey, encrypted) { | ||
return secretbox_1.decrypt(secretKey, encrypted); | ||
function decryptBlob(secretKey, encrypted, codec = codecs_1.default.msgpack) { | ||
return codec.decode(secretbox_1.decrypt(secretKey, encrypted)); | ||
} | ||
exports.decryptBlob = decryptBlob; | ||
//# sourceMappingURL=index.js.map |
import { Buffer } from '../util'; | ||
import { IHandshakeInit, IReader, IHandshakeInitOptions, IHandshakeAccept, IHandshakeAcceptMessage, IHandshakeAcceptOptions, IHandshakeConfirmation, IHandshakeAcceptJSON, IHandshakeConfirmationOptions, IHandshakeConfirmationJSON, IConnection, IHandshakeInitJSON } from '../types'; | ||
import { Connection } from '../primitives'; | ||
import { IHandshakeInit, IReader, IHandshakeInitOptions, IHandshakeAccept, IHandshakeAcceptMessage, IHandshakeAcceptOptions, IHandshakeConfirmation, IHandshakeAcceptJSON, IHandshakeConfirmationOptions, IHandshakeConfirmationJSON, IConnection, IHandshakeInitJSON, MsgPackCodec, IWriter } from '../types'; | ||
export declare const HANDSHAKE_MSG_VERSION: Buffer; | ||
export declare class HandshakeInit implements IHandshakeInit { | ||
input: IReader; | ||
input: IReader<MsgPackCodec>; | ||
firstMessage: Uint8Array; | ||
@@ -13,11 +12,16 @@ handshakeSecret: Uint8Array; | ||
} | ||
export declare class HandshakeAccept extends Connection implements IHandshakeAccept { | ||
export declare class HandshakeAccept implements IHandshakeAccept { | ||
acceptMessage: IHandshakeAcceptMessage; | ||
constructor(ops: IHandshakeAcceptOptions); | ||
input: IReader<MsgPackCodec>; | ||
output: IWriter<MsgPackCodec>; | ||
connectionKey: Uint8Array; | ||
_connectionKeyBase64?: string; | ||
constructor(opts: IHandshakeAcceptOptions); | ||
get connectionKeyBase64(): string; | ||
toJSON(): IHandshakeAcceptJSON; | ||
finalize(message: Uint8Array): IConnection; | ||
finalize(message: Uint8Array): IConnection<MsgPackCodec, MsgPackCodec>; | ||
} | ||
export declare class HandshakeConfirmation implements IHandshakeConfirmation { | ||
finalMessage: Uint8Array; | ||
connection: IConnection; | ||
connection: IConnection<MsgPackCodec, MsgPackCodec>; | ||
constructor(opts: IHandshakeConfirmationOptions); | ||
@@ -24,0 +28,0 @@ toJSON(): IHandshakeConfirmationJSON; |
@@ -41,13 +41,22 @@ "use strict"; | ||
} | ||
function processHandshake(msg) { | ||
if (msg[0] !== exports.HANDSHAKE_MSG_VERSION[0]) { | ||
throw Object.assign(new Error(`Error while processing handshake: Unknown handshake format: ${msg[0]}`), { code: 'unknown-message-format', messageFormat: msg[0] }); | ||
const handshakeCodec = { | ||
name: 'handshake', | ||
encode: ({ token, writerKey }) => util_1.Buffer.concat([exports.HANDSHAKE_MSG_VERSION, token, writerKey]), | ||
decode: msg => { | ||
if (msg[0] !== exports.HANDSHAKE_MSG_VERSION[0]) { | ||
throw Object.assign(new Error(`Error while processing handshake: Unknown handshake format: ${msg[0]}`), { code: 'unknown-message-format', messageFormat: msg[0] }); | ||
} | ||
if (msg.length !== 161) { | ||
throw Object.assign(new Error(`Error while processing handshake: Invalid handshake size: ${msg.length}`), { code: 'invalid-size', size: msg.length }); | ||
} | ||
return { | ||
token: msg.slice(1, 33), | ||
writerKey: msg.slice(33) | ||
}; | ||
} | ||
if (msg.length !== 161) { | ||
throw Object.assign(new Error(`Error while processing handshake: Invalid handshake size: ${msg.length}`), { code: 'invalid-size', size: msg.length }); | ||
} | ||
return { | ||
token: msg.slice(1, 33), | ||
writerKey: msg.slice(33) | ||
}; | ||
}; | ||
function remove(input, prop) { | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete input[prop]; | ||
return input; | ||
} | ||
@@ -62,3 +71,3 @@ class HandshakeInit { | ||
return { | ||
input: this.input.toJSON(), | ||
input: remove(this.input.toJSON(), 'codec'), | ||
firstMessage: util_1.bufferToString(this.firstMessage, 'base64'), | ||
@@ -71,11 +80,8 @@ handshakeSecret: util_1.bufferToString(this.handshakeSecret, 'base64') | ||
const backChannel = primitives_1.createChannel(); | ||
const sendKey = secretbox_1.decrypt(secretKey, util_1.Buffer.from(accept.secret, 'base64')); | ||
if (!(sendKey instanceof Uint8Array)) { | ||
throw Object.assign(new Error(`Expected buffer in decrypted message, got: ${sendKey.constructor.name}`), { code: 'invalid-message', sendKey }); | ||
} | ||
const writerKey = secretbox_1.decrypt(secretKey, util_1.Buffer.from(accept.secret, 'base64')); | ||
return new HandshakeConfirmation({ | ||
connection: new primitives_1.Connection({ | ||
output: new primitives_1.Writer({ writerKey: sendKey }), | ||
connectionKey: new primitives_1.Connection({ | ||
output: new primitives_1.Writer({ writerKey: writerKey }), | ||
input: backChannel.reader | ||
}), | ||
}).connectionKey, | ||
// In case you are wondering why we not just simply return "backChannel" as sender | ||
@@ -90,9 +96,22 @@ // but instead pass it in two messages: the reason is that without this step | ||
exports.HandshakeInit = HandshakeInit; | ||
class HandshakeAccept extends primitives_1.Connection { | ||
constructor(ops) { | ||
super(ops); | ||
this.acceptMessage = ops.acceptMessage; | ||
class HandshakeAccept { | ||
constructor(opts) { | ||
const parts = primitives_1.getIOFromConnectionOptions(opts); | ||
this.input = parts.input; | ||
this.output = parts.output; | ||
this.connectionKey = parts.connectionKey; | ||
this._connectionKeyBase64 = parts.connectionKeyBase64; | ||
this.acceptMessage = opts.acceptMessage; | ||
} | ||
get connectionKeyBase64() { | ||
if (this._connectionKeyBase64 === undefined) { | ||
this._connectionKeyBase64 = util_1.bufferToString(this.connectionKey, 'base64'); | ||
} | ||
return this._connectionKeyBase64; | ||
} | ||
toJSON() { | ||
return Object.assign(Object.assign({}, super.toJSON()), { acceptMessage: this.acceptMessage }); | ||
return { | ||
connectionKey: this.connectionKeyBase64, | ||
acceptMessage: this.acceptMessage | ||
}; | ||
} | ||
@@ -102,3 +121,3 @@ finalize(message) { | ||
input: this.input, | ||
output: new primitives_1.Writer({ writerKey: message }) | ||
output: { writerKey: message } | ||
}); | ||
@@ -110,3 +129,3 @@ } | ||
constructor(opts) { | ||
this.connection = new primitives_1.Connection(opts.connection); | ||
this.connection = new primitives_1.Connection({ connectionKey: opts.connectionKey }); | ||
this.finalMessage = util_1.toBuffer(opts.finalMessage); | ||
@@ -116,3 +135,3 @@ } | ||
return { | ||
connection: this.connection.toJSON(), | ||
connectionKey: this.connection.connectionKeyBase64, | ||
finalMessage: util_1.bufferToString(this.finalMessage, 'base64') | ||
@@ -129,7 +148,3 @@ }; | ||
handshakeSecret: handshake.secretKey, | ||
firstMessage: util_1.Buffer.concat([ | ||
exports.HANDSHAKE_MSG_VERSION, | ||
handshake.publicKey, | ||
channel.writer.writerKey | ||
]) | ||
firstMessage: handshakeCodec.encode({ token: handshake.publicKey, writerKey: channel.writer.writerKey }) | ||
}); | ||
@@ -139,3 +154,3 @@ } | ||
function acceptHandshake(firstMessage) { | ||
const { token, writerKey } = processHandshake(firstMessage); | ||
const { token, writerKey } = handshakeCodec.decode(firstMessage); | ||
const handshake = createHandshake(); | ||
@@ -145,4 +160,4 @@ const secretKey = computeSecret(handshake.secretKey, token); | ||
return new HandshakeAccept({ | ||
output: { writerKey }, | ||
input: receiver, | ||
output: writerKey, | ||
input: receiver.readerKey, | ||
acceptMessage: { | ||
@@ -149,0 +164,0 @@ token: util_1.bufferToString(handshake.publicKey, 'base64'), |
{ | ||
"name": "@consento/crypto", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"description": "Crypto functionality used in Consento", | ||
@@ -10,2 +10,4 @@ "main": "index.js", | ||
"dependencies": { | ||
"@consento/codecs": "^1.0.0", | ||
"@msgpack/msgpack": "^2.3.0", | ||
"abort-controller": "^3.0.0", | ||
@@ -12,0 +14,0 @@ "buffer": "^5.6.0", |
import { IVerifier, IReader, IWriter, IChannel, IChannelJSON, IChannelOptions } from '../types'; | ||
import { Inspectable } from '../util'; | ||
import { InspectOptions } from 'inspect-custom-symbol'; | ||
export declare function createChannel(): Channel; | ||
export declare class Channel extends Inspectable implements IChannel { | ||
_reader?: IReader; | ||
_writer?: IWriter; | ||
_channelKey?: Uint8Array; | ||
import { Codec, CodecOption } from '@consento/codecs'; | ||
export declare class Channel<TCodec extends CodecOption = undefined> extends Inspectable implements IChannel<Codec<TCodec, 'msgpack'>> { | ||
reader: IReader<Codec<TCodec, 'msgpack'>>; | ||
writer: IWriter<Codec<TCodec, 'msgpack'>>; | ||
channelKey: Uint8Array; | ||
_channelKeyBase64?: string; | ||
constructor({ channelKey }: IChannelOptions); | ||
constructor({ channelKey, inVector, outVector, codec }: IChannelOptions<TCodec>); | ||
recodec<TCodec extends CodecOption = undefined>(codec: TCodec): IChannel<Codec<TCodec, 'msgpack'>>; | ||
get codec(): Codec<TCodec, 'msgpack'>; | ||
get verifyKey(): Uint8Array; | ||
get verifyKeyBase64(): string; | ||
get verifyKeyHex(): string; | ||
get channelKey(): Uint8Array; | ||
get channelKeyBase64(): string; | ||
get reader(): IReader; | ||
get writer(): IWriter; | ||
get verifier(): IVerifier; | ||
_inspect(_: number, { stylize }: InspectOptions): string; | ||
toJSON(): IChannelJSON; | ||
toJSON(): IChannelJSON<Codec<TCodec, 'msgpack'>>; | ||
} |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -25,42 +6,32 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Channel = exports.createChannel = void 0; | ||
exports.Channel = void 0; | ||
const util_1 = require("../util"); | ||
const Reader_1 = require("./Reader"); | ||
const Writer_1 = require("./Writer"); | ||
const sodium = __importStar(require("sodium-universal")); | ||
const key_1 = require("./key"); | ||
const pretty_hash_1 = __importDefault(require("pretty-hash")); | ||
const { sodium_malloc: malloc, crypto_box_PUBLICKEYBYTES: CRYPTO_BOX_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES: CRYPTO_BOX_SECRETKEYBYTES, crypto_box_keypair: boxKeyPair, crypto_sign_PUBLICKEYBYTES: CRYPTO_SIGN_PUBLICKEYBYTES, crypto_sign_SECRETKEYBYTES: CRYPTO_SIGN_SECRETKEYBYTES, crypto_sign_keypair: signKeyPair } = sodium.default; | ||
function createEncryptionKeys() { | ||
const keys = { | ||
publicKey: malloc(CRYPTO_BOX_PUBLICKEYBYTES), | ||
privateKey: malloc(CRYPTO_BOX_SECRETKEYBYTES) | ||
}; | ||
boxKeyPair(keys.publicKey, keys.privateKey); | ||
return keys; | ||
} | ||
function createSignKeys() { | ||
const keys = { | ||
publicKey: malloc(CRYPTO_SIGN_PUBLICKEYBYTES), | ||
privateKey: malloc(CRYPTO_SIGN_SECRETKEYBYTES) | ||
}; | ||
signKeyPair(keys.publicKey, keys.privateKey); | ||
return keys; | ||
} | ||
function createChannel() { | ||
const encrypt = createEncryptionKeys(); | ||
const sign = createSignKeys(); | ||
return new Channel({ channelKey: util_1.Buffer.concat([encrypt.publicKey, sign.publicKey, encrypt.privateKey, sign.privateKey]) }); | ||
} | ||
exports.createChannel = createChannel; | ||
class Channel extends util_1.Inspectable { | ||
constructor({ channelKey }) { | ||
constructor({ channelKey, inVector, outVector, codec }) { | ||
super(); | ||
if (typeof channelKey === 'string') { | ||
this._channelKeyBase64 = channelKey; | ||
this.channelKey = util_1.toBuffer(channelKey); | ||
} | ||
else { | ||
this._channelKey = channelKey; | ||
this.channelKey = channelKey; | ||
} | ||
this.reader = new Reader_1.Reader({ readerKey: key_1.readerKeyFromChannelKey(this.channelKey), inVector, codec }); | ||
this.writer = new Writer_1.Writer({ writerKey: key_1.writerKeyFromChannelKey(this.channelKey), outVector, codec }); | ||
} | ||
recodec(codec) { | ||
return new Channel({ | ||
channelKey: this.channelKey, | ||
inVector: this.reader.inVector, | ||
outVector: this.writer.outVector, | ||
codec | ||
}); | ||
} | ||
get codec() { | ||
return this.reader.codec; | ||
} | ||
get verifyKey() { | ||
@@ -75,26 +46,8 @@ return this.reader.verifyKey; | ||
} | ||
get channelKey() { | ||
if (this._channelKey === undefined) { | ||
this._channelKey = util_1.toBuffer(this._channelKeyBase64); | ||
} | ||
return this._channelKey; | ||
} | ||
get channelKeyBase64() { | ||
if (this._channelKeyBase64 === undefined) { | ||
this._channelKeyBase64 = util_1.bufferToString(this._channelKey, 'base64'); | ||
this._channelKeyBase64 = util_1.bufferToString(this.channelKey, 'base64'); | ||
} | ||
return this._channelKeyBase64; | ||
} | ||
get reader() { | ||
if (this._reader === undefined) { | ||
this._reader = new Reader_1.Reader({ readerKey: key_1.readerKeyFromChannelKey(this.channelKey) }); | ||
} | ||
return this._reader; | ||
} | ||
get writer() { | ||
if (this._writer === undefined) { | ||
this._writer = new Writer_1.Writer({ writerKey: key_1.writerKeyFromChannelKey(this.channelKey) }); | ||
} | ||
return this._writer; | ||
} | ||
get verifier() { | ||
@@ -105,7 +58,11 @@ return this.reader.verifier; | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Channel(${stylize(pretty_hash_1.default(this.reader.verifyKey), 'string')})`; | ||
return `Channel(${stylize(this.codec.name, 'special')}|${stylize(pretty_hash_1.default(this.reader.verifyKey), 'string')})`; | ||
} | ||
toJSON() { | ||
var _a, _b; | ||
return { | ||
channelKey: this.channelKeyBase64 | ||
channelKey: this.channelKeyBase64, | ||
inVector: (_a = this.reader.inVector) === null || _a === void 0 ? void 0 : _a.toJSON(), | ||
outVector: (_b = this.writer.outVector) === null || _b === void 0 ? void 0 : _b.toJSON(), | ||
codec: this.reader.codec.name | ||
}; | ||
@@ -112,0 +69,0 @@ } |
import { IConnection, IReader, IWriter, IConnectionJSON, IConnectionOptions } from '../types'; | ||
import { Inspectable } from '../util'; | ||
import { InspectOptions } from 'inspect-custom-symbol'; | ||
export declare class Connection extends Inspectable implements IConnection { | ||
_input?: IReader; | ||
_output?: IWriter; | ||
_connectionKey?: Uint8Array; | ||
import { Codec, CodecOption } from '@consento/codecs'; | ||
export declare function getIOFromConnectionOptions<TInputCodec extends CodecOption = undefined, TOutputCodec extends CodecOption = undefined>(opts: IConnectionOptions<TInputCodec, TOutputCodec>): { | ||
connectionKey: Uint8Array; | ||
connectionKeyBase64?: string; | ||
input: IReader<Codec<TInputCodec, 'msgpack'>>; | ||
output: IWriter<Codec<TOutputCodec, 'msgpack'>>; | ||
}; | ||
export declare class Connection<TInputCodec extends CodecOption = undefined, TOutputCodec extends CodecOption = undefined> extends Inspectable implements IConnection<Codec<TInputCodec, 'msgpack'>, Codec<TOutputCodec, 'msgpack'>> { | ||
input: IReader<Codec<TInputCodec, 'msgpack'>>; | ||
output: IWriter<Codec<TOutputCodec, 'msgpack'>>; | ||
connectionKey: Uint8Array; | ||
_connectionKeyBase64?: string; | ||
constructor(opts: IConnectionOptions); | ||
get connectionKey(): Uint8Array; | ||
constructor(opts: IConnectionOptions<TInputCodec, TOutputCodec>); | ||
get connectionKeyBase64(): string; | ||
get input(): IReader; | ||
get output(): IWriter; | ||
toJSON(): IConnectionJSON; | ||
toJSON(): IConnectionJSON<Codec<TInputCodec, 'msgpack'>, Codec<TOutputCodec, 'msgpack'>>; | ||
_inspect(_: number, { stylize }: InspectOptions): string; | ||
} |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Connection = void 0; | ||
exports.Connection = exports.getIOFromConnectionOptions = void 0; | ||
const Reader_1 = require("./Reader"); | ||
@@ -14,18 +14,63 @@ const Writer_1 = require("./Writer"); | ||
const pretty_hash_1 = __importDefault(require("pretty-hash")); | ||
function isConnectionOptionsByKey(input) { | ||
return 'connectionKey' in input; | ||
} | ||
function isConnectionOptionsByIO(input) { | ||
return 'input' in input && 'output' in input; | ||
} | ||
function getIOFromConnectionOptions(opts) { | ||
let readerKey; | ||
let writerKey; | ||
let inCodec = opts.inCodec; | ||
let outCodec = opts.outCodec; | ||
let inVector = opts.inVector; | ||
let outVector = opts.outVector; | ||
let connectionKey; | ||
let connectionKeyBase64; | ||
if (isConnectionOptionsByKey(opts)) { | ||
if (typeof opts.connectionKey === 'string') { | ||
connectionKeyBase64 = opts.connectionKey; | ||
} | ||
connectionKey = buffer_1.toBuffer(opts.connectionKey); | ||
readerKey = key_1.inReaderKeyFromConnectionKey(connectionKey); | ||
writerKey = key_1.outWriterKeyFromConnectionKey(connectionKey); | ||
} | ||
else if (isConnectionOptionsByIO(opts)) { | ||
if (buffer_1.isStringOrBuffer(opts.input)) { | ||
readerKey = buffer_1.toBuffer(opts.input); | ||
} | ||
else { | ||
readerKey = buffer_1.toBuffer(opts.input.readerKey); | ||
inVector = inVector !== null && inVector !== void 0 ? inVector : opts.input.inVector; | ||
inCodec = inCodec !== null && inCodec !== void 0 ? inCodec : opts.input.codec; | ||
} | ||
if (buffer_1.isStringOrBuffer(opts.output)) { | ||
writerKey = buffer_1.toBuffer(opts.output); | ||
} | ||
else { | ||
writerKey = buffer_1.toBuffer(opts.output.writerKey); | ||
outVector = outVector !== null && outVector !== void 0 ? outVector : opts.output.outVector; | ||
outCodec = outCodec !== null && outCodec !== void 0 ? outCodec : opts.output.codec; | ||
} | ||
connectionKey = util_1.Buffer.concat([readerKey, writerKey]); | ||
} | ||
else { | ||
throw new Error('Options for connection invalid, either connectionKey or input/output must be given.'); | ||
} | ||
return { | ||
connectionKey, | ||
connectionKeyBase64, | ||
input: new Reader_1.Reader({ readerKey, inVector, codec: inCodec }), | ||
output: new Writer_1.Writer({ writerKey, outVector, codec: outCodec }) | ||
}; | ||
} | ||
exports.getIOFromConnectionOptions = getIOFromConnectionOptions; | ||
class Connection extends util_1.Inspectable { | ||
constructor(opts) { | ||
super(); | ||
if ('connectionKey' in opts) { | ||
if (typeof opts.connectionKey === 'string') { | ||
this._connectionKeyBase64 = opts.connectionKey; | ||
} | ||
else { | ||
this._connectionKey = opts.connectionKey; | ||
} | ||
} | ||
else { | ||
this._input = buffer_1.isStringOrBuffer(opts.input) ? new Reader_1.Reader({ readerKey: opts.input }) : 'readerKey' in opts.input ? new Reader_1.Reader(opts.input) : opts.input; | ||
this._output = buffer_1.isStringOrBuffer(opts.output) ? new Writer_1.Writer({ writerKey: opts.output }) : 'writerKey' in opts.output ? new Writer_1.Writer(opts.output) : opts.output; | ||
this._connectionKey = util_1.Buffer.concat([this._input.readerKey, this._output.writerKey]); | ||
} | ||
const parts = getIOFromConnectionOptions(opts); | ||
this.input = parts.input; | ||
this.output = parts.output; | ||
this.connectionKey = parts.connectionKey; | ||
this._connectionKeyBase64 = parts.connectionKeyBase64; | ||
if (buffer_1.bufferEquals(this.input.verifyKey, this.output.verifyKey)) { | ||
@@ -35,29 +80,16 @@ throw new Error('Can not create a connection with both the writer and the reader have the same id! Did you mean to restore a channel?'); | ||
} | ||
get connectionKey() { | ||
if (this._connectionKey === undefined) { | ||
this._connectionKey = buffer_1.toBuffer(this._connectionKeyBase64); | ||
} | ||
return this._connectionKey; | ||
} | ||
get connectionKeyBase64() { | ||
if (this._connectionKeyBase64 === undefined) { | ||
this._connectionKeyBase64 = buffer_1.bufferToString(this._connectionKey, 'base64'); | ||
this._connectionKeyBase64 = buffer_1.bufferToString(this.connectionKey, 'base64'); | ||
} | ||
return this._connectionKeyBase64; | ||
} | ||
get input() { | ||
if (this._input === undefined) { | ||
this._input = new Reader_1.Reader({ readerKey: key_1.inReaderKeyFromConnectionKey(this.connectionKey) }); | ||
} | ||
return this._input; | ||
} | ||
get output() { | ||
if (this._output === undefined) { | ||
this._output = new Writer_1.Writer({ writerKey: key_1.outWriterKeyFromConnectionKey(this.connectionKey) }); | ||
} | ||
return this._output; | ||
} | ||
toJSON() { | ||
var _a, _b; | ||
return { | ||
connectionKey: this.connectionKeyBase64 | ||
connectionKey: this.connectionKeyBase64, | ||
inCodec: this.input.codec.name, | ||
outCodec: this.output.codec.name, | ||
inVector: (_a = this.input.inVector) === null || _a === void 0 ? void 0 : _a.toJSON(), | ||
outVector: (_b = this.output.outVector) === null || _b === void 0 ? void 0 : _b.toJSON() | ||
}; | ||
@@ -67,3 +99,12 @@ } | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Connection(input=${stylize(pretty_hash_1.default(this.input.verifyKey), 'string')}, output=${stylize(pretty_hash_1.default(this.output.verifyKey), 'string')})`; | ||
let input = `input=${stylize(this.input.codec.name, 'special')}|${stylize(pretty_hash_1.default(this.input.verifyKey), 'string')}`; | ||
if (this.input.inVector !== undefined) { | ||
input += `#${this.input.inVector.index}`; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
let output = `output=${stylize(this.output.codec.name, 'special')}|${stylize(pretty_hash_1.default(this.output.verifyKey), 'string')}`; | ||
if (this.output.outVector !== undefined) { | ||
output += `#${this.output.outVector.index}`; | ||
} | ||
return `Connection(${input}, ${output})`; | ||
} | ||
@@ -70,0 +111,0 @@ } |
@@ -1,7 +0,15 @@ | ||
import { IEncryptedMessage } from '../types'; | ||
import { IEncodable } from '../util/types'; | ||
export declare function encryptMessage(writeKey: Uint8Array, message: IEncodable): Uint8Array; | ||
import { IChannelOptions, IEncryptedMessage, IEncryptionKeys, ISignKeys, ISignVector } from '../types'; | ||
import { Channel } from './Channel'; | ||
import { CodecOption } from '@consento/codecs'; | ||
export declare function createEncryptionKeys(): IEncryptionKeys; | ||
export declare function createSignKeys(): ISignKeys; | ||
export declare function encryptMessage(writeKey: Uint8Array, message: any): Uint8Array; | ||
export declare function verify(verifyKey: Uint8Array, signature: Uint8Array, body: Uint8Array): boolean; | ||
export declare function verifyMessage(verifyKey: Uint8Array, message: IEncryptedMessage): boolean; | ||
export declare function decryptMessage(verifyKey: Uint8Array, writeKey: Uint8Array, readKey: Uint8Array, message: IEncryptedMessage | Uint8Array): IEncodable; | ||
export declare function decryptMessage(verifyKey: Uint8Array, writeKey: Uint8Array, readKey: Uint8Array, message: IEncryptedMessage | Uint8Array): any; | ||
export declare function sign(signKey: Uint8Array, body: Uint8Array): Uint8Array; | ||
export declare function createSignVectors(): { | ||
inVector: ISignVector; | ||
outVector: ISignVector; | ||
}; | ||
export declare function createChannel<TCodec extends CodecOption = undefined>(opts?: Omit<IChannelOptions<TCodec>, 'channelKey'>): Channel<TCodec>; |
@@ -22,9 +22,30 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.sign = exports.decryptMessage = exports.verifyMessage = exports.verify = exports.encryptMessage = void 0; | ||
exports.createChannel = exports.createSignVectors = exports.sign = exports.decryptMessage = exports.verifyMessage = exports.verify = exports.encryptMessage = exports.createSignKeys = exports.createEncryptionKeys = void 0; | ||
const types_1 = require("../types"); | ||
const sodium = __importStar(require("sodium-universal")); | ||
const buffer_1 = require("../util/buffer"); | ||
const { crypto_box_SEALBYTES: CRYPTO_BOX_SEALBYTES, crypto_sign_BYTES: CRYPTO_SIGN_BYTES, crypto_box_seal_open: boxSealOpen, crypto_box_seal: boxSeal, crypto_sign_verify_detached: _verify, crypto_sign_detached: signDetached, sodium_malloc: malloc } = sodium.default; | ||
const msgpack_1 = require("@msgpack/msgpack"); | ||
const Channel_1 = require("./Channel"); | ||
const buffer_1 = require("buffer"); | ||
const SignVector_1 = require("./SignVector"); | ||
const { crypto_box_PUBLICKEYBYTES: CRYPTO_BOX_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES: CRYPTO_BOX_SECRETKEYBYTES, crypto_box_SEALBYTES: CRYPTO_BOX_SEALBYTES, crypto_box_seal: boxSeal, crypto_box_seal_open: boxSealOpen, crypto_box_keypair: boxKeyPair, crypto_sign_BYTES: CRYPTO_SIGN_BYTES, crypto_sign_PUBLICKEYBYTES: CRYPTO_SIGN_PUBLICKEYBYTES, crypto_sign_SECRETKEYBYTES: CRYPTO_SIGN_SECRETKEYBYTES, crypto_sign_keypair: signKeyPair, crypto_sign_detached: signDetached, crypto_sign_verify_detached: _verify, sodium_malloc: malloc } = sodium.default; | ||
function createEncryptionKeys() { | ||
const keys = { | ||
encryptKey: malloc(CRYPTO_BOX_PUBLICKEYBYTES), | ||
decryptKey: malloc(CRYPTO_BOX_SECRETKEYBYTES) | ||
}; | ||
boxKeyPair(keys.encryptKey, keys.decryptKey); | ||
return keys; | ||
} | ||
exports.createEncryptionKeys = createEncryptionKeys; | ||
function createSignKeys() { | ||
const keys = { | ||
verifyKey: malloc(CRYPTO_SIGN_PUBLICKEYBYTES), | ||
signKey: malloc(CRYPTO_SIGN_SECRETKEYBYTES) | ||
}; | ||
signKeyPair(keys.verifyKey, keys.signKey); | ||
return keys; | ||
} | ||
exports.createSignKeys = createSignKeys; | ||
function encryptMessage(writeKey, message) { | ||
const msgBuffer = buffer_1.anyToBuffer(message); | ||
const msgBuffer = msgpack_1.encode(message); | ||
const body = malloc(msgBuffer.length + CRYPTO_BOX_SEALBYTES); | ||
@@ -59,3 +80,3 @@ boxSeal(body, msgBuffer, writeKey); | ||
} | ||
return buffer_1.bufferToAny(messageDecrypted); | ||
return msgpack_1.decode(messageDecrypted); | ||
} | ||
@@ -69,2 +90,16 @@ exports.decryptMessage = decryptMessage; | ||
exports.sign = sign; | ||
function createSignVectors() { | ||
const keys = createSignKeys(); | ||
return { | ||
inVector: new SignVector_1.SignVector({ next: keys.verifyKey }), | ||
outVector: new SignVector_1.SignVector({ next: keys.signKey }) | ||
}; | ||
} | ||
exports.createSignVectors = createSignVectors; | ||
function createChannel(opts) { | ||
const encrypt = createEncryptionKeys(); | ||
const sign = createSignKeys(); | ||
return new Channel_1.Channel(Object.assign({ channelKey: buffer_1.Buffer.concat([encrypt.encryptKey, sign.verifyKey, encrypt.decryptKey, sign.signKey]) }, opts)); | ||
} | ||
exports.createChannel = createChannel; | ||
//# sourceMappingURL=fn.js.map |
@@ -1,5 +0,6 @@ | ||
import { IVerifier, IReader, IReaderJSON, IEncryptedMessage, IReaderOptions } from '../types'; | ||
import { IEncodable, Inspectable } from '../util'; | ||
import { IVerifier, IReader, IReaderJSON, IEncryptedMessage, IReaderOptions, ISignVector } from '../types'; | ||
import { Inspectable } from '../util'; | ||
import { InspectOptions } from 'inspect-custom-symbol'; | ||
export declare class Reader extends Inspectable implements IReader { | ||
import { Codec, CodecOption } from '@consento/codecs'; | ||
export declare class Reader<TCodec extends CodecOption = undefined> extends Inspectable implements IReader<Codec<TCodec, 'msgpack'>> { | ||
_receiveKey?: Uint8Array; | ||
@@ -10,3 +11,6 @@ _receiveKeyBase64?: string; | ||
_verifier?: IVerifier; | ||
constructor({ readerKey: receiveKey }: IReaderOptions); | ||
inVector?: ISignVector; | ||
codec: Codec<TCodec, 'msgpack'>; | ||
constructor({ readerKey, inVector, codec }: IReaderOptions<TCodec>); | ||
recodec<TCodec extends CodecOption = undefined>(codec: TCodec): IReader<Codec<TCodec, 'msgpack'>>; | ||
get verifyKey(): Uint8Array; | ||
@@ -20,6 +24,7 @@ get verifyKeyHex(): string; | ||
get readerKeyBase64(): string; | ||
toJSON(): IReaderJSON; | ||
toJSON(): IReaderJSON<Codec<TCodec, 'msgpack'>>; | ||
_inspect(_: number, { stylize }: InspectOptions): string; | ||
encryptOnly(message: IEncodable): Uint8Array; | ||
decrypt(encrypted: IEncryptedMessage): IEncodable; | ||
encryptOnly(message: any): Uint8Array; | ||
decrypt(encrypted: IEncryptedMessage): any; | ||
decryptNext(encrypted: IEncryptedMessage): any; | ||
} |
@@ -7,2 +7,3 @@ "use strict"; | ||
exports.Reader = void 0; | ||
const types_1 = require("../types"); | ||
const Verifier_1 = require("./Verifier"); | ||
@@ -13,12 +14,33 @@ const key_1 = require("./key"); | ||
const pretty_hash_1 = __importDefault(require("pretty-hash")); | ||
const msgpack_1 = require("@msgpack/msgpack"); | ||
const SignVector_1 = require("./SignVector"); | ||
const codecs_1 = __importDefault(require("@consento/codecs")); | ||
function assertVectoredMessage(input) { | ||
if (!Array.isArray(input)) { | ||
throw Object.assign(new Error('Message needs to be an array'), { code: types_1.EDecryptionError.invalidMessage }); | ||
} | ||
if (input.length === 0) { | ||
throw Object.assign(new Error('The next structure needs to have a body.'), { code: types_1.EDecryptionError.missingBody }); | ||
} | ||
if (input.length === 1) { | ||
throw Object.assign(new Error('The next structure needs to have a signature.'), { code: types_1.EDecryptionError.missingSignature }); | ||
} | ||
} | ||
class Reader extends util_1.Inspectable { | ||
constructor({ readerKey: receiveKey }) { | ||
constructor({ readerKey, inVector, codec }) { | ||
super(); | ||
if (typeof receiveKey === 'string') { | ||
this._receiveKeyBase64 = receiveKey; | ||
if (typeof readerKey === 'string') { | ||
this._receiveKeyBase64 = readerKey; | ||
} | ||
else { | ||
this._receiveKey = receiveKey; | ||
this._receiveKey = readerKey; | ||
} | ||
if (util_1.exists(inVector)) { | ||
this.inVector = new SignVector_1.SignVector(inVector); | ||
} | ||
this.codec = codecs_1.default(codec, 'msgpack'); | ||
} | ||
recodec(codec) { | ||
return new Reader({ readerKey: this.readerKey, inVector: this.inVector, codec }); | ||
} | ||
get verifyKey() { | ||
@@ -64,7 +86,13 @@ return this.verifier.verifyKey; | ||
toJSON() { | ||
return { readerKey: this.readerKeyBase64 }; | ||
var _a; | ||
return { | ||
readerKey: this.readerKeyBase64, | ||
inVector: (_a = this.inVector) === null || _a === void 0 ? void 0 : _a.toJSON(), | ||
codec: this.codec.name | ||
}; | ||
} | ||
_inspect(_, { stylize }) { | ||
const vector = this.inVector !== undefined ? `#${this.inVector.index}` : ''; | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Reader(${stylize(pretty_hash_1.default(this.verifyKey), 'string')})`; | ||
return `Reader(${stylize(this.codec.name, 'special')}|${stylize(pretty_hash_1.default(this.verifyKey), 'string')}${vector})`; | ||
} | ||
@@ -77,4 +105,14 @@ encryptOnly(message) { | ||
} | ||
decryptNext(encrypted) { | ||
if (this.inVector === undefined) { | ||
return this.decrypt(encrypted); | ||
} | ||
const raw = msgpack_1.decode(this.decrypt(encrypted)); | ||
assertVectoredMessage(raw); | ||
const [body, signature] = raw; | ||
this.inVector.verify(body, signature); | ||
return msgpack_1.decode(body); | ||
} | ||
} | ||
exports.Reader = Reader; | ||
//# sourceMappingURL=Reader.js.map |
@@ -1,11 +0,15 @@ | ||
import { IVerifier, IWriter, IWriterJSON, IEncryptedMessage, IWriterOptions } from '../types'; | ||
import { IEncodable, Inspectable } from '../util'; | ||
import { IVerifier, IWriter, IWriterJSON, IEncryptedMessage, IWriterOptions, ISignVector } from '../types'; | ||
import { Inspectable } from '../util'; | ||
import { InspectOptions } from 'inspect-custom-symbol'; | ||
export declare class Writer extends Inspectable implements IWriter { | ||
_sendKey?: Uint8Array; | ||
_sendKeyBase64?: string; | ||
import { Codec, CodecOption } from '@consento/codecs'; | ||
export declare class Writer<TCodec extends CodecOption = undefined> extends Inspectable implements IWriter<Codec<TCodec, 'msgpack'>> { | ||
_writerKey?: Uint8Array; | ||
_writerKeyBase64?: string; | ||
_annonymous?: IVerifier; | ||
_signKey?: Uint8Array; | ||
_encryptKey?: Uint8Array; | ||
constructor({ writerKey: sendKey }: IWriterOptions); | ||
outVector?: ISignVector; | ||
codec: Codec<TCodec, 'msgpack'>; | ||
constructor({ writerKey, outVector, codec }: IWriterOptions<TCodec>); | ||
recodec<TCodec extends CodecOption = undefined>(codec: TCodec): IWriter<Codec<TCodec, 'msgpack'>>; | ||
get signKey(): Uint8Array; | ||
@@ -19,7 +23,9 @@ get encryptKey(): Uint8Array; | ||
get verifier(): IVerifier; | ||
toJSON(): IWriterJSON; | ||
toJSON(): IWriterJSON<Codec<TCodec, 'msgpack'>>; | ||
_inspect(_: number, { stylize }: InspectOptions): string; | ||
sign(data: Uint8Array): Uint8Array; | ||
encryptOnly(message: IEncodable): Uint8Array; | ||
encrypt(message: IEncodable): IEncryptedMessage; | ||
encryptOnly(message: any): Uint8Array; | ||
encrypt(message: any): IEncryptedMessage; | ||
encryptNext(message: any): IEncryptedMessage; | ||
encryptOnlyNext(message: any): Uint8Array; | ||
} |
@@ -12,12 +12,22 @@ "use strict"; | ||
const pretty_hash_1 = __importDefault(require("pretty-hash")); | ||
const SignVector_1 = require("./SignVector"); | ||
const msgpack_1 = require("@msgpack/msgpack"); | ||
const codecs_1 = __importDefault(require("@consento/codecs")); | ||
class Writer extends util_1.Inspectable { | ||
constructor({ writerKey: sendKey }) { | ||
constructor({ writerKey, outVector, codec }) { | ||
super(); | ||
if (typeof sendKey === 'string') { | ||
this._sendKeyBase64 = sendKey; | ||
if (typeof writerKey === 'string') { | ||
this._writerKeyBase64 = writerKey; | ||
} | ||
else { | ||
this._sendKey = sendKey; | ||
this._writerKey = writerKey; | ||
} | ||
if (util_1.exists(outVector)) { | ||
this.outVector = new SignVector_1.SignVector(outVector); | ||
} | ||
this.codec = codecs_1.default(codec, 'msgpack'); | ||
} | ||
recodec(codec) { | ||
return new Writer({ writerKey: this.writerKey, outVector: this.outVector, codec }); | ||
} | ||
get signKey() { | ||
@@ -36,12 +46,12 @@ if (this._signKey === undefined) { | ||
get writerKey() { | ||
if (this._sendKey === undefined) { | ||
this._sendKey = util_1.toBuffer(this._sendKeyBase64); | ||
if (this._writerKey === undefined) { | ||
this._writerKey = util_1.toBuffer(this._writerKeyBase64); | ||
} | ||
return this._sendKey; | ||
return this._writerKey; | ||
} | ||
get writerKeyBase64() { | ||
if (this._sendKeyBase64 === undefined) { | ||
this._sendKeyBase64 = util_1.bufferToString(this._sendKey, 'base64'); | ||
if (this._writerKeyBase64 === undefined) { | ||
this._writerKeyBase64 = util_1.bufferToString(this._writerKey, 'base64'); | ||
} | ||
return this._sendKeyBase64; | ||
return this._writerKeyBase64; | ||
} | ||
@@ -64,7 +74,13 @@ get verifyKey() { | ||
toJSON() { | ||
return { writerKey: this.writerKeyBase64 }; | ||
var _a; | ||
return { | ||
writerKey: this.writerKeyBase64, | ||
outVector: (_a = this.outVector) === null || _a === void 0 ? void 0 : _a.toJSON(), | ||
codec: this.codec.name | ||
}; | ||
} | ||
_inspect(_, { stylize }) { | ||
const vector = this.outVector !== undefined ? `#${this.outVector.index}` : ''; | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Writer(${stylize(pretty_hash_1.default(this.verifyKey), 'string')})`; | ||
return `Writer(${stylize(this.codec.name, 'special')}|${stylize(pretty_hash_1.default(this.verifyKey), 'string')}${vector})`; | ||
} | ||
@@ -84,4 +100,21 @@ sign(data) { | ||
} | ||
encryptNext(message) { | ||
const body = this.encryptOnlyNext(message); | ||
return { | ||
signature: this.sign(body), | ||
body | ||
}; | ||
} | ||
encryptOnlyNext(message) { | ||
if (this.outVector === undefined) { | ||
return this.encryptOnly(message); | ||
} | ||
const body = msgpack_1.encode(message); | ||
return this.encryptOnly(msgpack_1.encode([ | ||
body, | ||
this.outVector.sign(body) | ||
])); | ||
} | ||
} | ||
exports.Writer = Writer; | ||
//# sourceMappingURL=Writer.js.map |
101
Readme.md
@@ -25,2 +25,6 @@ # @consento/crypto | ||
- `Handshake` - the process to connect two separate processes/devices resulting in a `Connection` for each process. | ||
- `SignVector` - operations on a `Channel` **may** be `vectored` with means that there is a new sign/verify keypair for every new message. | ||
The `SignVector` holds the `index` and current `sign` or `verify` key. | ||
- `Codec` - Data written by a reader or read by a writer will be transported binary (`Uint8Array`), a `Codec` specifies how an object read | ||
or written will be translated from/to binary data. | ||
@@ -58,2 +62,26 @@ ## Sending/Receiving encrypted messages | ||
To make sure that the order of the encrypted messages is maintained you can use `SignVector`s that will rotate the signing | ||
key for each message. | ||
```javascript | ||
const { createSignVectors } = require('@consento/crypto') | ||
const { inVector, outVector } = createSignVectors() | ||
const message = Buffer.from('hello world') | ||
const sigA = outVector.sign(message) | ||
const sigB = outVector.sign(message) // Both signatures are different!j | ||
inVector.verify(message, sigA) | ||
inVector.verify(message, sigB) // The signatures need to be verified in order, else an exception will be thrown | ||
// Using the in-/outVector in combination with readers and writers will affect the `encryptNext`, `decryptNext` operation | ||
const { reader, writer } = createChannel() | ||
reader.inVector = inVector | ||
writer.outVector = outVector | ||
const encrypted = writer.encryptNext('hello world') // With the inVector and outVector set, the order is maintained | ||
const message = reader.decryptNext(encrypted) // This would thrown an error if the signature can't be verified | ||
``` | ||
All objects created using `createChannel` are well de-/serializable: | ||
@@ -72,2 +100,17 @@ | ||
## Codecs | ||
Any data sent out through `Writer`s or `Reader`s is encoded using mechanism, by default it will be using `msgpack` | ||
but you can specify any codec supported by [`@consento/codecs`](https://github.com/consento-org/codecs). | ||
```js | ||
const { createChannel } = require('@consento/crypto') | ||
const { writer } = createChannel({ codec: 'json' }) // | ||
writer.encrypt({ foo: 'hello' }) // Changes the binary format to be utf-8 encoded JSON data. | ||
const differentCodec = new Writer({ ...writer.toJSON(), codec: 'msgpack' }) | ||
writer.encrypt({ foo: 'hello' }) // Looks same but the binary data is now encoded using msgpack | ||
``` | ||
### .verifier | ||
@@ -86,3 +129,3 @@ | ||
Encrypt and sign a given input with the sender key. | ||
Encrypt and sign a given input with the `encryptKey` and `signKey`. | ||
@@ -109,2 +152,51 @@ - `body` - what you like to encrypt, any serializable object is possible | ||
#### signVector.sign(message) | ||
- `message` - an `Uint8Array` that should be signed. | ||
```javascript | ||
const { outVector } = createSignVectors() | ||
inVector.verify('hello world') | ||
``` | ||
#### signVector.verify(message, signature) | ||
- `message` - an `Uint8Array` with the message for the signature | ||
- `signature` - an `Uint8Array` that contains the signature | ||
```javascript | ||
const { inVector } = createSignVectors() | ||
inVector.verify(message, signature) | ||
``` | ||
#### writer.outVector, reader.inVector | ||
An optional property which enables vectored encryption in `writer.encryptNext` and | ||
`writer.encryptNextOnly` and `reader.decryptNext` respectively. | ||
#### writer.encryptNext(body) | ||
If an `.outVector` is present, this method will add a signature from the `outVector` to | ||
the data before encrypting and signing the data, else behaves same as `writer.encrypt`. | ||
- `body` - what you like to encrypt, any serializable object is possible | ||
```javascript | ||
const encrypted = writer.encryptNext('secret message') | ||
encrypted.signature // Uint8Array | ||
encrypted.body // Uint8Array | ||
``` | ||
#### writer.encryptNextOnly(body) | ||
If an `.outVector` is present, this method will add a signature from the `outVector` to | ||
the data before encrypting the data, else it behaves same as `writer.encryptOnly`. | ||
- `body` - what you like to encrypt, any serializable object is possible | ||
```javascript | ||
const encrypted = writer.encryptNextOnly('secret message') | ||
encrypted // Uint8Array with an encrypted message | ||
``` | ||
#### writer.sign(data) | ||
@@ -154,2 +246,9 @@ | ||
#### reader.decryptNext(encrypted) | ||
If an `.inVector` is present, this method will verify the signature using the `inVector` to | ||
the data after decrypting the data, else it behaves same as `writer.decrypt`. | ||
- `encrypted` - `{ signature: Uint8Array, body: Uint8Array }` as created by `writer.encryptNext` or `Uint8Array` created with `writer.encryptNextOnly` | ||
## Creating a handshake | ||
@@ -156,0 +255,0 @@ |
import { IEncryptedBlob, IEncryptedBlobJSON } from '../types' | ||
import { bufferToString } from '../util/buffer' | ||
import { Buffer, IEncodable } from '../util/types' | ||
import { Buffer } from '../util/types' | ||
import { encrypt, decrypt, createSecret } from '../util/secretbox' | ||
import * as sodium from 'sodium-universal' | ||
import codecs, { INamedCodec } from '@consento/codecs' | ||
@@ -89,6 +90,6 @@ const { | ||
export function encryptBlob (encodable: IEncodable): { blob: IEncryptedBlob, encrypted: Uint8Array } { | ||
export function encryptBlob <TType = any> (encodable: TType, codec: INamedCodec<string, TType> = codecs.msgpack): { blob: IEncryptedBlob, encrypted: Uint8Array } { | ||
const secretKey = createSecret() | ||
const path = pathForSecretKey(secretKey) | ||
const encrypted = encrypt(secretKey, encodable) | ||
const encrypted = encrypt(secretKey, codec.encode(encodable)) | ||
return { | ||
@@ -100,4 +101,4 @@ blob: newBlob(secretKey, path, encrypted.length), | ||
export function decryptBlob (secretKey: Uint8Array, encrypted: Uint8Array): IEncodable { | ||
return decrypt(secretKey, encrypted) | ||
export function decryptBlob <TType = any> (secretKey: Uint8Array, encrypted: Uint8Array, codec: INamedCodec<string, TType> = codecs.msgpack): TType { | ||
return codec.decode(decrypt(secretKey, encrypted)) | ||
} |
import { Buffer, toBuffer, bufferToString, isStringOrBuffer } from '../util' | ||
import { IHandshakeInit, IReader, IHandshakeInitOptions, IHandshakeAccept, IHandshakeAcceptMessage, IHandshakeAcceptOptions, IHandshakeConfirmation, IHandshakeAcceptJSON, IHandshakeConfirmationOptions, IHandshakeConfirmationJSON, IConnection, IHandshakeInitJSON } from '../types' | ||
import { createChannel, Reader, Writer, Connection } from '../primitives' | ||
import { IHandshakeInit, IReader, IHandshakeInitOptions, IHandshakeAccept, IHandshakeAcceptMessage, IHandshakeAcceptOptions, IHandshakeConfirmation, IHandshakeAcceptJSON, IHandshakeConfirmationOptions, IHandshakeConfirmationJSON, IConnection, IHandshakeInitJSON, MsgPackCodec, IWriter } from '../types' | ||
import { createChannel, Reader, Writer, Connection, getIOFromConnectionOptions } from '../primitives' | ||
import { randomBuffer } from '../util/randomBuffer' | ||
import { decrypt, encrypt } from '../util/secretbox' | ||
import * as sodium from 'sodium-universal' | ||
import { INamedCodec } from '@consento/codecs' | ||
@@ -29,20 +30,28 @@ const { | ||
} | ||
function processHandshake (msg: Uint8Array): { | ||
token: Uint8Array | ||
writerKey: Uint8Array | ||
} { | ||
if (msg[0] !== HANDSHAKE_MSG_VERSION[0]) { | ||
throw Object.assign(new Error(`Error while processing handshake: Unknown handshake format: ${msg[0]}`), { code: 'unknown-message-format', messageFormat: msg[0] }) | ||
const handshakeCodec: INamedCodec<'handshake', { token: Uint8Array, writerKey: Uint8Array }> = { | ||
name: 'handshake', | ||
encode: ({ token, writerKey }) => Buffer.concat([HANDSHAKE_MSG_VERSION, token, writerKey]), | ||
decode: msg => { | ||
if (msg[0] !== HANDSHAKE_MSG_VERSION[0]) { | ||
throw Object.assign(new Error(`Error while processing handshake: Unknown handshake format: ${msg[0]}`), { code: 'unknown-message-format', messageFormat: msg[0] }) | ||
} | ||
if (msg.length !== 161) { | ||
throw Object.assign(new Error(`Error while processing handshake: Invalid handshake size: ${msg.length}`), { code: 'invalid-size', size: msg.length }) | ||
} | ||
return { | ||
token: msg.slice(1, 33), | ||
writerKey: msg.slice(33) | ||
} | ||
} | ||
if (msg.length !== 161) { | ||
throw Object.assign(new Error(`Error while processing handshake: Invalid handshake size: ${msg.length}`), { code: 'invalid-size', size: msg.length }) | ||
} | ||
return { | ||
token: msg.slice(1, 33), | ||
writerKey: msg.slice(33) | ||
} | ||
} | ||
function remove <T, TProp extends keyof T> (input: T, prop: TProp): Omit<T, TProp> { | ||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete | ||
delete input[prop] | ||
return input | ||
} | ||
export class HandshakeInit implements IHandshakeInit { | ||
input: IReader | ||
input: IReader<MsgPackCodec> | ||
firstMessage: Uint8Array | ||
@@ -59,3 +68,3 @@ handshakeSecret: Uint8Array | ||
return { | ||
input: this.input.toJSON(), | ||
input: remove(this.input.toJSON(), 'codec'), | ||
firstMessage: bufferToString(this.firstMessage, 'base64'), | ||
@@ -69,11 +78,8 @@ handshakeSecret: bufferToString(this.handshakeSecret, 'base64') | ||
const backChannel = createChannel() | ||
const sendKey = decrypt(secretKey, Buffer.from(accept.secret, 'base64')) | ||
if (!(sendKey instanceof Uint8Array)) { | ||
throw Object.assign(new Error(`Expected buffer in decrypted message, got: ${sendKey.constructor.name}`), { code: 'invalid-message', sendKey }) | ||
} | ||
const writerKey = decrypt(secretKey, Buffer.from(accept.secret, 'base64')) | ||
return new HandshakeConfirmation({ | ||
connection: new Connection({ | ||
output: new Writer({ writerKey: sendKey }), | ||
connectionKey: new Connection({ | ||
output: new Writer({ writerKey: writerKey }), | ||
input: backChannel.reader | ||
}), | ||
}).connectionKey, | ||
// In case you are wondering why we not just simply return "backChannel" as sender | ||
@@ -88,13 +94,29 @@ // but instead pass it in two messages: the reason is that without this step | ||
export class HandshakeAccept extends Connection implements IHandshakeAccept { | ||
export class HandshakeAccept implements IHandshakeAccept { | ||
acceptMessage: IHandshakeAcceptMessage | ||
constructor (ops: IHandshakeAcceptOptions) { | ||
super(ops) | ||
this.acceptMessage = ops.acceptMessage | ||
input: IReader<MsgPackCodec> | ||
output: IWriter<MsgPackCodec> | ||
connectionKey: Uint8Array | ||
_connectionKeyBase64?: string | ||
constructor (opts: IHandshakeAcceptOptions) { | ||
const parts = getIOFromConnectionOptions(opts) | ||
this.input = parts.input | ||
this.output = parts.output | ||
this.connectionKey = parts.connectionKey | ||
this._connectionKeyBase64 = parts.connectionKeyBase64 | ||
this.acceptMessage = opts.acceptMessage | ||
} | ||
get connectionKeyBase64 (): string { | ||
if (this._connectionKeyBase64 === undefined) { | ||
this._connectionKeyBase64 = bufferToString(this.connectionKey, 'base64') | ||
} | ||
return this._connectionKeyBase64 | ||
} | ||
toJSON (): IHandshakeAcceptJSON { | ||
return { | ||
...super.toJSON(), | ||
connectionKey: this.connectionKeyBase64, | ||
acceptMessage: this.acceptMessage | ||
@@ -104,6 +126,6 @@ } | ||
finalize (message: Uint8Array): IConnection { | ||
finalize (message: Uint8Array): IConnection<MsgPackCodec, MsgPackCodec> { | ||
return new Connection({ | ||
input: this.input, | ||
output: new Writer({ writerKey: message }) | ||
output: { writerKey: message } | ||
}) | ||
@@ -115,6 +137,6 @@ } | ||
finalMessage: Uint8Array | ||
connection: IConnection | ||
connection: IConnection<MsgPackCodec, MsgPackCodec> | ||
constructor (opts: IHandshakeConfirmationOptions) { | ||
this.connection = new Connection(opts.connection) | ||
this.connection = new Connection({ connectionKey: opts.connectionKey }) | ||
this.finalMessage = toBuffer(opts.finalMessage) | ||
@@ -125,3 +147,3 @@ } | ||
return { | ||
connection: this.connection.toJSON(), | ||
connectionKey: this.connection.connectionKeyBase64, | ||
finalMessage: bufferToString(this.finalMessage, 'base64') | ||
@@ -138,7 +160,3 @@ } | ||
handshakeSecret: handshake.secretKey, | ||
firstMessage: Buffer.concat([ | ||
HANDSHAKE_MSG_VERSION, | ||
handshake.publicKey, | ||
channel.writer.writerKey | ||
]) | ||
firstMessage: handshakeCodec.encode({ token: handshake.publicKey, writerKey: channel.writer.writerKey }) | ||
}) | ||
@@ -151,3 +169,3 @@ } | ||
writerKey | ||
} = processHandshake(firstMessage) | ||
} = handshakeCodec.decode(firstMessage) | ||
const handshake = createHandshake() | ||
@@ -157,4 +175,4 @@ const secretKey = computeSecret(handshake.secretKey, token) | ||
return new HandshakeAccept({ | ||
output: { writerKey }, | ||
input: receiver, | ||
output: writerKey, | ||
input: receiver.readerKey, | ||
acceptMessage: { | ||
@@ -161,0 +179,0 @@ token: bufferToString(handshake.publicKey, 'base64'), |
@@ -9,64 +9,41 @@ import { | ||
} from '../types' | ||
import { Buffer, bufferToString, toBuffer, Inspectable } from '../util' | ||
import { bufferToString, toBuffer, Inspectable } from '../util' | ||
import { Reader } from './Reader' | ||
import { Writer } from './Writer' | ||
import * as sodium from 'sodium-universal' | ||
import { readerKeyFromChannelKey, writerKeyFromChannelKey } from './key' | ||
import { InspectOptions } from 'inspect-custom-symbol' | ||
import prettyHash from 'pretty-hash' | ||
import { Codec, CodecOption } from '@consento/codecs' | ||
const { | ||
sodium_malloc: malloc, | ||
crypto_box_PUBLICKEYBYTES: CRYPTO_BOX_PUBLICKEYBYTES, | ||
crypto_box_SECRETKEYBYTES: CRYPTO_BOX_SECRETKEYBYTES, | ||
crypto_box_keypair: boxKeyPair, | ||
crypto_sign_PUBLICKEYBYTES: CRYPTO_SIGN_PUBLICKEYBYTES, | ||
crypto_sign_SECRETKEYBYTES: CRYPTO_SIGN_SECRETKEYBYTES, | ||
crypto_sign_keypair: signKeyPair | ||
} = sodium.default | ||
interface IRawKeys { | ||
publicKey: Uint8Array | ||
privateKey: Uint8Array | ||
} | ||
function createEncryptionKeys (): IRawKeys { | ||
const keys = { | ||
publicKey: malloc(CRYPTO_BOX_PUBLICKEYBYTES), | ||
privateKey: malloc(CRYPTO_BOX_SECRETKEYBYTES) | ||
} | ||
boxKeyPair(keys.publicKey, keys.privateKey) | ||
return keys | ||
} | ||
function createSignKeys (): IRawKeys { | ||
const keys = { | ||
publicKey: malloc(CRYPTO_SIGN_PUBLICKEYBYTES), | ||
privateKey: malloc(CRYPTO_SIGN_SECRETKEYBYTES) | ||
} | ||
signKeyPair(keys.publicKey, keys.privateKey) | ||
return keys | ||
} | ||
export function createChannel (): Channel { | ||
const encrypt = createEncryptionKeys() | ||
const sign = createSignKeys() | ||
return new Channel({ channelKey: Buffer.concat([encrypt.publicKey, sign.publicKey, encrypt.privateKey, sign.privateKey]) }) | ||
} | ||
export class Channel extends Inspectable implements IChannel { | ||
_reader?: IReader | ||
_writer?: IWriter | ||
_channelKey?: Uint8Array | ||
export class Channel <TCodec extends CodecOption = undefined> extends Inspectable implements IChannel<Codec<TCodec, 'msgpack'>> { | ||
reader: IReader<Codec<TCodec, 'msgpack'>> | ||
writer: IWriter<Codec<TCodec, 'msgpack'>> | ||
channelKey: Uint8Array | ||
_channelKeyBase64?: string | ||
constructor ({ channelKey }: IChannelOptions) { | ||
constructor ({ channelKey, inVector, outVector, codec }: IChannelOptions<TCodec>) { | ||
super() | ||
if (typeof channelKey === 'string') { | ||
this._channelKeyBase64 = channelKey | ||
this.channelKey = toBuffer(channelKey) | ||
} else { | ||
this._channelKey = channelKey | ||
this.channelKey = channelKey | ||
} | ||
this.reader = new Reader({ readerKey: readerKeyFromChannelKey(this.channelKey), inVector, codec }) | ||
this.writer = new Writer({ writerKey: writerKeyFromChannelKey(this.channelKey), outVector, codec }) | ||
} | ||
recodec <TCodec extends CodecOption = undefined> (codec: TCodec): IChannel<Codec<TCodec, 'msgpack'>> { | ||
return new Channel({ | ||
channelKey: this.channelKey, | ||
inVector: this.reader.inVector, | ||
outVector: this.writer.outVector, | ||
codec | ||
}) | ||
} | ||
get codec (): Codec<TCodec, 'msgpack'> { | ||
return this.reader.codec | ||
} | ||
get verifyKey (): Uint8Array { | ||
@@ -84,12 +61,5 @@ return this.reader.verifyKey | ||
get channelKey (): Uint8Array { | ||
if (this._channelKey === undefined) { | ||
this._channelKey = toBuffer(this._channelKeyBase64 as unknown as string) | ||
} | ||
return this._channelKey | ||
} | ||
get channelKeyBase64 (): string { | ||
if (this._channelKeyBase64 === undefined) { | ||
this._channelKeyBase64 = bufferToString(this._channelKey as unknown as Uint8Array, 'base64') | ||
this._channelKeyBase64 = bufferToString(this.channelKey as unknown as Uint8Array, 'base64') | ||
} | ||
@@ -99,16 +69,2 @@ return this._channelKeyBase64 | ||
get reader (): IReader { | ||
if (this._reader === undefined) { | ||
this._reader = new Reader({ readerKey: readerKeyFromChannelKey(this.channelKey) }) | ||
} | ||
return this._reader | ||
} | ||
get writer (): IWriter { | ||
if (this._writer === undefined) { | ||
this._writer = new Writer({ writerKey: writerKeyFromChannelKey(this.channelKey) }) | ||
} | ||
return this._writer | ||
} | ||
get verifier (): IVerifier { | ||
@@ -120,10 +76,13 @@ return this.reader.verifier | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Channel(${stylize(prettyHash(this.reader.verifyKey), 'string')})` | ||
return `Channel(${stylize(this.codec.name, 'special')}|${stylize(prettyHash(this.reader.verifyKey), 'string')})` | ||
} | ||
toJSON (): IChannelJSON { | ||
toJSON (): IChannelJSON<Codec<TCodec, 'msgpack'>> { | ||
return { | ||
channelKey: this.channelKeyBase64 | ||
channelKey: this.channelKeyBase64, | ||
inVector: this.reader.inVector?.toJSON(), | ||
outVector: this.writer.outVector?.toJSON(), | ||
codec: this.reader.codec.name | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { IConnection, IReader, IWriter, IConnectionJSON, IConnectionOptions } from '../types' | ||
import { IConnection, IReader, IWriter, IConnectionJSON, IConnectionOptions, IConnectionOptionsByKey, IConnectionOptionsByIO } from '../types' | ||
import { Reader } from './Reader' | ||
@@ -9,32 +9,76 @@ import { Writer } from './Writer' | ||
import prettyHash from 'pretty-hash' | ||
import { Codec, CodecOption } from '@consento/codecs' | ||
export class Connection extends Inspectable implements IConnection { | ||
_input?: IReader | ||
_output?: IWriter | ||
_connectionKey?: Uint8Array | ||
_connectionKeyBase64?: string | ||
function isConnectionOptionsByKey (input: IConnectionOptions<any, any>): input is IConnectionOptionsByKey { | ||
return 'connectionKey' in input | ||
} | ||
constructor (opts: IConnectionOptions) { | ||
super() | ||
if ('connectionKey' in opts) { | ||
if (typeof opts.connectionKey === 'string') { | ||
this._connectionKeyBase64 = opts.connectionKey | ||
} else { | ||
this._connectionKey = opts.connectionKey | ||
} | ||
function isConnectionOptionsByIO (input: IConnectionOptions<any, any>): input is IConnectionOptionsByIO { | ||
return 'input' in input && 'output' in input | ||
} | ||
export function getIOFromConnectionOptions <TInputCodec extends CodecOption = undefined, TOutputCodec extends CodecOption = undefined> (opts: IConnectionOptions<TInputCodec, TOutputCodec>): { | ||
connectionKey: Uint8Array | ||
connectionKeyBase64?: string | ||
input: IReader<Codec<TInputCodec, 'msgpack'>> | ||
output: IWriter<Codec<TOutputCodec, 'msgpack'>> | ||
} { | ||
let readerKey: Uint8Array | ||
let writerKey: Uint8Array | ||
let inCodec = opts.inCodec | ||
let outCodec = opts.outCodec | ||
let inVector = opts.inVector | ||
let outVector = opts.outVector | ||
let connectionKey: Uint8Array | ||
let connectionKeyBase64: string | undefined | ||
if (isConnectionOptionsByKey(opts)) { | ||
if (typeof opts.connectionKey === 'string') { | ||
connectionKeyBase64 = opts.connectionKey | ||
} | ||
connectionKey = toBuffer(opts.connectionKey) | ||
readerKey = inReaderKeyFromConnectionKey(connectionKey) | ||
writerKey = outWriterKeyFromConnectionKey(connectionKey) | ||
} else if (isConnectionOptionsByIO(opts)) { | ||
if (isStringOrBuffer(opts.input)) { | ||
readerKey = toBuffer(opts.input) | ||
} else { | ||
this._input = isStringOrBuffer(opts.input) ? new Reader({ readerKey: opts.input }) : 'readerKey' in opts.input ? new Reader(opts.input) : opts.input | ||
this._output = isStringOrBuffer(opts.output) ? new Writer({ writerKey: opts.output }) : 'writerKey' in opts.output ? new Writer(opts.output) : opts.output | ||
this._connectionKey = Buffer.concat([this._input.readerKey, this._output.writerKey]) | ||
readerKey = toBuffer(opts.input.readerKey) | ||
inVector = inVector ?? opts.input.inVector | ||
inCodec = inCodec ?? opts.input.codec as any | ||
} | ||
if (bufferEquals(this.input.verifyKey, this.output.verifyKey)) { | ||
throw new Error('Can not create a connection with both the writer and the reader have the same id! Did you mean to restore a channel?') | ||
if (isStringOrBuffer(opts.output)) { | ||
writerKey = toBuffer(opts.output) | ||
} else { | ||
writerKey = toBuffer(opts.output.writerKey) | ||
outVector = outVector ?? opts.output.outVector | ||
outCodec = outCodec ?? opts.output.codec as any | ||
} | ||
connectionKey = Buffer.concat([readerKey, writerKey]) | ||
} else { | ||
throw new Error('Options for connection invalid, either connectionKey or input/output must be given.') | ||
} | ||
return { | ||
connectionKey, | ||
connectionKeyBase64, | ||
input: new Reader({ readerKey, inVector, codec: inCodec }), | ||
output: new Writer({ writerKey, outVector, codec: outCodec }) | ||
} | ||
} | ||
get connectionKey (): Uint8Array { | ||
if (this._connectionKey === undefined) { | ||
this._connectionKey = toBuffer(this._connectionKeyBase64 as unknown as string) | ||
export class Connection <TInputCodec extends CodecOption = undefined, TOutputCodec extends CodecOption = undefined> extends Inspectable implements IConnection<Codec<TInputCodec, 'msgpack'>, Codec<TOutputCodec, 'msgpack'>> { | ||
input: IReader<Codec<TInputCodec, 'msgpack'>> | ||
output: IWriter<Codec<TOutputCodec, 'msgpack'>> | ||
connectionKey: Uint8Array | ||
_connectionKeyBase64?: string | ||
constructor (opts: IConnectionOptions<TInputCodec, TOutputCodec>) { | ||
super() | ||
const parts = getIOFromConnectionOptions(opts) | ||
this.input = parts.input | ||
this.output = parts.output | ||
this.connectionKey = parts.connectionKey | ||
this._connectionKeyBase64 = parts.connectionKeyBase64 | ||
if (bufferEquals(this.input.verifyKey, this.output.verifyKey)) { | ||
throw new Error('Can not create a connection with both the writer and the reader have the same id! Did you mean to restore a channel?') | ||
} | ||
return this._connectionKey | ||
} | ||
@@ -44,3 +88,3 @@ | ||
if (this._connectionKeyBase64 === undefined) { | ||
this._connectionKeyBase64 = bufferToString(this._connectionKey as unknown as Uint8Array, 'base64') | ||
this._connectionKeyBase64 = bufferToString(this.connectionKey as unknown as Uint8Array, 'base64') | ||
} | ||
@@ -50,19 +94,9 @@ return this._connectionKeyBase64 | ||
get input (): IReader { | ||
if (this._input === undefined) { | ||
this._input = new Reader({ readerKey: inReaderKeyFromConnectionKey(this.connectionKey) }) | ||
} | ||
return this._input | ||
} | ||
get output (): IWriter { | ||
if (this._output === undefined) { | ||
this._output = new Writer({ writerKey: outWriterKeyFromConnectionKey(this.connectionKey) }) | ||
} | ||
return this._output | ||
} | ||
toJSON (): IConnectionJSON { | ||
toJSON (): IConnectionJSON<Codec<TInputCodec, 'msgpack'>, Codec<TOutputCodec, 'msgpack'>> { | ||
return { | ||
connectionKey: this.connectionKeyBase64 | ||
connectionKey: this.connectionKeyBase64, | ||
inCodec: this.input.codec.name, | ||
outCodec: this.output.codec.name, | ||
inVector: this.input.inVector?.toJSON(), | ||
outVector: this.output.outVector?.toJSON() | ||
} | ||
@@ -73,4 +107,13 @@ } | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Connection(input=${stylize(prettyHash(this.input.verifyKey), 'string')}, output=${stylize(prettyHash(this.output.verifyKey), 'string')})` | ||
let input: string = `input=${stylize(this.input.codec.name, 'special')}|${stylize(prettyHash(this.input.verifyKey), 'string')}` | ||
if (this.input.inVector !== undefined) { | ||
input += `#${this.input.inVector.index}` | ||
} | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
let output: string = `output=${stylize(this.output.codec.name, 'special')}|${stylize(prettyHash(this.output.verifyKey), 'string')}` | ||
if (this.output.outVector !== undefined) { | ||
output += `#${this.output.outVector.index}` | ||
} | ||
return `Connection(${input}, ${output})` | ||
} | ||
} |
@@ -1,18 +0,45 @@ | ||
import { EDecryptionError, IEncryptedMessage } from '../types' | ||
import { EDecryptionError, IChannelOptions, IEncryptedMessage, IEncryptionKeys, ISignKeys, ISignVector } from '../types' | ||
import * as sodium from 'sodium-universal' | ||
import { bufferToAny, anyToBuffer } from '../util/buffer' | ||
import { IEncodable } from '../util/types' | ||
import { encode, decode } from '@msgpack/msgpack' | ||
import { Channel } from './Channel' | ||
import { Buffer } from 'buffer' | ||
import { SignVector } from './SignVector' | ||
import { CodecOption } from '@consento/codecs' | ||
const { | ||
crypto_box_PUBLICKEYBYTES: CRYPTO_BOX_PUBLICKEYBYTES, | ||
crypto_box_SECRETKEYBYTES: CRYPTO_BOX_SECRETKEYBYTES, | ||
crypto_box_SEALBYTES: CRYPTO_BOX_SEALBYTES, | ||
crypto_box_seal: boxSeal, | ||
crypto_box_seal_open: boxSealOpen, | ||
crypto_box_keypair: boxKeyPair, | ||
crypto_sign_BYTES: CRYPTO_SIGN_BYTES, | ||
crypto_box_seal_open: boxSealOpen, | ||
crypto_box_seal: boxSeal, | ||
crypto_sign_PUBLICKEYBYTES: CRYPTO_SIGN_PUBLICKEYBYTES, | ||
crypto_sign_SECRETKEYBYTES: CRYPTO_SIGN_SECRETKEYBYTES, | ||
crypto_sign_keypair: signKeyPair, | ||
crypto_sign_detached: signDetached, | ||
crypto_sign_verify_detached: _verify, | ||
crypto_sign_detached: signDetached, | ||
sodium_malloc: malloc | ||
} = sodium.default | ||
export function encryptMessage (writeKey: Uint8Array, message: IEncodable): Uint8Array { | ||
const msgBuffer = anyToBuffer(message) | ||
export function createEncryptionKeys (): IEncryptionKeys { | ||
const keys = { | ||
encryptKey: malloc(CRYPTO_BOX_PUBLICKEYBYTES), | ||
decryptKey: malloc(CRYPTO_BOX_SECRETKEYBYTES) | ||
} | ||
boxKeyPair(keys.encryptKey, keys.decryptKey) | ||
return keys | ||
} | ||
export function createSignKeys (): ISignKeys { | ||
const keys = { | ||
verifyKey: malloc(CRYPTO_SIGN_PUBLICKEYBYTES), | ||
signKey: malloc(CRYPTO_SIGN_SECRETKEYBYTES) | ||
} | ||
signKeyPair(keys.verifyKey, keys.signKey) | ||
return keys | ||
} | ||
export function encryptMessage (writeKey: Uint8Array, message: any): Uint8Array { | ||
const msgBuffer = encode(message) | ||
const body = malloc(msgBuffer.length + CRYPTO_BOX_SEALBYTES) | ||
@@ -31,3 +58,3 @@ boxSeal(body, msgBuffer, writeKey) | ||
export function decryptMessage (verifyKey: Uint8Array, writeKey: Uint8Array, readKey: Uint8Array, message: IEncryptedMessage | Uint8Array): IEncodable { | ||
export function decryptMessage (verifyKey: Uint8Array, writeKey: Uint8Array, readKey: Uint8Array, message: IEncryptedMessage | Uint8Array): any { | ||
let bodyIn: Uint8Array | ||
@@ -47,3 +74,3 @@ if (message instanceof Uint8Array) { | ||
} | ||
return bufferToAny(messageDecrypted) | ||
return decode(messageDecrypted) | ||
} | ||
@@ -56,1 +83,18 @@ | ||
} | ||
export function createSignVectors (): { inVector: ISignVector, outVector: ISignVector } { | ||
const keys = createSignKeys() | ||
return { | ||
inVector: new SignVector({ next: keys.verifyKey }), | ||
outVector: new SignVector({ next: keys.signKey }) | ||
} | ||
} | ||
export function createChannel <TCodec extends CodecOption = undefined> (opts?: Omit<IChannelOptions<TCodec>, 'channelKey'>): Channel<TCodec> { | ||
const encrypt = createEncryptionKeys() | ||
const sign = createSignKeys() | ||
return new Channel<TCodec>({ | ||
channelKey: Buffer.concat([encrypt.encryptKey, sign.verifyKey, encrypt.decryptKey, sign.signKey]), | ||
...opts | ||
}) | ||
} |
@@ -1,10 +0,25 @@ | ||
import { IVerifier, IReader, IReaderJSON, IEncryptedMessage, IReaderOptions } from '../types' | ||
import { IVerifier, IReader, IReaderJSON, IEncryptedMessage, IReaderOptions, ISignVector, EDecryptionError } from '../types' | ||
import { Verifier } from './Verifier' | ||
import { encryptKeyFromSendOrReceiveKey, decryptKeyFromReceiveKey, verifyKeyFromSendOrReceiveKey } from './key' | ||
import { bufferToString, IEncodable, Inspectable, toBuffer } from '../util' | ||
import { bufferToString, exists, Inspectable, toBuffer } from '../util' | ||
import { encryptMessage, decryptMessage } from './fn' | ||
import { InspectOptions } from 'inspect-custom-symbol' | ||
import prettyHash from 'pretty-hash' | ||
import { decode } from '@msgpack/msgpack' | ||
import { SignVector } from './SignVector' | ||
import codecs, { Codec, CodecOption } from '@consento/codecs' | ||
export class Reader extends Inspectable implements IReader { | ||
function assertVectoredMessage (input: any): asserts input is [ body: Uint8Array, signature: Uint8Array ] { | ||
if (!Array.isArray(input)) { | ||
throw Object.assign(new Error('Message needs to be an array'), { code: EDecryptionError.invalidMessage }) | ||
} | ||
if (input.length === 0) { | ||
throw Object.assign(new Error('The next structure needs to have a body.'), { code: EDecryptionError.missingBody }) | ||
} | ||
if (input.length === 1) { | ||
throw Object.assign(new Error('The next structure needs to have a signature.'), { code: EDecryptionError.missingSignature }) | ||
} | ||
} | ||
export class Reader <TCodec extends CodecOption = undefined> extends Inspectable implements IReader <Codec<TCodec, 'msgpack'>> { | ||
_receiveKey?: Uint8Array | ||
@@ -15,12 +30,22 @@ _receiveKeyBase64?: string | ||
_verifier?: IVerifier | ||
inVector?: ISignVector | ||
codec: Codec<TCodec, 'msgpack'> | ||
constructor ({ readerKey: receiveKey }: IReaderOptions) { | ||
constructor ({ readerKey, inVector, codec }: IReaderOptions<TCodec>) { | ||
super() | ||
if (typeof receiveKey === 'string') { | ||
this._receiveKeyBase64 = receiveKey | ||
if (typeof readerKey === 'string') { | ||
this._receiveKeyBase64 = readerKey | ||
} else { | ||
this._receiveKey = receiveKey | ||
this._receiveKey = readerKey | ||
} | ||
if (exists(inVector)) { | ||
this.inVector = new SignVector(inVector) | ||
} | ||
this.codec = codecs(codec, 'msgpack') | ||
} | ||
recodec <TCodec extends CodecOption = undefined> (codec: TCodec): IReader<Codec<TCodec, 'msgpack'>> { | ||
return new Reader({ readerKey: this.readerKey, inVector: this.inVector, codec }) | ||
} | ||
get verifyKey (): Uint8Array { | ||
@@ -73,16 +98,21 @@ return this.verifier.verifyKey | ||
toJSON (): IReaderJSON { | ||
return { readerKey: this.readerKeyBase64 } | ||
toJSON (): IReaderJSON<Codec<TCodec, 'msgpack'>> { | ||
return { | ||
readerKey: this.readerKeyBase64, | ||
inVector: this.inVector?.toJSON(), | ||
codec: this.codec.name | ||
} | ||
} | ||
_inspect (_: number, { stylize }: InspectOptions): string { | ||
const vector = this.inVector !== undefined ? `#${this.inVector.index}` : '' | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Reader(${stylize(prettyHash(this.verifyKey), 'string')})` | ||
return `Reader(${stylize(this.codec.name, 'special')}|${stylize(prettyHash(this.verifyKey), 'string')}${vector})` | ||
} | ||
encryptOnly (message: IEncodable): Uint8Array { | ||
encryptOnly (message: any): Uint8Array { | ||
return encryptMessage(this.encryptKey, message) | ||
} | ||
decrypt (encrypted: IEncryptedMessage): IEncodable { | ||
decrypt (encrypted: IEncryptedMessage): any { | ||
return decryptMessage( | ||
@@ -95,2 +125,13 @@ this.verifier.verifyKey, | ||
} | ||
decryptNext (encrypted: IEncryptedMessage): any { | ||
if (this.inVector === undefined) { | ||
return this.decrypt(encrypted) | ||
} | ||
const raw = decode(this.decrypt(encrypted)) | ||
assertVectoredMessage(raw) | ||
const [body, signature] = raw | ||
this.inVector.verify(body, signature) | ||
return decode(body) | ||
} | ||
} |
@@ -1,25 +0,38 @@ | ||
import { IVerifier, IWriter, IWriterJSON, IEncryptedMessage, IWriterOptions } from '../types' | ||
import { IVerifier, IWriter, IWriterJSON, IEncryptedMessage, IWriterOptions, ISignVector } from '../types' | ||
import { Verifier } from './Verifier' | ||
import { encryptKeyFromSendOrReceiveKey, signKeyFromSendKey, verifyKeyFromSendOrReceiveKey } from './key' | ||
import { bufferToString, toBuffer, IEncodable, Inspectable } from '../util' | ||
import { bufferToString, toBuffer, Inspectable, exists } from '../util' | ||
import { encryptMessage, sign } from './fn' | ||
import { InspectOptions } from 'inspect-custom-symbol' | ||
import prettyHash from 'pretty-hash' | ||
import { SignVector } from './SignVector' | ||
import { encode } from '@msgpack/msgpack' | ||
import codecs, { Codec, CodecOption } from '@consento/codecs' | ||
export class Writer extends Inspectable implements IWriter { | ||
_sendKey?: Uint8Array | ||
_sendKeyBase64?: string | ||
export class Writer <TCodec extends CodecOption = undefined> extends Inspectable implements IWriter<Codec<TCodec, 'msgpack'>> { | ||
_writerKey?: Uint8Array | ||
_writerKeyBase64?: string | ||
_annonymous?: IVerifier | ||
_signKey?: Uint8Array | ||
_encryptKey?: Uint8Array | ||
outVector?: ISignVector | ||
codec: Codec<TCodec, 'msgpack'> | ||
constructor ({ writerKey: sendKey }: IWriterOptions) { | ||
constructor ({ writerKey, outVector, codec }: IWriterOptions<TCodec>) { | ||
super() | ||
if (typeof sendKey === 'string') { | ||
this._sendKeyBase64 = sendKey | ||
if (typeof writerKey === 'string') { | ||
this._writerKeyBase64 = writerKey | ||
} else { | ||
this._sendKey = sendKey | ||
this._writerKey = writerKey | ||
} | ||
if (exists(outVector)) { | ||
this.outVector = new SignVector(outVector) | ||
} | ||
this.codec = codecs(codec, 'msgpack') | ||
} | ||
recodec <TCodec extends CodecOption = undefined> (codec: TCodec): IWriter<Codec<TCodec, 'msgpack'>> { | ||
return new Writer({ writerKey: this.writerKey, outVector: this.outVector, codec }) | ||
} | ||
get signKey (): Uint8Array { | ||
@@ -40,13 +53,13 @@ if (this._signKey === undefined) { | ||
get writerKey (): Uint8Array { | ||
if (this._sendKey === undefined) { | ||
this._sendKey = toBuffer(this._sendKeyBase64 as unknown as string) | ||
if (this._writerKey === undefined) { | ||
this._writerKey = toBuffer(this._writerKeyBase64 as unknown as string) | ||
} | ||
return this._sendKey | ||
return this._writerKey | ||
} | ||
get writerKeyBase64 (): string { | ||
if (this._sendKeyBase64 === undefined) { | ||
this._sendKeyBase64 = bufferToString(this._sendKey as unknown as Uint8Array, 'base64') | ||
if (this._writerKeyBase64 === undefined) { | ||
this._writerKeyBase64 = bufferToString(this._writerKey as unknown as Uint8Array, 'base64') | ||
} | ||
return this._sendKeyBase64 | ||
return this._writerKeyBase64 | ||
} | ||
@@ -73,9 +86,14 @@ | ||
toJSON (): IWriterJSON { | ||
return { writerKey: this.writerKeyBase64 } | ||
toJSON (): IWriterJSON<Codec<TCodec, 'msgpack'>> { | ||
return { | ||
writerKey: this.writerKeyBase64, | ||
outVector: this.outVector?.toJSON(), | ||
codec: this.codec.name | ||
} | ||
} | ||
_inspect (_: number, { stylize }: InspectOptions): string { | ||
const vector = this.outVector !== undefined ? `#${this.outVector.index}` : '' | ||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions | ||
return `Writer(${stylize(prettyHash(this.verifyKey), 'string')})` | ||
return `Writer(${stylize(this.codec.name, 'special')}|${stylize(prettyHash(this.verifyKey), 'string')}${vector})` | ||
} | ||
@@ -87,7 +105,7 @@ | ||
encryptOnly (message: IEncodable): Uint8Array { | ||
encryptOnly (message: any): Uint8Array { | ||
return encryptMessage(this.encryptKey, message) | ||
} | ||
encrypt (message: IEncodable): IEncryptedMessage { | ||
encrypt (message: any): IEncryptedMessage { | ||
const body = encryptMessage(this.encryptKey, message) | ||
@@ -99,2 +117,21 @@ return { | ||
} | ||
encryptNext (message: any): IEncryptedMessage { | ||
const body = this.encryptOnlyNext(message) | ||
return { | ||
signature: this.sign(body), | ||
body | ||
} | ||
} | ||
encryptOnlyNext (message: any): Uint8Array { | ||
if (this.outVector === undefined) { | ||
return this.encryptOnly(message) | ||
} | ||
const body = encode(message) | ||
return this.encryptOnly(encode([ | ||
body, | ||
this.outVector.sign(body) | ||
])) | ||
} | ||
} |
166
src/types.ts
/* eslint-disable @typescript-eslint/method-signature-style */ | ||
import { IEncodable, IStringOrBuffer } from './util/types' | ||
import codecs, { Codec, CodecOption, INamedCodec } from '@consento/codecs' | ||
import { IStringOrBuffer } from './util/types' | ||
@@ -9,5 +10,23 @@ export interface IEncryptedMessage { | ||
export interface IEncryptionKeys { | ||
encryptKey: Uint8Array | ||
decryptKey: Uint8Array | ||
} | ||
export interface ISignKeys { | ||
signKey: Uint8Array | ||
verifyKey: Uint8Array | ||
} | ||
export enum EDecryptionError { | ||
invalidEncryption = 'invalid-encryption', | ||
invalidSignature = 'invalid-signature' | ||
invalidSignature = 'invalid-signature', | ||
invalidMessage = 'invalid-message', | ||
missingBody = 'missing-body', | ||
missingSignature = 'missing-signature', | ||
unexpectedIndex = 'unexpected-index', | ||
vectorIntegrity = 'vector-integrity', | ||
vectorPayload = 'vector-payload', | ||
vectorIndex = 'vector-index', | ||
vectorNext = 'vector-next' | ||
} | ||
@@ -35,12 +54,18 @@ | ||
export interface IWriterJSON { | ||
type PropType<TObj, TProp extends keyof TObj> = TObj[TProp] | ||
export interface IWriterJSON <TCodec extends INamedCodec> { | ||
writerKey: string | ||
outVector?: ISignVectorJSON | ||
codec: PropType<TCodec, 'name'> | ||
} | ||
export interface IWriterOptions { | ||
export interface IWriterOptions <TCodec extends CodecOption> { | ||
writerKey: IStringOrBuffer | ||
outVector?: ISignVectorOptions | ||
codec?: TCodec | ||
} | ||
export interface IWriter extends IChannelActor { | ||
toJSON(): IWriterJSON | ||
export interface IWriter <TCodec extends INamedCodec> extends IChannelActor { | ||
toJSON(): IWriterJSON<TCodec> | ||
readonly writerKey: Uint8Array | ||
@@ -51,20 +76,30 @@ readonly writerKeyBase64: string | ||
readonly verifier: IVerifier | ||
readonly codec: TCodec | ||
outVector?: ISignVector | ||
sign(data: Uint8Array): Uint8Array | ||
encrypt(message: IEncodable): IEncryptedMessage | ||
encryptOnly(message: IEncodable): Uint8Array | ||
encrypt(message: any): IEncryptedMessage | ||
encryptOnly(message: any): Uint8Array | ||
encryptNext(message: any): IEncryptedMessage | ||
encryptOnlyNext(message: any): Uint8Array | ||
} | ||
export interface IReaderJSON { | ||
export interface IReaderJSON <TCodec extends INamedCodec> { | ||
readerKey: string | ||
inVector?: ISignVectorJSON | ||
codec: PropType<TCodec, 'name'> | ||
} | ||
export interface IReaderOptions { | ||
export interface IReaderOptions <TCodec extends CodecOption> { | ||
readerKey: IStringOrBuffer | ||
inVector?: ISignVectorOptions | ||
codec?: TCodec | ||
} | ||
export interface IReader extends IChannelActor { | ||
export interface IReader <TCodec extends INamedCodec> extends IChannelActor { | ||
readonly readerKey: Uint8Array | ||
readonly readerKeyBase64: string | ||
readonly verifier: IVerifier | ||
toJSON(): IReaderJSON | ||
readonly codec: TCodec | ||
inVector?: ISignVector | ||
toJSON(): IReaderJSON<TCodec> | ||
/** | ||
@@ -75,40 +110,85 @@ * Decrypts a message written by an associated Sender | ||
*/ | ||
decrypt(encrypted: IEncryptedMessage | Uint8Array): IEncodable | ||
encryptOnly(message: IEncodable): Uint8Array | ||
decrypt(encrypted: IEncryptedMessage | Uint8Array): any | ||
decryptNext(encrypted: IEncryptedMessage | Uint8Array): any | ||
encryptOnly(message: any): Uint8Array | ||
} | ||
export interface IConnectionJSON { | ||
export interface ISignVector { | ||
next: Uint8Array | ||
nextBase64: string | ||
index: number | ||
increment (next: Uint8Array): Uint8Array | ||
sign (message: Uint8Array): Uint8Array | ||
verify (message: Uint8Array, signature: Uint8Array): void | ||
toJSON (): ISignVectorJSON | ||
} | ||
export interface ISignVectorOptions { | ||
next: IStringOrBuffer | ||
index?: number | ||
} | ||
export interface ISignVectorJSON { | ||
next: string | ||
index: number | ||
} | ||
export interface IConnectionJSON <TInputCodec extends INamedCodec, TOutputCodec extends INamedCodec> { | ||
connectionKey: string | ||
inCodec: PropType<TInputCodec, 'name'> | ||
outCodec: PropType<TOutputCodec, 'name'> | ||
inVector?: ISignVectorJSON | ||
outVector?: ISignVectorJSON | ||
} | ||
export type IConnectionOptions = { | ||
export interface IConnectionOptionsByKey { | ||
connectionKey: IStringOrBuffer | ||
} | { | ||
input: IStringOrBuffer | IReader | IReaderOptions | ||
output: IStringOrBuffer | IWriter | IWriterOptions | ||
} | ||
export interface IChannelJSON { | ||
export interface IConnectionOptionsByIO <TInputCodec extends CodecOption = undefined, TOutputCodec extends CodecOption = undefined> { | ||
input: IStringOrBuffer | IReader<Codec<TInputCodec>> | IReaderOptions<TInputCodec> | ||
output: IStringOrBuffer | IWriter<Codec<TOutputCodec>> | IWriterOptions<TOutputCodec> | ||
} | ||
export type IConnectionOptions <TInputCodec extends CodecOption, TOutputCodec extends CodecOption> = (IConnectionOptionsByKey | IConnectionOptionsByIO<TInputCodec, TOutputCodec>) & { | ||
inCodec?: TInputCodec | ||
outCodec?: TOutputCodec | ||
inVector?: ISignVectorOptions | ||
outVector?: ISignVectorOptions | ||
} | ||
export interface IChannelJSON<TCodec extends INamedCodec> { | ||
channelKey: string | ||
codec: PropType<TCodec, 'name'> | ||
inVector?: ISignVectorJSON | ||
outVector?: ISignVectorJSON | ||
} | ||
export interface IChannelOptions { | ||
export interface IChannelOptions <TCodec extends CodecOption> { | ||
channelKey: IStringOrBuffer | ||
codec?: TCodec | ||
inVector?: ISignVectorOptions | ||
outVector?: ISignVectorOptions | ||
} | ||
export interface IChannel extends IChannelActor { | ||
export interface IChannel <TCodec extends INamedCodec> extends IChannelActor { | ||
verifier: IVerifier | ||
toJSON(): IChannelJSON | ||
codec: TCodec | ||
channelKey: Uint8Array | ||
channelKeyBase64: string | ||
reader: IReader<TCodec> | ||
writer: IWriter<TCodec> | ||
toJSON(): IChannelJSON<TCodec> | ||
} | ||
export interface IConnection { | ||
output: IWriter | ||
input: IReader | ||
export interface IConnection <TInput extends INamedCodec, TOutput extends INamedCodec> { | ||
output: IWriter<TOutput> | ||
input: IReader<TInput> | ||
connectionKey: Uint8Array | ||
connectionKeyBase64: string | ||
toJSON(): IConnectionJSON | ||
toJSON(): IConnectionJSON<TInput, TOutput> | ||
} | ||
export interface IHandshakeInitJSON { | ||
input: IReaderJSON | ||
input: Omit<IReaderJSON<any>, 'codec'> | ||
firstMessage: string | ||
@@ -119,3 +199,3 @@ handshakeSecret: string | ||
export interface IHandshakeInitOptions { | ||
input: IReader | IReaderOptions | ||
input: Omit<IReaderOptions<any>, 'codec'> | ||
firstMessage: IStringOrBuffer | ||
@@ -126,3 +206,3 @@ handshakeSecret: IStringOrBuffer | ||
export interface IHandshakeInit { | ||
input: IReader | ||
input: IReader<MsgPackCodec> | ||
firstMessage: Uint8Array | ||
@@ -139,18 +219,30 @@ handshakeSecret: Uint8Array | ||
export interface IHandshakeAcceptJSON extends IConnectionJSON { | ||
export interface IHandshakeAcceptJSON { | ||
connectionKey: string | ||
acceptMessage: IHandshakeAcceptMessage | ||
} | ||
export type IHandshakeAcceptOptions = IConnectionOptions & { | ||
export type IHandshakeAcceptOptions = ({ | ||
connectionKey: IStringOrBuffer | ||
} | { | ||
input: IStringOrBuffer | ||
output: IStringOrBuffer | ||
}) & { | ||
acceptMessage: IHandshakeAcceptMessage | ||
} | ||
export interface IHandshakeAccept extends IConnection { | ||
export type MsgPackCodec = typeof codecs.msgpack | ||
export interface IHandshakeAccept { | ||
connectionKey: Uint8Array | ||
connectionKeyBase64: string | ||
input: IReader<MsgPackCodec> | ||
output: IWriter<MsgPackCodec> | ||
acceptMessage: IHandshakeAcceptMessage | ||
toJSON(): IHandshakeAcceptJSON | ||
finalize(message: Uint8Array): IConnection | ||
finalize(message: Uint8Array): IConnection<MsgPackCodec, MsgPackCodec> | ||
} | ||
export interface IHandshakeConfirmationOptions { | ||
connection: IConnectionOptions | ||
connectionKey: IStringOrBuffer | ||
finalMessage: IStringOrBuffer | ||
@@ -160,3 +252,3 @@ } | ||
export interface IHandshakeConfirmationJSON { | ||
connection: IConnectionJSON | ||
connectionKey: string | ||
finalMessage: string | ||
@@ -166,3 +258,3 @@ } | ||
export interface IHandshakeConfirmation { | ||
connection: IConnection | ||
connection: IConnection<MsgPackCodec, MsgPackCodec> | ||
finalMessage: Uint8Array | ||
@@ -169,0 +261,0 @@ toJSON(): IHandshakeConfirmationJSON |
@@ -1,14 +0,3 @@ | ||
import { IEncodable, IStringOrBuffer, EEncoding, Buffer } from './types' | ||
import { IStringOrBuffer, EEncoding, Buffer } from './types' | ||
function enumBuffer (num: number): Uint8Array { | ||
const buf = new Uint8Array(1) | ||
buf[0] = num | ||
return buf | ||
} | ||
const MESSAGE_BINARY_UINT8 = enumBuffer(1) | ||
const MESSAGE_BINARY_BUFFER = enumBuffer(2) | ||
const MESSAGE_STRING = enumBuffer(3) | ||
const MESSAGE_JSON = enumBuffer(4) | ||
export function concatUint8Arrays (arrays: Uint8Array[]): Uint8Array { | ||
@@ -25,18 +14,2 @@ const byteLength = arrays.reduce((len, array) => len + array.byteLength, 0) | ||
export function anyToBuffer (message: IEncodable): Uint8Array { | ||
if (typeof message === 'function') { | ||
throw Object.assign(new Error('Cannot turn a function into a buffer'), { code: 'function-not-supported' }) | ||
} | ||
if (message instanceof Buffer) { | ||
return Buffer.concat([MESSAGE_BINARY_BUFFER, message] as Uint8Array[]) | ||
} | ||
if (message instanceof Uint8Array) { | ||
return concatUint8Arrays([MESSAGE_BINARY_UINT8, message]) | ||
} | ||
if (typeof message === 'string') { | ||
return concatUint8Arrays([MESSAGE_STRING, Buffer.from(message)]) | ||
} | ||
return concatUint8Arrays([MESSAGE_JSON, Buffer.from(JSON.stringify(message))]) | ||
} | ||
export function isStringOrBuffer (input: any): input is IStringOrBuffer { | ||
@@ -73,22 +46,1 @@ if (typeof input === 'string') return true | ||
} | ||
export function bufferToAny (buffer: Uint8Array): IEncodable { | ||
switch (buffer[0]) { | ||
case MESSAGE_BINARY_UINT8[0]: | ||
if (buffer instanceof Buffer) { | ||
return new Uint8Array(buffer.buffer, buffer.byteOffset + 1, buffer.byteLength - 1) | ||
} | ||
return buffer.slice(1) | ||
case MESSAGE_BINARY_BUFFER[0]: | ||
if (buffer instanceof Buffer) { | ||
return buffer.slice(1) | ||
} | ||
return Buffer.from(buffer.slice(1)) | ||
case MESSAGE_STRING[0]: | ||
return bufferToString(buffer.slice(1)) | ||
case MESSAGE_JSON[0]: | ||
return JSON.parse(bufferToString(buffer.slice(1))) | ||
default: | ||
throw Object.assign(new Error(`Couldnt deserialize from buffer: Unknown object type[${buffer[0]}].`), { code: 'deserialization-error', type: buffer[0] }) | ||
} | ||
} |
import { Buffer } from 'buffer' | ||
import * as sodium from 'sodium-universal' | ||
import { anyToBuffer, bufferToAny } from './buffer' | ||
import { randomBuffer } from './randomBuffer' | ||
import { IEncodable } from './types' | ||
@@ -28,5 +26,4 @@ const { | ||
export function encrypt (secretKey: Uint8Array, body: IEncodable): Uint8Array { | ||
export function encrypt (secretKey: Uint8Array, message: Uint8Array): Uint8Array { | ||
const nonce = randomBuffer(CRYPTO_SECRETBOX_NONCEBYTES) | ||
const message = anyToBuffer(body) | ||
const ciphertext = malloc(CRYPTO_SECRETBOX_MACBYTES + message.length) | ||
@@ -38,3 +35,3 @@ secretBoxEasy(ciphertext, message, nonce, secretKey) | ||
export function decrypt (secretKey: Uint8Array, encrypted: Uint8Array): IEncodable { | ||
export function decrypt (secretKey: Uint8Array, encrypted: Uint8Array): Uint8Array { | ||
const [nonce, ciphertext] = split(encrypted, CRYPTO_SECRETBOX_NONCEBYTES) | ||
@@ -45,3 +42,3 @@ const decrypted = malloc(ciphertext.length - CRYPTO_SECRETBOX_MACBYTES) | ||
} | ||
return bufferToAny(decrypted) | ||
return decrypted | ||
} |
@@ -11,3 +11,2 @@ import { AbortSignal } from 'abort-controller' | ||
export type IStringOrBuffer = Uint8Array | string | ||
export type IEncodable = IStringOrBuffer | object | ||
@@ -14,0 +13,0 @@ export type EEncoding = 'base64' | 'hex' | 'utf8' |
158
types.d.ts
@@ -1,2 +0,3 @@ | ||
import { IEncodable, IStringOrBuffer } from './util/types'; | ||
import codecs, { Codec, CodecOption, INamedCodec } from '@consento/codecs'; | ||
import { IStringOrBuffer } from './util/types'; | ||
export interface IEncryptedMessage { | ||
@@ -6,5 +7,21 @@ signature: Uint8Array; | ||
} | ||
export interface IEncryptionKeys { | ||
encryptKey: Uint8Array; | ||
decryptKey: Uint8Array; | ||
} | ||
export interface ISignKeys { | ||
signKey: Uint8Array; | ||
verifyKey: Uint8Array; | ||
} | ||
export declare enum EDecryptionError { | ||
invalidEncryption = "invalid-encryption", | ||
invalidSignature = "invalid-signature" | ||
invalidSignature = "invalid-signature", | ||
invalidMessage = "invalid-message", | ||
missingBody = "missing-body", | ||
missingSignature = "missing-signature", | ||
unexpectedIndex = "unexpected-index", | ||
vectorIntegrity = "vector-integrity", | ||
vectorPayload = "vector-payload", | ||
vectorIndex = "vector-index", | ||
vectorNext = "vector-next" | ||
} | ||
@@ -27,10 +44,15 @@ export interface IVerifierJSON { | ||
} | ||
export interface IWriterJSON { | ||
declare type PropType<TObj, TProp extends keyof TObj> = TObj[TProp]; | ||
export interface IWriterJSON<TCodec extends INamedCodec> { | ||
writerKey: string; | ||
outVector?: ISignVectorJSON; | ||
codec: PropType<TCodec, 'name'>; | ||
} | ||
export interface IWriterOptions { | ||
export interface IWriterOptions<TCodec extends CodecOption> { | ||
writerKey: IStringOrBuffer; | ||
outVector?: ISignVectorOptions; | ||
codec?: TCodec; | ||
} | ||
export interface IWriter extends IChannelActor { | ||
toJSON(): IWriterJSON; | ||
export interface IWriter<TCodec extends INamedCodec> extends IChannelActor { | ||
toJSON(): IWriterJSON<TCodec>; | ||
readonly writerKey: Uint8Array; | ||
@@ -41,17 +63,27 @@ readonly writerKeyBase64: string; | ||
readonly verifier: IVerifier; | ||
readonly codec: TCodec; | ||
outVector?: ISignVector; | ||
sign(data: Uint8Array): Uint8Array; | ||
encrypt(message: IEncodable): IEncryptedMessage; | ||
encryptOnly(message: IEncodable): Uint8Array; | ||
encrypt(message: any): IEncryptedMessage; | ||
encryptOnly(message: any): Uint8Array; | ||
encryptNext(message: any): IEncryptedMessage; | ||
encryptOnlyNext(message: any): Uint8Array; | ||
} | ||
export interface IReaderJSON { | ||
export interface IReaderJSON<TCodec extends INamedCodec> { | ||
readerKey: string; | ||
inVector?: ISignVectorJSON; | ||
codec: PropType<TCodec, 'name'>; | ||
} | ||
export interface IReaderOptions { | ||
export interface IReaderOptions<TCodec extends CodecOption> { | ||
readerKey: IStringOrBuffer; | ||
inVector?: ISignVectorOptions; | ||
codec?: TCodec; | ||
} | ||
export interface IReader extends IChannelActor { | ||
export interface IReader<TCodec extends INamedCodec> extends IChannelActor { | ||
readonly readerKey: Uint8Array; | ||
readonly readerKeyBase64: string; | ||
readonly verifier: IVerifier; | ||
toJSON(): IReaderJSON; | ||
readonly codec: TCodec; | ||
inVector?: ISignVector; | ||
toJSON(): IReaderJSON<TCodec>; | ||
/** | ||
@@ -62,33 +94,73 @@ * Decrypts a message written by an associated Sender | ||
*/ | ||
decrypt(encrypted: IEncryptedMessage | Uint8Array): IEncodable; | ||
encryptOnly(message: IEncodable): Uint8Array; | ||
decrypt(encrypted: IEncryptedMessage | Uint8Array): any; | ||
decryptNext(encrypted: IEncryptedMessage | Uint8Array): any; | ||
encryptOnly(message: any): Uint8Array; | ||
} | ||
export interface IConnectionJSON { | ||
export interface ISignVector { | ||
next: Uint8Array; | ||
nextBase64: string; | ||
index: number; | ||
increment(next: Uint8Array): Uint8Array; | ||
sign(message: Uint8Array): Uint8Array; | ||
verify(message: Uint8Array, signature: Uint8Array): void; | ||
toJSON(): ISignVectorJSON; | ||
} | ||
export interface ISignVectorOptions { | ||
next: IStringOrBuffer; | ||
index?: number; | ||
} | ||
export interface ISignVectorJSON { | ||
next: string; | ||
index: number; | ||
} | ||
export interface IConnectionJSON<TInputCodec extends INamedCodec, TOutputCodec extends INamedCodec> { | ||
connectionKey: string; | ||
inCodec: PropType<TInputCodec, 'name'>; | ||
outCodec: PropType<TOutputCodec, 'name'>; | ||
inVector?: ISignVectorJSON; | ||
outVector?: ISignVectorJSON; | ||
} | ||
export declare type IConnectionOptions = { | ||
export interface IConnectionOptionsByKey { | ||
connectionKey: IStringOrBuffer; | ||
} | { | ||
input: IStringOrBuffer | IReader | IReaderOptions; | ||
output: IStringOrBuffer | IWriter | IWriterOptions; | ||
} | ||
export interface IConnectionOptionsByIO<TInputCodec extends CodecOption = undefined, TOutputCodec extends CodecOption = undefined> { | ||
input: IStringOrBuffer | IReader<Codec<TInputCodec>> | IReaderOptions<TInputCodec>; | ||
output: IStringOrBuffer | IWriter<Codec<TOutputCodec>> | IWriterOptions<TOutputCodec>; | ||
} | ||
export declare type IConnectionOptions<TInputCodec extends CodecOption, TOutputCodec extends CodecOption> = (IConnectionOptionsByKey | IConnectionOptionsByIO<TInputCodec, TOutputCodec>) & { | ||
inCodec?: TInputCodec; | ||
outCodec?: TOutputCodec; | ||
inVector?: ISignVectorOptions; | ||
outVector?: ISignVectorOptions; | ||
}; | ||
export interface IChannelJSON { | ||
export interface IChannelJSON<TCodec extends INamedCodec> { | ||
channelKey: string; | ||
codec: PropType<TCodec, 'name'>; | ||
inVector?: ISignVectorJSON; | ||
outVector?: ISignVectorJSON; | ||
} | ||
export interface IChannelOptions { | ||
export interface IChannelOptions<TCodec extends CodecOption> { | ||
channelKey: IStringOrBuffer; | ||
codec?: TCodec; | ||
inVector?: ISignVectorOptions; | ||
outVector?: ISignVectorOptions; | ||
} | ||
export interface IChannel extends IChannelActor { | ||
export interface IChannel<TCodec extends INamedCodec> extends IChannelActor { | ||
verifier: IVerifier; | ||
toJSON(): IChannelJSON; | ||
codec: TCodec; | ||
channelKey: Uint8Array; | ||
channelKeyBase64: string; | ||
reader: IReader<TCodec>; | ||
writer: IWriter<TCodec>; | ||
toJSON(): IChannelJSON<TCodec>; | ||
} | ||
export interface IConnection { | ||
output: IWriter; | ||
input: IReader; | ||
export interface IConnection<TInput extends INamedCodec, TOutput extends INamedCodec> { | ||
output: IWriter<TOutput>; | ||
input: IReader<TInput>; | ||
connectionKey: Uint8Array; | ||
connectionKeyBase64: string; | ||
toJSON(): IConnectionJSON; | ||
toJSON(): IConnectionJSON<TInput, TOutput>; | ||
} | ||
export interface IHandshakeInitJSON { | ||
input: IReaderJSON; | ||
input: Omit<IReaderJSON<any>, 'codec'>; | ||
firstMessage: string; | ||
@@ -98,3 +170,3 @@ handshakeSecret: string; | ||
export interface IHandshakeInitOptions { | ||
input: IReader | IReaderOptions; | ||
input: Omit<IReaderOptions<any>, 'codec'>; | ||
firstMessage: IStringOrBuffer; | ||
@@ -104,3 +176,3 @@ handshakeSecret: IStringOrBuffer; | ||
export interface IHandshakeInit { | ||
input: IReader; | ||
input: IReader<MsgPackCodec>; | ||
firstMessage: Uint8Array; | ||
@@ -115,23 +187,34 @@ handshakeSecret: Uint8Array; | ||
} | ||
export interface IHandshakeAcceptJSON extends IConnectionJSON { | ||
export interface IHandshakeAcceptJSON { | ||
connectionKey: string; | ||
acceptMessage: IHandshakeAcceptMessage; | ||
} | ||
export declare type IHandshakeAcceptOptions = IConnectionOptions & { | ||
export declare type IHandshakeAcceptOptions = ({ | ||
connectionKey: IStringOrBuffer; | ||
} | { | ||
input: IStringOrBuffer; | ||
output: IStringOrBuffer; | ||
}) & { | ||
acceptMessage: IHandshakeAcceptMessage; | ||
}; | ||
export interface IHandshakeAccept extends IConnection { | ||
export declare type MsgPackCodec = typeof codecs.msgpack; | ||
export interface IHandshakeAccept { | ||
connectionKey: Uint8Array; | ||
connectionKeyBase64: string; | ||
input: IReader<MsgPackCodec>; | ||
output: IWriter<MsgPackCodec>; | ||
acceptMessage: IHandshakeAcceptMessage; | ||
toJSON(): IHandshakeAcceptJSON; | ||
finalize(message: Uint8Array): IConnection; | ||
finalize(message: Uint8Array): IConnection<MsgPackCodec, MsgPackCodec>; | ||
} | ||
export interface IHandshakeConfirmationOptions { | ||
connection: IConnectionOptions; | ||
connectionKey: IStringOrBuffer; | ||
finalMessage: IStringOrBuffer; | ||
} | ||
export interface IHandshakeConfirmationJSON { | ||
connection: IConnectionJSON; | ||
connectionKey: string; | ||
finalMessage: string; | ||
} | ||
export interface IHandshakeConfirmation { | ||
connection: IConnection; | ||
connection: IConnection<MsgPackCodec, MsgPackCodec>; | ||
finalMessage: Uint8Array; | ||
@@ -151,1 +234,2 @@ toJSON(): IHandshakeConfirmationJSON; | ||
} | ||
export {}; |
@@ -8,3 +8,11 @@ "use strict"; | ||
EDecryptionError["invalidSignature"] = "invalid-signature"; | ||
EDecryptionError["invalidMessage"] = "invalid-message"; | ||
EDecryptionError["missingBody"] = "missing-body"; | ||
EDecryptionError["missingSignature"] = "missing-signature"; | ||
EDecryptionError["unexpectedIndex"] = "unexpected-index"; | ||
EDecryptionError["vectorIntegrity"] = "vector-integrity"; | ||
EDecryptionError["vectorPayload"] = "vector-payload"; | ||
EDecryptionError["vectorIndex"] = "vector-index"; | ||
EDecryptionError["vectorNext"] = "vector-next"; | ||
})(EDecryptionError = exports.EDecryptionError || (exports.EDecryptionError = {})); | ||
//# sourceMappingURL=types.js.map |
@@ -1,4 +0,3 @@ | ||
import { IEncodable, IStringOrBuffer, EEncoding } from './types'; | ||
import { IStringOrBuffer, EEncoding } from './types'; | ||
export declare function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array; | ||
export declare function anyToBuffer(message: IEncodable): Uint8Array; | ||
export declare function isStringOrBuffer(input: any): input is IStringOrBuffer; | ||
@@ -9,2 +8,1 @@ export declare function toBuffer(stringOrBuffer: IStringOrBuffer): Uint8Array; | ||
export declare function bufferToString(buffer: Uint8Array, encoding?: EEncoding): string; | ||
export declare function bufferToAny(buffer: Uint8Array): IEncodable; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.bufferToAny = exports.bufferToString = exports.bufferEquals = exports.bufferCompare = exports.toBuffer = exports.isStringOrBuffer = exports.anyToBuffer = exports.concatUint8Arrays = void 0; | ||
exports.bufferToString = exports.bufferEquals = exports.bufferCompare = exports.toBuffer = exports.isStringOrBuffer = exports.concatUint8Arrays = void 0; | ||
const types_1 = require("./types"); | ||
function enumBuffer(num) { | ||
const buf = new Uint8Array(1); | ||
buf[0] = num; | ||
return buf; | ||
} | ||
const MESSAGE_BINARY_UINT8 = enumBuffer(1); | ||
const MESSAGE_BINARY_BUFFER = enumBuffer(2); | ||
const MESSAGE_STRING = enumBuffer(3); | ||
const MESSAGE_JSON = enumBuffer(4); | ||
function concatUint8Arrays(arrays) { | ||
@@ -25,18 +16,2 @@ const byteLength = arrays.reduce((len, array) => len + array.byteLength, 0); | ||
exports.concatUint8Arrays = concatUint8Arrays; | ||
function anyToBuffer(message) { | ||
if (typeof message === 'function') { | ||
throw Object.assign(new Error('Cannot turn a function into a buffer'), { code: 'function-not-supported' }); | ||
} | ||
if (message instanceof types_1.Buffer) { | ||
return types_1.Buffer.concat([MESSAGE_BINARY_BUFFER, message]); | ||
} | ||
if (message instanceof Uint8Array) { | ||
return concatUint8Arrays([MESSAGE_BINARY_UINT8, message]); | ||
} | ||
if (typeof message === 'string') { | ||
return concatUint8Arrays([MESSAGE_STRING, types_1.Buffer.from(message)]); | ||
} | ||
return concatUint8Arrays([MESSAGE_JSON, types_1.Buffer.from(JSON.stringify(message))]); | ||
} | ||
exports.anyToBuffer = anyToBuffer; | ||
function isStringOrBuffer(input) { | ||
@@ -81,23 +56,2 @@ if (typeof input === 'string') | ||
exports.bufferToString = bufferToString; | ||
function bufferToAny(buffer) { | ||
switch (buffer[0]) { | ||
case MESSAGE_BINARY_UINT8[0]: | ||
if (buffer instanceof types_1.Buffer) { | ||
return new Uint8Array(buffer.buffer, buffer.byteOffset + 1, buffer.byteLength - 1); | ||
} | ||
return buffer.slice(1); | ||
case MESSAGE_BINARY_BUFFER[0]: | ||
if (buffer instanceof types_1.Buffer) { | ||
return buffer.slice(1); | ||
} | ||
return types_1.Buffer.from(buffer.slice(1)); | ||
case MESSAGE_STRING[0]: | ||
return bufferToString(buffer.slice(1)); | ||
case MESSAGE_JSON[0]: | ||
return JSON.parse(bufferToString(buffer.slice(1))); | ||
default: | ||
throw Object.assign(new Error(`Couldnt deserialize from buffer: Unknown object type[${buffer[0]}].`), { code: 'deserialization-error', type: buffer[0] }); | ||
} | ||
} | ||
exports.bufferToAny = bufferToAny; | ||
//# sourceMappingURL=buffer.js.map |
@@ -1,4 +0,3 @@ | ||
import { IEncodable } from './types'; | ||
export declare function createSecret(): Uint8Array; | ||
export declare function encrypt(secretKey: Uint8Array, body: IEncodable): Uint8Array; | ||
export declare function decrypt(secretKey: Uint8Array, encrypted: Uint8Array): IEncodable; | ||
export declare function encrypt(secretKey: Uint8Array, message: Uint8Array): Uint8Array; | ||
export declare function decrypt(secretKey: Uint8Array, encrypted: Uint8Array): Uint8Array; |
@@ -25,3 +25,2 @@ "use strict"; | ||
const sodium = __importStar(require("sodium-universal")); | ||
const buffer_2 = require("./buffer"); | ||
const randomBuffer_1 = require("./randomBuffer"); | ||
@@ -40,5 +39,4 @@ const { crypto_secretbox_KEYBYTES: CRYPTO_SECRETBOX_KEYBYTES, crypto_secretbox_NONCEBYTES: CRYPTO_SECRETBOX_NONCEBYTES, crypto_secretbox_MACBYTES: CRYPTO_SECRETBOX_MACBYTES, crypto_secretbox_easy: secretBoxEasy, crypto_secretbox_open_easy: secretBoxOpenEasy, sodium_malloc: malloc } = sodium.default; | ||
exports.createSecret = createSecret; | ||
function encrypt(secretKey, body) { | ||
function encrypt(secretKey, message) { | ||
const nonce = randomBuffer_1.randomBuffer(CRYPTO_SECRETBOX_NONCEBYTES); | ||
const message = buffer_2.anyToBuffer(body); | ||
const ciphertext = malloc(CRYPTO_SECRETBOX_MACBYTES + message.length); | ||
@@ -56,5 +54,5 @@ secretBoxEasy(ciphertext, message, nonce, secretKey); | ||
} | ||
return buffer_2.bufferToAny(decrypted); | ||
return decrypted; | ||
} | ||
exports.decrypt = decrypt; | ||
//# sourceMappingURL=secretbox.js.map |
@@ -7,3 +7,2 @@ import { AbortSignal } from 'abort-controller'; | ||
export declare type IStringOrBuffer = Uint8Array | string; | ||
export declare type IEncodable = IStringOrBuffer | object; | ||
export declare type EEncoding = 'base64' | 'hex' | 'utf8'; | ||
@@ -10,0 +9,0 @@ export declare class AbortError extends Error { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
201725
109
3667
359
7
+ Added@consento/codecs@^1.0.0
+ Added@msgpack/msgpack@^2.3.0
+ Added@consento/codecs@1.1.0(transitive)
+ Added@msgpack/msgpack@2.8.0(transitive)
+ Addedbase32-decode@1.0.0(transitive)
+ Addedbase32-encode@1.2.0(transitive)
+ Addedbuffer@6.0.3(transitive)
+ Addedto-data-view@1.1.0(transitive)