Browse Source

Import upstream version 8

Sergio Correia 3 years ago
parent
commit
c78f46c060

+ 0 - 60
Makefile.am

@@ -1,60 +0,0 @@
-DISTCHECK_CONFIGURE_FLAGS = --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
-BUILT_SOURCES=
-CLEANFILES=
-man1_MANS=
-man8_MANS=
-
-AM_CFLAGS = @TANG_CFLAGS@ @jose_CFLAGS@
-LDADD = @jose_LIBS@ @http_parser_LIBS@
-
-cachedir = $(localstatedir)/cache/$(PACKAGE_NAME)
-jwkdir = $(localstatedir)/db/$(PACKAGE_NAME)
-
-nodist_systemdsystemunit_DATA = \
-    units/tangd@.service \
-    units/tangd.socket \
-    units/tangd-update.path \
-    units/tangd-update.service \
-    units/tangd-keygen.service
-
-dist_libexec_SCRIPTS = src/tangd-update src/tangd-keygen
-dist_bin_SCRIPTS = src/tang-show-keys
-libexec_PROGRAMS = src/tangd
-
-man_ADOC_FILES= \
-	doc/tang-show-keys.1.adoc \
-	doc/tang.8.adoc
-
-if HAVE_A2X
-man_ROFF_FILES = $(man_ADOC_FILES:.adoc=.roff)
-BUILT_SOURCES += $(man_ROFF_FILES)
-CLEANFILES += $(man_ROFF_FILES) $(man_ROFF_FILES:.roff=)
-
-$(top_builddir)/%.roff: %.adoc
-	$(MKDIR_P) $$(dirname $@)
-	$(A2X) -f manpage $^ -D $(top_builddir)/$$(dirname $@)
-	$(INSTALL) -m 644 $(top_builddir)/$(@:.roff=) $(top_builddir)/$@
-
-man1_MANS += doc/tang-show-keys.1
-man8_MANS += doc/tang.8
-endif
-
-src_tangd_SOURCES = src/http.c src/http.h src/tangd.c
-
-%: %.in
-	$(AM_V_GEN)mkdir -p "`dirname "$@"`"
-	$(AM_V_GEN)$(SED) \
-		-e 's,@libexecdir\@,$(libexecdir),g' \
-		-e 's,@jwkdir\@,$(jwkdir),g' \
-		-e 's,@cachedir\@,$(cachedir),g' \
-		$(srcdir)/$@.in > $@
-
-AM_TESTS_ENVIRONMENT = SD_ACTIVATE="@SD_ACTIVATE@" PATH=$(srcdir)/src:$(builddir)/src:$(PATH)
-TESTS = tests/adv tests/rec
-
-CLEANFILES += $(nodist_systemdsystemunit_DATA)
-EXTRA_DIST = \
-    $(foreach unit,$(nodist_systemdsystemunit_DATA),$(unit).in) \
-    COPYING \
-    $(TESTS) \
-    $(man_ADOC_FILES)

File diff suppressed because it is too large
+ 0 - 1396
Makefile.in


+ 278 - 0
README.md

