Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Off-the-Record Messaging Protocol implemented in JavaScript
JavaScript HTML C Other

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
build
etc
lib
specs
test
vendor
.gitignore
.jshintrc
.travis.yml
Makefile
bower.json
changelog.md
contributing.md
gruntfile.js
index.js
license
package.json
readme.md

readme.md

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!

Math.random() is replaced by salsa20.js, seeded with either window.crypto.getRandomValues() in the browser, or crypto.randomBytes() in node.js.


Install

Include the build files on the page,

<!-- Load dependencies -->
<script src="build/dep/salsa20.js"></script>
<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

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) {
  console.log("message to display to the user: " + msg)
})

buddy.on('io', function (msg) {
  console.log("message to send to buddy: " + msg)
})

buddy.on('error', function (err) {
  console.log("error occurred: " + err)
})

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

var rcvmsg = "Message from buddy."
buddy.receiveMsg(rcvmsg)

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

var newmsg = "Message to userA."
buddy.sendMsg(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,

buddy.endOtr()

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) {
  switch (type) {
    case 'question':
      // call(data) some function with question?
      // return the user supplied data to
      // userA.smpSecret(secret)
      break
    case 'trust':
      // smp completed or aborted
      // check userA.trust and update ui accordingly
      break
    default:
      throw new Error('Unknown type.')
  }
})

If the protocol successfully runs to completion,

buddy.trust === true

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

Links

Spec:

Using:


In The Wild

A sampling of projects that use this library:


Donate

Bitcoins: 139WGwU6pXvXQnEvCD9j967gUmgW85DLec


License

MPL v2.0

Something went wrong with that request. Please try again.