Merge branch 'master' of github.com:bitpay/bitauth

Conflicts:
	.gitignore
	README.md
	package.json
This commit is contained in:
Eric Martindale 2014-06-26 13:46:22 -04:00
commit bca110a9ea
14 changed files with 539 additions and 46 deletions

26
.gitignore vendored
View File

@ -1,25 +1,3 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
dist/bitauth.browser.js
dist/bitauth.browser.min.js

180
README.md
View File

@ -1,2 +1,180 @@
bitauth
BitAuth
=======
Passwordless authentication using Bitcoin cryptography
# Overview
BitAuth is a way to do secure, passwordless authentication using the cryptography
in Bitcoin. Instead of using a shared secret, the client signs each request using
a private key and the server checks to make sure the signature is valid and matches
the public key.
## Advantages over other authentication mechanisms
* By signing each request, man in the middle attacks are impossible.
* A nonce is part of the data signed, which prevents replay attacks.
* The cryptography in Bitcoin is rock solid and is securing billions
of dollars worth of bitcoins.
* It uses elliptic curve cryptography which performs much better than RSA.
* Because the private key is never revealed to the server, it does
not need to be exchanged between the server and client over a side channel like
in HMAC.
## Technical Overview
BitAuth uses the same technology in Bitcoin. A public private key pair is created
using elliptic curve secp256k1. The public SIN (System identification number),
like a bitcoin address, is the RIPEMD 160, SHA256 hash of the public key.
See https://en.bitcoin.it/wiki/Identity_protocol_v1 for complete details.
In each request, the client includes a nonce to prevent replay attacks. The client
signs the full url with the request body concatenated if there is one. The signature
is included in the x-signature header and the public key is included in the
x-pubkey header.
The server verifies that the signature is valid and that it matches the public key.
It then computes the SIN from the public key, and sees whether that SIN has access
to the requested resource. The nonce is checked to make sure it is higher than
the previously used nonce.
## Technology is readily available
With the growing popularity of Bitcoin, there are already libraries written in
many languages. Because BitAuth uses the same technology as Bitcoin, it is easy
to start using BitAuth.
## Problems with password authentication
* Have to keep track of a separate password for every web service. People forget
passwords, encouraging them to reuse passwords and opening themselves up to
having multiple services compromised.
* Brute force attacks on weak passwords.
* Passwords may travel over plaintext
* Passwords in databases being leaked
* Phishing attacks to steal passwords
## Passwordless based authentication across web services
With BitAuth, users can use the same, strong password to encrypt their keys and
not worry about one service gaining access to another.
In the future, an identity system could be built around BitAuth keys where a user
could create one key to represent an identity which could authenticate against
multiple services.
In order for this to work, there would have to be a browser
integration or plugin which would manage these identities and a Javascript API
where websites could sign requests going to their website with the private key,
but without exposing the private key to the third party sites.
There also needs to be a public place to store SIN's, preferably in
a decentralized blockchain or datastore like namecoin. Key revocations could
be stored here as well as reviews/feedback to build a reputation around an
identity.
# Getting Started
Example server
```
var express = require('express');
var bodyParser = require('body-parser');
var rawBody = require('../lib/middleware/rawbody');
var bitauth = require('../lib/middleware/bitauth');
var users = {
'Tf7UNQnxB8SccfoyZScQmb34V2GdEtQkzDz': {name: 'Alice'},
'Tf22EUFxHWh4wmA3sDuw151W5C5g32jgph2': {name: 'Bob'}
};
var pizzas = [];
var app = express();
app.use(rawBody);
app.use(bodyParser());
app.get('/user', bitauth, function(req, res) {
if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'});
res.send(200, users[req.sin]);
});
app.post('/pizzas', bitauth, function(req, res) {
if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'});
var pizza = req.body;
pizza.owner = users[req.sin].name;
pizzas.push(pizza);
res.send(200, req.body);
});
app.get('/pizzas', function(req, res) {
res.send(200, pizzas);
});
app.listen(3000);
```
Example client
```
var request = require('request');
var bitauth = require('../lib/bitauth');
// These can be generated with bitauth.generateSin()
var keys = {
alice: '38f93bdda21a5c4a7bae4eb75bb7811cbc3eb627176805c1009ff2099263c6ad',
bob: '09880c962437080d72f72c8c63a69efd65d086c9e7851a87b76373eb6ce9aab5'
};
// GET
for(k in keys) {
var url = 'http://localhost:3000/user';
var dataToSign = url;
var options = {
url: url,
headers: {
'x-pubkey': bitauth.getPublicKeyFromPrivateKey(keys[k]),
'x-signature': bitauth.sign(dataToSign, keys[k])
}
};
request.get(options, function(err, response, body) {
if(err) {
console.log(err);
}
if(body) {
console.log(body);
}
});
}
var pizzas = ['pepperoni', 'sausage', 'veggie', 'hawaiian'];
// POST
for(k in keys) {
var url = 'http://localhost:3000/pizzas';
var data = {type: pizzas[Math.floor(Math.random() * pizzas.length)]};
var dataToSign = url + JSON.stringify(data);
var options = {
url: url,
headers: {
'x-pubkey': bitauth.getPublicKeyFromPrivateKey(keys[k]),
'x-signature': bitauth.sign(dataToSign, keys[k])
},
json: data
};
request.post(options, function(err, response, body) {
if(err) {
console.log(err);
}
if(body) {
console.log(body);
}
});
}
```

