Skip to content

Commit

Permalink
add first cut (#301)
Browse files Browse the repository at this point in the history
WAMP-cryptosign support
  • Loading branch information
oberstet committed May 23, 2017
1 parent c306584 commit 37fb323
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 3 deletions.
6 changes: 6 additions & 0 deletions doc/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# AutobahnJS - Change Log

## v17.5.2

* new: WAMP-cryptosign authentication support

---

## v0.9.1
* compatibility with latest WAMP v2 spec ("RC-2, 2014/02/22")

Expand Down
158 changes: 158 additions & 0 deletions lib/auth/cryptosign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
///////////////////////////////////////////////////////////////////////////////
//
// AutobahnJS - http://autobahn.ws, http://wamp.ws
//
// A JavaScript library for WAMP ("The Web Application Messaging Protocol").
//
// Copyright (c) Crossbar.io Technologies GmbH and contributors
//
// Licensed under the MIT License.
// http://www.opensource.org/licenses/mit-license.php
//
///////////////////////////////////////////////////////////////////////////////

var nacl = require('tweetnacl');
var util = require('../util.js');
var log = require('../log.js');
var connection = require('../connection.js');


function load_private_key (name, force_regenerate) {
var seed = util.atob(localStorage.getItem(name));
if (!seed || force_regenerate) {
seed = nacl.randomBytes(nacl.sign.seedLength);
localStorage.setItem(name, util.btoa(seed));
log.debug('new key seed "' + name + '" saved to local storage!');
} else {
log.debug('key seed "' + name + '" loaded from local storage!');
}
return nacl.sign.keyPair.fromSeed(seed);
}

exports.load_private_key = load_private_key;


function delete_private_key (name) {
// FIXME: poor man's secure erase
for (var i = 0; i < 5; ++i) {
seed = nacl.randomBytes(nacl.sign.seedLength);
localStorage.setItem(name, util.btoa(seed));
localStorage.setItem(name, '');
localStorage.setItem(name, null);
}
}

exports.delete_private_key = delete_private_key;


function sign_challenge (pkey, extra) {
var challenge = util.htob(extra.challenge);
var signature = nacl.sign.detached(challenge, pkey.secretKey);
var res = util.btoh(signature) + util.btoh(challenge);
return res;
}

exports.sign_challenge = sign_challenge;


function public_key (pkey) {
return util.btoh(pkey.publicKey);
}

exports.public_key = public_key;


function create_connection (config) {

var url = config.url;
var realm = config.realm;
var authid = config.authid;
var pkey = config.pkey;
var activation_code = config.activation_code;
var request_new_activation_code = config.request_new_activation_code;
var serializers = config.serializers;

if (config.debug) {
console.log(url);
console.log(realm);
console.log(authid);
console.log(pkey);
console.log(activation_code);
console.log(request_new_activation_code);
console.log(serializers);
}

function onchallenge (session, method, extra) {
// we only know how to process WAMP-cryptosign here!
if (method == "cryptosign") {
// and to do so, we let above helper sign the
// WAMP-cryptosign challenge as required
// and return a signature
return sign_challenge(pkey, extra);
} else {
throw "don't know how to authenticate using '" + method + "'";
}
}

authextra = {
// forward the client pubkey: this allows us to omit authid as
// the router can identify us with the pubkey already
pubkey: public_key(pkey),

// not yet implemented. a public key the router should provide
// a trustchain for it's public key. the trustroot can eg be
// hard-coded in the client, or come from a command line option.
trustroot: null,

// not yet implemented. for authenticating the router, this
// challenge will need to be signed by the router and send back
// in AUTHENTICATE for client to verify. A string with a hex
// encoded 32 bytes random value.
challenge: null,

// FIXME: at least on NodeJS, it should be possible to implement
// this additional security measure!
//channel_binding: 'tls-unique'
channel_binding: null,

// you should only provide an activation_code the very first time
// the key pair used is paired. a token can only be used exactly once
// and reusing it, even from the original client, will result in an error!
activation_code: activation_code,

// if true, request sending a new email with a new activation code
request_new_activation_code: request_new_activation_code
}

// now create a AutobahnJS Connection object
// with WAMP-cryptosign being the only configured
// authentication method:
var _connection = new connection.Connection({
// this MUST be given
url: url,

// this MAY be given - if not, then connect to global user realm
// if given, the user must have access permissions for the respective
// management realm (to which both users and fabric nodes are connected)
realm: realm,

// this MAY be given (but MUST be given on register/pairing)
authid: authid,

// this MUST be given
authmethods: ["cryptosign"],

// see above
onchallenge: onchallenge,

// see above
authextra: authextra,

// WAMP serializers to use
serializers: config.serializers
});

return _connection;
}

exports.create_connection = create_connection;
4 changes: 4 additions & 0 deletions lib/autobahn.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var when = require('when');

var msgpack = require('msgpack-lite');
var cbor = require('cbor');
var nacl = require('tweetnacl');

if ('AUTOBAHN_DEBUG' in global && AUTOBAHN_DEBUG) {
// https://github.com/cujojs/when/blob/master/docs/api.md#whenmonitor
Expand All @@ -39,6 +40,7 @@ var serializer = require('./serializer.js');

var persona = require('./auth/persona.js');
var cra = require('./auth/cra.js');
var cryptosign = require('./auth/cryptosign.js');

exports.version = pjson.version;

Expand All @@ -59,10 +61,12 @@ exports.serializer = serializer;

exports.auth_persona = persona.auth;
exports.auth_cra = cra;
exports.auth_cryptosign = cryptosign;

exports.when = when;
exports.msgpack = msgpack;
exports.cbor = cbor;
exports.nacl = nacl;

exports.util = util;
exports.log = log;
2 changes: 1 addition & 1 deletion lib/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -1126,7 +1126,7 @@ Session.prototype.log = function () {

Session.prototype.join = function (realm, authmethods, authid, authextra) {

util.assert(typeof realm === 'string', "Session.join: <realm> must be a string");
util.assert(!realm || typeof realm === 'string', "Session.join: <realm> must be a string");
util.assert(!authmethods || Array.isArray(authmethods), "Session.join: <authmethods> must be an array []");
util.assert(!authid || typeof authid === 'string', "Session.join: <authid> must be a string");

Expand Down
71 changes: 70 additions & 1 deletion lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,76 @@ var when = require('when');



function _base64_to_uint8array (input) {
var raw = new Buffer(input, 'base64');
var arr = new Uint8Array(new ArrayBuffer(raw.length));
for(i = 0; i < raw.length; i++) {
arr[i] = raw[i];
}
return arr;
};

exports.base64_to_uint8array = _base64_to_uint8array;


function _string_to_uint8array (str) {
var raw = new Buffer(str, 'utf8');
var arr = new Uint8Array(new ArrayBuffer(raw.length));
for(i = 0; i < raw.length; i++) {
arr[i] = raw[i];
}
return arr;
};

exports.string_to_uint8array = _string_to_uint8array


function _atob (s) {
return new Uint8Array(atob(s).split("").map(function(c) { return c.charCodeAt(0); }));
}

exports.atob = _atob


function _btoa (b) {
return btoa(String.fromCharCode.apply(null, b));
}

exports.btoa = _btoa


function _btoh (bytes) {
var res = '';
for (var i = 0; i < bytes.length; ++i) {
res += ('0' + (bytes[i] & 0xFF).toString(16)).slice(-2);
}
return res;
}

exports.btoh = _btoh


function _htob (hex) {
if (typeof hex !== 'string') {
throw new TypeError('Expected input to be a string')
}

if ((hex.length % 2) !== 0) {
throw new RangeError('Expected string to be an even number of characters')
}

var view = new Uint8Array(hex.length / 2)

for (var i = 0; i < hex.length; i += 2) {
view[i / 2] = parseInt(hex.substring(i, i + 2), 16)
}

return view
}

exports.htob = _htob


var rand_normal = function (mean, sd) {
// Derive a Gaussian from Uniform random variables
// http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
Expand Down Expand Up @@ -200,7 +270,6 @@ var defaults = function () {
};



exports.rand_normal = rand_normal;
exports.assert = assert;
exports.http_post = http_post;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "autobahn",
"version": "17.5.1",
"version": "17.5.2",
"description": "An implementation of The Web Application Messaging Protocol (WAMP).",
"main": "index.js",
"scripts": {
Expand Down
Loading

0 comments on commit 37fb323

Please sign in to comment.