mirror of
				https://github.com/therootcompany/greenlock.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "0237336e8f1e4c60759d05653269ebb0102e8338" and "aece586c90e3f9c331aeb115d02b44413fe9a1d0" have entirely different histories.
		
	
	
		
			0237336e8f
			...
			aece586c90
		
	
		
| @ -1,8 +0,0 @@ | ||||
| { | ||||
|   "bracketSpacing": true, | ||||
|   "printWidth": 80, | ||||
|   "singleQuote": true, | ||||
|   "tabWidth": 4, | ||||
|   "trailingComma": "none", | ||||
|   "useTabs": true | ||||
| } | ||||
							
								
								
									
										396
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										396
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,375 +1,41 @@ | ||||
| Copyright 2015-2019 AJ ONeal | ||||
| Copyright 2017 AJ ONeal | ||||
| 
 | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
| This is open source software; you can redistribute it and/or modify it under the | ||||
| terms of either: | ||||
| 
 | ||||
| 1. Definitions | ||||
| -------------- | ||||
|    a) the "MIT License" | ||||
|    b) the "Apache-2.0 License" | ||||
| 
 | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
| MIT License | ||||
| 
 | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
|    Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|    of this software and associated documentation files (the "Software"), to deal | ||||
|    in the Software without restriction, including without limitation the rights | ||||
|    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|    copies of the Software, and to permit persons to whom the Software is | ||||
|    furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
|    The above copyright notice and this permission notice shall be included in all | ||||
|    copies or substantial portions of the Software. | ||||
| 
 | ||||
| 1.4. "Covered Software" | ||||
|     means Source Code Form to which the initial Contributor has attached | ||||
|     the notice in Exhibit A, the Executable Form of such Source Code | ||||
|     Form, and Modifications of such Source Code Form, in each case | ||||
|     including portions thereof. | ||||
|    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
|    SOFTWARE. | ||||
| 
 | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
| Apache-2.0 License Summary | ||||
| 
 | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|     (b) that the Covered Software was made available under the terms of | ||||
|         version 1.1 or earlier of the License, but not also under the | ||||
|         terms of a Secondary License. | ||||
|      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
| 
 | ||||
| 1.7. "Larger Work" | ||||
|     means a work that combines Covered Software with other material, in | ||||
|     a separate file or files, that is not Covered Software. | ||||
| 
 | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
| 
 | ||||
| 1.9. "Licensable" | ||||
|     means having the right to grant, to the maximum extent possible, | ||||
|     whether at the time of the initial grant or subsequently, any and | ||||
|     all of the rights conveyed by this License. | ||||
| 
 | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
| 
 | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
| 
 | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
| 
 | ||||
| 1.11. "Patent Claims" of a Contributor | ||||
|     means any patent claim(s), including without limitation, method, | ||||
|     process, and apparatus claims, in any patent Licensable by such | ||||
|     Contributor that would be infringed, but for the grant of the | ||||
|     License, by the making, using, selling, offering for sale, having | ||||
|     made, import, or transfer of either its Contributions or its | ||||
|     Contributor Version. | ||||
| 
 | ||||
| 1.12. "Secondary License" | ||||
|     means either the GNU General Public License, Version 2.0, the GNU | ||||
|     Lesser General Public License, Version 2.1, the GNU Affero General | ||||
|     Public License, Version 3.0, or any later versions of those | ||||
|     licenses. | ||||
| 
 | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
| 
 | ||||
| 1.14. "You" (or "Your") | ||||
|     means an individual or a legal entity exercising rights under this | ||||
|     License. For legal entities, "You" includes any entity that | ||||
|     controls, is controlled by, or is under common control with You. For | ||||
|     purposes of this definition, "control" means (a) the power, direct | ||||
|     or indirect, to cause the direction or management of such entity, | ||||
|     whether by contract or otherwise, or (b) ownership of more than | ||||
|     fifty percent (50%) of the outstanding shares or beneficial | ||||
|     ownership of such entity. | ||||
| 
 | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
| 
 | ||||
| 2.1. Grants | ||||
| 
 | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
| 
 | ||||
| (a) under intellectual property rights (other than patent or trademark) | ||||
|     Licensable by such Contributor to use, reproduce, make available, | ||||
|     modify, display, perform, distribute, and otherwise exploit its | ||||
|     Contributions, either on an unmodified basis, with Modifications, or | ||||
|     as part of a Larger Work; and | ||||
| 
 | ||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||
|     for sale, have made, import, and otherwise transfer either its | ||||
|     Contributions or its Contributor Version. | ||||
| 
 | ||||
| 2.2. Effective Date | ||||
| 
 | ||||
| The licenses granted in Section 2.1 with respect to any Contribution | ||||
| become effective for each Contribution on the date the Contributor first | ||||
| distributes such Contribution. | ||||
| 
 | ||||
| 2.3. Limitations on Grant Scope | ||||
| 
 | ||||
| The licenses granted in this Section 2 are the only rights granted under | ||||
| this License. No additional rights or licenses will be implied from the | ||||
| distribution or licensing of Covered Software under this License. | ||||
| Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||
| Contributor: | ||||
| 
 | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
| 
 | ||||
| (b) for infringements caused by: (i) Your and any other third party's | ||||
|     modifications of Covered Software, or (ii) the combination of its | ||||
|     Contributions with other software (except as part of its Contributor | ||||
|     Version); or | ||||
| 
 | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
| 
 | ||||
| This License does not grant any rights in the trademarks, service marks, | ||||
| or logos of any Contributor (except as may be necessary to comply with | ||||
| the notice requirements in Section 3.4). | ||||
| 
 | ||||
| 2.4. Subsequent Licenses | ||||
| 
 | ||||
| No Contributor makes additional grants as a result of Your choice to | ||||
| distribute the Covered Software under a subsequent version of this | ||||
| License (see Section 10.2) or under the terms of a Secondary License (if | ||||
| permitted under the terms of Section 3.3). | ||||
| 
 | ||||
| 2.5. Representation | ||||
| 
 | ||||
| Each Contributor represents that the Contributor believes its | ||||
| Contributions are its original creation(s) or it has sufficient rights | ||||
| to grant the rights to its Contributions conveyed by this License. | ||||
| 
 | ||||
| 2.6. Fair Use | ||||
| 
 | ||||
| This License is not intended to limit any rights You have under | ||||
| applicable copyright doctrines of fair use, fair dealing, or other | ||||
| equivalents. | ||||
| 
 | ||||
| 2.7. Conditions | ||||
| 
 | ||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||
| in Section 2.1. | ||||
| 
 | ||||
| 3. Responsibilities | ||||
| ------------------- | ||||
| 
 | ||||
| 3.1. Distribution of Source Form | ||||
| 
 | ||||
| All distribution of Covered Software in Source Code Form, including any | ||||
| Modifications that You create or to which You contribute, must be under | ||||
| the terms of this License. You must inform recipients that the Source | ||||
| Code Form of the Covered Software is governed by the terms of this | ||||
| License, and how they can obtain a copy of this License. You may not | ||||
| attempt to alter or restrict the recipients' rights in the Source Code | ||||
| Form. | ||||
| 
 | ||||
| 3.2. Distribution of Executable Form | ||||
| 
 | ||||
| If You distribute Covered Software in Executable Form then: | ||||
| 
 | ||||
| (a) such Covered Software must also be made available in Source Code | ||||
|     Form, as described in Section 3.1, and You must inform recipients of | ||||
|     the Executable Form how they can obtain a copy of such Source Code | ||||
|     Form by reasonable means in a timely manner, at a charge no more | ||||
|     than the cost of distribution to the recipient; and | ||||
| 
 | ||||
| (b) You may distribute such Executable Form under the terms of this | ||||
|     License, or sublicense it under different terms, provided that the | ||||
|     license for the Executable Form does not attempt to limit or alter | ||||
|     the recipients' rights in the Source Code Form under this License. | ||||
| 
 | ||||
| 3.3. Distribution of a Larger Work | ||||
| 
 | ||||
| You may create and distribute a Larger Work under terms of Your choice, | ||||
| provided that You also comply with the requirements of this License for | ||||
| the Covered Software. If the Larger Work is a combination of Covered | ||||
| Software with a work governed by one or more Secondary Licenses, and the | ||||
| Covered Software is not Incompatible With Secondary Licenses, this | ||||
| License permits You to additionally distribute such Covered Software | ||||
| under the terms of such Secondary License(s), so that the recipient of | ||||
| the Larger Work may, at their option, further distribute the Covered | ||||
| Software under the terms of either this License or such Secondary | ||||
| License(s). | ||||
| 
 | ||||
| 3.4. Notices | ||||
| 
 | ||||
| You may not remove or alter the substance of any license notices | ||||
| (including copyright notices, patent notices, disclaimers of warranty, | ||||
| or limitations of liability) contained within the Source Code Form of | ||||
| the Covered Software, except that You may alter any license notices to | ||||
| the extent required to remedy known factual inaccuracies. | ||||
| 
 | ||||
| 3.5. Application of Additional Terms | ||||
| 
 | ||||
| You may choose to offer, and to charge a fee for, warranty, support, | ||||
| indemnity or liability obligations to one or more recipients of Covered | ||||
| Software. However, You may do so only on Your own behalf, and not on | ||||
| behalf of any Contributor. You must make it absolutely clear that any | ||||
| such warranty, support, indemnity, or liability obligation is offered by | ||||
| You alone, and You hereby agree to indemnify every Contributor for any | ||||
| liability incurred by such Contributor as a result of warranty, support, | ||||
| indemnity or liability terms You offer. You may include additional | ||||
| disclaimers of warranty and limitations of liability specific to any | ||||
| jurisdiction. | ||||
| 
 | ||||
| 4. Inability to Comply Due to Statute or Regulation | ||||
| --------------------------------------------------- | ||||
| 
 | ||||
| If it is impossible for You to comply with any of the terms of this | ||||
| License with respect to some or all of the Covered Software due to | ||||
| statute, judicial order, or regulation then You must: (a) comply with | ||||
| the terms of this License to the maximum extent possible; and (b) | ||||
| describe the limitations and the code they affect. Such description must | ||||
| be placed in a text file included with all distributions of the Covered | ||||
| Software under this License. Except to the extent prohibited by statute | ||||
| or regulation, such description must be sufficiently detailed for a | ||||
| recipient of ordinary skill to be able to understand it. | ||||
| 
 | ||||
| 5. Termination | ||||
| -------------- | ||||
| 
 | ||||
| 5.1. The rights granted under this License will terminate automatically | ||||
| if You fail to comply with any of its terms. However, if You become | ||||
| compliant, then the rights granted under this License from a particular | ||||
| Contributor are reinstated (a) provisionally, unless and until such | ||||
| Contributor explicitly and finally terminates Your grants, and (b) on an | ||||
| ongoing basis, if such Contributor fails to notify You of the | ||||
| non-compliance by some reasonable means prior to 60 days after You have | ||||
| come back into compliance. Moreover, Your grants from a particular | ||||
| Contributor are reinstated on an ongoing basis if such Contributor | ||||
| notifies You of the non-compliance by some reasonable means, this is the | ||||
| first time You have received notice of non-compliance with this License | ||||
| from such Contributor, and You become compliant prior to 30 days after | ||||
| Your receipt of the notice. | ||||
| 
 | ||||
| 5.2. If You initiate litigation against any entity by asserting a patent | ||||
| infringement claim (excluding declaratory judgment actions, | ||||
| counter-claims, and cross-claims) alleging that a Contributor Version | ||||
| directly or indirectly infringes any patent, then the rights granted to | ||||
| You by any and all Contributors for the Covered Software under Section | ||||
| 2.1 of this License shall terminate. | ||||
| 
 | ||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||
| end user license agreements (excluding distributors and resellers) which | ||||
| have been validly granted by You or Your distributors under this License | ||||
| prior to termination shall survive termination. | ||||
| 
 | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  6. Disclaimer of Warranty                                           * | ||||
| *  -------------------------                                           * | ||||
| *                                                                      * | ||||
| *  Covered Software is provided under this License on an "as is"       * | ||||
| *  basis, without warranty of any kind, either expressed, implied, or  * | ||||
| *  statutory, including, without limitation, warranties that the       * | ||||
| *  Covered Software is free of defects, merchantable, fit for a        * | ||||
| *  particular purpose or non-infringing. The entire risk as to the     * | ||||
| *  quality and performance of the Covered Software is with You.        * | ||||
| *  Should any Covered Software prove defective in any respect, You     * | ||||
| *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||
| *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||
| *  essential part of this License. No use of any Covered Software is   * | ||||
| *  authorized under this License except under this disclaimer.         * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
| 
 | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  7. Limitation of Liability                                          * | ||||
| *  --------------------------                                          * | ||||
| *                                                                      * | ||||
| *  Under no circumstances and under no legal theory, whether tort      * | ||||
| *  (including negligence), contract, or otherwise, shall any           * | ||||
| *  Contributor, or anyone who distributes Covered Software as          * | ||||
| *  permitted above, be liable to You for any direct, indirect,         * | ||||
| *  special, incidental, or consequential damages of any character      * | ||||
| *  including, without limitation, damages for lost profits, loss of    * | ||||
| *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||
| *  and all other commercial damages or losses, even if such party      * | ||||
| *  shall have been informed of the possibility of such damages. This   * | ||||
| *  limitation of liability shall not apply to liability for death or   * | ||||
| *  personal injury resulting from such party's negligence to the       * | ||||
| *  extent applicable law prohibits such limitation. Some               * | ||||
| *  jurisdictions do not allow the exclusion or limitation of           * | ||||
| *  incidental or consequential damages, so this exclusion and          * | ||||
| *  limitation may not apply to You.                                    * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
| 
 | ||||
| 8. Litigation | ||||
| ------------- | ||||
| 
 | ||||
| Any litigation relating to this License may be brought only in the | ||||
| courts of a jurisdiction where the defendant maintains its principal | ||||
| place of business and such litigation shall be governed by laws of that | ||||
| jurisdiction, without reference to its conflict-of-law provisions. | ||||
| Nothing in this Section shall prevent a party's ability to bring | ||||
| cross-claims or counter-claims. | ||||
| 
 | ||||
| 9. Miscellaneous | ||||
| ---------------- | ||||
| 
 | ||||
| This License represents the complete agreement concerning the subject | ||||
| matter hereof. If any provision of this License is held to be | ||||
| unenforceable, such provision shall be reformed only to the extent | ||||
| necessary to make it enforceable. Any law or regulation which provides | ||||
| that the language of a contract shall be construed against the drafter | ||||
| shall not be used to construe this License against a Contributor. | ||||
| 
 | ||||
| 10. Versions of the License | ||||
| --------------------------- | ||||
| 
 | ||||
| 10.1. New Versions | ||||
| 
 | ||||
| Mozilla Foundation is the license steward. Except as provided in Section | ||||
| 10.3, no one other than the license steward has the right to modify or | ||||
| publish new versions of this License. Each version will be given a | ||||
| distinguishing version number. | ||||
| 
 | ||||
| 10.2. Effect of New Versions | ||||
| 
 | ||||
| You may distribute the Covered Software under the terms of the version | ||||
| of the License under which You originally received the Covered Software, | ||||
| or under the terms of any subsequent version published by the license | ||||
| steward. | ||||
| 
 | ||||
| 10.3. Modified Versions | ||||
| 
 | ||||
| If you create software not governed by this License, and you want to | ||||
| create a new license for such software, you may create and use a | ||||
| modified version of this License if you rename the license and remove | ||||
| any references to the name of the license steward (except to note that | ||||
| such modified license differs from this License). | ||||
| 
 | ||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||
| Licenses | ||||
| 
 | ||||
| If You choose to distribute Source Code Form that is Incompatible With | ||||
| Secondary Licenses under the terms of this version of the License, the | ||||
| notice described in Exhibit B of this License must be attached. | ||||
| 
 | ||||
| Exhibit A - Source Code Form License Notice | ||||
| ------------------------------------------- | ||||
| 
 | ||||
|   This Source Code Form is subject to the terms of the Mozilla Public | ||||
|   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|   file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||
| 
 | ||||
| If it is not possible or desirable to put the notice in a particular | ||||
| file, then You may include the notice in a location (such as a LICENSE | ||||
| file in a relevant directory) where a recipient would be likely to look | ||||
| for such a notice. | ||||
| 
 | ||||
| You may add additional accurate notices of copyright ownership. | ||||
| 
 | ||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||
| --------------------------------------------------------- | ||||
| 
 | ||||
|   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||
|   defined by the Mozilla Public License, v. 2.0. | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
|  | ||||
							
								
								
									
										372
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										372
									
								
								README.md
									
									
									
									
									
								
							| @ -1,85 +1,91 @@ | ||||
| # Greenlock v3 on its way (Nov 1st, 2019) | ||||
|  | ||||
| 
 | ||||
| Greenlock v3 is in private beta (for backers) and will be available publicly by Nov 1st. | ||||
|  | ||||
| 
 | ||||