11
dist/README.md vendored Normal file
View File

@ -0,0 +1,11 @@
# BitAuth Browser Bundle
To build a browser compatible version of BitAuth, run the following command from
the project's root directory:
```
npm run make-dist
```
This will output `bitauth.browser.js` to this directory. The script introduces a
global variable at `window.bitauth`.

58
examples/client.js Normal file
View File

@ -0,0 +1,58 @@
var request = require('request');
var bitauth = require('../lib/bitauth');
// These can be generated with bitauth.generateSin()
var keys = {
alice: '38f93bdda21a5c4a7bae4eb75bb7811cbc3eb627176805c1009ff2099263c6ad',
bob: '09880c962437080d72f72c8c63a69efd65d086c9e7851a87b76373eb6ce9aab5'
};
// GET
for(k in keys) {
var url = 'http://localhost:3000/user';
var dataToSign = url;
var options = {
url: url,
headers: {
'x-pubkey': bitauth.getPublicKeyFromPrivateKey(keys[k]),
'x-signature': bitauth.sign(dataToSign, keys[k])
}
};
request.get(options, function(err, response, body) {
if(err) {
console.log(err);
}
if(body) {
console.log(body);
}
});
}
var pizzas = ['pepperoni', 'sausage', 'veggie', 'hawaiian'];
// POST
for(k in keys) {
var url = 'http://localhost:3000/pizzas';
var data = {type: pizzas[Math.floor(Math.random() * pizzas.length)]};
var dataToSign = url + JSON.stringify(data);
var options = {
url: url,
headers: {
'x-pubkey': bitauth.getPublicKeyFromPrivateKey(keys[k]),
'x-signature': bitauth.sign(dataToSign, keys[k])
},
json: data
};
request.post(options, function(err, response, body) {
if(err) {
console.log(err);
}
if(body) {
console.log(body);
}
});
}

35
examples/server.js Normal file
View File

@ -0,0 +1,35 @@
var express = require('express');
var bodyParser = require('body-parser');
var rawBody = require('../lib/middleware/rawbody');
var bitauth = require('../lib/middleware/bitauth');
var users = {
'Tf7UNQnxB8SccfoyZScQmb34V2GdEtQkzDz': {name: 'Alice'},
'Tf22EUFxHWh4wmA3sDuw151W5C5g32jgph2': {name: 'Bob'}
};
var pizzas = [];
var app = express();
app.use(rawBody);
app.use(bodyParser());
app.get('/user', bitauth, function(req, res) {
if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'});
res.send(200, users[req.sin]);
});
app.post('/pizzas', bitauth, function(req, res) {
if(!req.sin || !users[req.sin]) return res.send(401, {error: 'Unauthorized'});
var pizza = req.body;
pizza.owner = users[req.sin].name;
pizzas.push(pizza);
res.send(200, req.body);
});
app.get('/pizzas', function(req, res) {
res.send(200, pizzas);
});
app.listen(3000);

