Giter Site home page Giter Site logo

arlolra / otr Goto Github PK

View Code? Open in Web Editor NEW
455.0 25.0 61.0 1.08 MB

Off-the-Record Messaging Protocol implemented in JavaScript

Home Page: https://arlolra.github.io/otr/

License: Mozilla Public License 2.0

Makefile 0.16% JavaScript 69.37% HTML 29.32% C 1.06% R 0.10%

otr's Introduction

Build Status

Warning

This library hasn't been properly vetted by security researchers. Do not use in life and death situations!

Install

Include the build files on the page,

<!-- Load dependencies -->
<script src="build/dep/bigint.js"></script>
<script src="build/dep/crypto.js"></script>
<script src="build/dep/eventemitter.js"></script>

<!-- Load otr.js or otr.min.js -->
<script src="build/otr.min.js"></script>

Here's an example use in the browser.

Although this is a client library, it can be used on the server.

npm install otr

And then,

var DSA = require('otr').DSA
var OTR = require('otr').OTR

Build

The contents of build/ are the result of calling make build and are only updated with releases. Please submit patches against lib/ and vendor/.

Release

The normal flow for making a release is as follows,

make test
// bump the version numbers in package.json / bower.json
make build
git changelog  // cleanup the changelog
git commit -m "bump version"
git tag -a vX.X.X -m "version X.X.X"
git push origin master
git push --tags
npm publish
// update github releases and pages

Usage

Initial setup: Compute your long-lived key beforehand. Currently this is expensive and can take several seconds.

// precompute your DSA key
var myKey = new DSA()

For each user you're communicating with, instantiate an OTR object.

// provide options
var options = {
    fragment_size: 140
  , send_interval: 200
  , priv: myKey
}

var buddy = new OTR(options)

buddy.on('ui', function (msg, encrypted, meta) {
  console.log("message to display to the user: " + msg)
  // encrypted === true, if the received msg was encrypted
  console.log("(optional) with receiveMsg attached meta data: " + meta)
})

buddy.on('io', function (msg, meta) {
  console.log("message to send to buddy: " + msg)
  console.log("(optional) with sendMsg attached meta data: " + meta)
})

buddy.on('error', function (err, severity) {
  if (severity === 'error')  // either 'error' or 'warn'
    console.error("error occurred: " + err)
})

New message from buddy received: Pass the received message to the receiveMsg method.

var rcvmsg = "Message from buddy."
var meta = "optional some meta data, like delay"
buddy.receiveMsg(rcvmsg, meta)

Send a message to buddy: Pass the message to the sendMsg method.

var newmsg = "Message to userA."
var meta = "optional some meta data, like message id"
buddy.sendMsg(newmsg, meta)

Going encrypted: Initially, messages are sent in plaintext. To manually initiate the authenticated key exchange.

buddy.sendQueryMsg()

Alternatively, one can set the policy REQUIRE_ENCRYPTION and send a plaintext message. This will store the message, initiate the authentication and then, upon success, send it out.

buddy.REQUIRE_ENCRYPTION = true
buddy.sendMsg('My plaintext message to be encrypted.')

Another policy, SEND_WHITESPACE_TAG, will append tags to plaintext messages, indicating a willingness to speak OTR. If the recipient in turn has set the policy WHITESPACE_START_AKE, the AKE will be initiated.

Close private connection: To end an encrypted communication session,

buddy.endOtr(function() {
  // calls back when the 'disconnect' message has been sent
})

will return the message state to plaintext and notify the correspondent.

Options: A dictionary of the current options accepted by the OTR constructor.

var options = {

  // long-lived private key
  priv: new DSA(),

  // turn on some debuggin logs
  debug: false,

  // fragment the message in case of char limits
  fragment_size: 140,

  // ms delay between sending fragmented msgs, avoid rate limits
  send_interval: 200

}

Status

A listener can be attached for status changes. These are non-standard codes, specific to this OTR library, indicating various things like the AKE success.

buddy.on('status', function (state) {
  switch (state) {
    case OTR.CONST.STATUS_AKE_SUCCESS:
      // sucessfully ake'd with buddy
      // check if buddy.msgstate === OTR.CONST.MSGSTATE_ENCRYPTED
      break
    case OTR.CONST.STATUS_END_OTR:
      // if buddy.msgstate === OTR.CONST.MSGSTATE_FINISHED
      // inform the user that his correspondent has closed his end
      // of the private connection and the user should do the same
      break
  }
})

Policies

To be set on a per-correspondent basis. The defaults are as follows:

// Allow version 2 or 3 of the OTR protocol to be used.
ALLOW_V2 = true
ALLOW_V3 = true

