Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Salmon provenance using OpenSSL #3

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ If you are using this from source, you can install the dependencies using npm fr


Or you can install all dependencies manually: Or you can install all dependencies manually:
* [node](http://nodejs.org/) v4+ is required, I'm developing against trunk. * [node](http://nodejs.org/) v4+ is required, I'm developing against trunk.
* [node-o3-xml](https://github.com/ajaxorg/node-o3-xml/)
* [Mu](https://github.com/raycmorgan/Mu/tree/v2) the v2 branch * [Mu](https://github.com/raycmorgan/Mu/tree/v2) the v2 branch
* [Flow](https://github.com/willconant/flow-js) * [Flow](https://github.com/willconant/flow-js)
* [Base64](https://github.com/pkrumins/node-base64)


Documentation Documentation
------------- -------------
Expand Down
17 changes: 17 additions & 0 deletions bin/magicsig.js
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node

var Util = require('util');
var Salmon = require('../lib/ostatus').salmon;


Util.puts("Generating key... this can take a while due to pure-JSness");
var keys = Salmon.generateKeys();

// Public
Util.puts("Public key:");
Util.puts("data:application/magic-public-key," + keys.public);
Util.puts("(Put it in your user's LRDD file)");

// Private
Util.puts("Private key:");
Util.puts(keys.private);
39 changes: 39 additions & 0 deletions bin/send_salmon.coffee
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env coffee

fs = require('fs')
request = require('request')
Salmon = require('../lib/ostatus').salmon;


if process.argv.length isnt 5
console.error "Usage: #{process.argv[0]} #{process.argv[1]} <atom.xml> <private.key> <http://salmon/endpoint>"
process.exit 1

# Load parameters
atomPayload = fs.readFileSync process.argv[2]
privKey = fs.readFileSync process.argv[3]
salmonEndpoint = process.argv[4]

# Sign & prepare envelope
envelope = "<?xml version='1.0' encoding='UTF-8'?>\n" +
Salmon.signEnvelopeXML(atomPayload, privKey).toString()
console.log 'Generated envelope: ' + envelope

# Send envelope via HTTP
opts =
method: 'POST'
uri: salmonEndpoint
body: envelope
headers:
'Content-Type': 'application/magic-envelope+xml'

request opts, (error, response, body) ->
if error
console.error error.message
return
if response.statusCode is 200
console.log body
else
console.error response.statusCode
console.log body

148 changes: 91 additions & 57 deletions lib/ostatus/salmon.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -27,13 +27,8 @@ var Sys = require('sys'),
Flow = require('flow'), Flow = require('flow'),
Util = require('util'), Util = require('util'),
Http = require('./http.js'), Http = require('./http.js'),
Xml = require('o3-xml');
Path = require('path'), Path = require('path'),
Mu = require('mu'); LTX = require('ltx'),
Base64 = require('base64');
Crypto = require('crypto');
Bn = require('bignumber');
Ostatus = require('ostatus');
Buffer = require('buffer').Buffer; Buffer = require('buffer').Buffer;


/* /*
Expand Down Expand Up @@ -74,6 +69,7 @@ function unpack(body, callback) {
}); });
} }


/* TODO: rewrite with SAX */
function xmlToJs(body, callback) { function xmlToJs(body, callback) {
try { try {
var doc = Xml.parseFromString(body); var doc = Xml.parseFromString(body);
Expand Down Expand Up @@ -101,52 +97,6 @@ function xmlToJs(body, callback) {
} }
} }


function verify_signature(me, key) {
// Assemble the signature base string
var M = me.data + "." + base64url_encode(me.data_type, 'ascii') + "." + base64url_encode(me.encoding, 'ascii') + "." + base64url_encode(me.alg, 'ascii');
//console.log("M: " + M);
console.log("Key: " + key);

// Compute the SHA256 digest hash
var sha256 = Crypto.createHash('sha256');
sha256.update(M);
var hash = sha256.digest('hex');
console.log("Hash: " + hash);

// Decode the signature from the base64url encoded value we have in the envelope
var sig = Bn.b64toBA(me.sigs[0].value.replace(/-/g, '+').replace(/_/g, '/'));
var t = "";
for(var i=0; i<sig.length; i++) t+= Bn.byte2Hex(sig[i]);
//console.log("Sign: " + t);

// Extract the key elements from the key encoding
var key = key.replace(/-/g, '+').replace(/_/g, '/').split('.');
var n = Bn.b64tohex(key[1]);
var e = "10001"; //Rsa.b64tohex(key[2]);

// Prepare the RSA Engine
var rsa = new Bn.Key();
rsa.setPublic(n, e);

// Perform RSA Public on the signature string
var v = rsa.doPublic(new Bn.BigInteger(sig));

// Convert the verified output into a string and keep the last 64 characters
var buf = new Buffer(v.toByteArray());
var b = buf.toString('hex');
console.log("Decoded string: " + b);

var signed_hash = b.substr(b.length-64, 64);
//console.log("signed hash: " + signed_hash);

// This should match the hash we have computed
if (hash == signed_hash) {
return true;
} else {
return false;
}
}

function _grabKey(jrd) { function _grabKey(jrd) {
if (jrd != null && jrd.links != undefined) { if (jrd != null && jrd.links != undefined) {
for(var i=0; i<jrd.links.length; i++) { for(var i=0; i<jrd.links.length; i++) {
Expand All @@ -156,19 +106,103 @@ function _grabKey(jrd) {
} }
} }


//From: https://github.com/ptarjan/base64url/blob/master/node.js function signEnvelopeXML(data, privKey, data_type, key_id) {
var me = signEnvelopeJSON(data, privKey, data_type, key_id);
var meEl = new LTX.Element('me:env',
{ 'xmlns:me': 'http://salmon-protocol.org/ns/magic-env' }).
c('me:data',
{ type: me.data_type }).t(me.data).up().
c('me:encoding').t(me.encoding).up().
c('me:alg').t(me.alg).up();
me.sigs.forEach(function(sig) {
var sigEl = meEl.c('me:sig').t(sig.value);
if (sig.key_id)
sigEl.attrs.key_id = sig.key_id;
});
return meEl;
}

function signEnvelopeJSON(data, privKey, data_type, key_id) {
var me = {
data: base64url_encode(data),
data_type: data_type || 'application/atom+xml',
encoding: 'base64url',
alg: 'RSA-SHA256'
};

var sig = {};
sig.value = base64url_encode(generateSignature(me, privKey));
if (key_id)
sig.key_id = key_id;

me.sigs = [sig];
return me;
}

function verifyEnvelopeJSON(me, pubKey) {
return me.sigs && me.sigs.some(function(sig) {
return verifySignature(me, sig.value, pubKey);
});
}

// Assemble the signature base string
function baseString(data, data_type, encoding, alg) {
return [data /* already transported base64url-encoded */,
base64url_encode(data_type),
base64url_encode(encoding),
base64url_encode(alg)
].join('.');
}

var Provenance = require('../../build/default/provenance');

function generateKeys() {
var key = Provenance.generate();
key.public = ["RSA",
base64url_encode(key.public.n),
base64url_encode(key.public.e)
].join('.');
return key;
}

function generateSignature(me, privKey) {
var m = baseString(me.data, me.data_type,
me.encoding || 'base64url',
me.alg || 'RSA-SHA256');
return Provenance.signRSASHA256(m, privKey);
}

function verifySignature(me, sig, pubKey) {
var match;
if ((match = pubKey.match(/^RSA\.([^\.]+)\.([^\.]+)$/))) {
var m = baseString(me.data, me.data_type,
me.encoding || 'base64url',
me.alg || 'RSA-SHA256');
return Provenance.verifyRSASHA256(m, base64url_decode(sig),
{ n: base64url_decode(match[1]),
e: base64url_decode(match[2])
});
}
else
throw TypeError('Invalid public key');
}

function base64url_decode(input) { function base64url_decode(input) {
return Base64.decode(input.replace(/-/g, '+').replace(/_/g, '/')); return new Buffer(input.toString().replace(/-/g, '+').replace(/_/g, '/'), 'base64');
} }


// Encode to Base64url and removing padding (as per salmon spec) // Encode to Base64url and removing padding (as per salmon spec)
function base64url_encode(input) { function base64url_encode(input) {
var buf = new Buffer(input); return new Buffer(input).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
return Base64.encode(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
} }


exports.unpack = unpack; exports.unpack = unpack;
exports.xmlToJs = xmlToJs; exports.xmlToJs = xmlToJs;
exports.base64url_decode = base64url_decode; exports.base64url_decode = base64url_decode;
exports.base64url_encode = base64url_encode; exports.base64url_encode = base64url_encode;
exports.verify_signature = verify_signature; exports.generateKeys = generateKeys;
exports.generateSignature = generateSignature;
exports.verifySignature = verifySignature;
exports.signEnvelopeJSON = signEnvelopeJSON;
exports.signEnvelopeXML = signEnvelopeXML;
exports.verifyEnvelopeJSON = verifyEnvelopeJSON;
6 changes: 4 additions & 2 deletions package.json
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
, "author": "Laurent Eschenauer <laurent@eschenauer.be> (http://eschnou.com)" , "author": "Laurent Eschenauer <laurent@eschenauer.be> (http://eschnou.com)"
, "bin" : { "status" : "./bin/status.js", "profile": "./bin/profile.js", "webfinger": "./bin/webfinger.js"} , "bin" : { "status" : "./bin/status.js", "profile": "./bin/profile.js", "webfinger": "./bin/webfinger.js"}
, "main" : "./lib/ostatus" , "main" : "./lib/ostatus"
, "scripts" : {
"install": "node-waf configure build"
,"update": "node-waf build"
}
, "repository" : { "type": "git", "url": "https://github.com/eschnou/node-ostatus.git"} , "repository" : { "type": "git", "url": "https://github.com/eschnou/node-ostatus.git"}
, "dependencies" : { , "dependencies" : {
"mu" : "http://github.com/raycmorgan/Mu/tarball/v2" "mu" : "http://github.com/raycmorgan/Mu/tarball/v2"
,"sax" : ">=0.1.2" ,"sax" : ">=0.1.2"
,"flow" : "0.2.x" ,"flow" : "0.2.x"
,"base64" : "2.0.x"
,"bignumber" : "1.1.x"
} }
} }
Loading