2 Commits 99d9a8b74b ... 96785d4df4

Author SHA1 Message Date
  Sergio Arroutbi 96785d4df4 Import upstream version 14 7 months ago
  Sergio Arroutbi c09ecacff0 Import upstream version 13 8 months ago
58 changed files with 715 additions and 269 deletions
  1. 131 0
      .github/workflows/build.yml
  2. 51 0
      .github/workflows/install-dependencies
  3. 47 0
      .gitignore
  4. 0 34
      .travis.docker
  5. 0 52
      .travis.install
  6. 0 30
      .travis.script
  7. 0 40
      .travis.yml
  8. 5 3
      README.md
  9. 1 1
      cmd/jose.c
  10. 40 1
      cmd/jwk/gen.c
  11. 2 2
      cmd/meson.build
  12. 1 1
      doc/doxygen/Doxyfile
  13. 3 3
      doc/doxygen/man/man3/jose_jws.3
  14. 1 0
      doc/man/jose-jwe-enc.1.adoc
  15. 0 0
      include/jose/b64.h
  16. 0 0
      include/jose/cfg.h
  17. 0 0
      include/jose/io.h
  18. 0 0
      include/jose/jose.h.in
  19. 0 0
      include/jose/jwe.h
  20. 0 0
      include/jose/jwk.h
  21. 3 3
      jose/jws.h
  22. 3 2
      jose/meson.build
  23. 0 0
      include/jose/openssl.h
  24. 3 0
      include/meson.build
  25. 11 0
      lib/hooks.c
  26. 5 0
      lib/hooks.h
  27. 6 6
      lib/hsh.c
  28. 7 19
      lib/jwe.c
  29. 1 0
      lib/libjose.map
  30. 15 3
      lib/meson.build
  31. 58 0
      lib/misc.c
  32. 6 0
      lib/misc.h
  33. 7 2
      lib/openssl/aescbch.c
  34. 8 2
      lib/openssl/aesgcm.c
  35. 2 1
      lib/openssl/ec.c
  36. 25 4
      lib/openssl/ecdsa.c
  37. 3 3
      lib/openssl/hmac.c
  38. 3 1
      lib/openssl/jwk.c
  39. 1 1
      lib/openssl/oct.c
  40. 7 2
      lib/openssl/pbes2.c
  41. 1 1
      lib/openssl/rsaes.c
  42. 3 0
      lib/zlib/deflate.c
  43. 15 12
      meson.build
  44. 1 0
      meson_options.txt
  45. 99 22
      tests/alg_comp.c
  46. 6 3
      tests/alg_encr.c
  47. 3 0
      tests/alg_hash.c
  48. 3 0
      tests/api_b64.c
  49. 96 3
      tests/api_jwe.c
  50. 1 0
      tests/cve-2023-50967/cve-2023-50967.jwe
  51. 1 0
      tests/cve-2023-50967/cve-2023-50967.jwk
  52. 2 3
      tests/issue-75/meson.build
  53. 0 2
      tests/jose-fmt
  54. 5 0
      tests/jose-jwe-dec
  55. 11 2
      tests/jose-jwe-enc
  56. 7 0
      tests/jose-jwk-gen
  57. 0 2
      tests/jose-jwk-use
  58. 5 3
      tests/meson.build

+ 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;
+}

+ 5 - 0
lib/hooks.h

@@ -20,6 +20,8 @@
 #include <jose/jws.h>
 #include <jose/jwe.h>
 
+#define MAX_COMPRESSED_SIZE (256*1024)
+
 typedef enum {
     JOSE_HOOK_JWK_KIND_NONE = 0,
     JOSE_HOOK_JWK_KIND_TYPE,
@@ -184,3 +186,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;

+ 7 - 19
lib/jwe.c

@@ -275,14 +275,8 @@ jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
                     jose_io_t *next)
 {
     const jose_hook_alg_t *alg = NULL;
-    jose_io_auto_t *zip = NULL;
-    json_auto_t *prt = NULL;
     const char *h = NULL;
     const char *k = NULL;
-    const char *z = NULL;
-
-    prt = jose_b64_dec_load(json_object_get(jwe, "protected"));
-    (void) json_unpack(prt, "{s:s}", "zip", &z);
 
     if (json_unpack(jwe, "{s?{s?s}}", "unprotected", "enc", &h) < 0)
         return NULL;
@@ -336,19 +330,7 @@ jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
     if (!encode_protected(jwe))
         return NULL;
 
-    if (z) {
-        const jose_hook_alg_t *a = NULL;
-
-        a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z);
-        if (!a)
-            return NULL;
-
-        zip = a->comp.def(a, cfg, next);
-        if (!zip)
-            return NULL;
-    }
-
-    return alg->encr.enc(alg, cfg, jwe, cek, zip ? zip : next);
+    return alg->encr.enc(alg, cfg, jwe, cek, next);
 }
 
 void *
