1
0
Quellcode durchsuchen

Merge upstream version 13

Christoph Biedl vor 8 Monaten
Ursprung
Commit
2f74b2a5ed

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

@@ -0,0 +1,131 @@
+---
+name: build
+
+on:
+  push:
+    paths-ignore:
+      - '**.md'
+  pull_request:
+    paths-ignore:
+      - '**.md'
+
+jobs:
+  linux:
+    runs-on: ubuntu-latest
+    continue-on-error: ${{ ! matrix.stable }}
+    strategy:
+      matrix:
+        compiler:
+          - gcc
+          - clang
+        os:
+          - fedora:latest
+          - quay.io/centos/centos:stream9
+          - quay.io/centos/centos:stream8
+          - debian:testing
+          - debian:latest
+          - ubuntu:rolling
+          - ubuntu:jammy
+          - ubuntu:focal
+        stable:: [true]
+        include:
+          - compiler: gcc
+            os: quay.io/fedora/fedora:rawhide
+            stable: false
+          - compiler: clang
+            os: quay.io/fedora/fedora:rawhide
+            stable: false
+          - compiler: gcc
+            os: ubuntu:devel
+            stable: false
+          - compiler: clang
+            os: ubuntu:devel
+            stable: false
+    steps:
+      - uses: actions/checkout@v4
+
+      - 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 jose
+        run: |
+          mkdir -p build && cd build
+          export ninja=$(command -v ninja)
+          [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
+          meson setup .. || 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
+          cat build/meson-private/jose.pc
+
+    container:
+      image: ${{matrix.os}}
+      env:
+        DISTRO: ${{matrix.os}}
+        CC: ${{ matrix.compiler }}
+
+  osx:
+    runs-on: macos-latest
+    strategy:
+      matrix:
+        compiler:
+          - gcc
+          - clang
+    steps:
+      - uses: actions/checkout@v4
+
+      - 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 jose
+        run: |
+          mkdir -p build && cd build
+          export ninja=$(command -v ninja)
+          [ -z "${ninja}" ] && export ninja=$(command -v ninja-build)
+          CFLAGS=-I$(brew --prefix openssl)/include LDFLAGS=-L$(brew --prefix openssl)/lib PKG_CONFIG_PATH=$(brew --prefix openssl)/lib/pkgconfig meson setup .. || 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
+          cat build/meson-private/jose.pc
+
+    env:
+      DISTRO: osx:macos-latest
+      CC: ${{ matrix.compiler }}
+
+# vim:set ts=2 sw=2 et:

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

@@ -0,0 +1,51 @@
+#!/bin/sh -ex
+
+COMMON="meson curl git file bzip2 asciidoc jq ${CC}"
+
+case "${DISTRO}" in
+osx:*)
+    brew update
+    for pkg in pkg-config jansson openssl zlib meson; do
+        brew ls --versions "${pkg}" || brew install "${pkg}"
+        brew outdated "${pkg}" || brew upgrade "${pkg}" || true
+    done
+    ;;
+
+debian:*|ubuntu:*)
+    export DEBIAN_FRONTEND=noninteractive
+    apt clean
+    apt update
+    # We get some errors once in a while, so let's try a few times.
+    for i in 1 2 3; do
+        apt -y install build-essential pkg-config libssl-dev zlib1g-dev \
+                       libjansson-dev ${COMMON} && break
+        sleep 5
+    done
+    ;;
+
+*fedora:*)
+    echo 'max_parallel_downloads=10' >> /etc/dnf/dnf.conf
+    dnf -y clean all
+    dnf -y --setopt=deltarpm=0 update
+    dnf -y install ${COMMON} pkgconfig openssl-devel zlib-devel jansson-devel
+    ;;
+
+centos:7)
+    yum -y clean all
+    yum -y --setopt=deltarpm=0 update
+    yum install -y yum-utils epel-release centos-release-scl llvm-toolset-7
+    yum -y install ${COMMON}
+    yum-builddep -y jose
+    ;;
+
+*centos:stream*)
+    dnf -y clean all
+    dnf -y --allowerasing --setopt=deltarpm=0 update
+    dnf install -y yum-utils epel-release
+    dnf config-manager -y --set-enabled crb \
+        || dnf config-manager -y --set-enabled powertools || :
+    dnf -y --allowerasing install ${COMMON}
+    dnf builddep -y jose
+    ;;
+esac
+# vim: set ts=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80:

+ 47 - 0
.gitignore