9
index.js Normal file
View File

@ -0,0 +1,9 @@
// get base functionality
var bitauth = require('./lib/bitauth');
// add node-specific encrypt/decrypt
bitauth.encrypt = require('./lib/encrypt');
bitauth.decrypt = require('./lib/decrypt');
module.exports = bitauth;

62
lib/bitauth.js Normal file
View File

@ -0,0 +1,62 @@
var crypto = require('crypto');
var bitcore = require('bitcore');
var Key = bitcore.Key;
var SIN = bitcore.SIN;
var SINKey = bitcore.SINKey
var util = bitcore.util;
var BitAuth = {};
BitAuth.generateSin = function() {
var sk = new SINKey();
sk.generate();
return sk.storeObj();
};
BitAuth.getPublicKeyFromPrivateKey = function(privkey) {
try {
var key = new Key();
key.private = new Buffer(privkey, 'hex');
key.regenerateSync();
return key.public.toString('hex');
} catch (err) {
console.log(err);
return null;
}
};
BitAuth.getSinFromPublicKey = function(pubkey) {
var pubkeyHash = util.sha256ripe160(new Buffer(pubkey, 'hex'));
var sin = new SIN(SIN.SIN_EPHEM, pubkeyHash);
return sin.toString();
}
BitAuth.sign = function(data, privkey) {
var hash = util.sha256(data);
try {
var key = new Key();
key.private = new Buffer(privkey, 'hex');
return key.signSync(hash).toString('hex');
} catch (err) {
console.log(err.stack);
console.log(err);
return null;
}
};
BitAuth.verifySignature = function(data, pubkey, signature, callback) {
var hash = util.sha256(data);
try {
var key = new Key();
key.public = new Buffer(pubkey, 'hex');
key.verifySignature(hash, new Buffer(signature, 'hex'), callback);
} catch (err) {
callback(err, false);
}
};
module.exports = BitAuth;

14
lib/decrypt.js Normal file
View File

@ -0,0 +1,14 @@
var base58 = require('base58-native');
var crypto = require('crypto');
module.exports = function decrypt(password, str) {
var aes256 = crypto.createDecipher('aes-256-cbc', password);
var a = aes256.update(base58.decode(str));
var b = aes256.final();
var buf = new Buffer(a.length + b.length);
a.copy(buf, 0);
b.copy(buf, a.length);
return buf.toString('utf8');
};

14
lib/encrypt.js Normal file
View File

@ -0,0 +1,14 @@
var base58 = require('base58-native');
var crypto = require('crypto');
module.exports = function encrypt(password, str) {
var aes256 = crypto.createCipher('aes-256-cbc', password);
var a = aes256.update(str, 'utf8');
var b = aes256.final();
var buf = new Buffer(a.length + b.length);
a.copy(buf, 0);
b.copy(buf, a.length);
return base58.encode(buf);
};

24
lib/middleware/bitauth.js Normal file
View File

@ -0,0 +1,24 @@
var bitauth = require('../bitauth');
module.exports = function(req, res, next) {
if(req.headers && req.headers['x-pubkey'] && req.headers['x-signature']) {
// Check signature is valid
// First construct data to check signature on
var fullUrl = req.protocol + '://' + req.get('host') + req.url;
var data = fullUrl + req.rawBody;
bitauth.verifySignature(data, req.headers['x-pubkey'], req.headers['x-signature'], function(err, result) {
if(err || !result) {
return res.send(400, {error: 'Invalid signature'});
}
// Get the SIN from the public key
var sin = bitauth.getSinFromPublicKey(req.headers['x-pubkey']);
if(!sin) return res.send(400, {error: 'Bad public key'});
req.sin = sin;
next();
});
} else {
next();
}
};

View File