@@ -463,6 +445,12 @@ jose_jwe_dec_cek(jose_cfg_t *cfg, const json_t *jwe, const json_t *cek,
     o = jose_io_malloc(cfg, &pt, ptl);
     d = jose_jwe_dec_cek_io(cfg, jwe, cek, o);
     i = jose_b64_dec_io(d);
+
+    /* Here we make sure the ciphertext is not larger than our
+     * compression limit. */
+    if (zip_in_protected_header((json_t*)jwe) && ctl > MAX_COMPRESSED_SIZE)
+        return false;
+
     if (!o || !d || !i || !i->feed(i, ct, ctl) || !i->done(i))
         return NULL;
 

+ 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;

+ 15 - 3
lib/meson.build

@@ -2,11 +2,17 @@ flags = '-Wl,--version-script=' + meson.current_source_dir() + '/libjose.map'
 code = 'int main() { return 0; }'
 cc = meson.get_compiler('c')
 
-if not cc.links(code, args: flags, name: '-Wl,--version-script=...')
-  flags = [ '-export-symbols-regex=^jose_.*' ]
+if host_machine.system() == 'freebsd'
+  if not cc.links(code, args: flags + ',--undefined-version' , name: '-Wl,--version-script=...')
+     flags = [ '-export-symbols-regex=^jose_.*' ]
+  endif
+else
+  if not cc.links(code, args: flags, name: '-Wl,--version-script=...')
+     flags = [ '-export-symbols-regex=^jose_.*' ]
+  endif
 endif
 
-libjose = library('jose',
+libjose_lib = shared_library('jose',
   'misc.c',           'misc.h',
   'cfg.c',
   'io.c',
@@ -39,9 +45,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
+)

+ 58 - 0
lib/misc.c

@@ -18,6 +18,7 @@
 #include "misc.h"
 #include <jose/b64.h>
 #include <string.h>
+#include "hooks.h"
 
 bool
 encode_protected(json_t *obj)
@@ -42,6 +43,63 @@ zero(void *mem, size_t len)
     memset(mem, 0, len);
 }
 
+
+bool
+handle_zip_enc(json_t *json, const void *in, size_t len, void **data, size_t *datalen)
+{
+    json_t *prt = NULL;
+    char *z = NULL;
+    const jose_hook_alg_t *a = NULL;
+    jose_io_auto_t *zip = NULL;
+    jose_io_auto_t *zipdata = NULL;
+
+    prt = json_object_get(json, "protected");
+    if (prt && json_is_string(prt))
+        prt = jose_b64_dec_load(prt);
+
+    /* Check if we have "zip" in the protected header. */
+    if (json_unpack(prt, "{s:s}", "zip", &z) == -1) {
+        /* No zip. */
+        *data = (void*)in;
+        *datalen = len;
+        return true;
+    }
+
+    /* OK, we have "zip", so we should compress the payload before
+     * the encryption takes place. */
+    a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z);
+    if (!a)
+        return false;
+
+    zipdata = jose_io_malloc(NULL, data, datalen);
+    if (!zipdata)
+        return false;
+
+    zip = a->comp.def(a, NULL, zipdata);
+    if (!zip || !zip->feed(zip, in, len) || !zip->done(zip))
+        return false;
+
+    return true;
+}
+
+bool
+zip_in_protected_header(json_t *json)
+{
+    json_t *prt = NULL;
+    char *z = NULL;
+
+    prt = json_object_get(json, "protected");
+    if (prt && json_is_string(prt))
+        prt = jose_b64_dec_load(prt);
+
+    /* Check if we have "zip" in the protected header. */
+    if (json_unpack(prt, "{s:s}", "zip", &z) == -1)
+        return false;
+
+    /* We have "zip", but let's validate the alg also. */
+    return jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, z) != NULL;
+}
+
 static void __attribute__((constructor))
 constructor(void)
 {

+ 6 - 0
lib/misc.h

@@ -30,3 +30,9 @@ encode_protected(json_t *obj);
 
 void
 zero(void *mem, size_t len);
+
+bool
+handle_zip_enc(json_t *jwe, const void *in, size_t len, void **data, size_t *data_len);
+
+bool
+zip_in_protected_header(json_t *jwe);

+ 7 - 2
lib/openssl/aescbch.c

@@ -18,6 +18,7 @@
 #include "misc.h"
 #include <jose/b64.h>
 #include "../hooks.h"
+#include "../misc.h"
 
 #include <openssl/rand.h>
 #include <openssl/sha.h>
@@ -155,9 +156,13 @@ enc_feed(jose_io_t *io, const void *in, size_t len)
     io_t *i = containerof(io, io_t, io);
 
     uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
-    const uint8_t *pt = in;
+    uint8_t *pt = NULL;
+    size_t ptlen = 0;
 
-    for (size_t j = 0; j < len; j++) {
+    if (!handle_zip_enc(i->json, in, len, (void**)&pt, &ptlen))
+        return false;
+
+    for (size_t j = 0; j < ptlen; j++) {
         int l = 0;
 
         if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0)

+ 8 - 2
lib/openssl/aesgcm.c

@@ -18,6 +18,7 @@
 #include "misc.h"
 #include <jose/b64.h>
 #include "../hooks.h"
+#include "../misc.h"
 
 #include <openssl/rand.h>
 
@@ -103,10 +104,15 @@ static bool
 enc_feed(jose_io_t *io, const void *in, size_t len)
 {
     io_t *i = containerof(io, io_t, io);
-    const uint8_t *pt = in;
     int l = 0;
 
-    for (size_t j = 0; j < len; j++) {
+    uint8_t *pt = NULL;
+    size_t ptlen = 0;
+
+    if (!handle_zip_enc(i->json, in, len, (void**)&pt, &ptlen))
+        return false;
+
+    for (size_t j = 0; j < ptlen; j++) {
         uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
 
         if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0)

+ 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;
     }
 

+ 1 - 1
lib/openssl/oct.c

@@ -45,7 +45,7 @@ jwk_make_execute(jose_cfg_t *cfg, json_t *jwk)
     if (json_unpack(jwk, "{s:I}", "bytes", &len) < 0)
         return false;
 
-    if (len > KEYMAX)
+    if (len <= 0 || len > KEYMAX)
         return false;
 
     if (RAND_bytes(key, len) <= 0)

+ 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

+ 3 - 0
lib/zlib/deflate.c

@@ -113,6 +113,9 @@ def_free(jose_io_t *io)
 static bool
 inf_feed(jose_io_t *io, const void *in, size_t len)
 {
+    if (len > MAX_COMPRESSED_SIZE) {
+        return false;
+    }
     return feed(io, in, len, inflate);
 }
 

+ 15 - 12
meson.build

@@ -1,21 +1,20 @@
 project('jose', 'c', license: 'APL2',
-  version: '11',
+  version: '14',
   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')

+ 99 - 22
tests/alg_comp.c

@@ -19,6 +19,10 @@
 #include <jose/jose.h>
 #include <assert.h>
 #include <string.h>
+#include <stdlib.h>
+
+static int g_high_compression_tested = 0;
+static int g_low_compression_tested = 0;
 
 const struct {
     const char *alg;
@@ -41,22 +45,80 @@ const struct {
     {}
 };
 
-typedef typeof(((jose_hook_alg_t *) NULL)->comp.inf) comp_func_t;
+const uint32_t long_string_tests[] = {
+    2000, 200000, 10000000, 0
+};
+
+static uint8_t* get_random_string(uint32_t length)
+{
+    assert(length);
+    uint8_t* c = (uint8_t*)malloc(length*sizeof(uint8_t));
+    assert(c);
+    for (uint32_t i=0; i<length; i++) {
+        c[i] = 'A' + (random() % 26);
+    }
+    return c;
+}
 
 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_long_string(size_t inputlen) {
+    jose_io_auto_t *b = NULL;
+    jose_io_auto_t *c = NULL;
+    jose_io_auto_t *z = NULL;
+    void *buf1 = NULL;
+    void *buf2 = NULL;
+    size_t blen = 0;
+    size_t clen = 0;
+    const jose_hook_alg_t *a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, "DEF");
+    uint8_t* str = get_random_string(inputlen);
+
+    /* Test compression first. */
+    b = jose_io_malloc(NULL, &buf1, &blen);
+    assert(b);
+    z = a->comp.def(a, NULL, b);
+    assert(z);
+
+    assert(z->feed(z, str, inputlen));
+    assert(z->done(z));
+
+    /* Test decompression now */
+    c = jose_io_malloc(NULL, &buf2, &clen);
+    assert(b);
+    z = a->comp.inf(a, NULL, c);
+    assert(z);
+
+    /* If length>MAX_COMPRESSED_SIZE, it must fail due to high decompression size */
+    if(blen > MAX_COMPRESSED_SIZE) {
+        assert(!z->feed(z, buf1, blen));
+        g_high_compression_tested = 1;
+    } else {
+        assert(z->feed(z, buf1, blen));
+	g_low_compression_tested = 1;
+        /* Compare the final output with the original input. */
+        assert(clen == inputlen);
+        assert(memcmp(buf2, str, inputlen) == 0);
+    }
+    assert(z->done(z));
+    free(str);
+}
+
+static void
+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 +130,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,22 +173,19 @@ 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));
     }
 
+    for (size_t i = 0; long_string_tests[i]; i++) {
+        test_long_string(long_string_tests[i]);
+    }
+
+    assert(1 == g_high_compression_tested);
+    assert(1 == g_low_compression_tested);
+
     return EXIT_SUCCESS;
 }

