mirror of
				https://github.com/therootcompany/greenlock.js.git
				synced 2024-11-16 17:29:00 +00:00 
			
		
		
		
	Compare commits
	
		
			80 Commits
		
	
	
		
			aece586c90
			...
			0237336e8f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0237336e8f | ||
|  | f7dbdc1ea5 | ||
|  | 40401779d9 | ||
| 94fd657562 | |||
| 33e6f800b8 | |||
| 929429f1ef | |||
| 977de24648 | |||
| 9f0dd2e64b | |||
| 6650defebb | |||
| ac237148ba | |||
| 22651f1343 | |||
| 07cd2ced9c | |||
|  | 000b99cb5e | ||
|  | 4513a6d49c | ||
| fb69d25250 | |||
| c8b895633c | |||
| 396cac3b74 | |||
|  | 5316af67be | ||
| 93b9158b1b | |||
| e75d390842 | |||
| 2569c36260 | |||
| 44cd0f3d2e | |||
| 0a382fdb44 | |||
| 1766790424 | |||
| 6aafe3d663 | |||
| 2c936a21ce | |||
| e29d237a2d | |||
| 0d14db1f1c | |||
| f4cdbe7a47 | |||
| 1b388788d8 | |||
| 461ad43620 | |||
| ddaebd9387 | |||
| 8afda1184e | |||
| 0cbdf53322 | |||
| 90f65a1a63 | |||
| ca219a00e4 | |||
| d5d14bd968 | |||
| 51ef9be517 | |||
| 082f0e4522 | |||
| 593c2d5fca | |||
| fc513b3e70 | |||
| 7320cf624c | |||
| 6d2a62e7b5 | |||
| 0601df80c6 | |||
| fe44523243 | |||
| 03e2513919 | |||
| df0f870665 | |||
| e60b4356c1 | |||
| 4960604440 | |||
| eb86d4444b | |||
| 5d82ea60c5 | |||
| ff000c40f1 | |||
| c45fcdf150 | |||
| 7e08b4c157 | |||
| 8375f6ef5c | |||
| 64107756a1 | |||
| 61715ab952 | |||
| 56ec8cbd36 | |||
| 37d9ac0436 | |||
| 7ee525018c | |||
| 382a7cc4a9 | |||
| a612f4f98b | |||
| 2abdfcc665 | |||
| bc13451368 | |||
| 62bd2ab4c7 | |||
| 108e59ef8b | |||
| e71298c305 | |||
| 781a735146 | |||
| 540ac6c310 | |||
| 8c32887b10 | |||
| 2cfba7a2e7 | |||
| ea02a93fba | |||
| 992a684a28 | |||
| 67e0885675 | |||
| a6bd58506c | |||
| b7505cbccb | |||
| 3562b9ebfb | |||
| 282f748e77 | |||
| 20e8d09219 | |||
| 1abd3e43de | 
							
								
								
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |   "bracketSpacing": true, | ||||||
|  |   "printWidth": 80, | ||||||
|  |   "singleQuote": true, | ||||||
|  |   "tabWidth": 4, | ||||||
|  |   "trailingComma": "none", | ||||||
|  |   "useTabs": true | ||||||
|  | } | ||||||
							
								
								
									
										396
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										396
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,41 +1,375 @@ | |||||||
| Copyright 2017 AJ ONeal | Copyright 2015-2019 AJ ONeal | ||||||
| 
 | 
 | ||||||
| This is open source software; you can redistribute it and/or modify it under the | Mozilla Public License Version 2.0 | ||||||
| terms of either: | ================================== | ||||||
| 
 | 
 | ||||||
|    a) the "MIT License" | 1. Definitions | ||||||
|    b) the "Apache-2.0 License" | -------------- | ||||||
| 
 | 
 | ||||||
| MIT License | 1.1. "Contributor" | ||||||
|  |     means each individual or legal entity that creates, contributes to | ||||||
|  |     the creation of, or owns Covered Software. | ||||||
| 
 | 
 | ||||||
|    Permission is hereby granted, free of charge, to any person obtaining a copy | 1.2. "Contributor Version" | ||||||
|    of this software and associated documentation files (the "Software"), to deal |     means the combination of the Contributions of others (if any) used | ||||||
|    in the Software without restriction, including without limitation the rights |     by a Contributor and that particular Contributor's Contribution. | ||||||
|    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: |  | ||||||
| 
 | 
 | ||||||
|    The above copyright notice and this permission notice shall be included in all | 1.3. "Contribution" | ||||||
|    copies or substantial portions of the Software. |     means Covered Software of a particular Contributor. | ||||||
| 
 | 
 | ||||||
|    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 1.4. "Covered Software" | ||||||
|    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |     means Source Code Form to which the initial Contributor has attached | ||||||
|    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |     the notice in Exhibit A, the Executable Form of such Source Code | ||||||
|    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |     Form, and Modifications of such Source Code Form, in each case | ||||||
|    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |     including portions thereof. | ||||||
|    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |  | ||||||
|    SOFTWARE. |  | ||||||
| 
 | 
 | ||||||
| Apache-2.0 License Summary | 1.5. "Incompatible With Secondary Licenses" | ||||||
|  |     means | ||||||
| 
 | 
 | ||||||
|    Licensed under the Apache License, Version 2.0 (the "License"); |     (a) that the initial Contributor has attached the notice described | ||||||
|    you may not use this file except in compliance with the License. |         in Exhibit B to the Covered Software; or | ||||||
|    You may obtain a copy of the License at |  | ||||||
| 
 | 
 | ||||||
|      http://www.apache.org/licenses/LICENSE-2.0 |     (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. | ||||||
| 
 | 
 | ||||||
|    Unless required by applicable law or agreed to in writing, software | 1.6. "Executable Form" | ||||||
|    distributed under the License is distributed on an "AS IS" BASIS, |     means any form of the work other than Source Code Form. | ||||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | ||||||
|    See the License for the specific language governing permissions and | 1.7. "Larger Work" | ||||||
|    limitations under the License. |     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. | ||||||
|  | |||||||
							
								
								
									
										446
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										446
									
								
								README.md
									
									
									
									
									
								
							| @ -1,91 +1,85 @@ | |||||||
|  | # Greenlock v3 on its way (Nov 1st, 2019) | ||||||
| 
 | 
 | ||||||
|  | Greenlock v3 is in private beta (for backers) and will be available publicly by Nov 1st. | ||||||
| 
 | 
 | ||||||