// Refuse to send unencrypted messages.
REQUIRE_ENCRYPTION = false

// Advertise your support of OTR using the whitespace tag.
SEND_WHITESPACE_TAG = false

// Start the OTR AKE when you receive a whitespace tag.
WHITESPACE_START_AKE = false

// Start the OTR AKE when you receive an OTR Error Message.
ERROR_START_AKE = false

Instance Tags

These are intended to be persistent and can be precomputed.

var myTag = OTR.makeInstanceTag()
var options = { instance_tag: myTag }

var buddy = new OTR(options)

Fingerprints

OTR public key fingerprints can be obtained as follows:

// assume you've gone through the ake with buddy
var buddy = new OTR({ priv: myKey })
// buddy.msgstate === OTR.CONST.MSGSTATE_ENCRYPTED

// for my key, either one of the following
myKey.fingerprint()
// or,
buddy.priv.fingerprint()

// for their key
buddy.their_priv_pk.fingerprint()

Socialist Millionaire Protocol

At any time after establishing encryption, either party can initiate SMP to detect impersonation or man-in-the-middle attacks. A shared secret, exchanged through an out-of-band channel prior to starting the conversation, is required.

var secret = "ghostbusters"
buddy.smpSecret(secret)

A question can be supplied, as a reminder of the shared secret.

var question = "who are you going to call?"
buddy.smpSecret(secret, question)

If you plan on using SMP, as opposed to just allowing fingerprints for verification, provide on optional callback when initiating OTR, otherwise a no-opt is fired.

var buddy = new OTR()

buddy.on('smp', function (type, data, act) {
  switch (type) {
    case 'question':
      // call(data) some function with question?
      // return the user supplied data to
      // userA.smpSecret(secret)
      break
    case 'trust':
      // smp completed
      // check data (true|false) and update ui accordingly
      // act ("asked"|"answered") provides info one who initiated the smp
      break
    case 'abort':
      // smp was aborted. notify the user or update ui
    default:
      throw new Error('Unknown type.')
  }
})

Both users should run the SMP to establish trust. Further, it should be run each time a partner presents a fresh long-lived key.

Private Keys

To export a private, long-lived key:

var myKey = new DSA()
var string = myKey.packPrivate()  // returns a Base64 encoded string

It can then be imported as follows,

string = "AAAAAACA4COdKHpU/np9F8EDdnGiJJmc89p ... I9BzTkQduFA7ovXAMY="
myKey = DSA.parsePrivate(string)

Importing the (somewhat) standard libotr s-expression format works as well,

// in node.js
var fs = require('fs')
string = fs.readFileSync("~/.purple/otr.private_key", 'utf8')

