use ACME to create account
This commit is contained in:
parent
b81ff7550b
commit
8b2e6e69d0
|
@ -10,6 +10,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
var pkg = require('../package.json');
|
var pkg = require('../package.json');
|
||||||
|
var Keypairs = require('keypairs');
|
||||||
|
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
//var url = require('url');
|
//var url = require('url');
|
||||||
|
@ -422,7 +423,7 @@ controllers.newAccount = function (req, res) {
|
||||||
// TODO clean up error messages to be similar to ACME
|
// TODO clean up error messages to be similar to ACME
|
||||||
|
|
||||||
// check if there's a public key
|
// check if there's a public key
|
||||||
if (!req.jws || !req.jws.header.kid || !req.jws.header.jwk) {
|
if (!req.jws || !req.jws.header.jwk) {
|
||||||
res.statusCode = 422;
|
res.statusCode = 422;
|
||||||
res.send({ error: { message: "jws body was not present or could not be validated" } });
|
res.send({ error: { message: "jws body was not present or could not be validated" } });
|
||||||
return;
|
return;
|
||||||
|
@ -448,7 +449,7 @@ controllers.newAccount = function (req, res) {
|
||||||
return verifyJws(req.jws.header.jwk, req.jws).then(function (verified) {
|
return verifyJws(req.jws.header.jwk, req.jws).then(function (verified) {
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
res.statusCode = 422;
|
res.statusCode = 422;
|
||||||
res.send({ error: { message: "jws body was not present or could not be validated" } });
|
res.send({ error: { message: "jws body failed verification" } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,7 +484,7 @@ controllers.newAccount = function (req, res) {
|
||||||
}
|
}
|
||||||
res.statusCode = 201;
|
res.statusCode = 201;
|
||||||
account = {};
|
account = {};
|
||||||
account._id = crypto.randomBytes(16).toString('base64');
|
account._id = toUrlSafe(crypto.randomBytes(16).toString('base64'));
|
||||||
// TODO be better about this
|
// TODO be better about this
|
||||||
account.location = myBaseUrl + '/acme/accounts/' + account._id;
|
account.location = myBaseUrl + '/acme/accounts/' + account._id;
|
||||||
account.thumb = thumb;
|
account.thumb = thumb;
|
||||||
|
@ -620,12 +621,38 @@ function jwsEggspress(req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ua = req.headers['user-agent'];
|
var ua = req.headers['user-agent'];
|
||||||
|
var trusted = false;
|
||||||
var vjwk;
|
var vjwk;
|
||||||
var pubs;
|
var pubs;
|
||||||
|
var kid = req.jws.header.kid;
|
||||||
|
var p = Promise.resolve();
|
||||||
|
if (!kid && !req.jws.header.jwk) {
|
||||||
|
res.send({ error: { message: "jws protected header must include either 'kid' or 'jwk'" } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (req.jws.header.jwk) {
|
||||||
|
if (kid) {
|
||||||
|
// TODO kid and jwk are mutually exclusive
|
||||||
|
//res.send({ error: { message: "jws protected header must not include both 'kid' and 'jwk'" } });
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
kid = req.jws.header.jwk.kid;
|
||||||
|
p = Keypairs.thumbprint({ jwk: req.jws.header.jwk }).then(function (thumb) {
|
||||||
|
if (kid && kid !== thumb) {
|
||||||
|
res.send({ error: { message: "jwk included 'kid' for key id, but it did not match the key's thumbprint" } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
kid = thumb;
|
||||||
|
req.jws.header.jwk.kid = thumb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Check if this is a key we already trust
|
// Check if this is a key we already trust
|
||||||
DB.pubs.some(function (jwk) {
|
DB.pubs.some(function (jwk) {
|
||||||
if (jwk.kid === req.jws.header.kid) {
|
if (jwk.kid === kid) {
|
||||||
|
trusted = true;
|
||||||
vjwk = jwk;
|
vjwk = jwk;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -645,33 +672,32 @@ function jwsEggspress(req, res, next) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.then(function () {
|
||||||
// Check if there aren't any keys that we trust
|
// Check if there aren't any keys that we trust
|
||||||
// and this has signed itself, then make it a key we trust
|
// and this has signed itself, then make it a key we trust
|
||||||
// (TODO: move this all to the new account function)
|
// (TODO: move this all to the new account function)
|
||||||
if ((0 === pubs.length && req.jws.header.jwk)) {
|
if (0 === pubs.length) { trusted = true; }
|
||||||
vjwk = req.jws.header.jwk;
|
if (!vjwk) { vjwk = req.jws.header.jwk; }
|
||||||
if (!vjwk.kid) { throw Error("Impossible: no key id"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't verify if it can't be verified
|
// Don't verify if it can't be verified
|
||||||
if (!vjwk) {
|
if (!vjwk) { return null; }
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the verification
|
// Run the verification
|
||||||
|
return p.then(function () {
|
||||||
return verifyJws(vjwk, req.jws).then(function (verified) {
|
return verifyJws(vjwk, req.jws).then(function (verified) {
|
||||||
if (true !== verified) { return; }
|
if (true !== verified) { return null; }
|
||||||
|
|
||||||
// Mark as verified
|
// Mark as verified
|
||||||
req.jws.verified = verified;
|
req.jws.verified = verified;
|
||||||
|
req.jws.trusted = trusted;
|
||||||
vjwk.useragent = ua;
|
vjwk.useragent = ua;
|
||||||
|
|
||||||
// (double check) DO NOT save if there are existing pubs
|
// (double check) DO NOT save if there are existing pubs
|
||||||
if (0 !== pubs.length) { return; }
|
if (0 !== pubs.length) { return null; }
|
||||||
|
|
||||||
DB.pubs.push(vjwk);
|
DB.pubs.push(vjwk);
|
||||||
return keystore.set(vjwk.kid + PUBEXT, vjwk);
|
return keystore.set(vjwk.kid + PUBEXT, vjwk);
|
||||||
|
});
|
||||||
|
});
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
@ -1001,9 +1027,12 @@ function handleApi() {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
app.get('/acme/directory', function (req, res) {
|
app.get('/acme/directory', function (req, res) {
|
||||||
|
var myBaseUrl = (req.connection.encrypted ? 'https' : 'http') + '://' + req.headers.host;
|
||||||
res.send({
|
res.send({
|
||||||
'new-nonce': '/acme/new-nonce'
|
'newNonce': '/acme/new-nonce'
|
||||||
, 'new-account': '/acme/new-acct'
|
, 'newAccount': '/acme/new-acct'
|
||||||
|
// TODO link to the terms that the user selects
|
||||||
|
, 'meta': { 'termsOfService': myBaseUrl + '/acme/terms.html' }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
app.head('/acme/new-nonce', controllers.newNonce);
|
app.head('/acme/new-nonce', controllers.newNonce);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
var Vue = window.Vue;
|
var Vue = window.Vue;
|
||||||
var Telebit = window.TELEBIT;
|
var Telebit = window.TELEBIT;
|
||||||
var Keypairs = window.Keypairs;
|
var Keypairs = window.Keypairs;
|
||||||
|
var ACME = window.ACME;
|
||||||
var api = {};
|
var api = {};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -516,25 +517,59 @@ function run(key) {
|
||||||
|
|
||||||
// TODO protect key with passphrase (or QR code?)
|
// TODO protect key with passphrase (or QR code?)
|
||||||
function getKey() {
|
function getKey() {
|
||||||
var key;
|
var jwk;
|
||||||
try {
|
try {
|
||||||
key = JSON.parse(localStorage.getItem('key'));
|
jwk = JSON.parse(localStorage.getItem('key'));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
if (key && key.kid && key.d) {
|
if (jwk && jwk.kid && jwk.d) {
|
||||||
return Promise.resolve(key);
|
return Promise.resolve(jwk);
|
||||||
}
|
}
|
||||||
return Keypairs.generate().then(function (pair) {
|
return Keypairs.generate().then(function (pair) {
|
||||||
key = pair.private;
|
jwk = pair.private;
|
||||||
localStorage.setItem('key', JSON.stringify(key));
|
localStorage.setItem('key', JSON.stringify(jwk));
|
||||||
return key;
|
return jwk;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmail() {
|
||||||
|
return Promise.resolve().then(function () {
|
||||||
|
var email = localStorage.getItem('email');
|
||||||
|
if (email) { return email; }
|
||||||
|
while (!email) {
|
||||||
|
email = window.prompt("Email address (device owner)?");
|
||||||
|
}
|
||||||
|
return email;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function requestAccount() {
|
||||||
|
return getKey().then(function (jwk) {
|
||||||
|
return getEmail().then(function(email) {
|
||||||
|
// creates new or returns existing
|
||||||
|
var acme = ACME.create({});
|
||||||
|
var url = window.location.protocol + '//' + window.location.host + '/acme/directory';
|
||||||
|
return acme.init(url).then(function () {
|
||||||
|
return acme.accounts.create({
|
||||||
|
agreeToTerms: function (tos) { return tos; }
|
||||||
|
, accountKeypair: { privateKeyJwk: jwk }
|
||||||
|
, email: email
|
||||||
|
}).then(function (account) {
|
||||||
|
console.log('account:');
|
||||||
|
console.log(account);
|
||||||
|
if (account.id) {
|
||||||
|
localStorage.setItem('email', email);
|
||||||
|
}
|
||||||
|
return jwk;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.api = api;
|
window.api = api;
|
||||||
getKey().then(function (key) {
|
requestAccount().then(function (jwk) {
|
||||||
run(key);
|
run(jwk);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
document.body.hidden = false;
|
document.body.hidden = false;
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
|
@ -2567,7 +2567,7 @@ ACME.create = function create(me) {
|
||||||
// me.debug = true;
|
// me.debug = true;
|
||||||
me.challengePrefixes = ACME.challengePrefixes;
|
me.challengePrefixes = ACME.challengePrefixes;
|
||||||
me.Keypairs = me.Keypairs || exports.Keypairs || require('keypairs').Keypairs;
|
me.Keypairs = me.Keypairs || exports.Keypairs || require('keypairs').Keypairs;
|
||||||
me.CSR = me.CSR || exports.cSR || require('CSR').CSR;
|
me.CSR = me.CSR || exports.CSR || require('CSR').CSR;
|
||||||
me._nonces = [];
|
me._nonces = [];
|
||||||
me._canUse = {};
|
me._canUse = {};
|
||||||
if (!me._baseUrl) {
|
if (!me._baseUrl) {
|
||||||
|
|
Loading…
Reference in New Issue