@@ -0,0 +1,47 @@
+*~
+*.a
+*.o
+*.la
+*.lo
+*.log
+*.m4
+*.pc
+*.so
+*.swp
+*.swo
+*.trs
+*_t
+.*
+aclocal.m4
+ar-lib
+autom4te.cache
+build
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+configure-stamp
+depcomp
+install-sh
+libtool
+ltmain.sh
+Makecache
+Makefile.in
+Makefile
+missing
+tags
+test-*
+vgcore.*
+
+cmd/jose
+jose/jose.h
+
+tests/alg_*
+tests/api_*
+!tests/alg_*.c
+!tests/api_*.c
+
+doc/doxygen/man/man3/*
+!doc/doxygen/man/man3/jose_*.3

+ 0 - 34
.travis.docker

@@ -1,34 +0,0 @@
-#!/bin/bash -ex
-
-case "$1" in
-  before_install)
-    if [ "$TRAVIS_OS_NAME" == "linux" ]; then
-      docker create \
-        --name=$TRAVIS_COMMIT -t \
-        -v `pwd`:/tmp/build \
-        -w /tmp/build \
-        $DISTRO /bin/cat
-      docker start $TRAVIS_COMMIT
-    fi
-    ;;
-
-  after_script)
-    if [ "$TRAVIS_OS_NAME" == "linux" ]; then
-      docker kill $TRAVIS_COMMIT
-      docker rm $TRAVIS_COMMIT
-    fi
-    ;;
-
-  *)
-    if [ -x ./.travis.$1 ]; then
-      if [ "$TRAVIS_OS_NAME" == "linux" ]; then
-          docker exec \
-            `bash <(curl -s https://codecov.io/env)` \
-            -e CC -e DISTRO \
-            $TRAVIS_COMMIT ./.travis.$1
-      else
-          ./.travis.$1
-      fi
-    fi
-    ;;
-esac

+ 0 - 52
.travis.install

@@ -1,52 +0,0 @@
-#!/bin/bash -ex
-
-COMMON="meson curl git make file bzip2 $CC"
-
-case "$DISTRO" in
-  osx:*)
-    brew update
-    for pkg in jansson openssl zlib meson; do
-        brew ls --versions $pkg || brew install $pkg
-        brew outdated $pkg || brew upgrade $pkg || true
-    done
-    ;;
-
-  debian:*|ubuntu:*)
-    # This solves an intermittant error when fetching packages on debian
-    sed -i 's|httpredir.debian.org|ftp.us.debian.org|g' /etc/apt/sources.list
-
-    apt-get clean
-
-    while ! apt-get update; do
-        sleep 5
-    done
-
-    while ! apt-get -y \
-        -o Dpkg::Options::="--force-confdef" \
-        -o Dpkg::Options::="--force-confnew" \
-        dist-upgrade; do
-        sleep 5
-    done
-
-    while ! apt-get -y install $COMMON \
-        build-essential pkg-config libssl-dev zlib1g-dev libjansson-dev; do
-        sleep 5
-    done
-    ;;
-
-  fedora:*)
-    dnf -y clean all
-    dnf -y --setopt=deltarpm=0 update
-    dnf -y install $COMMON pkgconfig openssl-devel zlib-devel jansson-devel findutils
-    ;;
-
-  centos:*)
-    yum -y clean all
-    yum -y --setopt=deltarpm=0 update
-    yum install -y yum-utils
-    yum config-manager -y --set-enabled PowerTools
-    yum -y install epel-release
-    yum -y install $COMMON pkgconfig openssl-devel zlib-devel jansson-devel findutils gcc
-    sed -i 's|>=1\.0\.2|>=1\.0\.1|' meson.build
-    ;;
-esac

+ 0 - 30
.travis.script

@@ -1,30 +0,0 @@
-#!/bin/bash -ex
-
-function findexe() {
-  while [ $# -gt 0 ]; do
-    while read -d: path; do
-        [ -f "$path/$1" -a -x "$path/$1" ] && echo "$path/$1" && return 0
-    done <<< "$PATH:"
-    shift
-  done
-  return 1
-}
-
-LC_ALL=`locale -a | grep -i '^en_US\.utf'` || LC_ALL=`locale -a | grep -i '^c\.utf'`
-export LC_ALL
-
-mkdir build
-cd build
-
-export CFLAGS="-g -coverage"
-
-if ! meson ..; then
-  cat meson-logs/meson-log.txt >&2
-  exit 1
-fi
-
-ninja=`findexe ninja ninja-build`
-"$ninja" test
-
-bash <(curl -s https://codecov.io/bash) 2>&1 \
-  | egrep -v "has arcs (to entry|from exit) block"

+ 0 - 40
.travis.yml

@@ -1,40 +0,0 @@
-sudo: required
-arch:
-  - amd64
-  - ppc64le
-os: linux
-language: c
-compiler:
-  - clang
-  - gcc
-
-services: docker
-
-matrix:
-  include:
-    - osx_image: xcode9.3
-      compiler: clang
-      os: osx
-      env:
-        - DISTRO=osx:xcode9.3
-        - PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig:/usr/local/opt/zlib/lib/pkgconfig
-    - osx_image: xcode8.3
-      compiler: clang
-      os: osx
-      env:
-        - DISTRO=osx:xcode8.3
-        - PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig:/usr/local/opt/zlib/lib/pkgconfig
-
-env:
-  matrix:
-    - DISTRO=fedora:rawhide
-    - DISTRO=fedora:latest
-    - DISTRO=centos:latest
-    - DISTRO=debian:unstable
-    - DISTRO=debian:testing
-    - DISTRO=ubuntu:devel
-
-before_install: ./.travis.docker before_install
-install: ./.travis.docker install
-script: ./.travis.docker script
-after_script: ./.travis.docker after_script

+ 5 - 3
README.md

@@ -29,6 +29,7 @@ José is extensively tested against the RFC test vectors.
 | ES256              |    YES    |   Signature    |     EC   |
 | ES384              |    YES    |   Signature    |     EC   |
 | ES512              |    YES    |   Signature    |     EC   |
+| ES256K             |    YES    |   Signature    |     EC   |
 | PS256              |    YES    |   Signature    |    RSA   |
 | PS384              |    YES    |   Signature    |    RSA   |
 | PS512              |    YES    |   Signature    |    RSA   |
@@ -112,7 +113,7 @@ Decryption failed!
 Building Jose is fairly straightforward:
 
     $ mkdir build && cd build
-    $ meson .. --prefix=/usr
+    $ meson setup .. --prefix=/usr
     $ ninja
     $ sudo ninja install
 
@@ -123,9 +124,10 @@ You can even run the tests if you'd like:
 To build a FreeBSD, HardenedBSD or OPNsense package
 use:
 
-    (as root) # pkg install meson pkgconf jansson openssl
+    (as root) # pkg install meson pkgconf jansson openssl asciidoc jq
+
     $ mkdir build && cd build
-    $ meson .. --prefix=/usr/local
+    $ meson setup .. --prefix=/usr/local
     $ ninja
     $ meson test
     (as root) # ninja install

+ 1 - 1
cmd/jose.c

@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-#include <cmd/jose.h>
+#include "jose.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>

+ 40 - 1
cmd/jwk/gen.c

@@ -16,7 +16,9 @@
  */
 
 #include "jwk.h"
