Pages

Tuesday, December 31, 2013

node.js, express.js & the redis sessions store with encryption

node.js

For those of you that are already familiar with the wonderful language that is node.js please read ahead. For those that are not, lets close the gap.

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

express.js

Much like any other web or interpreted language (Ruby, PHP, Python etc), node.js also has existing framworks to cut down development time. express.js is one of those frameworks.

One of the very nice things about express.js is performing operations such as the following:

$ npm install -g express
$ express --sessions --css stylus --ejs myapp
     
  create : myapp
  create : myapp/package.json
  create : myapp/app.js
  create : myapp/public
  create : myapp/public/javascripts
  create : myapp/public/images
  create : myapp/public/stylesheets
  create : myapp/public/stylesheets/style.styl
  create : myapp/routes
  create : myapp/routes/index.js
  create : myapp/views
  create : myapp/views/index.ejs

install dependencies:

$ cd myapp && npm install

run the app:

$ node app

What the above output performs is create a new project (in the exampled named 'myapp') that contains a very simple web application complete with stylesheets, HTML & the code necessary to run a web server application.

In addition their repo contains numerous project examples to help you get your own project up and running quickly.

redis

If your not familiar with redis perhaps an alternate tool such as memcached is more familiar. Both are software which can be used to serve content quickly and efficiently as they do not utilize disk writes etc and instead store the key/value data within memory making them extremely fast and useful for things such as 'session storage'.

Now that we have some background on redis, lets go back to talking about express.js. The express.js framework is simply a high level API providing access to the connect.js middleware for node.js.

sessions

This particular concent is in its simplest form the earliest method of allowing for persistant variable use within the HTTP 1.0 protocol. Because HTTP is considered a 'stateless' protocol developers needed a way to keep information from one page request to the next.

Use of sessions within the express.js framework is very easy. See below for a simple example of its use within the project we created earlier.

app.use(express.cookieParser());
app.use(express.session({secret: 'kung fu'}));

connect.js custom session store

There are many reasons one might want to use a custom session store. From the connect.js session manual:

Warning: connection.session() MemoryStore is not designed for a production environment, as it will leak memory, and will not scale past a single process.

As noted to ensure the scalability and stability of any web application requiring use of sessions it is best to utilize a custom session store.

Not to worry there are plenty of quality session store middleware modules we can choose from.

connect-redis-crypto

The connect-redis-crypto module is based on the connect-redis module which provides a transparent method of getting/setting/destroying server side sessions from connect.js middlware and implemented within the express.js framework.

The reason why connect-redis-crypto exists is because according to OWASP session management recommendations...

The stored information can include the client IP address, User-Agent, e-mail, username, user ID, role, privilege level, access rights, language preferences, account ID, current state, last login, session timeouts, and other internal session details. If the session objects and properties contain sensitive information, such as credit card numbers, it is required to duly encrypt and protect the session management repository.

Now lets see how to use it within our project:

var express = require('express'),
    RedisStore = require('connect-redis')(express);
     
app.use(express.session({
  secret: 'cookie signature secret',
  store: store: new RedisStore({
    secret: 'sessions crypto secret'
  })
}));

Summary

Not everyone is going to require the use of encryption for managing their session data but for those that do my fork of an already great module should be of assistance.

Monday, December 9, 2013

node.js, DH key exchanges & HMAC

What the !@#$ are you talking about?

Security is difficult to understand, cryptology doubly so. I recently read a wonderful blog post regarding 'cryptographic right answers'.

Seeing as this particular blog came from an expert in the field of cryptology implementation (et al; the developer of 'scrypt' hashing algorithm) and security (former BSD security officer), I think it is worth a read.

If your in a rush, the summary goes somewhere along the lines of 'use xyz algorithm due to the following abc strengths' and 'always use an HMAC to prevent tampering'.

Use SSL/TLS! blah blah!

Yes, I agree, however this protocol is not without its faults and weaknesses

The majority of my interest in this stems from researching methods to prevent or isolate and protect HTTP header & content in the event of a cipher downgrade, side channel, brute force, session renegotiation attack stemming from a traditional MITM or BGP routing bug presents itself.

Use oAuth! blah, blah!

This is also a very valid point and I also agree. However, leaving these fields visibly defined makes it easy in the event of a 'replay attack'.

The majority of today's HMAC use within the HTTP/HTTPS protocols get embedded within the browser response or the clients request headers as shown in figure 1 and defined in the IETF oAuth draft. The 'oauth_signature' field is clearly shown. (To test or see more examples of the oAuth header values, please visit hueniverse.com)

Figure 1
GET /?abc=123 HTTP/1.1
Host: abc.com:443
Authorization: OAuth realm="https://abc.com/",
    oauth_consumer_key="xyz",
    oauth_token="456",
    oauth_nonce="213",
    oauth_timestamp="0",
    oauth_signature_method="HMAC-SHA1",
    oauth_version="2.0",
    oauth_signature="Q%2BuWrnEFBv8CEwtNuxn3bIdkjsY%3D"