// leaving out the terminal backslashes needed for multiline strings in js
string = "(privkeys
  (account
    (name "[email protected]")
    (protocol prpl-jabber)
    (private-key
      (dsa
        (p #00FC07 ... 2AEFD07A2081#)
        (q #ASD5FF ... LKJDF898DK12#)
        (g #535E3E ... 1E3BC1FC6F26#)
        (y #0AC867 ... 8969009B6ECF#)
        (x #14D034 ... F72D79043216#)
      )
    )
  )
)"

myKey = DSA.parsePrivate(string, true)

Extra Symmetric Key

In version 3 of the protocol, an extra symmetric key is derived during the AKE. This may be used for secure communication over a different channel (e.g., file transfer, voice chat).

var filename = "test.zip"
var buddy = new OTR()
buddy.sendFile(filename)
buddy.on('file', function (type, key, filename) {
  // type === 'send'
  // key should be used to encrypt filename
  // and sent through a different channel
})

On the other end,

var friend = new OTR()
friend.on('file', function (type, key, filename) {
  // type === 'receive'
  // decrypt filename with key, once received
})

WebWorkers

Some support exists for calling computationally expensive work off the main thread. However, some feedback on these APIs would be appreciated.

// generate a DSA key in a web worker
DSA.createInWebWorker(null, function (key) {
		var buddy = new OTR({
			priv: key,
			// setting `smw` to a truthy value will perform the socialist
			// millionaire protocol in a webworker.
			smw: {}
		})
  })

WebWorkers don't have access to window.crypto.getRandomValues(), so they will need to include Salsa20.

<script src="build/dep/salsa20.js"></script>

Links

Spec:

Using:

In The Wild

A sampling of projects that use this library:

Donate

Bitcoins: 1BWLnnig89fpn8hCcASd2B1YbfK6j1vtX3

License

MPL v2.0

otr's People

Contributors

arlolra avatar astorije avatar erbbysam avatar hainish avatar jcbrand avatar mvayngrib avatar ro31337 avatar sualko 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

otr's Issues

Change message encodings from Latin1 to Utf8

Message encodings (note, not encodings for MACs or IVs, just plaintext to ciphertext or ciphertext to plaintext) need to be changed from Latin1 to Utf8. This is because Latin1 does not support sending and receiving messages in say, Arabic script.

Example:

encryptAES = function (msg, c, iv) {
    var opts = {
        mode: CryptoJS.mode.CTR,
        iv: CryptoJS.enc.Latin1.parse(iv),
        padding: CryptoJS.pad.NoPadding
    }
    var aesctr = CryptoJS.AES.encrypt (
        CryptoJS.enc.Latin1.parse(msg),
        CryptoJS.enc.Hex.parse(c),
        opts
    )
    return aesctr.toString();
}

decryptAES = function (msg, c, iv) {
    msg = CryptoJS.enc.Base64.parse(msg);
    var opts = {
        mode: CryptoJS.mode.CTR,
        iv: CryptoJS.enc.Latin1.parse(iv),
        padding: CryptoJS.pad.NoPadding
    }
    var aesctr = CryptoJS.AES.decrypt(
        CryptoJS.enc.Base64.stringify(msg),
        CryptoJS.enc.Hex.parse(c),
        opts
    )
    return aesctr.toString(CryptoJS.enc.Latin1);
}

Change to:

encryptAES = function (msg, c, iv) {
    var opts = {
        mode: CryptoJS.mode.CTR,
        iv: CryptoJS.enc.Latin1.parse(iv),
        padding: CryptoJS.pad.NoPadding
    }
    var aesctr = CryptoJS.AES.encrypt (
        CryptoJS.enc.Utf8.parse(msg),
        CryptoJS.enc.Hex.parse(c),
        opts
    )
    return aesctr.toString();
}

decryptAES = function (msg, c, iv) {
    msg = CryptoJS.enc.Base64.parse(msg);
    var opts = {
        mode: CryptoJS.mode.CTR,
        iv: CryptoJS.enc.Latin1.parse(iv),
        padding: CryptoJS.pad.NoPadding
    }
    var aesctr = CryptoJS.AES.decrypt(
        CryptoJS.enc.Base64.stringify(msg),
        CryptoJS.enc.Hex.parse(c),
        opts
    )
    return aesctr.toString(CryptoJS.enc.Utf8);
}

I'm ready to submit a patch ASAP if you'd like me to. I've already modified this for my local copy of otr.js and it did solve the problem where I couldn't send Arabic messages.

Error in Bigint in Firefox

Can't get this work on Firefox (20.0). If only I include biging.js like this

<script src="js/salsa20.js"></script>
<script src="js/crypto.js"></script>
<script src="js/bigint.js"></script>

it says throw new Error('Keys should not be generated without CSPRNG.')
Further usage is impossible because if only I try to call DSA constructor I'll get BigInt is undefined and DSA is not a constructor

Some TODOs

  • Send less informative erros to avoid error code distinguishing attacks.
  • Consider reseeding occasionally to replenish depleted entropy.
  • SMP api needs to be fleshed out ... also, verify that the correct SSID is used.
  • Switch to an evented model
  • Import long lived keys from adium / pidgin

Use WebCryptoAPI when available

Since WebCrypto API is starting to show up in browsers, I'd like to request that it be used instead of CryptoJS when available (similar to the way OpenPGP.js is doing it[1]).

Crypto Audit

Has this implementation undergone a crypto audit?

Thanks.

On AKE success, doesn't check for DH pubkey equality before skipping key replacement

See the Key Management section in both Protocol v3 and v2.

Upon completing the AKE:
If the specified keyid equals either their_keyid or their_keyid-1, and the DH pubkey contained in the AKE messages matches the one you've stored for that keyid, that's great. Otherwise, forget all values of their_y[], and of their_keyid, and set their_keyid to the keyid value given in the AKE messages, and their_y[their_keyid] to the DH pubkey value given in the AKE messages. their_y[their_keyid-1] should be set to NULL.

In ake.js:126, you check for keyid match with the existing OTR object, but you don't seem to look at the OTR object's public key to check the second condition ("the DH pubkey contained in the AKE messages matches the one you've stored for that keyid") anywhere. So if the public keys don't match, but the keyids do, the library fails to replace the keys, and you end up with a situation where A and B have different ideas of what key to use.

This is causing issues for me with chats where A drops silently, and B stays on, and then A is reinstantiated, reAKEs with B, and then tries to chat (messages after that AKE succeeds have mismatched MACs).

support for instantiating long-lived private keys from external data

DSA class can export public key as string and parse it back.
That should be possible for private key too. Users of the library could then easily export the long-lived key, e.g. encrypt it with a passphrase and store in HTML5 local storage or on a server. On the next session, the same key could be recovered.

Security Researcher Vetting?

Will this be evaluated by security researchers? I'm interested in using this for some projects, but I don't know how to test for effectiveness.

OTR.CONST.STATUS_END_OTR is not actually an end

After OTR.CONST.STATUS_END_OTR triggered, otr sends some more packages so it is not actually the moment when otr ended.

In my case I use XMPP to deliver messages and when disconnecting I have to first run otr.endOtr() and wait until it stop sending any messages and only then run disconnection routine.

So if I put xmpp.disconnect() in OTR.CONST.STATUS_END_OTR handler it would case error because xmpp would be disconnected but otr would try to send few more packages. And this is the problem.

Constant time comparison not constant if length different

The constant time comparison function will return false faster if the two inputs are not equal length[1]. I'm not sure it's a security risk, but Defensive JS has a constant time comparison function that is still constant time even with different input lengths[2].

Replace seedrandom with Salsa20

Cryptocat uses otr.js, while replacing seedrandom.js. We do this because Math.seedrandom() relies on an RC4 RNG, which is considered deprecated.

Instead, we rely on salsa20.js, which is seedable in a similar fashion to seedrandom.js (except it takes a byte array instead of a string,) but returns a faster and more secure CSPRNG.

If you're okay with this, Arlo, I'd like to submit a pull request in the near future that replaces seedrandom.js with salsa20.js. I will also include the necessary functions for otr.js to interface with and seed salsa20.js.

Message padding

Does it make sense to pad messages so that the encrypted message cannot be guessed by length? Or is otr taking care of this?

No warning if we receive an encrypted message where instance tags don't match

If we receive a data message and our msgstate is plaintext we should receive an error:

https://github.com/arlolra/otr/blob/master/lib/otr.js#L396

if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED || msg.length !== 8) {
   if (!ign) this.error('Received an unreadable encrypted message.', true)
   return
}

But the message will be ignored at https://github.com/arlolra/otr/blob/master/lib/otr.js#L595

if ( msg.version === CONST.OTR_VERSION_3 &&
   this.checkInstanceTags(msg.instance_tags)
) return  // ignore

AES IVs

Verify that the message length doesn't exceed the 8 byte chunk counter available in the AES IVs.

Failing test

  1. OTR should send fragments:
    AssertionError: Message state unencrypted. Msg: MACs do not match.

Introduced in a726c96

session management question

is there a way to manage sessions? I have the following scenario:

User A and User B are talking
User B falls off a cliff
User B gets back online, but has different instance tags so User A ignores him.

One way of solving this:

User A's OTR instance can notify it about query messages, e.g. via an event 'query'. Then User A can create a new OTR instance to respond to that new connection

Do you know of a better way?

EDIT: skimmed over OTR spec...it's long :) Here's another option I see, though I'm not sure I see confirmation in the spec:

When receiving a "query" message, if you already know the other party's instance tags, then in addition to re-initializing AKE, like the code already does, reset this.their_instance_tag. Otherwise continuing with AKE has no chance of succeeding.

EDIT: oof, another obvious solution is having persistent instance tags. But if for the sake of argument you don't use persistent instance tags?

Initiate SMP without question

If I try to send a smp request without a question, it send "undefined" as question.

otr_object.smpSecret("secret")

I think this problem is related to line 529 from otr.js.

question = CryptoJS.enc.Utf8.parse(question).toString(CryptoJS.enc.Latin1)

Workaround:

otr_object.smpSecret("secret", "")

OMEMO.js

Is there any chance for you to join to @omemo and experiment with OMEMO.js?

It would be quite a challenge, since OMEMO is Device Trusted.

With OMEMO you no longer trust user identities but device identities. If you are communicating with a contact for the first time or if that contact recently got a new device, you will be presented with a fingerprint for that device. You can then either verify that fingerprint out of band (for example via a quick phone call) or, if you are reasonably sure that your transport is secure (for instance if you are chatting on the same, trusted server), you can choose to trust a device on first use. If you have trusted devices of your contact in the past you can also use those devices as a secure channel to verify the fingerprint of a new device by having your contact verify the fingerprint via chat.

Read more at https://conversations.im/omemo/

Attach meta data to message

I want to implement XEP-0184 (received notifications) and for that reason I have to attach an unique id to a sending message and bind it to the plaintext message. Now the problem, how can I do this with the current implementation?

{msg: "foo", id: "bar"}  -----> OTR.sendMsg()  -----> build xml stanza with id

Because I think there is no working solution, I added an extra parameter to the sendMsg method which will be passed through to the io event handler. What do you think about that? Are you interested in a pr?

Regards,
Klaus

Does this package securely erase keys from memory?

According to the OTR protocol, keys should be securely erased from memory (not just dereferenced, or zeroed once) after they are used. Does this package do that? I couldn't find evidence in the source code.

AKE should be documented in README.md

AKE should be documented in README.md as a necessary step, since exchanging encrypted messages is impossible without doing AKE first. If anyone could point me to some test AKE code, I can handle the documentation myself.

Math.random not a cryptographically secure PRNG

Math.random cannot be relied upon for cryptographic purposes.

Currently, the much stronger window.crypto.getRandomValues(), relying on OS-level entropy, is only implemented in Chrome >= 11.0 and Safari >= 3.1
https://developer.mozilla.org/en-US/docs/DOM/window.crypto.getRandomValues

A stronger pure-js PRNG has been developed, which is meant as a drop-in replacement for Math.random:
http://baagoe.org/en/w/index.php/Better_random_numbers_for_javascript

window.crypto is preferred, but Alea can be used as a fallback

use npm modules instead of bundling deps

first of all, thanks for the wonderful library!

I think it would be easier to use if it didn't bundle all of its dependencies. Can you replace them with the ones from npm? crypto-js and events, not sure about bigint, maybe this one. I tried replacing crypto and works just fine, but it'd obviously require a bunch of other changes in gruntfile, dsa-webworker, etc. What do you think?

[sm][ake] base64encode msgs

All the message for SM and AKE need to be joined and base64 encoded with (CryptoJS.enc.Latin1.parse()).toString(CryptoJS.enc.base64) and then formed into otr messages ('?OTR:' + b64 + '.'), and then unpackaged on the other side. There's an order in the spec.

For the SM, BigInts need to be HLP.packMPI'd first.

Right now, messages are just passed with JSON.

Does not work in Opera

Opera gets stuck at new DSA() with the thread locked at 100% CPU indefinitely. Not sure why.

OTR fingerprint mismatch

It seems that OTR fingerprints sometimes do not match (even when there is no MITM). This seems to happen randomly. It does not happen every time.

Scenario: Two people join Cryptocat

Person A sees his own fingerprint as: ABC123
Person B sees person A's fingerprint as: BBB222

This is a real security issue, please investigate. I'm investigating too right now.

Investigate interop with other implementations

Converse.js user is saying that message fragmentation is preventing initiation. They claim to have tried with several clients, including a nightly Jitsi, which should have an otr4j that implements it. Pidgin seems to be the only one working for them, which makes sense because we test against libotr.

bigint.js: bpe is probably smaller than intended

https://github.com/arlolra/otr/blob/master/vendor/bigint.js#L206 has the following code:

for (bpe = 0; (1<<(bpe+1)) > (1<<bpe); bpe++);  // bpe = number of bits in the mantissa on this platform
bpe>>=1; // bpe = number of bits in one element of the array representing the bigInt

From the comments, the author appears to expect bpe to end up as half of the number of significant bits in a floating point mantissa or fraction, rounded down. It does not; it ends up as 15.

Explanation:

The number of significant bits in the fraction (the correct term used in IEEE) of a JavaScript floating-point number is 52, independent of platform. (Whether you can actually use all the bits depends on some corner cases and I won't make any claims about that here.)

The code above calculates bpe = 30 in the first line. This is because the left-shift operator treats its left argument as a signed 32-bit integer, as per section 11.7.1 of the ECMAScript 5.1 spec. http://www.ecma-international.org/ecma-262/5.1/ . That is, 1<<(bpe+1) overflows to negative at bpe = 30. It then calculates bpe = 15 in the second line.

This does not affect correctness but it does make the representation unnecessarily inefficient, and in any case the comment is wrong.

npm publish fail

publishing v0.2.11 to the npm registry failed. putting this here so I don't forget to try again

Is Posible to Use OTR in Titanium appcelerator?

I'm trying to create a chat application using Titanium appcelerator for iOS. For that i have implemented OTR.JS in my node js application. That works fine, But now i want to implement OTR.JS in Titanium Appcelator.

I searching modules in Titanium Appcelator to implement OTR for my application. But I can't found any solution for this.

So can anyone clarify me, To use OTR.JS in my Titanium Appcelator Application. If there is any example, please suggest me.

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.