@@ -0,0 +1,278 @@
+[![build](https://github.com/latchset/tang/workflows/build/badge.svg)](https://github.com/latchset/tang/actions)
+[![coverage](https://codecov.io/gh/latchset/tang/branch/master/graph/badge.svg)](https://codecov.io/gh/latchset/tang)
+
+# Tang
+
+## Welcome to Tang!
+Tang is a server for binding data to network presence.
+
+This sounds fancy, but the concept is simple. You have some data, but you only
+want it to be available when the system containing the data is on a certain,
+usually secure, network. This is where Tang comes in.
+
+First, the client gets a list of the Tang server's advertised asymmetric keys.
+This can happen online by a simple HTTP GET. Alternatively, since the keys are
+asymmetric, the public key list can be distributed out of band.
+
+Second, the client uses one of these public keys to generate a unique,
+cryptographically strong encryption key. The data is then encrypted using this
+key. Once the data is encrypted, the key is discarded. Some small metadata is
+produced as part of this operation which the client should store in a
+convenient location. This process of encrypting data is the provisioning step.
+
+Third, when the client is ready to access its data, it simply loads the
+metadata produced in the provisioning step and performs an HTTP POST in order
+to recover the encryption key. This process is the recovery step.
+
+#### Tang Versus Key Escrow: Ease of Use and Simple Security
+
+Tang provides an easy and secure alternative to key escrows.
+
+Before Tang, automated decryption usually took the form of generating a key,
+encrypting data with it and then storing the key in a remote server. This
+remote server is called a key escrow.
+
+The concept of key escrow is simple, but managing it can be complex.
+
+Key escrows are stateful by nature. And since they store live data (the
+encryption keys), they must be surrounded by a sophisticated backup policy.
+This backup policy also needs to be carefully secured, otherwise improper
+access to the keys could be obtained. Further, since keys are transferred over
+the wire, typically SSL/TLS is used. SSL/TLS is a large protocol, with a
+corresponding large attack surface; resulting in attacks like Heartbleed. Even
+further, escrows require a comprehensive authentication policy. Without this
+any user on the network can fetch any key. Often this is deployed using X.509
+certificates, which bring their own complexity.
+
+In contrast, Tang is stateless and doesn't require TLS or authentication. Tang
+also has limited knowledge. Unlike escrows, where the server has knowledge of
+every key ever used, Tang never sees a single client key. Tang never gains any
+identifying information from the client.
+
+|                |   Escrow   |   Tang   |
+|---------------:|:----------:|:--------:|
+|      Stateless |     No     |    Yes   |
+|          X.509 |  Required  | Optional |
+|        SSL/TLS |  Required  | Optional |
+| Authentication |  Required  | Optional |
+|      Anonymous |     No     |    Yes   |
+
+## Getting Started
+### Dependencies
+
+Tang requires a few other software libraries:
+
+1. http-parser >= 2.8.0 - https://github.com/nodejs/http-parser
+2. systemd - https://github.com/systemd/systemd
+3. jose >= 8 - https://github.com/latchset/jose
+
+#### Fedora
+
+Tang is packaged for Fedora. This package should be used as it contains
+additional settings (such as SETGID directories) out of the box. To install it:
+
+    $ sudo dnf install tang
+
+If you really want to build from source on Fedora, you will need the following
+packages:
+
+1. http-parser - ``http-parser-devel``
+2. systemd - ``systemd``
+3. jose - ``jose``, ``libjose-devel``
+4. curl - curl (only needed for running tests)
+
+#### OpenWrt
+
+Tang is also capable of running on devices without systemd even for example
+OpenWrt (see: [this PR](https://github.com/openwrt/packages/pull/5447)).
+Instead of using systemd for socket activation you can use another daemon for
+spawning services like xinetd.
+
+An example of configuration file for Tang using xinetd can be found in the
+`units/` directory.
+
+#### Docker Container
+
+Tang is also available as a [Docker
+Container](https://gitlab.com/AdrianKoshka/tang-docker-container/).
+
+Care should be taken to ensure that, when deploying in a container cluster,
+that the Tang keys are not stored on the same physical medium that you wish to
+protect.
+
+### Building and Installing from Source
+
+Building Tang is fairly straightforward:
+
+    $ mkdir build && cd build
+    $ meson .. --prefix=/usr
+    $ ninja
+    $ sudo ninja install
+
+You can even run the tests if you'd like:
+
+    $ meson test
+
+### Server Enablement
+
+Once installed, starting a Tang server is simple:
+
+    $ sudo systemctl enable tangd.socket --now
+
+This command will enable Tang for startup at boot and will additionally start
+it immediately. During the first startup, your initial signing and exchange
+keys will be generated automatically.
+
+That's it! You're up and running!
+
+### Key Rotation
+
+It is important to periodically rotate your keys. This is a simple three step
+process. In this example, we will rotate only a signing key; but all key types
+should be rotated.
+
+First, generate the new keys (see jose documentation for more options):
+
+    $ sudo jose jwk gen -i '{"alg":"ES512"}' -o /var/db/tang/newsig.jwk
+    $ sudo jose jwk gen -i '{"alg":"ECMR"}' -o /var/db/tang/newexc.jwk
+
+Second, disable advertisement of the previous key:
+
+    $ sudo mv /var/db/tang/oldsig.jwk /var/db/tang/.oldsig.jwk
+
+Third, after some reasonable period of time you may delete the old keys. You
+should only delete the old keys when you are sure that no client require them
+anymore. You have been warned.
+
+## Tang Protocol
+
+Tang relies on the JSON Object Signing and Encryption (JOSE) standards.
+All messages in the Tang protocol are valid JOSE objects. Because of this,
+you can easily write your own trivial Tang clients using off-the-shelf JOSE
+libraries and/or command-line utilities. However, this also implies that
+comprehending the Tang protocol will require a basic understanding of JOSE
+objects.
+
+All Tang messages are transported using a simple HTTP REST API.
+
+| Method   | Path         | Operation                                     |
+|---------:|:-------------|:----------------------------------------------|
+|    `GET` | `/adv`       | Fetch public keys                             |
+|    `GET` | `/adv/{kid}` | Fetch public keys using specified signing key |
+|   `POST` | `/rec/{kid}` | Perform recovery using specified exchange key |
+
+### Advertisement
+
+The advertisement reply message contains a JWS-signed JWKSet.
+
+The (outer) JWS contains signatures using all of the advertised signing JWKs.
+
+The (inner) JWKSet contains all of the advertised public JWKs. This includes
+all advertised signing, encryption and exchange JWKs.
+
+Typically, a client will perform "Trust On First Use" in order to trust the
+server's advertisement. However, once the client trusts at least one signing
+JWK, further advertisements can be requested using that signing JWK. This
+allows clients to upgrade their chain of trust.
+
+### Binding
+
+Tang implements the McCallum-Relyea exchange as described below.
+
+The basic idea of a McCallum-Relyea exchange is that the client performs an
+ECDH key exchange in order to produce the binding key, but then discards its
+own private key so that the Tang server is the only party that can reconstitute
+the binding key. Additionally, a third, ephemeral key is used to blind the
+client's public key and the binding key so that only the client can unblind
+them. In short, blinding makes the recovery request and response
+indistinguishable from random to both eavesdroppers and the Tang server itself.
+
+The POST request and reply bodies are JWK objects.
+
+#### Provisioning
+
+The client selects one of the Tang server's exchange keys (`sJWK`; identified
+by the use of `deriveKey` in the `sJWK`'s `key_ops` attribute). The client
+generates a new (random) JWK (`cJWK`). The client performs its half of a
+standard ECDH exchange producing `dJWK` which it uses to encrypt the data.
+Afterwards, it discards `dJWK` and the private key from `cJWK`.
+
+The client then stores `cJWK` for later use in the recovery step. Generally
+speaking, the client may also store other data, such as the URL of the Tang
+server or the trusted advertisement signing keys.
+
+Expressed mathematically (capital = private key):
+
+    s = g * S # sJWK (Server operation)
+    c = g * C # cJWK
+    K = s * C # dJWK
+
+#### Recovery
+
+To recover `dJWK` after discarding it, the client generates a third ephemeral
+key (`eJWK`). Using `eJWK`, the client performs elliptic curve group addition
+of `eJWK` and `cJWK`, producing `xJWK`. The client POSTs `xJWK` to the server.
+
+The server then performs its half of the ECDH key exchange using `xJWK` and
+`sJWK`, producing `yJWK`. The server returns `yJWK` to the client.
+
+The client then performs half of an ECDH key exchange between `eJWK` and
+`sJWK`, producing `zJWK`. Subtracting `zJWK` from `yJWK` produces `dJWK` again.
+
+Expressed mathematically (capital = private key):
+
+    e = g * E # eJWK
+    x = c + e # xJWK
+    y = x * S # yJWK (Server operation)
+    z = s * E # zJWK
+    K = y - z # dJWK
+
+##### Understanding the Algorithm
+
+To understand this algorithm, let us consider it without the ephemeral `eJWK`.
+The math in this example depicts a standard ECDH.
+
+    s = g * S # sJWK (Server advertisement)
+    c = g * C # cJWK (Client provisioning)
+    K = s * C # dJWK (Client provisioning)
+
+    K = c * S # dJWK (Server recovery)
+
+In the above case, the provisioning step is identical and the recovery step
+does not use `eJWK`. Here, it becomes obvious that the client could simply send
+its own public key (`cJWK`) to the server and receive back `dJWK`.
+
+This example has a serious problem, however: both the identity of the client
+(`cJWK`) and its secure decryption key (`dJWK`) are leaked to both the server
+and any eavesdroppers. To overcome this problem, we use the ephemeral key
+(`eJWK`) to blind both values.
+
+#### Security Considerations
+
+Let's think about the security of this system.
+
+So long as the client discards its private key, the client cannot recover
+`dJWK` without the Tang server. This is fundamentally the same assumption used
+by Diffie-Hellman (and ECDH).
+
+There are thus three avenues of attack which we will consider in turn:
+
+1. Man-in-the-Middle
+2. Compromise the client to gain access to `cJWK`
+3. Compromise the server to gain access to `sJWK`'s private key
+
+In the first case, the eavesdropper in this case sees the client send `xJWK`
+and receive `yJWK`. Since, these packets are blinded by `eJWK`, only the party
+that can unblind these values is the client itself (since only it has `eJWK`'s
+private key). Thus, the MitM attack fails.
+
+In the second case, it is of utmost importance that the client protect `cJWK`
+from prying eyes. This may include device permissions, filesystem permissions,
+security frameworks (such as SELinux) or even the use of hardware encryption
+such as a TPM. How precisely this is accomplished is an exercise left to the
+client implementation.
+
+In the third case, the Tang server must protect the private key for `sJWK`.
+In this implementation, access is controlled by filesystem permissions and
+the service's policy. An alternative implementation might use hardware
+cryptography (for example, an HSM) to protect the private key.

File diff suppressed because it is too large
+ 0 - 1496
aclocal.m4


+ 0 - 348
compile

@@ -1,348 +0,0 @@
-#!/bin/sh
-# Wrapper for compilers which do not understand '-c -o'.
-
-scriptversion=2016-01-11.22; # UTC
-
-# Copyright (C) 1999-2017 Free Software Foundation, Inc.
-# Written by Tom Tromey <tromey@cygnus.com>.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-# This file is maintained in Automake, please report
-# bugs to <bug-automake@gnu.org> or send patches to
-# <automake-patches@gnu.org>.
-
-nl='
-'
-
-# We need space, tab and new line, in precisely that order.  Quoting is
-# there to prevent tools from complaining about whitespace usage.
-IFS=" ""	$nl"
-
-file_conv=
-
-# func_file_conv build_file lazy
-# Convert a $build file to $host form and store it in $file
-# Currently only supports Windows hosts. If the determined conversion
-# type is listed in (the comma separated) LAZY, no conversion will
-# take place.
-func_file_conv ()
-{
-  file=$1
-  case $file in
-    / | /[!/]*) # absolute file, and not a UNC file
-      if test -z "$file_conv"; then
-	# lazily determine how to convert abs files
-	case `uname -s` in
-	  MINGW*)
-	    file_conv=mingw
-	    ;;
-	  CYGWIN*)
-	    file_conv=cygwin
-	    ;;
-	  *)
-	    file_conv=wine
-	    ;;
-	esac
-      fi
-      case $file_conv/,$2, in
-	*,$file_conv,*)
-	  ;;
-	mingw/*)
-	  file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'`
-	  ;;
-	cygwin/*)
-	  file=`cygpath -m "$file" || echo "$file"`
-	  ;;
-	wine/*)
-	  file=`winepath -w "$file" || echo "$file"`
-	  ;;
-      esac
-      ;;
-  esac
-}
-
-# func_cl_dashL linkdir
-# Make cl look for libraries in LINKDIR
-func_cl_dashL ()
-{
-  func_file_conv "$1"
-  if test -z "$lib_path"; then
-    lib_path=$file
-  else
-    lib_path="$lib_path;$file"
-  fi
-  linker_opts="$linker_opts -LIBPATH:$file"
-}
-
-# func_cl_dashl library
-# Do a library search-path lookup for cl
-func_cl_dashl ()
-{
-  lib=$1
-  found=no
-  save_IFS=$IFS
-  IFS=';'
-  for dir in $lib_path $LIB
-  do
-    IFS=$save_IFS
-    if $shared && test -f "$dir/$lib.dll.lib"; then
-      found=yes
-      lib=$dir/$lib.dll.lib
-      break
-    fi
-    if test -f "$dir/$lib.lib"; then
-      found=yes
-      lib=$dir/$lib.lib
-      break
-    fi
-    if test -f "$dir/lib$lib.a"; then
-      found=yes
-      lib=$dir/lib$lib.a
-      break
-    fi
-  done
-  IFS=$save_IFS
-
-  if test "$found" != yes; then
-    lib=$lib.lib
-  fi
-}
-
-# func_cl_wrapper cl arg...
-# Adjust compile command to suit cl
-func_cl_wrapper ()
-{
-  # Assume a capable shell
-  lib_path=
-  shared=:
-  linker_opts=
-  for arg
-  do
-    if test -n "$eat"; then
-      eat=
-    else
-      case $1 in
-	-o)
-	  # configure might choose to run compile as 'compile cc -o foo foo.c'.
-	  eat=1
-	  case $2 in
-	    *.o | *.[oO][bB][jJ])
-	      func_file_conv "$2"
-	      set x "$@" -Fo"$file"
-	      shift
-	      ;;
-	    *)
-	      func_file_conv "$2"
-	      set x "$@" -Fe"$file"
-	      shift
-	      ;;
-	  esac
-	  ;;
-	-I)
-	  eat=1
-	  func_file_conv "$2" mingw
-	  set x "$@" -I"$file"
-	  shift
-	  ;;
-	-I*)
-	  func_file_conv "${1#-I}" mingw
-	  set x "$@" -I"$file"
-	  shift
-	  ;;
-	-l)
-	  eat=1
-	  func_cl_dashl "$2"
-	  set x "$@" "$lib"
-	  shift
-	  ;;
-	-l*)
-	  func_cl_dashl "${1#-l}"
-	  set x "$@" "$lib"
-	  shift
-	  ;;
-	-L)
-	  eat=1
-	  func_cl_dashL "$2"
-	  ;;
-	-L*)
-	  func_cl_dashL "${1#-L}"
-	  ;;
-	-static)
-	  shared=false
-	  ;;
-	-Wl,*)
-	  arg=${1#-Wl,}
-	  save_ifs="$IFS"; IFS=','
-	  for flag in $arg; do
-	    IFS="$save_ifs"
-	    linker_opts="$linker_opts $flag"
-	  done
-	  IFS="$save_ifs"
-	  ;;
-	-Xlinker)
-	  eat=1
-	  linker_opts="$linker_opts $2"
-	  ;;
-	-*)
-	  set x "$@" "$1"
-	  shift
-	  ;;
-	*.cc | *.CC | *.cxx | *.CXX | *.[cC]++)
-	  func_file_conv "$1"
-	  set x "$@" -Tp"$file"
-	  shift
-	  ;;
-	*.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO])
-	  func_file_conv "$1" mingw
-	  set x "$@" "$file"
-	  shift
-	  ;;
-	*)
-	  set x "$@" "$1"
-	  shift
-	  ;;
-      esac
-    fi
-    shift
-  done
-  if test -n "$linker_opts"; then
-    linker_opts="-link$linker_opts"
-  fi
-  exec "$@" $linker_opts
-  exit 1
-}
-
-eat=
-
-case $1 in
-  '')
-     echo "$0: No command.  Try '$0 --help' for more information." 1>&2
-     exit 1;
-     ;;
-  -h | --h*)
-    cat <<\EOF
-Usage: compile [--help] [--version] PROGRAM [ARGS]
-
-Wrapper for compilers which do not understand '-c -o'.
-Remove '-o dest.o' from ARGS, run PROGRAM with the remaining
-arguments, and rename the output as expected.
-
-If you are trying to build a whole package this is not the
-right script to run: please start by reading the file 'INSTALL'.
-
-Report bugs to <bug-automake@gnu.org>.
-EOF
-    exit $?
-    ;;
-  -v | --v*)
-    echo "compile $scriptversion"
-    exit $?
-    ;;
-  cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \
-  icl | *[/\\]icl | icl.exe | *[/\\]icl.exe )
-    func_cl_wrapper "$@"      # Doesn't return...
-    ;;
-esac
-
-ofile=
-cfile=
-
-for arg
-do
-  if test -n "$eat"; then
-    eat=
-  else
-    case $1 in
-      -o)
-	# configure might choose to run compile as 'compile cc -o foo foo.c'.
-	# So we strip '-o arg' only if arg is an object.
-	eat=1
-	case $2 in
-	  *.o | *.obj)
-	    ofile=$2
-	    ;;
-	  *)
-	    set x "$@" -o "$2"
-	    shift
-	    ;;
-	esac
-	;;
-      *.c)
-	cfile=$1
-	set x "$@" "$1"
-	shift
-	;;
-      *)
-	set x "$@" "$1"
-	shift
-	;;
-    esac
-  fi
-  shift
-done
-
-if test -z "$ofile" || test -z "$cfile"; then
-  # If no '-o' option was seen then we might have been invoked from a
-  # pattern rule where we don't need one.  That is ok -- this is a
-  # normal compilation that the losing compiler can handle.  If no
-  # '.c' file was seen then we are probably linking.  That is also
-  # ok.
-  exec "$@"
-fi
-
-# Name of file we expect compiler to create.
-cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'`
-
-# Create the lock directory.
-# Note: use '[/\\:.-]' here to ensure that we don't use the same name
-# that we are using for the .o file.  Also, base the name on the expected
-# object file name, since that is what matters with a parallel build.
-lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d
-while true; do
-  if mkdir "$lockdir" >/dev/null 2>&1; then
-    break
-  fi
-  sleep 1
-done
-# FIXME: race condition here if user kills between mkdir and trap.
-trap "rmdir '$lockdir'; exit 1" 1 2 15
-
-# Run the compile.
-"$@"
-ret=$?
-
-if test -f "$cofile"; then
-  test "$cofile" = "$ofile" || mv "$cofile" "$ofile"
-elif test -f "${cofile}bj"; then
-  test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile"
-fi
-
-rmdir "$lockdir"
-exit $ret
-
-# Local Variables:
-# mode: shell-script
-# sh-indentation: 2
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC0"
-# time-stamp-end: "; # UTC"
-# End:

File diff suppressed because it is too large
+ 0 - 1476
config.guess


File diff suppressed because it is too large
+ 0 - 1836
config.sub


File diff suppressed because it is too large
+ 0 - 5869
configure


+ 0 - 87
configure.ac

@@ -1,87 +0,0 @@
-AC_PREREQ(2.59)
-AC_INIT(tang, 7)
-AC_CANONICAL_SYSTEM
-AC_PROG_CC_C99
-AC_PROG_SED
-
-AM_INIT_AUTOMAKE([subdir-objects foreign no-dist-gzip dist-bzip2 parallel-tests])
-AM_SILENT_RULES([yes])
-AM_PROG_CC_C_O
-
-PKG_PROG_PKG_CONFIG([0.25])
-
-AC_CHECK_LIB([dl], [dlopen], [AC_SUBST([dl_LIBS], [-ldl])],
-             [AC_CHECK_LIB([dl], [dlopen], [AC_SUBST([dl_LIBS], [-ldl])],
-	                   [AC_MSG_ERROR([unable to find dlopen])])])
-
-AC_CHECK_HEADER([http_parser.h], [],
-		[AC_MSG_ERROR([http-parser required!])], [
-#include <http_parser.h>
-#ifndef HTTP_STATUS_MAP
-#error HTTP_STATUS_MAP not defined!
-#endif
-])
-
-AC_CHECK_LIB([http_parser], [http_parser_execute],
-             [AC_SUBST(http_parser_LIBS, [-lhttp_parser])],
-             [AC_MSG_ERROR([http-parser required!])])
-
-PKG_CHECK_MODULES([jose], [jose >= 8])
-PKG_CHECK_MODULES([systemd], [systemd])
-
-AC_ARG_WITH([systemdsystemunitdir],
-            [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],
-            [],
-            [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
-
-AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])
-
-for ac_prog in systemd-socket-activate systemd-activate; do
-    AC_CHECK_PROG([SD_ACTIVATE], [$ac_prog], [$as_dir/$ac_prog], [],
-		  [$PATH$PATH_SEPARATOR$($PKG_CONFIG --variable=systemdutildir systemd)])
-    test -n "$SD_ACTIVATE" && break
-done
-
-test -n "$SD_ACTIVATE" || AC_MSG_ERROR([systemd-socket-activate required!])
-
-AC_MSG_CHECKING([systemd-socket-activate inetd flag])
-if $SD_ACTIVATE --help | grep -q inetd; then
-    SD_ACTIVATE="$SD_ACTIVATE --inetd"
-    AC_MSG_RESULT([--inetd])
-else
-    AC_MSG_RESULT([(default)])
-fi
-
-AC_SUBST(SD_ACTIVATE)
-
-TANG_CFLAGS="\
--Wall \
--Wextra \
--Werror \
--Wstrict-aliasing \
--Wchar-subscripts \
--Wformat-security \
--Wmissing-declarations \
--Wmissing-prototypes \
--Wnested-externs \
--Wpointer-arith \
--Wshadow \
--Wsign-compare \
--Wstrict-prototypes \
--Wtype-limits \
--Wunused-function \
--Wno-missing-field-initializers \
--Wno-unused-parameter \
-"
-AC_SUBST([TANG_CFLAGS])
-
-AC_CHECK_PROGS(A2X, [a2x])
-
-if test "x$A2X" = "x"; then
-   AC_MSG_WARN([asciidoc / a2x not found -- man pages will not be generated and installed])
-fi
-
-AM_CONDITIONAL(HAVE_A2X, [test -n "$A2X"])
-
-AC_CONFIG_FILES([Makefile])
-AC_OUTPUT

+ 0 - 791
depcomp

@@ -1,791 +0,0 @@
-#!/bin/sh
-# depcomp - compile a program generating dependencies as side-effects
-
-scriptversion=2016-01-11.22; # UTC
-
-# Copyright (C) 1999-2017 Free Software Foundation, Inc.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
-
-case $1 in
-  '')
-    echo "$0: No command.  Try '$0 --help' for more information." 1>&2
-    exit 1;
-    ;;
-  -h | --h*)
-    cat <<\EOF
-Usage: depcomp [--help] [--version] PROGRAM [ARGS]
-
-Run PROGRAMS ARGS to compile a file, generating dependencies
-as side-effects.
-
-Environment variables:
-  depmode     Dependency tracking mode.
-  source      Source file read by 'PROGRAMS ARGS'.
-  object      Object file output by 'PROGRAMS ARGS'.
-  DEPDIR      directory where to store dependencies.
-  depfile     Dependency file to output.
-  tmpdepfile  Temporary file to use when outputting dependencies.
-  libtool     Whether libtool is used (yes/no).
-
-Report bugs to <bug-automake@gnu.org>.
-EOF
-    exit $?
-    ;;
-  -v | --v*)
-    echo "depcomp $scriptversion"
-    exit $?
-    ;;
-esac
-
-# Get the directory component of the given path, and save it in the
-# global variables '$dir'.  Note that this directory component will
-# be either empty or ending with a '/' character.  This is deliberate.
-set_dir_from ()
-{
-  case $1 in
-    */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;;
-      *) dir=;;
-  esac
-}
-
-# Get the suffix-stripped basename of the given path, and save it the
-# global variable '$base'.
-set_base_from ()
-{
-  base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'`
-}
-
-# If no dependency file was actually created by the compiler invocation,
-# we still have to create a dummy depfile, to avoid errors with the
-# Makefile "include basename.Plo" scheme.
-make_dummy_depfile ()
-{
-  echo "#dummy" > "$depfile"
-}
-
-# Factor out some common post-processing of the generated depfile.
-# Requires the auxiliary global variable '$tmpdepfile' to be set.
-aix_post_process_depfile ()
-{
-  # If the compiler actually managed to produce a dependency file,
-  # post-process it.
-  if test -f "$tmpdepfile"; then
-    # Each line is of the form 'foo.o: dependency.h'.
-    # Do two passes, one to just change these to
-    #   $object: dependency.h
-    # and one to simply output
-    #   dependency.h:
-    # which is needed to avoid the deleted-header problem.
-    { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile"
-      sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile"
-    } > "$depfile"
-    rm -f "$tmpdepfile"
-  else
-    make_dummy_depfile
-  fi
-}
-
-# A tabulation character.
-tab='	'
-# A newline character.
-nl='
-'
-# Character ranges might be problematic outside the C locale.
-# These definitions help.
-upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ
-lower=abcdefghijklmnopqrstuvwxyz
-digits=0123456789
-alpha=${upper}${lower}
-
-if test -z "$depmode" || test -z "$source" || test -z "$object"; then
-  echo "depcomp: Variables source, object and depmode must be set" 1>&2
-  exit 1
-fi
-
-# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po.
-depfile=${depfile-`echo "$object" |
-  sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`}
-tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
-
-rm -f "$tmpdepfile"
-
-# Avoid interferences from the environment.
-gccflag= dashmflag=
-
-# Some modes work just like other modes, but use different flags.  We
-# parameterize here, but still list the modes in the big case below,
-# to make depend.m4 easier to write.  Note that we *cannot* use a case
-# here, because this file can only contain one case statement.
-if test "$depmode" = hp; then
-  # HP compiler uses -M and no extra arg.
-  gccflag=-M
-  depmode=gcc
-fi
-
-if test "$depmode" = dashXmstdout; then
-  # This is just like dashmstdout with a different argument.
-  dashmflag=-xM
-  depmode=dashmstdout
-fi
-
-cygpath_u="cygpath -u -f -"
-if test "$depmode" = msvcmsys; then
-  # This is just like msvisualcpp but w/o cygpath translation.
-  # Just convert the backslash-escaped backslashes to single forward
-  # slashes to satisfy depend.m4
-  cygpath_u='sed s,\\\\,/,g'
-  depmode=msvisualcpp
-fi
-
-if test "$depmode" = msvc7msys; then
-  # This is just like msvc7 but w/o cygpath translation.
-  # Just convert the backslash-escaped backslashes to single forward
-  # slashes to satisfy depend.m4
-  cygpath_u='sed s,\\\\,/,g'
-  depmode=msvc7
-fi
-
-if test "$depmode" = xlc; then
-  # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information.
-  gccflag=-qmakedep=gcc,-MF
-  depmode=gcc
-fi
-
-case "$depmode" in
-gcc3)
-## gcc 3 implements dependency tracking that does exactly what
-## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
-## it if -MD -MP comes after the -MF stuff.  Hmm.
-## Unfortunately, FreeBSD c89 acceptance of flags depends upon
-## the command line argument order; so add the flags where they
-## appear in depend2.am.  Note that the slowdown incurred here
-## affects only configure: in makefiles, %FASTDEP% shortcuts this.
-  for arg
-  do
-    case $arg in
-    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
-    *)  set fnord "$@" "$arg" ;;
-    esac
-    shift # fnord
-    shift # $arg
-  done
-  "$@"
-  stat=$?
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  mv "$tmpdepfile" "$depfile"
-  ;;
-
-gcc)
-## Note that this doesn't just cater to obsosete pre-3.x GCC compilers.
-## but also to in-use compilers like IMB xlc/xlC and the HP C compiler.
-## (see the conditional assignment to $gccflag above).
-## There are various ways to get dependency output from gcc.  Here's
-## why we pick this rather obscure method:
-## - Don't want to use -MD because we'd like the dependencies to end
-##   up in a subdir.  Having to rename by hand is ugly.
-##   (We might end up doing this anyway to support other compilers.)
-## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
-##   -MM, not -M (despite what the docs say).  Also, it might not be
-##   supported by the other compilers which use the 'gcc' depmode.
-## - Using -M directly means running the compiler twice (even worse
-##   than renaming).
-  if test -z "$gccflag"; then
-    gccflag=-MD,
-  fi
-  "$@" -Wp,"$gccflag$tmpdepfile"
-  stat=$?
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  # The second -e expression handles DOS-style file names with drive
-  # letters.
-  sed -e 's/^[^:]*: / /' \
-      -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
-## This next piece of magic avoids the "deleted header file" problem.
-## The problem is that when a header file which appears in a .P file
-## is deleted, the dependency causes make to die (because there is
-## typically no way to rebuild the header).  We avoid this by adding
-## dummy dependencies for each header file.  Too bad gcc doesn't do
-## this for us directly.
-## Some versions of gcc put a space before the ':'.  On the theory
-## that the space means something, we add a space to the output as
-## well.  hp depmode also adds that space, but also prefixes the VPATH
-## to the object.  Take care to not repeat it in the output.
-## Some versions of the HPUX 10.20 sed can't process this invocation
-## correctly.  Breaking it into two sed invocations is a workaround.
-  tr ' ' "$nl" < "$tmpdepfile" \
-    | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \
-    | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-hp)
-  # This case exists only to let depend.m4 do its work.  It works by
-  # looking at the text of this script.  This case will never be run,
-  # since it is checked for above.
-  exit 1
-  ;;
-
-sgi)
-  if test "$libtool" = yes; then
-    "$@" "-Wp,-MDupdate,$tmpdepfile"
-  else
-    "$@" -MDupdate "$tmpdepfile"
-  fi
-  stat=$?
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-
-  if test -f "$tmpdepfile"; then  # yes, the sourcefile depend on other files
-    echo "$object : \\" > "$depfile"
-    # Clip off the initial element (the dependent).  Don't try to be
-    # clever and replace this with sed code, as IRIX sed won't handle
-    # lines with more than a fixed number of characters (4096 in
-    # IRIX 6.2 sed, 8192 in IRIX 6.5).  We also remove comment lines;
-    # the IRIX cc adds comments like '#:fec' to the end of the
-    # dependency line.
-    tr ' ' "$nl" < "$tmpdepfile" \
-      | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \
-      | tr "$nl" ' ' >> "$depfile"
-    echo >> "$depfile"
-    # The second pass generates a dummy entry for each header file.
-    tr ' ' "$nl" < "$tmpdepfile" \
-      | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
-      >> "$depfile"
-  else
-    make_dummy_depfile
-  fi
-  rm -f "$tmpdepfile"
-  ;;
-
-xlc)
-  # This case exists only to let depend.m4 do its work.  It works by
-  # looking at the text of this script.  This case will never be run,
-  # since it is checked for above.
-  exit 1
-  ;;
-
-aix)
-  # The C for AIX Compiler uses -M and outputs the dependencies
-  # in a .u file.  In older versions, this file always lives in the
-  # current directory.  Also, the AIX compiler puts '$object:' at the
-  # start of each line; $object doesn't have directory information.
-  # Version 6 uses the directory in both cases.
-  set_dir_from "$object"
-  set_base_from "$object"
-  if test "$libtool" = yes; then
-    tmpdepfile1=$dir$base.u
-    tmpdepfile2=$base.u
-    tmpdepfile3=$dir.libs/$base.u
-    "$@" -Wc,-M
-  else
-    tmpdepfile1=$dir$base.u
-    tmpdepfile2=$dir$base.u
-    tmpdepfile3=$dir$base.u
-    "$@" -M
-  fi
-  stat=$?
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-    exit $stat
-  fi
-
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-  do
-    test -f "$tmpdepfile" && break
-  done
-  aix_post_process_depfile
-  ;;
-
-tcc)
-  # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26
-  # FIXME: That version still under development at the moment of writing.
-  #        Make that this statement remains true also for stable, released
-  #        versions.
-  # It will wrap lines (doesn't matter whether long or short) with a
-  # trailing '\', as in:
-  #
-  #   foo.o : \
-  #    foo.c \
-  #    foo.h \
-  #
-  # It will put a trailing '\' even on the last line, and will use leading
-  # spaces rather than leading tabs (at least since its commit 0394caf7
-  # "Emit spaces for -MD").
-  "$@" -MD -MF "$tmpdepfile"
-  stat=$?
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-  # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'.
-  # We have to change lines of the first kind to '$object: \'.
-  sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile"
-  # And for each line of the second kind, we have to emit a 'dep.h:'
-  # dummy dependency, to avoid the deleted-header problem.
-  sed -n -e 's|^  *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-## The order of this option in the case statement is important, since the
-## shell code in configure will try each of these formats in the order
-## listed in this file.  A plain '-MD' option would be understood by many
-## compilers, so we must ensure this comes after the gcc and icc options.
-pgcc)
-  # Portland's C compiler understands '-MD'.
-  # Will always output deps to 'file.d' where file is the root name of the
-  # source file under compilation, even if file resides in a subdirectory.
-  # The object file name does not affect the name of the '.d' file.
-  # pgcc 10.2 will output
-  #    foo.o: sub/foo.c sub/foo.h
-  # and will wrap long lines using '\' :
-  #    foo.o: sub/foo.c ... \
-  #     sub/foo.h ... \
-  #     ...
-  set_dir_from "$object"
-  # Use the source, not the object, to determine the base name, since
-  # that's sadly what pgcc will do too.
-  set_base_from "$source"
-  tmpdepfile=$base.d
-
-  # For projects that build the same source file twice into different object
-  # files, the pgcc approach of using the *source* file root name can cause
-  # problems in parallel builds.  Use a locking strategy to avoid stomping on
-  # the same $tmpdepfile.
-  lockdir=$base.d-lock
-  trap "
-    echo '$0: caught signal, cleaning up...' >&2
-    rmdir '$lockdir'
-    exit 1
-  " 1 2 13 15
-  numtries=100
-  i=$numtries
-  while test $i -gt 0; do
-    # mkdir is a portable test-and-set.
-    if mkdir "$lockdir" 2>/dev/null; then
-      # This process acquired the lock.
-      "$@" -MD
-      stat=$?
-      # Release the lock.
-      rmdir "$lockdir"
-      break
-    else
-      # If the lock is being held by a different process, wait
-      # until the winning process is done or we timeout.
-      while test -d "$lockdir" && test $i -gt 0; do
-        sleep 1
-        i=`expr $i - 1`
-      done
-    fi
-    i=`expr $i - 1`
-  done
-  trap - 1 2 13 15
-  if test $i -le 0; then
-    echo "$0: failed to acquire lock after $numtries attempts" >&2
-    echo "$0: check lockdir '$lockdir'" >&2
-    exit 1
-  fi
-
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-  # Each line is of the form `foo.o: dependent.h',
-  # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'.
-  # Do two passes, one to just change these to
-  # `$object: dependent.h' and one to simply `dependent.h:'.
-  sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile"
-  # Some versions of the HPUX 10.20 sed can't process this invocation
-  # correctly.  Breaking it into two sed invocations is a workaround.
-  sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \
-    | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-hp2)
-  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
-  # compilers, which have integrated preprocessors.  The correct option
-  # to use with these is +Maked; it writes dependencies to a file named
-  # 'foo.d', which lands next to the object file, wherever that
-  # happens to be.
-  # Much of this is similar to the tru64 case; see comments there.
-  set_dir_from  "$object"
-  set_base_from "$object"
-  if test "$libtool" = yes; then
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir.libs/$base.d
-    "$@" -Wc,+Maked
-  else
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir$base.d
-    "$@" +Maked
-  fi
-  stat=$?
-  if test $stat -ne 0; then
-     rm -f "$tmpdepfile1" "$tmpdepfile2"
-     exit $stat
-  fi
-
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
-  do
-    test -f "$tmpdepfile" && break
-  done
-  if test -f "$tmpdepfile"; then
-    sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile"
-    # Add 'dependent.h:' lines.
-    sed -ne '2,${
-               s/^ *//
-               s/ \\*$//
-               s/$/:/
-               p
-             }' "$tmpdepfile" >> "$depfile"
-  else
-    make_dummy_depfile
-  fi
-  rm -f "$tmpdepfile" "$tmpdepfile2"
-  ;;
-
-tru64)
-  # The Tru64 compiler uses -MD to generate dependencies as a side
-  # effect.  'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'.
-  # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
-  # dependencies in 'foo.d' instead, so we check for that too.
-  # Subdirectories are respected.
-  set_dir_from  "$object"
-  set_base_from "$object"
-
-  if test "$libtool" = yes; then
-    # Libtool generates 2 separate objects for the 2 libraries.  These
-    # two compilations output dependencies in $dir.libs/$base.o.d and
-    # in $dir$base.o.d.  We have to check for both files, because
-    # one of the two compilations can be disabled.  We should prefer
-    # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
-    # automatically cleaned when .libs/ is deleted, while ignoring
-    # the former would cause a distcleancheck panic.
-    tmpdepfile1=$dir$base.o.d          # libtool 1.5
-    tmpdepfile2=$dir.libs/$base.o.d    # Likewise.
-    tmpdepfile3=$dir.libs/$base.d      # Compaq CCC V6.2-504
-    "$@" -Wc,-MD
-  else
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir$base.d
-    tmpdepfile3=$dir$base.d
-    "$@" -MD
-  fi
-
-  stat=$?
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-    exit $stat
-  fi
-
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-  do
-    test -f "$tmpdepfile" && break
-  done
-  # Same post-processing that is required for AIX mode.
-  aix_post_process_depfile
-  ;;
-
-msvc7)
-  if test "$libtool" = yes; then
-    showIncludes=-Wc,-showIncludes
-  else
-    showIncludes=-showIncludes
-  fi
-  "$@" $showIncludes > "$tmpdepfile"
-  stat=$?
-  grep -v '^Note: including file: ' "$tmpdepfile"
-  if test $stat -ne 0; then
-    rm -f "$tmpdepfile"
-    exit $stat
-  fi
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  # The first sed program below extracts the file names and escapes
-  # backslashes for cygpath.  The second sed program outputs the file
-  # name when reading, but also accumulates all include files in the
-  # hold buffer in order to output them again at the end.  This only
-  # works with sed implementations that can handle large buffers.
-  sed < "$tmpdepfile" -n '
-/^Note: including file:  *\(.*\)/ {
-  s//\1/
-  s/\\/\\\\/g
-  p
-}' | $cygpath_u | sort -u | sed -n '
-s/ /\\ /g
-s/\(.*\)/'"$tab"'\1 \\/p
-s/.\(.*\) \\/\1:/
-H
-$ {
-  s/.*/'"$tab"'/
-  G
-  p
-}' >> "$depfile"
-  echo >> "$depfile" # make sure the fragment doesn't end with a backslash
-  rm -f "$tmpdepfile"
-  ;;
-
-msvc7msys)
-  # This case exists only to let depend.m4 do its work.  It works by
-  # looking at the text of this script.  This case will never be run,
-  # since it is checked for above.
-  exit 1
-  ;;
-
-#nosideeffect)
-  # This comment above is used by automake to tell side-effect
-  # dependency tracking mechanisms from slower ones.
-
-dashmstdout)
-  # Important note: in order to support this mode, a compiler *must*
-  # always write the preprocessed file to stdout, regardless of -o.
-  "$@" || exit $?
-
-  # Remove the call to Libtool.
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-
-  # Remove '-o $object'.
-  IFS=" "
-  for arg
-  do
-    case $arg in
-    -o)
-      shift
-      ;;
-    $object)
-      shift
-      ;;
-    *)
-      set fnord "$@" "$arg"
-      shift # fnord
-      shift # $arg
-      ;;
-    esac
-  done
-
-  test -z "$dashmflag" && dashmflag=-M
-  # Require at least two characters before searching for ':'
-  # in the target name.  This is to cope with DOS-style filenames:
-  # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise.
-  "$@" $dashmflag |
-    sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile"
-  rm -f "$depfile"
-  cat < "$tmpdepfile" > "$depfile"
-  # Some versions of the HPUX 10.20 sed can't process this sed invocation
-  # correctly.  Breaking it into two sed invocations is a workaround.
-  tr ' ' "$nl" < "$tmpdepfile" \
-    | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
-    | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-dashXmstdout)
-  # This case only exists to satisfy depend.m4.  It is never actually
-  # run, as this mode is specially recognized in the preamble.
-  exit 1
-  ;;
-
-makedepend)
-  "$@" || exit $?
-  # Remove any Libtool call
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-  # X makedepend
-  shift
-  cleared=no eat=no
-  for arg
-  do
-    case $cleared in
-    no)
-      set ""; shift
-      cleared=yes ;;
-    esac
-    if test $eat = yes; then
-      eat=no
-      continue
-    fi
-    case "$arg" in
-    -D*|-I*)
-      set fnord "$@" "$arg"; shift ;;
-    # Strip any option that makedepend may not understand.  Remove
-    # the object too, otherwise makedepend will parse it as a source file.
-    -arch)
-      eat=yes ;;
-    -*|$object)
-      ;;
-    *)
-      set fnord "$@" "$arg"; shift ;;
-    esac
-  done
-  obj_suffix=`echo "$object" | sed 's/^.*\././'`
-  touch "$tmpdepfile"
-  ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@"
-  rm -f "$depfile"
-  # makedepend may prepend the VPATH from the source file name to the object.
-  # No need to regex-escape $object, excess matching of '.' is harmless.
-  sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile"
-  # Some versions of the HPUX 10.20 sed can't process the last invocation
-  # correctly.  Breaking it into two sed invocations is a workaround.
-  sed '1,2d' "$tmpdepfile" \
-    | tr ' ' "$nl" \
-    | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \
-    | sed -e 's/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile" "$tmpdepfile".bak
-  ;;
-
-cpp)
-  # Important note: in order to support this mode, a compiler *must*
-  # always write the preprocessed file to stdout.
-  "$@" || exit $?
-
-  # Remove the call to Libtool.
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-
-  # Remove '-o $object'.
-  IFS=" "
-  for arg
-  do
-    case $arg in
-    -o)
-      shift
-      ;;
-    $object)
-      shift
-      ;;
-    *)
-      set fnord "$@" "$arg"
-      shift # fnord
-      shift # $arg
-      ;;
-    esac
-  done
-
-  "$@" -E \
-    | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
-             -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
-    | sed '$ s: \\$::' > "$tmpdepfile"
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  cat < "$tmpdepfile" >> "$depfile"
-  sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-msvisualcpp)
-  # Important note: in order to support this mode, a compiler *must*
-  # always write the preprocessed file to stdout.
-  "$@" || exit $?
-
-  # Remove the call to Libtool.
-  if test "$libtool" = yes; then
-    while test "X$1" != 'X--mode=compile'; do
-      shift
-    done
-    shift
-  fi
-
-  IFS=" "
-  for arg
-  do
-    case "$arg" in
-    -o)
-      shift
-      ;;
-    $object)
-      shift
-      ;;
-    "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
-        set fnord "$@"
-        shift
-        shift
-        ;;
-    *)
-        set fnord "$@" "$arg"
-        shift
-        shift
-        ;;
-    esac
-  done
-  "$@" -E 2>/dev/null |
-  sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile"
-  rm -f "$depfile"
-  echo "$object : \\" > "$depfile"
-  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile"
-  echo "$tab" >> "$depfile"
-  sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile"
-  rm -f "$tmpdepfile"
-  ;;
-
-msvcmsys)
-  # This case exists only to let depend.m4 do its work.  It works by
-  # looking at the text of this script.  This case will never be run,
-  # since it is checked for above.
-  exit 1
-  ;;
-
-none)
-  exec "$@"
-  ;;
-
-*)
-  echo "Unknown depmode $depmode" 1>&2
-  exit 1
-  ;;
-esac
-
-exit 0
-
-# Local Variables:
-# mode: shell-script
-# sh-indentation: 2
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC0"
-# time-stamp-end: "; # UTC"
-# End:

+ 4 - 0
doc/meson.build

@@ -0,0 +1,4 @@
+mans += join_paths(meson.current_source_dir(), 'tang-show-keys.1')
+mans += join_paths(meson.current_source_dir(), 'tang.8')
+
+# vim:set ts=2 sw=2 et:

+ 3 - 3
doc/tang.8.adoc

@@ -19,7 +19,7 @@ client can automatically decrypt the data when it is able to contact the
 escrow server and fetch the key.
 
 However, escrow servers have many additional requirements, including
-authentication (so that clients can't get keys they aren't suppossed to have)
+authentication (so that clients can't get keys they aren't supposed to have)
 and transport encryption (so that attackers listening on the network can't
 eavesdrop on the keys in transit).
 
@@ -84,7 +84,7 @@ result in data loss. You have been warned.
 == HIGH PERFORMANCE
 
 The Tang protocol is extremely fast. However, in the default setup we
-use systemd socket activiation to start one process per connection. This
+use systemd socket activation to start one process per connection. This
 imposes a performance overhead. For most deployments, this is still probably
 quick enough, given that Tang is extremely lightweight. But for larger
 deployments, greater performance can be achieved.
@@ -101,7 +101,7 @@ module.
 
 Tang provides two methods for building a high availability deployment.
 
-1. Client redundency (recommended)
+1. Client redundancy (recommended)
 2. Key sharing with DNS round-robin
 
 While it may be tempting to share keys between Tang servers, this method

+ 0 - 501
install-sh

@@ -1,501 +0,0 @@
-#!/bin/sh
-# install - install a program, script, or datafile
-
-scriptversion=2016-01-11.22; # UTC
-
-# This originates from X11R5 (mit/util/scripts/install.sh), which was
-# later released in X11R6 (xc/config/util/install.sh) with the
-# following copyright and license.
-#
-# Copyright (C) 1994 X Consortium
-#
-# 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
-# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
-# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
-# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the name of the X Consortium shall not
-# be used in advertising or otherwise to promote the sale, use or other deal-
-# ings in this Software without prior written authorization from the X Consor-
-# tium.
-#
-#
-# FSF changes to this file are in the public domain.
-#
-# Calling this script install-sh is preferred over install.sh, to prevent
-# 'make' implicit rules from creating a file called install from it
-# when there is no Makefile.
-#
-# This script is compatible with the BSD install script, but was written
-# from scratch.
-
-tab='	'
-nl='
-'
-IFS=" $tab$nl"
-
-# Set DOITPROG to "echo" to test this script.
-
-doit=${DOITPROG-}
-doit_exec=${doit:-exec}
-
-# Put in absolute file names if you don't have them in your path;
-# or use environment vars.
-
-chgrpprog=${CHGRPPROG-chgrp}
-chmodprog=${CHMODPROG-chmod}
-chownprog=${CHOWNPROG-chown}
-cmpprog=${CMPPROG-cmp}
-cpprog=${CPPROG-cp}
-mkdirprog=${MKDIRPROG-mkdir}
-mvprog=${MVPROG-mv}
-rmprog=${RMPROG-rm}
-stripprog=${STRIPPROG-strip}
-
-posix_mkdir=
-
-# Desired mode of installed file.
-mode=0755
-
-chgrpcmd=
-chmodcmd=$chmodprog
-chowncmd=
-mvcmd=$mvprog
-rmcmd="$rmprog -f"
-stripcmd=
-
-src=
-dst=
-dir_arg=
-dst_arg=
-
-copy_on_change=false
-is_target_a_directory=possibly
-
-usage="\
-Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
-   or: $0 [OPTION]... SRCFILES... DIRECTORY
-   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
-   or: $0 [OPTION]... -d DIRECTORIES...
-
-In the 1st form, copy SRCFILE to DSTFILE.
-In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
-In the 4th, create DIRECTORIES.
-
-Options:
-     --help     display this help and exit.
-     --version  display version info and exit.
-
-  -c            (ignored)
-  -C            install only if different (preserve the last data modification time)
-  -d            create directories instead of installing files.
-  -g GROUP      $chgrpprog installed files to GROUP.
-  -m MODE       $chmodprog installed files to MODE.
-  -o USER       $chownprog installed files to USER.
-  -s            $stripprog installed files.
-  -t DIRECTORY  install into DIRECTORY.
-  -T            report an error if DSTFILE is a directory.
-
-Environment variables override the default commands:
-  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
-  RMPROG STRIPPROG
-"
-
-while test $# -ne 0; do
-  case $1 in
-    -c) ;;
-
-    -C) copy_on_change=true;;
-
-    -d) dir_arg=true;;
-
-    -g) chgrpcmd="$chgrpprog $2"
-        shift;;
-
-    --help) echo "$usage"; exit $?;;
-
-    -m) mode=$2
-        case $mode in
-          *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
-            echo "$0: invalid mode: $mode" >&2
-            exit 1;;
-        esac
-        shift;;
-
-    -o) chowncmd="$chownprog $2"
-        shift;;
-
-    -s) stripcmd=$stripprog;;
-
-    -t)
-        is_target_a_directory=always
-        dst_arg=$2
-        # Protect names problematic for 'test' and other utilities.
-        case $dst_arg in
-          -* | [=\(\)!]) dst_arg=./$dst_arg;;
-        esac
-        shift;;
-
-    -T) is_target_a_directory=never;;
-
-    --version) echo "$0 $scriptversion"; exit $?;;
-
-    --) shift
-        break;;
-
-    -*) echo "$0: invalid option: $1" >&2
-        exit 1;;
-
-    *)  break;;
-  esac
-  shift
-done
-
-# We allow the use of options -d and -T together, by making -d
-# take the precedence; this is for compatibility with GNU install.
-
-if test -n "$dir_arg"; then
-  if test -n "$dst_arg"; then
-    echo "$0: target directory not allowed when installing a directory." >&2
-    exit 1
-  fi
-fi
-
-if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
-  # When -d is used, all remaining arguments are directories to create.
-  # When -t is used, the destination is already specified.
-  # Otherwise, the last argument is the destination.  Remove it from $@.
-  for arg
-  do
-    if test -n "$dst_arg"; then
-      # $@ is not empty: it contains at least $arg.
-      set fnord "$@" "$dst_arg"
-      shift # fnord
-    fi
-    shift # arg
-    dst_arg=$arg
-    # Protect names problematic for 'test' and other utilities.
-    case $dst_arg in
-      -* | [=\(\)!]) dst_arg=./$dst_arg;;
-    esac
-  done
-fi
-
-if test $# -eq 0; then
-  if test -z "$dir_arg"; then
-    echo "$0: no input file specified." >&2
-    exit 1
-  fi
-  # It's OK to call 'install-sh -d' without argument.
-  # This can happen when creating conditional directories.
-  exit 0
-fi
-
-if test -z "$dir_arg"; then
-  if test $# -gt 1 || test "$is_target_a_directory" = always; then
-    if test ! -d "$dst_arg"; then
-      echo "$0: $dst_arg: Is not a directory." >&2
-      exit 1
-    fi
-  fi
-fi
-
-if test -z "$dir_arg"; then
-  do_exit='(exit $ret); exit $ret'
-  trap "ret=129; $do_exit" 1
-  trap "ret=130; $do_exit" 2
-  trap "ret=141; $do_exit" 13
-  trap "ret=143; $do_exit" 15
-
-  # Set umask so as not to create temps with too-generous modes.
-  # However, 'strip' requires both read and write access to temps.
-  case $mode in
-    # Optimize common cases.
-    *644) cp_umask=133;;
-    *755) cp_umask=22;;
-
-    *[0-7])
-      if test -z "$stripcmd"; then
-        u_plus_rw=
-      else
-        u_plus_rw='% 200'
-      fi
-      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
-    *)
-      if test -z "$stripcmd"; then
-        u_plus_rw=
-      else
-        u_plus_rw=,u+rw
-      fi
-      cp_umask=$mode$u_plus_rw;;
-  esac
-fi
-
-for src
-do
-  # Protect names problematic for 'test' and other utilities.
-  case $src in
-    -* | [=\(\)!]) src=./$src;;
-  esac
-
-  if test -n "$dir_arg"; then
-    dst=$src
-    dstdir=$dst
-    test -d "$dstdir"
-    dstdir_status=$?
-  else
-
-    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
-    # might cause directories to be created, which would be especially bad
-    # if $src (and thus $dsttmp) contains '*'.
-    if test ! -f "$src" && test ! -d "$src"; then
-      echo "$0: $src does not exist." >&2
-      exit 1
-    fi
-
-    if test -z "$dst_arg"; then
-      echo "$0: no destination specified." >&2
-      exit 1
-    fi
-    dst=$dst_arg
-
-    # If destination is a directory, append the input filename; won't work
-    # if double slashes aren't ignored.
-    if test -d "$dst"; then
-      if test "$is_target_a_directory" = never; then
-        echo "$0: $dst_arg: Is a directory" >&2
-        exit 1
-      fi
-      dstdir=$dst
-      dst=$dstdir/`basename "$src"`
-      dstdir_status=0
-    else
-      dstdir=`dirname "$dst"`
-      test -d "$dstdir"
-      dstdir_status=$?
-    fi
-  fi
-
-  obsolete_mkdir_used=false
-
-  if test $dstdir_status != 0; then
-    case $posix_mkdir in
-      '')
-        # Create intermediate dirs using mode 755 as modified by the umask.
-        # This is like FreeBSD 'install' as of 1997-10-28.
-        umask=`umask`
-        case $stripcmd.$umask in
-          # Optimize common cases.
-          *[2367][2367]) mkdir_umask=$umask;;
-          .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
-
-          *[0-7])
-            mkdir_umask=`expr $umask + 22 \
-              - $umask % 100 % 40 + $umask % 20 \
-              - $umask % 10 % 4 + $umask % 2
-            `;;
-          *) mkdir_umask=$umask,go-w;;
-        esac
-
-        # With -d, create the new directory with the user-specified mode.
-        # Otherwise, rely on $mkdir_umask.
-        if test -n "$dir_arg"; then
-          mkdir_mode=-m$mode
-        else
-          mkdir_mode=
-        fi
-
-        posix_mkdir=false
-        case $umask in
-          *[123567][0-7][0-7])
-            # POSIX mkdir -p sets u+wx bits regardless of umask, which
-            # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
-            ;;
-          *)
-            tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
-            trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
-
-            if (umask $mkdir_umask &&
-                exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
-            then
-              if test -z "$dir_arg" || {
-                   # Check for POSIX incompatibilities with -m.
-                   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
-                   # other-writable bit of parent directory when it shouldn't.
-                   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
-                   ls_ld_tmpdir=`ls -ld "$tmpdir"`
-                   case $ls_ld_tmpdir in
-                     d????-?r-*) different_mode=700;;
-                     d????-?--*) different_mode=755;;
-                     *) false;;
-                   esac &&
-                   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
-                     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
-                     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
-                   }
-                 }
-              then posix_mkdir=:
-              fi
-              rmdir "$tmpdir/d" "$tmpdir"
-            else
-              # Remove any dirs left behind by ancient mkdir implementations.
-              rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
-            fi
-            trap '' 0;;
-        esac;;
-    esac
-
-    if
-      $posix_mkdir && (
-        umask $mkdir_umask &&
-        $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
-      )
-    then :
-    else
-
-      # The umask is ridiculous, or mkdir does not conform to POSIX,
-      # or it failed possibly due to a race condition.  Create the
-      # directory the slow way, step by step, checking for races as we go.
-
-      case $dstdir in
-        /*) prefix='/';;
-        [-=\(\)!]*) prefix='./';;
-        *)  prefix='';;
-      esac
-
-      oIFS=$IFS
-      IFS=/
-      set -f
-      set fnord $dstdir
-      shift
-      set +f
-      IFS=$oIFS
-
-      prefixes=
-
-      for d
-      do
-        test X"$d" = X && continue
-
-        prefix=$prefix$d
-        if test -d "$prefix"; then
-          prefixes=
-        else
-          if $posix_mkdir; then
-            (umask=$mkdir_umask &&
-             $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
-            # Don't fail if two instances are running concurrently.
-            test -d "$prefix" || exit 1
-          else
-            case $prefix in
-              *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
-              *) qprefix=$prefix;;
-            esac
-            prefixes="$prefixes '$qprefix'"
-          fi
-        fi
-        prefix=$prefix/
-      done
-
-      if test -n "$prefixes"; then
-        # Don't fail if two instances are running concurrently.
-        (umask $mkdir_umask &&
-         eval "\$doit_exec \$mkdirprog $prefixes") ||
-          test -d "$dstdir" || exit 1
-        obsolete_mkdir_used=true
-      fi
-    fi
-  fi
-
-  if test -n "$dir_arg"; then
-    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
-    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
-    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
-      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
-  else
-
-    # Make a couple of temp file names in the proper directory.
-    dsttmp=$dstdir/_inst.$$_
-    rmtmp=$dstdir/_rm.$$_
-
-    # Trap to clean up those temp files at exit.
-    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
-
-    # Copy the file name to the temp name.
-    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
-
-    # and set any options; do chmod last to preserve setuid bits.
-    #
-    # If any of these fail, we abort the whole thing.  If we want to
-    # ignore errors from any of these, just make sure not to ignore
-    # errors from the above "$doit $cpprog $src $dsttmp" command.
-    #
-    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
-    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
-    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
-    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
-
-    # If -C, don't bother to copy if it wouldn't change the file.
-    if $copy_on_change &&
-       old=`LC_ALL=C ls -dlL "$dst"     2>/dev/null` &&
-       new=`LC_ALL=C ls -dlL "$dsttmp"  2>/dev/null` &&
-       set -f &&
-       set X $old && old=:$2:$4:$5:$6 &&
-       set X $new && new=:$2:$4:$5:$6 &&
-       set +f &&
-       test "$old" = "$new" &&
-       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
-    then
-      rm -f "$dsttmp"
-    else
-      # Rename the file to the real destination.
-      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
-
-      # The rename failed, perhaps because mv can't rename something else
-      # to itself, or perhaps because mv is so ancient that it does not
-      # support -f.
-      {
-        # Now remove or move aside any old file at destination location.
-        # We try this two ways since rm can't unlink itself on some
-        # systems and the destination file might be busy for other
-        # reasons.  In this case, the final cleanup might fail but the new
-        # file should still install successfully.
-        {
-          test ! -f "$dst" ||
-          $doit $rmcmd -f "$dst" 2>/dev/null ||
-          { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
-            { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
-          } ||
-          { echo "$0: cannot unlink or rename $dst" >&2
-            (exit 1); exit 1
-          }
-        } &&
-
-        # Now rename the file to the real destination.
-        $doit $mvcmd "$dsttmp" "$dst"
-      }
-    fi || exit 1
-
-    trap '' 0
-  fi
-done
-
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC0"
-# time-stamp-end: "; # UTC"
-# End:

+ 84 - 0
meson.build

@@ -0,0 +1,84 @@
+project('tang', 'c',
+  version: '8',
+  license: 'GPL3+',
+  default_options: [
+    'c_std=c99',
+    'prefix=/usr',
+    'sysconfdir=/etc',
+    'localstatedir=/var',
+    'warning_level=3',
+    'werror=true'
+  ]
+)
+
+libexecdir = join_paths(get_option('prefix'), get_option('libexecdir'))
+sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir'))
+bindir = join_paths(get_option('prefix'), get_option('bindir'))
+systemunitdir = join_paths(get_option('prefix'), 'lib/systemd/system')
+licensedir = join_paths(get_option('prefix'), 'share', 'licenses', meson.project_name())
+jwkdir = join_paths(get_option('localstatedir'), 'db', meson.project_name())
+
+data = configuration_data()
+data.set('libexecdir', libexecdir)
+data.set('sysconfdir', sysconfdir)
+data.set('systemunitdir', systemunitdir)
+data.set('jwkdir', jwkdir)
+
+add_project_arguments(
+  '-D_POSIX_C_SOURCE=200809L',
+  '-Wstrict-aliasing',
+  '-Wchar-subscripts',
+  '-Wformat',
+  '-Wformat-security',
+  '-Wmissing-declarations',
+  '-Wmissing-prototypes',
+  '-Wnested-externs',
+  '-Wpointer-arith',
+  '-Wshadow',
+  '-Wsign-compare',
+  '-Wstrict-prototypes',
+  '-Wtype-limits',
+  '-Wunused-function',
+  '-Wno-missing-field-initializers',
+  '-Wno-unused-parameter',
+  '-Wno-pedantic',
+  language: 'c'
+)
+
+jose = dependency('jose', version: '>=8')
+a2x = find_program('a2x', required: false)
+compiler = meson.get_compiler('c')
+if not compiler.has_header('http_parser.h')
+  error('http-parser devel files not found.')
+endif
+http_parser = compiler.find_library('http_parser')
+
+licenses = ['COPYING']
+libexecbins = []
+bins = []
+mans = []
+units = []
+
+subdir('doc')
+subdir('src')
+subdir('units')
+subdir('tests')
+
+install_data(libexecbins, install_dir: libexecdir)
+install_data(bins, install_dir: bindir)
+install_data(units, install_dir: systemunitdir)
+install_data(licenses, install_dir: licensedir)
+
+if a2x.found()
+  foreach m : mans
+    custom_target(m.split('/')[-1], input: m + '.adoc', output: m.split('/')[-1],
+      command: [a2x, '-f', 'manpage', '-D', meson.current_build_dir(), '@INPUT@'],
+      install_dir: join_paths(get_option('mandir'), 'man' + m.split('.')[-1]),
+      install: true
+    )
+  endforeach
+else
+  warning('Will not build man pages due to missing a2x (asciidoc) dependency!')
+endif
+
+# vim:set ts=2 sw=2 et:

+ 0 - 215
missing

@@ -1,215 +0,0 @@
-#!/bin/sh
-# Common wrapper for a few potentially missing GNU programs.
-
-scriptversion=2016-01-11.22; # UTC
-
-# Copyright (C) 1996-2017 Free Software Foundation, Inc.
-# Originally written by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
-
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-if test $# -eq 0; then
-  echo 1>&2 "Try '$0 --help' for more information"
-  exit 1
-fi
-
-case $1 in
-
-  --is-lightweight)
-    # Used by our autoconf macros to check whether the available missing
-    # script is modern enough.
-    exit 0
-    ;;
-
-  --run)
-    # Back-compat with the calling convention used by older automake.
-    shift
-    ;;
-
-  -h|--h|--he|--hel|--help)
-    echo "\
-$0 [OPTION]... PROGRAM [ARGUMENT]...
-
-Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due
-to PROGRAM being missing or too old.
-
-Options:
-  -h, --help      display this help and exit
-  -v, --version   output version information and exit
-
-Supported PROGRAM values:
-  aclocal   autoconf  autoheader   autom4te  automake  makeinfo
-  bison     yacc      flex         lex       help2man
-
-Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and
-'g' are ignored when checking the name.
-
-Send bug reports to <bug-automake@gnu.org>."
-    exit $?
-    ;;
-
-  -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
-    echo "missing $scriptversion (GNU Automake)"
-    exit $?
-    ;;
-
-  -*)
-    echo 1>&2 "$0: unknown '$1' option"
-    echo 1>&2 "Try '$0 --help' for more information"
-    exit 1
-    ;;
-
-esac
-
-# Run the given program, remember its exit status.
-"$@"; st=$?
-
-# If it succeeded, we are done.
-test $st -eq 0 && exit 0
-
-# Also exit now if we it failed (or wasn't found), and '--version' was
-# passed; such an option is passed most likely to detect whether the
-# program is present and works.
-case $2 in --version|--help) exit $st;; esac
-
-# Exit code 63 means version mismatch.  This often happens when the user
-# tries to use an ancient version of a tool on a file that requires a
-# minimum version.
-if test $st -eq 63; then
-  msg="probably too old"
-elif test $st -eq 127; then
-  # Program was missing.
-  msg="missing on your system"
-else
-  # Program was found and executed, but failed.  Give up.
-  exit $st
-fi
-
-perl_URL=http://www.perl.org/
-flex_URL=http://flex.sourceforge.net/
-gnu_software_URL=http://www.gnu.org/software
-
-program_details ()
-{
-  case $1 in
-    aclocal|automake)
-      echo "The '$1' program is part of the GNU Automake package:"
-      echo "<$gnu_software_URL/automake>"
-      echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:"
-      echo "<$gnu_software_URL/autoconf>"
-      echo "<$gnu_software_URL/m4/>"
-      echo "<$perl_URL>"
-      ;;
-    autoconf|autom4te|autoheader)
-      echo "The '$1' program is part of the GNU Autoconf package:"
-      echo "<$gnu_software_URL/autoconf/>"
-      echo "It also requires GNU m4 and Perl in order to run:"
-      echo "<$gnu_software_URL/m4/>"
-      echo "<$perl_URL>"
-      ;;
-  esac
-}
-
-give_advice ()
-{
-  # Normalize program name to check for.
-  normalized_program=`echo "$1" | sed '
-    s/^gnu-//; t
-    s/^gnu//; t
-    s/^g//; t'`
-
-  printf '%s\n' "'$1' is $msg."
-
-  configure_deps="'configure.ac' or m4 files included by 'configure.ac'"
-  case $normalized_program in
-    autoconf*)
-      echo "You should only need it if you modified 'configure.ac',"
-      echo "or m4 files included by it."
-      program_details 'autoconf'
-      ;;
-    autoheader*)
-      echo "You should only need it if you modified 'acconfig.h' or"
-      echo "$configure_deps."
-      program_details 'autoheader'
-      ;;
-    automake*)
-      echo "You should only need it if you modified 'Makefile.am' or"
-      echo "$configure_deps."
-      program_details 'automake'
-      ;;
-    aclocal*)
-      echo "You should only need it if you modified 'acinclude.m4' or"
-      echo "$configure_deps."
-      program_details 'aclocal'
-      ;;
-   autom4te*)
-      echo "You might have modified some maintainer files that require"
-      echo "the 'autom4te' program to be rebuilt."
-      program_details 'autom4te'
-      ;;
-    bison*|yacc*)
-      echo "You should only need it if you modified a '.y' file."
-      echo "You may want to install the GNU Bison package:"
-      echo "<$gnu_software_URL/bison/>"
-      ;;
-    lex*|flex*)
-      echo "You should only need it if you modified a '.l' file."
-      echo "You may want to install the Fast Lexical Analyzer package:"
-      echo "<$flex_URL>"
-      ;;
-    help2man*)
-      echo "You should only need it if you modified a dependency" \
-           "of a man page."
-      echo "You may want to install the GNU Help2man package:"
-      echo "<$gnu_software_URL/help2man/>"
-    ;;
-    makeinfo*)
-      echo "You should only need it if you modified a '.texi' file, or"
-      echo "any other file indirectly affecting the aspect of the manual."
-      echo "You might want to install the Texinfo package:"
-      echo "<$gnu_software_URL/texinfo/>"
-      echo "The spurious makeinfo call might also be the consequence of"
-      echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might"
-      echo "want to install GNU make:"
-      echo "<$gnu_software_URL/make/>"
-      ;;
-    *)
-      echo "You might have modified some files without having the proper"
-      echo "tools for further handling them.  Check the 'README' file, it"
-      echo "often tells you about the needed prerequisites for installing"
-      echo "this package.  You may also peek at any GNU archive site, in"
-      echo "case some other package contains this missing '$1' program."
-      ;;
-  esac
-}
-
-give_advice "$1" | sed -e '1s/^/WARNING: /' \
-                       -e '2,$s/^/         /' >&2
-
-# Propagate the correct exit status (expected to be 127 for a program
-# not found, 63 for a program that failed due to version mismatch).
-exit $st
-
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC0"
-# time-stamp-end: "; # UTC"
-# End:

+ 456 - 0
src/keys.c

@@ -0,0 +1,456 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2020 Red Hat, Inc.
+ * Author: Sergio Correia <scorreia@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <stdio.h>
+
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include <jose/jws.h>
+
+#include "keys.h"
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+static const char**
+supported_hashes(void)
+{
+    /* TODO: check if jose has a way to export the hash algorithms it
+     * supports. */
+    static const char* hashes[] = {"S1", "S224", "S256", "S384", "S512", NULL};
+    return hashes;
+}
+
+static int
+is_hash(const char* alg)
+{
+    if (!alg) {
+        return 0;
+    }
+
+    const char** algs = supported_hashes();
+    for (size_t a = 0; algs[a]; a++) {
+        if (strcmp(alg, algs[a]) == 0) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static json_t*
+jwk_generate(const char* alg)
+{
+    json_auto_t* jalg = json_pack("{s:s}", "alg", alg);
+    if (!jalg) {
+        fprintf(stderr, "Error packing JSON with alg %s\n", alg);
+        return NULL;
+    }
+
+    if (!jose_jwk_gen(NULL, jalg)) {
+        fprintf(stderr, "Error generating JWK with alg %s\n", alg);
+        return NULL;
+    }
+
+    return json_incref(jalg);
+}
+
+static char*
+jwk_thumbprint(const json_t* jwk, const char* alg)
+{
+    size_t elen = 0;
+    size_t dlen = 0;
+
+    if (!jwk) {
+        fprintf(stderr, "Invalid JWK\n");
+        return NULL;
+    }
+
+    if (!alg || !is_hash(alg)) {
+        fprintf(stderr, "Invalid hash algorithm (%s)\n", alg);
+        return NULL;
+    }
+
+    dlen = jose_jwk_thp_buf(NULL, NULL, alg, NULL, 0);
+    if (dlen == SIZE_MAX) {
+        fprintf(stderr, "Error determining hash size for %s\n", alg);
+        return NULL;
+    }
+
+    elen = jose_b64_enc_buf(NULL, dlen, NULL, 0);
+    if (elen == SIZE_MAX) {
+        fprintf(stderr, "Error determining encoded size for %s\n", alg);
+        return NULL;
+    }
+
+    uint8_t dec[dlen];
+    char enc[elen];
+
+    if (!jose_jwk_thp_buf(NULL, jwk, alg, dec, sizeof(dec))) {
+        fprintf(stderr, "Error making thumbprint\n");
+        return NULL;
+    }
+
+    if (jose_b64_enc_buf(dec, dlen, enc, sizeof(enc)) != elen) {
+        fprintf(stderr, "Error encoding data Base64\n");
+        return NULL;
+    }
+
+    return strndup(enc, elen);
+}
+
+void
+free_tang_keys_info(struct tang_keys_info* tki)
+{
+    if (!tki) {
+        return;
+    }
+
+    json_t* to_free[] = {tki->m_keys, tki->m_rotated_keys,
+                         tki->m_payload, tki->m_sign
+    };
+    size_t len = sizeof(to_free) / sizeof(to_free[0]);
+
+    for (size_t i = 0; i < len; i++) {
+        if (to_free[i] == NULL) {
+            continue;
+        }
+        json_decref(to_free[i]);
+    }
+    free(tki);
+}
+
+void
+cleanup_tang_keys_info(struct tang_keys_info** tki)
+{
+    if (!tki || !*tki) {
+        return;
+    }
+    free_tang_keys_info(*tki);
+    *tki = NULL;
+}
+
+static struct tang_keys_info*
+new_tang_keys_info(void)
+{
+    struct tang_keys_info* tki = calloc(1, sizeof(*tki));
+    if (!tki) {
+        return NULL;
+    }
+
+    tki->m_keys = json_array();
+    tki->m_rotated_keys = json_array();
+    tki->m_payload = json_array();
+    tki->m_sign = json_array();
+
+    if (!tki->m_keys || !tki->m_rotated_keys ||
+        !tki->m_payload || !tki->m_sign) {
+        free_tang_keys_info(tki);
+        return NULL;
+    }
+    tki->m_keys_count = 0;
+    return tki;
+}
+
+static int
+jwk_valid_for(const json_t* jwk, const char* use)
+{
+    if (!jwk || !use) {
+        return 0;
+    }
+    return jose_jwk_prm(NULL, jwk, false, use);
+}
+
+static int
+jwk_valid_for_signing_and_verifying(const json_t* jwk)
+{
+    const char* uses[] = {"sign", "verify", NULL};
+    int ret = 1;
+    for (int i = 0; uses[i]; i++) {
+        if (!jwk_valid_for(jwk, uses[i])) {
+            ret = 0;
+            break;
+        }
+    }
+    return ret;
+}
+
+static int
+jwk_valid_for_signing(const json_t* jwk)
+{
+    return jwk_valid_for(jwk, "sign");
+}
+
+static int
+jwk_valid_for_deriving_keys(const json_t* jwk)
+{
+    return jwk_valid_for(jwk, "deriveKey");
+}
+
+static void
+cleanup_str(char** str)
+{
+    if (!str || !*str) {
+        return;
+    }
+    free(*str);
+    *str = NULL;
+}
+
+static json_t*
+jwk_sign(const json_t* to_sign, const json_t* sig_keys)
+{
+    if (!sig_keys || !json_is_array(sig_keys) || !json_is_array(to_sign)) {
+        return NULL;
+    }
+
+    json_auto_t* to_sign_copy = json_deep_copy(to_sign);
+    if (!jose_jwk_pub(NULL, to_sign_copy)) {
+        fprintf(stderr, "Error removing private material from data to sign\n");
+        return NULL;
+    }
+
+    json_auto_t* payload = json_pack("{s:O}", "keys", to_sign_copy);
+    json_auto_t* sig_template = json_pack("{s:{s:s}}",
+                                          "protected", "cty", "jwk-set+json");
+
+    /* Use the template with the signing keys. */
+    json_auto_t* sig_template_arr = json_array();
+    size_t arr_size = json_array_size(sig_keys);
+    for (size_t i = 0; i < arr_size; i++) {
+        if (json_array_append(sig_template_arr, sig_template) == -1) {
+            fprintf(stderr, "Unable to append sig template to array\n");
+            return NULL;
+        }
+    }
+
+    __attribute__ ((__cleanup__(cleanup_str))) char* data_to_sign = json_dumps(payload, 0);
+    json_auto_t* jws = json_pack("{s:o}", "payload",
+                                 jose_b64_enc(data_to_sign, strlen(data_to_sign)));
+
+    if (!jose_jws_sig(NULL, jws, sig_template_arr, sig_keys)) {
+        fprintf(stderr, "Error trying to jose_jws_sign\n");
+        return NULL;
+    }
+    return json_incref(jws);
+}
+
+static json_t*
+find_by_thp(struct tang_keys_info* tki, const char* target)
+{
+    if (!tki) {
+        return NULL;
+    }
+
+    json_auto_t* keys = json_deep_copy(tki->m_keys);
+    json_array_extend(keys, tki->m_rotated_keys);
+
+    size_t idx;
+    json_t* jwk;
+    const char** hashes = supported_hashes();
+    json_array_foreach(keys, idx, jwk) {
+        for (int i = 0; hashes[i]; i++) {
+            __attribute__ ((__cleanup__(cleanup_str))) char* thumbprint = jwk_thumbprint(jwk, hashes[i]);
+            if (strcmp(thumbprint, target) != 0) {
+                continue;
+            }
+
+            if (jwk_valid_for_deriving_keys(jwk)) {
+                return json_incref(jwk);
+            } else if (jwk_valid_for_signing(jwk)) {
+                json_auto_t* sign = json_deep_copy(tki->m_sign);
+                if (json_array_append(sign, jwk) == -1) {
+                    return NULL;
+                }
+                json_auto_t* jws = jwk_sign(tki->m_payload, sign);
+                if (!jws) {
+                    return NULL;
+                }
+                return json_incref(jws);
+            }
+        }
+    }
+    return NULL;
+}
+
+static int
+prepare_payload_and_sign(struct tang_keys_info* tki)
+{
+    if (!tki) {
+        return 0;
+    }
+
+    size_t idx;
+    json_t* jwk;
+    json_array_foreach(tki->m_keys, idx, jwk) {
+        if (jwk_valid_for_signing_and_verifying(jwk)) {
+            if (json_array_append(tki->m_sign, jwk) == -1) {
+                continue;
+            }
+            if (json_array_append(tki->m_payload, jwk) == -1) {
+                continue;
+            }
+        } else if (jwk_valid_for_deriving_keys(jwk)) {
+            if (json_array_append(tki->m_payload, jwk) == -1) {
+                continue;
+            }
+        }
+    }
+    if (json_array_size(tki->m_sign) == 0 || json_array_size(tki->m_payload) == 0) {
+        return 0;
+    }
+    return 1;
+}
+
+static int
+create_new_keys(const char* jwkdir)
+{
+    const char** hashes = supported_hashes();
+    const char* alg[] = {"ES512", "ECMR", NULL};
+    char path[PATH_MAX];
+    for (int i = 0; alg[i] != NULL; i++) {
+        json_auto_t* jwk = jwk_generate(alg[i]);
+        if (!jwk) {
+            return 0;
+        }
+        __attribute__ ((__cleanup__(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[0]);
+        if (!thp) {
+            return 0;
+        }
+        if (snprintf(path, PATH_MAX, "%s/%s.jwk", jwkdir, thp) < 0) {
+            fprintf(stderr, "Unable to prepare variable with file full path (%s)\n", thp);
+            return 0;
+        }
+        path[sizeof(path) - 1] = '\0';
+        if (json_dump_file(jwk, path, 0) == -1) {
+            fprintf(stderr, "Error saving JWK to file (%s)\n", path);
+            return 0;
+        }
+    }
+    return 1;
+}
+
+static struct tang_keys_info*
+load_keys(const char* jwkdir)
+{
+    struct tang_keys_info* tki = new_tang_keys_info();
+    if (!tki) {
+        return NULL;
+    }
+
+    struct dirent* d;
+    DIR* dir = opendir(jwkdir);
+    if (!dir) {
+        free_tang_keys_info(tki);
+        return NULL;
+    }
+
+    char filepath[PATH_MAX];
+    const char* pattern = ".jwk";
+    while ((d = readdir(dir)) != NULL) {
+        if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
+            continue;
+        }
+
+        char* dot = strrchr(d->d_name, '.');
+        if (!dot) {
+            continue;
+        }
+
+        if (strcmp(dot, pattern) == 0) {
+            /* Found a file with .jwk extension. */
+            if (snprintf(filepath, PATH_MAX, "%s/%s", jwkdir, d->d_name) < 0) {
+                fprintf(stderr, "Unable to prepare variable with file full path (%s); skipping\n", d->d_name);
+                continue;
+            }
+            filepath[sizeof(filepath) - 1] = '\0';
+            json_auto_t* json = json_load_file(filepath, 0, NULL);
+            if (!json) {
+                fprintf(stderr, "Invalid JSON file (%s); skipping\n", filepath);
+                continue;
+            }
+
+            json_t* arr = tki->m_keys;
+            if (d->d_name[0] == '.') {
+                arr = tki->m_rotated_keys;
+            }
+            if (json_array_append(arr, json) == -1) {
+                fprintf(stderr, "Unable to append JSON (%s) to array; skipping\n", d->d_name);
+                continue;
+            }
+            tki->m_keys_count++;
+        }
+    }
+    closedir(dir);
+    return tki;
+}
+
+struct tang_keys_info*
+read_keys(const char* jwkdir)
+{
+    struct tang_keys_info* tki = load_keys(jwkdir);
+    if (!tki) {
+        return NULL;
+    }
+
+    if (tki->m_keys_count == 0) {
+        /* Let's attempt to create a new pair of keys. */
+        free_tang_keys_info(tki);
+        if (!create_new_keys(jwkdir)) {
+            return NULL;
+        }
+        tki = load_keys(jwkdir);
+    }
+
+    if (!prepare_payload_and_sign(tki)) {
+        free_tang_keys_info(tki);
+        return NULL;
+    }
+    return tki;
+}
+
+json_t*
+find_jws(struct tang_keys_info* tki, const char* thp)
+{
+    if (!tki) {
+        return NULL;
+    }
+
+    if (thp == NULL) {
+        /* Default advertisement. */
+        json_auto_t* jws = jwk_sign(tki->m_payload, tki->m_sign);
+        if (!jws) {
+            return NULL;
+        }
+        return json_incref(jws);
+    }
+    return find_by_thp(tki, thp);
+}
+
+json_t*
+find_jwk(struct tang_keys_info* tki, const char* thp)
+{
+    if (!tki || !thp) {
+        return NULL;
+    }
+    return find_by_thp(tki, thp);
+}

+ 45 - 0
src/keys.h

@@ -0,0 +1,45 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2020 Red Hat, Inc.
+ * Author: Sergio Correia <scorreia@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <jansson.h>
+#include <stddef.h>
+
+struct tang_keys_info {
+    /* Arrays. */
+    json_t* m_keys;               /* Regular keys. */
+    json_t* m_rotated_keys;       /* Rotated keys. */
+
+    json_t* m_payload;            /* Payload made of regular keys capable of
+                                   * either signing+verifying or deriving new
+                                   * keys. */
+
+    json_t* m_sign;               /* Set of signing keys made from regular
+                                     keys. */
+
+    size_t m_keys_count;          /* Number of keys (regular + rotated). */
+
+};
+
+void cleanup_tang_keys_info(struct tang_keys_info**);
+void free_tang_keys_info(struct tang_keys_info*);
+struct tang_keys_info* read_keys(const char* /* jwkdir */);
+json_t* find_jws(struct tang_keys_info* /* tki */, const char* /* thp */);
+json_t* find_jwk(struct tang_keys_info* /* tki */, const char* /* thp */);

+ 13 - 0
src/meson.build

@@ -0,0 +1,13 @@
+tangd = executable('tangd',
+  'http.c',
+  'keys.c',
+  'tangd.c',
+  dependencies: [jose, http_parser],
+  install: true,
+  install_dir: libexecdir
+)
+
+bins += join_paths(meson.current_source_dir(), 'tang-show-keys')
+libexecbins += join_paths(meson.current_source_dir(), 'tangd-keygen')
+
+# vim:set ts=2 sw=2 et:

+ 0 - 83
src/tangd-update

@@ -1,83 +0,0 @@
-#!/bin/bash
-# vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:
-#
-# Copyright (c) 2016 Red Hat, Inc.
-# Author: Nathaniel McCallum <npmccallum@redhat.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-
-TMP='{"protected":{"cty":"jwk-set+json"}}'
-
-trap 'exit' ERR
-
-shopt -s nullglob
-
-HASHES=`jose alg -k hash`
-
-if [ $# -ne 2 ] || [ ! -d "$1" ]; then
-    echo "Usage: $0 <jwkdir> <cachedir>" >&2
-    exit 1
-fi
-
-[ ! -d "$2" ] && mkdir -p -m 0700 "$2"
-
-src=`realpath "$1"`
-dst=`realpath "$2"`
-
-payl=()
-sign=()
-
-for jwk in $src/*.jwk; do
-    if jose jwk use -i "$jwk" -r -u sign -u verify; then
-        sign+=("-s" "$TMP" "-k" "$jwk")
-        payl+=("-i" "$jwk")
-    elif jose jwk use -i "$jwk" -r -u deriveKey; then
-        payl+=("-i" "$jwk")
-    else
-        echo "Skipping invalid key: $jwk" >&2
-    fi
-done
-
-if [ ${#sign[@]} -gt 0 ]; then
-    jose jwk pub -s "${payl[@]}" \
-        | jose jws sig -I- "${sign[@]}" -o "$dst/.default.jws"
-    mv -f "$dst/.default.jws" "$dst/default.jws"
-    new=default.jws
-fi
-
-shopt -s dotglob
-
-for jwk in $src/*.jwk; do
-    for hsh in $HASHES; do
-        thp=`jose jwk thp -i "$jwk" -a $hsh`
-
-        if jose jwk use -i "$jwk" -r -u deriveKey; then
-            ln -sf "$jwk" "$dst/.$thp.jwk"
-            mv -f "$dst/.$thp.jwk" "$dst/$thp.jwk"
-            new="$new\n$thp.jwk"
-        elif jose jwk use -i "$jwk" -r -u sign; then
-            keys=("${sign[@]}" -s "$TMP" -k "$jwk")
-            jose jwk pub -s "${payl[@]}" \
-                | jose jws sig -I- "${keys[@]}" -o "$dst/.$thp.jws"
-            mv -f "$dst/.$thp.jws" "$dst/$thp.jws"
-            new="$new\n$thp.jws"
-        fi
-    done
-done
-
-for f in "$dst"/*; do
-    b=`basename "$f"`
-    echo -e "$new" | grep -q "^$b\$" || rm -f "$f"
-done

+ 22 - 32
src/tangd.c

@@ -28,6 +28,7 @@
 #include <unistd.h>
 
 #include <jose/jose.h>
+#include "keys.h"
 
 static void
 str_cleanup(char **str)
@@ -36,23 +37,20 @@ str_cleanup(char **str)
         free(*str);
 }
 
-static void
-FILE_cleanup(FILE **file)
-{
-    if (file && *file)
-        fclose(*file);
-}
-
 static int
 adv(enum http_method method, const char *path, const char *body,
     regmatch_t matches[], void *misc)
 {
-    __attribute__((cleanup(FILE_cleanup))) FILE *file = NULL;
     __attribute__((cleanup(str_cleanup))) char *adv = NULL;
     __attribute__((cleanup(str_cleanup))) char *thp = NULL;
-    char filename[PATH_MAX] = {};
-    const char *cachedir = misc;
-    struct stat st = {};
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL;
+    json_auto_t *jws = NULL;
+    const char *jwkdir = misc;
+
+    tki = read_keys(jwkdir);
+    if (!tki || tki->m_keys_count == 0) {
+        return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
+    }
 
     if (matches[1].rm_so < matches[1].rm_eo) {
         size_t size = matches[1].rm_eo - matches[1].rm_so;
@@ -61,24 +59,15 @@ adv(enum http_method method, const char *path, const char *body,
             return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
     }
 
-    if (snprintf(filename, sizeof(filename),
-                 "%s/%s.jws", cachedir, thp ? thp : "default") < 0)
-        return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
-
-    file = fopen(filename, "r");
-    if (!file)
+    jws = find_jws(tki, thp);
+    if (!jws) {
         return http_reply(HTTP_STATUS_NOT_FOUND, NULL);
+    }
 
-    if (fstat(fileno(file), &st) != 0)
-        return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
-
-    adv = calloc(st.st_size + 1, 1);
+    adv = json_dumps(jws, 0);
     if (!adv)
         return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
 
-    if (fread(adv, st.st_size, 1, file) != 1)
-        return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
-
     return http_reply(HTTP_STATUS_OK,
                       "Content-Type: application/jose+json\r\n"
                       "Content-Length: %zu\r\n"
@@ -91,9 +80,9 @@ rec(enum http_method method, const char *path, const char *body,
 {
     __attribute__((cleanup(str_cleanup))) char *enc = NULL;
     __attribute__((cleanup(str_cleanup))) char *thp = NULL;
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info *tki = NULL;
     size_t size = matches[1].rm_eo - matches[1].rm_so;
-    char filename[PATH_MAX] = {};
-    const char *cachedir = misc;
+    const char *jwkdir = misc;
     json_auto_t *jwk = NULL;
     json_auto_t *req = NULL;
     json_auto_t *rep = NULL;
@@ -124,15 +113,16 @@ rec(enum http_method method, const char *path, const char *body,
     /*
      * Parse and validate the server-side JWK
      */
+    tki = read_keys(jwkdir);
+    if (!tki || tki->m_keys_count == 0) {
+        return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
+    }
 
     thp = strndup(&path[matches[1].rm_so], size);
     if (!thp)
         return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
 
-    if (snprintf(filename, sizeof(filename), "%s/%s.jwk", cachedir, thp) < 0)
-        return http_reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
-
-    jwk = json_load_file(filename, 0, NULL);
+    jwk = find_jwk(tki, thp);
     if (!jwk)
         return http_reply(HTTP_STATUS_NOT_FOUND, NULL);
 
@@ -188,7 +178,7 @@ main(int argc, char *argv[])
     http_parser_init(&parser, HTTP_REQUEST);
 
     if (argc != 2) {
-        fprintf(stderr, "Usage: %s <cachedir>\n", argv[0]);
+        fprintf(stderr, "Usage: %s <jwkdir>\n", argv[0]);
         return EXIT_FAILURE;
     }
 
@@ -215,7 +205,7 @@ main(int argc, char *argv[])
         if (parser.http_errno != 0) {
             fprintf(stderr, "HTTP Parsing Error: %s\n",
                     http_errno_description(parser.http_errno));
-            return EXIT_FAILURE;
+            return EXIT_SUCCESS;
         }
 
         memmove(req, &req[r], rcvd - r);

+ 0 - 148
test-driver

@@ -1,148 +0,0 @@
-#!/bin/sh
-# test-driver - basic testsuite driver script.
-
-scriptversion=2016-01-11.22; # UTC
-
-# Copyright (C) 2011-2017 Free Software Foundation, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# As a special exception to the GNU General Public License, if you
-# distribute this file as part of a program that contains a
-# configuration script generated by Autoconf, you may include it under
-# the same distribution terms that you use for the rest of that program.
-
-# This file is maintained in Automake, please report
-# bugs to <bug-automake@gnu.org> or send patches to
-# <automake-patches@gnu.org>.
-
-# Make unconditional expansion of undefined variables an error.  This
-# helps a lot in preventing typo-related bugs.
-set -u
-
-usage_error ()
-{
-  echo "$0: $*" >&2
-  print_usage >&2
-  exit 2
-}
-
-print_usage ()
-{
-  cat <<END
-Usage:
-  test-driver --test-name=NAME --log-file=PATH --trs-file=PATH
-              [--expect-failure={yes|no}] [--color-tests={yes|no}]
-              [--enable-hard-errors={yes|no}] [--]
-              TEST-SCRIPT [TEST-SCRIPT-ARGUMENTS]
-The '--test-name', '--log-file' and '--trs-file' options are mandatory.
-END
-}
-
-test_name= # Used for reporting.
-log_file=  # Where to save the output of the test script.
-trs_file=  # Where to save the metadata of the test run.
-expect_failure=no
-color_tests=no
-enable_hard_errors=yes
-while test $# -gt 0; do
-  case $1 in
-  --help) print_usage; exit $?;;
-  --version) echo "test-driver $scriptversion"; exit $?;;
-  --test-name) test_name=$2; shift;;
-  --log-file) log_file=$2; shift;;
-  --trs-file) trs_file=$2; shift;;
-  --color-tests) color_tests=$2; shift;;
-  --expect-failure) expect_failure=$2; shift;;
-  --enable-hard-errors) enable_hard_errors=$2; shift;;
-  --) shift; break;;
-  -*) usage_error "invalid option: '$1'";;
-   *) break;;
-  esac
-  shift
-done
-
-missing_opts=
-test x"$test_name" = x && missing_opts="$missing_opts --test-name"
-test x"$log_file"  = x && missing_opts="$missing_opts --log-file"
-test x"$trs_file"  = x && missing_opts="$missing_opts --trs-file"
-if test x"$missing_opts" != x; then
-  usage_error "the following mandatory options are missing:$missing_opts"
-fi
-
-if test $# -eq 0; then
-  usage_error "missing argument"
-fi
-
-if test $color_tests = yes; then
-  # Keep this in sync with 'lib/am/check.am:$(am__tty_colors)'.
-  red='' # Red.
-  grn='' # Green.
-  lgn='' # Light green.
-  blu='' # Blue.
-  mgn='' # Magenta.
-  std=''     # No color.
-else
-  red= grn= lgn= blu= mgn= std=
-fi
-
-do_exit='rm -f $log_file $trs_file; (exit $st); exit $st'
-trap "st=129; $do_exit" 1
-trap "st=130; $do_exit" 2
-trap "st=141; $do_exit" 13
-trap "st=143; $do_exit" 15
-
-# Test script is run here.
-"$@" >$log_file 2>&1
-estatus=$?
-
-if test $enable_hard_errors = no && test $estatus -eq 99; then
-  tweaked_estatus=1
-else
-  tweaked_estatus=$estatus
-fi
-
-case $tweaked_estatus:$expect_failure in
-  0:yes) col=$red res=XPASS recheck=yes gcopy=yes;;
-  0:*)   col=$grn res=PASS  recheck=no  gcopy=no;;
-  77:*)  col=$blu res=SKIP  recheck=no  gcopy=yes;;
-  99:*)  col=$mgn res=ERROR recheck=yes gcopy=yes;;
-  *:yes) col=$lgn res=XFAIL recheck=no  gcopy=yes;;
-  *:*)   col=$red res=FAIL  recheck=yes gcopy=yes;;
-esac
-
-# Report the test outcome and exit status in the logs, so that one can
-# know whether the test passed or failed simply by looking at the '.log'
-# file, without the need of also peaking into the corresponding '.trs'
-# file (automake bug#11814).
-echo "$res $test_name (exit status: $estatus)" >>$log_file
-
-# Report outcome to console.
-echo "${col}${res}${std}: $test_name"
-
-# Register the test result, and other relevant metadata.
-echo ":test-result: $res" > $trs_file
-echo ":global-test-result: $res" >> $trs_file
-echo ":recheck: $recheck" >> $trs_file
-echo ":copy-in-global-log: $gcopy" >> $trs_file
-
-# Local Variables:
-# mode: shell-script
-# sh-indentation: 2
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-time-zone: "UTC0"
-# time-stamp-end: "; # UTC"
-# End:

+ 2 - 4
tests/adv

@@ -36,15 +36,13 @@ trap 'exit' ERR
 
 export TMP=`mktemp -d`
 mkdir -p $TMP/db
-mkdir -p $TMP/cache
 
 tangd-keygen $TMP/db sig exc
 jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.sig.jwk
 jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.oth.jwk
-tangd-update $TMP/db $TMP/cache
 
 export PORT=`shuf -i 1024-65536 -n 1`
-$SD_ACTIVATE -l "127.0.0.1:$PORT" -a $VALGRIND tangd $TMP/cache &
+$SD_ACTIVATE -l "127.0.0.1:$PORT" -a $VALGRIND tangd $TMP/db &
 export PID=$!
 sleep 0.5
 
@@ -84,4 +82,4 @@ fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` \
                -g 0 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU \
                -g 1 -Og protected -SyOg cty -Sq "jwk-set+json" -EUUUUU
 
-test $(tang-show-keys $PORT) == $(jose jwk thp -i $TMP/db/sig.jwk)
+test "$(tang-show-keys $PORT)" == "$(jose jwk thp -i $TMP/db/sig.jwk)"

+ 1 - 0
tests/keys/-bWkGaJi0Zdvxaj4DCp28umLcRA.jwk

@@ -0,0 +1 @@
+{"alg":"ECMR","crv":"P-521","d":"AQ3u1g0L__GIGSJRX1LtjSArwJxxQz0kWXIi-X4PqwoheoeY57cw36pmWmyVsn43jDEZ6SAsiNeIw9sHDkFZe1VV","key_ops":["deriveKey"],"kty":"EC","x":"AcfowrKEKteg_jKX1CiR2RQfbUGJ73KXlcl8AgIDAgN7R6yNKWpKhZNBmV2tAxxMCQcIksqQl17UXwemvH2j2fem","y":"ACrb-y4ZhLIGX-41QYgJhniiZ85qkjILbkVUcC8gBYxOAnKWIpMGLsjrT3AYhM6jk6puwnNYbEM28s2caAEogUcA"}

+ 1 - 0
tests/keys/.r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk

@@ -0,0 +1 @@
+{"alg":"ES512","crv":"P-521","d":"Af5SfYh_4LmBDF9dCGDQRDA52yzqnzDeo-GZU05hpnLr6bsIBTFpc8rdcCm95mhZ59ngC-6WNtmAF_nLCDcHKg0A","key_ops":["sign","verify"],"kty":"EC","x":"AUxS3DXdoONUB-6-nyzdd3V42iD7obGTJ1m40t3V6jzXfABWp_gtTidwiKyDJQXxhEzMSToo-V0RGq6Qz2XTOgPe","y":"AFrkJfkLGJz_2v-k3-wdydckVcBXql2NR66HaF0U9NlcfGLezQau7XihArm4GE3-sHoQLsRa-HvYET9zyd9Syh5y"}

+ 1 - 0
tests/keys/.uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk

@@ -0,0 +1 @@
+{"alg":"ECMR","crv":"P-521","d":"Acuk66sHvSS5TN-p0XhwHhVKyifYr-ecur-7zfgQZMzSq9SeBJjuX6Ttav-7AbnVvXU_S55BgtGL5iymXGuMguCp","key_ops":["deriveKey"],"kty":"EC","x":"AEz9EwBOIrLeV4in6M1oWVnOWVR7ubkFB0R0-AwyIL7u5-7G3u2tvPIJRQY-l1Wttn7Ar4DhflcMhnb3rk5hT5yh","y":"AZt35wqOhNsnEi-GLAgyCaiW_c6h6Zyo4xwjuvXzmQMDwh9MtdaUigFuBOTlfRj1uri_YBqdpI09nYrqqgx97Ca6"}

+ 1 - 0
tests/keys/another-bad-file

@@ -0,0 +1 @@
+foobar

+ 0 - 0
tests/keys/empty.jwk


+ 1 - 0
tests/keys/invalid.jwk

@@ -0,0 +1 @@
+foo

+ 1 - 0
tests/keys/qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk

@@ -0,0 +1 @@
+{"alg":"ES512","crv":"P-521","d":"AUQpuWtoSqURl0OuWQ-I9i4X1F3sDak0Hbf9Ixj7uwjA20A0ABJdCHbai1Ai0t3yoxWKPYi6t2XjjeRzHIKyhXbf","key_ops":["sign","verify"],"kty":"EC","x":"ACbYnvh1EtLePkM5jCtuxLpMroOUNRfv-wQdXgT5AQ5bhSLv6wkrBzh1rwymo-fmCNWzrcTflzqWf-wXAd00lokM","y":"AEaDByxXfbee4TlPXgPRg5S4MVOqgObjX6_JJkySTudSfOygcx7dujsf32dOEI25d8bNKUgdjQt8lc5XtHeWXH3a"}

+ 42 - 0
tests/meson.build

@@ -0,0 +1,42 @@
+incdir = include_directories(
+  join_paths('..', 'src')
+)
+
+test_data = configuration_data()
+test_data.set('testjwkdir', join_paths(meson.source_root(), 'tests','keys'))
+
+test_keys_c = configure_file(
+  input: 'test-keys.c.in',
+  output: 'test-keys.c',
+  configuration: test_data
+)
+
+test_keys = executable('test-keys',
+  test_keys_c,
+  'test-util.c',
+  dependencies: [jose],
+  include_directories: incdir
+)
+
+sd_activate = find_program(
+  'systemd-socket-activate',
+  'systemd-activate',
+  required: false
+)
+
+env = environment()
+env.prepend('PATH',
+  join_paths(meson.source_root(), 'src'),
+  join_paths(meson.build_root(), 'src'),
+  separator: ':'
+)
+
+if sd_activate.found()
+  env.set('SD_ACTIVATE', sd_activate.path() + ' --inetd')
+
+  test('adv', find_program('adv'), env: env, timeout: 60)
+  test('rec', find_program('rec'), env: env)
+endif
+test('test-keys', test_keys, env: env, timeout: 60)
+
+# vim:set ts=2 sw=2 et:

+ 1 - 3
tests/rec

@@ -28,11 +28,9 @@ trap 'exit' ERR
 
 export TMP=`mktemp -d`
 mkdir -p $TMP/db
-mkdir -p $TMP/cache
 
 # Generate the server keys
 tangd-keygen $TMP/db sig exc
-tangd-update $TMP/db $TMP/cache
 
 # Generate the client keys
 exc_kid=`jose jwk thp -i $TMP/db/exc.jwk`
@@ -42,7 +40,7 @@ jose jwk pub -i $TMP/exc.jwk -o $TMP/exc.pub.jwk
 
 # Start the server
 port=`shuf -i 1024-65536 -n 1`
-$SD_ACTIVATE -l 127.0.0.1:$port -a $VALGRIND tangd $TMP/cache &
+$SD_ACTIVATE -l 127.0.0.1:$port -a $VALGRIND tangd $TMP/db &
 export PID=$!
 sleep 0.5
 

+ 257 - 0
tests/test-keys.c.in

@@ -0,0 +1,257 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2020 Red Hat, Inc.
+ * Author: Sergio Correia <scorreia@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "keys.c"
+#include "test-util.h"
+
+const char* jwkdir = "@testjwkdir@";
+
+struct thp_result {
+    const char* thp;
+    int valid;
+};
+
+struct test_result_int {
+    const char* data;
+    int expected;
+};
+
+static void
+test_create_new_keys(void)
+{
+   __attribute__((cleanup(cleanup_str))) char* newdir = create_tempdir();
+    ASSERT(newdir);
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(newdir);
+    ASSERT(tki);
+    ASSERT(tki->m_keys_count == 2);
+    remove_tempdir(newdir);
+}
+
+
+static void
+test_is_hash(void)
+{
+    const struct test_result_int test_data[] = {
+        {NULL, 0},
+        {"", 0},
+        {"ES512", 0},
+        {"ECMR", 0},
+        {"foobar", 0},
+        {"{", 0},
+        {"[}", 0},
+        {"[]", 0},
+        {"S1", 1},
+        {"S224", 1},
+        {"S256", 1},
+        {"S384", 1},
+        {"S512", 1},
+        {"S42", 0}
+    };
+    for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
+        int ret = is_hash(test_data[i].data);
+        ASSERT_WITH_MSG(ret == test_data[i].expected, "i = %d, alg = %s", i, test_data[i].data);
+    };
+
+}
+
+static void
+test_jwk_generate(void)
+{
+    const struct test_result_int test_data[] = {
+        {NULL, 0},
+        {"", 0},
+        {"ES512", 1},
+        {"ECMR", 1},
+        {"foobar", 0},
+        {"{", 0},
+        {"[}", 0},
+        {"[]", 0}
+    };
+
+    for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
+        json_auto_t* jwk = jwk_generate(test_data[i].data);
+        ASSERT_WITH_MSG(!!jwk == test_data[i].expected, "i = %d, alg = %s", i, test_data[i].data);
+    };
+}
+
+static void
+test_find_jws(void)
+{
+    const struct thp_result test_data[] = {
+        {"00BUQM4A7NYxbOrBR9QDfkzGVGj3k57Fs4jCbJxcLYAgRFHu5B7jtbL97x1T7stQ", 1},
+        {"dd5qbN1lQ6UWdZszbfx2oIcH34ShklzFL1SUQg", 1},
+        {"dOZkUtZ_gLDUP53GIlyAxHMNuyrk8vdY-XXND32GccqNbT_MKpqGC-13-GNEye48", 1},
+        {"DZrlBQvfvlwPQlvH_IieBdc_KpesEramLygVL_rFr7g", 1},
+        {"FL_Zt5fFadUL4syeMMpUnss8aKdCrPGFy3102JGR3EE", 1},
+        {"qgmqJSo6AEEuVQY7zVlklqdTMqY", 1},
+        {"r4E2wG1u_YyKUo0N0rIK7jJF5Xg", 1},
+        {"ugJ4Ula-YABQIiJ-0g3B_jpFpF2nl3W-DNpfLdXArhTusV0QCcd1vtgDeGHEPzpm7jEsyC7VYYSSOkZicK22mw", 1},
+        {"up0Z4fRhpd4O5QwBaMCXDTlrvxCmZacU0MD8kw", 1},
+        {"vllHS-M0aQFCo2yUCcAahMU4TAtXACyeuRf-zbmmTPBg7V0Pb-RRFGo5C6MnpzdirK8B3ORLOsN8RyXClvtjxA", 1},
+        {NULL, 1},
+        {"a", 0},
+        {"foo", 0},
+        {"bar", 0},
+        {"XXXXXXXXXXXXXXXXXX", 0}
+    };
+
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir);
+    for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
+        json_auto_t* jws = find_jws(tki, test_data[i].thp);
+        ASSERT_WITH_MSG(!!jws == test_data[i].valid, "i = %d, thp = %s", i, test_data[i].thp);
+    }
+
+    /* Passing NULL to find_jws should return the default advertisement */
+    json_auto_t* adv = find_jws(tki, NULL);
+    ASSERT(adv);
+
+
+    /*
+     * The default set of signing keys are the signing keys that are not
+     * rotated. The payload is made of deriving keys that are also not
+     * rotated. The default advertisement should be signed by this set of
+     * default signing keys.
+     */
+    ASSERT(jose_jws_ver(NULL, adv, NULL, tki->m_sign, 1));
+
+    /* find_jws should be able to respond to thumbprints of keys using any
+     * of jose supported hash algorithms. */
+    const char** hashes = supported_hashes();
+    size_t idx;
+    json_t* jwk;
+
+    /* Let's put together all the keys, including rotated ones. */
+    json_auto_t* keys = json_deep_copy(tki->m_keys);
+    ASSERT(keys);
+    ASSERT(json_array_extend(keys, tki->m_rotated_keys) == 0);
+    ASSERT(json_array_size(keys) == (size_t)tki->m_keys_count);
+
+    for (int i = 0; hashes[i]; i++) {
+        json_array_foreach(keys, idx, jwk) {
+            if (!jwk_valid_for_signing(jwk)) {
+                continue;
+            }
+            __attribute__((cleanup(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[i]);
+            ASSERT_WITH_MSG(thp, "i = %d, hash = %s, key idx = %d", i, hashes[i], idx);
+            json_auto_t* jws = find_jws(tki, thp);
+            ASSERT_WITH_MSG(jws, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
+
+            /* Signing keys should sign the payload, in addition to the
+             * default set of signing keys. */
+            json_auto_t* sign = json_deep_copy(tki->m_sign);
+            ASSERT_WITH_MSG(sign, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
+            ASSERT_WITH_MSG(json_array_append(sign, jwk) == 0, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
+            ASSERT_WITH_MSG(jose_jws_ver(NULL, jws, NULL, sign, 1), "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
+        }
+    }
+}
+
+static void
+test_find_jwk(void)
+{
+    const struct thp_result test_data[] = {
+        {"1HdF3XKRSsuZdkpXNurBPoL_pvxdvCOlHuhB4DP-4xWFqbZ51zo29kR4fSiT3BGy9UrHVJ26JMBLOA1vKq3lxA", 1},
+        {"9U8qgy_YjyY6Isuq6QuiKEiYZgNJShcGgJx5FJzCu6m3N6zFaIPy_HDkxkVqAZ9E", 1},
+        {"-bWkGaJi0Zdvxaj4DCp28umLcRA", 1},
+        {"Cy73glFjs6B6RU7wy6vWxAc-2bJy5VJOT9LyK80eKgZ8k27wXZ-3rjsuNU5tua_yHWtluyoSYtjoKXfI0E8ESw", 1},
+        {"kfjbqx_b3BsgPC87HwlOWL9daGMMHBzxcFLClw", 1},
+        {"L4xg2tZXTEVbsK39bzOZM1jGWn3HtOxF5gh6F9YVf5Q", 1},
+        {"LsVAV2ig5LlfstM8TRSf-c7IAkLpNYbIysNuRCVlxocRCGqAh6-f9PklM4nU4N-J", 1},
+        {"OkAcDxYHNlo7-tul8OubYuWXB8CPEhAkcacCmhTclMU", 1},
+        {"uZ0s8YTXcGcuWduWWBSiR2OjOVg", 1},
+        {"WEpfFyeoNKkE2-TosN_bP-gd9UgRvQCZpVasZQ", 1},
+        {NULL, 0},
+        {"a", 0},
+        {"foo", 0},
+        {"bar", 0},
+        {"XXXXXXXXXXXXXXXXXX", 0},
+    };
+
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir);
+
+    for (int i = 0, len = ARRAY_COUNT(test_data); i < len; i++) {
+        json_auto_t* tjwk = find_jwk(tki, test_data[i].thp);
+        ASSERT_WITH_MSG(!!tjwk == test_data[i].valid, "i = %d, thp = %s", i, test_data[i].thp);
+    }
+    /* Passing NULL to find_jwk should fail */
+    json_auto_t* bad_jwk = find_jwk(tki, NULL);
+    ASSERT(bad_jwk == NULL);
+
+    /* find_jwk should be able to respond to thumbprints of keys using any
+     * of jose supported hash algorithms. */
+    const char** hashes = supported_hashes();
+    size_t idx;
+    json_t* jwk;
+
+    /* Let's put together all the keys, including rotated ones. */
+    json_auto_t* keys = json_deep_copy(tki->m_keys);
+    ASSERT(keys);
+    ASSERT(json_array_extend(keys, tki->m_rotated_keys) == 0);
+    ASSERT(json_array_size(keys) == (size_t)tki->m_keys_count);
+
+    for (int i = 0; hashes[i]; i++) {
+        json_array_foreach(keys, idx, jwk) {
+            if (!jwk_valid_for_deriving_keys(jwk)) {
+                continue;
+            }
+            __attribute__((cleanup(cleanup_str))) char* thp = jwk_thumbprint(jwk, hashes[i]);
+            json_auto_t* tjwk = find_jwk(tki, thp);
+            ASSERT_WITH_MSG(tjwk, "i = %d, hash = %s, key idx = %d, thp = %s", i, hashes[i], idx, thp);
+        }
+    }
+}
+
+static void
+test_read_keys(void)
+{
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(jwkdir);
+    ASSERT(tki);
+
+    /*
+     * Keys in tests/keys:
+     * - .uZ0s8YTXcGcuWduWWBSiR2OjOVg.jwk
+     * - .r4E2wG1u_YyKUo0N0rIK7jJF5Xg.jwk
+     * - qgmqJSo6AEEuVQY7zVlklqdTMqY.jwk
+     * - -bWkGaJi0Zdvxaj4DCp28umLcRA.jwk
+     */
+    ASSERT(tki->m_keys_count == 4);
+    ASSERT(json_array_size(tki->m_keys) == 2);
+    ASSERT(json_array_size(tki->m_rotated_keys) == 2);
+
+    const char* invalid_jwkdir = "foobar";
+    __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki2 = read_keys(invalid_jwkdir);
+    ASSERT(tki2 == NULL);
+}
+
+static void
+run_tests(void)
+{
+    test_read_keys();
+    test_find_jwk();
+    test_find_jws();
+    test_jwk_generate();
+    test_is_hash();
+    test_create_new_keys();
+}
+
+int main(int argc, char** argv)
+{
+    run_tests();
+    return 0;
+}

+ 75 - 0
tests/test-util.c

@@ -0,0 +1,75 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2020 Red Hat, Inc.
+ * Author: Sergio Correia <scorreia@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define  _XOPEN_SOURCE 500L
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ftw.h>
+
+#include "test-util.h"
+
+void
+assert_func(const char* filename,
+            int lineno,
+            const char* funcname,
+            const char* expr,
+            const char* fmt,
+            ...)
+{
+    char buffer[MAX_BUF_LEN] = {};
+    if (fmt) {
+        va_list ap;
+        va_start(ap, fmt);
+        vsnprintf(buffer, MAX_BUF_LEN, fmt, ap);
+        va_end(ap);
+        buffer[strcspn(buffer, "\r\n")] = '\0';
+    }
+    fprintf(stderr, "%s:%d: assertion '%s' failed in %s(). %s\n", filename,
+                                                                  lineno,
+                                                                  expr,
+                                                                  funcname,
+                                                                  buffer);
+    abort();
+}
+
+static int
+nftw_remove_callback(const char* path, const struct stat* stat,
+                     int type, struct FTW* ftw)
+{
+    return remove(path);
+}
+
+char*
+create_tempdir(void)
+{
+    char template[] = "/tmp/tang.test.XXXXXX";
+    char *tmpdir = mkdtemp(template);
+    return strdup(tmpdir);
+}
+
+int
+remove_tempdir(const char* path)
+{
+    return nftw(path, nftw_remove_callback, FOPEN_MAX, FTW_DEPTH|FTW_MOUNT|FTW_PHYS);
+}
+

+ 46 - 0
tests/test-util.h

@@ -0,0 +1,46 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright (c) 2020 Red Hat, Inc.
+ * Author: Sergio Correia <scorreia@redhat.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdarg.h>
+
+#define ARRAY_COUNT(arr) (sizeof(arr)/sizeof(0[arr]))
+#define MAX_BUF_LEN 2048
+
+void
+assert_func(const char* /* filename */,
+            int /* line number */,
+            const char* /* function name */,
+            const char* /* expression */,
+            const char*  /* format */,
+            ...);
+
+#define ASSERT_WITH_MSG(expr, fmt, ...) \
+     if (!(expr)) \
+        assert_func(__FILE__, __LINE__, __FUNCTION__, #expr, fmt, ##__VA_ARGS__)
+
+#define ASSERT(expr) \
+    ASSERT_WITH_MSG(expr, NULL)
+
+char*
+create_tempdir(void);
+
+int
+remove_tempdir(const char* /* path */);

+ 10 - 0
units/meson.build

@@ -0,0 +1,10 @@
+tangd_service = configure_file(
+  input: 'tangd@.service.in',
+  output: 'tangd@.service',
+  configuration: data
+)
+
+units += join_paths(meson.current_source_dir(), 'tangd.socket')
+units += tangd_service
+
+# vim:set ts=2 sw=2 et:

+ 0 - 8
units/tangd-keygen.service.in

@@ -1,8 +0,0 @@
-[Unit]
-Description=Tang Server key generation script
-ConditionDirectoryNotEmpty=|!@jwkdir@
-Requires=tangd-update.path
-
-[Service]
-Type=oneshot
-ExecStart=@libexecdir@/tangd-keygen @jwkdir@

+ 0 - 4
units/tangd-update.path.in

@@ -1,4 +0,0 @@
-[Path]
-PathChanged=@jwkdir@
-MakeDirectory=true
-DirectoryMode=0700

+ 0 - 6
units/tangd-update.service.in

@@ -1,6 +0,0 @@
-[Unit]
-Description=Tang Server key update script
-
-[Service]
-Type=oneshot
-ExecStart=@libexecdir@/tangd-update @jwkdir@ @cachedir@

+ 9 - 0
units/tangd.socket

@@ -0,0 +1,9 @@
+[Unit]
+Description=Tang Server socket
+
+[Socket]
+ListenStream=80
+Accept=true
+
+[Install]
+WantedBy=sockets.target

+ 0 - 14
units/tangd.socket.in

@@ -1,14 +0,0 @@
-[Unit]
-Description=Tang Server socket
-Requires=tangd-keygen.service
-Requires=tangd-update.service
-Requires=tangd-update.path
-After=tangd-keygen.service
-After=tangd-update.service
-
-[Socket]
-ListenStream=80
-Accept=true
-
-[Install]
-WantedBy=multi-user.target

+ 10 - 0
units/tangd.xinetd

@@ -0,0 +1,10 @@
+service tangd
+{
+    port = 80
+    server_args = /var/db/tang
+    server = /usr/libexec/tangd
+    socket_type = stream
+    protocol = tcp
+    user = root
+    wait = no
+}

+ 1 - 1
units/tangd@.service.in

@@ -5,4 +5,4 @@ Description=Tang Server
 StandardInput=socket
 StandardOutput=socket
 StandardError=journal
-ExecStart=@libexecdir@/tangd @cachedir@
+ExecStart=@libexecdir@/tangd @jwkdir@