peculiarventures / 2key-ratchet Goto Github PK
View Code? Open in Web Editor NEW2key-ratchet is an implementation of a Double Ratchet protocol and X3DH in TypeScript utilizing WebCrypto.
License: MIT License
2key-ratchet is an implementation of a Double Ratchet protocol and X3DH in TypeScript utilizing WebCrypto.
License: MIT License
Currently AsymRatchet::encrypt returns 2 types, as a pattern this is not good, it would be better to revise the interface to address this.
Right now we do not do validation of ratchet data before use:
2key-ratchet/src/test/ratchet.ts
Line 133 in 3538a14
One of the many benefits of 25519 is that you can use the same key for signing and encryption without risk.
The move to secp256r1 requires us to introduce an exchange key, which we sign with the identity key creating a cryptographic binding between the two.
This introduces a potential risk of an exchange key replay, we need to review this scenario and ensure that either there is no risk or we must design a mitigation to this
We are currently not publishing Coveralls reports, this is due to some problems on the coveralls service that prevent it from seeing this repository.
It is easy to envision how keys might get generated, published and never replaced. We should ensure we track when pre-keys are created and then make sure they are regenerated periodically, maybe once a week.
protected async randomBundle() {
const preKeyBundle = new PreKeyBundleProtocol();
await preKeyBundle.identity.fill(this.identity);
const preKeyId = getRandomInt(1, this.identity.signedPreKeys.length).toString();
preKeyBundle.preKeySigned.key = this.identity.preKeys.load(preKeyId.toString()).key.publicKey;
await preKeyBundle.preKeySigned.sign(this.identity.signingKey.privateKey);
return preKeyBundle.exportProto();
}
Hi,
I'm using the 2key-ratchet library for end-to-end encryption in my React.js app.
After installing CSP on the website, I started facing issues with data encryption.
When trying to encrypt data, I'm getting an error asking me to add the "unsafe-eval" property to the CSP.
The application's security policy does not allow me to add the "unsafe-eval" property to the CSP.
Could you please fix this on your side or let me know how I can fix it on my side?
import * as DKeyRatchetSource from '2key-ratchet';
const DKeyRatchet = overwrite(DKeyRatchetSource);
// overwrite the package
function overwrite(dependency) {
const HASH_NAME = "SHA-256";
const HMAC_NAME = "HMAC";
dependency.Secret.importHMAC = function (raw) {
return dependency.getEngine().crypto.subtle
.importKey("raw", raw, { name: HMAC_NAME, hash: { name: HASH_NAME } }, true, ["sign", "verify"]);
};
return dependency;
}
// convert the encrypted message to a buffer and create a protocol instance from it
export async function processMessage(message) {
const messageRaw = convertBase64ToBuffer(message);
let messageEncrypted = await DKeyRatchet.MessageSignedProtocol.importProto(messageRaw).catch(() => { });
if (!messageEncrypted) {
messageEncrypted = await DKeyRatchet.PreKeyMessageProtocol.importProto(messageRaw); // Error in this method
}
return messageEncrypted;
}
We do not want each application to build this component, as such we should implement a session store that can be relied upon across browsers and node. We can possibly use IndexDB and a polyfill for node.
One of the most common usages will be with WebSockets, lets make an example using websockets to make this easier for people to get started.
Hello! I'm running the simple example with one line added to set the crypto engine (full source below). I see (node:98107) UnhandledPromiseRejectionWarning: Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
as a result. Any thoughts on what is wrong?
const DKeyRatchet = require("2key-ratchet");
const { Convert } = require("pvtsutils");
const { Crypto } = require("@peculiar/webcrypto");
const crypto = new Crypto();
DKeyRatchet.setEngine("@peculiar/webcrypto", crypto);
let AliceID, BobID;
let AlicePreKeyBundleProto;
let BobMessage;
Promise.resolve()
.then(() => {
// Create Alice's identity
return DKeyRatchet.Identity.create(16453, 1, 1)
.then((identity) => {
AliceID = identity;
// Create PreKeyBundle
let AlicePreKeyBundle = new DKeyRatchet.PreKeyBundleProtocol();
AlicePreKeyBundle.identity.fill(AliceID)
.then(() => {
AlicePreKeyBundle.registrationId = AliceID.id;
// Add info about signed PreKey
const preKey = AliceID.signedPreKeys[0];
AlicePreKeyBundle.preKeySigned.id = 0;
AlicePreKeyBundle.preKeySigned.key = preKey.publicKey;
return AlicePreKeyBundle.preKeySigned.sign(AliceID.signingKey.privateKey)
.then(() => {
// Convert proto to bytes
return AlicePreKeyBundle.exportProto();
})
.then((bytes) => {
AlicePreKeyBundleProto = bytes;
console.log("Alice's bundle: ", AlicePreKeyBundleProto);
});
})
})
.then(() => {
// Create Bob's identity
return DKeyRatchet.Identity.create(0, 1, 1)
.then((identity) => {
BobID = identity;
// Parse Alice's bundle
return DKeyRatchet.PreKeyBundleProtocol.importProto(AlicePreKeyBundleProto)
.then((bundle) => {
// Create Bob's cipher
return DKeyRatchet.AsymmetricRatchet.create(BobID, bundle);
})
})
.then((BobCipher) => {
// Encrypt message for Alice
return BobCipher.encrypt(Convert.FromUtf8String("Hello Alice!!!"));
})
.then((proto) => {
// convert message to bytes array
return proto.exportProto()
})
.then((bytes) => {
BobMessage = bytes;
console.log("Bob's encrypted message:", BobMessage);
})
})
.then(() => {
// Decrypt message by Alice
// Note: First message from Bob must be PreKeyMessage
// parse Bob's message
return DKeyRatchet.PreKeyMessageProtocol.importProto(BobMessage)
.then((proto) => {
// Creat Alice's cipher for Bob's message
return DKeyRatchet.AsymmetricRatchet.create(AliceID, proto)
.then((AliceCipher) => {
// Decrypt message
return AliceCipher.decrypt(proto.signedMessage);
})
.then((bytes) => {
console.log("Bob's decrypted message:", Convert.ToUtf8String(bytes));
})
});
})
})
.catch((e) => {
console.error(e);
})
We use rollup in our other projects we should use it as the bundler here also
Using 2key-ratchet requires setting an engine which I couldn't find in the docs. What options are available to include as parameters?
Error: WebCrypto engine is empty. Use setEngine to resolve it.
.DKeyRatchet.setEngine('WebCrypto')
UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'subtle' of undefined
which is due to the crypto
attribute not being declared as far as I can tell.Right now 2key presumes an ordered series of messages, this is problematic in a few cases.
For example when a client is invoked with promises you dont know in which sequence things will happen.
2key needs to make sure it uses the right key for the right message so that when invoked with promise.all, as an example, it uses the right key for each message.
There should be a README.md in the example directory that explains how to build and run it.
Right now the caller needs to do a few things, to establish their session with a peer, in an ideal world these steps would be taken care of by a higher level construct so there are fewer things to go wrong.
We should think about how we can simplify things for the caller.
This is probably related to: #9
But right now tests do not work on a clean machine or in CI.
Right now we hard code to secp256r1 and SHA2-256, maybe we should allow consumers to specify which curve and hash algorithm to use.
protobufjs has several options, one is minimal but its not clear if that works for us.
We should update the readme with examples of including the dependencies that the subsequent code would need to run so people do not need to guess which flavor of protobufjs is needed.
Hi,
Apologies for the second issue.
I currently am able to get the first message sent and decrypted, but I am unable to figure out how to properly use SignedMessageProtocol to send new messages.
Do you mind pointing me in the right direction?
Right now all dependencies are in devDependencies
"devDependencies": {
"@types/chai": "^3.4.34",
"@types/mocha": "^2.2.38",
"@types/node": "^7.0.2",
"@types/protobufjs": "^5.0.31",
"mocha": "^3.2.0",
"chai": "^3.5.0",
"protobufjs": "^6.6.3",
"tslib": "^1.5.0"
}
I suspect some should move to dependencies
, for example:
"dependencies": {
"@types/protobufjs": "^5.0.31",
"protobufjs": "^6.6.3",
"tslib": "^1.5.0"
}
Looking at the test for the usage of one time pre key, i found this,
`
async function Test() {
const identity = await createIdentity(1);
const bundle = await createPreKeyBundle(identity);
bundle.preKey.id = 1;
bundle.preKey.key = bundle.preKeySigned.key;
const raw = await bundle.exportProto();
const bundle2 = await PreKeyBundleProtocol.importProto(raw);
assert.isFalse(bundle2.preKey.isEmpty());
assert.isFalse(bundle2.preKeySigned.isEmpty());
assert.isFalse(bundle2.identity.isEmpty());
}
`
Although this works, the bundle does get exported and imported correctly with the prekey present, using it in the server example provided, and running the server-client example yields, this error.
envelope routines:EVP_DecryptFinal_ex:bad decrypt:../deps/openssl/openssl/crypto/evp/evp_enc.c`
Looking around, i found that this happens when the key provided is wrong and it's not able to decrypt but i don't think it's the case here. Any suggestions as to why this could be happening?
Hi,
I'm attempting to use this package but I'm running into the error stated in the title.
The code is copied exactly from the example let PreKeyBundle = new DKeyRatchet.PreKeyBundleProtocol();
Thanks for the help!
Hey, so been using this for a personal project, and it looks like when i use the toJSON
available in Identity
class, it's able to convert i believe most of the data but fails to convert the SigningKey
which has the publicKey
and privateKey
which is of object CryptoKey
.
When trying to store this on client side applications using Node JS, stringify'ing this causes data to go completely missing and end up like this.
"{"id":2,"signingKey":{"privateKey":{},"publicKey":{},"thumbprint":"fbc-----------------3fe3707ff5e97898d6----------f028ea128b"},"exchangeKey":{"privateKey":{},"publicKey":{},"thumbprint":"0ef4fa--------4751c82f1--------fe11b62020--------ddb60a"},"preKeys":[],"signedPreKeys":[{"privateKey":{},"publicKey":{},"thumbprint":"4f1c26----------0d0023db800dd547---------dd776eba682"}],"createdAt":"---------------"}"
Note: The hyphens are just blocking out data
Could the CryptoKey use the tsprotobuf to export, or is it already there and i'm not seeing it ?
Since we do not control the peer we should have a check to ensure that the peer does not send us the identity key as the exchange key.
We should also do check on the incoming keys to ensure the points in the curve are in the expected group.
We should have pre-compiled packages in the repository
Have been reading the Signal and WhatsApp docs on how they use X3DH to support group messaging. Just wondering - has there been any thought on using 2key-ratchet
for that in it's current version?
Right now we show the size of the module itself but not its required dependencies, we should also include an approximate total size.
For example what does:
Add to the total package size?
The minimal prodobuf.js seems to add 6kb
While tslib.js seems to add 6.5k
If correct the total size to get the capabilities of this library it costs them 78.5Kb which isn't bad.
For users to make meaningful decisions on size this needs to be understood.
There are some constants from the specifications based on the use of 25519 that we need to revisit given our use of secp356r1.
Specifically:
2key-ratchet/src/classes/asym_ratchet.ts
Line 52 in 3538a14
We will want to show people that the project works, the easiest way to do this is by adding a basic demo page in the gh-pages branch.
A CVE has been reported with a dependency, the issue may not be relevant to 2key-ratchet but we should update.
Need to move to growl ~> 1.10.0
Related is: CVE-2017-16042
It is harder for an attacker if we randomize what PreKey
we use from a bundle vs just taking one off the top.
This is to track that work.
We should add a table to the README.md that shows how large the package is when compiled.
We do not want to have every application deal with securely storing trusted keys and chains, as such, we should provide an implementation that they can all use. Possibly using IndexDB with a polyfill on Node.
Is there an easier way to persist the cipher object (DKeyRatchet.AsymetricRatchet) ?
I tried serializing with toJson() and hydrating with fromJson(), but there seems to be a bug in the implementation for cipher generated from PreKeyBundleProtocol as there are some fields that is not properly hydrated.
import * as DKeyRatchet from "..";
import {Convert} from "pvtsutils";
import {Crypto} from "@peculiar/webcrypto";
import * as _ from 'lodash';
function getObjectDiff(obj1: any, obj2:any) {
const diff = Object.keys(obj1).reduce((result, key) => {
if (!obj2.hasOwnProperty(key)) {
result.push(key);
} else if (_.isEqual(obj1[key], obj2[key])) {
const resultKeyIndex = result.indexOf(key);
result.splice(resultKeyIndex, 1);
}
return result;
}, Object.keys(obj2));
return diff;
}
async function compareCiphers(cipher: DKeyRatchet.AsymmetricRatchet) {
const cipherJson = await cipher.toJSON();
const identity = cipher.identity;
const remoteIdentity = cipher.remoteIdentity;
const restoredCipher = await DKeyRatchet.AsymmetricRatchet.fromJSON(identity, remoteIdentity, cipherJson);
console.log(_.isEqual(cipher, restoredCipher));
console.log(getObjectDiff(cipher, restoredCipher));
console.log('cipher: ', cipher.currentStep);
console.log('restoredCipher: ', restoredCipher.currentStep);
}
async function main() {
const crypto = new Crypto();
DKeyRatchet.setEngine("@peculiar/webcrypto", crypto);
const time = new Date().getTime();
const aliceID = await DKeyRatchet.Identity.create(time, 1);
const alicePreKeyBundle = new DKeyRatchet.PreKeyBundleProtocol();
await alicePreKeyBundle.identity.fill(aliceID);
alicePreKeyBundle.registrationId = aliceID.id;
const preKey = aliceID.signedPreKeys[0];
alicePreKeyBundle.preKeySigned.id = 0;
alicePreKeyBundle.preKeySigned.key = preKey.publicKey;
await alicePreKeyBundle.preKeySigned.sign(aliceID.signingKey.privateKey);
// Bob sending encrypted message to Alice
const bobID = await DKeyRatchet.Identity.create(2000, 1);
const aliceBundle = await DKeyRatchet.PreKeyBundleProtocol.importProto(alicePreKeyBundle);
const bobCipher = await DKeyRatchet.AsymmetricRatchet.create(bobID, aliceBundle);
const bobEncryptedMessage = await bobCipher.encrypt(Convert.FromUtf8String('Hello Alice from Bob'));
console.log('bobCipher');
await compareCiphers(bobCipher);
}
main().catch((e) => console.error(e));
The result:
bobCipher
false
[ 'currentStep', 'remotePreKeyId', 'remotePreKeySignedId', 'id' ]
cipher: DHRatchetStep {
remoteRatchetKey: ECPublicKey {
key: CryptoKey {
algorithm: [Object],
type: 'public',
extractable: true,
usages: []
},
serialized: ArrayBuffer {
[Uint8Contents]: <76 bf 7e 3f fd 9c 2b 26 5a b9 0b 9e 02 e0 7a 3e e3 aa 36 e7 5a 48 39 f4 bc ea 82 53 7f a8 f6 52 13 03 1b
23 03 b6 e5 d8 b4 25 ed 8f 2f e6 88 c7 93 0d 97 00 3b e4 af 85 51 fe b4 a5 65 ce 47 3f>,
byteLength: 64
},
id: 'd1e3669b7d3b03567e57171c50435049424996f9b5d5c5a30bcd92bdc8a9c01f'
},
sendingChain: SendingRatchet {
counter: 1,
rootKey: CryptoKey {
algorithm: [Object],
type: 'secret',
extractable: false,
usages: [Array]
}
}
}
restoredCipher: DHRatchetStep {}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.