| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  | # letiny-core
 | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  | A framework for building letsencrypt clients, forked from `letiny`. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 03:33:33 +00:00
										 |  |  | Supports all of: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  |   * node with `ursa` (works fast) | 
					
						
							| 
									
										
										
										
											2015-12-16 00:58:36 +00:00
										 |  |  |   * node with `forge` (works on windows) | 
					
						
							| 
									
										
										
										
											2015-12-16 03:33:33 +00:00
										 |  |  |   * browser WebCrypto (not implemented, but... Let's Encrypt over WebRTC anyone?) | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  |   * any javascript implementation | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 01:33:05 +00:00
										 |  |  | ### These aren't the droids you're looking for
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This is a library / framework for building letsencrypt clients. | 
					
						
							|  |  |  | You probably want one of these pre-built clients instead: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 21:01:40 -08:00
										 |  |  |   * [`letsencrypt`](https://github.com/Daplie/node-letsencrypt) (compatible with the official client) | 
					
						
							|  |  |  |   * `letiny` (lightweight client cli) | 
					
						
							| 
									
										
										
										
											2015-12-16 17:21:03 -08:00
										 |  |  |   * [`letsencrypt-express`](https://github.com/Daplie/letsencrypt-express) (automatic https for express) | 
					
						
							| 
									
										
										
										
											2015-12-16 01:33:05 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:46:47 -08:00
										 |  |  | ## Install & Usage:
 | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  | ```bash | 
					
						
							|  |  |  | npm install --save letiny-core | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:58:36 +00:00
										 |  |  | You will follow these steps to obtain certificates: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * discover ACME registration urls with `getAcmeUrls` | 
					
						
							|  |  |  | * register a user account with `registerNewAccount` | 
					
						
							|  |  |  | * implement a method to agree to the terms of service as `agreeToTos` | 
					
						
							|  |  |  | * get certificates with `getCertificate` | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | * implement a method to store the challenge token as `setChallenge` | 
					
						
							|  |  |  | * implement a method to get the challenge token as `getChallenge` | 
					
						
							|  |  |  | * implement a method to remove the challenge token as `removeChallenge` | 
					
						
							| 
									
										
										
										
											2015-12-16 00:58:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:06:19 -08:00
										 |  |  | ### Demo
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | You can see this working for yourself, but you'll need to be on an internet connected computer with a domain. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Get a temporary domain for testing | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | npm install -g ddns-cli | 
					
						
							|  |  |  | ddns --random --email user@example.com --agree | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Note: use **YOUR EMAIL** and accept the terms of service (run `ddns --help` to see them). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <!-- TODO tutorial on ddns --> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Install letiny-core and its dependencies. **Note**: it's okay if you're on windows | 
					
						
							|  |  |  | and `ursa` fails to compile. It'll still work. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | git clone https://github.com/Daplie/letiny-core.git ~/letiny-core | 
					
						
							|  |  |  | pushd ~/letiny-core | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | npm install | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Run the demo: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:15:29 -08:00
										 |  |  | ```bash | 
					
						
							| 
									
										
										
										
											2015-12-16 00:06:19 -08:00
										 |  |  | node examples/letsencrypt.js user@example.com example.com | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Note: use **YOUR TEMPORARY DOMAIN** and **YOUR EMAIL**. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:46:47 -08:00
										 |  |  | ## API
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The Goodies | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```javascript | 
					
						
							|  |  |  | // Accounts | 
					
						
							| 
									
										
										
										
											2015-12-15 22:03:02 -08:00
										 |  |  | LeCore.registerNewAccount(options, cb)        // returns "regr" registration data | 
					
						
							| 
									
										
										
										
											2015-12-15 20:46:47 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     { newRegUrl: '<url>'                      //    no defaults, specify acmeUrls.newAuthz | 
					
						
							|  |  |  |     , email: '<email>'                        //    valid email (server checks MX records) | 
					
						
							|  |  |  |     , accountPrivateKeyPem: '<ASCII PEM>'     //    callback to allow user interaction for tosUrl | 
					
						
							|  |  |  |     , agreeToTerms: fn (tosUrl, cb) {}        //    must specify agree=tosUrl to continue (or falsey to end) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Registration | 
					
						
							|  |  |  | LeCore.getCertificate(options, cb)            // returns (err, pems={ key, cert, ca }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     { newAuthzUrl: '<url>'                    //    specify acmeUrls.newAuthz | 
					
						
							|  |  |  |     , newCertUrl: '<url>'                     //    specify acmeUrls.newCert | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     , domainPrivateKeyPem: '<ASCII PEM>' | 
					
						
							|  |  |  |     , accountPrivateKeyPem: '<ASCII PEM>' | 
					
						
							|  |  |  |     , domains: ['example.com'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     , setChallenge: fn (hostname, key, val, cb) | 
					
						
							|  |  |  |     , removeChallenge: fn (hostname, key, cb) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-12-15 22:03:02 -08:00
										 |  |  |      | 
					
						
							|  |  |  | // Discovery URLs | 
					
						
							|  |  |  | LeCore.getAcmeUrls(acmeDiscoveryUrl, cb)      // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert}) | 
					
						
							| 
									
										
										
										
											2015-12-15 20:46:47 -08:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Helpers & Stuff | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```javascript | 
					
						
							|  |  |  | // Constants | 
					
						
							|  |  |  | LeCore.productionServerUrl                // https://acme-v01.api.letsencrypt.org/directory | 
					
						
							|  |  |  | LeCore.stagingServerUrl                   // https://acme-staging.api.letsencrypt.org/directory | 
					
						
							|  |  |  | LeCore.acmeChallengePrefix                // /.well-known/acme-challenge/ | 
					
						
							|  |  |  | LeCore.configDir                          // /etc/letsencrypt/ | 
					
						
							|  |  |  | LeCore.logsDir                            // /var/log/letsencrypt/ | 
					
						
							|  |  |  | LeCore.workDir                            // /var/lib/letsencrypt/ | 
					
						
							|  |  |  | LeCore.knownEndpoints                     // new-authz, new-cert, new-reg, revoke-cert | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTTP Client Helpers | 
					
						
							|  |  |  | LeCore.Acme                               // Signs requests with JWK | 
					
						
							|  |  |  |     acme = new Acme(lePrivateKey)           // privateKey format is abstract | 
					
						
							|  |  |  |     acme.post(url, body, cb)                // POST with signature | 
					
						
							|  |  |  |     acme.parseLinks(link)                   // (internal) parses 'link' header | 
					
						
							|  |  |  |     acme.getNonce(url, cb)                  // (internal) HEAD request to get 'replay-nonce' strings | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Note: some of these are not async, | 
					
						
							|  |  |  | // but they will be soon. Don't rely | 
					
						
							|  |  |  | // on their API yet. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Crypto Helpers | 
					
						
							|  |  |  | LeCore.leCrypto | 
					
						
							|  |  |  |     generateRsaKeypair(bitLen, exponent, cb);     // returns { privateKeyPem, privateKeyJwk, publicKeyPem, publicKeyMd5 } | 
					
						
							|  |  |  |     thumbprint(lePubKey)                          // generates public key thumbprint | 
					
						
							|  |  |  |     generateSignature(lePrivKey, bodyBuf, nonce)  // generates a signature | 
					
						
							|  |  |  |     privateJwkToPems(jwk)                         // { n: '...', e: '...', iq: '...', ... } to PEMs | 
					
						
							|  |  |  |     privatePemToJwk                               // PEM to JWK (see line above) | 
					
						
							|  |  |  |     importPemPrivateKey(privateKeyPem)            // (internal) returns abstract private key | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For testing and development, you can also inject the dependencies you want to use: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```javascript | 
					
						
							|  |  |  | LeCore = LeCore.create({ | 
					
						
							|  |  |  |   request: require('request') | 
					
						
							|  |  |  | , leCrypto: rquire('./lib/letsencrypt-forge') | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // now uses node `request` (could also use jQuery or Angular in the browser) | 
					
						
							|  |  |  | LeCore.getAcmeUrls(discoveryUrl, function (err, urls) { | 
					
						
							|  |  |  |   console.log(urls); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Example
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Below you'll find a stripped-down example. You can see the full example in the example folder. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * [example/](https://github.com/Daplie/letiny-core/blob/master/example/) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:18:01 -08:00
										 |  |  | #### Register Account & Domain
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:46:47 -08:00
										 |  |  | This is how you **register an ACME account** and **get an HTTPS certificate** | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  | ```javascript | 
					
						
							|  |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  | var LeCore = require('letiny-core'); | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | var email = 'user@example.com';                   // CHANGE TO YOUR EMAIL | 
					
						
							|  |  |  | var domains = 'example.com';                      // CHANGE TO YOUR DOMAIN | 
					
						
							|  |  |  | var acmeDiscoveryUrl = LeCore.stagingServerUrl;   // CHANGE to production, when ready | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var accountPrivateKeyPem = null; | 
					
						
							|  |  |  | var domainPrivateKeyPem = null; | 
					
						
							|  |  |  | var acmeUrls = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | LeCore.leCrypto.generateRsaKeypair(2048, 65537, function (err, pems) { | 
					
						
							|  |  |  |     // ... | 
					
						
							|  |  |  |     LeCore.getAcmeUrls(acmeDiscoveryUrl, function (err, urls) { | 
					
						
							|  |  |  |         // ... | 
					
						
							|  |  |  |         runDemo(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function runDemo() { | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  |     LeCore.registerNewAccount( | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |         { newRegUrl: acmeUrls.newReg | 
					
						
							|  |  |  |         , email: email | 
					
						
							|  |  |  |         , accountPrivateKeyPem: accountPrivateKeyPem | 
					
						
							|  |  |  |         , agreeToTerms: function (tosUrl, done) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               // agree to the exact version of these terms | 
					
						
							|  |  |  |               done(null, tosUrl); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |       , function (err, regr) { | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |             LeCore.getCertificate( | 
					
						
							|  |  |  |                 { newAuthzUrl: acmeUrls.newAuthz | 
					
						
							|  |  |  |                 , newCertUrl: acmeUrls.newCert | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |                 , domainPrivateKeyPem: domainPrivateKeyPem | 
					
						
							|  |  |  |                 , accountPrivateKeyPem: accountPrivateKeyPem | 
					
						
							|  |  |  |                 , domains: domains | 
					
						
							| 
									
										
										
										
											2015-12-16 03:23:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |                 , setChallenge: challengeStore.set | 
					
						
							|  |  |  |                 , removeChallenge: challengeStore.remove | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               , function (err, certs) { | 
					
						
							| 
									
										
										
										
											2015-12-16 03:23:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |                   // Note: you should save certs to disk (or db) | 
					
						
							|  |  |  |                   certStore.set(domains[0], certs, function () { | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |                       // ... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                   }); | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |                 } | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-16 00:51:44 +00:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:17:33 -08:00
										 |  |  | **But wait**, there's more! | 
					
						
							|  |  |  | See [example/letsencrypt.js](https://github.com/Daplie/letiny-core/blob/master/example/letsencrypt.js) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:18:01 -08:00
										 |  |  | #### Run a Server on 80, 443, and 5001 (https/tls)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | That will fail unless you have a webserver running on 80 and 443 (or 5001) | 
					
						
							|  |  |  | to respond to `/.well-known/acme-challenge/xxxxxxxx` with the proper token | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```javascript | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | var https = require('https'); | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | var http = require('http'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | var LeCore = deps.LeCore; | 
					
						
							|  |  |  | var httpsOptions = deps.httpsOptions; | 
					
						
							|  |  |  | var challengeStore = deps.challengeStore; | 
					
						
							|  |  |  | var certStore = deps.certStore; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Challenge Handler | 
					
						
							|  |  |  | // | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | function acmeResponder(req, res) { | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |   if (0 !== req.url.indexOf(LeCore.acmeChallengePrefix)) { | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  |     res.end('Hello World!'); | 
					
						
							|  |  |  |     return; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  |   var key = req.url.slice(LeCore.acmeChallengePrefix.length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   challengeStore.get(req.hostname, key, function (err, val) { | 
					
						
							|  |  |  |     res.end(val || 'Error'); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Server | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | https.createServer(httpsOptions, acmeResponder).listen(5001, function () { | 
					
						
							|  |  |  |   console.log('Listening https on', this.address()); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | http.createServer(acmeResponder).listen(80, function () { | 
					
						
							|  |  |  |   console.log('Listening http on', this.address()); | 
					
						
							|  |  |  | }); | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:17:33 -08:00
										 |  |  | **But wait**, there's more! | 
					
						
							|  |  |  | See [example/serve.js](https://github.com/Daplie/letiny-core/blob/master/example/serve.js) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | #### Put some storage in place
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | Finally, you need an implementation of `challengeStore`: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```javascript | 
					
						
							|  |  |  | var challengeCache = {}; | 
					
						
							|  |  |  | var challengeStore = { | 
					
						
							|  |  |  |   set: function (hostname, key, value, cb) { | 
					
						
							|  |  |  |     challengeCache[key] = value; | 
					
						
							|  |  |  |     cb(null); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | , get: function (hostname, key, cb) { | 
					
						
							|  |  |  |     cb(null, challengeCache[key]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | , remove: function (hostname, key, cb) { | 
					
						
							|  |  |  |     delete challengeCache[key]; | 
					
						
							|  |  |  |     cb(null); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2015-12-15 20:32:40 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | var certCache = {}; | 
					
						
							|  |  |  | var certStore = { | 
					
						
							|  |  |  |   set: function (hostname, certs, cb) { | 
					
						
							|  |  |  |     certCache[hostname] = certs; | 
					
						
							|  |  |  |     cb(null); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | , get: function (hostname, cb) { | 
					
						
							|  |  |  |     cb(null, certCache[hostname]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | , remove: function (hostname, cb) { | 
					
						
							|  |  |  |     delete certCache[hostname]; | 
					
						
							|  |  |  |     cb(null); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 00:17:33 -08:00
										 |  |  | **But wait**, there's more! | 
					
						
							|  |  |  | See | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | * [example/challenge-store.js](https://github.com/Daplie/letiny-core/blob/master/challenge-store.js) | 
					
						
							|  |  |  | * [example/cert-store.js](https://github.com/Daplie/letiny-core/blob/master/cert-store.js) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-16 01:18:40 +00:00
										 |  |  | ## Authors
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   * ISRG | 
					
						
							|  |  |  |   * Anatol Sommer  (https://github.com/anatolsommer) | 
					
						
							|  |  |  |   * AJ ONeal <aj@daplie.com> (https://daplie.com) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | ## Licence
 | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-13 17:50:04 +01:00
										 |  |  | MPL 2.0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-15 22:07:02 +00:00
										 |  |  | All of the code is available under the MPL-2.0. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Some of the files are original work not modified from `letiny` | 
					
						
							|  |  |  | and are made available under MIT as well (check file headers). |