369 Commits

Author SHA1 Message Date
Lukas Schauer
1dbbc64ce9 implement workaround for openssl regression (fixes #981)
The introduction of the `-multi` option to the x509 subcommand
introduced a regression to the `-checkend` behaviour, preventing
openssl to correctly indicate the certificate expiry status via
its exit code.

This commit introduces a (maybe temporary) workaround by instead
checking the output string.
2025-10-24 09:22:31 +02:00
Lukas Schauer
12877bb238 throw error with information about OCSP deprecation if certificate doesn't indicate OCSP support 2025-07-05 11:13:45 +02:00
Lukas Schauer
ad43e250b2 allow KEEP_GOING to also skip over ocsp stapling errors, update ocsp error message with a hint about deprecation on some CAs 2025-07-05 10:55:33 +02:00
Lukas Schauer
8e9e5ef9c7 also allow setting KEEP_GOING as a config option 2025-07-05 10:54:29 +02:00
Lukas Schauer
a7deeaedbc set empty subject for ip-certificates
as suggested by @candlerb in #783
2025-07-05 10:28:13 +02:00
Victor Coss
3d95f18000 Don't allow CDN's to send cached responses
A lot of CA's use a CDN service to protect and speed up their ACME service. These CDN services can sometimes miss-behave and send cached results. For example DigiCert's ACME service uses the Imperva CDN. It will send cached results on the DNS validation, challenge endpoint, resulting in it being stuck in the processing status, thus dehydrated is hung and never gets the certificate.
2025-06-17 19:52:29 +02:00
Lukas Schauer
ce9eb300e2 implemented domain validation timeout 2025-06-17 19:51:27 +02:00
Lukas Schauer
9cfcd66f15 small addition to 0.7.2 changelog 2025-05-18 02:28:57 +02:00
Lukas Schauer
73bb54a4b2 updated changelog 2025-05-18 02:16:14 +02:00
Lukas Schauer
3a71a7ad94 only validate existance of wellknown directory or hook script when actually necessary (fixes #965) 2025-05-18 02:07:04 +02:00
Lukas Schauer
0290338853 post-v0.7.2-release 2025-05-18 01:36:16 +02:00
Lukas Schauer
fcca67b53c release v0.7.2 2025-05-18 01:34:32 +02:00
Lukas Schauer
cf9e6a33fd Allow for automatic deletion of old files 2025-05-02 15:00:48 +02:00
Lukas Schauer
bec154f070 Added a configuration parameter to allow for timeouts during order processing (fixes #955) 2025-05-02 14:42:57 +02:00
Lukas Schauer
0141d86267 Update README (closes #964) 2025-05-02 14:38:45 +02:00
Lukas Schauer
a86a176805 use temporary csr file instead of stdin (keeps compatibility to older openssl versions) 2025-04-23 11:24:42 +02:00
Lukas Schauer
200cd68e7e updated changelog 2025-04-14 19:49:31 +02:00
Christian Kujau
e973cb2d8a Disable warning when reading CSRs from stdin.
Coming across the same warning that was reported in
[PR#929](https://github.com/dehydrated-io/dehydrated/pull/929 "Suppress
openssl warning about reading from stdin") this is my attempt to disable
this warning. Instead of discarding stderr in total (this can still be
useful), we just use the "-in" parameter as hinted in the warning:

 $ foo=$(cat req.csr)
 $ <<<${foo} openssl req -noout -verify > /dev/null; echo $?
 Warning: Will read cert request from stdin since no -in option is given
 0

 $ <<<${foo} openssl req -in - -noout -verify > /dev/null; echo $?
 0
2025-04-14 19:42:15 +02:00
Lukas Schauer
7c438c484f added google ca to example config and added documentation link to error message 2025-04-14 19:12:59 +02:00
hshh
a94f451014 Add support for Google Trust Services.
Official Documentation: https://cloud.google.com/certificate-manager/docs/public-ca-tutorial
The first registration requires obtaining EAB_KID and EAB_HMAC_KEY according to the document, and setting CONTACT_EMAIL, EAB_HMAC_KEY, EAB_KID in the configuration file.
2025-04-14 18:59:59 +02:00
Bob Idle
a615a55ad6 Update dehydrated repo urls in man page 2025-04-14 18:57:00 +02:00
Lukas Schauer
f6d82e2715 fix small issue with certificate profile selection (use key instead of value) 2025-04-14 18:49:44 +02:00
Lukas Schauer
1a1cb94a61 added changelog + default config entries for certificate profile selection 2025-04-14 18:41:38 +02:00
Youfu Zhang
5ab8c3806d implemented certificate profile selection (draft-aaron-acme-profiles-00)
https://letsencrypt.org/2025/01/09/acme-profiles/
https://datatracker.ietf.org/doc/html/draft-aaron-acme-profiles-00

Signed-off-by: Youfu Zhang <zhangyoufu@gmail.com>
2025-04-14 18:35:10 +02:00
Lukas Schauer
4ea5081640 renew certificates with 32 days remaining (instead of 30) to avoid issues with monthly cronjobs (fixes #963) 2025-04-11 10:33:07 +02:00
Wilfried Teiken
4fd777e87e Ignore output of 'openssl req -verify'.
Newer versions of openssl seem to send the verify outout to stdout instead of
stderr in the past. Ignore that output when retrieving altnames.
2023-12-05 02:36:40 +01:00
Lukas Schauer
e3ef43c816 fix zsh compatibility (fixes #896) 2023-01-16 22:41:05 +01:00
Alexander Sulfrian
67b111a7b0 Replace all escaped slashes in json strings (closes #866)
${var/pattern/string} will only replace the first occurence. We should
use ${var//pattern/string} to replace all escaped slashes.
2022-10-31 16:27:16 +01:00
Daniel Molkentin
fa68ad8b23 improve man page based on feedback from debian-l10n-english (fixes #873, closes #875)
Also propagate changes to dehydrated help and README.md
2022-10-31 16:22:04 +01:00
Lukas Schauer
5c4adf6baa added note about dehydrated irc channel 2022-10-31 15:46:28 +01:00
Lukas Schauer
35bfea55b6 increase dehydrated version for git master use 2022-10-31 15:46:07 +01:00
Lukas Schauer
ea84199863 release 0.7.1 (it finally happened!) 2022-10-31 15:12:38 +01:00
Krayon
6091ba4bc2 Add missing checks and fix hexdump output (closes #878) 2022-10-31 15:12:04 +01:00
Lukas Schauer
6fb8eba56a implemented workaround for retrying on badNonce errors 2022-09-07 15:09:57 +02:00
Simon Deziel
19c7fbbf47 egrep is deprecated
egrep has been deprecated since 2007 and warns it's obsolete since:
https://git.savannah.gnu.org/cgit/grep.git/commit/?id=a9515624709865d480e3142fd959bccd1c9372d1

Signed-off-by: Simon Deziel <simon@sdeziel.info>
2022-04-07 21:49:56 +02:00
Lukas Schauer
7128e6b63c rfc8738: fix CN on certs with mixed ip+dns 2022-04-07 01:34:21 +02:00
Lukas Schauer
861f4c733d rfc8738: only replace ip with reverse dns thingy if tls-alpn-01 is used 2022-04-07 01:33:48 +02:00
Lukas Schauer
ad3f08084c implemented rfc 8738 support 2022-04-06 22:23:43 +02:00
Lukas Schauer
784fb806c8 really reverted regression in somehow broken array expansion from e963438c.. 2021-11-02 09:05:19 +01:00
Lukas Schauer
b2574b16d1 reverted regression in somehow broken array expansion from e963438c (fixes #850) 2021-11-02 09:01:00 +01:00
Lukas Schauer
da641588ce removed old logo 2021-11-01 19:25:17 +01:00
Lukas Schauer
8e6ddf6286 readme and (temporary) logo update 2021-11-01 19:22:50 +01:00
Lukas Schauer
8e5977890a fix regression from e963438c (fixes #849) 2021-11-01 18:57:57 +01:00
Lukas Schauer
3bcf0c7f5a use noglob helpers for domains.txt.d parsing 2021-11-01 18:57:49 +01:00
Lukas Schauer
b347bc9086 added some changes to changelog 2021-10-31 22:58:06 +01:00
Lukas Schauer
08477170e9 Exit with error if somebody is trying to use EC account keys with ACME v1 2021-10-31 22:36:40 +01:00
Lukas Schauer
f4cf92bae5 extend ec algorithms with secp521r1 (not yet supported by LetsEncrypt, but maybe by other CAs) 2021-10-31 22:31:09 +01:00
Lukas Schauer
93573cda3c experimental support for ec account keys (fixes #827) 2021-10-31 22:29:44 +01:00
Lukas Schauer
607a6088d3 Avoid writing ec-parameters to private-key file (fixes #830) 2021-10-31 20:20:40 +01:00
Lukas Schauer
880c99aa63 Better solution for issue #845 2021-10-31 20:11:31 +01:00
Lukas Schauer
7ac25358ef Show error if chain is configured for a CA which doesn't offer alternate chains (fixes #845) 2021-10-31 20:06:50 +01:00
Lukas Schauer
5733863b93 added warning about possible behaviour-change with new domains.txt.d feature 2021-10-31 19:55:41 +01:00
Marc Schütz
f6a84a88fa Support reading domains from drop-in snippets in domains.txt.d 2021-10-31 19:48:28 +01:00
Lukas Schauer
e963438c5a make shellcheck happy again 2021-10-31 19:33:03 +01:00
Stefaan Ghysels
095165ee96 Only check existing certs when necessary 2021-10-31 19:29:00 +01:00
Simon Deziel
199cd59774 Remove debug echo in command_cleanup()
Signed-off-by: Simon Deziel <simon@sdeziel.info>
2021-10-31 19:23:01 +01:00
Elan Ruusamäe
e17456778f Use consistent indent in hook.sh 2021-10-31 19:21:26 +01:00
Brian Bennett
71f6bc617e Better handling around grep/awk 2021-10-31 19:17:49 +01:00
Joao Morais
6ee4ae508e fix command_version on Darwin/macOS 11
Current output of `uname` on Darwin/macOS 11 is only `Darwin`, which
breaks the premisse used in `command_version()`. This update adds
`Darwin` alongside `BSD`.
2021-10-31 19:08:52 +01:00
27o
91cccc0c23 ensure newline before new section in openssl.cnf
openssl.cnf may not end with a newline. The section [SAN] will then not be found as it is added to the last line of openssl.cnf.
2021-04-23 02:03:23 +02:00
Lukas Schauer
ab016803dd expand documentation on using letsencrypt staging ca 2021-04-18 03:48:47 +02:00
Nick
7d8573af12 Update staging.md to use ACMEv2 server (closes #812)
letsencrypt is phasing out the v1 server:

```
  + ERROR: An error occurred while sending get-request to https://acme-staging.api.letsencrypt.org/directory (Status 403)

Details:
HTTP/2 403
server: nginx
date: Thu, 01 Apr 2021 20:48:17 GMT
content-type: application/problem+json
content-length: 189
etag: "600b3710-bd"

{
  "type": "urn:acme:error:serverInternal",
  "detail": "ACMEv1 Brownout in Progress. ACMEv1 will fully turn off on June 1, 2021. Check https://letsencrypt.status.io/ for more details."
}

```
2021-04-18 03:44:41 +02:00
Lukas Schauer
fb06530097 command_sign_csr: redirect fds after init_system (fixes #816) 2021-04-16 14:32:05 +02:00
Lukas Schauer
5c1551e946 remove some dots :) 2021-03-29 20:20:52 +02:00
Marcus Rückert
20c27b291c Add more examples to show case how to create certs
e.g. with different key algorithms
2021-03-29 20:19:41 +02:00
Lukas Schauer
24f66a3473 generic support for weird curl versions with lower-case headers and no whitespace 2021-03-21 20:51:10 +01:00
joele89
21bff55b7c Updating nonce handler for newer versions of F5 2021-03-21 20:46:31 +01:00
Glenn Strauss
374fce0249 document using -t tls-alpn-01 with lighttpd 2021-03-21 20:42:23 +01:00
Glenn Strauss
00941472b2 add -t tls-alpn-01 to command line help 2021-02-18 16:56:05 +01:00
Michel Lespinasse
527933db24 Per-certificate config fixes
- Ensure that all per-certificate settings are saved and restored in
  store_configvars() and reset_configvars() - that's what makes them
  per-certificate in the first place...

- Add OCSP_FETCH and OCSP_DAYS in the documented list of supported
  per-certificate configs, since the code does allow these.
2021-02-18 16:51:14 +01:00
Nathan Felton
33a421f1e4 Support for LibreSSL version of openssl on macOS 2021-02-18 16:47:24 +01:00
Lukas Schauer
dd0bbd2405 update copyright year 2021-02-18 16:47:23 +01:00
Lukas Schauer
26660e11c7 Fixed small unassigned variable issue 2020-12-12 03:12:13 +01:00
Lukas Schauer
316054ad1c Do not revalidate authorizations on forced renewal
This commit introduces a new cli argument `--force-validation` which,
when used in combination with `--force` ignores valid domain
authorizations and forces a revalidation.

This has been implemented since at least LE seems to have changed some
behavior on valid authorizations. Only the previously validated
authorization-type is reusable, causing dehydrated to error out when
changing from recently validated authorization types while still trying
to force-renew certificates for whatever reason (e.g. changing algorithms).
2020-12-12 03:01:59 +01:00
Lukas Schauer
29b67962ac fix CN extraction for older openssl versions 2020-12-11 18:02:51 +01:00
Lukas Schauer
3a7795589b bump changelog for new draft releases 2020-12-10 16:56:13 +01:00
Lukas Schauer
082da2527c preparing for release 0.7.0 2020-12-10 16:54:26 +01:00
Lukas Schauer
e784ba3853 use normal error behaviour for failing http requests (fixes #782) 2020-12-10 16:32:26 +01:00
Lukas Schauer
abd369d062 allow to set domains.txt as cli argument (fixes #678) 2020-12-10 16:07:28 +01:00
Lukas Schauer
cb7fb82beb use secp384r1 as default (instead of rsa, fixes #651) 2020-12-10 16:01:54 +01:00
Lukas Schauer
174616becd use secp384r1 as default (instead of rsa, fixes #651) 2020-12-10 16:01:08 +01:00
Raphael Hoegger
27fd41d75f adding new CLI Command (--cleanupdelete / -gcd) to cleanup+delete (instead of just moving to /archive) (closes #587) 2020-12-10 14:58:14 +01:00
Lukas Schauer
ea106ef72e allow setting OCSP_FETCH and OCSP_DAYS per certificate config (closes #602, thx @bjacke) 2020-12-10 14:28:30 +01:00
Lukas Schauer
f2d6a6152e cleanup: also remove dangling symlinks 2020-12-10 14:15:07 +01:00
Arnout Vandecappelle (Essensium/Mind)
129ec851ed cleanup: also do cleanup if symlink is broken (closes #667)
The cleanup command skips filetypes for which the symlink is broken or
doesn't exist. However, if dehydrated fails, we may end up in exactly
the situation that the symlink doesn't exist (yet). If dehydrated fails
repeatedly, we may end up with a lot of old cert.csr, cert.pem and
privkey.pem files, so we really want to be able to clean them up.

Remove all files if the symlink is broken/missing, instead of skipping
those files.

Signed-off-by: Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>
2020-12-10 14:14:35 +01:00
Georg Altmann
835963fa6e make alpn-validation certificates and keys group readable (closes #754, fixes #753) 2020-12-10 14:00:26 +01:00
Daniel Molkentin
829aaeff2d Fix OCSP_FETCH with libressl
libressl did not pick up the implicit host header patches
of OpenSSL 1.1 even in version 3 and thus exhibits the same
behavior as OpenSSL 1.0.

Patch by Chen, Chih-Chia <pigfoot@gmail.com>

Fixes #778
2020-12-10 13:52:47 +01:00
Lukas Schauer
481aba7d7b remove quotes from per-cert-config vars to allow for spaces (fixes #789, closes #791) 2020-12-09 20:41:17 +01:00
Lukas Schauer
fbcaac89f9 changed method for parsing issuer cn, fixing compatibility with some openssl versions 2020-12-09 19:38:27 +01:00
Lukas Schauer
589e9f30b3 show available options if preferred chain is not found 2020-11-21 16:19:08 +01:00
Lukas Schauer
f2103340f3 fix spaces in sudo arguments 2020-11-20 17:09:04 +01:00
Lukas Schauer
c670c18299 added display-terms to changelog+readme 2020-11-13 20:49:18 +01:00
Daniel Molkentin
7cc9e2d07f add --display-terms to display the URL for the current ToS
Implements #649
2020-11-13 20:47:49 +01:00
Lukas Schauer
7dfde364a3 added support for requesting preferred-chain instead of default chain 2020-11-13 20:36:51 +01:00
Lukas Schauer
7d3288f428 one more \s -> [[:space:]] replacement 2020-09-30 11:35:06 +02:00
Jarkko Oranen
e69df6521b Replace \s with [[:space:]] for compatibility 2020-09-30 11:32:23 +02:00
Daniel Molkentin
8ddead4854 Complain about deactivated accounts 2020-09-28 01:15:48 +02:00
Daniel Molkentin
308b3ec750 implement account deactivation through --deactivate parameter
This is an updated version of https://github.com/lukas2511/dehydrated/files/2641548/dehydrated_add_deactivate_command.diff.txt

Fixes #216
2020-09-28 01:15:47 +02:00
Daniel Molkentin
39e1068a87 Don't require sudo before we know we really need it
Fixes #665
2020-09-27 22:26:20 +02:00
Daniel Molkentin
6d9fcd2588 Do not fail silently with invalid sudo user/group 2020-09-27 22:26:18 +02:00
Daniel Molkentin
60cb678e3b add more CAs, now that support for CA presets is implemented
- letsencrypt-test (LE staging CA)
- buypass (verified to work with the new json parsing, see #653)
- buypass-test analogously
2020-09-27 20:41:34 +02:00
Daniel Molkentin
5f8cfa50ba fix OS name detection
before applying heuristics, use PRETTY_NAME from os-release(3),
which reliably exists on all common linux distributions.

keep the /etc/issue parsing as fallback.
2020-09-27 20:35:18 +02:00
Lukas Schauer
b3abc41dbe tmpfix: log error if acmev1 validation is denied + fix unbound variable 2020-09-15 17:27:24 +02:00
Lukas Schauer
b3b2fee496 eab: use hex key instead of binary (fixes issue with nullbytes) 2020-09-14 18:59:41 +02:00
Lukas Schauer
416fd0fd1b do not fail on challenge in "processing" state (fixes #759) 2020-09-14 18:31:24 +02:00
Lukas Schauer
142c69dd90 fixed bad typo.. 2020-09-14 18:28:05 +02:00
Lukas Schauer
74c136905b readme+changelog 2020-09-14 18:24:01 +02:00
Lukas Schauer
5fc1175aef EAB + ZeroSSL support 2020-09-14 18:22:36 +02:00
Lukas Schauer
4b91fcf498 read boolean values from json 2020-09-14 18:19:08 +02:00
Lukas Schauer
11323d0727 removed accidental shebang 2020-09-14 18:18:35 +02:00
Lukas Schauer
a9a64c9fd0 use presets for some CAs instead of requiring full urls 2020-09-14 16:37:16 +02:00
Jason Francis
42a0fc9a5e fix tls-alpn-01 configuration example 2020-07-05 22:30:38 +02:00
Lukas Schauer
e119d9136b fixed some typos (fixes #725, fixes #741, fixes #740) 2020-07-05 22:29:57 +02:00
j-ed
275fb40ab4 removed tmp file in 'generate_alpn_certificate' function
Made sure that the temp file will be removed at the end of the function.
2020-07-05 21:41:12 +02:00
Lukas Schauer
7e92850957 fixed zsh compatibility 2020-07-05 04:13:11 +02:00
Lukas Schauer
bb5a1473d1 merged temporary json.sh into dehydrated, fixed authorization "pending" loop 2020-07-04 21:51:32 +02:00
Lukas Schauer
7f970b527c experimental json.sh support 2020-07-04 21:36:23 +02:00
Krayon
dc552c602e Use existing curl version var
Signed-off-by: Krayon <krayon.git@qdnx.org>
2020-04-28 21:36:52 +02:00
Lukas Schauer
9827a411b3 removed instructions for importing from "official" client (certbot) as it probably doesn't work anymore and there isn't really much use for it anyway 2020-04-28 21:29:24 +02:00
Lukas Schauer
4a55f93896 fix link to wiki in documentation (fixes #690) 2020-04-28 21:27:49 +02:00
Lukas Schauer
a07c8d14f6 reworked dependency check and moved it up a bit in code (fixes #715, resolves #717 again...) 2020-04-28 21:25:08 +02:00
Lukas Schauer
42047fdf11 added changelog 2020-04-28 21:10:22 +02:00
Lukas Schauer
76d7e31981 added note about newline encoded in accounts directory hashes (resolves #730) 2020-04-28 21:03:55 +02:00
Lukas Schauer
4fd4d4d3c2 temporarily store raw curl version output to fix check (fixes #717) 2020-04-28 20:58:46 +02:00
Lukas Schauer
229f7186a6 store errorcode while using KEEP_GOING (fixes #659) 2020-04-28 20:39:04 +02:00
Lukas Schauer
4b7a1e4ce6 report issues with hook scripts instead of silently exiting (fixes #733, fixes #686) 2020-04-28 20:13:03 +02:00
Lukas Schauer
871efe653b skip exit_hook in _exiterr if KEEP_GOING is enabled (fixes #686) 2020-04-28 18:36:02 +02:00
Rogdham
dbb0ef1ce1 Move from account ID to account URL
We store the account URL on account creation in the account_id.json file.

When reading the file, if the attribute is missing, we retrieve the account URL
from the CA ( https://tools.ietf.org/html/rfc8555#section-7.3.1 ) and edit the
file.

Per https://tools.ietf.org/html/rfc8555#section-7.3
> The server returns this account object in a 201 (Created) response, with the
> account URL in a Location header field.  The account URL is used as the "kid"
> value in the JWS authenticating subsequent requests by this account (see
> Section 6.2).  The account URL is also used for requests for management
> actions on this account, as described below.
2020-04-03 09:16:30 +02:00
Lukas Schauer
fcfb077a95 redirect output of cert expiry check (fixes #713) 2020-04-02 12:44:40 +02:00
Lukas Schauer
bc9344392a fixed typo (closes #712) 2020-04-02 12:23:09 +02:00
Lukas Schauer
5b7c898b63 only show order processing/pending message when waiting 2020-04-02 12:15:17 +02:00
Rogdham
58bd926e30 Don't assume order status to be valid
Per https://tools.ietf.org/html/rfc8555#section-7.1.3

> status (required, string):  The status of this order.  Possible values are
> "pending", "ready", "processing", "valid", and "invalid".  See Section 7.1.6.
2020-04-02 12:09:27 +02:00
Rogdham
c8333f5a56 Fix challenge response POST body in ACMEv2
Per https://tools.ietf.org/html/rfc8555#section-7.5.1

> The client indicates to the server that it is ready for the challenge
> validation by sending an empty JSON body ("{}") carried in a POST
> request to the challenge URL (not the authorization URL).
2020-03-30 21:34:43 +02:00
Lukas Schauer
307eaadddf updated notice about move 2020-01-30 22:51:25 +01:00
Lukas Schauer
dfffb1b88b added note about moving the repository 2020-01-30 01:45:17 +01:00
Lukas Schauer
e2eeaf7ec6 added funding.yml 2019-11-11 22:46:13 +01:00
Lukas Schauer
946e5712ba fixed small logic bug 2019-10-09 02:05:54 +02:00
Lukas Schauer
018254974c Merge tag 'v0.6.5' 2019-07-06 19:25:32 +02:00
Lukas Schauer
05eda91a2f release 0.6.5 (fixed apiv1 compatibility...) 2019-06-26 12:33:35 +02:00
Lukas Schauer
f60f2f81e8 release 0.6.5 (fixed apiv1 compatibility...) 2019-06-26 12:29:39 +02:00
Lukas Schauer
4f358e22f4 release 0.6.4 (fixed account id handling, again) 2019-06-25 15:28:09 +02:00
Lukas Schauer
f9d0b1bd70 release 0.6.3 2019-06-25 12:50:45 +02:00
Lukas Schauer
be13dcd454 fixed fetching of account information (fixes #652, fixes #647, fixes #650, closes #648) 2019-06-25 12:19:20 +02:00
Lukas Schauer
74a536c161 added documentation about possible future removal of api version 1 2019-03-04 23:23:40 +01:00
Lukas Schauer
444cea4669 Revert "cleanup: removed api version 1 support (closes #510)"
Since a few CAs out there actually seem to (only) support ACME v1 I
decided to revert the removal and keep ACME v1 around, at least until
it eventually becomes a bigger inconvenience to maintain.

This reverts commit aadf7d5e64.
2019-03-04 23:11:07 +01:00
Lukas Schauer
ea93170959 BSD bugfixes for version command (closes #619) 2019-03-03 21:51:01 +01:00
Lukas Schauer
133e31de0b tiny documentation fix: per-certificate-config can override PRIVATE_KEY_ROLLOVER (closes #614) 2019-03-03 20:38:47 +01:00
Lukas Schauer
aadf7d5e64 cleanup: removed api version 1 support (closes #510) 2019-03-03 20:32:10 +01:00
Lukas Schauer
e4a32acbe2 new hook: sync_cert (closes #609) 2019-03-03 20:22:41 +01:00
Lukas Schauer
1c77730373 call exit_hook with error message (fixes #630) 2019-03-03 20:08:18 +01:00
Lukas Schauer
e623fcc024 implement POST-as-GET (closes #626) 2019-03-03 20:00:13 +01:00
Lukas Schauer
585ed5404b updated oid for tls-alpn verification token (fixes #624) 2019-01-18 13:25:57 +01:00
Lukas Schauer
92aa1ecd5a document DOMAINS_D parameter in example config (fixes #575, closes #582) 2018-10-20 13:05:20 +02:00
Lukas Schauer
5783a2dd45 fixed a bug that resulted in a deleted domains.txt when using incorrect parameters in combination with signcsr (fixes #597) 2018-10-20 12:27:23 +02:00
Lukas Schauer
fba49ba28e implemented initial support for tls-alpn-01 verification 2018-07-26 04:44:29 +02:00
Lukas Schauer
10d4b98e7f Only match Replace-Nonce header at beginning of line 2018-05-09 21:01:57 +02:00
Florent
e4e712c03a Fixes #559 : when HTTP/2 is used, header names are lower case. So adding ignore case option (-i) to grep's. 2018-05-09 21:00:05 +02:00
Lukas Schauer
2a8af8fda7 made ocsp refresh interval configurable 2018-05-07 03:31:43 +02:00
Lukas Schauer
9165cfdebf added dns-txt-foo to troubleshooting.md 2018-05-01 17:54:13 +02:00
Lukas Schauer
b5dddd7a2b prepare for next version 2018-04-27 13:08:44 +02:00
Lukas Schauer
ce3d658377 release 0.6.2 2018-04-25 23:22:40 +02:00
Lukas Schauer
89de83c994 add explanation on HEADERS parameter to request_failure hook (fixes #545) 2018-04-25 22:48:16 +02:00
Lukas Schauer
52c2c19994 added workaround for use with advanced filesystem ACLs (as originally suggested in #467) 2018-04-20 02:55:07 +02:00
Tobias Tiederle
53c458c318 fix parameter 2018-04-15 16:08:30 +02:00
Lukas Schauer
ba5928776f fix behaviour for fetching missing additional account information with unknown keys 2018-04-13 22:09:52 +02:00
Lukas Schauer
dff7d4ea35 allow registration using cli-specified private key (#534) 2018-04-13 21:34:11 +02:00
Lukas Schauer
0262997451 also call clean_challenge hook for http-01 challenges (fixes #536) 2018-04-13 20:59:27 +02:00
Lukas Schauer
8ba56a8048 renamed ocsp hook to deploy_ocsp, exported altnames, added example hook 2018-04-08 22:44:28 +02:00
Ike Johnson
2fca309e94 Add ocsp_update hook
In relation to issue #513
2018-04-08 22:31:56 +02:00
Daniel Molkentin
13b8a3f29f fix date in man page 2018-04-08 22:21:33 +02:00
Lukas Schauer
a67816468a also run request_failure and invalid_challenge when HOOK_CHAIN is defined (fixes #450) 2018-04-08 22:18:30 +02:00
Lukas Schauer
ea46aee44f fixed cleanup for invalid challenges 2018-04-08 22:18:16 +02:00
Lukas Schauer
537877a0e2 allow for widely-used non-standard CSR label (fixes #488) 2018-03-26 00:00:50 +02:00
Lukas Schauer
981179a770 hail hydra! 🐙 2018-03-24 16:18:01 +01:00
Lukas Schauer
ff18d39aa8 strip validationRecord from challenge before grepping for "url" string... (fixes #515) 2018-03-18 20:12:04 +01:00
John L. Villalovos
7c40c727a0 Improve documentation on wildcards
Improve the documentation on how to use wildcard domains.

Also give more examples in the docs/examples/domains.txt file.
2018-03-17 13:27:15 +01:00
Lukas Schauer
9f1ff67870 removed dual use of challenge_identifiers variable (fixes #511) 2018-03-17 01:40:19 +01:00
Lukas Schauer
b116e6bc2b close weird external file descriptors 2018-03-15 13:52:51 +01:00
Lukas Schauer
6083218501 removed some unused code 2018-03-15 13:52:15 +01:00
Lukas Schauer
2533931cf1 don't walk certificate chain for ACMEv2 (certificate contains chain by default) 2018-03-14 18:54:51 +01:00
Lukas Schauer
b93eac3893 fixed CA url in example config 2018-03-13 21:08:20 +01:00
Lukas Schauer
e374d21d45 prepare for next version 2018-03-13 20:59:20 +01:00
Lukas Schauer
70d261a729 release v0.6.1 2018-03-13 20:57:52 +01:00
Lukas Schauer
947dbb9e29 use new acme-v02 endpoint by default 2018-03-13 20:48:42 +01:00
Lukas Schauer
8a414e55bc prepare for next version 2018-03-11 20:22:38 +01:00
Lukas Schauer
fd3fc8af62 release 0.6.0 2018-03-11 20:19:25 +01:00
Lukas Schauer
6e802ddc19 include content-type in post requests (fixes #491) 2018-03-09 12:25:37 +01:00
Lukas Schauer
0211d24577 require a valid alias to be set for certain wildcard certificates (fixes #483) 2018-03-02 18:53:00 +01:00
Lukas Schauer
68274646bb curl: use custom user agent (temporarily using a bit of 1337) 2018-03-01 21:39:00 +01:00
Lukas Schauer
c0bcf91410 show error details on ocsp update failure 2018-02-22 22:32:39 +01:00
Lukas Schauer
a91074b707 fixed undefined-variable bug on early connection failure (while accessing ca directory) 2018-02-22 22:22:04 +01:00
Ewald Dieterich
a6a07779ad fixed "sed: invalid option -- 'E'" 2018-02-22 19:28:36 +01:00
Lukas Schauer
a6e6aa7445 fixed spurious return code from hook bricker... 2018-02-14 15:20:26 +01:00
Lukas Schauer
dcdb2940fb removed random prefix from hook bricker 2018-02-14 14:58:11 +01:00
Lukas Schauer
0ade30cc74 hook-bricker now also warns users on per-certificate-config-hooks 2018-02-13 21:45:30 +01:00
Lukas Schauer
bc34f3aa86 merged random hook with human-readable-message 2018-02-13 21:34:02 +01:00
Lukas Schauer
5940c55e18 prepared future migration to new acmev2 endpoint 2018-02-07 03:14:29 +01:00
Lukas Schauer
2eedd69ee9 request_failure hook: added http response headers as new parameter 2018-02-06 23:33:28 +01:00
Lukas Schauer
be252c7db9 updated changelog 2018-02-06 23:22:53 +01:00
Lukas Schauer
9ebab3e026 added call to random hook to make it clear to hook authors that unknown hooks should just be ignored 2018-02-06 23:16:28 +01:00
Lukas Schauer
ad291207d0 fetch account information if missing 2018-02-06 23:08:40 +01:00
Lukas Schauer
a7b2af2b92 http_request: make http headers available on fd 4 2018-02-06 23:08:16 +01:00
Lukas Schauer
082ed17a0a added acmev2 staging information to docs 2018-02-06 21:58:42 +01:00
Lukas Schauer
dec5ad5840 read url to terms of service from ca directory 2018-02-06 21:58:42 +01:00
Lukas Schauer
da67297288 only write csr file if renewal will be requested 2018-02-06 21:19:33 +01:00
Lukas Schauer
83bf2664b0 added a few simple example use cases to example hook script 2018-02-06 21:13:37 +01:00
Lukas Schauer
63854b752b New hook: generate_csr (see example hook script for more information, implements #475, replaces #377) 2018-02-06 20:57:33 +01:00
Lukas Schauer
901f9f76e2 pre-bump version 2018-02-06 20:53:09 +01:00
Lukas Schauer
b5de2e26eb sign_domain: Use existing CSR with matching timestamp 2018-02-06 20:41:26 +01:00
Lukas Schauer
73a116e879 Create required certificate and chaincache directories outside of sign_domain 2018-02-06 20:40:32 +01:00
Lukas Schauer
9c35fce61e Pre-generate timestamp outside of sign_domain 2018-02-06 20:39:23 +01:00
Lukas Schauer
87194f6596 Remove additional whitespace from extract_altnames 2018-02-06 20:38:02 +01:00
Mattia Rizzolo
62d37c9b3d Fix grammer error in the manpage (fixes #466)
"allows to" requires a subject (e.g. "allows one to"), without it's just
syntactically wrong.  Change the verb entirely to workaround the
problem.
2018-02-06 19:14:12 +01:00
Lukas Schauer
b53cb6643b moved manpage to docs directory 2018-02-06 18:53:21 +01:00
Lukas Schauer
fb41783885 automatic discovery of remote acme api version 2018-02-05 19:20:28 +01:00
Nick Muerdter
0bc0bd13d6 Fix globbing of CONFIG_D *.sh files.
With the globbing changes made in
61083cf522 to globally disable globbing by
default, this broke the ability to load the CONFIG_D `*.sh` files.

This re-enables globbing when reading these `*.sh` files and then disables it
again afterwards. Note that this also keeps globbing enabled inside the
loop, when sourcing the individual `*.sh` files for backwards
compatibility (so if the individual config scripts relied on the default
of globbing being enabled, there won't be any change in behavior).
2018-02-03 16:11:14 -07:00
Lukas Schauer
6d02bfdb42 shrink "logo" a bit 2018-02-03 22:14:43 +01:00
Lukas Schauer
727443483d added acmev2 information to readme 2018-02-03 22:12:32 +01:00
Lukas Schauer
7a0e71c6c2 follow location on http get-requests 2018-02-03 22:03:58 +01:00
Lukas Schauer
45f5c17260 fixed altname extraction of csr with wildcard domains, moved altname extraction from sign_csr to command_sign_csr 2018-02-02 23:47:29 +01:00
Lukas Schauer
61083cf522 disable globbing globally (only allow for cleanup routine) 2018-02-02 23:45:34 +01:00
Lukas Schauer
afba7c694c moved deploy_challenge to earlier loop so it works with multiple challenge tokens on the same identifier (important for wildcard certificate), fixed array-name, removed hook-chain warning 2018-01-28 19:48:25 +01:00
Pandark
471899b4d8 Add ^~ to nginx location block
To make sure it is not overridden.
> http://nginx.org/en/docs/http/ngx_http_core_module.html#location :
> If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.
2018-01-28 06:18:10 +01:00
Lukas Schauer
ec5dbcc816 updated changelog 2018-01-28 06:14:44 +01:00
Lukas Schauer
0f69481e2b rewrote challenge validation to iterate over authorizations instead of altnames (fixes some acmev2 validation edgecases), also removed broken test-script (for now) 2018-01-28 06:13:37 +01:00
Lukas Schauer
6f3fed496d rewrote donation section in readme 2018-01-28 06:13:01 +01:00
Lukas Schauer
5fd93ea874 be more verbose for acme v2 challenge handling 2018-01-27 22:51:39 +01:00
Lukas Schauer
656af8cadc don't fail on nested json array in challenge info 2018-01-13 23:10:31 +01:00
Lukas Schauer
3e521e1c01 fixed domains.txt parsing (theoretically compatible with wildcard domains) 2018-01-13 20:54:55 +01:00
Martin Strobel
68cb1e0661 ACME v02 Support 2018-01-13 20:17:25 +01:00
Lukas Schauer
35a9f31643 changelog template, year update 2018-01-13 20:10:32 +01:00
Lukas Schauer
4a811759dc version 0.5.0 2018-01-13 20:08:12 +01:00
Lukas Schauer
2adc57791c Add optional user and group configuration (fixes #434) 2017-12-18 00:35:26 +01:00
Lukas Schauer
f35aed6ae6 replace backticks with escaped dollarbracethingy (fixes #438) 2017-12-18 00:01:47 +01:00
Lukas Schauer
b6b56d0df7 export certificate alias to be used in hook scripts 2017-12-17 23:54:19 +01:00
Lukas Schauer
13c853d43b also reset configvars after domains loop 2017-12-17 23:51:23 +01:00
Lukas Schauer
c62f3d91fc implement certificate aliases as suggested by typingArtist (fixes #396) 2017-12-17 23:50:46 +01:00
typingArtist
eb1c4ac41d make certdir a parameter to sign_domain 2017-12-17 22:55:12 +01:00
Daniel Molkentin
3ec54e7e0f Add man page 2017-12-14 00:35:54 +01:00
sirrkitt
88267db7e2 Update wellknown.md
add Hiawatha to list
2017-12-14 00:33:11 +01:00
Lukas Schauer
eb4aaefda1 also inform about still-valid ocsp stapling files (fixes #457) 2017-12-14 00:11:40 +01:00
Lukas Schauer
3d97799d6a always revalidate challenges if --force is set (fixes #370) 2017-11-07 14:43:41 +01:00
Exagone313
742c0ad176 fix ocsp.der symlink 2017-10-22 16:31:25 +02:00
Andreas Loibl
7f410e9bff fix account command
backup file path generation should split the filename on the last dot instead of the first
2017-10-17 16:39:46 +02:00
Lukas Schauer
da3428a84a use nullglob, disable warning on empty CONFIG_D directory 2017-09-21 18:10:01 +02:00
Lukas Schauer
b5e178ea75 allow for spaces when extracting commonName from csr (fixes #423) 2017-09-20 15:44:05 +02:00
Lukas Schauer
bc20ec79f3 also show freebsd version 2017-09-20 15:31:38 +02:00
Marcin Gryszkalis
ce9b42d8ad fix issue #426 - version info on FreeBSD 2017-09-20 15:28:57 +02:00
Lukas Schauer
f838d93f40 stop verification loop after invalid challenge (fixes #431) 2017-09-20 15:17:30 +02:00
typingArtist
0be0ab083f replace ${CERTDIR}/${domain} with ${certdir} everywhere
• improves readability
• allows ${certdir} to be changed independent from ${domain} more easily
2017-07-18 15:46:25 +02:00
Lukas Schauer
58647cab65 added OPENSSL variable to example config (#414) 2017-07-18 15:46:25 +02:00
Lukas Schauer
c57ad87e7c fixed error handling on non-2xx http status codes (#413) 2017-07-18 03:29:39 +02:00
Lukas Schauer
2687054d25 cut path from url for ocsp host 2017-07-13 00:53:32 +02:00
Lukas Schauer
2b76d038d3 ocsp fetching should now also work with older openssl versions 2017-07-12 16:00:25 +02:00
Lukas Schauer
e339b28159 add host header to ocsp request 2017-07-12 15:33:56 +02:00
Lukas Schauer
4f3bd3e956 fixed exit_hook 2017-07-11 10:06:42 +02:00
Lukas Schauer
f86290ea52 revocation: don't fail if certificate already has been revoked (fixes #236) 2017-07-11 01:30:30 +02:00
Lukas Schauer
f1bc2b14ba cleanup old ocsp response files 2017-07-11 00:50:05 +02:00
Lukas Schauer
367ef574f1 export altnames so it can be used in hook scripts (fixes #360) 2017-07-11 00:47:37 +02:00
Lukas Schauer
4e7fb80bcd support otherName SAN entries for domain verification (fixes #356) 2017-07-11 00:35:59 +02:00
Lukas Schauer
ee75c5dca7 Initial support for fetching OCSP status to be used for OCSP stapling (as suggested in #385) 2017-07-11 00:28:36 +02:00
Lukas Schauer
82ca3ffcd3 added giant donation section to readme (please send me all your money) 2017-07-10 22:48:50 +02:00
Lukas Schauer
bb99742aa7 load config for version information but disable verification 2017-07-10 21:36:32 +02:00
Lukas Schauer
60583d3ef9 added hook to run before cron command (fixes #371) 2017-07-10 21:36:10 +02:00
Lukas Schauer
cbb661ca17 specify openssl config location when converting CSR to DER (fixes #397) 2017-07-10 20:32:05 +02:00
Lukas Schauer
67cf20765c updated changelog 2017-07-10 20:28:38 +02:00
Lukas Schauer
89377a1004 git: ignore chains directory 2017-07-10 20:28:20 +02:00
Lukas Schauer
dc600e39b8 cleanup: also move .pem-revoked files (fixes #237) 2017-07-10 20:27:42 +02:00
Lukas Schauer
16e91b415b added auto-cleanup feature to changelog 2017-07-10 20:23:27 +02:00
Lukas Schauer
e6d6882c78 added option to automatically run cleanup routine (implements #389) 2017-07-10 20:21:30 +02:00
Herman van Rink
14a5f63077 Redirect additional errors to STDERR 2017-07-10 19:59:52 +02:00
Herman van Rink
5787cd6a47 Remove double output redirection, _exiterr already does >&2 2017-07-10 19:59:52 +02:00
ProBackup-nl
875c1f74e5 Add some formatting to improve human scannability (while reading) 2017-07-10 19:53:39 +02:00
Lukas Schauer
db18820991 made openssl binary configurable (closes #393, closes #379) 2017-07-10 19:13:52 +02:00
Lukas Schauer
2f775d0e2a remove duplicate -a short option from --account (fixes #410) 2017-07-10 18:21:06 +02:00
Lukas Schauer
f2b589430c added version command 2017-07-10 17:21:22 +02:00
Lukas Schauer
533aa80129 replaced source url with dehydrated.de 2017-07-10 17:15:29 +02:00
Lukas Schauer
d1f215b652 fixed typos as suggested by @jwilk (closes #369) 2017-07-10 16:55:18 +02:00
Andreas Thienemann
bd57777c62 Ability to provide extra curl options
In some situations it might be necessary to pass extra commands to
the curl binary, e.g. proxy authentication credentials.

Adds the CURL_OPTS config option.
2017-07-10 16:44:39 +02:00
Haddon CD
ba31a505d2 Add MSYS support 2017-07-10 16:40:36 +02:00
Chase Bolt
0dcf94dd3d dont strip for docker containers that cwd is / 2017-07-10 16:36:14 +02:00
Andreas Thienemann
9ea75e7cfb Support older bash releases
Bash 3.0 and others seem to have serious issues running dehydrated.
https://github.com/lukas2511/dehydrated/issues/284 tracks the
problem but got closed as it seems too hard to support "stone age"
distributions.

Turns out it is actually only a three line change. ;-)
2017-07-10 15:29:30 +02:00
Ben Elliston
33c77e6daa Add some comments about IPv6. 2017-07-10 15:26:50 +02:00
Lukas Schauer
d685463673 implemented issuer-chain cache 2017-07-10 15:06:50 +02:00
Lukas Schauer
98ad01a110 allow using parts of dehydrated without running the main script (intended for testing parts of the script) 2017-07-10 14:51:55 +02:00
Lukas Schauer
8709d21ef2 updated usage instructions in readme (added --account) 2017-07-10 14:08:29 +02:00
Lukas Schauer
6ebaae416c removed build status from readme (test system seems to be broken...) 2017-07-10 14:07:28 +02:00
Lukas Schauer
7fc4040f47 updated changelog with account-update feature 2017-07-10 14:05:45 +02:00
Anton Avramov
ec1599e3b6 Added new feature Update registration contact #239 2017-07-10 14:02:21 +02:00
Lukas Schauer
116386486b release 0.4.0 2017-02-05 15:33:17 +01:00
Lukas Schauer
aed4272e97 improved register command (closes #350) 2017-02-05 15:32:29 +01:00
Lukas Schauer
ad21b41e27 also test under osx 2017-02-04 13:54:22 +01:00
Lukas Schauer
3d8d320c9f trying to fix compatibility with mawk 2017-02-04 13:32:56 +01:00
Lukas Schauer
449490a981 Merge branch 'simondeziel-fix-example-hook' 2017-01-31 02:58:29 +01:00
Simon Deziel
fe17753dd5 Invoke bash through /usr/bin/env 2017-01-30 20:47:23 -05:00
Simon Deziel
27a416511f The example hook script uses bash test regex
Use bash explictly as done for the main script.

Signed-off-by: Simon Deziel <simon.deziel@gmail.com>
2017-01-30 20:36:19 -05:00
Lukas Schauer
e5452922e9 whitelist handlers for example hook (fixes #348) 2017-01-30 03:53:59 +01:00
Lukas Schauer
ee65261ea8 exclude root certificate from certificate chain 2017-01-30 03:45:21 +01:00
Lukas Schauer
03f0dc18b9 use awk instead of grep for reading trailing line after match (fixes #255) 2017-01-30 00:12:01 +01:00
Lukas Schauer
197ca8e82c Revert "curl: use custom user agent"
This reverts commit a5fde931f8.
2017-01-29 22:56:42 +01:00
Lukas Schauer
a5fde931f8 curl: use custom user agent 2017-01-29 22:42:02 +01:00
Lukas Schauer
6a32f20e00 ask user to read and accept license, added register-command, fullchain.pem is now actually the full chain 2017-01-29 22:06:53 +01:00
Lukas Schauer
b2376ed437 updated changelog 2017-01-29 16:06:24 +01:00
Lukas Schauer
57197306d7 export some config variables for use within hook scripts 2017-01-29 16:03:59 +01:00
Lukas Schauer
298a7e9aaf added exit_hook hook 2017-01-29 15:48:03 +01:00
Lukas Schauer
0f299623be updated changelog 2017-01-29 14:33:41 +01:00
Lukas Schauer
cfc00c42a2 check for diff using command 2017-01-29 14:04:43 +01:00
Lukas Schauer
5d92c3b352 use awk instead of tr for changing case 2017-01-29 14:00:45 +01:00
Lukas Schauer
e2e2c362d2 check for mktemp using command (busybox compatibility) 2017-01-29 13:58:42 +01:00
Lukas Schauer
b36d638a91 missing filename in config path 2017-01-06 00:40:03 +01:00
Lukas Schauer
84274f7436 moved and rewritten config section of readme 2017-01-06 00:38:13 +01:00
Maximilian Weber
c2a735f99e Update Readme.md (#301)
Add config section
2017-01-06 00:32:53 +01:00
Lukas Schauer
de36a63fe6 removed note about project renaming 2017-01-06 00:31:03 +01:00
Nicolás Kovac
81eecedc5a Fix: bash to sh notation on invalid_challenge() hook 2017-01-06 00:18:57 +01:00
nkovacne
404dc3fe0f Adding the request_failure hook (#326) 2017-01-06 00:15:51 +01:00
Lukas Schauer
3c1d2673d1 trying to fix #320 again 2016-12-22 22:56:50 +01:00
Lukas Schauer
69eea9527f removed some spaces 2016-12-22 10:00:05 +01:00
Aaron Roydhouse
636fa1a559 Test for case when challenge_altnames is empty (#321)
When all names in a cert have already been validated, the challenge_altnames array will be empty, causes an error in later code. This patch adds a test to handle that case.
2016-12-22 09:58:48 +01:00
Lukas Schauer
7f30826a6d removed temporary wrapper script 2016-12-19 01:21:43 +01:00
nkovacne
318cf2011d Adding the invalid_challenge hook (#278) 2016-12-18 20:31:08 +01:00
Eihrister
8456855e48 Add new parameter --lock-suffix. (#287)
* Add new parameter --lock-suffix.

This enables automation systems to run multiple instances of dehydrated
while still maintaining a locking facility. This is necessary for
projects like https://github.com/GUI/lua-resty-auto-ssl.

With this feature, one could run the script multiple times, while still
having a form of locking:

./dehydrated --lock-suffix test1.example.com -d test1.example.com
./dehydrated --lock-suffix test2.example.com -d test2.example.com
./dehydrated --lock-suffix test3.example.com -d test3.example.com
./dehydrated --lock-suffix test4.example.com -d test4.example.com
./dehydrated --lock-suffix test5.example.com -d test5.example.com

When starts the script with the same locking suffix, the scripts exits
like it normally would when locked. This will give you the benefits of
using --no-lock, without the disadvantages of it.

* Fixed unbound variable error for new PARAM_LOCKFILE_SUFFIX.
2016-12-18 20:25:49 +01:00
sth
9729751d93 Skip challenge for already validated domains (#293)
* skip challenge for already validated domains

* only call deploy_challenge hook if there is work

No need to call the hook if there are no challenges to deploy
2016-12-18 20:25:05 +01:00
CJ Johnson
6086983c02 Minor Grammar Stuff (#319)
* typo

s/pullrequest/pull request/

* proper noun & possessive apostrophe

s/letsencrypts/Let's Encrypt's/

* proper noun / spacing /

s/curl/cURL/
s/letsencryprt/Let's Encrypt/
2016-12-18 20:19:19 +01:00
Lukas Schauer
607c89cae2 updated ngrok url in test.sh 2016-10-17 22:46:28 +02:00
crza
a13e410363 Basic implementation for private key rollover (#294)
* initial commit for PRIVATE_KEY_ROLLOVER

* fix if syntax

* rolloverkey without  timestamps

* update example config: PRIVATE_KEY_ROLLOVER

* rolloverkey creation logic updated

* updated tests. untested.

* added cleanup for rolloverkeys: if disabled, delete privkey.roll.pem
2016-10-17 22:40:03 +02:00
Elan Ruusamäe
d62a5eeb1e fix lighttpd syntax (#299)
at least in 1.4 the syntax is `server.modules` and it's an array.
and it's always good idea to keep trailing comma to avoid syntax errors when adding new entries.
2016-10-17 22:11:34 +02:00
Elan Ruusamäe
83fa54cc38 examples/hook: no bashism (#300)
use plain shell syntax,
also protect against if arguments contain spaces.
2016-10-17 22:08:14 +02:00
Lukas Schauer
a316a094df Revert "strip leading null-bytes from urlbase64" (see #282)
This reverts commit 319852dc8c.
2016-09-21 13:20:51 +02:00
Lukas Schauer
319852dc8c strip leading null-bytes from urlbase64 (fixes #275) 2016-09-17 13:15:15 +02:00
Lukas Schauer
7eca8aec5a use temporary file for DER->PEM conversion (fixes #279) 2016-09-17 13:02:48 +02:00
Lukas Schauer
0c1e958d19 added temporary wrapper script for compatibility with old config locations and symlinks, will be removed in a few weeks 2016-09-17 12:46:46 +02:00
leonklingele
caeed7d5e3 Make example hook.sh file executable. (#253)
So one can easily `cp docs/examples/hook.sh .`
2016-09-14 15:11:25 +02:00
Lukas Schauer
047ba60d0d updated date in license file
really early for this change
2016-09-13 20:39:10 +02:00
Lukas Schauer
b95b15cad6 update readme with explanation why this project was renamed 2016-09-13 20:15:30 +02:00
Lukas Schauer
ec49a4433b fixed a few things after project rename 2016-09-13 20:00:43 +02:00
Lukas Schauer
0ae567aced Update CHANGELOG 2016-09-13 19:55:34 +02:00
Lukas Schauer
6c12f97b9f readme fix 2016-09-13 19:53:43 +02:00
Lukas Schauer
64e35463cb renamed project to dehydrated and main script to dehydrated.sh 2016-09-13 19:48:27 +02:00
Lukas Schauer
992beecbdb release v0.3.0 2016-09-07 21:20:28 +02:00
leonklingele
6192b33ac2 ECDSA is supported since February 10, 2016 (#260)
Let's Encrypt will however sign all ECDSA certs with an RSA intermediate certificate.
https://letsencrypt.org/upcoming-features/
2016-08-22 15:40:37 +02:00
leonklingele
47602dea04 Update staging doc: Let's Encrypt is no longer in beta. (#259) 2016-08-22 15:38:29 +02:00
leonklingele
3a66a7f8d2 Fix default license help text in example config (#254) 2016-08-06 15:32:53 +02:00
Lukas Schauer
624ce4436a updated readme 2016-08-04 00:11:08 +02:00
Lukas Schauer
d81eb58536 Only print full chain on signcsr command if --full-chain/-fc is set (related to #150) 2016-08-04 00:11:02 +02:00
BtbN
117d5d6228 Echo newline after error details 2016-08-03 23:50:59 +02:00
BtbN
34565c193d Add support for --keep-going in cron mode
Fixes #154
2016-08-03 23:50:59 +02:00
Lukas Schauer
ca0249c46c Update staging.md 2016-08-03 23:47:57 +02:00
Lukas Schauer
afabfff06e updated url to letsencrypt license agreement 2016-08-01 20:35:46 +02:00
Lukas Schauer
bd9cc5b0c4 Added option to run letsencrypt.sh without locks
This should only be used when letsencrypt.sh is under control by a
different script which makes sure that no two processes are touching the
same files.
2016-07-21 12:34:40 +02:00
Lukas Schauer
194464b04b Default WELLKNOWN location is now /var/www/letsencrypt
With this change private and public files are now separated by default.
2016-07-20 17:04:25 +02:00
chkhanu
364bcccf74 Added option to select IP version of name to address resolution (#231) 2016-07-20 16:49:04 +02:00
Martin Schut
44aca90cd7 Introduce per cert configuration directory DOMAINS_D (#242) 2016-07-13 13:23:15 +02:00
Lukas Schauer
2042b177c7 modified lighttpd example config 2016-06-10 23:41:59 +02:00
Domen Puncer Kugler
ae98ff6767 Add Lighttpd example to wellknown.md (#224) 2016-06-08 01:49:50 +02:00
Lukas Schauer
194d543fa1 removed ACCOUNT_KEY and ACCOUNT_KEY_JSON from example config 2016-06-04 11:12:30 +02:00
Christian Tacke
df292dece2 Include method and URL in curl error (#214)
In case curl gives an error, it's helpful to know the URL
being tried and the method. In the GET case, one can easily
retry in the shell and debug this.
2016-06-04 04:15:16 +02:00
Lukas Schauer
f4138efab9 changed dependency check for diff to be compatible with openbsd diff (fixes #219) 2016-06-04 04:12:21 +02:00
Lukas Schauer
034ec30c7d added multi-account support (fixes #92, #163) 2016-06-04 04:01:24 +02:00
Lukas Schauer
ec48906992 initial support for configuration on per-certificate base (#105) 2016-05-26 17:11:42 +02:00
Lukas Schauer
64864f5fa6 added signcsr change to CHANGELOG 2016-05-26 16:04:01 +02:00
Lukas Schauer
620c7eb23e output CA cert for signcsr command (fixes #150) 2016-05-26 15:44:59 +02:00
Lukas Schauer
4e8f944b72 added CERTDIR change to CHANGELOG 2016-05-26 15:06:07 +02:00
Lukas Schauer
8e77ba5e02 added option to set csr-flag indicating ocsp stapling to be mandatory 2016-05-26 15:02:23 +02:00
Alex Macleod
0d8b928923 Check that the detected config is a file (#211) 2016-05-26 15:02:15 +02:00
Alex Macleod
785ffa5539 Make certificate output location configurable (#210) 2016-05-26 14:53:01 +02:00
Lukas Schauer
d5b285868e renamed "config.sh" to "config" 2016-05-22 22:34:28 +02:00
Daniel Beyer
722430039b Remove unneded shebang for config.sh.example 2016-05-22 22:25:14 +02:00
Lukas Schauer
41aae07343 Update CHANGELOG 2016-05-22 18:50:17 +02:00
Daniel Beyer
a3e5ed361b Make location of domains.txt configurable (#204)
This is implemented by defining ${DOMAINS_TXT} in config.sh. If not
set in config.sh, it defaults to the previously (hard-coded) location,
which is ${BASEDIR}/domains.txt.
2016-05-22 18:49:17 +02:00
28 changed files with 3678 additions and 1372 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: lukas2511
custom: ["https://paypal.me/lukas2511", "http://www.amazon.de/registry/wishlist/1TUCFJK35IO4Q"]

5
.gitignore vendored
View File

@@ -1,8 +1,9 @@
private_key.pem
private_key.json
domains.txt
config.sh
config
hook.sh
certs/*
archive/*
.acme-challenges/*
accounts/*
chains/*

View File

@@ -1,10 +0,0 @@
sudo: false
language: shell
cache:
directories:
- ngrok
script:
- export CI="true"
- ./test.sh

168
CHANGELOG
View File

@@ -1,7 +1,173 @@
# Change Log
This file contains a log of major changes in letsencrypt.sh
This file contains a log of major changes in dehydrated
## [x.x.x] - xxxx-xx-xx
## Added
- Added a configuration parameter to allow for timeouts during domain validation processing (`VALIDATION_TIMEOUT`, defaults to 0 = no timeout)
## Changed
- Only validate existance of wellknown directory or hook script when actually needed
- Also allow setting `KEEP_GOING` in config file instead of relying on cli arguments
- Allow skipping over OCSP stapling errors, indicate that some CAs no longer support OCSP
- Throw error with information about OCSP deprecation if certificate doesn't indicate OCSP support
## [0.7.2] - 2025-05-18
## Added
- Implemented support for certificate profile selection
- Added a configuration parameter to allow for timeouts during order processing (`ORDER_TIMEOUT`, defaults to 0 = no timeout)
- Allowed for automatic deletion of old files (`AUTO_CLEANUP_DELETE`, disabled by default)
- Added CA presets for Google Trust Services (prod: google, test: google-test)
## Changed
- Renew certificates with 32 days remaining (instead of 30) to avoid issues with monthly cronjobs (`RENEW_DAYS=32`)
## Fixed
- Changed behaviour of `openssl req` stdin handling to fix compatibility with OpenSSL version 3.2+
## [0.7.1] - 2022-10-31
## Changed
- `--force` no longer forces domain name revalidation by default, a new argument `--force-validation` has been added for that
- Added support for EC secp521r1 algorithm (works with e.g. zerossl)
- `EC PARAMETERS` are no longer written to privkey.pem (didn't seem necessary and was causing issues with various software)
## Fixed
- Requests resulting in `badNonce` errors are now automatically retried (fixes operation with LE staging servers)
- Deprecated `egrep` usage has been removed
## Added
- Implemented EC for account keys
- Domain list now also read from domains.txt.d subdirectory (behaviour might change, see docs)
- Implemented RFC 8738 (validating/signing certificates for IP addresses instead of domain names) support (this will not work with most public CAs, if any!)
## [0.7.0] - 2020-12-10
## Added
- Support for external account bindings
- Special support for ZeroSSL
- Support presets for some CAs instead of requiring URLs
- Allow requesting preferred chain (`--preferred-chain`)
- Added method to show CAs current terms of service (`--display-terms`)
- Allow setting path to domains.txt using cli arguments (`--domains-txt`)
- Added new cli command `--cleanupdelete` which deletes old files instead of archiving them
## Fixed
- No more silent failures on broken hook-scripts
- Better error-handling with KEEP_GOING enabled
- Check actual order status instead of assuming it's valid
- Don't include keyAuthorization in challenge validation (RFC compliance)
## Changed
- Using EC secp384r1 as default certificate type
- Use JSON.sh to parse JSON
- Use account URL instead of account ID (RFC compliance)
- Dehydrated now has a new home: https://github.com/dehydrated-io/dehydrated
- Added `OCSP_FETCH` and `OCSP_DAYS` to per-certificate configurable options
- Cleanup now also removes dangling symlinks
## [0.6.5] - 2019-06-26
## Fixed
- Fixed broken APIv1 compatibility from last update
## [0.6.4] - 2019-06-25
## Changed
- Fetch account ID from Location header instead of account json
## [0.6.3] - 2019-06-25
## Changed
- OCSP refresh interval is now configurable
- Implemented POST-as-GET
- Call exit_hook on errors (with error-message as first parameter)
## Added
- Initial support for tls-alpn-01 validation
- New hook: sync_cert (for syncing certificate files to disk, see example hook description)
## Fixes
- Fetch account information after registration to avoid missing account id
## [0.6.2] - 2018-04-25
## Added
- New deploy_ocsp hook
- Allow account registration with custom key
## Changed
- Don't walk certificate chain for ACMEv2 (certificate contains chain by default)
- Improved documentation on wildcards
## Fixes
- Added workaround for compatibility with filesystem ACLs
- Close unwanted external file-descriptors
- Fixed JSON parsing on force-renewal
- Fixed cleanup of challenge files/dns-entries on validation errors
- A few more minor fixes
## [0.6.1] - 2018-03-13
## Changed
- Use new ACME v2 endpoint by default
## [0.6.0] - 2018-03-11
## Changed
- Challenge validation loop has been modified to loop over authorization identifiers instead of altnames (ACMEv2 + wildcard support)
- Removed LICENSE parameter from config (terms of service is now acquired directly from the CA directory)
## Added
- Support for ACME v02 (including wildcard certificates!)
- New hook: generate_csr (see example hook script for more information)
- Calling random hook on startup to make it clear to hook script authors that unknown hooks should just be ignored...
## [0.5.0] - 2018-01-13
## Changed
- Certificate chain is now cached (CHAINCACHE)
- OpenSSL binary path is now configurable (OPENSSL)
- Cleanup now also moves revoked certificates
## Added
- New feature for updating contact information (--account)
- Allow automatic cleanup on exit (AUTO_CLEANUP)
- Initial support for fetching OCSP status to be used for OCSP stapling (OCSP_FETCH)
- Certificates can now have aliases to create multiple certificates with identical set of domains (see --alias and domains.txt documentation)
- Allow dehydrated to run as specified user (/group)
## [0.4.0] - 2017-02-05
## Changed
- dehydrated now asks you to read and accept the CAs terms of service before creating an account
- Skip challenges for already validated domains
- Removed need for some special commands (BusyBox compatibility)
- Exported a few more variables for use in hook-scripts
- fullchain.pem now actually contains the full chain instead of just the certificate with an intermediate cert
## Added
- Added private-key rollover functionality
- Added `--lock-suffix` option for allowing parallel execution
- Added `invalid_challenge` hook
- Added `request_failure` hook
- Added `exit_hook` hook
- Added standalone `register` command
## [0.3.1] - 2016-09-13
## Changed
- Renamed project to `dehydrated`.
- Default WELLKNOWN location is now `/var/www/dehydrated`
- Config location is renamed to `dehydrated` (e.g. `/etc/dehydrated`)
## [0.3.0] - 2016-09-07
## Changed
- Config is now named `config` instead of `config.sh`!
- Location of domains.txt is now configurable via DOMAINS_TXT config variable
- Location of certs directory is now configurable via CERTDIR config variable
- signcsr command now also outputs chain certificate if --full-chain/-fc is set
- Location of account-key(s) changed
- Default WELLKNOWN location is now `/var/www/letsencrypt`
- New version of Let's Encrypt Subscriber Agreement
## Added
- Added option to add CSR-flag indicating OCSP stapling to be mandatory
- Initial support for configuration on per-certificate base
- Support for per-CA account keys and custom config for output cert directory, license, etc.
- Added option to select IP version of name to address resolution
- Added option to run letsencrypt.sh without locks
## Fixed
- letsencrypt.sh no longer stores account keys from invalid registrations
## [0.2.0] - 2016-05-22
### Changed

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Lukas Schauer
Copyright (c) 2015-2021 Lukas Schauer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,51 +1,97 @@
# letsencrypt.sh [![Build Status](https://travis-ci.org/lukas2511/letsencrypt.sh.svg?branch=master)](https://travis-ci.org/lukas2511/letsencrypt.sh)
# dehydrated [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=23P9DSJBTY7C8)
This is a client for signing certificates with an ACME-server (currently only provided by letsencrypt) implemented as a relatively simple bash-script.
![](docs/logo.png)
Dehydrated is a client for signing certificates with an ACME-server (e.g. Let's Encrypt) implemented as a relatively simple (zsh-compatible) bash-script.
This client supports both ACME v1 and the new ACME v2 including support for wildcard certificates!
It uses the `openssl` utility for everything related to actually handling keys and certificates, so you need to have that installed.
Other dependencies are: curl, sed, grep, mktemp (all found on almost any system, curl being the only exception)
Other dependencies are: cURL, sed, grep, awk, mktemp (all found pre-installed on almost any system, cURL being the only exception).
Current features:
- Signing of a list of domains
- Signing of a CSR
- Renewal if a certificate is about to expire or SAN (subdomains) changed
- Signing of a list of domains (including wildcard domains!)
- Signing of a custom CSR (either standalone or completely automated using hooks!)
- Renewal if a certificate is about to expire or defined set of domains changed
- Certificate revocation
- and lots more..
Please keep in mind that this software and even the acme-protocol are relatively young and may still have some unresolved issues.
Feel free to report any issues you find with this script or contribute by submitting a pullrequest.
Please keep in mind that this software, the ACME-protocol and all supported CA servers out there are relatively young and there might be a few issues. Feel free to report any issues you find with this script or contribute by submitting a pull request,
but please check for duplicates first (feel free to comment on those to get things rolling).
### Getting started
## Getting started
For getting started I recommend taking a look at [docs/domains_txt.md](docs/domains_txt.md), [docs/wellknown.md](docs/wellknown.md) and the [Usage](#usage) section on this page (you'll probably only need the `-c` option).
Generally you want to set up your WELLKNOWN path first, and then fill in domains.txt.
**Please note that you should use the staging URL when experimenting with this script to not hit letsencrypts rate limits.** See [docs/staging.md](docs/staging.md).
**Please note that you should use the staging URL when experimenting with this script to not hit Let's Encrypt's rate limits.** See [docs/staging.md](docs/staging.md).
If you have any problems take a look at our [Troubleshooting](docs/troubleshooting.md) guide.
## Config
dehydrated is looking for a config file in a few different places, it will use the first one it can find in this order:
- `/etc/dehydrated/config`
- `/usr/local/etc/dehydrated/config`
- The current working directory of your shell
- The directory from which dehydrated was run
Have a look at [docs/examples/config](docs/examples/config) to get started, copy it to e.g. `/etc/dehydrated/config`
and edit it to fit your needs.
## Usage:
```text
Usage: ./letsencrypt.sh [-h] [command [argument]] [parameter [argument]] [parameter [argument]] ...
Usage: ./dehydrated [-h] [command [argument]] [parameter [argument]] [parameter [argument]] ...
Default command: help
Commands:
--cron (-c) Sign/renew non-existant/changed/expiring certificates.
--version (-v) Print version information
--display-terms Display current terms of service
--register Register account key
--account Update account contact information
--cron (-c) Sign/renew non-existent/changed/expiring certificates.
--signcsr (-s) path/to/csr.pem Sign a given CSR, output CRT on stdout (advanced usage)
--revoke (-r) path/to/cert.pem Revoke specified certificate
--deactivate Deactivate account
--cleanup (-gc) Move unused certificate files to archive directory
--cleanup-delete (-gcd) Deletes (!) unused certificate files
--help (-h) Show help text
--env (-e) Output configuration variables for use in other scripts
Parameters:
--accept-terms Accept CAs terms of service
--full-chain (-fc) Print full chain when using --signcsr
--ipv4 (-4) Resolve names to IPv4 addresses only
--ipv6 (-6) Resolve names to IPv6 addresses only
--domain (-d) domain.tld Use specified domain name(s) instead of domains.txt entry (one certificate!)
--force (-x) Force renew of certificate even if it is longer valid than value in RENEW_DAYS
--ca url/preset Use specified CA URL or preset
--alias certalias Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified)
--keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode
--force (-x) Force certificate renewal even if it is not due to expire within RENEW_DAYS
--force-validation Force revalidation of domain names (used in combination with --force)
--no-lock (-n) Don't use lockfile (potentially dangerous!)
--lock-suffix example.com Suffix lockfile name with a string (useful for with -d)
--ocsp Sets option in CSR indicating OCSP stapling to be mandatory
--privkey (-p) path/to/key.pem Use specified private key instead of account key (useful for revocation)
--config (-f) path/to/config.sh Use specified config file
--domains-txt path/to/domains.txt Use specified domains.txt instead of default/configured one
--config (-f) path/to/config Use specified config file
--hook (-k) path/to/hook.sh Use specified script for hooks
--challenge (-t) http-01|dns-01 Which challenge should be used? Currently http-01 and dns-01 are supported
--preferred-chain issuer-cn Use alternative certificate chain identified by issuer CN
--out (-o) certs/directory Output certificates into the specified directory
--alpn alpn-certs/directory Output alpn verification certificates into the specified directory
--challenge (-t) http-01|dns-01|tls-alpn-01 Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported
--algo (-a) rsa|prime256v1|secp384r1 Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
--acme-profile profile_name Use specified ACME profile
--order-timeout seconds Amount of seconds to wait for processing of order until erroring out
--validation-timeout seconds Amount of seconds to wait for processing of domain validations until erroring out
```
## Chat
Dehydrated has an official IRC-channel `#dehydrated` on libera.chat that can be used for general discussion and suggestions.
The channel can also be accessed with Matrix using the official libera.chat bridge at `#dehydrated:libera.chat`.

2539
dehydrated Executable file

File diff suppressed because it is too large Load Diff

19
docs/acme-v1.md Normal file
View File

@@ -0,0 +1,19 @@
## (Future) Removal of API version 1
The ACME API version 1 was never really standardized and was only supported by Let's Encrypt. Even though the protocol specification was public,
it wasn't really friendly to be integrated into existing CA systems so initial adoption was basically non-existant.
ACME version 2 is being designed to overcome these issues by becoming an official IETF standard and supporting a more traditional approach of account
and order management in the backend, making it friendlier to integrate into existing systems centered around those. It has since become a semi-stable IETF
standard draft which only ever got two breaking changes, Content-Type enforcement and `POST-as-GET`, the latter being announced in October 2018 to be enforced
by November 2019. See https://datatracker.ietf.org/wg/acme/documents/ for a better insight into the draft and its changes.
Next to backend changes that many users won't really care about ACME v2 has all of the features ACME v1 had, but also some additional new features like
e.g. support for [wildcard certificates](domains_txt.md#wildcards).
Since ACME v2 is basically to be considered stable and ACME v1 has no real benefits over v2, there doesn't seem to be much of a reason to keep the old
protocol around, but since there actually are a few Certificate Authorities and resellers that implemented the v1 protocol and didn't yet make the change
to v2, so dehydrated still supports the old protocol for now.
Please keep in mind that support for the old ACME protocol version 1 might get removed at any point of bigger inconvenience, e.g. on code changes that
would require a lot of work or ugly workarounds to keep both versions supported.

View File

@@ -2,9 +2,18 @@
This script also supports the new `dns-01`-type verification. This type of verification requires you to be able to create a specific `TXT` DNS record for each hostname included in the certificate.
You need a hook script that deploys the challenge to your DNS server!
You need a hook script that deploys the challenge to your DNS server.
The hook script (indicated in the config.sh file or the --hook/-k command line argument) gets four arguments: an operation name (clean_challenge, deploy_challenge, or deploy_cert) and some operands for that. For deploy_challenge $2 is the domain name for which the certificate is required, $3 is a "challenge token" (which is not needed for dns-01), and $4 is a token which needs to be inserted in a TXT record for the domain.
The hook script (indicated in the config file or the `--hook/-k` command line argument) gets four arguments:
$1 an operation name (`clean_challenge`, `deploy_challenge`, `deploy_cert`, `invalid_challenge` or `request_failure`) and some operands for that.
For `deploy_challenge`
$2 is the domain name for which the certificate is required,
$3 is a "challenge token" (which is not needed for dns-01), and
$4 is a token which needs to be inserted in a TXT record for the domain.
Typically, you will need to split the subdomain name in two, the subdomain name and the domain name separately. For example, for "my.example.com", you'll need "my" and "example.com" separately. You then have to prefix "_acme-challenge." before the subdomain name, as in "_acme-challenge.my" and set a TXT record for that on the domain (e.g. "example.com") which has the value supplied in $4
@@ -13,10 +22,10 @@ _acme-challenge IN TXT $4
_acme-challenge.my IN TXT $4
```
That could be done manually (as most providers don't have a DNS API), by having your hook script echo $1, $2 and $4 and then wait (read -s -r -e < /dev/tty) - give it a little time to get into their DNS system. Usually providers give you a boxes to put "_acme-challenge.my" and the token value in, and a dropdown to choose the record type, TXT.
That could be done manually (as most providers don't have a DNS API), by having your hook script echo $1, $2 and $4 and then wait (`read -s -r -e < /dev/tty`) - give it a little time to get into their DNS system. Usually providers give you a boxes to put "_acme-challenge.my" and the token value in, and a dropdown to choose the record type, TXT.
Or when you do have a DNS API, pass the details accordingly to achieve the same thing.
You can delete the TXT record when called with operation clean_challenge, when $2 is also the domain name.
You can delete the TXT record when called with operation `clean_challenge`, when $2 is also the domain name.
Here are some examples: [Examples for DNS-01 hooks](https://github.com/lukas2511/letsencrypt.sh/wiki/Examples-for-DNS-01-hooks)
Here are some examples: [Examples for DNS-01 hooks](https://github.com/dehydrated-io/dehydrated/wiki)

View File

@@ -1,13 +1,107 @@
### domains.txt
## domains.txt
letsencrypt.sh uses the file `domains.txt` as configuration for which certificates should be requested.
dehydrated uses the file `domains.txt` as configuration for which certificates
should be requested.
The file should have the following format:
```text
example.org
example.com www.example.com
example.net www.example.net wiki.example.net
```
This states that there should be two certificates `example.com` and `example.net`,
with the other domains in the corresponding line being their alternative names.
This states that there are the following certificates:
* `example.org` without any *alternative names*
* `example.com` with an *alternative name* of `www.example.com`
* `example.net` with the *alternative names*: `www.example.net` and
`wiki.example.net`
### Aliases
You can define an *alias* for your certificate which will (instead of the
primary domain) be used as the directory name under your `CERTDIR` and for a
per-certificate lookup. This is done using the `>` character. This allows
multiple certificates with identical sets of domains but different
configuration to exist.
Here is an example of using an *alias* called `certalias` for creating the
certificate for `example.net` with *alternative names* `www.example.net` and
`wiki.example.net`. The certificate will be stored in the directory `certalias`
under your `CERTDIR`.
```text
example.net www.example.net wiki.example.net > certalias
```
This allows to set per certificates options. The options you can change are
explained in [Per Certificate Config](per-certificate-config.md).
If you want to create different certificate types for the same domain
you can use:
```text
*.service.example.org service.example.org > star_service_example_org_rsa
*.service.example.org service.example.org > star_service_example_org_ecdsa
```
Then add a config file `certs/star_service_example_org_rsa/config` with
the value
```
KEY_ALGO="rsa"
```
or respectively
```
KEY_ALGO="ecdsa"
```
### Wildcards
Support for wildcards was added by the ACME v2 protocol.
Certificates with a wildcard domain as the first (or only) name require an
*alias* to be set. *Aliases* can't start with `*.`.
For example to create the wildcard for `*.service.example.com` your
`domains.txt` could use the *alias* method like this:
```text
*.service.example.com > star_service_example_com
```
This creates a wildcard certificate for only `*.service.example.com` and will
store it in the directory `star_service_example_com` under your `CERTDIR`. As a
note this certificate will **NOT** be valid for `service.example.com` but only
for `*.service.example.com`. So it would, for example, be valid for
`foo.service.example.com`.
Another way to create it is using *alternative names*. For example your
`domains.txt` could do this:
```text
service.example.com *.service.example.com
eggs.example.com *.ham.example.com
```
This creates two certificates one for `service.example.com` with an
*alternative name* of `*.service.example.com` and a second certificate for
`eggs.example.com` with an *alternative name* of `*.ham.example.com`.
**Note:** The first certificate is valid for both `service.example.com` and for
`*.service.example.com` which can be a useful way to create wildcard
certificates.
### Drop-in directory
If a directory named `domains.txt.d` exists in the same location as
`domains.txt`, the contents of `*.txt` files in that directory are appended to
the list of domains, in alphabetical order of the filenames. This is useful for
automation, as it doesn't require editing an existing file to add new domains.
Warning: Behaviour of this might change as the naming between `domains.txt.d`
and the `DOMAINS_D` config variable (which is used for per-certificate
configuration) is a bit confusing.

View File

@@ -1,5 +1,4 @@
### Elliptic Curve Cryptography (ECC)
This script also supports certificates with Elliptic Curve public keys!
Be aware that at the moment this is not available on the production servers from letsencrypt.
Please read https://community.letsencrypt.org/t/ecdsa-testing-on-staging/8809/ for the current state of ECC support.
Simply set the `KEY_ALGO` variable in one of the config files.

144
docs/examples/config Normal file
View File

@@ -0,0 +1,144 @@
########################################################
# This is the main config file for dehydrated #
# #
# This file is looked for in the following locations: #
# $SCRIPTDIR/config (next to this script) #
# /usr/local/etc/dehydrated/config #
# /etc/dehydrated/config #
# ${PWD}/config (in current working-directory) #
# #
# Default values of this config are in comments #
########################################################
# Which user should dehydrated run as? This will be implicitly enforced when running as root
#DEHYDRATED_USER=
# Which group should dehydrated run as? This will be implicitly enforced when running as root
#DEHYDRATED_GROUP=
# Resolve names to addresses of IP version only. (curl)
# supported values: 4, 6
# default: <unset>
#IP_VERSION=
# URL to certificate authority or internal preset
# Presets: letsencrypt, letsencrypt-test, zerossl, buypass, buypass-test, google, google-test
# default: letsencrypt
#CA="letsencrypt"
# Path to old certificate authority
# Set this value to your old CA value when upgrading from ACMEv1 to ACMEv2 under a different endpoint.
# If dehydrated detects an account-key for the old CA it will automatically reuse that key
# instead of registering a new one.
# default: https://acme-v01.api.letsencrypt.org/directory
#OLDCA="https://acme-v01.api.letsencrypt.org/directory"
# Which challenge should be used? Currently http-01, dns-01 and tls-alpn-01 are supported
#CHALLENGETYPE="http-01"
# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# default: <unset>
#CONFIG_D=
# Directory for per-domain configuration files.
# If not set, per-domain configurations are sourced from each certificates output directory.
# default: <unset>
#DOMAINS_D=
# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
#BASEDIR=$SCRIPTDIR
# File containing the list of domains to request certificates for (default: $BASEDIR/domains.txt)
#DOMAINS_TXT="${BASEDIR}/domains.txt"
# Output directory for generated certificates
#CERTDIR="${BASEDIR}/certs"
# Output directory for alpn verification certificates
#ALPNCERTDIR="${BASEDIR}/alpn-certs"
# Directory for account keys and registration information
#ACCOUNTDIR="${BASEDIR}/accounts"
# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/dehydrated)
#WELLKNOWN="/var/www/dehydrated"
# Default keysize for private keys (default: 4096)
#KEYSIZE="4096"
# Path to openssl config file (default: <unset> - tries to figure out system default)
#OPENSSL_CNF=
# Path to OpenSSL binary (default: "openssl")
#OPENSSL="openssl"
# Extra options passed to the curl binary (default: <unset>)
#CURL_OPTS=
# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: <unset>
#HOOK=
# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
#HOOK_CHAIN="no"
# Minimum days before expiration to automatically renew certificate (default: 32)
#RENEW_DAYS="32"
# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
#PRIVATE_KEY_RENEW="yes"
# Create an extra private key for rollover (default: no)
#PRIVATE_KEY_ROLLOVER="no"
# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
#KEY_ALGO=secp384r1
# E-mail to use during the registration (default: <unset>)
#CONTACT_EMAIL=
# Lockfile location, to prevent concurrent access (default: $BASEDIR/lock)
#LOCKFILE="${BASEDIR}/lock"
# Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no)
#OCSP_MUST_STAPLE="no"
# Fetch OCSP responses (default: no)
#OCSP_FETCH="no"
# OCSP refresh interval (default: 5 days)
#OCSP_DAYS=5
# Issuer chain cache directory (default: $BASEDIR/chains)
#CHAINCACHE="${BASEDIR}/chains"
# Automatic cleanup (default: no)
#AUTO_CLEANUP="no"
# Delete files during automatic cleanup instead of moving to archive (default: no)
#AUTO_CLEANUP_DELETE="no"
# ACME API version (default: auto)
#API=auto
# Preferred issuer chain (default: <unset> -> uses default chain)
#PREFERRED_CHAIN=
# Request certificate with specific profile (default: <unset>)
#ACME_PROFILE=
# Amount of seconds to wait for processing of order until erroring out (default: 0 => no timeout)
#ORDER_TIMEOUT=0
# Skip over errors during certificate orders and updating of OCSP stapling information (default: no)
#KEEP_GOING=no

View File

@@ -1,76 +0,0 @@
#!/usr/bin/env bash
########################################################
# This is the main config file for letsencrypt.sh #
# #
# This file is looked for in the following locations: #
# $SCRIPTDIR/config.sh (next to this script) #
# /usr/local/etc/letsencrypt.sh/config.sh #
# /etc/letsencrypt.sh/config.sh #
# ${PWD}/config.sh (in current working-directory) #
# #
# Default values of this config are in comments #
########################################################
# Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory)
#CA="https://acme-v01.api.letsencrypt.org/directory"
# Path to license agreement (default: https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf)
#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
# Which challenge should be used? Currently http-01 and dns-01 are supported
#CHALLENGETYPE="http-01"
# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# default: <unset>
#CONFIG_D=
# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
#BASEDIR=$SCRIPTDIR
# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: $BASEDIR/.acme-challenges)
#WELLKNOWN="${BASEDIR}/.acme-challenges"
# Location of private account key (default: $BASEDIR/private_key.pem)
#ACCOUNT_KEY="${BASEDIR}/private_key.pem"
# Location of private account registration information (default: $BASEDIR/private_key.json)
#ACCOUNT_KEY_JSON="${BASEDIR}/private_key.json"
# Default keysize for private keys (default: 4096)
#KEYSIZE="4096"
# Path to openssl config file (default: <unset> - tries to figure out system default)
#OPENSSL_CNF=
# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: <unset>
#HOOK=
# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
#HOOK_CHAIN="no"
# Minimum days before expiration to automatically renew certificate (default: 30)
#RENEW_DAYS="30"
# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
#PRIVATE_KEY_RENEW="yes"
# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
#KEY_ALGO=rsa
# E-mail to use during the registration (default: <unset>)
#CONTACT_EMAIL=
# Lockfile location, to prevent concurrent access (default: $BASEDIR/lock)
#LOCKFILE="${BASEDIR}/lock"

39
docs/examples/domains.txt Normal file
View File

@@ -0,0 +1,39 @@
# Create certificate for 'example.org' with an alternative name of
# 'www.example.org'. It will be stored in the directory ${CERT_DIR}/example.org
example.org www.example.org
# Create certificate for 'example.com' with alternative names of
# 'www.example.com' & 'wiki.example.com'. It will be stored in the directory
# ${CERT_DIR}/example.com
example.com www.example.com wiki.example.com
# Using the alias 'certalias' create certificate for 'example.net' with
# alternate name 'www.example.net' and store it in the directory
# ${CERTDIR}/certalias
example.net www.example.net > certalias
# Using the alias 'service_example_com' create a wildcard certificate for
# '*.service.example.com' and store it in the directory
# ${CERTDIR}/service_example_com
# NOTE: It is NOT a certificate for 'service.example.com'
*.service.example.com > service_example_com
# Using the alias 'star_service_example_org' create a wildcard certificate for
# '*.service.example.org' with an alternative name of `service.example.org'
# and store it in the directory ${CERTDIR}/star_service_example_org
# NOTE: It is a certificate for 'service.example.org'
*.service.example.org service.example.org > star_service_example_org
# Optionally you can also append the certificate algorithm here to create
# multiple certificate types for the same domain.
#
# This allows to set per certificates options. How to do this is
# explained in [domains.txt documentation](domains_txt.md).
#
*.service.example.org service.example.org > star_service_example_org_rsa
*.service.example.org service.example.org > star_service_example_org_ecdsa
# Create a certificate for 'service.example.net' with an alternative name of
# '*.service.example.net' (which is a wildcard domain) and store it in the
# directory ${CERTDIR}/service.example.net
service.example.net *.service.example.net

View File

@@ -1,2 +0,0 @@
example.org www.example.org
example.com www.example.com wiki.example.com

220
docs/examples/hook.sh Executable file
View File

@@ -0,0 +1,220 @@
#!/usr/bin/env bash
deploy_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
# This hook is called once for every domain that needs to be
# validated, including any alternative names you may have listed.
#
# Parameters:
# - DOMAIN
# The domain name (CN or subject alternative name) being
# validated.
# - TOKEN_FILENAME
# The name of the file containing the token to be served for HTTP
# validation. Should be served by your web server as
# /.well-known/acme-challenge/${TOKEN_FILENAME}.
# - TOKEN_VALUE
# The token value that needs to be served for validation. For DNS
# validation, this is what you want to put in the _acme-challenge
# TXT record. For HTTP validation it is the value that is expected
# be found in the $TOKEN_FILENAME file.
# Simple example: Use nsupdate with local named
# printf 'server 127.0.0.1\nupdate add _acme-challenge.%s 300 IN TXT "%s"\nsend\n' "${DOMAIN}" "${TOKEN_VALUE}" | nsupdate -k /var/run/named/session.key
}
clean_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
# This hook is called after attempting to validate each domain,
# whether or not validation was successful. Here you can delete
# files or DNS records that are no longer needed.
#
# The parameters are the same as for deploy_challenge.
# Simple example: Use nsupdate with local named
# printf 'server 127.0.0.1\nupdate delete _acme-challenge.%s TXT "%s"\nsend\n' "${DOMAIN}" "${TOKEN_VALUE}" | nsupdate -k /var/run/named/session.key
}
sync_cert() {
local KEYFILE="${1}" CERTFILE="${2}" FULLCHAINFILE="${3}" CHAINFILE="${4}" REQUESTFILE="${5}"
# This hook is called after the certificates have been created but before
# they are symlinked. This allows you to sync the files to disk to prevent
# creating a symlink to empty files on unexpected system crashes.
#
# This hook is not intended to be used for further processing of certificate
# files, see deploy_cert for that.
#
# Parameters:
# - KEYFILE
# The path of the file containing the private key.
# - CERTFILE
# The path of the file containing the signed certificate.
# - FULLCHAINFILE
# The path of the file containing the full certificate chain.
# - CHAINFILE
# The path of the file containing the intermediate certificate(s).
# - REQUESTFILE
# The path of the file containing the certificate signing request.
# Simple example: sync the files before symlinking them
# sync "${KEYFILE}" "${CERTFILE}" "${FULLCHAINFILE}" "${CHAINFILE}" "${REQUESTFILE}"
}
deploy_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
# This hook is called once for each certificate that has been
# produced. Here you might, for instance, copy your new certificates
# to service-specific locations and reload the service.
#
# Parameters:
# - DOMAIN
# The primary domain name, i.e. the certificate common
# name (CN).
# - KEYFILE
# The path of the file containing the private key.
# - CERTFILE
# The path of the file containing the signed certificate.
# - FULLCHAINFILE
# The path of the file containing the full certificate chain.
# - CHAINFILE
# The path of the file containing the intermediate certificate(s).
# - TIMESTAMP
# Timestamp when the specified certificate was created.
# Simple example: Copy file to nginx config
# cp "${KEYFILE}" "${FULLCHAINFILE}" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl
# systemctl reload nginx
}
deploy_ocsp() {
local DOMAIN="${1}" OCSPFILE="${2}" TIMESTAMP="${3}"
# This hook is called once for each updated ocsp stapling file that has
# been produced. Here you might, for instance, copy your new ocsp stapling
# files to service-specific locations and reload the service.
#
# Parameters:
# - DOMAIN
# The primary domain name, i.e. the certificate common
# name (CN).
# - OCSPFILE
# The path of the ocsp stapling file
# - TIMESTAMP
# Timestamp when the specified ocsp stapling file was created.
# Simple example: Copy file to nginx config
# cp "${OCSPFILE}" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl
# systemctl reload nginx
}
unchanged_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
# This hook is called once for each certificate that is still
# valid and therefore wasn't reissued.
#
# Parameters:
# - DOMAIN
# The primary domain name, i.e. the certificate common
# name (CN).
# - KEYFILE
# The path of the file containing the private key.
# - CERTFILE
# The path of the file containing the signed certificate.
# - FULLCHAINFILE
# The path of the file containing the full certificate chain.
# - CHAINFILE
# The path of the file containing the intermediate certificate(s).
}
invalid_challenge() {
local DOMAIN="${1}" RESPONSE="${2}"
# This hook is called if the challenge response has failed, so domain
# owners can be aware and act accordingly.
#
# Parameters:
# - DOMAIN
# The primary domain name, i.e. the certificate common
# name (CN).
# - RESPONSE
# The response that the verification server returned
# Simple example: Send mail to root
# printf "Subject: Validation of ${DOMAIN} failed!\n\nOh noez!" | sendmail root
}
request_failure() {
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" HEADERS="${4}"
# This hook is called when an HTTP request fails (e.g., when the ACME
# server is busy, returns an error, etc). It will be called upon any
# response code that does not start with '2'. Useful to alert admins
# about problems with requests.
#
# Parameters:
# - STATUSCODE
# The HTML status code that originated the error.
# - REASON
# The specified reason for the error.
# - REQTYPE
# The kind of request that was made (GET, POST...)
# - HEADERS
# HTTP headers returned by the CA
# Simple example: Send mail to root
# printf "Subject: HTTP request failed failed!\n\nA http request failed with status ${STATUSCODE}!" | sendmail root
}
generate_csr() {
local DOMAIN="${1}" CERTDIR="${2}" ALTNAMES="${3}"
# This hook is called before any certificate signing operation takes place.
# It can be used to generate or fetch a certificate signing request with external
# tools.
# The output should be just the certificate signing request formatted as PEM.
#
# Parameters:
# - DOMAIN
# The primary domain as specified in domains.txt. This does not need to
# match with the domains in the CSR, it's basically just the directory name.
# - CERTDIR
# Certificate output directory for this particular certificate. Can be used
# for storing additional files.
# - ALTNAMES
# All domain names for the current certificate as specified in domains.txt.
# Again, this doesn't need to match with the CSR, it's just there for convenience.
# Simple example: Look for pre-generated CSRs
# if [ -e "${CERTDIR}/pre-generated.csr" ]; then
# cat "${CERTDIR}/pre-generated.csr"
# fi
}
startup_hook() {
# This hook is called before the cron command to do some initial tasks
# (e.g. starting a webserver).
:
}
exit_hook() {
local ERROR="${1:-}"
# This hook is called at the end of the cron command and can be used to
# do some final (cleanup or other) tasks.
#
# Parameters:
# - ERROR
# Contains error message if dehydrated exits with error
}
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|sync_cert|deploy_cert|deploy_ocsp|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then
"$HANDLER" "$@"
fi

View File

@@ -1,77 +0,0 @@
#!/usr/bin/env bash
function deploy_challenge {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
# This hook is called once for every domain that needs to be
# validated, including any alternative names you may have listed.
#
# Parameters:
# - DOMAIN
# The domain name (CN or subject alternative name) being
# validated.
# - TOKEN_FILENAME
# The name of the file containing the token to be served for HTTP
# validation. Should be served by your web server as
# /.well-known/acme-challenge/${TOKEN_FILENAME}.
# - TOKEN_VALUE
# The token value that needs to be served for validation. For DNS
# validation, this is what you want to put in the _acme-challenge
# TXT record. For HTTP validation it is the value that is expected
# be found in the $TOKEN_FILENAME file.
}
function clean_challenge {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
# This hook is called after attempting to validate each domain,
# whether or not validation was successful. Here you can delete
# files or DNS records that are no longer needed.
#
# The parameters are the same as for deploy_challenge.
}
function deploy_cert {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
# This hook is called once for each certificate that has been
# produced. Here you might, for instance, copy your new certificates
# to service-specific locations and reload the service.
#
# Parameters:
# - DOMAIN
# The primary domain name, i.e. the certificate common
# name (CN).
# - KEYFILE
# The path of the file containing the private key.
# - CERTFILE
# The path of the file containing the signed certificate.
# - FULLCHAINFILE
# The path of the file containing the full certificate chain.
# - CHAINFILE
# The path of the file containing the intermediate certificate(s).
# - TIMESTAMP
# Timestamp when the specified certificate was created.
}
function unchanged_cert {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
# This hook is called once for each certificate that is still
# valid and therefore wasn't reissued.
#
# Parameters:
# - DOMAIN
# The primary domain name, i.e. the certificate common
# name (CN).
# - KEYFILE
# The path of the file containing the private key.
# - CERTFILE
# The path of the file containing the signed certificate.
# - FULLCHAINFILE
# The path of the file containing the full certificate chain.
# - CHAINFILE
# The path of the file containing the intermediate certificate(s).
}
HANDLER=$1; shift; $HANDLER $@

View File

@@ -9,7 +9,7 @@ See below for an example on how the calls change:
### HOOK_CHAIN="no" (default behaviour)
```
# INFO: Using main config file /etc/letsencrypt.sh/config.sh
# INFO: Using main config file /etc/dehydrated/config
Processing lukas.im with alternative names: www.lukas.im
+ Checking domain name(s) of existing cert... unchanged.
+ Checking expire date of existing cert...
@@ -31,13 +31,13 @@ HOOK: clean_challenge www.lukas.im blublublu blublublu.supersecure
+ Checking certificate...
+ Done!
+ Creating fullchain.pem...
HOOK: deploy_cert lukas.im /etc/letsencrypt.sh/certs/lukas.im/privkey.pem /etc/letsencrypt.sh/certs/lukas.im/cert.pem /etc/letsencrypt.sh/certs/lukas.im/fullchain.pem /etc/letsencrypt.sh/certs/lukas.im/chain.pem 1460152442
HOOK: deploy_cert lukas.im /etc/dehydrated/certs/lukas.im/privkey.pem /etc/dehydrated/certs/lukas.im/cert.pem /etc/dehydrated/certs/lukas.im/fullchain.pem /etc/dehydrated/certs/lukas.im/chain.pem 1460152442
+ Done!
```
### HOOK_CHAIN="yes"
```
# INFO: Using main config file /etc/letsencrypt.sh/config.sh
# INFO: Using main config file /etc/dehydrated/config
Processing lukas.im with alternative names: www.lukas.im
+ Checking domain name(s) of existing cert... unchanged.
+ Checking expire date of existing cert...
@@ -57,7 +57,6 @@ HOOK: clean_challenge lukas.im blablabla blablabla.supersecure www.lukas.im blub
+ Checking certificate...
+ Done!
+ Creating fullchain.pem...
HOOK: deploy_cert lukas.im /etc/letsencrypt.sh/certs/lukas.im/privkey.pem /etc/letsencrypt.sh/certs/lukas.im/cert.pem /etc/letsencrypt.sh/certs/lukas.im/fullchain.pem /etc/letsencrypt.sh/certs/lukas.im/chain.pem 1460152408
HOOK: deploy_cert lukas.im /etc/dehydrated/certs/lukas.im/privkey.pem /etc/dehydrated/certs/lukas.im/cert.pem /etc/dehydrated/certs/lukas.im/fullchain.pem /etc/dehydrated/certs/lukas.im/chain.pem 1460152408
+ Done!
```

View File

@@ -1,3 +0,0 @@
# Import
If you want to import existing keys from the official letsencrypt client have a look at [Import from official letsencrypt client](https://github.com/lukas2511/letsencrypt.sh/wiki/Import-from-official-letsencrypt-client).

BIN
docs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

155
docs/man/dehydrated.1 Normal file
View File

@@ -0,0 +1,155 @@
.TH DEHYDRATED 1 2018-01-13 "Dehydrated ACME Client"
.SH NAME
dehydrated \- ACME client implemented as a shell-script
.SH SYNOPSIS
.B dehydrated
[\fBcommand\fR [\fBargument\fR]]
[\fBargument\fR [\fBargument\fR]]
.IR ...
.SH DESCRIPTION
A client for ACME-based Certificate Authorities, such as LetsEncrypt. It can
be used to request and obtain TLS certificates from an ACME-based
certificate authority.
Before any certificates can be requested, Dehydrated needs
to acquire an account with the Certificate Authorities. Optionally, an email
address can be provided. It will be used to e.g. notify about expiring
certificates. You will usually need to accept the Terms of Service of the CA.
Dehydrated will notify if no account is configured. Run with \fB--register
--accept-terms\fR to create a new account.
Next, all domain names must be provided in domains.txt. The format is line
based: If the file contains two lines "example.com" and "example.net",
dehydrated will request two certificate, one for "example.com" and the other
for "example.net". A single line containing "example.com example.net" will request a
single certificate valid for both "example.net" and "example.com" through the \fISubject
Alternative Name\fR (SAN) field.
For the next step, one way of verifying domain name ownership needs to be
configured. Dehydrated implements \fIhttp-01\fR and \fIdns-01\fR verification.
The \fIhttp-01\fR verification provides proof of ownership by providing a
challenge token. In order to do that, the directory referenced in the
\fIWELLKNOWN\fR config variable needs to be exposed at
\fIhttp://{domain}/.well-known/acme-challenge/\fR, where {domain} is every
domain name specified in \fIdomains.txt\fR. Dehydrated does not provide its
own challenge responder, but relies on an existing web server to provide the
challenge response. See \fIwellknown.md\fR for configuration examples of
popular web servers.
The \fIdns-01\fR verification works by providing a challenge token through DNS.
This is especially interesting for hosts that cannot be exposed to the public
Internet. Because adding records to DNS zones is oftentimes highly specific to
the software or the DNS provider at hand, there are many third party hooks
available for dehydrated. See \fIdns-verification.md\fR for hooks for popular
DNS servers and DNS hosters.
Finally, the certificates need to be requested and updated on a regular basis.
This can happen through a cron job or a timer. Initially, you may enforce this
by invoking \fIdehydrated -c\fR manually.
After a successful run, certificates are stored in
\fI/etc/dehydrated/certs/{domain}\fR, where {domain} is the domain name in the
first column of \fIdomains.txt\fR.
.SH OPTIONS
.BR Commands
.TP
.BR \-\-version ", " \-v
Print version information
.TP
.BR \-\-register
Register account key
.TP
.BR \-\-account
Update account contact information
.TP
.BR \-\-cron ", " \-c
Sign/renew non\-existent/changed/expiring certificates.
.TP
.BR \-\-signcsr ", " \-s " " \fIpath/to/csr.pem\fR
Sign a given CSR, output CRT on stdout (advanced usage)
.TP
.BR \-\-revoke ", " \-r " " \fIpath/to/cert.pem\fR
Revoke specified certificate
.TP
.BR \-\-cleanup ", " \-gc
Move unused certificate files to archive directory
.TP
.BR \-\-help ", " \-h
Show help text
.TP
.BR \-\-env ", " \-e
Output configuration variables for use in other scripts
.PP
.BR Parameters
.TP
.BR \-\-accept\-terms
Accept CAs terms of service
.TP
.BR \-\-full\-chain ", " \-fc
Print full chain when using \fB\-\-signcsr\fR
.TP
.BR \-\-ipv4 ", " \-4
Resolve names to IPv4 addresses only
.TP
.BR \-\-ipv6 ", " \-6
Resolve names to IPv6 addresses only
.TP
.BR \-\-domain ", " \-d " " \fIdomain.tld\fR
Use specified domain name(s) instead of domains.txt entry (one certificate!)
.TP
.BR \-\-keep\-going ", " \-g
Keep going after encountering an error while creating/renewing multiple
certificates in cron mode
.TP
.BR \-\-force ", " \-x
Force certificate renewal even if it is not due to expire within RENEW_DAYS
.TP
.BR \-\-no\-lock ", " \-n
Don't use lockfile (potentially dangerous!)
.TP
.BR \-\-lock\-suffix " " \fIexample.com\fR
Suffix lockfile name with a string (useful for use with \-d)
.TP
.BR \-\-ocsp
Sets option in CSR indicating OCSP stapling to be mandatory
.TP
.BR \-\-privkey ", " \-p " " \fIpath/to/key.pem\fR
Use specified private key instead of account key (useful for revocation)
.TP
.BR \-\-config ", " \-f " " \fIpath/to/config\fR
Use specified config file
.TP
.BR \-\-hook ", " \-k " " \fIpath/to/hook.sh\fR
Use specified script for hooks
.TP
.BR \-\-out ", " \-o " " \fIcerts/directory\fR
Output certificates into the specified directory
.TP
.BR \-\-challenge ", " \-t " " \fI[http\-01|dns\-01]\fR
Which challenge should be used? Currently http\-01 and dns\-01 are supported
.TP
.BR \-\-algo ", " \-a " " \fI[rsa|prime256v1|secp384r1]\fR
Which public key algorithm should be used? Supported: rsa, prime256v1 and
secp384r1
.SH DIAGNOSTICS
The program exits 0 if everything was fine, 1 if an error occurred.
.SH BUGS
Please report any bugs that you may encounter at the project web site
.UR https://github.com/dehydrated-io/dehydrated/issues
.UE .
.SH AUTHOR
Dehydrated was written by Lukas Schauer. This man page was contributed by
Daniel Molkentin.
.SH COPYRIGHT
Copyright 2015-2018 by Lukas Schauer and the respective contributors.
Provided under the MIT License. See the LICENSE file that accompanies the
distribution for licensing information.
.SH SEE ALSO
Full documentation along with configuration examples are provided in the \fIdocs\fR
directory of the distribution, or at
.UR https://github.com/dehydrated-io/dehydrated/tree/master/docs
.UE .

View File

@@ -0,0 +1,29 @@
# Config on per-certificate base
dehydrated allows a few configuration variables to be set on a per-certificate base.
To use this feature create a `config` file in the certificates output directory (e.g. `certs/example.org/config`).
Currently supported options:
- PRIVATE_KEY_RENEW
- PRIVATE_KEY_ROLLOVER
- KEY_ALGO
- KEYSIZE
- OCSP_MUST_STAPLE
- OCSP_FETCH
- OCSP_DAYS
- CHALLENGETYPE
- HOOK
- HOOK_CHAIN
- WELLKNOWN
- OPENSSL_CNF
- RENEW_DAYS
- PREFERRED_CHAIN
## DOMAINS_D
If `DOMAINS_D` is set, dehydrated will use it for your per-certificate configurations.
Instead of `certs/example.org/config` it will look for a configuration under `DOMAINS_D/example.org`.
If an alias is set, it will be used instead of the primary domain name.

View File

@@ -1,15 +1,14 @@
# Staging
Lets Encrypt has stringent rate limits in place during the public beta period.
Lets Encrypt has stringent rate limits in place.
If you start testing using the production endpoint (which is the default),
you will quickly hit these limits and find yourself locked out.
To avoid this, please set the CA property to the Lets Encrypt staging server URL in your `config.sh` file:
To avoid this, please set the CA property to the Lets Encrypt staging server URL in your config file:
```bash
CA="https://acme-staging.api.letsencrypt.org/directory"
CA="https://acme-staging-v02.api.letsencrypt.org/directory"
```
Please keep in mind that at the time of writing this letsencrypt.sh doesn't have support for registration management,
so if you change CA you'll have to move your `private_key.pem` (and, if you care, `private_key.json`) out of the way.
Alternatively you can define the CA using the CLI argument `--ca letsencrypt-test` (`letsencrypt-test` is an integrated preset-CA corresponding to the URL above).

124
docs/tls-alpn.md Normal file
View File

@@ -0,0 +1,124 @@
# TLS-ALPN-01
With `tls-alpn-01`-type verification Let's Encrypt (or the ACME-protocol in general) is checking if you are in control of a domain by accessing
your webserver using a custom ALPN and expecting a specially crafted TLS certificate containing a verification token.
It will do that for any (sub-)domain you want to sign a certificate for.
Dehydrated generates the required verification certificates, but the delivery is out of its scope.
### Example lighttpd config
lighttpd can be configured to recognize ALPN `acme-tls/1` and to respond to such
requests using the specially crafted TLS certificates generated by dehydrated.
Configure lighttpd and dehydrated to use the same path for these certificates.
(Be sure to allow read access to the user account under which the lighttpd
server is running.) `mkdir -p /etc/dehydrated/alpn-certs`
lighttpd.conf:
```
ssl.acme-tls-1 = "/etc/dehydrated/alpn-certs"
```
When renewing certificates, specify `-t tls-alpn-01` and `--alpn /etc/dehydrated/alpn-certs` to dehydrated, e.g.
```
dehydrated -t tls-alpn-01 --alpn /etc/dehydrated/alpn-certs -c --out /etc/lighttpd/certs -d www.example.com
# gracefully reload lighttpd to use the new certificates by sending lighttpd pid SIGUSR1
systemctl reload lighttpd
```
### Example nginx config
On an nginx tcp load-balancer you can use the `ssl_preread` module to map a different port for acme-tls
requests than for e.g. HTTP/2 or HTTP/1.1 requests.
Your config should look something like this:
```nginx
stream {
map $ssl_preread_alpn_protocols $tls_port {
~\bacme-tls/1\b 10443;
default 443;
}
server {
listen 443;
listen [::]:443;
proxy_pass 10.13.37.42:$tls_port;
ssl_preread on;
}
}
```
That way https requests are forwarded to port 443 on the backend server, and acme-tls/1 requests are
forwarded to port 10443.
In the future nginx might support internal routing based on custom ALPNs, but for now you'll have to
use a custom responder for the alpn verification certificates (see below).
### Example responder
I hacked together a simple responder in Python, it might not be the best, but it works for me:
```python
#!/usr/bin/env python3
import ssl
import socketserver
import threading
import re
import os
ALPNDIR="/etc/dehydrated/alpn-certs"
PROXY_PROTOCOL=False
FALLBACK_CERTIFICATE="/etc/ssl/certs/ssl-cert-snakeoil.pem"
FALLBACK_KEY="/etc/ssl/private/ssl-cert-snakeoil.key"
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def create_context(self, certfile, keyfile, first=False):
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.set_ciphers('ECDHE+AESGCM')
ssl_context.set_alpn_protocols(["acme-tls/1"])
ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
if first:
ssl_context.set_servername_callback(self.load_certificate)
ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile)
return ssl_context
def load_certificate(self, sslsocket, sni_name, sslcontext):
print("Got request for %s" % sni_name)
if not re.match(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$', sni_name):
return
certfile = os.path.join(ALPNDIR, "%s.crt.pem" % sni_name)
keyfile = os.path.join(ALPNDIR, "%s.key.pem" % sni_name)
if not os.path.exists(certfile) or not os.path.exists(keyfile):
return
sslsocket.context = self.create_context(certfile, keyfile)
def handle(self):
if PROXY_PROTOCOL:
buf = b""
while b"\r\n" not in buf:
buf += self.request.recv(1)
ssl_context = self.create_context(FALLBACK_CERTIFICATE, FALLBACK_KEY, True)
newsock = ssl_context.wrap_socket(self.request, server_side=True)
if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 10443
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler, bind_and_activate=False)
server.allow_reuse_address = True
try:
server.server_bind()
server.server_activate()
server.serve_forever()
except:
server.shutdown()
```

View File

@@ -6,33 +6,47 @@ Generally if the following information doesn't provide a solution to your proble
You probably changed from staging-CA to production-CA (or the other way).
Currently letsencrypt.sh doesn't detect a missing registration on the selected CA,
Currently dehydrated doesn't detect a missing registration on the selected CA,
the current workaround is to move `private_key.pem` (and, if you care, `private_key.json`) out of the way so the scripts generates and registers a new one.
This will hopefully be fixed in the future.
## "Provided agreement URL [LICENSE1] does not match current agreement URL [LICENSE2]"
Set LICENSE in your config to the value in place of "LICENSE2".
LICENSE1 and LICENSE2 are just placeholders for the real values in this troubleshooting document!
## "Error creating new cert :: Too many certificates already issued for: [...]"
This is not an issue with letsencrypt.sh but an API limit with letsencrypt.
This is not an issue with dehydrated but an API limit with boulder (the ACME server).
At the time of writing this you can only create 5 certificates per domain in a sliding window of 7 days.
## "Certificate request has 123 names, maximum is 100."
This also is an API limit from letsencrypt, you are requesting to sign a certificate with way too many domains.
This also is an API limit from boulder, you are requesting to sign a certificate with way too many domains.
## Invalid challenges
There are a few factors that could result in invalid challenges.
If you are using http validation make sure that the path you have configured with WELLKNOWN is readable under your domain.
If you are using HTTP validation make sure that the path you have configured with WELLKNOWN is readable under your domain.
To test this create a file (e.g. `test.txt`) in that directory and try opening it with your browser: `http://example.org/.well-known/acme-challenge/test.txt`.
To test this create a file (e.g. `test.txt`) in that directory and try opening it with your browser: `http://example.org/.well-known/acme-challenge/test.txt`. Note that if you have an IPv6 address, the challenge connection will be on IPv6. Be sure that you test HTTP connections on both IPv4 and IPv6. Checking the test file in your browser is often not sufficient because the browser just fails over to IPv4.
If you get any error you'll have to fix your webserver configuration.
If you get any error you'll have to fix your web server configuration.
## DNS invalid challenge since dehydrated 0.6.0 / Why are DNS challenges deployed first and verified later?
Since Let's Encrypt (and in general the ACMEv2 protocol) now supports wildcard domains there is a situation where DNS caching can become a problem.
If somebody wants to validate a certificate with `example.org` and `*.example.org` there are two tokens that have to be deployed on `_acme-challenge.example.org`.
If dehydrated would deploy and verify each token on its own the CA would cache the first token on `_acme-challenge.example.org` and the next challenge would simply fail.
Let's Encrypt uses your DNS TTL with a max limit of 5 minutes, but this doesn't seem to be part of the ACME protocol, just some LE specific configuration,
so with other CAs and certain DNS providers who don't allow low TTLs this could potentially take hours.
Since dehydrated now deploys all challenges first that no longer is a problem. The CA will query and cache both challenges, and both authorizations can be validated.
Some hook-scripts were written in a way that erases the old TXT record rather than adding a new entry, those should be (and many of them already have been) fixed.
There are certain DNS providers which really only allow one TXT record on a domain. This is really odd and you should probably contact your DNS provider and ask them
to fix this.
If for whatever reason you can't switch DNS providers and your DNS provider only supports one TXT record and doesn't want to fix that you could try splitting your
certificate into multiple certificates and add a sleep in the `deploy_cert` hook.
If you can't do that or really don't want to please leave a comment on https://github.com/lukas2511/dehydrated/issues/554,
if many people are having this unfixable problem I might try to implement a workaround.

View File

@@ -5,7 +5,7 @@ It will do that for any (sub-)domain you want to sign a certificate for.
At the moment you'll need to have that location available over normal HTTP on port 80 (redirect to HTTPS will work, but starting point is always HTTP!).
letsencrypt.sh has a config variable called `WELLKNOWN`, which corresponds to the directory which should be served under `/.well-known/acme-challenge` on your domain. So in the above example the token would have been saved as `$WELLKNOWN/m4g1C-t0k3n`.
dehydrated has a config variable called `WELLKNOWN`, which corresponds to the directory which should be served under `/.well-known/acme-challenge` on your domain. So in the above example the token would have been saved as `$WELLKNOWN/m4g1C-t0k3n`.
If you only have one docroot on your server you could easily do something like `WELLKNOWN=/var/www/.well-known/acme-challenge`, for anything else look at the example below.
@@ -13,7 +13,7 @@ If you only have one docroot on your server you could easily do something like `
If you have more than one docroot (or you are using your server as a reverse proxy / load balancer) the simple configuration mentioned above wouldn't work, but with just a few lines of webserver configuration this can be solved.
An example would be to create a directory `/var/www/letsencrypt` and set `WELLKNOWN=/var/www/letsencrypt` in the scripts config.
An example would be to create a directory `/var/www/dehydrated` and set `WELLKNOWN=/var/www/dehydrated` in the scripts config.
You'll need to configure aliases on your Webserver:
@@ -24,8 +24,8 @@ With Nginx you'll need to add this to any of your `server`/VHost config blocks:
```nginx
server {
[...]
location /.well-known/acme-challenge {
alias /var/www/letsencrypt;
location ^~ /.well-known/acme-challenge {
alias /var/www/dehydrated;
}
[...]
}
@@ -36,9 +36,9 @@ server {
With Apache just add this to your config and it should work in any VHost:
```apache
Alias /.well-known/acme-challenge /var/www/letsencrypt
Alias /.well-known/acme-challenge /var/www/dehydrated
<Directory /var/www/letsencrypt>
<Directory /var/www/dehydrated>
Options None
AllowOverride None
@@ -54,3 +54,25 @@ Alias /.well-known/acme-challenge /var/www/letsencrypt
</IfModule>
</Directory>
```
### Lighttpd example config
With Lighttpd just add this to your config and it should work in any VHost:
```lighttpd
server.modules += ("alias")
alias.url += (
"/.well-known/acme-challenge/" => "/var/www/dehydrated/",
)
```
### Hiawatha example config
With Hiawatha just add an alias to your config file for each VirtualHost and it should work:
```hiawatha
VirtualHost {
Hostname = example.tld subdomain.mywebsite.tld
Alias = /.well-known/acme-challenge:/var/www/dehydrated
}
```

View File

@@ -1,918 +0,0 @@
#!/usr/bin/env bash
# letsencrypt.sh by lukas2511
# Source: https://github.com/lukas2511/letsencrypt.sh
#
# This script is licensed under The MIT License (see LICENSE for more information).
set -e
set -u
set -o pipefail
[[ -n "${ZSH_VERSION:-}" ]] && set -o SH_WORD_SPLIT && set +o FUNCTION_ARGZERO
umask 077 # paranoid umask, we're creating private keys
# Find directory in which this script is stored by traversing all symbolic links
SOURCE="${0}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
BASEDIR="${SCRIPTDIR}"
# Create (identifiable) temporary files
_mktemp() {
# shellcheck disable=SC2068
mktemp ${@:-} "${TMPDIR:-/tmp}/letsencrypt.sh-XXXXXX"
}
# Check for script dependencies
check_dependencies() {
# just execute some dummy and/or version commands to see if required tools exist and are actually usable
openssl version > /dev/null 2>&1 || _exiterr "This script requires an openssl binary."
_sed "" < /dev/null > /dev/null 2>&1 || _exiterr "This script requires sed with support for extended (modern) regular expressions."
command -v grep > /dev/null 2>&1 || _exiterr "This script requires grep."
_mktemp -u > /dev/null 2>&1 || _exiterr "This script requires mktemp."
# curl returns with an error code in some ancient versions so we have to catch that
set +e
curl -V > /dev/null 2>&1
retcode="$?"
set -e
if [[ ! "${retcode}" = "0" ]] && [[ ! "${retcode}" = "2" ]]; then
_exiterr "This script requires curl."
fi
}
# Setup default config values, search for and load configuration files
load_config() {
# Check for config in various locations
if [[ -z "${CONFIG:-}" ]]; then
for check_config in "/etc/letsencrypt.sh" "/usr/local/etc/letsencrypt.sh" "${PWD}" "${SCRIPTDIR}"; do
if [[ -e "${check_config}/config.sh" ]]; then
BASEDIR="${check_config}"
CONFIG="${check_config}/config.sh"
break
fi
done
fi
# Default values
CA="https://acme-v01.api.letsencrypt.org/directory"
LICENSE="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
CHALLENGETYPE="http-01"
CONFIG_D=
HOOK=
HOOK_CHAIN="no"
RENEW_DAYS="30"
ACCOUNT_KEY=
ACCOUNT_KEY_JSON=
KEYSIZE="4096"
WELLKNOWN=
PRIVATE_KEY_RENEW="yes"
KEY_ALGO=rsa
OPENSSL_CNF="$(openssl version -d | cut -d\" -f2)/openssl.cnf"
CONTACT_EMAIL=
LOCKFILE=
if [[ -z "${CONFIG:-}" ]]; then
echo "#" >&2
echo "# !! WARNING !! No main config file found, using default config!" >&2
echo "#" >&2
elif [[ -e "${CONFIG}" ]]; then
echo "# INFO: Using main config file ${CONFIG}"
BASEDIR="$(dirname "${CONFIG}")"
# shellcheck disable=SC1090
. "${CONFIG}"
else
_exiterr "Specified config file doesn't exist."
fi
if [[ -n "${CONFIG_D}" ]]; then
if [[ ! -d "${CONFIG_D}" ]]; then
_exiterr "The path ${CONFIG_D} specified for CONFIG_D does not point to a directory." >&2
fi
for check_config_d in "${CONFIG_D}"/*.sh; do
if [[ ! -e "${check_config_d}" ]]; then
echo "# !! WARNING !! Extra configuration directory ${CONFIG_D} exists, but no configuration found in it." >&2
break
elif [[ -f "${check_config_d}" ]] && [[ -r "${check_config_d}" ]]; then
echo "# INFO: Using additional config file ${check_config_d}"
# shellcheck disable=SC1090
. "${check_config_d}"
else
_exiterr "Specified additional config ${check_config_d} is not readable or not a file at all." >&2
fi
done
fi
# Remove slash from end of BASEDIR. Mostly for cleaner outputs, doesn't change functionality.
BASEDIR="${BASEDIR%%/}"
# Check BASEDIR and set default variables
[[ -d "${BASEDIR}" ]] || _exiterr "BASEDIR does not exist: ${BASEDIR}"
[[ -z "${ACCOUNT_KEY}" ]] && ACCOUNT_KEY="${BASEDIR}/private_key.pem"
[[ -z "${ACCOUNT_KEY_JSON}" ]] && ACCOUNT_KEY_JSON="${BASEDIR}/private_key.json"
[[ -z "${WELLKNOWN}" ]] && WELLKNOWN="${BASEDIR}/.acme-challenges"
[[ -z "${LOCKFILE}" ]] && LOCKFILE="${BASEDIR}/lock"
[[ -n "${PARAM_HOOK:-}" ]] && HOOK="${PARAM_HOOK}"
[[ -n "${PARAM_CHALLENGETYPE:-}" ]] && CHALLENGETYPE="${PARAM_CHALLENGETYPE}"
[[ -n "${PARAM_KEY_ALGO:-}" ]] && KEY_ALGO="${PARAM_KEY_ALGO}"
[[ "${CHALLENGETYPE}" =~ (http-01|dns-01) ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... can not continue."
if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
_exiterr "Challenge type dns-01 needs a hook script for deployment... can not continue."
fi
[[ "${KEY_ALGO}" =~ ^(rsa|prime256v1|secp384r1)$ ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... can not continue."
}
# Initialize system
init_system() {
load_config
# Lockfile handling (prevents concurrent access)
LOCKDIR="$(dirname "${LOCKFILE}")"
[[ -w "${LOCKDIR}" ]] || _exiterr "Directory ${LOCKDIR} for LOCKFILE ${LOCKFILE} is not writable, aborting."
( set -C; date > "${LOCKFILE}" ) 2>/dev/null || _exiterr "Lock file '${LOCKFILE}' present, aborting."
remove_lock() { rm -f "${LOCKFILE}"; }
trap 'remove_lock' EXIT
# Get CA URLs
CA_DIRECTORY="$(http_request get "${CA}")"
CA_NEW_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value new-cert)" &&
CA_NEW_AUTHZ="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value new-authz)" &&
CA_NEW_REG="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value new-reg)" &&
# shellcheck disable=SC2015
CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revoke-cert)" ||
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
# Export some environment variables to be used in hook script
export WELLKNOWN BASEDIR CONFIG
# Checking for private key ...
register_new_key="no"
if [[ -n "${PARAM_ACCOUNT_KEY:-}" ]]; then
# a private key was specified from the command line so use it for this run
echo "Using private key ${PARAM_ACCOUNT_KEY} instead of account key"
ACCOUNT_KEY="${PARAM_ACCOUNT_KEY}"
ACCOUNT_KEY_JSON="${PARAM_ACCOUNT_KEY}.json"
else
# Check if private account key exists, if it doesn't exist yet generate a new one (rsa key)
if [[ ! -e "${ACCOUNT_KEY}" ]]; then
echo "+ Generating account key..."
_openssl genrsa -out "${ACCOUNT_KEY}" "${KEYSIZE}"
register_new_key="yes"
fi
fi
openssl rsa -in "${ACCOUNT_KEY}" -check 2>/dev/null > /dev/null || _exiterr "Account key is not valid, can not continue."
# Get public components from private key and calculate thumbprint
pubExponent64="$(printf '%x' "$(openssl rsa -in "${ACCOUNT_KEY}" -noout -text | awk '/publicExponent/ {print $2}')" | hex2bin | urlbase64)"
pubMod64="$(openssl rsa -in "${ACCOUNT_KEY}" -noout -modulus | cut -d'=' -f2 | hex2bin | urlbase64)"
thumbprint="$(printf '{"e":"%s","kty":"RSA","n":"%s"}' "${pubExponent64}" "${pubMod64}" | openssl dgst -sha256 -binary | urlbase64)"
# If we generated a new private key in the step above we have to register it with the acme-server
if [[ "${register_new_key}" = "yes" ]]; then
echo "+ Registering account key with letsencrypt..."
[[ ! -z "${CA_NEW_REG}" ]] || _exiterr "Certificate authority doesn't allow registrations."
# If an email for the contact has been provided then adding it to the registration request
if [[ -n "${CONTACT_EMAIL}" ]]; then
signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "contact":["mailto:'"${CONTACT_EMAIL}"'"], "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}"
else
signed_request "${CA_NEW_REG}" '{"resource": "new-reg", "agreement": "'"$LICENSE"'"}' > "${ACCOUNT_KEY_JSON}"
fi
fi
if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" ]]; then
_exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
fi
}
# Different sed version for different os types...
_sed() {
if [[ "${OSTYPE}" = "Linux" ]]; then
sed -r "${@}"
else
sed -E "${@}"
fi
}
# Print error message and exit with error
_exiterr() {
echo "ERROR: ${1}" >&2
exit 1
}
# Remove newlines and whitespace from json
clean_json() {
tr -d '\r\n' | _sed -e 's/ +/ /g' -e 's/\{ /{/g' -e 's/ \}/}/g' -e 's/\[ /[/g' -e 's/ \]/]/g'
}
# Encode data as url-safe formatted base64
urlbase64() {
# urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_'
openssl base64 -e | tr -d '\n\r' | _sed -e 's:=*$::g' -e 'y:+/:-_:'
}
# Convert hex string to binary data
hex2bin() {
# Remove spaces, add leading zero, escape as hex string and parse with printf
printf -- "$(cat | _sed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')"
}
# Get string value from json dictionary
get_json_string_value() {
local filter
filter=$(printf 's/.*"%s": *"\([^"]*\)".*/\\1/p' "$1")
sed -n "${filter}"
}
# OpenSSL writes to stderr/stdout even when there are no errors. So just
# display the output if the exit code was != 0 to simplify debugging.
_openssl() {
set +e
out="$(openssl "${@}" 2>&1)"
res=$?
set -e
if [[ ${res} -ne 0 ]]; then
echo " + ERROR: failed to run $* (Exitcode: ${res})" >&2
echo >&2
echo "Details:" >&2
echo "${out}" >&2
echo >&2
exit ${res}
fi
}
# Send http(s) request with specified method
http_request() {
tempcont="$(_mktemp)"
set +e
if [[ "${1}" = "head" ]]; then
statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
curlret="${?}"
elif [[ "${1}" = "get" ]]; then
statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}")"
curlret="${?}"
elif [[ "${1}" = "post" ]]; then
statuscode="$(curl -s -w "%{http_code}" -o "${tempcont}" "${2}" -d "${3}")"
curlret="${?}"
else
set -e
_exiterr "Unknown request method: ${1}"
fi
set -e
if [[ ! "${curlret}" = "0" ]]; then
_exiterr "Problem connecting to server (curl returned with ${curlret})"
fi
if [[ ! "${statuscode:0:1}" = "2" ]]; then
echo " + ERROR: An error occurred while sending ${1}-request to ${2} (Status ${statuscode})" >&2
echo >&2
echo "Details:" >&2
cat "${tempcont}" >&2
rm -f "${tempcont}"
# Wait for hook script to clean the challenge if used
if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token:+set}" ]]; then
"${HOOK}" "clean_challenge" '' "${challenge_token}" "${keyauth}"
fi
# remove temporary domains.txt file if used
[[ -n "${PARAM_DOMAIN:-}" && -n "${DOMAINS_TXT:-}" ]] && rm "${DOMAINS_TXT}"
exit 1
fi
cat "${tempcont}"
rm -f "${tempcont}"
}
# Send signed request
signed_request() {
# Encode payload as urlbase64
payload64="$(printf '%s' "${2}" | urlbase64)"
# Retrieve nonce from acme-server
nonce="$(http_request head "${CA}" | grep Replay-Nonce: | awk -F ': ' '{print $2}' | tr -d '\n\r')"
# Build header with just our public key and algorithm information
header='{"alg": "RS256", "jwk": {"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}}'
# Build another header which also contains the previously received nonce and encode it as urlbase64
protected='{"alg": "RS256", "jwk": {"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}, "nonce": "'"${nonce}"'"}'
protected64="$(printf '%s' "${protected}" | urlbase64)"
# Sign header with nonce and our payload with our private key and encode signature as urlbase64
signed64="$(printf '%s' "${protected64}.${payload64}" | openssl dgst -sha256 -sign "${ACCOUNT_KEY}" | urlbase64)"
# Send header + extended header + payload + signature to the acme-server
data='{"header": '"${header}"', "protected": "'"${protected64}"'", "payload": "'"${payload64}"'", "signature": "'"${signed64}"'"}'
http_request post "${1}" "${data}"
}
# Extracts all subject names from a CSR
# Outputs either the CN, or the SANs, one per line
extract_altnames() {
csr="${1}" # the CSR itself (not a file)
if ! <<<"${csr}" openssl req -verify -noout 2>/dev/null; then
_exiterr "Certificate signing request isn't valid"
fi
reqtext="$( <<<"${csr}" openssl req -noout -text )"
if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then
# SANs used, extract these
altnames="$( <<<"${reqtext}" grep -A1 '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$' | tail -n1 )"
# split to one per line:
# shellcheck disable=SC1003
altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/g' )"
# we can only get DNS: ones signed
if grep -qv '^DNS:' <<<"${altnames}"; then
_exiterr "Certificate signing request contains non-DNS Subject Alternative Names"
fi
# strip away the DNS: prefix
altnames="$( <<<"${altnames}" _sed -e 's/^DNS://' )"
echo "${altnames}"
else
# No SANs, extract CN
altnames="$( <<<"${reqtext}" grep '^[[:space:]]*Subject:' | _sed -e 's/.* CN=([^ /,]*).*/\1/' )"
echo "${altnames}"
fi
}
# Create certificate for domain(s) and outputs it FD 3
sign_csr() {
csr="${1}" # the CSR itself (not a file)
if { true >&3; } 2>/dev/null; then
: # fd 3 looks OK
else
_exiterr "sign_csr: FD 3 not open"
fi
shift 1 || true
altnames="${*:-}"
if [ -z "${altnames}" ]; then
altnames="$( extract_altnames "${csr}" )"
fi
if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then
_exiterr "Certificate authority doesn't allow certificate signing"
fi
local idx=0
if [[ -n "${ZSH_VERSION:-}" ]]; then
local -A challenge_uris challenge_tokens keyauths deploy_args
else
local -a challenge_uris challenge_tokens keyauths deploy_args
fi
# Request challenges
for altname in ${altnames}; do
# Ask the acme-server for new challenge token and extract them from the resulting json block
echo " + Requesting challenge for ${altname}..."
response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${altname}"'"}}' | clean_json)"
challenges="$(printf '%s\n' "${response}" | sed -n 's/.*\("challenges":[^\[]*\[[^]]*]\).*/\1/p')"
repl=$'\n''{' # fix syntax highlighting in Vim
challenge="$(printf "%s" "${challenges//\{/${repl}}" | grep \""${CHALLENGETYPE}"\")"
challenge_token="$(printf '%s' "${challenge}" | get_json_string_value token | _sed 's/[^A-Za-z0-9_\-]/_/g')"
challenge_uri="$(printf '%s' "${challenge}" | get_json_string_value uri)"
if [[ -z "${challenge_token}" ]] || [[ -z "${challenge_uri}" ]]; then
_exiterr "Can't retrieve challenges (${response})"
fi
# Challenge response consists of the challenge token and the thumbprint of our public certificate
keyauth="${challenge_token}.${thumbprint}"
case "${CHALLENGETYPE}" in
"http-01")
# Store challenge response in well-known location and make world-readable (so that a webserver can access it)
printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_token}"
chmod a+r "${WELLKNOWN}/${challenge_token}"
keyauth_hook="${keyauth}"
;;
"dns-01")
# Generate DNS entry content for dns-01 validation
keyauth_hook="$(printf '%s' "${keyauth}" | openssl dgst -sha256 -binary | urlbase64)"
;;
esac
challenge_uris[${idx}]="${challenge_uri}"
keyauths[${idx}]="${keyauth}"
challenge_tokens[${idx}]="${challenge_token}"
# Note: assumes args will never have spaces!
deploy_args[${idx}]="${altname} ${challenge_token} ${keyauth_hook}"
idx=$((idx+1))
done
# Wait for hook script to deploy the challenges if used
# shellcheck disable=SC2068
[[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[@]}
# Respond to challenges
idx=0
for altname in ${altnames}; do
challenge_token="${challenge_tokens[${idx}]}"
keyauth="${keyauths[${idx}]}"
# Wait for hook script to deploy the challenge if used
# shellcheck disable=SC2086
[[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]}
# Ask the acme-server to verify our challenge and wait until it is no longer pending
echo " + Responding to challenge for ${altname}..."
result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}' | clean_json)"
reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
while [[ "${reqstatus}" = "pending" ]]; do
sleep 1
result="$(http_request get "${challenge_uris[${idx}]}")"
reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
done
[[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_token}"
# Wait for hook script to clean the challenge if used
if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" != "yes" ]] && [[ -n "${challenge_token}" ]]; then
# shellcheck disable=SC2086
"${HOOK}" "clean_challenge" ${deploy_args[${idx}]}
fi
idx=$((idx+1))
if [[ "${reqstatus}" = "valid" ]]; then
echo " + Challenge is valid!"
else
break
fi
done
# Wait for hook script to clean the challenges if used
# shellcheck disable=SC2068
[[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]] && "${HOOK}" "clean_challenge" ${deploy_args[@]}
if [[ "${reqstatus}" != "valid" ]]; then
# Clean up any remaining challenge_tokens if we stopped early
if [[ "${CHALLENGETYPE}" = "http-01" ]]; then
while [ ${idx} -lt ${#challenge_tokens[@]} ]; do
rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"
idx=$((idx+1))
done
fi
_exiterr "Challenge is invalid! (returned: ${reqstatus}) (result: ${result})"
fi
# Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem
echo " + Requesting certificate..."
csr64="$( <<<"${csr}" openssl req -outform DER | urlbase64)"
crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | openssl base64 -e)"
crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )"
# Try to load the certificate to detect corruption
echo " + Checking certificate..."
_openssl x509 -text <<<"${crt}"
echo "${crt}" >&3
unset challenge_token
echo " + Done!"
}
# Create certificate for domain(s)
sign_domain() {
domain="${1}"
altnames="${*}"
timestamp="$(date +%s)"
echo " + Signing domains..."
if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then
_exiterr "Certificate authority doesn't allow certificate signing"
fi
# If there is no existing certificate directory => make it
if [[ ! -e "${BASEDIR}/certs/${domain}" ]]; then
echo " + Creating new directory ${BASEDIR}/certs/${domain} ..."
mkdir -p "${BASEDIR}/certs/${domain}"
fi
privkey="privkey.pem"
# generate a new private key if we need or want one
if [[ ! -r "${BASEDIR}/certs/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
echo " + Generating private key..."
privkey="privkey-${timestamp}.pem"
case "${KEY_ALGO}" in
rsa) _openssl genrsa -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem" "${KEYSIZE}";;
prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem";;
esac
fi
# Generate signing request config and the actual signing request
echo " + Generating signing request..."
SAN=""
for altname in ${altnames}; do
SAN+="DNS:${altname}, "
done
SAN="${SAN%%, }"
local tmp_openssl_cnf
tmp_openssl_cnf="$(_mktemp)"
cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}"
printf "[SAN]\nsubjectAltName=%s" "${SAN}" >> "${tmp_openssl_cnf}"
openssl req -new -sha256 -key "${BASEDIR}/certs/${domain}/${privkey}" -out "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config "${tmp_openssl_cnf}"
rm -f "${tmp_openssl_cnf}"
crt_path="${BASEDIR}/certs/${domain}/cert-${timestamp}.pem"
# shellcheck disable=SC2086
sign_csr "$(< "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" )" ${altnames} 3>"${crt_path}"
# Create fullchain.pem
echo " + Creating fullchain.pem..."
cat "${crt_path}" > "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem"
http_request get "$(openssl x509 -in "${BASEDIR}/certs/${domain}/cert-${timestamp}.pem" -noout -text | grep 'CA Issuers - URI:' | cut -d':' -f2-)" > "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem"
if ! grep -q "BEGIN CERTIFICATE" "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem"; then
openssl x509 -in "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem" -inform DER -out "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem" -outform PEM
fi
cat "${BASEDIR}/certs/${domain}/chain-${timestamp}.pem" >> "${BASEDIR}/certs/${domain}/fullchain-${timestamp}.pem"
# Update symlinks
[[ "${privkey}" = "privkey.pem" ]] || ln -sf "privkey-${timestamp}.pem" "${BASEDIR}/certs/${domain}/privkey.pem"
ln -sf "chain-${timestamp}.pem" "${BASEDIR}/certs/${domain}/chain.pem"
ln -sf "fullchain-${timestamp}.pem" "${BASEDIR}/certs/${domain}/fullchain.pem"
ln -sf "cert-${timestamp}.csr" "${BASEDIR}/certs/${domain}/cert.csr"
ln -sf "cert-${timestamp}.pem" "${BASEDIR}/certs/${domain}/cert.pem"
# Wait for hook script to clean the challenge and to deploy cert if used
export KEY_ALGO
[[ -n "${HOOK}" ]] && "${HOOK}" "deploy_cert" "${domain}" "${BASEDIR}/certs/${domain}/privkey.pem" "${BASEDIR}/certs/${domain}/cert.pem" "${BASEDIR}/certs/${domain}/fullchain.pem" "${BASEDIR}/certs/${domain}/chain.pem" "${timestamp}"
unset challenge_token
echo " + Done!"
}
# Usage: --cron (-c)
# Description: Sign/renew non-existant/changed/expiring certificates.
command_sign_domains() {
init_system
if [[ -n "${PARAM_DOMAIN:-}" ]]; then
DOMAINS_TXT="$(_mktemp)"
printf -- "${PARAM_DOMAIN}" > "${DOMAINS_TXT}"
elif [[ -e "${BASEDIR}/domains.txt" ]]; then
DOMAINS_TXT="${BASEDIR}/domains.txt"
else
_exiterr "domains.txt not found and --domain not given"
fi
# Generate certificates for all domains found in domains.txt. Check if existing certificate are about to expire
ORIGIFS="${IFS}"
IFS=$'\n'
for line in $(<"${DOMAINS_TXT}" tr -d '\r' | tr '[:upper:]' '[:lower:]' | _sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g' -e 's/[[:space:]]+/ /g' | (grep -vE '^(#|$)' || true)); do
IFS="${ORIGIFS}"
domain="$(printf '%s\n' "${line}" | cut -d' ' -f1)"
morenames="$(printf '%s\n' "${line}" | cut -s -d' ' -f2-)"
cert="${BASEDIR}/certs/${domain}/cert.pem"
force_renew="${PARAM_FORCE:-no}"
if [[ -z "${morenames}" ]];then
echo "Processing ${domain}"
else
echo "Processing ${domain} with alternative names: ${morenames}"
fi
if [[ -e "${cert}" ]]; then
printf " + Checking domain name(s) of existing cert..."
certnames="$(openssl x509 -in "${cert}" -text -noout | grep DNS: | _sed 's/DNS://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')"
givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//' | _sed 's/^ //')"
if [[ "${certnames}" = "${givennames}" ]]; then
echo " unchanged."
else
echo " changed!"
echo " + Domain name(s) are not matching!"
echo " + Names in old certificate: ${certnames}"
echo " + Configured names: ${givennames}"
echo " + Forcing renew."
force_renew="yes"
fi
fi
if [[ -e "${cert}" ]]; then
echo " + Checking expire date of existing cert..."
valid="$(openssl x509 -enddate -noout -in "${cert}" | cut -d= -f2- )"
printf " + Valid till %s " "${valid}"
if openssl x509 -checkend $((RENEW_DAYS * 86400)) -noout -in "${cert}"; then
printf "(Longer than %d days). " "${RENEW_DAYS}"
if [[ "${force_renew}" = "yes" ]]; then
echo "Ignoring because renew was forced!"
else
# Certificate-Names unchanged and cert is still valid
echo "Skipping renew!"
[[ -n "${HOOK}" ]] && "${HOOK}" "unchanged_cert" "${domain}" "${BASEDIR}/certs/${domain}/privkey.pem" "${BASEDIR}/certs/${domain}/cert.pem" "${BASEDIR}/certs/${domain}/fullchain.pem" "${BASEDIR}/certs/${domain}/chain.pem"
continue
fi
else
echo "(Less than ${RENEW_DAYS} days). Renewing!"
fi
fi
# shellcheck disable=SC2086
sign_domain ${line}
done
# remove temporary domains.txt file if used
[[ -n "${PARAM_DOMAIN:-}" ]] && rm -f "${DOMAINS_TXT}"
exit 0
}
# Usage: --signcsr (-s) path/to/csr.pem
# Description: Sign a given CSR, output CRT on stdout (advanced usage)
command_sign_csr() {
# redirect stdout to stderr
# leave stdout over at fd 3 to output the cert
exec 3>&1 1>&2
init_system
csrfile="${1}"
if [ ! -r "${csrfile}" ]; then
_exiterr "Could not read certificate signing request ${csrfile}"
fi
sign_csr "$(< "${csrfile}" )"
exit 0
}
# Usage: --revoke (-r) path/to/cert.pem
# Description: Revoke specified certificate
command_revoke() {
init_system
[[ -n "${CA_REVOKE_CERT}" ]] || _exiterr "Certificate authority doesn't allow certificate revocation."
cert="${1}"
if [[ -L "${cert}" ]]; then
# follow symlink and use real certificate name (so we move the real file and not the symlink at the end)
local link_target
link_target="$(readlink -n "${cert}")"
if [[ "${link_target}" =~ ^/ ]]; then
cert="${link_target}"
else
cert="$(dirname "${cert}")/${link_target}"
fi
fi
[[ -f "${cert}" ]] || _exiterr "Could not find certificate ${cert}"
echo "Revoking ${cert}"
cert64="$(openssl x509 -in "${cert}" -inform PEM -outform DER | urlbase64)"
response="$(signed_request "${CA_REVOKE_CERT}" '{"resource": "revoke-cert", "certificate": "'"${cert64}"'"}' | clean_json)"
# if there is a problem with our revoke request _request (via signed_request) will report this and "exit 1" out
# so if we are here, it is safe to assume the request was successful
echo " + Done."
echo " + Renaming certificate to ${cert}-revoked"
mv -f "${cert}" "${cert}-revoked"
}
# Usage: --cleanup (-gc)
# Description: Move unused certificate files to archive directory
command_cleanup() {
load_config
# Create global archive directory if not existant
if [[ ! -e "${BASEDIR}/archive" ]]; then
mkdir "${BASEDIR}/archive"
fi
# Loop over all certificate directories
for certdir in "${BASEDIR}/certs/"*; do
# Skip if entry is not a folder
[[ -d "${certdir}" ]] || continue
# Get certificate name
certname="$(basename "${certdir}")"
# Create certitifaces archive directory if not existant
archivedir="${BASEDIR}/archive/${certname}"
if [[ ! -e "${archivedir}" ]]; then
mkdir "${archivedir}"
fi
# Loop over file-types (certificates, keys, signing-requests, ...)
for filetype in cert.csr cert.pem chain.pem fullchain.pem privkey.pem; do
# Skip if symlink is broken
[[ -r "${certdir}/${filetype}" ]] || continue
# Look up current file in use
current="$(basename "$(readlink "${certdir}/${filetype}")")"
# Split filetype into name and extension
filebase="$(echo "${filetype}" | cut -d. -f1)"
fileext="$(echo "${filetype}" | cut -d. -f2)"
# Loop over all files of this type
for file in "${certdir}/${filebase}-"*".${fileext}"; do
# Handle case where no files match the wildcard
[[ -f "${file}" ]] || break
# Check if current file is in use, if unused move to archive directory
filename="$(basename "${file}")"
if [[ ! "${filename}" = "${current}" ]]; then
echo "Moving unused file to archive directory: ${certname}/${filename}"
mv "${certdir}/${filename}" "${archivedir}/${filename}"
fi
done
done
done
exit 0
}
# Usage: --help (-h)
# Description: Show help text
command_help() {
printf "Usage: %s [-h] [command [argument]] [parameter [argument]] [parameter [argument]] ...\n\n" "${0}"
printf "Default command: help\n\n"
echo "Commands:"
grep -e '^[[:space:]]*# Usage:' -e '^[[:space:]]*# Description:' -e '^command_.*()[[:space:]]*{' "${0}" | while read -r usage; read -r description; read -r command; do
if [[ ! "${usage}" =~ Usage ]] || [[ ! "${description}" =~ Description ]] || [[ ! "${command}" =~ ^command_ ]]; then
_exiterr "Error generating help text."
fi
printf " %-32s %s\n" "${usage##"# Usage: "}" "${description##"# Description: "}"
done
printf -- "\nParameters:\n"
grep -E -e '^[[:space:]]*# PARAM_Usage:' -e '^[[:space:]]*# PARAM_Description:' "${0}" | while read -r usage; read -r description; do
if [[ ! "${usage}" =~ Usage ]] || [[ ! "${description}" =~ Description ]]; then
_exiterr "Error generating help text."
fi
printf " %-32s %s\n" "${usage##"# PARAM_Usage: "}" "${description##"# PARAM_Description: "}"
done
}
# Usage: --env (-e)
# Description: Output configuration variables for use in other scripts
command_env() {
echo "# letsencrypt.sh configuration"
load_config
typeset -p CA LICENSE CHALLENGETYPE HOOK HOOK_CHAIN RENEW_DAYS ACCOUNT_KEY ACCOUNT_KEY_JSON KEYSIZE WELLKNOWN PRIVATE_KEY_RENEW OPENSSL_CNF CONTACT_EMAIL LOCKFILE
}
# Main method (parses script arguments and calls command_* methods)
main() {
COMMAND=""
set_command() {
[[ -z "${COMMAND}" ]] || _exiterr "Only one command can be executed at a time. See help (-h) for more information."
COMMAND="${1}"
}
check_parameters() {
if [[ -z "${1:-}" ]]; then
echo "The specified command requires additional parameters. See help:" >&2
echo >&2
command_help >&2
exit 1
elif [[ "${1:0:1}" = "-" ]]; then
_exiterr "Invalid argument: ${1}"
fi
}
[[ -z "${@}" ]] && eval set -- "--help"
while (( ${#} )); do
case "${1}" in
--help|-h)
command_help
exit 0
;;
--env|-e)
set_command env
;;
--cron|-c)
set_command sign_domains
;;
--signcsr|-s)
shift 1
set_command sign_csr
check_parameters "${1:-}"
PARAM_CSR="${1}"
;;
--revoke|-r)
shift 1
set_command revoke
check_parameters "${1:-}"
PARAM_REVOKECERT="${1}"
;;
--cleanup|-gc)
set_command cleanup
;;
# PARAM_Usage: --domain (-d) domain.tld
# PARAM_Description: Use specified domain name(s) instead of domains.txt entry (one certificate!)
--domain|-d)
shift 1
check_parameters "${1:-}"
if [[ -z "${PARAM_DOMAIN:-}" ]]; then
PARAM_DOMAIN="${1}"
else
PARAM_DOMAIN="${PARAM_DOMAIN} ${1}"
fi
;;
# PARAM_Usage: --force (-x)
# PARAM_Description: Force renew of certificate even if it is longer valid than value in RENEW_DAYS
--force|-x)
PARAM_FORCE="yes"
;;
# PARAM_Usage: --privkey (-p) path/to/key.pem
# PARAM_Description: Use specified private key instead of account key (useful for revocation)
--privkey|-p)
shift 1
check_parameters "${1:-}"
PARAM_ACCOUNT_KEY="${1}"
;;
# PARAM_Usage: --config (-f) path/to/config.sh
# PARAM_Description: Use specified config file
--config|-f)
shift 1
check_parameters "${1:-}"
CONFIG="${1}"
;;
# PARAM_Usage: --hook (-k) path/to/hook.sh
# PARAM_Description: Use specified script for hooks
--hook|-k)
shift 1
check_parameters "${1:-}"
PARAM_HOOK="${1}"
;;
# PARAM_Usage: --challenge (-t) http-01|dns-01
# PARAM_Description: Which challenge should be used? Currently http-01 and dns-01 are supported
--challenge|-t)
shift 1
check_parameters "${1:-}"
PARAM_CHALLENGETYPE="${1}"
;;
# PARAM_Usage: --algo (-a) rsa|prime256v1|secp384r1
# PARAM_Description: Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
--algo|-a)
shift 1
check_parameters "${1:-}"
PARAM_KEY_ALGO="${1}"
;;
*)
echo "Unknown parameter detected: ${1}" >&2
echo >&2
command_help >&2
exit 1
;;
esac
shift 1
done
case "${COMMAND}" in
env) command_env;;
sign_domains) command_sign_domains;;
sign_csr) command_sign_csr "${PARAM_CSR}";;
revoke) command_revoke "${PARAM_REVOKECERT}";;
cleanup) command_cleanup;;
*) command_help; exit 1;;
esac
}
# Determine OS type
OSTYPE="$(uname)"
# Check for missing dependencies
check_dependencies
# Run script
main "${@:-}"

228
test.sh
View File

@@ -1,228 +0,0 @@
#!/usr/bin/env bash
# Fail early
set -eu -o pipefail
# Check if running in CI environment
if [[ ! "${CI:-false}" == "true" ]]; then
echo "ERROR: Not running in CI environment!"
exit 1
fi
_TEST() {
echo
echo "${1} "
}
_SUBTEST() {
echo -n " + ${1} "
}
_PASS() {
echo -e "[\u001B[32mPASS\u001B[0m]"
}
_FAIL() {
echo -e "[\u001B[31mFAIL\u001B[0m]"
echo
echo "Problem: ${@}"
echo
echo "STDOUT:"
cat tmplog
echo
echo "STDERR:"
cat errorlog
exit 1
}
_CHECK_FILE() {
_SUBTEST "Checking if file '${1}' exists..."
if [[ -e "${1}" ]]; then
_PASS
else
_FAIL "Missing file: ${1}"
fi
}
_CHECK_LOG() {
_SUBTEST "Checking if log contains '${1}'..."
if grep -- "${1}" tmplog > /dev/null; then
_PASS
else
_FAIL "Missing in log: ${1}"
fi
}
_CHECK_NOT_LOG() {
_SUBTEST "Checking if log doesn't contain '${1}'..."
if grep -- "${1}" tmplog > /dev/null; then
_FAIL "Found in log: ${1}"
else
_PASS
fi
}
_CHECK_ERRORLOG() {
_SUBTEST "Checking if errorlog is empty..."
if [[ -z "$(cat errorlog)" ]]; then
_PASS
else
_FAIL "Non-empty errorlog"
fi
}
# If not found (should be cached in travis) download ngrok
if [[ ! -e "ngrok/ngrok" ]]; then
(
mkdir -p ngrok
cd ngrok
wget https://dl.ngrok.com/ngrok_2.0.19_linux_amd64.zip -O ngrok.zip
unzip ngrok.zip ngrok
chmod +x ngrok
)
fi
# Run ngrok and grab temporary url from logfile
ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp.log &
ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp2.log &
ngrok/ngrok http 8080 --log stdout --log-format logfmt --log-level debug > tmp3.log &
sleep 2
TMP_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp.log | head -1 | cut -d':' -f2)"
TMP2_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp2.log | head -1 | cut -d':' -f2)"
TMP3_URL="$(grep -Eo "Hostname:[a-z0-9]+.ngrok.io" tmp3.log | head -1 | cut -d':' -f2)"
if [[ -z "${TMP_URL}" ]] || [[ -z "${TMP2_URL}" ]] || [[ -z "${TMP3_URL}" ]]; then
echo "Couldn't get an url from ngrok, not a letsencrypt.sh bug, tests can't continue."
exit 1
fi
# Run python webserver in .acme-challenges directory to serve challenge responses
mkdir -p .acme-challenges/.well-known/acme-challenge
(
cd .acme-challenges
python -m SimpleHTTPServer 8080 > /dev/null 2> /dev/null
) &
# Generate config and create empty domains.txt
echo 'CA="https://testca.kurz.pw/directory"' > config.sh
echo 'LICENSE="https://testca.kurz.pw/terms/v1"' >> config.sh
echo 'WELLKNOWN=".acme-challenges/.well-known/acme-challenge"' >> config.sh
echo 'RENEW_DAYS="14"' >> config.sh
touch domains.txt
# Check if help command is working
_TEST "Checking if help command is working..."
./letsencrypt.sh --help > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Default command: help"
_CHECK_LOG "--help (-h)"
_CHECK_LOG "--domain (-d) domain.tld"
_CHECK_ERRORLOG
# Run in cron mode with empty domains.txt (should only generate private key and exit)
_TEST "First run in cron mode, checking if private key is generated and registered"
./letsencrypt.sh --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Registering account key"
_CHECK_FILE "private_key.pem"
_CHECK_ERRORLOG
# Temporarily move config out of the way and try signing certificate by using temporary config location
_TEST "Try signing using temporary config location and with domain as command line parameter"
mv config.sh tmp_config.sh
./letsencrypt.sh --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" -f tmp_config.sh > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_NOT_LOG "Checking domain name(s) of existing cert"
_CHECK_LOG "Generating private key"
_CHECK_LOG "Requesting challenge for ${TMP_URL}"
_CHECK_LOG "Requesting challenge for ${TMP2_URL}"
_CHECK_LOG "Challenge is valid!"
_CHECK_LOG "Creating fullchain.pem"
_CHECK_LOG "Done!"
_CHECK_ERRORLOG
mv tmp_config.sh config.sh
# Move private key and add new location to config
mv private_key.pem account_key.pem
echo 'PRIVATE_KEY="./account_key.pem"' >> config.sh
# Add third domain to command-lime, should force renewal.
_TEST "Run in cron mode again, this time adding third domain, should force renewal."
./letsencrypt.sh --cron --domain "${TMP_URL}" --domain "${TMP2_URL}" --domain "${TMP3_URL}" > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Domain name(s) are not matching!"
_CHECK_LOG "Forcing renew."
_CHECK_LOG "Generating private key"
_CHECK_LOG "Requesting challenge for ${TMP_URL}"
_CHECK_LOG "Requesting challenge for ${TMP2_URL}"
_CHECK_LOG "Requesting challenge for ${TMP3_URL}"
_CHECK_LOG "Challenge is valid!"
_CHECK_LOG "Creating fullchain.pem"
_CHECK_LOG "Done!"
_CHECK_ERRORLOG
# Prepare domains.txt
# Modify TMP3_URL to be uppercase to check for upper-lower-case mismatch bugs
echo "${TMP_URL} ${TMP2_URL} $(tr 'a-z' 'A-Z' <<<"${TMP3_URL}")" >> domains.txt
# Run in cron mode again (should find a non-expiring certificate and do nothing)
_TEST "Run in cron mode again, this time with domain in domains.txt, should find non-expiring certificate"
./letsencrypt.sh --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Checking domain name(s) of existing cert... unchanged."
_CHECK_LOG "Skipping renew"
_CHECK_ERRORLOG
# Disable private key renew
echo 'PRIVATE_KEY_RENEW="no"' >> config.sh
# Run in cron mode one last time, with domain in domains.txt and force-resign (should find certificate, resign anyway, and not generate private key)
_TEST "Run in cron mode one last time, with domain in domains.txt and force-resign"
./letsencrypt.sh --cron --force > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Checking domain name(s) of existing cert... unchanged."
_CHECK_LOG "Ignoring because renew was forced!"
_CHECK_NOT_LOG "Generating private key"
_CHECK_LOG "Requesting challenge for ${TMP_URL}"
_CHECK_LOG "Requesting challenge for ${TMP2_URL}"
_CHECK_LOG "Requesting challenge for ${TMP3_URL}"
_CHECK_LOG "Challenge is valid!"
_CHECK_LOG "Creating fullchain.pem"
_CHECK_LOG "Done!"
_CHECK_ERRORLOG
# Check if signcsr command is working
_TEST "Running signcsr command"
./letsencrypt.sh --signcsr certs/${TMP_URL}/cert.csr > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "BEGIN CERTIFICATE"
_CHECK_LOG "END CERTIFICATE"
_CHECK_NOT_LOG "ERROR"
# Delete account key (not needed anymore)
rm account_key.pem
# Check if renewal works
_TEST "Run in cron mode again, to check if renewal works"
echo 'RENEW_DAYS="300"' >> config.sh
./letsencrypt.sh --cron > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Checking domain name(s) of existing cert... unchanged."
_CHECK_LOG "Renewing!"
_CHECK_ERRORLOG
# Check if certificate is valid in various ways
_TEST "Verifying certificate..."
_SUBTEST "Verifying certificate on its own..."
openssl x509 -in "certs/${TMP_URL}/cert.pem" -noout -text > tmplog 2> errorlog && _PASS || _FAIL
_CHECK_LOG "CN=${TMP_URL}"
_CHECK_LOG "${TMP2_URL}"
_SUBTEST "Verifying file with full chain..."
openssl x509 -in "certs/${TMP_URL}/fullchain.pem" -noout -text > /dev/null 2>> errorlog && _PASS || _FAIL
_SUBTEST "Verifying certificate against CA certificate..."
(openssl verify -verbose -CAfile "certs/${TMP_URL}/fullchain.pem" -purpose sslserver "certs/${TMP_URL}/fullchain.pem" 2>&1 || true) | (grep -v ': OK$' || true) >> errorlog 2>> errorlog && _PASS || _FAIL
_CHECK_ERRORLOG
# Revoke certificate using certificate key
_TEST "Revoking certificate..."
./letsencrypt.sh --revoke "certs/${TMP_URL}/cert.pem" --privkey "certs/${TMP_URL}/privkey.pem" > tmplog 2> errorlog || _FAIL "Script execution failed"
REAL_CERT="$(readlink -n "certs/${TMP_URL}/cert.pem")"
_CHECK_LOG "Revoking certs/${TMP_URL}/${REAL_CERT}"
_CHECK_LOG "Done."
_CHECK_FILE "certs/${TMP_URL}/${REAL_CERT}-revoked"
_CHECK_ERRORLOG
# Test cleanup command
_TEST "Cleaning up certificates"
./letsencrypt.sh --cleanup > tmplog 2> errorlog || _FAIL "Script execution failed"
_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/cert-"
_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/chain-"
_CHECK_LOG "Moving unused file to archive directory: ${TMP_URL}/fullchain-"
_CHECK_ERRORLOG
# All done
exit 0