| You can keep an eye for updates on the [campaign page](https://indiegogo.com/at/greenlock) and, | ||||
| if this has been a useful project that's saved you time, [please contribute](https://paypal.me/rootprojects/99). | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| # [Greenlock](https://git.rootprojects.org/root/greenlock.js)™ for node.js | a [Root](https://rootprojects.org) project | ||||
| Greenlock™ for node.js | ||||
| ===== | ||||
| 
 | ||||
| Greenlock provides Free SSL, Free Wildcard SSL, and Fully Automated HTTPS <br> | ||||
| <small>certificates issued by Let's Encrypt v2 via [ACME](https://git.rootprojects.org/root/acme-v2.js)</small> | ||||
| <small>certificates issued by Let's Encrypt v2 via [ACME](https://git.coolaj86.com/coolaj86/acme-v2.js)</small> | ||||
| 
 | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 
 | ||||
| | Sponsored by [ppl](https://ppl.family) | | ||||
| Greenlock works | ||||
| in the [Commandline](https://git.rootprojects.org/root/greenlock-cli.js) (cli), | ||||
| as a [Web Server](https://git.rootprojects.org/root/greenlock-express.js), | ||||
| in [Web Browsers](https://greenlock.domains) (WebCrypto), | ||||
| in the [Commandline](https://git.coolaj86.com/coolaj86/greenlock-cli.js) (cli), | ||||
| as a [Web Server](https://git.coolaj86.com/coolaj86/greenlock-server.js), | ||||
| in [Web Browsers](https://git.coolaj86.com/coolaj86/greenlock.html) (WebCrypto), | ||||
| and with **node.js** ([npm](https://www.npmjs.com/package/greenlock)). | ||||
| 
 | ||||
| # Features | ||||
| Features | ||||
| ======== | ||||
| 
 | ||||
| -   [x] Actively Maintained and Commercially Supported | ||||
|     -   [x] VanillaJS | ||||
|     -   [x] Limited Dependencies | ||||
|     -   [x] MPL-2.0 licensed (great for hobbyists and DIYers) | ||||
|     -   [x] [Contact us](mailto:support@rootprojects.org?subject=Greenlock%20Commercial%20Support) for Business Support Plans and Commercial LTS Licensing (great for IoT, On-Prem, Web Hosting, etc) | ||||
|   - [x] Actively Maintained and Supported | ||||
|   - [x] Automatic HTTPS | ||||
|     - [x] Free SSL | ||||
|     - [x] Free Wildcard SSL | ||||
|     - [x] Multiple domain support (up to 100 altnames per SAN) | ||||
|     - [x] Dynamic Virtual Hosting (vhost) | ||||
|     - [x] Automatical renewal (10 to 14 days before expiration) | ||||
| -   [x] Great ACME support via [acme.js](https://git.rootprojects.org/root/acme-v2.js) | ||||
|   - [x] Great ACME support via [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js) | ||||
|     - [x] "dry run" with self-diagnostics | ||||
|     - [x] ACME draft 12 | ||||
|     - [x] Let's Encrypt v2 | ||||
|     -   [x] ~Let's Encrypt v1~ (deprecated) | ||||
| -   [x] [Commandline](https://git.rootprojects.org/root/greenlock-cli.js) (cli) Utilities | ||||
|     - [x] Let's Encrypt v1 | ||||
|   - [x] [Commandline](https://git.coolaj86.com/coolaj86/greenlock-cli.js) (cli) Utilities | ||||
|     - [x] Works with `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell`, and more | ||||
| -   [x] [Browser](https://git.rootprojects.org/root/greenlock.html) Support | ||||
|   - [x] [Browser](https://git.coolaj86.com/coolaj86/greenlock.html) Support | ||||
|   - [x] Full node.js support, with modules for | ||||
|     -   [x] [http/https](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples), [Express.js](https://git.rootprojects.org/root/greenlock-express.js), [hapi](https://git.rootprojects.org/root/greenlock-hapi.js), [Koa](https://git.rootprojects.org/root/greenlock-koa.js), [rill](https://git.rootprojects.org/root/greenlock-rill.js), spdy, etc | ||||
| -   [x] Great for securing your Raspberry Pi and IoT projects | ||||
|     - [x] [http/https](https://git.coolaj86.com/coolaj86/greenlock-express.js/src/branch/master/examples/https-server.js), [Express.js](https://git.coolaj86.com/coolaj86/greenlock-express.js), [cluster](https://git.coolaj86.com/coolaj86/greenlock-cluster.js), [hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js), [Koa](https://git.coolaj86.com/coolaj86/greenlock-koa.js), [rill](https://git.coolaj86.com/coolaj86/greenlock-rill.js), [restify](https://git.coolaj86.com/coolaj86/greenlock-restify.js), spdy, etc | ||||
|   - [x] Great for securing your Raspberry Pi | ||||
|   - [x] Extensible Plugin Support | ||||
|     - [x] AWS S3, AWS Route53, Azure, CloudFlare, Consul, Digital Ocean, etcd, Redis | ||||
| 
 | ||||
| ## Greenlock.js for Middleware | ||||
| Greenlock.js for Middleware | ||||
| ------ | ||||
| 
 | ||||
| Documentation for using Greenlock with | ||||
| [http/https](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples), | ||||
| [Express.js](https://git.rootprojects.org/root/greenlock-express.js), | ||||
| [hapi](https://git.rootprojects.org/root/greenlock-hapi.js), | ||||
| [Koa](https://git.rootprojects.org/root/greenlock-koa.js), | ||||
| [rill](https://git.rootprojects.org/root/greenlock-rill.js). | ||||
| [http/https](https://git.coolaj86.com/coolaj86/greenlock-express.js/src/branch/master/examples/https-server.js), | ||||
| [Express.js](https://git.coolaj86.com/coolaj86/greenlock-express.js), | ||||
| [cluster](https://git.coolaj86.com/coolaj86/greenlock-cluster.js), | ||||
| [hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js), | ||||
| [Koa](https://git.coolaj86.com/coolaj86/greenlock-koa.js), | ||||
| [rill](https://git.coolaj86.com/coolaj86/greenlock-rill.js). | ||||
| [restify](https://git.coolaj86.com/coolaj86/greenlock-restify.js). | ||||
| 
 | ||||
| # Table of Contents | ||||
| Table of Contents | ||||
| ================= | ||||
| 
 | ||||
| -   Install | ||||
| -   **QuickStart** | ||||
| -   Simple Examples | ||||
| -   Example with ALL OPTIONS | ||||
| -   API | ||||
| -   Developer API | ||||
| -   Change History | ||||
| -   License | ||||
|   * Install | ||||
|   * Simple Examples | ||||
|   * Example with ALL OPTIONS | ||||
|   * API | ||||
|   * Developer API | ||||
|   * Change History | ||||
|   * License | ||||
| 
 | ||||
| # Install | ||||
| Install | ||||
| ======= | ||||
| 
 | ||||
| ```bash | ||||
| npm install --save greenlock@2.x | ||||
| ``` | ||||
| 
 | ||||
| **Optional** for _more efficient_ RSA key generation you must use node v10.12+ | ||||
| **Optional** dependency for *more efficient* RSA key generation: | ||||
| <small>(important for those on ARM devices like Raspberry Pi)</small> | ||||
| ```bash | ||||
| npm install --save ursa | ||||
| ``` | ||||
| 
 | ||||
| **Optional** dependency for *Let's Encrypt v01* (pre-draft ACME spec) compatibility: | ||||
| <small>(important for those on ARM devices like Raspberry Pi)</small> | ||||
| ```bash | ||||
| npm install --save le-acme-core | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| ### Production vs Staging | ||||
| 
 | ||||
| @ -97,40 +103,24 @@ unless you're very clear on what the failure was and how to fix it. | ||||
| { server: 'https://acme-staging-v02.api.letsencrypt.org/directory' } | ||||
| ``` | ||||
| 
 | ||||
| ### QuickStart Screencast | ||||
| 
 | ||||
| Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu.be/e8vaR4CEZ5s) | ||||
| 
 | ||||
| <a href="https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk"><img src="https://i.imgur.com/Y8ix6Ts.png" title="QuickStart Video" alt="YouTube Video Preview" /></a> | ||||
| 
 | ||||
| -   [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro | ||||
| -   [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example | ||||
| -   [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas | ||||
| 
 | ||||
| #### Production Configuration (Part 2) | ||||
| 
 | ||||
| -   [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project | ||||
| -   [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback | ||||
| 
 | ||||
| #### Security Concerns (Part 3) | ||||
| 
 | ||||
| -   [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation | ||||
| 
 | ||||
| # Easy as 1, 2, 3... 4 | ||||
| Easy as 1, 2, 3... 4 | ||||
| ===== | ||||
| 
 | ||||
| Greenlock is built to incredibly easy to use, without sacrificing customization or extensibility. | ||||
| 
 | ||||
| The following examples range from just a few lines of code for getting started, | ||||
| to more robust examples that you might start with for an enterprise-grade use of the ACME api. | ||||
| 
 | ||||
| -   Automatic HTTPS (for single sites) | ||||
| -   Fully Automatic HTTPS (for multi-domain vhosts) | ||||
| -   Manual HTTPS (for API integration) | ||||
| * Automatic HTTPS (for single sites) | ||||
| * Fully Automatic HTTPS (for multi-domain vhosts) | ||||
| * Manual HTTPS (for API integration) | ||||
| 
 | ||||
| ## Automatic HTTPS | ||||
| Automatic HTTPS | ||||
| --------------- | ||||
| 
 | ||||
| **Note**: For (fully) automatic HTTPS you may prefer | ||||
| the [Express.js module](https://git.rootprojects.org/root/greenlock-express.js) | ||||
| the [Express.js module](https://git.coolaj86.com/coolaj86/greenlock-express.js) | ||||
| 
 | ||||
| This works for most people, but it's not as fun as some of the other examples. | ||||
| 
 | ||||
| @ -144,37 +134,38 @@ Great when | ||||
| // INIT GREENLOCK // | ||||
| //////////////////// | ||||
| 
 | ||||
| var greenlock = require('greenlock').create({ | ||||
| 	email: 'user@example.com', // IMPORTANT: Change email and domains | ||||
| 	agreeTos: true, // Accept Let's Encrypt v2 Agreement | ||||
| 	configDir: '~/.config/acme', // A writable folder (a non-fs plugin) | ||||
| var path = require('path'); | ||||
| var os = require('os') | ||||
| var Greenlock = require('greenlock'); | ||||
| 
 | ||||
| 	communityMember: true, // Get (rare) non-mandatory updates about cool greenlock-related stuff (default false) | ||||
| 	securityUpdates: true // Important and mandatory notices related to security or breaking API changes (default true) | ||||
| var greenlock = Greenlock.create({ | ||||
|   agreeTos: true                      // Accept Let's Encrypt v2 Agreement | ||||
| , email: 'user@example.com'           // IMPORTANT: Change email and domains | ||||
| , approveDomains: [ 'example.com' ] | ||||
| , communityMember: false              // Optionally get important updates (security, api changes, etc) | ||||
|                                       // and submit stats to help make Greenlock better | ||||
| , version: 'draft-12' | ||||
| , server: 'https://acme-v02.api.letsencrypt.org/directory' | ||||
| , configDir: path.join(os.homedir(), 'acme/etc') | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| //////////////////// | ||||
| // CREATE SERVERS // | ||||
| //////////////////// | ||||
| 
 | ||||
| var redir = require('redirect-https')(); | ||||
| require('http') | ||||
| 	.createServer(greenlock.middleware(redir)) | ||||
| 	.listen(80); | ||||
| require('http').createServer(greenlock.middleware(redir)).listen(80); | ||||
| 
 | ||||
| require('spdy') | ||||
| 	.createServer(greenlock.tlsOptions, function(req, res) { | ||||
| require('https').createServer(greenlock.tlsOptions, function (req, res) { | ||||
|   res.end('Hello, Secure World!'); | ||||
| 	}) | ||||
| 	.listen(443); | ||||
| }).listen(443); | ||||
| ``` | ||||
| 
 | ||||
| ## Fully Automatic HTTPS | ||||
| Fully Automatic HTTPS | ||||
| ------------ | ||||
| 
 | ||||
| **Note**: For (fully) automatic HTTPS you may prefer | ||||
| the [Express.js module](https://git.rootprojects.org/root/greenlock-express.js) | ||||
| the [Express.js module](https://git.coolaj86.com/coolaj86/greenlock-express.js) | ||||
| 
 | ||||
| Great when | ||||
| 
 | ||||
| @ -188,34 +179,29 @@ Great when | ||||
| //////////////////// | ||||
| 
 | ||||
| var path = require('path'); | ||||
| var os = require('os'); | ||||
| var os = require('os') | ||||
| var Greenlock = require('greenlock'); | ||||
| 
 | ||||
| var greenlock = Greenlock.create({ | ||||
| 	version: 'draft-12', | ||||
| 	server: 'https://acme-v02.api.letsencrypt.org/directory', | ||||
|   version: 'draft-12' | ||||
| , server: 'https://acme-v02.api.letsencrypt.org/directory' | ||||
| 
 | ||||
| 	// Use the approveDomains callback to set per-domain config | ||||
| 	// (default: approve any domain that passes self-test of built-in challenges) | ||||
| 	approveDomains: approveDomains, | ||||
| 
 | ||||
| 	// the default servername to use when the client doesn't specify | ||||
| 	servername: 'example.com', | ||||
|   // approve a growing list of domains | ||||
| , approveDomains: approveDomains | ||||
| 
 | ||||
|   // If you wish to replace the default account and domain key storage plugin | ||||
| 	store: require('le-store-fs').create({ | ||||
| 		configDir: path.join(os.homedir(), 'acme/etc'), | ||||
| 		webrootPath: '/tmp/acme-challenges' | ||||
| , store: require('le-store-certbot').create({ | ||||
|     configDir: path.join(os.homedir(), 'acme/etc') | ||||
|   , webrootPath: '/tmp/acme-challenges' | ||||
|   }) | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| ///////////////////// | ||||
| // APPROVE DOMAINS // | ||||
| ///////////////////// | ||||
| 
 | ||||
| var http01 = require('le-challenge-fs').create({ | ||||
| 	webrootPath: '/tmp/acme-challenges' | ||||
| }); | ||||
| var http01 = require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }); | ||||
| function approveDomains(opts, certs, cb) { | ||||
|   // This is where you check your database and associated | ||||
|   // email addresses with domains and agreements and such | ||||
| @ -228,10 +214,13 @@ function approveDomains(opts, certs, cb) { | ||||
| 
 | ||||
|   // The domains being approved for the first time are listed in opts.domains | ||||
|   // Certs being renewed are listed in certs.altnames | ||||
| 	// certs.domains; | ||||
| 	// certs.altnames; | ||||
|   if (certs) { | ||||
|     opts.domains = certs.altnames; | ||||
|   } | ||||
|   else { | ||||
|     opts.email = 'john.doe@example.com'; | ||||
|     opts.agreeTos = true; | ||||
|   } | ||||
| 
 | ||||
|   // NOTE: you can also change other options such as `challengeType` and `challenge` | ||||
|   // opts.challengeType = 'http-01'; | ||||
| @ -240,23 +229,21 @@ function approveDomains(opts, certs, cb) { | ||||
|   cb(null, { options: opts, certs: certs }); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| //////////////////// | ||||
| // CREATE SERVERS // | ||||
| //////////////////// | ||||
| 
 | ||||
| var redir = require('redirect-https')(); | ||||
| require('http') | ||||
| 	.createServer(greenlock.middleware(redir)) | ||||
| 	.listen(80); | ||||
| require('http').createServer(greenlock.middleware(redir)).listen(80); | ||||
| 
 | ||||
| require('https') | ||||
| 	.createServer(greenlock.tlsOptions, function(req, res) { | ||||
| require('https').createServer(greenlock.tlsOptions, function (req, res) { | ||||
|   res.end('Hello, Secure World!'); | ||||
| 	}) | ||||
| 	.listen(443); | ||||
| }).listen(443); | ||||
| ``` | ||||
| 
 | ||||
| ## Manual HTTPS | ||||
| Manual HTTPS | ||||
| ------------- | ||||
| 
 | ||||
| Here's a taste of the API that you might use if building a commandline tool or API integration | ||||
| that doesn't use node's SNICallback. | ||||
| @ -304,24 +291,20 @@ greenlock.register(opts).then(function (certs) { | ||||
| The domain key and ssl certificates you get back can be used in a webserver like this: | ||||
| 
 | ||||
| ```js | ||||
| var tlsOptions = { | ||||
| 	key: certs.privkey, | ||||
| 	cert: certs.cert + '\r\n' + certs.chain | ||||
| }; | ||||
| require('https') | ||||
| 	.createServer(tlsOptions, function(req, res) { | ||||
| var tlsOptions = { key: certs.privkey, cert: certs.cert + '\r\n' + certs.chain }; | ||||
| require('https').createServer(tlsOptions, function (req, res) { | ||||
|   res.end('Hello, Secure World!'); | ||||
| 	}) | ||||
| 	.listen(443); | ||||
| }).listen(443); | ||||
| ``` | ||||
| 
 | ||||
| # Example with ALL OPTIONS | ||||
| Example with ALL OPTIONS | ||||
| ========= | ||||
| 
 | ||||
| The configuration consists of 3 components: | ||||
| 
 | ||||
| -   Storage Backend (search npm for projects starting with 'le-store-') | ||||
| -   ACME Challenge Handlers (search npm for projects starting with 'le-challenge-') | ||||
| -   Letsencryt Config (this is all you) | ||||
| * Storage Backend (search npm for projects starting with 'le-store-') | ||||
| * ACME Challenge Handlers (search npm for projects starting with 'le-challenge-') | ||||
| * Letsencryt Config (this is all you) | ||||
| 
 | ||||
| ```javascript | ||||
| 'use strict'; | ||||
| @ -331,7 +314,7 @@ var greenlock; | ||||
| 
 | ||||
| 
 | ||||
| // Storage Backend | ||||
| var leStore = require('greenlock-store-fs').create({ | ||||
| var leStore = require('le-store-certbot').create({ | ||||
|   configDir: '~/acme/etc'                                 // or /etc/letsencrypt or wherever | ||||
| , debug: false | ||||
| }); | ||||
| @ -435,7 +418,8 @@ Here's what `results` looks like: | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## API | ||||
| API | ||||
| --- | ||||
| 
 | ||||
| The full end-user API is exposed in the example above and includes all relevant options. | ||||
| 
 | ||||
| @ -448,7 +432,7 @@ greenlock.check(opts) | ||||
| 
 | ||||
| We do expose a few helper functions: | ||||
| 
 | ||||
| -   Greenlock.validDomain(hostname) // returns '' or the hostname string if it's a valid ascii or punycode domain name | ||||
| * Greenlock.validDomain(hostname) // returns '' or the hostname string if it's a valid ascii or punycode domain name | ||||
| 
 | ||||
| TODO fetch domain tld list | ||||
| 
 | ||||
| @ -456,114 +440,78 @@ TODO fetch domain tld list | ||||
| 
 | ||||
| The following variables will be tempalted in any strings passed to the options object: | ||||
| 
 | ||||
| -   `~/` replaced with `os.homedir()` i.e. `/Users/aj` | ||||
| -   `:hostname` replaced with the first domain in the list i.e. `example.com` | ||||
| * `~/` replaced with `os.homedir()` i.e. `/Users/aj` | ||||
| * `:hostname` replaced with the first domain in the list i.e. `example.com` | ||||
| 
 | ||||
| ### Dangerous Options | ||||
| 
 | ||||
| By default SNI is made to lowercase and is automatically rejected if it contains invalid characters for a domain. | ||||
| This behavior can be modified: | ||||
| 
 | ||||
| -   `__dns_allow_dangerous_names` allow SNI names like "Robert'); DROP TABLE Students;" | ||||
| -   `__dns_preserve_case` passes SNI names such as "ExAMpLE.coM" without converting to lower case | ||||
| 
 | ||||
| ## Developer API | ||||
| Developer API | ||||
| ------------- | ||||
| 
 | ||||
| If you are developing an `le-store-*` or `le-challenge-*` plugin you need to be aware of | ||||
| additional internal API expectations. | ||||
| 
 | ||||
| **IMPORTANT**: | ||||
| 
 | ||||
| Use `v3.0.0` as your initial version - NOT v0.1.0 and NOT v1.0.0 and NOT v2.0.0. | ||||
| This is to indicate that your module is compatible with v3 (v2.7+) of node-greenlock. | ||||
| Use `v2.0.0` as your initial version - NOT v0.1.0 and NOT v1.0.0 and NOT v3.0.0. | ||||
| This is to indicate that your module is compatible with v2.x of node-greenlock. | ||||
| 
 | ||||
| Since the public API for your module is defined by node-greenlock the major version | ||||
| should be kept in sync. | ||||
| 
 | ||||
| ### store implementation | ||||
| 
 | ||||
| See [greenlock-store-test](https://git.rootprojects.org/root/greenlock-store-test.js) | ||||
| and [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | ||||
| See <https://git.coolaj86.com/coolaj86/le-store-SPEC.js> | ||||
| 
 | ||||
| -   accounts. | ||||
|     -   checkKeypair(opts) | ||||
|     -   check(opts) | ||||
|     -   setKeypair(opts) | ||||
|     -   set(opts) | ||||
| -   certificates. | ||||
|     -   checkKeypair(opts) | ||||
|     -   check(opts) | ||||
|     -   setKeypair(opts) | ||||
|     -   set(opts) | ||||
| * getOptions() | ||||
| * accounts. | ||||
|   * checkKeypair(opts, cb) | ||||
|   * check(opts, cb) | ||||
|   * setKeypair(opts, keypair, cb) | ||||
|   * set(opts, reg, cb) | ||||
| * certificates. | ||||
|   * checkKeypair(opts, cb) | ||||
|   * check(opts, cb) | ||||
|   * setKeypair(opts, keypair, cb) | ||||
|   * set(opts, reg, cb) | ||||
| 
 | ||||
| ### challenge implementation | ||||
| 
 | ||||
| See [greenlock-challenge-test](https://git.rootprojects.org/root/greenlock-challenge-test.js), | ||||
| [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js), | ||||
| and [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) | ||||
| See https://git.coolaj86.com/coolaj86/le-challenge-fs.js | ||||
| 
 | ||||
| -   `.set(opts);` | ||||
| -   `.get(opts);` | ||||
| -   `.remove(opts);` | ||||
| * `.set(opts, domain, key, value, cb);`         // opts will be saved with domain/key | ||||
| * `.get(opts, domain, key, cb);`                // opts will be retrieved by domain/key | ||||
| * `.remove(opts, domain, key, cb);`             // opts will be retrieved by domain/key | ||||
| 
 | ||||
| # Change History | ||||
| Change History | ||||
| ============== | ||||
| * v2.2 - Let's Encrypt v2 Support | ||||
|   * v2.2.11 - documentation updates | ||||
|   * v2.2.10 - don't let SNICallback swallow approveDomains errors 6286883fc2a6ebfff711a540a2e4d92f3ac2907c | ||||
|   * v2.2.8 - communityMember option support | ||||
|   * v2.2.7 - bugfix for wildcard support | ||||
|   * v2.2.5 - node v6.x compat | ||||
|   * v2.2.4 - don't promisify all of `dns` | ||||
|   * v2.2.3 - `renewWithin` default to 14 days | ||||
|   * v2.2.2 - replace git dependency with npm | ||||
|   * v2.2.1 - April 2018 **Let's Encrypt v2** support | ||||
| * v2.1.17 - Nov 5th 2017 migrate back to personal repo | ||||
| * v2.1.9 - Jan 18th 2017 renamed to greenlock | ||||
| * v2.0.2 - Aug 9th 2016 update readme | ||||
| * v2.0.1 - Aug 9th 2016 | ||||
|   * major refactor | ||||
|   * simplified API | ||||
|   * modular plugins | ||||
|   * knock out bugs | ||||
| * v1.5.0 now using letiny-core v2.0.0 and rsa-compat | ||||
| * v1.4.x I can't remember... but it's better! | ||||
| * v1.1.0 Added letiny-core, removed node-letsencrypt-python | ||||
| * v1.0.2 Works with node-letsencrypt-python | ||||
| * v1.0.0 Thar be dragons | ||||
| 
 | ||||
| -   v2.7 | ||||
|     -   API: transitional for v3 API (Promies, async/await) | ||||
|     -   Security: Zero external dependencies | ||||
|     -   Plugins: `greenlock-store-fs` replaces `le-store-certbot` as the default storage plugin | ||||
|     -   Features: Full wildcard support | ||||
|     -   Licensing: Commercial licensing and support plans now available | ||||
| -   v2.6 | ||||
|     -   better defaults, fewer explicit options | ||||
|     -   better pre-flight self-tests, explicit domains not required | ||||
| -   v2.5 | ||||
|     -   bugfix JWK (update rsa-compat) | ||||
|     -   eliminate all external non-optional dependencies | ||||
| -   v2.4 | ||||
|     -   v2.4.3 - add security updates (default true) independent of community updates (default false) | ||||
| -   v2.2 - Let's Encrypt v2 Support | ||||
|     -   v2.2.11 - documentation updates | ||||
|     -   v2.2.10 - don't let SNICallback swallow approveDomains errors 6286883fc2a6ebfff711a540a2e4d92f3ac2907c | ||||
|     -   v2.2.8 - communityMember option support | ||||
|     -   v2.2.7 - bugfix for wildcard support | ||||
|     -   v2.2.5 - node v6.x compat | ||||
|     -   v2.2.4 - don't promisify all of `dns` | ||||
|     -   v2.2.3 - `renewWithin` default to 14 days | ||||
|     -   v2.2.2 - replace git dependency with npm | ||||
|     -   v2.2.1 - April 2018 **Let's Encrypt v2** support | ||||
| -   v2.1.17 - Nov 5th 2017 migrate back to personal repo | ||||
| -   v2.1.9 - Jan 18th 2017 renamed to greenlock | ||||
| -   v2.0.2 - Aug 9th 2016 update readme | ||||
| -   v2.0.1 - Aug 9th 2016 | ||||
|     -   major refactor | ||||
|     -   simplified API | ||||
|     -   modular plugins | ||||
|     -   knock out bugs | ||||
| -   v1.5.0 now using letiny-core v2.0.0 and rsa-compat | ||||
| -   v1.4.x I can't remember... but it's better! | ||||
| -   v1.1.0 Added letiny-core, removed node-letsencrypt-python | ||||
| -   v1.0.2 Works with node-letsencrypt-python | ||||
| -   v1.0.0 Thar be dragons | ||||
| LICENSE | ||||
| ======= | ||||
| 
 | ||||
| # Commercial Licensing | ||||
| Dual-licensed MIT and Apache-2.0 | ||||
| 
 | ||||
| As the number of businesses using Greenlock commercially has increased, we've become more aware of the need for quick-turnaround support and licenses that allow for local private modifications. Currently we offer LTS support and commercial licensing models for IoT, On-Prem, and Web Hosting. Please [contact us](mailto:support@rootprojects.org?subject=Greenlock%20Commercial%20Support) to learn more. | ||||
| See LICENSE | ||||
| 
 | ||||
| Our [trademark policy](https://therootcompany.com/legal/#trademark) is pretty much "attribute, but don't confuse". Your users should understand that your product _uses_ Greenlock and not be confused to think that it _is_ Greenlock. | ||||
| 
 | ||||
| # Legal & Rules of the Road | ||||
| 
 | ||||
| Greenlock™ is a [trademark](https://rootprojects.org/legal/#trademark) of AJ ONeal | ||||
| 
 | ||||
| The rule of thumb is "attribute, but don't confuse". For example: | ||||
| 
 | ||||
| > Built with [Greenlock.js](https://git.rootprojects.org/root/greenlock.js) (a [Root](https://rootprojects.org) project). | ||||
| 
 | ||||
| Please [contact us](mailto:aj@therootcompany.com) if you have any questions in regards to our trademark, | ||||
| attribution, and/or visible source policies. We want to build great software and a great community. | ||||
| 
 | ||||
| [Greenlock™](https://git.rootprojects.org/root/greenlock.js) | | ||||
| MPL-2.0 | | ||||
| [Terms of Use](https://therootcompany.com/legal/#terms) | | ||||
| [Privacy Policy](https://therootcompany.com/legal/#privacy) | ||||
| Greenlock™ is a trademark of AJ ONeal | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| # STOP | ||||
| STOP | ||||
| ==== | ||||
| 
 | ||||
| **These aren't the droids you're looking for.** | ||||
| 
 | ||||
| @ -6,7 +7,8 @@ You probably don't want to use `greenlock` directly. | ||||
| 
 | ||||
| Instead, look here: | ||||
| 
 | ||||
| ## Webservers | ||||
| Webservers | ||||
| ---------- | ||||
| 
 | ||||
| For any type of webserver (express, hapi, koa, connect, https, spdy, etc), | ||||
| you're going to want to take a look at | ||||
| @ -14,7 +16,8 @@ you're going to want to take a look at | ||||
| 
 | ||||
| <https://git.coolaj86.com/coolaj86/greenlock-express.js> | ||||
| 
 | ||||
| ## CLIs | ||||
| CLIs | ||||
| ---- | ||||
| 
 | ||||
| For any type of CLI (like what you want to use with bash, fish, zsh, cmd.exe, PowerShell, etc), | ||||
| you're going to want to take a look at | ||||
| @ -22,7 +25,8 @@ you're going to want to take a look at | ||||
| 
 | ||||
| <https://git.coolaj86.com/coolaj86/greenlock-cli.js> | ||||
| 
 | ||||
| # No, I wanted greenlock | ||||
| No, I wanted greenlock | ||||
| ====================== | ||||
| 
 | ||||
| Well, take a look at the API in the main README | ||||
| and you can also check out the code in the repos above. | ||||
|  | ||||
| @ -5,64 +5,55 @@ var Greenlock = require('../'); | ||||
| var db = {}; | ||||
| 
 | ||||
| var config = { | ||||
| 	server: 'https://acme-v02.api.letsencrypt.org/directory', | ||||
| 	version: 'draft-11', | ||||
|   server: 'https://acme-v02.api.letsencrypt.org/directory' | ||||
| , version: 'draft-11' | ||||
| 
 | ||||
| 	configDir: require('os').homedir() + '/acme/etc', // or /etc/acme or wherever
 | ||||
| , configDir: require('os').homedir() + '/acme/etc'          // or /etc/acme or wherever
 | ||||
| 
 | ||||
| 	privkeyPath: ':config/live/:hostname/privkey.pem', //
 | ||||
| 	fullchainPath: ':config/live/:hostname/fullchain.pem', // Note: both that :config and :hostname
 | ||||
| 	certPath: ':config/live/:hostname/cert.pem', //       will be templated as expected
 | ||||
| 	chainPath: ':config/live/:hostname/chain.pem', //
 | ||||
| , privkeyPath: ':config/live/:hostname/privkey.pem'         //
 | ||||
| , fullchainPath: ':config/live/:hostname/fullchain.pem'     // Note: both that :config and :hostname
 | ||||
| , certPath: ':config/live/:hostname/cert.pem'               //       will be templated as expected
 | ||||
| , chainPath: ':config/live/:hostname/chain.pem'             //
 | ||||
| 
 | ||||
| 	rsaKeySize: 2048, | ||||
| , rsaKeySize: 2048 | ||||
| 
 | ||||
| 	debug: true | ||||
| , debug: true | ||||
| }; | ||||
| 
 | ||||
| var handlers = { | ||||
| 	setChallenge: function(opts, hostname, key, val, cb) { | ||||
| 		// called during the ACME server handshake, before validation
 | ||||
|   setChallenge: function (opts, hostname, key, val, cb) {   // called during the ACME server handshake, before validation
 | ||||
|     db[key] = { | ||||
| 			hostname: hostname, | ||||
| 			key: key, | ||||
| 			val: val | ||||
|       hostname: hostname | ||||
|     , key: key | ||||
|     , val: val | ||||
|     }; | ||||
| 
 | ||||
|     cb(null); | ||||
| 	}, | ||||
| 	removeChallenge: function(opts, hostname, key, cb) { | ||||
| 		// called after validation on both success and failure
 | ||||
|   } | ||||
| , removeChallenge: function (opts, hostname, key, cb) {     // called after validation on both success and failure
 | ||||
|     db[key] = null; | ||||
|     cb(null); | ||||
| 	}, | ||||
| 	getChallenge: function(opts, hostname, key, cb) { | ||||
| 		// this is special because it is called by the webserver
 | ||||
|   } | ||||
| , getChallenge: function (opts, hostname, key, cb) {        // this is special because it is called by the webserver
 | ||||
|     cb(null, db[key].val);                                  // (see greenlock-cli/bin & greenlock-express/standalone),
 | ||||
|                                                             // not by the library itself
 | ||||
| 	}, | ||||
| 	agreeToTerms: function(tosUrl, cb) { | ||||
| 		// gives you an async way to expose the legal agreement
 | ||||
|   } | ||||
| , agreeToTerms: function (tosUrl, cb) {                     // gives you an async way to expose the legal agreement
 | ||||
|     cb(null, tosUrl);                                       // (terms of use) to your users before accepting
 | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| var greenlock = Greenlock.create(config, handlers); | ||||
| console.error( | ||||
| 	'CHANGE THE EMAIL, DOMAINS, AND AGREE TOS IN THE EXAMPLE BEFORE RUNNING IT' | ||||
| ); | ||||
| console.error("CHANGE THE EMAIL, DOMAINS, AND AGREE TOS IN THE EXAMPLE BEFORE RUNNING IT"); | ||||
| process.exit(1); | ||||
|                                                             // checks :conf/renewal/:hostname.conf
 | ||||
| greenlock.register( | ||||
| 	{ | ||||
| 		// and either renews or registers
 | ||||
| 		domains: ['example.com'], // CHANGE TO YOUR DOMAIN
 | ||||
| 		email: 'user@email.com', // CHANGE TO YOUR EMAIL
 | ||||
| 		agreeTos: false, // set to true to automatically accept an agreement
 | ||||
| greenlock.register({                                        // and either renews or registers
 | ||||
|   domains: ['example.com']                                  // CHANGE TO YOUR DOMAIN
 | ||||
| , email: 'user@email.com'                                   // CHANGE TO YOUR EMAIL
 | ||||
| , agreeTos: false                                           // set to true to automatically accept an agreement
 | ||||
|                                                             // which you have pre-approved (not recommended)
 | ||||
| 		rsaKeySize: 2048 | ||||
| 	}, | ||||
| 	function(err) { | ||||
| , rsaKeySize: 2048 | ||||
| }, function (err) { | ||||
|   if (err) { | ||||
|     // Note: you must have a webserver running
 | ||||
|     // and expose handlers.getChallenge to it
 | ||||
| @ -73,5 +64,4 @@ greenlock.register( | ||||
|   } else { | ||||
|     console.log('success'); | ||||
|   } | ||||
| 	} | ||||
| ); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										764
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										764
									
								
								index.js
									
									
									
									
									
								
							| @ -1,52 +1,25 @@ | ||||
| 'use strict'; | ||||
| /*global Promise*/ | ||||
| require('./lib/compat.js'); | ||||
| 
 | ||||
| // I hate this code so much.
 | ||||
| // Soooo many shims for backwards compatibility (some stuff dating back to v1)
 | ||||
| // v3 will be a clean break and I'll delete half of the code...
 | ||||
| 
 | ||||
| var DAY = 24 * 60 * 60 * 1000; | ||||
| //var MIN = 60 * 1000;
 | ||||
| var ACME = require('acme-v2/compat').ACME; | ||||
| var pkg = require('./package.json'); | ||||
| var util = require('util'); | ||||
| 
 | ||||
| function promisifyAllSelf(obj) { | ||||
| 	if (obj.__promisified) { | ||||
| 		return obj; | ||||
| var PromiseA; | ||||
| try { | ||||
|   PromiseA = require('bluebird'); | ||||
| } catch(e) { | ||||
|   PromiseA = global.Promise; | ||||
| } | ||||
| var util = require('util'); | ||||
| function promisifyAllSelf(obj) { | ||||
|   if (obj.__promisified) { return obj; } | ||||
|   Object.keys(obj).forEach(function (key) { | ||||
| 		if ('function' === typeof obj[key] && !/Async$/.test(key)) { | ||||
|     if ('function' === typeof obj[key]) { | ||||
|       obj[key + 'Async'] = util.promisify(obj[key]); | ||||
|     } | ||||
|   }); | ||||
|   obj.__promisified = true; | ||||
|   return obj; | ||||
| } | ||||
| function promisifyAllStore(obj) { | ||||
| 	Object.keys(obj).forEach(function(key) { | ||||
| 		if ('function' !== typeof obj[key] || /Async$/.test(key)) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		var p; | ||||
| 		if (0 === obj[key].length || 1 === obj[key].length) { | ||||
| 			// wrap just in case it's synchronous (or improperly throws)
 | ||||
| 			p = function(opts) { | ||||
| 				return Promise.resolve().then(function() { | ||||
| 					return obj[key](opts); | ||||
| 				}); | ||||
| 			}; | ||||
| 		} else { | ||||
| 			p = util.promisify(obj[key]); | ||||
| 		} | ||||
| 		// internal backwards compat
 | ||||
| 		obj[key + 'Async'] = p; | ||||
| 	}); | ||||
| 	obj.__promisified = true; | ||||
| 	return obj; | ||||
| } | ||||
| 
 | ||||
| var Greenlock = module.exports; | ||||
| Greenlock.Greenlock = Greenlock; | ||||
| @ -58,20 +31,20 @@ function _log(debug) { | ||||
|   if (debug) { | ||||
|     var args = Array.prototype.slice.call(arguments); | ||||
|     args.shift(); | ||||
| 		args.unshift('[gl/index.js]'); | ||||
|     args.unshift("[gl/index.js]"); | ||||
|     console.log.apply(console, args); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| Greenlock.defaults = { | ||||
| 	productionServerUrl: 'https://acme-v01.api.letsencrypt.org/directory', | ||||
| 	stagingServerUrl: 'https://acme-staging.api.letsencrypt.org/directory', | ||||
|   productionServerUrl: 'https://acme-v01.api.letsencrypt.org/directory' | ||||
| , stagingServerUrl: 'https://acme-staging.api.letsencrypt.org/directory' | ||||
| 
 | ||||
| 	rsaKeySize: ACME.rsaKeySize || 2048, | ||||
| 	challengeType: ACME.challengeType || 'http-01', | ||||
| 	challengeTypes: ACME.challengeTypes || ['http-01', 'dns-01'], | ||||
| , rsaKeySize: ACME.rsaKeySize || 2048 | ||||
| , challengeType: ACME.challengeType || 'http-01' | ||||
| , challengeTypes: ACME.challengeTypes || [ 'http-01', 'dns-01' ] | ||||
| 
 | ||||
| 	acmeChallengePrefix: ACME.acmeChallengePrefix | ||||
| , acmeChallengePrefix: ACME.acmeChallengePrefix | ||||
| }; | ||||
| 
 | ||||
| // backwards compat
 | ||||
| @ -82,27 +55,27 @@ Object.keys(Greenlock.defaults).forEach(function(key) { | ||||
| // show all possible options
 | ||||
| var u; // undefined
 | ||||
| Greenlock._undefined = { | ||||
| 	acme: u, | ||||
| 	store: u, | ||||
| 	//, challenge: u
 | ||||
| 	challenges: u, | ||||
| 	sni: u, | ||||
| 	tlsOptions: u, | ||||
|   acme: u | ||||
| , store: u | ||||
| , challenge: u | ||||
| , challenges: u | ||||
| , sni: u | ||||
| , tlsOptions: u | ||||
| 
 | ||||
| 	register: u, | ||||
| 	check: u, | ||||
| , register: u | ||||
| , check: u | ||||
| 
 | ||||
| 	renewWithin: u, // le-auto-sni and core
 | ||||
| , renewWithin: u // le-auto-sni and core
 | ||||
| //, renewBy: u // le-auto-sni
 | ||||
| 	acmeChallengePrefix: u, | ||||
| 	rsaKeySize: u, | ||||
| 	challengeType: u, | ||||
| 	server: u, | ||||
| 	version: u, | ||||
| 	agreeToTerms: u, | ||||
| 	_ipc: u, | ||||
| 	duplicate: u, | ||||
| 	_acmeUrls: u | ||||
| , acmeChallengePrefix: u | ||||
| , rsaKeySize: u | ||||
| , challengeType: u | ||||
| , server: u | ||||
| , version: u | ||||
| , agreeToTerms: u | ||||
| , _ipc: u | ||||
| , duplicate: u | ||||
| , _acmeUrls: u | ||||
| }; | ||||
| Greenlock._undefine = function (gl) { | ||||
|   Object.keys(Greenlock._undefined).forEach(function (key) { | ||||
| @ -114,19 +87,12 @@ Greenlock._undefine = function(gl) { | ||||
|   return gl; | ||||
| }; | ||||
| Greenlock.create = function (gl) { | ||||
| 	if (!gl.store) { | ||||
| 		console.warn( | ||||
| 			"Deprecation Notice: You're haven't chosen a storage strategy." + | ||||
| 				" The old default is 'le-store-certbot', but the new default will be 'greenlock-store-fs'." + | ||||
| 				" Please `npm install greenlock-store-fs@3` and explicitly set `{ store: require('greenlock-store-fs') }`." | ||||
| 		); | ||||
| 		gl.store = require('le-store-certbot').create({ | ||||
| 			debug: gl.debug, | ||||
| 			configDir: gl.configDir, | ||||
| 			logsDir: gl.logsDir, | ||||
| 			webrootPath: gl.webrootPath | ||||
|   gl.store = gl.store || require('le-store-certbot').create({ | ||||
|     debug: gl.debug | ||||
|   , configDir: gl.configDir | ||||
|   , logsDir: gl.logsDir | ||||
|   , webrootPath: gl.webrootPath | ||||
|   }); | ||||
| 	} | ||||
|   gl.core = require('./lib/core'); | ||||
|   var log = gl.log || _log; | ||||
| 
 | ||||
| @ -135,20 +101,16 @@ Greenlock.create = function(gl) { | ||||
|   } | ||||
|   if (!gl.challenges['http-01']) { | ||||
|     gl.challenges['http-01'] = require('le-challenge-fs').create({ | ||||
| 			debug: gl.debug, | ||||
| 			webrootPath: gl.webrootPath | ||||
|       debug: gl.debug | ||||
|     , webrootPath: gl.webrootPath | ||||
|     }); | ||||
|   } | ||||
|   if (!gl.challenges['dns-01']) { | ||||
|     try { | ||||
| 			gl.challenges['dns-01'] = require('le-challenge-ddns').create({ | ||||
| 				debug: gl.debug | ||||
| 			}); | ||||
|       gl.challenges['dns-01'] = require('le-challenge-ddns').create({ debug: gl.debug }); | ||||
|     } catch(e) { | ||||
|       try { | ||||
| 				gl.challenges['dns-01'] = require('le-challenge-dns').create({ | ||||
| 					debug: gl.debug | ||||
| 				}); | ||||
|         gl.challenges['dns-01'] = require('le-challenge-dns').create({ debug: gl.debug }); | ||||
|       } catch(e) { | ||||
|         // not yet implemented
 | ||||
|       } | ||||
| @ -161,65 +123,41 @@ Greenlock.create = function(gl) { | ||||
|   gl.challengeType = gl.challengeType || Greenlock.challengeType; | ||||
|   gl._ipc = ipc; | ||||
|   gl._communityPackage = gl._communityPackage || 'greenlock.js'; | ||||
| 	if ('greenlock.js' === gl._communityPackage) { | ||||
| 		gl._communityPackageVersion = pkg.version; | ||||
| 	} else { | ||||
| 		gl._communityPackageVersion = | ||||
| 			gl._communityPackageVersion || 'greenlock.js-' + pkg.version; | ||||
| 	} | ||||
| 	gl.agreeToTerms = | ||||
| 		gl.agreeToTerms || | ||||
| 		function(args, agreeCb) { | ||||
| 			agreeCb( | ||||
| 				new Error( | ||||
| 					"'agreeToTerms' was not supplied to Greenlock and 'agreeTos' was not supplied to Greenlock.register" | ||||
| 				) | ||||
| 			); | ||||
|   gl.agreeToTerms = gl.agreeToTerms || function (args, agreeCb) { | ||||
|     agreeCb(new Error("'agreeToTerms' was not supplied to Greenlock and 'agreeTos' was not supplied to Greenlock.register")); | ||||
|   }; | ||||
| 
 | ||||
| 	if (!gl.renewWithin) { | ||||
| 		gl.renewWithin = 14 * DAY; | ||||
| 	} | ||||
|   if (!gl.renewWithin) { gl.renewWithin = 14 * DAY; } | ||||
|   // renewBy has a default in le-sni-auto
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   ///////////////////////////
 | ||||
|   // BEGIN VERSION MADNESS //
 | ||||
|   ///////////////////////////
 | ||||
| 
 | ||||
| 	gl.version = gl.version || 'draft-11'; | ||||
| 	gl.server = gl.server || 'https://acme-v02.api.letsencrypt.org/directory'; | ||||
|   if (!gl.version) { | ||||
|     //console.warn("Please specify version: 'v01' (Let's Encrypt v1) or 'draft-12' (Let's Encrypt v2 / ACME draft 12)");
 | ||||
| 		console.warn(''); | ||||
| 		console.warn(''); | ||||
| 		console.warn(''); | ||||
| 		console.warn( | ||||
| 			'==========================================================' | ||||
| 		); | ||||
| 		console.warn( | ||||
| 			'==                greenlock.js (v2.2.0+)                ==' | ||||
| 		); | ||||
| 		console.warn( | ||||
| 			'==========================================================' | ||||
| 		); | ||||
| 		console.warn(''); | ||||
|     console.warn(""); | ||||
|     console.warn(""); | ||||
|     console.warn(""); | ||||
|     console.warn("=========================================================="); | ||||
|     console.warn("==                greenlock.js (v2.2.0+)                =="); | ||||
|     console.warn("=========================================================="); | ||||
|     console.warn(""); | ||||
|     console.warn("Please specify 'version' option:"); | ||||
| 		console.warn(''); | ||||
| 		console.warn( | ||||
| 			"        'draft-12' for Let's Encrypt v2 and ACME draft 12" | ||||
| 		); | ||||
|     console.warn(""); | ||||
|     console.warn("        'draft-12' for Let's Encrypt v2 and ACME draft 12"); | ||||
|     console.warn("        ('v02' is an alias of 'draft-12'"); | ||||
| 		console.warn(''); | ||||
| 		console.warn('or'); | ||||
| 		console.warn(''); | ||||
|     console.warn(""); | ||||
|     console.warn("or"); | ||||
|     console.warn(""); | ||||
|     console.warn("        'v01' for Let's Encrypt v1 (deprecated)"); | ||||
| 		console.warn( | ||||
| 			"         (also 'npm install --save le-acme-core' as this legacy dependency will soon be removed)" | ||||
| 		); | ||||
| 		console.warn(''); | ||||
| 		console.warn('This will be required in versions v2.3+'); | ||||
| 		console.warn(''); | ||||
| 		console.warn(''); | ||||
|     console.warn("         (also 'npm install --save le-acme-core' as this legacy dependency will soon be removed)"); | ||||
|     console.warn(""); | ||||
|     console.warn("This will be required in versions v2.3+"); | ||||
|     console.warn(""); | ||||
|     console.warn(""); | ||||
|   } else if ('v02' === gl.version) { | ||||
|     gl.version = 'draft-11'; | ||||
|   } else if ('draft-12' === gl.version) { | ||||
| @ -231,95 +169,76 @@ Greenlock.create = function(gl) { | ||||
|   } | ||||
| 
 | ||||
|   if (!gl.server) { | ||||
| 		throw new Error( | ||||
| 			"opts.server must specify an ACME directory URL, such as 'https://acme-staging-v02.api.letsencrypt.org/directory'" | ||||
| 		); | ||||
|     throw new Error("opts.server must specify an ACME directory URL, such as 'https://acme-staging-v02.api.letsencrypt.org/directory'"); | ||||
|   } | ||||
|   if ('staging' === gl.server || 'production' === gl.server) { | ||||
|     if ('staging' === gl.server) { | ||||
|       gl.server = 'https://acme-staging.api.letsencrypt.org/directory'; | ||||
|       gl.version = 'v01'; | ||||
|       gl._deprecatedServerName = 'staging'; | ||||
| 		} else if ('production' === gl.server) { | ||||
|     } | ||||
|     else if ('production' === gl.server) { | ||||
|       gl.server = 'https://acme-v01.api.letsencrypt.org/directory'; | ||||
|       gl.version = 'v01'; | ||||
|       gl._deprecatedServerName = 'production'; | ||||
|     } | ||||
| 		console.warn(''); | ||||
| 		console.warn(''); | ||||
| 		console.warn('=== WARNING ==='); | ||||
| 		console.warn(''); | ||||
| 		console.warn( | ||||
| 			"Due to versioning issues the '" + | ||||
| 				gl._deprecatedServerName + | ||||
| 				"' option is deprecated." | ||||
| 		); | ||||
| 		console.warn('Please specify the full url and version.'); | ||||
| 		console.warn(''); | ||||
| 		console.warn('For APIs add:'); | ||||
| 		console.warn('\t, "version": "' + gl.version + '"'); | ||||
| 		console.warn('\t, "server": "' + gl.server + '"'); | ||||
| 		console.warn(''); | ||||
| 		console.warn('For the CLI add:'); | ||||
|     console.warn(""); | ||||
|     console.warn(""); | ||||
|     console.warn("=== WARNING ==="); | ||||
|     console.warn(""); | ||||
|     console.warn("Due to versioning issues the '" + gl._deprecatedServerName + "' option is deprecated."); | ||||
|     console.warn("Please specify the full url and version."); | ||||
|     console.warn(""); | ||||
|     console.warn("For APIs add:"); | ||||
|     console.warn("\t, \"version\": \"" + gl.version + "\""); | ||||
|     console.warn("\t, \"server\": \"" + gl.server + "\""); | ||||
|     console.warn(""); | ||||
|     console.warn("For the CLI add:"); | ||||
|     console.warn("\t--acme-url '" + gl.server + "' \\"); | ||||
|     console.warn("\t--acme-version '" + gl.version + "' \\"); | ||||
| 		console.warn(''); | ||||
| 		console.warn(''); | ||||
|     console.warn(""); | ||||
|     console.warn(""); | ||||
|   } | ||||
| 
 | ||||
|   function loadLeV01() { | ||||
| 		console.warn(''); | ||||
| 		console.warn('=== WARNING ==='); | ||||
| 		console.warn(''); | ||||
|     console.warn(""); | ||||
|     console.warn("=== WARNING ==="); | ||||
|     console.warn(""); | ||||
|     console.warn("Let's Encrypt v1 is deprecated."); | ||||
|     console.warn("Please update to Let's Encrypt v2 (ACME draft 12)"); | ||||
| 		console.warn(''); | ||||
|     console.warn(""); | ||||
|     try { | ||||
|       return require('le-acme-core').ACME; | ||||
|     } catch(e) { | ||||
| 			console.error(''); | ||||
| 			console.error('=== Error (easy-to-fix) ==='); | ||||
| 			console.error(''); | ||||
| 			console.error( | ||||
| 				"Hey, this isn't a big deal, but you need to manually add v1 support:" | ||||
| 			); | ||||
| 			console.error(''); | ||||
| 			console.error('        npm install --save le-acme-core'); | ||||
| 			console.error(''); | ||||
| 			console.error( | ||||
| 				'Just run that real quick, restart, and everything will work great.' | ||||
| 			); | ||||
| 			console.error(''); | ||||
| 			console.error(''); | ||||
|       console.error(""); | ||||
|       console.error("=== Error (easy-to-fix) ==="); | ||||
|       console.error(""); | ||||
|       console.error("Hey, this isn't a big deal, but you need to manually add v1 support:"); | ||||
|       console.error(""); | ||||
|       console.error("        npm install --save le-acme-core"); | ||||
|       console.error(""); | ||||
|       console.error("Just run that real quick, restart, and everything will work great."); | ||||
|       console.error(""); | ||||
|       console.error(""); | ||||
|       process.exit(e.code || 13); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| 	if ( | ||||
| 		-1 !== | ||||
| 		[ | ||||
| 			'https://acme-v02.api.letsencrypt.org/directory', | ||||
| 			'https://acme-staging-v02.api.letsencrypt.org/directory' | ||||
| 		].indexOf(gl.server) | ||||
|   if (-1 !== [ | ||||
|       'https://acme-v02.api.letsencrypt.org/directory' | ||||
|     , 'https://acme-staging-v02.api.letsencrypt.org/directory' ].indexOf(gl.server) | ||||
|   ) { | ||||
|     if ('draft-11' !== gl.version) { | ||||
| 			console.warn( | ||||
| 				"Detected Let's Encrypt v02 URL. Changing version to draft-12." | ||||
| 			); | ||||
|       console.warn("Detected Let's Encrypt v02 URL. Changing version to draft-12."); | ||||
|       gl.version = 'draft-11'; | ||||
|     } | ||||
| 	} else if ( | ||||
| 		-1 !== | ||||
| 			[ | ||||
| 				'https://acme-v01.api.letsencrypt.org/directory', | ||||
| 				'https://acme-staging.api.letsencrypt.org/directory' | ||||
| 			].indexOf(gl.server) || | ||||
| 		'v01' === gl.version | ||||
|   } else if (-1 !== [ | ||||
|       'https://acme-v01.api.letsencrypt.org/directory' | ||||
|     , 'https://acme-staging.api.letsencrypt.org/directory' ].indexOf(gl.server) | ||||
|     || 'v01' === gl.version | ||||
|   ) { | ||||
|     if ('v01' !== gl.version) { | ||||
| 			console.warn( | ||||
| 				"Detected Let's Encrypt v01 URL (deprecated). Changing version to v01." | ||||
| 			); | ||||
|       console.warn("Detected Let's Encrypt v01 URL (deprecated). Changing version to v01."); | ||||
|       gl.version = 'v01'; | ||||
|     } | ||||
|   } | ||||
| @ -330,112 +249,74 @@ Greenlock.create = function(gl) { | ||||
|   // END VERSION MADNESS //
 | ||||
|   /////////////////////////
 | ||||
| 
 | ||||
| 	gl.acme = | ||||
| 		gl.acme || | ||||
| 		ACME.create({ | ||||
| 			debug: gl.debug, | ||||
| 			skipChallengeTest: gl.skipChallengeTest, | ||||
| 			skipDryRun: gl.skipDryRun | ||||
| 		}); | ||||
| 
 | ||||
| 
 | ||||
|   gl.acme = gl.acme || ACME.create({ debug: gl.debug }); | ||||
|   if (gl.acme.create) { | ||||
|     gl.acme = gl.acme.create(gl); | ||||
|   } | ||||
|   gl.acme = promisifyAllSelf(gl.acme); | ||||
| 	gl._acmeOpts = | ||||
| 		(gl.acme.getOptions && gl.acme.getOptions()) || gl.acme.options || {}; | ||||
|   gl._acmeOpts = gl.acme.getOptions(); | ||||
|   Object.keys(gl._acmeOpts).forEach(function (key) { | ||||
|     if (!(key in gl)) { | ||||
|       gl[key] = gl._acmeOpts[key]; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
| 	try { | ||||
|   if (gl.store.create) { | ||||
|     gl.store = gl.store.create(gl); | ||||
|   } | ||||
|   gl.store = promisifyAllSelf(gl.store); | ||||
| 		gl.store.accounts = promisifyAllStore(gl.store.accounts); | ||||
| 		gl.store.certificates = promisifyAllStore(gl.store.certificates); | ||||
| 		gl._storeOpts = | ||||
| 			(gl.store.getOptions && gl.store.getOptions()) || | ||||
| 			gl.store.options || | ||||
| 			{}; | ||||
| 	} catch (e) { | ||||
| 		console.error(e); | ||||
| 		console.error( | ||||
| 			'\nPROBABLE CAUSE:\n' + | ||||
| 				'\tYour greenlock-store module should have a create function and return { options, accounts, certificates }\n' | ||||
| 		); | ||||
| 		process.exit(18); | ||||
| 		return; | ||||
| 	} | ||||
|   gl.store.accounts = promisifyAllSelf(gl.store.accounts); | ||||
|   gl.store.certificates = promisifyAllSelf(gl.store.certificates); | ||||
|   gl._storeOpts = gl.store.getOptions(); | ||||
|   Object.keys(gl._storeOpts).forEach(function (key) { | ||||
|     if (!(key in gl)) { | ||||
|       gl[key] = gl._storeOpts[key]; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
| 
 | ||||
|   //
 | ||||
|   // Backwards compat for <= v2.1.7
 | ||||
|   //
 | ||||
|   if (gl.challenge) { | ||||
| 		console.warn( | ||||
| 			"Deprecated use of gl.challenge. Use gl.challenges['" + | ||||
| 				Greenlock.challengeType + | ||||
| 				"'] instead." | ||||
| 		); | ||||
|     console.warn("Deprecated use of gl.challenge. Use gl.challenges['" + Greenlock.challengeType + "'] instead."); | ||||
|     gl.challenges[gl.challengeType] = gl.challenge; | ||||
| 		gl.challenge = undefined; | ||||
|   } | ||||
| 
 | ||||
| 	Object.keys(gl.challenges || {}).forEach(function(challengeType) { | ||||
|   Greenlock.challengeTypes.forEach(function (challengeType) { | ||||
|     var challenger = gl.challenges[challengeType]; | ||||
| 
 | ||||
|     if (!challenger) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (challenger.create) { | ||||
|       challenger = gl.challenges[challengeType] = challenger.create(gl); | ||||
|     } | ||||
| 		challenger = gl.challenges[challengeType] = promisifyAllSelf( | ||||
| 			challenger | ||||
| 		); | ||||
| 		gl['_challengeOpts_' + challengeType] = | ||||
| 			(challenger.getOptions && challenger.getOptions()) || | ||||
| 			challenger.options || | ||||
| 			{}; | ||||
| 		Object.keys(gl['_challengeOpts_' + challengeType]).forEach(function( | ||||
| 			key | ||||
| 		) { | ||||
|     if (!challenger.getOptionsAsync) { | ||||
|       challenger = gl.challenges[challengeType] = promisifyAllSelf(challenger); | ||||
|     } | ||||
|     gl['_challengeOpts_' + challengeType] = challenger.getOptions(); | ||||
|     Object.keys(gl['_challengeOpts_' + challengeType]).forEach(function (key) { | ||||
|       if (!(key in gl)) { | ||||
|         gl[key] = gl['_challengeOpts_' + challengeType][key]; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     // TODO wrap these here and now with tplCopy?
 | ||||
| 		if (!challenger.set || ![5, 2, 1].includes(challenger.set.length)) { | ||||
| 			throw new Error( | ||||
| 				'gl.challenges[' + | ||||
| 					challengeType + | ||||
| 					'].set receives the wrong number of arguments.' + | ||||
| 					' You must define setChallenge as function (opts) { return Promise.resolve(); }' | ||||
| 			); | ||||
|     if (!challenger.set || 5 !== challenger.set.length) { | ||||
|       throw new Error("gl.challenges[" + challengeType + "].set receives the wrong number of arguments." | ||||
|         + " You must define setChallenge as function (opts, domain, token, keyAuthorization, cb) { }"); | ||||
|     } | ||||
| 		if (challenger.get && ![4, 2, 1].includes(challenger.get.length)) { | ||||
| 			throw new Error( | ||||
| 				'gl.challenges[' + | ||||
| 					challengeType + | ||||
| 					'].get receives the wrong number of arguments.' + | ||||
| 					' You must define getChallenge as function (opts) { return Promise.resolve(); }' | ||||
| 			); | ||||
|     if (challenger.get && 4 !== challenger.get.length) { | ||||
|       throw new Error("gl.challenges[" + challengeType + "].get receives the wrong number of arguments." | ||||
|         + " You must define getChallenge as function (opts, domain, token, cb) { }"); | ||||
|     } | ||||
| 		if ( | ||||
| 			!challenger.remove || | ||||
| 			![4, 2, 1].includes(challenger.remove.length) | ||||
| 		) { | ||||
| 			throw new Error( | ||||
| 				'gl.challenges[' + | ||||
| 					challengeType + | ||||
| 					'].remove receives the wrong number of arguments.' + | ||||
| 					' You must define removeChallenge as function (opts) { return Promise.resolve(); }' | ||||
| 			); | ||||
|     if (!challenger.remove || 4 !== challenger.remove.length) { | ||||
|       throw new Error("gl.challenges[" + challengeType + "].remove receives the wrong number of arguments." | ||||
|         + " You must define removeChallenge as function (opts, domain, token, cb) { }"); | ||||
|     } | ||||
| 
 | ||||
| /* | ||||
| @ -452,44 +333,6 @@ Greenlock.create = function(gl) { | ||||
| 
 | ||||
|   gl.sni = gl.sni || null; | ||||
|   gl.tlsOptions = gl.tlsOptions || gl.httpsOptions || {}; | ||||
| 
 | ||||
| 	// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||
| 	gl._updateServernames = function(cert) { | ||||
| 		if (!gl._certnames) { | ||||
| 			gl._certnames = {}; | ||||
| 		} | ||||
| 
 | ||||
| 		// Note: Any given domain could exist on multiple certs
 | ||||
| 		// (especially during renewal where some may be added)
 | ||||
| 		// hence we use a separate object for each domain and list each domain on it
 | ||||
| 		// to get the minimal full set associated with each cert and domain
 | ||||
| 		var allDomains = [cert.subject].concat(cert.altnames.slice(0)); | ||||
| 		allDomains.forEach(function(name) { | ||||
| 			name = name.toLowerCase(); | ||||
| 			if (!gl._certnames[name]) { | ||||
| 				gl._certnames[name] = {}; | ||||
| 			} | ||||
| 			allDomains.forEach(function(name2) { | ||||
| 				name2 = name2.toLowerCase(); | ||||
| 				gl._certnames[name][name2] = true; | ||||
| 			}); | ||||
| 		}); | ||||
| 	}; | ||||
| 	gl._checkServername = function(safeHost, servername) { | ||||
| 		// odd, but acceptable
 | ||||
| 		if (!safeHost || !servername) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		if (safeHost === servername) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		// connection established with servername and session is re-used for allowed name
 | ||||
| 		if (gl._certnames[servername] && gl._certnames[servername][safeHost]) { | ||||
| 			return true; | ||||
| 		} | ||||
| 		return false; | ||||
| 	}; | ||||
| 
 | ||||
|   if (!gl.tlsOptions.SNICallback) { | ||||
|     if (!gl.getCertificatesAsync && !gl.getCertificates) { | ||||
|       if (Array.isArray(gl.approveDomains)) { | ||||
| @ -497,56 +340,31 @@ Greenlock.create = function(gl) { | ||||
|         gl.approveDomains = null; | ||||
|       } | ||||
|       if (!gl.approveDomains) { | ||||
| 				gl.approveDomains = function(lexOpts, cb) { | ||||
|         gl.approvedDomains = gl.approvedDomains || []; | ||||
|         gl.approveDomains = function (lexOpts, certs, cb) { | ||||
|           var err; | ||||
|           var emsg; | ||||
| 
 | ||||
|           if (!gl.email) { | ||||
| 						throw new Error( | ||||
| 							'le-sni-auto is not properly configured. Missing email' | ||||
| 						); | ||||
|             throw new Error("le-sni-auto is not properly configured. Missing email"); | ||||
|           } | ||||
|           if (!gl.agreeTos) { | ||||
| 						throw new Error( | ||||
| 							'le-sni-auto is not properly configured. Missing agreeTos' | ||||
| 						); | ||||
|             throw new Error("le-sni-auto is not properly configured. Missing agreeTos"); | ||||
|           } | ||||
| 					if (!/[a-z]/i.test(lexOpts.domain)) { | ||||
| 						cb( | ||||
| 							new Error( | ||||
| 								'le-sni-auto does not allow IP addresses in SNI' | ||||
| 							) | ||||
| 						); | ||||
| 						return; | ||||
|           if (!gl.approvedDomains.length) { | ||||
|             throw new Error("le-sni-auto is not properly configured. Missing approveDomains(domain, certs, callback)"); | ||||
|           } | ||||
| 
 | ||||
| 					if (!Array.isArray(gl.approvedDomains)) { | ||||
| 						// The acme-v2 package uses pre-flight test challenges to
 | ||||
| 						// verify that each requested domain is hosted by the server
 | ||||
| 						// these checks are sufficient for most use cases
 | ||||
| 						return cb(null, lexOpts); | ||||
| 					} | ||||
| 
 | ||||
| 					if ( | ||||
| 						lexOpts.domains.every(function(domain) { | ||||
|           if (lexOpts.domains.every(function (domain) { | ||||
|             return -1 !== gl.approvedDomains.indexOf(domain); | ||||
| 						}) | ||||
| 					) { | ||||
| 						// commented this out because people expect to be able to edit the list of domains
 | ||||
| 						// lexOpts.domains = gl.approvedDomains.slice(0);
 | ||||
|           })) { | ||||
|             lexOpts.domains = gl.approvedDomains.slice(0); | ||||
|             lexOpts.email = gl.email; | ||||
|             lexOpts.agreeTos = gl.agreeTos; | ||||
| 						lexOpts.communityMember = gl.communityMember; | ||||
| 						lexOpts.telemetry = gl.telemetry; | ||||
| 						return cb(null, lexOpts); | ||||
|             lexOpts.communityMember = lexOpts.communityMember; | ||||
|             return cb(null, { options: lexOpts, certs: certs }); | ||||
|           } | ||||
| 
 | ||||
| 					emsg = | ||||
| 						"tls SNI for '" + | ||||
| 						lexOpts.domains.join(',') + | ||||
| 						"' rejected: not in list '" + | ||||
| 						gl.approvedDomains + | ||||
| 						"'"; | ||||
|           emsg = "tls SNI for '" + lexOpts.domains.join(',') + "' rejected: not in list '" + gl.approvedDomains + "'"; | ||||
|           log(gl.debug, emsg, lexOpts.domains, gl.approvedDomains); | ||||
|           err = new Error(emsg); | ||||
|           err.code = 'E_REJECT_SNI'; | ||||
| @ -556,161 +374,57 @@ Greenlock.create = function(gl) { | ||||
| 
 | ||||
|       gl.getCertificates = function (domain, certs, cb) { | ||||
|         // certs come from current in-memory cache, not lookup
 | ||||
| 				log( | ||||
| 					gl.debug, | ||||
| 					'gl.getCertificates called for', | ||||
| 					domain, | ||||
| 					'with certs for', | ||||
| 					(certs && certs.altnames) || 'NONE' | ||||
| 				); | ||||
| 				var opts = { | ||||
| 					domain: domain, | ||||
| 					domains: (certs && certs.altnames) || [domain], | ||||
| 					certs: certs, | ||||
| 					certificate: {}, | ||||
| 					account: {} | ||||
| 				}; | ||||
| 				opts.wildname = | ||||
| 					'*.' + | ||||
| 					(domain || '') | ||||
| 						.split('.') | ||||
| 						.slice(1) | ||||
| 						.join('.'); | ||||
|         log(gl.debug, 'gl.getCertificates called for', domain, 'with certs for', certs && certs.altnames || 'NONE'); | ||||
|         var opts = { domain: domain, domains: certs && certs.altnames || [ domain ] }; | ||||
| 
 | ||||
| 				function cb2(results) { | ||||
| 					log( | ||||
| 						gl.debug, | ||||
| 						'gl.approveDomains called with certs for', | ||||
| 						(results.certs && results.certs.altnames) || 'NONE', | ||||
| 						'and options:' | ||||
| 					); | ||||
| 					log(gl.debug, results.options || results); | ||||
| 					var err; | ||||
| 					if (!results) { | ||||
| 						err = new Error('E_REJECT_SNI'); | ||||
| 						err.code = 'E_REJECT_SNI'; | ||||
| 						eb2(err); | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					var options = results.options || results; | ||||
| 					if (opts !== options) { | ||||
| 						Object.keys(options).forEach(function(key) { | ||||
| 							if ( | ||||
| 								'undefined' !== typeof options[key] && | ||||
| 								'domain' !== key | ||||
| 							) { | ||||
| 								opts[key] = options[key]; | ||||
| 							} | ||||
| 						}); | ||||
| 						options = opts; | ||||
| 					} | ||||
| 					if ( | ||||
| 						Array.isArray(options.altnames) && | ||||
| 						options.altnames.length | ||||
| 					) { | ||||
| 						options.domains = options.altnames; | ||||
| 					} | ||||
| 					options.altnames = options.domains; | ||||
| 					// just in case we get a completely different object from the one we originally created
 | ||||
| 					if (!options.account) { | ||||
| 						options.account = {}; | ||||
| 					} | ||||
| 					if (!options.certificate) { | ||||
| 						options.certificate = {}; | ||||
| 					} | ||||
| 					if (results.certs) { | ||||
| 						log(gl.debug, 'gl renewing'); | ||||
| 						return gl.core.certificates | ||||
| 							.renewAsync(options, results.certs) | ||||
| 							.then( | ||||
| 								function(certs) { | ||||
| 									// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||
| 									gl._updateServernames(certs); | ||||
| 									cb(null, certs); | ||||
| 								}, | ||||
| 								function(e) { | ||||
| 									console.debug( | ||||
| 										"Error renewing certificate for '" + | ||||
| 											domain + | ||||
| 											"':" | ||||
| 									); | ||||
| 									console.debug(e); | ||||
| 									console.error(''); | ||||
| 									cb(e); | ||||
| 								} | ||||
| 							); | ||||
| 					} else { | ||||
| 						log( | ||||
| 							gl.debug, | ||||
| 							'gl getting from disk or registering new' | ||||
| 						); | ||||
| 						return gl.core.certificates.getAsync(options).then( | ||||
| 							function(certs) { | ||||
| 								// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||
| 								gl._updateServernames(certs); | ||||
| 								cb(null, certs); | ||||
| 							}, | ||||
| 							function(e) { | ||||
| 								console.debug( | ||||
| 									"Error loading/registering certificate for '" + | ||||
| 										domain + | ||||
| 										"':" | ||||
| 								); | ||||
| 								console.debug(e); | ||||
| 								console.error(''); | ||||
| 								cb(e); | ||||
| 							} | ||||
| 						); | ||||
| 					} | ||||
| 				} | ||||
| 				function eb2(_err) { | ||||
|         try { | ||||
|           gl.approveDomains(opts, certs, function (_err, results) { | ||||
|             if (_err) { | ||||
|               if (false !== gl.logRejectedDomains) { | ||||
| 						console.error( | ||||
| 							"[Error] approveDomains rejected tls sni '" + | ||||
| 								domain + | ||||
| 								"'" | ||||
| 						); | ||||
| 						console.error( | ||||
| 							'[Error] (see https://git.coolaj86.com/coolaj86/greenlock.js/issues/11)' | ||||
| 						); | ||||
|                 console.error("[Error] approveDomains rejected tls sni '" + domain + "'"); | ||||
|                 console.error("[Error] (see https://git.coolaj86.com/coolaj86/greenlock.js/issues/11)"); | ||||
|                 if ('E_REJECT_SNI' !== _err.code) { | ||||
| 							console.error( | ||||
| 								'[Error] This is the rejection message:' | ||||
| 							); | ||||
|                   console.error("[Error] This is the rejection message:"); | ||||
|                   console.error(_err.message); | ||||
|                 } | ||||
| 						console.error(''); | ||||
|                 console.error(""); | ||||
|               } | ||||
|               cb(_err); | ||||
|               return; | ||||
|             } | ||||
| 				function mb2(_err, results) { | ||||
| 					if (_err) { | ||||
| 						eb2(_err); | ||||
| 						return; | ||||
| 					} | ||||
| 					cb2(results); | ||||
| 				} | ||||
| 
 | ||||
| 				try { | ||||
| 					if (1 === gl.approveDomains.length) { | ||||
| 						Promise.resolve(gl.approveDomains(opts)) | ||||
| 							.then(cb2) | ||||
| 							.catch(eb2); | ||||
| 					} else if (2 === gl.approveDomains.length) { | ||||
| 						gl.approveDomains(opts, mb2); | ||||
| 					} else { | ||||
| 						gl.approveDomains(opts, certs, mb2); | ||||
|             log(gl.debug, 'gl.approveDomains called with certs for', results.certs && results.certs.altnames || 'NONE', 'and options:'); | ||||
|             log(gl.debug, results.options); | ||||
| 
 | ||||
|             if (results.certs) { | ||||
|               log(gl.debug, 'gl renewing'); | ||||
|               return gl.core.certificates.renewAsync(results.options, results.certs).then( | ||||
|                 function (certs) { cb(null, certs); } | ||||
|               , function (e) { | ||||
|                   console.debug("Error renewing certificate for '" + domain + "':"); | ||||
|                   console.debug(e); | ||||
|                   console.error(""); | ||||
|                   cb(e); | ||||
|                 } | ||||
|               ); | ||||
|             } | ||||
|             else { | ||||
|               log(gl.debug, 'gl getting from disk or registering new'); | ||||
|               return gl.core.certificates.getAsync(results.options).then( | ||||
|                 function (certs) { cb(null, certs); } | ||||
|               , function (e) { | ||||
|                   console.debug("Error loading/registering certificate for '" + domain + "':"); | ||||
|                   console.debug(e); | ||||
|                   console.error(""); | ||||
|                   cb(e); | ||||
|                 } | ||||
|               ); | ||||
|             } | ||||
|           }); | ||||
|         } catch(e) { | ||||
| 					console.error( | ||||
| 						'[ERROR] Something went wrong in approveDomains:' | ||||
| 					); | ||||
|           console.error("[ERROR] Something went wrong in approveDomains:"); | ||||
|           console.error(e); | ||||
| 					console.error( | ||||
| 						"BUT WAIT! Good news: It's probably your fault, so you can probably fix it." | ||||
| 					); | ||||
|           console.error("BUT WAIT! Good news: It's probably your fault, so you can probably fix it."); | ||||
|         } | ||||
|       }; | ||||
|     } | ||||
| @ -718,32 +432,22 @@ Greenlock.create = function(gl) { | ||||
|     if (gl.sni.create) { | ||||
|       gl.sni = gl.sni.create(gl); | ||||
|     } | ||||
| 		gl.tlsOptions.SNICallback = function(_domain, cb) { | ||||
|     gl.tlsOptions.SNICallback = function (domain, cb) { | ||||
|       // format and (lightly) sanitize sni so that users can be naive
 | ||||
|       // and not have to worry about SQL injection or fs discovery
 | ||||
| 			var domain = (_domain || '').toLowerCase(); | ||||
|       domain = (domain||'').toLowerCase(); | ||||
|       // hostname labels allow a-z, 0-9, -, and are separated by dots
 | ||||
|       // _ is sometimes allowed
 | ||||
| 			// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
 | ||||
| 			if ( | ||||
| 				!gl.__sni_allow_dangerous_names && | ||||
| 				(!/^[a-z0-9_\.\-]+$/i.test(domain) || | ||||
| 					-1 !== domain.indexOf('..')) | ||||
| 			) { | ||||
|       if (!/^[a-z0-9_\.\-]+$/i.test(domain) || -1 !== domain.indexOf('..')) { | ||||
|         log(gl.debug, "invalid sni '" + domain + "'"); | ||||
| 				cb(new Error('invalid SNI')); | ||||
|         cb(new Error("invalid SNI")); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
| 				gl.sni.sniCallback( | ||||
| 					(gl.__sni_preserve_case && _domain) || domain, | ||||
| 					cb | ||||
| 				); | ||||
|         gl.sni.sniCallback(domain, cb); | ||||
|       } catch(e) { | ||||
| 				console.error( | ||||
| 					'[ERROR] Something went wrong in the SNICallback:' | ||||
| 				); | ||||
|         console.error("[ERROR] Something went wrong in the SNICallback:"); | ||||
|         console.error(e); | ||||
|         cb(e); | ||||
|       } | ||||
| @ -776,95 +480,5 @@ Greenlock.create = function(gl) { | ||||
|     gl.middleware = gl.middleware.create(gl); | ||||
|   } | ||||
| 
 | ||||
| 	//var SERVERNAME_RE = /^[a-z0-9\.\-_]+$/;
 | ||||
| 	var SERVERNAME_G = /[^a-z0-9\.\-_]/; | ||||
| 	gl.middleware.sanitizeHost = function(app) { | ||||
| 		return function(req, res, next) { | ||||
| 			function realNext() { | ||||
| 				if ('function' === typeof app) { | ||||
| 					app(req, res); | ||||
| 				} else if ('function' === typeof next) { | ||||
| 					next(); | ||||
| 				} else { | ||||
| 					res.statusCode = 500; | ||||
| 					res.end('Error: no middleware assigned'); | ||||
| 				} | ||||
| 			} | ||||
| 			// Get the host:port combo, if it exists
 | ||||
| 			var host = (req.headers.host || '').split(':'); | ||||
| 
 | ||||
| 			// if not, move along
 | ||||
| 			if (!host[0]) { | ||||
| 				realNext(); | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			// if so, remove non-allowed characters
 | ||||
| 			var safehost = host[0].toLowerCase().replace(SERVERNAME_G, ''); | ||||
| 
 | ||||
| 			// if there were unallowed characters, complain
 | ||||
| 			if ( | ||||
| 				!gl.__sni_allow_dangerous_names && | ||||
| 				safehost.length !== host[0].length | ||||
| 			) { | ||||
| 				res.statusCode = 400; | ||||
| 				res.end("Malformed HTTP Header: 'Host: " + host[0] + "'"); | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			// make lowercase
 | ||||
| 			if (!gl.__sni_preserve_case) { | ||||
| 				host[0] = safehost; | ||||
| 				req.headers.host = host.join(':'); | ||||
| 			} | ||||
| 
 | ||||
| 			// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
 | ||||
| 			if (req.socket.encrypted && !gl.__sni_allow_domain_fronting) { | ||||
| 				if (req.socket && 'string' === typeof req.socket.servername) { | ||||
| 					// Workaround for https://github.com/nodejs/node/issues/22389
 | ||||
| 					if ( | ||||
| 						!gl._checkServername( | ||||
| 							safehost, | ||||
| 							req.socket.servername.toLowerCase() | ||||
| 						) | ||||
| 					) { | ||||
| 						res.statusCode = 400; | ||||
| 						res.setHeader( | ||||
| 							'Content-Type', | ||||
| 							'text/html; charset=utf-8' | ||||
| 						); | ||||
| 						res.end( | ||||
| 							'<h1>Domain Fronting Error</h1>' + | ||||
| 								"<p>This connection was secured using TLS/SSL for '" + | ||||
| 								req.socket.servername.toLowerCase() + | ||||
| 								"'</p>" + | ||||
| 								"<p>The HTTP request specified 'Host: " + | ||||
| 								safehost + | ||||
| 								"', which is (obviously) different.</p>" + | ||||
| 								'<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>' | ||||
| 						); | ||||
| 						return; | ||||
| 					} | ||||
| 				} else if ( | ||||
| 					safehost && | ||||
| 					!gl.middleware.sanitizeHost._skip_fronting_check | ||||
| 				) { | ||||
| 					// TODO how to handle wrapped sockets, as with telebit?
 | ||||
| 					console.warn( | ||||
| 						'\n\n\n[greenlock] WARN: no string for req.socket.servername,' + | ||||
| 							" skipping fronting check for '" + | ||||
| 							safehost + | ||||
| 							"'\n\n\n" | ||||
| 					); | ||||
| 					gl.middleware.sanitizeHost._skip_fronting_check = true; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// carry on
 | ||||
| 			realNext(); | ||||
| 		}; | ||||
| 	}; | ||||
| 	gl.middleware.sanitizeHost._skip_fronting_check = false; | ||||
| 
 | ||||
|   return gl; | ||||
| }; | ||||
|  | ||||
| @ -1,80 +1,29 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| function addCommunityMember(opts) { | ||||
| 	// { name, version, email, domains, action, communityMember, telemetry }
 | ||||
| function addCommunityMember(pkg, email, domains) { | ||||
|   setTimeout(function () { | ||||
|     var https = require('https'); | ||||
| 	var req = https.request( | ||||
| 		{ | ||||
| 			hostname: 'api.ppl.family', | ||||
| 			port: 443, | ||||
| 			path: '/api/ppl.family/public/list', | ||||
| 			method: 'POST', | ||||
| 			headers: { | ||||
|     var req = https.request({ | ||||
|       hostname: 'api.ppl.family' | ||||
|     , port: 443 | ||||
|     , path: '/api/ppl.family/public/list' | ||||
|     , method: 'POST' | ||||
|     , headers: { | ||||
|         'Content-Type': 'application/json' | ||||
|       } | ||||
| 		}, | ||||
| 		function(err, resp) { | ||||
| 			if (err) { | ||||
| 				return; | ||||
| 			} | ||||
|     }, function (err, resp) { | ||||
|       if (err) { return; } | ||||
|       resp.on('data', function () {}); | ||||
| 		} | ||||
| 	); | ||||
| 	req.on('error', function(error) { | ||||
| 		/* ignore */ | ||||
|     }); | ||||
| 	var os = require('os'); | ||||
| 	var data = { | ||||
| 		address: opts.email, | ||||
| 		// greenlock-security is transactional and security only
 | ||||
| 		list: opts.communityMember | ||||
| 			? opts.name + '@ppl.family' | ||||
| 			: 'greenlock-security@ppl.family', | ||||
| 		action: opts.action, // reg | renew
 | ||||
| 		package: opts.name, | ||||
| 		// hashed for privacy, but so we can still get some telemetry and inform users
 | ||||
| 		// if abnormal things are happening (like several registrations for the same domain each day)
 | ||||
| 		domain: (opts.domains || []) | ||||
| 			.map(function(d) { | ||||
| 				return require('crypto') | ||||
| 					.createHash('sha1') | ||||
| 					.update(d) | ||||
| 					.digest('base64') | ||||
| 					.replace(/\//g, '_') | ||||
| 					.replace(/\+/g, '-') | ||||
| 					.replace(/=/g, ''); | ||||
| 			}) | ||||
| 			.join(',') | ||||
| 	}; | ||||
| 	if (false !== opts.telemetry) { | ||||
| 		data.arch = process.arch || os.arch(); | ||||
| 		data.platform = process.platform || os.platform(); | ||||
| 		data.release = os.release(); | ||||
| 		data.version = opts.version; | ||||
| 		data.node = process.version; | ||||
| 	} | ||||
| 	req.write(JSON.stringify(data, 2, null)); | ||||
|     req.write(JSON.stringify({ | ||||
|       address: email | ||||
|     , comment: (pkg || 'community') + '  member w/ ' + (domains||[]).map(function (d) { | ||||
|         return require('crypto').createHash('sha1').update(d).digest('base64') | ||||
|           .replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, ''); | ||||
|       }).join(',') | ||||
|     })); | ||||
|     req.end(); | ||||
|   }, 50); | ||||
| } | ||||
| 
 | ||||
| function delay(ms) { | ||||
| 	return new Promise(function(resolve) { | ||||
| 		return setTimeout(resolve, ms); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| module.exports.add = function(opts) { | ||||
| 	return delay(50) | ||||
| 		.then(() => { | ||||
| 			return addCommunityMember(opts); | ||||
| 		}) | ||||
| 		.catch(function(ex) { | ||||
| 			/* ignore */ | ||||
| 		}); | ||||
| }; | ||||
| 
 | ||||
| if (require.main === module) { | ||||
| 	//addCommunityMember('greenlock-express.js', 'reg', 'coolaj86+test42@gmail.com', ['coolaj86.com'], true);
 | ||||
| 	//addCommunityMember('greenlock.js', 'reg', 'coolaj86+test37@gmail.com', ['oneal.im'], false);
 | ||||
| 	//addCommunityMember('greenlock.js', 'reg', 'coolaj86+test11@gmail.com', ['ppl.family'], true);
 | ||||
| } | ||||
| module.exports.add = addCommunityMember; | ||||
|  | ||||
| @ -1,23 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| function requireBluebird() { | ||||
| 	try { | ||||
| 		return require('bluebird'); | ||||
| 	} catch (e) { | ||||
| 		console.error(''); | ||||
| 		console.error( | ||||
| 			"DON'T PANIC. You're running an old version of node with incomplete Promise support." | ||||
| 		); | ||||
| 		console.error('EASY FIX: `npm install --save bluebird`'); | ||||
| 		console.error(''); | ||||
| 		throw e; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| if ('undefined' === typeof Promise) { | ||||
| 	global.Promise = requireBluebird(); | ||||
| } | ||||
| 
 | ||||
| if ('function' !== typeof require('util').promisify) { | ||||
| 	require('util').promisify = requireBluebird().promisify; | ||||
| } | ||||
							
								
								
									
										829
									
								
								lib/core.js
									
									
									
									
									
								
							
							
						
						
									
										829
									
								
								lib/core.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -6,38 +6,36 @@ function _log(debug) { | ||||
|   if (debug) { | ||||
|     var args = Array.prototype.slice.call(arguments); | ||||
|     args.shift(); | ||||
| 		args.unshift('[greenlock/lib/middleware.js]'); | ||||
|     args.unshift("[greenlock/lib/middleware.js]"); | ||||
|     console.log.apply(console, args); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| module.exports.create = function (gl) { | ||||
|   if (!gl.challenges['http-01'] || !gl.challenges['http-01'].get) { | ||||
| 		throw new Error('middleware requires challenge plugin with get method'); | ||||
|     throw new Error("middleware requires challenge plugin with get method"); | ||||
|   } | ||||
|   var log = gl.log || _log; | ||||
| 
 | ||||
| 	log(gl.debug, 'created middleware'); | ||||
|   log(gl.debug, "created middleware"); | ||||
|   return function (_app) { | ||||
|     if (_app && 'function' !== typeof _app) { | ||||
| 			throw new Error( | ||||
| 				'use greenlock.middleware() or greenlock.middleware(function (req, res) {})' | ||||
| 			); | ||||
|       throw new Error("use greenlock.middleware() or greenlock.middleware(function (req, res) {})"); | ||||
|     } | ||||
|     var prefix = gl.acmeChallengePrefix || '/.well-known/acme-challenge/'; | ||||
| 
 | ||||
|     return function (req, res, next) { | ||||
|       if (0 !== req.url.indexOf(prefix)) { | ||||
| 				log(gl.debug, 'no match, skipping middleware'); | ||||
|         log(gl.debug, "no match, skipping middleware"); | ||||
|         if ('function' === typeof _app) { | ||||
|           _app(req, res, next); | ||||
| 				} else if ('function' === typeof next) { | ||||
|         } | ||||
|         else if ('function' === typeof next) { | ||||
|           next(); | ||||
| 				} else { | ||||
|         } | ||||
|         else { | ||||
|           res.statusCode = 500; | ||||
| 					res.end( | ||||
| 						"[500] Developer Error: app.use('/', greenlock.middleware()) or greenlock.middleware(app)" | ||||
| 					); | ||||
|           res.end("[500] Developer Error: app.use('/', greenlock.middleware()) or greenlock.middleware(app)"); | ||||
|         } | ||||
|         return; | ||||
|       } | ||||
| @ -45,64 +43,26 @@ module.exports.create = function(gl) { | ||||
|       log(gl.debug, "this must be tinder, 'cuz it's a match!"); | ||||
| 
 | ||||
|       var token = req.url.slice(prefix.length); | ||||
| 			var hostname = | ||||
| 				req.hostname || | ||||
| 				(req.headers.host || '').toLowerCase().replace(/:.*/, ''); | ||||
|       var hostname = req.hostname || (req.headers.host || '').toLowerCase().replace(/:.*/, ''); | ||||
| 
 | ||||
| 			log(gl.debug, 'hostname', hostname, 'token', token); | ||||
|       log(gl.debug, "hostname", hostname, "token", token); | ||||
| 
 | ||||
|       var copy = utils.merge({ domains: [ hostname ] }, gl); | ||||
|       copy = utils.tplCopy(copy); | ||||
| 			copy.challenge = {}; | ||||
| 			copy.challenge.type = 'http-01'; // obviously...
 | ||||
| 			copy.challenge.identifier = { type: 'dns', value: hostname }; | ||||
| 			copy.challenge.wildcard = false; | ||||
| 			copy.challenge.token = token; | ||||
| 			copy.challenge.altname = hostname; | ||||
| 
 | ||||
| 			function cb(opts) { | ||||
| 				var secret = opts.keyAuthorization || opts; | ||||
| 				if (secret && 'string' === typeof secret) { | ||||
|       // TODO tpl copy?
 | ||||
|       // TODO need to restore challengeType
 | ||||
|       gl.challenges['http-01'].get(copy, hostname, token, function (err, secret) { | ||||
|         if (err || !token) { | ||||
|           res.statusCode = 404; | ||||
|           res.setHeader('Content-Type', 'application/json; charset=utf-8'); | ||||
|           res.end('{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }'); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         res.setHeader('Content-Type', 'text/plain; charset=utf-8'); | ||||
|         res.end(secret); | ||||
| 					return; | ||||
| 				} | ||||
| 				eb(new Error("couldn't retrieve keyAuthorization")); | ||||
| 				return; | ||||
| 			} | ||||
| 			function eb(/*err*/) { | ||||
| 				res.statusCode = 404; | ||||
| 				res.setHeader( | ||||
| 					'Content-Type', | ||||
| 					'application/json; charset=utf-8' | ||||
| 				); | ||||
| 				res.end( | ||||
| 					'{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }' | ||||
| 				); | ||||
| 				return; | ||||
| 			} | ||||
| 			function mb(err, result) { | ||||
| 				if (err) { | ||||
| 					eb(err); | ||||
| 					return; | ||||
| 				} | ||||
| 				cb(result); | ||||
| 			} | ||||
| 
 | ||||
| 			var challenger = gl.challenges['http-01'].get; | ||||
| 			if (1 === challenger.length) { | ||||
| 				/*global Promise*/ | ||||
| 				return Promise.resolve() | ||||
| 					.then(function() { | ||||
| 						return gl.challenges['http-01'].get(copy); | ||||
| 					}) | ||||
| 					.then(cb) | ||||
| 					.catch(eb); | ||||
| 			} else if (2 === challenger.length) { | ||||
| 				gl.challenges['http-01'].get(copy, mb); | ||||
| 			} else { | ||||
| 				gl.challenges['http-01'].get(copy, hostname, token, mb); | ||||
| 			} | ||||
|       }); | ||||
|     }; | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -1,24 +0,0 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var utils = require('./utils.js'); | ||||
| var cert = { subject: 'example.com', altnames: ['*.bar.com', 'foo.net'] }; | ||||
| if (utils.certHasDomain(cert, 'bad.com')) { | ||||
| 	throw new Error('allowed bad domain'); | ||||
| } | ||||
| if (!utils.certHasDomain(cert, 'example.com')) { | ||||
| 	throw new Error('missed subject'); | ||||
| } | ||||
| if (utils.certHasDomain(cert, 'bar.com')) { | ||||
| 	throw new Error('allowed bad (missing) sub'); | ||||
| } | ||||
| if (!utils.certHasDomain(cert, 'foo.bar.com')) { | ||||
| 	throw new Error("didn't allow valid wildcarded-domain"); | ||||
| } | ||||
| if (utils.certHasDomain(cert, 'dub.foo.bar.com')) { | ||||
| 	throw new Error('allowed sub-sub domain'); | ||||
| } | ||||
| if (!utils.certHasDomain(cert, 'foo.net')) { | ||||
| 	throw new Error('missed altname'); | ||||
| } | ||||
| 
 | ||||
| console.info('PASSED'); | ||||
							
								
								
									
										60
									
								
								lib/utils.js
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								lib/utils.js
									
									
									
									
									
								
							| @ -1,15 +1,17 @@ | ||||
| 'use strict'; | ||||
| require('./compat.js'); | ||||
| 
 | ||||
| var path = require('path'); | ||||
| var homeRe = new RegExp('^~(\\/|\\\\|\\' + path.sep + ')'); | ||||
| var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")"); | ||||
| // very basic check. Allows *.example.com.
 | ||||
| var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/; | ||||
| var punycode = require('punycode'); | ||||
| var dnsResolveMxAsync = require('util').promisify(require('dns').resolveMx); | ||||
| var promisify = (require('util').promisify || require('bluebird').promisify); | ||||
| var dnsResolveMxAsync = promisify(require('dns').resolveMx); | ||||
| 
 | ||||
| module.exports.attachCertInfo = function (results) { | ||||
| 	var certInfo = require('cert-info').info(results.cert); | ||||
|   // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | ||||
|   var getCertInfo = require('certpem').info; | ||||
|   var certInfo = getCertInfo(results.cert); | ||||
| 
 | ||||
|   // subject, altnames, issuedAt, expiresAt
 | ||||
|   Object.keys(certInfo).forEach(function (key) { | ||||
| @ -19,23 +21,6 @@ module.exports.attachCertInfo = function(results) { | ||||
|   return results; | ||||
| }; | ||||
| 
 | ||||
| module.exports.certHasDomain = function(certInfo, _domain) { | ||||
| 	var names = (certInfo.altnames || []).slice(0); | ||||
| 	names.push(certInfo.subject); | ||||
| 	return names.some(function(name) { | ||||
| 		var domain = _domain.toLowerCase(); | ||||
| 		name = name.toLowerCase(); | ||||
| 		if ('*.' === name.substr(0, 2)) { | ||||
| 			name = name.substr(2); | ||||
| 			domain = domain | ||||
| 				.split('.') | ||||
| 				.slice(1) | ||||
| 				.join('.'); | ||||
| 		} | ||||
| 		return name === domain; | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| module.exports.isValidDomain = function (domain) { | ||||
|   if (re.test(domain)) { | ||||
|     return domain; | ||||
| @ -57,29 +42,11 @@ module.exports.merge = function(/*defaults, args*/) { | ||||
| 
 | ||||
|   allDefaults.forEach(function (defaults) { | ||||
|     Object.keys(defaults).forEach(function (key) { | ||||
| 			/* | ||||
|       if ('challenges' === key && copy[key] && defaults[key]) { | ||||
|         Object.keys(defaults[key]).forEach(function (k) { | ||||
|           copy[key][k] = defaults[key][k]; | ||||
|         }); | ||||
|       } else { | ||||
|         copy[key] = defaults[key]; | ||||
|       } | ||||
|     */ | ||||
|       copy[key] = defaults[key]; | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   Object.keys(args).forEach(function (key) { | ||||
| 		/* | ||||
|     if ('challenges' === key && copy[key] && args[key]) { | ||||
|         Object.keys(args[key]).forEach(function (k) { | ||||
|           copy[key][k] = args[key][k]; | ||||
|         }); | ||||
|     } else { | ||||
|       copy[key] = args[key]; | ||||
|     } | ||||
|     */ | ||||
|     copy[key] = args[key]; | ||||
|   }); | ||||
| 
 | ||||
| @ -91,7 +58,7 @@ module.exports.tplCopy = function(copy) { | ||||
|   var tplKeys; | ||||
| 
 | ||||
|   copy.hostnameGet = function (copy) { | ||||
| 		return copy.subject || (copy.domains || [])[0] || copy.domain; | ||||
|     return (copy.domains || [])[0] || copy.domain; | ||||
|   }; | ||||
| 
 | ||||
|   Object.keys(copy).forEach(function (key) { | ||||
| @ -144,22 +111,17 @@ module.exports.testEmail = function(email) { | ||||
|     return Promise.reject(err); | ||||
|   } | ||||
| 
 | ||||
| 	return dnsResolveMxAsync(parts[1]).then( | ||||
| 		function(records) { | ||||
|   return dnsResolveMxAsync(parts[1]).then(function (records) { | ||||
|     // records only returns when there is data
 | ||||
|     if (!records.length) { | ||||
| 				throw new Error( | ||||
| 					'sanity check fail: success, but no MX records returned' | ||||
| 				); | ||||
|       throw new Error("sanity check fail: success, but no MX records returned"); | ||||
|     } | ||||
|     return email; | ||||
| 		}, | ||||
| 		function(err) { | ||||
|   }, function (err) { | ||||
|     if ('ENODATA' === err.code) { | ||||
|       err = new Error("no MX records found for '" + parts[1] + "'"); | ||||
|       err.code = 'E_EMAIL'; | ||||
|       return Promise.reject(err); | ||||
|     } | ||||
| 		} | ||||
| 	); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
							
								
								
									
										117
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										117
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,117 +0,0 @@ | ||||
| { | ||||
| 	"name": "greenlock", | ||||
| 	"version": "2.8.9", | ||||
| 	"lockfileVersion": 1, | ||||
| 	"requires": true, | ||||
| 	"dependencies": { | ||||
| 		"@root/mkdirp": { | ||||
| 			"version": "1.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||
| 			"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | ||||
| 		}, | ||||
| 		"@root/request": { | ||||
| 			"version": "1.3.11", | ||||
| 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz", | ||||
| 			"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw==" | ||||
| 		}, | ||||
| 		"acme": { | ||||
| 			"version": "1.3.5", | ||||
| 			"resolved": "https://registry.npmjs.org/acme/-/acme-1.3.5.tgz", | ||||
| 			"integrity": "sha512-KIFVyMho7y3RxRSTzkuX031TmfXwzl0ioy8+r2pnfLz6YWFQ5q7a/cYUDTgIbrFMPe/syY26Qv1DOdHQ5ARWcw==", | ||||
| 			"requires": { | ||||
| 				"acme-v2": "^1.8.6" | ||||
| 			} | ||||
| 		}, | ||||
| 		"acme-dns-01-cli": { | ||||
| 			"version": "3.0.7", | ||||
| 			"resolved": "https://registry.npmjs.org/acme-dns-01-cli/-/acme-dns-01-cli-3.0.7.tgz", | ||||
| 			"integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" | ||||
| 		}, | ||||
| 		"acme-v2": { | ||||
| 			"version": "1.8.6", | ||||
| 			"resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.8.6.tgz", | ||||
| 			"integrity": "sha512-LWdicUYHTGDtYX7LlgsQurmM9txwfAFydg7mQLPKHrFMnNNtfJEtHC2fWfr+pFGNb3XKIbvyFUoyFB6cOmWRpA==", | ||||
| 			"requires": { | ||||
| 				"@root/request": "^1.3.11", | ||||
| 				"rsa-compat": "^2.0.8" | ||||
| 			} | ||||
| 		}, | ||||
| 		"cert-info": { | ||||
| 			"version": "1.5.1", | ||||
| 			"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", | ||||
| 			"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" | ||||
| 		}, | ||||
| 		"eckles": { | ||||
| 			"version": "1.4.1", | ||||
| 			"resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz", | ||||
| 			"integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==" | ||||
| 		}, | ||||
| 		"greenlock-store-fs": { | ||||
| 			"version": "3.0.2", | ||||
| 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.0.2.tgz", | ||||
| 			"integrity": "sha512-t4So75yKs1+7TqmxD5UKdf+zOQU0/4o0lb2auf5zUcAo7fwwNLOAXyWnnZRL3WuFBUiBGh1qXWleuMua0d3LPg==", | ||||
| 			"requires": { | ||||
| 				"@root/mkdirp": "^1.0.0", | ||||
| 				"safe-replace": "^1.1.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"keypairs": { | ||||
| 			"version": "1.2.14", | ||||
| 			"resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz", | ||||
| 			"integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==", | ||||
| 			"requires": { | ||||
| 				"eckles": "^1.4.1", | ||||
| 				"rasha": "^1.2.4" | ||||
| 			} | ||||
| 		}, | ||||
| 		"le-challenge-fs": { | ||||
| 			"version": "2.0.9", | ||||
| 			"resolved": "https://registry.npmjs.org/le-challenge-fs/-/le-challenge-fs-2.0.9.tgz", | ||||
| 			"integrity": "sha512-stzI6rxd+aXGxBl87QJKKY/i/wl3uz6EoWzX2xSazJvCPSYBQys1RVNgOcf0SfUQPh6TBCFJFSJkiR4mznb4sg==", | ||||
| 			"requires": { | ||||
| 				"@root/mkdirp": "^1.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"le-sni-auto": { | ||||
| 			"version": "2.1.9", | ||||
| 			"resolved": "https://registry.npmjs.org/le-sni-auto/-/le-sni-auto-2.1.9.tgz", | ||||
| 			"integrity": "sha512-QmQHNwQDi/56GY8+qczFZ06FZbxaeJQjbjEhwwQHhkJ9IHhIQFkPfCT/OyDfLj4gqLIrg5ZX8CemxxVZnLEYfg==" | ||||
| 		}, | ||||
| 		"le-store-certbot": { | ||||
| 			"version": "2.2.3", | ||||
| 			"resolved": "https://registry.npmjs.org/le-store-certbot/-/le-store-certbot-2.2.3.tgz", | ||||
| 			"integrity": "sha512-c4ACR+v+JKMiAOOshLh6gdCKA7wIWR16+mROMLpQjq3rXJ3Vm8FaBHe2H+crT+flP+g7FmciAwUlfOJEJpIuCQ==", | ||||
| 			"requires": { | ||||
| 				"@root/mkdirp": "^1.0.0", | ||||
| 				"pyconf": "^1.1.7", | ||||
| 				"safe-replace": "^1.1.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"pyconf": { | ||||
| 			"version": "1.1.7", | ||||
| 			"resolved": "https://registry.npmjs.org/pyconf/-/pyconf-1.1.7.tgz", | ||||
| 			"integrity": "sha512-v4clh33m68sjtMsh8XMpjhGWb/MQODAYZ1y7ORG5Qv58UK25OddoB+oXyexgDkK8ttFui/lZm2sQDgA2Ftjfkw==", | ||||
| 			"requires": { | ||||
| 				"safe-replace": "^1.0.2" | ||||
| 			} | ||||
| 		}, | ||||
| 		"rasha": { | ||||
| 			"version": "1.2.5", | ||||
| 			"resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz", | ||||
| 			"integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw==" | ||||
| 		}, | ||||
| 		"rsa-compat": { | ||||
| 			"version": "2.0.8", | ||||
| 			"resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", | ||||
| 			"integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==", | ||||
| 			"requires": { | ||||
| 				"keypairs": "^1.2.14" | ||||
| 			} | ||||
| 		}, | ||||
| 		"safe-replace": { | ||||
| 			"version": "1.1.0", | ||||
| 			"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | ||||
| 			"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										59
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								package.json
									
									
									
									
									
								
							| @ -1,53 +1,74 @@ | ||||
| { | ||||
|   "name": "greenlock", | ||||
| 	"version": "2.8.9", | ||||
| 	"description": "Greenlock is Let's Encrypt (ACME) client for node.js", | ||||
| 	"homepage": "https://greenlock.domains/", | ||||
|   "version": "2.3.7", | ||||
|   "description": "Let's Encrypt for node.js on npm", | ||||
|   "main": "index.js", | ||||
|   "files": [ | ||||
|     "lib" | ||||
|   ], | ||||
|   "scripts": { | ||||
| 		"bump": "npm version -m \"chore(release): bump to v%s\"", | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
| 		"url": "https://git.rootprojects.org/root/greenlock.js.git" | ||||
|     "url": "https://git.coolaj86.com/coolaj86/greenlock.js.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "Let's Encrypt", | ||||
|     "letsencrypt", | ||||
|     "ACME", | ||||
|     "v2", | ||||
|     "v02", | ||||
|     "draft-11", | ||||
|     "draft-12", | ||||
|     "auto-sni", | ||||
|     "draft", | ||||
|     "11", | ||||
|     "12", | ||||
|     "Free SSL", | ||||
|     "Automated HTTPS", | ||||
|     "tls", | ||||
| 		"https" | ||||
|     "https", | ||||
|     "Greenlock", | ||||
|     "letsencrypt.org", | ||||
|     "le", | ||||
|     "le.js", | ||||
|     "node", | ||||
|     "nodejs", | ||||
|     "node.js", | ||||
|     "client" | ||||
|   ], | ||||
| 	"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)", | ||||
| 	"license": "MPL-2.0", | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "(MIT OR Apache-2.0)", | ||||
|   "bugs": { | ||||
| 		"url": "https://git.rootprojects.org/root/greenlock.js/issues" | ||||
|     "url": "https://git.coolaj86.com/coolaj86/greenlock.js/issues" | ||||
|   }, | ||||
|   "homepage": "https://git.coolaj86.com/coolaj86/greenlock.js", | ||||
|   "devDependencies": { | ||||
|     "request": "^2.75.0" | ||||
|   }, | ||||
|   "trulyOptionalDependencies": { | ||||
|     "bluebird": "^3.5.1", | ||||
| 		"le-acme-core": "^2.1.3" | ||||
|     "le-acme-core": "^2.1.3", | ||||
|     "ursa": "^0.9.4" | ||||
|   }, | ||||
|   "dependencies": { | ||||
| 		"acme": "^1.3.5", | ||||
| 		"acme-dns-01-cli": "^3.0.0", | ||||
| 		"acme-v2": "^1.8.7", | ||||
| 		"cert-info": "^1.5.1", | ||||
| 		"greenlock-store-fs": "^3.0.2", | ||||
| 		"keypairs": "^1.2.14", | ||||
|     "acme": "^1.0.6", | ||||
|     "acme-v2": "^1.2.0", | ||||
|     "asn1js": "^1.2.12", | ||||
|     "certpem": "^1.0.0", | ||||
|     "le-challenge-fs": "^2.0.2", | ||||
| 		"le-sni-auto": "^2.1.9", | ||||
| 		"le-store-certbot": "^2.2.4", | ||||
| 		"rsa-compat": "^2.0.8" | ||||
|     "le-sni-auto": "^2.1.3", | ||||
|     "le-store-certbot": "^2.1.7", | ||||
|     "node.extend": "^1.1.5", | ||||
|     "pkijs": "^1.3.27", | ||||
|     "rsa-compat": "^1.4.0" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=4.5" | ||||
|   }, | ||||
|   "gitDependencies": { | ||||
|     "acme": "git+https://git.coolaj86.com/coolaj86/acme-.js.git#v1.0", | ||||
|     "le-acme-core": "git+https://git.coolaj86.com/coolaj86/le-acme-core.js.git#v2.1" | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										106
									
								
								tests/challenge-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								tests/challenge-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var PromiseA = require('bluebird'); | ||||
| var path = require('path'); | ||||
| var requestAsync = PromiseA.promisify(require('request')); | ||||
| var LE = require('../').LE; | ||||
| var le = LE.create({ | ||||
|   server: 'staging' | ||||
| , acme: require('le-acme-core').ACME.create() | ||||
| , store: require('le-store-certbot').create({ | ||||
|     configDir: '~/letsencrypt.test/etc'.split('/').join(path.sep) | ||||
|   , webrootPath: '~/letsencrypt.test/var/:hostname'.split('/').join(path.sep) | ||||
|   }) | ||||
| , challenge: require('le-challenge-fs').create({ | ||||
|     webrootPath: '~/letsencrypt.test/var/:hostname'.split('/').join(path.sep) | ||||
|   }) | ||||
| , debug: true | ||||
| }); | ||||
| var utils = require('../lib/utils'); | ||||
| 
 | ||||
| if ('/.well-known/acme-challenge/' !== LE.acmeChallengePrefix) { | ||||
|   throw new Error("Bad constant 'acmeChallengePrefix'"); | ||||
| } | ||||
| 
 | ||||
| var baseUrl; | ||||
| // could use localhost as well, but for the sake of an FQDN for testing, we use this
 | ||||
| // also, example.com is just a junk domain to make sure that it is ignored
 | ||||
| // (even though it should always be an array of only one element in lib/core.js)
 | ||||
| var domains = [ 'localhost.daplie.com', 'example.com' ]; // or just localhost
 | ||||
| var token = 'token-id'; | ||||
| var secret = 'key-secret'; | ||||
| 
 | ||||
| var tests = [ | ||||
|   function () { | ||||
|     console.log('Test Url:', baseUrl + token); | ||||
|     return requestAsync({ url: baseUrl + token }).then(function (req) { | ||||
|       if (404 !== req.statusCode) { | ||||
|         console.log(req.statusCode); | ||||
|         throw new Error("Should be status 404"); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , function () { | ||||
|     var copy = utils.merge({ domains: domains }, le); | ||||
|     copy = utils.tplCopy(copy); | ||||
|     return PromiseA.promisify(le.challenge.set)(copy, domains[0], token, secret); | ||||
|   } | ||||
| 
 | ||||
| , function () { | ||||
|     return requestAsync(baseUrl + token).then(function (req) { | ||||
|       if (200 !== req.statusCode) { | ||||
|         console.log(req.statusCode, req.body); | ||||
|         throw new Error("Should be status 200"); | ||||
|       } | ||||
| 
 | ||||
|       if (req.body !== secret) { | ||||
|         console.error(token, secret, req.body); | ||||
|         throw new Error("req.body should be secret"); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , function () { | ||||
|     var copy = utils.merge({ domains: domains }, le); | ||||
|     copy = utils.tplCopy(copy); | ||||
|     return PromiseA.promisify(le.challenge.remove)(copy, domains[0], token); | ||||
|   } | ||||
| 
 | ||||
| , function () { | ||||
|     return requestAsync(baseUrl + token).then(function (req) { | ||||
|       if (404 !== req.statusCode) { | ||||
|         console.log(req.statusCode); | ||||
|         throw new Error("Should be status 404"); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| ]; | ||||
| 
 | ||||
| function run() { | ||||
|   //var express = require(express);
 | ||||
|   var server = require('http').createServer(le.middleware()); | ||||
|   server.listen(0, function () { | ||||
|     console.log('Server running, proceeding to test.'); | ||||
|     baseUrl = 'http://' + domains[0] + ':' + server.address().port + LE.acmeChallengePrefix; | ||||
| 
 | ||||
|     function next() { | ||||
|       var test = tests.shift(); | ||||
|       if (!test) { | ||||
|         console.info('All tests passed'); | ||||
|         server.close(); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       test().then(next, function (err) { | ||||
|         console.error('ERROR'); | ||||
|         console.error(err.stack); | ||||
|         server.close(); | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     next(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| run(); | ||||
| @ -2,13 +2,13 @@ | ||||
| 
 | ||||
| var LE = require('../').LE; | ||||
| var le = LE.create({ | ||||
| 	server: 'staging', | ||||
| 	acme: require('le-acme-core').ACME.create(), | ||||
| 	store: require('le-store-certbot').create({ | ||||
| 		configDir: '~/letsencrypt.test/etc/', | ||||
| 		webrootPath: '~/letsencrypt.test/tmp/:hostname' | ||||
| 	}), | ||||
| 	debug: true | ||||
|   server: 'staging' | ||||
| , acme: require('le-acme-core').ACME.create() | ||||
| , store: require('le-store-certbot').create({ | ||||
|     configDir: '~/letsencrypt.test/etc/' | ||||
|   , webrootPath: '~/letsencrypt.test/tmp/:hostname' | ||||
|   }) | ||||
| , debug: true | ||||
| }); | ||||
| 
 | ||||
| // TODO test generateRsaKey code path separately
 | ||||
| @ -21,31 +21,23 @@ var testAccountId = '939573edbf2506c92c9ab32131209d7b'; | ||||
| 
 | ||||
| var tests = [ | ||||
|   function () { | ||||
| 		return le.core.accounts | ||||
| 			.checkAsync({ | ||||
|     return le.core.accounts.checkAsync({ | ||||
|       accountId: testAccountId | ||||
| 			}) | ||||
| 			.then(function(account) { | ||||
|     }).then(function (account) { | ||||
|       if (!account) { | ||||
| 					throw new Error( | ||||
| 						'Test account should exist when searched by account id.' | ||||
| 					); | ||||
|         throw new Error("Test account should exist when searched by account id."); | ||||
|       } | ||||
|     }); | ||||
| 	}, | ||||
|   } | ||||
| 
 | ||||
| 	function() { | ||||
| 		return le.core.accounts | ||||
| 			.checkAsync({ | ||||
| , function () { | ||||
|     return le.core.accounts.checkAsync({ | ||||
|       email: testEmail | ||||
| 			}) | ||||
| 			.then(function(account) { | ||||
|     }).then(function (account) { | ||||
|       console.log('account.regr'); | ||||
|       console.log(account.regr); | ||||
|       if (!account) { | ||||
| 					throw new Error( | ||||
| 						'Test account should exist when searched by email.' | ||||
| 					); | ||||
|         throw new Error("Test account should exist when searched by email."); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @ -2,13 +2,13 @@ | ||||
| 
 | ||||
| var LE = require('../').LE; | ||||
| var le = LE.create({ | ||||
| 	server: 'staging', | ||||
| 	acme: require('le-acme-core').ACME.create(), | ||||
| 	store: require('le-store-certbot').create({ | ||||
| 		configDir: '~/letsencrypt.test/etc/', | ||||
| 		webrootPath: '~/letsencrypt.test/tmp/:hostname' | ||||
| 	}), | ||||
| 	debug: true | ||||
|   server: 'staging' | ||||
| , acme: require('le-acme-core').ACME.create() | ||||
| , store: require('le-store-certbot').create({ | ||||
|     configDir: '~/letsencrypt.test/etc/' | ||||
|   , webrootPath: '~/letsencrypt.test/tmp/:hostname' | ||||
|   }) | ||||
| , debug: true | ||||
| }); | ||||
| 
 | ||||
| //var testId = Math.round(Date.now() / 1000).toString();
 | ||||
| @ -19,103 +19,74 @@ var testAccount; | ||||
| 
 | ||||
| var tests = [ | ||||
|   function () { | ||||
| 		return le.core.accounts | ||||
| 			.checkAsync({ | ||||
|     return le.core.accounts.checkAsync({ | ||||
|       email: testEmail | ||||
| 			}) | ||||
| 			.then(function(account) { | ||||
|     }).then(function (account) { | ||||
|       if (account) { | ||||
|         console.error(account); | ||||
| 					throw new Error('Test account should not exist.'); | ||||
|         throw new Error("Test account should not exist."); | ||||
|       } | ||||
|     }); | ||||
| 	}, | ||||
| 	function() { | ||||
| 		return le.core.accounts | ||||
| 			.registerAsync({ | ||||
| 				email: testEmail, | ||||
| 				agreeTos: false, | ||||
| 				rsaKeySize: 2048 | ||||
| 			}) | ||||
| 			.then( | ||||
| 				function(/*account*/) { | ||||
| 					throw new Error( | ||||
| 						"Should not register if 'agreeTos' is not truthy." | ||||
| 					); | ||||
| 				}, | ||||
| 				function(err) { | ||||
|   } | ||||
| , function () { | ||||
|     return le.core.accounts.registerAsync({ | ||||
|       email: testEmail | ||||
|     , agreeTos: false | ||||
|     , rsaKeySize: 2048 | ||||
|     }).then(function (/*account*/) { | ||||
|       throw new Error("Should not register if 'agreeTos' is not truthy."); | ||||
|     }, function (err) { | ||||
|       if (err.code !== 'E_ARGS') { | ||||
|         throw err; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 			); | ||||
| 	}, | ||||
| 	function() { | ||||
| 		return le.core.accounts | ||||
| 			.registerAsync({ | ||||
| 				email: testEmail, | ||||
| 				agreeTos: true, | ||||
| 				rsaKeySize: 1024 | ||||
| 			}) | ||||
| 			.then( | ||||
| 				function(/*account*/) { | ||||
| 					throw new Error( | ||||
| 						"Should not register if 'rsaKeySize' is less than 2048." | ||||
| 					); | ||||
| 				}, | ||||
| 				function(err) { | ||||
| , function () { | ||||
|     return le.core.accounts.registerAsync({ | ||||
|       email: testEmail | ||||
|     , agreeTos: true | ||||
|     , rsaKeySize: 1024 | ||||
|     }).then(function (/*account*/) { | ||||
|       throw new Error("Should not register if 'rsaKeySize' is less than 2048."); | ||||
|     }, function (err) { | ||||
|       if (err.code !== 'E_ARGS') { | ||||
|         throw err; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 			); | ||||
| 	}, | ||||
| 	function() { | ||||
| 		return le.core.accounts | ||||
| 			.registerAsync({ | ||||
| 				email: fakeEmail, | ||||
| 				agreeTos: true, | ||||
| 				rsaKeySize: 2048 | ||||
| 			}) | ||||
| 			.then( | ||||
| 				function(/*account*/) { | ||||
| , function () { | ||||
|     return le.core.accounts.registerAsync({ | ||||
|       email: fakeEmail | ||||
|     , agreeTos: true | ||||
|     , rsaKeySize: 2048 | ||||
|     }).then(function (/*account*/) { | ||||
|       // TODO test mx record
 | ||||
| 					throw new Error( | ||||
| 						'Registration should NOT succeed with a bad email address.' | ||||
| 					); | ||||
| 				}, | ||||
| 				function(err) { | ||||
|       throw new Error("Registration should NOT succeed with a bad email address."); | ||||
|     }, function (err) { | ||||
|       if (err.code !== 'E_EMAIL') { | ||||
|         throw err; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 			); | ||||
| 	}, | ||||
| 	function() { | ||||
| 		return le.core.accounts | ||||
| 			.registerAsync({ | ||||
| 				email: testEmail, | ||||
| 				agreeTos: true, | ||||
| 				rsaKeySize: 2048 | ||||
| 			}) | ||||
| 			.then(function(account) { | ||||
| , function () { | ||||
|     return le.core.accounts.registerAsync({ | ||||
|       email: testEmail | ||||
|     , agreeTos: true | ||||
|     , rsaKeySize: 2048 | ||||
|     }).then(function (account) { | ||||
|       testAccount = account; | ||||
| 
 | ||||
|       console.log(testEmail); | ||||
|       console.log(testAccount); | ||||
| 
 | ||||
|       if (!account) { | ||||
| 					throw new Error( | ||||
| 						'Registration should always return a new account.' | ||||
| 					); | ||||
|         throw new Error("Registration should always return a new account."); | ||||
|       } | ||||
|       if (!account.email) { | ||||
| 					throw new Error('Registration should return the email.'); | ||||
|         throw new Error("Registration should return the email."); | ||||
|       } | ||||
|       if (!account.id) { | ||||
| 					throw new Error( | ||||
| 						'Registration should return the account id.' | ||||
| 					); | ||||
|         throw new Error("Registration should return the account id."); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @ -1,18 +0,0 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
| 
 | ||||
| # This test is intended to run on a digital ocean instance on which all of the | ||||
| # following domains are listed on the same certificate as either subject or altnames: | ||||
| # test.ppl.family, www.test.ppl.family, test.greenlock.domains, www.test.greenlock.domains | ||||
| 
 | ||||
| # -k for insecure to allow staging certificates | ||||
| curl -k -sf https://test.ppl.family | grep -i Hello >/dev/null && echo "PASS no servername" || echo "FAIL no servername" | ||||
| curl -k -sf https://test.ppl.family -H "Host: test.ppl.family" | grep -i Hello >/dev/null && echo "PASS same servername" || echo "FAIL same servername" | ||||
| curl -k -sf https://test.ppl.family -H "Host: www.test.ppl.family" | grep -i Hello >/dev/null && echo "PASS similar altnames" || echo "FAIL similar altnames" | ||||
| curl -k -sf https://test.ppl.family -H "Host: www.test.greenlock.domains" | grep -i Hello >/dev/null && echo "PASS full altnames" || echo "FAIL full altnames" | ||||
| 
 | ||||
| curl -k -sf https://test.greenlock.domains -H "Host: test.greenlock.domains" | grep -i Hello >/dev/null && echo "PASS use altname first" || echo "FAIL altname only" | ||||
| curl -k -sf https://test.greenlock.domains -H "Host: test.ppl.family" | grep -i Hello >/dev/null && echo "PASS use altname, pass subject" || echo "FAIL sub + altname" | ||||
| 
 | ||||
| curl -k -s https://test.ppl.family -H "Host: example.com" | grep -i 'Domain Fronting' >/dev/null && echo "PASS detect fronting" || echo "FAIL detect fronting" | ||||
| echo "PASS ALL" | ||||
| @ -2,16 +2,16 @@ | ||||
| 
 | ||||
| var LE = require('../').LE; | ||||
| var le = LE.create({ | ||||
| 	server: 'staging', | ||||
| 	acme: require('le-acme-core').ACME.create(), | ||||
| 	store: require('le-store-certbot').create({ | ||||
| 		configDir: '~/letsencrypt.test/etc', | ||||
|   server: 'staging' | ||||
| , acme: require('le-acme-core').ACME.create() | ||||
| , store: require('le-store-certbot').create({ | ||||
|     configDir: '~/letsencrypt.test/etc' | ||||
|   , webrootPath: '~/letsencrypt.test/var/:hostname' | ||||
|   }) | ||||
| , challenge: require('le-challenge-fs').create({ | ||||
|     webrootPath: '~/letsencrypt.test/var/:hostname' | ||||
| 	}), | ||||
| 	challenge: require('le-challenge-fs').create({ | ||||
| 		webrootPath: '~/letsencrypt.test/var/:hostname' | ||||
| 	}), | ||||
| 	debug: true | ||||
|   }) | ||||
| , debug: true | ||||
| }); | ||||
| 
 | ||||
| // TODO test generateRsaKey code path separately
 | ||||
| @ -25,30 +25,22 @@ var testDomains = ['pokemap.hellabit.com', 'www.pokemap.hellabit.com']; | ||||
| 
 | ||||
| var tests = [ | ||||
|   function () { | ||||
| 		return le.core.certificates | ||||
| 			.checkAsync({ | ||||
|     return le.core.certificates.checkAsync({ | ||||
|       domains: [ 'example.com', 'www.example.com' ] | ||||
| 			}) | ||||
| 			.then(function(cert) { | ||||
|     }).then(function (cert) { | ||||
|       if (cert) { | ||||
| 					throw new Error( | ||||
| 						'Bogus domain should not have certificate.' | ||||
| 					); | ||||
|         throw new Error("Bogus domain should not have certificate."); | ||||
|       } | ||||
|     }); | ||||
| 	}, | ||||
|   } | ||||
| 
 | ||||
| 	function() { | ||||
| 		return le.core.certificates | ||||
| 			.getAsync({ | ||||
| 				email: testEmail, | ||||
| 				domains: testDomains | ||||
| 			}) | ||||
| 			.then(function(certs) { | ||||
| , function () { | ||||
|     return le.core.certificates.getAsync({ | ||||
|       email: testEmail | ||||
|     , domains: testDomains | ||||
|     }).then(function (certs) { | ||||
|       if (!certs) { | ||||
| 					throw new Error( | ||||
| 						'Should have acquired certificate for domains.' | ||||
| 					); | ||||
|         throw new Error("Should have acquired certificate for domains."); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @ -2,16 +2,16 @@ | ||||
| 
 | ||||
| var LE = require('../').LE; | ||||
| var le = LE.create({ | ||||
| 	server: 'staging', | ||||
| 	acme: require('le-acme-core').ACME.create(), | ||||
| 	store: require('le-store-certbot').create({ | ||||
| 		configDir: '~/letsencrypt.test/etc', | ||||
|   server: 'staging' | ||||
| , acme: require('le-acme-core').ACME.create() | ||||
| , store: require('le-store-certbot').create({ | ||||
|     configDir: '~/letsencrypt.test/etc' | ||||
|   , webrootPath: '~/letsencrypt.test/var/:hostname' | ||||
|   }) | ||||
| , challenge: require('le-challenge-fs').create({ | ||||
|     webrootPath: '~/letsencrypt.test/var/:hostname' | ||||
| 	}), | ||||
| 	challenge: require('le-challenge-fs').create({ | ||||
| 		webrootPath: '~/letsencrypt.test/var/:hostname' | ||||
| 	}), | ||||
| 	debug: true | ||||
|   }) | ||||
| , debug: true | ||||
| }); | ||||
| 
 | ||||
| // TODO test generateRsaKey code path separately
 | ||||
| @ -27,83 +27,48 @@ var testCerts; | ||||
| var tests = [ | ||||
|   function () { | ||||
|     // TODO test that an altname also fetches the proper certificate
 | ||||
| 		return le.core.certificates | ||||
| 			.checkAsync({ | ||||
|     return le.core.certificates.checkAsync({ | ||||
|       domains: testDomains | ||||
| 			}) | ||||
| 			.then(function(certs) { | ||||
|     }).then(function (certs) { | ||||
|       if (!certs) { | ||||
| 					throw new Error( | ||||
| 						'Either certificates.registerAsync (in previous test)' + | ||||
| 							' or certificates.checkAsync (in this test) failed.' | ||||
| 					); | ||||
|         throw new Error("Either certificates.registerAsync (in previous test)" | ||||
|           + " or certificates.checkAsync (in this test) failed."); | ||||
|       } | ||||
| 
 | ||||
|       testCerts = certs; | ||||
| 				console.log( | ||||
| 					'Issued At', | ||||
| 					new Date(certs.issuedAt).toISOString() | ||||
| 				); | ||||
| 				console.log( | ||||
| 					'Expires At', | ||||
| 					new Date(certs.expiresAt).toISOString() | ||||
| 				); | ||||
|       console.log('Issued At', new Date(certs.issuedAt).toISOString()); | ||||
|       console.log('Expires At', new Date(certs.expiresAt).toISOString()); | ||||
| 
 | ||||
|       if (certs.expiresAt <= Date.now()) { | ||||
| 					throw new Error( | ||||
| 						'Certificates are already expired. They cannot be tested for duplicate or forced renewal.' | ||||
| 					); | ||||
|         throw new Error("Certificates are already expired. They cannot be tested for duplicate or forced renewal."); | ||||
|       } | ||||
|     }); | ||||
| 	}, | ||||
|   } | ||||
| 
 | ||||
| 	function() { | ||||
| 		return le.core.certificates | ||||
| 			.renewAsync( | ||||
| 				{ | ||||
| 					email: testEmail, | ||||
| 					domains: testDomains | ||||
| 				}, | ||||
| 				testCerts | ||||
| 			) | ||||
| 			.then( | ||||
| 				function() { | ||||
| 					throw new Error( | ||||
| 						'Should not have renewed non-expired certificates.' | ||||
| 					); | ||||
| 				}, | ||||
| 				function(err) { | ||||
| , function () { | ||||
|     return le.core.certificates.renewAsync({ | ||||
|       email: testEmail | ||||
|     , domains: testDomains | ||||
|     }, testCerts).then(function () { | ||||
|       throw new Error("Should not have renewed non-expired certificates."); | ||||
|     }, function (err) { | ||||
|       if ('E_NOT_RENEWABLE' !== err.code) { | ||||
|         throw err; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 			); | ||||
| 	}, | ||||
| 
 | ||||
| 	function() { | ||||
| 		return le.core.certificates | ||||
| 			.renewAsync( | ||||
| 				{ | ||||
| 					email: testEmail, | ||||
| 					domains: testDomains, | ||||
| 					renewWithin: 720 * 24 * 60 * 60 * 1000 | ||||
| 				}, | ||||
| 				testCerts | ||||
| 			) | ||||
| 			.then(function(certs) { | ||||
| 				console.log( | ||||
| 					'Issued At', | ||||
| 					new Date(certs.issuedAt).toISOString() | ||||
| 				); | ||||
| 				console.log( | ||||
| 					'Expires At', | ||||
| 					new Date(certs.expiresAt).toISOString() | ||||
| 				); | ||||
| , function () { | ||||
|     return le.core.certificates.renewAsync({ | ||||
|       email: testEmail | ||||
|     , domains: testDomains | ||||
|     , renewWithin: 720 * 24 * 60 * 60 * 1000 | ||||
|     }, testCerts).then(function (certs) { | ||||
|       console.log('Issued At', new Date(certs.issuedAt).toISOString()); | ||||
|       console.log('Expires At', new Date(certs.expiresAt).toISOString()); | ||||
| 
 | ||||
|       if (certs.issuedAt === testCerts.issuedAt) { | ||||
| 					throw new Error( | ||||
| 						'Should not have returned existing certificates.' | ||||
| 					); | ||||
|         throw new Error("Should not have returned existing certificates."); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user