| Greenlock™ for node.js | 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 provides Free SSL, Free Wildcard SSL, and Fully Automated HTTPS <br> | Greenlock provides Free SSL, Free Wildcard SSL, and Fully Automated HTTPS <br> | ||||||
| <small>certificates issued by Let's Encrypt v2 via [ACME](https://git.coolaj86.com/coolaj86/acme-v2.js)</small> | <small>certificates issued by Let's Encrypt v2 via [ACME](https://git.rootprojects.org/root/acme-v2.js)</small> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| | Sponsored by [ppl](https://ppl.family) | |  | ||||||
| Greenlock works | Greenlock works | ||||||
| in the [Commandline](https://git.coolaj86.com/coolaj86/greenlock-cli.js) (cli), | in the [Commandline](https://git.rootprojects.org/root/greenlock-cli.js) (cli), | ||||||
| as a [Web Server](https://git.coolaj86.com/coolaj86/greenlock-server.js), | as a [Web Server](https://git.rootprojects.org/root/greenlock-express.js), | ||||||
| in [Web Browsers](https://git.coolaj86.com/coolaj86/greenlock.html) (WebCrypto), | in [Web Browsers](https://greenlock.domains) (WebCrypto), | ||||||
| and with **node.js** ([npm](https://www.npmjs.com/package/greenlock)). | and with **node.js** ([npm](https://www.npmjs.com/package/greenlock)). | ||||||
| 
 | 
 | ||||||
| Features | # Features | ||||||
| ======== |  | ||||||
| 
 | 
 | ||||||
|   - [x] Actively Maintained and Supported | -   [x] Actively Maintained and Commercially Supported | ||||||
|   - [x] Automatic HTTPS |     -   [x] VanillaJS | ||||||
|     - [x] Free SSL |     -   [x] Limited Dependencies | ||||||
|     - [x] Free Wildcard SSL |     -   [x] MPL-2.0 licensed (great for hobbyists and DIYers) | ||||||
|     - [x] Multiple domain support (up to 100 altnames per SAN) |     -   [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] Dynamic Virtual Hosting (vhost) | -   [x] Automatic HTTPS | ||||||
|     - [x] Automatical renewal (10 to 14 days before expiration) |     -   [x] Free SSL | ||||||
|   - [x] Great ACME support via [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js) |     -   [x] Free Wildcard SSL | ||||||
|     - [x] "dry run" with self-diagnostics |     -   [x] Multiple domain support (up to 100 altnames per SAN) | ||||||
|     - [x] ACME draft 12 |     -   [x] Dynamic Virtual Hosting (vhost) | ||||||
|     - [x] Let's Encrypt v2 |     -   [x] Automatical renewal (10 to 14 days before expiration) | ||||||
|     - [x] Let's Encrypt v1 | -   [x] Great ACME support via [acme.js](https://git.rootprojects.org/root/acme-v2.js) | ||||||
|   - [x] [Commandline](https://git.coolaj86.com/coolaj86/greenlock-cli.js) (cli) Utilities |     -   [x] "dry run" with self-diagnostics | ||||||
|     - [x] Works with `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell`, and more |     -   [x] ACME draft 12 | ||||||
|   - [x] [Browser](https://git.coolaj86.com/coolaj86/greenlock.html) Support |     -   [x] Let's Encrypt v2 | ||||||
|   - [x] Full node.js support, with modules for |     -   [x] ~Let's Encrypt v1~ (deprecated) | ||||||
|     - [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] [Commandline](https://git.rootprojects.org/root/greenlock-cli.js) (cli) Utilities | ||||||
|   - [x] Great for securing your Raspberry Pi |     -   [x] Works with `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell`, and more | ||||||
|   - [x] Extensible Plugin Support | -   [x] [Browser](https://git.rootprojects.org/root/greenlock.html) Support | ||||||
|     - [x] AWS S3, AWS Route53, Azure, CloudFlare, Consul, Digital Ocean, etcd, Redis | -   [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] 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 | Documentation for using Greenlock with | ||||||
| [http/https](https://git.coolaj86.com/coolaj86/greenlock-express.js/src/branch/master/examples/https-server.js), | [http/https](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples), | ||||||
| [Express.js](https://git.coolaj86.com/coolaj86/greenlock-express.js), | [Express.js](https://git.rootprojects.org/root/greenlock-express.js), | ||||||
| [cluster](https://git.coolaj86.com/coolaj86/greenlock-cluster.js), | [hapi](https://git.rootprojects.org/root/greenlock-hapi.js), | ||||||
| [hapi](https://git.coolaj86.com/coolaj86/greenlock-hapi.js), | [Koa](https://git.rootprojects.org/root/greenlock-koa.js), | ||||||
| [Koa](https://git.coolaj86.com/coolaj86/greenlock-koa.js), | [rill](https://git.rootprojects.org/root/greenlock-rill.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 | -   Install | ||||||
|   * Simple Examples | -   **QuickStart** | ||||||
|   * Example with ALL OPTIONS | -   Simple Examples | ||||||
|   * API | -   Example with ALL OPTIONS | ||||||
|   * Developer API | -   API | ||||||
|   * Change History | -   Developer API | ||||||
|   * License | -   Change History | ||||||
|  | -   License | ||||||
| 
 | 
 | ||||||
| Install | # Install | ||||||
| ======= |  | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| npm install --save greenlock@2.x | npm install --save greenlock@2.x | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| **Optional** dependency for *more efficient* RSA key generation: | **Optional** for _more efficient_ RSA key generation you must use node v10.12+ | ||||||
| <small>(important for those on ARM devices like Raspberry Pi)</small> | <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 | ### Production vs Staging | ||||||
| 
 | 
 | ||||||
| @ -103,75 +97,90 @@ unless you're very clear on what the failure was and how to fix it. | |||||||
| { server: 'https://acme-staging-v02.api.letsencrypt.org/directory' } | { server: 'https://acme-staging-v02.api.letsencrypt.org/directory' } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ### QuickStart Screencast | ||||||
| 
 | 
 | ||||||
| Easy as 1, 2, 3... 4 | 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 | ||||||
| 
 | 
 | ||||||
| Greenlock is built to incredibly easy to use, without sacrificing customization or extensibility. | 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, | 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. | to more robust examples that you might start with for an enterprise-grade use of the ACME api. | ||||||
| 
 | 
 | ||||||
| * Automatic HTTPS (for single sites) | -   Automatic HTTPS (for single sites) | ||||||
| * Fully Automatic HTTPS (for multi-domain vhosts) | -   Fully Automatic HTTPS (for multi-domain vhosts) | ||||||
| * Manual HTTPS (for API integration) | -   Manual HTTPS (for API integration) | ||||||
| 
 | 
 | ||||||
| Automatic HTTPS | ## Automatic HTTPS | ||||||
| --------------- |  | ||||||
| 
 | 
 | ||||||
| **Note**: For (fully) automatic HTTPS you may prefer | **Note**: For (fully) automatic HTTPS you may prefer | ||||||
| the [Express.js module](https://git.coolaj86.com/coolaj86/greenlock-express.js) | the [Express.js module](https://git.rootprojects.org/root/greenlock-express.js) | ||||||
| 
 | 
 | ||||||
| This works for most people, but it's not as fun as some of the other examples. | This works for most people, but it's not as fun as some of the other examples. | ||||||
| 
 | 
 | ||||||
| Great when | Great when | ||||||
| 
 | 
 | ||||||
|  - [x] You only need a limited number of certificates | -   [x] You only need a limited number of certificates | ||||||
|  - [x] You want to use the bare node http and https modules without fluff | -   [x] You want to use the bare node http and https modules without fluff | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| //////////////////// | //////////////////// | ||||||
| // INIT GREENLOCK // | // INIT GREENLOCK // | ||||||
| //////////////////// | //////////////////// | ||||||
| 
 | 
 | ||||||
| var path = require('path'); | var greenlock = require('greenlock').create({ | ||||||
| var os = require('os') | 	email: 'user@example.com', // IMPORTANT: Change email and domains | ||||||
| var Greenlock = require('greenlock'); | 	agreeTos: true, // Accept Let's Encrypt v2 Agreement | ||||||
|  | 	configDir: '~/.config/acme', // A writable folder (a non-fs plugin) | ||||||
| 
 | 
 | ||||||
| var greenlock = Greenlock.create({ | 	communityMember: true, // Get (rare) non-mandatory updates about cool greenlock-related stuff (default false) | ||||||
|   agreeTos: true                      // Accept Let's Encrypt v2 Agreement | 	securityUpdates: true // Important and mandatory notices related to security or breaking API changes (default true) | ||||||
| , 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 // | // CREATE SERVERS // | ||||||
| //////////////////// | //////////////////// | ||||||
| 
 | 
 | ||||||
| var redir = require('redirect-https')(); | 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('spdy') | ||||||
|   res.end('Hello, Secure World!'); | 	.createServer(greenlock.tlsOptions, function(req, res) { | ||||||
| }).listen(443); | 		res.end('Hello, Secure World!'); | ||||||
|  | 	}) | ||||||
|  | 	.listen(443); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Fully Automatic HTTPS | ## Fully Automatic HTTPS | ||||||
| ------------ |  | ||||||
| 
 | 
 | ||||||
| **Note**: For (fully) automatic HTTPS you may prefer | **Note**: For (fully) automatic HTTPS you may prefer | ||||||
| the [Express.js module](https://git.coolaj86.com/coolaj86/greenlock-express.js) | the [Express.js module](https://git.rootprojects.org/root/greenlock-express.js) | ||||||
| 
 | 
 | ||||||
| Great when | Great when | ||||||
| 
 | 
 | ||||||
|  - [x] You have a growing number of domains | -   [x] You have a growing number of domains | ||||||
|  - [x] You're integrating into your own hosting solution | -   [x] You're integrating into your own hosting solution | ||||||
|  - [x] Customize ACME http-01 or dns-01 challenge | -   [x] Customize ACME http-01 or dns-01 challenge | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| //////////////////// | //////////////////// | ||||||
| @ -179,71 +188,75 @@ Great when | |||||||
| //////////////////// | //////////////////// | ||||||
| 
 | 
 | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var os = require('os') | var os = require('os'); | ||||||
| var Greenlock = require('greenlock'); | var Greenlock = require('greenlock'); | ||||||
| 
 | 
 | ||||||
| var greenlock = Greenlock.create({ | var greenlock = Greenlock.create({ | ||||||
|   version: 'draft-12' | 	version: 'draft-12', | ||||||
| , server: 'https://acme-v02.api.letsencrypt.org/directory' | 	server: 'https://acme-v02.api.letsencrypt.org/directory', | ||||||
| 
 | 
 | ||||||
|   // approve a growing list of domains | 	// Use the approveDomains callback to set per-domain config | ||||||
| , approveDomains: approveDomains | 	// (default: approve any domain that passes self-test of built-in challenges) | ||||||
|  | 	approveDomains: approveDomains, | ||||||
| 
 | 
 | ||||||
|   // If you wish to replace the default account and domain key storage plugin | 	// the default servername to use when the client doesn't specify | ||||||
| , store: require('le-store-certbot').create({ | 	servername: 'example.com', | ||||||
|     configDir: path.join(os.homedir(), 'acme/etc') | 
 | ||||||
|   , webrootPath: '/tmp/acme-challenges' | 	// 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' | ||||||
|  | 	}) | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| ///////////////////// | ///////////////////// | ||||||
| // APPROVE DOMAINS // | // 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) { | function approveDomains(opts, certs, cb) { | ||||||
|   // This is where you check your database and associated | 	// This is where you check your database and associated | ||||||
|   // email addresses with domains and agreements and such | 	// email addresses with domains and agreements and such | ||||||
| 
 | 
 | ||||||
|   // Opt-in to submit stats and get important updates | 	// Opt-in to submit stats and get important updates | ||||||
|   opts.communityMember = true; | 	opts.communityMember = true; | ||||||
| 
 | 
 | ||||||
|   // If you wish to replace the default challenge plugin, you may do so here | 	// If you wish to replace the default challenge plugin, you may do so here | ||||||
|   opts.challenges = { 'http-01': http01 }; | 	opts.challenges = { 'http-01': http01 }; | ||||||
| 
 | 
 | ||||||
|   // The domains being approved for the first time are listed in opts.domains | 	// The domains being approved for the first time are listed in opts.domains | ||||||
|   // Certs being renewed are listed in certs.altnames | 	// Certs being renewed are listed in certs.altnames | ||||||
|   if (certs) { | 	// certs.domains; | ||||||
|     opts.domains = certs.altnames; | 	// certs.altnames; | ||||||
|   } | 	opts.email = 'john.doe@example.com'; | ||||||
|   else { | 	opts.agreeTos = true; | ||||||
|     opts.email = 'john.doe@example.com'; |  | ||||||
|     opts.agreeTos = true; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   // NOTE: you can also change other options such as `challengeType` and `challenge` | 	// NOTE: you can also change other options such as `challengeType` and `challenge` | ||||||
|   // opts.challengeType = 'http-01'; | 	// opts.challengeType = 'http-01'; | ||||||
|   // opts.challenge = require('le-challenge-fs').create({}); | 	// opts.challenge = require('le-challenge-fs').create({}); | ||||||
| 
 | 
 | ||||||
|   cb(null, { options: opts, certs: certs }); | 	cb(null, { options: opts, certs: certs }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| //////////////////// | //////////////////// | ||||||
| // CREATE SERVERS // | // CREATE SERVERS // | ||||||
| //////////////////// | //////////////////// | ||||||
| 
 | 
 | ||||||
| var redir = require('redirect-https')(); | 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') | ||||||
|   res.end('Hello, Secure World!'); | 	.createServer(greenlock.tlsOptions, function(req, res) { | ||||||
| }).listen(443); | 		res.end('Hello, Secure World!'); | ||||||
|  | 	}) | ||||||
|  | 	.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 | 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. | that doesn't use node's SNICallback. | ||||||
| @ -291,20 +304,24 @@ greenlock.register(opts).then(function (certs) { | |||||||
| The domain key and ssl certificates you get back can be used in a webserver like this: | The domain key and ssl certificates you get back can be used in a webserver like this: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| var tlsOptions = { key: certs.privkey, cert: certs.cert + '\r\n' + certs.chain }; | var tlsOptions = { | ||||||
| require('https').createServer(tlsOptions, function (req, res) { | 	key: certs.privkey, | ||||||
|   res.end('Hello, Secure World!'); | 	cert: certs.cert + '\r\n' + certs.chain | ||||||
| }).listen(443); | }; | ||||||
|  | require('https') | ||||||
|  | 	.createServer(tlsOptions, function(req, res) { | ||||||
|  | 		res.end('Hello, Secure World!'); | ||||||
|  | 	}) | ||||||
|  | 	.listen(443); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Example with ALL OPTIONS | # Example with ALL OPTIONS | ||||||
| ========= |  | ||||||
| 
 | 
 | ||||||
| The configuration consists of 3 components: | The configuration consists of 3 components: | ||||||
| 
 | 
 | ||||||
| * Storage Backend (search npm for projects starting with 'le-store-') | -   Storage Backend (search npm for projects starting with 'le-store-') | ||||||
| * ACME Challenge Handlers (search npm for projects starting with 'le-challenge-') | -   ACME Challenge Handlers (search npm for projects starting with 'le-challenge-') | ||||||
| * Letsencryt Config (this is all you) | -   Letsencryt Config (this is all you) | ||||||
| 
 | 
 | ||||||
| ```javascript | ```javascript | ||||||
| 'use strict'; | 'use strict'; | ||||||
| @ -314,7 +331,7 @@ var greenlock; | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // Storage Backend | // Storage Backend | ||||||
| var leStore = require('le-store-certbot').create({ | var leStore = require('greenlock-store-fs').create({ | ||||||
|   configDir: '~/acme/etc'                                 // or /etc/letsencrypt or wherever |   configDir: '~/acme/etc'                                 // or /etc/letsencrypt or wherever | ||||||
| , debug: false | , debug: false | ||||||
| }); | }); | ||||||
| @ -418,8 +435,7 @@ Here's what `results` looks like: | |||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| API | ## API | ||||||
| --- |  | ||||||
| 
 | 
 | ||||||
| The full end-user API is exposed in the example above and includes all relevant options. | The full end-user API is exposed in the example above and includes all relevant options. | ||||||
| 
 | 
 | ||||||
| @ -432,7 +448,7 @@ greenlock.check(opts) | |||||||
| 
 | 
 | ||||||
| We do expose a few helper functions: | 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 | TODO fetch domain tld list | ||||||
| 
 | 
 | ||||||
| @ -440,78 +456,114 @@ TODO fetch domain tld list | |||||||
| 
 | 
 | ||||||
| The following variables will be tempalted in any strings passed to the options object: | The following variables will be tempalted in any strings passed to the options object: | ||||||
| 
 | 
 | ||||||
| * `~/` replaced with `os.homedir()` i.e. `/Users/aj` | -   `~/` replaced with `os.homedir()` i.e. `/Users/aj` | ||||||
| * `:hostname` replaced with the first domain in the list i.e. `example.com` | -   `:hostname` replaced with the first domain in the list i.e. `example.com` | ||||||
| 
 | 
 | ||||||
| Developer API | ### 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 | ||||||
| 
 | 
 | ||||||
| If you are developing an `le-store-*` or `le-challenge-*` plugin you need to be aware of | If you are developing an `le-store-*` or `le-challenge-*` plugin you need to be aware of | ||||||
| additional internal API expectations. | additional internal API expectations. | ||||||
| 
 | 
 | ||||||
| **IMPORTANT**: | **IMPORTANT**: | ||||||
| 
 | 
 | ||||||
| Use `v2.0.0` as your initial version - NOT v0.1.0 and NOT v1.0.0 and NOT v3.0.0. | 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 v2.x of node-greenlock. | This is to indicate that your module is compatible with v3 (v2.7+) of node-greenlock. | ||||||
| 
 | 
 | ||||||
| Since the public API for your module is defined by node-greenlock the major version | Since the public API for your module is defined by node-greenlock the major version | ||||||
| should be kept in sync. | should be kept in sync. | ||||||
| 
 | 
 | ||||||
| ### store implementation | ### store implementation | ||||||
| 
 | 
 | ||||||
| See <https://git.coolaj86.com/coolaj86/le-store-SPEC.js> | 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) | ||||||
| 
 | 
 | ||||||
| * getOptions() | -   accounts. | ||||||
| * accounts. |     -   checkKeypair(opts) | ||||||
|   * checkKeypair(opts, cb) |     -   check(opts) | ||||||
|   * check(opts, cb) |     -   setKeypair(opts) | ||||||
|   * setKeypair(opts, keypair, cb) |     -   set(opts) | ||||||
|   * set(opts, reg, cb) | -   certificates. | ||||||
| * certificates. |     -   checkKeypair(opts) | ||||||
|   * checkKeypair(opts, cb) |     -   check(opts) | ||||||
|   * check(opts, cb) |     -   setKeypair(opts) | ||||||
|   * setKeypair(opts, keypair, cb) |     -   set(opts) | ||||||
|   * set(opts, reg, cb) |  | ||||||
| 
 | 
 | ||||||
| ### challenge implementation | ### challenge implementation | ||||||
| 
 | 
 | ||||||
| See https://git.coolaj86.com/coolaj86/le-challenge-fs.js | 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) | ||||||
| 
 | 
 | ||||||
| * `.set(opts, domain, key, value, cb);`         // opts will be saved with domain/key | -   `.set(opts);` | ||||||
| * `.get(opts, domain, key, cb);`                // opts will be retrieved by domain/key | -   `.get(opts);` | ||||||
| * `.remove(opts, domain, key, cb);`             // opts will be retrieved by domain/key | -   `.remove(opts);` | ||||||
| 
 | 
 | ||||||
| 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 |  | ||||||
| 
 | 
 | ||||||
| LICENSE | -   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 | ||||||
| 
 | 
 | ||||||
| Dual-licensed MIT and Apache-2.0 | # Commercial Licensing | ||||||
| 
 | 
 | ||||||
| See LICENSE | 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. | ||||||
| 
 | 
 | ||||||
| Greenlock™ is a trademark of AJ ONeal | 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) | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| STOP | # STOP | ||||||
| ==== |  | ||||||
| 
 | 
 | ||||||
| **These aren't the droids you're looking for.** | **These aren't the droids you're looking for.** | ||||||
| 
 | 
 | ||||||
| @ -7,8 +6,7 @@ You probably don't want to use `greenlock` directly. | |||||||
| 
 | 
 | ||||||
| Instead, look here: | Instead, look here: | ||||||
| 
 | 
 | ||||||
| Webservers | ## Webservers | ||||||
| ---------- |  | ||||||
| 
 | 
 | ||||||
| For any type of webserver (express, hapi, koa, connect, https, spdy, etc), | For any type of webserver (express, hapi, koa, connect, https, spdy, etc), | ||||||
| you're going to want to take a look at | you're going to want to take a look at | ||||||
| @ -16,8 +14,7 @@ you're going to want to take a look at | |||||||
| 
 | 
 | ||||||
| <https://git.coolaj86.com/coolaj86/greenlock-express.js> | <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), | 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 | you're going to want to take a look at | ||||||
| @ -25,8 +22,7 @@ you're going to want to take a look at | |||||||
| 
 | 
 | ||||||
| <https://git.coolaj86.com/coolaj86/greenlock-cli.js> | <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 | Well, take a look at the API in the main README | ||||||
| and you can also check out the code in the repos above. | and you can also check out the code in the repos above. | ||||||
|  | |||||||
| @ -5,63 +5,73 @@ var Greenlock = require('../'); | |||||||
| var db = {}; | var db = {}; | ||||||
| 
 | 
 | ||||||
| var config = { | var config = { | ||||||
|   server: 'https://acme-v02.api.letsencrypt.org/directory' | 	server: 'https://acme-v02.api.letsencrypt.org/directory', | ||||||
| , version: 'draft-11' | 	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'         //
 | 	privkeyPath: ':config/live/:hostname/privkey.pem', //
 | ||||||
| , fullchainPath: ':config/live/:hostname/fullchain.pem'     // Note: both that :config and :hostname
 | 	fullchainPath: ':config/live/:hostname/fullchain.pem', // Note: both that :config and :hostname
 | ||||||
| , certPath: ':config/live/:hostname/cert.pem'               //       will be templated as expected
 | 	certPath: ':config/live/:hostname/cert.pem', //       will be templated as expected
 | ||||||
| , chainPath: ':config/live/:hostname/chain.pem'             //
 | 	chainPath: ':config/live/:hostname/chain.pem', //
 | ||||||
| 
 | 
 | ||||||
| , rsaKeySize: 2048 | 	rsaKeySize: 2048, | ||||||
| 
 | 
 | ||||||
| , debug: true | 	debug: true | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var handlers = { | var handlers = { | ||||||
|   setChallenge: function (opts, hostname, key, val, cb) {   // called during the ACME server handshake, before validation
 | 	setChallenge: function(opts, hostname, key, val, cb) { | ||||||
|     db[key] = { | 		// called during the ACME server handshake, before validation
 | ||||||
|       hostname: hostname | 		db[key] = { | ||||||
|     , key: key | 			hostname: hostname, | ||||||
|     , val: val | 			key: key, | ||||||
|     }; | 			val: val | ||||||
|  | 		}; | ||||||
| 
 | 
 | ||||||
|     cb(null); | 		cb(null); | ||||||
|   } | 	}, | ||||||
| , removeChallenge: function (opts, hostname, key, cb) {     // called after validation on both success and failure
 | 	removeChallenge: function(opts, hostname, key, cb) { | ||||||
|     db[key] = null; | 		// called after validation on both success and failure
 | ||||||
|     cb(null); | 		db[key] = null; | ||||||
|   } | 		cb(null); | ||||||
| , 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),
 | 	getChallenge: function(opts, hostname, key, cb) { | ||||||
|                                                             // not by the library itself
 | 		// this is special because it is called by the webserver
 | ||||||
|   } | 		cb(null, db[key].val); // (see greenlock-cli/bin & greenlock-express/standalone),
 | ||||||
| , agreeToTerms: function (tosUrl, cb) {                     // gives you an async way to expose the legal agreement
 | 		// not by the library itself
 | ||||||
|     cb(null, tosUrl);                                       // (terms of use) to your users before accepting
 | 	}, | ||||||
|   } | 	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); | 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); | process.exit(1); | ||||||
|                                                             // checks :conf/renewal/:hostname.conf
 | // checks :conf/renewal/:hostname.conf
 | ||||||
| greenlock.register({                                        // and either renews or registers
 | greenlock.register( | ||||||
|   domains: ['example.com']                                  // CHANGE TO YOUR DOMAIN
 | 	{ | ||||||
| , email: 'user@email.com'                                   // CHANGE TO YOUR EMAIL
 | 		// and either renews or registers
 | ||||||
| , agreeTos: false                                           // set to true to automatically accept an agreement
 | 		domains: ['example.com'], // CHANGE TO YOUR DOMAIN
 | ||||||
|                                                             // which you have pre-approved (not recommended)
 | 		email: 'user@email.com', // CHANGE TO YOUR EMAIL
 | ||||||
| , rsaKeySize: 2048 | 		agreeTos: false, // set to true to automatically accept an agreement
 | ||||||
| }, function (err) { | 		// which you have pre-approved (not recommended)
 | ||||||
|   if (err) { | 		rsaKeySize: 2048 | ||||||
|     // Note: you must have a webserver running
 | 	}, | ||||||
|     // and expose handlers.getChallenge to it
 | 	function(err) { | ||||||
|     // in order to pass validation
 | 		if (err) { | ||||||
|     // See greenlock-cli and or greenlock-express
 | 			// Note: you must have a webserver running
 | ||||||
|     console.error('[Error]: greenlock/examples/standalone'); | 			// and expose handlers.getChallenge to it
 | ||||||
|     console.error(err.stack); | 			// in order to pass validation
 | ||||||
|   } else { | 			// See greenlock-cli and or greenlock-express
 | ||||||
|     console.log('success'); | 			console.error('[Error]: greenlock/examples/standalone'); | ||||||
|   } | 			console.error(err.stack); | ||||||
| }); | 		} else { | ||||||
|  | 			console.log('success'); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | ); | ||||||
|  | |||||||
							
								
								
									
										101
									
								
								lib/community.js
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								lib/community.js
									
									
									
									
									
								
							| @ -1,29 +1,80 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
| 
 | 
 | ||||||
| function addCommunityMember(pkg, email, domains) { | function addCommunityMember(opts) { | ||||||
|   setTimeout(function () { | 	// { name, version, email, domains, action, communityMember, telemetry }
 | ||||||
|     var https = require('https'); | 	var https = require('https'); | ||||||
|     var req = https.request({ | 	var req = https.request( | ||||||
|       hostname: 'api.ppl.family' | 		{ | ||||||
|     , port: 443 | 			hostname: 'api.ppl.family', | ||||||
|     , path: '/api/ppl.family/public/list' | 			port: 443, | ||||||
|     , method: 'POST' | 			path: '/api/ppl.family/public/list', | ||||||
|     , headers: { | 			method: 'POST', | ||||||
|         'Content-Type': 'application/json' | 			headers: { | ||||||
|       } | 				'Content-Type': 'application/json' | ||||||
|     }, function (err, resp) { | 			} | ||||||
|       if (err) { return; } | 		}, | ||||||
|       resp.on('data', function () {}); | 		function(err, resp) { | ||||||
|     }); | 			if (err) { | ||||||
|     req.write(JSON.stringify({ | 				return; | ||||||
|       address: email | 			} | ||||||
|     , comment: (pkg || 'community') + '  member w/ ' + (domains||[]).map(function (d) { | 			resp.on('data', function() {}); | ||||||
|         return require('crypto').createHash('sha1').update(d).digest('base64') | 		} | ||||||
|           .replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, ''); | 	); | ||||||
|       }).join(',') | 	req.on('error', function(error) { | ||||||
|     })); | 		/* ignore */ | ||||||
|     req.end(); | 	}); | ||||||
|   }, 50); | 	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.end(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports.add = addCommunityMember; | 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);
 | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										23
									
								
								lib/compat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/compat.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | |||||||
|  | '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; | ||||||
|  | } | ||||||
							
								
								
									
										1265
									
								
								lib/core.js
									
									
									
									
									
								
							
							
						
						
									
										1265
									
								
								lib/core.js
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -3,66 +3,106 @@ | |||||||
| var utils = require('./utils'); | var utils = require('./utils'); | ||||||
| 
 | 
 | ||||||
| function _log(debug) { | function _log(debug) { | ||||||
|   if (debug) { | 	if (debug) { | ||||||
|     var args = Array.prototype.slice.call(arguments); | 		var args = Array.prototype.slice.call(arguments); | ||||||
|     args.shift(); | 		args.shift(); | ||||||
|     args.unshift("[greenlock/lib/middleware.js]"); | 		args.unshift('[greenlock/lib/middleware.js]'); | ||||||
|     console.log.apply(console, args); | 		console.log.apply(console, args); | ||||||
|   } | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports.create = function (gl) { | module.exports.create = function(gl) { | ||||||
|   if (!gl.challenges['http-01'] || !gl.challenges['http-01'].get) { | 	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; | 	var log = gl.log || _log; | ||||||
| 
 | 
 | ||||||
|   log(gl.debug, "created middleware"); | 	log(gl.debug, 'created middleware'); | ||||||
|   return function (_app) { | 	return function(_app) { | ||||||
|     if (_app && 'function' !== typeof _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/'; | 			); | ||||||
|  | 		} | ||||||
|  | 		var prefix = gl.acmeChallengePrefix || '/.well-known/acme-challenge/'; | ||||||
| 
 | 
 | ||||||
|     return function (req, res, next) { | 		return function(req, res, next) { | ||||||
|       if (0 !== req.url.indexOf(prefix)) { | 			if (0 !== req.url.indexOf(prefix)) { | ||||||
|         log(gl.debug, "no match, skipping middleware"); | 				log(gl.debug, 'no match, skipping middleware'); | ||||||
|         if ('function' === typeof _app) { | 				if ('function' === typeof _app) { | ||||||
|           _app(req, res, next); | 					_app(req, res, next); | ||||||
|         } | 				} else if ('function' === typeof next) { | ||||||
|         else if ('function' === typeof next) { | 					next(); | ||||||
|           next(); | 				} else { | ||||||
|         } | 					res.statusCode = 500; | ||||||
|         else { | 					res.end( | ||||||
|           res.statusCode = 500; | 						"[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; | 				return; | ||||||
|       } | 			} | ||||||
| 
 | 
 | ||||||
|       log(gl.debug, "this must be tinder, 'cuz it's a match!"); | 			log(gl.debug, "this must be tinder, 'cuz it's a match!"); | ||||||
| 
 | 
 | ||||||
|       var token = req.url.slice(prefix.length); | 			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); | 			var copy = utils.merge({ domains: [hostname] }, gl); | ||||||
|       copy = utils.tplCopy(copy); | 			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; | ||||||
| 
 | 
 | ||||||
|       // TODO tpl copy?
 | 			function cb(opts) { | ||||||
|       // TODO need to restore challengeType
 | 				var secret = opts.keyAuthorization || opts; | ||||||
|       gl.challenges['http-01'].get(copy, hostname, token, function (err, secret) { | 				if (secret && 'string' === typeof secret) { | ||||||
|         if (err || !token) { | 					res.setHeader('Content-Type', 'text/plain; charset=utf-8'); | ||||||
|           res.statusCode = 404; | 					res.end(secret); | ||||||
|           res.setHeader('Content-Type', 'application/json; charset=utf-8'); | 					return; | ||||||
|           res.end('{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }'); | 				} | ||||||
|           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); | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
|         res.setHeader('Content-Type', 'text/plain; charset=utf-8'); | 			var challenger = gl.challenges['http-01'].get; | ||||||
|         res.end(secret); | 			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); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 	}; | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										24
									
								
								lib/utils-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/utils-test.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | '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'); | ||||||
							
								
								
									
										240
									
								
								lib/utils.js
									
									
									
									
									
								
							
							
						
						
									
										240
									
								
								lib/utils.js
									
									
									
									
									
								
							| @ -1,127 +1,165 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  | require('./compat.js'); | ||||||
| 
 | 
 | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var homeRe = new RegExp("^~(\\/|\\\\|\\" + path.sep + ")"); | var homeRe = new RegExp('^~(\\/|\\\\|\\' + path.sep + ')'); | ||||||
| // very basic check. Allows *.example.com.
 | // very basic check. Allows *.example.com.
 | ||||||
| var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/; | var re = /^(\*\.)?[a-zA-Z0-9\.\-]+$/; | ||||||
| var punycode = require('punycode'); | var punycode = require('punycode'); | ||||||
| var promisify = (require('util').promisify || require('bluebird').promisify); | var dnsResolveMxAsync = require('util').promisify(require('dns').resolveMx); | ||||||
| var dnsResolveMxAsync = promisify(require('dns').resolveMx); |  | ||||||
| 
 | 
 | ||||||
| module.exports.attachCertInfo = function (results) { | module.exports.attachCertInfo = function(results) { | ||||||
|   // XXX Note: Parsing the certificate info comes at a great cost (~500kb)
 | 	var certInfo = require('cert-info').info(results.cert); | ||||||
|   var getCertInfo = require('certpem').info; |  | ||||||
|   var certInfo = getCertInfo(results.cert); |  | ||||||
| 
 | 
 | ||||||
|   // subject, altnames, issuedAt, expiresAt
 | 	// subject, altnames, issuedAt, expiresAt
 | ||||||
|   Object.keys(certInfo).forEach(function (key) { | 	Object.keys(certInfo).forEach(function(key) { | ||||||
|     results[key] = certInfo[key]; | 		results[key] = certInfo[key]; | ||||||
|   }); | 	}); | ||||||
| 
 | 
 | ||||||
|   return results; | 	return results; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports.isValidDomain = function (domain) { | module.exports.certHasDomain = function(certInfo, _domain) { | ||||||
|   if (re.test(domain)) { | 	var names = (certInfo.altnames || []).slice(0); | ||||||
|     return domain; | 	names.push(certInfo.subject); | ||||||
|   } | 	return names.some(function(name) { | ||||||
| 
 | 		var domain = _domain.toLowerCase(); | ||||||
|   domain = punycode.toASCII(domain); | 		name = name.toLowerCase(); | ||||||
| 
 | 		if ('*.' === name.substr(0, 2)) { | ||||||
|   if (re.test(domain)) { | 			name = name.substr(2); | ||||||
|     return domain; | 			domain = domain | ||||||
|   } | 				.split('.') | ||||||
| 
 | 				.slice(1) | ||||||
|   return ''; | 				.join('.'); | ||||||
|  | 		} | ||||||
|  | 		return name === domain; | ||||||
|  | 	}); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports.merge = function (/*defaults, args*/) { | module.exports.isValidDomain = function(domain) { | ||||||
|   var allDefaults = Array.prototype.slice.apply(arguments); | 	if (re.test(domain)) { | ||||||
|   var args = allDefaults.shift(); | 		return domain; | ||||||
|   var copy = {}; | 	} | ||||||
| 
 | 
 | ||||||
|   allDefaults.forEach(function (defaults) { | 	domain = punycode.toASCII(domain); | ||||||
|     Object.keys(defaults).forEach(function (key) { |  | ||||||
|       copy[key] = defaults[key]; |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   Object.keys(args).forEach(function (key) { | 	if (re.test(domain)) { | ||||||
|     copy[key] = args[key]; | 		return domain; | ||||||
|   }); | 	} | ||||||
| 
 | 
 | ||||||
|   return copy; | 	return ''; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports.tplCopy = function (copy) { | module.exports.merge = function(/*defaults, args*/) { | ||||||
|   var homedir = require('os').homedir(); | 	var allDefaults = Array.prototype.slice.apply(arguments); | ||||||
|   var tplKeys; | 	var args = allDefaults.shift(); | ||||||
|  | 	var copy = {}; | ||||||
| 
 | 
 | ||||||
|   copy.hostnameGet = function (copy) { | 	allDefaults.forEach(function(defaults) { | ||||||
|     return (copy.domains || [])[0] || copy.domain; | 		Object.keys(defaults).forEach(function(key) { | ||||||
|   }; | 			/* | ||||||
| 
 |       if ('challenges' === key && copy[key] && defaults[key]) { | ||||||
|   Object.keys(copy).forEach(function (key) { |         Object.keys(defaults[key]).forEach(function (k) { | ||||||
|     var newName; |           copy[key][k] = defaults[key][k]; | ||||||
|     if (!/Get$/.test(key)) { |         }); | ||||||
|       return; |       } else { | ||||||
|     } |         copy[key] = defaults[key]; | ||||||
| 
 |  | ||||||
|     newName = key.replace(/Get$/, ''); |  | ||||||
|     copy[newName] = copy[newName] || copy[key](copy); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   tplKeys = Object.keys(copy); |  | ||||||
|   tplKeys.sort(function (a, b) { |  | ||||||
|     return b.length - a.length; |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   tplKeys.forEach(function (key) { |  | ||||||
|     if ('string' !== typeof copy[key]) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     copy[key] = copy[key].replace(homeRe, homedir + path.sep); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   tplKeys.forEach(function (key) { |  | ||||||
|     if ('string' !== typeof copy[key]) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     tplKeys.forEach(function (tplname) { |  | ||||||
|       if (!copy[tplname]) { |  | ||||||
|         // what can't be templated now may be templatable later
 |  | ||||||
|         return; |  | ||||||
|       } |       } | ||||||
|       copy[key] = copy[key].replace(':' + tplname, copy[tplname]); |     */ | ||||||
|     }); | 			copy[key] = defaults[key]; | ||||||
|   }); | 		}); | ||||||
|  | 	}); | ||||||
| 
 | 
 | ||||||
|   return copy; | 	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]; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	return copy; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports.testEmail = function (email) { | module.exports.tplCopy = function(copy) { | ||||||
|   var parts = (email||'').split('@'); | 	var homedir = require('os').homedir(); | ||||||
|   var err; | 	var tplKeys; | ||||||
| 
 | 
 | ||||||
|   if (2 !== parts.length || !parts[0] || !parts[1]) { | 	copy.hostnameGet = function(copy) { | ||||||
|     err = new Error("malformed email address '" + email + "'"); | 		return copy.subject || (copy.domains || [])[0] || copy.domain; | ||||||
|     err.code = 'E_EMAIL'; | 	}; | ||||||
|     return Promise.reject(err); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   return dnsResolveMxAsync(parts[1]).then(function (records) { | 	Object.keys(copy).forEach(function(key) { | ||||||
|     // records only returns when there is data
 | 		var newName; | ||||||
|     if (!records.length) { | 		if (!/Get$/.test(key)) { | ||||||
|       throw new Error("sanity check fail: success, but no MX records returned"); | 			return; | ||||||
|     } | 		} | ||||||
|     return email; | 
 | ||||||
|   }, function (err) { | 		newName = key.replace(/Get$/, ''); | ||||||
|     if ('ENODATA' === err.code) { | 		copy[newName] = copy[newName] || copy[key](copy); | ||||||
|       err = new Error("no MX records found for '" + parts[1] + "'"); | 	}); | ||||||
|       err.code = 'E_EMAIL'; | 
 | ||||||
|       return Promise.reject(err); | 	tplKeys = Object.keys(copy); | ||||||
|     } | 	tplKeys.sort(function(a, b) { | ||||||
|   }); | 		return b.length - a.length; | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	tplKeys.forEach(function(key) { | ||||||
|  | 		if ('string' !== typeof copy[key]) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		copy[key] = copy[key].replace(homeRe, homedir + path.sep); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	tplKeys.forEach(function(key) { | ||||||
|  | 		if ('string' !== typeof copy[key]) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		tplKeys.forEach(function(tplname) { | ||||||
|  | 			if (!copy[tplname]) { | ||||||
|  | 				// what can't be templated now may be templatable later
 | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			copy[key] = copy[key].replace(':' + tplname, copy[tplname]); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	return copy; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module.exports.testEmail = function(email) { | ||||||
|  | 	var parts = (email || '').split('@'); | ||||||
|  | 	var err; | ||||||
|  | 
 | ||||||
|  | 	if (2 !== parts.length || !parts[0] || !parts[1]) { | ||||||
|  | 		err = new Error("malformed email address '" + email + "'"); | ||||||
|  | 		err.code = 'E_EMAIL'; | ||||||
|  | 		return Promise.reject(err); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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' | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 			return email; | ||||||
|  | 		}, | ||||||
|  | 		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
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | |||||||
|  | { | ||||||
|  | 	"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==" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								package.json
									
									
									
									
									
								
							| @ -1,74 +1,53 @@ | |||||||
| { | { | ||||||
|   "name": "greenlock", | 	"name": "greenlock", | ||||||
|   "version": "2.3.7", | 	"version": "2.8.9", | ||||||
|   "description": "Let's Encrypt for node.js on npm", | 	"description": "Greenlock is Let's Encrypt (ACME) client for node.js", | ||||||
|   "main": "index.js", | 	"homepage": "https://greenlock.domains/", | ||||||
|   "files": [ | 	"main": "index.js", | ||||||
|     "lib" | 	"files": [ | ||||||
|   ], | 		"lib" | ||||||
|   "scripts": { | 	], | ||||||
|     "test": "echo \"Error: no test specified\" && exit 1" | 	"scripts": { | ||||||
|   }, | 		"bump": "npm version -m \"chore(release): bump to v%s\"", | ||||||
|   "repository": { | 		"test": "echo \"Error: no test specified\" && exit 1" | ||||||
|     "type": "git", | 	}, | ||||||
|     "url": "https://git.coolaj86.com/coolaj86/greenlock.js.git" | 	"repository": { | ||||||
|   }, | 		"type": "git", | ||||||
|   "keywords": [ | 		"url": "https://git.rootprojects.org/root/greenlock.js.git" | ||||||
|     "Let's Encrypt", | 	}, | ||||||
|     "letsencrypt", | 	"keywords": [ | ||||||
|     "ACME", | 		"Let's Encrypt", | ||||||
|     "v2", | 		"letsencrypt", | ||||||
|     "v02", | 		"ACME", | ||||||
|     "draft-11", | 		"v2", | ||||||
|     "draft-12", | 		"auto-sni", | ||||||
|     "auto-sni", | 		"Free SSL", | ||||||
|     "draft", | 		"Automated HTTPS", | ||||||
|     "11", | 		"tls", | ||||||
|     "12", | 		"https" | ||||||
|     "Free SSL", | 	], | ||||||
|     "Automated HTTPS", | 	"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)", | ||||||
|     "tls", | 	"license": "MPL-2.0", | ||||||
|     "https", | 	"bugs": { | ||||||
|     "Greenlock", | 		"url": "https://git.rootprojects.org/root/greenlock.js/issues" | ||||||
|     "letsencrypt.org", | 	}, | ||||||
|     "le", | 	"trulyOptionalDependencies": { | ||||||
|     "le.js", | 		"bluebird": "^3.5.1", | ||||||
|     "node", | 		"le-acme-core": "^2.1.3" | ||||||
|     "nodejs", | 	}, | ||||||
|     "node.js", | 	"dependencies": { | ||||||
|     "client" | 		"acme": "^1.3.5", | ||||||
|   ], | 		"acme-dns-01-cli": "^3.0.0", | ||||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | 		"acme-v2": "^1.8.7", | ||||||
|   "license": "(MIT OR Apache-2.0)", | 		"cert-info": "^1.5.1", | ||||||
|   "bugs": { | 		"greenlock-store-fs": "^3.0.2", | ||||||
|     "url": "https://git.coolaj86.com/coolaj86/greenlock.js/issues" | 		"keypairs": "^1.2.14", | ||||||
|   }, | 		"le-challenge-fs": "^2.0.2", | ||||||
|   "homepage": "https://git.coolaj86.com/coolaj86/greenlock.js", | 		"le-sni-auto": "^2.1.9", | ||||||
|   "devDependencies": { | 		"le-store-certbot": "^2.2.4", | ||||||
|     "request": "^2.75.0" | 		"rsa-compat": "^2.0.8" | ||||||
|   }, | 	}, | ||||||
|   "trulyOptionalDependencies": { | 	"engines": { | ||||||
|     "bluebird": "^3.5.1", | 		"node": ">=4.5" | ||||||
|     "le-acme-core": "^2.1.3", | 	} | ||||||
|     "ursa": "^0.9.4" |  | ||||||
|   }, |  | ||||||
|   "dependencies": { |  | ||||||
|     "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.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" |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,106 +0,0 @@ | |||||||
| '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 = require('../').LE; | ||||||
| var le = LE.create({ | var le = LE.create({ | ||||||
|   server: 'staging' | 	server: 'staging', | ||||||
| , acme: require('le-acme-core').ACME.create() | 	acme: require('le-acme-core').ACME.create(), | ||||||
| , store: require('le-store-certbot').create({ | 	store: require('le-store-certbot').create({ | ||||||
|     configDir: '~/letsencrypt.test/etc/' | 		configDir: '~/letsencrypt.test/etc/', | ||||||
|   , webrootPath: '~/letsencrypt.test/tmp/:hostname' | 		webrootPath: '~/letsencrypt.test/tmp/:hostname' | ||||||
|   }) | 	}), | ||||||
| , debug: true | 	debug: true | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| // TODO test generateRsaKey code path separately
 | // TODO test generateRsaKey code path separately
 | ||||||
| @ -20,37 +20,45 @@ var testEmail = 'coolaj86+le.' + testId + '@gmail.com'; | |||||||
| var testAccountId = '939573edbf2506c92c9ab32131209d7b'; | var testAccountId = '939573edbf2506c92c9ab32131209d7b'; | ||||||
| 
 | 
 | ||||||
| var tests = [ | var tests = [ | ||||||
|   function () { | 	function() { | ||||||
|     return le.core.accounts.checkAsync({ | 		return le.core.accounts | ||||||
|       accountId: testAccountId | 			.checkAsync({ | ||||||
|     }).then(function (account) { | 				accountId: testAccountId | ||||||
|       if (!account) { | 			}) | ||||||
|         throw new Error("Test account should exist when searched by account id."); | 			.then(function(account) { | ||||||
|       } | 				if (!account) { | ||||||
|     }); | 					throw new Error( | ||||||
|   } | 						'Test account should exist when searched by account id.' | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 	}, | ||||||
| 
 | 
 | ||||||
| , function () { | 	function() { | ||||||
|     return le.core.accounts.checkAsync({ | 		return le.core.accounts | ||||||
|       email: testEmail | 			.checkAsync({ | ||||||
|     }).then(function (account) { | 				email: testEmail | ||||||
|       console.log('account.regr'); | 			}) | ||||||
|       console.log(account.regr); | 			.then(function(account) { | ||||||
|       if (!account) { | 				console.log('account.regr'); | ||||||
|         throw new Error("Test account should exist when searched by email."); | 				console.log(account.regr); | ||||||
|       } | 				if (!account) { | ||||||
|     }); | 					throw new Error( | ||||||
|   } | 						'Test account should exist when searched by email.' | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| function run() { | function run() { | ||||||
|   var test = tests.shift(); | 	var test = tests.shift(); | ||||||
|   if (!test) { | 	if (!test) { | ||||||
|     console.info('All tests passed'); | 		console.info('All tests passed'); | ||||||
|     return; | 		return; | ||||||
|   } | 	} | ||||||
| 
 | 
 | ||||||
|   test().then(run); | 	test().then(run); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| run(); | run(); | ||||||
|  | |||||||
| @ -2,13 +2,13 @@ | |||||||
| 
 | 
 | ||||||
| var LE = require('../').LE; | var LE = require('../').LE; | ||||||
| var le = LE.create({ | var le = LE.create({ | ||||||
|   server: 'staging' | 	server: 'staging', | ||||||
| , acme: require('le-acme-core').ACME.create() | 	acme: require('le-acme-core').ACME.create(), | ||||||
| , store: require('le-store-certbot').create({ | 	store: require('le-store-certbot').create({ | ||||||
|     configDir: '~/letsencrypt.test/etc/' | 		configDir: '~/letsencrypt.test/etc/', | ||||||
|   , webrootPath: '~/letsencrypt.test/tmp/:hostname' | 		webrootPath: '~/letsencrypt.test/tmp/:hostname' | ||||||
|   }) | 	}), | ||||||
| , debug: true | 	debug: true | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| //var testId = Math.round(Date.now() / 1000).toString();
 | //var testId = Math.round(Date.now() / 1000).toString();
 | ||||||
| @ -18,88 +18,117 @@ var testEmail = 'coolaj86+le.' + testId + '@gmail.com'; | |||||||
| var testAccount; | var testAccount; | ||||||
| 
 | 
 | ||||||
| var tests = [ | var tests = [ | ||||||
|   function () { | 	function() { | ||||||
|     return le.core.accounts.checkAsync({ | 		return le.core.accounts | ||||||
|       email: testEmail | 			.checkAsync({ | ||||||
|     }).then(function (account) { | 				email: testEmail | ||||||
|       if (account) { | 			}) | ||||||
|         console.error(account); | 			.then(function(account) { | ||||||
|         throw new Error("Test account should not exist."); | 				if (account) { | ||||||
|       } | 					console.error(account); | ||||||
|     }); | 					throw new Error('Test account should not exist.'); | ||||||
|   } | 				} | ||||||
| , function () { | 			}); | ||||||
|     return le.core.accounts.registerAsync({ | 	}, | ||||||
|       email: testEmail | 	function() { | ||||||
|     , agreeTos: false | 		return le.core.accounts | ||||||
|     , rsaKeySize: 2048 | 			.registerAsync({ | ||||||
|     }).then(function (/*account*/) { | 				email: testEmail, | ||||||
|       throw new Error("Should not register if 'agreeTos' is not truthy."); | 				agreeTos: false, | ||||||
|     }, function (err) { | 				rsaKeySize: 2048 | ||||||
|       if (err.code !== 'E_ARGS') { | 			}) | ||||||
|         throw err; | 			.then( | ||||||
|       } | 				function(/*account*/) { | ||||||
|     }); | 					throw new Error( | ||||||
|   } | 						"Should not register if 'agreeTos' is not truthy." | ||||||
| , function () { | 					); | ||||||
|     return le.core.accounts.registerAsync({ | 				}, | ||||||
|       email: testEmail | 				function(err) { | ||||||
|     , agreeTos: true | 					if (err.code !== 'E_ARGS') { | ||||||
|     , rsaKeySize: 1024 | 						throw err; | ||||||
|     }).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: testEmail, | ||||||
| , function () { | 				agreeTos: true, | ||||||
|     return le.core.accounts.registerAsync({ | 				rsaKeySize: 1024 | ||||||
|       email: fakeEmail | 			}) | ||||||
|     , agreeTos: true | 			.then( | ||||||
|     , rsaKeySize: 2048 | 				function(/*account*/) { | ||||||
|     }).then(function (/*account*/) { | 					throw new Error( | ||||||
|       // TODO test mx record
 | 						"Should not register if 'rsaKeySize' is less than 2048." | ||||||
|       throw new Error("Registration should NOT succeed with a bad email address."); | 					); | ||||||
|     }, function (err) { | 				}, | ||||||
|       if (err.code !== 'E_EMAIL') { | 				function(err) { | ||||||
|         throw err; | 					if (err.code !== 'E_ARGS') { | ||||||
|       } | 						throw err; | ||||||
|     }); | 					} | ||||||
|   } | 				} | ||||||
| , function () { | 			); | ||||||
|     return le.core.accounts.registerAsync({ | 	}, | ||||||
|       email: testEmail | 	function() { | ||||||
|     , agreeTos: true | 		return le.core.accounts | ||||||
|     , rsaKeySize: 2048 | 			.registerAsync({ | ||||||
|     }).then(function (account) { | 				email: fakeEmail, | ||||||
|       testAccount = account; | 				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) { | ||||||
|  | 					if (err.code !== 'E_EMAIL') { | ||||||
|  | 						throw err; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			); | ||||||
|  | 	}, | ||||||
|  | 	function() { | ||||||
|  | 		return le.core.accounts | ||||||
|  | 			.registerAsync({ | ||||||
|  | 				email: testEmail, | ||||||
|  | 				agreeTos: true, | ||||||
|  | 				rsaKeySize: 2048 | ||||||
|  | 			}) | ||||||
|  | 			.then(function(account) { | ||||||
|  | 				testAccount = account; | ||||||
| 
 | 
 | ||||||
|       console.log(testEmail); | 				console.log(testEmail); | ||||||
|       console.log(testAccount); | 				console.log(testAccount); | ||||||
| 
 | 
 | ||||||
|       if (!account) { | 				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."); | 				} | ||||||
|       } | 				if (!account.email) { | ||||||
|       if (!account.id) { | 					throw new Error('Registration should return the email.'); | ||||||
|         throw new Error("Registration should return the account id."); | 				} | ||||||
|       } | 				if (!account.id) { | ||||||
|     }); | 					throw new Error( | ||||||
|   } | 						'Registration should return the account id.' | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| function run() { | function run() { | ||||||
|   var test = tests.shift(); | 	var test = tests.shift(); | ||||||
|   if (!test) { | 	if (!test) { | ||||||
|     console.info('All tests passed'); | 		console.info('All tests passed'); | ||||||
|     return; | 		return; | ||||||
|   } | 	} | ||||||
| 
 | 
 | ||||||
|   test().then(run); | 	test().then(run); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| run(); | run(); | ||||||
|  | |||||||
							
								
								
									
										18
									
								
								tests/domain-fronting.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								tests/domain-fronting.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | #!/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 = require('../').LE; | ||||||
| var le = LE.create({ | var le = LE.create({ | ||||||
|   server: 'staging' | 	server: 'staging', | ||||||
| , acme: require('le-acme-core').ACME.create() | 	acme: require('le-acme-core').ACME.create(), | ||||||
| , store: require('le-store-certbot').create({ | 	store: require('le-store-certbot').create({ | ||||||
|     configDir: '~/letsencrypt.test/etc' | 		configDir: '~/letsencrypt.test/etc', | ||||||
|   , webrootPath: '~/letsencrypt.test/var/:hostname' | 		webrootPath: '~/letsencrypt.test/var/:hostname' | ||||||
|   }) | 	}), | ||||||
| , challenge: require('le-challenge-fs').create({ | 	challenge: require('le-challenge-fs').create({ | ||||||
|     webrootPath: '~/letsencrypt.test/var/:hostname' | 		webrootPath: '~/letsencrypt.test/var/:hostname' | ||||||
|   }) | 	}), | ||||||
| , debug: true | 	debug: true | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| // TODO test generateRsaKey code path separately
 | // TODO test generateRsaKey code path separately
 | ||||||
| @ -21,54 +21,62 @@ var le = LE.create({ | |||||||
| var testId = 'test1000'; | var testId = 'test1000'; | ||||||
| var testEmail = 'coolaj86+le.' + testId + '@gmail.com'; | var testEmail = 'coolaj86+le.' + testId + '@gmail.com'; | ||||||
| // TODO integrate with Daplie Domains for junk domains to test with
 | // TODO integrate with Daplie Domains for junk domains to test with
 | ||||||
| var testDomains = [ 'pokemap.hellabit.com', 'www.pokemap.hellabit.com' ]; | var testDomains = ['pokemap.hellabit.com', 'www.pokemap.hellabit.com']; | ||||||
| 
 | 
 | ||||||
| var tests = [ | var tests = [ | ||||||
|   function () { | 	function() { | ||||||
|     return le.core.certificates.checkAsync({ | 		return le.core.certificates | ||||||
|       domains: [ 'example.com', 'www.example.com' ] | 			.checkAsync({ | ||||||
|     }).then(function (cert) { | 				domains: ['example.com', 'www.example.com'] | ||||||
|       if (cert) { | 			}) | ||||||
|         throw new Error("Bogus domain should not have certificate."); | 			.then(function(cert) { | ||||||
|       } | 				if (cert) { | ||||||
|     }); | 					throw new Error( | ||||||
|   } | 						'Bogus domain should not have certificate.' | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 	}, | ||||||
| 
 | 
 | ||||||
| , function () { | 	function() { | ||||||
|     return le.core.certificates.getAsync({ | 		return le.core.certificates | ||||||
|       email: testEmail | 			.getAsync({ | ||||||
|     , domains: testDomains | 				email: testEmail, | ||||||
|     }).then(function (certs) { | 				domains: testDomains | ||||||
|       if (!certs) { | 			}) | ||||||
|         throw new Error("Should have acquired certificate for domains."); | 			.then(function(certs) { | ||||||
|       } | 				if (!certs) { | ||||||
|     }); | 					throw new Error( | ||||||
|   } | 						'Should have acquired certificate for domains.' | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| function run() { | function run() { | ||||||
|   //var express = require(express);
 | 	//var express = require(express);
 | ||||||
|   var server = require('http').createServer(le.middleware()); | 	var server = require('http').createServer(le.middleware()); | ||||||
|   server.listen(80, function () { | 	server.listen(80, function() { | ||||||
|     console.log('Server running, proceeding to test.'); | 		console.log('Server running, proceeding to test.'); | ||||||
| 
 | 
 | ||||||
|     function next() { | 		function next() { | ||||||
|       var test = tests.shift(); | 			var test = tests.shift(); | ||||||
|       if (!test) { | 			if (!test) { | ||||||
|         server.close(); | 				server.close(); | ||||||
|         console.info('All tests passed'); | 				console.info('All tests passed'); | ||||||
|         return; | 				return; | ||||||
|       } | 			} | ||||||
| 
 | 
 | ||||||
|       test().then(next, function (err) { | 			test().then(next, function(err) { | ||||||
|         console.error('ERROR'); | 				console.error('ERROR'); | ||||||
|         console.error(err.stack); | 				console.error(err.stack); | ||||||
|         server.close(); | 				server.close(); | ||||||
|       }); | 			}); | ||||||
|     } | 		} | ||||||
| 
 | 
 | ||||||
|     next(); | 		next(); | ||||||
|   }); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| run(); | run(); | ||||||
|  | |||||||
| @ -2,16 +2,16 @@ | |||||||
| 
 | 
 | ||||||
| var LE = require('../').LE; | var LE = require('../').LE; | ||||||
| var le = LE.create({ | var le = LE.create({ | ||||||
|   server: 'staging' | 	server: 'staging', | ||||||
| , acme: require('le-acme-core').ACME.create() | 	acme: require('le-acme-core').ACME.create(), | ||||||
| , store: require('le-store-certbot').create({ | 	store: require('le-store-certbot').create({ | ||||||
|     configDir: '~/letsencrypt.test/etc' | 		configDir: '~/letsencrypt.test/etc', | ||||||
|   , webrootPath: '~/letsencrypt.test/var/:hostname' | 		webrootPath: '~/letsencrypt.test/var/:hostname' | ||||||
|   }) | 	}), | ||||||
| , challenge: require('le-challenge-fs').create({ | 	challenge: require('le-challenge-fs').create({ | ||||||
|     webrootPath: '~/letsencrypt.test/var/:hostname' | 		webrootPath: '~/letsencrypt.test/var/:hostname' | ||||||
|   }) | 	}), | ||||||
| , debug: true | 	debug: true | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| // TODO test generateRsaKey code path separately
 | // TODO test generateRsaKey code path separately
 | ||||||
| @ -21,82 +21,117 @@ var le = LE.create({ | |||||||
| var testId = 'test1000'; | var testId = 'test1000'; | ||||||
| var testEmail = 'coolaj86+le.' + testId + '@gmail.com'; | var testEmail = 'coolaj86+le.' + testId + '@gmail.com'; | ||||||
| // TODO integrate with Daplie Domains for junk domains to test with
 | // TODO integrate with Daplie Domains for junk domains to test with
 | ||||||
| var testDomains = [ 'pokemap.hellabit.com', 'www.pokemap.hellabit.com' ]; | var testDomains = ['pokemap.hellabit.com', 'www.pokemap.hellabit.com']; | ||||||
| var testCerts; | var testCerts; | ||||||
| 
 | 
 | ||||||
| var tests = [ | var tests = [ | ||||||
|   function () { | 	function() { | ||||||
|     // TODO test that an altname also fetches the proper certificate
 | 		// TODO test that an altname also fetches the proper certificate
 | ||||||
|     return le.core.certificates.checkAsync({ | 		return le.core.certificates | ||||||
|       domains: testDomains | 			.checkAsync({ | ||||||
|     }).then(function (certs) { | 				domains: testDomains | ||||||
|       if (!certs) { | 			}) | ||||||
|         throw new Error("Either certificates.registerAsync (in previous test)" | 			.then(function(certs) { | ||||||
|           + " or certificates.checkAsync (in this test) failed."); | 				if (!certs) { | ||||||
|       } | 					throw new Error( | ||||||
|  | 						'Either certificates.registerAsync (in previous test)' + | ||||||
|  | 							' or certificates.checkAsync (in this test) failed.' | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
|       testCerts = certs; | 				testCerts = certs; | ||||||
|       console.log('Issued At', new Date(certs.issuedAt).toISOString()); | 				console.log( | ||||||
|       console.log('Expires At', new Date(certs.expiresAt).toISOString()); | 					'Issued At', | ||||||
|  | 					new Date(certs.issuedAt).toISOString() | ||||||
|  | 				); | ||||||
|  | 				console.log( | ||||||
|  | 					'Expires At', | ||||||
|  | 					new Date(certs.expiresAt).toISOString() | ||||||
|  | 				); | ||||||
| 
 | 
 | ||||||
|       if (certs.expiresAt <= Date.now()) { | 				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 () { | 	function() { | ||||||
|     return le.core.certificates.renewAsync({ | 		return le.core.certificates | ||||||
|       email: testEmail | 			.renewAsync( | ||||||
|     , domains: testDomains | 				{ | ||||||
|     }, testCerts).then(function () { | 					email: testEmail, | ||||||
|       throw new Error("Should not have renewed non-expired certificates."); | 					domains: testDomains | ||||||
|     }, function (err) { | 				}, | ||||||
|       if ('E_NOT_RENEWABLE' !== err.code) { | 				testCerts | ||||||
|         throw err; | 			) | ||||||
|       } | 			.then( | ||||||
|     }); | 				function() { | ||||||
|   } | 					throw new Error( | ||||||
|  | 						'Should not have renewed non-expired certificates.' | ||||||
|  | 					); | ||||||
|  | 				}, | ||||||
|  | 				function(err) { | ||||||
|  | 					if ('E_NOT_RENEWABLE' !== err.code) { | ||||||
|  | 						throw err; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			); | ||||||
|  | 	}, | ||||||
| 
 | 
 | ||||||
| , function () { | 	function() { | ||||||
|     return le.core.certificates.renewAsync({ | 		return le.core.certificates | ||||||
|       email: testEmail | 			.renewAsync( | ||||||
|     , domains: testDomains | 				{ | ||||||
|     , renewWithin: 720 * 24 * 60 * 60 * 1000 | 					email: testEmail, | ||||||
|     }, testCerts).then(function (certs) { | 					domains: testDomains, | ||||||
|       console.log('Issued At', new Date(certs.issuedAt).toISOString()); | 					renewWithin: 720 * 24 * 60 * 60 * 1000 | ||||||
|       console.log('Expires At', new Date(certs.expiresAt).toISOString()); | 				}, | ||||||
|  | 				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) { | 				if (certs.issuedAt === testCerts.issuedAt) { | ||||||
|         throw new Error("Should not have returned existing certificates."); | 					throw new Error( | ||||||
|       } | 						'Should not have returned existing certificates.' | ||||||
|     }); | 					); | ||||||
|   } | 				} | ||||||
|  | 			}); | ||||||
|  | 	} | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| function run() { | function run() { | ||||||
|   //var express = require(express);
 | 	//var express = require(express);
 | ||||||
|   var server = require('http').createServer(le.middleware()); | 	var server = require('http').createServer(le.middleware()); | ||||||
|   server.listen(80, function () { | 	server.listen(80, function() { | ||||||
|     console.log('Server running, proceeding to test.'); | 		console.log('Server running, proceeding to test.'); | ||||||
| 
 | 
 | ||||||
|     function next() { | 		function next() { | ||||||
|       var test = tests.shift(); | 			var test = tests.shift(); | ||||||
|       if (!test) { | 			if (!test) { | ||||||
|         server.close(); | 				server.close(); | ||||||
|         console.info('All tests passed'); | 				console.info('All tests passed'); | ||||||
|         return; | 				return; | ||||||
|       } | 			} | ||||||
| 
 | 
 | ||||||
|       test().then(next, function (err) { | 			test().then(next, function(err) { | ||||||
|         console.error('ERROR'); | 				console.error('ERROR'); | ||||||
|         console.error(err.stack); | 				console.error(err.stack); | ||||||
|         server.close(); | 				server.close(); | ||||||
|       }); | 			}); | ||||||
|     } | 		} | ||||||
| 
 | 
 | ||||||
|     next(); | 		next(); | ||||||
|   }); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| run(); | run(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user