Manuel Araoz
11 years ago
8 changed files with 5234 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,148 @@ |
|||
'use strict'; |
|||
|
|||
var Message = require('./Message'); |
|||
var ECIES = require('./ECIES'); |
|||
|
|||
/* Encrypted, authenticated messages to be shared between copayers */ |
|||
var AuthMessage = function() { |
|||
}; |
|||
|
|||
AuthMessage.encode = function(topubkey, fromkey, payload, opts) { |
|||
var version1 = new Buffer([1]); //peers will reject messges containing not-understood version1
|
|||
//i.e., increment version1 to prevent communications with old clients
|
|||
var version2 = new Buffer([0]); //peers will not reject messages containing not-understood version2
|
|||
//i.e., increment version2 to allow communication with old clients, but signal new clients
|
|||
|
|||
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') |
|||
}; |
|||
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 !== 1) { |
|||
throw new Error('Invalid version number'); |
|||
} |
|||
|
|||
if (version2 !== 0) { |
|||
//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'); |
|||
} |
|||
|
|||
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,160 @@ |
|||
'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(); |
|||
|
|||
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 message = new Buffer('message'); |
|||
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 message = new Buffer('message'); |
|||
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 message = new Buffer('message'); |
|||
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 message = new Buffer('message'); |
|||
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 message = new Buffer('message'); |
|||
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