MAJOR: Updates for Authenticated Web UI and CLI #30
| @ -10,6 +10,7 @@ try { | ||||
| } | ||||
| 
 | ||||
| var pkg = require('../package.json'); | ||||
| var Keypairs = require('keypairs'); | ||||
| 
 | ||||
| var crypto = require('crypto'); | ||||
| //var url = require('url');
 | ||||
| @ -422,7 +423,7 @@ controllers.newAccount = function (req, res) { | ||||
|     // TODO clean up error messages to be similar to ACME
 | ||||
| 
 | ||||
|     // 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.send({ error: { message: "jws body was not present or could not be validated" } }); | ||||
|       return; | ||||
| @ -448,7 +449,7 @@ controllers.newAccount = function (req, res) { | ||||
|     return verifyJws(req.jws.header.jwk, req.jws).then(function (verified) { | ||||
|       if (!verified) { | ||||
|         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; | ||||
|       } | ||||
| 
 | ||||
| @ -483,7 +484,7 @@ controllers.newAccount = function (req, res) { | ||||
|           } | ||||
|           res.statusCode = 201; | ||||
|           account = {}; | ||||
|           account._id = crypto.randomBytes(16).toString('base64'); | ||||
|           account._id = toUrlSafe(crypto.randomBytes(16).toString('base64')); | ||||
|           // TODO be better about this
 | ||||
|           account.location = myBaseUrl + '/acme/accounts/' + account._id; | ||||
|           account.thumb = thumb; | ||||
| @ -620,12 +621,38 @@ function jwsEggspress(req, res, next) { | ||||
|   } | ||||
| 
 | ||||
|   var ua = req.headers['user-agent']; | ||||
|   var trusted = false; | ||||
|   var vjwk; | ||||
|   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
 | ||||
|   DB.pubs.some(function (jwk) { | ||||
|     if (jwk.kid === req.jws.header.kid) { | ||||
|     if (jwk.kid === kid) { | ||||
|       trusted = true; | ||||
|       vjwk = jwk; | ||||
|       return true; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
| @ -645,33 +672,32 @@ function jwsEggspress(req, res, next) { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // Check if there aren't any keys that we trust
 | ||||
|   // and this has signed itself, then make it a key we trust
 | ||||
|   // (TODO: move this all to the new account function)
 | ||||
|   if ((0 === pubs.length && req.jws.header.jwk)) { | ||||
|     vjwk = req.jws.header.jwk; | ||||
|     if (!vjwk.kid) { throw Error("Impossible: no key id"); } | ||||
|   } | ||||
|   p.then(function () { | ||||
|     // Check if there aren't any keys that we trust
 | ||||
|     // and this has signed itself, then make it a key we trust
 | ||||
|     // (TODO: move this all to the new account function)
 | ||||
|     if (0 === pubs.length) { trusted = true; } | ||||
|     if (!vjwk) { vjwk = req.jws.header.jwk; } | ||||
|     // Don't verify if it can't be verified
 | ||||
|     if (!vjwk) { return null; } | ||||
| 
 | ||||
|   // Don't verify if it can't be verified
 | ||||
|   if (!vjwk) { | ||||
|     next(); | ||||
|     return; | ||||
|   } | ||||
|     // Run the  verification
 | ||||
|     return p.then(function () { | ||||
|       return verifyJws(vjwk, req.jws).then(function (verified) { | ||||
|         if (true !== verified) { return null; } | ||||
| 
 | ||||
|   // Run the  verification
 | ||||
|   return verifyJws(vjwk, req.jws).then(function (verified) { | ||||
|     if (true !== verified) { return; } | ||||
|         // Mark as verified
 | ||||
|         req.jws.verified = verified; | ||||
|         req.jws.trusted = trusted; | ||||
|         vjwk.useragent = ua; | ||||
| 
 | ||||
|     // Mark as verified
 | ||||
|     req.jws.verified = verified; | ||||
|     vjwk.useragent = ua; | ||||
|         // (double check) DO NOT save if there are existing pubs
 | ||||
|         if (0 !== pubs.length) { return null; } | ||||
| 
 | ||||
|     // (double check) DO NOT save if there are existing pubs
 | ||||
|     if (0 !== pubs.length) { return; } | ||||
| 
 | ||||
|     DB.pubs.push(vjwk); | ||||
|     return keystore.set(vjwk.kid + PUBEXT, vjwk); | ||||
|         DB.pubs.push(vjwk); | ||||
|         return keystore.set(vjwk.kid + PUBEXT, vjwk); | ||||
|       }); | ||||
|     }); | ||||
|   }).then(function () { | ||||
|     next(); | ||||
|   }); | ||||
| @ -1001,9 +1027,12 @@ function handleApi() { | ||||
|     next(); | ||||
|   }); | ||||
|   app.get('/acme/directory', function (req, res) { | ||||
|     var myBaseUrl = (req.connection.encrypted ? 'https' : 'http') + '://' + req.headers.host; | ||||
|     res.send({ | ||||
|       'new-nonce': '/acme/new-nonce' | ||||
|     , 'new-account': '/acme/new-acct' | ||||
|       'newNonce': '/acme/new-nonce' | ||||
|     , '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); | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| var Vue = window.Vue; | ||||
| var Telebit = window.TELEBIT; | ||||
| var Keypairs = window.Keypairs; | ||||
| var ACME = window.ACME; | ||||
| var api = {}; | ||||
| 
 | ||||
| /* | ||||
| @ -516,25 +517,59 @@ function run(key) { | ||||
| 
 | ||||
| // TODO protect key with passphrase (or QR code?)
 | ||||
| function getKey() { | ||||
|   var key; | ||||
|   var jwk; | ||||
|   try { | ||||
|     key = JSON.parse(localStorage.getItem('key')); | ||||
|     jwk = JSON.parse(localStorage.getItem('key')); | ||||
|   } catch(e) { | ||||
|     // ignore
 | ||||
|   } | ||||
|   if (key && key.kid && key.d) { | ||||
|     return Promise.resolve(key); | ||||
|   if (jwk && jwk.kid && jwk.d) { | ||||
|     return Promise.resolve(jwk); | ||||
|   } | ||||
|   return Keypairs.generate().then(function (pair) { | ||||
|     key = pair.private; | ||||
|     localStorage.setItem('key', JSON.stringify(key)); | ||||
|     return key; | ||||
|     jwk = pair.private; | ||||
|     localStorage.setItem('key', JSON.stringify(jwk)); | ||||
|     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; | ||||
| getKey().then(function (key) { | ||||
|   run(key); | ||||
| requestAccount().then(function (jwk) { | ||||
|   run(jwk); | ||||
|   setTimeout(function () { | ||||
|     document.body.hidden = false; | ||||
|   }, 50); | ||||
|  | ||||
| @ -2567,7 +2567,7 @@ ACME.create = function create(me) { | ||||
|   // me.debug = true;
 | ||||
|   me.challengePrefixes = ACME.challengePrefixes; | ||||
|   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._canUse = {}; | ||||
|   if (!me._baseUrl) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user