Coder Social home page Coder Social logo

peculiarventures / 2key-ratchet Goto Github PK

View Code? Open in Web Editor NEW
108.0 11.0 13.0 464 KB

2key-ratchet is an implementation of a Double Ratchet protocol and X3DH in TypeScript utilizing WebCrypto.

License: MIT License

TypeScript 99.11% JavaScript 0.89%
diffie-hellman secp256r1 ecc secrecy privacy encryption cryptography webcrypto typescript javascript

2key-ratchet's People

Contributors

dependabot[bot] avatar microshine avatar rmhrisk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

2key-ratchet's Issues

createPreKeyBundle helper is needed

Example

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();
}

Update growl version

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

Add websocket example using library

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.

Review `devDependencies` and move appropriate ones into `dependencies`

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"
}

Example - "bad decrypt"

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);
    })

Update readme with example of specifying the dependencies

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.

Need to implement a session store

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.

Persisting cipher object

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 {}

Using 'unset-eval' in Content Security Policy throws the error

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;
}


Screenshot

Simplify calling semantics

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.

Support asynchronous messaging

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.

Add basic gh-pages demo page

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.

envelope routines:EVP_DecryptFinal_ex:bad decrypt when using of one time pre key test.

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?

Add total size, including dependencies to readme

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.

Need to implement a trust store

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.

No known options for function setEngine

Using 2key-ratchet requires setting an engine which I couldn't find in the docs. What options are available to include as parameters?

Steps to Reproduce

  • Use this example.
  • This raises Error: WebCrypto engine is empty. Use setEngine to resolve it..
  • Set the engine DKeyRatchet.setEngine('WebCrypto')
  • This results in UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'subtle' of undefined which is due to the crypto attribute not being declared as far as I can tell.

Json export not converting CryptoKey to object causing storage problems.

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 ?

Add coveralls reporting

We are currently not publishing Coveralls reports, this is due to some problems on the coveralls service that prevent it from seeing this repository.

Any suggestions for a group message example?

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?

Review exchange key usage and ensure no risk, or design mitigation for key replay.

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.