No Password Management

    otr

    0.2.16 • Public • Published

    Build Status

    Off-the Record Messaging Protocol in JavaScript

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

    Install

    npm i otr

    DownloadsWeekly Downloads

    47

    Version

    0.2.16

    License

    MPL-2.0

    Last publish

    Collaborators

    • arlolra