2key-ratchet
Advanced tools
Comparing version 1.0.14 to 1.0.15
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2019 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
* Copyright (c) 2016-2020, Peculiar Ventures, All rights reserved. | ||
*/ | ||
'use strict'; | ||
@@ -14,6 +10,14 @@ | ||
var utils = require('pvtsutils'); | ||
var tslib_1 = require('tslib'); | ||
var tslib = require('tslib'); | ||
var tsprotobuf = require('tsprotobuf'); | ||
var events = require('events'); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
const SIGN_ALGORITHM_NAME = "ECDSA"; | ||
@@ -29,2 +33,10 @@ const DH_ALGORITHM_NAME = "ECDH"; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let engine = null; | ||
@@ -37,2 +49,9 @@ if (typeof self !== "undefined") { | ||
} | ||
/** | ||
* Sets crypto engine | ||
* | ||
* @export | ||
* @param {string} name Name of engine | ||
* @param {Crypto} crypto WebCrypto implementation | ||
*/ | ||
function setEngine(name, crypto) { | ||
@@ -44,2 +63,9 @@ engine = { | ||
} | ||
/** | ||
* Returns crypto engine | ||
* It throws exception if engine is empty. | ||
* | ||
* @export | ||
* @returns {ICryptoEngine} | ||
*/ | ||
function getEngine() { | ||
@@ -52,3 +78,20 @@ if (!engine) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class Curve { | ||
/** | ||
* Generates new EC key pair | ||
* | ||
* @static | ||
* @param {ECKeyType} type type of EC key. ECDSA | ECDH | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static async generateKeyPair(type) { | ||
@@ -65,5 +108,26 @@ const name = type; | ||
} | ||
/** | ||
* Derives 32 bytes from EC keys | ||
* | ||
* @static | ||
* @param {ECDHPrivateKey} privateKey EC private key | ||
* @param {ECPublicKey} publicKey EC public key | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static deriveBytes(privateKey, publicKey) { | ||
return getEngine().crypto.subtle.deriveBits({ name: "ECDH", public: publicKey.key }, privateKey, 256); | ||
} | ||
/** | ||
* Verifies signature | ||
* | ||
* @static | ||
* @param {ECPublicKey} signingKey | ||
* @param {ArrayBuffer} message | ||
* @param {ArrayBuffer} signature | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static verify(signingKey, message, signature) { | ||
@@ -73,2 +137,12 @@ return getEngine().crypto.subtle | ||
} | ||
/** | ||
* Calculates signature | ||
* | ||
* @static | ||
* @param {ECDHPrivateKey} signingKey | ||
* @param {ArrayBuffer} message | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static async sign(signingKey, message) { | ||
@@ -94,4 +168,21 @@ return getEngine().crypto.subtle.sign({ name: "ECDSA", hash: this.DIGEST_ALGORITHM }, signingKey, message); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
const AES_ALGORITHM = { name: "AES-CBC", length: 256 }; | ||
class Secret { | ||
/** | ||
* Returns ArrayBuffer of random bytes | ||
* | ||
* @static | ||
* @param {number} size size of output buffer | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static randomBytes(size) { | ||
@@ -102,22 +193,99 @@ const array = new Uint8Array(size); | ||
} | ||
/** | ||
* Calculates digest | ||
* | ||
* @static | ||
* @param {string} alg | ||
* @param {ArrayBuffer} message | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static digest(alg, message) { | ||
return getEngine().crypto.subtle.digest(alg, message); | ||
} | ||
/** | ||
* Encrypts data | ||
* | ||
* @static | ||
* @param {CryptoKey} key | ||
* @param {ArrayBuffer} data | ||
* @param {ArrayBuffer} iv | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static encrypt(key, data, iv) { | ||
return getEngine().crypto.subtle.encrypt({ name: SECRET_KEY_NAME, iv: new Uint8Array(iv) }, key, data); | ||
} | ||
/** | ||
* Decrypts data | ||
* | ||
* @static | ||
* @param {CryptoKey} key | ||
* @param {ArrayBuffer} data | ||
* @param {ArrayBuffer} iv | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static decrypt(key, data, iv) { | ||
return getEngine().crypto.subtle.decrypt({ name: SECRET_KEY_NAME, iv: new Uint8Array(iv) }, key, data); | ||
} | ||
/** | ||
* Creates HMAC key from raw data | ||
* | ||
* @static | ||
* @param {ArrayBuffer} raw | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static importHMAC(raw) { | ||
// console.log("HMAC:", Convert.ToHex(raw)); | ||
return getEngine().crypto.subtle | ||
.importKey("raw", raw, { name: HMAC_NAME, hash: { name: HASH_NAME } }, false, ["sign", "verify"]); | ||
} | ||
/** | ||
* Creates AES key from raw data | ||
* | ||
* @static | ||
* @param {ArrayBuffer} raw | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static importAES(raw) { | ||
// console.log("AES:", Convert.ToHex(raw)); | ||
return getEngine().crypto.subtle.importKey("raw", raw, AES_ALGORITHM, false, ["encrypt", "decrypt"]); | ||
} | ||
/** | ||
* Calculates signature | ||
* | ||
* @static | ||
* @param {CryptoKey} key | ||
* @param {ArrayBuffer} data | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static async sign(key, data) { | ||
return await getEngine().crypto.subtle.sign({ name: HMAC_NAME, hash: HASH_NAME }, key, data); | ||
} | ||
/** | ||
* HKDF rfc5869 | ||
* | ||
* @static | ||
* @param {ArrayBuffer} IKM input keying material | ||
* @param {number} [keysCount] amount of calculated keys | ||
* @param {CryptoKey} [salt] salt value (a non-secret random value) | ||
* - if not provided, it is set to a string of HashLen zeros. | ||
* @param {any} [info=new ArrayBuffer(0)] | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
static async HKDF(IKM, keysCount = 1, salt, info = new ArrayBuffer(0)) { | ||
// https://www.ietf.org/rfc/rfc5869.txt | ||
// PRK = HMAC-Hash(salt, IKM) | ||
if (!salt) { | ||
@@ -128,2 +296,13 @@ salt = await this.importHMAC(new Uint8Array(32).buffer); | ||
const infoBuffer = new ArrayBuffer(32 + info.byteLength + 1); | ||
/** | ||
* N = ceil(L/HashLen) | ||
* T = T(1) | T(2) | T(3) | ... | T(N) | ||
* OKM = first L octets of T | ||
* | ||
* where: | ||
* T(0) = empty string (zero length) | ||
* T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) | ||
* T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) | ||
* T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) | ||
*/ | ||
const PRK = await this.importHMAC(PRKBytes); | ||
@@ -138,3 +317,26 @@ const T = [new ArrayBuffer(0)]; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
/** | ||
* Implementation of EC public key | ||
* | ||
* @export | ||
* @class ECPublicKey | ||
*/ | ||
class ECPublicKey { | ||
/** | ||
* Creates new instance of ECPublicKey from CryptoKey | ||
* | ||
* @static | ||
* @param {CryptoKey} publicKey | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
static async create(publicKey) { | ||
@@ -150,2 +352,3 @@ const res = new this(); | ||
res.key = publicKey; | ||
// Serialize public key to JWK | ||
const jwk = await getEngine().crypto.subtle.exportKey("jwk", publicKey); | ||
@@ -162,2 +365,12 @@ if (!(jwk.x && jwk.y)) { | ||
} | ||
/** | ||
* Creates ECPublicKey from raw data | ||
* | ||
* @static | ||
* @param {ArrayBuffer} bytes | ||
* @param {ECKeyType} type type of EC key. ECDSA | ECDH | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
static async importKey(bytes, type) { | ||
@@ -178,5 +391,19 @@ const x = utils.Convert.ToBase64Url(bytes.slice(0, 32)); | ||
} | ||
/** | ||
* Returns key in raw format | ||
* | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
serialize() { | ||
return this.serialized; | ||
} | ||
/** | ||
* Returns SHA-256 digest of key | ||
* | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
async thumbprint() { | ||
@@ -187,2 +414,10 @@ const bytes = await this.serialize(); | ||
} | ||
/** | ||
* Returns `true` if current is equal to given parameter | ||
* | ||
* @param {*} other | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
async isEqual(other) { | ||
@@ -196,3 +431,18 @@ if (!(other && other instanceof ECPublicKey)) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class Identity { | ||
constructor(id, signingKey, exchangeKey) { | ||
this.id = id; | ||
this.signingKey = signingKey; | ||
this.exchangeKey = exchangeKey; | ||
this.preKeys = []; | ||
this.signedPreKeys = []; | ||
} | ||
static async fromJSON(obj) { | ||
@@ -211,5 +461,7 @@ const signingKey = await Curve.ecKeyPairFromJson(obj.signingKey); | ||
res.createdAt = new Date(); | ||
// generate preKey | ||
for (let i = 0; i < preKeyAmount; i++) { | ||
res.preKeys.push(await Curve.generateKeyPair("ECDH")); | ||
} | ||
// generate signedPreKey | ||
for (let i = 0; i < signedPreKeyAmount; i++) { | ||
@@ -220,9 +472,2 @@ res.signedPreKeys.push(await Curve.generateKeyPair("ECDH")); | ||
} | ||
constructor(id, signingKey, exchangeKey) { | ||
this.id = id; | ||
this.signingKey = signingKey; | ||
this.exchangeKey = exchangeKey; | ||
this.preKeys = []; | ||
this.signedPreKeys = []; | ||
} | ||
async toJSON() { | ||
@@ -261,2 +506,10 @@ const preKeys = []; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class RemoteIdentity { | ||
@@ -298,2 +551,3 @@ static fill(protocol) { | ||
this.createdAt = new Date(obj.createdAt); | ||
// verify signature | ||
const ok = await this.verify(); | ||
@@ -306,11 +560,27 @@ if (!ok) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let BaseProtocol = class BaseProtocol extends tsprotobuf.ObjectProto { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 0, type: "uint32", defaultValue: 1 }) | ||
], BaseProtocol.prototype, "version", void 0); | ||
BaseProtocol = tslib_1.__decorate([ | ||
BaseProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "Base" }) | ||
], BaseProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class ECDSAPublicKeyConverter { | ||
@@ -341,2 +611,10 @@ static async set(value) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
var IdentityProtocol_1; | ||
@@ -362,36 +640,52 @@ exports.IdentityProtocol = IdentityProtocol_1 = class IdentityProtocol extends BaseProtocol { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 1, converter: ECDSAPublicKeyConverter }) | ||
], exports.IdentityProtocol.prototype, "signingKey", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 2, converter: ECDHPublicKeyConverter }) | ||
], exports.IdentityProtocol.prototype, "exchangeKey", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 3 }) | ||
], exports.IdentityProtocol.prototype, "signature", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 4, converter: DateConverter }) | ||
], exports.IdentityProtocol.prototype, "createdAt", void 0); | ||
exports.IdentityProtocol = IdentityProtocol_1 = tslib_1.__decorate([ | ||
exports.IdentityProtocol = IdentityProtocol_1 = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "Identity" }) | ||
], exports.IdentityProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let MessageProtocol = class MessageProtocol extends BaseProtocol { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 1, converter: ECDHPublicKeyConverter, required: true }) | ||
], MessageProtocol.prototype, "senderRatchetKey", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 2, type: "uint32", required: true }) | ||
], MessageProtocol.prototype, "counter", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 3, type: "uint32", required: true }) | ||
], MessageProtocol.prototype, "previousCounter", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 4, converter: tsprotobuf.ArrayBufferConverter, required: true }) | ||
], MessageProtocol.prototype, "cipherText", void 0); | ||
MessageProtocol = tslib_1.__decorate([ | ||
MessageProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "Message" }) | ||
], MessageProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
exports.MessageSignedProtocol = class MessageSignedProtocol extends BaseProtocol { | ||
@@ -418,51 +712,75 @@ async sign(hmacKey) { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 1, converter: ECDSAPublicKeyConverter, required: true }) | ||
], exports.MessageSignedProtocol.prototype, "senderKey", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 2, parser: MessageProtocol, required: true }) | ||
], exports.MessageSignedProtocol.prototype, "message", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 3, required: true }) | ||
], exports.MessageSignedProtocol.prototype, "signature", void 0); | ||
exports.MessageSignedProtocol = tslib_1.__decorate([ | ||
exports.MessageSignedProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "MessageSigned" }) | ||
], exports.MessageSignedProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
exports.PreKeyMessageProtocol = class PreKeyMessageProtocol extends BaseProtocol { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 1, type: "uint32", required: true }) | ||
], exports.PreKeyMessageProtocol.prototype, "registrationId", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 2, type: "uint32" }) | ||
], exports.PreKeyMessageProtocol.prototype, "preKeyId", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 3, type: "uint32", required: true }) | ||
], exports.PreKeyMessageProtocol.prototype, "preKeySignedId", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 4, converter: ECDHPublicKeyConverter, required: true }) | ||
], exports.PreKeyMessageProtocol.prototype, "baseKey", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 5, parser: exports.IdentityProtocol, required: true }) | ||
], exports.PreKeyMessageProtocol.prototype, "identity", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 6, parser: exports.MessageSignedProtocol, required: true }) | ||
], exports.PreKeyMessageProtocol.prototype, "signedMessage", void 0); | ||
exports.PreKeyMessageProtocol = tslib_1.__decorate([ | ||
exports.PreKeyMessageProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "PreKeyMessage" }) | ||
], exports.PreKeyMessageProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let PreKeyProtocol = class PreKeyProtocol extends BaseProtocol { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 1, type: "uint32", required: true }) | ||
], PreKeyProtocol.prototype, "id", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 2, converter: ECDHPublicKeyConverter, required: true }) | ||
], PreKeyProtocol.prototype, "key", void 0); | ||
PreKeyProtocol = tslib_1.__decorate([ | ||
PreKeyProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "PreKey" }) | ||
], PreKeyProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let PreKeySignedProtocol = class PreKeySignedProtocol extends PreKeyProtocol { | ||
@@ -476,27 +794,43 @@ async sign(key) { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 3, converter: tsprotobuf.ArrayBufferConverter, required: true }) | ||
], PreKeySignedProtocol.prototype, "signature", void 0); | ||
PreKeySignedProtocol = tslib_1.__decorate([ | ||
PreKeySignedProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "PreKeySigned" }) | ||
], PreKeySignedProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
exports.PreKeyBundleProtocol = class PreKeyBundleProtocol extends BaseProtocol { | ||
}; | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 1, type: "uint32", required: true }) | ||
], exports.PreKeyBundleProtocol.prototype, "registrationId", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 2, parser: exports.IdentityProtocol, required: true }) | ||
], exports.PreKeyBundleProtocol.prototype, "identity", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 3, parser: PreKeyProtocol }) | ||
], exports.PreKeyBundleProtocol.prototype, "preKey", void 0); | ||
tslib_1.__decorate([ | ||
tslib.__decorate([ | ||
tsprotobuf.ProtobufProperty({ id: 4, parser: PreKeySignedProtocol, required: true }) | ||
], exports.PreKeyBundleProtocol.prototype, "preKeySigned", void 0); | ||
exports.PreKeyBundleProtocol = tslib_1.__decorate([ | ||
exports.PreKeyBundleProtocol = tslib.__decorate([ | ||
tsprotobuf.ProtobufElement({ name: "PreKeyBundle" }) | ||
], exports.PreKeyBundleProtocol); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class Stack { | ||
@@ -515,3 +849,3 @@ constructor(maxSize = 20) { | ||
if (this.length === this.maxSize) { | ||
this.items = this.items.slice(1); | ||
this.items = this.items.slice(1); // pop first item from the items | ||
} | ||
@@ -532,2 +866,11 @@ this.items.push(item); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
// Constants for KDF_CK function | ||
const CIPHER_KEY_KDF_INPUT = new Uint8Array([1]).buffer; | ||
@@ -555,2 +898,12 @@ const ROOT_KEY_KDF_INPUT = new Uint8Array([2]).buffer; | ||
} | ||
/** | ||
* calculates new keys by rootKey KDF_CK(ck) | ||
* https://whispersystems.org/docs/specifications/doubleratchet/#external-functions | ||
* | ||
* @protected | ||
* @param {CryptoKey} rootKey | ||
* @returns | ||
* | ||
* @memberOf SymmetricRatchet | ||
*/ | ||
async calculateKey(rootKey) { | ||
@@ -565,2 +918,10 @@ const cipherKeyBytes = await Secret.sign(rootKey, CIPHER_KEY_KDF_INPUT); | ||
} | ||
/** | ||
* Move to next step of ratchet | ||
* | ||
* @protected | ||
* @returns | ||
* | ||
* @memberOf SymmetricRatchet | ||
*/ | ||
async click() { | ||
@@ -574,5 +935,21 @@ const rootKey = this.rootKey; | ||
} | ||
/** | ||
* Implementation of Sending chain | ||
* | ||
* @export | ||
* @class SendingRatchet | ||
* @extends {SymmetricRatchet} | ||
*/ | ||
class SendingRatchet extends SymmetricRatchet { | ||
/** | ||
* Encrypts message | ||
* | ||
* @param {ArrayBuffer} message | ||
* @returns CipherMessage type | ||
* | ||
* @memberOf SendingRatchet | ||
*/ | ||
async encrypt(message) { | ||
const cipherKey = await this.click(); | ||
// calculate keys | ||
const keys = await Secret.HKDF(cipherKey, 3, void 0, INFO_MESSAGE_KEYS); | ||
@@ -605,2 +982,3 @@ const aesKey = await Secret.importAES(keys[0]); | ||
const cipherKey = await this.getKey(counter); | ||
// calculate keys | ||
const keys = await Secret.HKDF(cipherKey, 3, void 0, INFO_MESSAGE_KEYS); | ||
@@ -626,3 +1004,34 @@ const aesKey = await Secret.importAES(keys[0]); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
/** | ||
* Authentication. Calculates rootKey for DH ratchet | ||
* | ||
* https://whispersystems.org/docs/specifications/x3dh/#sending-the-initial-message | ||
* | ||
* @static | ||
* @param {boolean} flag Sets order for auth mechanism. | ||
* - If `true` then DH1 || DH2, otherwise DH2 || DH1 | ||
* @param {Identity} IKA | ||
* @param {IECKeyPair} EKA | ||
* @param {RemoteIdentity} IKB Bob's identity key IKB | ||
* @param {ECPublicKey} SPKB Bob's signed prekey SPKB | ||
* @param {ECPublicKey} [OPKB] Bob's one-time prekey OPKB. Optionally | ||
* | ||
*/ | ||
async function authenticateA(IKa, EKa, IKb, SPKb, OPKb) { | ||
/** | ||
* If the bundle does not contain a one-time PreKey | ||
* | ||
* DH1 = DH(IKa, SPKb) | ||
* DH2 = DH(EKa, IKb) | ||
* DH3 = DH(EKa, SPKb) | ||
* SK = KDF(DH1 || DH2 || DH3) | ||
*/ | ||
const DH1 = await Curve.deriveBytes(IKa.exchangeKey.privateKey, SPKb); | ||
@@ -633,2 +1042,9 @@ const DH2 = await Curve.deriveBytes(EKa.privateKey, IKb); | ||
if (OPKb) { | ||
/** | ||
* If the bundle does contain a one-time PreKey, | ||
* the calculation is modified to include an additional DH | ||
* | ||
* DH4 = DH(EKA, OPKB) | ||
* SK = KDF(DH1 || DH2 || DH3 || DH4) | ||
*/ | ||
DH4 = await Curve.deriveBytes(EKa.privateKey, OPKb); | ||
@@ -641,3 +1057,3 @@ } | ||
const F = _F.buffer; | ||
const KM = utils.combine(F, DH1, DH2, DH3, DH4); | ||
const KM = utils.combine(F, DH1, DH2, DH3, DH4); // TODO: F || KM, where F = 0xFF * N | ||
const keys = await Secret.HKDF(KM, 1, void 0, INFO_TEXT); | ||
@@ -647,2 +1063,10 @@ return await Secret.importHMAC(keys[0]); | ||
async function authenticateB(IKb, SPKb, IKa, EKa, OPKb) { | ||
/** | ||
* If the bundle does not contain a one-time PreKey | ||
* | ||
* DH1 = DH(IKa, SPKb) | ||
* DH2 = DH(EKa, IKb) | ||
* DH3 = DH(EKa, SPKb) | ||
* SK = KDF(DH1 || DH2 || DH3) | ||
*/ | ||
const DH1 = await Curve.deriveBytes(SPKb.privateKey, IKa); | ||
@@ -653,2 +1077,9 @@ const DH2 = await Curve.deriveBytes(IKb.exchangeKey.privateKey, EKa); | ||
if (OPKb) { | ||
/** | ||
* If the bundle does contain a one-time PreKey, | ||
* the calculation is modified to include an additional DH | ||
* | ||
* DH4 = DH(EKA, OPKB) | ||
* SK = KDF(DH1 || DH2 || DH3 || DH4) | ||
*/ | ||
DH4 = await Curve.deriveBytes(OPKb, EKa); | ||
@@ -661,6 +1092,13 @@ } | ||
const F = _F.buffer; | ||
const KM = utils.combine(F, DH1, DH2, DH3, DH4); | ||
const KM = utils.combine(F, DH1, DH2, DH3, DH4); // TODO: F || KM, where F = 0xFF * N | ||
const keys = await Secret.HKDF(KM, 1, void 0, INFO_TEXT); | ||
return await Secret.importHMAC(keys[0]); | ||
} | ||
/** | ||
* Implementation Diffie-Hellman ratchet | ||
* https://whispersystems.org/docs/specifications/doubleratchet/#diffie-hellman-ratchet | ||
* | ||
* @export | ||
* @class AsymmetricRatchet | ||
*/ | ||
class AsymmetricRatchet extends events.EventEmitter { | ||
@@ -674,2 +1112,12 @@ constructor() { | ||
} | ||
/** | ||
* Creates new ratchet for given identity from PreKeyBundle or PreKey messages | ||
* | ||
* @static | ||
* @param {Identity} identity | ||
* @param {(PreKeyBundleProtocol | PreKeyMessageProtocol)} protocol | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
static async create(identity, protocol) { | ||
@@ -679,5 +1127,8 @@ let rootKey; | ||
if (protocol instanceof exports.PreKeyBundleProtocol) { | ||
// PreKey Bundle | ||
// verify remote identity key | ||
if (!await protocol.identity.verify()) { | ||
throw new Error("Error: Remote client's identity key is invalid."); | ||
} | ||
// verify remote signed prekey | ||
if (!await protocol.preKeySigned.verify(protocol.identity.signingKey)) { | ||
@@ -695,5 +1146,9 @@ throw new Error("Error: Remote client's signed prekey is invalid."); | ||
else { | ||
// PreKey Message | ||
// verify remote identity | ||
if (!await protocol.identity.verify()) { | ||
throw new Error("Error: Remote client's identity key is invalid."); | ||
// INFO: We can move THROW to verify function. Cause verification is critical. | ||
} | ||
// get signed prekey for identity | ||
const signedPreKey = identity.signedPreKeys[protocol.preKeySignedId]; | ||
@@ -703,2 +1158,3 @@ if (!signedPreKey) { | ||
} | ||
// get one-time prekey | ||
let preKey; | ||
@@ -712,2 +1168,3 @@ if (protocol.preKeyId !== void 0) { | ||
} | ||
// console.info(`${this.name}:Create Diffie-Hellman ratchet for ${identity.id}`); | ||
ratchet.identity = identity; | ||
@@ -731,3 +1188,12 @@ ratchet.id = identity.id; | ||
} | ||
/** | ||
* Verifies and decrypts data from SignedMessage | ||
* | ||
* @param {MessageSignedProtocol} protocol | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
async decrypt(protocol) { | ||
// console.info(`${this.constructor.name}:Decrypts message`); | ||
return this.queuePromise("encrypt", async () => { | ||
@@ -741,2 +1207,3 @@ const remoteRatchetKey = protocol.message.senderRatchetKey; | ||
if (!step) { | ||
// We ve got new ratchet key, creating new ReceivingChain | ||
const ratchetStep = new DHRatchetStep(); | ||
@@ -749,2 +1216,3 @@ ratchetStep.remoteRatchetKey = remoteRatchetKey; | ||
if (!step.receivingChain) { | ||
// got 1 message for current ratchet step, have to create ReceiverRatchet | ||
step.receivingChain = await this.createChain(this.currentRatchetKey.privateKey, remoteRatchetKey, ReceivingRatchet); | ||
@@ -754,2 +1222,3 @@ } | ||
this.update(); | ||
// verifying message signature | ||
protocol.senderKey = this.remoteIdentity.signingKey; | ||
@@ -763,8 +1232,19 @@ protocol.receiverKey = this.identity.signingKey.publicKey; | ||
} | ||
/** | ||
* Encrypts message | ||
* | ||
* @param {ArrayBuffer} message | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
async encrypt(message) { | ||
// console.info(`${this.constructor.name}:Encrypts message`); | ||
return this.queuePromise("encrypt", async () => { | ||
if (this.currentStep.receivingChain && !this.currentStep.sendingChain) { | ||
// close ratchet step | ||
this.counter++; | ||
this.currentRatchetKey = await this.generateRatchetKey(); | ||
} | ||
// if false then no incoming message with new ratchet key, using old DH ratchet | ||
if (!this.currentStep.sendingChain) { | ||
@@ -782,2 +1262,3 @@ if (!this.currentStep.remoteRatchetKey) { | ||
this.currentStep.sendingChain.counter === 1) { | ||
// we send first message, MUST be PreKey message, otherwise SignedMessage | ||
preKeyMessage = new exports.PreKeyMessageProtocol(); | ||
@@ -793,2 +1274,3 @@ preKeyMessage.registrationId = this.identity.id; | ||
signedMessage.senderKey = this.identity.signingKey.publicKey; | ||
// message | ||
signedMessage.message.cipherText = encryptedMessage.cipherText; | ||
@@ -844,6 +1326,16 @@ signedMessage.message.counter = this.currentStep.sendingChain.counter - 1; | ||
} | ||
/** | ||
* Generate new ratchet key | ||
* | ||
* @protected | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
generateRatchetKey() { | ||
return Curve.generateKeyPair("ECDH"); | ||
} | ||
// tslint:disable-next-line:max-line-length | ||
async createChain(ourRatchetKey, theirRatchetKey, ratchetClass) { | ||
// console.info(`${this.constructor.name}:${this.id}:Creating new ${ratchetClass.name}`); | ||
const derivedBytes = await Curve.deriveBytes(ourRatchetKey, theirRatchetKey); | ||
@@ -854,3 +1346,3 @@ const keys = await Secret.HKDF(derivedBytes, 2, this.rootKey, INFO_RATCHET); | ||
const chain = new ratchetClass(chainKey); | ||
this.rootKey = rootKey; | ||
this.rootKey = rootKey; // update rootKey | ||
return chain; | ||
@@ -869,2 +1361,8 @@ } | ||
} | ||
/** | ||
* Implementation of step of the Diffie-Hellman ratchet | ||
* | ||
* @export | ||
* @class DHRatchetStep | ||
*/ | ||
class DHRatchetStep { | ||
@@ -901,3 +1399,17 @@ static async fromJSON(obj) { | ||
} | ||
/** | ||
* Implements collection of DHRatchetStep | ||
* | ||
* @export | ||
* @class DHRatchetStepStack | ||
* @extends {Stack<DHRatchetStep>} | ||
*/ | ||
class DHRatchetStepStack extends Stack { | ||
/** | ||
* Returns DHRatchetStep by given remote client's ratchet key | ||
* @param {ECPublicKey} remoteRatchetKey remote client's ratchet key | ||
* @returns | ||
* | ||
* @memberOf DHRatchetStepStack | ||
*/ | ||
getStep(remoteRatchetKey) { | ||
@@ -918,3 +1430,3 @@ let found; | ||
exports.RemoteIdentity = RemoteIdentity; | ||
exports.getEngine = getEngine; | ||
exports.setEngine = setEngine; | ||
exports.getEngine = getEngine; |
/** | ||
* Copyright (c) 2016-2020, Peculiar Ventures, All rights reserved. | ||
*/ | ||
import { Convert, combine, isEqual } from 'pvtsutils'; | ||
import { __decorate } from 'tslib'; | ||
import { ProtobufProperty, ProtobufElement, ObjectProto, ArrayBufferConverter } from 'tsprotobuf'; | ||
import { EventEmitter } from 'events'; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2019 Peculiar Ventures, Inc | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
@@ -9,7 +18,2 @@ * https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
*/ | ||
import { Convert, combine, isEqual } from 'pvtsutils'; | ||
import { __decorate } from 'tslib'; | ||
import { ProtobufProperty, ProtobufElement, ObjectProto, ArrayBufferConverter } from 'tsprotobuf'; | ||
import { EventEmitter } from 'events'; | ||
const SIGN_ALGORITHM_NAME = "ECDSA"; | ||
@@ -25,2 +29,10 @@ const DH_ALGORITHM_NAME = "ECDH"; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let engine = null; | ||
@@ -33,2 +45,9 @@ if (typeof self !== "undefined") { | ||
} | ||
/** | ||
* Sets crypto engine | ||
* | ||
* @export | ||
* @param {string} name Name of engine | ||
* @param {Crypto} crypto WebCrypto implementation | ||
*/ | ||
function setEngine(name, crypto) { | ||
@@ -40,2 +59,9 @@ engine = { | ||
} | ||
/** | ||
* Returns crypto engine | ||
* It throws exception if engine is empty. | ||
* | ||
* @export | ||
* @returns {ICryptoEngine} | ||
*/ | ||
function getEngine() { | ||
@@ -48,3 +74,20 @@ if (!engine) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class Curve { | ||
/** | ||
* Generates new EC key pair | ||
* | ||
* @static | ||
* @param {ECKeyType} type type of EC key. ECDSA | ECDH | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static async generateKeyPair(type) { | ||
@@ -61,5 +104,26 @@ const name = type; | ||
} | ||
/** | ||
* Derives 32 bytes from EC keys | ||
* | ||
* @static | ||
* @param {ECDHPrivateKey} privateKey EC private key | ||
* @param {ECPublicKey} publicKey EC public key | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static deriveBytes(privateKey, publicKey) { | ||
return getEngine().crypto.subtle.deriveBits({ name: "ECDH", public: publicKey.key }, privateKey, 256); | ||
} | ||
/** | ||
* Verifies signature | ||
* | ||
* @static | ||
* @param {ECPublicKey} signingKey | ||
* @param {ArrayBuffer} message | ||
* @param {ArrayBuffer} signature | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static verify(signingKey, message, signature) { | ||
@@ -69,2 +133,12 @@ return getEngine().crypto.subtle | ||
} | ||
/** | ||
* Calculates signature | ||
* | ||
* @static | ||
* @param {ECDHPrivateKey} signingKey | ||
* @param {ArrayBuffer} message | ||
* @returns | ||
* | ||
* @memberOf Curve | ||
*/ | ||
static async sign(signingKey, message) { | ||
@@ -90,4 +164,21 @@ return getEngine().crypto.subtle.sign({ name: "ECDSA", hash: this.DIGEST_ALGORITHM }, signingKey, message); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
const AES_ALGORITHM = { name: "AES-CBC", length: 256 }; | ||
class Secret { | ||
/** | ||
* Returns ArrayBuffer of random bytes | ||
* | ||
* @static | ||
* @param {number} size size of output buffer | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static randomBytes(size) { | ||
@@ -98,22 +189,99 @@ const array = new Uint8Array(size); | ||
} | ||
/** | ||
* Calculates digest | ||
* | ||
* @static | ||
* @param {string} alg | ||
* @param {ArrayBuffer} message | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static digest(alg, message) { | ||
return getEngine().crypto.subtle.digest(alg, message); | ||
} | ||
/** | ||
* Encrypts data | ||
* | ||
* @static | ||
* @param {CryptoKey} key | ||
* @param {ArrayBuffer} data | ||
* @param {ArrayBuffer} iv | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static encrypt(key, data, iv) { | ||
return getEngine().crypto.subtle.encrypt({ name: SECRET_KEY_NAME, iv: new Uint8Array(iv) }, key, data); | ||
} | ||
/** | ||
* Decrypts data | ||
* | ||
* @static | ||
* @param {CryptoKey} key | ||
* @param {ArrayBuffer} data | ||
* @param {ArrayBuffer} iv | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static decrypt(key, data, iv) { | ||
return getEngine().crypto.subtle.decrypt({ name: SECRET_KEY_NAME, iv: new Uint8Array(iv) }, key, data); | ||
} | ||
/** | ||
* Creates HMAC key from raw data | ||
* | ||
* @static | ||
* @param {ArrayBuffer} raw | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static importHMAC(raw) { | ||
// console.log("HMAC:", Convert.ToHex(raw)); | ||
return getEngine().crypto.subtle | ||
.importKey("raw", raw, { name: HMAC_NAME, hash: { name: HASH_NAME } }, false, ["sign", "verify"]); | ||
} | ||
/** | ||
* Creates AES key from raw data | ||
* | ||
* @static | ||
* @param {ArrayBuffer} raw | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static importAES(raw) { | ||
// console.log("AES:", Convert.ToHex(raw)); | ||
return getEngine().crypto.subtle.importKey("raw", raw, AES_ALGORITHM, false, ["encrypt", "decrypt"]); | ||
} | ||
/** | ||
* Calculates signature | ||
* | ||
* @static | ||
* @param {CryptoKey} key | ||
* @param {ArrayBuffer} data | ||
* @returns | ||
* | ||
* @memberOf Secret | ||
*/ | ||
static async sign(key, data) { | ||
return await getEngine().crypto.subtle.sign({ name: HMAC_NAME, hash: HASH_NAME }, key, data); | ||
} | ||
/** | ||
* HKDF rfc5869 | ||
* | ||
* @static | ||
* @param {ArrayBuffer} IKM input keying material | ||
* @param {number} [keysCount] amount of calculated keys | ||
* @param {CryptoKey} [salt] salt value (a non-secret random value) | ||
* - if not provided, it is set to a string of HashLen zeros. | ||
* @param {any} [info=new ArrayBuffer(0)] | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
static async HKDF(IKM, keysCount = 1, salt, info = new ArrayBuffer(0)) { | ||
// https://www.ietf.org/rfc/rfc5869.txt | ||
// PRK = HMAC-Hash(salt, IKM) | ||
if (!salt) { | ||
@@ -124,2 +292,13 @@ salt = await this.importHMAC(new Uint8Array(32).buffer); | ||
const infoBuffer = new ArrayBuffer(32 + info.byteLength + 1); | ||
/** | ||
* N = ceil(L/HashLen) | ||
* T = T(1) | T(2) | T(3) | ... | T(N) | ||
* OKM = first L octets of T | ||
* | ||
* where: | ||
* T(0) = empty string (zero length) | ||
* T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) | ||
* T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) | ||
* T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) | ||
*/ | ||
const PRK = await this.importHMAC(PRKBytes); | ||
@@ -134,3 +313,26 @@ const T = [new ArrayBuffer(0)]; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
/** | ||
* Implementation of EC public key | ||
* | ||
* @export | ||
* @class ECPublicKey | ||
*/ | ||
class ECPublicKey { | ||
/** | ||
* Creates new instance of ECPublicKey from CryptoKey | ||
* | ||
* @static | ||
* @param {CryptoKey} publicKey | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
static async create(publicKey) { | ||
@@ -146,2 +348,3 @@ const res = new this(); | ||
res.key = publicKey; | ||
// Serialize public key to JWK | ||
const jwk = await getEngine().crypto.subtle.exportKey("jwk", publicKey); | ||
@@ -158,2 +361,12 @@ if (!(jwk.x && jwk.y)) { | ||
} | ||
/** | ||
* Creates ECPublicKey from raw data | ||
* | ||
* @static | ||
* @param {ArrayBuffer} bytes | ||
* @param {ECKeyType} type type of EC key. ECDSA | ECDH | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
static async importKey(bytes, type) { | ||
@@ -174,5 +387,19 @@ const x = Convert.ToBase64Url(bytes.slice(0, 32)); | ||
} | ||
/** | ||
* Returns key in raw format | ||
* | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
serialize() { | ||
return this.serialized; | ||
} | ||
/** | ||
* Returns SHA-256 digest of key | ||
* | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
async thumbprint() { | ||
@@ -183,2 +410,10 @@ const bytes = await this.serialize(); | ||
} | ||
/** | ||
* Returns `true` if current is equal to given parameter | ||
* | ||
* @param {*} other | ||
* @returns | ||
* | ||
* @memberOf ECPublicKey | ||
*/ | ||
async isEqual(other) { | ||
@@ -192,3 +427,18 @@ if (!(other && other instanceof ECPublicKey)) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class Identity { | ||
constructor(id, signingKey, exchangeKey) { | ||
this.id = id; | ||
this.signingKey = signingKey; | ||
this.exchangeKey = exchangeKey; | ||
this.preKeys = []; | ||
this.signedPreKeys = []; | ||
} | ||
static async fromJSON(obj) { | ||
@@ -207,5 +457,7 @@ const signingKey = await Curve.ecKeyPairFromJson(obj.signingKey); | ||
res.createdAt = new Date(); | ||
// generate preKey | ||
for (let i = 0; i < preKeyAmount; i++) { | ||
res.preKeys.push(await Curve.generateKeyPair("ECDH")); | ||
} | ||
// generate signedPreKey | ||
for (let i = 0; i < signedPreKeyAmount; i++) { | ||
@@ -216,9 +468,2 @@ res.signedPreKeys.push(await Curve.generateKeyPair("ECDH")); | ||
} | ||
constructor(id, signingKey, exchangeKey) { | ||
this.id = id; | ||
this.signingKey = signingKey; | ||
this.exchangeKey = exchangeKey; | ||
this.preKeys = []; | ||
this.signedPreKeys = []; | ||
} | ||
async toJSON() { | ||
@@ -257,2 +502,10 @@ const preKeys = []; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class RemoteIdentity { | ||
@@ -294,2 +547,3 @@ static fill(protocol) { | ||
this.createdAt = new Date(obj.createdAt); | ||
// verify signature | ||
const ok = await this.verify(); | ||
@@ -302,2 +556,10 @@ if (!ok) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let BaseProtocol = class BaseProtocol extends ObjectProto { | ||
@@ -312,2 +574,10 @@ }; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class ECDSAPublicKeyConverter { | ||
@@ -338,2 +608,10 @@ static async set(value) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
var IdentityProtocol_1; | ||
@@ -375,2 +653,10 @@ let IdentityProtocol = IdentityProtocol_1 = class IdentityProtocol extends BaseProtocol { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let MessageProtocol = class MessageProtocol extends BaseProtocol { | ||
@@ -394,2 +680,10 @@ }; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let MessageSignedProtocol = class MessageSignedProtocol extends BaseProtocol { | ||
@@ -429,2 +723,10 @@ async sign(hmacKey) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let PreKeyMessageProtocol = class PreKeyMessageProtocol extends BaseProtocol { | ||
@@ -454,2 +756,10 @@ }; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let PreKeyProtocol = class PreKeyProtocol extends BaseProtocol { | ||
@@ -467,2 +777,10 @@ }; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let PreKeySignedProtocol = class PreKeySignedProtocol extends PreKeyProtocol { | ||
@@ -483,2 +801,10 @@ async sign(key) { | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
let PreKeyBundleProtocol = class PreKeyBundleProtocol extends BaseProtocol { | ||
@@ -502,2 +828,10 @@ }; | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
class Stack { | ||
@@ -516,3 +850,3 @@ constructor(maxSize = 20) { | ||
if (this.length === this.maxSize) { | ||
this.items = this.items.slice(1); | ||
this.items = this.items.slice(1); // pop first item from the items | ||
} | ||
@@ -533,2 +867,11 @@ this.items.push(item); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
// Constants for KDF_CK function | ||
const CIPHER_KEY_KDF_INPUT = new Uint8Array([1]).buffer; | ||
@@ -556,2 +899,12 @@ const ROOT_KEY_KDF_INPUT = new Uint8Array([2]).buffer; | ||
} | ||
/** | ||
* calculates new keys by rootKey KDF_CK(ck) | ||
* https://whispersystems.org/docs/specifications/doubleratchet/#external-functions | ||
* | ||
* @protected | ||
* @param {CryptoKey} rootKey | ||
* @returns | ||
* | ||
* @memberOf SymmetricRatchet | ||
*/ | ||
async calculateKey(rootKey) { | ||
@@ -566,2 +919,10 @@ const cipherKeyBytes = await Secret.sign(rootKey, CIPHER_KEY_KDF_INPUT); | ||
} | ||
/** | ||
* Move to next step of ratchet | ||
* | ||
* @protected | ||
* @returns | ||
* | ||
* @memberOf SymmetricRatchet | ||
*/ | ||
async click() { | ||
@@ -575,5 +936,21 @@ const rootKey = this.rootKey; | ||
} | ||
/** | ||
* Implementation of Sending chain | ||
* | ||
* @export | ||
* @class SendingRatchet | ||
* @extends {SymmetricRatchet} | ||
*/ | ||
class SendingRatchet extends SymmetricRatchet { | ||
/** | ||
* Encrypts message | ||
* | ||
* @param {ArrayBuffer} message | ||
* @returns CipherMessage type | ||
* | ||
* @memberOf SendingRatchet | ||
*/ | ||
async encrypt(message) { | ||
const cipherKey = await this.click(); | ||
// calculate keys | ||
const keys = await Secret.HKDF(cipherKey, 3, void 0, INFO_MESSAGE_KEYS); | ||
@@ -606,2 +983,3 @@ const aesKey = await Secret.importAES(keys[0]); | ||
const cipherKey = await this.getKey(counter); | ||
// calculate keys | ||
const keys = await Secret.HKDF(cipherKey, 3, void 0, INFO_MESSAGE_KEYS); | ||
@@ -627,3 +1005,34 @@ const aesKey = await Secret.importAES(keys[0]); | ||
/** | ||
* | ||
* 2key-ratchet | ||
* Copyright (c) 2016 Peculiar Ventures, Inc | ||
* Based on https://whispersystems.org/docs/specifications/doubleratchet/ and | ||
* https://whispersystems.org/docs/specifications/x3dh/ by Open Whisper Systems | ||
* | ||
*/ | ||
/** | ||
* Authentication. Calculates rootKey for DH ratchet | ||
* | ||
* https://whispersystems.org/docs/specifications/x3dh/#sending-the-initial-message | ||
* | ||
* @static | ||
* @param {boolean} flag Sets order for auth mechanism. | ||
* - If `true` then DH1 || DH2, otherwise DH2 || DH1 | ||
* @param {Identity} IKA | ||
* @param {IECKeyPair} EKA | ||
* @param {RemoteIdentity} IKB Bob's identity key IKB | ||
* @param {ECPublicKey} SPKB Bob's signed prekey SPKB | ||
* @param {ECPublicKey} [OPKB] Bob's one-time prekey OPKB. Optionally | ||
* | ||
*/ | ||
async function authenticateA(IKa, EKa, IKb, SPKb, OPKb) { | ||
/** | ||
* If the bundle does not contain a one-time PreKey | ||
* | ||
* DH1 = DH(IKa, SPKb) | ||
* DH2 = DH(EKa, IKb) | ||
* DH3 = DH(EKa, SPKb) | ||
* SK = KDF(DH1 || DH2 || DH3) | ||
*/ | ||
const DH1 = await Curve.deriveBytes(IKa.exchangeKey.privateKey, SPKb); | ||
@@ -634,2 +1043,9 @@ const DH2 = await Curve.deriveBytes(EKa.privateKey, IKb); | ||
if (OPKb) { | ||
/** | ||
* If the bundle does contain a one-time PreKey, | ||
* the calculation is modified to include an additional DH | ||
* | ||
* DH4 = DH(EKA, OPKB) | ||
* SK = KDF(DH1 || DH2 || DH3 || DH4) | ||
*/ | ||
DH4 = await Curve.deriveBytes(EKa.privateKey, OPKb); | ||
@@ -642,3 +1058,3 @@ } | ||
const F = _F.buffer; | ||
const KM = combine(F, DH1, DH2, DH3, DH4); | ||
const KM = combine(F, DH1, DH2, DH3, DH4); // TODO: F || KM, where F = 0xFF * N | ||
const keys = await Secret.HKDF(KM, 1, void 0, INFO_TEXT); | ||
@@ -648,2 +1064,10 @@ return await Secret.importHMAC(keys[0]); | ||
async function authenticateB(IKb, SPKb, IKa, EKa, OPKb) { | ||
/** | ||
* If the bundle does not contain a one-time PreKey | ||
* | ||
* DH1 = DH(IKa, SPKb) | ||
* DH2 = DH(EKa, IKb) | ||
* DH3 = DH(EKa, SPKb) | ||
* SK = KDF(DH1 || DH2 || DH3) | ||
*/ | ||
const DH1 = await Curve.deriveBytes(SPKb.privateKey, IKa); | ||
@@ -654,2 +1078,9 @@ const DH2 = await Curve.deriveBytes(IKb.exchangeKey.privateKey, EKa); | ||
if (OPKb) { | ||
/** | ||
* If the bundle does contain a one-time PreKey, | ||
* the calculation is modified to include an additional DH | ||
* | ||
* DH4 = DH(EKA, OPKB) | ||
* SK = KDF(DH1 || DH2 || DH3 || DH4) | ||
*/ | ||
DH4 = await Curve.deriveBytes(OPKb, EKa); | ||
@@ -662,6 +1093,13 @@ } | ||
const F = _F.buffer; | ||
const KM = combine(F, DH1, DH2, DH3, DH4); | ||
const KM = combine(F, DH1, DH2, DH3, DH4); // TODO: F || KM, where F = 0xFF * N | ||
const keys = await Secret.HKDF(KM, 1, void 0, INFO_TEXT); | ||
return await Secret.importHMAC(keys[0]); | ||
} | ||
/** | ||
* Implementation Diffie-Hellman ratchet | ||
* https://whispersystems.org/docs/specifications/doubleratchet/#diffie-hellman-ratchet | ||
* | ||
* @export | ||
* @class AsymmetricRatchet | ||
*/ | ||
class AsymmetricRatchet extends EventEmitter { | ||
@@ -675,2 +1113,12 @@ constructor() { | ||
} | ||
/** | ||
* Creates new ratchet for given identity from PreKeyBundle or PreKey messages | ||
* | ||
* @static | ||
* @param {Identity} identity | ||
* @param {(PreKeyBundleProtocol | PreKeyMessageProtocol)} protocol | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
static async create(identity, protocol) { | ||
@@ -680,5 +1128,8 @@ let rootKey; | ||
if (protocol instanceof PreKeyBundleProtocol) { | ||
// PreKey Bundle | ||
// verify remote identity key | ||
if (!await protocol.identity.verify()) { | ||
throw new Error("Error: Remote client's identity key is invalid."); | ||
} | ||
// verify remote signed prekey | ||
if (!await protocol.preKeySigned.verify(protocol.identity.signingKey)) { | ||
@@ -696,5 +1147,9 @@ throw new Error("Error: Remote client's signed prekey is invalid."); | ||
else { | ||
// PreKey Message | ||
// verify remote identity | ||
if (!await protocol.identity.verify()) { | ||
throw new Error("Error: Remote client's identity key is invalid."); | ||
// INFO: We can move THROW to verify function. Cause verification is critical. | ||
} | ||
// get signed prekey for identity | ||
const signedPreKey = identity.signedPreKeys[protocol.preKeySignedId]; | ||
@@ -704,2 +1159,3 @@ if (!signedPreKey) { | ||
} | ||
// get one-time prekey | ||
let preKey; | ||
@@ -713,2 +1169,3 @@ if (protocol.preKeyId !== void 0) { | ||
} | ||
// console.info(`${this.name}:Create Diffie-Hellman ratchet for ${identity.id}`); | ||
ratchet.identity = identity; | ||
@@ -732,3 +1189,12 @@ ratchet.id = identity.id; | ||
} | ||
/** | ||
* Verifies and decrypts data from SignedMessage | ||
* | ||
* @param {MessageSignedProtocol} protocol | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
async decrypt(protocol) { | ||
// console.info(`${this.constructor.name}:Decrypts message`); | ||
return this.queuePromise("encrypt", async () => { | ||
@@ -742,2 +1208,3 @@ const remoteRatchetKey = protocol.message.senderRatchetKey; | ||
if (!step) { | ||
// We ve got new ratchet key, creating new ReceivingChain | ||
const ratchetStep = new DHRatchetStep(); | ||
@@ -750,2 +1217,3 @@ ratchetStep.remoteRatchetKey = remoteRatchetKey; | ||
if (!step.receivingChain) { | ||
// got 1 message for current ratchet step, have to create ReceiverRatchet | ||
step.receivingChain = await this.createChain(this.currentRatchetKey.privateKey, remoteRatchetKey, ReceivingRatchet); | ||
@@ -755,2 +1223,3 @@ } | ||
this.update(); | ||
// verifying message signature | ||
protocol.senderKey = this.remoteIdentity.signingKey; | ||
@@ -764,8 +1233,19 @@ protocol.receiverKey = this.identity.signingKey.publicKey; | ||
} | ||
/** | ||
* Encrypts message | ||
* | ||
* @param {ArrayBuffer} message | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
async encrypt(message) { | ||
// console.info(`${this.constructor.name}:Encrypts message`); | ||
return this.queuePromise("encrypt", async () => { | ||
if (this.currentStep.receivingChain && !this.currentStep.sendingChain) { | ||
// close ratchet step | ||
this.counter++; | ||
this.currentRatchetKey = await this.generateRatchetKey(); | ||
} | ||
// if false then no incoming message with new ratchet key, using old DH ratchet | ||
if (!this.currentStep.sendingChain) { | ||
@@ -783,2 +1263,3 @@ if (!this.currentStep.remoteRatchetKey) { | ||
this.currentStep.sendingChain.counter === 1) { | ||
// we send first message, MUST be PreKey message, otherwise SignedMessage | ||
preKeyMessage = new PreKeyMessageProtocol(); | ||
@@ -794,2 +1275,3 @@ preKeyMessage.registrationId = this.identity.id; | ||
signedMessage.senderKey = this.identity.signingKey.publicKey; | ||
// message | ||
signedMessage.message.cipherText = encryptedMessage.cipherText; | ||
@@ -845,6 +1327,16 @@ signedMessage.message.counter = this.currentStep.sendingChain.counter - 1; | ||
} | ||
/** | ||
* Generate new ratchet key | ||
* | ||
* @protected | ||
* @returns | ||
* | ||
* @memberOf AsymmetricRatchet | ||
*/ | ||
generateRatchetKey() { | ||
return Curve.generateKeyPair("ECDH"); | ||
} | ||
// tslint:disable-next-line:max-line-length | ||
async createChain(ourRatchetKey, theirRatchetKey, ratchetClass) { | ||
// console.info(`${this.constructor.name}:${this.id}:Creating new ${ratchetClass.name}`); | ||
const derivedBytes = await Curve.deriveBytes(ourRatchetKey, theirRatchetKey); | ||
@@ -855,3 +1347,3 @@ const keys = await Secret.HKDF(derivedBytes, 2, this.rootKey, INFO_RATCHET); | ||
const chain = new ratchetClass(chainKey); | ||
this.rootKey = rootKey; | ||
this.rootKey = rootKey; // update rootKey | ||
return chain; | ||
@@ -870,2 +1362,8 @@ } | ||
} | ||
/** | ||
* Implementation of step of the Diffie-Hellman ratchet | ||
* | ||
* @export | ||
* @class DHRatchetStep | ||
*/ | ||
class DHRatchetStep { | ||
@@ -902,3 +1400,17 @@ static async fromJSON(obj) { | ||
} | ||
/** | ||
* Implements collection of DHRatchetStep | ||
* | ||
* @export | ||
* @class DHRatchetStepStack | ||
* @extends {Stack<DHRatchetStep>} | ||
*/ | ||
class DHRatchetStepStack extends Stack { | ||
/** | ||
* Returns DHRatchetStep by given remote client's ratchet key | ||
* @param {ECPublicKey} remoteRatchetKey remote client's ratchet key | ||
* @returns | ||
* | ||
* @memberOf DHRatchetStepStack | ||
*/ | ||
getStep(remoteRatchetKey) { | ||
@@ -916,2 +1428,2 @@ let found; | ||
export { AsymmetricRatchet, Identity, RemoteIdentity, setEngine, getEngine, IdentityProtocol, MessageSignedProtocol, PreKeyMessageProtocol, PreKeyBundleProtocol }; | ||
export { AsymmetricRatchet, Identity, IdentityProtocol, MessageSignedProtocol, PreKeyBundleProtocol, PreKeyMessageProtocol, RemoteIdentity, getEngine, setEngine }; |
{ | ||
"name": "2key-ratchet", | ||
"version": "1.0.14", | ||
"version": "1.0.15", | ||
"description": "2key-ratchet is an implementation of a Double Ratchet protocol and X3DH in TypeScript utilizing WebCrypto.", | ||
@@ -40,19 +40,19 @@ "main": "dist/2key-ratchet.js", | ||
"dependencies": { | ||
"pvtsutils": "^1.0.4", | ||
"tslib": "^1.9.3", | ||
"tsprotobuf": "^1.0.12" | ||
"pvtsutils": "^1.0.9", | ||
"tslib": "^1.11.1", | ||
"tsprotobuf": "^1.0.13" | ||
}, | ||
"devDependencies": { | ||
"@peculiar/webcrypto": "^1.0.5", | ||
"@types/chai": "^4.1.7", | ||
"@types/mocha": "^5.2.6", | ||
"@types/node": "^10.3.4", | ||
"@peculiar/webcrypto": "^1.0.23", | ||
"@types/chai": "^4.2.11", | ||
"@types/mocha": "^7.0.2", | ||
"@types/node": "^12.12.30", | ||
"chai": "^4.2.0", | ||
"coveralls": "^3.0.2", | ||
"mocha": "^5.2.0", | ||
"nyc": "^13.3.0", | ||
"rollup": "^1.2.2", | ||
"rollup-plugin-typescript": "^1.0.0", | ||
"ts-node": "^8.0.2", | ||
"typescript": "^3.3.3" | ||
"coveralls": "^3.0.9", | ||
"mocha": "^7.1.0", | ||
"nyc": "^15.0.0", | ||
"rollup": "^2.0.6", | ||
"rollup-plugin-typescript2": "^0.26.0", | ||
"ts-node": "^8.6.2", | ||
"typescript": "^3.8.3" | ||
}, | ||
@@ -74,3 +74,10 @@ "nyc": { | ||
] | ||
}, | ||
"mocha": { | ||
"require": "ts-node/register", | ||
"extension": [ | ||
"ts" | ||
], | ||
"watch-files": "test/**/*.ts" | ||
} | ||
} |
@@ -5,3 +5,3 @@ # SCENARIOS | ||
Progressive Web Applications (PWAs) in particular can benefit from the traits offered by these protocols, with that said the utility is not limited to these cases, some use csaes that might be interesting include: | ||
Progressive Web Applications (PWAs) in particular can benefit from the traits offered by these protocols, with that said the utility is not limited to these cases, some use cases that might be interesting include: | ||
@@ -8,0 +8,0 @@ | **SCENARIO** | **DESCRIPTION** | |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
133485
3329
Updatedpvtsutils@^1.0.9
Updatedtslib@^1.11.1
Updatedtsprotobuf@^1.0.13