MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user