@ -0,0 +1,7 @@
module.exports = function(req, res, next) {
req.rawBody = '';
req.on('data', function(chunk) {
req.rawBody += chunk;
});
next();
};

View File

@ -1,25 +1,35 @@
{
"name": "bitauth",
"version": "0.0.1",
"author": "Satoshi Nakamoto <satoshi@bitpay.com>",
"description": "The secure authentication framework, built on Bitcore.",
"contributors": [
{
"name": "Patrick Nagurny",
"email": "patrick@bitpay.com"
}
],
"keywords": [
"bitcoin",
"SIN",
"System Identification Number",
"token"
],
"dependencies" : {
"bitcore" : ">0.1.0"
"description": "Passwordless authentication using Bitcoin cryptography",
"author": {
"name": "Patrick Nagurny",
"email": "patrick@bitpay.com"
},
"license": "MIT",
"engines": {
"node": ">=0.10"
"contributors": [
{
"name": "Gordon Hall",
"email": "gordon@bitpay.com"
}
],
"scripts": {
"make-dist": "sh scripts/make-dist.sh",
"test": "node_modules/.bin/mocha test/* --reporter spec",
"postinstall": "npm run make-dist"
},
"main": "index.js",
"version": "0.1.1",
"dependencies": {
"bitcore": ">= 0.1.9",
"request": "^2.36.0",
"express": "^4.3.1",
"base58-native": "^0.1.4",
"body-parser": "^1.2.0"
},
"devDependencies": {
"uglify-js": "~2.4.14",
"browserify": "~4.1.11",
"should": "~4.0.4",
"mocha": "~1.20.1"
}
}
}

11
scripts/make-dist.sh Normal file
View File

@ -0,0 +1,11 @@
cd node_modules/bitcore
echo "Building browser bundle for bitcore..."
node browser/build -s lib/Key,lib/SINKey,lib/SIN,util/util
echo "Building browser bundle for bitauth..."
cd ../../
node_modules/.bin/browserify lib/bitauth.js -s bitauth -x buffertools -i bitcore -o dist/bitauth.browser.js
echo "Compiling bitcore and bitauth..."
node_modules/.bin/uglifyjs node_modules/bitcore/browser/bundle.js dist/bitauth.browser.js -b -o dist/bitauth.browser.js
echo "Minifying bundle..."
node_modules/.bin/uglifyjs dist/bitauth.browser.js -o dist/bitauth.browser.min.js
echo "Done!"

82
test/bitauth.js Normal file
View File

@ -0,0 +1,82 @@
var should = require('should');
var bitauth = require('../index');
describe('bitauth', function() {
var keys = null;
var contract = 'keyboard cat';
var secret = 'o hai, nsa. how i do teh cryptos?';
var password = 's4705hiru13z!';
var signature = null;
var enc = null;
describe('#generateSin', function() {
it('should generate a sin object', function(done) {
keys = bitauth.generateSin();
should.exist(keys);
should.exist(keys.pub);
should.exist(keys.priv);
should.exist(keys.sin);
done();
});
});
describe('#getPublicKeyFromPrivateKey', function() {
it('should properly get the public key', function(done) {
bitauth.getPublicKeyFromPrivateKey(keys.priv).should.equal(keys.pub);
done();
});
});
describe('#getSinFromPublicKey', function() {
it('should properly get the sin', function(done) {
bitauth.getSinFromPublicKey(keys.pub).should.equal(keys.sin);
done();
});
});
describe('#sign', function() {
it('should sign the string', function(done) {
signature = bitauth.sign(contract, keys.priv);
should.exist(signature);
done();
});
});
describe('#verifySignature', function() {
it('should verify the signature', function(done) {
bitauth.verifySignature(contract, keys.pub, signature, done);
});
});
describe('#encrypt', function() {
it('should encrypt the secret message', function(done) {
enc = bitauth.encrypt(password, secret);
should.exist(enc);
done();
});
});
describe('#decrypt', function() {
it('should decrypt the secret message', function(done) {
var dec = bitauth.decrypt(password, enc);
should.exist(dec);
done();
});
});
});