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

Already on GitHub? Sign in to your account

Salmon provenance using OpenSSL #3

Closed
wants to merge 18 commits into
from
View
@@ -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:
* [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
* [Flow](https://github.com/willconant/flow-js)
-* [Base64](https://github.com/pkrumins/node-base64)
Documentation
-------------
View
@@ -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);
View
@@ -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
+
View
@@ -27,13 +27,8 @@ var Sys = require('sys'),
Flow = require('flow'),
Util = require('util'),
Http = require('./http.js'),
- Xml = require('o3-xml');
Path = require('path'),
- Mu = require('mu');
- Base64 = require('base64');
- Crypto = require('crypto');
- Bn = require('bignumber');
- Ostatus = require('ostatus');
+ LTX = require('ltx'),
Buffer = require('buffer').Buffer;
/*
@@ -74,6 +69,7 @@ function unpack(body, callback) {
});
}
+/* TODO: rewrite with SAX */
function xmlToJs(body, callback) {
try {
var doc = Xml.parseFromString(body);
@@ -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) {
if (jrd != null && jrd.links != undefined) {
for(var i=0; i<jrd.links.length; i++) {
@@ -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) {
- 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)
function base64url_encode(input) {
- var buf = new Buffer(input);
- return Base64.encode(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
+ return new Buffer(input).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, '');
}
exports.unpack = unpack;
exports.xmlToJs = xmlToJs;
exports.base64url_decode = base64url_decode;
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;
View
@@ -7,12 +7,14 @@
, "author": "Laurent Eschenauer <laurent@eschenauer.be> (http://eschnou.com)"
, "bin" : { "status" : "./bin/status.js", "profile": "./bin/profile.js", "webfinger": "./bin/webfinger.js"}
, "main" : "./lib/ostatus"
+, "scripts" : {
+ "install": "node-waf configure build"
+ ,"update": "node-waf build"
+}
, "repository" : { "type": "git", "url": "https://github.com/eschnou/node-ostatus.git"}
, "dependencies" : {
"mu" : "http://github.com/raycmorgan/Mu/tarball/v2"
,"sax" : ">=0.1.2"
,"flow" : "0.2.x"
- ,"base64" : "2.0.x"
- ,"bignumber" : "1.1.x"
}
}
Oops, something went wrong.