Compare commits
80 Commits
aece586c90
...
0237336e8f
Author | SHA1 | Date |
---|---|---|
AJ ONeal | 0237336e8f | |
AJ ONeal | f7dbdc1ea5 | |
AJ ONeal | 40401779d9 | |
AJ ONeal | 94fd657562 | |
AJ ONeal | 33e6f800b8 | |
AJ ONeal | 929429f1ef | |
AJ ONeal | 977de24648 | |
AJ ONeal | 9f0dd2e64b | |
AJ ONeal | 6650defebb | |
AJ ONeal | ac237148ba | |
AJ ONeal | 22651f1343 | |
AJ ONeal | 07cd2ced9c | |
Jonathan Hui | 000b99cb5e | |
Jonathan Hui | 4513a6d49c | |
AJ ONeal | fb69d25250 | |
AJ ONeal | c8b895633c | |
AJ ONeal | 396cac3b74 | |
AJ ONeal | 5316af67be | |
AJ ONeal | 93b9158b1b | |
AJ ONeal | e75d390842 | |
AJ ONeal | 2569c36260 | |
AJ ONeal | 44cd0f3d2e | |
AJ ONeal | 0a382fdb44 | |
AJ ONeal | 1766790424 | |
AJ ONeal | 6aafe3d663 | |
AJ ONeal | 2c936a21ce | |
AJ ONeal | e29d237a2d | |
AJ ONeal | 0d14db1f1c | |
AJ ONeal | f4cdbe7a47 | |
AJ ONeal | 1b388788d8 | |
AJ ONeal | 461ad43620 | |
AJ ONeal | ddaebd9387 | |
AJ ONeal | 8afda1184e | |
AJ ONeal | 0cbdf53322 | |
AJ ONeal | 90f65a1a63 | |
AJ ONeal | ca219a00e4 | |
AJ ONeal | d5d14bd968 | |
AJ ONeal | 51ef9be517 | |
AJ ONeal | 082f0e4522 | |
AJ ONeal | 593c2d5fca | |
AJ ONeal | fc513b3e70 | |
AJ ONeal | 7320cf624c | |
AJ ONeal | 6d2a62e7b5 | |
AJ ONeal | 0601df80c6 | |
AJ ONeal | fe44523243 | |
AJ ONeal | 03e2513919 | |
AJ ONeal | df0f870665 | |
AJ ONeal | e60b4356c1 | |
AJ ONeal | 4960604440 | |
AJ ONeal | eb86d4444b | |
AJ ONeal | 5d82ea60c5 | |
AJ ONeal | ff000c40f1 | |
AJ ONeal | c45fcdf150 | |
AJ ONeal | 7e08b4c157 | |
AJ ONeal | 8375f6ef5c | |
AJ ONeal | 64107756a1 | |
AJ ONeal | 61715ab952 | |
AJ ONeal | 56ec8cbd36 | |
AJ ONeal | 37d9ac0436 | |
AJ ONeal | 7ee525018c | |
AJ ONeal | 382a7cc4a9 | |
AJ ONeal | a612f4f98b | |
AJ ONeal | 2abdfcc665 | |
AJ ONeal | bc13451368 | |
AJ ONeal | 62bd2ab4c7 | |
AJ ONeal | 108e59ef8b | |
AJ ONeal | e71298c305 | |
AJ ONeal | 781a735146 | |
AJ ONeal | 540ac6c310 | |
AJ ONeal | 8c32887b10 | |
AJ ONeal | 2cfba7a2e7 | |
AJ ONeal | ea02a93fba | |
AJ ONeal | 992a684a28 | |
AJ ONeal | 67e0885675 | |
AJ ONeal | a6bd58506c | |
AJ ONeal | b7505cbccb | |
AJ ONeal | 3562b9ebfb | |
AJ ONeal | 282f748e77 | |
AJ ONeal | 20e8d09219 | |
AJ ONeal | 1abd3e43de |
|
@ -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.
|
||||||
|
|
372
README.md
372
README.md
|
@ -1,91 +1,85 @@
|
||||||
!["Greenlock Logo"](https://git.coolaj86.com/coolaj86/greenlock.js/raw/branch/master/logo/greenlock-1063x250.png "Greenlock lock logo and work mark")
|
# Greenlock v3 on its way (Nov 1st, 2019)
|
||||||
|
|
||||||
!["Greenlock Function"](https://git.coolaj86.com/coolaj86/greenlock.js/raw/branch/master/logo/from-not-secure-to-secure-url-bar.png "from url bar showing not secure to url bar showing secure")
|
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 Logo"](https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/greenlock-1063x250.png 'Greenlock lock logo and work mark')
|
||||||
|
|
||||||
|
!["Greenlock Function"](https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/from-not-secure-to-secure-url-bar.png 'from url bar showing not secure to url bar showing secure')
|
||||||
|
|
||||||
|
# [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>
|
||||||
|
|
||||||
!["Lifetime Downloads"](https://img.shields.io/npm/dt/greenlock.svg "Lifetime Download Count can't be shown")
|
!["Lifetime Downloads"](https://img.shields.io/npm/dt/greenlock.svg "Lifetime Download Count can't be shown")
|
||||||
!["Monthly Downloads"](https://img.shields.io/npm/dm/greenlock.svg "Monthly Download Count can't be shown")
|
!["Monthly Downloads"](https://img.shields.io/npm/dm/greenlock.svg "Monthly Download Count can't be shown")
|
||||||
!["Weekly Downloads"](https://img.shields.io/npm/dw/greenlock.svg "Weekly Download Count can't be shown")
|
!["Weekly Downloads"](https://img.shields.io/npm/dw/greenlock.svg "Weekly Download Count can't be shown")
|
||||||
!["Stackoverflow Questions"](https://img.shields.io/stackexchange/stackoverflow/t/greenlock.svg "S.O. Question count can't be shown")
|
!["Stackoverflow Questions"](https://img.shields.io/stackexchange/stackoverflow/t/greenlock.svg "S.O. Question count can't be shown")
|
||||||
|
|
||||||
| 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] VanillaJS
|
||||||
|
- [x] Limited Dependencies
|
||||||
|
- [x] MPL-2.0 licensed (great for hobbyists and DIYers)
|
||||||
|
- [x] [Contact us](mailto:support@rootprojects.org?subject=Greenlock%20Commercial%20Support) for Business Support Plans and Commercial LTS Licensing (great for IoT, On-Prem, Web Hosting, etc)
|
||||||
- [x] Automatic HTTPS
|
- [x] Automatic HTTPS
|
||||||
- [x] Free SSL
|
- [x] Free SSL
|
||||||
- [x] Free Wildcard SSL
|
- [x] Free Wildcard SSL
|
||||||
- [x] Multiple domain support (up to 100 altnames per SAN)
|
- [x] Multiple domain support (up to 100 altnames per SAN)
|
||||||
- [x] Dynamic Virtual Hosting (vhost)
|
- [x] Dynamic Virtual Hosting (vhost)
|
||||||
- [x] Automatical renewal (10 to 14 days before expiration)
|
- [x] Automatical renewal (10 to 14 days before expiration)
|
||||||
- [x] Great ACME support via [acme.js](https://git.coolaj86.com/coolaj86/acme-v2.js)
|
- [x] Great ACME support via [acme.js](https://git.rootprojects.org/root/acme-v2.js)
|
||||||
- [x] "dry run" with self-diagnostics
|
- [x] "dry run" with self-diagnostics
|
||||||
- [x] ACME draft 12
|
- [x] ACME draft 12
|
||||||
- [x] Let's Encrypt v2
|
- [x] Let's Encrypt v2
|
||||||
- [x] Let's Encrypt v1
|
- [x] ~Let's Encrypt v1~ (deprecated)
|
||||||
- [x] [Commandline](https://git.coolaj86.com/coolaj86/greenlock-cli.js) (cli) Utilities
|
- [x] [Commandline](https://git.rootprojects.org/root/greenlock-cli.js) (cli) Utilities
|
||||||
- [x] Works with `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell`, and more
|
- [x] Works with `bash`, `fish`, `zsh`, `cmd.exe`, `PowerShell`, and more
|
||||||
- [x] [Browser](https://git.coolaj86.com/coolaj86/greenlock.html) Support
|
- [x] [Browser](https://git.rootprojects.org/root/greenlock.html) Support
|
||||||
- [x] Full node.js support, with modules for
|
- [x] Full node.js support, with modules for
|
||||||
- [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] [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
|
- [x] Great for securing your Raspberry Pi and IoT projects
|
||||||
- [x] Extensible Plugin Support
|
- [x] Extensible Plugin Support
|
||||||
- [x] AWS S3, AWS Route53, Azure, CloudFlare, Consul, Digital Ocean, etcd, Redis
|
- [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,24 +97,40 @@ 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.
|
||||||
|
|
||||||
|
@ -134,38 +144,37 @@ Great when
|
||||||
// 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')
|
||||||
|
.createServer(greenlock.tlsOptions, function(req, res) {
|
||||||
res.end('Hello, Secure World!');
|
res.end('Hello, Secure World!');
|
||||||
}).listen(443);
|
})
|
||||||
|
.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
|
||||||
|
|
||||||
|
@ -179,29 +188,34 @@ 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,
|
||||||
|
|
||||||
|
// the default servername to use when the client doesn't specify
|
||||||
|
servername: 'example.com',
|
||||||
|
|
||||||
// If you wish to replace the default account and domain key storage plugin
|
// If you wish to replace the default account and domain key storage plugin
|
||||||
, store: require('le-store-certbot').create({
|
store: require('le-store-fs').create({
|
||||||
configDir: path.join(os.homedir(), 'acme/etc')
|
configDir: path.join(os.homedir(), 'acme/etc'),
|
||||||
, webrootPath: '/tmp/acme-challenges'
|
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
|
||||||
|
@ -214,13 +228,10 @@ function approveDomains(opts, certs, cb) {
|
||||||
|
|
||||||
// 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;
|
||||||
}
|
|
||||||
else {
|
|
||||||
opts.email = 'john.doe@example.com';
|
opts.email = 'john.doe@example.com';
|
||||||
opts.agreeTos = true;
|
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';
|
||||||
|
@ -229,21 +240,23 @@ function approveDomains(opts, certs, cb) {
|
||||||
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')
|
||||||
|
.createServer(greenlock.tlsOptions, function(req, res) {
|
||||||
res.end('Hello, Secure World!');
|
res.end('Hello, Secure World!');
|
||||||
}).listen(443);
|
})
|
||||||
|
.listen(443);
|
||||||
```
|
```
|
||||||
|
|
||||||
Manual HTTPS
|
## Manual HTTPS
|
||||||
-------------
|
|
||||||
|
|
||||||
Here's a taste of the API that you might use if building a commandline tool or API integration
|
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,
|
||||||
|
cert: certs.cert + '\r\n' + certs.chain
|
||||||
|
};
|
||||||
|
require('https')
|
||||||
|
.createServer(tlsOptions, function(req, res) {
|
||||||
res.end('Hello, Secure World!');
|
res.end('Hello, Secure World!');
|
||||||
}).listen(443);
|
})
|
||||||
|
.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,55 +5,64 @@ 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) {
|
||||||
|
// called during the ACME server handshake, before validation
|
||||||
db[key] = {
|
db[key] = {
|
||||||
hostname: hostname
|
hostname: hostname,
|
||||||
, key: key
|
key: key,
|
||||||
, val: val
|
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) {
|
||||||
|
// called after validation on both success and failure
|
||||||
db[key] = null;
|
db[key] = null;
|
||||||
cb(null);
|
cb(null);
|
||||||
}
|
},
|
||||||
, getChallenge: function (opts, hostname, key, cb) { // this is special because it is called by the webserver
|
getChallenge: function(opts, hostname, key, cb) {
|
||||||
|
// this is special because it is called by the webserver
|
||||||
cb(null, db[key].val); // (see greenlock-cli/bin & greenlock-express/standalone),
|
cb(null, db[key].val); // (see greenlock-cli/bin & greenlock-express/standalone),
|
||||||
// not by the library itself
|
// not by the library itself
|
||||||
}
|
},
|
||||||
, agreeToTerms: function (tosUrl, cb) { // gives you an async way to expose the legal agreement
|
agreeToTerms: function(tosUrl, cb) {
|
||||||
|
// gives you an async way to expose the legal agreement
|
||||||
cb(null, tosUrl); // (terms of use) to your users before accepting
|
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
|
||||||
|
email: 'user@email.com', // CHANGE TO YOUR EMAIL
|
||||||
|
agreeTos: false, // set to true to automatically accept an agreement
|
||||||
// which you have pre-approved (not recommended)
|
// which you have pre-approved (not recommended)
|
||||||
, rsaKeySize: 2048
|
rsaKeySize: 2048
|
||||||
}, function (err) {
|
},
|
||||||
|
function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
// Note: you must have a webserver running
|
// Note: you must have a webserver running
|
||||||
// and expose handlers.getChallenge to it
|
// and expose handlers.getChallenge to it
|
||||||
|
@ -64,4 +73,5 @@ greenlock.register({ // and either renews
|
||||||
} else {
|
} else {
|
||||||
console.log('success');
|
console.log('success');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
770
index.js
770
index.js
|
@ -1,25 +1,52 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
/*global Promise*/
|
||||||
|
require('./lib/compat.js');
|
||||||
|
|
||||||
|
// I hate this code so much.
|
||||||
|
// Soooo many shims for backwards compatibility (some stuff dating back to v1)
|
||||||
|
// v3 will be a clean break and I'll delete half of the code...
|
||||||
|
|
||||||
var DAY = 24 * 60 * 60 * 1000;
|
var DAY = 24 * 60 * 60 * 1000;
|
||||||
//var MIN = 60 * 1000;
|
//var MIN = 60 * 1000;
|
||||||
var ACME = require('acme-v2/compat').ACME;
|
var ACME = require('acme-v2/compat').ACME;
|
||||||
var PromiseA;
|
var pkg = require('./package.json');
|
||||||
try {
|
|
||||||
PromiseA = require('bluebird');
|
|
||||||
} catch(e) {
|
|
||||||
PromiseA = global.Promise;
|
|
||||||
}
|
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
function promisifyAllSelf(obj) {
|
function promisifyAllSelf(obj) {
|
||||||
if (obj.__promisified) { return obj; }
|
if (obj.__promisified) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
Object.keys(obj).forEach(function(key) {
|
Object.keys(obj).forEach(function(key) {
|
||||||
if ('function' === typeof obj[key]) {
|
if ('function' === typeof obj[key] && !/Async$/.test(key)) {
|
||||||
obj[key + 'Async'] = util.promisify(obj[key]);
|
obj[key + 'Async'] = util.promisify(obj[key]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
obj.__promisified = true;
|
obj.__promisified = true;
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
function promisifyAllStore(obj) {
|
||||||
|
Object.keys(obj).forEach(function(key) {
|
||||||
|
if ('function' !== typeof obj[key] || /Async$/.test(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p;
|
||||||
|
if (0 === obj[key].length || 1 === obj[key].length) {
|
||||||
|
// wrap just in case it's synchronous (or improperly throws)
|
||||||
|
p = function(opts) {
|
||||||
|
return Promise.resolve().then(function() {
|
||||||
|
return obj[key](opts);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
p = util.promisify(obj[key]);
|
||||||
|
}
|
||||||
|
// internal backwards compat
|
||||||
|
obj[key + 'Async'] = p;
|
||||||
|
});
|
||||||
|
obj.__promisified = true;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
var Greenlock = module.exports;
|
var Greenlock = module.exports;
|
||||||
Greenlock.Greenlock = Greenlock;
|
Greenlock.Greenlock = Greenlock;
|
||||||
|
@ -31,20 +58,20 @@ 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("[gl/index.js]");
|
args.unshift('[gl/index.js]');
|
||||||
console.log.apply(console, args);
|
console.log.apply(console, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Greenlock.defaults = {
|
Greenlock.defaults = {
|
||||||
productionServerUrl: 'https://acme-v01.api.letsencrypt.org/directory'
|
productionServerUrl: 'https://acme-v01.api.letsencrypt.org/directory',
|
||||||
, stagingServerUrl: 'https://acme-staging.api.letsencrypt.org/directory'
|
stagingServerUrl: 'https://acme-staging.api.letsencrypt.org/directory',
|
||||||
|
|
||||||
, rsaKeySize: ACME.rsaKeySize || 2048
|
rsaKeySize: ACME.rsaKeySize || 2048,
|
||||||
, challengeType: ACME.challengeType || 'http-01'
|
challengeType: ACME.challengeType || 'http-01',
|
||||||
, challengeTypes: ACME.challengeTypes || [ 'http-01', 'dns-01' ]
|
challengeTypes: ACME.challengeTypes || ['http-01', 'dns-01'],
|
||||||
|
|
||||||
, acmeChallengePrefix: ACME.acmeChallengePrefix
|
acmeChallengePrefix: ACME.acmeChallengePrefix
|
||||||
};
|
};
|
||||||
|
|
||||||
// backwards compat
|
// backwards compat
|
||||||
|
@ -55,27 +82,27 @@ Object.keys(Greenlock.defaults).forEach(function (key) {
|
||||||
// show all possible options
|
// show all possible options
|
||||||
var u; // undefined
|
var u; // undefined
|
||||||
Greenlock._undefined = {
|
Greenlock._undefined = {
|
||||||
acme: u
|
acme: u,
|
||||||
, store: u
|
store: u,
|
||||||
, challenge: u
|
//, challenge: u
|
||||||
, challenges: u
|
challenges: u,
|
||||||
, sni: u
|
sni: u,
|
||||||
, tlsOptions: u
|
tlsOptions: u,
|
||||||
|
|
||||||
, register: u
|
register: u,
|
||||||
, check: u
|
check: u,
|
||||||
|
|
||||||
, renewWithin: u // le-auto-sni and core
|
renewWithin: u, // le-auto-sni and core
|
||||||
//, renewBy: u // le-auto-sni
|
//, renewBy: u // le-auto-sni
|
||||||
, acmeChallengePrefix: u
|
acmeChallengePrefix: u,
|
||||||
, rsaKeySize: u
|
rsaKeySize: u,
|
||||||
, challengeType: u
|
challengeType: u,
|
||||||
, server: u
|
server: u,
|
||||||
, version: u
|
version: u,
|
||||||
, agreeToTerms: u
|
agreeToTerms: u,
|
||||||
, _ipc: u
|
_ipc: u,
|
||||||
, duplicate: u
|
duplicate: u,
|
||||||
, _acmeUrls: u
|
_acmeUrls: u
|
||||||
};
|
};
|
||||||
Greenlock._undefine = function(gl) {
|
Greenlock._undefine = function(gl) {
|
||||||
Object.keys(Greenlock._undefined).forEach(function(key) {
|
Object.keys(Greenlock._undefined).forEach(function(key) {
|
||||||
|
@ -87,12 +114,19 @@ Greenlock._undefine = function (gl) {
|
||||||
return gl;
|
return gl;
|
||||||
};
|
};
|
||||||
Greenlock.create = function(gl) {
|
Greenlock.create = function(gl) {
|
||||||
gl.store = gl.store || require('le-store-certbot').create({
|
if (!gl.store) {
|
||||||
debug: gl.debug
|
console.warn(
|
||||||
, configDir: gl.configDir
|
"Deprecation Notice: You're haven't chosen a storage strategy." +
|
||||||
, logsDir: gl.logsDir
|
" The old default is 'le-store-certbot', but the new default will be 'greenlock-store-fs'." +
|
||||||
, webrootPath: gl.webrootPath
|
" Please `npm install greenlock-store-fs@3` and explicitly set `{ store: require('greenlock-store-fs') }`."
|
||||||
|
);
|
||||||
|
gl.store = require('le-store-certbot').create({
|
||||||
|
debug: gl.debug,
|
||||||
|
configDir: gl.configDir,
|
||||||
|
logsDir: gl.logsDir,
|
||||||
|
webrootPath: gl.webrootPath
|
||||||
});
|
});
|
||||||
|
}
|
||||||
gl.core = require('./lib/core');
|
gl.core = require('./lib/core');
|
||||||
var log = gl.log || _log;
|
var log = gl.log || _log;
|
||||||
|
|
||||||
|
@ -101,16 +135,20 @@ Greenlock.create = function (gl) {
|
||||||
}
|
}
|
||||||
if (!gl.challenges['http-01']) {
|
if (!gl.challenges['http-01']) {
|
||||||
gl.challenges['http-01'] = require('le-challenge-fs').create({
|
gl.challenges['http-01'] = require('le-challenge-fs').create({
|
||||||
debug: gl.debug
|
debug: gl.debug,
|
||||||
, webrootPath: gl.webrootPath
|
webrootPath: gl.webrootPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!gl.challenges['dns-01']) {
|
if (!gl.challenges['dns-01']) {
|
||||||
try {
|
try {
|
||||||
gl.challenges['dns-01'] = require('le-challenge-ddns').create({ debug: gl.debug });
|
gl.challenges['dns-01'] = require('le-challenge-ddns').create({
|
||||||
|
debug: gl.debug
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
gl.challenges['dns-01'] = require('le-challenge-dns').create({ debug: gl.debug });
|
gl.challenges['dns-01'] = require('le-challenge-dns').create({
|
||||||
|
debug: gl.debug
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// not yet implemented
|
// not yet implemented
|
||||||
}
|
}
|
||||||
|
@ -123,41 +161,65 @@ Greenlock.create = function (gl) {
|
||||||
gl.challengeType = gl.challengeType || Greenlock.challengeType;
|
gl.challengeType = gl.challengeType || Greenlock.challengeType;
|
||||||
gl._ipc = ipc;
|
gl._ipc = ipc;
|
||||||
gl._communityPackage = gl._communityPackage || 'greenlock.js';
|
gl._communityPackage = gl._communityPackage || 'greenlock.js';
|
||||||
gl.agreeToTerms = gl.agreeToTerms || function (args, agreeCb) {
|
if ('greenlock.js' === gl._communityPackage) {
|
||||||
agreeCb(new Error("'agreeToTerms' was not supplied to Greenlock and 'agreeTos' was not supplied to Greenlock.register"));
|
gl._communityPackageVersion = pkg.version;
|
||||||
|
} else {
|
||||||
|
gl._communityPackageVersion =
|
||||||
|
gl._communityPackageVersion || 'greenlock.js-' + pkg.version;
|
||||||
|
}
|
||||||
|
gl.agreeToTerms =
|
||||||
|
gl.agreeToTerms ||
|
||||||
|
function(args, agreeCb) {
|
||||||
|
agreeCb(
|
||||||
|
new Error(
|
||||||
|
"'agreeToTerms' was not supplied to Greenlock and 'agreeTos' was not supplied to Greenlock.register"
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!gl.renewWithin) { gl.renewWithin = 14 * DAY; }
|
if (!gl.renewWithin) {
|
||||||
|
gl.renewWithin = 14 * DAY;
|
||||||
|
}
|
||||||
// renewBy has a default in le-sni-auto
|
// renewBy has a default in le-sni-auto
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
// BEGIN VERSION MADNESS //
|
// BEGIN VERSION MADNESS //
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
|
|
||||||
|
gl.version = gl.version || 'draft-11';
|
||||||
|
gl.server = gl.server || 'https://acme-v02.api.letsencrypt.org/directory';
|
||||||
if (!gl.version) {
|
if (!gl.version) {
|
||||||
//console.warn("Please specify version: 'v01' (Let's Encrypt v1) or 'draft-12' (Let's Encrypt v2 / ACME draft 12)");
|
//console.warn("Please specify version: 'v01' (Let's Encrypt v1) or 'draft-12' (Let's Encrypt v2 / ACME draft 12)");
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("==========================================================");
|
console.warn(
|
||||||
console.warn("== greenlock.js (v2.2.0+) ==");
|
'=========================================================='
|
||||||
console.warn("==========================================================");
|
);
|
||||||
console.warn("");
|
console.warn(
|
||||||
|
'== greenlock.js (v2.2.0+) =='
|
||||||
|
);
|
||||||
|
console.warn(
|
||||||
|
'=========================================================='
|
||||||
|
);
|
||||||
|
console.warn('');
|
||||||
console.warn("Please specify 'version' option:");
|
console.warn("Please specify 'version' option:");
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn(" 'draft-12' for Let's Encrypt v2 and ACME draft 12");
|
console.warn(
|
||||||
|
" 'draft-12' for Let's Encrypt v2 and ACME draft 12"
|
||||||
|
);
|
||||||
console.warn(" ('v02' is an alias of 'draft-12'");
|
console.warn(" ('v02' is an alias of 'draft-12'");
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("or");
|
console.warn('or');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn(" 'v01' for Let's Encrypt v1 (deprecated)");
|
console.warn(" 'v01' for Let's Encrypt v1 (deprecated)");
|
||||||
console.warn(" (also 'npm install --save le-acme-core' as this legacy dependency will soon be removed)");
|
console.warn(
|
||||||
console.warn("");
|
" (also 'npm install --save le-acme-core' as this legacy dependency will soon be removed)"
|
||||||
console.warn("This will be required in versions v2.3+");
|
);
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("");
|
console.warn('This will be required in versions v2.3+');
|
||||||
|
console.warn('');
|
||||||
|
console.warn('');
|
||||||
} else if ('v02' === gl.version) {
|
} else if ('v02' === gl.version) {
|
||||||
gl.version = 'draft-11';
|
gl.version = 'draft-11';
|
||||||
} else if ('draft-12' === gl.version) {
|
} else if ('draft-12' === gl.version) {
|
||||||
|
@ -169,76 +231,95 @@ Greenlock.create = function (gl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gl.server) {
|
if (!gl.server) {
|
||||||
throw new Error("opts.server must specify an ACME directory URL, such as 'https://acme-staging-v02.api.letsencrypt.org/directory'");
|
throw new Error(
|
||||||
|
"opts.server must specify an ACME directory URL, such as 'https://acme-staging-v02.api.letsencrypt.org/directory'"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if ('staging' === gl.server || 'production' === gl.server) {
|
if ('staging' === gl.server || 'production' === gl.server) {
|
||||||
if ('staging' === gl.server) {
|
if ('staging' === gl.server) {
|
||||||
gl.server = 'https://acme-staging.api.letsencrypt.org/directory';
|
gl.server = 'https://acme-staging.api.letsencrypt.org/directory';
|
||||||
gl.version = 'v01';
|
gl.version = 'v01';
|
||||||
gl._deprecatedServerName = 'staging';
|
gl._deprecatedServerName = 'staging';
|
||||||
}
|
} else if ('production' === gl.server) {
|
||||||
else if ('production' === gl.server) {
|
|
||||||
gl.server = 'https://acme-v01.api.letsencrypt.org/directory';
|
gl.server = 'https://acme-v01.api.letsencrypt.org/directory';
|
||||||
gl.version = 'v01';
|
gl.version = 'v01';
|
||||||
gl._deprecatedServerName = 'production';
|
gl._deprecatedServerName = 'production';
|
||||||
}
|
}
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("=== WARNING ===");
|
console.warn('=== WARNING ===');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("Due to versioning issues the '" + gl._deprecatedServerName + "' option is deprecated.");
|
console.warn(
|
||||||
console.warn("Please specify the full url and version.");
|
"Due to versioning issues the '" +
|
||||||
console.warn("");
|
gl._deprecatedServerName +
|
||||||
console.warn("For APIs add:");
|
"' option is deprecated."
|
||||||
console.warn("\t, \"version\": \"" + gl.version + "\"");
|
);
|
||||||
console.warn("\t, \"server\": \"" + gl.server + "\"");
|
console.warn('Please specify the full url and version.');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("For the CLI add:");
|
console.warn('For APIs add:');
|
||||||
|
console.warn('\t, "version": "' + gl.version + '"');
|
||||||
|
console.warn('\t, "server": "' + gl.server + '"');
|
||||||
|
console.warn('');
|
||||||
|
console.warn('For the CLI add:');
|
||||||
console.warn("\t--acme-url '" + gl.server + "' \\");
|
console.warn("\t--acme-url '" + gl.server + "' \\");
|
||||||
console.warn("\t--acme-version '" + gl.version + "' \\");
|
console.warn("\t--acme-version '" + gl.version + "' \\");
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadLeV01() {
|
function loadLeV01() {
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("=== WARNING ===");
|
console.warn('=== WARNING ===');
|
||||||
console.warn("");
|
console.warn('');
|
||||||
console.warn("Let's Encrypt v1 is deprecated.");
|
console.warn("Let's Encrypt v1 is deprecated.");
|
||||||
console.warn("Please update to Let's Encrypt v2 (ACME draft 12)");
|
console.warn("Please update to Let's Encrypt v2 (ACME draft 12)");
|
||||||
console.warn("");
|
console.warn('');
|
||||||
try {
|
try {
|
||||||
return require('le-acme-core').ACME;
|
return require('le-acme-core').ACME;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("");
|
console.error('');
|
||||||
console.error("=== Error (easy-to-fix) ===");
|
console.error('=== Error (easy-to-fix) ===');
|
||||||
console.error("");
|
console.error('');
|
||||||
console.error("Hey, this isn't a big deal, but you need to manually add v1 support:");
|
console.error(
|
||||||
console.error("");
|
"Hey, this isn't a big deal, but you need to manually add v1 support:"
|
||||||
console.error(" npm install --save le-acme-core");
|
);
|
||||||
console.error("");
|
console.error('');
|
||||||
console.error("Just run that real quick, restart, and everything will work great.");
|
console.error(' npm install --save le-acme-core');
|
||||||
console.error("");
|
console.error('');
|
||||||
console.error("");
|
console.error(
|
||||||
|
'Just run that real quick, restart, and everything will work great.'
|
||||||
|
);
|
||||||
|
console.error('');
|
||||||
|
console.error('');
|
||||||
process.exit(e.code || 13);
|
process.exit(e.code || 13);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-1 !== [
|
if (
|
||||||
'https://acme-v02.api.letsencrypt.org/directory'
|
-1 !==
|
||||||
, 'https://acme-staging-v02.api.letsencrypt.org/directory' ].indexOf(gl.server)
|
[
|
||||||
|
'https://acme-v02.api.letsencrypt.org/directory',
|
||||||
|
'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||||
|
].indexOf(gl.server)
|
||||||
) {
|
) {
|
||||||
if ('draft-11' !== gl.version) {
|
if ('draft-11' !== gl.version) {
|
||||||
console.warn("Detected Let's Encrypt v02 URL. Changing version to draft-12.");
|
console.warn(
|
||||||
|
"Detected Let's Encrypt v02 URL. Changing version to draft-12."
|
||||||
|
);
|
||||||
gl.version = 'draft-11';
|
gl.version = 'draft-11';
|
||||||
}
|
}
|
||||||
} else if (-1 !== [
|
} else if (
|
||||||
'https://acme-v01.api.letsencrypt.org/directory'
|
-1 !==
|
||||||
, 'https://acme-staging.api.letsencrypt.org/directory' ].indexOf(gl.server)
|
[
|
||||||
|| 'v01' === gl.version
|
'https://acme-v01.api.letsencrypt.org/directory',
|
||||||
|
'https://acme-staging.api.letsencrypt.org/directory'
|
||||||
|
].indexOf(gl.server) ||
|
||||||
|
'v01' === gl.version
|
||||||
) {
|
) {
|
||||||
if ('v01' !== gl.version) {
|
if ('v01' !== gl.version) {
|
||||||
console.warn("Detected Let's Encrypt v01 URL (deprecated). Changing version to v01.");
|
console.warn(
|
||||||
|
"Detected Let's Encrypt v01 URL (deprecated). Changing version to v01."
|
||||||
|
);
|
||||||
gl.version = 'v01';
|
gl.version = 'v01';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,74 +330,112 @@ Greenlock.create = function (gl) {
|
||||||
// END VERSION MADNESS //
|
// END VERSION MADNESS //
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
|
|
||||||
|
gl.acme =
|
||||||
|
gl.acme ||
|
||||||
gl.acme = gl.acme || ACME.create({ debug: gl.debug });
|
ACME.create({
|
||||||
|
debug: gl.debug,
|
||||||
|
skipChallengeTest: gl.skipChallengeTest,
|
||||||
|
skipDryRun: gl.skipDryRun
|
||||||
|
});
|
||||||
if (gl.acme.create) {
|
if (gl.acme.create) {
|
||||||
gl.acme = gl.acme.create(gl);
|
gl.acme = gl.acme.create(gl);
|
||||||
}
|
}
|
||||||
gl.acme = promisifyAllSelf(gl.acme);
|
gl.acme = promisifyAllSelf(gl.acme);
|
||||||
gl._acmeOpts = gl.acme.getOptions();
|
gl._acmeOpts =
|
||||||
|
(gl.acme.getOptions && gl.acme.getOptions()) || gl.acme.options || {};
|
||||||
Object.keys(gl._acmeOpts).forEach(function(key) {
|
Object.keys(gl._acmeOpts).forEach(function(key) {
|
||||||
if (!(key in gl)) {
|
if (!(key in gl)) {
|
||||||
gl[key] = gl._acmeOpts[key];
|
gl[key] = gl._acmeOpts[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
if (gl.store.create) {
|
if (gl.store.create) {
|
||||||
gl.store = gl.store.create(gl);
|
gl.store = gl.store.create(gl);
|
||||||
}
|
}
|
||||||
gl.store = promisifyAllSelf(gl.store);
|
gl.store = promisifyAllSelf(gl.store);
|
||||||
gl.store.accounts = promisifyAllSelf(gl.store.accounts);
|
gl.store.accounts = promisifyAllStore(gl.store.accounts);
|
||||||
gl.store.certificates = promisifyAllSelf(gl.store.certificates);
|
gl.store.certificates = promisifyAllStore(gl.store.certificates);
|
||||||
gl._storeOpts = gl.store.getOptions();
|
gl._storeOpts =
|
||||||
|
(gl.store.getOptions && gl.store.getOptions()) ||
|
||||||
|
gl.store.options ||
|
||||||
|
{};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
console.error(
|
||||||
|
'\nPROBABLE CAUSE:\n' +
|
||||||
|
'\tYour greenlock-store module should have a create function and return { options, accounts, certificates }\n'
|
||||||
|
);
|
||||||
|
process.exit(18);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Object.keys(gl._storeOpts).forEach(function(key) {
|
Object.keys(gl._storeOpts).forEach(function(key) {
|
||||||
if (!(key in gl)) {
|
if (!(key in gl)) {
|
||||||
gl[key] = gl._storeOpts[key];
|
gl[key] = gl._storeOpts[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Backwards compat for <= v2.1.7
|
// Backwards compat for <= v2.1.7
|
||||||
//
|
//
|
||||||
if (gl.challenge) {
|
if (gl.challenge) {
|
||||||
console.warn("Deprecated use of gl.challenge. Use gl.challenges['" + Greenlock.challengeType + "'] instead.");
|
console.warn(
|
||||||
|
"Deprecated use of gl.challenge. Use gl.challenges['" +
|
||||||
|
Greenlock.challengeType +
|
||||||
|
"'] instead."
|
||||||
|
);
|
||||||
gl.challenges[gl.challengeType] = gl.challenge;
|
gl.challenges[gl.challengeType] = gl.challenge;
|
||||||
|
gl.challenge = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
Greenlock.challengeTypes.forEach(function (challengeType) {
|
Object.keys(gl.challenges || {}).forEach(function(challengeType) {
|
||||||
var challenger = gl.challenges[challengeType];
|
var challenger = gl.challenges[challengeType];
|
||||||
|
|
||||||
if (!challenger) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (challenger.create) {
|
if (challenger.create) {
|
||||||
challenger = gl.challenges[challengeType] = challenger.create(gl);
|
challenger = gl.challenges[challengeType] = challenger.create(gl);
|
||||||
}
|
}
|
||||||
if (!challenger.getOptionsAsync) {
|
challenger = gl.challenges[challengeType] = promisifyAllSelf(
|
||||||
challenger = gl.challenges[challengeType] = promisifyAllSelf(challenger);
|
challenger
|
||||||
}
|
);
|
||||||
gl['_challengeOpts_' + challengeType] = challenger.getOptions();
|
gl['_challengeOpts_' + challengeType] =
|
||||||
Object.keys(gl['_challengeOpts_' + challengeType]).forEach(function (key) {
|
(challenger.getOptions && challenger.getOptions()) ||
|
||||||
|
challenger.options ||
|
||||||
|
{};
|
||||||
|
Object.keys(gl['_challengeOpts_' + challengeType]).forEach(function(
|
||||||
|
key
|
||||||
|
) {
|
||||||
if (!(key in gl)) {
|
if (!(key in gl)) {
|
||||||
gl[key] = gl['_challengeOpts_' + challengeType][key];
|
gl[key] = gl['_challengeOpts_' + challengeType][key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO wrap these here and now with tplCopy?
|
// TODO wrap these here and now with tplCopy?
|
||||||
if (!challenger.set || 5 !== challenger.set.length) {
|
if (!challenger.set || ![5, 2, 1].includes(challenger.set.length)) {
|
||||||
throw new Error("gl.challenges[" + challengeType + "].set receives the wrong number of arguments."
|
throw new Error(
|
||||||
+ " You must define setChallenge as function (opts, domain, token, keyAuthorization, cb) { }");
|
'gl.challenges[' +
|
||||||
|
challengeType +
|
||||||
|
'].set receives the wrong number of arguments.' +
|
||||||
|
' You must define setChallenge as function (opts) { return Promise.resolve(); }'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (challenger.get && 4 !== challenger.get.length) {
|
if (challenger.get && ![4, 2, 1].includes(challenger.get.length)) {
|
||||||
throw new Error("gl.challenges[" + challengeType + "].get receives the wrong number of arguments."
|
throw new Error(
|
||||||
+ " You must define getChallenge as function (opts, domain, token, cb) { }");
|
'gl.challenges[' +
|
||||||
|
challengeType +
|
||||||
|
'].get receives the wrong number of arguments.' +
|
||||||
|
' You must define getChallenge as function (opts) { return Promise.resolve(); }'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!challenger.remove || 4 !== challenger.remove.length) {
|
if (
|
||||||
throw new Error("gl.challenges[" + challengeType + "].remove receives the wrong number of arguments."
|
!challenger.remove ||
|
||||||
+ " You must define removeChallenge as function (opts, domain, token, cb) { }");
|
![4, 2, 1].includes(challenger.remove.length)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'gl.challenges[' +
|
||||||
|
challengeType +
|
||||||
|
'].remove receives the wrong number of arguments.' +
|
||||||
|
' You must define removeChallenge as function (opts) { return Promise.resolve(); }'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -333,6 +452,44 @@ Greenlock.create = function (gl) {
|
||||||
|
|
||||||
gl.sni = gl.sni || null;
|
gl.sni = gl.sni || null;
|
||||||
gl.tlsOptions = gl.tlsOptions || gl.httpsOptions || {};
|
gl.tlsOptions = gl.tlsOptions || gl.httpsOptions || {};
|
||||||
|
|
||||||
|
// Workaround for https://github.com/nodejs/node/issues/22389
|
||||||
|
gl._updateServernames = function(cert) {
|
||||||
|
if (!gl._certnames) {
|
||||||
|
gl._certnames = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Any given domain could exist on multiple certs
|
||||||
|
// (especially during renewal where some may be added)
|
||||||
|
// hence we use a separate object for each domain and list each domain on it
|
||||||
|
// to get the minimal full set associated with each cert and domain
|
||||||
|
var allDomains = [cert.subject].concat(cert.altnames.slice(0));
|
||||||
|
allDomains.forEach(function(name) {
|
||||||
|
name = name.toLowerCase();
|
||||||
|
if (!gl._certnames[name]) {
|
||||||
|
gl._certnames[name] = {};
|
||||||
|
}
|
||||||
|
allDomains.forEach(function(name2) {
|
||||||
|
name2 = name2.toLowerCase();
|
||||||
|
gl._certnames[name][name2] = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
gl._checkServername = function(safeHost, servername) {
|
||||||
|
// odd, but acceptable
|
||||||
|
if (!safeHost || !servername) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (safeHost === servername) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// connection established with servername and session is re-used for allowed name
|
||||||
|
if (gl._certnames[servername] && gl._certnames[servername][safeHost]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
if (!gl.tlsOptions.SNICallback) {
|
if (!gl.tlsOptions.SNICallback) {
|
||||||
if (!gl.getCertificatesAsync && !gl.getCertificates) {
|
if (!gl.getCertificatesAsync && !gl.getCertificates) {
|
||||||
if (Array.isArray(gl.approveDomains)) {
|
if (Array.isArray(gl.approveDomains)) {
|
||||||
|
@ -340,31 +497,56 @@ Greenlock.create = function (gl) {
|
||||||
gl.approveDomains = null;
|
gl.approveDomains = null;
|
||||||
}
|
}
|
||||||
if (!gl.approveDomains) {
|
if (!gl.approveDomains) {
|
||||||
gl.approvedDomains = gl.approvedDomains || [];
|
gl.approveDomains = function(lexOpts, cb) {
|
||||||
gl.approveDomains = function (lexOpts, certs, cb) {
|
|
||||||
var err;
|
var err;
|
||||||
var emsg;
|
var emsg;
|
||||||
|
|
||||||
if (!gl.email) {
|
if (!gl.email) {
|
||||||
throw new Error("le-sni-auto is not properly configured. Missing email");
|
throw new Error(
|
||||||
|
'le-sni-auto is not properly configured. Missing email'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!gl.agreeTos) {
|
if (!gl.agreeTos) {
|
||||||
throw new Error("le-sni-auto is not properly configured. Missing agreeTos");
|
throw new Error(
|
||||||
|
'le-sni-auto is not properly configured. Missing agreeTos'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!gl.approvedDomains.length) {
|
if (!/[a-z]/i.test(lexOpts.domain)) {
|
||||||
throw new Error("le-sni-auto is not properly configured. Missing approveDomains(domain, certs, callback)");
|
cb(
|
||||||
}
|
new Error(
|
||||||
if (lexOpts.domains.every(function (domain) {
|
'le-sni-auto does not allow IP addresses in SNI'
|
||||||
return -1 !== gl.approvedDomains.indexOf(domain);
|
)
|
||||||
})) {
|
);
|
||||||
lexOpts.domains = gl.approvedDomains.slice(0);
|
return;
|
||||||
lexOpts.email = gl.email;
|
|
||||||
lexOpts.agreeTos = gl.agreeTos;
|
|
||||||
lexOpts.communityMember = lexOpts.communityMember;
|
|
||||||
return cb(null, { options: lexOpts, certs: certs });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emsg = "tls SNI for '" + lexOpts.domains.join(',') + "' rejected: not in list '" + gl.approvedDomains + "'";
|
if (!Array.isArray(gl.approvedDomains)) {
|
||||||
|
// The acme-v2 package uses pre-flight test challenges to
|
||||||
|
// verify that each requested domain is hosted by the server
|
||||||
|
// these checks are sufficient for most use cases
|
||||||
|
return cb(null, lexOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lexOpts.domains.every(function(domain) {
|
||||||
|
return -1 !== gl.approvedDomains.indexOf(domain);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
// commented this out because people expect to be able to edit the list of domains
|
||||||
|
// lexOpts.domains = gl.approvedDomains.slice(0);
|
||||||
|
lexOpts.email = gl.email;
|
||||||
|
lexOpts.agreeTos = gl.agreeTos;
|
||||||
|
lexOpts.communityMember = gl.communityMember;
|
||||||
|
lexOpts.telemetry = gl.telemetry;
|
||||||
|
return cb(null, lexOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
emsg =
|
||||||
|
"tls SNI for '" +
|
||||||
|
lexOpts.domains.join(',') +
|
||||||
|
"' rejected: not in list '" +
|
||||||
|
gl.approvedDomains +
|
||||||
|
"'";
|
||||||
log(gl.debug, emsg, lexOpts.domains, gl.approvedDomains);
|
log(gl.debug, emsg, lexOpts.domains, gl.approvedDomains);
|
||||||
err = new Error(emsg);
|
err = new Error(emsg);
|
||||||
err.code = 'E_REJECT_SNI';
|
err.code = 'E_REJECT_SNI';
|
||||||
|
@ -374,57 +556,161 @@ Greenlock.create = function (gl) {
|
||||||
|
|
||||||
gl.getCertificates = function(domain, certs, cb) {
|
gl.getCertificates = function(domain, certs, cb) {
|
||||||
// certs come from current in-memory cache, not lookup
|
// certs come from current in-memory cache, not lookup
|
||||||
log(gl.debug, 'gl.getCertificates called for', domain, 'with certs for', certs && certs.altnames || 'NONE');
|
log(
|
||||||
var opts = { domain: domain, domains: certs && certs.altnames || [ domain ] };
|
gl.debug,
|
||||||
|
'gl.getCertificates called for',
|
||||||
|
domain,
|
||||||
|
'with certs for',
|
||||||
|
(certs && certs.altnames) || 'NONE'
|
||||||
|
);
|
||||||
|
var opts = {
|
||||||
|
domain: domain,
|
||||||
|
domains: (certs && certs.altnames) || [domain],
|
||||||
|
certs: certs,
|
||||||
|
certificate: {},
|
||||||
|
account: {}
|
||||||
|
};
|
||||||
|
opts.wildname =
|
||||||
|
'*.' +
|
||||||
|
(domain || '')
|
||||||
|
.split('.')
|
||||||
|
.slice(1)
|
||||||
|
.join('.');
|
||||||
|
|
||||||
try {
|
function cb2(results) {
|
||||||
gl.approveDomains(opts, certs, function (_err, results) {
|
log(
|
||||||
if (_err) {
|
gl.debug,
|
||||||
|
'gl.approveDomains called with certs for',
|
||||||
|
(results.certs && results.certs.altnames) || 'NONE',
|
||||||
|
'and options:'
|
||||||
|
);
|
||||||
|
log(gl.debug, results.options || results);
|
||||||
|
var err;
|
||||||
|
if (!results) {
|
||||||
|
err = new Error('E_REJECT_SNI');
|
||||||
|
err.code = 'E_REJECT_SNI';
|
||||||
|
eb2(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = results.options || results;
|
||||||
|
if (opts !== options) {
|
||||||
|
Object.keys(options).forEach(function(key) {
|
||||||
|
if (
|
||||||
|
'undefined' !== typeof options[key] &&
|
||||||
|
'domain' !== key
|
||||||
|
) {
|
||||||
|
opts[key] = options[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
options = opts;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
Array.isArray(options.altnames) &&
|
||||||
|
options.altnames.length
|
||||||
|
) {
|
||||||
|
options.domains = options.altnames;
|
||||||
|
}
|
||||||
|
options.altnames = options.domains;
|
||||||
|
// just in case we get a completely different object from the one we originally created
|
||||||
|
if (!options.account) {
|
||||||
|
options.account = {};
|
||||||
|
}
|
||||||
|
if (!options.certificate) {
|
||||||
|
options.certificate = {};
|
||||||
|
}
|
||||||
|
if (results.certs) {
|
||||||
|
log(gl.debug, 'gl renewing');
|
||||||
|
return gl.core.certificates
|
||||||
|
.renewAsync(options, results.certs)
|
||||||
|
.then(
|
||||||
|
function(certs) {
|
||||||
|
// Workaround for https://github.com/nodejs/node/issues/22389
|
||||||
|
gl._updateServernames(certs);
|
||||||
|
cb(null, certs);
|
||||||
|
},
|
||||||
|
function(e) {
|
||||||
|
console.debug(
|
||||||
|
"Error renewing certificate for '" +
|
||||||
|
domain +
|
||||||
|
"':"
|
||||||
|
);
|
||||||
|
console.debug(e);
|
||||||
|
console.error('');
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log(
|
||||||
|
gl.debug,
|
||||||
|
'gl getting from disk or registering new'
|
||||||
|
);
|
||||||
|
return gl.core.certificates.getAsync(options).then(
|
||||||
|
function(certs) {
|
||||||
|
// Workaround for https://github.com/nodejs/node/issues/22389
|
||||||
|
gl._updateServernames(certs);
|
||||||
|
cb(null, certs);
|
||||||
|
},
|
||||||
|
function(e) {
|
||||||
|
console.debug(
|
||||||
|
"Error loading/registering certificate for '" +
|
||||||
|
domain +
|
||||||
|
"':"
|
||||||
|
);
|
||||||
|
console.debug(e);
|
||||||
|
console.error('');
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function eb2(_err) {
|
||||||
if (false !== gl.logRejectedDomains) {
|
if (false !== gl.logRejectedDomains) {
|
||||||
console.error("[Error] approveDomains rejected tls sni '" + domain + "'");
|
console.error(
|
||||||
console.error("[Error] (see https://git.coolaj86.com/coolaj86/greenlock.js/issues/11)");
|
"[Error] approveDomains rejected tls sni '" +
|
||||||
|
domain +
|
||||||
|
"'"
|
||||||
|
);
|
||||||
|
console.error(
|
||||||
|
'[Error] (see https://git.coolaj86.com/coolaj86/greenlock.js/issues/11)'
|
||||||
|
);
|
||||||
if ('E_REJECT_SNI' !== _err.code) {
|
if ('E_REJECT_SNI' !== _err.code) {
|
||||||
console.error("[Error] This is the rejection message:");
|
console.error(
|
||||||
|
'[Error] This is the rejection message:'
|
||||||
|
);
|
||||||
console.error(_err.message);
|
console.error(_err.message);
|
||||||
}
|
}
|
||||||
console.error("");
|
console.error('');
|
||||||
}
|
}
|
||||||
cb(_err);
|
cb(_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
function mb2(_err, results) {
|
||||||
|
if (_err) {
|
||||||
|
eb2(_err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb2(results);
|
||||||
|
}
|
||||||
|
|
||||||
log(gl.debug, 'gl.approveDomains called with certs for', results.certs && results.certs.altnames || 'NONE', 'and options:');
|
try {
|
||||||
log(gl.debug, results.options);
|
if (1 === gl.approveDomains.length) {
|
||||||
|
Promise.resolve(gl.approveDomains(opts))
|
||||||
if (results.certs) {
|
.then(cb2)
|
||||||
log(gl.debug, 'gl renewing');
|
.catch(eb2);
|
||||||
return gl.core.certificates.renewAsync(results.options, results.certs).then(
|
} else if (2 === gl.approveDomains.length) {
|
||||||
function (certs) { cb(null, certs); }
|
gl.approveDomains(opts, mb2);
|
||||||
, function (e) {
|
} else {
|
||||||
console.debug("Error renewing certificate for '" + domain + "':");
|
gl.approveDomains(opts, certs, mb2);
|
||||||
console.debug(e);
|
|
||||||
console.error("");
|
|
||||||
cb(e);
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log(gl.debug, 'gl getting from disk or registering new');
|
|
||||||
return gl.core.certificates.getAsync(results.options).then(
|
|
||||||
function (certs) { cb(null, certs); }
|
|
||||||
, function (e) {
|
|
||||||
console.debug("Error loading/registering certificate for '" + domain + "':");
|
|
||||||
console.debug(e);
|
|
||||||
console.error("");
|
|
||||||
cb(e);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[ERROR] Something went wrong in approveDomains:");
|
console.error(
|
||||||
|
'[ERROR] Something went wrong in approveDomains:'
|
||||||
|
);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
console.error("BUT WAIT! Good news: It's probably your fault, so you can probably fix it.");
|
console.error(
|
||||||
|
"BUT WAIT! Good news: It's probably your fault, so you can probably fix it."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -432,22 +718,32 @@ Greenlock.create = function (gl) {
|
||||||
if (gl.sni.create) {
|
if (gl.sni.create) {
|
||||||
gl.sni = gl.sni.create(gl);
|
gl.sni = gl.sni.create(gl);
|
||||||
}
|
}
|
||||||
gl.tlsOptions.SNICallback = function (domain, cb) {
|
gl.tlsOptions.SNICallback = function(_domain, cb) {
|
||||||
// format and (lightly) sanitize sni so that users can be naive
|
// format and (lightly) sanitize sni so that users can be naive
|
||||||
// and not have to worry about SQL injection or fs discovery
|
// and not have to worry about SQL injection or fs discovery
|
||||||
domain = (domain||'').toLowerCase();
|
var domain = (_domain || '').toLowerCase();
|
||||||
// hostname labels allow a-z, 0-9, -, and are separated by dots
|
// hostname labels allow a-z, 0-9, -, and are separated by dots
|
||||||
// _ is sometimes allowed
|
// _ is sometimes allowed
|
||||||
if (!/^[a-z0-9_\.\-]+$/i.test(domain) || -1 !== domain.indexOf('..')) {
|
// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
|
||||||
|
if (
|
||||||
|
!gl.__sni_allow_dangerous_names &&
|
||||||
|
(!/^[a-z0-9_\.\-]+$/i.test(domain) ||
|
||||||
|
-1 !== domain.indexOf('..'))
|
||||||
|
) {
|
||||||
log(gl.debug, "invalid sni '" + domain + "'");
|
log(gl.debug, "invalid sni '" + domain + "'");
|
||||||
cb(new Error("invalid SNI"));
|
cb(new Error('invalid SNI'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gl.sni.sniCallback(domain, cb);
|
gl.sni.sniCallback(
|
||||||
|
(gl.__sni_preserve_case && _domain) || domain,
|
||||||
|
cb
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[ERROR] Something went wrong in the SNICallback:");
|
console.error(
|
||||||
|
'[ERROR] Something went wrong in the SNICallback:'
|
||||||
|
);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
cb(e);
|
cb(e);
|
||||||
}
|
}
|
||||||
|
@ -480,5 +776,95 @@ Greenlock.create = function (gl) {
|
||||||
gl.middleware = gl.middleware.create(gl);
|
gl.middleware = gl.middleware.create(gl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//var SERVERNAME_RE = /^[a-z0-9\.\-_]+$/;
|
||||||
|
var SERVERNAME_G = /[^a-z0-9\.\-_]/;
|
||||||
|
gl.middleware.sanitizeHost = function(app) {
|
||||||
|
return function(req, res, next) {
|
||||||
|
function realNext() {
|
||||||
|
if ('function' === typeof app) {
|
||||||
|
app(req, res);
|
||||||
|
} else if ('function' === typeof next) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end('Error: no middleware assigned');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the host:port combo, if it exists
|
||||||
|
var host = (req.headers.host || '').split(':');
|
||||||
|
|
||||||
|
// if not, move along
|
||||||
|
if (!host[0]) {
|
||||||
|
realNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if so, remove non-allowed characters
|
||||||
|
var safehost = host[0].toLowerCase().replace(SERVERNAME_G, '');
|
||||||
|
|
||||||
|
// if there were unallowed characters, complain
|
||||||
|
if (
|
||||||
|
!gl.__sni_allow_dangerous_names &&
|
||||||
|
safehost.length !== host[0].length
|
||||||
|
) {
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.end("Malformed HTTP Header: 'Host: " + host[0] + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make lowercase
|
||||||
|
if (!gl.__sni_preserve_case) {
|
||||||
|
host[0] = safehost;
|
||||||
|
req.headers.host = host.join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
|
||||||
|
if (req.socket.encrypted && !gl.__sni_allow_domain_fronting) {
|
||||||
|
if (req.socket && 'string' === typeof req.socket.servername) {
|
||||||
|
// Workaround for https://github.com/nodejs/node/issues/22389
|
||||||
|
if (
|
||||||
|
!gl._checkServername(
|
||||||
|
safehost,
|
||||||
|
req.socket.servername.toLowerCase()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Type',
|
||||||
|
'text/html; charset=utf-8'
|
||||||
|
);
|
||||||
|
res.end(
|
||||||
|
'<h1>Domain Fronting Error</h1>' +
|
||||||
|
"<p>This connection was secured using TLS/SSL for '" +
|
||||||
|
req.socket.servername.toLowerCase() +
|
||||||
|
"'</p>" +
|
||||||
|
"<p>The HTTP request specified 'Host: " +
|
||||||
|
safehost +
|
||||||
|
"', which is (obviously) different.</p>" +
|
||||||
|
'<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
safehost &&
|
||||||
|
!gl.middleware.sanitizeHost._skip_fronting_check
|
||||||
|
) {
|
||||||
|
// TODO how to handle wrapped sockets, as with telebit?
|
||||||
|
console.warn(
|
||||||
|
'\n\n\n[greenlock] WARN: no string for req.socket.servername,' +
|
||||||
|
" skipping fronting check for '" +
|
||||||
|
safehost +
|
||||||
|
"'\n\n\n"
|
||||||
|
);
|
||||||
|
gl.middleware.sanitizeHost._skip_fronting_check = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// carry on
|
||||||
|
realNext();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
gl.middleware.sanitizeHost._skip_fronting_check = false;
|
||||||
|
|
||||||
return gl;
|
return gl;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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',
|
||||||
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
}, function (err, resp) {
|
},
|
||||||
if (err) { return; }
|
function(err, resp) {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
resp.on('data', function() {});
|
resp.on('data', function() {});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.on('error', function(error) {
|
||||||
|
/* ignore */
|
||||||
});
|
});
|
||||||
req.write(JSON.stringify({
|
var os = require('os');
|
||||||
address: email
|
var data = {
|
||||||
, comment: (pkg || 'community') + ' member w/ ' + (domains||[]).map(function (d) {
|
address: opts.email,
|
||||||
return require('crypto').createHash('sha1').update(d).digest('base64')
|
// greenlock-security is transactional and security only
|
||||||
.replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '');
|
list: opts.communityMember
|
||||||
}).join(',')
|
? 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();
|
req.end();
|
||||||
}, 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
847
lib/core.js
847
lib/core.js
File diff suppressed because it is too large
Load Diff
|
@ -6,36 +6,38 @@ 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 {
|
||||||
else {
|
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end("[500] Developer Error: app.use('/', greenlock.middleware()) or greenlock.middleware(app)");
|
res.end(
|
||||||
|
"[500] Developer Error: app.use('/', greenlock.middleware()) or greenlock.middleware(app)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -43,26 +45,64 @@ module.exports.create = function (gl) {
|
||||||
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.statusCode = 404;
|
|
||||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
res.end('{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
||||||
res.end(secret);
|
res.end(secret);
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
eb(new Error("couldn't retrieve keyAuthorization"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function eb(/*err*/) {
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.setHeader(
|
||||||
|
'Content-Type',
|
||||||
|
'application/json; charset=utf-8'
|
||||||
|
);
|
||||||
|
res.end(
|
||||||
|
'{ "error": { "message": "Error: These aren\'t the tokens you\'re looking for. Move along." } }'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function mb(err, result) {
|
||||||
|
if (err) {
|
||||||
|
eb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
var challenger = gl.challenges['http-01'].get;
|
||||||
|
if (1 === challenger.length) {
|
||||||
|
/*global Promise*/
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function() {
|
||||||
|
return gl.challenges['http-01'].get(copy);
|
||||||
|
})
|
||||||
|
.then(cb)
|
||||||
|
.catch(eb);
|
||||||
|
} else if (2 === challenger.length) {
|
||||||
|
gl.challenges['http-01'].get(copy, mb);
|
||||||
|
} else {
|
||||||
|
gl.challenges['http-01'].get(copy, hostname, token, mb);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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');
|
60
lib/utils.js
60
lib/utils.js
|
@ -1,17 +1,15 @@
|
||||||
'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) {
|
||||||
|
@ -21,6 +19,23 @@ module.exports.attachCertInfo = function (results) {
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.certHasDomain = function(certInfo, _domain) {
|
||||||
|
var names = (certInfo.altnames || []).slice(0);
|
||||||
|
names.push(certInfo.subject);
|
||||||
|
return names.some(function(name) {
|
||||||
|
var domain = _domain.toLowerCase();
|
||||||
|
name = name.toLowerCase();
|
||||||
|
if ('*.' === name.substr(0, 2)) {
|
||||||
|
name = name.substr(2);
|
||||||
|
domain = domain
|
||||||
|
.split('.')
|
||||||
|
.slice(1)
|
||||||
|
.join('.');
|
||||||
|
}
|
||||||
|
return name === domain;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.isValidDomain = function(domain) {
|
module.exports.isValidDomain = function(domain) {
|
||||||
if (re.test(domain)) {
|
if (re.test(domain)) {
|
||||||
return domain;
|
return domain;
|
||||||
|
@ -42,11 +57,29 @@ module.exports.merge = function (/*defaults, args*/) {
|
||||||
|
|
||||||
allDefaults.forEach(function(defaults) {
|
allDefaults.forEach(function(defaults) {
|
||||||
Object.keys(defaults).forEach(function(key) {
|
Object.keys(defaults).forEach(function(key) {
|
||||||
|
/*
|
||||||
|
if ('challenges' === key && copy[key] && defaults[key]) {
|
||||||
|
Object.keys(defaults[key]).forEach(function (k) {
|
||||||
|
copy[key][k] = defaults[key][k];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
copy[key] = defaults[key];
|
||||||
|
}
|
||||||
|
*/
|
||||||
copy[key] = defaults[key];
|
copy[key] = defaults[key];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(args).forEach(function(key) {
|
Object.keys(args).forEach(function(key) {
|
||||||
|
/*
|
||||||
|
if ('challenges' === key && copy[key] && args[key]) {
|
||||||
|
Object.keys(args[key]).forEach(function (k) {
|
||||||
|
copy[key][k] = args[key][k];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
copy[key] = args[key];
|
||||||
|
}
|
||||||
|
*/
|
||||||
copy[key] = args[key];
|
copy[key] = args[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,7 +91,7 @@ module.exports.tplCopy = function (copy) {
|
||||||
var tplKeys;
|
var tplKeys;
|
||||||
|
|
||||||
copy.hostnameGet = function(copy) {
|
copy.hostnameGet = function(copy) {
|
||||||
return (copy.domains || [])[0] || copy.domain;
|
return copy.subject || (copy.domains || [])[0] || copy.domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(copy).forEach(function(key) {
|
Object.keys(copy).forEach(function(key) {
|
||||||
|
@ -111,17 +144,22 @@ module.exports.testEmail = function (email) {
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dnsResolveMxAsync(parts[1]).then(function (records) {
|
return dnsResolveMxAsync(parts[1]).then(
|
||||||
|
function(records) {
|
||||||
// records only returns when there is data
|
// records only returns when there is data
|
||||||
if (!records.length) {
|
if (!records.length) {
|
||||||
throw new Error("sanity check fail: success, but no MX records returned");
|
throw new Error(
|
||||||
|
'sanity check fail: success, but no MX records returned'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return email;
|
return email;
|
||||||
}, function (err) {
|
},
|
||||||
|
function(err) {
|
||||||
if ('ENODATA' === err.code) {
|
if ('ENODATA' === err.code) {
|
||||||
err = new Error("no MX records found for '" + parts[1] + "'");
|
err = new Error("no MX records found for '" + parts[1] + "'");
|
||||||
err.code = 'E_EMAIL';
|
err.code = 'E_EMAIL';
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
package.json
59
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",
|
||||||
|
"homepage": "https://greenlock.domains/",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
"lib"
|
"lib"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"bump": "npm version -m \"chore(release): bump to v%s\"",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.coolaj86.com/coolaj86/greenlock.js.git"
|
"url": "https://git.rootprojects.org/root/greenlock.js.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Let's Encrypt",
|
"Let's Encrypt",
|
||||||
"letsencrypt",
|
"letsencrypt",
|
||||||
"ACME",
|
"ACME",
|
||||||
"v2",
|
"v2",
|
||||||
"v02",
|
|
||||||
"draft-11",
|
|
||||||
"draft-12",
|
|
||||||
"auto-sni",
|
"auto-sni",
|
||||||
"draft",
|
|
||||||
"11",
|
|
||||||
"12",
|
|
||||||
"Free SSL",
|
"Free SSL",
|
||||||
"Automated HTTPS",
|
"Automated HTTPS",
|
||||||
"tls",
|
"tls",
|
||||||
"https",
|
"https"
|
||||||
"Greenlock",
|
|
||||||
"letsencrypt.org",
|
|
||||||
"le",
|
|
||||||
"le.js",
|
|
||||||
"node",
|
|
||||||
"nodejs",
|
|
||||||
"node.js",
|
|
||||||
"client"
|
|
||||||
],
|
],
|
||||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
"author": "AJ ONeal <coolaj86@gmail.com> (https://solderjs.com/)",
|
||||||
"license": "(MIT OR Apache-2.0)",
|
"license": "MPL-2.0",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.coolaj86.com/coolaj86/greenlock.js/issues"
|
"url": "https://git.rootprojects.org/root/greenlock.js/issues"
|
||||||
},
|
|
||||||
"homepage": "https://git.coolaj86.com/coolaj86/greenlock.js",
|
|
||||||
"devDependencies": {
|
|
||||||
"request": "^2.75.0"
|
|
||||||
},
|
},
|
||||||
"trulyOptionalDependencies": {
|
"trulyOptionalDependencies": {
|
||||||
"bluebird": "^3.5.1",
|
"bluebird": "^3.5.1",
|
||||||
"le-acme-core": "^2.1.3",
|
"le-acme-core": "^2.1.3"
|
||||||
"ursa": "^0.9.4"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acme": "^1.0.6",
|
"acme": "^1.3.5",
|
||||||
"acme-v2": "^1.2.0",
|
"acme-dns-01-cli": "^3.0.0",
|
||||||
"asn1js": "^1.2.12",
|
"acme-v2": "^1.8.7",
|
||||||
"certpem": "^1.0.0",
|
"cert-info": "^1.5.1",
|
||||||
|
"greenlock-store-fs": "^3.0.2",
|
||||||
|
"keypairs": "^1.2.14",
|
||||||
"le-challenge-fs": "^2.0.2",
|
"le-challenge-fs": "^2.0.2",
|
||||||
"le-sni-auto": "^2.1.3",
|
"le-sni-auto": "^2.1.9",
|
||||||
"le-store-certbot": "^2.1.7",
|
"le-store-certbot": "^2.2.4",
|
||||||
"node.extend": "^1.1.5",
|
"rsa-compat": "^2.0.8"
|
||||||
"pkijs": "^1.3.27",
|
|
||||||
"rsa-compat": "^1.4.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.5"
|
"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
|
||||||
|
@ -21,23 +21,31 @@ var testAccountId = '939573edbf2506c92c9ab32131209d7b';
|
||||||
|
|
||||||
var tests = [
|
var tests = [
|
||||||
function() {
|
function() {
|
||||||
return le.core.accounts.checkAsync({
|
return le.core.accounts
|
||||||
|
.checkAsync({
|
||||||
accountId: testAccountId
|
accountId: testAccountId
|
||||||
}).then(function (account) {
|
})
|
||||||
|
.then(function(account) {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error("Test account should exist when searched by account id.");
|
throw new Error(
|
||||||
|
'Test account should exist when searched by account id.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
, function () {
|
function() {
|
||||||
return le.core.accounts.checkAsync({
|
return le.core.accounts
|
||||||
|
.checkAsync({
|
||||||
email: testEmail
|
email: testEmail
|
||||||
}).then(function (account) {
|
})
|
||||||
|
.then(function(account) {
|
||||||
console.log('account.regr');
|
console.log('account.regr');
|
||||||
console.log(account.regr);
|
console.log(account.regr);
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error("Test account should exist when searched by email.");
|
throw new Error(
|
||||||
|
'Test account should exist when searched by email.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
var LE = require('../').LE;
|
var LE = 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();
|
||||||
|
@ -19,74 +19,103 @@ var testAccount;
|
||||||
|
|
||||||
var tests = [
|
var tests = [
|
||||||
function() {
|
function() {
|
||||||
return le.core.accounts.checkAsync({
|
return le.core.accounts
|
||||||
|
.checkAsync({
|
||||||
email: testEmail
|
email: testEmail
|
||||||
}).then(function (account) {
|
})
|
||||||
|
.then(function(account) {
|
||||||
if (account) {
|
if (account) {
|
||||||
console.error(account);
|
console.error(account);
|
||||||
throw new Error("Test account should not exist.");
|
throw new Error('Test account should not exist.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
, function () {
|
function() {
|
||||||
return le.core.accounts.registerAsync({
|
return le.core.accounts
|
||||||
email: testEmail
|
.registerAsync({
|
||||||
, agreeTos: false
|
email: testEmail,
|
||||||
, rsaKeySize: 2048
|
agreeTos: false,
|
||||||
}).then(function (/*account*/) {
|
rsaKeySize: 2048
|
||||||
throw new Error("Should not register if 'agreeTos' is not truthy.");
|
})
|
||||||
}, function (err) {
|
.then(
|
||||||
|
function(/*account*/) {
|
||||||
|
throw new Error(
|
||||||
|
"Should not register if 'agreeTos' is not truthy."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
if (err.code !== 'E_ARGS') {
|
if (err.code !== 'E_ARGS') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
, function () {
|
);
|
||||||
return le.core.accounts.registerAsync({
|
},
|
||||||
email: testEmail
|
function() {
|
||||||
, agreeTos: true
|
return le.core.accounts
|
||||||
, rsaKeySize: 1024
|
.registerAsync({
|
||||||
}).then(function (/*account*/) {
|
email: testEmail,
|
||||||
throw new Error("Should not register if 'rsaKeySize' is less than 2048.");
|
agreeTos: true,
|
||||||
}, function (err) {
|
rsaKeySize: 1024
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
function(/*account*/) {
|
||||||
|
throw new Error(
|
||||||
|
"Should not register if 'rsaKeySize' is less than 2048."
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
if (err.code !== 'E_ARGS') {
|
if (err.code !== 'E_ARGS') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
, function () {
|
);
|
||||||
return le.core.accounts.registerAsync({
|
},
|
||||||
email: fakeEmail
|
function() {
|
||||||
, agreeTos: true
|
return le.core.accounts
|
||||||
, rsaKeySize: 2048
|
.registerAsync({
|
||||||
}).then(function (/*account*/) {
|
email: fakeEmail,
|
||||||
|
agreeTos: true,
|
||||||
|
rsaKeySize: 2048
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
function(/*account*/) {
|
||||||
// TODO test mx record
|
// TODO test mx record
|
||||||
throw new Error("Registration should NOT succeed with a bad email address.");
|
throw new Error(
|
||||||
}, function (err) {
|
'Registration should NOT succeed with a bad email address.'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
if (err.code !== 'E_EMAIL') {
|
if (err.code !== 'E_EMAIL') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
, function () {
|
);
|
||||||
return le.core.accounts.registerAsync({
|
},
|
||||||
email: testEmail
|
function() {
|
||||||
, agreeTos: true
|
return le.core.accounts
|
||||||
, rsaKeySize: 2048
|
.registerAsync({
|
||||||
}).then(function (account) {
|
email: testEmail,
|
||||||
|
agreeTos: true,
|
||||||
|
rsaKeySize: 2048
|
||||||
|
})
|
||||||
|
.then(function(account) {
|
||||||
testAccount = 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) {
|
if (!account.email) {
|
||||||
throw new Error("Registration should return the email.");
|
throw new Error('Registration should return the email.');
|
||||||
}
|
}
|
||||||
if (!account.id) {
|
if (!account.id) {
|
||||||
throw new Error("Registration should return the account id.");
|
throw new Error(
|
||||||
|
'Registration should return the account id.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
|
||||||
})
|
|
||||||
, challenge: require('le-challenge-fs').create({
|
|
||||||
webrootPath: '~/letsencrypt.test/var/:hostname'
|
webrootPath: '~/letsencrypt.test/var/:hostname'
|
||||||
})
|
}),
|
||||||
, debug: true
|
challenge: require('le-challenge-fs').create({
|
||||||
|
webrootPath: '~/letsencrypt.test/var/:hostname'
|
||||||
|
}),
|
||||||
|
debug: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO test generateRsaKey code path separately
|
// TODO test generateRsaKey code path separately
|
||||||
|
@ -25,22 +25,30 @@ var testDomains = [ 'pokemap.hellabit.com', 'www.pokemap.hellabit.com' ];
|
||||||
|
|
||||||
var tests = [
|
var tests = [
|
||||||
function() {
|
function() {
|
||||||
return le.core.certificates.checkAsync({
|
return le.core.certificates
|
||||||
|
.checkAsync({
|
||||||
domains: ['example.com', 'www.example.com']
|
domains: ['example.com', 'www.example.com']
|
||||||
}).then(function (cert) {
|
})
|
||||||
|
.then(function(cert) {
|
||||||
if (cert) {
|
if (cert) {
|
||||||
throw new Error("Bogus domain should not have certificate.");
|
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
|
||||||
|
})
|
||||||
|
.then(function(certs) {
|
||||||
if (!certs) {
|
if (!certs) {
|
||||||
throw new Error("Should have acquired certificate for domains.");
|
throw new Error(
|
||||||
|
'Should have acquired certificate for domains.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
|
|
||||||
var LE = require('../').LE;
|
var LE = 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'
|
|
||||||
})
|
|
||||||
, challenge: require('le-challenge-fs').create({
|
|
||||||
webrootPath: '~/letsencrypt.test/var/:hostname'
|
webrootPath: '~/letsencrypt.test/var/:hostname'
|
||||||
})
|
}),
|
||||||
, debug: true
|
challenge: require('le-challenge-fs').create({
|
||||||
|
webrootPath: '~/letsencrypt.test/var/:hostname'
|
||||||
|
}),
|
||||||
|
debug: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO test generateRsaKey code path separately
|
// TODO test generateRsaKey code path separately
|
||||||
|
@ -27,48 +27,83 @@ 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
|
||||||
|
.checkAsync({
|
||||||
domains: testDomains
|
domains: testDomains
|
||||||
}).then(function (certs) {
|
})
|
||||||
|
.then(function(certs) {
|
||||||
if (!certs) {
|
if (!certs) {
|
||||||
throw new Error("Either certificates.registerAsync (in previous test)"
|
throw new Error(
|
||||||
+ " or certificates.checkAsync (in this test) failed.");
|
'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) {
|
},
|
||||||
|
testCerts
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
function() {
|
||||||
|
throw new Error(
|
||||||
|
'Should not have renewed non-expired certificates.'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
if ('E_NOT_RENEWABLE' !== err.code) {
|
if ('E_NOT_RENEWABLE' !== err.code) {
|
||||||
throw err;
|
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.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue