Browse Source

Import upstream version 11

Sergio Correia 11 months ago
parent
commit
b136f0618e

+ 64 - 0
.github/workflows/build.yml

@@ -0,0 +1,64 @@
+---
+name: build
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ubuntu-18.04
+    continue-on-error: ${{ ! matrix.stable }}
+    strategy:
+      matrix:
+        os:
+          - fedora:latest
+          - centos:7
+          - centos:8
+          - debian:testing
+          - debian:latest
+          - ubuntu:rolling
+          - ubuntu:bionic
+        stable: [true]
+        include:
+          - os: fedora:rawhide
+            stable: false
+          - os: ubuntu:devel
+            stable: false
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Show OS information
+        run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available
+
+      - name: Install build dependencies
+        run: bash .github/workflows/install-dependencies
+
+      - name: Build tang
+        run: |
+          mkdir -p build && cd build
+          export ninja=$(command -v ninja)
+          [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
+          meson .. || cat meson-logs/meson-log.txt >&2
+          ${ninja}
+
+      - name: Run tests
+        run: |
+          cd build
+          if ! meson test ; then
+              cat meson-logs/testlog.txt >&2
+              exit -1
+          fi
+
+      - name: Show full test logs
+        run: |
+          if [ -r build/meson-logs/testlog.txt ]; then
+            cat build/meson-logs/testlog.txt >&2
+          else
+            echo "No test log available" >&2
+          fi
+
+    container:
+      image: ${{matrix.os}}
+      env:
+        DISTRO: ${{matrix.os}}
+
+# vim:set ts=2 sw=2 et:

+ 61 - 0
.github/workflows/coverage.yml

@@ -0,0 +1,61 @@
+---
+name: coverage
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ubuntu-18.04
+    strategy:
+      matrix:
+        os:
+          - ubuntu:latest
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Show OS information
+        run: cat /etc/os-release 2>/dev/null || echo /etc/os-release not available
+
+      - name: Install build dependencies
+        run: bash .github/workflows/install-dependencies
+
+      - name: Build tang
+        run: |
+          mkdir -p build && cd build
+          export ninja=$(command -v ninja)
+          [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
+          meson .. -Db_coverage=true || cat meson-logs/meson-log.txt >&2
+          ${ninja}
+
+      - name: Run tests
+        run: |
+          cd build
+          meson test || cat meson-logs/testlog.txt >&2
+
+      - name: Show full test logs
+        run: |
+          if [ -r build/meson-logs/testlog.txt ]; then
+            cat build/meson-logs/testlog.txt >&2
+          else
+            echo "No test log available" >&2
+          fi
+
+      - name: Create coverage report
+        run: |
+          cd build
+          export ninja=$(command -v ninja)
+          [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
+          gcovr -r .. -f ../src -f src/ -e ../tests -e tests -x coverage.xml
+
+      - uses: codecov/codecov-action@v1
+        with:
+          file: build/coverage.xml
+          fail_ci_if_error: true # optional (default = false)
+          verbose: true # optional (default = false)
+
+    container:
+      image: ${{matrix.os}}
+      env:
+        DISTRO: ${{matrix.os}}
+
+# vim:set ts=2 sw=2 et:

+ 30 - 0
.github/workflows/install-dependencies

@@ -0,0 +1,30 @@
+#!/bin/sh -ex
+
+case "${DISTRO}" in
+debian:*|ubuntu:*)
+    export DEBIAN_FRONTEND=noninteractive
+    apt clean
+    apt update
+    apt -y install gcc meson pkg-config libjose-dev jose libhttp-parser-dev \
+                   systemd gcovr curl socat
+    ;;
+
+fedora:*)
+    echo 'max_parallel_downloads=10' >> /etc/dnf/dnf.conf
+    dnf -y clean all
+    dnf -y --setopt=deltarpm=0 update
+    dnf -y install gcc meson pkgconfig libjose-devel jose http-parser-devel \
+                   systemd gcovr curl socat
+    ;;
+
+centos:*)
+    yum -y clean all
+    yum -y --setopt=deltarpm=0 update
+    yum install -y yum-utils epel-release
+    yum config-manager -y --set-enabled PowerTools \
+        || yum config-manager -y --set-enabled powertools || :
+    yum -y install meson socat
+    yum-builddep -y tang
+    ;;
+esac
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 1 - 0
doc/meson.build

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

+ 19 - 1
doc/tang.8.adoc

@@ -40,7 +40,13 @@ protocol, see the Tang project's homepage.
 
 Getting a Tang server up and running is simple:
 
+ifdef::freebsd[]
+    $ sudo service tangd enable
+    $ sudo service tangd start
+endif::[]
+ifndef::freebsd[]
     $ sudo systemctl enable tangd.socket --now
+endif::[]
 
 That's it. The server is now running with a fresh set of cryptographic keys
 and will automatically start on the next reboot.
@@ -49,8 +55,13 @@ and will automatically start on the next reboot.
 
 Tang intends to be a minimal network service and therefore does not have any
 configuration. To adjust the network settings, you can override the
+ifdef::freebsd[]
+variables in the */usr/local/etc/rc.d/tangd* file.
+endif::[]
+ifndef::freebsd[]
 *tangd.socket* unit file using the standard systemd mechanisms. See
 link:systemd.unit.5.adoc[*systemd.unit*(5)] and link:systemd.socket.5.adoc[*systemd.socket*(5)] for more information.
+endif::[]
 
 == KEY ROTATION
 
@@ -59,6 +70,10 @@ periodically rotate your keys. The precise interval at which you should rotate
 depends upon your application, key sizes and institutional policy. For some
 common recommendations, see: https://www.keylength.com.
 
+There is a convenience script to deal with this. See
+link:tangd-rotate-keys.1.adoc[*tangd-rotate-keys*(1)] for more information.
+This can also be performed manually as described below.
+
 To rotate keys, first we need to generate new keys in the key database
 directory. This is typically */var/db/tang*. For example, you can create
 new signature and exchange keys with the following commands:
@@ -130,10 +145,13 @@ Nathaniel McCallum <npmccallum@redhat.com>
 
 == SEE ALSO
 
+ifndef::freebsd[]
 link:systemd.unit.5.adoc[*systemd.unit*(5)],
 link:systemd.socket.5.adoc[*systemd.socket*(5)],
+endif::[]
 link:jose-jwk-gen.1.adoc[*jose-jwk-gen*(1)],
-link:tang-show-keys.1.adoc[*tang-show-keys*(1)]
+link:tang-show-keys.1.adoc[*tang-show-keys*(1)],
+link:tangd-rotate-keys.1.adoc[*tangd-rotate-keys*(1)]
 
 == FURTHER READING
 

+ 46 - 0
doc/tangd-rotate-keys.1.adoc

@@ -0,0 +1,46 @@
+tangd-rotate-keys(1)
+====================
+:doctype: manpage
+
+== NAME
+
+tangd-rotate-keys - Perform rotation of tang keys
+
+== SYNOPSIS
+
+*tangd-rotate-keys* [-h] [-v] -d <KEYDIR>
+
+== DESCRIPTION
+
+in order to preserve the security of the system over the long run, you need to periodically
+rotate your keys. The precise interval at which you should rotate depends upon your application,
+key sizes and institutional policy. For some common recommendations, see: https://www.keylength.com.
+
+*tangd-rotate-keys* generates new keys in the key database directory given by the *-d* option.
+This is typically */var/db/tang*. It also rename the old keys to have a leading . in order to
+hide them from advertisement.
+
+Tang will immediately pick up all changes. No restart is required.
+
+At this point, new client bindings will pick up the new keys and old clients can continue to
+utilize the old keys. Once you are sure that all the old clients have been migrated to use the
+new keys, you can remove the old keys. Be aware that removing the old keys while clients are
+still using them can result in data loss. You have been warned.
+
+== OPTIONS
+*  *-d* <KEYDIR>:
+  The directory with the keys, e.g. /var/db/tang
+
+* *-h*:
+  Display the usage information
+
+* *-v*:
+  Verbose. Display additional info on keys created/rotated
+
+== AUTHOR
+
+Sergio Correia <scorreia@redhat.com>
+
+== SEE ALSO
+
+link:tang.8.adoc[*tang*(8)]

+ 10 - 4
meson.build

@@ -1,5 +1,5 @@
 project('tang', 'c',
-  version: '10',
+  version: '11',
   license: 'GPL3+',
   default_options: [
     'c_std=c99',
@@ -16,7 +16,7 @@ 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())
-if build_machine.system() == 'freebsd'
+if host_machine.system() == 'freebsd'
   licensedir += '-'+meson.project_version()
 endif
 jwkdir = join_paths(get_option('localstatedir'), 'db', meson.project_name())
@@ -26,6 +26,8 @@ data.set('libexecdir', libexecdir)
 data.set('sysconfdir', sysconfdir)
 data.set('systemunitdir', systemunitdir)
 data.set('jwkdir', jwkdir)
+data.set('user', get_option('user'))
+data.set('group', get_option('group'))
 
 add_project_arguments(
   '-D_POSIX_C_SOURCE=200809L',
@@ -54,7 +56,11 @@ compiler = meson.get_compiler('c')
 if not compiler.has_header('http_parser.h',args : '-I/usr/local/include')
   error('http-parser devel files not found.')
 endif
-http_parser = compiler.find_library('http_parser',dirs:['/usr/lib','/usr/local/lib'])
+if host_machine.system() == 'freebsd'
+  http_parser = compiler.find_library('http_parser',dirs : '/usr/local/lib')
+else
+  http_parser = compiler.find_library('http_parser')
+endif
 
 licenses = ['COPYING']
 libexecbins = []
@@ -75,7 +81,7 @@ 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@'],
+      command: [a2x, '--attribute=' + build_machine.system(), '-f', 'manpage', '-D', meson.current_build_dir(), '@INPUT@'],
       install_dir: join_paths(get_option('mandir'), 'man' + m.split('.')[-1]),
       install: true
     )

+ 2 - 0
meson_options.txt

@@ -0,0 +1,2 @@
+option('user', type: 'string', value: 'tang', description: 'Unprivileged user for tang operations')
+option('group', type: 'string', value: 'tang', description: 'Unprivileged group for tang operations')

+ 24 - 16
src/keys.c

@@ -17,6 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <sys/stat.h>
 #include <stdlib.h>
 #include <string.h>
 #include <dirent.h>
@@ -263,23 +264,10 @@ find_by_thp(struct tang_keys_info* tki, const char* target)
     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) {
+            if (!thumbprint || 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 json_incref(jwk);
         }
     }
     return NULL;
@@ -337,6 +325,12 @@ create_new_keys(const char* jwkdir)
             fprintf(stderr, "Error saving JWK to file (%s)\n", path);
             return 0;
         }
+
+        /* Set 0440 permission for the new key. */
+        if (chmod(path, S_IRUSR | S_IRGRP) == -1) {
+            fprintf(stderr, "Unable to set permissions for JWK file (%s)\n", path);
+            return 0;
+        }
     }
     return 1;
 }
@@ -438,7 +432,21 @@ find_jws(struct tang_keys_info* tki, const char* thp)
         }
         return json_incref(jws);
     }
-    return find_by_thp(tki, thp);
+
+    json_auto_t* jwk = find_by_thp(tki, thp);
+    if (!jwk_valid_for_signing(jwk)) {
+        return NULL;
+    }
+
+    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);
 }
 
 json_t*

+ 16 - 2
src/meson.build

@@ -7,8 +7,22 @@ tangd = executable('tangd',
   install_dir: libexecdir
 )
 
+tangd_keygen = configure_file(
+  input: 'tangd-keygen.in',
+  output: 'tangd-keygen',
+  configuration: data,
+  install: true,
+  install_dir: libexecdir
+)
+
+tangd_rotate_keys = configure_file(
+  input: 'tangd-rotate-keys.in',
+  output: 'tangd-rotate-keys',
+  configuration: data,
+  install: true,
+  install_dir: libexecdir
+)
+
 bins += join_paths(meson.current_source_dir(), 'tang-show-keys')
-libexecbins += join_paths(meson.current_source_dir(), 'tangd-keygen')
-libexecbins += join_paths(meson.current_source_dir(), 'tangd-rotate-keys')
 
 # vim:set ts=2 sw=2 et:

+ 2 - 3
src/tang-show-keys

@@ -27,10 +27,9 @@ fi
 
 port=${1-80}
 
-adv=$(curl -sSf localhost:$port/adv)
+adv=$(curl -sSf "localhost:$port/adv")
 
 THP_DEFAULT_HASH=S256    # SHA-256.
-echo $adv \
-    | jose fmt -j- -g payload -y -o- \
+jose fmt --json "${adv}" -g payload -y -o- \
     | jose jwk use -i- -r -u verify -o- \
     | jose jwk thp -i- -a "${THP_DEFAULT_HASH}"

+ 20 - 7
src/tangd-keygen

@@ -18,20 +18,33 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-trap 'exit' ERR
+set -e
 
-if [ $# -ne 1 -a $# -ne 3 ] || [ ! -d "$1" ]; then
+usage() {
     echo "Usage: $0 <jwkdir> [<sig> <exc>]" >&2
     exit 1
-fi
+}
+
+set_perms() {
+    chmod -- 0440 "${1}"
+    if ! chown -- @user@:@group@ "${1}" 2>/dev/null; then
+        echo "Unable to change owner/group for ${1} to @user@:@group@" >&2
+    fi
+}
+
+[ $# -ne 1 ] && [ $# -ne 3 ] && usage
+[ -d "$1" ] || usage
 
 [ $# -eq 3 ] && sig=$2 && exc=$3
 
 THP_DEFAULT_HASH=S256     # SHA-256.
-jwe=`jose jwk gen -i '{"alg":"ES512"}'`
+jwe=$(jose jwk gen -i '{"alg":"ES512"}')
 [ -z "$sig" ] && sig=$(echo "$jwe" | jose jwk thp -i- -a "${THP_DEFAULT_HASH}")
-echo "$jwe" > $1/$sig.jwk
+echo "$jwe" > "$1/$sig.jwk"
+set_perms "$1/$sig.jwk"
+
 
-jwe=`jose jwk gen -i '{"alg":"ECMR"}'`
+jwe=$(jose jwk gen -i '{"alg":"ECMR"}')
 [ -z "$exc" ] && exc=$(echo "$jwe" | jose jwk thp -i- -a "${THP_DEFAULT_HASH}")
-echo "$jwe" > $1/$exc.jwk
+echo "$jwe" > "$1/$exc.jwk"
+set_perms "$1/$exc.jwk"

+ 11 - 3
src/tangd-rotate-keys

@@ -21,7 +21,7 @@
 SUMMARY="Perform rotation of tang keys"
 
 usage() {
-    local _ret="${1:-1}"
+    _ret="${1:-1}"
     exec >&2
     echo "Usage: ${0} [-h] [-v] -d <KEYDIR>"
     echo
@@ -37,8 +37,8 @@ usage() {
 }
 
 log() {
-    local _msg="${1}"
-    local _verbose="${2:-}"
+    _msg="${1}"
+    _verbose="${2:-}"
     [ -z "${_verbose}" ] && return 0
     echo "${_msg}" >&2
 }
@@ -48,6 +48,13 @@ error() {
     usage 1
 }
 
+set_perms() {
+    chmod -- 0440 "${1}"
+    if ! chown -- @user@:@group@ "${1}" 2>/dev/null; then
+        echo "Unable to change owner/group for ${1} to @user@:@group@" >&2
+    fi
+}
+
 JWKDIR=
 VERBOSE=
 while getopts "hvd:" o; do
@@ -78,6 +85,7 @@ cd "${JWKDIR}" || error "Unable to change to keys directory '${JWKDIR}'"
         thp="$(printf '%s' "${jwe}" | jose jwk thp --input=- \
                                            -a "${DEFAULT_THP_HASH}")"
         echo "${jwe}" > "${thp}.jwk"
+        set_perms "${thp}.jwk"
         log "Created new key ${thp}.jwk" "${VERBOSE}"
     done
 cd - >/dev/null

+ 39 - 8
tests/adv

@@ -27,6 +27,10 @@ export TMP=`mktemp -d`
 mkdir -p $TMP/db
 
 tangd-keygen $TMP/db sig exc
+# Make sure keys generated by tangd-keygen have proper permissions.
+valid_key_perm "${TMP}/db/sig.jwk"
+valid_key_perm "${TMP}/db/exc.jwk"
+
 jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.sig.jwk
 jose jwk gen -i '{"alg": "ES512"}' -o $TMP/db/.oth.jwk
 
@@ -36,11 +40,11 @@ export PID=$!
 sleep 0.5
 
 # Make sure requests on the root fail
-! fetch /
+fetch / && expected_fail
 
 # The request should fail (404) for non-signature key IDs
-! fetch /adv/`jose jwk thp -i $TMP/db/exc.jwk`
-! fetch /adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk`
+fetch /adv/`jose jwk thp -i $TMP/db/exc.jwk` && expected_fail
+fetch /adv/`jose jwk thp -a S512 -i $TMP/db/exc.jwk` && expected_fail
 
 # The default advertisement fetch should succeed and pass verification
 fetch /adv
@@ -52,17 +56,17 @@ fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
 fetch /adv/`jose jwk thp -a S512 -i $TMP/db/sig.jwk` | ver $TMP/db/sig.jwk
 
 # Requesting an adv by an advertised key ID should't be signed by hidden keys
-! fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk
-! fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk
+fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.sig.jwk && expected_fail
+fetch /adv/`jose jwk thp -i $TMP/db/sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
 
 # Verify that the default advertisement is not signed with hidden signature keys
-! fetch /adv/ | ver $TMP/db/.oth.jwk
-! fetch /adv/ | ver $TMP/db/.sig.jwk
+fetch /adv/ | ver $TMP/db/.oth.jwk && expected_fail
+fetch /adv/ | ver $TMP/db/.sig.jwk && expected_fail
 
 # A private key advertisement is signed by all advertised keys and the requested private key
 fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/sig.jwk
 fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.sig.jwk
-! fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk
+fetch /adv/`jose jwk thp -i $TMP/db/.sig.jwk` | ver $TMP/db/.oth.jwk && expected_fail
 
 # Verify that the advertisements contain the cty parameter
 fetch /adv | jose fmt -j- -Og protected -SyOg cty -Sq "jwk-set+json" -E
@@ -93,6 +97,13 @@ fetch /adv
 # Lets's now test with multiple pairs of keys.
 for i in 1 2 3 4 5 6 7 8 9; do
     tangd-keygen "${TMP}"/db other-sig-${i} other-exc-${i}
+    # Make sure the requested keys exist and are valid.
+    validate_sig "${TMP}/db/other-sig-${i}.jwk"
+    validate_exc "${TMP}/db/other-exc-${i}.jwk"
+
+    # Make sure keys generated by tangd-keygen have proper permissions.
+    valid_key_perm "${TMP}/db/other-sig-${i}.jwk"
+    valid_key_perm "${TMP}/db/other-exc-${i}.jwk"
 done
 
 # Verify the advertisement is correct.
@@ -104,3 +115,23 @@ for jwk in "${TMP}"/db/other-sig-*.jwk; do
         fetch /adv/"$(jose jwk thp -a "${alg}" -i "${jwk}")" | ver "${jwk}"
     done
 done
+
+# Now let's test keys rotation.
+tangd-rotate-keys -d "${TMP}/db"
+for i in 1 2 3 4 5 6 7 8 9; do
+    # Make sure keys were excluded from advertisement.
+    validate_sig "${TMP}/db/.other-sig-${i}.jwk"
+    validate_exc "${TMP}/db/.other-exc-${i}.jwk"
+done
+
+# And test also that we have valid keys after rotation.
+thp=
+for jwk in "${TMP}"/db/*.jwk; do
+    validate_sig "${jwk}" && thp="$(jose jwk thp -a "${THP_DEFAULT_HASH}" \
+                                    -i "${jwk}")"
+
+    # Make sure keys generated by tangd-rotate-keys have proper permissions.
+    valid_key_perm "${jwk}"
+done
+[ -z "${thp}" ] && die "There should be valid keys after rotation"
+test "$(tang-show-keys $PORT)" = "${thp}"

+ 29 - 0
tests/helpers

@@ -56,7 +56,36 @@ validate() {
     fi
 }
 
+validate_sig() {
+    jose fmt --json "${1}" --output=- | jose jwk use --input=- --required \
+        --use verify 2>/dev/null
+}
+
+validate_exc() {
+    jose fmt --json "${1}" --output=- | jose jwk use --input=- --required \
+        --use deriveKey 2>/dev/null
+}
+
 sanity_check() {
     # Skip test if socat is not available.
     [ -n "${SOCAT}" ] || exit 77
 }
+
+die() {
+    echo "${1}" >&2
+    exit 1
+}
+
+valid_key_perm() {
+    if [ -n "${TANG_BSD}" ]; then
+        _perm="$(stat -f %Lp "${1}")"
+    else
+        _perm="$(stat -c %a "${1}")"
+    fi
+    [ "${_perm}" = "440" ]
+}
+
+expected_fail () {
+    echo "Test was expected to fail" >&2
+    exit 1
+}

+ 1 - 1
tests/meson.build

@@ -32,7 +32,7 @@ env.prepend('PATH',
   separator: ':'
 )
 
-if build_machine.system() == 'freebsd'
+if host_machine.system() == 'freebsd'
   env.set('TANG_BSD', '1')
 endif
 

+ 5 - 2
tests/rec

@@ -28,6 +28,9 @@ mkdir -p $TMP/db
 
 # Generate the server keys
 tangd-keygen $TMP/db sig exc
+# Make sure keys generated by tangd-keygen have proper permissions.
+valid_key_perm "${TMP}/db/sig.jwk"
+valid_key_perm "${TMP}/db/exc.jwk"
 
 # Generate the client keys
 exc_kid=`jose jwk thp -i $TMP/db/exc.jwk`
@@ -42,8 +45,8 @@ export PID=$!
 sleep 0.5
 
 # Make sure that GET fails
-! curl -sf http://127.0.0.1:$PORT/rec
-! curl -sf http://127.0.0.1:$PORT/rec/
+curl -sf http://127.0.0.1:$PORT/rec && expected_fail
+curl -sf http://127.0.0.1:$PORT/rec/ && expected_fail
 
 # Make a recovery request (NOTE: this is insecure! Don't do this in real code!)
 good=`jose jwk exc -i '{"alg":"ECMR","key_ops":["deriveKey"]}' -l $TMP/exc.jwk -r $TMP/db/exc.jwk`

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

@@ -33,6 +33,56 @@ struct test_result_int {
 };
 
 static void
+verify_keys_permissions(const char* targetdir)
+{
+    struct stat st;
+    struct dirent* d;
+    DIR* dir = opendir(targetdir);
+    ASSERT(dir);
+    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", targetdir, 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';
+            ASSERT(stat(filepath, &st) == 0);
+
+            ASSERT_WITH_MSG(st.st_mode & (S_IRUSR | S_IRGRP), "key = %s, missing perm (0%o)", filepath, (S_IRUSR | S_IRGRP));
+            int unexpected_perms[] = {
+                S_ISUID, /* 04000 set-user-ID */
+                S_ISGID, /* 02000 set-group-ID */
+                S_IWUSR, /* 00200 write by owner */
+                S_IXUSR, /* 00100 execute/search by owner */
+                S_IWGRP, /* 00020 write by group */
+                S_IXGRP, /* 00010 execute/search by group */
+                S_IROTH, /* 00004 read by others */
+                S_IWOTH, /* 00002 write by others */
+                S_IXOTH, /* 00001 execute/search by others */
+                0
+            };
+            for (int i = 0; unexpected_perms[i] != 0; i++) {
+                ASSERT_WITH_MSG((st.st_mode & unexpected_perms[i]) == 0, "key = %s, i = %d, unexpected perm (0%o)", filepath, i, unexpected_perms[i]);
+            }
+
+        }
+    }
+    closedir(dir);
+}
+
+static void
 test_create_new_keys(void)
 {
    __attribute__((cleanup(cleanup_str))) char* newdir = create_tempdir();
@@ -40,6 +90,10 @@ test_create_new_keys(void)
     __attribute__((cleanup(cleanup_tang_keys_info))) struct tang_keys_info* tki = read_keys(newdir);
     ASSERT(tki);
     ASSERT(tki->m_keys_count == 2);
+
+    /* Make sure keys have proper permissions. */
+    verify_keys_permissions(newdir);
+
     remove_tempdir(newdir);
 }
 