+#include "../../lib/hooks.h"
 #include <unistd.h>
+#include <string.h>
 
 #define SUMMARY "Creates a random JWK for each input JWK template"
 
@@ -59,6 +61,42 @@ static const jcmd_cfg_t cfgs[] = {
     {}
 };
 
+static int jcmd_jwk_invalid_key(const char* key, const char* msg) {
+    int invalid_key = strcmp(key, "kty") && strcmp(key, "alg");
+    if (invalid_key)
+        fprintf(stderr, "%s, unknown json key:%s\n", msg, key);
+    return invalid_key;
+}
+
+static int jcmd_jwk_invalid_algo(const json_t* value, const char* msg) {
+    if (json_is_string(value)) {
+        const char* algo = json_string_value(value);
+        if (!jose_hook_alg_find_any(algo)) {
+            fprintf(stderr, "%s, unknown algorithm:%s\n", msg, algo);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int
+jcmd_jwk_dump_error(json_t* elem)
+{
+    if (!json_is_object(elem))
+        return -1;
+
+    for (void* iter = json_object_iter(elem); iter;
+         iter = json_object_iter_next(elem, iter)) {
+        const char* msg = "JWK generation failed";
+        const char* key = json_object_iter_key(iter);
+        if (jcmd_jwk_invalid_key(key, msg) ||
+            jcmd_jwk_invalid_algo(json_object_iter_value(iter), msg)) {
+            break;
+        }
+    }
+    return 0;
+}
+
 static void
 jcmd_opt_cleanup(jcmd_opt_t *opt)
 {
@@ -81,7 +119,8 @@ jcmd_jwk_gen(int argc, char *argv[])
 
     for (size_t i = 0; i < json_array_size(opt.keys); i++) {
         if (!jose_jwk_gen(NULL, json_array_get(opt.keys, i))) {
-            fprintf(stderr, "JWK generation failed!\n");
+            if (jcmd_jwk_dump_error(json_array_get(opt.keys, i)) < 0)
+                fprintf(stderr, "JWK generation failed! (unknown issue)\n");
             return EXIT_FAILURE;
         }
     }

+ 2 - 2
cmd/meson.build

@@ -21,7 +21,7 @@ executable(meson.project_name(),
   'jwe/enc.c',
   'alg.c',
   'fmt.c',
-  dependencies: jansson,
-  link_with: libjose,
+
+  dependencies: libjose_dep,
   install: true
 )

+ 1 - 1
doc/doxygen/Doxyfile

@@ -1323,7 +1323,7 @@ CHM_FILE               =
 HHC_LOCATION           =
 
 # The GENERATE_CHI flag controls if a separate .chi index file is generated
-# (YES) or that it should be included in the master .chm file (NO).
+# (YES) or that it should be included in the primary .chm file (NO).
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 

+ 3 - 3
doc/doxygen/man/man3/jose_jws.3

@@ -55,16 +55,16 @@ json_t *sig(const char *str, const json_t *jwk) {
 Likewise, to verify this signature (again, error handling omitted): 
 .PP
 .nf
-char *ver(const json_t *jwe, const json_t *jwk) {
+char *ver(const json_t *jws, const json_t *jwk) {
     char *str = NULL;
     size_t len = 0;
 
     if (!jose_jws_ver(NULL, jws, NULL, jwk))
         return NULL;
 
-    len = jose_b64_dec(json_object_get(jwe, "payload"), NULL, 0);
+    len = jose_b64_dec(json_object_get(jws, "payload"), NULL, 0);
     str = calloc(1, len + 1);
-    jose_b64_dec(json_object_get(jwe, "payload"), str, len);
+    jose_b64_dec(json_object_get(jws, "payload"), str, len);
     return str;
 }
 

+ 1 - 0
doc/man/jose-jwe-enc.1.adoc

@@ -110,6 +110,7 @@ Compress plaintext before encryption:
     $ jose jwe enc -i '{"protected":{"zip":"DEF"}}' ...
 
 Encrypt with two keys and two passwords:
+
     $ jose jwk gen -i '{"alg":"ECDH-ES+A128KW"}' -o ec.jwk
     $ jose jwk gen -i '{"alg":"RSA1_5"}' -o rsa.jwk
     $ jose jwe enc -I msg.txt -p -k ec.jwk -p -k rsa.jwk -o msg.jwe

jose/b64.h → include/jose/b64.h


jose/cfg.h → include/jose/cfg.h


jose/io.h → include/jose/io.h


jose/jose.h.in → include/jose/jose.h.in


jose/jwe.h → include/jose/jwe.h


jose/jwk.h → include/jose/jwk.h


+ 3 - 3
jose/jws.h

@@ -34,16 +34,16 @@
  *
  * Likewise, to verify this signature (again, error handling omitted):
  *
- *     char *ver(const json_t *jwe, const json_t *jwk) {
+ *     char *ver(const json_t *jws, const json_t *jwk) {
  *         char *str = NULL;
  *         size_t len = 0;
  *
  *         if (!jose_jws_ver(NULL, jws, NULL, jwk))
  *             return NULL;
  *
- *         len = jose_b64_dec(json_object_get(jwe, "payload"), NULL, 0);
+ *         len = jose_b64_dec(json_object_get(jws, "payload"), NULL, 0);
  *         str = calloc(1, len + 1);
- *         jose_b64_dec(json_object_get(jwe, "payload"), str, len);
+ *         jose_b64_dec(json_object_get(jws, "payload"), str, len);
  *         return str;
  *     }
  *

+ 3 - 2
jose/meson.build

@@ -1,13 +1,14 @@
 cd = configuration_data()
 cd.set('VERSION', meson.project_version())
-configure_file(
+
+jose_h = configure_file(
   input: 'jose.h.in',
   output: 'jose.h',
   configuration: cd
 )
 
 install_headers(
-  meson.current_build_dir() + '/jose.h',
+  jose_h,
   'cfg.h',
   'io.h',
   'b64.h',

jose/openssl.h → include/jose/openssl.h


+ 3 - 0
include/meson.build

@@ -0,0 +1,3 @@
+incdir = include_directories('.')
+
+subdir('jose')

+ 11 - 0
lib/hooks.c

@@ -60,3 +60,14 @@ jose_hook_alg_find(jose_hook_alg_kind_t kind, const char *name)
 
     return NULL;
 }
+
+const jose_hook_alg_t *
+jose_hook_alg_find_any(const char *name)
+{
+    for (const jose_hook_alg_t *a = algs; a; a = a->next) {
+        if (strcmp(a->name, name) == 0) {
+            return a;
+        }
+    }
+    return NULL;
+}

+ 3 - 0
lib/hooks.h

@@ -184,3 +184,6 @@ jose_hook_alg_list(void);
 
 const jose_hook_alg_t *
 jose_hook_alg_find(jose_hook_alg_kind_t kind, const char *name);
+
+const jose_hook_alg_t *
+jose_hook_alg_find_any(const char *name);

+ 6 - 6
lib/hsh.c

@@ -25,7 +25,7 @@
 json_t *
 hsh(jose_cfg_t *cfg, const char *alg, const void *data, size_t dlen)
 {
-    jose_io_auto_t *hsh = NULL;
+    jose_io_auto_t *_hsh = NULL;
     jose_io_auto_t *enc = NULL;
     jose_io_auto_t *buf = NULL;
     char b[1024] = {};
@@ -33,8 +33,8 @@ hsh(jose_cfg_t *cfg, const char *alg, const void *data, size_t dlen)
 
     buf = jose_io_buffer(cfg, b, &l);
     enc = jose_b64_enc_io(buf);
-    hsh = hsh_io(cfg, alg, enc);
-    if (!buf || !enc || !hsh || !hsh->feed(hsh, data, dlen) || !hsh->done(hsh))
+    _hsh = hsh_io(cfg, alg, enc);
+    if (!buf || !enc || !_hsh || !_hsh->feed(_hsh, data, dlen) || !_hsh->done(_hsh))
         return NULL;
 
     return json_stringn(b, l);
@@ -57,7 +57,7 @@ hsh_buf(jose_cfg_t *cfg, const char *alg,
         const void *data, size_t dlen, void *hash, size_t hlen)
 {
     const jose_hook_alg_t *a = NULL;
-    jose_io_auto_t *hsh = NULL;
+    jose_io_auto_t *_hsh = NULL;
     jose_io_auto_t *buf = NULL;
 
     a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, alg);
@@ -71,8 +71,8 @@ hsh_buf(jose_cfg_t *cfg, const char *alg,
         return SIZE_MAX;
 
     buf = jose_io_buffer(cfg, hash, &hlen);
-    hsh = a->hash.hsh(a, cfg, buf);
-    if (!buf || !hsh || !hsh->feed(hsh, data, dlen) || !hsh->done(hsh))
+    _hsh = a->hash.hsh(a, cfg, buf);
+    if (!buf || !_hsh || !_hsh->feed(_hsh, data, dlen) || !_hsh->done(_hsh))
         return SIZE_MAX;
 
     return hlen;

+ 1 - 0
lib/libjose.map

@@ -16,6 +16,7 @@ LIBJOSE_1.0 {
 		jose_cfg_incref;
 		jose_cfg_set_err_func;
 		jose_hook_alg_find;
+		jose_hook_alg_find_any;
 		jose_hook_alg_list;
 		jose_hook_alg_push;
 		jose_hook_jwk_list;

+ 7 - 1
lib/meson.build

@@ -6,7 +6,7 @@ if not cc.links(code, args: flags, name: '-Wl,--version-script=...')
   flags = [ '-export-symbols-regex=^jose_.*' ]
 endif
 
-libjose = library('jose',
+libjose_lib = shared_library('jose',
   'misc.c',           'misc.h',
   'cfg.c',
   'io.c',
@@ -39,9 +39,15 @@ libjose = library('jose',
   'openssl/rsaes.c',
   'openssl/rsassa.c',
 
+  include_directories: incdir,
   dependencies: [zlib, jansson, libcrypto, threads],
   version: '0.0.0',
   link_args: flags,
   install: true
 )
 
+libjose_dep = declare_dependency(
+  include_directories: incdir,
+  dependencies: jansson,
+  link_with: libjose_lib
+)

+ 2 - 1
lib/openssl/ec.c

@@ -48,10 +48,11 @@ jwk_make_execute(jose_cfg_t *cfg, json_t *jwk)
     if (json_unpack(jwk, "{s?s}", "crv", &crv) < 0)
         return false;
 
-    switch (str2enum(crv, "P-256", "P-384", "P-521", NULL)) {
+    switch (str2enum(crv, "P-256", "P-384", "P-521", "secp256k1", NULL)) {
     case 0: nid = NID_X9_62_prime256v1; break;
     case 1: nid = NID_secp384r1; break;
     case 2: nid = NID_secp521r1; break;
+    case 3: nid = NID_secp256k1; break;
     default: return false;
     }
 

+ 25 - 4
lib/openssl/ecdsa.c

@@ -22,7 +22,7 @@
 
 #include <string.h>
 
-#define NAMES "ES256", "ES384", "ES512"
+#define NAMES "ES256", "ES384", "ES512", "ES256K"
 
 typedef struct {
     jose_io_t io;
@@ -137,6 +137,19 @@ alg2crv(const char *alg)
     case 0: return "P-256";
     case 1: return "P-384";
     case 2: return "P-521";
+    case 3: return "secp256k1";
+    default: return NULL;
+    }
+}
+
+static const char *
+alg2hash(const char *alg)
+{
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: return "S256";
+    case 1: return "S384";
+    case 2: return "S512";
+    case 3: return "S256";
     default: return NULL;
     }
 }
@@ -200,10 +213,11 @@ alg_sign_sug(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
     if (!type || strcmp(type, "EC") != 0)
         return NULL;
 
-    switch (str2enum(curv, "P-256", "P-384", "P-521", NULL)) {
+    switch (str2enum(curv, "P-256", "P-384", "P-521", "secp256k1", NULL)) {
     case 0: return "ES256";
     case 1: return "ES384";
     case 2: return "ES512";
+    case 3: return "ES256K";
     default: return NULL;
     }
 }
@@ -216,7 +230,7 @@ alg_sign_sig(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jws,
     jose_io_auto_t *io = NULL;
     io_t *i = NULL;
 
-    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, &alg->name[1]);
+    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, alg2hash(alg->name));
     if (!halg)
         return NULL;
 
@@ -248,7 +262,7 @@ alg_sign_ver(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jws,
     jose_io_auto_t *io = NULL;
     io_t *i = NULL;
 
-    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, &alg->name[1]);
+    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, alg2hash(alg->name));
     if (!halg)
         return NULL;
 
@@ -302,6 +316,13 @@ constructor(void)
           .sign.sug = alg_sign_sug,
           .sign.sig = alg_sign_sig,
           .sign.ver = alg_sign_ver },
+        { .kind = JOSE_HOOK_ALG_KIND_SIGN,
+          .name = "ES256K",
+          .sign.sprm = "sign",
+          .sign.vprm = "verify",
+          .sign.sug = alg_sign_sug,
+          .sign.sig = alg_sign_sig,
+          .sign.ver = alg_sign_ver },
         {}
     };
 

+ 3 - 3
lib/openssl/hmac.c

@@ -95,7 +95,7 @@ ver_done(jose_io_t *io)
 }
 
 static HMAC_CTX *
-hmac(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+jhmac(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
      const json_t *sig, const json_t *jwk)
 {
     uint8_t key[KEYMAX] = {};
@@ -251,7 +251,7 @@ alg_sign_sig(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jws,
 
     i->obj = json_incref(jws);
     i->sig = json_incref(sig);
-    i->hctx = hmac(alg, cfg, sig, jwk);
+    i->hctx = jhmac(alg, cfg, sig, jwk);
     if (!i->obj || !i->sig || !i->hctx)
         return NULL;
 
@@ -275,7 +275,7 @@ alg_sign_ver(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jws,
     io->free = io_free;
 
     i->sig = json_incref((json_t *) sig);
-    i->hctx = hmac(alg, cfg, sig, jwk);
+    i->hctx = jhmac(alg, cfg, sig, jwk);
     if (!i->sig || !i->hctx)
         return NULL;
 

+ 3 - 1
lib/openssl/jwk.c

@@ -169,6 +169,7 @@ jose_openssl_jwk_from_EC_POINT(jose_cfg_t *cfg, const EC_GROUP *grp,
     case NID_X9_62_prime256v1: crv = "P-256"; break;
     case NID_secp384r1: crv = "P-384"; break;
     case NID_secp521r1: crv = "P-521"; break;
+    case NID_secp256k1: crv = "secp256k1"; break;
     default: return NULL;
     }
 
@@ -366,10 +367,11 @@ jose_openssl_jwk_to_EC_KEY(jose_cfg_t *cfg, const json_t *jwk)
     if (strcmp(kty, "EC") != 0)
         return NULL;
 
-    switch (str2enum(crv, "P-256", "P-384", "P-521", NULL)) {
+    switch (str2enum(crv, "P-256", "P-384", "P-521", "secp256k1", NULL)) {
     case 0: nid = NID_X9_62_prime256v1; break;
     case 1: nid = NID_secp384r1; break;
     case 2: nid = NID_secp521r1; break;
+    case 3: nid = NID_secp256k1; break;
     default: return NULL;
     }
 

+ 7 - 2
lib/openssl/pbes2.c

@@ -25,6 +25,8 @@
 #include <string.h>
 
 #define NAMES "PBES2-HS256+A128KW", "PBES2-HS384+A192KW", "PBES2-HS512+A256KW"
+#define P2C_MIN_ITERATIONS 1000
+#define P2C_MAX_ITERATIONS 32768
 
 static json_t *
 pbkdf2(const char *alg, jose_cfg_t *cfg, const json_t *jwk, int iter,
@@ -193,7 +195,7 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
     json_auto_t *hdr = NULL;
     const char *aes = NULL;
     json_t *h = NULL;
-    int p2c = 10000;
+    int p2c = P2C_MAX_ITERATIONS;
     size_t stl = 0;
 
     if (!json_object_get(cek, "k") && !jose_jwk_gen(cfg, cek))
@@ -226,7 +228,7 @@ alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
         json_object_set_new(h, "p2c", json_integer(p2c)) < 0)
         return false;
 
-    if (p2c < 1000)
+    if (p2c < P2C_MIN_ITERATIONS || p2c > P2C_MAX_ITERATIONS)
         return false;
 
     if (json_object_set_new(h, "p2s", jose_b64_enc(st, stl)) == -1)
@@ -268,6 +270,9 @@ alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
     if (json_unpack(hdr, "{s:I}", "p2c", &p2c) == -1)
         return false;
 
+    if (p2c > P2C_MAX_ITERATIONS)
+        return false;
+
     stl = jose_b64_dec(json_object_get(hdr, "p2s"), NULL, 0);
     if (stl < 8 || stl > sizeof(st))
         return false;

+ 1 - 1
lib/openssl/rsaes.c

@@ -25,7 +25,7 @@
 
 #include <string.h>
 
-#ifdef EVP_PKEY_CTX_set_rsa_oaep_md
+#if defined (EVP_PKEY_CTX_set_rsa_oaep_md) || (OPENSSL_VERSION_NUMBER >= 0x30000000L)
 #define NAMES "RSA1_5", "RSA-OAEP", "RSA-OAEP-224", "RSA-OAEP-256", "RSA-OAEP-384", "RSA-OAEP-512"
 #define HAVE_OAEP
 #else

+ 15 - 12
meson.build

@@ -1,21 +1,20 @@
 project('jose', 'c', license: 'APL2',
-  version: '11',
+  version: '13',
   default_options: [
     'c_std=gnu99',
     'prefix=/usr',
     'warning_level=2',
     'werror=true'
-  ]
+  ],
+  meson_version: '>=0.47.0',
 )
 
 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
 
 add_project_arguments(
-  '-I' + meson.current_source_dir(),
-  '-I' + meson.current_build_dir(),
   '-Wstrict-aliasing',
   '-Wchar-subscripts',
   '-Wformat-security',
@@ -39,13 +38,14 @@ zlib = dependency('zlib')
 threads = dependency('threads')
 jansson = dependency('jansson', version: '>=2.10')
 libcrypto = dependency('libcrypto', version: '>=1.0.2')
-a2x = find_program('a2x', required: false)
+a2x = find_program('a2x', required: get_option('docs'))
+jq = find_program('jq', required: false)
 
 mans = []
 
 licenses = ['COPYING']
 
-subdir('jose')
+subdir('include')
 subdir('doc')
 subdir('lib')
 subdir('cmd')
@@ -59,10 +59,9 @@ pkg.generate(
   version: meson.project_version(),
   filebase: meson.project_name(),
   name: 'José Library',
-
-  requires_private: [ 'zlib', 'libcrypto' ],
-  libraries: libjose,
-  requires: 'jansson',
+  libraries_private: [ zlib, libcrypto ],
+  libraries: libjose_lib,
+  requires: jansson,
 )
 
 if a2x.found()
@@ -73,6 +72,10 @@ if a2x.found()
       install: true
     )
   endforeach
-else
+elif get_option('docs').auto()
   warning('Will not build man pages due to missing dependencies!')
 endif
+
+if not jq.found()
+  message('jq not found (unrequired but recommended)')
+endif

+ 1 - 0
meson_options.txt

@@ -0,0 +1 @@
+option('docs', type: 'feature', description: 'Whether to build asciidoc manpages')

+ 32 - 23
tests/alg_comp.c

@@ -41,22 +41,23 @@ const struct {
     {}
 };
 
-typedef typeof(((jose_hook_alg_t *) NULL)->comp.inf) comp_func_t;
-
 static void
-test(const jose_hook_alg_t *a, comp_func_t func, bool iter,
-     const uint8_t *i, size_t il,
-     const uint8_t *o, size_t ol)
+test(const jose_hook_alg_t *a, bool iter,
+     const uint8_t *i, size_t il)
 {
     jose_io_auto_t *b = NULL;
+    jose_io_auto_t *c = NULL;
     jose_io_auto_t *z = NULL;
-    void *buf = NULL;
-    size_t len = 0;
+    void *buf1 = NULL;
+    void *buf2 = NULL;
+    size_t blen = 0;
+    size_t clen = 0;
 
-    b = jose_io_malloc(NULL, &buf, &len);
+    /* Test compression first. */
+    b = jose_io_malloc(NULL, &buf1, &blen);
     assert(b);
 
-    z = func(a, NULL, b);
+    z = a->comp.def(a, NULL, b);
     assert(z);
 
     if (iter) {
@@ -68,8 +69,26 @@ test(const jose_hook_alg_t *a, comp_func_t func, bool iter,
 
     assert(z->done(z));
 
-    assert(len == ol);
-    assert(memcmp(buf, o, ol) == 0);
+    /* Test decompression now. */
+    c = jose_io_malloc(NULL, &buf2, &clen);
+    assert(b);
+
+    z = a->comp.inf(a, NULL, c);
+    assert(z);
+
+    if (iter) {
+        uint8_t *m = buf1;
+        for (size_t j = 0; j < blen; j++)
+            assert(z->feed(z, &m[j], 1));
+    } else {
+        assert(z->feed(z, buf1, blen));
+    }
+
+    assert(z->done(z));
+
+    /* Compare the final output with the original input. */
+    assert(clen == il);
+    assert(memcmp(buf2, i, il) == 0);
 }
 
 int
@@ -93,20 +112,10 @@ main(int argc, char *argv[])
         assert(jose_b64_dec_buf(tests[i].def, strlen(tests[i].def),
                                 tst_def, sizeof(tst_def)) == sizeof(tst_def));
 
-        test(a, a->comp.def, false,
-             tst_inf, sizeof(tst_inf),
-             tst_def, sizeof(tst_def));
-
-        test(a, a->comp.inf, false,
-             tst_def, sizeof(tst_def),
+        test(a, false,
              tst_inf, sizeof(tst_inf));
 
-        test(a, a->comp.def, true,
-             tst_inf, sizeof(tst_inf),
-             tst_def, sizeof(tst_def));
-
-        test(a, a->comp.inf, true,
-             tst_def, sizeof(tst_def),
+        test(a, true,
              tst_inf, sizeof(tst_inf));
     }
 

+ 6 - 3
tests/alg_encr.c

@@ -64,9 +64,12 @@ test(const jose_hook_alg_t *a, const char *pt, json_t *cek, bool iter)
     assert(d);
 
     if (iter) {
-        uint8_t *xxx = ebuf;
-        for (size_t i = 0; i < elen; i++)
-            assert(d->feed(d, &xxx[i], 1));
+	if (elen) {
+	    uint8_t *xxx = ebuf;
+	    for (size_t i = 0; i < elen; i++) {
+		assert(d->feed(d, &xxx[i], 1));
+	    }
+	}
     } else {
         assert(d->feed(d, ebuf, elen));
     }

+ 3 - 0
tests/alg_hash.c

@@ -87,6 +87,9 @@ main(int argc, char *argv[])
     for (size_t i = 0; v[i].alg; i++) {
         const jose_hook_alg_t *alg = NULL;
 
+        alg = jose_hook_alg_find_any(v[i].alg);
+        assert(alg);
+
         alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, v[i].alg);
         assert(alg);
 

+ 3 - 0
tests/api_b64.c

@@ -62,6 +62,7 @@ main(int argc, char *argv[])
     for (uint16_t i = 0; i <= UINT8_MAX; i++) {
         union encoding enc = { i };
         uint8_t dec[3] = {};
+        assert(dec != NULL);
         assert(jose_b64_dec_buf(enc.enc, 1, dec, sizeof(dec)) == SIZE_MAX);
     }
 
@@ -74,6 +75,7 @@ main(int argc, char *argv[])
     for (uint16_t i = 0; i <= UINT8_MAX; i++) {
         uint8_t dec[3] = { i };
         union encoding enc = {};
+        assert(dec != NULL);
         assert(jose_b64_enc_buf(dec, 1, enc.enc, sizeof(enc.enc)) == 2);
         set(val, enc.idx);
     }
@@ -106,6 +108,7 @@ main(int argc, char *argv[])
         for (uint16_t j = 0; j <= UINT8_MAX; j++) {
             uint8_t dec[3] = { i, j };
             union encoding enc = {};
+            assert(dec != NULL);
             assert(jose_b64_enc_buf(dec, 2, enc.enc, sizeof(enc.enc)) == 3);
             set(val, enc.idx);
         }

+ 1 - 0
tests/cve-2023-50967/cve-2023-50967.jwe

@@ -0,0 +1 @@
+{"ciphertext":"aaPb-JYGACs-loPwJkZewg","encrypted_key":"P1h8q8wLVxqYsZUuw6iEQTzgXVZHCsu8Eik-oqbE4AJGIDto3gb3SA","header":{"alg":"PBES2-HS256+A128KW","p2c":1000000000,"p2s":"qUQQWWkyyIqculSiC93mlg"},"iv":"Clg3JX9oNl_ck3sLSGrlgg","protected":"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"i7vga9tJkwRswFd7HlyD_A"}

+ 1 - 0
tests/cve-2023-50967/cve-2023-50967.jwk

@@ -0,0 +1 @@
+{"alg":"PBES2-HS256+A128KW","k":"VHBLJ4-PmnqELoKbQoXuRA","key_ops":["wrapKey","unwrapKey"],"kty":"oct"}

+ 2 - 3
tests/issue-75/meson.build

@@ -1,9 +1,8 @@
 e = environment()
 
-openssl = dependency('openssl', required: false)
+openssl = dependency('openssl', version: '>= 1.1.0',  required: false)
 if openssl.found()
   issue75 = executable('issue75', 'issue-75.c',
-                       dependencies: [jansson, openssl],
-                       link_with: libjose)
+                       dependencies: [libjose_dep, openssl])
   test('issue75', issue75, workdir : meson.current_source_dir(), env: e, timeout: 30)
 endif

+ 0 - 2
tests/jose-fmt

@@ -1,7 +1,5 @@
 #!/bin/sh -ex
 
-export PATH=../cmd:$PATH
-
 jose fmt -j '{}' -O
 ! jose fmt -j '{}' -A
 ! jose fmt -j '{}' -S

+ 5 - 0
tests/jose-jwe-dec

@@ -53,3 +53,8 @@ test "`jose jwe dec -i $prfx.12.jweg -k $prfx.12.jwk`"   = "`cat $prfx.12.pt`"
 test "`jose jwe dec -i $prfx.13.jweg -k $prfx.13.1.jwk`" = "`cat $prfx.13.pt`"
 test "`jose jwe dec -i $prfx.13.jweg -k $prfx.13.2.jwk`" = "`cat $prfx.13.pt`"
 test "`jose jwe dec -i $prfx.13.jweg -k $prfx.13.3.jwk`" = "`cat $prfx.13.pt`"
+
+# CVE-2023-50967 - test originally from https://github.com/P3ngu1nW/CVE_Request/blob/main/latch-jose.md
+# This test is expected to fail quickly on patched systems.
+prfx="${CVE_2023_50967}/cve-2023-50967"
+! test "$(jose jwe dec -i $prfx.jwe -k $prfx.jwk)"

+ 2 - 2
tests/jose-jwe-enc

@@ -15,7 +15,7 @@ jwk=$tmpdir/jwk
 jwe=$tmpdir/jwe
 
 jqopt() {
-    if ! which jq >/dev/null 2>&1; then
+    if ! command -v jq >/dev/null 2>&1; then
         echo "$3"
     else
         jq -r "if $2 | type | . = \"string\" then $2 else error(\"\") end" < $1
@@ -23,7 +23,7 @@ jqopt() {
 }
 
 jqbopt() {
-    if ! which jq >/dev/null 2>&1; then
+    if ! command -v jq >/dev/null 2>&1; then
         echo "$4"
     else
         jq -r "if $2 | type | . = \"string\" then $2 else error(\"\") end" < $1 \

+ 7 - 0
tests/jose-jwk-gen

@@ -17,6 +17,7 @@ done
 jose jwk gen -i '{ "kty": "EC", "crv": "P-256" }'
 jose jwk gen -i '{ "kty": "EC", "crv": "P-384" }'
 jose jwk gen -i '{ "kty": "EC", "crv": "P-521" }'
+jose jwk gen -i '{ "kty": "EC", "crv": "secp256k1" }'
 
 jose jwk gen -i '{ "kty": "RSA", "bits": 3072 }'
 ! jose jwk gen -i '{ "kty": "RSA", "bits": 3072, "e": 257 }'
@@ -34,6 +35,12 @@ jose jwk gen -i '{ "kty": "oct", "bytes": 32 }'
 ! jose jwk gen -i '{"alg": "dir"}'
 
 ##
+### Test invalid keys
+##
+
+! jose jwk gen -i '{"not_valid": "RS256"}'
+
+##
 ### Test the set output option
 ##
 

+ 0 - 2
tests/jose-jwk-use

@@ -1,7 +1,5 @@
 #!/bin/sh -ex
 
-export PATH=../cmd:$PATH
-
 echo '{}' | jose jwk use -i- -u encrypt
 ! echo '{}' | jose jwk use -i- -r -u encrypt
 

+ 5 - 3
tests/meson.build

@@ -31,20 +31,22 @@ progs = [
 e = environment()
 e.prepend('PATH', meson.current_build_dir() + '/../cmd', separator: ':')
 e.set('VECTORS', meson.current_source_dir() + '/vectors')
+e.set('CVE_2023_50967', meson.current_source_dir() + '/cve-2023-50967')
+
 
 foreach p: progs
-  exe = executable(p, p + '.c', dependencies: jansson, link_with: libjose)
+  exe = executable(p, p + '.c', dependencies: libjose_dep)
   if p == 'api_b64'
     to = 1800
   else
-    to = 30
+    to = 180
   endif
   test(p, exe, timeout: to)
 endforeach
 
 foreach s: scripts
   exe = find_program('./' + s)
-  test(s, exe, env: e, timeout: 60)
+  test(s, exe, env: e, timeout: 900)
 endforeach
 
 subdir('issue-75')