+ 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);
         }

+ 96 - 3
tests/api_jwe.c

@@ -19,8 +19,10 @@
 #include <assert.h>
 #include <string.h>
 
+#include "../lib/hooks.h" /* for MAX_COMPRESSED_SIZE */
+
 static bool
-dec(json_t *jwe, json_t *jwk)
+dec_cmp(json_t *jwe, json_t *jwk, const char* expected_data, size_t expected_len)
 {
     bool ret = false;
     char *pt = NULL;
@@ -30,10 +32,10 @@ dec(json_t *jwe, json_t *jwk)
     if (!pt)
         goto error;
 
-    if (ptl != 4)
+    if (ptl != expected_len)
         goto error;
 
-    if (strcmp(pt, "foo") != 0)
+    if (strcmp(pt, expected_data) != 0)
         goto error;
 
     ret = true;
@@ -43,12 +45,40 @@ error:
     return ret;
 }
 
+static bool
+dec(json_t *jwe, json_t *jwk)
+{
+    return dec_cmp(jwe, jwk, "foo", 4);
+}
+
+struct zip_test_data_t {
+    char* data;
+    size_t datalen;
+    bool expected;
+};
+
+static char*
+make_data(size_t len)
+{
+    assert(len > 0);
+
+    char *data = malloc(len);
+    assert(data);
+
+    for (size_t i = 0; i < len; i++) {
+        data[i] = 'A' + (random() % 26);
+    }
+    data[len-1] = '\0';
+    return data;
+}
+
 int
 main(int argc, char *argv[])
 {
     json_auto_t *jwke = json_pack("{s:s}", "alg", "ECDH-ES+A128KW");
     json_auto_t *jwkr = json_pack("{s:s}", "alg", "RSA1_5");
     json_auto_t *jwko = json_pack("{s:s}", "alg", "A128KW");
+    json_auto_t *jwkz = json_pack("{s:s, s:i}", "kty", "oct", "bytes", 16);
     json_auto_t *set0 = json_pack("{s:[O,O]}", "keys", jwke, jwko);
     json_auto_t *set1 = json_pack("{s:[O,O]}", "keys", jwkr, jwko);
     json_auto_t *set2 = json_pack("{s:[O,O]}", "keys", jwke, jwkr);
@@ -57,6 +87,7 @@ main(int argc, char *argv[])
     assert(jose_jwk_gen(NULL, jwke));
     assert(jose_jwk_gen(NULL, jwkr));
     assert(jose_jwk_gen(NULL, jwko));
+    assert(jose_jwk_gen(NULL, jwkz));
 
     json_decref(jwe);
     assert((jwe = json_object()));
@@ -98,5 +129,67 @@ main(int argc, char *argv[])
     assert(dec(jwe, set1));
     assert(dec(jwe, set2));
 
+
+    json_decref(jwe);
+    assert((jwe = json_pack("{s:{s:s,s:s,s:s,s:s}}", "protected", "alg", "A128KW", "enc", "A128GCM", "typ", "JWE", "zip", "DEF")));
+    assert(jose_jwe_enc(NULL, jwe, NULL, jwkz, "foo", 4));
+    assert(dec(jwe, jwkz));
+    assert(!dec(jwe, jwkr));
+    assert(!dec(jwe, jwko));
+    assert(!dec(jwe, set0));
+    assert(!dec(jwe, set1));
+    assert(!dec(jwe, set2));
+
+    /* Some tests with "zip": "DEF" */
+    struct zip_test_data_t zip[] = {
+        {
+            .data =  make_data(5),
+            .datalen = 5,
+            .expected = true,
+        },
+        {
+            .data =  make_data(50),
+            .datalen = 50,
+            .expected = true,
+        },
+        {
+            .data =  make_data(1000),
+            .datalen = 1000,
+            .expected = true,
+        },
+        {
+            .data =  make_data(10000000),
+            .datalen = 10000000,
+            .expected = false, /* compressed len will be ~8000000+
+                                * (i.e. > MAX_COMPRESSED_SIZE)
+                                */
+        },
+        {
+            .data =  make_data(50000),
+            .datalen = 50000,
+            .expected = true
+        },
+        {
+
+            .data = NULL
+        }
+    };
+
+    for (size_t i = 0; zip[i].data != NULL; i++) {
+        json_decref(jwe);
+        assert((jwe = json_pack("{s:{s:s,s:s,s:s,s:s}}", "protected", "alg", "A128KW", "enc", "A128GCM", "typ", "JWE", "zip", "DEF")));
+        assert(jose_jwe_enc(NULL, jwe, NULL, jwkz, zip[i].data, zip[i].datalen));
+
+        /* Now let's get the ciphertext compressed len. */
+        char *ct = NULL;
+        size_t ctl = 0;
+        assert(json_unpack(jwe, "{s:s%}", "ciphertext", &ct, &ctl) != -1);
+        /* And check our expectation is correct. */
+        assert(zip[i].expected == (ctl < MAX_COMPRESSED_SIZE));
+
+        assert(dec_cmp(jwe, jwkz, zip[i].data, zip[i].datalen) == zip[i].expected);
+        free(zip[i].data);
+        zip[i].data = NULL;
+    }
     return EXIT_SUCCESS;
 }

+ 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)"

+ 11 - 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 \
@@ -74,4 +74,13 @@ for msg in "hi" "this is a longer message that is more than one block"; do
         printf '%s' "$msg" | jose jwe enc -I- -k $jwk -o $jwe
         [ "`jose jwe dec -i $jwe -k $jwk -O-`" = "$msg" ]
     done
+
+    # "zip": "DEF"
+    tmpl='{"kty":"oct","bytes":32}'
+    for enc in A128CBC-HS256 A192CBC-HS384 A256CBC-HS512 A128GCM A192GCM A256GCM; do
+        jose jwk gen -i "${tmpl}" -o "${jwk}"
+        zip="$(printf '{"alg":"A128KW","enc":"%s","zip":"DEF"}' "${enc}")"
+        printf '%s' "${msg}" | jose jwe enc -i "${zip}" -I- -k "${jwk}" -o "${jwe}"
+        [ "$(jose jwe dec -i "${jwe}" -k "${jwk}" -O-)" = "${msg}" ]
+    done
 done

+ 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')