summaryrefslogtreecommitdiff
path: root/usr.sbin/acme-client
diff options
context:
space:
mode:
authorFlorian Obser <florian@cvs.openbsd.org>2016-08-31 22:01:43 +0000
committerFlorian Obser <florian@cvs.openbsd.org>2016-08-31 22:01:43 +0000
commitb079979c58ecf59474cdfd312647eea6408870ef (patch)
tree793fccb82966d239250ce0c7023c1c421be082dd /usr.sbin/acme-client
parent70677c65a4fa7b624d3f5a75e33afd3ad0dbefc7 (diff)
Import Kristaps' letskencrypt and call it acme-client in tree.
OK to get it in deraadt@ (and probably beck@) At least deraadt@, beck@ and otto@ are fine with the name and the disagreements stopped.
Diffstat (limited to 'usr.sbin/acme-client')
-rw-r--r--usr.sbin/acme-client/ChangeLog1186
-rw-r--r--usr.sbin/acme-client/Makefile40
-rw-r--r--usr.sbin/acme-client/acctproc.c440
-rw-r--r--usr.sbin/acme-client/base64.c130
-rw-r--r--usr.sbin/acme-client/certproc.c261
-rw-r--r--usr.sbin/acme-client/chngproc.c174
-rw-r--r--usr.sbin/acme-client/dbg.c51
-rw-r--r--usr.sbin/acme-client/dnsproc.c206
-rw-r--r--usr.sbin/acme-client/extern.h267
-rw-r--r--usr.sbin/acme-client/fileproc.c221
-rw-r--r--usr.sbin/acme-client/http.c851
-rw-r--r--usr.sbin/acme-client/http.h94
-rw-r--r--usr.sbin/acme-client/jsmn.c332
-rw-r--r--usr.sbin/acme-client/jsmn.h97
-rw-r--r--usr.sbin/acme-client/json.c632
-rw-r--r--usr.sbin/acme-client/keyproc.c293
-rw-r--r--usr.sbin/acme-client/letskencrypt.1323
-rw-r--r--usr.sbin/acme-client/main.c485
-rw-r--r--usr.sbin/acme-client/netproc.c811
-rw-r--r--usr.sbin/acme-client/revokeproc.c375
-rw-r--r--usr.sbin/acme-client/rsa.c97
-rw-r--r--usr.sbin/acme-client/rsa.h23
-rw-r--r--usr.sbin/acme-client/sandbox-pledge.c83
-rw-r--r--usr.sbin/acme-client/util-pledge.c65
-rw-r--r--usr.sbin/acme-client/util.c317
25 files changed, 7854 insertions, 0 deletions
diff --git a/usr.sbin/acme-client/ChangeLog b/usr.sbin/acme-client/ChangeLog
new file mode 100644
index 00000000000..51a88c65bb4
--- /dev/null
+++ b/usr.sbin/acme-client/ChangeLog
@@ -0,0 +1,1186 @@
+2016-08-19 07:17 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_1_10): Merge
+ https://github.com/kristapsdz/letskencrypt-portable/pull/6 --
+ thanks, Bernard!
+
+2016-08-06 12:45 kristaps
+
+ * Makefile (tags: VERSION_0_1_10): Allow a fake-install prefix.
+ From https://github.com/kristapsdz/letskencrypt/pull/16 .
+
+2016-08-06 12:38 kristaps
+
+ * letskencrypt.1: Document -a.
+
+2016-08-06 12:37 kristaps
+
+ * main.c (tags: VERSION_0_1_10): Allow overriding agreement and
+ also update usage message.
+
+2016-08-06 12:37 kristaps
+
+ * extern.h, netproc.c (utags: VERSION_0_1_10): Adding override for
+ agreement with -a flag. Noted (and partially patched) in
+ https://github.com/kristapsdz/letskencrypt/pull/15 and by
+ ``pasta'' over e-mail -- thanks!
+
+2016-07-28 08:37 kristaps
+
+ * letskencrypt.1: Document backing up.
+
+2016-07-28 08:32 kristaps
+
+ * extern.h, fileproc.c (tags: VERSION_0_1_10), main.c: Initial
+ backing-up of certificates. Suggested by
+ https://github.com/kristapsdz/letskencrypt/issues/12 and
+ https://github.com/kristapsdz/letskencrypt/issues/9
+
+2016-07-16 05:59 kristaps
+
+ * acctproc.c (tags: VERSION_0_1_10), keyproc.c (tags:
+ VERSION_0_1_10), main.c (utags: VERSION_0_1_9): Properly check -n
+ and -N existence in main.c, allowing them to propogate to netproc
+ and so on. This reverts a prior change that was incomplete.
+
+2016-07-16 05:11 kristaps
+
+ * extern.h (tags: VERSION_0_1_9): Allow PATH_VAR_EMPTY to be
+ overridden. Apparently not all systems have this directory, so
+ let them provide their own.
+
+2016-07-16 05:10 kristaps
+
+ * acctproc.c, keyproc.c, letskencrypt.1 (tags: VERSION_0_1_9),
+ main.c: When using -N or -n, try to open the key-file first, then
+ only create it if it doesn't exist. This allows using -nN even
+ after first creating the files. From
+ https://github.com/kristapsdz/letskencrypt/issues/8
+
+2016-07-12 23:51 kristaps
+
+ * letskencrypt.1: Fix example and reorder exit status
+ documentation.
+
+2016-07-12 23:12 kristaps
+
+ * letskencrypt.1: Note new exit codes, change "mkdir -m" for mkdir
+ and chmod (not all systems have the -m flag), and use the return
+ codes in the example.
+
+2016-07-12 23:06 kristaps
+
+ * main.c: Fix usage message, fix error message to be a bit more
+ useful (as noted in
+ https://github.com/kristapsdz/letskencrypt-portable/issues/3 )
+ and finally change the error code to be "2" if nothing changed on
+ the disc, otherwise 0 on success (the certificates updated) and 1
+ on failure.
+
+2016-07-12 23:04 kristaps
+
+ * fileproc.c (tags: VERSION_0_1_9): Return a special error code
+ when we update certificates.
+
+2016-07-12 23:02 kristaps
+
+ * extern.h, util.c (tags: VERSION_0_1_10, VERSION_0_1_9): Add check
+ for extended error code (i.e., exit status of 2).
+
+2016-07-12 00:11 kristaps
+
+ * util.c (tags: VERSION_0_1_8): Silence a coverity issue. No
+ logical change.
+
+2016-07-11 23:42 kristaps
+
+ * main.c (tags: VERSION_0_1_8): Fix access invocation.
+
+2016-07-11 23:22 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_1_8): Add manual bits for -N,
+ domain key registration.
+
+2016-07-11 23:19 kristaps
+
+ * main.c: Turn on domain key creation.
+
+2016-07-11 23:18 kristaps
+
+ * keyproc.c (tags: VERSION_0_1_8): Note domain key, not account
+ key.
+
+2016-07-11 23:18 kristaps
+
+ * Makefile (tags: VERSION_0_1_9, VERSION_0_1_8), keyproc.c: Adding
+ key creation to keyproc.
+
+2016-07-11 23:08 kristaps
+
+ * Makefile, acctproc.c (tags: VERSION_0_1_8), rsa.c (tags:
+ VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8), rsa.h (tags:
+ VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8): Move rsa key
+ creation and loading into their own file (and header) for usage
+ (current) by acctproc and (pending) keyproc.
+
+2016-07-11 23:07 kristaps
+
+ * extern.h (tags: VERSION_0_1_8): Cosmetic fix.
+
+2016-07-11 22:40 kristaps
+
+ * extern.h, keyproc.c, main.c: Beginning of code to let the keyproc
+ create a new RSA domain key. This was prompted by
+ https://github.com/kristapsdz/letskencrypt/issues/7
+
+2016-07-09 22:34 kristaps
+
+ * letskencrypt.1: Add some example usage.
+
+2016-07-02 04:24 kristaps
+
+ * http.c (tags: VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8): On
+ OpenBSD 5.7, tls_read and family behave strangely: account for
+ that.
+
+2016-07-02 02:59 kristaps
+
+ * http.c: OpenBSD 5.7 needs stdint.h for uintptr_t.
+
+2016-07-02 00:19 kristaps
+
+ * main.c: Use isalnum instead of isalpha for domain name
+ validation. Submitted by Remco and as
+ https://github.com/kristapsdz/letskencrypt/pull/5
+
+2016-06-27 23:25 kristaps
+
+ * acctproc.c (tags: VERSION_0_1_7): Have creation of account key be
+ properly umasked. From a patch by Remco---thanks!
+
+2016-06-27 22:51 kristaps
+
+ * README.md (tags: VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8,
+ VERSION_0_1_7): Update to note NetBSD.
+
+2016-06-25 04:45 kristaps
+
+ * acctproc.c: Also move the key creation into an RSA-specific
+ format, directly from a patch by Remco---thanks!
+
+2016-06-25 04:38 kristaps
+
+ * acctproc.c: Split out more RSA-specific functions, from a
+ modified patch by Remco---thanks!
+
+2016-06-25 04:22 kristaps
+
+ * acctproc.c, extern.h (tags: VERSION_0_1_7), json.c (tags:
+ VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8, VERSION_0_1_7):
+ Note RSA-specific functions as patched by Remco---thanks!
+
+2016-06-25 04:03 kristaps
+
+ * main.c (tags: VERSION_0_1_7): Memory leak in error path.
+
+2016-06-25 03:59 kristaps
+
+ * dnsproc.c (tags: VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8,
+ VERSION_0_1_7), main.c: Initialise variable and also downgrade
+ "cached" message to trace mode.
+
+2016-06-25 00:57 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_1_7): Document multi-domain
+ setup.
+
+2016-06-25 00:50 kristaps
+
+ * netproc.c (tags: VERSION_0_1_9, VERSION_0_1_8, VERSION_0_1_7):
+ Don't modify the input buffer when tracing!
+
+2016-06-25 00:18 kristaps
+
+ * main.c: Introduce -m (not documented while I test it) that
+ appends the initial domain to all paths. This makes it easier to
+ use in systems where one's invoking letskencrypt multiple times.
+ Also add some simple validation of the domain names to prevent
+ them from (1) trampling the directory structure and (2) being
+ bogus in general.
+
+2016-06-24 23:16 kristaps
+
+ * netproc.c: Do what the documentation says regarding -v -v and
+ dump buffers.
+
+2016-06-03 05:02 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_1_6): Be more specific about
+ RSA-ness.
+
+2016-06-03 05:00 kristaps
+
+ * keyproc.c (tags: VERSION_0_1_7, VERSION_0_1_6): Remove dependency
+ on RSA for the domain key. This completely lifts the
+ restrictions, as the certificate creation is opaque. This is
+ from a patch by Remco--thanks!
+
+2016-06-03 04:58 kristaps
+
+ * acctproc.c (tags: VERSION_0_1_6): Require key to be RSA (for
+ now). This builds on a patch submitted by Remco--thank you!
+
+2016-06-03 04:50 kristaps
+
+ * acctproc.c: Remove dependency on RSA for account key. This is
+ only the first step, and makes the key extraction be generic. It
+ will need more interoperability with the signing process to
+ actually work.
+
+2016-06-03 03:27 kristaps
+
+ * extern.h (tags: VERSION_0_1_6): Fix typo noted by @kAworu in
+ https://github.com/kristapsdz/letskencrypt/pull/3 -- thanks!
+
+2016-06-02 05:06 kristaps
+
+ * keyproc.c: Fix an error-path memory leak and make more specific
+ notes as to why I'm not touching the memory (right now) of this
+ mystery function. This raised by @kAworu in pull/2 -- thanks!
+
+2016-06-02 04:42 kristaps
+
+ * json.c (tags: VERSION_0_1_6): Handle zero-length arrays and mark
+ file-scoped function as static. The former from a patch by
+ @kAworu in pull/2 -- thanks!
+
+2016-06-02 04:38 kristaps
+
+ * json.c: Correct allocation size as noted by @kAworu in pull/2.
+
+2016-06-02 04:02 kristaps
+
+ * http.c (tags: VERSION_0_1_7, VERSION_0_1_6): Protect against
+ zero-length read (EOF) freeing with the realloc. Found by
+ Remco--thanks!
+
+2016-06-01 15:54 kristaps
+
+ * acctproc.c, certproc.c (tags: VERSION_0_1_10, VERSION_0_1_9,
+ VERSION_0_1_8, VERSION_0_1_7, VERSION_0_1_6), chngproc.c (tags:
+ VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8, VERSION_0_1_7,
+ VERSION_0_1_6), dnsproc.c (tags: VERSION_0_1_6), fileproc.c
+ (tags: VERSION_0_1_8, VERSION_0_1_7, VERSION_0_1_6), keyproc.c,
+ main.c (tags: VERSION_0_1_6), netproc.c (tags: VERSION_0_1_6),
+ revokeproc.c (tags: VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8,
+ VERSION_0_1_7, VERSION_0_1_6): Kick out a lot of old header files
+ we don't use any more.
+
+2016-06-01 15:29 kristaps
+
+ * letskencrypt.1: Remove implementation notes. Suggested by
+ deraadt@ among others.
+
+2016-06-01 15:03 kristaps
+
+ * acctproc.c, certproc.c, dnsproc.c, extern.h, keyproc.c,
+ letskencrypt.1, main.c, netproc.c, revokeproc.c: Prune out
+ setting a user: this is no longer necessary as all procs not
+ fully pledged need the root user for chroot.
+
+2016-06-01 15:01 kristaps
+
+ * util-pledge.c (tags: VERSION_0_1_10, VERSION_0_1_9,
+ VERSION_0_1_8, VERSION_0_1_7, VERSION_0_1_6): Fix compilation.
+
+2016-06-01 14:56 kristaps
+
+ * acctproc.c, certproc.c, dnsproc.c, extern.h, keyproc.c, main.c,
+ netproc.c, revokeproc.c, util-pledge.c: The user-drop now occurs
+ only in the -portable code, so we don't want to carry around all
+ sorts of extra information we don't use.
+
+2016-06-01 14:19 kristaps
+
+ * util-pledge.c: Re-add chroot(2) for file process and challenge
+ process. This is because the whitepath doesn't exist (yet) for
+ the former, and the challenge process has unknown files in a
+ known path.
+
+2016-06-01 14:18 kristaps
+
+ * sandbox-pledge.c (tags: VERSION_0_1_10, VERSION_0_1_9,
+ VERSION_0_1_8, VERSION_0_1_7, VERSION_0_1_6): Remove comment
+ about rename(2) (ack'd by deraadt@) and remove rpath from inet
+ pledge, which was a holdover from libcurl.
+
+2016-06-01 13:33 kristaps
+
+ * main.c: Fix usage. patch by Caspar Schutijser--thanks!
+
+2016-05-26 08:38 kristaps
+
+ * Makefile (tags: VERSION_0_1_7, VERSION_0_1_6), chroot-pledge.c,
+ util-pledge.c: Rename chroot-pledge into util-pledge, which is
+ more appropriate.
+
+2016-05-26 08:30 kristaps
+
+ * Makefile, chroot-pledge.c, dnsproc.c: Make the compiler happy.
+
+2016-05-26 08:25 kristaps
+
+ * chroot-pledge.c, util.c (tags: VERSION_0_1_7, VERSION_0_1_6):
+ What's the point of dropping root privileges if root can't do
+ anything?
+
+2016-05-26 08:19 kristaps
+
+ * Makefile, chroot-pledge.c, extern.h, main.c, util.c: "Embrace the
+ pledge".
+
+2016-05-25 04:46 kristaps
+
+ * http.c (tags: VERSION_0_1_5): Try to close connection properly.
+
+2016-05-25 04:35 kristaps
+
+ * http.c: Figure out what tls_close does with the socket.
+
+2016-05-25 04:34 kristaps
+
+ * revokeproc.c (tags: VERSION_0_1_5): Fix scanning ahead for SAN
+ DNS entries.
+
+2016-05-25 03:47 kristaps
+
+ * netproc.c (tags: VERSION_0_1_5): Have sreq and nreq return the
+ HTTP error or -1, remove some debugging messages, use the new
+ http_get members instead of calling functions. "Clean-up."
+
+2016-05-25 03:46 kristaps
+
+ * http.c, http.h (tags: VERSION_0_1_10, VERSION_0_1_9,
+ VERSION_0_1_8, VERSION_0_1_7, VERSION_0_1_6, VERSION_0_1_5): Make
+ sure the connection is closed as soon as the body is read and
+ also put the head and body buffer pointers into http_get.
+
+2016-05-25 03:43 kristaps
+
+ * dnsproc.c (tags: VERSION_0_1_5): Cache the last DNS response and
+ return that, if the subsequent request is the same.
+
+2016-05-24 17:17 kristaps
+
+ * README.md (tags: VERSION_0_1_6, VERSION_0_1_5): Clean up the
+ readme notes.
+
+2016-05-24 17:10 kristaps
+
+ * http.c: Change feature test.
+
+2016-05-24 14:32 kristaps
+
+ * http.c: Needed for compat glue.
+
+2016-05-24 13:32 kristaps
+
+ * http.c: For the time being, allow tls_read/write to be ifdef'd
+ for two different versions of the API.
+
+2016-05-24 13:03 kristaps
+
+ * Makefile (tags: VERSION_0_1_5): Fix lib.
+
+2016-05-24 11:04 kristaps
+
+ * netproc.c: Don't use strdup() for bodies--they can be binary.
+
+2016-05-24 10:39 kristaps
+
+ * netproc.c: Initial [working] removal of curl from netproc. This
+ replaces the nreq() and sreq() functions with those using http.h.
+ There is still a fair amount of superfluous debugging going on,
+ and the system isn't "optimum" regarding memory at all.
+
+2016-05-24 10:38 kristaps
+
+ * http.c: Weaken TLS validation. Is this necessary?
+
+2016-05-24 10:37 kristaps
+
+ * Makefile: Stop using curl, and instead use the home-grown http.c
+ and -tls.
+
+2016-05-24 10:36 kristaps
+
+ * extern.h, util.c (utags: VERSION_0_1_5): Max the maximum DNS
+ entries be globally known and also add the new dnsproc comm
+ identifiers.
+
+2016-05-24 10:35 kristaps
+
+ * dnsproc.c: Have dnsproc transfer both the IP/IPv6 to netproc and
+ also the family itself. Move the maximum number of queried
+ servers into extern.h.
+
+2016-05-24 09:42 kristaps
+
+ * http.c, http.h: Continue fleshing out http.h implementation.
+
+2016-05-24 04:48 kristaps
+
+ * http.c: Missing header for compilation.
+
+2016-05-24 04:42 kristaps
+
+ * http.c: Start to kick out libcurl with a small HTTP client
+ originally inspired by https://github.com/snimmagadda/http.
+
+2016-05-22 12:04 kristaps
+
+ * README.md (tags: VERSION_0_1_4): Note use of MIT license in
+ JSMN's files.
+
+2016-05-22 12:03 kristaps
+
+ * Makefile, extern.h, json.c (tags: VERSION_0_1_5), netproc.c
+ (utags: VERSION_0_1_4): Kick out json-c in favour of jsmn (with
+ an array->tree wrapper).
+
+2016-05-22 11:55 kristaps
+
+ * jsmn.c, jsmn.h (utags: VERSION_0_1_10, VERSION_0_1_4,
+ VERSION_0_1_5, VERSION_0_1_6, VERSION_0_1_7, VERSION_0_1_8,
+ VERSION_0_1_9): Put license directly into jsmn.c and jsmn.h, just
+ to be clear about it.
+
+2016-05-22 11:54 kristaps
+
+ * jsmn.c, jsmn.h: Add JSMN: https://github.com/zserge/jsmn.
+
+2016-05-22 11:53 kristaps
+
+ * certproc.c (tags: VERSION_0_1_5), revokeproc.c (utags:
+ VERSION_0_1_4): It's not clear whether the lengths returned by
+ the BIO are nil-terminated (and valgrind suggests they aren't),
+ so make sure that they are always nil-terminated.
+
+2016-05-20 13:09 kristaps
+
+ * extern.h, main.c (tags: VERSION_0_1_5, VERSION_0_1_4),
+ revokeproc.c: When we start up, check that the domains listed on
+ the command-line are those on the certificate, if found. NOTE:
+ what if there's no SAN entry at all?
+
+2016-05-20 08:35 kristaps
+
+ * Makefile: Forgotten PREFIX variable.
+
+2016-05-20 08:28 kristaps
+
+ * keyproc.c (tags: VERSION_0_1_5, VERSION_0_1_4): Fix erroneous
+ check of realloc return value.
+
+2016-05-20 08:07 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_1_5, VERSION_0_1_4,
+ VERSION_0_1_3): Fix typo.
+
+2016-05-20 08:07 kristaps
+
+ * acctproc.c (tags: VERSION_0_1_5, VERSION_0_1_4), certproc.c,
+ chngproc.c (tags: VERSION_0_1_5, VERSION_0_1_4), dnsproc.c (tags:
+ VERSION_0_1_4), fileproc.c (tags: VERSION_0_1_5, VERSION_0_1_4),
+ keyproc.c, main.c, netproc.c, revokeproc.c (utags:
+ VERSION_0_1_3): No need to have the dropfs, dropprivs, or sandbox
+ functions double-report their error.
+
+2016-05-20 08:05 kristaps
+
+ * main.c: Pre-check that the files exist: no need to fork if we
+ don't need to. Also, the console can get spammed by multiple
+ procs writing into stderr.
+
+2016-05-20 05:49 kristaps
+
+ * Makefile (tags: VERSION_0_1_3), README.md (tags: VERSION_0_1_3),
+ letskencrypt.dot: Strip out dot-file and www rule: this all goes
+ into the letskencrypt-www repo. Strip down the README.md file to
+ only what's necessary.
+
+2016-05-19 17:02 kristaps
+
+ * chngproc.c: Convert chngproc to ignore reader failure.
+
+2016-05-19 17:00 kristaps
+
+ * certproc.c: Convert certproc to ignore reader failure.
+
+2016-05-19 16:49 kristaps
+
+ * keyproc.c: Rename label to "out" (consistency) and allow for
+ reader failure.
+
+2016-05-19 16:40 kristaps
+
+ * acctproc.c: Have acctproc properly handle reader termination.
+
+2016-05-19 16:33 kristaps
+
+ * revokeproc.c: Last nit: make writestr also be ok if the reader
+ has exited.
+
+2016-05-19 16:31 kristaps
+
+ * revokeproc.c: Have revokeproc properly handle the case where the
+ reader fails.
+
+2016-05-19 16:29 kristaps
+
+ * util.c (tags: VERSION_0_1_4, VERSION_0_1_3): Have the writer
+ functions notify us whether the reader has exited.
+
+2016-05-19 15:56 kristaps
+
+ * acctproc.c, certproc.c, chngproc.c, dnsproc.c, keyproc.c,
+ netproc.c, revokeproc.c: Have writeop, writestr, and writebuf all
+ return -1 on failure, 0 on end of file (epipe), and 1 on success.
+ This addresses all the callers.
+
+2016-05-19 09:01 kristaps
+
+ * README.md (tags: VERSION_0_0_5): We're no longer just using the
+ staging server. Here we go!
+
+2016-05-19 09:01 kristaps
+
+ * letskencrypt.1, main.c (utags: VERSION_0_0_5): Document the -s
+ flag.
+
+2016-05-19 08:59 kristaps
+
+ * extern.h (tags: VERSION_0_1_3, VERSION_0_0_5), main.c, netproc.c
+ (tags: VERSION_0_0_5): Flip on real versus staging servers.
+ bsd.lv is now eating its dogfood.
+
+2016-05-19 08:58 kristaps
+
+ * keyproc.c (tags: VERSION_0_0_5): Fix how SAN is registered with
+ the key. In prior versions, we were having one SAN entry per
+ domain. However, apparently this is not allowed; instead we now
+ have a single SAN extension entry with the full list.
+
+2016-05-19 07:58 kristaps
+
+ * certproc.c, fileproc.c (utags: VERSION_0_0_4, VERSION_0_0_5):
+ Make filenames in debug messages more meaningful.
+
+2016-05-19 07:22 kristaps
+
+ * netproc.c (tags: VERSION_0_0_4): Forgot to close revokeproc
+ channel.
+
+2016-05-19 06:10 kristaps
+
+ * README.md (tags: VERSION_0_0_4): Remove the coverity note (that's
+ going into the -portable version).
+
+2016-05-19 06:09 kristaps
+
+ * extern.h (tags: VERSION_0_0_4), json.c (tags: VERSION_0_1_3,
+ VERSION_0_0_5, VERSION_0_0_4), netproc.c: Significantly clean up
+ the handling of HTTP document bodies: first, only invoke the JSON
+ functions locally, within a doXXXX function; second, don't read
+ into the JSON parser, but into an intermediary buffer (allowing
+ us to dump it on error); third, move some fetch bodies from the
+ main netproc() function into their own functions; and lastly,
+ store the CA nonce agency as a variable (we'll use this later
+ when using other servers).
+
+2016-05-19 05:51 kristaps
+
+ * main.c (tags: VERSION_0_0_4): Remove debugging message.
+
+2016-05-19 05:09 kristaps
+
+ * netproc.c: Push communication-related parameters into struct
+ conn. Makes the code a bit more readable.
+
+2016-05-19 05:08 kristaps
+
+ * main.c: No functional change: just order getopt() parameters for
+ easier search.
+
+2016-05-18 16:03 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_0_4), main.c: Allow overriding
+ the priv-drop user.
+
+2016-05-18 15:32 kristaps
+
+ * util.c (tags: VERSION_0_0_5, VERSION_0_0_4): Use strsignal()
+ instead of a hack.
+
+2016-05-18 15:22 kristaps
+
+ * dbg.c (tags: VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8,
+ VERSION_0_1_7, VERSION_0_1_6, VERSION_0_1_5, VERSION_0_1_4,
+ VERSION_0_1_3, VERSION_0_0_5, VERSION_0_0_4), extern.h, main.c,
+ util.c: Remove all logging in favour of warnx et al. Remove
+ wrong-headed attempt at setproctitle.
+
+2016-05-18 13:01 kristaps
+
+ * acctproc.c (tags: VERSION_0_0_5, VERSION_0_0_4), certproc.c,
+ chngproc.c (tags: VERSION_0_0_5, VERSION_0_0_4), dbg.c, extern.h,
+ fileproc.c, json.c, keyproc.c (tags: VERSION_0_0_4), netproc.c,
+ revokeproc.c (tags: VERSION_0_0_5, VERSION_0_0_4),
+ sandbox-pledge.c (tags: VERSION_0_1_5, VERSION_0_1_4,
+ VERSION_0_1_3, VERSION_0_0_5, VERSION_0_0_4), util.c: Kick out
+ dowarn in favour of warn.
+
+2016-05-18 12:53 kristaps
+
+ * acctproc.c, certproc.c, chngproc.c, dbg.c, dnsproc.c (tags:
+ VERSION_0_0_5, VERSION_0_0_4), extern.h, fileproc.c, keyproc.c,
+ netproc.c, revokeproc.c, util.c:
+ Replace dowarnx() with warnx().
+
+2016-05-18 12:35 kristaps
+
+ * main.c: Start using setproctitle(). First step in kicking out
+ dbg.c.
+
+2016-05-18 11:44 kristaps
+
+ * util.c: Move setresuid goop into -portable.
+
+2016-05-18 07:18 kristaps
+
+ * README.md: Fix broken link.
+
+2016-05-18 07:10 kristaps
+
+ * README.md (tags: VERSION_0_0_3): Note -portable and FreeBSD.
+
+2016-05-18 06:37 kristaps
+
+ * dnsproc.c (tags: VERSION_0_0_3): FreeBSD nit. This will be
+ smoothed out in subsequent improvement of -portable.
+
+2016-05-18 06:36 kristaps
+
+ * README.md: Note that we now do revocation.
+
+2016-05-18 06:19 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_0_3): Update the manpage with
+ revocation instructions.
+
+2016-05-18 05:54 kristaps
+
+ * README.md, sandbox-pledge.c (tags: VERSION_0_0_3): Require
+ OpenBSD >= 5.9.
+
+2016-05-18 05:49 kristaps
+
+ * util.c (tags: VERSION_0_0_3): Catch buffers larger than BUFSIZ
+ bytes.
+
+2016-05-18 05:32 kristaps
+
+ * sandbox-pledge.c: Add forgotten break statement.
+
+2016-05-18 05:30 kristaps
+
+ * Makefile (tags: VERSION_0_0_5, VERSION_0_0_4, VERSION_0_0_3):
+ Clean up the Makefile now that we have less cruft.
+
+2016-05-18 05:25 kristaps
+
+ * Makefile, README.md, config.h, main.c (tags: VERSION_0_0_3),
+ sandbox-pledge.c: Start stripping out compatibility, which is now
+ in letskencrypt-portable.
+
+2016-05-18 05:00 kristaps
+
+ * Makefile, acctproc.c (tags: VERSION_0_0_3), certproc.c (tags:
+ VERSION_0_0_3), chngproc.c (tags: VERSION_0_0_3), dnsproc.c,
+ extern.h (tags: VERSION_0_0_3), fileproc.c (tags: VERSION_0_0_3),
+ keyproc.c (tags: VERSION_0_0_3), netproc.c (tags: VERSION_0_0_3),
+ revokeproc.c (tags: VERSION_0_0_3), sandbox-pledge.c: Split all
+ sandbox operations into their own file. This is part of the
+ ongoing re-structure into the main and -portable branch.
+
+2016-05-18 04:31 kristaps
+
+ * Makefile, acctproc.c, base64.c (tags: VERSION_0_1_10,
+ VERSION_0_1_9, VERSION_0_1_8, VERSION_0_1_7, VERSION_0_1_6,
+ VERSION_0_1_5, VERSION_0_1_4, VERSION_0_1_3, VERSION_0_0_5,
+ VERSION_0_0_4, VERSION_0_0_3), certproc.c, chngproc.c, config.h,
+ dbg.c (tags: VERSION_0_0_3), dnsproc.c, extern.h, fileproc.c,
+ json.c (tags: VERSION_0_0_3), keyproc.c, main.c, netproc.c,
+ revokeproc.c, util.c: Finish the revocation function. Also,
+ start to lay the groundwork for a -portable and OpenBSD version
+ of the software with a guarded config.h inclusion.
+
+2016-05-17 08:06 kristaps
+
+ * letskencrypt.1: Add note on revokeproc to manpage.
+
+2016-05-17 08:04 kristaps
+
+ * main.c: Plug still-open fd.
+
+2016-05-17 07:55 kristaps
+
+ * netproc.c: Re-add accidentally-removed check for certificate
+ non-expiration.
+
+2016-05-17 07:52 kristaps
+
+ * README.md, extern.h, letskencrypt.1, letskencrypt.dot (tags:
+ VERSION_0_0_5, VERSION_0_0_4, VERSION_0_0_3), main.c, netproc.c,
+ revokeproc.c: Check for expiration date of certificate, if found.
+ This makes it possible to simply run letskencrypt as a cronjob
+ without worrying about overloading the ACME server.
+
+2016-05-17 07:51 kristaps
+
+ * chngproc.c: Have chngproc's magic testing phase (which isn't an
+ official option) not have files made in the challengedir at all.
+
+2016-05-17 05:47 kristaps
+
+ * Makefile, dbg.c, revokeproc.c: Add in the initial framework for
+ checking certificate revocation times.
+
+2016-05-17 05:46 kristaps
+
+ * netproc.c: Continue cleaning up operations (in netproc).
+
+2016-05-17 05:45 kristaps
+
+ * acctproc.c, certproc.c, chngproc.c, dnsproc.c, extern.h,
+ fileproc.c, main.c, util.c: Continue making operations more
+ semantically meaningful. Continue building in revocation
+ facility.
+
+2016-05-16 17:21 kristaps
+
+ * certproc.c, extern.h, json.c, main.c, netproc.c: Initial steps of
+ revocation. This is pretty straightforward.
+
+2016-05-16 15:48 kristaps
+
+ * README.md: Continue to polish the documentation.
+
+2016-05-16 15:25 kristaps
+
+ * acctproc.c, certproc.c, chngproc.c, fileproc.c: When closing out,
+ close sockets first to cause depending processes to bail earlier.
+
+2016-05-16 15:25 kristaps
+
+ * netproc.c: Failing start-up for account or key proc doesn't error
+ us.
+
+2016-05-16 15:24 kristaps
+
+ * letskencrypt.1: Calm down people afraid of root.
+
+2016-05-16 15:14 kristaps
+
+ * acctproc.c, certproc.c, extern.h, keyproc.c, netproc.c, util.c:
+ Have the keyproc and acctproc notify the netproc when they've
+ started, and have the netproc wait til they have: there's no
+ point in talking to Let's Encrypt if these services haven't
+ started.
+
+2016-05-16 12:07 kristaps
+
+ * Makefile, README.md, letskencrypt.1, letskencrypt.dot: Add some
+ media for the GH site and fix a mistake in the manpage.
+
+2016-05-16 10:10 kristaps
+
+ * README.md: Update README a bit.
+
+2016-05-16 10:06 kristaps
+
+ * letskencrypt.1: Be more terse in the implementation notes.
+
+2016-05-16 09:58 kristaps
+
+ * chngproc.c, extern.h, main.c: Add a secret and undocumented
+ feature that allows me to create files in the challenge directory
+ on another system. Don't use this.
+
+2016-05-16 09:57 kristaps
+
+ * dnsproc.c: Have dnsproc properly return (and not exit) like the
+ other processes.
+
+2016-05-16 09:51 kristaps
+
+ * netproc.c: Fix CID 111099.
+
+2016-05-16 09:47 kristaps
+
+ * util.c: Fix CID 111100.
+
+2016-05-16 09:38 kristaps
+
+ * README.md, letskencrypt.1: More documentation notes on why Linux
+ and Mac OS X are a bad idea.
+
+2016-05-16 09:24 kristaps
+
+ * letskencrypt.1: Update notes on dnsproc.
+
+2016-05-16 09:21 kristaps
+
+ * README.md: Clean up the README.
+
+2016-05-16 09:20 kristaps
+
+ * chngproc.c: Don't let the testing code make it out.
+
+2016-05-16 09:19 kristaps
+
+ * netproc.c: We don't close any fds before the out, so don't check
+ them against -1.
+
+2016-05-16 09:19 kristaps
+
+ * netproc.c: Remove comment that no longer belongs.
+
+2016-05-16 09:18 kristaps
+
+ * acctproc.c: Account key doesn't need read permissions.
+
+2016-05-16 09:16 kristaps
+
+ * main.c: Fix an off-by-one and also fix closing the DNS file
+ descriptor.
+
+2016-05-16 09:15 kristaps
+
+ * netproc.c: Push the DNS resolution into one function for clarity.
+ Prune a lot of unused variables.
+
+2016-05-16 08:27 kristaps
+
+ * dnsproc.c: Add the dnsproc manager. This was noted by deraadt@.
+ This does nothing but looks up addresses as used by netproc.
+
+2016-05-16 04:51 kristaps
+
+ * Makefile, chngproc.c, dbg.c, extern.h, main.c, netproc.c, util.c:
+ Initial check-in of a separate process for DNS management.
+
+2016-05-16 03:57 kristaps
+
+ * letskencrypt.1: Fix typo found by Anthony Bentley--thanks!
+
+2016-05-15 13:31 kristaps
+
+ * main.c: Fix for https://github.com/kristapsdz/letskencrypt/pull/1
+ posted by https://github.com/pozdnychev -- thanks!
+
+2016-05-15 11:47 kristaps
+
+ * README.md, letskencrypt.1: Documentation on Linux.
+
+2016-05-15 11:44 kristaps
+
+ * fileproc.c, netproc.c: Catch __attribute__ warnings.
+
+2016-05-15 11:39 kristaps
+
+ * netproc.c: Fully demonstrate that chroot doesn't work on Linux
+ for netproc.
+
+2016-05-15 11:38 kristaps
+
+ * chngproc.c: Linux compatibility.
+
+2016-05-15 11:37 kristaps
+
+ * util.c: Drop privs in the correct order and make sure sys_signame
+ is not used on Linux.
+
+2016-05-15 11:00 kristaps
+
+ * chngproc.c, fileproc.c: Continue minimising the pledges.
+
+2016-05-15 10:39 kristaps
+
+ * chngproc.c: Reduce the number of pledges in chngproc.
+
+2016-05-15 10:24 kristaps
+
+ * certproc.c, chngproc.c, extern.h, fileproc.c, keyproc.c, main.c,
+ netproc.c, util.c: Add forgotten waitpid for COMP_FILE and add
+ some readops to make sure that netproc failing doesn't cause
+ short reads and exits.
+
+2016-05-15 09:53 kristaps
+
+ * extern.h, json.c: Function attributes for messages.
+
+2016-05-15 08:06 kristaps
+
+ * acctproc.c, util.c: Fix a segfault.
+
+2016-05-15 07:41 kristaps
+
+ * json.c, netproc.c: Properly catch when the challenge has been
+ verified.
+
+2016-05-15 07:35 kristaps
+
+ * README.md, letskencrypt.1: More documentation.
+
+2016-05-15 06:58 kristaps
+
+ * json.c: Add more documentation.
+
+2016-05-15 06:57 kristaps
+
+ * keyproc.c: Catch error return code.
+
+2016-05-15 06:56 kristaps
+
+ * acctproc.c: Documents key bits.
+
+2016-05-15 06:56 kristaps
+
+ * letskencrypt.1: Consistency in naming "Let's Encrypt".
+
+2016-05-15 06:48 kristaps
+
+ * acctproc.c, certproc.c, chngproc.c, extern.h, fileproc.c,
+ keyproc.c, main.c, netproc.c, util.c: Look again at the
+ relinquishing of privilege. Move privilege-dropping before the
+ pledge just for consistency.
+
+2016-05-14 21:39 kristaps
+
+ * README.md: Again, fix links.
+
+2016-05-14 21:37 kristaps
+
+ * README.md: Fix links.
+
+2016-05-14 21:31 kristaps
+
+ * README.md: Small notes.
+
+2016-05-14 21:01 kristaps
+
+ * certproc.c: De-constify.
+
+2016-05-14 21:00 kristaps
+
+ * certproc.c, extern.h, fileproc.c, letskencrypt.1, main.c, util.c:
+ Cleanup and abstractions.
+
+2016-05-14 20:33 kristaps
+
+ * certproc.c, extern.h, fileproc.c, letskencrypt.1, main.c,
+ netproc.c, util.c: Fully download chain and fullchain.
+
+2016-05-14 20:32 kristaps
+
+ * dbg.c: Fix stdout/stderr in debugging messages.
+
+2016-05-14 18:34 kristaps
+
+ * certproc.c, netproc.c: Minor formatting.
+
+2016-05-14 18:05 kristaps
+
+ * Makefile, certproc.c, dbg.c, extern.h, fileproc.c, main.c,
+ util.c: Split out reading and verifying the certificate from
+ writing to the file.
+
+2016-05-14 17:46 kristaps
+
+ * acctproc.c, certproc.c, chngproc.c, keyproc.c, main.c, netproc.c:
+ Move proccomp setting into main.
+
+2016-05-14 16:43 kristaps
+
+ * acctproc.c, extern.h, json.c: Move more JSON into json.c.
+
+2016-05-14 16:26 kristaps
+
+ * chngproc.c, dbg.c, extern.h, json.c, netproc.c: Move all JSON
+ things into json.c.
+
+2016-05-14 15:42 kristaps
+
+ * acctproc.c, certproc.c, extern.h, keyproc.c, main.c: Clean-ups
+ for better readability.
+
+2016-05-14 10:39 kristaps
+
+ * Makefile: Have BSD form by the default in the Makefile.
+
+2016-05-14 10:38 kristaps
+
+ * README.md: Document compilation on Liinux.
+
+2016-05-14 10:38 kristaps
+
+ * Makefile, acctproc.c, json.c, keyproc.c, main.c, netproc.c,
+ util.c: Compiling on Linux.
+
+2016-05-14 10:25 kristaps
+
+ * LICENSE.md (tags: VERSION_0_1_10, VERSION_0_1_9, VERSION_0_1_8,
+ VERSION_0_1_7, VERSION_0_1_6, VERSION_0_1_5, VERSION_0_1_4,
+ VERSION_0_1_3, VERSION_0_0_5, VERSION_0_0_4, VERSION_0_0_3),
+ README.md: GitHub files.
+
+2016-05-14 10:14 kristaps
+
+ * letskencrypt.1 (tags: VERSION_0_0_2): Naming.
+
+2016-05-14 10:13 kristaps
+
+ * main.c, netproc.c (utags: VERSION_0_0_2): Set temporary directory
+ permissions.
+
+2016-05-14 10:06 kristaps
+
+ * letskencrypt.1: More documentation.
+
+2016-05-14 10:03 kristaps
+
+ * acctproc.c (tags: VERSION_0_0_2), certproc.c (tags:
+ VERSION_0_0_2), chngproc.c (tags: VERSION_0_0_2), extern.h (tags:
+ VERSION_0_0_2), keyproc.c (tags: VERSION_0_0_2), letskencrypt.1,
+ main.c, netproc.c: Priv dropping and more documentation.
+
+2016-05-14 09:38 kristaps
+
+ * base64.c (tags: VERSION_0_0_2), dbg.c (tags: VERSION_0_0_2),
+ extern.h, main.c, netproc.c, util.c (tags: VERSION_0_0_2):
+ Privilege dropping (beginning).
+
+2016-05-14 09:12 kristaps
+
+ * letskencrypt.1: More documentation.
+
+2016-05-14 07:18 kristaps
+
+ * chngproc.c, extern.h, json.c (tags: VERSION_0_0_2),
+ letskencrypt.1, main.c, netproc.c, util.c: Fully-working cycle
+ with SAN enabled.
+
+2016-05-13 18:16 kristaps
+
+ * Makefile (tags: VERSION_0_0_2), acctproc.c, extern.h, keyproc.c,
+ letskencrypt.1, main.c, netproc.c: Add manpage, continue working
+ in SAN.
+
+2016-05-13 16:48 kristaps
+
+ * certproc.c, dbg.c, extern.h, json.c, keyproc.c, main.c,
+ netproc.c, util.c: Add initial support for SAN.
+
+2016-05-13 12:23 kristaps
+
+ * acctproc.c, certproc.c: First fully-working version.
+
+2016-05-13 11:49 kristaps
+
+ * Makefile, acctproc.c, certproc.c, chngproc.c, dbg.c, extern.h,
+ keyproc.c, main.c, netproc.c, util.c: Add certificate process to
+ manage certificates. Lots of cleanup w/r/t logging and process
+ titles.
+
+2016-05-13 11:08 kristaps
+
+ * base64.c, extern.h, keyproc.c, netproc.c, util.c: Certificate
+ submission and download.
+
+2016-05-13 10:46 kristaps
+
+ * acctproc.c, chngproc.c, extern.h, keyproc.c, netproc.c, util.c:
+ Fully working submission of certificate to CA.
+
+2016-05-13 09:59 kristaps
+
+ * Makefile, acctproc.c, base64.c, chngproc.c, dbg.c, extern.h,
+ json.c, keyproc.c, main.c, netproc.c, util.c: Split out JSON
+ handling code.
+
+2016-05-13 09:44 kristaps
+
+ * netproc.c, util.c: Clean some bugs found with scan-build.
+
+2016-05-13 09:40 kristaps
+
+ * chngproc.c, netproc.c: Full challenge-request-response cycle in
+ place.
+
+2016-05-13 09:14 kristaps
+
+ * acctproc.c, chngproc.c, keyproc.c, main.c, netproc.c, util.c:
+ Compiling on OpenBSD (prior to pledge).
+
+2016-05-13 09:06 kristaps
+
+ * netproc.c: Retrying for challenge.
+
+2016-05-13 08:52 kristaps
+
+ * acctproc.c, chngproc.c, extern.h, main.c, netproc.c, util.c:
+ Cleaning up writing/reading.
+
+2016-05-13 08:20 kristaps
+
+ * Makefile, acctproc.c, chngproc.c, extern.h, keyproc.c, main.c,
+ netproc.c, util.c: Moving on to functionality of the
+ challenge-responds.
+
+2016-05-13 04:29 kristaps
+
+ * acctproc.c, base64.c, extern.h, main.c, netproc.c, util.c: New
+ keys are now properly submitted.
+
+2016-05-12 18:55 kristaps
+
+ * acctproc.c, dbg.c, netproc.c: Full connection to acme, fixing
+ syntax errors.
+
+2016-05-12 17:33 kristaps
+
+ * Makefile, acctproc.c, netproc.c: Parsing of JSON directory.
+
+2016-05-12 17:09 kristaps
+
+ * Makefile, acctproc.c, base64.c, dbg.c, extern.h, keyproc.c,
+ main.c, netproc.c, util.c: Still being built: push signing into
+ acctproc and have netproc properly start to handle the CA
+ interaction.
+
+2016-05-12 08:34 kristaps
+
+ * acctproc.c, keyproc.c, netproc.c: Full creation of request
+ thumbprint.
+
+2016-05-12 08:07 kristaps
+
+ * Makefile, acctproc.c, dbg.c, extern.h, keyproc.c, main.c,
+ netproc.c (utags: VERSION_0): Import first.
+
+2016-05-12 08:07 kristaps
+
+ * Makefile, acctproc.c, dbg.c, extern.h, keyproc.c, main.c,
+ netproc.c: Initial revision
+
diff --git a/usr.sbin/acme-client/Makefile b/usr.sbin/acme-client/Makefile
new file mode 100644
index 00000000000..b1633c2996e
--- /dev/null
+++ b/usr.sbin/acme-client/Makefile
@@ -0,0 +1,40 @@
+PREFIX = /usr/local
+CFLAGS += -g -W -Wall
+OBJS = acctproc.o \
+ base64.o \
+ certproc.o \
+ chngproc.o \
+ dbg.o \
+ dnsproc.o \
+ fileproc.o \
+ http.o \
+ jsmn.o \
+ json.o \
+ keyproc.o \
+ main.o \
+ netproc.o \
+ revokeproc.o \
+ rsa.o \
+ sandbox-pledge.o \
+ util.o \
+ util-pledge.o
+
+letskencrypt: $(OBJS)
+ $(CC) -o $@ $(OBJS) -ltls -lssl -lcrypto
+
+rsa.o acctproc.o keyproc.o: rsa.h
+
+jsmn.o json.o: jsmn.h
+
+http.o netproc.o: http.h
+
+install: letskencrypt
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ mkdir -p $(DESTDIR)$(PREFIX)/man/man1
+ install -m 0755 letskencrypt $(DESTDIR)$(PREFIX)/bin
+ install -m 0644 letskencrypt.1 $(DESTDIR)$(PREFIX)/man/man1
+
+$(OBJS): extern.h
+
+clean:
+ rm -f letskencrypt $(OBJS)
diff --git a/usr.sbin/acme-client/acctproc.c b/usr.sbin/acme-client/acctproc.c
new file mode 100644
index 00000000000..9c0a04dfce3
--- /dev/null
+++ b/usr.sbin/acme-client/acctproc.c
@@ -0,0 +1,440 @@
+/* $Id: acctproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+
+#include "extern.h"
+#include "rsa.h"
+
+/*
+ * Converts a BIGNUM to the form used in JWK.
+ * This is essentially a base64-encoded big-endian binary string
+ * representation of the number.
+ */
+static char *
+bn2string(const BIGNUM *bn)
+{
+ int len;
+ char *buf, *bbuf;
+
+ /* Extract big-endian representation of BIGNUM. */
+
+ len = BN_num_bytes(bn);
+ if (NULL == (buf = malloc(len))) {
+ warn("malloc");
+ return(NULL);
+ } else if (len != BN_bn2bin(bn, (unsigned char *)buf)) {
+ warnx("BN_bn2bin");
+ free(buf);
+ return(NULL);
+ }
+
+ /* Convert to base64url. */
+
+ if (NULL == (bbuf = base64buf_url(buf, len))) {
+ warnx("base64buf_url");
+ free(buf);
+ return(NULL);
+ }
+
+ free(buf);
+ return(bbuf);
+}
+
+/*
+ * Extract the relevant RSA components from the key and create the JSON
+ * thumbprint from them.
+ */
+static char *
+op_thumb_rsa(EVP_PKEY *pkey)
+{
+ char *exp, *mod, *json;
+ RSA *r;
+
+ exp = mod = json = NULL;
+
+ if (NULL == (r = EVP_PKEY_get1_RSA(pkey)))
+ warnx("EVP_PKEY_get1_RSA");
+ else if (NULL == (mod = bn2string(r->n)))
+ warnx("bn2string");
+ else if (NULL == (exp = bn2string(r->e)))
+ warnx("bn2string");
+ else if (NULL == (json = json_fmt_thumb_rsa(exp, mod)))
+ warnx("json_fmt_thumb_rsa");
+
+ free(exp);
+ free(mod);
+ return(json);
+}
+
+/*
+ * The thumbprint operation is used for the challenge sequence.
+ */
+static int
+op_thumbprint(int fd, EVP_PKEY *pkey)
+{
+ char *thumb, *dig64;
+ int rc;
+ unsigned int digsz;
+ unsigned char *dig;
+
+ EVP_MD_CTX *ctx;
+
+ rc = 0;
+ thumb = dig64 = NULL;
+ dig = NULL;
+ ctx = NULL;
+
+ /* Construct the thumbprint input itself. */
+
+ switch (EVP_PKEY_type(pkey->type)) {
+ case EVP_PKEY_RSA:
+ if (NULL != (thumb = op_thumb_rsa(pkey)))
+ break;
+ goto out;
+ default:
+ warnx("EVP_PKEY_type: unknown key type");
+ goto out;
+ }
+
+ /*
+ * Compute the SHA256 digest of the thumbprint then
+ * base64-encode the digest itself.
+ * If the reader is closed when we write, ignore it (we'll pick
+ * it up in the read loop).
+ */
+
+ if (NULL == (dig = malloc(EVP_MAX_MD_SIZE))) {
+ warn("malloc");
+ goto out;
+ } else if (NULL == (ctx = EVP_MD_CTX_create())) {
+ warnx("EVP_MD_CTX_create");
+ goto out;
+ } else if ( ! EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
+ warnx("EVP_SignInit_ex");
+ goto out;
+ } else if ( ! EVP_DigestUpdate(ctx, thumb, strlen(thumb))) {
+ warnx("EVP_SignUpdate");
+ goto out;
+ } else if ( ! EVP_DigestFinal_ex(ctx, dig, &digsz)) {
+ warnx("EVP_SignFinal");
+ goto out;
+ } else if (NULL == (dig64 = base64buf_url((char *)dig, digsz))) {
+ warnx("base64buf_url");
+ goto out;
+ } else if (writestr(fd, COMM_THUMB, dig64) < 0)
+ goto out;
+
+ rc = 1;
+out:
+ if (NULL != ctx)
+ EVP_MD_CTX_destroy(ctx);
+
+ free(thumb);
+ free(dig);
+ free(dig64);
+ return(rc);
+}
+
+static int
+op_sign_rsa(char **head, char **prot, EVP_PKEY *pkey, const char *nonce)
+{
+ RSA *r;
+ char *exp, *mod;
+ int rc;
+
+ *head = *prot = exp = mod = NULL;
+ rc = 0;
+
+ /*
+ * First, extract relevant portions of our private key.
+ * Then construct the public header.
+ * Finally, format the header combined with the nonce.
+ */
+
+ if (NULL == (r = EVP_PKEY_get1_RSA(pkey)))
+ warnx("EVP_PKEY_get1_RSA");
+ else if (NULL == (mod = bn2string(r->n)))
+ warnx("bn2string");
+ else if (NULL == (exp = bn2string(r->e)))
+ warnx("bn2string");
+ else if (NULL == (*head = json_fmt_header_rsa(exp, mod)))
+ warnx("json_fmt_header_rsa");
+ else if (NULL == (*prot = json_fmt_protected_rsa(exp, mod, nonce)))
+ warnx("json_fmt_protected_rsa");
+ else
+ rc = 1;
+
+ free(exp);
+ free(mod);
+ return(rc);
+}
+
+/*
+ * Operation to sign a message with the account key.
+ * This requires the sender ("fd") to provide the payload and a nonce.
+ */
+static int
+op_sign(int fd, EVP_PKEY *pkey)
+{
+ char *nonce, *pay,
+ *pay64, *prot, *prot64, *head,
+ *sign, *dig64, *fin;
+ int cc, rc;
+ unsigned int digsz;
+ unsigned char *dig;
+ EVP_MD_CTX *ctx;
+
+ rc = 0;
+ pay = nonce = head = fin =
+ sign = prot = prot64 = pay64 = dig64 = NULL;
+ dig = NULL;
+ ctx = NULL;
+
+ /* Read our payload and nonce from the requestor. */
+
+ if (NULL == (pay = readstr(fd, COMM_PAY)))
+ goto out;
+ else if (NULL == (nonce = readstr(fd, COMM_NONCE)))
+ goto out;
+
+ /* Base64-encode the payload. */
+
+ if (NULL == (pay64 = base64buf_url(pay, strlen(pay)))) {
+ warnx("base64buf_url");
+ goto out;
+ }
+
+ switch (EVP_PKEY_type(pkey->type)) {
+ case EVP_PKEY_RSA:
+ if ( ! op_sign_rsa(&head, &prot, pkey, nonce))
+ goto out;
+ break;
+ default:
+ warnx("EVP_PKEY_type");
+ goto out;
+ }
+
+ /* The header combined with the nonce, base64. */
+
+ if (NULL == (prot64 = base64buf_url(prot, strlen(prot)))) {
+ warnx("base64buf_url");
+ goto out;
+ }
+
+ /* Now the signature material. */
+
+ cc = asprintf(&sign, "%s.%s", prot64, pay64);
+ if (-1 == cc) {
+ warn("asprintf");
+ sign = NULL;
+ goto out;
+ }
+
+ if (NULL == (dig = malloc(EVP_PKEY_size(pkey)))) {
+ warn("malloc");
+ goto out;
+ }
+
+ /*
+ * Here we go: using our RSA key as merged into the envelope,
+ * sign a SHA256 digest of our message.
+ */
+
+ if (NULL == (ctx = EVP_MD_CTX_create())) {
+ warnx("EVP_MD_CTX_create");
+ goto out;
+ } else if ( ! EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) {
+ warnx("EVP_SignInit_ex");
+ goto out;
+ } else if ( ! EVP_SignUpdate(ctx, sign, strlen(sign))) {
+ warnx("EVP_SignUpdate");
+ goto out;
+ } else if ( ! EVP_SignFinal(ctx, dig, &digsz, pkey)) {
+ warnx("EVP_SignFinal");
+ goto out;
+ } else if (NULL == (dig64 = base64buf_url((char *)dig, digsz))) {
+ warnx("base64buf_url");
+ goto out;
+ }
+
+ /*
+ * Write back in the correct JSON format.
+ * If the reader is closed, just ignore it (we'll pick it up
+ * when we next enter the read loop).
+ */
+
+ if (NULL == (fin = json_fmt_signed(head, prot64, pay64, dig64))) {
+ warnx("json_fmt_signed");
+ goto out;
+ } else if (writestr(fd, COMM_REQ, fin) < 0)
+ goto out;
+
+ rc = 1;
+out:
+ if (NULL != ctx)
+ EVP_MD_CTX_destroy(ctx);
+
+ free(pay);
+ free(sign);
+ free(pay64);
+ free(nonce);
+ free(head);
+ free(prot);
+ free(prot64);
+ free(dig);
+ free(dig64);
+ free(fin);
+ return(rc);
+}
+
+int
+acctproc(int netsock, const char *acctkey, int newacct)
+{
+ FILE *f;
+ EVP_PKEY *pkey;
+ long lval;
+ enum acctop op;
+ unsigned char rbuf[64];
+ int rc, cc;
+ mode_t prev;
+
+ f = NULL;
+ pkey = NULL;
+ rc = 0;
+
+ /*
+ * First, open our private key file read-only or write-only if
+ * we're creating from scratch.
+ * Set our umask to be maximally restrictive.
+ */
+
+ prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
+ f = fopen(acctkey, newacct ? "wx" : "r");
+ umask(prev);
+
+ if (NULL == f) {
+ warn("%s", acctkey);
+ goto out;
+ }
+
+ /* File-system, user, and sandbox jailing. */
+
+ if ( ! sandbox_before())
+ goto out;
+
+ ERR_load_crypto_strings();
+
+ if ( ! dropfs(PATH_VAR_EMPTY))
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * Seed our PRNG with data from arc4random().
+ * Do this until we're told it's ok and use increments of 64
+ * bytes (arbitrarily).
+ */
+
+ while (0 == RAND_status()) {
+ arc4random_buf(rbuf, sizeof(rbuf));
+ RAND_seed(rbuf, sizeof(rbuf));
+ }
+
+ if (newacct) {
+ if (NULL == (pkey = rsa_key_create(f, acctkey)))
+ goto out;
+ dodbg("%s: generated RSA account key", acctkey);
+ } else {
+ if (NULL == (pkey = rsa_key_load(f, acctkey)))
+ goto out;
+ doddbg("%s: loaded RSA account key", acctkey);
+ }
+
+ fclose(f);
+ f = NULL;
+
+ /* Notify the netproc that we've started up. */
+
+ if (0 == (cc = writeop(netsock, COMM_ACCT_STAT, ACCT_READY)))
+ rc = 1;
+ if (cc <= 0)
+ goto out;
+
+ /*
+ * Now we wait for requests from the network-facing process.
+ * It might ask us for our thumbprint, for example, or for us to
+ * sign a message.
+ */
+
+ for (;;) {
+ op = ACCT__MAX;
+ if (0 == (lval = readop(netsock, COMM_ACCT)))
+ op = ACCT_STOP;
+ else if (ACCT_SIGN == lval || ACCT_THUMBPRINT == lval)
+ op = lval;
+
+ if (ACCT__MAX == op) {
+ warnx("unknown operation from netproc");
+ goto out;
+ } else if (ACCT_STOP == op)
+ break;
+
+ switch (op) {
+ case (ACCT_SIGN):
+ if (op_sign(netsock, pkey))
+ break;
+ warnx("op_sign");
+ goto out;
+ case (ACCT_THUMBPRINT):
+ if (op_thumbprint(netsock, pkey))
+ break;
+ warnx("op_thumbprint");
+ goto out;
+ default:
+ abort();
+ }
+ }
+
+ rc = 1;
+out:
+ close(netsock);
+ if (NULL != f)
+ fclose(f);
+ if (NULL != pkey)
+ EVP_PKEY_free(pkey);
+ ERR_print_errors_fp(stderr);
+ ERR_free_strings();
+ return(rc);
+}
+
diff --git a/usr.sbin/acme-client/base64.c b/usr.sbin/acme-client/base64.c
new file mode 100644
index 00000000000..6f4364ddaa5
--- /dev/null
+++ b/usr.sbin/acme-client/base64.c
@@ -0,0 +1,130 @@
+/* $Id: base64.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/types.h>
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "extern.h"
+
+static const char b64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+/*
+ * Compute the maximum buffer required for a base64 encoded string of
+ * length "len".
+ */
+size_t
+base64len(size_t len)
+{
+
+ return(((len + 2) / 3 * 4) + 1);
+}
+
+/*
+ * Base64 computation.
+ * This is heavily "assert"-d because Coverity complains.
+ */
+size_t
+base64buf(char *enc, const char *str, size_t len)
+{
+ size_t i, val;
+ char *p;
+
+ p = enc;
+
+ for (i = 0; i < len - 2; i += 3) {
+ val = (str[i] >> 2) & 0x3F;
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+
+ val = ((str[i] & 0x3) << 4) |
+ ((int)(str[i + 1] & 0xF0) >> 4);
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+
+ val = ((str[i + 1] & 0xF) << 2) |
+ ((int)(str[i + 2] & 0xC0) >> 6);
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+
+ val = str[i + 2] & 0x3F;
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+ }
+
+ if (i < len) {
+ val = (str[i] >> 2) & 0x3F;
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+
+ if (i == (len - 1)) {
+ val = ((str[i] & 0x3) << 4);
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+ *p++ = '=';
+ } else {
+ val = ((str[i] & 0x3) << 4) |
+ ((int)(str[i + 1] & 0xF0) >> 4);
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+
+ val = ((str[i + 1] & 0xF) << 2);
+ assert(val < sizeof(b64));
+ *p++ = b64[val];
+ }
+ *p++ = '=';
+ }
+
+ *p++ = '\0';
+ return(p - enc);
+}
+
+/*
+ * Pass a stream of bytes to be base64 encoded, then converted into
+ * base64url format.
+ * Returns NULL on allocation failure (not logged).
+ */
+char *
+base64buf_url(const char *data, size_t len)
+{
+ size_t i, sz;
+ char *buf;
+
+ sz = base64len(len);
+ if (NULL == (buf = malloc(sz)))
+ return(NULL);
+
+ base64buf(buf, data, len);
+
+ for (i = 0; i < sz; i++)
+ if ('+' == buf[i])
+ buf[i] = '-';
+ else if ('/' == buf[i])
+ buf[i] = '_';
+ else if ('=' == buf[i])
+ buf[i] = '\0';
+
+ return(buf);
+}
diff --git a/usr.sbin/acme-client/certproc.c b/usr.sbin/acme-client/certproc.c
new file mode 100644
index 00000000000..9f9e88300c4
--- /dev/null
+++ b/usr.sbin/acme-client/certproc.c
@@ -0,0 +1,261 @@
+/* $Id: certproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/err.h>
+
+#include "extern.h"
+
+#define MARKER "-----BEGIN CERTIFICATE-----"
+
+/*
+ * Convert an X509 certificate to a buffer of "sz".
+ * We don't guarantee that it's nil-terminated.
+ * Returns NULL on failure.
+ */
+static char *
+x509buf(X509 *x, size_t *sz)
+{
+ BIO *bio;
+ char *p;
+ int ssz;
+
+ /* Convert X509 to PEM in BIO. */
+
+ if (NULL == (bio = BIO_new(BIO_s_mem()))) {
+ warnx("BIO_new");
+ return(NULL);
+ } else if ( ! PEM_write_bio_X509(bio, x)) {
+ warnx("PEM_write_bio_X509");
+ BIO_free(bio);
+ return(NULL);
+ }
+
+ /*
+ * Now convert bio to string.
+ * Make into nil-terminated, just in case.
+ */
+
+ if (NULL == (p = calloc(1, bio->num_write + 1))) {
+ warn("calloc");
+ BIO_free(bio);
+ return(NULL);
+ }
+
+ ssz = BIO_read(bio, p, bio->num_write);
+ if (ssz < 0 || (unsigned)ssz != bio->num_write) {
+ warnx("BIO_read");
+ BIO_free(bio);
+ return(NULL);
+ }
+
+ *sz = ssz;
+ BIO_free(bio);
+ return(p);
+}
+
+int
+certproc(int netsock, int filesock)
+{
+ char *csr, *chain, *url;
+ unsigned char *csrcp, *chaincp;
+ size_t csrsz, chainsz;
+ int i, rc, idx, cc;
+ enum certop op;
+ long lval;
+ X509 *x, *chainx;
+ X509_EXTENSION *ext;
+ X509V3_EXT_METHOD *method;
+ void *entries;
+ STACK_OF(CONF_VALUE) *val;
+ CONF_VALUE *nval;
+
+ ext = NULL;
+ idx = -1;
+ method = NULL;
+ chain = csr = url = NULL;
+ rc = 0;
+ x = chainx = NULL;
+
+ /* File-system and sandbox jailing. */
+
+ if ( ! sandbox_before())
+ goto out;
+
+ ERR_load_crypto_strings();
+
+ if ( ! dropfs(PATH_VAR_EMPTY))
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /* Read what the netproc wants us to do. */
+
+ op = CERT__MAX;
+ if (0 == (lval = readop(netsock, COMM_CSR_OP)))
+ op = CERT_STOP;
+ else if (CERT_REVOKE == lval || CERT_UPDATE == lval)
+ op = lval;
+
+ if (CERT_STOP == op) {
+ rc = 1;
+ goto out;
+ } else if (CERT__MAX == op) {
+ warnx("unknown operation from netproc");
+ goto out;
+ }
+
+ /*
+ * Pass revocation right through to fileproc.
+ * If the reader is terminated, ignore it.
+ */
+
+ if (CERT_REVOKE == op) {
+ if (writeop(filesock, COMM_CHAIN_OP, FILE_REMOVE) >= 0)
+ rc = 1;
+ goto out;
+ }
+
+ /*
+ * Wait until we receive the DER encoded (signed) certificate
+ * from the network process.
+ * Then convert the DER encoding into an X509 certificate.
+ */
+
+ if (NULL == (csr = readbuf(netsock, COMM_CSR, &csrsz)))
+ goto out;
+
+ csrcp = (u_char *)csr;
+ x = d2i_X509(NULL, (const u_char **)&csrcp, csrsz);
+ if (NULL == x) {
+ warnx("d2i_X509");
+ goto out;
+ }
+
+ /*
+ * Extract the CA Issuers from its NID.
+ * TODO: I have no idea what I'm doing.
+ */
+
+ idx = X509_get_ext_by_NID(x, NID_info_access, idx);
+ if (idx >= 0 && NULL != (ext = X509_get_ext(x, idx)))
+ method = (X509V3_EXT_METHOD *)X509V3_EXT_get(ext);
+
+ entries = X509_get_ext_d2i(x, NID_info_access, 0, 0);
+ if (NULL != method && NULL != entries) {
+ val = method->i2v(method, entries, 0);
+ for (i = 0; i < sk_CONF_VALUE_num(val); i++) {
+ nval = sk_CONF_VALUE_value(val, i);
+ if (strcmp(nval->name, "CA Issuers - URI"))
+ continue;
+ url = strdup(nval->value);
+ if (NULL == url) {
+ warn("strdup");
+ goto out;
+ }
+ break;
+ }
+ }
+
+ if (NULL == url) {
+ warnx("no CA issuer registered with certificate");
+ goto out;
+ }
+
+ /* Write the CA issuer to the netsock. */
+
+ if (writestr(netsock, COMM_ISSUER, url) <= 0)
+ goto out;
+
+ /* Read the full-chain back from the netsock. */
+
+ if (NULL == (chain = readbuf(netsock, COMM_CHAIN, &chainsz)))
+ goto out;
+
+ /*
+ * Then check if the chain is PEM-encoded by looking to see if
+ * it begins with the PEM marker.
+ * If so, ship it as-is; otherwise, convert to a PEM encoded
+ * buffer and ship that.
+ * FIXME: if PEM, re-parse it.
+ */
+
+ if (chainsz <= strlen(MARKER) ||
+ strncmp(chain, MARKER, strlen(MARKER))) {
+ chaincp = (u_char *)chain;
+ chainx = d2i_X509(NULL,
+ (const u_char **)&chaincp, chainsz);
+ if (NULL == chainx) {
+ warnx("d2i_X509");
+ goto out;
+ }
+ free(chain);
+ if (NULL == (chain = x509buf(chainx, &chainsz)))
+ goto out;
+ }
+
+ /* Allow reader termination to just push us out. */
+
+ if (0 == (cc = writeop(filesock, COMM_CHAIN_OP, FILE_CREATE)))
+ rc = 1;
+ if (cc <= 0)
+ goto out;
+ if (0 == (cc = writebuf(filesock, COMM_CHAIN, chain, chainsz)))
+ rc = 1;
+ if (cc <= 0)
+ goto out;
+
+ /*
+ * Next, convert the X509 to a buffer and send that.
+ * Reader failure doesn't change anything.
+ */
+
+ free(chain);
+ if (NULL == (chain = x509buf(x, &chainsz)))
+ goto out;
+ if (writebuf(filesock, COMM_CSR, chain, chainsz) < 0)
+ goto out;
+
+ rc = 1;
+out:
+ close(netsock);
+ close(filesock);
+ if (NULL != x)
+ X509_free(x);
+ if (NULL != chainx)
+ X509_free(chainx);
+ free(csr);
+ free(url);
+ free(chain);
+ ERR_print_errors_fp(stderr);
+ ERR_free_strings();
+ return(rc);
+}
+
diff --git a/usr.sbin/acme-client/chngproc.c b/usr.sbin/acme-client/chngproc.c
new file mode 100644
index 00000000000..a868572a7fc
--- /dev/null
+++ b/usr.sbin/acme-client/chngproc.c
@@ -0,0 +1,174 @@
+/* $Id: chngproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+chngproc(int netsock, const char *root, int remote)
+{
+ int rc;
+ long lval;
+ enum chngop op;
+ char *tok, *th, *fmt;
+ char **fs;
+ size_t i, fsz;
+ void *pp;
+ int fd, cc;
+
+ rc = 0;
+ th = tok = fmt = NULL;
+ fd = -1;
+ fs = NULL;
+ fsz = 0;
+
+ /* File-system and sandbox jailing. */
+
+ if ( ! sandbox_before())
+ goto out;
+ else if ( ! dropfs(root))
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * Loop while we wait to get a thumbprint and token.
+ * We'll get this for each SAN request.
+ */
+
+ for (;;) {
+ op = CHNG__MAX;
+ if (0 == (lval = readop(netsock, COMM_CHNG_OP)))
+ op = CHNG_STOP;
+ else if (CHNG_SYN == lval)
+ op = lval;
+
+ if (CHNG__MAX == op) {
+ warnx("unknown operation from netproc");
+ goto out;
+ } else if (CHNG_STOP == op)
+ break;
+
+ assert(CHNG_SYN == op);
+
+ /*
+ * Read the thumbprint and token.
+ * The token is the filename, so store that in a vector
+ * of tokens that we'll later clean up.
+ */
+
+ if (NULL == (th = readstr(netsock, COMM_THUMB)))
+ goto out;
+ else if (NULL == (tok = readstr(netsock, COMM_TOK)))
+ goto out;
+
+ /* Vector appending... */
+
+ pp = realloc(fs, (fsz + 1) * sizeof(char *));
+ if (NULL == pp) {
+ warn("realloc");
+ goto out;
+ }
+ fs = pp;
+ fs[fsz] = tok;
+ tok = NULL;
+ fsz++;
+
+ if (-1 == asprintf(&fmt, "%s.%s", fs[fsz - 1], th)) {
+ warn("asprintf");
+ goto out;
+ }
+
+ /*
+ * I use this for testing when letskencrypt is being run
+ * on machines apart from where I'm hosting the
+ * challenge directory.
+ * DON'T DEPEND ON THIS FEATURE.
+ */
+ if (remote) {
+ puts("RUN THIS IN THE CHALLENGE DIRECTORY");
+ puts("YOU HAVE 20 SECONDS...");
+ printf("doas sh -c \"echo %s > %s\"\n",
+ fmt, fs[fsz - 1]);
+ sleep(20);
+ puts("TIME'S UP.");
+ } else {
+ /*
+ * Create and write to our challenge file.
+ * Note: we use file descriptors instead of FILE
+ * because we want to minimise our pledges.
+ */
+ fd = open(fs[fsz - 1],
+ O_WRONLY|O_EXCL|O_CREAT, 0444);
+ if (-1 == fd) {
+ warn("%s", fs[fsz - 1]);
+ goto out;
+ } if (-1 == write(fd, fmt, strlen(fmt))) {
+ warn("%s", fs[fsz - 1]);
+ goto out;
+ } else if (-1 == close(fd)) {
+ warn("%s", fs[fsz - 1]);
+ goto out;
+ }
+ fd = -1;
+ }
+
+ free(th);
+ free(fmt);
+ th = fmt = NULL;
+
+ dodbg("%s/%s: created", root, fs[fsz - 1]);
+
+ /*
+ * Write our acknowledgement.
+ * Ignore reader failure.
+ */
+
+ cc = writeop(netsock, COMM_CHNG_ACK, CHNG_ACK);
+ if (0 == cc)
+ break;
+ if (cc < 0)
+ goto out;
+ }
+
+ rc = 1;
+out:
+ close(netsock);
+ if (-1 != fd)
+ close(fd);
+ for (i = 0; i < fsz; i++) {
+ if (-1 == unlink(fs[i]) && ENOENT != errno)
+ warn("%s", fs[i]);
+ free(fs[i]);
+ }
+ free(fs);
+ free(fmt);
+ free(th);
+ free(tok);
+ return(rc);
+}
diff --git a/usr.sbin/acme-client/dbg.c b/usr.sbin/acme-client/dbg.c
new file mode 100644
index 00000000000..d3910fdcf4c
--- /dev/null
+++ b/usr.sbin/acme-client/dbg.c
@@ -0,0 +1,51 @@
+/* $Id: dbg.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <err.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "extern.h"
+
+void
+doddbg(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (verbose < 2)
+ return;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+}
+
+void
+dodbg(const char *fmt, ...)
+{
+ va_list ap;
+
+ if ( ! verbose)
+ return;
+
+ va_start(ap, fmt);
+ vwarnx(fmt, ap);
+ va_end(ap);
+}
diff --git a/usr.sbin/acme-client/dnsproc.c b/usr.sbin/acme-client/dnsproc.c
new file mode 100644
index 00000000000..e00c3454af3
--- /dev/null
+++ b/usr.sbin/acme-client/dnsproc.c
@@ -0,0 +1,206 @@
+/* $Id: dnsproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+struct addr {
+ int family; /* 4 for PF_INET, 6 for PF_INET6 */
+ char ip[INET6_ADDRSTRLEN];
+};
+
+/*
+ * This is a modified version of host_dns in config.c of OpenBSD's ntpd.
+ */
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+static ssize_t
+host_dns(const char *s, struct addr *vec)
+{
+ struct addrinfo hints, *res0, *res;
+ int error;
+ ssize_t vecsz;
+ struct sockaddr *sa;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ /* ntpd MUST NOT use AI_ADDRCONFIG here */
+
+ error = getaddrinfo(s, NULL, &hints, &res0);
+
+ if (error == EAI_AGAIN ||
+ /* FIXME */
+#ifndef __FreeBSD__
+ error == EAI_NODATA ||
+#endif
+ error == EAI_NONAME)
+ return(0);
+
+ if (error) {
+ warnx("%s: parse error: %s",
+ s, gai_strerror(error));
+ return(-1);
+ }
+
+ for (vecsz = 0, res = res0;
+ NULL != res && vecsz < MAX_SERVERS_DNS;
+ res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+
+ sa = res->ai_addr;
+
+ if (AF_INET == res->ai_family) {
+ vec[vecsz].family = 4;
+ inet_ntop(AF_INET,
+ &(((struct sockaddr_in *)sa)->sin_addr),
+ vec[vecsz].ip, INET6_ADDRSTRLEN);
+ } else {
+ vec[vecsz].family = 6;
+ inet_ntop(AF_INET6,
+ &(((struct sockaddr_in6 *)sa)->sin6_addr),
+ vec[vecsz].ip, INET6_ADDRSTRLEN);
+ }
+
+ dodbg("%s: DNS: %s", s, vec[vecsz].ip);
+ vecsz++;
+ break;
+ }
+
+ freeaddrinfo(res0);
+ return(vecsz);
+}
+
+int
+dnsproc(int nfd)
+{
+ int rc, cc;
+ char *look, *last;
+ struct addr v[MAX_SERVERS_DNS];
+ long lval;
+ size_t i;
+ ssize_t vsz;
+ enum dnsop op;
+
+ rc = 0;
+ look = last = NULL;
+ vsz = 0;
+
+ /*
+ * Why don't we chroot() here?
+ * On OpenBSD, the pledge(2) takes care of our constraining the
+ * environment to DNS resolution only, so the chroot(2) is
+ * unnecessary.
+ * On Mac OS X, we can't chroot(2): we'd need to have an mdns
+ * responder thing in each jail.
+ * On Linux, forget it. getaddrinfo(2) pulls on all sorts of
+ * mystery meat.
+ */
+
+ if ( ! sandbox_before())
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * This is simple: just loop on a request operation, and each
+ * time we write back zero or more entries.
+ * Also do a simple trick and cache the last lookup.
+ */
+
+ for (;;) {
+ op = DNS__MAX;
+ if (0 == (lval = readop(nfd, COMM_DNS)))
+ op = DNS_STOP;
+ else if (DNS_LOOKUP == lval)
+ op = lval;
+
+ if (DNS__MAX == op) {
+ warnx("unknown operation from netproc");
+ goto out;
+ } else if (DNS_STOP == op)
+ break;
+
+ if (NULL == (look = readstr(nfd, COMM_DNSQ)))
+ goto out;
+
+ /*
+ * Check if we're asked to repeat the lookup.
+ * If not, request it from host_dns().
+ */
+
+ if (NULL == last || strcmp(look, last)) {
+ if ((vsz = host_dns(look, v)) < 0)
+ goto out;
+
+ free(last);
+ last = look;
+ look = NULL;
+ } else {
+ doddbg("%s: cached", look);
+ free(look);
+ look = NULL;
+ }
+
+ if (0 == (cc = writeop(nfd, COMM_DNSLEN, vsz)))
+ break;
+ else if (cc < 0)
+ goto out;
+ for (i = 0; i < (size_t)vsz; i++) {
+ if (writeop(nfd, COMM_DNSF, v[i].family) <= 0)
+ goto out;
+ if (writestr(nfd, COMM_DNSA, v[i].ip) <= 0)
+ goto out;
+ }
+ }
+
+ rc = 1;
+out:
+ close(nfd);
+ free(look);
+ free(last);
+ return(rc);
+}
diff --git a/usr.sbin/acme-client/extern.h b/usr.sbin/acme-client/extern.h
new file mode 100644
index 00000000000..10f4323aa99
--- /dev/null
+++ b/usr.sbin/acme-client/extern.h
@@ -0,0 +1,267 @@
+/* $Id: extern.h,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef EXTERN_H
+#define EXTERN_H
+
+#ifndef PATH_VAR_EMPTY
+#define PATH_VAR_EMPTY "/var/empty"
+#endif
+
+#define MAX_SERVERS_DNS 8
+
+#define CERT_PEM "cert.pem"
+#define CERT_BAK "cert.pem~"
+#define CHAIN_PEM "chain.pem"
+#define CHAIN_BAK "chain.pem~"
+#define FCHAIN_PEM "fullchain.pem"
+#define FCHAIN_BAK "fullchain.pem~"
+
+
+/*
+ * Requests to and from acctproc.
+ */
+enum acctop {
+ ACCT_STOP = 0,
+ ACCT_READY,
+ ACCT_SIGN,
+ ACCT_THUMBPRINT,
+ ACCT__MAX
+};
+
+/*
+ * Requests to and from chngproc.
+ */
+enum chngop {
+ CHNG_STOP = 0,
+ CHNG_SYN,
+ CHNG_ACK,
+ CHNG__MAX
+};
+
+/*
+ * Requests to keyproc.
+ */
+enum keyop {
+ KEY_STOP = 0,
+ KEY_READY,
+ KEY__MAX
+};
+
+/*
+ * Requests to certproc.
+ */
+enum certop {
+ CERT_STOP = 0,
+ CERT_REVOKE,
+ CERT_UPDATE,
+ CERT__MAX
+};
+
+/*
+ * Requests to fileproc.
+ */
+enum fileop {
+ FILE_STOP = 0,
+ FILE_REMOVE,
+ FILE_CREATE,
+ FILE__MAX
+};
+
+/*
+ * Requests to dnsproc.
+ */
+enum dnsop {
+ DNS_STOP = 0,
+ DNS_LOOKUP,
+ DNS__MAX
+};
+
+enum revokeop {
+ REVOKE_STOP = 0,
+ REVOKE_CHECK,
+ REVOKE_EXP,
+ REVOKE_OK,
+ REVOKE__MAX
+};
+
+/*
+ * Our components.
+ * Each one of these is in a separated, isolated process.
+ */
+enum comp {
+ COMP_NET, /* network-facing (to ACME) */
+ COMP_KEY, /* handles domain keys */
+ COMP_CERT, /* handles domain certificates */
+ COMP_ACCOUNT, /* handles account key */
+ COMP_CHALLENGE, /* handles challenges */
+ COMP_FILE, /* handles writing certs */
+ COMP_DNS, /* handles DNS lookups */
+ COMP_REVOKE, /* checks X509 expiration */
+ COMP__MAX
+};
+
+/*
+ * Inter-process communication labels.
+ * This is purely for looking at debugging.
+ */
+enum comm {
+ COMM_REQ,
+ COMM_THUMB,
+ COMM_CERT,
+ COMM_PAY,
+ COMM_NONCE,
+ COMM_TOK,
+ COMM_CHNG_OP,
+ COMM_CHNG_ACK,
+ COMM_ACCT,
+ COMM_ACCT_STAT,
+ COMM_CSR,
+ COMM_CSR_OP,
+ COMM_ISSUER,
+ COMM_CHAIN,
+ COMM_CHAIN_OP,
+ COMM_DNS,
+ COMM_DNSQ,
+ COMM_DNSA,
+ COMM_DNSF,
+ COMM_DNSLEN,
+ COMM_KEY_STAT,
+ COMM_REVOKE_OP,
+ COMM_REVOKE_CHECK,
+ COMM_REVOKE_RESP,
+ COMM__MAX
+};
+
+/*
+ * This contains the URI and token of an ACME-issued challenge.
+ * A challenge consists of a token, which we must present on the
+ * (presumably!) local machine to an ACME connection; and a URI, to
+ * which we must connect to verify the token.
+ */
+struct chng {
+ char *uri; /* uri on ACME server */
+ char *token; /* token we must offer */
+ size_t retry; /* how many times have we tried */
+ int status; /* challenge accepted? */
+};
+
+/*
+ * This consists of the services offered by the CA.
+ * They must all be filled in.
+ */
+struct capaths {
+ char *newauthz; /* new authorisation */
+ char *newcert; /* sign certificate */
+ char *newreg; /* new acme account */
+ char *revokecert; /* revoke certificate */
+};
+
+struct jsmnn;
+
+__BEGIN_DECLS
+
+/*
+ * Start with our components.
+ * These are all isolated and talk to each other using sockets.
+ */
+int acctproc(int, const char *, int);
+int certproc(int, int);
+int chngproc(int, const char *, int);
+int dnsproc(int);
+int revokeproc(int, const char *,
+ int, int, const char *const *, size_t);
+int fileproc(int, int, const char *);
+int keyproc(int, const char *,
+ const char **, size_t, int);
+int netproc(int, int, int, int, int, int, int, int, int,
+ const char *const *, size_t, const char *);
+
+/*
+ * Debugging functions.
+ * These just route to warnx according to the verbosity.
+ */
+void dodbg(const char *, ...)
+ __attribute__((format(printf, 1, 2)));
+void doddbg(const char *, ...)
+ __attribute__((format(printf, 1, 2)));
+
+/*
+ * Read and write things from the wire.
+ * The readers behave differently with respect to EOF.
+ */
+long readop(int, enum comm);
+char *readbuf(int, enum comm, size_t *);
+char *readstr(int, enum comm);
+int writebuf(int, enum comm, const void *, size_t);
+int writestr(int, enum comm, const char *);
+int writeop(int, enum comm, long);
+
+int checkexit(pid_t, enum comp);
+int checkexit_ext(int *, pid_t, enum comp);
+
+/*
+ * Base64 and URL encoding.
+ * Returns a buffer or NULL on allocation error.
+ */
+size_t base64buf(char *, const char *, size_t);
+size_t base64len(size_t);
+char *base64buf_url(const char *, size_t);
+
+/*
+ * JSON parsing routines.
+ * Keep this all in on place, though it's only used by one file.
+ */
+struct jsmnn *json_parse(const char *, size_t);
+void json_free(struct jsmnn *);
+int json_parse_response(struct jsmnn *);
+void json_free_challenge(struct chng *);
+int json_parse_challenge(struct jsmnn *, struct chng *);
+void json_free_capaths(struct capaths *);
+int json_parse_capaths(struct jsmnn *, struct capaths *);
+
+char *json_fmt_challenge(const char *, const char *);
+char *json_fmt_newauthz(const char *);
+char *json_fmt_newcert(const char *);
+char *json_fmt_newreg(const char *);
+char *json_fmt_protected_rsa(const char *,
+ const char *, const char *);
+char *json_fmt_revokecert(const char *);
+char *json_fmt_header_rsa(const char *, const char *);
+char *json_fmt_thumb_rsa(const char *, const char *);
+char *json_fmt_signed(const char *,
+ const char *, const char *, const char *);
+
+int dropprivs(void);
+int dropfs(const char *);
+int checkprivs(void);
+
+int sandbox_after(void);
+int sandbox_before(void);
+
+/*
+ * Should we print debugging messages?
+ */
+int verbose;
+
+/*
+ * What component is the process within (COMP__MAX for none)?
+ */
+enum comp proccomp;
+
+__END_DECLS
+
+#endif /* ! EXTERN_H */
diff --git a/usr.sbin/acme-client/fileproc.c b/usr.sbin/acme-client/fileproc.c
new file mode 100644
index 00000000000..33f15418a97
--- /dev/null
+++ b/usr.sbin/acme-client/fileproc.c
@@ -0,0 +1,221 @@
+/* $Id: fileproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static int
+serialise(const char *tmp, const char *real,
+ const char *v, size_t vsz,
+ const char *v2, size_t v2sz)
+{
+ int fd;
+
+ /*
+ * Write into backup location, overwriting.
+ * Then atomically (?) do the rename.
+ */
+
+ fd = open(tmp, O_WRONLY|O_CREAT|O_TRUNC, 0444);
+ if (-1 == fd) {
+ warn("%s", tmp);
+ return(0);
+ } else if ((ssize_t)vsz != write(fd, v, vsz)) {
+ warnx("%s", tmp);
+ close(fd);
+ return(0);
+ } else if (NULL != v2 && (ssize_t)v2sz != write(fd, v2, v2sz)) {
+ warnx("%s", tmp);
+ close(fd);
+ return(0);
+ } else if (-1 == close(fd)) {
+ warn("%s", tmp);
+ return(0);
+ } else if (-1 == rename(tmp, real)) {
+ warn("%s", real);
+ return(0);
+ }
+
+ return(1);
+}
+
+int
+fileproc(int certsock, int backup, const char *certdir)
+{
+ char *csr, *ch;
+ size_t chsz, csz;
+ int rc;
+ long lval;
+ enum fileop op;
+ time_t t;
+ char file[PATH_MAX];
+
+ csr = ch = NULL;
+ rc = 0;
+
+ /* File-system and sandbox jailing. */
+
+ if ( ! sandbox_before())
+ goto out;
+ else if ( ! dropfs(certdir))
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /* Read our operation. */
+
+ op = FILE__MAX;
+ if (0 == (lval = readop(certsock, COMM_CHAIN_OP)))
+ op = FILE_STOP;
+ else if (FILE_CREATE == lval || FILE_REMOVE == lval)
+ op = lval;
+
+ if (FILE_STOP == op) {
+ rc = 1;
+ goto out;
+ } else if (FILE__MAX == op) {
+ warnx("unknown operation from certproc");
+ goto out;
+ }
+
+ /*
+ * If we're backing up, then copy all files (found) by linking
+ * them to the file followed by the epoch in seconds.
+ * If we're going to remove, the unlink(2) will cause the
+ * original to go away.
+ * If we're going to update, the rename(2) will replace the
+ * certificate, leaving the backup as the only one.
+ */
+
+ if (backup) {
+ t = time(NULL);
+ snprintf(file, sizeof(file),
+ "cert-%llu.pem", (unsigned long long)t);
+ if (-1 == link(CERT_PEM, file) && ENOENT != errno) {
+ warnx("%s/%s", certdir, CERT_PEM);
+ goto out;
+ } else
+ dodbg("%s/%s: linked to %s",
+ certdir, CERT_PEM, file);
+
+ snprintf(file, sizeof(file),
+ "chain-%llu.pem", (unsigned long long)t);
+ if (-1 == link(CHAIN_PEM, file) && ENOENT != errno) {
+ warnx("%s/%s", certdir, CHAIN_PEM);
+ goto out;
+ } else
+ dodbg("%s/%s: linked to %s",
+ certdir, CHAIN_PEM, file);
+
+ snprintf(file, sizeof(file),
+ "fullchain-%llu.pem", (unsigned long long)t);
+ if (-1 == link(FCHAIN_PEM, file) && ENOENT != errno) {
+ warnx("%s/%s", certdir, FCHAIN_PEM);
+ goto out;
+ } else
+ dodbg("%s/%s: linked to %s",
+ certdir, FCHAIN_PEM, file);
+ }
+
+ /*
+ * If revoking certificates, just unlink the files.
+ * We return the special error code of 2 to indicate that the
+ * certificates were removed.
+ */
+
+ if (FILE_REMOVE == op) {
+ if (-1 == unlink(CERT_PEM) && ENOENT != errno) {
+ warn("%s/%s", certdir, CERT_PEM);
+ goto out;
+ } else
+ dodbg("%s/%s: unlinked", certdir, CERT_PEM);
+
+ if (-1 == unlink(CHAIN_PEM) && ENOENT != errno) {
+ warn("%s/%s", certdir, CHAIN_PEM);
+ goto out;
+ } else
+ dodbg("%s/%s: unlinked", certdir, CHAIN_PEM);
+
+ if (-1 == unlink(FCHAIN_PEM) && ENOENT != errno) {
+ warn("%s/%s", certdir, FCHAIN_PEM);
+ goto out;
+ } else
+ dodbg("%s/%s: unlinked", certdir, FCHAIN_PEM);
+
+ rc = 2;
+ goto out;
+ }
+
+ /*
+ * Start by downloading the chain PEM as a buffer.
+ * This is not nil-terminated, but we're just going to guess
+ * that it's well-formed and not actually touch the data.
+ * Once downloaded, dump it into CHAIN_BAK.
+ */
+
+ if (NULL == (ch = readbuf(certsock, COMM_CHAIN, &chsz)))
+ goto out;
+ if ( ! serialise(CHAIN_BAK, CHAIN_PEM, ch, chsz, NULL, 0))
+ goto out;
+
+ dodbg("%s/%s: created", certdir, CHAIN_PEM);
+
+ /*
+ * Next, wait until we receive the DER encoded (signed)
+ * certificate from the network process.
+ * This comes as a stream of bytes: we don't know how many, so
+ * just keep downloading.
+ */
+
+ if (NULL == (csr = readbuf(certsock, COMM_CSR, &csz)))
+ goto out;
+ if ( ! serialise(CERT_BAK, CERT_PEM, csr, csz, NULL, 0))
+ goto out;
+
+ dodbg("%s/%s: created", certdir, CERT_PEM);
+
+ /*
+ * Finally, create the full-chain file.
+ * This is just the concatenation of the certificate and chain.
+ * We return the special error code 2 to indicate that the
+ * on-file certificates were changed.
+ */
+
+ if ( ! serialise(FCHAIN_BAK, FCHAIN_PEM, csr, csz, ch, chsz))
+ goto out;
+
+ dodbg("%s/%s: created", certdir, FCHAIN_PEM);
+
+ rc = 2;
+out:
+ close(certsock);
+ free(csr);
+ free(ch);
+ return(rc);
+}
diff --git a/usr.sbin/acme-client/http.c b/usr.sbin/acme-client/http.c
new file mode 100644
index 00000000000..8838af44c2c
--- /dev/null
+++ b/usr.sbin/acme-client/http.c
@@ -0,0 +1,851 @@
+/* $Id: http.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tls.h>
+#include <unistd.h>
+
+#include "http.h"
+#include "extern.h"
+
+/*
+ * A buffer for transferring HTTP/S data.
+ */
+struct httpxfer {
+ char *hbuf; /* header transfer buffer */
+ size_t hbufsz; /* header buffer size */
+ int headok; /* header has been parsed */
+ char *bbuf; /* body transfer buffer */
+ size_t bbufsz; /* body buffer size */
+ int bodyok; /* body has been parsed */
+ char *headbuf; /* lookaside buffer for headers */
+ struct httphead *head; /* parsed headers */
+ size_t headsz; /* number of headers */
+};
+
+/*
+ * An HTTP/S connection object.
+ */
+struct http {
+ int fd; /* connected socket */
+ short port; /* port number */
+ struct source src; /* endpoint (raw) host */
+ char *path; /* path to request */
+ char *host; /* name of endpoint host */
+ struct tls_config *cfg; /* if TLS */
+ struct tls *ctx; /* if TLS */
+ writefp writer; /* write function */
+ readfp reader; /* read function */
+};
+
+static ssize_t
+dosysread(char *buf, size_t sz, const struct http *http)
+{
+ ssize_t rc;
+
+ rc = read(http->fd, buf, sz);
+ if (rc < 0)
+ warn("%s: read", http->src.ip);
+ return(rc);
+}
+
+static ssize_t
+dosyswrite(const void *buf, size_t sz, const struct http *http)
+{
+ ssize_t rc;
+
+ rc = write(http->fd, buf, sz);
+ if (rc < 0)
+ warn("%s: write", http->src.ip);
+ return(rc);
+}
+
+#if defined(TLS_READ_AGAIN) && defined(TLS_WRITE_AGAIN)
+/*
+ * Old-style libtls calls.
+ * These changed between 5.8 and 5.9.
+ */
+static ssize_t
+dotlsread(char *buf, size_t sz, const struct http *http)
+{
+ size_t out, tot = 0;
+ int rc;
+
+ for (;;) {
+ out = 0;
+ rc = tls_read(http->ctx, buf, sz, &out);
+ if (out > 0) {
+ buf += out;
+ assert(sz >= out);
+ sz -= out;
+ tot += out;
+ }
+ if (TLS_READ_AGAIN == rc)
+ continue;
+ else if (0 == out || 0 == sz || 0 == rc)
+ break;
+ warnx("%s: tls_read: %s",
+ http->src.ip, tls_error(http->ctx));
+ return(-1);
+ }
+
+ return(tot);
+}
+
+static ssize_t
+dotlswrite(const void *buf, size_t sz, const struct http *http)
+{
+ size_t out, tot = 0;
+ int rc;
+
+ for (;;) {
+ out = 0;
+ rc = tls_write(http->ctx, buf, sz, &out);
+ if (out > 0) {
+ buf += out;
+ assert(sz >= out);
+ sz -= out;
+ tot += out;
+ }
+ if (TLS_WRITE_AGAIN == rc)
+ continue;
+ else if (0 == out || 0 == rc || 0 == rc)
+ break;
+ warnx("%s: tls_write: %s",
+ http->src.ip, tls_error(http->ctx));
+ return(-1);
+ }
+
+ return(tot);
+}
+#else
+/*
+ * New-style libtls calls.
+ */
+static ssize_t
+dotlsread(char *buf, size_t sz, const struct http *http)
+{
+ ssize_t rc;
+
+ do
+ rc = tls_read(http->ctx, buf, sz);
+ while (TLS_WANT_POLLIN == rc ||
+ TLS_WANT_POLLOUT == rc);
+
+ if (rc < 0)
+ warnx("%s: tls_read: %s",
+ http->src.ip,
+ tls_error(http->ctx));
+ return(rc);
+}
+
+static ssize_t
+dotlswrite(const void *buf, size_t sz, const struct http *http)
+{
+ ssize_t rc;
+
+ do
+ rc = tls_write(http->ctx, buf, sz);
+ while (TLS_WANT_POLLIN == rc ||
+ TLS_WANT_POLLOUT == rc);
+
+ if (rc < 0)
+ warnx("%s: tls_write: %s",
+ http->src.ip,
+ tls_error(http->ctx));
+ return(rc);
+}
+#endif
+
+static ssize_t
+http_read(char *buf, size_t sz, const struct http *http)
+{
+ ssize_t ssz, xfer;
+
+ xfer = 0;
+ do {
+ if ((ssz = http->reader(buf, sz, http)) < 0)
+ return(-1);
+ if (0 == ssz)
+ break;
+ xfer += ssz;
+ sz -= ssz;
+ buf += ssz;
+ } while (ssz > 0 && sz > 0);
+
+ return(xfer);
+}
+
+static int
+http_write(const void *buf, size_t sz, const struct http *http)
+{
+ ssize_t ssz, xfer;
+
+ xfer = sz;
+ while (sz > 0) {
+ if ((ssz = http->writer(buf, sz, http)) < 0)
+ return(-1);
+ sz -= ssz;
+ buf += ssz;
+ }
+ return(xfer);
+}
+
+/*
+ * Between 5.8 and 5.9, libtls changed its semantics.
+ * In the old way, tls_close() will close the underlying file
+ * descriptors.
+ * In the new way, it won't.
+ */
+void
+http_disconnect(struct http *http)
+{
+
+ if (NULL != http->ctx) {
+ /* TLS connection. */
+ if (-1 == tls_close(http->ctx))
+ warnx("%s: tls_close: %s",
+ http->src.ip,
+ tls_error(http->ctx));
+ if (NULL != http->ctx)
+ tls_free(http->ctx);
+#if ! defined(TLS_READ_AGAIN) && ! defined(TLS_WRITE_AGAIN)
+ if (-1 == close(http->fd))
+ warn("%s: close", http->src.ip);
+#endif
+ } else if (-1 != http->fd) {
+ /* Non-TLS connection. */
+ if (-1 == close(http->fd))
+ warn("%s: close", http->src.ip);
+ }
+
+ http->fd = -1;
+ http->ctx = NULL;
+}
+
+void
+http_free(struct http *http)
+{
+
+ if (NULL == http)
+ return;
+ http_disconnect(http);
+ if (NULL != http->cfg)
+ tls_config_free(http->cfg);
+ free(http->host);
+ free(http->path);
+ free(http->src.ip);
+ free(http);
+}
+
+struct http *
+http_alloc(const struct source *addrs, size_t addrsz,
+ const char *host, short port, const char *path)
+{
+ struct sockaddr_storage ss;
+ int family, fd, c;
+ socklen_t len;
+ size_t cur, i = 0;
+ struct http *http;
+
+ /* Do this while we still have addresses to connect. */
+again:
+ if (i == addrsz)
+ return(NULL);
+ cur = i++;
+
+ /* Convert to PF_INET or PF_INET6 address from string. */
+
+ memset(&ss, 0, sizeof(struct sockaddr_storage));
+
+ if (4 == addrs[cur].family) {
+ family = PF_INET;
+ ((struct sockaddr_in *)&ss)->sin_family = AF_INET;
+ ((struct sockaddr_in *)&ss)->sin_port = htons(port);
+ c = inet_pton(AF_INET, addrs[cur].ip,
+ &((struct sockaddr_in *)&ss)->sin_addr);
+ len = sizeof(struct sockaddr_in);
+ } else if (6 == addrs[cur].family) {
+ family = PF_INET6;
+ ((struct sockaddr_in6 *)&ss)->sin6_family = AF_INET6;
+ ((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
+ c = inet_pton(AF_INET6, addrs[cur].ip,
+ &((struct sockaddr_in6 *)&ss)->sin6_addr);
+ len = sizeof(struct sockaddr_in6);
+ } else {
+ warnx("%s: unknown family", addrs[cur].ip);
+ goto again;
+ }
+
+ if (c < 0) {
+ warn("%s: inet_ntop", addrs[cur].ip);
+ goto again;
+ } else if (0 == c) {
+ warnx("%s: inet_ntop", addrs[cur].ip);
+ goto again;
+ }
+
+ /* Create socket and connect. */
+
+ fd = socket(family, SOCK_STREAM, 0);
+ if (-1 == fd) {
+ warn("%s: socket", addrs[cur].ip);
+ goto again;
+ } else if (-1 == connect(fd, (struct sockaddr *)&ss, len)) {
+ warn("%s: connect", addrs[cur].ip);
+ close(fd);
+ goto again;
+ }
+
+ /* Allocate the communicator. */
+
+ http = calloc(1, sizeof(struct http));
+ if (NULL == http) {
+ warn("calloc");
+ close(fd);
+ return(NULL);
+ }
+ http->fd = fd;
+ http->port = port;
+ http->src.family = addrs[cur].family;
+ http->src.ip = strdup(addrs[cur].ip);
+ http->host = strdup(host);
+ http->path = strdup(path);
+ if (NULL == http->src.ip ||
+ NULL == http->host ||
+ NULL == http->path) {
+ warn("strdup");
+ goto err;
+ }
+
+ /* If necessary, do our TLS setup. */
+
+ if (443 != port) {
+ http->writer = dosyswrite;
+ http->reader = dosysread;
+ return(http);
+ }
+
+ http->writer = dotlswrite;
+ http->reader = dotlsread;
+
+ if (-1 == tls_init()) {
+ warn("tls_init");
+ goto err;
+ }
+
+ http->cfg = tls_config_new();
+ if (NULL == http->cfg) {
+ warn("tls_config_new");
+ goto err;
+ }
+
+ tls_config_set_protocols(http->cfg, TLS_PROTOCOLS_ALL);
+
+ /* FIXME: is this necessary? */
+ tls_config_insecure_noverifycert(http->cfg);
+
+ if (-1 == tls_config_set_ciphers(http->cfg, "compat")) {
+ warn("tls_config_set_ciphers");
+ goto err;
+ } else if (NULL == (http->ctx = tls_client())) {
+ warn("tls_client");
+ goto err;
+ } else if (-1 == tls_configure(http->ctx, http->cfg)) {
+ warnx("%s: tls_configure: %s",
+ http->src.ip, tls_error(http->ctx));
+ goto err;
+ }
+
+ if (0 != tls_connect_socket
+ (http->ctx, http->fd, http->host)) {
+ warnx("%s: tls_connect_socket: %s, %s",
+ http->src.ip, http->host,
+ tls_error(http->ctx));
+ goto err;
+ }
+
+ return(http);
+err:
+ http_free(http);
+ return(NULL);
+}
+
+struct httpxfer *
+http_open(const struct http *http, const void *p, size_t psz)
+{
+ char *req;
+ int c;
+ struct httpxfer *trans;
+
+ if (NULL == p) {
+ c = asprintf(&req,
+ "GET %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "\r\n",
+ http->path, http->host);
+ } else {
+ c = asprintf(&req,
+ "POST %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n",
+ http->path, http->host, psz);
+ }
+ if (-1 == c) {
+ warn("asprintf");
+ return(NULL);
+ } else if ( ! http_write(req, c, http)) {
+ free(req);
+ return(NULL);
+ } else if (NULL != p && ! http_write(p, psz, http)) {
+ free(req);
+ return(NULL);
+ }
+
+ free(req);
+
+ trans = calloc(1, sizeof(struct httpxfer));
+ if (NULL == trans)
+ warn("calloc");
+ return(trans);
+}
+
+void
+http_close(struct httpxfer *x)
+{
+
+ if (NULL == x)
+ return;
+ free(x->hbuf);
+ free(x->bbuf);
+ free(x->headbuf);
+ free(x->head);
+ free(x);
+}
+
+/*
+ * Read the HTTP body from the wire.
+ * If invoked multiple times, this will return the same pointer with the
+ * same data (or NULL, if the original invocation returned NULL).
+ * Returns NULL if read or allocation errors occur.
+ * You must not free the returned pointer.
+ */
+char *
+http_body_read(const struct http *http,
+ struct httpxfer *trans, size_t *sz)
+{
+ char buf[BUFSIZ];
+ ssize_t ssz;
+ void *pp;
+ size_t szp;
+
+ if (NULL == sz)
+ sz = &szp;
+
+ /* Have we already parsed this? */
+
+ if (trans->bodyok > 0) {
+ *sz = trans->bbufsz;
+ return(trans->bbuf);
+ } else if (trans->bodyok < 0)
+ return(NULL);
+
+ *sz = 0;
+ trans->bodyok = -1;
+
+ do {
+ /* If less than sizeof(buf), at EOF. */
+ if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
+ return(NULL);
+ else if (0 == ssz)
+ break;
+ pp = realloc(trans->bbuf, trans->bbufsz + ssz);
+ if (NULL == pp) {
+ warn("realloc");
+ return(NULL);
+ }
+ trans->bbuf = pp;
+ memcpy(trans->bbuf + trans->bbufsz, buf, ssz);
+ trans->bbufsz += ssz;
+ } while (sizeof(buf) == ssz);
+
+ trans->bodyok = 1;
+ *sz = trans->bbufsz;
+ return(trans->bbuf);
+}
+
+struct httphead *
+http_head_get(const char *v, struct httphead *h, size_t hsz)
+{
+ size_t i;
+
+ for (i = 0; i < hsz; i++) {
+ if (strcmp(h[i].key, v))
+ continue;
+ return(&h[i]);
+ }
+ return(NULL);
+}
+
+/*
+ * Look through the headers and determine our HTTP code.
+ * This will return -1 on failure, otherwise the code.
+ */
+int
+http_head_status(const struct http *http,
+ struct httphead *h, size_t sz)
+{
+ int rc;
+ unsigned int code;
+ struct httphead *st;
+
+ if (NULL == (st = http_head_get("Status", h, sz))) {
+ warnx("%s: no status header", http->src.ip);
+ return(-1);
+ }
+
+ rc = sscanf(st->val, "%*s %u %*s", &code);
+ if (rc < 0) {
+ warn("sscanf");
+ return(-1);
+ } else if (1 != rc) {
+ warnx("%s: cannot convert status header",
+ http->src.ip);
+ return(-1);
+ }
+ return(code);
+}
+
+/*
+ * Parse headers from the transfer.
+ * Malformed headers are skipped.
+ * A special "Status" header is added for the HTTP status line.
+ * This can only happen once http_head_read has been called with
+ * success.
+ * This can be invoked multiple times: it will only parse the headers
+ * once and after that it will just return the cache.
+ * You must not free the returned pointer.
+ * If the original header parse failed, or if memory allocation fails
+ * internally, this returns NULL.
+ */
+struct httphead *
+http_head_parse(const struct http *http,
+ struct httpxfer *trans, size_t *sz)
+{
+ size_t hsz, szp;
+ struct httphead *h;
+ char *cp, *ep, *ccp, *buf;
+
+ if (NULL == sz)
+ sz = &szp;
+
+ /*
+ * If we've already parsed the headers, return the
+ * previously-parsed buffer now.
+ * If we have errors on the stream, return NULL now.
+ */
+
+ if (NULL != trans->head) {
+ *sz = trans->headsz;
+ return(trans->head);
+ } else if (trans->headok <= 0)
+ return(NULL);
+
+ if (NULL == (buf = strdup(trans->hbuf))) {
+ warn("strdup");
+ return(NULL);
+ }
+ hsz = 0;
+ cp = buf;
+
+ do {
+ if (NULL != (cp = strstr(cp, "\r\n")))
+ cp += 2;
+ hsz++;
+ } while (NULL != cp);
+
+ /*
+ * Allocate headers, then step through the data buffer, parsing
+ * out headers as we have them.
+ * We know at this point that the buffer is nil-terminated in
+ * the usual way.
+ */
+
+ h = calloc(hsz, sizeof(struct httphead));
+ if (NULL == h) {
+ warn("calloc");
+ free(buf);
+ return(NULL);
+ }
+
+ *sz = hsz;
+ hsz = 0;
+ cp = buf;
+
+ do {
+ if (NULL != (ep = strstr(cp, "\r\n"))) {
+ *ep = '\0';
+ ep += 2;
+ }
+ if (0 == hsz) {
+ h[hsz].key = "Status";
+ h[hsz++].val = cp;
+ continue;
+ }
+
+ /* Skip bad headers. */
+ if (NULL == (ccp = strchr(cp, ':'))) {
+ warnx("%s: header without separator",
+ http->src.ip);
+ continue;
+ }
+
+ *ccp++ = '\0';
+ while (isspace((int)*ccp))
+ ccp++;
+ h[hsz].key = cp;
+ h[hsz++].val = ccp;
+ } while (NULL != (cp = ep));
+
+ trans->headbuf = buf;
+ trans->head = h;
+ trans->headsz = hsz;
+ return(h);
+}
+
+/*
+ * Read the HTTP headers from the wire.
+ * If invoked multiple times, this will return the same pointer with the
+ * same data (or NULL, if the original invocation returned NULL).
+ * Returns NULL if read or allocation errors occur.
+ * You must not free the returned pointer.
+ */
+char *
+http_head_read(const struct http *http,
+ struct httpxfer *trans, size_t *sz)
+{
+ char buf[BUFSIZ];
+ ssize_t ssz;
+ char *ep;
+ void *pp;
+ size_t szp;
+
+ if (NULL == sz)
+ sz = &szp;
+
+ /* Have we already parsed this? */
+
+ if (trans->headok > 0) {
+ *sz = trans->hbufsz;
+ return(trans->hbuf);
+ } else if (trans->headok < 0)
+ return(NULL);
+
+ *sz = 0;
+ ep = NULL;
+ trans->headok = -1;
+
+ /*
+ * Begin by reading by BUFSIZ blocks until we reach the header
+ * termination marker (two CRLFs).
+ * We might read into our body, but that's ok: we'll copy out
+ * the body parts into our body buffer afterward.
+ */
+
+ do {
+ /* If less than sizeof(buf), at EOF. */
+ if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
+ return(NULL);
+ else if (0 == ssz)
+ break;
+ pp = realloc(trans->hbuf, trans->hbufsz + ssz);
+ if (NULL == pp) {
+ warn("realloc");
+ return(NULL);
+ }
+ trans->hbuf = pp;
+ memcpy(trans->hbuf + trans->hbufsz, buf, ssz);
+ trans->hbufsz += ssz;
+ /* Search for end of headers marker. */
+ ep = memmem(trans->hbuf, trans->hbufsz, "\r\n\r\n", 4);
+ } while (NULL == ep && sizeof(buf) == ssz);
+
+ if (NULL == ep) {
+ warnx("%s: partial transfer", http->src.ip);
+ return(NULL);
+ }
+ *ep = '\0';
+
+ /*
+ * The header data is invalid if it has any binary characters in
+ * it: check that now.
+ * This is important because we want to guarantee that all
+ * header keys and pairs are properly nil-terminated.
+ */
+
+ if (strlen(trans->hbuf) != (uintptr_t)(ep - trans->hbuf)) {
+ warnx("%s: binary data in header", http->src.ip);
+ return(NULL);
+ }
+
+ /*
+ * Copy remaining buffer into body buffer.
+ */
+
+ ep += 4;
+ trans->bbufsz = (trans->hbuf + trans->hbufsz) - ep;
+ trans->bbuf = malloc(trans->bbufsz);
+ if (NULL == trans->bbuf) {
+ warn("malloc");
+ return(NULL);
+ }
+ memcpy(trans->bbuf, ep, trans->bbufsz);
+
+ trans->headok = 1;
+ *sz = trans->hbufsz;
+ return(trans->hbuf);
+}
+
+void
+http_get_free(struct httpget *g)
+{
+
+ if (NULL == g)
+ return;
+ http_close(g->xfer);
+ http_free(g->http);
+ free(g);
+}
+
+struct httpget *
+http_get(const struct source *addrs, size_t addrsz,
+ const char *domain, short port, const char *path,
+ const void *post, size_t postsz)
+{
+ struct http *h;
+ struct httpxfer *x;
+ struct httpget *g;
+ struct httphead *head;
+ size_t headsz, bodsz, headrsz;
+ int code;
+ char *bod, *headr;
+
+ h = http_alloc(addrs, addrsz, domain, port, path);
+ if (NULL == h)
+ return(NULL);
+
+ if (NULL == (x = http_open(h, post, postsz))) {
+ http_free(h);
+ return(NULL);
+ } else if (NULL == (headr = http_head_read(h, x, &headrsz))) {
+ http_close(x);
+ http_free(h);
+ return(NULL);
+ } else if (NULL == (bod = http_body_read(h, x, &bodsz))) {
+ http_close(x);
+ http_free(h);
+ return(NULL);
+ }
+
+ http_disconnect(h);
+
+ if (NULL == (head = http_head_parse(h, x, &headsz))) {
+ http_close(x);
+ http_free(h);
+ return(NULL);
+ } else if ((code = http_head_status(h, head, headsz)) < 0) {
+ http_close(x);
+ http_free(h);
+ return(NULL);
+ }
+
+ if (NULL == (g = calloc(1, sizeof(struct httpget)))) {
+ warn("calloc");
+ http_close(x);
+ http_free(h);
+ return(NULL);
+ }
+
+ g->headpart = headr;
+ g->headpartsz = headrsz;
+ g->bodypart = bod;
+ g->bodypartsz = bodsz;
+ g->head = head;
+ g->headsz = headsz;
+ g->code = code;
+ g->xfer = x;
+ g->http = h;
+ return(g);
+}
+
+#if 0
+int
+main(void)
+{
+ struct httpget *g;
+ struct httphead *httph;
+ size_t i, httphsz;
+ struct source addrs[2];
+ size_t addrsz;
+
+#if 0
+ addrs[0].ip = "127.0.0.1";
+ addrs[0].family = 4;
+ addrsz = 1;
+#else
+ addrs[0].ip = "2a00:1450:400a:806::2004";
+ addrs[0].family = 6;
+ addrs[1].ip = "193.135.3.123";
+ addrs[1].family = 4;
+ addrsz = 2;
+#endif
+
+#if 0
+ g = http_get(addrs, addrsz, "localhost", 80, "/index.html");
+#else
+ g = http_get(addrs, addrsz, "www.google.ch", 80, "/index.html", NULL, 0);
+#endif
+
+ if (NULL == g)
+ errx(EXIT_FAILURE, "http_get");
+
+ httph = http_head_parse(g->http, g->xfer, &httphsz);
+ warnx("code: %d", g->code);
+
+ for (i = 0; i < httphsz; i++)
+ warnx("head: [%s]=[%s]", httph[i].key, httph[i].val);
+
+ http_get_free(g);
+ return(EXIT_SUCCESS);
+}
+#endif
diff --git a/usr.sbin/acme-client/http.h b/usr.sbin/acme-client/http.h
new file mode 100644
index 00000000000..9ed1d163595
--- /dev/null
+++ b/usr.sbin/acme-client/http.h
@@ -0,0 +1,94 @@
+/* $Id: http.h,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef HTTP_H
+#define HTTP_H
+
+struct source {
+ int family; /* 4 (PF_INET) or 6 (PF_INET6) */
+ char *ip; /* IPV4 or IPV6 address */
+};
+
+struct http;
+
+/*
+ * Write and read callbacks to allow HTTP and HTTPS.
+ * Both of these return the number of bytes read (or written) or -1 on
+ * failure.
+ * 0 bytes read means that the connection has closed.
+ */
+typedef ssize_t (*writefp)(const void *, size_t, const struct http *);
+typedef ssize_t (*readfp)(char *, size_t, const struct http *);
+
+/*
+ * HTTP/S header pair.
+ * There's also a cooked-up pair, "Status", with the status code.
+ * Both strings are nil-terminated.
+ */
+struct httphead {
+ const char *key;
+ const char *val;
+};
+
+/*
+ * Grab all information from a transfer.
+ * DO NOT free any parts of this, and editing the parts (e.g., changing
+ * the underlying strings) will persist; so in short, don't.
+ * All of these values will be set upon http_get() success.
+ */
+struct httpget {
+ struct httpxfer *xfer; /* underlying transfer */
+ struct http *http; /* underlying connection */
+ int code; /* return code */
+ struct httphead *head; /* headers */
+ size_t headsz; /* number of headers */
+ char *headpart; /* header buffer */
+ size_t headpartsz; /* size of headpart */
+ char *bodypart; /* body buffer */
+ size_t bodypartsz; /* size of bodypart */
+};
+
+__BEGIN_DECLS
+
+/* Convenience functions. */
+struct httpget *http_get(const struct source *, size_t,
+ const char *, short, const char *,
+ const void *, size_t);
+void http_get_free(struct httpget *);
+
+/* Allocation and release. */
+struct http *http_alloc(const struct source *, size_t,
+ const char *, short, const char *);
+void http_free(struct http *);
+struct httpxfer *http_open(const struct http *, const void *, size_t);
+void http_close(struct httpxfer *);
+void http_disconnect(struct http *);
+
+/* Access. */
+char *http_head_read(const struct http *,
+ struct httpxfer *, size_t *);
+struct httphead *http_head_parse(const struct http *,
+ struct httpxfer *, size_t *);
+char *http_body_read(const struct http *,
+ struct httpxfer *, size_t *);
+int http_head_status(const struct http *,
+ struct httphead *, size_t);
+struct httphead *http_head_get(const char *,
+ struct httphead *, size_t);
+
+__END_DECLS
+
+#endif /* HTTP_H */
diff --git a/usr.sbin/acme-client/jsmn.c b/usr.sbin/acme-client/jsmn.c
new file mode 100644
index 00000000000..26676fa7b2f
--- /dev/null
+++ b/usr.sbin/acme-client/jsmn.c
@@ -0,0 +1,332 @@
+/*
+ Copyright (c) 2010 Serge A. Zaitsev
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*
+ */
+#include "jsmn.h"
+
+/**
+ * Allocates a fresh unused token from the token pull.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *tok;
+ if (parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = -1;
+ tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+ tok->parent = -1;
+#endif
+ return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
+ int start, int end) {
+ token->type = type;
+ token->start = start;
+ token->end = end;
+ token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+ int start;
+
+ start = parser->pos;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
+ case ':':
+#endif
+ case '\t' : case '\r' : case '\n' : case ' ' :
+ case ',' : case ']' : case '}' :
+ goto found;
+ }
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#ifdef JSMN_STRICT
+ /* In strict mode primitive must be followed by a comma/object/array */
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+#endif
+
+found:
+ if (tokens == NULL) {
+ parser->pos--;
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ parser->pos--;
+ return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+
+ int start = parser->pos;
+
+ parser->pos++;
+
+ /* Skip starting quote */
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c = js[parser->pos];
+
+ /* Quote: end of string */
+ if (c == '\"') {
+ if (tokens == NULL) {
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ return 0;
+ }
+
+ /* Backslash: Quoted symbol expected */
+ if (c == '\\' && parser->pos + 1 < len) {
+ int i;
+ parser->pos++;
+ switch (js[parser->pos]) {
+ /* Allowed escaped symbols */
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ break;
+ /* Allows escaped symbol \uXXXX */
+ case 'u':
+ parser->pos++;
+ for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ /* If it isn't a hex character we have an error */
+ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ /* Unexpected symbol */
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ int r;
+ int i;
+ jsmntok_t *token;
+ int count = parser->toknext;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c;
+ jsmntype_t type;
+
+ c = js[parser->pos];
+ switch (c) {
+ case '{': case '[':
+ count++;
+ if (tokens == NULL) {
+ break;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL)
+ return JSMN_ERROR_NOMEM;
+ if (parser->toksuper != -1) {
+ tokens[parser->toksuper].size++;
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ }
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+ token->start = parser->pos;
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case '}': case ']':
+ if (tokens == NULL)
+ break;
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+ if (parser->toknext < 1) {
+ return JSMN_ERROR_INVAL;
+ }
+ token = &tokens[parser->toknext - 1];
+ for (;;) {
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ token->end = parser->pos + 1;
+ parser->toksuper = token->parent;
+ break;
+ }
+ if (token->parent == -1) {
+ break;
+ }
+ token = &tokens[token->parent];
+ }
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ parser->toksuper = -1;
+ token->end = parser->pos + 1;
+ break;
+ }
+ }
+ /* Error if unmatched closing bracket */
+ if (i == -1) return JSMN_ERROR_INVAL;
+ for (; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+#endif
+ break;
+ case '\"':
+ r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+ case '\t' : case '\r' : case '\n' : case ' ':
+ break;
+ case ':':
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case ',':
+ if (tokens != NULL && parser->toksuper != -1 &&
+ tokens[parser->toksuper].type != JSMN_ARRAY &&
+ tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+ parser->toksuper = tokens[parser->toksuper].parent;
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+ }
+#endif
+ }
+ break;
+#ifdef JSMN_STRICT
+ /* In strict mode primitives are: numbers and booleans */
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
+ case '5': case '6': case '7' : case '8': case '9':
+ case 't': case 'f': case 'n' :
+ /* And they must not be keys of the object */
+ if (tokens != NULL && parser->toksuper != -1) {
+ jsmntok_t *t = &tokens[parser->toksuper];
+ if (t->type == JSMN_OBJECT ||
+ (t->type == JSMN_STRING && t->size != 0)) {
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#else
+ /* In non-strict mode every unquoted value is a primitive */
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+
+#ifdef JSMN_STRICT
+ /* Unexpected char in strict mode */
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ if (tokens != NULL) {
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ /* Unmatched opened object or array */
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ return JSMN_ERROR_PART;
+ }
+ }
+ }
+
+ return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+void jsmn_init(jsmn_parser *parser) {
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
diff --git a/usr.sbin/acme-client/jsmn.h b/usr.sbin/acme-client/jsmn.h
new file mode 100644
index 00000000000..0a3c5d7265e
--- /dev/null
+++ b/usr.sbin/acme-client/jsmn.h
@@ -0,0 +1,97 @@
+/*
+ Copyright (c) 2010 Serge A. Zaitsev
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*
+ */
+#ifndef __JSMN_H_
+#define __JSMN_H_
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * o Object
+ * o Array
+ * o String
+ * o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+ JSMN_UNDEFINED = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3,
+ JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+ /* Not enough tokens were provided */
+ JSMN_ERROR_NOMEM = -1,
+ /* Invalid character inside JSON string */
+ JSMN_ERROR_INVAL = -2,
+ /* The string is not a full JSON packet, more bytes expected */
+ JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * @param type type (object, array, string etc.)
+ * @param start start position in JSON data string
+ * @param end end position in JSON data string
+ */
+typedef struct {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string
+ */
+typedef struct {
+ unsigned int pos; /* offset in the JSON string */
+ unsigned int toknext; /* next token to allocate */
+ int toksuper; /* superior token node, e.g parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
+ * a single JSON object.
+ */
+int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __JSMN_H_ */
diff --git a/usr.sbin/acme-client/json.c b/usr.sbin/acme-client/json.c
new file mode 100644
index 00000000000..0c23f5490d7
--- /dev/null
+++ b/usr.sbin/acme-client/json.c
@@ -0,0 +1,632 @@
+/* $Id: json.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "jsmn.h"
+#include "extern.h"
+
+struct jsmnp;
+
+/*
+ * A node in the JSMN parse tree.
+ * Each of this corresponds to an object in the original JSMN token
+ * list, although the contents have been extracted properly.
+ */
+struct jsmnn {
+ struct parse *p; /* parser object */
+ union {
+ char *str; /* JSMN_PRIMITIVE, JSMN_STRING */
+ struct jsmnp *obj; /* JSMN_OBJECT */
+ struct jsmnn **array; /* JSMN_ARRAY */
+ } d;
+ size_t fields; /* entries in "d" */
+ jsmntype_t type; /* type of node */
+};
+
+/*
+ * Objects consist of node pairs: the left-hand side (before the colon)
+ * and the right-hand side---the data.
+ */
+struct jsmnp {
+ struct jsmnn *lhs; /* left of colon */
+ struct jsmnn *rhs; /* right of colon */
+};
+
+/*
+ * Object for converting the JSMN token array into a tree.
+ */
+struct parse {
+ struct jsmnn *nodes; /* all nodes */
+ size_t cur; /* current number */
+ size_t max; /* nodes in "nodes" */
+};
+
+/*
+ * Recursive part for convertin a JSMN token array into a tree.
+ * See "example/jsondump.c" for its construction (it's the same except
+ * for how it handles allocation errors).
+ */
+static ssize_t
+build(struct parse *parse, struct jsmnn **np,
+ jsmntok_t *t, const char *js, size_t sz)
+{
+ size_t i, j;
+ struct jsmnn *n;
+ ssize_t tmp;
+
+ if (0 == sz)
+ return(0);
+
+ assert(parse->cur < parse->max);
+ n = *np = &parse->nodes[parse->cur++];
+ n->p = parse;
+ n->type = t->type;
+
+ switch (t->type) {
+ case (JSMN_STRING):
+ /* FALLTHROUGH */
+ case (JSMN_PRIMITIVE):
+ n->fields = 1;
+ n->d.str = strndup
+ (js + t->start,
+ t->end - t->start);
+ if (NULL == n->d.str)
+ break;
+ return(1);
+ case (JSMN_OBJECT):
+ n->fields = t->size;
+ n->d.obj = calloc(n->fields,
+ sizeof(struct jsmnp));
+ if (NULL == n->d.obj)
+ break;
+ for (i = j = 0; i < (size_t)t->size; i++) {
+ tmp = build(parse,
+ &n->d.obj[i].lhs,
+ t + 1 + j, js, sz - j);
+ if (tmp < 0)
+ break;
+ j += tmp;
+ tmp = build(parse,
+ &n->d.obj[i].rhs,
+ t + 1 + j, js, sz - j);
+ if (tmp < 0)
+ break;
+ j += tmp;
+ }
+ if (i < (size_t)t->size)
+ break;
+ return(j + 1);
+ case (JSMN_ARRAY):
+ n->fields = t->size;
+ n->d.array = calloc(n->fields,
+ sizeof(struct jsmnn *));
+ if (NULL == n->d.array)
+ break;
+ for (i = j = 0; i < (size_t)t->size; i++) {
+ tmp = build(parse,
+ &n->d.array[i],
+ t + 1 + j, js, sz - j);
+ if (tmp < 0)
+ break;
+ j += tmp;
+ }
+ if (i < (size_t)t->size)
+ break;
+ return(j + 1);
+ default:
+ break;
+ }
+
+ return(-1);
+}
+
+/*
+ * Fully free up a parse sequence.
+ * This handles all nodes sequentially, not recursively.
+ */
+static void
+jsmnparse_free(struct parse *p)
+{
+ size_t i;
+
+ if (NULL == p)
+ return;
+ for (i = 0; i < p->max; i++)
+ if (JSMN_ARRAY == p->nodes[i].type)
+ free(p->nodes[i].d.array);
+ else if (JSMN_OBJECT == p->nodes[i].type)
+ free(p->nodes[i].d.obj);
+ else if (JSMN_PRIMITIVE == p->nodes[i].type)
+ free(p->nodes[i].d.str);
+ else if (JSMN_STRING == p->nodes[i].type)
+ free(p->nodes[i].d.str);
+ free(p->nodes);
+ free(p);
+}
+
+/*
+ * Allocate a tree representation of "t".
+ * This returns NULL on allocation failure or when sz is zero, in which
+ * case all resources allocated along the way are freed already.
+ */
+static struct jsmnn *
+jsmntree_alloc(jsmntok_t *t, const char *js, size_t sz)
+{
+ struct jsmnn *first;
+ struct parse *p;
+
+ if (0 == sz)
+ return(NULL);
+
+ p = calloc(1, sizeof(struct parse));
+ if (NULL == p)
+ return(NULL);
+
+ p->max = sz;
+ p->nodes = calloc(p->max, sizeof(struct jsmnn));
+ if (NULL == p->nodes) {
+ free(p);
+ return(NULL);
+ }
+
+ if (build(p, &first, t, js, sz) < 0) {
+ jsmnparse_free(p);
+ first = NULL;
+ }
+
+ return(first);
+}
+
+/*
+ * Call through to free parse contents.
+ */
+void
+json_free(struct jsmnn *first)
+{
+
+ if (NULL != first)
+ jsmnparse_free(first->p);
+}
+
+/*
+ * Just check that the array object is in fact an object.
+ */
+static struct jsmnn *
+json_getarrayobj(struct jsmnn *n)
+{
+
+ return (JSMN_OBJECT != n->type ? NULL : n);
+}
+
+/*
+ * Extract an array from the returned JSON object, making sure that it's
+ * the correct type.
+ * Returns NULL on failure.
+ */
+static struct jsmnn *
+json_getarray(struct jsmnn *n, const char *name)
+{
+ size_t i;
+
+ if (JSMN_OBJECT != n->type)
+ return(NULL);
+ for (i = 0; i < n->fields; i++) {
+ if (JSMN_STRING != n->d.obj[i].lhs->type &&
+ JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
+ continue;
+ else if (strcmp(name, n->d.obj[i].lhs->d.str))
+ continue;
+ break;
+ }
+ if (i == n->fields)
+ return(NULL);
+ if (JSMN_ARRAY != n->d.obj[i].rhs->type)
+ return(NULL);
+ return(n->d.obj[i].rhs);
+}
+
+/*
+ * Extract a single string from the returned JSON object, making sure
+ * that it's the correct type.
+ * Returns NULL on failure.
+ */
+static char *
+json_getstr(struct jsmnn *n, const char *name)
+{
+ size_t i;
+ char *cp;
+
+ if (JSMN_OBJECT != n->type)
+ return(NULL);
+ for (i = 0; i < n->fields; i++) {
+ if (JSMN_STRING != n->d.obj[i].lhs->type &&
+ JSMN_PRIMITIVE != n->d.obj[i].lhs->type)
+ continue;
+ else if (strcmp(name, n->d.obj[i].lhs->d.str))
+ continue;
+ break;
+ }
+ if (i == n->fields)
+ return(NULL);
+ if (JSMN_STRING != n->d.obj[i].rhs->type &&
+ JSMN_PRIMITIVE != n->d.obj[i].rhs->type)
+ return(NULL);
+
+ cp = strdup(n->d.obj[i].rhs->d.str);
+ if (NULL == cp)
+ warn("strdup");
+ return(cp);
+}
+
+/*
+ * Completely free the challenge response body.
+ */
+void
+json_free_challenge(struct chng *p)
+{
+
+ free(p->uri);
+ free(p->token);
+ p->uri = p->token = NULL;
+}
+
+/*
+ * Parse the response from the ACME server when we're waiting to see
+ * whether the challenge has been ok.
+ */
+int
+json_parse_response(struct jsmnn *n)
+{
+ char *resp;
+ int rc;
+
+ if (NULL == n)
+ return(-1);
+ if (NULL == (resp = json_getstr(n, "status")))
+ return(-1);
+
+ if (0 == strcmp(resp, "valid"))
+ rc = 1;
+ else if (0 == strcmp(resp, "pending"))
+ rc = 0;
+ else
+ rc = -1;
+
+ free(resp);
+ return(rc);
+}
+
+/*
+ * Parse the response from a new-authz, which consists of challenge
+ * information, into a structure.
+ * We only care about the HTTP-01 response.
+ */
+int
+json_parse_challenge(struct jsmnn *n, struct chng *p)
+{
+ struct jsmnn *array, *obj;
+ size_t i;
+ int rc;
+ char *type;
+
+ if (NULL == n)
+ return(0);
+
+ array = json_getarray(n, "challenges");
+ if (NULL == array)
+ return(0);
+
+ for (i = 0; i < array->fields; i++) {
+ obj = json_getarrayobj(array->d.array[i]);
+ if (NULL == obj)
+ continue;
+ type = json_getstr(obj, "type");
+ if (NULL == type)
+ continue;
+ rc = strcmp(type, "http-01");
+ free(type);
+ if (rc)
+ continue;
+ p->uri = json_getstr(obj, "uri");
+ p->token = json_getstr(obj, "token");
+ return(NULL != p->uri &&
+ NULL != p->token);
+ }
+
+ return(0);
+}
+
+/*
+ * Extract the CA paths from the JSON response object.
+ * Return zero on failure, non-zero on success.
+ */
+int
+json_parse_capaths(struct jsmnn *n, struct capaths *p)
+{
+
+ if (NULL == n)
+ return(0);
+
+ p->newauthz = json_getstr(n, "new-authz");
+ p->newcert = json_getstr(n, "new-cert");
+ p->newreg = json_getstr(n, "new-reg");
+ p->revokecert = json_getstr(n, "revoke-cert");
+
+ return(NULL != p->newauthz &&
+ NULL != p->newcert &&
+ NULL != p->newreg &&
+ NULL != p->revokecert);
+}
+
+/*
+ * Free up all of our CA-noted paths (which may all be NULL).
+ */
+void
+json_free_capaths(struct capaths *p)
+{
+
+ free(p->newauthz);
+ free(p->newcert);
+ free(p->newreg);
+ free(p->revokecert);
+ memset(p, 0, sizeof(struct capaths));
+}
+
+/*
+ * Parse an HTTP response body from a buffer of size "sz".
+ * Returns an opaque pointer on success, otherwise NULL on error.
+ */
+struct jsmnn *
+json_parse(const char *buf, size_t sz)
+{
+ struct jsmnn *n;
+ jsmn_parser p;
+ jsmntok_t *tok;
+ int r;
+ size_t tokcount;
+
+ jsmn_init(&p);
+ tokcount = 128;
+
+ /* Do this until we don't need any more tokens. */
+again:
+ tok = calloc(tokcount, sizeof(jsmntok_t));
+ if (NULL == tok) {
+ warn("calloc");
+ return(NULL);
+ }
+
+ /* Actually try to parse the JSON into the tokens. */
+
+ r = jsmn_parse(&p, buf, sz, tok, tokcount);
+ if (r < 0 && JSMN_ERROR_NOMEM == r) {
+ tokcount *= 2;
+ free(tok);
+ goto again;
+ } else if (r < 0) {
+ warnx("jsmn_parse: %d", r);
+ free(tok);
+ return(NULL);
+ }
+
+ /* Now parse the tokens into a tree. */
+
+ n = jsmntree_alloc(tok, buf, r);
+ free(tok);
+ return(n);
+}
+
+/*
+ * Format the "new-reg" resource request.
+ */
+char *
+json_fmt_newreg(const char *license)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"new-reg\", "
+ "\"agreement\": \"%s\""
+ "}", license);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "new-authz" resource request.
+ */
+char *
+json_fmt_newauthz(const char *domain)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"new-authz\", "
+ "\"identifier\": "
+ "{\"type\": \"dns\", \"value\": \"%s\"}"
+ "}", domain);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "challenge" resource request.
+ */
+char *
+json_fmt_challenge(const char *token, const char *thumb)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"challenge\", "
+ "\"keyAuthorization\": \"%s.%s\""
+ "}", token, thumb);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "new-cert" resource request.
+ */
+char *
+json_fmt_revokecert(const char *cert)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"revoke-cert\", "
+ "\"certificate\": \"%s\""
+ "}", cert);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Format the "new-cert" resource request.
+ */
+char *
+json_fmt_newcert(const char *cert)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"resource\": \"new-cert\", "
+ "\"csr\": \"%s\""
+ "}", cert);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Header component of json_fmt_signed().
+ */
+char *
+json_fmt_header_rsa(const char *exp, const char *mod)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"alg\": \"RS256\", "
+ "\"jwk\": "
+ "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}"
+ "}", exp, mod);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Protected component of json_fmt_signed().
+ */
+char *
+json_fmt_protected_rsa(const char *exp, const char *mod, const char *nce)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"alg\": \"RS256\", "
+ "\"jwk\": "
+ "{\"e\": \"%s\", \"kty\": \"RSA\", \"n\": \"%s\"}, "
+ "\"nonce\": \"%s\""
+ "}", exp, mod, nce);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Signed message contents for the CA server.
+ */
+char *
+json_fmt_signed(const char *header, const char *protected,
+ const char *payload, const char *digest)
+{
+ int c;
+ char *p;
+
+ c = asprintf(&p, "{"
+ "\"header\": %s, "
+ "\"protected\": \"%s\", "
+ "\"payload\": \"%s\", "
+ "\"signature\": \"%s\""
+ "}", header, protected, payload, digest);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
+
+/*
+ * Produce thumbprint input.
+ * This isn't technically a JSON string--it's the input we'll use for
+ * hashing and digesting.
+ * However, it's in the form of a JSON string, so do it here.
+ */
+char *
+json_fmt_thumb_rsa(const char *exp, const char *mod)
+{
+ int c;
+ char *p;
+
+ /*NOTE: WHITESPACE IS IMPORTANT. */
+
+ c = asprintf(&p,
+ "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}",
+ exp, mod);
+ if (-1 == c) {
+ warn("asprintf");
+ p = NULL;
+ }
+ return(p);
+}
diff --git a/usr.sbin/acme-client/keyproc.c b/usr.sbin/acme-client/keyproc.c
new file mode 100644
index 00000000000..5f728919b84
--- /dev/null
+++ b/usr.sbin/acme-client/keyproc.c
@@ -0,0 +1,293 @@
+/* $Id: keyproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "extern.h"
+#include "rsa.h"
+
+/*
+ * This was lifted more or less directly from demos/x509/mkreq.c of the
+ * OpenSSL source code.
+ */
+static int
+add_ext(STACK_OF(X509_EXTENSION) *sk, int nid, const char *value)
+{
+ X509_EXTENSION *ex;
+ char *cp;
+
+ /*
+ * XXX: I don't like this at all.
+ * There's no documentation for X509V3_EXT_conf_nid, so I'm not
+ * sure if the "value" parameter is ever written to, touched,
+ * etc.
+ * The 'official' examples suggest not (they use a string
+ * literal as the input), but to be safe, I'm doing an
+ * allocation here and just letting it go.
+ * This leaks memory, but bounded to the number of SANs.
+ */
+
+ if (NULL == (cp = strdup(value))) {
+ warn("strdup");
+ return(0);
+ }
+ ex = X509V3_EXT_conf_nid(NULL, NULL, nid, cp);
+ if (NULL == ex) {
+ warnx("X509V3_EXT_conf_nid");
+ free(cp);
+ return(0);
+ }
+ sk_X509_EXTENSION_push(sk, ex);
+ return(1);
+}
+
+/*
+ * Create an X509 certificate from the private key we have on file.
+ * To do this, we first open the key file, then jail ourselves.
+ * We then use the crypto library to create the certificate within the
+ * jail and, on success, ship it to "netsock" as an X509 request.
+ */
+int
+keyproc(int netsock, const char *keyfile,
+ const char **alts, size_t altsz, int newkey)
+{
+ char *der64, *der, *dercp, *sans, *san;
+ FILE *f;
+ size_t i, sansz;
+ void *pp;
+ EVP_PKEY *pkey;
+ X509_REQ *x;
+ X509_NAME *name;
+ unsigned char rbuf[64];
+ int len, rc, cc, nid;
+ mode_t prev;
+ STACK_OF(X509_EXTENSION) *exts;
+
+ x = NULL;
+ pkey = NULL;
+ name = NULL;
+ der = der64 = sans = san = NULL;
+ rc = 0;
+ exts = NULL;
+
+ /*
+ * First, open our private key file read-only or write-only if
+ * we're creating from scratch.
+ * Set our umask to be maximally restrictive.
+ */
+
+ prev = umask((S_IWUSR | S_IXUSR) | S_IRWXG | S_IRWXO);
+ f = fopen(keyfile, newkey ? "wx" : "r");
+ umask(prev);
+
+ if (NULL == f) {
+ warn("%s", keyfile);
+ goto out;
+ }
+
+ /* File-system, user, and sandbox jail. */
+
+ if ( ! sandbox_before())
+ goto out;
+
+ ERR_load_crypto_strings();
+
+ if ( ! dropfs(PATH_VAR_EMPTY))
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * Seed our PRNG with data from arc4random().
+ * Do this until we're told it's ok and use increments of 64
+ * bytes (arbitrarily).
+ * TODO: is this sufficient as a RAND source?
+ */
+
+ while (0 == RAND_status()) {
+ arc4random_buf(rbuf, sizeof(rbuf));
+ RAND_seed(rbuf, sizeof(rbuf));
+ }
+
+ if (newkey) {
+ if (NULL == (pkey = rsa_key_create(f, keyfile)))
+ goto out;
+ dodbg("%s: generated RSA domain key", keyfile);
+ } else {
+ if (NULL == (pkey = rsa_key_load(f, keyfile)))
+ goto out;
+ doddbg("%s: loaded RSA domain key", keyfile);
+ }
+
+ fclose(f);
+ f = NULL;
+
+ /*
+ * Generate our certificate from the EVP public key.
+ * Then set it as the X509 requester's key.
+ */
+
+ if (NULL == (x = X509_REQ_new())) {
+ warnx("X509_new");
+ goto out;
+ } else if ( ! X509_REQ_set_pubkey(x, pkey)) {
+ warnx("X509_set_pubkey");
+ goto out;
+ }
+
+ /* Now specify the common name that we'll request. */
+
+ if (NULL == (name = X509_NAME_new())) {
+ warnx("X509_NAME_new");
+ goto out;
+ } else if ( ! X509_NAME_add_entry_by_txt(name, "CN",
+ MBSTRING_ASC, (u_char *)alts[0], -1, -1, 0)) {
+ warnx("X509_NAME_add_entry_by_txt: CN=%s", alts[0]);
+ goto out;
+ } else if ( ! X509_REQ_set_subject_name(x, name)) {
+ warnx("X509_req_set_issuer_name");
+ goto out;
+ }
+
+ /*
+ * Now add the SAN extensions.
+ * This was lifted more or less directly from demos/x509/mkreq.c
+ * of the OpenSSL source code.
+ * (The zeroth altname is the domain name.)
+ * TODO: is this the best way of doing this?
+ */
+
+ if (altsz > 1) {
+ nid = NID_subject_alt_name;
+ if (NULL == (exts = sk_X509_EXTENSION_new_null())) {
+ warnx("sk_X509_EXTENSION_new_null");
+ goto out;
+ }
+ /* Initialise to empty string. */
+ if (NULL == (sans = strdup(""))) {
+ warn("strdup");
+ goto out;
+ }
+ sansz = strlen(sans) + 1;
+
+ /*
+ * For each SAN entry, append it to the string.
+ * We need a single SAN entry for all of the SAN
+ * domains: NOT an entry per domain!
+ */
+
+ for (i = 1; i < altsz; i++) {
+ cc = asprintf(&san, "%sDNS:%s",
+ i > 1 ? "," : "", alts[i]);
+ if (-1 == cc) {
+ warn("asprintf");
+ goto out;
+ }
+ pp = realloc(sans, sansz + strlen(san));
+ if (NULL == pp) {
+ warn("realloc");
+ goto out;
+ }
+ sans = pp;
+ sansz += strlen(san);
+ strlcat(sans, san, sansz);
+ free(san);
+ san = NULL;
+ }
+
+ if ( ! add_ext(exts, nid, sans)) {
+ warnx("add_ext");
+ goto out;
+ } else if ( ! X509_REQ_add_extensions(x, exts)) {
+ warnx("X509_REQ_add_extensions");
+ goto out;
+ }
+ sk_X509_EXTENSION_pop_free
+ (exts, X509_EXTENSION_free);
+ }
+
+ /* Sign the X509 request using SHA256. */
+
+ if ( ! X509_REQ_sign(x, pkey, EVP_sha256())) {
+ warnx("X509_sign");
+ goto out;
+ }
+
+ /* Now, serialise to DER, then base64. */
+
+ if ((len = i2d_X509_REQ(x, NULL)) < 0) {
+ warnx("i2d_X509");
+ goto out;
+ } else if (NULL == (der = dercp = malloc(len))) {
+ warn("malloc");
+ goto out;
+ } else if (len != i2d_X509_REQ(x, (u_char **)&dercp)) {
+ warnx("i2d_X509");
+ goto out;
+ } else if (NULL == (der64 = base64buf_url(der, len))) {
+ warnx("base64buf_url");
+ goto out;
+ }
+
+ /*
+ * Write that we're ready, then write.
+ * We ignore reader-closed failure, as we're just going to roll
+ * into the exit case anyway.
+ */
+
+ if (writeop(netsock, COMM_KEY_STAT, KEY_READY) < 0)
+ goto out;
+ if (writestr(netsock, COMM_CERT, der64) < 0)
+ goto out;
+
+ rc = 1;
+out:
+ close(netsock);
+ if (NULL != f)
+ fclose(f);
+ free(der);
+ free(der64);
+ free(sans);
+ free(san);
+ if (NULL != x)
+ X509_REQ_free(x);
+ if (NULL != name)
+ X509_NAME_free(name);
+ if (NULL != pkey)
+ EVP_PKEY_free(pkey);
+ ERR_print_errors_fp(stderr);
+ ERR_free_strings();
+ return(rc);
+}
+
diff --git a/usr.sbin/acme-client/letskencrypt.1 b/usr.sbin/acme-client/letskencrypt.1
new file mode 100644
index 00000000000..72aa22cf319
--- /dev/null
+++ b/usr.sbin/acme-client/letskencrypt.1
@@ -0,0 +1,323 @@
+.Dd $Mdocdate: August 31 2016 $
+.Dt LETSKENCRYPT 1
+.Os
+.Sh NAME
+.Nm letskencrypt
+.Nd secure Let's Encrypt client
+.\" .Sh LIBRARY
+.\" For sections 2, 3, and 9 only.
+.\" Not used in OpenBSD.
+.Sh SYNOPSIS
+.Nm letskencrypt
+.Op Fl bFmnNrsv
+.Op Fl a Ar agreement
+.Op Fl C Ar challengedir
+.Op Fl c Ar certdir
+.Op Fl f Ar accountkey
+.Op Fl k Ar domainkey
+.Ar domain
+.Op Ar altnames...
+.Sh DESCRIPTION
+The
+.Nm
+utility submits an X509 certificate for
+.Ar domain
+and its alternate DNS names
+.Ar altnames
+to a
+.Dq Let's Encrypt
+server for automated signing.
+It can also revoke previously-submitted signatures.
+It must be run as root.
+(Why?
+.Xr chroot 2 . )
+.Pp
+By default, it uses
+.Pa /var/www/letsencrypt
+for responding to challenges
+.Pq Fl C ,
+.Pa /etc/ssl/letsencrypt
+for the public certificate directory
+.Pq Fl c ,
+.Pa /etc/ssl/letsencrypt/private/privkey.pem
+for the domain private key
+.Pq Fl k ,
+and
+.Pa /etc/letsencrypt/privkey.pem
+for the account private key
+.Pq Fl f .
+All of these must exist unless you use
+.Fl n
+and/or
+.Fl N ,
+which will generate the account and domain private keys, respectively.
+Its arguments are as follows:
+.Bl -tag -width Ds
+.It Fl b
+Back up all
+.Sx Certificates
+in the certificate directory.
+This will only back up if something will be done to them (remove or
+replace).
+The backups are called
+.Pa cert-NNNNN.pem ,
+.Pa chain-NNNNN.pem ,
+and
+.Pa fullchain-NNNNN.pem ,
+where
+.Li NNNNN
+is the current UNIX epoch.
+Any given backup effort will use the same epoch time for all three
+certificates.
+If there are no certificates in place, this does nothing.
+.It Fl F
+Force updating the certificate signature even if it's too soon.
+.It Fl m
+Append
+.Ar domain
+to all default paths except the challenge path
+.Pq i.e., those that are overriden by Fl c , k , f .
+Thus,
+.Ar foo.com
+as the initial domain would make the default domain private key into
+.Pa /etc/ssl/letsencrypt/private/foo.com/privkey.pem .
+This is useful in setups with multiple domain sets.
+.It Fl n
+Create a new 4096-bit RSA account key if one does not already exist.
+.It Fl N
+Create a new 4096-bit RSA domain key if one does not already exist.
+.It Fl r
+Revoke the X509 certificate found in
+.Sx Certificates .
+.It Fl s
+Use the
+.Dq Let's Encrypt
+staging server instead of the real thing.
+.It Fl v
+Verbose operation.
+Specify twice to also trace communication and data transfers.
+.It Fl a Ar agreement
+Use an alternative agreement URL.
+The default uses the current one, but it may be out of date.
+.It Fl C Ar challengedir
+Where to register challenges.
+See
+.Sx Challenges
+for details.
+.It Fl c Ar certdir
+Where to put public certificates.
+See
+.Sx Certificates
+for details.
+.It Fl f Ar accountkey
+The account private key.
+This was either made with a previous
+.Dq Let's Encrypt
+client or with
+.Fl n .
+.It Fl k Ar domainkey
+The private key for the domain.
+This may also be created with
+.Fl N .
+.It Ar domain
+The domain name.
+The only difference between this and the
+.Ar altnames
+is that it's put into the certificate's
+.Li CN
+field and is use the
+.Dq main
+domain when specifying
+.Fl m .
+.It Ar altnames
+Alternative names
+.Pq Dq SAN
+for the domain name.
+The number of SAN entries is limited by
+.Dq Let's Encrypt
+to 100 or so.
+.El
+.Pp
+The process by which
+.Nm
+obtains signed certificates is roughly as follows.
+In this, the
+.Dq CA
+is the ACME server for Let's Encrypt.
+.Bl -enum
+.It
+Access the CA (unauthenticated) and requests its list of resources.
+.It
+Optionally create and register a new RSA account key.
+.It
+Read and process the RSA account key.
+This is used to authenticate each subsequent communication to the CA.
+.It
+For each domain name,
+.Bl -enum
+.It
+submit a challenge for authentication to the CA,
+.It
+create a challenge response file,
+.It
+wait until the CA has verified the challenge.
+.El
+.It
+Read and extract the domain key.
+.It
+Create an X509 request from the doman key for the domain and its
+alternative names.
+.It
+Submit a request for signature to the CA.
+.It
+Download the signed X509 certificate.
+.It
+Extract the CA issuer from the X509 certificate.
+.It
+Download the certificate chain from the issuer.
+.El
+.Pp
+The revocation sequence is similar:
+.Bl -enum
+.It
+Request list of resources, manage RSA account key as in the case for
+signing.
+.It
+Read and extract the X509 certificate (if found).
+.It
+Create an X509 revocation request.
+.It
+Submit a request for revocation to the CA.
+.It
+Remove the certificate, the chain, and the full-chain.
+.El
+.Ss Challenges
+Let's Encrypt uses challenges to verify that the submitter has access to
+the registered domains.
+.Nm
+implements only the
+.Dq http-01
+challenge type, where a file is created within a directory accessible by
+a locally-run web server configured for the requested domain.
+For example, for the domain
+.Dq foo.com
+and alternate
+.Dq www.foo.com
+and the default challenge directory, an Apache configuration snippet
+might be as follows:
+.Bd -literal
+<VirtualHost *:80>
+ [...]
+ ServerName foo.com
+ ServerAlias www.foo.com
+ Alias /.well-known/acme-challenge /var/www/letsencrypt
+ <Directory /var/www/letsencrypt>
+ Options None
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+ </Directory>
+</VirtualHost>
+.Ed
+.Pp
+This way, the files placed in
+.Pa /var/www/letsencrypt
+will be properly mapped by the web server when the Let's Encrypt
+responds to a challenge.
+.Ss Certificates
+Public certificates (domain certificate, chain, and the full-chain) are
+placed by default in
+.Pa /etc/ssl/letsencrypt
+as
+.Pa cert.pem ,
+.Pa chain.pem ,
+and
+.Pa fullchain.pem ,
+respectively.
+These are all created as the root user with mode 444.
+.Pp
+An nginx configuration using these might be as follows:
+.Bd -literal
+server {
+ listen 443;
+ server_name foo.com www.foo.com;
+ [...]
+ ssl_certificate /etc/ssl/letsencrypt/fullchain.pem;
+ ssl_certificate_key /etc/ssl/letsencrypt/private/privkey.pem;
+}
+.Ed
+.Pp
+The
+.Pa cert.pem
+file, if found, is checked for its expiration: if more than 30 days from
+expiring,
+.Nm
+will not attempt to refresh the signature.
+.\" .Sh CONTEXT
+.\" For section 9 functions only.
+.\" .Sh IMPLEMENTATION NOTES
+.\" Not used in OpenBSD.
+.\" .Sh RETURN VALUES
+.\" For sections 2, 3, and 9 function return values only.
+.\" .Sh ENVIRONMENT
+.\" For sections 1, 6, 7, and 8 only.
+.\" .Sh FILES
+.Sh EXIT STATUS
+.Nm
+returns 1 on failure, 2 if the certificates didn't change (up to date),
+or 0 if certificates were changed (revoked or updated).
+.\" For sections 1, 6, and 8 only.
+.Sh EXAMPLES
+To create and submit a new key for a single domain, assuming that the
+web server has already been configured to map the challenge directory
+as in the
+.Sx Challenges
+section:
+.Bd -literal
+# mkdir /var/www/letsencrypt
+# mkdir /etc/ssl/letsencrypt
+# mkdir /etc/ssl/letsencrypt/private /etc/letsencrypt
+# chmod 0700 /etc/ssl/letsencrypt/private /etc/letsencrypt
+# letskencrypt -vNn foo.com www.foo.com smtp.foo.com
+.Ed
+.Pp
+After generating the necessary directories, the above will create all
+keys and submit them to the server.
+You'll then probably want to restart your web server to pick up the new
+certificates.
+.Pp
+You can then keep your certificates fresh with a daily
+.Xr cron 8
+invocation running the following:
+.Bd -literal
+#! /bin/sh
+
+letskencrypt foo.com www.foo.com smtp.foo.com
+
+if [ $? -eq 0 ]
+then
+ /etc/rc.d/httpd reload
+fi
+.Ed
+.Pp
+You'll need to replace the httpd-reload statement with the correct
+script to have your web server reload its certificates.
+.\" .Sh DIAGNOSTICS
+.\" For sections 1, 4, 6, 7, 8, and 9 printf/stderr messages only.
+.\" .Sh ERRORS
+.\" For sections 2, 3, 4, and 9 errno settings only.
+.Sh SEE ALSO
+.Xr openssl 1
+.\" .Sh STANDARDS
+.\" .Sh HISTORY
+.\" .Sh AUTHORS
+.\" .Sh CAVEATS
+.Sh BUGS
+The challenge and certificate processes currently retain their (root)
+privileges.
+.Pp
+For the time being,
+.Nm
+only supports RSA as an account key format.
+.\" .Sh SECURITY CONSIDERATIONS
+.\" Not used in OpenBSD.
diff --git a/usr.sbin/acme-client/main.c b/usr.sbin/acme-client/main.c
new file mode 100644
index 00000000000..ee20c4afaf4
--- /dev/null
+++ b/usr.sbin/acme-client/main.c
@@ -0,0 +1,485 @@
+/* $Id: main.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define AGREEMENT "https://letsencrypt.org" \
+ "/documents/LE-SA-v1.1.1-August-1-2016.pdf"
+#define SSL_DIR "/etc/ssl/letsencrypt"
+#define SSL_PRIV_DIR "/etc/ssl/letsencrypt/private"
+#define ETC_DIR "/etc/letsencrypt"
+#define WWW_DIR "/var/www/letsencrypt"
+#define PRIVKEY_FILE "privkey.pem"
+
+/*
+ * This isn't RFC1035 compliant, but does the bare minimum in making
+ * sure that we don't get bogus domain names on the command line, which
+ * might otherwise screw up our directory structure.
+ * Returns zero on failure, non-zero on success.
+ */
+static int
+domain_valid(const char *cp)
+{
+
+ for ( ; '\0' != *cp; cp++)
+ if (!('.' == *cp || '-' == *cp ||
+ '_' == *cp || isalnum((int)*cp)))
+ return(0);
+ return(1);
+}
+
+/*
+ * Wrap around asprintf(3), which sometimes nullifies the input values,
+ * sometimes not, but always returns <0 on error.
+ * Returns NULL on failure or the pointer on success.
+ */
+static char *
+doasprintf(const char *fmt, ...)
+{
+ int c;
+ char *cp;
+ va_list ap;
+
+ va_start(ap, fmt);
+ c = vasprintf(&cp, fmt, ap);
+ va_end(ap);
+ return(c < 0 ? NULL : cp);
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *domain, *agreement;
+ char *certdir, *acctkey, *chngdir, *keyfile;
+ int key_fds[2], acct_fds[2], chng_fds[2],
+ cert_fds[2], file_fds[2], dns_fds[2],
+ rvk_fds[2];
+ pid_t pids[COMP__MAX];
+ int c, rc, newacct, remote, revoke, force,
+ staging, multidir, newkey, backup;
+ extern int verbose;
+ extern enum comp proccomp;
+ size_t i, altsz, ne;
+ const char **alts;
+
+ alts = NULL;
+ newacct = remote = revoke = verbose = force =
+ multidir = staging = newkey = backup = 0;
+ certdir = keyfile = acctkey = chngdir = NULL;
+ agreement = AGREEMENT;
+
+ while (-1 != (c = getopt(argc, argv, "bFmnNrstva:f:c:C:k:")))
+ switch (c) {
+ case ('a'):
+ agreement = optarg;
+ break;
+ case ('b'):
+ backup = 1;
+ break;
+ case ('c'):
+ free(certdir);
+ if (NULL == (certdir = strdup(optarg)))
+ err(EXIT_FAILURE, "strdup");
+ break;
+ case ('C'):
+ free(chngdir);
+ if (NULL == (chngdir = strdup(optarg)))
+ err(EXIT_FAILURE, "strdup");
+ break;
+ case ('f'):
+ free(acctkey);
+ if (NULL == (acctkey = strdup(optarg)))
+ err(EXIT_FAILURE, "strdup");
+ break;
+ case ('F'):
+ force = 1;
+ break;
+ case ('k'):
+ free(keyfile);
+ if (NULL == (keyfile = strdup(optarg)))
+ err(EXIT_FAILURE, "strdup");
+ break;
+ case ('m'):
+ multidir = 1;
+ break;
+ case ('n'):
+ newacct = 1;
+ break;
+ case ('N'):
+ newkey = 1;
+ break;
+ case ('r'):
+ revoke = 1;
+ break;
+ case ('s'):
+ staging = 1;
+ break;
+ case ('t'):
+ /*
+ / Undocumented feature.
+ * Don't use it.
+ */
+ remote = 1;
+ break;
+ case ('v'):
+ verbose = verbose ? 2 : 1;
+ break;
+ default:
+ goto usage;
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (0 == argc)
+ goto usage;
+
+ /* Make sure that the domains are sane. */
+
+ for (i = 0; i < (size_t)argc; i++) {
+ if (domain_valid(argv[i]))
+ continue;
+ errx(EXIT_FAILURE, "%s: bad domain syntax", argv[i]);
+ }
+
+ domain = argv[0];
+ argc--;
+ argv++;
+
+ if ( ! checkprivs())
+ errx(EXIT_FAILURE, "must be run as root");
+
+ /*
+ * Now we allocate our directories and file paths IFF we haven't
+ * specified them on the command-line.
+ * If we're in "multidir" (-m) mode, we use our initial domain
+ * name when specifying the prefixes.
+ * Otherwise, we put them all in a known location.
+ */
+
+ if (NULL == certdir)
+ certdir = multidir ?
+ doasprintf(SSL_DIR "/%s", domain) :
+ strdup(SSL_DIR);
+ if (NULL == keyfile)
+ keyfile = multidir ?
+ doasprintf(SSL_PRIV_DIR "/%s/"
+ PRIVKEY_FILE, domain) :
+ strdup(SSL_PRIV_DIR "/" PRIVKEY_FILE);
+ if (NULL == acctkey)
+ acctkey = multidir ?
+ doasprintf(ETC_DIR "/%s/"
+ PRIVKEY_FILE, domain) :
+ strdup(ETC_DIR "/" PRIVKEY_FILE);
+ if (NULL == chngdir)
+ chngdir = strdup(WWW_DIR);
+
+ if (NULL == certdir || NULL == keyfile ||
+ NULL == acctkey || NULL == chngdir)
+ err(EXIT_FAILURE, "strdup");
+
+ /*
+ * Do some quick checks to see if our paths exist.
+ * This will be done in the children, but we might as well check
+ * now before the fork.
+ */
+
+ ne = 0;
+
+ if (-1 == access(certdir, R_OK)) {
+ warnx("%s: -c directory must exist", certdir);
+ ne++;
+ }
+
+ if ( ! newkey && -1 == access(keyfile, R_OK)) {
+ warnx("%s: -k file must exist", keyfile);
+ ne++;
+ } else if (newkey && -1 != access(keyfile, R_OK)) {
+ dodbg("%s: domain key exists "
+ "(not creating)", keyfile);
+ newkey = 0;
+ }
+
+ if (-1 == access(chngdir, R_OK)) {
+ warnx("%s: -C directory must exist", chngdir);
+ ne++;
+ }
+
+ if ( ! newacct && -1 == access(acctkey, R_OK)) {
+ warnx("%s: -f file must exist", acctkey);
+ ne++;
+ } else if (newacct && -1 != access(acctkey, R_OK)) {
+ dodbg("%s: account key exists "
+ "(not creating)", acctkey);
+ newacct = 0;
+ }
+
+ if (ne > 0)
+ exit(EXIT_FAILURE);
+
+ /* Set the zeroth altname as our domain. */
+
+ altsz = argc + 1;
+ alts = calloc(altsz, sizeof(char *));
+ if (NULL == alts)
+ err(EXIT_FAILURE, "calloc");
+ alts[0] = domain;
+ for (i = 0; i < (size_t)argc; i++)
+ alts[i + 1] = argv[i];
+
+ /*
+ * Open channels between our components.
+ */
+
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, key_fds))
+ err(EXIT_FAILURE, "socketpair");
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, acct_fds))
+ err(EXIT_FAILURE, "socketpair");
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, chng_fds))
+ err(EXIT_FAILURE, "socketpair");
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, cert_fds))
+ err(EXIT_FAILURE, "socketpair");
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, file_fds))
+ err(EXIT_FAILURE, "socketpair");
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, dns_fds))
+ err(EXIT_FAILURE, "socketpair");
+ if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds))
+ err(EXIT_FAILURE, "socketpair");
+
+ /* Start with the network-touching process. */
+
+ if (-1 == (pids[COMP_NET] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_NET]) {
+ proccomp = COMP_NET;
+ close(key_fds[0]);
+ close(acct_fds[0]);
+ close(chng_fds[0]);
+ close(cert_fds[0]);
+ close(file_fds[0]);
+ close(file_fds[1]);
+ close(dns_fds[0]);
+ close(rvk_fds[0]);
+ c = netproc(key_fds[1], acct_fds[1],
+ chng_fds[1], cert_fds[1],
+ dns_fds[1], rvk_fds[1],
+ newacct, revoke, staging,
+ (const char *const *)alts, altsz,
+ agreement);
+ free(alts);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(key_fds[1]);
+ close(acct_fds[1]);
+ close(chng_fds[1]);
+ close(cert_fds[1]);
+ close(dns_fds[1]);
+ close(rvk_fds[1]);
+
+ /* Now the key-touching component. */
+
+ if (-1 == (pids[COMP_KEY] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_KEY]) {
+ proccomp = COMP_KEY;
+ close(cert_fds[0]);
+ close(dns_fds[0]);
+ close(rvk_fds[0]);
+ close(acct_fds[0]);
+ close(chng_fds[0]);
+ close(file_fds[0]);
+ close(file_fds[1]);
+ c = keyproc(key_fds[0], keyfile,
+ (const char **)alts, altsz, newkey);
+ free(alts);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(key_fds[0]);
+
+ /* The account-touching component. */
+
+ if (-1 == (pids[COMP_ACCOUNT] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_ACCOUNT]) {
+ proccomp = COMP_ACCOUNT;
+ free(alts);
+ close(cert_fds[0]);
+ close(dns_fds[0]);
+ close(rvk_fds[0]);
+ close(chng_fds[0]);
+ close(file_fds[0]);
+ close(file_fds[1]);
+ c = acctproc(acct_fds[0], acctkey, newacct);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(acct_fds[0]);
+
+ /* The challenge-accepting component. */
+
+ if (-1 == (pids[COMP_CHALLENGE] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_CHALLENGE]) {
+ proccomp = COMP_CHALLENGE;
+ free(alts);
+ close(cert_fds[0]);
+ close(dns_fds[0]);
+ close(rvk_fds[0]);
+ close(file_fds[0]);
+ close(file_fds[1]);
+ c = chngproc(chng_fds[0], chngdir, remote);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(chng_fds[0]);
+
+ /* The certificate-handling component. */
+
+ if (-1 == (pids[COMP_CERT] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_CERT]) {
+ proccomp = COMP_CERT;
+ free(alts);
+ close(dns_fds[0]);
+ close(rvk_fds[0]);
+ close(file_fds[1]);
+ c = certproc(cert_fds[0], file_fds[0]);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(cert_fds[0]);
+ close(file_fds[0]);
+
+ /* The certificate-handling component. */
+
+ if (-1 == (pids[COMP_FILE] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_FILE]) {
+ proccomp = COMP_FILE;
+ free(alts);
+ close(dns_fds[0]);
+ close(rvk_fds[0]);
+ c = fileproc(file_fds[1], backup, certdir);
+ /*
+ * This is different from the other processes in that it
+ * can return 2 if the certificates were updated.
+ */
+ exit(c > 1 ? 2 :
+ (c ? EXIT_SUCCESS : EXIT_FAILURE));
+ }
+
+ close(file_fds[1]);
+
+ /* The DNS lookup component. */
+
+ if (-1 == (pids[COMP_DNS] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_DNS]) {
+ proccomp = COMP_DNS;
+ free(alts);
+ close(rvk_fds[0]);
+ c = dnsproc(dns_fds[0]);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(dns_fds[0]);
+
+ /* The expiration component. */
+
+ if (-1 == (pids[COMP_REVOKE] = fork()))
+ err(EXIT_FAILURE, "fork");
+
+ if (0 == pids[COMP_REVOKE]) {
+ proccomp = COMP_REVOKE;
+ c = revokeproc(rvk_fds[0], certdir,
+ force, revoke,
+ (const char *const *)alts, altsz);
+ free(alts);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ close(rvk_fds[0]);
+
+ /* Jail: sandbox, file-system, user. */
+
+ if ( ! sandbox_before())
+ exit(EXIT_FAILURE);
+ else if ( ! dropfs(PATH_VAR_EMPTY))
+ exit(EXIT_FAILURE);
+ else if ( ! dropprivs())
+ exit(EXIT_FAILURE);
+ else if ( ! sandbox_after())
+ exit(EXIT_FAILURE);
+
+ /*
+ * Collect our subprocesses.
+ * Require that they both have exited cleanly.
+ */
+
+ rc = checkexit(pids[COMP_KEY], COMP_KEY) +
+ checkexit(pids[COMP_CERT], COMP_CERT) +
+ checkexit(pids[COMP_NET], COMP_NET) +
+ checkexit_ext(&c, pids[COMP_FILE], COMP_FILE) +
+ checkexit(pids[COMP_ACCOUNT], COMP_ACCOUNT) +
+ checkexit(pids[COMP_CHALLENGE], COMP_CHALLENGE) +
+ checkexit(pids[COMP_DNS], COMP_DNS) +
+ checkexit(pids[COMP_REVOKE], COMP_REVOKE);
+
+ free(certdir);
+ free(keyfile);
+ free(acctkey);
+ free(chngdir);
+ free(alts);
+ return(COMP__MAX != rc ? EXIT_FAILURE :
+ (2 == c ? EXIT_SUCCESS : 2));
+usage:
+ fprintf(stderr, "usage: %s "
+ "[-bFmnNrsv] "
+ "[-a agreement] "
+ "[-C challengedir] "
+ "[-c certdir] "
+ "[-f accountkey] "
+ "[-k domainkey] "
+ "domain [altnames...]\n",
+ getprogname());
+ free(certdir);
+ free(keyfile);
+ free(acctkey);
+ free(chngdir);
+ return(EXIT_FAILURE);
+}
diff --git a/usr.sbin/acme-client/netproc.c b/usr.sbin/acme-client/netproc.c
new file mode 100644
index 00000000000..039f51a8205
--- /dev/null
+++ b/usr.sbin/acme-client/netproc.c
@@ -0,0 +1,811 @@
+/* $Id: netproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "http.h"
+#include "extern.h"
+
+#define URL_REAL_CA "https://acme-v01.api.letsencrypt.org/directory"
+#define URL_STAGE_CA "https://acme-staging.api.letsencrypt.org/directory"
+
+#define RETRY_DELAY 5
+#define RETRY_MAX 10
+
+/*
+ * Buffer used when collecting the results of a CURL transfer.
+ */
+struct buf {
+ char *buf; /* binary buffer */
+ size_t sz; /* length of buffer */
+};
+
+/*
+ * Used for CURL communications.
+ */
+struct conn {
+ const char *na; /* nonce authority */
+ int fd; /* acctproc handle */
+ int dfd; /* dnsproc handle */
+ struct buf buf; /* transfer buffer */
+};
+
+/*
+ * If something goes wrong (or we're tracing output), we dump the
+ * current transfer's data as a debug message.
+ * Make sure that print all non-printable characters as question marks
+ * so that we don't spam the console.
+ * Also, consolidate white-space.
+ * This of course will ruin string literals, but the intent here is just
+ * to show the message, not to replicate it.
+ */
+static void
+buf_dump(const struct buf *buf)
+{
+ size_t i;
+ int j;
+ char *nbuf;
+
+ if (0 == buf->sz)
+ return;
+ if (NULL == (nbuf = malloc(buf->sz)))
+ err(EXIT_FAILURE, "malloc");
+
+ for (j = 0, i = 0; i < buf->sz; i++)
+ if (isspace((int)buf->buf[i])) {
+ nbuf[j++] = ' ';
+ while (isspace((int)buf->buf[i]))
+ i++;
+ i--;
+ } else
+ nbuf[j++] = isprint((int)buf->buf[i]) ?
+ buf->buf[i] : '?';
+ dodbg("transfer buffer: [%.*s] (%zu bytes)", j, nbuf, buf->sz);
+ free(nbuf);
+}
+
+/*
+ * Extract the domain and port from a URL.
+ * The url must be formatted as schema://address[/stuff].
+ * This returns NULL on failure.
+ */
+static char *
+url2host(const char *host, short *port, char **path)
+{
+ char *url, *ep;
+
+ /* We only understand HTTP and HTTPS. */
+
+ if (0 == strncmp(host, "https://", 8)) {
+ *port = 443;
+ if (NULL == (url = strdup(host + 8))) {
+ warn("strdup");
+ return(NULL);
+ }
+ } else if (0 == strncmp(host, "http://", 7)) {
+ *port = 80;
+ if (NULL == (url = strdup(host + 7))) {
+ warn("strdup");
+ return(NULL);
+ }
+ } else {
+ warnx("%s: unknown schema", host);
+ return(NULL);
+ }
+
+ /* Terminate path part. */
+
+ if (NULL != (ep = strchr(url, '/'))) {
+ *path = strdup(ep);
+ *ep = '\0';
+ } else
+ *path = strdup("");
+
+ if (NULL == *path) {
+ warn("strdup");
+ free(url);
+ return(NULL);
+ }
+
+ return(url);
+}
+
+/*
+ * Contact dnsproc and resolve a host.
+ * Place the answers in "v" and return the number of answers, which can
+ * be at most MAX_SERVERS_DNS.
+ * Return <0 on failure.
+ */
+static ssize_t
+urlresolve(int fd, const char *host, struct source *v)
+{
+ char *addr;
+ size_t i, sz;
+ long lval;
+
+ if (writeop(fd, COMM_DNS, DNS_LOOKUP) <= 0)
+ return(-1);
+ else if (writestr(fd, COMM_DNSQ, host) <= 0)
+ return(-1);
+ else if ((lval = readop(fd, COMM_DNSLEN)) < 0)
+ return(-1);
+
+ sz = lval;
+ assert(sz <= MAX_SERVERS_DNS);
+
+ for (i = 0; i < sz; i++) {
+ memset(&v[i], 0, sizeof(struct source));
+ if ((lval = readop(fd, COMM_DNSF)) < 0)
+ goto err;
+ else if (4 != lval && 6 != lval)
+ goto err;
+ else if (NULL == (addr = readstr(fd, COMM_DNSA)))
+ goto err;
+ v[i].family = lval;
+ v[i].ip = addr;
+ }
+
+ return(sz);
+err:
+ for (i = 0; i < sz; i++)
+ free(v[i].ip);
+ return(-1);
+}
+
+/*
+ * Send a "regular" HTTP GET message to "addr" and stuff the response
+ * into the connection buffer.
+ * Return the HTTP error code or <0 on failure.
+ */
+static long
+nreq(struct conn *c, const char *addr)
+{
+ struct httpget *g;
+ struct source src[MAX_SERVERS_DNS];
+ char *host, *path;
+ short port;
+ size_t srcsz;
+ ssize_t ssz;
+ long code;
+
+ if (NULL == (host = url2host(addr, &port, &path)))
+ return(-1);
+
+ if ((ssz = urlresolve(c->dfd, host, src)) < 0) {
+ free(host);
+ free(path);
+ return(-1);
+ }
+ srcsz = ssz;
+
+ g = http_get(src, srcsz, host, port, path, NULL, 0);
+ free(host);
+ free(path);
+ if (NULL == g)
+ return(-1);
+
+ code = g->code;
+
+ /* Copy the body part into our buffer. */
+
+ free(c->buf.buf);
+ c->buf.sz = g->bodypartsz;
+ c->buf.buf = malloc(c->buf.sz);
+ memcpy(c->buf.buf, g->bodypart, c->buf.sz);
+ http_get_free(g);
+ if (NULL == c->buf.buf) {
+ warn("malloc");
+ return(-1);
+ }
+ return(code);
+}
+
+/*
+ * Create and send a signed communication to the ACME server.
+ * Stuff the response into the communication buffer.
+ * Return <0 on failure on the HTTP error code otherwise.
+ */
+static long
+sreq(struct conn *c, const char *addr, const char *req)
+{
+ struct httpget *g;
+ struct source src[MAX_SERVERS_DNS];
+ char *host, *path, *nonce, *reqsn;
+ short port;
+ struct httphead *h;
+ ssize_t ssz;
+ long code;
+
+ if (NULL == (host = url2host(c->na, &port, &path)))
+ return(-1);
+
+ if ((ssz = urlresolve(c->dfd, host, src)) < 0) {
+ free(host);
+ free(path);
+ return(-1);
+ }
+
+ g = http_get(src, (size_t)ssz, host, port, path, NULL, 0);
+ free(host);
+ free(path);
+ if (NULL == g)
+ return(-1);
+
+ h = http_head_get("Replay-Nonce", g->head, g->headsz);
+ if (NULL == h) {
+ warnx("%s: no replay nonce", c->na);
+ http_get_free(g);
+ return(-1);
+ } else if (NULL == (nonce = strdup(h->val))) {
+ warn("strdup");
+ http_get_free(g);
+ return(-1);
+ }
+ http_get_free(g);
+
+ /*
+ * Send the nonce and request payload to the acctproc.
+ * This will create the proper JSON object we need.
+ */
+
+ if (writeop(c->fd, COMM_ACCT, ACCT_SIGN) <= 0) {
+ free(nonce);
+ return(-1);
+ } else if (writestr(c->fd, COMM_PAY, req) <= 0) {
+ free(nonce);
+ return(-1);
+ } else if (writestr(c->fd, COMM_NONCE, nonce) <= 0) {
+ free(nonce);
+ return(-1);
+ }
+ free(nonce);
+
+ /* Now read back the signed payload. */
+
+ if (NULL == (reqsn = readstr(c->fd, COMM_REQ)))
+ return(-1);
+
+ /* Now send the signed payload to the CA. */
+
+ if (NULL == (host = url2host(addr, &port, &path))) {
+ free(reqsn);
+ return(-1);
+ } else if ((ssz = urlresolve(c->dfd, host, src)) < 0) {
+ free(host);
+ free(path);
+ free(reqsn);
+ return(-1);
+ }
+
+ g = http_get(src, (size_t)ssz, host,
+ port, path, reqsn, strlen(reqsn));
+
+ free(host);
+ free(path);
+ free(reqsn);
+ if (NULL == g)
+ return(-1);
+
+ /* Stuff response into parse buffer. */
+
+ code = g->code;
+
+ free(c->buf.buf);
+ c->buf.sz = g->bodypartsz;
+ c->buf.buf = malloc(c->buf.sz);
+ memcpy(c->buf.buf, g->bodypart, c->buf.sz);
+ http_get_free(g);
+ if (NULL == c->buf.buf) {
+ warn("malloc");
+ return(-1);
+ }
+ return(code);
+}
+
+/*
+ * Send to the CA that we want to authorise a new account.
+ * This only happens once for a new account key.
+ * Returns non-zero on success.
+ */
+static int
+donewreg(struct conn *c, const char *agreement,
+ const struct capaths *p)
+{
+ int rc;
+ char *req;
+ long lc;
+
+ rc = 0;
+ dodbg("%s: new-reg", p->newreg);
+
+ if (NULL == (req = json_fmt_newreg(agreement)))
+ warnx("json_fmt_newreg");
+ else if ((lc = sreq(c, p->newreg, req)) < 0)
+ warnx("%s: bad comm", p->newreg);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", p->newreg, lc);
+ else if (NULL == c->buf.buf || 0 == c->buf.sz)
+ warnx("%s: empty response", p->newreg);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Request a challenge for the given domain name.
+ * This must happen for each name "alt".
+ * On non-zero exit, fills in "chng" with the challenge.
+ */
+static int
+dochngreq(struct conn *c, const char *alt,
+ struct chng *chng, const struct capaths *p)
+{
+ int rc;
+ char *req;
+ long lc;
+ struct jsmnn *j;
+
+ j = NULL;
+ rc = 0;
+ dodbg("%s: req-auth: %s", p->newauthz, alt);
+
+ if (NULL == (req = json_fmt_newauthz(alt)))
+ warnx("json_fmt_newauthz");
+ else if ((lc = sreq(c, p->newauthz, req)) < 0)
+ warnx("%s: bad comm", p->newauthz);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", p->newauthz, lc);
+ else if (NULL == (j = json_parse(c->buf.buf, c->buf.sz)))
+ warnx("%s: bad JSON object", p->newauthz);
+ else if ( ! json_parse_challenge(j, chng))
+ warnx("%s: bad challenge", p->newauthz);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ json_free(j);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Note to the CA that a challenge response is in place.
+ */
+static int
+dochngresp(struct conn *c, const struct chng *chng, const char *th)
+{
+ int rc;
+ long lc;
+ char *req;
+
+ rc = 0;
+ dodbg("%s: challenge", chng->uri);
+
+ if (NULL == (req = json_fmt_challenge(chng->token, th)))
+ warnx("json_fmt_challenge");
+ else if ((lc = sreq(c, chng->uri, req)) < 0)
+ warnx("%s: bad comm", chng->uri);
+ else if (200 != lc && 201 != lc && 202 != lc)
+ warnx("%s: bad HTTP: %ld", chng->uri, lc);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Check with the CA whether a challenge has been processed.
+ * Note: we'll only do this a limited number of times, and pause for a
+ * time between checks, but this happens in the caller.
+ */
+static int
+dochngcheck(struct conn *c, struct chng *chng)
+{
+ int cc;
+ long lc;
+ struct jsmnn *j;
+
+ dodbg("%s: status", chng->uri);
+
+ if ((lc = nreq(c, chng->uri)) < 0) {
+ warnx("%s: bad comm", chng->uri);
+ return(0);
+ } else if (200 != lc && 201 != lc && 202 != lc) {
+ warnx("%s: bad HTTP: %ld", chng->uri, lc);
+ buf_dump(&c->buf);
+ return(0);
+ } else if (NULL == (j = json_parse(c->buf.buf, c->buf.sz))) {
+ warnx("%s: bad JSON object", chng->uri);
+ buf_dump(&c->buf);
+ return(0);
+ } else if (-1 == (cc = json_parse_response(j))) {
+ warnx("%s: bad response", chng->uri);
+ buf_dump(&c->buf);
+ json_free(j);
+ return(0);
+ } else if (cc > 0)
+ chng->status = 1;
+
+ json_free(j);
+ return(1);
+}
+
+static int
+dorevoke(struct conn *c, const char *addr, const char *cert)
+{
+ char *req;
+ int rc;
+ long lc;
+
+ lc = 0;
+ rc = 0;
+ dodbg("%s: revocation", addr);
+
+ if (NULL == (req = json_fmt_revokecert(cert)))
+ warnx("json_fmt_revokecert");
+ else if ((lc = sreq(c, addr, req)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc && 409 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else
+ rc = 1;
+
+ if (409 == lc)
+ warnx("%s: already revoked", addr);
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Submit our certificate to the CA.
+ * This, upon success, will return the signed CA.
+ */
+static int
+docert(struct conn *c, const char *addr, const char *cert)
+{
+ char *req;
+ int rc;
+ long lc;
+
+ rc = 0;
+ dodbg("%s: certificate", addr);
+
+ if (NULL == (req = json_fmt_newcert(cert)))
+ warnx("json_fmt_newcert");
+ else if ((lc = sreq(c, addr, req)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else if (0 == c->buf.sz || NULL == c->buf.buf)
+ warnx("%s: empty response", addr);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Look up directories from the certificate authority.
+ */
+static int
+dodirs(struct conn *c, const char *addr, struct capaths *paths)
+{
+ struct jsmnn *j;
+ long lc;
+ int rc;
+
+ j = NULL;
+ rc = 0;
+ dodbg("%s: directories", addr);
+
+ if ((lc = nreq(c, addr)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else if (NULL == (j = json_parse(c->buf.buf, c->buf.sz)))
+ warnx("json_parse");
+ else if ( ! json_parse_capaths(j, paths))
+ warnx("%s: bad CA paths", addr);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ json_free(j);
+ return(rc);
+}
+
+/*
+ * Request the full chain certificate.
+ */
+static int
+dofullchain(struct conn *c, const char *addr)
+{
+ int rc;
+ long lc;
+
+ rc = 0;
+ dodbg("%s: full chain", addr);
+
+ if ((lc = nreq(c, addr)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ return(rc);
+}
+
+/*
+ * Here we communicate with the letsencrypt server.
+ * For this, we'll need the certificate we want to upload and our
+ * account key information.
+ */
+int
+netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
+ int newacct, int revoke, int staging,
+ const char *const *alts, size_t altsz, const char *agreement)
+{
+ int rc;
+ size_t i;
+ char *cert, *thumb, *url;
+ struct conn c;
+ struct capaths paths;
+ struct chng *chngs;
+ long lval;
+
+ rc = 0;
+ memset(&paths, 0, sizeof(struct capaths));
+ memset(&c, 0, sizeof(struct conn));
+ url = cert = thumb = NULL;
+ chngs = NULL;
+
+ /* File-system, user, and sandbox jail. */
+
+ if ( ! sandbox_before())
+ goto out;
+ else if ( ! dropfs(PATH_VAR_EMPTY))
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * Wait until the acctproc, keyproc, and revokeproc have started
+ * up and are ready to serve us data.
+ * There's no point in running if these don't work.
+ * Then check whether revokeproc indicates that the certificate
+ * on file (if any) can be updated.
+ */
+
+ if (0 == (lval = readop(afd, COMM_ACCT_STAT))) {
+ rc = 1;
+ goto out;
+ } else if (ACCT_READY != lval) {
+ warnx("unknown operation from acctproc");
+ goto out;
+ }
+
+ if (0 == (lval = readop(kfd, COMM_KEY_STAT))) {
+ rc = 1;
+ goto out;
+ } else if (KEY_READY != lval) {
+ warnx("unknown operation from keyproc");
+ goto out;
+ }
+
+ if (0 == (lval = readop(rfd, COMM_REVOKE_RESP))) {
+ rc = 1;
+ goto out;
+ } else if (REVOKE_EXP != lval && REVOKE_OK != lval) {
+ warnx("unknown operation from revokeproc");
+ goto out;
+ }
+
+ /* If our certificate is up-to-date, return now. */
+
+ if (REVOKE_OK == lval) {
+ rc = 1;
+ goto out;
+ }
+
+ /* Allocate main state. */
+
+ chngs = calloc(altsz, sizeof(struct chng));
+ if (NULL == chngs) {
+ warn("calloc");
+ goto out;
+ }
+
+ c.dfd = dfd;
+ c.fd = afd;
+ c.na = staging ? URL_STAGE_CA : URL_REAL_CA;
+
+ /*
+ * Look up the domain of the ACME server.
+ * We'll use this ourselves instead of having libcurl do the DNS
+ * resolution itself.
+ */
+ if ( ! dodirs(&c, c.na, &paths))
+ goto out;
+
+ /*
+ * If we're meant to revoke, then wait for revokeproc to send us
+ * the certificate (if it's found at all).
+ * Following that, submit the request to the CA then notify the
+ * certproc, which will in turn notify the fileproc.
+ */
+
+ if (revoke) {
+ if (NULL == (cert = readstr(rfd, COMM_CSR)))
+ goto out;
+ if ( ! dorevoke(&c, paths.revokecert, cert))
+ goto out;
+ else if (writeop(cfd, COMM_CSR_OP, CERT_REVOKE) > 0)
+ rc = 1;
+ goto out;
+ }
+
+ /* If new, register with the CA server. */
+
+ if (newacct && ! donewreg(&c, agreement, &paths))
+ goto out;
+
+ /* Pre-authorise all domains with CA server. */
+
+ for (i = 0; i < altsz; i++)
+ if ( ! dochngreq(&c, alts[i], &chngs[i], &paths))
+ goto out;
+
+ /*
+ * We now have our challenges.
+ * We need to ask the acctproc for the thumbprint.
+ * We'll combine this to the challenge to create our response,
+ * which will be orchestrated by the chngproc.
+ */
+
+ if (writeop(afd, COMM_ACCT, ACCT_THUMBPRINT) <= 0)
+ goto out;
+ else if (NULL == (thumb = readstr(afd, COMM_THUMB)))
+ goto out;
+
+ /* We'll now ask chngproc to build the challenge. */
+
+ for (i = 0; i < altsz; i++) {
+ if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0)
+ goto out;
+ else if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
+ goto out;
+ else if (writestr(Cfd, COMM_TOK, chngs[i].token) <= 0)
+ goto out;
+
+ /* Read that the challenge has been made. */
+
+ if (CHNG_ACK != readop(Cfd, COMM_CHNG_ACK))
+ goto out;
+
+ /* Write to the CA that it's ready. */
+
+ if ( ! dochngresp(&c, &chngs[i], thumb))
+ goto out;
+ }
+
+ /*
+ * We now wait on the ACME server for each domain.
+ * Connect to the server (assume it's the same server) once
+ * every five seconds.
+ */
+
+ for (i = 0; i < altsz; i++) {
+ if (1 == chngs[i].status)
+ continue;
+
+ if (chngs[i].retry++ >= RETRY_MAX) {
+ warnx("%s: too many tries", chngs[i].uri);
+ goto out;
+ }
+
+ /* Sleep before every attempt. */
+ sleep(RETRY_DELAY);
+ if ( ! dochngcheck(&c, &chngs[i]))
+ goto out;
+ }
+
+ /*
+ * Write our acknowledgement that the challenges are over.
+ * The challenge process will remove all of the files.
+ */
+
+ if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0)
+ goto out;
+
+ /* Wait to receive the certificate itself. */
+
+ if (NULL == (cert = readstr(kfd, COMM_CERT)))
+ goto out;
+
+ /*
+ * Otherwise, submit the CA for signing, download the signed
+ * copy, and ship that into the certificate process for copying.
+ */
+
+ if ( ! docert(&c, paths.newcert, cert))
+ goto out;
+ else if (writeop(cfd, COMM_CSR_OP, CERT_UPDATE) <= 0)
+ goto out;
+ else if (writebuf(cfd, COMM_CSR, c.buf.buf, c.buf.sz) <= 0)
+ goto out;
+
+ /*
+ * Read back the issuer from the certproc.
+ * Then contact the issuer to get the certificate chain.
+ * Write this chain directly back to the certproc.
+ */
+
+ if (NULL == (url = readstr(cfd, COMM_ISSUER)))
+ goto out;
+ else if ( ! dofullchain(&c, url))
+ goto out;
+ else if (writebuf(cfd, COMM_CHAIN, c.buf.buf, c.buf.sz) <= 0)
+ goto out;
+
+ rc = 1;
+out:
+ close(cfd);
+ close(kfd);
+ close(afd);
+ close(Cfd);
+ close(dfd);
+ close(rfd);
+ free(cert);
+ free(url);
+ free(thumb);
+ free(c.buf.buf);
+ if (NULL != chngs)
+ for (i = 0; i < altsz; i++)
+ json_free_challenge(&chngs[i]);
+ free(chngs);
+ json_free_capaths(&paths);
+ return(rc);
+}
diff --git a/usr.sbin/acme-client/revokeproc.c b/usr.sbin/acme-client/revokeproc.c
new file mode 100644
index 00000000000..159442b3143
--- /dev/null
+++ b/usr.sbin/acme-client/revokeproc.c
@@ -0,0 +1,375 @@
+/* $Id: revokeproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/err.h>
+
+#include "extern.h"
+
+#define RENEW_ALLOW (30 * 24 * 60 * 60)
+
+/*
+ * Convert the X509's expiration time (which is in ASN1_TIME format)
+ * into a time_t value.
+ * There are lots of suggestions on the Internet on how to do this and
+ * they're really, really unsafe.
+ * Adapt those poor solutions to a safe one.
+ */
+static time_t
+X509expires(X509 *x)
+{
+ ASN1_TIME *time;
+ struct tm t;
+ unsigned char *str;
+ size_t i = 0;
+
+ time = X509_get_notAfter(x);
+ str = time->data;
+ memset(&t, 0, sizeof(t));
+
+ /* Account for 2 and 4-digit time. */
+
+ if (time->type == V_ASN1_UTCTIME) {
+ if (time->length <= 2) {
+ warnx("invalid ASN1_TIME");
+ return((time_t)-1);
+ }
+ t.tm_year =
+ (str[0] - '0') * 10 +
+ (str[1] - '0');
+ if (t.tm_year < 70)
+ t.tm_year += 100;
+ i = 2;
+ } else if (time->type == V_ASN1_GENERALIZEDTIME) {
+ if (time->length <= 4) {
+ warnx("invalid ASN1_TIME");
+ return((time_t)-1);
+ }
+ t.tm_year =
+ (str[0] - '0') * 1000 +
+ (str[1] - '0') * 100 +
+ (str[2] - '0') * 10 +
+ (str[3] - '0');
+ t.tm_year -= 1900;
+ i = 4;
+ }
+
+ /* Now the post-year parts. */
+
+ if (time->length <= (int)i + 10) {
+ warnx("invalid ASN1_TIME");
+ return((time_t)-1);
+ }
+
+ t.tm_mon = ((str[i + 0] - '0') * 10 + (str[i + 1] - '0')) - 1;
+ t.tm_mday = (str[i + 2] - '0') * 10 + (str[i + 3] - '0');
+ t.tm_hour = (str[i + 4] - '0') * 10 + (str[i + 5] - '0');
+ t.tm_min = (str[i + 6] - '0') * 10 + (str[i + 7] - '0');
+ t.tm_sec = (str[i + 8] - '0') * 10 + (str[i + 9] - '0');
+
+ return(mktime(&t));
+}
+
+int
+revokeproc(int fd, const char *certdir, int force, int revoke,
+ const char *const *alts, size_t altsz)
+{
+ int rc, cc, i, extsz, ssz;
+ long lval;
+ FILE *f;
+ size_t *found;
+ char *path, *der, *dercp, *der64, *san, *str, *tok;
+ X509 *x;
+ enum revokeop op, rop;
+ time_t t;
+ int len;
+ X509_EXTENSION *ex;
+ ASN1_OBJECT *obj;
+ BIO *bio;
+ size_t j;
+
+ found = NULL;
+ bio = NULL;
+ der = der64 = NULL;
+ rc = 0;
+ f = NULL;
+ path = NULL;
+ san = NULL;
+ x = NULL;
+
+ /*
+ * First try to open the certificate before we drop privileges
+ * and jail ourselves.
+ * We allow "f" to be NULL IFF the cert doesn't exist yet.
+ */
+
+ if (-1 == asprintf(&path, "%s/%s", certdir, CERT_PEM)) {
+ warn("asprintf");
+ goto out;
+ } else if (NULL == (f = fopen(path, "r")) && ENOENT != errno) {
+ warn("%s", path);
+ goto out;
+ }
+
+ /* File-system and sandbox jailing. */
+
+ if ( ! sandbox_before())
+ goto out;
+
+ ERR_load_crypto_strings();
+
+ if ( ! dropfs(PATH_VAR_EMPTY))
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * If we couldn't open the certificate, it doesn't exist so we
+ * haven't submitted it yet, so obviously we can mark that it
+ * has expired and we should renew it.
+ * If we're revoking, however, then that's an error!
+ * Ignore if the reader isn't reading in either case.
+ */
+
+ if (NULL == f && revoke) {
+ warnx("%s/%s: no certificate found",
+ certdir, CERT_PEM);
+ (void)writeop(fd, COMM_REVOKE_RESP, REVOKE_OK);
+ goto out;
+ } else if (NULL == f && ! revoke) {
+ if (writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP) >= 0)
+ rc = 1;
+ goto out;
+ }
+
+ if (NULL == (x = PEM_read_X509(f, NULL, NULL, NULL))) {
+ warnx("PEM_read_X509");
+ goto out;
+ }
+
+ /* Read out the expiration date. */
+
+ if ((time_t)-1 == (t = X509expires(x))) {
+ warnx("X509expires");
+ goto out;
+ }
+
+ /*
+ * Next, the long process to make sure that the SAN entries
+ * listed with the certificate fully cover those passed on the
+ * comamnd line.
+ */
+
+ extsz = NULL != x->cert_info->extensions ?
+ sk_X509_EXTENSION_num(x->cert_info->extensions) : 0;
+
+ /* Scan til we find the SAN NID. */
+
+ for (i = 0; i < extsz; i++) {
+ ex = sk_X509_EXTENSION_value
+ (x->cert_info->extensions, i);
+ assert(NULL != ex);
+ obj = X509_EXTENSION_get_object(ex);
+ assert(NULL != obj);
+ if (NID_subject_alt_name != OBJ_obj2nid(obj))
+ continue;
+
+ if (NULL != san) {
+ warnx("%s/%s: two SAN entries",
+ certdir, CERT_PEM);
+ goto out;
+ }
+
+ bio = BIO_new(BIO_s_mem());
+ if (NULL == bio) {
+ warnx("BIO_new");
+ goto out;
+ } else if ( ! X509V3_EXT_print(bio, ex, 0, 0)) {
+ warnx("X509V3_EXT_print");
+ goto out;
+ } else if (NULL == (san = calloc(1, bio->num_write + 1))) {
+ warn("calloc");
+ goto out;
+ }
+ ssz = BIO_read(bio, san, bio->num_write);
+ if (ssz < 0 || (unsigned)ssz != bio->num_write) {
+ warnx("BIO_read");
+ goto out;
+ }
+ }
+
+ if (NULL == san) {
+ warnx("%s/%s: does not have a SAN entry", certdir, CERT_PEM);
+ goto out;
+ }
+
+ /* An array of buckets: the number of entries found. */
+
+ if (NULL == (found = calloc(altsz, sizeof(size_t)))) {
+ warn("calloc");
+ goto out;
+ }
+
+ /*
+ * Parse the SAN line.
+ * Make sure that all of the domains are represented only once.
+ */
+
+ str = san;
+ while (NULL != (tok = strsep(&str, ","))) {
+ if ('\0' == *tok)
+ continue;
+ while (isspace((int)*tok))
+ tok++;
+ if (strncmp(tok, "DNS:", 4))
+ continue;
+ tok += 4;
+ for (j = 0; j < altsz; j++)
+ if (0 == strcmp(tok, alts[j]))
+ break;
+ if (j == altsz) {
+ warnx("%s/%s: unknown SAN entry: %s",
+ certdir, CERT_PEM, tok);
+ goto out;
+ }
+ if (found[j]++) {
+ warnx("%s/%s: duplicate SAN entry: %s",
+ certdir, CERT_PEM, tok);
+ goto out;
+ }
+ }
+
+ for (j = 0; j < altsz; j++) {
+ if (found[j])
+ continue;
+ warnx("%s/%s: domain not listed: %s",
+ certdir, CERT_PEM, alts[j]);
+ goto out;
+ }
+
+ /*
+ * If we're going to revoke, write the certificate to the
+ * netproc in DER and base64-encoded format.
+ * Then exit: we have nothing left to do.
+ */
+
+ if (revoke) {
+ dodbg("%s/%s: revocation", certdir, CERT_PEM);
+
+ /*
+ * First, tell netproc we're online.
+ * If they're down, then just exit without warning.
+ */
+
+ cc = writeop(fd, COMM_REVOKE_RESP, REVOKE_EXP);
+ if (0 == cc)
+ rc = 1;
+ if (cc <= 0)
+ goto out;
+
+ if ((len = i2d_X509(x, NULL)) < 0) {
+ warnx("i2d_X509");
+ goto out;
+ } else if (NULL == (der = dercp = malloc(len))) {
+ warn("malloc");
+ goto out;
+ } else if (len != i2d_X509(x, (u_char **)&dercp)) {
+ warnx("i2d_X509");
+ goto out;
+ } else if (NULL == (der64 = base64buf_url(der, len))) {
+ warnx("base64buf_url");
+ goto out;
+ } else if (writestr(fd, COMM_CSR, der64) >= 0)
+ rc = 1;
+
+ goto out;
+ }
+
+ rop = time(NULL) >= (t - RENEW_ALLOW) ? REVOKE_EXP : REVOKE_OK;
+
+ if (REVOKE_EXP == rop)
+ dodbg("%s/%s: certificate renewable: %lld days left",
+ certdir, CERT_PEM,
+ (long long)(t - time(NULL)) / 24 / 60 / 60);
+ else
+ dodbg("%s/%s: certificate valid: %lld days left",
+ certdir, CERT_PEM,
+ (long long)(t - time(NULL)) / 24 / 60 / 60);
+
+ if (REVOKE_OK == rop && force) {
+ warnx("%s/%s: forcing renewal", certdir, CERT_PEM);
+ rop = REVOKE_EXP;
+ }
+
+ /*
+ * We can re-submit it given RENEW_ALLOW time before.
+ * If netproc is down, just exit.
+ */
+
+ if (0 == (cc = writeop(fd, COMM_REVOKE_RESP, rop)))
+ rc = 1;
+ if (cc <= 0)
+ goto out;
+
+ op = REVOKE__MAX;
+ if (0 == (lval = readop(fd, COMM_REVOKE_OP)))
+ op = REVOKE_STOP;
+ else if (REVOKE_CHECK == lval)
+ op = lval;
+
+ if (REVOKE__MAX == op) {
+ warnx("unknown operation from netproc");
+ goto out;
+ } else if (REVOKE_STOP == op) {
+ rc = 1;
+ goto out;
+ }
+
+ rc = 1;
+out:
+ close(fd);
+ if (NULL != f)
+ fclose(f);
+ if (NULL != x)
+ X509_free(x);
+ if (NULL != bio)
+ BIO_free(bio);
+ free(san);
+ free(path);
+ free(der);
+ free(found);
+ free(der64);
+ ERR_print_errors_fp(stderr);
+ ERR_free_strings();
+ return(rc);
+}
diff --git a/usr.sbin/acme-client/rsa.c b/usr.sbin/acme-client/rsa.c
new file mode 100644
index 00000000000..54d987e5576
--- /dev/null
+++ b/usr.sbin/acme-client/rsa.c
@@ -0,0 +1,97 @@
+/* $Id: rsa.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <err.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+
+#include "rsa.h"
+
+/*
+ * Default number of bits when creating a new key.
+ */
+#define KBITS 4096
+
+/*
+ * Create an RSA key with the default KBITS number of bits.
+ */
+EVP_PKEY *
+rsa_key_create(FILE *f, const char *fname)
+{
+ EVP_PKEY_CTX *ctx;
+ EVP_PKEY *pkey;
+
+ ctx = NULL;
+ pkey = NULL;
+
+ /* First, create the context and the key. */
+
+ if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL))) {
+ warnx("EVP_PKEY_CTX_new_id");
+ goto err;
+ } else if (EVP_PKEY_keygen_init(ctx) <= 0) {
+ warnx("EVP_PKEY_keygen_init");
+ goto err;
+ } else if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KBITS) <= 0) {
+ warnx("EVP_PKEY_set_rsa_keygen_bits");
+ goto err;
+ } else if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+ warnx("EVP_PKEY_keygen");
+ goto err;
+ }
+
+ /* Serialise the key to the disc. */
+
+ if (PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL))
+ goto out;
+
+ warnx("%s: PEM_write_PrivateKey", fname);
+err:
+ if (NULL != pkey)
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+out:
+ if (NULL != ctx)
+ EVP_PKEY_CTX_free(ctx);
+ return(pkey);
+}
+
+
+EVP_PKEY *
+rsa_key_load(FILE *f, const char *fname)
+{
+ EVP_PKEY *pkey;
+
+ pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL);
+ if (NULL == pkey) {
+ warnx("%s: PEM_read_PrivateKey", fname);
+ return(NULL);
+ } else if (EVP_PKEY_RSA == EVP_PKEY_type(pkey->type))
+ return(pkey);
+
+ warnx("%s: unsupported key type", fname);
+ EVP_PKEY_free(pkey);
+ return(NULL);
+}
+
diff --git a/usr.sbin/acme-client/rsa.h b/usr.sbin/acme-client/rsa.h
new file mode 100644
index 00000000000..8e4162808ca
--- /dev/null
+++ b/usr.sbin/acme-client/rsa.h
@@ -0,0 +1,23 @@
+/* $Id: rsa.h,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef RSA_H
+#define RSA_H
+
+EVP_PKEY *rsa_key_create(FILE *, const char *);
+EVP_PKEY *rsa_key_load(FILE *, const char *);
+
+#endif /* ! RSA_H */
diff --git a/usr.sbin/acme-client/sandbox-pledge.c b/usr.sbin/acme-client/sandbox-pledge.c
new file mode 100644
index 00000000000..ece973d638a
--- /dev/null
+++ b/usr.sbin/acme-client/sandbox-pledge.c
@@ -0,0 +1,83 @@
+/* $Id: sandbox-pledge.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+sandbox_before(void)
+{
+
+ return(1);
+}
+
+int
+sandbox_after(void)
+{
+
+ switch (proccomp) {
+ case (COMP_ACCOUNT):
+ case (COMP_CERT):
+ case (COMP_KEY):
+ case (COMP_REVOKE):
+ case (COMP__MAX):
+ if (-1 == pledge("stdio", NULL)) {
+ warn("pledge");
+ return(0);
+ }
+ break;
+ case (COMP_CHALLENGE):
+ if (-1 == pledge("stdio cpath wpath", NULL)) {
+ warn("pledge");
+ return(0);
+ }
+ break;
+ case (COMP_DNS):
+ if (-1 == pledge("stdio dns", NULL)) {
+ warn("pledge");
+ return(0);
+ }
+ break;
+ case (COMP_FILE):
+ /*
+ * Rpath and cpath for rename, wpath and cpath for
+ * writing to the temporary.
+ */
+ if (-1 == pledge("stdio cpath wpath rpath", NULL)) {
+ warn("pledge");
+ return(0);
+ }
+ break;
+ case (COMP_NET):
+ if (-1 == pledge("stdio inet", NULL)) {
+ warn("pledge");
+ return(0);
+ }
+ break;
+ }
+ return(1);
+}
diff --git a/usr.sbin/acme-client/util-pledge.c b/usr.sbin/acme-client/util-pledge.c
new file mode 100644
index 00000000000..6b5e78d9b34
--- /dev/null
+++ b/usr.sbin/acme-client/util-pledge.c
@@ -0,0 +1,65 @@
+/* $Id: util-pledge.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <err.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+int
+dropfs(const char *path)
+{
+
+ /*
+ * Only the challenge and file processes touch files within the
+ * pledge, so only these need to be chrooted.
+ */
+
+ if (COMP_CHALLENGE != proccomp &&
+ COMP_FILE != proccomp)
+ return(1);
+
+ if (-1 == chroot(path))
+ warn("%s: chroot", path);
+ else if (-1 == chdir("/"))
+ warn("/: chdir");
+ else
+ return(1);
+
+ return(0);
+}
+
+int
+checkprivs(void)
+{
+
+ /* Needed for chroot(2) calls in dropfs(). */
+
+ return(0 == getuid());
+}
+
+int
+dropprivs(void)
+{
+
+ /* Don't need to drop privileges like this. */
+
+ return(1);
+}
diff --git a/usr.sbin/acme-client/util.c b/usr.sbin/acme-client/util.c
new file mode 100644
index 00000000000..d7e5b6322c6
--- /dev/null
+++ b/usr.sbin/acme-client/util.c
@@ -0,0 +1,317 @@
+/* $Id: util.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <sys/wait.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+static volatile sig_atomic_t sig;
+
+static const char *const comps[COMP__MAX] = {
+ "netproc", /* COMP_NET */
+ "keyproc", /* COMP_KEY */
+ "certproc", /* COMP_CERT */
+ "acctproc", /* COMP_ACCOUNT */
+ "challengeproc", /* COMP_CHALLENGE */
+ "fileproc", /* COMP_FILE */
+ "dnsproc", /* COMP_DNS */
+ "revokeproc", /* COMP_REVOKE */
+};
+
+static const char *const comms[COMM__MAX] = {
+ "req", /* COMM_REQ */
+ "thumbprint", /* COMM_THUMB */
+ "cert", /* COMM_CERT */
+ "payload", /* COMM_PAY */
+ "nonce", /* COMM_NONCE */
+ "token", /* COMM_TOK */
+ "challenge-op", /* COMM_CHNG_OP */
+ "challenge-ack", /* COMM_CHNG_ACK */
+ "account", /* COMM_ACCT */
+ "acctpro-status", /* COMM_ACCT_STAT */
+ "csr", /* COMM_CSR */
+ "csr-op", /* COMM_CSR_OP */
+ "issuer", /* COMM_ISSUER */
+ "chain", /* COMM_CHAIN */
+ "chain-op", /* COMM_CHAIN_OP */
+ "dns", /* COMM_DNS */
+ "dnsq", /* COMM_DNSQ */
+ "dns-address", /* COMM_DNSA */
+ "dns-family", /* COMM_DNSF */
+ "dns-length", /* COMM_DNSLEN */
+ "keyproc-status", /* COMM_KEY_STAT */
+ "revoke-op", /* COMM_REVOKE_OP */
+ "revoke-check", /* COMM_REVOKE_CHECK */
+ "revoke-response", /* COMM_REVOKE_RESP */
+};
+
+static void
+sigpipe(int code)
+{
+
+ (void)code;
+ sig = 1;
+}
+
+/*
+ * This will read a long-sized operation.
+ * Operations are usually enums, so this should be alright.
+ * We return 0 on EOF and LONG_MAX on failure.
+ */
+long
+readop(int fd, enum comm comm)
+{
+ ssize_t ssz;
+ long op;
+
+ ssz = read(fd, &op, sizeof(long));
+ if (ssz < 0) {
+ warn("read: %s", comms[comm]);
+ return(LONG_MAX);
+ } else if (ssz && ssz != sizeof(long)) {
+ warnx("short read: %s", comms[comm]);
+ return(LONG_MAX);
+ } else if (0 == ssz)
+ return(0);
+
+ return(op);
+}
+
+char *
+readstr(int fd, enum comm comm)
+{
+ size_t sz;
+
+ return(readbuf(fd, comm, &sz));
+}
+
+/*
+ * Read a buffer from the sender.
+ * This consists of two parts: the lenght of the buffer, and the buffer
+ * itself.
+ * We allow the buffer to be binary, but nil-terminate it anyway.
+ */
+char *
+readbuf(int fd, enum comm comm, size_t *sz)
+{
+ ssize_t ssz;
+ size_t rsz, lsz;
+ char *p;
+
+ p = NULL;
+
+ if ((ssz = read(fd, sz, sizeof(size_t))) < 0) {
+ warn("read: %s length", comms[comm]);
+ return(NULL);
+ } else if ((size_t)ssz != sizeof(size_t)) {
+ warnx("short read: %s length", comms[comm]);
+ return(NULL);
+ } else if (*sz > SIZE_MAX - 1) {
+ warnx("integer overflow");
+ return(NULL);
+ } else if (NULL == (p = calloc(1, *sz + 1))) {
+ warn("malloc");
+ return(NULL);
+ }
+
+ /* Catch this over several reads. */
+
+ rsz = 0;
+ lsz = *sz;
+ while (lsz) {
+ if ((ssz = read(fd, p + rsz, lsz)) < 0) {
+ warn("read: %s", comms[comm]);
+ break;
+ } else if (ssz > 0) {
+ assert((size_t)ssz <= lsz);
+ rsz += (size_t)ssz;
+ lsz -= (size_t)ssz;
+ }
+ }
+
+ if (lsz) {
+ warnx("couldn't read buffer: %s", comms[comm]);
+ free(p);
+ return(NULL);
+ }
+
+ return(p);
+}
+
+/*
+ * Wring a long-value to a communication pipe.
+ * Returns 0 if the reader has terminated, -1 on error, 1 on success.
+ */
+int
+writeop(int fd, enum comm comm, long op)
+{
+ void (*sigfp)(int);
+ ssize_t ssz;
+ int er;
+
+ sigfp = signal(SIGPIPE, sigpipe);
+
+ if ((ssz = write(fd, &op, sizeof(long))) < 0) {
+ if (EPIPE != (er = errno))
+ warn("write: %s", comms[comm]);
+ signal(SIGPIPE, sigfp);
+ return(EPIPE == er ? 0 : -1);
+ }
+
+ signal(SIGPIPE, sigfp);
+
+ if ((size_t)ssz != sizeof(long)) {
+ warnx("short write: %s", comms[comm]);
+ return(-1);
+ }
+
+ return(1);
+}
+
+/*
+ * Fully write the given buffer.
+ * Returns 0 if the reader has terminated, -1 on error, 1 on success.
+ */
+int
+writebuf(int fd, enum comm comm, const void *v, size_t sz)
+{
+ ssize_t ssz;
+ int er, rc;
+ void (*sigfp)(int);
+
+ rc = -1;
+
+ /*
+ * First, try to write the length.
+ * If the other end of the pipe has closed, we allow the short
+ * write to propogate as a return value of zero.
+ * To detect this, catch SIGPIPE.
+ */
+
+ sigfp = signal(SIGPIPE, sigpipe);
+
+ if ((ssz = write(fd, &sz, sizeof(size_t))) < 0) {
+ if (EPIPE != (er = errno))
+ warn("write: %s length", comms[comm]);
+ signal(SIGPIPE, sigfp);
+ return(EPIPE == er ? 0 : -1);
+ }
+
+ /* Now write errors cause us to bail. */
+
+ if ((size_t)ssz != sizeof(size_t))
+ warnx("short write: %s length", comms[comm]);
+ else if ((ssz = write(fd, v, sz)) < 0)
+ warn("write: %s", comms[comm]);
+ else if ((size_t)ssz != sz)
+ warnx("short write: %s", comms[comm]);
+ else
+ rc = 1;
+
+ signal(SIGPIPE, sigfp);
+ return(rc);
+}
+
+int
+writestr(int fd, enum comm comm, const char *v)
+{
+
+ return(writebuf(fd, comm, v, strlen(v)));
+}
+
+/*
+ * Make sure that the given process exits properly, i.e., properly
+ * exiting with EXIT_SUCCESS.
+ * Returns non-zero on success and zero on failure.
+ */
+int
+checkexit(pid_t pid, enum comp comp)
+{
+ int c, cc;
+ const char *cp;
+
+ if (-1 == waitpid(pid, &c, 0)) {
+ warn("waitpid");
+ return(0);
+ } else if ( ! WIFEXITED(c) && WIFSIGNALED(c)) {
+ cp = strsignal(WTERMSIG(c));
+ warnx("signal: %s(%u): %s", comps[comp], pid, cp);
+ return(0);
+ } else if ( ! WIFEXITED(c)) {
+ warnx("did not exit: %s(%u)", comps[comp], pid);
+ return(0);
+ } else if (EXIT_SUCCESS != WEXITSTATUS(c)) {
+ cc = WEXITSTATUS(c);
+ dodbg("bad exit: %s(%u): %d", comps[comp], pid, cc);
+ return(0);
+ }
+
+ return(1);
+}
+
+/*
+ * Make sure that the given process exits properly, i.e., properly
+ * exiting with EXIT_SUCCESS *or* 2.
+ * Returns non-zero on success and zero on failure and sets the "rc"
+ * value to be the exit status.
+ */
+int
+checkexit_ext(int *rc, pid_t pid, enum comp comp)
+{
+ int c;
+ const char *cp;
+
+ *rc = EXIT_FAILURE;
+
+ if (-1 == waitpid(pid, &c, 0)) {
+ warn("waitpid");
+ return(0);
+ }
+
+ if ( ! WIFEXITED(c) && WIFSIGNALED(c)) {
+ cp = strsignal(WTERMSIG(c));
+ warnx("signal: %s(%u): %s", comps[comp], pid, cp);
+ return(0);
+ } else if ( ! WIFEXITED(c)) {
+ warnx("did not exit: %s(%u)", comps[comp], pid);
+ return(0);
+ }
+
+ /* Now check extended status. */
+
+ if (EXIT_SUCCESS != (*rc = WEXITSTATUS(c)) && 2 != *rc) {
+ dodbg("bad exit: %s(%u): %d", comps[comp], pid, *rc);
+ return(0);
+ }
+ return(1);
+}
+