In the event of a communications channel being compromised the 'oauth_signature' field can be easily recalculated by an attacker based on the modified contents leaving the server/client none-the-wiser that their message has indeed been modified.

While the concept of utilizing a HMAC to verify a message is not a new concept, the question arises 'How can one use an HMAC effectively in an scenario where the TLS/SSL communications channel has been compromised?'

Ok, ok you made you point, now what?!

While I realize that my proposal is not a standard or even typical solution to the above mentioned attack vectors, it should prove beneficial if and when the need presents itself.

Figure 2 is an exportable module to assist in creating a 'hidden' payload which contains a valid HMAC digest & data payload. This of course could be expanded to accommodate for the use of a 'nonce' as well as a 'timestamp' (both can be very beneficial, and recommended, when it comes to providing the utmost security from attacks).

Figure 1
/*
 * node-dhcp-manager
 *
 * Copyright 2013 Jason Gerfen
 * All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

exports.createPayload = function(payload, digest, count) {
  var a = [],
      b = digest.split(''),
      iteration = count || 3,
      ret = []

  do {
    a.push(payload.substring(0, iteration))
  } while((payload = payload.substring(iteration, payload.length)) != "")

  for (var n = 0; n < a.length; n++) {
    if (b[n] && a[n]) {
        ret.push(a[n]+b[n]);
    } else if(b[n] && !a[n]) {
        ret.push(b[n]);
    } else if(!b[n] && a[n]) {
        ret.push(a[n])
    }
  }
  return ret.join('')
}

exports.extractPayload = function(payload, size, count) {
  var num = count + 1 || 4,
      regex1 = new RegExp('.{1,'+num+'}', 'g'),
      data1 = payload.match(regex1),
      regex2 = new RegExp('.{1,'+num+'}', 'g'),
      data2 = payload.match(regex2),
      digest = [],
      data = [],
      i = 0

  for (var n = 0; n < size; n++) {
    digest.push(data1[n].substr(count, data1[n].length))
  }

  data2.forEach(function(item) {
    if (i < size) {
      data.push(item.substr(0, item.length - 1))
    } else {
      data.push(item)
    }
    i++
  })

  return {
    digest: digest.join(''),
    payload: data.join('')
  }
}

What does this have to do with a DH exchange?!

Patience is a virtue! To utilize this particular module please refer to figure 3 which 'simulates' a Diffie-Hellman key exchange between two parties.

Figure 3
/*
 * node-dhcp-manager
 *
 * Copyright 2013 Jason Gerfen
 * All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

var chai = require('chai'),
    expect = chai.expect,
    should = chai.should(),
    crypto = require('../libs/crypto'),
    hs = require('../libs/handshake'),
    bob = crypto.init(),
    alice = crypto.init(),
    bobSecret = crypto.createSharedSecret(alice.pubKey),
    bobPubKey = crypto.encryptData(bobSecret, bob.pubKey),
    bobDigest = crypto.createDigest(bobSecret, bobPubKey),
    bobPayload = hs.createPayload(bobPubKey, bobDigest, 3)
    bobObj = hs.extractPayload(bobPayload, 128, 3),
    aliceSecret = crypto.createSharedSecret(bob.pubKey),
    alicePubKey = crypto.encryptData(aliceSecret, alice.pubKey),
    aliceDigest = crypto.createDigest(aliceSecret, alicePubKey)
    alicePayload = hs.createPayload(alicePubKey, aliceDigest, 3),
    aliceObj = hs.extractPayload(alicePayload, 128, 3)

describe('handshake.js', function(){

  describe('DH exchange', function(){
    it('bob & alice must share secret', function(done){

      expect(bobSecret).to.equal(aliceSecret)

      done()
    })
  })

  describe('create payload', function(){
    it('must return valid payload', function(done){

      expect(bobPayload).to.have.length(4224)
      expect(alicePayload).to.have.length(4224)

      done()
    })
  })

  describe('extract data from payload', function(){
    it('must contain valid payload & digest', function(done){

      bobObj.should.be.a('object')
      expect(bobObj).to.have.property('digest').with.length(128)
      expect(bobObj).to.have.property('payload').with.length(4096)

      aliceObj.should.be.a('object')
      expect(aliceObj).to.have.property('digest').with.length(128)
      expect(aliceObj).to.have.property('payload').with.length(4096)

      done()
    })
  })

  describe('verify digest', function(){
    it('must contain valid digest of payload', function(done){

      expect(bobObj.digest).to.equal(bobDigest)
      expect(aliceObj.digest).to.equal(aliceDigest)

      done()
    })
  })

  describe('verify keys', function(){
    it('must contain matching keys from originals', function(done){

      expect(crypto.decryptData(bobSecret, bobObj.payload)).to.equal(bob.pubKey)
      expect(crypto.decryptData(aliceSecret, aliceObj.payload)).to.equal(alice.pubKey)

      done()
    })
  })
})

Shameless plug

The above code snippets come from a much larger project which can be found @ node-dhcp-manager.