@@ -104,6 +158,11 @@ test_find_jws(void)
         {"ugJ4Ula-YABQIiJ-0g3B_jpFpF2nl3W-DNpfLdXArhTusV0QCcd1vtgDeGHEPzpm7jEsyC7VYYSSOkZicK22mw", 1},
         {"up0Z4fRhpd4O5QwBaMCXDTlrvxCmZacU0MD8kw", 1},
         {"vllHS-M0aQFCo2yUCcAahMU4TAtXACyeuRf-zbmmTPBg7V0Pb-RRFGo5C6MnpzdirK8B3ORLOsN8RyXClvtjxA", 1},
+        {"-bWkGaJi0Zdvxaj4DCp28umLcRA", 0},
+        {"WEpfFyeoNKkE2-TosN_bP-gd9UgRvQCZpVasZQ", 0},
+        {"L4xg2tZXTEVbsK39bzOZM1jGWn3HtOxF5gh6F9YVf5Q", 0},
+        {"9U8qgy_YjyY6Isuq6QuiKEiYZgNJShcGgJx5FJzCu6m3N6zFaIPy_HDkxkVqAZ9E", 0},
+        {"Cy73glFjs6B6RU7wy6vWxAc-2bJy5VJOT9LyK80eKgZ8k27wXZ-3rjsuNU5tua_yHWtluyoSYtjoKXfI0E8ESw", 0},
         {NULL, 1},
         {"a", 0},
         {"foo", 0},

+ 1 - 1
units/meson.build

@@ -3,7 +3,7 @@ tangd_service = configure_file(
   output: 'tangd@.service',
   configuration: data
 )
-if build_machine.system() == 'freebsd'
+if host_machine.system() == 'freebsd'
   tangd_rc = configure_file(
     input: 'tangd.rc.in',
     output: 'tangd',

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

@@ -6,3 +6,4 @@ StandardInput=socket
 StandardOutput=socket
 StandardError=journal
 ExecStart=@libexecdir@/tangd @jwkdir@
+User=@user@