@tradle/otr

0.2.17 • Public • Published

Off-the Record Messaging Protocol in JavaScript

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, callback)

callback is called after the 'io' event is emitted for newmsg

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 "foo@example.com")
    (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

Package Sidebar

Install

npm i @tradle/otr

Weekly Downloads

6

Version

0.2.17

License

MPL v2.0

Last publish

Collaborators

  • leichtgewicht
  • tenaciousmv
  • pgmemk
  • spwilko
  • genevayngrib