Manuel Aráoz
11 years ago
8 changed files with 5293 additions and 181 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -0,0 +1,179 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var Message = require('./Message'); |
||||
|
var ECIES = require('./ECIES'); |
||||
|
var preconditions = require('preconditions').singleton(); |
||||
|
var Key = require('./Key'); |
||||
|
|
||||
|
|
||||
|
var majorVersion = 1; |
||||
|
var minorVersion = 0; |
||||
|
|
||||
|
/* Encrypted, authenticated messages to be shared between copayers */ |
||||
|
var AuthMessage = function() {}; |
||||
|
|
||||
|
AuthMessage.setVersion = function(major, minor) { |
||||
|
majorVersion = major; |
||||
|
minorVersion = minor; |
||||
|
}; |
||||
|
|
||||
|
AuthMessage.encode = function(topubkey, fromkey, payload, opts) { |
||||
|
preconditions.checkArgument(fromkey instanceof Key, 'fromkey'); |
||||
|
if (typeof topubkey === 'string') { |
||||
|
topubkey = new Buffer(topubkey, 'hex'); |
||||
|
} |
||||
|
if (!(payload instanceof Buffer)) { |
||||
|
payload = new Buffer(JSON.stringify(payload)); |
||||
|
} |
||||
|
//peers should reject messges containing bigger major version
|
||||
|
//i.e., increment to prevent communications with old clients
|
||||
|
var version1 = new Buffer([majorVersion]); |
||||
|
|
||||
|
//peers should not reject messages containing not-understood minorversion
|
||||
|
//i.e., increment to allow communication with old clients, but signal new clients
|
||||
|
var version2 = new Buffer([minorVersion]); |
||||
|
|
||||
|
if (opts && opts.nonce && Buffer.isBuffer(opts.nonce) && opts.nonce.length == 8) { |
||||
|
var nonce = opts.nonce; |
||||
|
} else { |
||||
|
var nonce = new Buffer(8); |
||||
|
nonce.fill(0); //nonce is a big endian 8 byte number
|
||||
|
} |
||||
|
|
||||
|
var toencrypt = Buffer.concat([version1, version2, nonce, payload]); |
||||
|
var toencrypthexbuf = new Buffer(toencrypt.toString('hex')); //due to bug in sjcl/bitcore, must use hex string
|
||||
|
var encrypted = AuthMessage._encrypt(topubkey, toencrypthexbuf); |
||||
|
var sig = AuthMessage._sign(fromkey, encrypted); |
||||
|
var encoded = { |
||||
|
pubkey: fromkey.public.toString('hex'), |
||||
|
sig: sig.toString('hex'), |
||||
|
encrypted: encrypted.toString('hex'), |
||||
|
to: topubkey.toString('hex') |
||||
|
}; |
||||
|
return encoded; |
||||
|
}; |
||||
|
|
||||
|
AuthMessage.decode = function(key, encoded, opts) { |
||||
|
if (opts && opts.prevnonce && Buffer.isBuffer(opts.prevnonce) && opts.prevnonce.length == 8) { |
||||
|
var prevnonce = opts.prevnonce; |
||||
|
} else { |
||||
|
var prevnonce = new Buffer(8); |
||||
|
prevnonce.fill(0); //nonce is a big endian 8 byte number
|
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
var frompubkey = new Buffer(encoded.pubkey, 'hex'); |
||||
|
} catch (e) { |
||||
|
throw new Error('Error decoding public key: ' + e); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
var sig = new Buffer(encoded.sig, 'hex'); |
||||
|
var encrypted = new Buffer(encoded.encrypted, 'hex'); |
||||
|
} catch (e) { |
||||
|
throw new Error('Error decoding data: ' + e); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
var v = AuthMessage._verify(frompubkey, sig, encrypted); |
||||
|
} catch (e) { |
||||
|
throw new Error('Error verifying signature: ' + e); |
||||
|
} |
||||
|
|
||||
|
if (!v) { |
||||
|
throw new Error('Invalid signature'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
var decryptedhexbuf = AuthMessage._decrypt(key.private, encrypted); |
||||
|
var decrypted = new Buffer(decryptedhexbuf.toString(), 'hex'); //workaround for bug in bitcore/sjcl
|
||||
|
} catch (e) { |
||||
|
throw new Error('Cannot decrypt data: ' + e); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
var version1 = decrypted[0]; |
||||
|
var version2 = decrypted[1]; |
||||
|
var nonce = decrypted.slice(2, 10); |
||||
|
var payload = decrypted.slice(10); |
||||
|
} catch (e) { |
||||
|
throw new Error('Cannot parse decrypted data: ' + e); |
||||
|
} |
||||
|
|
||||
|
if (payload.length === 0) { |
||||
|
throw new Error('No data present'); |
||||
|
} |
||||
|
|
||||
|
if (version1 !== majorVersion) { |
||||
|
throw new Error('Invalid version number'); |
||||
|
} |
||||
|
|
||||
|
if (version2 !== minorVersion) { |
||||
|
//put special version2 handling code here, if ever needed
|
||||
|
} |
||||
|
|
||||
|
if (!AuthMessage._noncegt(nonce, prevnonce) && prevnonce.toString('hex') !== '0000000000000000') { |
||||
|
throw new Error('Nonce not equal to zero and not greater than the previous nonce'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
payload = JSON.parse(payload); |
||||
|
} catch (e) { |
||||
|
if (e instanceof SyntaxError) { |
||||
|
// if we can't parse a JSON, just return what we found
|
||||
|
} else { |
||||
|
throw e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var decoded = { |
||||
|
version1: version1, |
||||
|
version2: version2, |
||||
|
nonce: nonce, |
||||
|
payload: payload |
||||
|
}; |
||||
|
|
||||
|
return decoded; |
||||
|
}; |
||||
|
|
||||
|
//return true if nonce > prevnonce; false otherwise
|
||||
|
AuthMessage._noncegt = function(nonce, prevnonce) { |
||||
|
var noncep1 = nonce.slice(0, 4).readUInt32BE(0); |
||||
|
var prevnoncep1 = prevnonce.slice(0, 4).readUInt32BE(0); |
||||
|
|
||||
|
if (noncep1 > prevnoncep1) |
||||
|
return true; |
||||
|
|
||||
|
if (noncep1 < prevnoncep1) |
||||
|
return false; |
||||
|
|
||||
|
var noncep2 = nonce.slice(4, 8).readUInt32BE(0); |
||||
|
var prevnoncep2 = prevnonce.slice(4, 8).readUInt32BE(0); |
||||
|
|
||||
|
if (noncep2 > prevnoncep2) |
||||
|
return true; |
||||
|
|
||||
|
return false; |
||||
|
}; |
||||
|
|
||||
|
AuthMessage._encrypt = function(topubkey, payload, r, iv) { |
||||
|
var encrypted = ECIES.encrypt(topubkey, payload, r, iv); |
||||
|
return encrypted; |
||||
|
}; |
||||
|
|
||||
|
AuthMessage._decrypt = function(privkey, encrypted) { |
||||
|
var decrypted = ECIES.decrypt(privkey, encrypted); |
||||
|
return decrypted; |
||||
|
}; |
||||
|
|
||||
|
AuthMessage._sign = function(key, payload) { |
||||
|
var sig = Message.sign(payload, key); |
||||
|
return sig; |
||||
|
}; |
||||
|
|
||||
|
AuthMessage._verify = function(pubkey, signature, payload) { |
||||
|
var v = Message.verifyWithPubKey(pubkey, payload, signature); |
||||
|
return v; |
||||
|
}; |
||||
|
|
||||
|
module.exports = AuthMessage; |
@ -0,0 +1,157 @@ |
|||||
|
'use strict'; |
||||
|
|
||||
|
var chai = chai || require('chai'); |
||||
|
var should = chai.should(); |
||||
|
var sinon = require('sinon'); |
||||
|
var bitcore = bitcore || require('../bitcore'); |
||||
|
var AuthMessage = bitcore.AuthMessage; |
||||
|
var Key = bitcore.Key; |
||||
|
var util = bitcore.util; |
||||
|
|
||||
|
describe('AuthMessage model', function() { |
||||
|
var key = new Key(); |
||||
|
key.private = util.sha256(new Buffer('test')); |
||||
|
key.regenerateSync(); |
||||
|
|
||||
|
var key2 = new Key(); |
||||
|
key2.private = util.sha256(new Buffer('test 2')); |
||||
|
key2.regenerateSync(); |
||||
|
|
||||
|
var message = 'some message'; |
||||
|
|
||||
|
describe('#encode', function() { |
||||
|
|
||||
|
it('should encode a message', function() { |
||||
|
var message = new Buffer('message'); |
||||
|
var encoded = AuthMessage.encode(key2.public, key, message); |
||||
|
should.exist(encoded.pubkey); |
||||
|
should.exist(encoded.sig); |
||||
|
should.exist(encoded.encrypted); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#decode', function() { |
||||
|
|
||||
|
it('should decode an encoded message', function() { |
||||
|
var messagehex = message.toString('hex'); |
||||
|
var encoded = AuthMessage.encode(key2.public, key, message); |
||||
|
|
||||
|
var decoded = AuthMessage.decode(key2, encoded); |
||||
|
var payload = decoded.payload; |
||||
|
payload.toString('hex').should.equal(messagehex); |
||||
|
}); |
||||
|
|
||||
|
it('should decode an encoded message with proper prevnonce', function() { |
||||
|
var messagehex = message.toString('hex'); |
||||
|
var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 2]); |
||||
|
var opts = {nonce: nonce}; |
||||
|
var encoded = AuthMessage.encode(key2.public, key, message, opts); |
||||
|
|
||||
|
var prevnonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); |
||||
|
opts = {prevnonce: prevnonce}; |
||||
|
var decoded = AuthMessage.decode(key2, encoded, opts); |
||||
|
var payload = decoded.payload; |
||||
|
payload.toString('hex').should.equal(messagehex); |
||||
|
}); |
||||
|
|
||||
|
it('should decode an encoded message with proper prevnonce - for first part', function() { |
||||
|
var messagehex = message.toString('hex'); |
||||
|
var nonce = new Buffer([0, 0, 0, 2, 0, 0, 0, 0]); |
||||
|
var opts = {nonce: nonce}; |
||||
|
var encoded = AuthMessage.encode(key2.public, key, message, opts); |
||||
|
|
||||
|
var prevnonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); |
||||
|
opts = {prevnonce: prevnonce}; |
||||
|
var decoded = AuthMessage.decode(key2, encoded, opts); |
||||
|
var payload = decoded.payload; |
||||
|
payload.toString('hex').should.equal(messagehex); |
||||
|
}); |
||||
|
|
||||
|
it('should fail if prevnonce is too high', function() { |
||||
|
var messagehex = message.toString('hex'); |
||||
|
var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); |
||||
|
var opts = {nonce: nonce}; |
||||
|
var encoded = AuthMessage.encode(key2.public, key, message, opts); |
||||
|
|
||||
|
var prevnonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); |
||||
|
opts = {prevnonce: prevnonce}; |
||||
|
(function() {AuthMessage.decode(key2, encoded, opts)}).should.throw('Nonce not equal to zero and not greater than the previous nonce'); |
||||
|
}); |
||||
|
|
||||
|
it('should fail if prevnonce is too high - for first part', function() { |
||||
|
var messagehex = message.toString('hex'); |
||||
|
var nonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); |
||||
|
var opts = {nonce: nonce}; |
||||
|
var encoded = AuthMessage.encode(key2.public, key, message, opts); |
||||
|
|
||||
|
var prevnonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); |
||||
|
opts = {prevnonce: prevnonce}; |
||||
|
(function() {AuthMessage.decode(key2, encoded, opts)}).should.throw('Nonce not equal to zero and not greater than the previous nonce'); |
||||
|
}); |
||||
|
|
||||
|
it('should fail if the version number is incorrect', function() { |
||||
|
var payload = new Buffer('message'); |
||||
|
var fromkey = key; |
||||
|
var topubkey = key2.public; |
||||
|
var version1 = new Buffer([2]); |
||||
|
var version2 = new Buffer([0]); |
||||
|
var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]); |
||||
|
var toencrypt = Buffer.concat([version1, version2, nonce, payload]); |
||||
|
var toencrypt_workaround = new Buffer(toencrypt.toString('hex')); |
||||
|
var encrypted = AuthMessage._encrypt(topubkey, toencrypt_workaround); |
||||
|
var sig = AuthMessage._sign(fromkey, encrypted); |
||||
|
var encoded = { |
||||
|
pubkey: fromkey.public.toString('hex'), |
||||
|
sig: sig.toString('hex'), |
||||
|
encrypted: encrypted.toString('hex') |
||||
|
}; |
||||
|
|
||||
|
(function() {AuthMessage.decode(key2, encoded);}).should.throw('Invalid version number'); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#_encrypt', function() { |
||||
|
|
||||
|
it('should encrypt data', function() { |
||||
|
var payload = new Buffer('payload'); |
||||
|
var encrypted = AuthMessage._encrypt(key.public, payload); |
||||
|
encrypted.length.should.equal(129); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#_decrypt', function() { |
||||
|
var payload = new Buffer('payload'); |
||||
|
var payloadhex = payload.toString('hex'); |
||||
|
|
||||
|
it('should decrypt encrypted data', function() { |
||||
|
var encrypted = AuthMessage._encrypt(key.public, payload); |
||||
|
var decrypted = AuthMessage._decrypt(key.private, encrypted); |
||||
|
decrypted.toString('hex').should.equal(payloadhex); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#_sign', function() { |
||||
|
|
||||
|
it('should sign data', function() { |
||||
|
var payload = new Buffer('payload'); |
||||
|
var sig = AuthMessage._sign(key, payload); |
||||
|
sig.length.should.be.greaterThan(60); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
describe('#_verify', function() { |
||||
|
var payload = new Buffer('payload'); |
||||
|
var sig = AuthMessage._sign(key, payload); |
||||
|
|
||||
|
it('should verify signed data', function() { |
||||
|
AuthMessage._verify(key.public, sig, payload).should.equal(true); |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
}); |
Loading…
Reference in new issue