Browse Source

Import upstream version 10

Nathaniel McCallum 6 years ago
commit
1472d5ebaa
100 changed files with 48697 additions and 0 deletions
  1. 175 0
      COPYING
  2. 4 0
      Makefile.am
  3. 860 0
      Makefile.in
  4. 10528 0
      aclocal.m4
  5. 27 0
      cmd/Makefile.am
  6. 735 0
      cmd/Makefile.in
  7. 151 0
      cmd/alg.c
  8. 32 0
      cmd/b64/b64.h
  9. 95 0
      cmd/b64/dec.c
  10. 94 0
      cmd/b64/enc.c
  11. 695 0
      cmd/fmt.c
  12. 537 0
      cmd/jose.c
  13. 113 0
      cmd/jose.h
  14. 219 0
      cmd/jwe/dec.c
  15. 344 0
      cmd/jwe/enc.c
  16. 180 0
      cmd/jwe/fmt.c
  17. 59 0
      cmd/jwe/jwe.h
  18. 77 0
      cmd/jwe/pwd.h
  19. 70 0
      cmd/jwk/eql.c
  20. 144 0
      cmd/jwk/exc.c
  21. 114 0
      cmd/jwk/gen.c
  22. 38 0
      cmd/jwk/jwk.h
  23. 107 0
      cmd/jwk/pub.c
  24. 188 0
      cmd/jwk/thp.c
  25. 198 0
      cmd/jwk/use.c
  26. 154 0
      cmd/jws/fmt.c
  27. 112 0
      cmd/jws/jws.h
  28. 244 0
      cmd/jws/sig.c
  29. 172 0
      cmd/jws/ver.c
  30. 348 0
      compile
  31. 1476 0
      config.guess
  32. 1836 0
      config.sub
  33. 14785 0
      configure
  34. 65 0
      configure.ac
  35. 791 0
      depcomp
  36. 28 0
      doc/Makefile.am
  37. 594 0
      doc/Makefile.in
  38. 207 0
      doc/doxygen/man/man3/jose_b64.3
  39. 158 0
      doc/doxygen/man/man3/jose_cfg.3
  40. 219 0
      doc/doxygen/man/man3/jose_io.3
  41. 97 0
      doc/doxygen/man/man3/jose_io_t.3
  42. 514 0
      doc/doxygen/man/man3/jose_jwe.3
  43. 237 0
      doc/doxygen/man/man3/jose_jwk.3
  44. 233 0
      doc/doxygen/man/man3/jose_jws.3
  45. 48 0
      doc/ronn/jose-alg.1
  46. 51 0
      doc/ronn/jose-b64-dec.1
  47. 51 0
      doc/ronn/jose-b64-enc.1
  48. 262 0
      doc/ronn/jose-fmt.1
  49. 111 0
      doc/ronn/jose-jwe-dec.1
  50. 141 0
      doc/ronn/jose-jwe-enc.1
  51. 101 0
      doc/ronn/jose-jwe-fmt.1
  52. 92 0
      doc/ronn/jose-jwk-exc.1
  53. 112 0
      doc/ronn/jose-jwk-gen.1
  54. 66 0
      doc/ronn/jose-jwk-pub.1
  55. 95 0
      doc/ronn/jose-jwk-thp.1
  56. 111 0
      doc/ronn/jose-jwk-use.1
  57. 101 0
      doc/ronn/jose-jws-fmt.1
  58. 141 0
      doc/ronn/jose-jws-sig.1
  59. 106 0
      doc/ronn/jose-jws-ver.1
  60. 98 0
      doc/ronn/jose.1
  61. 501 0
      install-sh
  62. 12 0
      jose.pc.in
  63. 10 0
      jose/Makefile.am
  64. 578 0
      jose/Makefile.in
  65. 138 0
      jose/b64.h
  66. 137 0
      jose/cfg.h
  67. 198 0
      jose/io.h
  68. 43 0
      jose/jose.h
  69. 43 0
      jose/jose.h.in
  70. 446 0
      jose/jwe.h
  71. 198 0
      jose/jwk.h
  72. 197 0
      jose/jws.h
  73. 45 0
      jose/openssl.h
  74. 57 0
      lib/Makefile.am
  75. 807 0
      lib/Makefile.in
  76. 390 0
      lib/b64.c
  77. 133 0
      lib/cfg.c
  78. 62 0
      lib/hooks.c
  79. 186 0
      lib/hooks.h
  80. 79 0
      lib/hsh.c
  81. 82 0
      lib/hsh.h
  82. 332 0
      lib/io.c
  83. 527 0
      lib/jwe.c
  84. 490 0
      lib/jwk.c
  85. 317 0
      lib/jws.c
  86. 71 0
      lib/libjose.map
  87. 49 0
      lib/misc.c
  88. 32 0
      lib/misc.h
  89. 443 0
      lib/openssl/aescbch.c
  90. 380 0
      lib/openssl/aesgcm.c
  91. 276 0
      lib/openssl/aesgcmkw.c
  92. 268 0
      lib/openssl/aeskw.c
  93. 239 0
      lib/openssl/compat.c
  94. 75 0
      lib/openssl/compat.h
  95. 106 0
      lib/openssl/dir.c
  96. 77 0
      lib/openssl/ec.c
  97. 139 0
      lib/openssl/ecdh.c
  98. 483 0
      lib/openssl/ecdhes.c
  99. 310 0
      lib/openssl/ecdsa.c
  100. 0 0
      lib/openssl/ecmr.c

+ 175 - 0
COPYING

@@ -0,0 +1,175 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.

+ 4 - 0
Makefile.am

@@ -0,0 +1,4 @@
+SUBDIRS = . jose lib cmd tests doc
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = jose.pc

+ 860 - 0
Makefile.in

@@ -0,0 +1,860 @@
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+subdir = .
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \
+	$(am__configure_deps) $(am__DIST_COMMON)
+am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
+ configure.lineno config.status.lineno
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES = jose.pc
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+	ctags-recursive dvi-recursive html-recursive info-recursive \
+	install-data-recursive install-dvi-recursive \
+	install-exec-recursive install-html-recursive \
+	install-info-recursive install-pdf-recursive \
+	install-ps-recursive install-recursive installcheck-recursive \
+	installdirs-recursive pdf-recursive ps-recursive \
+	tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__installdirs = "$(DESTDIR)$(pkgconfigdir)"
+DATA = $(pkgconfig_DATA)
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
+  distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+  $(RECURSIVE_TARGETS) \
+  $(RECURSIVE_CLEAN_TARGETS) \
+  $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+	cscope distdir dist dist-all distcheck
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+CSCOPE = cscope
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/jose.pc.in COPYING \
+	compile config.guess config.sub install-sh ltmain.sh missing
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+am__remove_distdir = \
+  if test -d "$(distdir)"; then \
+    find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \
+      && rm -rf "$(distdir)" \
+      || { sleep 5 && rm -rf "$(distdir)"; }; \
+  else :; fi
+am__post_remove_distdir = $(am__remove_distdir)
+am__relativize = \
+  dir0=`pwd`; \
+  sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+  sed_rest='s,^[^/]*/*,,'; \
+  sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+  sed_butlast='s,/*[^/]*$$,,'; \
+  while test -n "$$dir1"; do \
+    first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+    if test "$$first" != "."; then \
+      if test "$$first" = ".."; then \
+        dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+        dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+      else \
+        first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+        if test "$$first2" = "$$first"; then \
+          dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+        else \
+          dir2="../$$dir2"; \
+        fi; \
+        dir0="$$dir0"/"$$first"; \
+      fi; \
+    fi; \
+    dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+  done; \
+  reldir="$$dir2"
+GZIP_ENV = --best
+DIST_ARCHIVES = $(distdir).tar.bz2
+DIST_TARGETS = dist-bzip2
+distuninstallcheck_listfiles = find . -type f -print
+am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \
+  | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$'
+distcleancheck_listfiles = find . -type f -print
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JOSE_CFLAGS = @JOSE_CFLAGS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENMP_CFLAGS = @OPENMP_CFLAGS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+jansson_CFLAGS = @jansson_CFLAGS@
+jansson_LIBS = @jansson_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zlib_CFLAGS = @zlib_CFLAGS@
+zlib_LIBS = @zlib_LIBS@
+SUBDIRS = . jose lib cmd tests doc
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = jose.pc
+all: all-recursive
+
+.SUFFIXES:
+am--refresh: Makefile
+	@:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \
+	      $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \
+		&& exit 0; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    echo ' $(SHELL) ./config.status'; \
+	    $(SHELL) ./config.status;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	$(SHELL) ./config.status --recheck
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	$(am__cd) $(srcdir) && $(AUTOCONF)
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	$(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
+$(am__aclocal_m4_deps):
+jose.pc: $(top_builddir)/config.status $(srcdir)/jose.pc.in
+	cd $(top_builddir) && $(SHELL) ./config.status $@
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+
+distclean-libtool:
+	-rm -f libtool config.lt
+install-pkgconfigDATA: $(pkgconfig_DATA)
+	@$(NORMAL_INSTALL)
+	@list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \
+	  $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \
+	done
+
+uninstall-pkgconfigDATA:
+	@$(NORMAL_UNINSTALL)
+	@list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir)
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+#     (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+	@fail=; \
+	if $(am__make_keepgoing); then \
+	  failcom='fail=yes'; \
+	else \
+	  failcom='exit 1'; \
+	fi; \
+	dot_seen=no; \
+	target=`echo $@ | sed s/-recursive//`; \
+	case "$@" in \
+	  distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+	  *) list='$(SUBDIRS)' ;; \
+	esac; \
+	for subdir in $$list; do \
+	  echo "Making $$target in $$subdir"; \
+	  if test "$$subdir" = "."; then \
+	    dot_seen=yes; \
+	    local_target="$$target-am"; \
+	  else \
+	    local_target="$$target"; \
+	  fi; \
+	  ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+	  || eval $$failcom; \
+	done; \
+	if test "$$dot_seen" = "no"; then \
+	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+	fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+	$(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	set x; \
+	here=`pwd`; \
+	if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+	  include_option=--etags-include; \
+	  empty_fix=.; \
+	else \
+	  include_option=--include; \
+	  empty_fix=; \
+	fi; \
+	list='$(SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    test ! -f $$subdir/TAGS || \
+	      set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+	  fi; \
+	done; \
+	$(am__define_uniq_tagged_files); \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	$(am__define_uniq_tagged_files); \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+cscope: cscope.files
+	test ! -s cscope.files \
+	  || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS)
+clean-cscope:
+	-rm -f cscope.files
+cscope.files: clean-cscope cscopelist
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+	list='$(am__tagged_files)'; \
+	case "$(srcdir)" in \
+	  [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+	  *) sdir=$(subdir)/$(srcdir) ;; \
+	esac; \
+	for i in $$list; do \
+	  if test -f "$$i"; then \
+	    echo "$(subdir)/$$i"; \
+	  else \
+	    echo "$$sdir/$$i"; \
+	  fi; \
+	done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+	-rm -f cscope.out cscope.in.out cscope.po.out cscope.files
+
+distdir: $(DISTFILES)
+	$(am__remove_distdir)
+	test -d "$(distdir)" || mkdir "$(distdir)"
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+	@list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+	  if test "$$subdir" = .; then :; else \
+	    $(am__make_dryrun) \
+	      || test -d "$(distdir)/$$subdir" \
+	      || $(MKDIR_P) "$(distdir)/$$subdir" \
+	      || exit 1; \
+	    dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+	    $(am__relativize); \
+	    new_distdir=$$reldir; \
+	    dir1=$$subdir; dir2="$(top_distdir)"; \
+	    $(am__relativize); \
+	    new_top_distdir=$$reldir; \
+	    echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+	    echo "     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+	    ($(am__cd) $$subdir && \
+	      $(MAKE) $(AM_MAKEFLAGS) \
+	        top_distdir="$$new_top_distdir" \
+	        distdir="$$new_distdir" \
+		am__remove_distdir=: \
+		am__skip_length_check=: \
+		am__skip_mode_fix=: \
+	        distdir) \
+	      || exit 1; \
+	  fi; \
+	done
+	-test -n "$(am__skip_mode_fix)" \
+	|| find "$(distdir)" -type d ! -perm -755 \
+		-exec chmod u+rwx,go+rx {} \; -o \
+	  ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
+	  ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
+	  ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+	|| chmod -R a+r "$(distdir)"
+dist-gzip: distdir
+	tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz
+	$(am__post_remove_distdir)
+dist-bzip2: distdir
+	tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2
+	$(am__post_remove_distdir)
+
+dist-lzip: distdir
+	tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz
+	$(am__post_remove_distdir)
+
+dist-xz: distdir
+	tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz
+	$(am__post_remove_distdir)
+
+dist-tarZ: distdir
+	@echo WARNING: "Support for distribution archives compressed with" \
+		       "legacy program 'compress' is deprecated." >&2
+	@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+	tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
+	$(am__post_remove_distdir)
+
+dist-shar: distdir
+	@echo WARNING: "Support for shar distribution archives is" \
+	               "deprecated." >&2
+	@echo WARNING: "It will be removed altogether in Automake 2.0" >&2
+	shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz
+	$(am__post_remove_distdir)
+
+dist-zip: distdir
+	-rm -f $(distdir).zip
+	zip -rq $(distdir).zip $(distdir)
+	$(am__post_remove_distdir)
+
+dist dist-all:
+	$(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:'
+	$(am__post_remove_distdir)
+
+# This target untars the dist file and tries a VPATH configuration.  Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+	case '$(DIST_ARCHIVES)' in \
+	*.tar.gz*) \
+	  eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\
+	*.tar.bz2*) \
+	  bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\
+	*.tar.lz*) \
+	  lzip -dc $(distdir).tar.lz | $(am__untar) ;;\
+	*.tar.xz*) \
+	  xz -dc $(distdir).tar.xz | $(am__untar) ;;\
+	*.tar.Z*) \
+	  uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
+	*.shar.gz*) \
+	  eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\
+	*.zip*) \
+	  unzip $(distdir).zip ;;\
+	esac
+	chmod -R a-w $(distdir)
+	chmod u+w $(distdir)
+	mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst
+	chmod a-w $(distdir)
+	test -d $(distdir)/_build || exit 0; \
+	dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \
+	  && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \
+	  && am__cwd=`pwd` \
+	  && $(am__cd) $(distdir)/_build/sub \
+	  && ../../configure \
+	    $(AM_DISTCHECK_CONFIGURE_FLAGS) \
+	    $(DISTCHECK_CONFIGURE_FLAGS) \
+	    --srcdir=../.. --prefix="$$dc_install_base" \
+	  && $(MAKE) $(AM_MAKEFLAGS) \
+	  && $(MAKE) $(AM_MAKEFLAGS) dvi \
+	  && $(MAKE) $(AM_MAKEFLAGS) check \
+	  && $(MAKE) $(AM_MAKEFLAGS) install \
+	  && $(MAKE) $(AM_MAKEFLAGS) installcheck \
+	  && $(MAKE) $(AM_MAKEFLAGS) uninstall \
+	  && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \
+	        distuninstallcheck \
+	  && chmod -R a-w "$$dc_install_base" \
+	  && ({ \
+	       (cd ../.. && umask 077 && mkdir "$$dc_destdir") \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \
+	       && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \
+	            distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \
+	      } || { rm -rf "$$dc_destdir"; exit 1; }) \
+	  && rm -rf "$$dc_destdir" \
+	  && $(MAKE) $(AM_MAKEFLAGS) dist \
+	  && rm -rf $(DIST_ARCHIVES) \
+	  && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \
+	  && cd "$$am__cwd" \
+	  || exit 1
+	$(am__post_remove_distdir)
+	@(echo "$(distdir) archives ready for distribution: "; \
+	  list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
+	  sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+distuninstallcheck:
+	@test -n '$(distuninstallcheck_dir)' || { \
+	  echo 'ERROR: trying to run $@ with an empty' \
+	       '$$(distuninstallcheck_dir)' >&2; \
+	  exit 1; \
+	}; \
+	$(am__cd) '$(distuninstallcheck_dir)' || { \
+	  echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \
+	  exit 1; \
+	}; \
+	test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \
+	   || { echo "ERROR: files left after uninstall:" ; \
+	        if test -n "$(DESTDIR)"; then \
+	          echo "  (check DESTDIR support)"; \
+	        fi ; \
+	        $(distuninstallcheck_listfiles) ; \
+	        exit 1; } >&2
+distcleancheck: distclean
+	@if test '$(srcdir)' = . ; then \
+	  echo "ERROR: distcleancheck can only run from a VPATH build" ; \
+	  exit 1 ; \
+	fi
+	@test `$(distcleancheck_listfiles) | wc -l` -eq 0 \
+	  || { echo "ERROR: files left in build directory after distclean:" ; \
+	       $(distcleancheck_listfiles) ; \
+	       exit 1; } >&2
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(DATA)
+installdirs: installdirs-recursive
+installdirs-am:
+	for dir in "$(DESTDIR)$(pkgconfigdir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-libtool \
+	distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-pkgconfigDATA
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+	-rm -f $(am__CONFIG_DISTCLEAN_FILES)
+	-rm -rf $(top_srcdir)/autom4te.cache
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-pkgconfigDATA
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+	am--refresh check check-am clean clean-cscope clean-generic \
+	clean-libtool cscope cscopelist-am ctags ctags-am dist \
+	dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \
+	dist-xz dist-zip distcheck distclean distclean-generic \
+	distclean-libtool distclean-tags distcleancheck distdir \
+	distuninstallcheck dvi dvi-am html html-am info info-am \
+	install install-am install-data install-data-am install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am install-man \
+	install-pdf install-pdf-am install-pkgconfigDATA install-ps \
+	install-ps-am install-strip installcheck installcheck-am \
+	installdirs installdirs-am maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-generic \
+	mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+	uninstall-am uninstall-pkgconfigDATA
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

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


+ 27 - 0
cmd/Makefile.am

@@ -0,0 +1,27 @@
+AM_CFLAGS = @JOSE_CFLAGS@ @jansson_CFLAGS@ -I$(top_srcdir) -I$(top_builddir)
+LDFLAGS += ../lib/libjose.la @jansson_LIBS@
+
+bin_PROGRAMS = jose
+jose_SOURCES = \
+    jose.c jose.h \
+    b64/b64.h \
+    b64/dec.c \
+    b64/enc.c \
+    jwk/jwk.h \
+    jwk/eql.c \
+    jwk/exc.c \
+    jwk/gen.c \
+    jwk/pub.c \
+    jwk/thp.c \
+    jwk/use.c \
+    jws/jws.h \
+    jws/fmt.c \
+    jws/sig.c \
+    jws/ver.c \
+    jwe/jwe.h \
+    jwe/pwd.h \
+    jwe/fmt.c \
+    jwe/dec.c \
+    jwe/enc.c \
+    alg.c \
+    fmt.c

+ 735 - 0
cmd/Makefile.in

@@ -0,0 +1,735 @@
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+bin_PROGRAMS = jose$(EXEEXT)
+subdir = cmd
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__dirstamp = $(am__leading_dot)dirstamp
+am_jose_OBJECTS = jose.$(OBJEXT) b64/dec.$(OBJEXT) b64/enc.$(OBJEXT) \
+	jwk/eql.$(OBJEXT) jwk/exc.$(OBJEXT) jwk/gen.$(OBJEXT) \
+	jwk/pub.$(OBJEXT) jwk/thp.$(OBJEXT) jwk/use.$(OBJEXT) \
+	jws/fmt.$(OBJEXT) jws/sig.$(OBJEXT) jws/ver.$(OBJEXT) \
+	jwe/fmt.$(OBJEXT) jwe/dec.$(OBJEXT) jwe/enc.$(OBJEXT) \
+	alg.$(OBJEXT) fmt.$(OBJEXT)
+jose_OBJECTS = $(am_jose_OBJECTS)
+jose_LDADD = $(LDADD)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 = 
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+DEFAULT_INCLUDES = -I.@am__isrc@
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+	$(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+	$(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo "  CC      " $@;
+am__v_CC_1 = 
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo "  CCLD    " $@;
+am__v_CCLD_1 = 
+SOURCES = $(jose_SOURCES)
+DIST_SOURCES = $(jose_SOURCES)
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JOSE_CFLAGS = @JOSE_CFLAGS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@ ../lib/libjose.la @jansson_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENMP_CFLAGS = @OPENMP_CFLAGS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+jansson_CFLAGS = @jansson_CFLAGS@
+jansson_LIBS = @jansson_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zlib_CFLAGS = @zlib_CFLAGS@
+zlib_LIBS = @zlib_LIBS@
+AM_CFLAGS = @JOSE_CFLAGS@ @jansson_CFLAGS@ -I$(top_srcdir) -I$(top_builddir)
+jose_SOURCES = \
+    jose.c jose.h \
+    b64/b64.h \
+    b64/dec.c \
+    b64/enc.c \
+    jwk/jwk.h \
+    jwk/eql.c \
+    jwk/exc.c \
+    jwk/gen.c \
+    jwk/pub.c \
+    jwk/thp.c \
+    jwk/use.c \
+    jws/jws.h \
+    jws/fmt.c \
+    jws/sig.c \
+    jws/ver.c \
+    jwe/jwe.h \
+    jwe/pwd.h \
+    jwe/fmt.c \
+    jwe/dec.c \
+    jwe/enc.c \
+    alg.c \
+    fmt.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign cmd/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign cmd/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+	fi; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p \
+	 || test -f $$p1 \
+	  ; then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' \
+	    -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	    echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	    $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' \
+	`; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	@list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+	echo " rm -f" $$list; \
+	rm -f $$list || exit $$?; \
+	test -n "$(EXEEXT)" || exit 0; \
+	list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+	echo " rm -f" $$list; \
+	rm -f $$list
+b64/$(am__dirstamp):
+	@$(MKDIR_P) b64
+	@: > b64/$(am__dirstamp)
+b64/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) b64/$(DEPDIR)
+	@: > b64/$(DEPDIR)/$(am__dirstamp)
+b64/dec.$(OBJEXT): b64/$(am__dirstamp) b64/$(DEPDIR)/$(am__dirstamp)
+b64/enc.$(OBJEXT): b64/$(am__dirstamp) b64/$(DEPDIR)/$(am__dirstamp)
+jwk/$(am__dirstamp):
+	@$(MKDIR_P) jwk
+	@: > jwk/$(am__dirstamp)
+jwk/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) jwk/$(DEPDIR)
+	@: > jwk/$(DEPDIR)/$(am__dirstamp)
+jwk/eql.$(OBJEXT): jwk/$(am__dirstamp) jwk/$(DEPDIR)/$(am__dirstamp)
+jwk/exc.$(OBJEXT): jwk/$(am__dirstamp) jwk/$(DEPDIR)/$(am__dirstamp)
+jwk/gen.$(OBJEXT): jwk/$(am__dirstamp) jwk/$(DEPDIR)/$(am__dirstamp)
+jwk/pub.$(OBJEXT): jwk/$(am__dirstamp) jwk/$(DEPDIR)/$(am__dirstamp)
+jwk/thp.$(OBJEXT): jwk/$(am__dirstamp) jwk/$(DEPDIR)/$(am__dirstamp)
+jwk/use.$(OBJEXT): jwk/$(am__dirstamp) jwk/$(DEPDIR)/$(am__dirstamp)
+jws/$(am__dirstamp):
+	@$(MKDIR_P) jws
+	@: > jws/$(am__dirstamp)
+jws/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) jws/$(DEPDIR)
+	@: > jws/$(DEPDIR)/$(am__dirstamp)
+jws/fmt.$(OBJEXT): jws/$(am__dirstamp) jws/$(DEPDIR)/$(am__dirstamp)
+jws/sig.$(OBJEXT): jws/$(am__dirstamp) jws/$(DEPDIR)/$(am__dirstamp)
+jws/ver.$(OBJEXT): jws/$(am__dirstamp) jws/$(DEPDIR)/$(am__dirstamp)
+jwe/$(am__dirstamp):
+	@$(MKDIR_P) jwe
+	@: > jwe/$(am__dirstamp)
+jwe/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) jwe/$(DEPDIR)
+	@: > jwe/$(DEPDIR)/$(am__dirstamp)
+jwe/fmt.$(OBJEXT): jwe/$(am__dirstamp) jwe/$(DEPDIR)/$(am__dirstamp)
+jwe/dec.$(OBJEXT): jwe/$(am__dirstamp) jwe/$(DEPDIR)/$(am__dirstamp)
+jwe/enc.$(OBJEXT): jwe/$(am__dirstamp) jwe/$(DEPDIR)/$(am__dirstamp)
+
+jose$(EXEEXT): $(jose_OBJECTS) $(jose_DEPENDENCIES) $(EXTRA_jose_DEPENDENCIES) 
+	@rm -f jose$(EXEEXT)
+	$(AM_V_CCLD)$(LINK) $(jose_OBJECTS) $(jose_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+	-rm -f b64/*.$(OBJEXT)
+	-rm -f jwe/*.$(OBJEXT)
+	-rm -f jwk/*.$(OBJEXT)
+	-rm -f jws/*.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/alg.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fmt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jose.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@b64/$(DEPDIR)/dec.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@b64/$(DEPDIR)/enc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwe/$(DEPDIR)/dec.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwe/$(DEPDIR)/enc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwe/$(DEPDIR)/fmt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwk/$(DEPDIR)/eql.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwk/$(DEPDIR)/exc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwk/$(DEPDIR)/gen.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwk/$(DEPDIR)/pub.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwk/$(DEPDIR)/thp.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jwk/$(DEPDIR)/use.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jws/$(DEPDIR)/fmt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jws/$(DEPDIR)/sig.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@jws/$(DEPDIR)/ver.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@	$(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+	$(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	set x; \
+	here=`pwd`; \
+	$(am__define_uniq_tagged_files); \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	$(am__define_uniq_tagged_files); \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+	list='$(am__tagged_files)'; \
+	case "$(srcdir)" in \
+	  [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+	  *) sdir=$(subdir)/$(srcdir) ;; \
+	esac; \
+	for i in $$list; do \
+	  if test -f "$$i"; then \
+	    echo "$(subdir)/$$i"; \
+	  else \
+	    echo "$$sdir/$$i"; \
+	  fi; \
+	done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+	-rm -f b64/$(DEPDIR)/$(am__dirstamp)
+	-rm -f b64/$(am__dirstamp)
+	-rm -f jwe/$(DEPDIR)/$(am__dirstamp)
+	-rm -f jwe/$(am__dirstamp)
+	-rm -f jwk/$(DEPDIR)/$(am__dirstamp)
+	-rm -f jwk/$(am__dirstamp)
+	-rm -f jws/$(DEPDIR)/$(am__dirstamp)
+	-rm -f jws/$(am__dirstamp)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR) b64/$(DEPDIR) jwe/$(DEPDIR) jwk/$(DEPDIR) jws/$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR) b64/$(DEPDIR) jwe/$(DEPDIR) jwk/$(DEPDIR) jws/$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+	mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \
+	clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \
+	ctags ctags-am distclean distclean-compile distclean-generic \
+	distclean-libtool distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+	tags tags-am uninstall uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

+ 151 - 0
cmd/alg.c

@@ -0,0 +1,151 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jose.h"
+#include "../lib/hooks.h"
+#include <string.h>
+
+#define SUMMARY "Lists all supported algorithms"
+
+typedef struct {
+    json_t *kinds;
+} jcmd_opt_t;
+
+static const char *prefix = "jose alg [-k KIND]\n\n" SUMMARY;
+
+static const struct {
+    const char *name;
+    jose_hook_alg_kind_t kind;
+} kinds[] = {
+    { "hash", JOSE_HOOK_ALG_KIND_HASH },
+    { "sign", JOSE_HOOK_ALG_KIND_SIGN },
+    { "wrap", JOSE_HOOK_ALG_KIND_WRAP },
+    { "encr", JOSE_HOOK_ALG_KIND_ENCR },
+    { "comp", JOSE_HOOK_ALG_KIND_COMP },
+    { "exch", JOSE_HOOK_ALG_KIND_EXCH },
+    {}
+};
+
+static jose_hook_alg_kind_t
+name2kind(const char *name)
+{
+    for (size_t i = 0; name && kinds[i].name; i++) {
+        if (strcmp(name, kinds[i].name) == 0)
+            return kinds[i].kind;
+    }
+
+    return JOSE_HOOK_ALG_KIND_NONE;
+}
+
+static bool
+opt_set_kind(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_t **all = vopt;
+
+    if (!*all)
+        *all = json_array();
+
+    if (strcmp(arg, "?") == 0) {
+        for (size_t i = 0; kinds[i].name; i++)
+            fprintf(stdout, "%s\n", kinds[i].name);
+
+        exit(EXIT_SUCCESS);
+    }
+
+    if (name2kind(arg) == JOSE_HOOK_ALG_KIND_NONE)
+        return false;
+
+    return json_array_append_new(*all, json_string(arg)) >= 0;
+}
+
+static const jcmd_doc_t doc_kind[] = {
+    { .arg = "KIND", .doc = "Restrict algorithm list to a certain kind" },
+    { .arg = "?",    .doc = "List valid algorithm kinds" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "kind", required_argument, .val = 'k' },
+        .off = offsetof(jcmd_opt_t, kinds),
+        .set = opt_set_kind,
+        .doc = doc_kind
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    json_decref(opt->kinds);
+}
+
+static int
+cmp(const void *a, const void *b)
+{
+    const char *const *aa = a;
+    const char *const *bb = b;
+    return strcasecmp(*aa, *bb);
+}
+
+static bool
+filter(const jcmd_opt_t *opt, jose_hook_alg_kind_t kind)
+{
+    size_t size = 0;
+
+    size = json_array_size(opt->kinds);
+    if (size == 0)
+        return true;
+
+    for (size_t i = 0; i < size; i++) {
+        if (kind == name2kind(json_string_value(json_array_get(opt->kinds, i))))
+            return true;
+    }
+
+    return false;
+}
+
+static int
+jcmd_alg(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+    size_t len = 0;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+        if (filter(&opt, a->kind))
+            len++;
+    }
+
+    const char *names[len];
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+        if (filter(&opt, a->kind))
+            names[--len] = a->name;
+    }
+
+    qsort(names, sizeof(names) / sizeof(*names), sizeof(*names), cmp);
+
+    for (size_t i = 0; i < sizeof(names) / sizeof(*names); i++)
+        fprintf(stdout, "%s\n", names[i]);
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_alg, "alg")

+ 32 - 0
cmd/b64/b64.h

@@ -0,0 +1,32 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../jose.h"
+
+#define jcmd_b64_opt_auto_t __JCMD_AUTO(jcmd_b64_opt)
+
+typedef struct {
+    FILE *input;
+    FILE *output;
+} jcmd_b64_opt_t;
+
+static inline void
+jcmd_b64_opt_cleanup(jcmd_b64_opt_t *opt)
+{
+    jcmd_file_cleanup(&opt->input);
+    jcmd_file_cleanup(&opt->output);
+}

+ 95 - 0
cmd/b64/dec.c

@@ -0,0 +1,95 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "b64.h"
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Decodes URL-safe Base64 data to binary"
+
+static const char *prefix = "jose b64 dec -i B64 [-O BIN]\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_input[] = {
+    { .arg = "FILE", .doc="Read Base64 (URL-safe) data from FILE" },
+    { .arg = "-",    .doc="Read Base64 (URL-safe) data from standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_output[] = {
+    { .arg = "FILE", .doc="Write binary data to FILE" },
+    { .arg = "-",    .doc="Write binary data to standard output" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "base64", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_b64_opt_t, input),
+        .set = jcmd_opt_set_ifile,
+        .doc = doc_input,
+    },
+    {
+        .opt = { "binary", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_b64_opt_t, output),
+        .set = jcmd_opt_set_ofile,
+        .doc = doc_output,
+        .def = "-",
+    },
+    {}
+};
+
+static int
+jcmd_b64_dec(int argc, char *argv[])
+{
+    jcmd_b64_opt_auto_t opt = {};
+    jose_io_auto_t *b64 = NULL;
+    jose_io_auto_t *out = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (!opt.input) {
+        fprintf(stderr, "Input not specified!\n");
+        return EXIT_FAILURE;
+    }
+
+    out = jose_io_file(NULL, opt.output);
+    if (!out)
+        return EXIT_FAILURE;
+
+    b64 = jose_b64_dec_io(out);
+    if (!b64)
+        return EXIT_FAILURE;
+
+    for (int c = fgetc(opt.input); c != EOF; c = fgetc(opt.input)) {
+        uint8_t b = c;
+
+        if (isspace(c))
+            continue;
+
+        if (!b64->feed(b64, &b, sizeof(b)))
+            return EXIT_FAILURE;
+    }
+
+    if (!b64->done(b64))
+        return EXIT_FAILURE;
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_b64_dec, "b64", "dec")

+ 94 - 0
cmd/b64/enc.c

@@ -0,0 +1,94 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "b64.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Encodes binary data to URL-safe Base64"
+
+static const char *prefix = "jose b64 enc -I BIN [-o B64]\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_input[] = {
+    { .arg = "FILE", .doc="Read binary data from FILE" },
+    { .arg = "-",    .doc="Read binary data from standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_output[] = {
+    { .arg = "FILE", .doc="Write Base64 (URL-safe) to FILE" },
+    { .arg = "-",    .doc="Write Base64 (URL-safe) to standard output" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "binary", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_b64_opt_t, input),
+        .set = jcmd_opt_set_ifile,
+        .doc = doc_input,
+    },
+    {
+        .opt = { "base64", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_b64_opt_t, output),
+        .set = jcmd_opt_set_ofile,
+        .doc = doc_output,
+        .def = "-",
+    },
+    {}
+};
+
+static int
+jcmd_b64_enc(int argc, char *argv[])
+{
+    jcmd_b64_opt_auto_t opt = {};
+    jose_io_auto_t *b64 = NULL;
+    jose_io_auto_t *out = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (!opt.input) {
+        fprintf(stderr, "Input not specified!\n");
+        return EXIT_FAILURE;
+    }
+
+    out = jose_io_file(NULL, opt.output);
+    if (!out)
+        return EXIT_FAILURE;
+
+    b64 = jose_b64_enc_io(out);
+    if (!b64)
+        return EXIT_FAILURE;
+
+    for (int c = fgetc(opt.input); c != EOF; c = fgetc(opt.input)) {
+        uint8_t b = c;
+
+        if (!b64->feed(b64, &b, sizeof(b)))
+            return EXIT_FAILURE;
+    }
+
+    if (!b64->done(b64))
+        return EXIT_FAILURE;
+
+    if (isatty(fileno(opt.output)))
+        fprintf(opt.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_b64_enc, "b64", "enc")

+ 695 - 0
cmd/fmt.c

@@ -0,0 +1,695 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jose.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <ctype.h>
+
+#define SUMMARY "Converts JSON between serialization formats"
+#define JAIN json_array_insert_new
+
+#ifdef __MINGW32__
+#define sscanf __mingw_sscanf
+#endif
+
+static const char *prefix = "jose fmt [OPTIONS]\n\n" SUMMARY;
+
+typedef struct {
+    json_t *args;
+} jcmd_opt_t;
+
+static size_t
+convert_int(const json_t *arr, const char *arg)
+{
+    ssize_t indx = 0;
+
+    if (sscanf(arg, "%zd", &indx) != 1)
+        return SIZE_MAX;
+
+    if (indx < 0)
+        indx += json_array_size(arr);
+
+    if (indx < 0)
+        return SIZE_MAX;
+
+    return indx;
+}
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    json_decref(opt->args);
+}
+
+static bool
+cmd_output(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    const int wflags = JSON_ENCODE_ANY | JSON_COMPACT | JSON_SORT_KEYS;
+    const char *s = json_string_value(arg);
+    FILE *file = NULL;
+    bool ret = false;
+
+    if (strcmp(s, "-") == 0)
+        file = stdout;
+    else
+        file = fopen(s, "w");
+    if (!file)
+        return false;
+
+    if (json_dumpf(cur, file, wflags) < 0)
+        goto egress;
+
+    if (isatty(fileno(file)) && fwrite("\n", 1, 1, file) != 1)
+        goto egress;
+
+    ret = true;
+
+egress:
+    if (strcmp(s, "-") != 0)
+        fclose(file);
+    return ret;
+}
+
+static bool
+cmd_foreach(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    const int wflags = JSON_ENCODE_ANY | JSON_COMPACT | JSON_SORT_KEYS;
+    const char *s = json_string_value(arg);
+    FILE *file = NULL;
+    bool ret = false;
+
+    if (!json_is_array(cur) && !json_is_object(cur))
+        return false;
+
+    if (strcmp(s, "-") == 0)
+        file = stdout;
+    else
+        file = fopen(s, "w");
+    if (!file)
+        return false;
+
+    if (json_is_array(cur)) {
+        json_t *v = NULL;
+        size_t i = 0;
+
+        json_array_foreach(cur, i, v) {
+            if (json_dumpf(v, file, wflags) < 0 ||
+                fprintf(file, "\n") < 0)
+                goto egress;
+        }
+    } else if (json_is_object(cur)) {
+        const char *k = NULL;
+        json_t *v = NULL;
+
+        json_object_foreach(cur, k, v) {
+            if (fprintf(file, "%s=", k) < 0 ||
+                json_dumpf(v, file, wflags) < 0 ||
+                fprintf(file, "\n") < 0)
+                goto egress;
+        }
+    }
+
+    ret = true;
+
+egress:
+    if (strcmp(s, "-") != 0)
+        fclose(file);
+    return ret;
+}
+
+static bool
+cmd_unquote(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    const char *s = json_string_value(arg);
+    FILE *file = NULL;
+    bool ret = false;
+
+    if (!json_is_string(cur))
+        return false;
+
+    if (strcmp(s, "-") == 0)
+        return fprintf(stdout, "%s\n", json_string_value(cur)) >= 0;
+
+    file = fopen(s, "w");
+    if (!file)
+        return false;
+
+    ret = fprintf(file, "%s\n", json_string_value(cur)) >= 0;
+    fclose(file);
+    return ret;
+}
+
+static bool
+cmd_move(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    json_int_t i = json_integer_value(arg);
+
+    if (json_array_insert(stk, i + 1, cur) < 0)
+        return false;
+
+    if (json_array_remove(stk, 0) < 0)
+        return false;
+
+    return true;
+}
+
+static bool
+cmd_trunc(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    size_t i = json_integer_value(arg);
+    size_t s;
+
+    for (s = json_array_size(cur); s > i; s--) {
+        if (json_array_remove(cur, s - 1) < 0)
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+cmd_insert(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    size_t i = json_integer_value(arg);
+    return json_array_insert(lst, i, cur) >= 0;
+}
+
+static bool
+cmd_append(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    if (json_is_array(lst))
+        return json_array_append(lst, cur) >= 0;
+
+    if (json_is_object(lst))
+        return json_object_update_missing(lst, cur) >= 0;
+
+    return false;
+}
+
+static bool
+cmd_extend(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    if (json_is_array(lst))
+        return json_array_extend(lst, cur) >= 0;
+
+    if (json_is_object(lst))
+        return json_object_update(lst, cur) >= 0;
+
+    return false;
+}
+
+static bool
+cmd_delete(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    const char *s = json_string_value(arg);
+
+    if (json_is_array(cur)) {
+        size_t indx;
+
+        indx = convert_int(cur, s);
+        if (indx == SIZE_MAX)
+            return false;
+
+        return json_array_remove(cur, indx) >= 0;
+    }
+
+    if (json_is_object(cur))
+        return json_object_del(cur, s) >= 0;
+
+    return false;
+}
+
+static bool
+cmd_length(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    size_t count = 0;
+
+    if (json_is_array(cur))
+        count = json_array_size(cur);
+    else if (json_is_object(cur))
+        count = json_object_size(cur);
+    else if (json_is_string(cur))
+        count = json_string_length(cur);
+    else
+        return false;
+
+    return json_array_insert_new(stk, 0, json_integer(count)) >= 0;
+}
+
+static bool
+cmd_empty(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    if (json_is_array(cur))
+        return json_array_clear(cur) >= 0;
+
+    if (json_is_object(cur))
+        return json_object_clear(cur) >= 0;
+
+    return false;
+}
+
+static bool
+cmd_get(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    const char *s = json_string_value(arg);
+    json_t *v = NULL;
+
+    if (json_is_array(cur)) {
+        size_t indx;
+
+        indx = convert_int(cur, s);
+        if (indx == SIZE_MAX)
+            return false;
+
+        v = json_array_get(cur, indx);
+    } else if (json_is_object(cur)) {
+        v = json_object_get(cur, s);
+    } else {
+        return false;
+    }
+
+    return json_array_insert(stk, 0, v) >= 0;
+}
+
+static bool
+cmd_set(const json_t *arg, json_t *stk, json_t *cur, json_t *lst)
+{
+    const char *s = json_string_value(arg);
+
+    if (json_is_array(lst)) {
+        size_t indx;
+
+        indx = convert_int(lst, s);
+        if (indx == SIZE_MAX)
+            return false;
+
+        return json_array_set(lst, indx, cur) >= 0;
+    }
+
+    if (json_is_object(lst))
+        return json_object_set(lst, s, cur) >= 0;
+
+    return false;
+}
+
+static const jcmd_doc_t doc_not[] = {
+    { .doc = "Invert the following assertion" },
+    {}
+};
+
+static const jcmd_doc_t doc_object[] = {
+    { .doc = "Assert TOP to be an object" },
+    {}
+};
+
+static const jcmd_doc_t doc_array[] = {
+    { .doc = "Assert TOP to be an array" },
+    {}
+};
+
+static const jcmd_doc_t doc_string[] = {
+    { .doc = "Assert TOP to be a string" },
+    {}
+};
+
+static const jcmd_doc_t doc_int[] = {
+    { .doc = "Assert TOP to be an integer" },
+    {}
+};
+
+static const jcmd_doc_t doc_real[] = {
+    { .doc = "Assert TOP to be a real" },
+    {}
+};
+
+static const jcmd_doc_t doc_number[] = {
+    { .doc = "Assert TOP to be a number" },
+    {}
+};
+
+static const jcmd_doc_t doc_true[] = {
+    { .doc = "Assert TOP to be true" },
+    {}
+};
+
+static const jcmd_doc_t doc_false[] = {
+    { .doc = "Assert TOP to be false" },
+    {}
+};
+
+static const jcmd_doc_t doc_bool[] = {
+    { .doc = "Assert TOP to be a boolean" },
+    {}
+};
+
+static const jcmd_doc_t doc_null[] = {
+    { .doc = "Assert TOP to be null" },
+    {}
+};
+
+static const jcmd_doc_t doc_equal[] = {
+    { .doc = "Assert TOP to be equal to PREV" },
+    {}
+};
+
+static const jcmd_doc_t doc_json[] = {
+    { .arg = "JSON", .doc = "Parse JSON constant, push onto TOP" },
+    { .arg = "FILE", .doc = "Read from FILE, push onto TOP" },
+    { .arg = "-",    .doc = "Read from STDIN, push onto TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_quote[] = {
+    { .arg = "STR",  .doc = "Convert STR to a string, push onto TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_output[] = {
+    { .arg = "FILE", .doc = "Write TOP to FILE" },
+    { .arg = "-",    .doc = "Write TOP to STDOUT" },
+    {}
+};
+
+static const jcmd_doc_t doc_foreach[] = {
+    { .arg = "FILE", .doc = "Write TOP (obj./arr.) to FILE, one line/item" },
+    { .arg = "-",    .doc = "Write TOP (obj./arr.) to STDOUT, one line/item" },
+    {}
+};
+
+static const jcmd_doc_t doc_unquote[] = {
+    { .arg = "FILE", .doc = "Write TOP (str.) to FILE without quotes" },
+    { .arg = "-",    .doc = "Write TOP (str.) to STDOUT without quotes" },
+    {}
+};
+
+static const jcmd_doc_t doc_copy[] = {
+    { .doc = "Deep copy TOP, push onto TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_query[] = {
+    { .doc = "Query the stack by deep copying and pushing onto TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_move[] = {
+    { .arg = "#", .doc = "Move TOP back # places on the stack" },
+    {}
+};
+
+static const jcmd_doc_t doc_unwind[] = {
+    { .doc = "Discard TOP from the stack" },
+    {}
+};
+
+static const jcmd_doc_t doc_trunc[] = {
+    { .arg = "#",  .doc = "Shrink TOP (arr.) to length #" },
+    { .arg = "-#", .doc = "Discard last # items from TOP (arr.)" },
+    {}
+};
+
+static const jcmd_doc_t doc_insert[] = {
+    { .arg = "#", .doc = "Insert TOP into PREV (arr.) at #" },
+    {}
+};
+
+static const jcmd_doc_t doc_append[] = {
+    { .doc = "Append TOP to the end of PREV (arr.)" },
+    { .doc = "Set missing values from TOP (obj.) into PREV (obj.)" },
+    {}
+};
+
+static const jcmd_doc_t doc_extend[] = {
+    { .doc = "Append items from TOP to the end of PREV (arr.)" },
+    { .doc = "Set all values from TOP (obj.) into PREV (obj.)" },
+    {}
+};
+
+static const jcmd_doc_t doc_delete[] = {
+    { .arg = "NAME", .doc = "Delete NAME from TOP (obj.)" },
+    { .arg = "#",    .doc = "Delete # from TOP (arr.)" },
+    { .arg = "-#",   .doc = "Delete # from the end of TOP (arr.)" },
+    {}
+};
+
+static const jcmd_doc_t doc_length[] = {
+    { .doc = "Push length of TOP (arr./str./obj.) to TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_empty[] = {
+    { .doc = "Erase all items from TOP (arr./obj.)" },
+    {}
+};
+
+static const jcmd_doc_t doc_get[] = {
+    { .arg = "NAME", .doc = "Get item with NAME from TOP (obj.), push to TOP" },
+    { .arg = "#",    .doc = "Get # item from TOP (arr.), push to TOP" },
+    { .arg = "-#",   .doc = "Get # item from the end of TOP (arr.), push to TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_set[] = {
+    { .arg = "NAME", .doc = "Sets TOP into PREV (obj.) with NAME" },
+    { .arg = "#",    .doc = "Sets TOP into PREV (obj.) at #" },
+    { .arg = "-#",   .doc = "Sets TOP into PREV (obj.) at # from the end" },
+    {}
+};
+
+static const jcmd_doc_t doc_b64l[] = {
+    { .doc = "URL-safe Base64 decode TOP (str.), push onto TOP" },
+    {}
+};
+
+static const jcmd_doc_t doc_b64d[] = {
+    { .doc = "URL-safe Base64 encode TOP, push onto TOP" },
+    {}
+};
+
+static bool
+opt_set_null(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_t **x = vopt;
+    if (!*x) *x = json_array();
+    return json_array_append_new(*x, json_pack("[i,n]", cfg->opt.val)) >= 0;
+}
+
+
+static bool
+opt_set_str(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_t **x = vopt;
+    if (!*x) *x = json_array();
+    return json_array_append_new(*x, json_pack("[i,s]", cfg->opt.val, arg)) >= 0;
+}
+
+static bool
+opt_set_int(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_t **x = vopt;
+    json_int_t j = 0;
+    int i = 0;
+
+    if (sscanf(arg, "%d", &i) != 1)
+        return false;
+
+    j = i;
+    if (!*x) *x = json_array();
+    return json_array_append_new(*x, json_pack("[i,I]", cfg->opt.val, j)) >= 0;
+}
+
+static bool
+opt_set_uint(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    unsigned int i = 0;
+    json_t **x = vopt;
+    json_int_t j = 0;
+
+    if (sscanf(arg, "%u", &i) != 1)
+        return false;
+
+    j = i;
+    if (!*x) *x = json_array();
+    return json_array_append_new(*x, json_pack("[i,I]", cfg->opt.val, j)) >= 0;
+}
+
+static bool
+opt_set_json(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_auto_t *j = NULL;
+    json_t **x = vopt;
+
+    if (!jcmd_opt_set_json(cfg, &j, arg))
+        return false;
+
+    if (!*x) *x = json_array();
+    return json_array_append_new(*x, json_pack("[i,O]", cfg->opt.val, j)) >= 0;
+}
+
+static const jcmd_cfg_t cfgs[] = {
+    { .opt = { "not",      no_argument,       .val = 'X' }, .doc = doc_not,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "object",   no_argument,       .val = 'O' }, .doc = doc_object,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "array",    no_argument,       .val = 'A' }, .doc = doc_array,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "string",   no_argument,       .val = 'S' }, .doc = doc_string,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "integer",  no_argument,       .val = 'I' }, .doc = doc_int,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "real",     no_argument,       .val = 'R' }, .doc = doc_real,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "number",   no_argument,       .val = 'N' }, .doc = doc_number,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "true",     no_argument,       .val = 'T' }, .doc = doc_true,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "false",    no_argument,       .val = 'F' }, .doc = doc_false,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "boolean",  no_argument,       .val = 'B' }, .doc = doc_bool,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "null",     no_argument,       .val = '0' }, .doc = doc_null,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "equal",    no_argument,       .val = 'E' }, .doc = doc_equal,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+
+    { .opt = { "query",    no_argument,       .val = 'Q' }, .doc = doc_query,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "move",     required_argument, .val = 'M' }, .doc = doc_move,
+      .set = opt_set_uint, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "unwind",   no_argument,       .val = 'U' }, .doc = doc_unwind,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+
+    { .opt = { "json",     required_argument, .val = 'j' }, .doc = doc_json,
+      .set = opt_set_json, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "copy",     no_argument,       .val = 'c' }, .doc = doc_copy,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "quote",    required_argument, .val = 'q' }, .doc = doc_quote,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "output",   required_argument, .val = 'o' }, .doc = doc_output,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "foreach",  required_argument, .val = 'f' }, .doc = doc_foreach,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "unquote",  required_argument, .val = 'u' }, .doc = doc_unquote,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+
+    { .opt = { "truncate", required_argument, .val = 't' }, .doc = doc_trunc,
+      .set = opt_set_int,  .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "insert",   required_argument, .val = 'i' }, .doc = doc_insert,
+      .set = opt_set_uint, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "append",   no_argument,       .val = 'a' }, .doc = doc_append,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "extend",   no_argument,       .val = 'x' }, .doc = doc_extend,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+
+    { .opt = { "delete",   required_argument, .val = 'd' }, .doc = doc_delete,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "length",   no_argument,       .val = 'l' }, .doc = doc_length,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "empty",    no_argument,       .val = 'e' }, .doc = doc_empty,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "get",      required_argument, .val = 'g' }, .doc = doc_get,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "set",      required_argument, .val = 's' }, .doc = doc_set,
+      .set = opt_set_str,  .off = offsetof(jcmd_opt_t, args) },
+
+    { .opt = { "b64load",  no_argument,       .val = 'y' }, .doc = doc_b64l,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+    { .opt = { "b64dump",  no_argument,       .val = 'Y' }, .doc = doc_b64d,
+      .set = opt_set_null, .off = offsetof(jcmd_opt_t, args) },
+
+    {}
+};
+
+static int
+jcmd_fmt(int argc, char *argv[])
+{
+    json_auto_t *stk = json_array();
+    jcmd_opt_auto_t opt = {};
+    unsigned char ret = 0;
+    bool not = false;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return -1;
+
+    for (size_t i = 0; i < json_array_size(opt.args); i++) {
+        json_t *lst = NULL;
+        json_t *cur = NULL;
+        json_t *p = NULL;
+        bool ok = false;
+        int o = 0;
+
+        if (json_unpack(json_array_get(opt.args, i), "[i,o!]", &o, &p) < 0)
+            return ++ret;
+
+        if (not && !strchr("OASIRNTFB0E", o))
+            return ret;
+
+        cur = json_array_get(stk, 0);
+        lst = json_array_get(stk, 1);
+        ret++;
+
+        switch (o) {
+        case 'X': ok = not = true;                                break;
+        case 'O': ok = not ^ json_is_object(cur);  not = false;   break;
+        case 'A': ok = not ^ json_is_array(cur);   not = false;   break;
+        case 'S': ok = not ^ json_is_string(cur);  not = false;   break;
+        case 'I': ok = not ^ json_is_integer(cur); not = false;   break;
+        case 'R': ok = not ^ json_is_real(cur);    not = false;   break;
+        case 'N': ok = not ^ json_is_number(cur);  not = false;   break;
+        case 'T': ok = not ^ json_is_true(cur);    not = false;   break;
+        case 'F': ok = not ^ json_is_false(cur);   not = false;   break;
+        case 'B': ok = not ^ json_is_boolean(cur); not = false;   break;
+        case '0': ok = not ^ json_is_null(cur);    not = false;   break;
+        case 'E': ok = not ^ json_equal(cur, lst); not = false;   break;
+        case 'Q': ok = JAIN(stk, 0, json_deep_copy(stk)) >= 0;    break;
+        case 'M': ok = cmd_move(p, stk, cur, lst);                break;
+        case 'U': ok = json_array_remove(stk, 0) >= 0;            break;
+        case 'j': ok = json_array_insert(stk, 0, p) >= 0;         break;
+        case 'c': ok = JAIN(stk, 0, json_deep_copy(cur)) >= 0;    break;
+        case 'q': ok = json_array_insert(stk, 0, p) >= 0;         break;
+        case 'o': ok = cmd_output(p, stk, cur, lst);              break;
+        case 'f': ok = cmd_foreach(p, stk, cur, lst);             break;
+        case 'u': ok = cmd_unquote(p, stk, cur, lst);             break;
+        case 't': ok = cmd_trunc(p, stk, cur, lst);               break;
+        case 'i': ok = cmd_insert(p, stk, cur, lst);              break;
+        case 'a': ok = cmd_append(p, stk, cur, lst);              break;
+        case 'x': ok = cmd_extend(p, stk, cur, lst);              break;
+        case 'd': ok = cmd_delete(p, stk, cur, lst);              break;
+        case 'l': ok = cmd_length(p, stk, cur, lst);              break;
+        case 'e': ok = cmd_empty(p, stk, cur, lst);               break;
+        case 'g': ok = cmd_get(p, stk, cur, lst);                 break;
+        case 's': ok = cmd_set(p, stk, cur, lst);                 break;
+        case 'Y': ok = JAIN(stk, 0, jose_b64_enc_dump(cur)) >= 0; break;
+        case 'y': ok = JAIN(stk, 0, jose_b64_dec_load(cur)) >= 0; break;
+        default:  ok = false;                                     break;
+        }
+
+        if (!ok)
+            return ret;
+    }
+
+    if (not)
+        return ret;
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_fmt, "fmt")

+ 537 - 0
cmd/jose.c

@@ -0,0 +1,537 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmd/jose.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#define MAXBUFLEN 1024
+#define RQARG required_argument
+#define NOARG no_argument
+
+static jcmd_t *cmds;
+
+static bool
+is_json_object_file(FILE *file)
+{
+    int c;
+
+    do {
+        c = fgetc(file);
+    } while (isspace(c));
+
+    return ungetc(c, file) == '{';
+}
+
+static bool
+jwks_extend(json_t *jwks, json_t *jwk_or_jwkset)
+{
+    json_t *keys = json_object_get(jwk_or_jwkset, "keys");
+    size_t size = json_array_size(keys);
+
+    if (!json_is_array(keys))
+        return json_array_append_new(jwks, jwk_or_jwkset) == 0;
+
+    for (size_t i = 0; i < size; i++) {
+        if (json_array_append(jwks, json_array_get(keys, i)) == -1) {
+            json_decref(jwk_or_jwkset);
+            return false;
+        }
+    }
+
+    json_decref(jwk_or_jwkset);
+    return size > 0;
+}
+
+void
+jcmd_push(jcmd_t *cmd)
+{
+    cmd->next = cmds;
+    cmds = cmd;
+}
+
+bool
+jcmd_opt_parse(int argc, char *argv[], const jcmd_cfg_t *cfgs, void *vopt,
+               const char *prefix)
+{
+    size_t ncfgs = 0;
+    int maxa = 0;
+    int maxl = 0;
+
+    for (; cfgs[ncfgs].doc; ncfgs++) {
+        const jcmd_cfg_t *cfg = &cfgs[ncfgs];
+
+        for (size_t i = 0; cfg->doc[i].doc; i++) {
+            int len = 0;
+
+            if (!cfg->doc[i].arg)
+                continue;
+
+            len = strlen(cfg->doc[i].arg);
+            if (len > maxa)
+                maxa = len;
+
+            len = strlen(cfg->opt.name) + len;
+            if (len > maxl)
+                maxl = len;
+        }
+
+        if (cfg->def) {
+            uint8_t *buf = vopt;
+            if (!cfg->set(cfg, &buf[cfg->off], cfg->def)) {
+                fprintf(stderr, "Invalid default value for %s!\n", cfg->opt.name);
+                return false;
+            }
+        }
+    }
+
+    char sopts[ncfgs * 3 + 3];
+    struct option lopts[ncfgs + 3];
+    memset(lopts, 0, sizeof(lopts));
+
+    lopts[0].has_arg = no_argument;
+    lopts[0].name = "help";
+    lopts[0].val = 'h';
+    lopts[1].has_arg = no_argument;
+    lopts[1].name = "version";
+    lopts[1].val = 'v';
+    strcpy(sopts, "hv");
+
+    for (size_t i = 0; i < ncfgs; i++) {
+        strncat(sopts, &(char) { cfgs[i].opt.val }, 1);
+        lopts[i + 2] = cfgs[i].opt;
+        switch (cfgs[i].opt.has_arg) {
+        case optional_argument: strcat(sopts, ":"); /* fallthrough */
+        case required_argument: strcat(sopts, ":"); /* fallthrough */
+        default: break;
+        }
+    }
+
+    for (int c; (c = getopt_long(argc, argv, sopts, lopts, NULL)) >= 0; ) {
+        bool found = false;
+
+        for (size_t i = 0; i < ncfgs; i++) {
+            uint8_t *buf = vopt;
+            if (cfgs[i].opt.val == c) {
+                found = true;
+
+                if (!cfgs[i].set(&cfgs[i], &buf[cfgs[i].off], optarg)) {
+                    fprintf(stderr, "Invalid %s!\n", cfgs[i].opt.name);
+                    goto usage;
+                }
+
+                break;
+            }
+        }
+
+        if (!found) {
+            switch (c) {
+            case 'h': goto usage;
+            case 'v': fprintf(stderr, "José %d\n", JOSE_VERSION); return false;
+            default:  fprintf(stderr, "Unknown option: %c!\n", c); goto usage;
+            }
+        }
+    }
+
+    return true;
+
+usage:
+    fprintf(stderr, "Usage: %s\n\n", prefix);
+
+    for (size_t i = 0; i < ncfgs; i++) {
+        for (size_t j = 0; cfgs[i].doc[j].doc; j++) {
+            const char *n = cfgs[i].opt.name;
+            const char  v = cfgs[i].opt.val;
+            const char *a = cfgs[i].doc[j].arg;
+            const char d = a ? '=' : ' ';
+            a = a ? a : "";
+            fprintf(stderr, "  -%c %-*s --%s%c%-*s  %s\n",
+                    v, maxa, a,
+                    n, d, maxl - (int) strlen(n), a,
+                    cfgs[i].doc[j].doc);
+        }
+
+        if (cfgs[i].def) {
+            fprintf(stderr, "%*sDefault: \"%s\"\n",
+                    maxa + maxl + 11, "", cfgs[i].def);
+        }
+
+        fprintf(stderr, "\n");
+    }
+
+    return false;
+}
+
+static bool
+valid_b64(const char *b64, size_t len)
+{
+    for (size_t i = 0; i < len; i++) {
+        if (b64[i] != 0 && !strchr(JOSE_B64_MAP, b64[i]))
+            return false;
+    }
+
+    return true;
+}
+
+static json_t *
+parse_compact(jcmd_opt_io_t *io, const char *arg)
+{
+    json_auto_t *tmp = json_object();
+    size_t i = 0;
+
+    for (size_t j = 0; io->fields[j].name; j++) {
+        const char *enc = strchr(&arg[i], '.');
+        size_t len = strlen(&arg[i]);
+
+        if (enc)
+            len = enc - &arg[i];
+        else if (io->fields[j + 1].name)
+            return NULL;
+
+        if (!valid_b64(&arg[i], len))
+            return NULL;
+
+        if (json_object_set_new(tmp, io->fields[j].name,
+                                json_stringn(&arg[i], len)) < 0)
+            return NULL;
+
+        i += len + 1;
+    }
+
+    return json_incref(tmp);
+}
+
+bool
+jcmd_opt_io_set_input(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    jcmd_opt_io_t *io = vopt;
+
+    jcmd_file_cleanup(&io->input);
+    json_decrefp(&io->obj);
+
+    io->obj = json_loads(arg, 0, NULL);
+    if (!io->obj)
+        io->obj = parse_compact(io, arg);
+    if (!io->obj) {
+        if (strcmp("-", arg) == 0)
+            io->input = stdin;
+        else
+            io->input = fopen(arg, "r");
+        if (!io->input)
+            return false;
+
+        if (is_json_object_file(io->input)) {
+            io->obj = json_loadf(io->input, JSON_DISABLE_EOF_CHECK, NULL);
+            jcmd_file_cleanup(&io->input);
+        } else {
+            io->obj = json_object();
+            for (size_t i = 0;
+                 io->fields[i].name &&
+                 io->fields[i + 1].name &&
+                 io->fields[i + 2].name; i++) {
+                if (json_object_set_new(io->obj, io->fields[i].name,
+                                        jcmd_compact_field(io->input)) < 0)
+                    return false;
+            }
+        }
+    }
+
+    return json_is_object(io->obj);
+}
+
+bool
+jcmd_opt_set_ifile(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    FILE **file = vopt;
+    jcmd_file_cleanup(file);
+    if (strcmp("-", arg) == 0)
+        *file = stdin;
+    else
+        *file = fopen(arg, "r");
+
+    return *file;
+}
+
+bool
+jcmd_opt_set_ofile(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    FILE **file = vopt;
+    jcmd_file_cleanup(file);
+    if (strcmp("-", arg) == 0)
+        *file = stdout;
+    else
+        *file = fopen(arg, "w");
+    return *file;
+}
+
+bool
+jcmd_opt_set_jsons(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_auto_t *tmp = NULL;
+    json_t **json = vopt;
+
+    if (!jcmd_opt_set_json(cfg, &tmp, arg))
+        return false;
+
+    if (!*json)
+        *json = json_array();
+
+    return json_array_append(*json, tmp) >= 0;
+}
+
+bool
+jcmd_opt_set_json(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    const int flags = JSON_DISABLE_EOF_CHECK | JSON_DECODE_ANY;
+    json_t **json = vopt;
+
+    json_decrefp(json);
+
+    *json = json_loads(arg, flags, NULL);
+    if (!*json) {
+        if (strcmp(arg, "-") == 0) {
+            *json = json_loadf(stdin, flags, NULL);
+        } else {
+            FILE_AUTO *file = fopen(arg, "r");
+            *json = json_loadf(file, flags, NULL);
+        }
+    }
+
+    return *json;
+}
+
+bool
+jcmd_opt_set_jwkt(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    const int flags = JSON_DISABLE_EOF_CHECK | JSON_DECODE_ANY;
+    json_auto_t *tmp = NULL;
+    json_t **jwks = vopt;
+
+    if (!*jwks)
+        *jwks = json_array();
+
+    tmp = json_loads(arg, flags, NULL);
+    if (!tmp) {
+        if (strcmp(arg, "-") == 0) {
+            tmp = json_loadf(stdin, flags, NULL);
+        } else {
+            FILE_AUTO *file = fopen(arg, "r");
+            tmp = json_loadf(file, flags, NULL);
+        }
+    }
+
+    if (!tmp)
+        return false;
+
+    switch (json_typeof(tmp)) {
+    case JSON_OBJECT:
+    case JSON_STRING:
+        return jwks_extend(*jwks, json_incref(tmp));
+    default:
+        return false;
+    }
+}
+
+bool
+jcmd_opt_set_jwks(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    const int flags = JSON_DISABLE_EOF_CHECK | JSON_DECODE_ANY;
+    json_auto_t *tmp = NULL;
+    json_t **jwks = vopt;
+
+    if (!*jwks)
+        *jwks = json_array();
+
+    if (strcmp(arg, "-") == 0) {
+        tmp = json_loadf(stdin, flags, NULL);
+    } else {
+        FILE_AUTO *file = fopen(arg, "r");
+        tmp = json_loadf(file, flags, NULL);
+    }
+
+    switch (tmp ? json_typeof(tmp) : JSON_INTEGER) {
+    case JSON_OBJECT:
+    case JSON_STRING:
+        return jwks_extend(*jwks, json_incref(tmp));
+    default:
+        return false;
+    }
+}
+
+bool
+jcmd_opt_set_flag(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    bool *flag = vopt;
+    return *flag = true;
+}
+
+void
+jcmd_opt_io_cleanup(jcmd_opt_io_t *io)
+{
+    if (!io)
+        return;
+
+    jcmd_file_cleanup(&io->detached);
+    jcmd_file_cleanup(&io->detach);
+    jcmd_file_cleanup(&io->output);
+    jcmd_file_cleanup(&io->input);
+    json_decrefp(&io->obj);
+}
+
+json_t *
+jcmd_compact_field(FILE *file)
+{
+    json_t *str = NULL;
+    char *buf = NULL;
+    size_t used = 0;
+    size_t size = 0;
+
+    for (int c = fgetc(file); c != EOF && c != '.'; c = fgetc(file)) {
+        if (used >= size) {
+            char *tmp = NULL;
+
+            size += 4096;
+            tmp = realloc(buf, size);
+            if (!tmp)
+                goto error;
+
+            buf = tmp;
+        }
+
+        buf[used++] = c;
+    }
+
+    str = json_stringn(buf ? buf : "", buf ? used : 0);
+
+error:
+    free(buf);
+    return str;
+}
+
+void
+jcmd_file_cleanup(FILE **file)
+{
+    if (file && *file) {
+        if (*file != stdin && *file != stdout)
+            fclose(*file);
+        *file = NULL;
+    }
+}
+
+static int
+nnames(const jcmd_t *cmd)
+{
+    int n = 0;
+
+    while (cmd->names[n])
+        n++;
+
+    return n;
+}
+
+static int
+cmp(const void *a, const void *b)
+{
+    const jcmd_t * const *ap = a;
+    const jcmd_t * const *bp = b;
+    int c = 0;
+
+    c = nnames(*ap) - nnames(*bp);
+
+    for (size_t i = 0; c == 0; i++) {
+        const char *an = (*ap)->names[i];
+        const char *bn = (*bp)->names[i];
+
+        if (!an && !bn)
+            return 0;
+
+        if (!an && bn)
+            return -1;
+
+        if (an && !bn)
+            return 1;
+
+        c = strcmp(an, bn);
+    }
+
+    return c;
+}
+
+int
+main(int argc, char *argv[])
+{
+    const char *last = NULL;
+    char full[40] = {};
+    size_t len = 0;
+
+    for (int i = 0; i < argc; i++)
+        len += strlen(argv[i]) + 1;
+
+    char cmd[len];
+
+    len = 0;
+    for (const jcmd_t *c = cmds; c; c = c->next) {
+        strcpy(cmd, "jose");
+        len++;
+
+        for (int i = 1; i < argc && c->names[i - 1]; i++) {
+            const char *name = c->names[i - 1];
+
+            if (strcmp(argv[i], name) != 0)
+                break;
+
+            if (!c->names[i]) {
+                argv[--i] = cmd;
+                return c->func(argc - i, argv + i);
+            }
+
+            strcat(cmd, " ");
+            strcat(cmd, name);
+        }
+    }
+
+    const jcmd_t *all[len];
+
+    for (const jcmd_t *c = cmds; c; c = c->next)
+        all[--len] = c;
+
+    qsort(all, sizeof(all) / sizeof(*all), sizeof(*all), cmp);
+
+    fprintf(stderr, "Usage: jose COMMAND [OPTIONS] [ARGUMENTS]\n\n");
+    fprintf(stderr, "Commands:\n");
+    for (size_t i = 0; i < sizeof(all) / sizeof(*all); i++) {
+        if (!(last && strcmp(all[i]->names[0], last) == 0))
+            fprintf(stderr, "\n");
+
+        strcpy(full, "jose");
+        for (size_t j = 0; all[i]->names[j]; j++) {
+            snprintf(full + strlen(full),
+                     sizeof(full) - strlen(full) - 1,
+                     " %s", all[i]->names[j]);
+        }
+
+        fprintf(stderr, "  %-13s %s\n", full, all[i]->desc);
+        last = all[i]->names[0];
+    }
+
+    fprintf(stderr, "\n");
+    return EXIT_FAILURE;
+}

+ 113 - 0
cmd/jose.h

@@ -0,0 +1,113 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <jose/jose.h>
+#include <getopt.h>
+
+#define __JCMD_AUTO(t) t ## _t __attribute__((cleanup(t ## _cleanup)))
+#define jcmd_opt_key_auto_t __JCMD_AUTO(jcmd_opt_key)
+#define jcmd_opt_io_auto_t  __JCMD_AUTO(jcmd_opt_io)
+#define jcmd_opt_auto_t     __JCMD_AUTO(jcmd_opt)
+#define FILE_AUTO      FILE __attribute__((cleanup(jcmd_file_cleanup)))
+
+#define JCMD_REGISTER(summary, function, ...)               \
+    static void __attribute__((constructor))                \
+    jcmd_ ## function ## _register(void)                    \
+    {                                                       \
+        static const char *names[] = { __VA_ARGS__, NULL }; \
+        static jcmd_t cmd = {                               \
+            .names = names,                                 \
+            .func = function,                               \
+            .desc = summary                                 \
+        };                                                  \
+        jcmd_push(&cmd);                                    \
+    }
+
+typedef struct jcmd_cfg jcmd_cfg_t;
+typedef bool jcmd_set_t(const jcmd_cfg_t *cfg, void *vopt, const char *arg);
+
+typedef struct {
+    const char *arg;
+    const char *doc;
+} jcmd_doc_t;
+
+struct jcmd_cfg {
+    const jcmd_doc_t *doc;
+    struct option opt;
+    const char *def;
+    jcmd_set_t *set;
+    off_t off;
+};
+
+typedef struct {
+    const char *name;
+    const char *mult;
+} jcmd_field_t;
+
+typedef struct {
+    const jcmd_field_t *fields;
+    FILE *detached;
+    bool  compact;
+    FILE *detach;
+    FILE *output;
+    FILE *input;
+    json_t *obj;
+} jcmd_opt_io_t;
+
+typedef struct jcmd jcmd_t;
+struct jcmd {
+    const jcmd_t *next;
+    const char *const *names;
+    int (*func)(int argc, char *argv[]);
+    const char *desc;
+};
+
+static const jcmd_doc_t jcmd_doc_key[] = {
+    { .arg = "FILE", .doc="Read JWK(Set) from FILE" },
+    { .arg = "-",    .doc="Read JWK(Set) from standard input" },
+    {}
+};
+
+void
+jcmd_push(jcmd_t *cmd);
+
+bool
+jcmd_opt_parse(int argc, char *argv[], const jcmd_cfg_t *cfgs, void *arg,
+               const char *prefix);
+
+jcmd_set_t jcmd_opt_io_set_input; /* Takes jcmd_opt_io_t* */
+jcmd_set_t jcmd_opt_set_ifile;    /* Takes FILE** */
+jcmd_set_t jcmd_opt_set_ofile;    /* Takes FILE** */
+jcmd_set_t jcmd_opt_set_jsons;    /* Takes json_t** */
+jcmd_set_t jcmd_opt_set_json;     /* Takes json_t** */
+jcmd_set_t jcmd_opt_set_jwkt;     /* Takes json_t** */
+jcmd_set_t jcmd_opt_set_jwks;     /* Takes json_t** */
+jcmd_set_t jcmd_opt_set_flag;     /* Takes bool* */
+
+void
+jcmd_opt_io_cleanup(jcmd_opt_io_t *io);
+
+void
+jcmd_opt_key_cleanup(jcmd_opt_io_t *io);
+
+json_t *
+jcmd_compact_field(FILE *file);
+
+void
+jcmd_file_cleanup(FILE **file);

+ 219 - 0
cmd/jwe/dec.c

@@ -0,0 +1,219 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwe.h"
+#include "pwd.h"
+#include <unistd.h>
+#include <string.h>
+
+#define SUMMARY "Decrypts a JWE using the supplied JWKs and outputs plaintext"
+
+typedef struct {
+    jcmd_opt_io_t io;
+    json_t *keys;
+    bool pwd;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwe dec -i JWE [-I CT] -k JWK [-p] [-O PT]\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_password[] = {
+    { .doc="Prompt for a decryption password, if necessary" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, io),
+        .set = jcmd_opt_io_set_input,
+        .doc = jcmd_jwe_doc_input,
+    },
+    {
+        .opt = { "detached", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_opt_t, io.detached),
+        .set = jcmd_opt_set_ifile,
+        .doc = jcmd_jwe_doc_detached,
+    },
+    {
+        .opt = { "password", no_argument, .val = 'p' },
+        .off = offsetof(jcmd_opt_t, pwd),
+        .set = jcmd_opt_set_flag,
+        .doc = doc_password,
+    },
+    {
+        .opt = { "key", required_argument, .val = 'k' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_doc_key,
+    },
+    {
+        .opt = { "detach", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_opt_t, io.detach),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jwe_doc_input,
+        .def = "-",
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_opt_io_cleanup(&opt->io);
+    json_decref(opt->keys);
+}
+
+static bool
+header_has_pbes2(const json_t *jwe, const json_t *rcp)
+{
+    json_auto_t *hdr = NULL;
+    const char *alg = NULL;
+
+    hdr = jose_jwe_hdr(jwe, rcp);
+    if (!hdr)
+        return false;
+
+    if (json_unpack(hdr, "{s:s}", "alg", &alg) < 0)
+        return false;
+
+    return strncmp(alg, "PBES2", strlen("PBES2")) == 0;
+}
+
+static bool
+jwe_has_pbes2(const json_t *jwe)
+{
+    json_t *rcps = NULL;
+
+    rcps = json_object_get(jwe, "recipients");
+    if (!json_is_array(rcps))
+        return header_has_pbes2(jwe, jwe);
+
+    for (size_t i = 0; i < json_array_size(rcps); i++) {
+        if (header_has_pbes2(jwe, json_array_get(rcps, i)))
+            return true;
+    }
+
+    return false;
+}
+
+static json_t *
+unwrap(const json_t *jwe, const json_t *jwks, bool prompt)
+{
+    json_auto_t *cek = NULL;
+
+    cek = jose_jwe_dec_jwk(NULL, jwe, NULL, jwks);
+    if (!cek && jwe_has_pbes2(jwe) && prompt) {
+        const char *pwd = NULL;
+
+        pwd = jwe_getpass("Please enter decryption password: ");
+        if (pwd) {
+            json_auto_t *jwk = json_string(pwd);
+            cek = jose_jwe_dec_jwk(NULL, jwe, NULL, jwk);
+        }
+    }
+
+    return json_incref(cek);
+}
+
+static int
+jcmd_jwe_dec(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = { .io.fields = jcmd_jwe_fields };
+    jose_io_auto_t *dec = NULL;
+    jose_io_auto_t *out = NULL;
+    json_auto_t *cek = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (!opt.io.obj) {
+        fprintf(stderr, "Invalid JWE!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (json_array_size(opt.keys) == 0 && !opt.pwd) {
+        fprintf(stderr, "MUST specify a JWK in non-interactive mode!\n\n");
+        return EXIT_FAILURE;
+    }
+
+    cek = unwrap(opt.io.obj, opt.keys, opt.pwd);
+    if (!cek) {
+        fprintf(stderr, "Unwrapping failed!\n");
+        return EXIT_FAILURE;
+    }
+
+    out = jose_io_file(NULL, opt.io.detach);
+    if (!out)
+        return EXIT_FAILURE;
+
+    dec = jose_jwe_dec_cek_io(NULL, opt.io.obj, cek, out);
+    if (!dec)
+        return EXIT_FAILURE;
+
+    if (!opt.io.detached) {
+        jose_io_auto_t *b64 = NULL;
+
+        b64 = jose_b64_dec_io(dec);
+        if (!b64)
+            return EXIT_FAILURE;
+
+        jose_io_auto(&dec);
+        dec = jose_io_incref(b64);
+    }
+
+    if (opt.io.detached || opt.io.input) {
+        FILE *f = opt.io.detached ? opt.io.detached : opt.io.input;
+
+        for (int c = fgetc(f); c != EOF; c = fgetc(f)) {
+            uint8_t b = c;
+
+            if (!opt.io.detached && b == '.')
+                break;
+
+            if (!dec->feed(dec, &b, sizeof(b)))
+                return EXIT_FAILURE;
+        }
+
+        for (int c = 0; opt.io.detached && opt.io.input && c != EOF && c != '.'; )
+            c = fgetc(opt.io.input);
+    } else {
+        const char *ct = NULL;
+        size_t ctl = 0;
+
+        if (json_unpack(opt.io.obj, "{s:s%}", "ciphertext", &ct, &ctl) < 0)
+            return EXIT_FAILURE;
+
+        if (!dec->feed(dec, ct, ctl))
+            return EXIT_FAILURE;
+    }
+
+    if (opt.io.input) {
+        if (json_object_set_new(opt.io.obj, "tag",
+                                jcmd_compact_field(opt.io.input)) < 0) {
+            fprintf(stderr, "Error reading last compact field!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (!dec->done(dec))
+        return EXIT_FAILURE;
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwe_dec, "jwe", "dec")

+ 344 - 0
cmd/jwe/enc.c

@@ -0,0 +1,344 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwe.h"
+#include "pwd.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Encrypts plaintext using one or more JWKs and outputs a JWE"
+
+typedef struct {
+    jcmd_opt_io_t io;
+    json_t *keys;
+    json_t *rcps;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwe enc [-i JWE] -I PT [-r RCP] -k JWK [-p] [-o JWE] [-O CT] [-c]\n\n" SUMMARY;
+
+static json_t *
+prompt(void)
+{
+    const char *c = NULL;
+    char *p = NULL;
+
+    while (!p || !c || strcmp(p, c) != 0) {
+        free(p);
+
+        p = strdup(jwe_getpass("Please enter an encryption password: "));
+        if (!p)
+            continue;
+
+        if (strlen(p) < 8) {
+            fprintf(stderr, "Password too short!\n");
+            continue;
+        }
+
+        c = jwe_getpass("Please re-enter the previous password: ");
+    }
+
+    free(p);
+    return json_string(c);
+}
+
+static bool
+opt_set_password(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_t **keys = vopt;
+
+    if (!*keys)
+        *keys = json_array();
+
+    return json_array_append_new(*keys, json_null()) == 0;
+}
+
+static const jcmd_doc_t doc_recipient[] = {
+    { .arg = "FILE", .doc="Read JWE recipient template from FILE" },
+    { .arg = "-",    .doc="Read JWE recipient template from standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_password[] = {
+    { .doc="Prompt for an encryption password" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, io),
+        .set = jcmd_opt_io_set_input,
+        .doc = jcmd_jwe_doc_input,
+        .def = "{}",
+    },
+    {
+        .opt = { "detached", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_opt_t, io.detached),
+        .set = jcmd_opt_set_ifile,
+        .doc = jcmd_jwe_doc_detached,
+    },
+    {
+        .opt = { "recipient", required_argument, .val = 'r' },
+        .off = offsetof(jcmd_opt_t, rcps),
+        .set = jcmd_opt_set_jsons,
+        .doc = doc_recipient,
+        .def = "{}",
+    },
+    {
+        .opt = { "key", required_argument, .val = 'k' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_doc_key,
+    },
+    {
+        .opt = { "password", no_argument, .val = 'p' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = opt_set_password,
+        .doc = doc_password,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, io.output),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jwe_doc_output,
+        .def = "-",
+    },
+    {
+        .opt = { "detach", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_opt_t, io.detach),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jwe_doc_detach,
+    },
+    {
+        .opt = { "compact", no_argument, .val = 'c' },
+        .off = offsetof(jcmd_opt_t, io.compact),
+        .set = jcmd_opt_set_flag,
+        .doc = jcmd_jwe_doc_compact,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_opt_io_cleanup(&opt->io);
+    json_decrefp(&opt->keys);
+    json_decrefp(&opt->rcps);
+}
+
+static bool
+opt_validate(jcmd_opt_t *opt)
+{
+    size_t nkeys = json_array_size(opt->keys);
+
+    if (nkeys == 0) {
+        fprintf(stderr, "Must specify a JWK or password!\n");
+        return false;
+    } else if (nkeys > 1 && opt->io.compact) {
+        fprintf(stderr, "Requested compact format with >1 recipient!\n");
+        return false;
+    }
+
+    if (!opt->io.detached) {
+        fprintf(stderr, "Must specify detached input!\n");
+        return false;
+    }
+
+    if (json_array_remove(opt->rcps, 0) < 0)
+        return false;
+
+    if (json_array_size(opt->keys) < json_array_size(opt->rcps)) {
+        fprintf(stderr, "Specified more recipients than keys!\n");
+        return false;
+    }
+
+    while (json_array_size(opt->rcps) < json_array_size(opt->keys)) {
+        if (json_array_append_new(opt->rcps, json_object()) < 0)
+            return false;
+    }
+
+    return true;
+}
+
+static json_t *
+wrap(jcmd_opt_t *opt)
+{
+    json_auto_t *cek = json_object();
+
+    for (size_t i = 0; i < json_array_size(opt->keys); i++) {
+        json_auto_t *jwk = json_incref(json_array_get(opt->keys, i));
+        json_t *rcp = json_array_get(opt->rcps, i);
+
+        if (json_is_null(jwk)) {
+            json_decref(jwk);
+            jwk = prompt();
+        }
+
+        if (!jose_jwe_enc_jwk(NULL, opt->io.obj, rcp, jwk, cek)) {
+            fprintf(stderr, "Wrapping failed!\n");
+            return NULL;
+        }
+    }
+
+    if (opt->io.compact) {
+        json_t *jh = NULL;
+
+        jh = jose_jwe_hdr(opt->io.obj, opt->io.obj);
+        if (!jh)
+            return NULL;
+
+        if (json_object_set_new(opt->io.obj, "protected", jh) < 0)
+            return NULL;
+
+        if (json_object_get(opt->io.obj, "unprotected") &&
+            json_object_del(opt->io.obj, "unprotected") < 0)
+            return NULL;
+
+        if (json_object_get(opt->io.obj, "header") &&
+            json_object_del(opt->io.obj, "header") < 0)
+            return NULL;
+    }
+
+    return json_incref(cek);
+}
+
+static int
+jcmd_jwe_enc(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = { .io.fields = jcmd_jwe_fields };
+    jose_io_auto_t *out = NULL;
+    jose_io_auto_t *enc = NULL;
+    json_auto_t *cek = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (!opt_validate(&opt))
+        return EXIT_FAILURE;
+
+    cek = wrap(&opt);
+    if (!cek)
+        return EXIT_FAILURE;
+
+    out = jose_io_file(NULL, opt.io.detach ? opt.io.detach : opt.io.output);
+    if (!out)
+        return EXIT_FAILURE;
+
+    if (!opt.io.detach) {
+        jose_io_auto_t *b64 = NULL;
+
+        b64 = jose_b64_enc_io(out);
+        if (!b64)
+            return EXIT_FAILURE;
+
+        jose_io_auto(&out);
+        out = jose_io_incref(b64);
+    }
+
+    enc = jose_jwe_enc_cek_io(NULL, opt.io.obj, cek, out);
+    if (!enc)
+        return EXIT_FAILURE;
+
+    if (!opt.io.detached) {
+        jose_io_auto_t *b64 = NULL;
+
+        b64 = jose_b64_dec_io(enc);
+        if (!b64)
+            return EXIT_FAILURE;
+
+        jose_io_auto(&enc);
+        enc = jose_io_incref(b64);
+    }
+
+    if (opt.io.compact) {
+        for (size_t i = 0; i < 3; i++) {
+            const char *k = jcmd_jwe_fields[i].name;
+            const char *v = NULL;
+
+            if (json_unpack(opt.io.obj, "{s?s}", k, &v) < 0)
+                return EXIT_FAILURE;
+
+            fprintf(opt.io.output, "%s.", v ? v : "");
+        }
+    } else {
+        fprintf(opt.io.output, "{");
+        if (!opt.io.detach)
+            fprintf(opt.io.output, "\"ciphertext\":\"");
+    }
+
+    if (opt.io.detached || opt.io.input) {
+        FILE *f = opt.io.detached ? opt.io.detached : opt.io.input;
+
+        for (int c = fgetc(f); c != EOF; c = fgetc(f)) {
+            uint8_t b = c;
+
+            if (!opt.io.detached && b == '.')
+                break;
+
+            if (!enc->feed(enc, &b, sizeof(b)))
+                return EXIT_FAILURE;
+        }
+
+        for (int c = 0; opt.io.detached && opt.io.input && c != EOF && c != '.'; )
+            c = fgetc(opt.io.input);
+    } else {
+        const char *ct = NULL;
+        size_t ctl = 0;
+
+        if (json_unpack(opt.io.obj, "{s:s%}", "ciphertext", &ct, &ctl) < 0)
+            return EXIT_FAILURE;
+
+        if (!enc->feed(enc, ct, ctl))
+            return EXIT_FAILURE;
+    }
+
+    if (opt.io.input) {
+        if (json_object_set_new(opt.io.obj, "tag",
+                                jcmd_compact_field(opt.io.input)) < 0) {
+            fprintf(stderr, "Error reading last compact field!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (!enc->done(enc))
+        return EXIT_FAILURE;
+
+    if (opt.io.compact) {
+        const char *v = NULL;
+
+        if (json_unpack(opt.io.obj, "{s:s}", "tag", &v) < 0) {
+            fprintf(stderr, "Missing tag parameter!\n");
+            return false;
+        }
+
+        fprintf(opt.io.output, ".%s", v);
+    } else {
+        if (!opt.io.detach)
+            fprintf(opt.io.output, "\",");
+        json_dumpf(opt.io.obj, opt.io.output,
+                   JSON_EMBED | JSON_COMPACT | JSON_SORT_KEYS);
+        fprintf(opt.io.output, "}");
+    }
+
+    if (isatty(fileno(opt.io.output)))
+        fprintf(opt.io.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwe_enc, "jwe", "enc")

+ 180 - 0
cmd/jwe/fmt.c

@@ -0,0 +1,180 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwe.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Converts a JWE between serialization formats"
+
+static const char *prefix =
+"jose jwe fmt -i JWE [-I CT] [-o JWE] [-O CT] [-c]\n\n" SUMMARY;
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .set = jcmd_opt_io_set_input,
+        .doc = jcmd_jwe_doc_input,
+    },
+    {
+        .opt = { "detached", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_opt_io_t, detached),
+        .set = jcmd_opt_set_ifile,
+        .doc = jcmd_jwe_doc_detached,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_io_t, output),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jwe_doc_input,
+        .def = "-",
+    },
+    {
+        .opt = { "detach", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_opt_io_t, detach),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jwe_doc_input,
+    },
+    {
+        .opt = { "compact", no_argument, .val = 'c' },
+        .off = offsetof(jcmd_opt_io_t, compact),
+        .set = jcmd_opt_set_flag,
+        .doc = jcmd_jwe_doc_compact,
+    },
+    {}
+};
+
+static int
+jcmd_jwe_fmt(int argc, char *argv[])
+{
+    jcmd_opt_io_auto_t opt = { .fields = jcmd_jwe_fields };
+    jose_io_auto_t *io = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (opt.detach) {
+        io = jose_io_file(NULL, opt.detach);
+    } else {
+        jose_io_auto_t *b64 = NULL;
+
+        io = jose_io_file(NULL, opt.output);
+        if (!io)
+            return EXIT_FAILURE;
+
+        b64 = jose_b64_enc_io(io);
+        if (!b64)
+            return EXIT_FAILURE;
+
+        jose_io_auto(&io);
+        io = jose_io_incref(b64);
+    }
+
+    if (!opt.detached) {
+        jose_io_auto_t *b64 = NULL;
+
+        b64 = jose_b64_dec_io(io);
+        if (!b64)
+            return EXIT_FAILURE;
+
+        jose_io_auto(&io);
+        io = jose_io_incref(b64);
+    }
+
+    if (opt.compact) {
+        for (size_t i = 0; strcmp(opt.fields[i].name, "ciphertext") != 0; i++) {
+            const jcmd_field_t *f = &opt.fields[i];
+            const char *k = f->name;
+            const char *v = NULL;
+
+            if (json_unpack(opt.obj, "{s:[{s?s}!]}", f->mult, k, &v) < 0 &&
+                json_unpack(opt.obj, "{s?s}", k, &v) < 0) {
+                fprintf(stderr, "Input JWS cannot be converted to compact.\n");
+                return EXIT_FAILURE;
+            }
+
+            fprintf(opt.output, "%s.", v ? v : "");
+        }
+    } else {
+        fprintf(opt.output, "{");
+        if (!opt.detach)
+            fprintf(opt.output, "\"ciphertext\":\"");
+    }
+
+    if (opt.detached || opt.input) {
+        FILE *f = opt.detached ? opt.detached : opt.input;
+
+        for (int c = fgetc(f); c != EOF; c = fgetc(f)) {
+            uint8_t b = c;
+
+            if (!opt.detached && b == '.')
+                break;
+
+            if (!io->feed(io, &b, sizeof(b)))
+                return EXIT_FAILURE;
+        }
+
+        for (int c = 0; opt.detached && opt.input && c != EOF && c != '.'; )
+            c = fgetc(opt.input);
+    } else {
+        const char *ct = NULL;
+        size_t ctl = 0;
+
+        if (json_unpack(opt.obj, "{s:s%}", "ciphertext", &ct, &ctl) < 0)
+            return EXIT_FAILURE;
+
+        if (!io->feed(io, ct, ctl))
+            return EXIT_FAILURE;
+    }
+
+    if (!io->done(io))
+        return EXIT_FAILURE;
+
+    if (opt.input) {
+        if (json_object_set_new(opt.obj, "tag",
+                                jcmd_compact_field(opt.input)) < 0) {
+            fprintf(stderr, "Error reading last compact field!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (opt.compact) {
+        const char *v = NULL;
+
+        if (json_unpack(opt.obj, "{s:s}", "tag", &v) < 0 &&
+            json_unpack(opt.obj, "{s:[{s:s}!]}", "recipients", "tag", &v) < 0) {
+            fprintf(stderr, "Missing tag parameter!\n");
+            return EXIT_FAILURE;
+        }
+
+        fprintf(opt.output, ".%s", v);
+    } else {
+        if (!opt.detach)
+            fprintf(opt.output, "\",");
+        json_dumpf(opt.obj, opt.output,
+                   JSON_EMBED | JSON_COMPACT | JSON_SORT_KEYS);
+        fprintf(opt.output, "}");
+    }
+
+    if (isatty(fileno(opt.output)))
+        fprintf(opt.output, "\n");
+
+    return EXIT_SUCCESS;
+
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwe_fmt, "jwe", "fmt")

+ 59 - 0
cmd/jwe/jwe.h

@@ -0,0 +1,59 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../jose.h"
+
+static const jcmd_field_t jcmd_jwe_fields[] = {
+    { .name = "protected" },
+    { .name = "encrypted_key", .mult = "recipients" },
+    { .name = "iv" },
+    { .name = "ciphertext" },
+    { .name = "tag" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwe_doc_input[] = {
+    { .arg = "JSON", .doc="Parse JWE from JSON" },
+    { .arg = "FILE", .doc="Read JWE from FILE" },
+    { .arg = "-",    .doc="Read JWE from standard input" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwe_doc_detached[] = {
+    { .arg = "FILE", .doc="Read decoded ciphertext from FILE" },
+    { .arg = "-",    .doc="Read decoded ciphertext from standard input" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwe_doc_output[] = {
+    { .arg = "FILE", .doc="Write JWE to FILE" },
+    { .arg = "-",    .doc="Write JWE to stdout (default)" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwe_doc_detach[] = {
+    { .arg = "FILE", .doc="Detach ciphertext and decode to FILE" },
+    { .arg = "-",    .doc="Detach ciphertext and decode to standard output" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwe_doc_compact[] = {
+    { .doc="Output JWE using compact serialization" },
+    {}
+};

+ 77 - 0
cmd/jwe/pwd.h

@@ -0,0 +1,77 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string.h>
+#include <ctype.h>
+
+#ifdef _WIN32
+static const char *
+jwe_getpass(const char *prompt)
+{
+    static char pwd[4096];
+
+    fprintf(stdout, "%s", prompt);
+
+    memset(pwd, 0, sizeof(pwd));
+    for (size_t i = 0; i < sizeof(pwd) - 1; i++) {
+        int c = fgetc(stdin);
+        if (c == EOF || !isprint(c) || isspace(c))
+            break;
+
+        pwd[i] = c;
+    }
+
+    return pwd;
+}
+#else
+#include <termios.h>
+static const char *
+jwe_getpass(const char *prompt)
+{
+    static char pwd[4096];
+    struct termios of, nf;
+    FILE *tty = NULL;
+
+    tty = fopen("/dev/tty", "r+");
+    if (!tty)
+        return NULL;
+
+    tcgetattr(fileno(tty), &of);
+    nf = of;
+    nf.c_lflag &= ~ECHO;
+    nf.c_lflag |= ECHONL;
+
+    if (tcsetattr(fileno(tty), TCSANOW, &nf) != 0)
+        return NULL;
+
+    fprintf(tty, "%s", prompt);
+
+    memset(pwd, 0, sizeof(pwd));
+    for (size_t i = 0; i < sizeof(pwd) - 1; i++) {
+        int c = fgetc(tty);
+        if (c == EOF || !isprint(c) || isspace(c))
+            break;
+
+        pwd[i] = c;
+    }
+
+    tcsetattr(fileno(tty), TCSANOW, &of);
+    return pwd;
+}
+#endif

+ 70 - 0
cmd/jwk/eql.c

@@ -0,0 +1,70 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwk.h"
+#include <string.h>
+
+#define SUMMARY "Determines if two or more JWKs are equal"
+
+typedef struct {
+    json_t *keys;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwk eql -i JWK -i JWK\n\n" SUMMARY;
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_jwk_doc_input,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    json_decrefp(&opt->keys);
+}
+
+static int
+jcmd_jwk_eql(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (json_array_size(opt.keys) < 2) {
+        fprintf(stderr, "Must specify at least two JWKs!\n");
+        return EXIT_FAILURE;
+    }
+
+    for (size_t i = 1; i < json_array_size(opt.keys); i++) {
+        const json_t *a = json_array_get(opt.keys, i - 1);
+        const json_t *b = json_array_get(opt.keys, i);
+
+        if (!jose_jwk_eql(NULL, a, b))
+            return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwk_eql, "jwk", "eql")

+ 144 - 0
cmd/jwk/exc.c

@@ -0,0 +1,144 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwk.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Performs a key exchange using the two input keys"
+
+typedef struct {
+    FILE *output;
+    json_t *keys;
+    json_t *lcl;
+    json_t *rem;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwk exc [-i JWK] -l JWK -r JWK [-o JWK]\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_input[] = {
+    { .arg = "JSON", .doc="Parse JWK template from JSON" },
+    { .arg = "FILE", .doc="Read JWK template from FILE" },
+    { .arg = "-",    .doc="Read JWK template from standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_output[] = {
+    { .arg = "FILE", .doc="Write JWK to FILE" },
+    { .arg = "-",    .doc="Write JWK to standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_local[] = {
+    { .arg = "FILE", .doc="Read local JWK from FILE" },
+    { .arg = "-",    .doc="Read local JWK from standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_remote[] = {
+    { .arg = "FILE", .doc="Read remote JWK from FILE" },
+    { .arg = "-",    .doc="Read remote JWK from standard input" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwkt,
+        .doc = doc_input,
+        .def = "{}",
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, output),
+        .set = jcmd_opt_set_ofile,
+        .doc = doc_output,
+        .def = "-",
+    },
+    {
+        .opt = { "local", required_argument, .val = 'l' },
+        .off = offsetof(jcmd_opt_t, lcl),
+        .set = jcmd_opt_set_jwks,
+        .doc = doc_local,
+    },
+    {
+        .opt = { "remote", required_argument, .val = 'r' },
+        .off = offsetof(jcmd_opt_t, rem),
+        .set = jcmd_opt_set_jwks,
+        .doc = doc_remote,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_file_cleanup(&opt->output);
+    json_decrefp(&opt->keys);
+    json_decrefp(&opt->lcl);
+    json_decrefp(&opt->rem);
+}
+
+static int
+jcmd_jwk_exc(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+    json_auto_t *key = NULL;
+    json_t *tmpl = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (json_array_size(opt.keys) > 1 && json_array_remove(opt.keys, 0) < 0)
+        return EXIT_FAILURE;
+
+    if (json_array_size(opt.lcl) != 1) {
+        fprintf(stderr, "Local JWK must be specified exactly once!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (json_array_size(opt.rem) != 1) {
+        fprintf(stderr, "Remote JWK must be specified exactly once!\n");
+        return EXIT_FAILURE;
+    }
+
+    key = jose_jwk_exc(NULL, json_array_get(opt.lcl, 0),
+                       json_array_get(opt.rem, 0));
+    if (!key) {
+        fprintf(stderr, "Error performing exchange!\n");
+        return EXIT_FAILURE;
+    }
+
+    tmpl = json_array_get(opt.keys, json_array_size(opt.keys) - 1);
+
+    if (json_object_update(tmpl, key) < 0)
+        return EXIT_FAILURE;
+
+    if (json_dumpf(tmpl, opt.output, JSON_COMPACT | JSON_SORT_KEYS) < 0) {
+        fprintf(stderr, "Error writing JWK!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (isatty(fileno(opt.output)))
+        fprintf(opt.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwk_exc, "jwk", "exc")

+ 114 - 0
cmd/jwk/gen.c

@@ -0,0 +1,114 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwk.h"
+#include <unistd.h>
+
+#define SUMMARY "Creates a random JWK for each input JWK template"
+
+typedef struct {
+    FILE *output;
+    json_t *keys;
+    bool set;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwk gen -i JWK [-s] [-o JWK]\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_input[] = {
+    { .arg = "JSON", .doc="Parse JWK(Set) template from JSON" },
+    { .arg = "FILE", .doc="Read JWK(Set) template from FILE" },
+    { .arg = "-",    .doc="Read JWK(Set) template from standard input" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument,  .val = 'i' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwkt,
+        .doc = doc_input,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, output),
+        .doc = jcmd_jwk_doc_output,
+        .set = jcmd_opt_set_ofile,
+        .def = "-",
+    },
+    {
+        .opt = { "set", no_argument, .val = 's' },
+        .off = offsetof(jcmd_opt_t, set),
+        .doc = jcmd_jwk_doc_set,
+        .set = jcmd_opt_set_flag,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_file_cleanup(&opt->output);
+    json_decrefp(&opt->keys);
+}
+
+static int
+jcmd_jwk_gen(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (json_array_size(opt.keys) == 0) {
+        fprintf(stderr, "At least one JWK template is required!\n");
+        return EXIT_FAILURE;
+    }
+
+    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");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (json_array_size(opt.keys) == 1 && !opt.set) {
+        if (json_dumpf(json_array_get(opt.keys, 0), opt.output,
+                       JSON_COMPACT | JSON_SORT_KEYS) < 0) {
+            fprintf(stderr, "Error dumping JWK!\n");
+            return EXIT_FAILURE;
+        }
+    } else {
+        json_auto_t *jwks = NULL;
+
+        jwks = json_pack("{s:O}", "keys", opt.keys);
+        if (!jwks)
+            return EXIT_FAILURE;
+
+        if (json_dumpf(jwks, opt.output, JSON_COMPACT | JSON_SORT_KEYS) < 0) {
+            fprintf(stderr, "Error dumping JWKSet!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (isatty(fileno(opt.output)))
+        fprintf(opt.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwk_gen, "jwk", "gen")

+ 38 - 0
cmd/jwk/jwk.h

@@ -0,0 +1,38 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../jose.h"
+
+static const jcmd_doc_t jcmd_jwk_doc_input[] = {
+    { .arg = "JSON", .doc="Parse JWK(Set) from JSON" },
+    { .arg = "FILE", .doc="Read JWK(Set) from FILE" },
+    { .arg = "-",    .doc="Read JWK(Set) standard input" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwk_doc_output[] = {
+    { .arg = "FILE", .doc="Write JWK(Set) to FILE" },
+    { .arg = "-",    .doc="Write JWK(Set) to standard input" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jwk_doc_set[] = {
+    { .doc="Always output a JWKSet" },
+    {}
+};

+ 107 - 0
cmd/jwk/pub.c

@@ -0,0 +1,107 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwk.h"
+#include <unistd.h>
+
+#define SUMMARY "Cleans private keys from a JWK"
+
+typedef struct {
+    FILE *output;
+    json_t *keys;
+    bool set;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwk pub -i JWK [-s] [-o JWK]\n\n" SUMMARY;
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument,  .val = 'i' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_jwk_doc_input,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, output),
+        .doc = jcmd_jwk_doc_output,
+        .set = jcmd_opt_set_ofile,
+        .def = "-",
+    },
+    {
+        .opt = { "set", no_argument, .val = 's' },
+        .off = offsetof(jcmd_opt_t, set),
+        .doc = jcmd_jwk_doc_set,
+        .set = jcmd_opt_set_flag,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_file_cleanup(&opt->output);
+    json_decrefp(&opt->keys);
+}
+
+static int
+jcmd_jwk_pub(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+    json_auto_t *out = NULL;
+    bool fail = false;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    for (size_t i = 0; !fail && i < json_array_size(opt.keys); i++)
+        fail |= !jose_jwk_pub(NULL, json_array_get(opt.keys, i));
+    if (fail) {
+        fprintf(stderr, "Error removing private keys!\n");
+        return EXIT_FAILURE;
+    }
+
+    switch (json_array_size(opt.keys)) {
+    case 0:
+        fprintf(stderr, "MUST specify at least one JWK(Set)!\n");
+        return EXIT_FAILURE;
+
+    case 1:
+        if (!opt.set) {
+            out = json_incref(json_array_get(opt.keys, 0));
+            break;
+        }
+        /* fallthrough */
+
+    default:
+        out = json_pack("{s:O}", "keys", opt.keys);
+        break;
+    }
+
+    if (json_dumpf(out, opt.output, JSON_SORT_KEYS | JSON_COMPACT) < 0) {
+        fprintf(stderr, "Error dumping JWK(Set)!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (isatty(fileno(opt.output)))
+        fprintf(opt.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwk_pub, "jwk", "pub")

+ 188 - 0
cmd/jwk/thp.c

@@ -0,0 +1,188 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwk.h"
+#include "../../lib/hooks.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Calculates the JWK thumbprint"
+
+typedef struct {
+    const char *hash;
+    const char *find;
+    json_t *keys;
+    FILE *output;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwk thp -i JWK [-a ALG] [-o THP]\n\n" SUMMARY;
+
+static bool
+opt_set_thp(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    const char **find = vopt;
+    *find = arg;
+    return true;
+}
+
+static bool
+opt_set_hash(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    const char **hash = vopt;
+
+    if (strcmp(arg, "?") == 0) {
+        for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+            if (a->kind == JOSE_HOOK_ALG_KIND_HASH)
+                fprintf(stdout, "%s\n", a->name);
+        }
+
+        exit(EXIT_SUCCESS);
+    }
+
+    if (!jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, arg))
+        return false;
+
+    *hash = arg;
+    return true;
+}
+
+static const jcmd_doc_t doc_hash[] = {
+    { .arg = "ALG", .doc = "Use the specified hash algorithm (case sensitive)" },
+    { .arg = "?",   .doc = "List available hash algorithms" },
+    {}
+};
+
+static const jcmd_doc_t doc_output[] = {
+    { .arg = "FILE", .doc="Write thumbprint(s) to FILE" },
+    { .arg = "-",    .doc="Write thumbprint(s) to standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_find[] = {
+    { .arg = "THP", .doc = "Search input keys for JWK with the given thumbprint" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_jwk_doc_input,
+    },
+    {
+        .opt = { "algorithm", required_argument, .val = 'a' },
+        .off = offsetof(jcmd_opt_t, hash),
+        .set = opt_set_hash,
+        .doc = doc_hash,
+        .def = "S1",
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, output),
+        .doc = doc_output,
+        .set = jcmd_opt_set_ofile,
+        .def = "-",
+    },
+    {
+        .opt = { "find", required_argument, .val = 'f' },
+        .off = offsetof(jcmd_opt_t, find),
+        .doc = doc_find,
+        .set = opt_set_thp
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_file_cleanup(&opt->output);
+    json_decrefp(&opt->keys);
+}
+
+static int
+jcmd_jwk_thp(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+    size_t elen = 0;
+    size_t dlen = 0;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (json_array_size(opt.keys) == 0) {
+        fprintf(stderr, "Must specify JWK(Set)!\n");
+        return EXIT_FAILURE;
+    }
+
+    dlen = jose_jwk_thp_buf(NULL, NULL, opt.hash, NULL, 0);
+    if (dlen == SIZE_MAX) {
+        fprintf(stderr, "Error determining hash size!\n");
+        return EXIT_FAILURE;
+    }
+
+    elen = jose_b64_enc_buf(NULL, dlen, NULL, 0);
+    if (elen == SIZE_MAX)
+        return EXIT_FAILURE;
+
+    for (size_t i = 0; i < json_array_size(opt.keys); i++) {
+        const json_t *jwk = json_array_get(opt.keys, i);
+
+        for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+            uint8_t dec[dlen];
+            char enc[elen];
+
+            if (a->kind != JOSE_HOOK_ALG_KIND_HASH)
+                continue;
+
+            if (!opt.find && strcmp(opt.hash, a->name) != 0)
+                continue;
+
+            if (!jose_jwk_thp_buf(NULL, jwk, opt.hash, dec, sizeof(dec))) {
+                fprintf(stderr, "Error making thumbprint!\n");
+                return EXIT_FAILURE;
+            }
+
+            if (jose_b64_enc_buf(dec, dlen, enc, sizeof(enc)) != elen)
+                return EXIT_FAILURE;
+
+            if (!opt.find) {
+                if (fwrite(enc, 1, elen, opt.output) != elen)
+                    return EXIT_FAILURE;
+
+                if (json_array_size(opt.keys) > 1 ||
+                    isatty(fileno(opt.output))) {
+                    if (fprintf(opt.output, "\n") != 1)
+                        return EXIT_FAILURE;
+                }
+            } else if (strlen(opt.find) == elen &&
+                       strncmp(opt.find, enc, elen) == 0) {
+                static const int flags = JSON_COMPACT | JSON_SORT_KEYS;
+
+                if (json_dumpf(jwk, opt.output, flags) < 0)
+                    return EXIT_FAILURE;
+
+                return EXIT_SUCCESS;
+           }
+        }
+    }
+
+    return opt.find ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwk_thp, "jwk", "thp")

+ 198 - 0
cmd/jwk/use.c

@@ -0,0 +1,198 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jwk.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Validates the permissions of a key for the specified use(s)"
+
+typedef struct {
+    FILE *output;
+    json_t *keys;
+    json_t *uses;
+    bool req;
+    bool all;
+    bool set;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jwk use -i JWK [-a] [-r] -u OP [-o JWK [-s]]\n\n" SUMMARY;
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_file_cleanup(&opt->output);
+    json_decrefp(&opt->keys);
+    json_decrefp(&opt->uses);
+}
+
+static bool
+opt_set_use(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
+{
+    json_t **uses = vopt;
+
+    if (!*uses)
+        *uses = json_array();
+
+    return json_array_append_new(*uses, json_string(arg)) == 0;
+}
+
+static const jcmd_doc_t doc_use[] = {
+    { .arg = "sign",       .doc = "Validate the key for signing" },
+    { .arg = "verify",     .doc = "Validate the key for verifying" },
+    { .arg = "encrypt",    .doc = "Validate the key for encrypting" },
+    { .arg = "decrypt",    .doc = "Validate the key for decrypting" },
+    { .arg = "wrapKey",    .doc = "Validate the key for wrapping" },
+    { .arg = "unwrapKey",  .doc = "Validate the key for unwrapping" },
+    { .arg = "deriveKey",  .doc = "Validate the key for deriving keys" },
+    { .arg = "deriveBits", .doc = "Validate the key for deriving bits" },
+    {}
+};
+
+static const jcmd_doc_t doc_all[] = {
+    { .doc = "Succeeds only if all operations are allowed" },
+    {}
+};
+
+static const jcmd_doc_t doc_req[] = {
+    { .doc = "Operations must be explicitly allowed" },
+    {}
+};
+
+static const jcmd_doc_t doc_output[] = {
+    { .arg = "FILE", .doc = "Filter keys to FILE as JWK(Set)" },
+    { .arg = "-",    .doc = "Filter keys to standard output as JWK(Set)" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_jwk_doc_input,
+    },
+    {
+        .opt = { "use", required_argument, .val = 'u' },
+        .off = offsetof(jcmd_opt_t, uses),
+        .set = opt_set_use,
+        .doc = doc_use,
+    },
+    {
+        .opt = { "all", no_argument, .val = 'a' },
+        .off = offsetof(jcmd_opt_t, all),
+        .set = jcmd_opt_set_flag,
+        .doc = doc_all,
+    },
+    {
+        .opt = { "required", no_argument, .val = 'r' },
+        .off = offsetof(jcmd_opt_t, req),
+        .set = jcmd_opt_set_flag,
+        .doc = doc_req,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, output),
+        .set = jcmd_opt_set_ofile,
+        .doc = doc_output,
+    },
+    {
+        .opt = { "set", no_argument, .val = 's' },
+        .off = offsetof(jcmd_opt_t, set),
+        .doc = jcmd_jwk_doc_set,
+        .set = jcmd_opt_set_flag,
+    },
+    {}
+};
+
+static int
+jcmd_jwk_use(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = {};
+    json_auto_t *arr = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (json_array_size(opt.uses) == 0) {
+        fprintf(stderr, "No uses specified!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (json_array_size(opt.keys) == 0) {
+        fprintf(stderr, "No JWK specified!\n");
+        return EXIT_FAILURE;
+    }
+
+    arr = json_array();
+    if (!arr)
+        return EXIT_FAILURE;
+
+    for (size_t i = 0; i < json_array_size(opt.keys); i++) {
+        json_t *jwk = json_array_get(opt.keys, i);
+        bool status = false;
+
+        for (size_t j = 0; j < json_array_size(opt.uses); j++) {
+            const char *use = json_string_value(json_array_get(opt.uses, j));
+
+            if (opt.all)
+                status |= !jose_jwk_prm(NULL, jwk, opt.req, use);
+            else
+                status |= jose_jwk_prm(NULL, jwk, opt.req, use);
+        }
+
+        status = opt.all ? !status : status;
+
+        if (opt.output) {
+            if (status && json_array_append(arr, jwk) < 0)
+                return EXIT_FAILURE;
+        } else if (!status) {
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (opt.output) {
+        json_auto_t *jwkset = NULL;
+
+        switch (json_array_size(arr)) {
+        case 0: return EXIT_FAILURE;
+        case 1:
+            if (!opt.set) {
+                jwkset = json_incref(json_array_get(arr, 0));
+                break;
+            }
+            /* fallthrough */
+        default:
+            jwkset = json_pack("{s:O}", "keys", arr);
+            break;
+        }
+
+        if (!jwkset)
+            return EXIT_FAILURE;
+
+        if (json_dumpf(jwkset, opt.output, JSON_COMPACT | JSON_SORT_KEYS) < 0)
+            return EXIT_FAILURE;
+
+        if (isatty(fileno(opt.output)))
+            fprintf(opt.output, "\n");
+    }
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jwk_use, "jwk", "use")

+ 154 - 0
cmd/jws/fmt.c

@@ -0,0 +1,154 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jws.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Converts a JWS between serialization formats"
+
+static const char *prefix =
+"jose jws fmt -i JWS [-I PAY] [-o JWS] [-O PAY] [-c]\n\n" SUMMARY;
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .set = jcmd_opt_io_set_input,
+        .doc = jcmd_jws_doc_input,
+    },
+    {
+        .opt = { "detached", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_opt_io_t, detached),
+        .set = jcmd_opt_set_ifile,
+        .doc = jcmd_jws_doc_detached,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_io_t, output),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jws_doc_output,
+        .def = "-",
+    },
+    {
+        .opt = { "detach", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_opt_io_t, detach),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jws_doc_detach,
+    },
+    {
+        .opt = { "compact", no_argument, .val = 'c' },
+        .off = offsetof(jcmd_opt_io_t, compact),
+        .set = jcmd_opt_set_flag,
+        .doc = jcmd_jws_doc_compact,
+    },
+    {}
+};
+
+static int
+jcmd_jws_fmt(int argc, char *argv[])
+{
+    jcmd_opt_io_auto_t opt = { .fields = jcmd_jws_fields };
+    jose_io_auto_t *io = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    io = jcmd_jws_prep_io(&opt, NULL);
+    if (!io)
+        return EXIT_FAILURE;
+
+    if (opt.compact) {
+        const jcmd_field_t *f = &jcmd_jws_fields[0];
+        const char *k = f->name;
+        const char *v = NULL;
+
+        if (json_unpack(opt.obj, "{s:[{s?s}!]}", f->mult, k, &v) < 0 &&
+            json_unpack(opt.obj, "{s?s}", k, &v) < 0) {
+            fprintf(stderr, "Input JWS cannot be converted to compact.\n");
+            return EXIT_FAILURE;
+        }
+
+        fprintf(opt.output, "%s.", v ? v : "");
+    } else {
+        fprintf(opt.output, "{");
+        if (!opt.detach)
+            fprintf(opt.output, "\"payload\":\"");
+    }
+
+    if (opt.detached || opt.input) {
+        FILE *f = opt.detached ? opt.detached : opt.input;
+
+        for (int c = fgetc(f); c != EOF; c = fgetc(f)) {
+            uint8_t b = c;
+
+            if (!opt.detached && b == '.')
+                break;
+
+            if (!io->feed(io, &b, sizeof(b)))
+                return EXIT_FAILURE;
+        }
+
+        for (int c = 0; opt.detached && opt.input && c != EOF && c != '.'; )
+            c = fgetc(opt.input);
+    } else {
+        const char *pay = NULL;
+        size_t payl = 0;
+
+        if (json_unpack(opt.obj, "{s?s%}", "payload", &pay, &payl) < 0)
+            return EXIT_FAILURE;
+
+        if (!io->feed(io, pay ? pay : "", payl))
+            return EXIT_FAILURE;
+    }
+
+    if (!io->done(io))
+        return EXIT_FAILURE;
+
+    if (opt.input) {
+        if (json_object_set_new(opt.obj, "signature",
+                                jcmd_compact_field(opt.input)) < 0) {
+            fprintf(stderr, "Error reading last compact field!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (opt.compact) {
+        const char *v = NULL;
+
+        if (json_unpack(opt.obj, "{s:s}", "signature", &v) < 0 &&
+            json_unpack(opt.obj, "{s:[{s:s}!]}",
+                        "signatures", "signature", &v) < 0) {
+            fprintf(stderr, "Missing signature parameter!\n");
+            return EXIT_FAILURE;
+        }
+
+        fprintf(opt.output, ".%s", v);
+    } else {
+        if (!opt.detach)
+            fprintf(opt.output, "\",");
+        json_dumpf(opt.obj, opt.output,
+                   JSON_EMBED | JSON_COMPACT | JSON_SORT_KEYS);
+        fprintf(opt.output, "}");
+    }
+
+    if (isatty(fileno(opt.output)))
+        fprintf(opt.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jws_fmt, "jws", "fmt")

+ 112 - 0
cmd/jws/jws.h

@@ -0,0 +1,112 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "../jose.h"
+#include <string.h>
+
+static const jcmd_field_t jcmd_jws_fields[] = {
+    { .name = "protected", .mult = "signatures" },
+    { .name = "payload" },
+    { .name = "signature", .mult = "signatures" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jws_doc_input[] = {
+    { .arg = "JSON", .doc="Parse JWS from JSON" },
+    { .arg = "FILE", .doc="Read JWS from FILE" },
+    { .arg = "-",    .doc="Read JWS from standard input" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jws_doc_detached[] = {
+    { .arg = "FILE", .doc="Read decoded payload from FILE" },
+    { .arg = "-",    .doc="Read decoded payload from standard input" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jws_doc_output[] = {
+    { .arg = "FILE", .doc="Write JWS to FILE" },
+    { .arg = "-",    .doc="Write JWS to stdout (default)" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jws_doc_detach[] = {
+    { .arg = "FILE", .doc="Detach payload and decode to FILE" },
+    { .arg = "-",    .doc="Detach payload and decode to standard output" },
+    {}
+};
+
+static const jcmd_doc_t jcmd_jws_doc_compact[] = {
+    { .doc="Output JWS using compact serialization" },
+    {}
+};
+
+static void
+jcmd_jws_ios_auto(jose_io_t ***iosp)
+{
+    jose_io_t **ios = *iosp;
+
+    for (size_t i = 0; ios && ios[i]; i++)
+        jose_io_auto(&ios[i]);
+}
+
+static jose_io_t *
+jcmd_jws_prep_io(jcmd_opt_io_t *opt, jose_io_t *io)
+{
+    jose_io_t __attribute__((cleanup(jcmd_jws_ios_auto))) **ios = NULL;
+    size_t i = 0;
+
+    ios = alloca(sizeof(*ios) * 3);
+    memset(ios, 0, sizeof(*ios) * 3);
+
+    if (io)
+        ios[i++] = io;
+
+    if (opt->detach) {
+        jose_io_auto_t *b64 = NULL;
+
+        ios[i] = jose_io_file(NULL, opt->detach);
+        if (!ios[i])
+            return NULL;
+
+        b64 = jose_b64_dec_io(ios[i]);
+        if (!b64)
+            return NULL;
+
+        jose_io_auto(&ios[i]);
+        ios[i] = jose_io_incref(b64);
+    } else if (opt->output) {
+        ios[i] = jose_io_file(NULL, opt->output);
+        if (!ios[i])
+            return NULL;
+    }
+
+    for (i = 0; opt->detached && ios[i]; i++) {
+        jose_io_auto_t *b64 = NULL;
+
+        b64 = jose_b64_enc_io(ios[i]);
+        if (!b64)
+            return NULL;
+
+        jose_io_decref(ios[i]);
+        ios[i] = jose_io_incref(b64);
+    }
+
+    return jose_io_multiplex(NULL, ios, true);
+}

+ 244 - 0
cmd/jws/sig.c

@@ -0,0 +1,244 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jws.h"
+#include <string.h>
+#include <unistd.h>
+
+#define SUMMARY "Signs a payload using one or more JWKs and outputs a JWS"
+
+typedef struct {
+    jcmd_opt_io_t io;
+    json_t *keys;
+    json_t *sigs;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jws sig [-i JWS] [-I PAY] [-s SIG] -k JWK [-o JWS] [-O PAY] [-c]"
+"\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_input[] = {
+    { .arg = "JSON", .doc="Parse JWS template from JSON" },
+    { .arg = "FILE", .doc="Read JWS template from FILE" },
+    { .arg = "-",    .doc="Read JWS template from standard input" },
+    {}
+};
+
+static const jcmd_doc_t doc_signature[] = {
+    { .arg = "JSON", .doc="Parse JWS signature template from JSON" },
+    { .arg = "FILE", .doc="Read JWS signature template from FILE" },
+    { .arg = "-",    .doc="Read JWS signature template standard input" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, io),
+        .set = jcmd_opt_io_set_input,
+        .doc = doc_input,
+        .def = "{}",
+    },
+    {
+        .opt = { "detached", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_opt_t, io.detached),
+        .set = jcmd_opt_set_ifile,
+        .doc = jcmd_jws_doc_detached,
+    },
+    {
+        .opt = { "signature", required_argument, .val = 's' },
+        .off = offsetof(jcmd_opt_t, sigs),
+        .set = jcmd_opt_set_jsons,
+        .doc = doc_signature,
+        .def = "{}",
+    },
+    {
+        .opt = { "key", required_argument, .val = 'k' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_doc_key,
+    },
+    {
+        .opt = { "output", required_argument, .val = 'o' },
+        .off = offsetof(jcmd_opt_t, io.output),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jws_doc_output,
+        .def = "-",
+    },
+    {
+        .opt = { "detach", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_opt_t, io.detach),
+        .set = jcmd_opt_set_ofile,
+        .doc = jcmd_jws_doc_detach,
+    },
+    {
+        .opt = { "compact", no_argument, .val = 'c' },
+        .off = offsetof(jcmd_opt_t, io.compact),
+        .set = jcmd_opt_set_flag,
+        .doc = jcmd_jws_doc_compact,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *opt)
+{
+    jcmd_opt_io_cleanup(&opt->io);
+    json_decref(opt->keys);
+    json_decref(opt->sigs);
+}
+
+static bool
+validate_input(jcmd_opt_t *opt)
+{
+    size_t nsigs = 0;
+
+    if (json_array_remove(opt->sigs, 0) < 0)
+        return false;
+
+    if (json_array_size(opt->keys) == 0) {
+        fprintf(stderr, "At least one JWK is required to sign!\n");
+        return false;
+    }
+
+    if (json_array_size(opt->keys) < json_array_size(opt->sigs)) {
+        fprintf(stderr, "Specified more signature templates than JWKs!\n");
+        return false;
+    }
+
+    nsigs += json_array_size(opt->keys);
+
+    if (json_is_array(json_object_get(opt->io.obj, "signatures")))
+        nsigs += json_array_size(json_object_get(opt->io.obj, "signatures"));
+
+    if (json_object_get(opt->io.obj, "protected") ||
+        json_object_get(opt->io.obj, "signature"))
+        nsigs += 1;
+
+    if (opt->io.compact && nsigs > 1) {
+        fprintf(stderr, "Too many signatures for compact serialization!\n");
+        return false;
+    }
+
+    if (json_array_size(opt->keys) < json_array_size(opt->sigs)) {
+        fprintf(stderr, "Specified more signatures than keys!\n");
+        return false;
+    }
+
+    while (json_array_size(opt->sigs) < json_array_size(opt->keys)) {
+        if (json_array_append_new(opt->sigs, json_object()) < 0)
+            return false;
+    }
+
+    return true;
+}
+
+static int
+jcmd_jws_sig(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = { .io.fields = jcmd_jws_fields };
+    jose_io_auto_t *io = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (!validate_input(&opt))
+        return EXIT_FAILURE;
+
+    io = jose_jws_sig_io(NULL, opt.io.obj, opt.sigs, opt.keys);
+    if (!io)
+        return EXIT_FAILURE;
+
+    io = jcmd_jws_prep_io(&opt.io, io);
+    if (!io)
+        return EXIT_FAILURE;
+
+    if (opt.io.compact) {
+        const char *v = NULL;
+        json_t *o = json_array_get(opt.sigs, 0);
+
+        if (json_unpack(o, "{s?s}", "protected", &v) < 0)
+            return EXIT_FAILURE;
+
+        fprintf(opt.io.output, "%s.", v ? v : "");
+    } else {
+        fprintf(opt.io.output, "{");
+        if (!opt.io.detach)
+            fprintf(opt.io.output, "\"payload\":\"");
+    }
+
+    if (opt.io.detached || opt.io.input) {
+        FILE *f = opt.io.detached ? opt.io.detached : opt.io.input;
+
+        for (int c = fgetc(f); c != EOF; c = fgetc(f)) {
+            uint8_t b = c;
+
+            if (!opt.io.detached && b == '.')
+                break;
+
+            if (!io->feed(io, &b, sizeof(b)))
+                return EXIT_FAILURE;
+        }
+
+        for (int c = 0; opt.io.detached && opt.io.input && c != EOF && c != '.'; )
+            c = fgetc(opt.io.input);
+    } else {
+        const char *pay = NULL;
+        size_t payl = 0;
+
+        if (json_unpack(opt.io.obj, "{s?s%}", "payload", &pay, &payl) < 0)
+            return EXIT_FAILURE;
+
+        if (!io->feed(io, pay ? pay : "", payl))
+            return EXIT_FAILURE;
+    }
+
+    if (opt.io.input) {
+        if (json_object_set_new(opt.io.obj, "signature",
+                                jcmd_compact_field(opt.io.input)) < 0) {
+            fprintf(stderr, "Error reading last compact field!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (!io->done(io))
+        return EXIT_FAILURE;
+
+    if (opt.io.compact) {
+        const char *v = NULL;
+
+        if (json_unpack(opt.io.obj, "{s:s}", "signature", &v) < 0) {
+            fprintf(stderr, "Missing signature parameter!\n");
+            return EXIT_FAILURE;
+        }
+
+        fprintf(opt.io.output, ".%s", v);
+    } else {
+        if (!opt.io.detach)
+            fprintf(opt.io.output, "\",");
+        json_dumpf(opt.io.obj, opt.io.output,
+                   JSON_EMBED | JSON_COMPACT | JSON_SORT_KEYS);
+        fprintf(opt.io.output, "}");
+    }
+
+    if (isatty(fileno(opt.io.output)))
+        fprintf(opt.io.output, "\n");
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jws_sig, "jws", "sig")

+ 172 - 0
cmd/jws/ver.c

@@ -0,0 +1,172 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jws.h"
+#include <string.h>
+#include <unistd.h>
+
+#define BLOCKS 1024
+#define SUMMARY "Verifies a JWS using the supplied JWKs and outputs payload"
+
+typedef struct {
+    jcmd_opt_io_t io;
+    json_t *keys;
+    bool all;
+} jcmd_opt_t;
+
+static const char *prefix =
+"jose jws ver -i JWS [-I PAY] -k JWK [-a] [-O PAY]\n\n" SUMMARY;
+
+static const jcmd_doc_t doc_all[] = {
+    { .doc="Ensure the JWS validates with all keys" },
+    {}
+};
+
+static const jcmd_doc_t doc_detach[] = {
+    { .arg = "FILE", .doc="Decode payload to FILE" },
+    { .arg = "-",    .doc="Decode payload to standard output" },
+    {}
+};
+
+static const jcmd_cfg_t cfgs[] = {
+    {
+        .opt = { "input", required_argument, .val = 'i' },
+        .off = offsetof(jcmd_opt_t, io),
+        .set = jcmd_opt_io_set_input,
+        .doc = jcmd_jws_doc_input,
+    },
+    {
+        .opt = { "detached", required_argument, .val = 'I' },
+        .off = offsetof(jcmd_opt_t, io.detached),
+        .set = jcmd_opt_set_ifile,
+        .doc = jcmd_jws_doc_detached,
+    },
+    {
+        .opt = { "key", required_argument, .val = 'k' },
+        .off = offsetof(jcmd_opt_t, keys),
+        .set = jcmd_opt_set_jwks,
+        .doc = jcmd_doc_key,
+    },
+    {
+        .opt = { "detach", required_argument, .val = 'O' },
+        .off = offsetof(jcmd_opt_t, io.detach),
+        .set = jcmd_opt_set_ofile,
+        .doc = doc_detach,
+    },
+    {
+        .opt = { "all", no_argument, .val = 'a' },
+        .off = offsetof(jcmd_opt_t, all),
+        .set = jcmd_opt_set_flag,
+        .doc = doc_all,
+    },
+    {}
+};
+
+static void
+jcmd_opt_cleanup(jcmd_opt_t *sig)
+{
+    jcmd_opt_io_cleanup(&sig->io);
+    json_decref(sig->keys);
+}
+
+static bool
+validate_input(const jcmd_opt_t *opt, json_t **sigs)
+{
+    if (json_array_size(opt->keys) == 0) {
+        fprintf(stderr, "MUST specify a JWK(Set)!\n");
+        return false;
+    }
+
+    if (!opt->io.obj) {
+        fprintf(stderr, "Invalid JWS!\n");
+        return false;
+    }
+
+    *sigs = json_incref(json_object_get(opt->io.obj, "signatures"));
+    if (!*sigs)
+        *sigs = json_pack("[O]", opt->io.obj);
+    if (!json_is_array(*sigs)) {
+        fprintf(stderr, "Signatures value must be an array!\n");
+        return false;
+    }
+
+    return true;
+}
+
+static int
+jcmd_jws_ver(int argc, char *argv[])
+{
+    jcmd_opt_auto_t opt = { .io.fields = jcmd_jws_fields };
+    jose_io_auto_t *io = NULL;
+    json_auto_t *sigs = NULL;
+
+    if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
+        return EXIT_FAILURE;
+
+    if (!validate_input(&opt, &sigs))
+        return EXIT_FAILURE;
+
+    io = jose_jws_ver_io(NULL, opt.io.obj, NULL, opt.keys, opt.all);
+    io = jcmd_jws_prep_io(&opt.io, io);
+    if (!io) {
+        fprintf(stderr, "Error initializing signature context!\n");
+        return EXIT_FAILURE;
+    }
+
+    if (opt.io.detached || opt.io.input) {
+        FILE *f = opt.io.detached ? opt.io.detached : opt.io.input;
+
+        for (int c = fgetc(f); c != EOF; c = fgetc(f)) {
+            uint8_t b = c;
+
+            if (!opt.io.detached && b == '.')
+                break;
+
+            if (!io->feed(io, &b, sizeof(b)))
+                return EXIT_FAILURE;
+        }
+
+        for (int c = 0; opt.io.detached && opt.io.input && c != EOF && c != '.'; )
+            c = fgetc(opt.io.input);
+    } else {
+        const char *pay = NULL;
+        size_t payl = 0;
+
+        if (json_unpack(opt.io.obj, "{s?s%}", "payload", &pay, &payl) < 0)
+            return EXIT_FAILURE;
+
+        if (!io->feed(io, pay ? pay : "", payl))
+            return EXIT_FAILURE;
+    }
+
+    if (opt.io.input) {
+        if (json_object_set_new(opt.io.obj, "signature",
+                                jcmd_compact_field(opt.io.input)) < 0) {
+            fprintf(stderr, "Error reading last compact field!\n");
+            return EXIT_FAILURE;
+        }
+    }
+
+    if (!io->done(io)) {
+        fprintf(stderr, "Signature validation failed!\n");
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
+
+JCMD_REGISTER(SUMMARY, jcmd_jws_ver, "jws", "ver")

+ 348 - 0
compile

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

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


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


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


+ 65 - 0
configure.ac

@@ -0,0 +1,65 @@
+AC_PREREQ(2.62)
+AC_INIT(jose, 10)
+AC_CANONICAL_SYSTEM
+AC_PROG_CC_C99
+
+AM_INIT_AUTOMAKE([subdir-objects foreign no-dist-gzip dist-bzip2 parallel-tests])
+AM_SILENT_RULES([yes])
+AM_PROG_CC_C_O
+
+LT_INIT([disable-static])
+
+PKG_PROG_PKG_CONFIG([0.25])
+
+PKG_CHECK_MODULES([zlib], [zlib])
+PKG_CHECK_MODULES([jansson], [jansson >= 2.10])
+PKG_CHECK_MODULES([libcrypto], [libcrypto >= 1.0.2])
+
+AC_OPENMP
+AC_SUBST([OPENMP_CFLAGS])
+
+JOSE_CFLAGS="\
+-Wall \
+-Wextra \
+-Werror \
+-Wstrict-aliasing \
+-Wchar-subscripts \
+-Wformat-security \
+-Wmissing-declarations \
+-Wmissing-prototypes \
+-Wnested-externs \
+-Wpointer-arith \
+-Wshadow \
+-Wsign-compare \
+-Wstrict-prototypes \
+-Wtype-limits \
+-Wunused-function \
+-Wno-missing-field-initializers \
+-Wno-unused-command-line-argument \
+-Wno-unused-parameter \
+-Wno-unknown-pragmas \
+"
+AC_SUBST([JOSE_CFLAGS])
+
+AC_MSG_CHECKING([for linker script support])
+LDVS=`$LD --help < /dev/null 2>/dev/null | grep version-script`
+if test "$LDVS"; then
+    have_ld_version_script=yes
+    AC_MSG_RESULT(yes)
+else
+    have_ld_version_script=no
+    AC_MSG_RESULT(no)
+fi
+AM_CONDITIONAL(HAVE_LD_VERSION_SCRIPT, test "$have_ld_version_script" = "yes")
+
+AC_CONFIG_FILES([
+    jose/Makefile
+    lib/Makefile
+    cmd/Makefile
+    tests/Makefile
+    doc/Makefile
+    Makefile
+    jose/jose.h
+    jose.pc
+])
+AC_OUTPUT

+ 791 - 0
depcomp

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

+ 28 - 0
doc/Makefile.am

@@ -0,0 +1,28 @@
+man1_MANS = \
+    ronn/jose.1 \
+    ronn/jose-alg.1 \
+    ronn/jose-fmt.1 \
+    ronn/jose-b64-dec.1 \
+    ronn/jose-b64-enc.1 \
+    ronn/jose-jwe-dec.1 \
+    ronn/jose-jwe-enc.1 \
+    ronn/jose-jwe-fmt.1 \
+    ronn/jose-jwk-exc.1 \
+    ronn/jose-jwk-gen.1 \
+    ronn/jose-jwk-pub.1 \
+    ronn/jose-jwk-thp.1 \
+    ronn/jose-jwk-use.1 \
+    ronn/jose-jws-fmt.1 \
+    ronn/jose-jws-sig.1 \
+    ronn/jose-jws-ver.1
+
+man3_MANS = \
+    doxygen/man/man3/jose_b64.3 \
+    doxygen/man/man3/jose_jwk.3 \
+    doxygen/man/man3/jose_jws.3 \
+    doxygen/man/man3/jose_cfg.3 \
+    doxygen/man/man3/jose_jwe.3 \
+    doxygen/man/man3/jose_io_t.3 \
+    doxygen/man/man3/jose_io.3
+
+EXTRA_DIST = $(man1_MANS) $(man3_MANS)

+ 594 - 0
doc/Makefile.in

@@ -0,0 +1,594 @@
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+subdir = doc
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+man1dir = $(mandir)/man1
+am__installdirs = "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man3dir)"
+man3dir = $(mandir)/man3
+NROFF = nroff
+MANS = $(man1_MANS) $(man3_MANS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JOSE_CFLAGS = @JOSE_CFLAGS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENMP_CFLAGS = @OPENMP_CFLAGS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+jansson_CFLAGS = @jansson_CFLAGS@
+jansson_LIBS = @jansson_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zlib_CFLAGS = @zlib_CFLAGS@
+zlib_LIBS = @zlib_LIBS@
+man1_MANS = \
+    ronn/jose.1 \
+    ronn/jose-alg.1 \
+    ronn/jose-fmt.1 \
+    ronn/jose-b64-dec.1 \
+    ronn/jose-b64-enc.1 \
+    ronn/jose-jwe-dec.1 \
+    ronn/jose-jwe-enc.1 \
+    ronn/jose-jwe-fmt.1 \
+    ronn/jose-jwk-exc.1 \
+    ronn/jose-jwk-gen.1 \
+    ronn/jose-jwk-pub.1 \
+    ronn/jose-jwk-thp.1 \
+    ronn/jose-jwk-use.1 \
+    ronn/jose-jws-fmt.1 \
+    ronn/jose-jws-sig.1 \
+    ronn/jose-jws-ver.1
+
+man3_MANS = \
+    doxygen/man/man3/jose_b64.3 \
+    doxygen/man/man3/jose_jwk.3 \
+    doxygen/man/man3/jose_jws.3 \
+    doxygen/man/man3/jose_cfg.3 \
+    doxygen/man/man3/jose_jwe.3 \
+    doxygen/man/man3/jose_io_t.3 \
+    doxygen/man/man3/jose_io.3
+
+EXTRA_DIST = $(man1_MANS) $(man3_MANS)
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign doc/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign doc/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+install-man1: $(man1_MANS)
+	@$(NORMAL_INSTALL)
+	@list1='$(man1_MANS)'; \
+	list2=''; \
+	test -n "$(man1dir)" \
+	  && test -n "`echo $$list1$$list2`" \
+	  || exit 0; \
+	echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \
+	$(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \
+	{ for i in $$list1; do echo "$$i"; done;  \
+	if test -n "$$list2"; then \
+	  for i in $$list2; do echo "$$i"; done \
+	    | sed -n '/\.1[a-z]*$$/p'; \
+	fi; \
+	} | while read p; do \
+	  if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; echo "$$p"; \
+	done | \
+	sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+	      -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+	sed 'N;N;s,\n, ,g' | { \
+	list=; while read file base inst; do \
+	  if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+	    echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \
+	    $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \
+	  fi; \
+	done; \
+	for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+	while read files; do \
+	  test -z "$$files" || { \
+	    echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \
+	    $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \
+	done; }
+
+uninstall-man1:
+	@$(NORMAL_UNINSTALL)
+	@list='$(man1_MANS)'; test -n "$(man1dir)" || exit 0; \
+	files=`{ for i in $$list; do echo "$$i"; done; \
+	} | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+	      -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+	dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir)
+install-man3: $(man3_MANS)
+	@$(NORMAL_INSTALL)
+	@list1='$(man3_MANS)'; \
+	list2=''; \
+	test -n "$(man3dir)" \
+	  && test -n "`echo $$list1$$list2`" \
+	  || exit 0; \
+	echo " $(MKDIR_P) '$(DESTDIR)$(man3dir)'"; \
+	$(MKDIR_P) "$(DESTDIR)$(man3dir)" || exit 1; \
+	{ for i in $$list1; do echo "$$i"; done;  \
+	if test -n "$$list2"; then \
+	  for i in $$list2; do echo "$$i"; done \
+	    | sed -n '/\.3[a-z]*$$/p'; \
+	fi; \
+	} | while read p; do \
+	  if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; echo "$$p"; \
+	done | \
+	sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^3][0-9a-z]*$$,3,;x' \
+	      -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+	sed 'N;N;s,\n, ,g' | { \
+	list=; while read file base inst; do \
+	  if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+	    echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man3dir)/$$inst'"; \
+	    $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man3dir)/$$inst" || exit $$?; \
+	  fi; \
+	done; \
+	for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+	while read files; do \
+	  test -z "$$files" || { \
+	    echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man3dir)'"; \
+	    $(INSTALL_DATA) $$files "$(DESTDIR)$(man3dir)" || exit $$?; }; \
+	done; }
+
+uninstall-man3:
+	@$(NORMAL_UNINSTALL)
+	@list='$(man3_MANS)'; test -n "$(man3dir)" || exit 0; \
+	files=`{ for i in $$list; do echo "$$i"; done; \
+	} | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^3][0-9a-z]*$$,3,;x' \
+	      -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+	dir='$(DESTDIR)$(man3dir)'; $(am__uninstall_files_from_dir)
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(MANS)
+installdirs:
+	for dir in "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man3dir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man1 install-man3
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-man
+
+uninstall-man: uninstall-man1 uninstall-man3
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+	cscopelist-am ctags-am distclean distclean-generic \
+	distclean-libtool distdir dvi dvi-am html html-am info info-am \
+	install install-am install-data install-data-am install-dvi \
+	install-dvi-am install-exec install-exec-am install-html \
+	install-html-am install-info install-info-am install-man \
+	install-man1 install-man3 install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-generic \
+	mostlyclean-libtool pdf pdf-am ps ps-am tags-am uninstall \
+	uninstall-am uninstall-man uninstall-man1 uninstall-man3
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

+ 207 - 0
doc/doxygen/man/man3/jose_b64.3

@@ -0,0 +1,207 @@
+.TH "jose_b64" 3 "Tue May 30 2017" "José" \" -*- nroff -*-
+.ad l
+.nh
+.SH NAME
+jose_b64 \- URL-safe Base64 Encoding & Decoding\&.  
+
+.SH SYNOPSIS
+.br
+.PP
+.SS "Functions"
+
+.in +1c
+.ti -1c
+.RI "size_t \fBjose_b64_dec\fP (const json_t *i, void *o, size_t ol)"
+.br
+.RI "Decodes a URL-safe Base64 JSON string to a buffer\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_b64_dec_io\fP (\fBjose_io_t\fP *next)"
+.br
+.RI "Creates a new IO object which performs URL-safe Base64 decoding\&. "
+.ti -1c
+.RI "size_t \fBjose_b64_dec_buf\fP (const void *i, size_t il, void *o, size_t ol)"
+.br
+.RI "Decodes a URL-safe Base64 buffer to an output buffer\&. "
+.ti -1c
+.RI "json_t * \fBjose_b64_dec_load\fP (const json_t *i)"
+.br
+.RI "Decodes a JSON string from a URL-safe Base64 JSON string\&. "
+.ti -1c
+.RI "json_t * \fBjose_b64_enc\fP (const void *i, size_t il)"
+.br
+.RI "Encodes data to a URL-safe Base64 JSON string\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_b64_enc_io\fP (\fBjose_io_t\fP *next)"
+.br
+.RI "Creates a new IO object which performs URL-safe Base64 encoding\&. "
+.ti -1c
+.RI "size_t \fBjose_b64_enc_buf\fP (const void *i, size_t il, void *o, size_t ol)"
+.br
+.RI "Encodes data to a URL-safe Base64 buffer\&. "
+.ti -1c
+.RI "json_t * \fBjose_b64_enc_dump\fP (const json_t *i)"
+.br
+.RI "Encodes the input JSON as a URL-safe Base64 JSON string\&. "
+.in -1c
+.SH "Detailed Description"
+.PP 
+URL-safe Base64 Encoding & Decoding\&. 
+
+
+.SH "Function Documentation"
+.PP 
+.SS "size_t jose_b64_dec (const json_t * i, void * o, size_t ol)"
+
+.PP
+Decodes a URL-safe Base64 JSON string to a buffer\&. If \fCo\fP is NULL, the number of output bytes necessary is returned\&.
+.PP
+This function will never write more than \fCol\fP bytes\&. If the output buffer is too small, an error will occur\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIi\fP The input URL-safe Base64 JSON string\&. 
+.br
+\fIo\fP The output buffer (may be NULL)\&. 
+.br
+\fIol\fP The size of the output buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The number of bytes that were (or would be) written\&. If an error occurs, SIZE_MAX is returned\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_b64_dec_io (\fBjose_io_t\fP * next)"
+
+.PP
+Creates a new IO object which performs URL-safe Base64 decoding\&. All data written to the returned IO object will be decoded before passing it on to the next IO object in the chain\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fInext\fP The next IO object in the chain\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SS "size_t jose_b64_dec_buf (const void * i, size_t il, void * o, size_t ol)"
+
+.PP
+Decodes a URL-safe Base64 buffer to an output buffer\&. If \fCo\fP is NULL, the number of output bytes necessary is returned\&.
+.PP
+This function will never write more than \fCol\fP bytes\&. If the output buffer is too small, an error will occur\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIi\fP The input URL-safe Base64 buffer\&. 
+.br
+\fIil\fP The size of the data in the input buffer\&. 
+.br
+\fIo\fP The output buffer\&. 
+.br
+\fIol\fP The size of the output buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The number of bytes that were (or would be) written\&. If an error occurs, SIZE_MAX is returned\&. 
+.RE
+.PP
+
+.SS "json_t* jose_b64_dec_load (const json_t * i)"
+
+.PP
+Decodes a JSON string from a URL-safe Base64 JSON string\&. 
+.PP
+\fBParameters:\fP
+.RS 4
+\fIi\fP The input URL-safe Base64 JSON string containing JSON data\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The output JSON data\&. 
+.RE
+.PP
+
+.SS "json_t* jose_b64_enc (const void * i, size_t il)"
+
+.PP
+Encodes data to a URL-safe Base64 JSON string\&. 
+.PP
+\fBParameters:\fP
+.RS 4
+\fIi\fP The input buffer\&. 
+.br
+\fIil\fP The size of the data in the input buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The decoded JSON data\&. If an error occurs, NULL is returned\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_b64_enc_io (\fBjose_io_t\fP * next)"
+
+.PP
+Creates a new IO object which performs URL-safe Base64 encoding\&. All data written to the returned IO object will be encoded before passing it on to the next IO object in the chain\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fInext\fP The next IO object in the chain\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SS "size_t jose_b64_enc_buf (const void * i, size_t il, void * o, size_t ol)"
+
+.PP
+Encodes data to a URL-safe Base64 buffer\&. If \fCo\fP is NULL, the number of output bytes necessary is returned\&.
+.PP
+This function will never write more than \fCol\fP bytes\&. If the output buffer is too small, an error will occur\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIi\fP The input buffer\&. 
+.br
+\fIil\fP The size of the data in the input buffer\&. 
+.br
+\fIo\fP The output URL-safe Base64 buffer\&. 
+.br
+\fIol\fP The size of the output buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The number of bytes that were (or would be) written\&. If an error occurs, SIZE_MAX is returned\&. 
+.RE
+.PP
+
+.SS "json_t* jose_b64_enc_dump (const json_t * i)"
+
+.PP
+Encodes the input JSON as a URL-safe Base64 JSON string\&. 
+.PP
+\fBParameters:\fP
+.RS 4
+\fIi\fP The input JSON data\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The output URL-safe Base64 JSON string\&. 
+.RE
+.PP
+
+.SH "Author"
+.PP 
+Generated automatically by Doxygen for José from the source code\&.

+ 158 - 0
doc/doxygen/man/man3/jose_cfg.3

@@ -0,0 +1,158 @@
+.TH "jose_cfg" 3 "Tue May 30 2017" "José" \" -*- nroff -*-
+.ad l
+.nh
+.SH NAME
+jose_cfg \- José Configuration\&.  
+
+.SH SYNOPSIS
+.br
+.PP
+.SS "Typedefs"
+
+.in +1c
+.ti -1c
+.RI "typedef jose_cfg_t \fBjose_cfg_auto_t\fP"
+.br
+.RI "Defines a jose_cfg_t which calls \fBjose_cfg_decref()\fP at end of scope\&. "
+.in -1c
+.SS "Functions"
+
+.in +1c
+.ti -1c
+.RI "jose_cfg_t * \fBjose_cfg\fP (void)"
+.br
+.RI "Creates a new configuration instance\&. "
+.ti -1c
+.RI "jose_cfg_t * \fBjose_cfg_incref\fP (jose_cfg_t *cfg)"
+.br
+.RI "Increases the reference count of a configuration instance\&. "
+.ti -1c
+.RI "void \fBjose_cfg_decref\fP (jose_cfg_t *cfg)"
+.br
+.RI "Decreases the reference count of a configuration instance\&. "
+.ti -1c
+.RI "void \fBjose_cfg_set_err_func\fP (jose_cfg_t *cfg, jose_cfg_err_t *err, void *misc)"
+.br
+.RI "Sets the error handler function for this configuration instance\&. "
+.ti -1c
+.RI "void * \fBjose_cfg_get_err_misc\fP (jose_cfg_t *cfg)"
+.br
+.RI "Gets the miscelaneous data associated with the current error handler\&. "
+.ti -1c
+.RI "void \fBjose_cfg_err\fP (jose_cfg_t *cfg, uint64_t err, const char *fmt,\&.\&.\&.)"
+.br
+.RI "Submit an error\&. "
+.in -1c
+.SH "Detailed Description"
+.PP 
+José Configuration\&. 
+
+
+.SH "Typedef Documentation"
+.PP 
+.SS "typedef jose_cfg_t \fBjose_cfg_auto_t\fP"
+
+.PP
+Defines a jose_cfg_t which calls \fBjose_cfg_decref()\fP at end of scope\&. For example: 
+.PP
+.nf
+void foo() {
+    jose_cfg_auto_t *cfg = jose_cfg();
+    // jose_cfg_decref() implicitly called
+}
+.fi
+.PP
+ 
+.SH "Function Documentation"
+.PP 
+.SS "jose_cfg_t* jose_cfg (void)"
+
+.PP
+Creates a new configuration instance\&. 
+.PP
+\fBReturns:\fP
+.RS 4
+A newly-allocated configuration instance\&. 
+.RE
+.PP
+
+.SS "jose_cfg_t* jose_cfg_incref (jose_cfg_t * cfg)"
+
+.PP
+Increases the reference count of a configuration instance\&. This function always succeeds\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The value of \fCcfg\fP (for convenience)\&. 
+.RE
+.PP
+
+.SS "void jose_cfg_decref (jose_cfg_t * cfg)"
+
+.PP
+Decreases the reference count of a configuration instance\&. When the reference count reaches zero, the configuration instance is freed\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context\&. 
+.RE
+.PP
+
+.SS "void jose_cfg_set_err_func (jose_cfg_t * cfg, jose_cfg_err_t * err, void * misc)"
+
+.PP
+Sets the error handler function for this configuration instance\&. The value of \fCmisc\fP will be passed to the error handler function\&.
+.PP
+You may pass NULL to \fCerr\fP to return to the default error handler\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context\&. 
+.br
+\fIerr\fP The error handler function you wish to enable\&. 
+.br
+\fImisc\fP The miscelaneous data you wish to pass to the error handler\&. 
+.RE
+.PP
+
+.SS "void* jose_cfg_get_err_misc (jose_cfg_t * cfg)"
+
+.PP
+Gets the miscelaneous data associated with the current error handler\&. 
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The miscelaneous data associated with the error handler\&. 
+.RE
+.PP
+
+.SS "void jose_cfg_err (jose_cfg_t * cfg, uint64_t err, const char * fmt,  \&.\&.\&.)"
+
+.PP
+Submit an error\&. The error handler will be called with the error provided\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIerr\fP The number corresponding to this error type\&. 
+.br
+\fIfmt\fP A printf()-style format string\&. 
+.br
+\fI\&.\&.\&.\fP The printf()-style arguments\&. 
+.RE
+.PP
+
+.SH "Author"
+.PP 
+Generated automatically by Doxygen for José from the source code\&.

+ 219 - 0
doc/doxygen/man/man3/jose_io.3

@@ -0,0 +1,219 @@
+.TH "jose_io" 3 "Tue May 30 2017" "José" \" -*- nroff -*-
+.ad l
+.nh
+.SH NAME
+jose_io \- IO Chaining\&.  
+
+.SH SYNOPSIS
+.br
+.PP
+.SS "Data Structures"
+
+.in +1c
+.ti -1c
+.RI "struct \fBjose_io_t\fP"
+.br
+.RI "The interface for chained IO\&. "
+.in -1c
+.SS "Typedefs"
+
+.in +1c
+.ti -1c
+.RI "typedef \fBjose_io_t\fP \fBjose_io_auto_t\fP"
+.br
+.RI "Defines a \fBjose_io_t\fP which calls \fBjose_io_decref()\fP at end of scope\&. "
+.in -1c
+.SS "Functions"
+
+.in +1c
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_io_incref\fP (\fBjose_io_t\fP *io)"
+.br
+.RI "Increases the reference count of an IO object\&. "
+.ti -1c
+.RI "void \fBjose_io_decref\fP (\fBjose_io_t\fP *io)"
+.br
+.RI "Decreases the reference count of an IO object\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_io_malloc\fP (jose_cfg_t *cfg, void **buf, size_t *len)"
+.br
+.RI "Creates a new IO object which collects data into a dynamic buffer\&. "
+.ti -1c
+.RI "void * \fBjose_io_malloc_steal\fP (void **buf)"
+.br
+.RI "Steals the buffer created by the \fBjose_io_malloc()\fP IO object\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_io_buffer\fP (jose_cfg_t *cfg, void *buf, size_t *len)"
+.br
+.RI "Creates a new IO object which collects data into a static buffer\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_io_file\fP (jose_cfg_t *cfg, FILE *file)"
+.br
+.RI "Creates a new IO object which writes data into a FILE\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_io_multiplex\fP (jose_cfg_t *cfg, \fBjose_io_t\fP **nexts, bool all)"
+.br
+.RI "Creates a new IO object which multiplexes data into multiple IO objects\&. "
+.in -1c
+.SH "Detailed Description"
+.PP 
+IO Chaining\&. 
+
+
+.SH "Typedef Documentation"
+.PP 
+.SS "typedef \fBjose_io_t\fP \fBjose_io_auto_t\fP"
+
+.PP
+Defines a \fBjose_io_t\fP which calls \fBjose_io_decref()\fP at end of scope\&. For example: 
+.PP
+.nf
+void foo() {
+    uint8_t *buf = NULL;
+    size_t len = 0;
+    jose_io_auto_t *io = jose_io_malloc(NULL, &buf, &len);
+    // jose_io_decref() implicitly called
+}
+.fi
+.PP
+ 
+.SH "Function Documentation"
+.PP 
+.SS "\fBjose_io_t\fP* jose_io_incref (\fBjose_io_t\fP * io)"
+
+.PP
+Increases the reference count of an IO object\&. This function always succeeds\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIio\fP The \fBjose_io_t\fP entity you are using\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The value of \fCio\fP (for convenience)\&. 
+.RE
+.PP
+
+.SS "void jose_io_decref (\fBjose_io_t\fP * io)"
+
+.PP
+Decreases the reference count of an IO object\&. When the reference count reaches zero, io->free() is called\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIio\fP The \fBjose_io_t\fP entity you are using\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_io_malloc (jose_cfg_t * cfg, void ** buf, size_t * len)"
+
+.PP
+Creates a new IO object which collects data into a dynamic buffer\&. The dynamic buffer is allocated into the \fCbuf\fP pointer you provided and the length of the buffer is stored in \fClen\fP\&. The pointer referenced by \fCbuf\fP must remain valid for the entire duration of the returned IO object\&.
+.PP
+The default behavior is for the IO object to zero and free the buffer when it is freed\&. This means that, by default, you own the buffer pointer but the buffer itself is owned by the IO object\&. You can, however, steal the buffer by setting the buffer pointer to NULL\&.
+.PP
+\fBSee also:\fP
+.RS 4
+\fBjose_io_malloc_steal()\fP 
+.RE
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIbuf\fP A buffer pointer pointer\&. 
+.br
+\fIlen\fP A pointer to the length of the buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SS "void* jose_io_malloc_steal (void ** buf)"
+
+.PP
+Steals the buffer created by the \fBjose_io_malloc()\fP IO object\&. This convenience function simply returns the value of \fC*buf\fP and then sets \fC*buf\fP to NULL\&.
+.PP
+\fBSee also:\fP
+.RS 4
+\fBjose_io_malloc()\fP 
+.RE
+.PP
+\fBParameters:\fP
+.RS 4
+\fIbuf\fP A pointer to the buffer pointer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The value of \fC*buf\fP before it is set to NULL\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_io_buffer (jose_cfg_t * cfg, void * buf, size_t * len)"
+
+.PP
+Creates a new IO object which collects data into a static buffer\&. The size of \fCbuf\fP MUST be specified in the variable pointed to by \fClen\fP\&. This will be the maximum data written\&. However, after the function returns, the variable pointed to by \fClen\fP will contain the current length of data in the buffer\&.
+.PP
+Unlike \fBjose_io_malloc()\fP, you own the buffer and it is not zeroed or freed when the IO object is freed\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIbuf\fP A buffer pointer\&. 
+.br
+\fIlen\fP A pointer to the length of the buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_io_file (jose_cfg_t * cfg, FILE * file)"
+
+.PP
+Creates a new IO object which writes data into a FILE\&. This function DOES NOT take ownership of the FILE\&. You are still responsible for calling fclose() at the appropriate time\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIfile\fP The output file which MUST be opened for writing or appending\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_io_multiplex (jose_cfg_t * cfg, \fBjose_io_t\fP ** nexts, bool all)"
+
+.PP
+Creates a new IO object which multiplexes data into multiple IO objects\&. If \fCall\fP is true, the success of all \fCnexts\fP is required\&. Otherwise, all but one of the \fCnexts\fP can fail before the error is propagated upward\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fInexts\fP A NULL-terminated array of IO object pointers\&. 
+.br
+\fIall\fP Whether or not the success of all \fCnexts\fP is required\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SH "Author"
+.PP 
+Generated automatically by Doxygen for José from the source code\&.

+ 97 - 0
doc/doxygen/man/man3/jose_io_t.3

@@ -0,0 +1,97 @@
+.TH "jose_io_t" 3 "Tue May 30 2017" "José" \" -*- nroff -*-
+.ad l
+.nh
+.SH NAME
+jose_io_t \- The interface for chained IO\&.  
+
+.SH SYNOPSIS
+.br
+.PP
+.SS "Data Fields"
+
+.in +1c
+.ti -1c
+.RI "bool(* \fBfeed\fP )(\fBjose_io_t\fP *io, const void *in, size_t len)"
+.br
+.RI "Pushes data into the IO chain\&. "
+.ti -1c
+.RI "bool(* \fBdone\fP )(\fBjose_io_t\fP *io)"
+.br
+.RI "Completes the IO chain\&. "
+.in -1c
+.SH "Detailed Description"
+.PP 
+The interface for chained IO\&. 
+
+
+.PP
+\fBSee also:\fP
+.RS 4
+\fBjose_io_malloc()\fP 
+.PP
+\fBjose_io_buffer()\fP 
+.PP
+\fBjose_io_file()\fP 
+.PP
+\fBjose_io_multiplex()\fP 
+.PP
+\fBjose_b64_enc_io()\fP 
+.PP
+\fBjose_b64_dec_io()\fP 
+.PP
+\fBjose_jws_sig_io()\fP 
+.PP
+\fBjose_jws_ver_io()\fP 
+.PP
+\fBjose_jwe_dec_io()\fP 
+.PP
+\fBjose_jwe_dec_cek_io()\fP 
+.PP
+\fBjose_jwe_enc_io()\fP 
+.PP
+\fBjose_jwe_enc_cek_io()\fP 
+.RE
+.PP
+
+.SH "Field Documentation"
+.PP 
+.SS "bool(* jose_io_t::feed) (\fBjose_io_t\fP *io, const void *in, size_t len)"
+
+.PP
+Pushes data into the IO chain\&. 
+.PP
+\fBParameters:\fP
+.RS 4
+\fIio\fP The \fBjose_io_t\fP entity you are using\&. 
+.br
+\fIin\fP The input buffer\&. 
+.br
+\fIlen\fP The length of the data in the input buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+Returns true if all data was consumed, otherwise false\&. 
+.RE
+.PP
+
+.SS "bool(* jose_io_t::done) (\fBjose_io_t\fP *io)"
+
+.PP
+Completes the IO chain\&. Any data stored in internal buffers will be flushed\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIio\fP The \fBjose_io_t\fP entity you are using\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+Returns true if flushing was successful, otherwise false\&. 
+.RE
+.PP
+
+
+.SH "Author"
+.PP 
+Generated automatically by Doxygen for José from the source code\&.

File diff suppressed because it is too large
+ 514 - 0
doc/doxygen/man/man3/jose_jwe.3


+ 237 - 0
doc/doxygen/man/man3/jose_jwk.3

@@ -0,0 +1,237 @@
+.TH "jose_jwk" 3 "Tue May 30 2017" "José" \" -*- nroff -*-
+.ad l
+.nh
+.SH NAME
+jose_jwk \- JSON Web Keys (RFC 7517)  
+
+.SH SYNOPSIS
+.br
+.PP
+.SS "Functions"
+
+.in +1c
+.ti -1c
+.RI "bool \fBjose_jwk_gen\fP (jose_cfg_t *cfg, json_t *jwk)"
+.br
+.RI "Generates a new JWK\&. "
+.ti -1c
+.RI "bool \fBjose_jwk_pub\fP (jose_cfg_t *cfg, json_t *jwk)"
+.br
+.RI "Removes all private key material from a JWK\&. "
+.ti -1c
+.RI "bool \fBjose_jwk_prm\fP (jose_cfg_t *cfg, const json_t *jwk, bool req, const char *op)"
+.br
+.RI "Determines if an operation is permitted for a JWK\&. "
+.ti -1c
+.RI "json_t * \fBjose_jwk_thp\fP (jose_cfg_t *cfg, const json_t *jwk, const char *alg)"
+.br
+.RI "Calculates the thumbprint of a JWK as a URL-safe Base64 encoded JSON string\&. "
+.ti -1c
+.RI "size_t \fBjose_jwk_thp_buf\fP (jose_cfg_t *cfg, const json_t *jwk, const char *alg, uint8_t *thp, size_t len)"
+.br
+.RI "Calculates the thumbprint of a JWK\&. "
+.ti -1c
+.RI "json_t * \fBjose_jwk_exc\fP (jose_cfg_t *cfg, const json_t *prv, const json_t *pub)"
+.br
+.RI "Perform a key exchange\&. "
+.in -1c
+.SH "Detailed Description"
+.PP 
+JSON Web Keys (RFC 7517) 
+
+A JSON Web Key (JWS) is a standard data format for expresing cryptographic keys in JSON\&.
+.PP
+\fBSee also:\fP
+.RS 4
+https://tools.ietf.org/html/rfc7517 
+.PP
+https://tools.ietf.org/html/rfc7638 
+.RE
+.PP
+
+.SH "Function Documentation"
+.PP 
+.SS "bool jose_jwk_gen (jose_cfg_t * cfg, json_t * jwk)"
+
+.PP
+Generates a new JWK\&. The JWK is generated using hints from the input in exactly the same format as you would find in the output\&. For example, the most common way to generate a key is to specify the algorithm you'd like to use the key with\&. For example (error handling omitted): 
+.PP
+.nf
+json_t *gen(void) {
+    json_auto_t *jwk = json_pack("{s:s}", "alg", "ES256");
+    jose_jwk_gen(NULL, jwk);
+    return json_incref(jwk);
+}
+
+.fi
+.PP
+.PP
+This method is preferred because other metadata can be inferred from the algorithm name, such as the key usage\&. Additionally, the algorithm metadata can be used to automatically generate correct headers when creating signatures (JWS) or encryptions (JWE)\&. Thus, you should always default to creating keys by their algorithm usage\&.
+.PP
+However, should your requirements differ, you can also generate a key using raw parameters (again, error handling omitted): 
+.PP
+.nf
+json_t *gen(void) {
+    json_auto_t *jwk = json_pack("{s:s,s:s}", "kty", "EC", "crv", "P-256");
+    jose_jwk_gen(NULL, jwk);
+    return json_incref(jwk);
+}
+
+json_t *gen(void) {
+    json_auto_t *jwk = json_pack("{s:s,s:i}", "kty", "RSA", "bits", 2048);
+    jose_jwk_gen(NULL, jwk);
+    return json_incref(jwk);
+}
+
+json_t *gen(void) {
+    json_auto_t *jwk = json_pack("{s:s,s:i}", "kty", "oct", "bytes", 32);
+    jose_jwk_gen(NULL, jwk);
+    return json_incref(jwk);
+}
+
+.fi
+.PP
+.PP
+In this case, 'bits' and 'bytes' will be removed from the final output\&.
+.PP
+\fBSee also:\fP
+.RS 4
+https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms 
+.RE
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjwk\fP The JWK to generate\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, true\&. Otherwise, false\&. 
+.RE
+.PP
+
+.SS "bool jose_jwk_pub (jose_cfg_t * cfg, json_t * jwk)"
+
+.PP
+Removes all private key material from a JWK\&. In addition, this function will remove any key operations from the \fCkey_ops\fP JWK property (if present) that apply only to the private key\&.
+.PP
+This function should be used before exporting keys to third parties\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjwk\fP The JWK to remove private keys from\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, true\&. Otherwise, false\&. 
+.RE
+.PP
+
+.SS "bool jose_jwk_prm (jose_cfg_t * cfg, const json_t * jwk, bool req, const char * op)"
+
+.PP
+Determines if an operation is permitted for a JWK\&. The operation to be confirmed (\fCop\fP) is always specified according to the syntax of the 'key_ops' JWK property, even when the 'use' property is defined on the JWK\&.
+.PP
+This function has two modes of operation\&. If \fCreq\fP is false, then JWKs which do not have any key use metadata will be approved for this operation\&. However, if \fCreq\fP is true then this metadata will be required for approval\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjwk\fP The JWK from which to remove private keys\&. 
+.br
+\fIreq\fP Whether JWK key use metadata is required or not\&. 
+.br
+\fIop\fP The opperation to seek approval for\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+When the JWK is approved, true\&. Otherwise, false\&. 
+.RE
+.PP
+
+.SS "json_t* jose_jwk_thp (jose_cfg_t * cfg, const json_t * jwk, const char * alg)"
+
+.PP
+Calculates the thumbprint of a JWK as a URL-safe Base64 encoded JSON string\&. This function is a thin wrapper around \fBjose_jwk_thp_buf()\fP\&.
+.PP
+\fBSee also:\fP
+.RS 4
+\fBjose_jwk_thp_buf()\fP 
+.RE
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjwk\fP The JWK to calculate the thumbprint for\&. 
+.br
+\fIalg\fP The hash algorithm to use\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, a newly-allocated JSON string\&. Otherwise, NULL\&. 
+.RE
+.PP
+
+.SS "size_t jose_jwk_thp_buf (jose_cfg_t * cfg, const json_t * jwk, const char * alg, uint8_t * thp, size_t len)"
+
+.PP
+Calculates the thumbprint of a JWK\&. This function calculates the thumbprint of a JWK according to the method defined by RFC 7638\&.
+.PP
+If \fCthp\fP is NULL, this function returns the size of the buffer required for the thumbprint output\&.
+.PP
+\fBSee also:\fP
+.RS 4
+https://tools.ietf.org/html/rfc7638 
+.RE
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjwk\fP The JWK to calculate the thumbprint for\&. 
+.br
+\fIalg\fP The hash algorithm to use\&. 
+.br
+\fIthp\fP The output hash buffer\&. 
+.br
+\fIlen\fP The size of the output hash buffer\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, the number of bytes written\&. Otherwise, SIZE_MAX\&. 
+.RE
+.PP
+
+.SS "json_t* jose_jwk_exc (jose_cfg_t * cfg, const json_t * prv, const json_t * pub)"
+
+.PP
+Perform a key exchange\&. The only currently implemented algorithm is ECDH\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIprv\fP The private JWK\&. 
+.br
+\fIpub\fP The public JWK\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, the JWK result of the key exchange\&. Otherwise, NULL\&. 
+.RE
+.PP
+
+.SH "Author"
+.PP 
+Generated automatically by Doxygen for José from the source code\&.

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

@@ -0,0 +1,233 @@
+.TH "jose_jws" 3 "Tue May 30 2017" "José" \" -*- nroff -*-
+.ad l
+.nh
+.SH NAME
+jose_jws \- JSON Web Signature (RFC 7515)  
+
+.SH SYNOPSIS
+.br
+.PP
+.SS "Functions"
+
+.in +1c
+.ti -1c
+.RI "json_t * \fBjose_jws_hdr\fP (const json_t *sig)"
+.br
+.RI "Merges the JOSE headers of a JWS signature object\&. "
+.ti -1c
+.RI "bool \fBjose_jws_sig\fP (jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk)"
+.br
+.RI "Creates one or more signatures in a JWS object\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_jws_sig_io\fP (jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk)"
+.br
+.RI "Creates one or more signatures in a JWS object using streaming\&. "
+.ti -1c
+.RI "bool \fBjose_jws_ver\fP (jose_cfg_t *cfg, const json_t *jws, const json_t *sig, const json_t *jwk, bool all)"
+.br
+.RI "Verifies signatures of one or more JWKs in a JWS object\&. "
+.ti -1c
+.RI "\fBjose_io_t\fP * \fBjose_jws_ver_io\fP (jose_cfg_t *cfg, const json_t *jws, const json_t *sig, const json_t *jwk, bool all)"
+.br
+.RI "Verifies signatures of one or more JWKs in a JWS object using streaming\&. "
+.in -1c
+.SH "Detailed Description"
+.PP 
+JSON Web Signature (RFC 7515) 
+
+JSON Web Token (RFC 7519)
+.PP
+A JSON Web Signature (JWS) is a standard data format for expresing cryptographic signatures in JSON\&. The signatures are produced using a JSON Web Key (JWK)\&.
+.PP
+For example, to create a simple signature of a string using a JWK (error handling omitted): 
+.PP
+.nf
+json_t *sig(const char *str, const json_t *jwk) {
+    json_auto_t *jws = json_pack("{s:o}", "payload",
+                                 jose_b64_enc(str, strlen(str)));
+    jose_jws_sig(NULL, jws, NULL, jwk);
+    return json_incref(jws);
+}
+
+.fi
+.PP
+.PP
+Likewise, to verify this signature (again, error handling omitted): 
+.PP
+.nf
+char *ver(const json_t *jwe, 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);
+    str = calloc(1, len + 1);
+    jose_b64_dec(json_object_get(jwe, "payload"), str, len);
+    return str;
+}
+
+.fi
+.PP
+.PP
+\fBSee also:\fP
+.RS 4
+https://tools.ietf.org/html/rfc7515
+.RE
+.PP
+A JSON Web Token (JWT) is a standard data format for expresing claims transferred between to parties in JSON\&. The JWT is wrapped in any number of Signatures (JWS) or Encryptions (JWE)\&.
+.PP
+\fBSee also:\fP
+.RS 4
+https://tools.ietf.org/html/rfc7515 
+.RE
+.PP
+
+.SH "Function Documentation"
+.PP 
+.SS "json_t* jose_jws_hdr (const json_t * sig)"
+
+.PP
+Merges the JOSE headers of a JWS signature object\&. 
+.PP
+\fBParameters:\fP
+.RS 4
+\fIsig\fP A JWS signature object\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The newly allocated JOSE header\&. 
+.RE
+.PP
+
+.SS "bool jose_jws_sig (jose_cfg_t * cfg, json_t * jws, json_t * sig, const json_t * jwk)"
+
+.PP
+Creates one or more signatures in a JWS object\&. The JWS object (\fCjws\fP) must contain the 'payload' property\&.
+.PP
+All signatures created will be appended to the JWS specified by \fCjws\fP\&. If the resulting JWS (\fCjws\fP) would contain only a single signature, the JWS will be represented in Flattened JWS JSON Serialization Syntax\&. Otherwise, it will be represented in General JWS JSON Serialization Syntax\&.
+.PP
+If \fCjwk\fP contains a JWK, a single signature is created\&. In this case, \fCjws\fP must contain either a JWS signature object template or NULL\&. You may specify algorithms or other signature behaviors simply by specifying them in the JOSE headers of the JWS signature object template as defined by RFC 7515\&. If a required property is missing, sensible defaults will be used and inserted into the JOSE headers; inferring them from the JWK (\fCjwk\fP) where possible\&.
+.PP
+If \fCjwk\fP contains an array of JWKs or a JWKSet, multiple signatures are created\&. In this case, the \fCsig\fP parameter must contain one of the following values:
+.PP
+.IP "1." 4
+A JWS signature object template that will be used for all signatures\&. In this case, a copy will be made for each signature and \fCsig\fP will not be modified in any way\&.
+.IP "2." 4
+An array of JWS signature object templates\&. Each template will be used with its corresponding JWK from \fCjwk\fP\&. If the arrays in \fCsig\fP and \fCjwk\fP are a different size, an error will occur\&.
+.IP "3." 4
+NULL\&. This has the same effect as passing NULL for each separate key\&.
+.PP
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjws\fP The JWS object\&. 
+.br
+\fIsig\fP The JWS signature object template(s) or NULL\&. 
+.br
+\fIjwk\fP The JWK(s) or JWKSet used for creating signatures\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, true\&. Otherwise, false\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_jws_sig_io (jose_cfg_t * cfg, json_t * jws, json_t * sig, const json_t * jwk)"
+
+.PP
+Creates one or more signatures in a JWS object using streaming\&. This function behaves substantially like \fBjose_jws_sig()\fP except:
+.PP
+The payload is not specified in the JWS (\fCjws\fP)\&. Rather, the payload is provided using the returned IO object\&. The input to the returned IO object will not be internally Base64 encoded\&. So you may need to prepend the IO chain with the result of \fBjose_b64_enc_io()\fP (depending on your situation)\&.
+.PP
+Likewise, the payload is not stored in the JWS object (\fCjws\fP)\&. This allows for detached payloads and decreases memory use for signatures over large payloads\&. If you would like to attach the payload, it is your responsibility to do so manually\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjws\fP The JWS object\&. 
+.br
+\fIsig\fP The JWS signature object template(s) or NULL\&. 
+.br
+\fIjwk\fP The JWK(s) or JWKSet used for creating signatures\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SS "bool jose_jws_ver (jose_cfg_t * cfg, const json_t * jws, const json_t * sig, const json_t * jwk, bool all)"
+
+.PP
+Verifies signatures of one or more JWKs in a JWS object\&. The JWS object (\fCjws\fP) must contain the 'payload' property\&.
+.PP
+If a single JWK (\fCjwk\fP) is specified, the \fCall\fP parameter is ignored\&. In this case, if you would like to verify a particular JWS signature object, you may specify it using the \fCsig\fP parameter\&. Otherwise, you may simply pass NULL to verify any of the JWS signature objects in the JWS object\&.
+.PP
+If \fCjwk\fP contains an array of JWKs or a JWKSet, the \fCall\fP parameter determines whether a valid signature is required for every JWK in order to successfully validate the JWS\&. For example, if you set \fCall\fP to false this function will succeed if a valid signature is found for any of the provided JWKs\&. When using this multiple JWK signature mode, the \fCsig\fP parameter must contain one of the following values:
+.PP
+.IP "1." 4
+A single JWS signature object to validate against all/any of the provided JWKs\&.
+.IP "2." 4
+An array of JWS signature objects\&. In this case, each JWS signature object will be mapped to its corresponding JWK from \fCjwk\fP\&. If the arrays in \fCsig\fP and \fCjwk\fP are a different size, an error will occur\&.
+.IP "3." 4
+NULL\&. This has the same effect as passing NULL for each separate key\&.
+.PP
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjws\fP The JWS object\&. 
+.br
+\fIsig\fP The JWS signature object(s) to verify or NULL\&. 
+.br
+\fIjwk\fP The JWK(s) or JWKSet used for verifying signatures\&. 
+.br
+\fIall\fP Whether or not to require validation of all JWKs\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+On success, true\&. Otherwise, false\&. 
+.RE
+.PP
+
+.SS "\fBjose_io_t\fP* jose_jws_ver_io (jose_cfg_t * cfg, const json_t * jws, const json_t * sig, const json_t * jwk, bool all)"
+
+.PP
+Verifies signatures of one or more JWKs in a JWS object using streaming\&. This function behaves substantially like \fBjose_jws_ver()\fP except:
+.PP
+The payload is not specified in the JWS (\fCjws\fP)\&. Rather, the payload is provided using the returned IO object\&. The input to the returned IO object will not be internally Base64 encoded\&. So you may need to prepend the IO chain with the result of \fBjose_b64_enc_io()\fP (depending on your situation)\&.
+.PP
+Final signature verification is delayed until \fBjose_io_t::done()\fP returns\&.
+.PP
+\fBParameters:\fP
+.RS 4
+\fIcfg\fP The configuration context (optional)\&. 
+.br
+\fIjws\fP The JWS object\&. 
+.br
+\fIsig\fP The JWS signature object(s) to verify or NULL\&. 
+.br
+\fIjwk\fP The JWK(s) or JWKSet used for verifying signatures\&. 
+.br
+\fIall\fP Whether or not to require validation of all JWKs\&. 
+.RE
+.PP
+\fBReturns:\fP
+.RS 4
+The new IO object or NULL on error\&. 
+.RE
+.PP
+
+.SH "Author"
+.PP 
+Generated automatically by Doxygen for José from the source code\&.

+ 48 - 0
doc/ronn/jose-alg.1

@@ -0,0 +1,48 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-ALG" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-alg\fR \- Lists all supported algorithms
+.
+.SH "SYNOPSIS"
+\fBjose alg\fR [\-k KIND]
+.
+.SH "OVERVIEW"
+The \fBjose alg\fR command lists the algorithms supported by all \fBjose\fR commands\.
+.
+.P
+Since \fBjose\fR supports different kinds of algorithms (encryption, signing, hashing, etc\.), you can limit the kinds of algorithms you would like to see using the \fB\-k\fR option (which can be specified multiple times)\. For a list of the different kinds of algorithms, use the \fB\-k ?\fR option\. If the \fB\-k\fR option is not used, all algorithms, regardless of their kind, will be listed\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-k\fR \fIKIND\fR, \fB\-\-kind\fR=\fIKIND\fR
+Restrict algorithm list to a certain kind
+.
+.TP
+\fB\-k\fR ?, \fB\-\-kind\fR=?
+List valid algorithm kinds
+.
+.SH "EXAMPLES"
+List all encryption algorithms:
+.
+.IP "" 4
+.
+.nf
+
+$ jose alg \-k encr
+A128CBC\-HS256
+A128GCM
+A192CBC\-HS384
+A192GCM
+A256CBC\-HS512
+A256GCM
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>

+ 51 - 0
doc/ronn/jose-b64-dec.1

@@ -0,0 +1,51 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-B64\-DEC" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-b64\-dec\fR \- Decodes URL\-safe Base64 data to binary
+.
+.SH "SYNOPSIS"
+\fBjose b64 dec\fR \-i B64 [\-O BIN]
+.
+.SH "OVERVIEW"
+The \fBjose b64 dec\fR command decodes URL\-safe Base64 data to binary format\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-base64\fR=\fIFILE\fR
+Read Base64 (URL\-safe) data from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-base64\fR=\-
+Read Base64 (URL\-safe) data from standard input
+.
+.TP
+\fB\-O\fR \fIFILE\fR, \fB\-\-binary\fR=\fIFILE\fR
+Write binary data to FILE
+.
+.TP
+\fB\-O\fR \-, \fB\-\-binary\fR=\-
+Write binary data to standard output
+.
+.SH "EXAMPLES"
+Decode a simple message:
+.
+.IP "" 4
+.
+.nf
+
+$ echo SGVsbG8sIFdvcmxkCg | jose b64 dec \-i\-
+Hello, World
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-b64\-enc\fR(1)

+ 51 - 0
doc/ronn/jose-b64-enc.1

@@ -0,0 +1,51 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-B64\-ENC" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-b64\-enc\fR \- Encodes binary data to URL\-safe Base64
+.
+.SH "SYNOPSIS"
+\fBjose b64 enc\fR \-I BIN [\-o B64]
+.
+.SH "OVERVIEW"
+The \fBjose b64 enc\fR command encodes binary data to URL\-safe Base64 format\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-I\fR \fIFILE\fR, \fB\-\-binary\fR=\fIFILE\fR
+Read binary data from FILE
+.
+.TP
+\fB\-I\fR \-, \fB\-\-binary\fR=\-
+Read binary data from standard input
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-base64\fR=\fIFILE\fR
+Write Base64 (URL\-safe) to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-base64\fR=\-
+Write Base64 (URL\-safe) to standard output
+.
+.SH "EXAMPLES"
+Encode a simple message:
+.
+.IP "" 4
+.
+.nf
+
+$ echo "Hello, World" | jose b64 enc \-I\-
+SGVsbG8sIFdvcmxkCg
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-b64\-dec\fR(1)

+ 262 - 0
doc/ronn/jose-fmt.1

@@ -0,0 +1,262 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-FMT" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-fmt\fR \- Converts JSON between serialization formats
+.
+.SH "SYNOPSIS"
+\fBjose fmt\fR \fIOPTIONS\fR
+.
+.SH "OVERVIEW"
+This \fBjose fmt\fR command provides a mechanism for building and parsing JSON objects from the command line\. It operates as a simple stack machine\. All commands operate on the TOP item of the stack and, occasionally, the PREV item of the stack\. Commands that require a specific type of value will indicate it in parentheses\. For example: "TOP (arr\.)"\.
+.
+.P
+This program returns 0 on success or the index of the option which failed\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-X\fR, \fB\-\-not\fR
+Invert the following assertion
+.
+.TP
+\fB\-O\fR, \fB\-\-object\fR
+Assert TOP to be an object
+.
+.TP
+\fB\-A\fR, \fB\-\-array\fR
+Assert TOP to be an array
+.
+.TP
+\fB\-S\fR, \fB\-\-string\fR
+Assert TOP to be a string
+.
+.TP
+\fB\-I\fR, \fB\-\-integer\fR
+Assert TOP to be an integer
+.
+.TP
+\fB\-R\fR, \fB\-\-real\fR
+Assert TOP to be a real
+.
+.TP
+\fB\-N\fR, \fB\-\-number\fR
+Assert TOP to be a number
+.
+.TP
+\fB\-T\fR, \fB\-\-true\fR
+Assert TOP to be true
+.
+.TP
+\fB\-F\fR, \fB\-\-false\fR
+Assert TOP to be false
+.
+.TP
+\fB\-B\fR, \fB\-\-boolean\fR
+Assert TOP to be a boolean
+.
+.TP
+\fB\-0\fR, \fB\-\-null\fR
+Assert TOP to be null
+.
+.TP
+\fB\-E\fR, \fB\-\-equal\fR
+Assert TOP to be equal to PREV
+.
+.TP
+\fB\-Q\fR, \fB\-\-query\fR
+Query the stack by deep copying and pushing onto TOP
+.
+.TP
+\fB\-M\fR #, \fB\-\-move\fR=#
+Move TOP back # places on the stack
+.
+.TP
+\fB\-U\fR, \fB\-\-unwind\fR
+Discard TOP from the stack
+.
+.TP
+\fB\-j\fR \fIJSON\fR, \fB\-\-json\fR=\fIJSON\fR
+Parse JSON constant, push onto TOP
+.
+.TP
+\fB\-j\fR \fIFILE\fR, \fB\-\-json\fR=\fIFILE\fR
+Read from FILE, push onto TOP
+.
+.TP
+\fB\-j\fR \-, \fB\-\-json\fR=\-
+Read from STDIN, push onto TOP
+.
+.TP
+\fB\-c\fR, \fB\-\-copy\fR
+Deep copy TOP, push onto TOP
+.
+.TP
+\fB\-q\fR \fISTR\fR, \fB\-\-quote\fR=\fISTR\fR
+Convert STR to a string, push onto TOP
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write TOP to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write TOP to STDOUT
+.
+.TP
+\fB\-f\fR \fIFILE\fR, \fB\-\-foreach\fR=\fIFILE\fR
+Write TOP (obj\./arr\.) to FILE, one line/item
+.
+.TP
+\fB\-f\fR \-, \fB\-\-foreach\fR=\-
+Write TOP (obj\./arr\.) to STDOUT, one line/item
+.
+.TP
+\fB\-u\fR \fIFILE\fR, \fB\-\-unquote\fR=\fIFILE\fR
+Write TOP (str\.) to FILE without quotes
+.
+.TP
+\fB\-u\fR \-, \fB\-\-unquote\fR=\-
+Write TOP (str\.) to STDOUT without quotes
+.
+.TP
+\fB\-t\fR #, \fB\-\-truncate\fR=#
+Shrink TOP (arr\.) to length #
+.
+.TP
+\fB\-t\fR \-#, \fB\-\-truncate\fR=\-#
+Discard last # items from TOP (arr\.)
+.
+.TP
+\fB\-i\fR #, \fB\-\-insert\fR=#
+Insert TOP into PREV (arr\.) at #
+.
+.TP
+\fB\-a\fR, \fB\-\-append\fR
+Append TOP to the end of PREV (arr\.)
+.
+.TP
+\fB\-a\fR, \fB\-\-append\fR
+Set missing values from TOP (obj\.) into PREV (obj\.)
+.
+.TP
+\fB\-x\fR, \fB\-\-extend\fR
+Append items from TOP to the end of PREV (arr\.)
+.
+.TP
+\fB\-x\fR, \fB\-\-extend\fR
+Set all values from TOP (obj\.) into PREV (obj\.)
+.
+.TP
+\fB\-d\fR \fINAME\fR, \fB\-\-delete\fR=\fINAME\fR
+Delete NAME from TOP (obj\.)
+.
+.TP
+\fB\-d\fR #, \fB\-\-delete\fR=#
+Delete # from TOP (arr\.)
+.
+.TP
+\fB\-d\fR \-#, \fB\-\-delete\fR=\-#
+Delete # from the end of TOP (arr\.)
+.
+.TP
+\fB\-l\fR, \fB\-\-length\fR
+Push length of TOP (arr\./str\./obj\.) to TOP
+.
+.TP
+\fB\-e\fR, \fB\-\-empty\fR
+Erase all items from TOP (arr\./obj\.)
+.
+.TP
+\fB\-g\fR \fINAME\fR, \fB\-\-get\fR=\fINAME\fR
+Get item with NAME from TOP (obj\.), push to TOP
+.
+.TP
+\fB\-g\fR #, \fB\-\-get\fR=#
+Get # item from TOP (arr\.), push to TOP
+.
+.TP
+\fB\-g\fR \-#, \fB\-\-get\fR=\-#
+Get # item from the end of TOP (arr\.), push to TOP
+.
+.TP
+\fB\-s\fR \fINAME\fR, \fB\-\-set\fR=\fINAME\fR
+Sets TOP into PREV (obj\.) with NAME
+.
+.TP
+\fB\-s\fR #, \fB\-\-set\fR=#
+Sets TOP into PREV (obj\.) at #
+.
+.TP
+\fB\-s\fR \-#, \fB\-\-set\fR=\-#
+Sets TOP into PREV (obj\.) at # from the end
+.
+.TP
+\fB\-y\fR, \fB\-\-b64load\fR
+URL\-safe Base64 decode TOP (str\.), push onto TOP
+.
+.TP
+\fB\-Y\fR, \fB\-\-b64dump\fR
+URL\-safe Base64 encode TOP, push onto TOP
+.
+.SH "EXAMPLES"
+Extract the \fBalg\fR parameter from a JWE Protected Header:
+.
+.IP "" 4
+.
+.nf
+
+$ jose fmt \-j "$jwe" \-Og protected \-yOg alg \-Su\-
+A128KW
+.
+.fi
+.
+.IP "" 0
+.
+.P
+List all JWKs in a JWKSet (one per line):
+.
+.IP "" 4
+.
+.nf
+
+$ echo "$jwkset" | jose fmt \-j\- \-Og keys \-Af\-
+{"kty":"oct",\.\.\.}
+{"kty":"EC",\.\.\.}
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Change the algorithm in a JWK:
+.
+.IP "" 4
+.
+.nf
+
+$ echo "$jwk" | jose fmt \-j\- \-j \'"A128GCM"\' \-s alg \-Uo\-
+{"kty":"oct","alg":"A128GCM",\.\.\.}
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Build a JWE template:
+.
+.IP "" 4
+.
+.nf
+
+$ jose fmt \-j \'{}\' \-cs unprotected \-q A128KW \-s alg \-UUo\-
+{"unprotected":{"alg":"A128KW"}}
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>

+ 111 - 0
doc/ronn/jose-jwe-dec.1

@@ -0,0 +1,111 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWE\-DEC" "1" "May 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jwe\-dec\fR \- Decrypts a JWE using the supplied JWKs
+.
+.SH "SYNOPSIS"
+\fBjose jwe dec\fR \-i JWE [\-I CT] \-k JWK [\-p] [\-O PT]
+.
+.SH "OVERVIEW"
+The \fBjose jwe dec\fR command decrypts a JWE using one or more JWK (\fB\-k\fR) or password (\fB\-p\fR)\. Decryption succeeds if any key is able to perform decryption\.
+.
+.P
+If the JWE is a detached JWE, meaning that the ciphertext is stored in binary form external to the JWE itself, the ciphertext can be loaded using the \fB\-I\fR parameter\.
+.
+.P
+Please note that, when specifying the \fB\-O\fR option to output the plaintext, plaintext output begins before ciphertext validation\. Therefore, you must check the return value of the command before using the data\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWE from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWE from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWE from standard input
+.
+.TP
+\fB\-I\fR \fIFILE\fR, \fB\-\-detached\fR=\fIFILE\fR
+Read decoded ciphertext from FILE
+.
+.TP
+\fB\-I\fR \-, \fB\-\-detached\fR=\-
+Read decoded ciphertext from standard input
+.
+.TP
+\fB\-p\fR, \fB\-\-password\fR
+Prompt for a decryption password, if necessary
+.
+.TP
+\fB\-k\fR \fIFILE\fR, \fB\-\-key\fR=\fIFILE\fR
+Read JWK(Set) from FILE
+.
+.TP
+\fB\-k\fR \-, \fB\-\-key=\-\fR
+Read JWK(Set) from standard input
+.
+.TP
+\fB\-O\fR \fIJSON\fR, \fB\-\-detach\fR=\fIJSON\fR
+Parse JWE from JSON
+.
+.TP
+\fB\-O\fR \fIFILE\fR, \fB\-\-detach\fR=\fIFILE\fR
+Read JWE from FILE
+.
+.TP
+\fB\-O\fR \-, \fB\-\-detach\fR=\-
+Read JWE from standard input
+.
+.SH "EXAMPLES"
+Decrypt a JWE with a JWK:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwe dec \-i msg\.jwe \-k rsa\.key \-O msg\.txt
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Decrypt a JWE with a password:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwe dec \-i msg\.jwe \-p \-O msg\.txt
+Please enter decryption password:
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Decrypt a JWE with either of two JWKs:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwe dec \-i msg\.jwe \-k ec\.jwk \-k rsa\.jwk \-O msg\.txt
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-jwe\-enc\fR(1), \fBjose\-jwe\-fmt\fR(1)

File diff suppressed because it is too large
+ 141 - 0
doc/ronn/jose-jwe-enc.1


+ 101 - 0
doc/ronn/jose-jwe-fmt.1

@@ -0,0 +1,101 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWE\-FMT" "1" "May 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jwe\-fmt\fR \- Converts a JWE between serialization formats
+.
+.SH "SYNOPSIS"
+\fBjose jwe fmt\fR \-i JWE [\-I CT] [\-o JWE] [\-O CT] [\-c]
+.
+.SH "OVERVIEW"
+The \fBjose jwe fmt\fR command converts a JWE into alternative serialization formats\. For example, it can:
+.
+.IP "1." 4
+Attach ciphertext to a detached JWE\.
+.
+.IP "2." 4
+Detach ciphertext from a JWE\.
+.
+.IP "3." 4
+Convert JWE Compact Serialization to JWE JSON Serialization\.
+.
+.IP "4." 4
+Convert JWE JSON Serialization to JWE Compact Serialization\.
+.
+.IP "" 0
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWE from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWE from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWE from standard input
+.
+.TP
+\fB\-I\fR \fIFILE\fR, \fB\-\-detached\fR=\fIFILE\fR
+Read decoded ciphertext from FILE
+.
+.TP
+\fB\-I\fR \-, \fB\-\-detached\fR=\-
+Read decoded ciphertext from standard input
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write JWE to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write JWE to stdout (default)
+.
+.TP
+\fB\-O\fR \fIFILE\fR, \fB\-\-detach\fR=\fIFILE\fR
+Detach ciphertext and decode to FILE
+.
+.TP
+\fB\-O\fR \-, \fB\-\-detach\fR=\-
+Detach ciphertext and decode to standard output
+.
+.TP
+\fB\-c\fR, \fB\-\-compact\fR
+Output JWE using compact serialization
+.
+.SH "EXAMPLES"
+Attach ciphertext to a detached JWE and emit JWE Compact Serialization:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwe fmt \-i msg\.jwe \-I msg\.ct \-o compact\.jwe \-c
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Detach ciphertext from a JWE:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwe fmt \-i msg\.jwe \-o detached\.jwe \-O detached\.ct
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-jwe\-dec\fR(1), \fBjose\-jwe\-enc\fR(1)

File diff suppressed because it is too large
+ 92 - 0
doc/ronn/jose-jwk-exc.1


+ 112 - 0
doc/ronn/jose-jwk-gen.1

@@ -0,0 +1,112 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWK\-GEN" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jwk\-gen\fR \- Creates a random JWK for each input JWK template
+.
+.SH "SYNOPSIS"
+\fBjose jwk gen\fR \-i JWK [\-o JWK]
+.
+.SH "OVERVIEW"
+The \fBjose jwk gen\fR command generates a key from one or more JWK(Set) templates\. If a single template is given as input, a single JWK will be output\. However, if multiple templates are given as input, a single JWKSet will be output containing all the keys\.
+.
+.P
+The best way to generate a key is to specify the algorithm it will be used with in the "alg" property of the JWK template\. This method should be preferred since, when generating from an algorithm, an appropriate "key_ops" parameter will be emitted automatically\. Further, having a JWK with the algorithm already specified will assist algorithm inference when encrypting or signing\.
+.
+.P
+Alternatively, you can generate a key by specifying its key type ("kty") JWK property, along with the required type\-specific generation parameter\. See the examples below for how to do this for each key type\. If the type\-specific generation parameter is non\-standard (for example: "bytes" and "bits"), it will be removed excluded from the output\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWK(Set) template from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWK(Set) template from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWK(Set) template from standard input
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write JWK(Set) to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write JWK(Set) to standard input
+.
+.TP
+\fB\-s\fR, \fB\-\-set\fR
+Always output a JWKSet
+.
+.SH "EXAMPLES"
+Generate three keys, each targeting a different algorithm:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"alg":"HS256"}\' \-o oct\.jwk
+$ jose jwk gen \-i \'{"alg":"RS256"}\' \-o rsa\.jwk
+$ jose jwk gen \-i \'{"alg":"ES256"}\' \-o ec\.jwk
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Generate three keys using key parameters rather than algorithms:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"kty":"oct","bytes":32}\' \-o oct\.jwk
+$ jose jwk gen \-i \'{"kty":"RSA","bits":4096}\' \-o rsa\.jwk
+$ jose jwk gen \-i \'{"kty":"EC","crv":"P\-256"}\' \-o ec\.jwk
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Create multiple keys at once using a JWKSet template:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \e
+  \-i \'{"keys":[{"alg":"HS256"},{"alg":"ES256"}]}\' \e
+  \-o keys\.jwkset
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Create multiple keys at once using multiple JWK templates:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \e
+  \-i \'{"alg":"HS256"}\' \e
+  \-i \'{"alg":"ES256"}\' \e
+  \-o keys\.jwkset
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-alg\fR(1), \fBjose\-jwe\-dec\fR(1), \fBjose\-jwe\-enc\fR(1), \fBjose\-jwk\-exc\fR(1), \fBjose\-jwk\-pub\fR(1), \fBjose\-jwk\-thp\fR(1), \fBjose\-jwk\-use\fR(1), \fBjose\-jws\-sig\fR(1), \fBjose\-jws\-ver\fR(1),

+ 66 - 0
doc/ronn/jose-jwk-pub.1

@@ -0,0 +1,66 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWK\-PUB" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jwk\-pub\fR \- Cleans private keys from a JWK
+.
+.SH "SYNOPSIS"
+\fBjose jwk pub\fR \-i JWK [\-o JWK]
+.
+.SH "OVERVIEW"
+The \fBjose jwk pub\fR command removes all private key material from one or more JWK(Set) inputs\. The output will contain only public key material\.
+.
+.P
+If the JWK contains the "key_ops" property, it will be automatically adjusted to include only operations relevant to public keys\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWK(Set) from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWK(Set) from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWK(Set) from standard input
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write JWK(Set) to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write JWK(Set) to standard input
+.
+.TP
+\fB\-s\fR, \fB\-\-set\fR
+Always output a JWKSet
+.
+.SH "EXAMPLES"
+Clean private key material from a JWK:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"alg":"ES256"}\' \-o prv\.jwk
+$ cat prv\.jwk
+{"alg":"ES256","crv":"P\-256","key_ops":["sign","verify"],"kty":"EC", \.\.\.}
+$ jose jwk pub \-i prv\.jwk \-o pub\.jwk
+$ cat pub\.jwk
+{"alg":"ES256","crv":"P\-256","key_ops":["verify"],"kty":"EC", \.\.\.}
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-alg\fR(1), \fBjose\-jwe\-enc\fR(1), \fBjose\-jwk\-exc\fR(1), \fBjose\-jwk\-gen\fR(1), \fBjose\-jwk\-thp\fR(1), \fBjose\-jwk\-use\fR(1), \fBjose\-jws\-ver\fR(1)

+ 95 - 0
doc/ronn/jose-jwk-thp.1

@@ -0,0 +1,95 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWK\-THP" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jwk\-thp\fR \- Calculates the JWK thumbprint
+.
+.SH "SYNOPSIS"
+\fBjose jwk thp\fR \-i JWK [\-H ALG] [\-o THP]
+.
+.SH "OVERVIEW"
+The \fBjose jwk thp\fR command calculates the thumbprint of one or more JWKs\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWK(Set) from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWK(Set) from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWK(Set) standard input
+.
+.TP
+\fB\-a\fR \fIALG\fR, \fB\-\-algorithm\fR=\fIALG\fR
+Use the specified hash algorithm (case sensitive)
+.
+.TP
+\fB\-a\fR ?, \fB\-\-algorithm\fR=?
+List available hash algorithms
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write thumbprint(s) to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write thumbprint(s) to standard input
+.
+.TP
+\fB\-f\fR \fITHP\fR, \fB\-\-find\fR=\fITHP\fR
+Search input keys for JWK with the given thumbprint
+.
+.SH "EXAMPLES"
+Calculate the S1 thumbprint of a newly generated key:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"alg":"ES256"}\' \-a S1 | jose jwk thp \-i\-
+BzmSH6W8a8LlbQ1mD0iBJdYj4x4
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Calculate the S256 thumbprints of a JWKSet containing two keys:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk thp \-i keys\.jwkset \-a S256
+6HJwXEuRh8gAkTz4BodEvcEj_KXkgjc\-7Qez3d4VNMs
+jo_j_O5gqYpKcZKHPp3miTszAeV60MXHvdb_kkjjTWE
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Find the input key with the given thumbprint:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk thp \-i keys\.jwkset \-f HYRNOxxOOHap0amTONoy1bHnS5M \-o key\.jwk
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-alg\fR(1), \fBjose\-jwk\-gen\fR(1),

+ 111 - 0
doc/ronn/jose-jwk-use.1

@@ -0,0 +1,111 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWK\-USE" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jwk\-use\fR \- Validates a key for the specified use(s)
+.
+.SH "SYNOPSIS"
+\fBjose jwk use\fR \-i JWK [\-a] [\-r] \-u OP
+.
+.SH "OVERVIEW"
+The \fBjose jwk use\fR command validates one or more JWK(Set) inputs for a given set of usages\. This will be validated against the "use" and "key_ops" properties of each JWK\.
+.
+.P
+By default, if a JWK has no restrictions an operation will be allowed\. However, by specifying the \fB\-r\fR option you can ensure that a JWK will not be allowed unless it explicitly permits the option\.
+.
+.P
+In normal operation, \fBjose jwk use\fR will fail if any of the JWKs do not validate\. However, if the \fB\-o\fR option is used \fBjose jwk use\fR will instead write a JWK(Set) containing all of the input keys that validate\. If no JWKs validate, the command will fail\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWK(Set) from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWK(Set) from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWK(Set) standard input
+.
+.TP
+\fB\-u\fR sign, \fB\-\-use\fR=sign
+Validate the key for signing
+.
+.TP
+\fB\-u\fR verify, \fB\-\-use\fR=verify
+Validate the key for verifying
+.
+.TP
+\fB\-u\fR encrypt, \fB\-\-use\fR=encrypt
+Validate the key for encrypting
+.
+.TP
+\fB\-u\fR decrypt, \fB\-\-use\fR=decrypt
+Validate the key for decrypting
+.
+.TP
+\fB\-u\fR wrapKey, \fB\-\-use\fR=wrapKey
+Validate the key for wrapping
+.
+.TP
+\fB\-u\fR unwrapKey, \fB\-\-use\fR=unwrapKey
+Validate the key for unwrapping
+.
+.TP
+\fB\-u\fR deriveKey, \fB\-\-use\fR=deriveKey
+Validate the key for deriving keys
+.
+.TP
+\fB\-u\fR deriveBits, \fB\-\-use\fR=deriveBits
+Validate the key for deriving bits
+.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Succeeds only if all operations are allowed
+.
+.TP
+\fB\-r\fR, \fB\-\-required\fR
+Operations must be explicitly allowed
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Filter keys to FILE as JWK(Set)
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Filter keys to standard output as JWK(Set)
+.
+.TP
+\fB\-s\fR, \fB\-\-set\fR
+Always output a JWKSet
+.
+.SH "EXAMPLES"
+Examples of both success and failure from a private and public key:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"alg":"ES256"}\' \-o prv\.jwk
+$ jose jwk pub \-i prv\.jwk \-o pub\.jwk
+$ jose jwk use \-i prv\.jwk \-u sign
+$ echo $?
+0
+$ jose jwk use \-i pub\.jwk \-u sign
+$ echo $?
+1
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-jwk\-gen\fR(1)

+ 101 - 0
doc/ronn/jose-jws-fmt.1

@@ -0,0 +1,101 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWS\-FMT" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jws\-fmt\fR \- Converts a JWS between serialization formats
+.
+.SH "SYNOPSIS"
+\fBjose jws fmt\fR \-i JWS [\-I PAY] [\-o JWS] [\-O PAY] [\-c]
+.
+.SH "OVERVIEW"
+The \fBjose jws fmt\fR command converts a JWS into alternative serialization formats\. For example, it can:
+.
+.IP "1." 4
+Attach payload to a detached JWS\.
+.
+.IP "2." 4
+Detach payload from a JWS\.
+.
+.IP "3." 4
+Convert JWS Compact Serialization to JWS JSON Serialization\.
+.
+.IP "4." 4
+Convert JWS JSON Serialization to JWS Compact Serialization\.
+.
+.IP "" 0
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWS from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWS from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWS from standard input
+.
+.TP
+\fB\-I\fR \fIFILE\fR, \fB\-\-detached\fR=\fIFILE\fR
+Read decoded payload from FILE
+.
+.TP
+\fB\-I\fR \-, \fB\-\-detached\fR=\-
+Read decoded payload from standard input
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write JWS to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write JWS to stdout (default)
+.
+.TP
+\fB\-O\fR \fIFILE\fR, \fB\-\-detach\fR=\fIFILE\fR
+Detach payload and decode to FILE
+.
+.TP
+\fB\-O\fR \-, \fB\-\-detach\fR=\-
+Detach payload and decode to standard output
+.
+.TP
+\fB\-c\fR, \fB\-\-compact\fR
+Output JWS using compact serialization
+.
+.SH "EXAMPLES"
+Attach payload to a detached JWS and emit JWS Compact Serialization:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jws fmt \-i msg\.jws \-I msg\.txt \-o compact\.jws \-c
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Detach payload from a JWS:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jws fmt \-i msg\.jws \-o detached\.jws \-O msg\.txt
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-jws\-sig\fR(1), \fBjose\-jws\-ver\fR(1)

+ 141 - 0
doc/ronn/jose-jws-sig.1

@@ -0,0 +1,141 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWS\-SIG" "1" "June 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jws\-sig\fR \- Signs a payload using one or more JWKs
+.
+.SH "SYNOPSIS"
+\fBjose jws sig\fR [\-i JWS] [\-I PAY] [\-s SIG] \-k JWK [\-o JWS] [\-O PAY] [\-c]
+.
+.SH "OVERVIEW"
+The \fBjose jws sig\fR command signs a payload using one or more JWKs\. The payload can be provided either in its decoded form (\fB\-I\fR) or embedded in an existing JWS (\fB\-i\fR)\.
+.
+.P
+A detached JWS can be created by specifying the \fB\-O\fR option\. In this case, the decoded payload will be written to the output specified and will not be included in the JWS\.
+.
+.P
+If only one key is used (\fB\-k\fR), the resulting JWS may be output in JWS Compact Serialization by using the \fB\-c\fR option\.
+.
+.P
+This command uses a template based approach for constructing a JWS\. You can specify templates of the JWS itself (\fB\-i\fR) or for the JWS Signature Object (\fB\-r\fR)\. Attributes specified in either of these templates will appear unmodified in the output\. One exception to this rule is that the JWS Protected Header should be specified in its decoded form in the JWS Signature Object template\. This command will automatically encode it as part of the encryption process\.
+.
+.P
+If you specify a JOSE Header Parameter (via either the \fB\-i\fR or \fB\-r\fR options) that affects the construction of the JWE, this command will attempt to behave according to this parameter as if it were configuration\. Currently, \fBjose\fR will modify its behavior for the "alg" JOSE Header Parameter (see RFC 7515 Section 4\.1\.1)\.
+.
+.P
+However, it is not necessary to provide any templates: \fBjose jwe enc\fR will automatically fill in the "alg" parameter by inferring the correct algorithm from the provided input JWKs\. Therefore, the \fB\-i\fR and \fB\-r\fR options should generally be used for providing extended JWE metadata\.
+.
+.P
+It is possible to specify an existing JWS as the JWS template input (\fB\-i\fR)\. This allows the addition of new signatures to an existing JWS\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWS template from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWS template from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWS template from standard input
+.
+.TP
+\fB\-I\fR \fIFILE\fR, \fB\-\-detached\fR=\fIFILE\fR
+Read decoded payload from FILE
+.
+.TP
+\fB\-I\fR \-, \fB\-\-detached\fR=\-
+Read decoded payload from standard input
+.
+.TP
+\fB\-s\fR \fIJSON\fR, \fB\-\-signature\fR=\fIJSON\fR
+Parse JWS signature template from JSON
+.
+.TP
+\fB\-s\fR \fIFILE\fR, \fB\-\-signature\fR=\fIFILE\fR
+Read JWS signature template from FILE
+.
+.TP
+\fB\-s\fR \-, \fB\-\-signature\fR=\-
+Read JWS signature template standard input
+.
+.TP
+\fB\-k\fR \fIFILE\fR, \fB\-\-key\fR=\fIFILE\fR
+Read JWK(Set) from FILE
+.
+.TP
+\fB\-k\fR \-, \fB\-\-key\fR=\-
+Read JWK(Set) from standard input
+.
+.TP
+\fB\-o\fR \fIFILE\fR, \fB\-\-output\fR=\fIFILE\fR
+Write JWS to FILE
+.
+.TP
+\fB\-o\fR \-, \fB\-\-output\fR=\-
+Write JWS to stdout (default)
+.
+.TP
+\fB\-O\fR \fIFILE\fR, \fB\-\-detach\fR=\fIFILE\fR
+Detach payload and decode to FILE
+.
+.TP
+\fB\-O\fR \-, \fB\-\-detach\fR=\-
+Detach payload and decode to standard output
+.
+.TP
+\fB\-c\fR, \fB\-\-compact\fR
+Output JWS using compact serialization
+.
+.SH "EXAMPLES"
+Sign data with a symmetric key using JWE JSON Serialization:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"alg":"HS256"}\' \-o key\.jwk
+$ jose jws sig \-I msg\.txt \-k key\.jwk \-o msg\.jws
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Sign data using detached JWE Compact Serialization:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jws sig \-I msg\.txt \-k key\.jwk \-O /dev/null \-c \-o msg\.jws
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Sign with two keys:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jwk gen \-i \'{"alg":"ES256"}\' \-o ec\.jwk
+$ jose jwk gen \-i \'{"alg":"RS256"}\' \-o rsa\.jwk
+$ jose jws sig \-I msg\.txt \-k ec\.jwk \-k rsa\.jwk \-o msg\.jws
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-jws\-sig\fR(1), \fBjose\-jws\-ver\fR(1)

+ 106 - 0
doc/ronn/jose-jws-ver.1

@@ -0,0 +1,106 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE\-JWS\-VER" "1" "May 2017" "" ""
+.
+.SH "NAME"
+\fBjose\-jws\-ver\fR \- Verifies a JWS using the supplied JWKs
+.
+.SH "SYNOPSIS"
+\fBjose jws ver\fR \-i JWS [\-I PAY] \-k JWK [\-a] [\-O PAY]
+.
+.SH "OVERVIEW"
+The \fBjose jws ver\fR command verifies a signature over a payload using one or more JWKs\. When specifying more than one JWK (\fB\-k\fR), the program will succeed when any of the provided JWKs successfully verify a signature\. Alternatively, if the \fB\-a\fR option is given, the program will succeed only when all JWKs successfully verify a signature\.
+.
+.P
+If the JWS is a detached JWS, meaning that the payload is stored in binary form external to the JWS itself, the payload can be loaded using the \fB\-I\fR parameter\.
+.
+.P
+Please note that, when specifying the \fB\-O\fR option to output the payload, the payload is output whether or not the signature validates\. Therefore, you must check the return value of the command before trusting the data\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-i\fR \fIJSON\fR, \fB\-\-input\fR=\fIJSON\fR
+Parse JWS from JSON
+.
+.TP
+\fB\-i\fR \fIFILE\fR, \fB\-\-input\fR=\fIFILE\fR
+Read JWS from FILE
+.
+.TP
+\fB\-i\fR \-, \fB\-\-input\fR=\-
+Read JWS from standard input
+.
+.TP
+\fB\-I\fR \fIFILE\fR, \fB\-\-detached\fR=\fIFILE\fR
+Read decoded payload from FILE
+.
+.TP
+\fB\-I\fR \-, \fB\-\-detached\fR=\-
+Read decoded payload from standard input
+.
+.TP
+\fB\-k\fR \fIFILE\fR, \fB\-\-key\fR=\fIFILE\fR
+Read JWK(Set) from FILE
+.
+.TP
+\fB\-k\fR \-, \fB\-\-key\fR=\-
+Read JWK(Set) from standard input
+.
+.TP
+\fB\-O\fR \fIFILE\fR, \fB\-\-detach\fR=\fIFILE\fR
+Decode payload to FILE
+.
+.TP
+\fB\-O\fR \-, \fB\-\-detach\fR=\-
+Decode payload to standard output
+.
+.TP
+\fB\-a\fR, \fB\-\-all\fR
+Ensure the JWS validates with all keys
+.
+.SH "EXAMPLES"
+Verify a regular JWS and output the payload:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jws ver \-i msg\.jws \-k key\.jwk \-O msg\.txt
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Verify a detached JWS without outputting the payload:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jws ver \-i msg\.jws \-I msg\.txt \-k key\.jwk
+.
+.fi
+.
+.IP "" 0
+.
+.P
+Ensure that a JWS is signed with all specified keys:
+.
+.IP "" 4
+.
+.nf
+
+$ jose jws ver \-i msg\.jws \-k ec\.jwk \-k rsa\.jwk \-a
+.
+.fi
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-jws\-fmt\fR(1), \fBjose\-jws\-sig\fR(1)

+ 98 - 0
doc/ronn/jose.1

@@ -0,0 +1,98 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "JOSE" "1" "May 2017" "" ""
+.
+.SH "NAME"
+\fBjose\fR \- Toolkit for performing JSON Object Signing and Encryption
+.
+.SH "SYNOPSIS"
+\fBjose alg\fR [\-k KIND]
+.
+.P
+\fBjose b64 dec\fR \-i B64 [\-O BIN]
+.
+.P
+\fBjose b64 enc\fR \-I BIN [\-o B64]
+.
+.P
+\fBjose jwe dec\fR \-i JWE [\-I CT] \-k JWK [\-p] [\-O PT]
+.
+.P
+\fBjose jwe enc\fR [\-i JWE] \-I PT \-k JWK [\-p] [\-r RCP] [\-o JWE] [\-O CT] [\-c]
+.
+.P
+\fBjose jwe fmt\fR \-i JWE [\-I CT] [\-o JWE] [\-O CT] [\-c]
+.
+.P
+\fBjose jwk exc\fR [\-i JWK] \-l JWK \-r JWK [\-o JWK]
+.
+.P
+\fBjose jwk gen\fR \-i JWK [\-o JWK]
+.
+.P
+\fBjose jwk pub\fR \-i JWK [\-o JWK]
+.
+.P
+\fBjose jwk thp\fR \-i JWK [\-a ALG] [\-o THP]
+.
+.P
+\fBjose jwk use\fR \-i JWK [\-a] [\-r] \-u OP
+.
+.P
+\fBjose jws fmt\fR \-i JWS [\-I PAY] [\-o JWS] [\-O PAY] [\-c]
+.
+.P
+\fBjose jws sig\fR [\-i JWS] [\-I PAY] [\-s SIG] \-k JWK [\-o JWS] [\-O PAY] [\-c]
+.
+.P
+\fBjose jws ver\fR \-i JWS [\-I PAY] \-k JWK [\-O PAY] [\-a]
+.
+.SH "OVERVIEW"
+José is a C\-language implementation of the Javascript Object Signing and Encryption standards\. Specifically, José aims towards implementing the following standards:
+.
+.IP "\(bu" 4
+RFC 7515 \- JSON Web Signature (JWS)
+.
+.IP "\(bu" 4
+RFC 7516 \- JSON Web Encryption (JWE)
+.
+.IP "\(bu" 4
+RFC 7517 \- JSON Web Key (JWK)
+.
+.IP "\(bu" 4
+RFC 7518 \- JSON Web Algorithms (JWA)
+.
+.IP "\(bu" 4
+RFC 7519 \- JSON Web Token (JWT)
+.
+.IP "\(bu" 4
+RFC 7520 \- Examples of Protecting Content Using JOSE
+.
+.IP "\(bu" 4
+RFC 7638 \- JSON Web Key (JWK) Thumbprint
+.
+.IP "" 0
+.
+.P
+The José command line utility provides facilities for the following:
+.
+.IP "\(bu" 4
+URL\-safe Base64 Encoding & Decoding
+.
+.IP "\(bu" 4
+Key Generation and Management
+.
+.IP "\(bu" 4
+Encryption & Decryption
+.
+.IP "\(bu" 4
+Signing & Verification
+.
+.IP "" 0
+.
+.SH "AUTHOR"
+Nathaniel McCallum <npmccallum@redhat\.com>
+.
+.SH "SEE ALSO"
+\fBjose\-alg\fR(1), \fBjose\-b64\-dec\fR(1), \fBjose\-b64\-enc\fR(1), \fBjose\-jwe\-dec\fR(1), \fBjose\-jwe\-enc\fR(1), \fBjose\-jwe\-fmt\fR(1), \fBjose\-jwk\-exc\fR(1), \fBjose\-jwk\-gen\fR(1), \fBjose\-jwk\-pub\fR(1), \fBjose\-jwk\-thp\fR(1), \fBjose\-jwk\-use\fR(1), \fBjose\-jws\-fmt\fR(1), \fBjose\-jws\-sig\fR(1), \fBjose\-jws\-ver\fR(1)

+ 501 - 0
install-sh

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

+ 12 - 0
jose.pc.in

@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=${prefix}/include
+
+Name: José Library
+Description: Library for managing JOSE objects
+Requires: jansson
+Requires.private: zlib libcrypto
+Version: @VERSION@
+Libs: -L${libdir} -ljose
+Cflags: -I${includedir}

+ 10 - 0
jose/Makefile.am

@@ -0,0 +1,10 @@
+incdir = $(includedir)/$(PACKAGE_NAME)
+inc_HEADERS = \
+    cfg.h \
+    io.h \
+    b64.h \
+    jwk.h \
+    jws.h \
+    jwe.h \
+    jose.h \
+    openssl.h

+ 578 - 0
jose/Makefile.in

@@ -0,0 +1,578 @@
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+subdir = jose
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(inc_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES = jose.h
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__installdirs = "$(DESTDIR)$(incdir)"
+HEADERS = $(inc_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/jose.h.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JOSE_CFLAGS = @JOSE_CFLAGS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENMP_CFLAGS = @OPENMP_CFLAGS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+jansson_CFLAGS = @jansson_CFLAGS@
+jansson_LIBS = @jansson_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zlib_CFLAGS = @zlib_CFLAGS@
+zlib_LIBS = @zlib_LIBS@
+incdir = $(includedir)/$(PACKAGE_NAME)
+inc_HEADERS = \
+    cfg.h \
+    io.h \
+    b64.h \
+    jwk.h \
+    jws.h \
+    jwe.h \
+    jose.h \
+    openssl.h
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign jose/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign jose/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+jose.h: $(top_builddir)/config.status $(srcdir)/jose.h.in
+	cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+install-incHEADERS: $(inc_HEADERS)
+	@$(NORMAL_INSTALL)
+	@list='$(inc_HEADERS)'; test -n "$(incdir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(incdir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(incdir)" || exit 1; \
+	fi; \
+	for p in $$list; do \
+	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+	  echo "$$d$$p"; \
+	done | $(am__base_list) | \
+	while read files; do \
+	  echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(incdir)'"; \
+	  $(INSTALL_HEADER) $$files "$(DESTDIR)$(incdir)" || exit $$?; \
+	done
+
+uninstall-incHEADERS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(inc_HEADERS)'; test -n "$(incdir)" || list=; \
+	files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+	dir='$(DESTDIR)$(incdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+	$(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	set x; \
+	here=`pwd`; \
+	$(am__define_uniq_tagged_files); \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	$(am__define_uniq_tagged_files); \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+	list='$(am__tagged_files)'; \
+	case "$(srcdir)" in \
+	  [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+	  *) sdir=$(subdir)/$(srcdir) ;; \
+	esac; \
+	for i in $$list; do \
+	  if test -f "$$i"; then \
+	    echo "$(subdir)/$$i"; \
+	  else \
+	    echo "$$sdir/$$i"; \
+	  fi; \
+	done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(HEADERS)
+installdirs:
+	for dir in "$(DESTDIR)$(incdir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+	-rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-incHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-incHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
+	clean-libtool cscopelist-am ctags ctags-am distclean \
+	distclean-generic distclean-libtool distclean-tags distdir dvi \
+	dvi-am html html-am info info-am install install-am \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-incHEADERS install-info install-info-am install-man \
+	install-pdf install-pdf-am install-ps install-ps-am \
+	install-strip installcheck installcheck-am installdirs \
+	maintainer-clean maintainer-clean-generic mostlyclean \
+	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+	tags tags-am uninstall uninstall-am uninstall-incHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

+ 138 - 0
jose/b64.h

@@ -0,0 +1,138 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \brief URL-safe Base64 Encoding & Decoding
+ * \defgroup jose_b64 Base64
+ * @{
+ */
+
+#pragma once
+
+#include "io.h"
+#include <jansson.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#define JOSE_B64_MAP "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+
+/**
+ * Decodes a URL-safe Base64 JSON string to a buffer.
+ *
+ * If \p o is NULL, the number of output bytes necessary is returned.
+ *
+ * This function will never write more than \p ol bytes. If the output buffer
+ * is too small, an error will occur.
+ *
+ * \param i  The input URL-safe Base64 JSON string.
+ * \param o  The output buffer (may be NULL).
+ * \param ol The size of the output buffer.
+ * \return   The number of bytes that were (or would be) written.
+ *           If an error occurs, SIZE_MAX is returned.
+ */
+size_t
+jose_b64_dec(const json_t *i, void *o, size_t ol);
+
+/**
+ * Creates a new IO object which performs URL-safe Base64 decoding.
+ *
+ * All data written to the returned IO object will be decoded before
+ * passing it on to the next IO object in the chain.
+ *
+ * \param next The next IO object in the chain.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_b64_dec_io(jose_io_t *next);
+
+/**
+ * Decodes a URL-safe Base64 buffer to an output buffer.
+ *
+ * If \p o is NULL, the number of output bytes necessary is returned.
+ *
+ * This function will never write more than \p ol bytes. If the output buffer
+ * is too small, an error will occur.
+ *
+ * \param i  The input URL-safe Base64 buffer.
+ * \param il The size of the data in the input buffer.
+ * \param o  The output buffer.
+ * \param ol The size of the output buffer.
+ * \return   The number of bytes that were (or would be) written.
+ *           If an error occurs, SIZE_MAX is returned.
+ */
+size_t
+jose_b64_dec_buf(const void *i, size_t il, void *o, size_t ol);
+
+/**
+ * Decodes a JSON string from a URL-safe Base64 JSON string.
+ *
+ * \param i The input URL-safe Base64 JSON string containing JSON data.
+ * \return  The output JSON data.
+ */
+json_t *
+jose_b64_dec_load(const json_t *i);
+
+/**
+ * Encodes data to a URL-safe Base64 JSON string.
+ *
+ * \param i  The input buffer.
+ * \param il The size of the data in the input buffer.
+ * \return   The decoded JSON data. If an error occurs, NULL is returned.
+ */
+json_t *
+jose_b64_enc(const void *i, size_t il);
+
+/**
+ * Creates a new IO object which performs URL-safe Base64 encoding.
+ *
+ * All data written to the returned IO object will be encoded before passing
+ * it on to the next IO object in the chain.
+ *
+ * \param next The next IO object in the chain.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_b64_enc_io(jose_io_t *next);
+
+/**
+ * Encodes data to a URL-safe Base64 buffer.
+ *
+ * If \p o is NULL, the number of output bytes necessary is returned.
+ *
+ * This function will never write more than \p ol bytes. If the output buffer
+ * is too small, an error will occur.
+ *
+ * \param i  The input buffer.
+ * \param il The size of the data in the input buffer.
+ * \param o  The output URL-safe Base64 buffer.
+ * \param ol The size of the output buffer.
+ * \return   The number of bytes that were (or would be) written.
+ *           If an error occurs, SIZE_MAX is returned.
+ */
+size_t
+jose_b64_enc_buf(const void *i, size_t il, void *o, size_t ol);
+
+/**
+ * Encodes the input JSON as a URL-safe Base64 JSON string.
+ *
+ * \param i The input JSON data.
+ * \return  The output URL-safe Base64 JSON string.
+ */
+json_t *
+jose_b64_enc_dump(const json_t *i);
+
+/** @} */

+ 137 - 0
jose/cfg.h

@@ -0,0 +1,137 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \brief José Configuration
+ * \defgroup jose_cfg Config
+ * @{
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+enum {
+    _JOSE_CFG_ERR_BASE = 0x1053000000000000ULL,
+    JOSE_CFG_ERR_JWK_INVALID,
+    JOSE_CFG_ERR_JWK_MISMATCH,
+    JOSE_CFG_ERR_JWK_DENIED,
+    JOSE_CFG_ERR_ALG_NOTSUP,
+    JOSE_CFG_ERR_ALG_NOINFER,
+    JOSE_CFG_ERR_JWS_INVALID,
+};
+
+#ifdef DOXYGEN
+/**
+ * Defines a jose_cfg_t which calls jose_cfg_decref() at end of scope.
+ *
+ * For example:
+ *
+ *     void foo() {
+ *         jose_cfg_auto_t *cfg = jose_cfg();
+ *         // jose_cfg_decref() implicitly called
+ *     }
+ */
+typedef jose_cfg_t jose_cfg_auto_t;
+#else
+#define jose_cfg_auto_t jose_cfg_t __attribute__((cleanup(jose_cfg_auto)))
+#endif
+
+typedef struct jose_cfg jose_cfg_t;
+typedef void (jose_cfg_err_t)(void *misc, const char *file, int line,
+                              uint64_t err, const char *fmt, va_list ap);
+
+/**
+ * Creates a new configuration instance.
+ *
+ * \return A newly-allocated configuration instance.
+ */
+jose_cfg_t *
+jose_cfg(void);
+
+void
+jose_cfg_auto(jose_cfg_t **cfg);
+
+/**
+ * Increases the reference count of a configuration instance.
+ *
+ * This function always succeeds.
+ *
+ * \param cfg  The configuration context.
+ * \return     The value of \p cfg (for convenience).
+ */
+jose_cfg_t *
+jose_cfg_incref(jose_cfg_t *cfg);
+
+/**
+ * Decreases the reference count of a configuration instance.
+ *
+ * When the reference count reaches zero, the configuration instance is freed.
+ *
+ * \param cfg  The configuration context.
+ */
+void
+jose_cfg_decref(jose_cfg_t *cfg);
+
+/**
+ * Sets the error handler function for this configuration instance.
+ *
+ * The value of \p misc will be passed to the error handler function.
+ *
+ * You may pass NULL to \p err to return to the default error handler.
+ *
+ * \param cfg  The configuration context.
+ * \param err  The error handler function you wish to enable.
+ * \param misc The miscelaneous data you wish to pass to the error handler.
+ */
+void
+jose_cfg_set_err_func(jose_cfg_t *cfg, jose_cfg_err_t *err, void *misc);
+
+/**
+ * Gets the miscelaneous data associated with the current error handler.
+ *
+ * \param cfg  The configuration context.
+ * \return     The miscelaneous data associated with the error handler.
+ */
+void *
+jose_cfg_get_err_misc(jose_cfg_t *cfg);
+
+#ifdef DOXYGEN
+/**
+ * Submit an error.
+ *
+ * The error handler will be called with the error provided.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param err  The number corresponding to this error type.
+ * \param fmt  A printf()-style format string.
+ * \param ...  The printf()-style arguments.
+ */
+void
+jose_cfg_err(jose_cfg_t *cfg, uint64_t err, const char *fmt, ...);
+#else
+void __attribute__((format(printf, 5, 6)))
+jose_cfg_err(jose_cfg_t *cfg, const char *file, int line, uint64_t err,
+             const char *fmt, ...);
+
+#define jose_cfg_err(cfg, err, ...) \
+    jose_cfg_err(cfg, __FILE__, __LINE__, err, __VA_ARGS__)
+#endif
+
+/** @} */

+ 198 - 0
jose/io.h

@@ -0,0 +1,198 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \brief IO Chaining
+ * \defgroup jose_io IO
+ * @{
+ */
+
+#pragma once
+
+#include "cfg.h"
+#include <stdbool.h>
+#include <stdio.h>
+
+#ifdef DOXYGEN
+/**
+ * Defines a jose_io_t which calls jose_io_decref() at end of scope.
+ *
+ * For example:
+ *
+ *     void foo() {
+ *         uint8_t *buf = NULL;
+ *         size_t len = 0;
+ *         jose_io_auto_t *io = jose_io_malloc(NULL, &buf, &len);
+ *         // jose_io_decref() implicitly called
+ *     }
+ */
+typedef jose_io_t jose_io_auto_t;
+
+/**
+ * The interface for chained IO.
+ *
+ * \see jose_io_malloc()
+ * \see jose_io_buffer()
+ * \see jose_io_file()
+ * \see jose_io_multiplex()
+ * \see jose_b64_enc_io()
+ * \see jose_b64_dec_io()
+ * \see jose_jws_sig_io()
+ * \see jose_jws_ver_io()
+ * \see jose_jwe_dec_io()
+ * \see jose_jwe_dec_cek_io()
+ * \see jose_jwe_enc_io()
+ * \see jose_jwe_enc_cek_io()
+ */
+typedef struct {
+    /**
+     * Pushes data into the IO chain.
+     *
+     * \param io  The jose_io_t entity you are using.
+     * \param in  The input buffer.
+     * \param len The length of the data in the input buffer.
+     * \return    Returns true if all data was consumed, otherwise false.
+     */
+    bool  (*feed)(jose_io_t *io, const void *in, size_t len);
+
+    /**
+     * Completes the IO chain.
+     *
+     * Any data stored in internal buffers will be flushed.
+     *
+     * \param io  The jose_io_t entity you are using.
+     * \return    Returns true if flushing was successful, otherwise false.
+     */
+    bool  (*done)(jose_io_t *io);
+} jose_io_t;
+#else
+#define jose_io_auto_t jose_io_t __attribute__((cleanup(jose_io_auto)))
+
+typedef struct jose_io jose_io_t;
+struct jose_io {
+    size_t  refs;
+    bool  (*feed)(jose_io_t *io, const void *in, size_t len);
+    bool  (*done)(jose_io_t *io);
+    void  (*free)(jose_io_t *io); /* Don't call this. Use jose_io_decref(). */
+};
+#endif
+
+void
+jose_io_auto(jose_io_t **io);
+
+/**
+ * Increases the reference count of an IO object.
+ *
+ * This function always succeeds.
+ *
+ * \param io The jose_io_t entity you are using.
+ * \return   The value of \p io (for convenience).
+ */
+jose_io_t *
+jose_io_incref(jose_io_t *io);
+
+/**
+ * Decreases the reference count of an IO object.
+ *
+ * When the reference count reaches zero, io->free() is called.
+ *
+ * \param io  The jose_io_t entity you are using.
+ */
+void
+jose_io_decref(jose_io_t *io);
+
+/**
+ * Creates a new IO object which collects data into a dynamic buffer.
+ *
+ * The dynamic buffer is allocated into the \p buf pointer you provided and
+ * the length of the buffer is stored in \p len. The pointer referenced by
+ * \p buf must remain valid for the entire duration of the returned IO object.
+ *
+ * The default behavior is for the IO object to zero and free the buffer when
+ * it is freed. This means that, by default, you own the buffer pointer but
+ * the buffer itself is owned by the IO object. You can, however, steal the
+ * buffer by setting the buffer pointer to NULL.
+ *
+ * \see jose_io_malloc_steal()
+ * \param cfg The configuration context (optional).
+ * \param buf A buffer pointer pointer.
+ * \param len A pointer to the length of the buffer.
+ * \return    The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_io_malloc(jose_cfg_t *cfg, void **buf, size_t *len);
+
+/**
+ * Steals the buffer created by the jose_io_malloc() IO object.
+ *
+ * This convenience function simply returns the value of \p *buf and then sets
+ * \p *buf to NULL.
+ *
+ * \see jose_io_malloc()
+ * \param buf A pointer to the buffer pointer.
+ * \return    The value of \p *buf before it is set to NULL.
+ */
+void *
+jose_io_malloc_steal(void **buf);
+
+/**
+ * Creates a new IO object which collects data into a static buffer.
+ *
+ * The size of \p buf MUST be specified in the variable pointed to by \p len.
+ * This will be the maximum data written. However, after the function returns,
+ * the variable pointed to by \p len will contain the current length of data in
+ * the buffer.
+ *
+ * Unlike jose_io_malloc(), you own the buffer and it is not zeroed or freed
+ * when the IO object is freed.
+ *
+ * \param cfg The configuration context (optional).
+ * \param buf A buffer pointer.
+ * \param len A pointer to the length of the buffer.
+ * \return    The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_io_buffer(jose_cfg_t *cfg, void *buf, size_t *len);
+
+/**
+ * Creates a new IO object which writes data into a FILE.
+ *
+ * This function DOES NOT take ownership of the FILE. You are still responsible
+ * for calling fclose() at the appropriate time.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param file The output file which MUST be opened for writing or appending.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_io_file(jose_cfg_t *cfg, FILE *file);
+
+/**
+ * Creates a new IO object which multiplexes data into multiple IO objects.
+ *
+ * If \p all is true, the success of all \p nexts is required. Otherwise,
+ * all but one of the \p nexts can fail before the error is propagated upward.
+ *
+ * \param cfg   The configuration context (optional).
+ * \param nexts A NULL-terminated array of IO object pointers.
+ * \param all   Whether or not the success of all \p nexts is required.
+ * \return      The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_io_multiplex(jose_cfg_t *cfg, jose_io_t **nexts, bool all);
+
+/** @} */

+ 43 - 0
jose/jose.h

@@ -0,0 +1,43 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \mainpage José API Documentation
+ * \author Nathaniel McCallum
+ * \copyright Apache License, Version 2.0
+ *
+ * José is a C-language implementation of the Javascript Object Signing and
+ * Encryption standards. Specifically, José aims towards implementing the
+ * following standards:
+ *
+ * * RFC 7515 - JSON Web Signature (JWS)
+ * * RFC 7516 - JSON Web Encryption (JWE)
+ * * RFC 7517 - JSON Web Key (JWK)
+ * * RFC 7518 - JSON Web Algorithms (JWA)
+ * * RFC 7519 - JSON Web Token (JWT)
+ * * RFC 7520 - Examples of Protecting Content Using JOSE
+ * * RFC 7638 - JSON Web Key (JWK) Thumbprint
+ */
+
+#pragma once
+
+#include <jose/b64.h>
+#include <jose/jwe.h>
+#include <jose/jwk.h>
+#include <jose/jws.h>
+
+#define JOSE_VERSION 10

+ 43 - 0
jose/jose.h.in

@@ -0,0 +1,43 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \mainpage José API Documentation
+ * \author Nathaniel McCallum
+ * \copyright Apache License, Version 2.0
+ *
+ * José is a C-language implementation of the Javascript Object Signing and
+ * Encryption standards. Specifically, José aims towards implementing the
+ * following standards:
+ *
+ * * RFC 7515 - JSON Web Signature (JWS)
+ * * RFC 7516 - JSON Web Encryption (JWE)
+ * * RFC 7517 - JSON Web Key (JWK)
+ * * RFC 7518 - JSON Web Algorithms (JWA)
+ * * RFC 7519 - JSON Web Token (JWT)
+ * * RFC 7520 - Examples of Protecting Content Using JOSE
+ * * RFC 7638 - JSON Web Key (JWK) Thumbprint
+ */
+
+#pragma once
+
+#include <jose/b64.h>
+#include <jose/jwe.h>
+#include <jose/jwk.h>
+#include <jose/jws.h>
+
+#define JOSE_VERSION @VERSION@

+ 446 - 0
jose/jwe.h

@@ -0,0 +1,446 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * JSON Web Encryption (RFC 7516)
+ *
+ * A JSON Web Encryption (JWE) object contains (usually) two levels of
+ * encryption. First, the plaintext is encrypted with a random symmetric key.
+ * In José, we call this key the Content Encryption Key (CEK). Next, the CEK is
+ * wrapped (encrypted) with one or more keys. These keys are standard JSON Web
+ * Keys (JWK) and may be symmetric or asymmetric.
+ *
+ * Thus there is (usually) one CEK and one or more JWKs. However, there are
+ * some exceptions to this rule. Two such examples are the algorithms: "dir"
+ * and "ECDH-ES". In the first case, the JWK is a symmetric key and is used
+ * directly as the CEK. In the second case, an ECDH key exchange is performed
+ * and the result is used directly as the CEK. But in general, the maxim holds.
+ *
+ * This means that you can encrypt the data using one CEK and then encrypt the
+ * CEK to multiple recipients. With this schema, multiple recipients can each
+ * decrypt the data using their own key without having to send separate
+ * ciphertext to each recipient.
+ *
+ * For maximum flexibility, José structures its API to take advantage of this
+ * schema. For example, when encrypting to two recipients, the code could look
+ * like this (error handling omitted):
+ *
+ *     json_t *enc(void *plaintext, size_t len, json_t *jwk0, json_t *jwk1) {
+ *         json_auto_t *jwe = json_object();
+ *         json_auto_t *cek = json_object();
+ *
+ *         // Wrap to the first recipient (CEK generated implicitly)
+ *         jose_jwe_enc_jwk(NULL, jwe, NULL, jwk0, cek);
+ *
+ *         // Wrap to the second recipient
+ *         jose_jwe_enc_jwk(NULL, jwe, NULL, jwk1, cek);
+ *
+ *         // Encrypt plaintext using the generated CEK
+ *         jose_jwe_enc_cek(NULL, jwe, cek, plaintext, len);
+ *
+ *         return json_incref(jwe);
+ *     }
+ *
+ * However, because José intends to be easy to use, we also provide shortcuts.
+ * For example, you could use a JWKSet which contains multiple JWKs:
+ *
+ *     json_t *enc(void *plaintext, size_t len, json_t *jwkset) {
+ *         json_auto_t *jwe = json_object();
+ *
+ *         // Perform wrapping and encryption to all recipients
+ *         jose_jwe_enc(NULL, jwe, NULL, jwkset, plaintext, len);
+ *
+ *         return json_incref(jwe);
+ *     }
+ *
+ * Here are two tips to remember. First, let José generate your CEK implicitly.
+ * Second, always perform wrapping before encryption. Both of these tips are
+ * important because some wrapping algorithms may impose constraints on the
+ * generation of the CEK.
+ *
+ * To decrypt a JWE, we just reverse the process. First, we use a JWK to
+ * unwrap the CEK. Then we use the CEK to decrypt the ciphertext. Here is how
+ * that might look in code (again, error handling omitted):
+ *
+ *     void *dec(json_t *jwe, json_t *jwk, size_t *len) {
+ *         json_auto_t *cek = NULL;
+ *
+ *         // Unwrap the CEK using our JWK
+ *         cek = jose_jwe_dec_jwk(NULL, jwe, NULL, jwk);
+ *
+ *         // Decrypt ciphertext using the CEK
+ *         return jose_jwe_dec_cek(NULL, jwe, cek, len);
+ *     }
+ *
+ * Or, again, in simplified form:
+ *
+ *     void *dec(json_t *jwe, json_t *jwk, size_t *len) {
+ *         return jose_jwe_dec(NULL, jwe, NULL, jwk, len);
+ *     }
+ *
+ * If you need to forward a JWE to a new recipient, you can do this without
+ * performing re-encryption. Just unwrap the CEK and then wrap the CEK again
+ * using a new JWK. For example:
+ *
+ *     void fwd(json_t *jwe, json_t *oldjwk, json_t *newjwk) {
+ *         json_auto_t *cek = NULL;
+ *
+ *         // Unwrap the CEK using the old JWK
+ *         cek = jose_jwe_dec_jwk(NULL, jwe, NULL, oldjwk);
+ *
+ *         // Wrap the CEK to the new JWK
+ *         jose_jwe_enc_jwk(NULL, jwe, NULL, newjwk, cek);
+ *     }
+ *
+ * In all the above examples, parameters like which encryption algorithms to
+ * use were inferred from our keys. Where such an inferrence cannot be made,
+ * sensible and secure defaults were chosen automatically. If you would like
+ * more control over the process, simply set parameters in the appropriate
+ * objects (more on this in the function documentation). For example,
+ * to enable plaintext compression, you can specify the \p zip property
+ * in the JWE Protected Header:
+ *
+ *     json_t *enc(void *plaintext, size_t len, json_t *jwkset) {
+ *         json_auto_t *jwe = json_pack("{s:{s:s}}", "protected", "zip", "DEF");
+ *
+ *         // Perform compressed wrapping and encryption to all recipients
+ *         jose_jwe_enc(NULL, jwe, NULL, jwkset, plaintext, len);
+ *
+ *         return json_incref(jwe);
+ *     }
+ *
+ * José currently supports the "alg", "enc" and "zip" header parameters, as
+ * well as any algorithm-specific header parameters used by the specific
+ * algorithms we implement. Other header parameters may be specified, but do
+ * not effect the behavior of José's JWE implementation.
+ *
+ * \defgroup jose_jwe JWE
+ * \see https://tools.ietf.org/html/rfc7516
+ * @{
+ */
+
+#pragma once
+
+#include "cfg.h"
+#include "io.h"
+#include <jansson.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Merges the JOSE headers of a JWE object and a JWE recpient object.
+ *
+ * \param jwe  A JWE object.
+ * \param rcp  A JWE recipient object.
+ * \return     The newly allocated JOSE header.
+ */
+json_t *
+jose_jwe_hdr(const json_t *jwe, const json_t *rcp);
+
+/**
+ * Wraps and encrypts plaintext.
+ *
+ * This function is a thin wrapper around the jose_jwe_enc_io() function
+ * allowing the user to specify the plaintext in a single call. The ciphertext
+ * output will be appended to the JWE as the "ciphertext" property.
+ *
+ * \see jose_jwe_enc_cek_io()
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param rcp  The JWE recipient object(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for wrapping.
+ * \param pt   The plaintext.
+ * \param ptl  The length of the plaintext.
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jwe_enc(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *jwk,
+             const void *pt, size_t ptl);
+
+/**
+ * Wraps and encrypts plaintext using streaming.
+ *
+ * This function is a thin wrapper around the jose_jwe_enc_jwk() and
+ * jose_jwe_enc_cek_io() functions, removing the complexity of managing the CEK.
+ *
+ * \see jose_jwe_enc_jwk()
+ * \see jose_jwe_enc_cek_io()
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param rcp  The JWE recipient object(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for wrapping.
+ * \param next The next IO object in the chain.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_jwe_enc_io(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *jwk,
+                jose_io_t *next);
+
+/**
+ * Wraps a CEK with a JWK.
+ *
+ * The purpose of this function is to wrap (encrypt) or, in some cases, produce
+ * the CEK (\p cek) from the provided JWK(s) (\p jwk). This function can be
+ * used in two modes: single-JWK and multi-JWK.
+ *
+ * In single-JWK mode, the \p jwk parameter contains a JWK object and the
+ * \p rcp parameter must contain either a JWE recipient object or NULL, in
+ * which case a default empty JWE recipient object is created internally.
+ *
+ * Multi-JWK mode works exactly the same as single-JWK mode except that it
+ * performs multiple wrappings in a single call. This mode is enabled by
+ * passing either an array of JWK objects or a JWKSet as the \p jwk parameter.
+ * In this mode, the \p rcp parameter contains one of the following values:
+ *
+ * 1. A JWE recipient object that will be used for all wrappings. In this case,
+ *    a copy will be made for each wrapping and \p rcp will not be modified in
+ *    any way.
+ *
+ * 2. An array of JWE recipient objects. Each object will be used with its
+ *    corresponding JWK from \p jwk. If the arrays in \p sig and \p jwk are a
+ *    different size, an error will occur.
+ *
+ * 3. NULL. This has the same effect as passing NULL for each separate JWK.
+ *
+ * In either mode, if the resulting JWE (\p jwe) would contain only a single
+ * recipient, the JWE will be represented in Flattened JWE JSON Serialization
+ * Syntax. Otherwise, it will be represented in General JWE JSON Serialization
+ * Syntax.
+ *
+ * If the "alg" parameter is not specified in the JOSE Header, it will be
+ * inferred from the JWK and the chosen algorithm will be added to the JWE
+ * Per-Recipient Unprotected Header. Likewise, any missing, required,
+ * algorithm-specific parameters will be either inferred or sensible, secure
+ * defaults will be chosen and the results will be added to the JWE
+ * Per-Recipient Unprotected Header.
+ *
+ * If the provided CEK (\p cek) does not contain key material, it will be
+ * implicitly generated during the first call to jose_jwe_enc_jwk(). This
+ * important feature enables the use of the "dir" and "ECDH-ES" algorithms.
+ * In the case of the "dir" algorithm, the JWK is the CEK and thus the key
+ * material is copied from \p jwk to \p cek. A similar situation arises with
+ * the algorithm "ECDH-ES" where the result of a key exchange is used as the
+ * CEK; thus, the CEK is produced during the wrapping process. This feature
+ * implies that if multiple wrappings are created, only one of them may have
+ * the algorithm "ECDH-ES" and it must be the first wrapping. Attempting to
+ * use "ECDH-ES" with an existing CEK will result in an error.
+ *
+ * It is additionally possible to pass a password JSON string as key input
+ * to the PBES2 family of algorithms anywhere a JWK can be used. Likewise, if
+ * the "alg" JOSE Header parameter is not specified and a JSON string is used
+ * in place of a JWK, the PBES2 family of algorithms will be inferred.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param rcp  The JWE recipient object(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for wrapping.
+ * \param cek  The CEK to wrap (if empty, generated).
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jwe_enc_jwk(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *jwk,
+                 json_t *cek);
+
+/**
+ * Encrypts plaintext with the CEK.
+ *
+ * This function is a thin wrapper around the jose_jwe_enc_cek_io() function
+ * allowing the user to specify the plaintext in a single call. The ciphertext
+ * output will be appended to the JWE as the "ciphertext" property.
+ *
+ * \see jose_jwe_enc_cek_io()
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param cek  The CEK object.
+ * \param pt   The plaintext.
+ * \param ptl  The length of the plaintext.
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jwe_enc_cek(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
+                 const void *pt, size_t ptl);
+
+/**
+ * Encrypts plaintext with the CEK using streaming.
+ *
+ * The plaintext is provided through the returned IO object. The plaintext
+ * will be encrypted and written to the \p next IO object. This IO object
+ * works on binary data, so you may need to use a URL-safe Base64 decoder on
+ * input and a URL-safe Base64 encoder on output, depending on your situation.
+ *
+ * Compressed plaintext can be implicitly enabled by specifying the "zip"
+ * parameter the JWE Protected Header.
+ *
+ * If the JWE Protected Header is a JSON object rather than an encoded string,
+ * this function will encode the JWE Protected Header to its URL-safe Base64
+ * encoding as defined in RFC 7516. However, this function will never modify
+ * a JWE Protected Header that is already encoded.
+ *
+ * If the "enc" parameter is not specified in the JWE Protected Header or the
+ * JWE Shared Unprotected Header, it will be inferred from the CEK and stored
+ * in either the JWE Protected Header or the JWE Shared Unprotected Header
+ * (preferring the JWE Protected header if it can be modified).
+ *
+ * Please note that the "tag" property will only be added to the JWE when
+ * \ref jose_io_t.done() returns.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param cek  The CEK object.
+ * \param next The next IO object in the chain.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_jwe_enc_cek_io(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
+                    jose_io_t *next);
+
+/**
+ * Unwraps and decrypts ciphertext.
+ *
+ * This function is a thin wrapper around the jose_jwe_dec_io() function
+ * allowing the user to obtain the plaintext in a single call. The ciphertext
+ * input will be obtained from the JWE "ciphertext" property.
+ *
+ * \see jose_jwe_dec_io()
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param rcp  The JWE recipient object(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for wrapping.
+ * \param ptl  The length of the plaintext (output).
+ * \return     The newly-allocated plaintext.
+ */
+void *
+jose_jwe_dec(jose_cfg_t *cfg, const json_t *jwe, const json_t *rcp,
+             const json_t *jwk, size_t *ptl);
+
+/**
+ * Unwraps and decrypts ciphertext using streaming.
+ *
+ * This function is a thin wrapper around the jose_jwe_dec_jwk() and
+ * jose_jwe_dec_cek_io() functions, removing the complexity of managing the CEK.
+ *
+ * \see jose_jwe_dec_jwk()
+ * \see jose_jwe_dec_cek_io()
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param rcp  The JWE recipient object(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for unwrapping.
+ * \param next The next IO object in the chain.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_jwe_dec_io(jose_cfg_t *cfg, const json_t *jwe, const json_t *rcp,
+                const json_t *jwk, jose_io_t *next);
+
+/**
+ * Unwraps a CEK with a JWK.
+ *
+ * The purpose of this function is to unwrap (decrypt) or, in some cases,
+ * produce the CEK (\p cek) from the provided JWK(s) (\p jwk). This function
+ * can be used in two modes: single-JWK and multi-JWK.
+ *
+ * In single-JWK mode, the \p jwk parameter contains a JWK object and the
+ * \p rcp parameter must contain either a JWE recipient object you wish to
+ * unwrap or NULL, in which case all JWE recipients will be tried.
+ *
+ * Multi-JWK mode works exactly the same as single-JWK mode except that it
+ * attempts to unwrap with multiple JWKs in a single call. This mode is enabled
+ * by passing either an array of JWK objects or a JWKSet as the \p jwk
+ * parameter. In this mode, the \p rcp parameter contains one of the following
+ * values:
+ *
+ * 1. A JWE recipient object that will be used for all attempted unwrappings.
+ *
+ * 2. An array of JWE recipient objects. Each object will be used with its
+ *    corresponding JWK from \p jwk. If the arrays in \p sig and \p jwk are a
+ *    different size, an error will occur.
+ *
+ * 3. NULL. This has the same effect as passing NULL for each separate JWK.
+ *
+ * In either mode, a CEK is returned for the first JWK that successfully
+ * unwraps a CEK. Two exceptions to this rule are if the "dir" or "ECDH-ES"
+ * algorithms are used. In this case, a CEK may be returned which will fail
+ * during decryption since there is no way to completely validate the JWK with
+ * these algorithms. Thus, we suggest placing the keys for these algorithms
+ * last when unwrapping with multiple JWKs.
+ *
+ * If the "alg" parameter is not specified in the JOSE Header, it will be
+ * inferred from the JWK. This includes using a JSON string in place of a JWK
+ * for the PBES2 family of algorithms.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param rcp  The JWE recipient object(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for wrapping.
+ * \return     On success, a newly-allocated CEK object. Otherwise, NULL.
+ */
+json_t *
+jose_jwe_dec_jwk(jose_cfg_t *cfg, const json_t *jwe, const json_t *rcp,
+                 const json_t *jwk);
+
+/**
+ * Decrypts ciphertext with the CEK.
+ *
+ * This function is a thin wrapper around the jose_jwe_dec_cek_io() function
+ * allowing the user to obtain the plaintext in a single call. The ciphertext
+ * input will be obtained from the JWE "ciphertext" property.
+ *
+ * \see jose_jwe_dec_cek_io()
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param cek  The CEK object.
+ * \param ptl  The length of the plaintext (output).
+ * \return     The newly-allocated plaintext.
+ */
+void *
+jose_jwe_dec_cek(jose_cfg_t *cfg, const json_t *jwe, const json_t *cek,
+                 size_t *ptl);
+
+/**
+ * Decrypts ciphertext with the CEK using streaming.
+ *
+ * The ciphertext is provided through the returned IO object. The ciphertext
+ * will be decrypted and written to the \p next IO object. This IO object
+ * works on binary data, so you may need to use a URL-safe Base64 decoder on
+ * input and a URL-safe Base64 encoder on output, depending on your situation.
+ *
+ * Please note that validation of the ciphertext integrity protection is delayed
+ * until \ref jose_io_t.done() returns. This means it is incredibly important
+ * to check this return value and it is also important to be careful with the
+ * plaintext emitted until this check is performed.
+ *
+ * Compressed plaintext will be internally decompressed if the "zip" property
+ * is appropriately defined.
+ *
+ * If the "enc" parameter is not specified in the JWE Protected Header or the
+ * JWE Shared Unprotected Header, it will be inferred from the CEK.
+ *
+ * Please note that the "tag" property on the JWE will only be accessed when
+ * \ref jose_io_t.done() is called. So you may define it at any time on the
+ * JWE object before calling \ref jose_io_t.done().
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jwe  The JWE object.
+ * \param cek  The CEK object.
+ * \param next The next IO object in the chain.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_jwe_dec_cek_io(jose_cfg_t *cfg, const json_t *jwe, const json_t *cek,
+                    jose_io_t *next);
+
+/** @} */

+ 198 - 0
jose/jwk.h

@@ -0,0 +1,198 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * JSON Web Keys (RFC 7517)
+ *
+ * A JSON Web Key (JWS) is a standard data format for expresing cryptographic
+ * keys in JSON.
+ *
+ * \defgroup jose_jwk JWK
+ * \see https://tools.ietf.org/html/rfc7517
+ * \see https://tools.ietf.org/html/rfc7638
+ * @{
+ */
+
+#pragma once
+
+#include "cfg.h"
+#include <jansson.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Generates a new JWK.
+ *
+ * The JWK is generated using hints from the input in exactly the same format
+ * as you would find in the output. For example, the most common way to
+ * generate a key is to specify the algorithm you'd like to use the key with.
+ * For example (error handling omitted):
+ *
+ *     json_t *gen(void) {
+ *         json_auto_t *jwk = json_pack("{s:s}", "alg", "ES256");
+ *         jose_jwk_gen(NULL, jwk);
+ *         return json_incref(jwk);
+ *     }
+ *
+ * This method is preferred because other metadata can be inferred from the
+ * algorithm name, such as the key usage. Additionally, the algorithm metadata
+ * can be used to automatically generate correct headers when creating
+ * signatures (JWS) or encryptions (JWE). Thus, you should always default to
+ * creating keys by their algorithm usage.
+ *
+ * However, should your requirements differ, you can also generate a key using
+ * raw parameters (again, error handling omitted):
+ *
+ *     json_t *gen(void) {
+ *         json_auto_t *jwk = json_pack("{s:s,s:s}", "kty", "EC", "crv", "P-256");
+ *         jose_jwk_gen(NULL, jwk);
+ *         return json_incref(jwk);
+ *     }
+ *
+ *     json_t *gen(void) {
+ *         json_auto_t *jwk = json_pack("{s:s,s:i}", "kty", "RSA", "bits", 2048);
+ *         jose_jwk_gen(NULL, jwk);
+ *         return json_incref(jwk);
+ *     }
+ *
+ *     json_t *gen(void) {
+ *         json_auto_t *jwk = json_pack("{s:s,s:i}", "kty", "oct", "bytes", 32);
+ *         jose_jwk_gen(NULL, jwk);
+ *         return json_incref(jwk);
+ *     }
+ *
+ * In this case, "bits" and "bytes" will be removed from the final output.
+ *
+ * \see https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms
+ * \param cfg  The configuration context (optional).
+ * \param jwk  The JWK to generate.
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jwk_gen(jose_cfg_t *cfg, json_t *jwk);
+
+/**
+ * Removes all private key material from a JWK.
+ *
+ * In addition, this function will remove any key operations from the
+ * \p key_ops JWK property (if present) that apply only to the private key.
+ *
+ * This function should be used before exporting keys to third parties.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jwk  The JWK to remove private keys from.
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jwk_pub(jose_cfg_t *cfg, json_t *jwk);
+
+/**
+ * Determines if an operation is permitted for a JWK.
+ *
+ * The operation to be confirmed (\p op) is always specified according to
+ * the syntax of the "key_ops" JWK property, even when the "use" property
+ * is defined on the JWK.
+ *
+ * This function has two modes of operation. If \p req is false, then JWKs
+ * which do not have any key use metadata will be approved for this operation.
+ * However, if \p req is true then this metadata will be required for approval.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jwk  The JWK from which to remove private keys.
+ * \param req  Whether JWK key use metadata is required or not.
+ * \param op   The opperation to seek approval for.
+ * \return     When the JWK is approved, true. Otherwise, false.
+ */
+bool
+jose_jwk_prm(jose_cfg_t *cfg, const json_t *jwk, bool req, const char *op);
+
+/**
+ * Determines whether two JWKs have equal key material.
+ *
+ * This function considers relevant the same properties used for generation of
+ * a thumbprint as defined by RFC 7638.
+ *
+ * \see jose_jwk_thp()
+ * \see jose_jwk_thp_buf()
+ * \param cfg  The configuration context (optional).
+ * \param a    The first JWK to consider.
+ * \param b    The second JWK to consider.
+ * \return     When the two JWKs are equal, true. Otherwise, false.
+ */
+bool
+jose_jwk_eql(jose_cfg_t *cfg, const json_t *a, const json_t *b);
+
+/**
+ * Calculates the thumbprint of a JWK as a URL-safe Base64 encoded JSON string.
+ *
+ * This function is a thin wrapper around jose_jwk_thp_buf().
+ *
+ * \see jose_jwk_thp_buf()
+ * \param cfg  The configuration context (optional).
+ * \param jwk  The JWK to calculate the thumbprint for.
+ * \param alg  The hash algorithm to use.
+ * \return     On success, a newly-allocated JSON string. Otherwise, NULL.
+ */
+json_t *
+jose_jwk_thp(jose_cfg_t *cfg, const json_t *jwk, const char *alg);
+
+/**
+ * Calculates the thumbprint of a JWK.
+ *
+ * This function calculates the thumbprint of a JWK according to the method
+ * defined by RFC 7638.
+ *
+ * If \p thp is NULL, this function returns the size of the buffer required
+ * for the thumbprint output.
+ *
+ * \see https://tools.ietf.org/html/rfc7638
+ * \param cfg  The configuration context (optional).
+ * \param jwk  The JWK to calculate the thumbprint for.
+ * \param alg  The hash algorithm to use.
+ * \param thp  The output hash buffer.
+ * \param len  The size of the output hash buffer.
+ * \return     On success, the number of bytes written. Otherwise, SIZE_MAX.
+ */
+size_t
+jose_jwk_thp_buf(jose_cfg_t *cfg, const json_t *jwk,
+                 const char *alg, uint8_t *thp, size_t len);
+
+/**
+ * Perform a key exchange.
+ *
+ * The algorithm for the exchange is inferred from the inputs.
+ *
+ * The ECDH algorithm performs a standard elliptic curve multiplication such
+ * that the public value of \p rem is multiplied by the private value of \p.
+ *
+ * The ECMR algorithm has three modes of operation. Where \p lcl has a
+ * private key (the "d" property), it performs exactly like ECDH. If \p lcl
+ * does not have a private key and \p rem does have a private key, elliptic
+ * curve addition is performed. Otherwise, if neither \p lcl nor \p rem have a
+ * private key, \p rem is subtracted from \p lcl using elliptic curve
+ * subtraction. When using ECMR, be sure to validate the content of your inputs
+ * to avoid triggering the incorrect operation!
+ *
+ * \param cfg  The configuration context (optional).
+ * \param lcl  The local JWK (usually public/private key pair).
+ * \param rem  The remote JWK (usually just a public key).
+ * \return     On success, the JWK result of the key exchange. Otherwise, NULL.
+ */
+json_t *
+jose_jwk_exc(jose_cfg_t *cfg, const json_t *lcl, const json_t *rem);
+
+/** @} */

+ 197 - 0
jose/jws.h

@@ -0,0 +1,197 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * JSON Web Signature (RFC 7515)
+ *
+ * A JSON Web Signature (JWS) is a standard data format for expresing
+ * cryptographic signatures in JSON. The signatures are produced using a JSON
+ * Web Key (JWK).
+ *
+ * For example, to create a simple signature of a string using a JWK (error
+ * handling omitted):
+ *
+ *     json_t *sig(const char *str, const json_t *jwk) {
+ *         json_auto_t *jws = json_pack("{s:o}", "payload",
+ *                                      jose_b64_enc(str, strlen(str)));
+ *         jose_jws_sig(NULL, jws, NULL, jwk);
+ *         return json_incref(jws);
+ *     }
+ *
+ * Likewise, to verify this signature (again, error handling omitted):
+ *
+ *     char *ver(const json_t *jwe, 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);
+ *         str = calloc(1, len + 1);
+ *         jose_b64_dec(json_object_get(jwe, "payload"), str, len);
+ *         return str;
+ *     }
+ *
+ * \defgroup jose_jws JWS
+ * \see https://tools.ietf.org/html/rfc7515
+ * @{
+ */
+
+#pragma once
+
+#include "cfg.h"
+#include "io.h"
+#include <jansson.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * Merges the JOSE headers of a JWS signature object.
+ *
+ * \param sig A JWS signature object.
+ * \return    The newly allocated JOSE header.
+ */
+json_t *
+jose_jws_hdr(const json_t *sig);
+
+/**
+ * Creates one or more signatures in a JWS object.
+ *
+ * The JWS object (\p jws) must contain the "payload" property.
+ *
+ * All signatures created will be appended to the JWS specified by \p jws. If
+ * the resulting JWS (\p jws) would contain only a single signature, the JWS
+ * will be represented in Flattened JWS JSON Serialization Syntax. Otherwise,
+ * it will be represented in General JWS JSON Serialization Syntax.
+ *
+ * If \p jwk contains a JWK, a single signature is created. In this case, \p jws
+ * must contain either a JWS signature object template or NULL. You may specify
+ * algorithms or other signature behaviors simply by specifying them in the JOSE
+ * headers of the JWS signature object template as defined by RFC 7515. If a
+ * required property is missing, sensible defaults will be used and inserted
+ * into the JOSE headers; inferring them from the JWK (\p jwk) where possible.
+ *
+ * If \p jwk contains an array of JWKs or a JWKSet, multiple signatures are
+ * created. In this case, the \p sig parameter must contain one of the
+ * following values:
+ *
+ * 1. A JWS signature object template that will be used for all signatures.
+ *    In this case, a copy will be made for each signature and \p sig will
+ *    not be modified in any way.
+ *
+ * 2. An array of JWS signature object templates. Each template will be
+ *    used with its corresponding JWK from \p jwk. If the arrays in \p sig
+ *    and \p jwk are a different size, an error will occur.
+ *
+ * 3. NULL. This has the same effect as passing NULL for each separate key.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jws  The JWS object.
+ * \param sig  The JWS signature object template(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for creating signatures.
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jws_sig(jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk);
+
+/**
+ * Creates one or more signatures in a JWS object using streaming.
+ *
+ * This function behaves substantially like jose_jws_sig() except:
+ *
+ * The payload is not specified in the JWS (\p jws). Rather, the payload is
+ * provided using the returned IO object. The input to the returned IO object
+ * will not be internally Base64 encoded. So you may need to prepend the IO
+ * chain with the result of jose_b64_enc_io() (depending on your situation).
+ *
+ * Likewise, the payload is not stored in the JWS object (\p jws). This allows
+ * for detached payloads and decreases memory use for signatures over large
+ * payloads. If you would like to attach the payload, it is your responsibility
+ * to do so manually.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jws  The JWS object.
+ * \param sig  The JWS signature object template(s) or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for creating signatures.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_jws_sig_io(jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk);
+
+
+/**
+ * Verifies signatures of one or more JWKs in a JWS object.
+ *
+ * The JWS object (\p jws) must contain the "payload" property.
+ *
+ * If a single JWK (\p jwk) is specified, the \p all parameter is ignored. In
+ * this case, if you would like to verify a particular JWS signature object,
+ * you may specify it using the \p sig parameter. Otherwise, you may simply
+ * pass NULL to verify any of the JWS signature objects in the JWS object.
+ *
+ * If \p jwk contains an array of JWKs or a JWKSet, the \p all parameter
+ * determines whether a valid signature is required for every JWK in order to
+ * successfully validate the JWS. For example, if you set \p all to false this
+ * function will succeed if a valid signature is found for any of the provided
+ * JWKs. When using this multiple JWK signature mode, the \p sig parameter must
+ * contain one of the following values:
+ *
+ * 1. A single JWS signature object to validate against all/any of the
+ *    provided JWKs.
+ *
+ * 2. An array of JWS signature objects. In this case, each JWS signature
+ *    object will be mapped to its corresponding JWK from \p jwk. If the
+ *    arrays in \p sig and \p jwk are a different size, an error will occur.
+ *
+ * 3. NULL. This has the same effect as passing NULL for each separate key.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jws  The JWS object.
+ * \param sig  The JWS signature object(s) to verify or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for verifying signatures.
+ * \param all  Whether or not to require validation of all JWKs.
+ * \return     On success, true. Otherwise, false.
+ */
+bool
+jose_jws_ver(jose_cfg_t *cfg, const json_t *jws, const json_t *sig,
+             const json_t *jwk, bool all);
+
+/**
+ * Verifies signatures of one or more JWKs in a JWS object using streaming.
+ *
+ * This function behaves substantially like jose_jws_ver() except:
+ *
+ * The payload is not specified in the JWS (\p jws). Rather, the payload is
+ * provided using the returned IO object. The input to the returned IO object
+ * will not be internally Base64 encoded. So you may need to prepend the IO
+ * chain with the result of jose_b64_enc_io() (depending on your situation).
+ *
+ * Final signature verification is delayed until \ref jose_io_t.done() returns.
+ *
+ * \param cfg  The configuration context (optional).
+ * \param jws  The JWS object.
+ * \param sig  The JWS signature object(s) to verify or NULL.
+ * \param jwk  The JWK(s) or JWKSet used for verifying signatures.
+ * \param all  Whether or not to require validation of all JWKs.
+ * \return     The new IO object or NULL on error.
+ */
+jose_io_t *
+jose_jws_ver_io(jose_cfg_t *cfg, const json_t *jws, const json_t *sig,
+                const json_t *jwk, bool all);
+
+/** @} */

+ 45 - 0
jose/openssl.h

@@ -0,0 +1,45 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "cfg.h"
+#include <jansson.h>
+#include <openssl/evp.h>
+#include <openssl/ec.h>
+
+json_t *
+jose_openssl_jwk_from_EVP_PKEY(jose_cfg_t *cfg, EVP_PKEY *key);
+
+json_t *
+jose_openssl_jwk_from_RSA(jose_cfg_t *cfg, const RSA *key);
+
+json_t *
+jose_openssl_jwk_from_EC_KEY(jose_cfg_t *cfg, const EC_KEY *key);
+
+json_t *
+jose_openssl_jwk_from_EC_POINT(jose_cfg_t *cfg, const EC_GROUP *grp,
+                               const EC_POINT *pub, const BIGNUM *prv);
+
+EVP_PKEY *
+jose_openssl_jwk_to_EVP_PKEY(jose_cfg_t *cfg, const json_t *jwk);
+
+RSA *
+jose_openssl_jwk_to_RSA(jose_cfg_t *cfg, const json_t *jwk);
+
+EC_KEY *
+jose_openssl_jwk_to_EC_KEY(jose_cfg_t *cfg, const json_t *jwk);

+ 57 - 0
lib/Makefile.am

@@ -0,0 +1,57 @@
+AM_CFLAGS = \
+    @libcrypto_CFLAGS@ \
+    @jansson_CFLAGS@ \
+    @zlib_CFLAGS@ \
+    @JOSE_CFLAGS@ \
+    -I $(top_srcdir) \
+    -pthread
+
+lib_LTLIBRARIES = libjose.la
+
+libjose_la_LDFLAGS = $(LDFLAGS)
+
+libjose_la_LIBADD = \
+    @libcrypto_LIBS@ \
+    @jansson_LIBS@ \
+    @zlib_LIBS@
+
+libjose_la_SOURCES = \
+    misc.c misc.h \
+    cfg.c \
+    io.c \
+    b64.c \
+    hsh.c hsh.h \
+    hooks.c hooks.h \
+    jwk.c \
+    jws.c \
+    jwe.c \
+    zlib/deflate.c \
+    openssl/aescbch.c \
+    openssl/aesgcm.c \
+    openssl/aesgcmkw.c \
+    openssl/aeskw.c \
+    openssl/compat.c openssl/compat.h \
+    openssl/dir.c \
+    openssl/ec.c \
+    openssl/ecdh.c \
+    openssl/ecdhes.c \
+    openssl/ecmr.c \
+    openssl/ecdsa.c \
+    openssl/hash.c \
+    openssl/hmac.c \
+    openssl/jwk.c \
+    openssl/lock.c \
+    openssl/misc.c openssl/misc.h \
+    openssl/oct.c \
+    openssl/pbes2.c \
+    openssl/rsa.c \
+    openssl/rsaes.c \
+    openssl/rsassa.c
+
+EXTRA_DIST = libjose.map
+
+if HAVE_LD_VERSION_SCRIPT
+libjose_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libjose.map
+else
+libjose_la_LDFLAGS += -export-symbols-regex '^jose_.*' -version-info 1:0:0
+endif

+ 807 - 0
lib/Makefile.in

@@ -0,0 +1,807 @@
+# Makefile.in generated by automake 1.15.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2017 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+@HAVE_LD_VERSION_SCRIPT_TRUE@am__append_1 = -Wl,--version-script=$(srcdir)/libjose.map
+@HAVE_LD_VERSION_SCRIPT_FALSE@am__append_2 = -export-symbols-regex '^jose_.*' -version-info 1:0:0
+subdir = lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+    $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+    *) f=$$p;; \
+  esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+  srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+  for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+  for p in $$list; do echo "$$p $$p"; done | \
+  sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+  $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+    if (++n[$$2] == $(am__install_max)) \
+      { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+    END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+  sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+  sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+  test -z "$$files" \
+    || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+    || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+         $(am__cd) "$$dir" && rm -f $$files; }; \
+  }
+am__installdirs = "$(DESTDIR)$(libdir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libjose_la_DEPENDENCIES =
+am__dirstamp = $(am__leading_dot)dirstamp
+am_libjose_la_OBJECTS = misc.lo cfg.lo io.lo b64.lo hsh.lo hooks.lo \
+	jwk.lo jws.lo jwe.lo zlib/deflate.lo openssl/aescbch.lo \
+	openssl/aesgcm.lo openssl/aesgcmkw.lo openssl/aeskw.lo \
+	openssl/compat.lo openssl/dir.lo openssl/ec.lo openssl/ecdh.lo \
+	openssl/ecdhes.lo openssl/ecmr.lo openssl/ecdsa.lo \
+	openssl/hash.lo openssl/hmac.lo openssl/jwk.lo openssl/lock.lo \
+	openssl/misc.lo openssl/oct.lo openssl/pbes2.lo openssl/rsa.lo \
+	openssl/rsaes.lo openssl/rsassa.lo
+libjose_la_OBJECTS = $(am_libjose_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 = 
+libjose_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+	$(libjose_la_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+DEFAULT_INCLUDES = -I.@am__isrc@
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+	$(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+	$(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo "  CC      " $@;
+am__v_CC_1 = 
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo "  CCLD    " $@;
+am__v_CCLD_1 = 
+SOURCES = $(libjose_la_SOURCES)
+DIST_SOURCES = $(libjose_la_SOURCES)
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JOSE_CFLAGS = @JOSE_CFLAGS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENMP_CFLAGS = @OPENMP_CFLAGS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+jansson_CFLAGS = @jansson_CFLAGS@
+jansson_LIBS = @jansson_LIBS@
+libcrypto_CFLAGS = @libcrypto_CFLAGS@
+libcrypto_LIBS = @libcrypto_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+zlib_CFLAGS = @zlib_CFLAGS@
+zlib_LIBS = @zlib_LIBS@
+AM_CFLAGS = \
+    @libcrypto_CFLAGS@ \
+    @jansson_CFLAGS@ \
+    @zlib_CFLAGS@ \
+    @JOSE_CFLAGS@ \
+    -I $(top_srcdir) \
+    -pthread
+
+lib_LTLIBRARIES = libjose.la
+libjose_la_LDFLAGS = $(LDFLAGS) $(am__append_1) $(am__append_2)
+libjose_la_LIBADD = \
+    @libcrypto_LIBS@ \
+    @jansson_LIBS@ \
+    @zlib_LIBS@
+
+libjose_la_SOURCES = \
+    misc.c misc.h \
+    cfg.c \
+    io.c \
+    b64.c \
+    hsh.c hsh.h \
+    hooks.c hooks.h \
+    jwk.c \
+    jws.c \
+    jwe.c \
+    zlib/deflate.c \
+    openssl/aescbch.c \
+    openssl/aesgcm.c \
+    openssl/aesgcmkw.c \
+    openssl/aeskw.c \
+    openssl/compat.c openssl/compat.h \
+    openssl/dir.c \
+    openssl/ec.c \
+    openssl/ecdh.c \
+    openssl/ecdhes.c \
+    openssl/ecmr.c \
+    openssl/ecdsa.c \
+    openssl/hash.c \
+    openssl/hmac.c \
+    openssl/jwk.c \
+    openssl/lock.c \
+    openssl/misc.c openssl/misc.h \
+    openssl/oct.c \
+    openssl/pbes2.c \
+    openssl/rsa.c \
+    openssl/rsaes.c \
+    openssl/rsassa.c
+
+EXTRA_DIST = libjose.map
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign lib/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+install-libLTLIBRARIES: $(lib_LTLIBRARIES)
+	@$(NORMAL_INSTALL)
+	@list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+	list2=; for p in $$list; do \
+	  if test -f $$p; then \
+	    list2="$$list2 $$p"; \
+	  else :; fi; \
+	done; \
+	test -z "$$list2" || { \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \
+	  echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \
+	  $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \
+	}
+
+uninstall-libLTLIBRARIES:
+	@$(NORMAL_UNINSTALL)
+	@list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \
+	for p in $$list; do \
+	  $(am__strip_dir) \
+	  echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \
+	  $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \
+	done
+
+clean-libLTLIBRARIES:
+	-test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES)
+	@list='$(lib_LTLIBRARIES)'; \
+	locs=`for p in $$list; do echo $$p; done | \
+	      sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+	      sort -u`; \
+	test -z "$$locs" || { \
+	  echo rm -f $${locs}; \
+	  rm -f $${locs}; \
+	}
+zlib/$(am__dirstamp):
+	@$(MKDIR_P) zlib
+	@: > zlib/$(am__dirstamp)
+zlib/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) zlib/$(DEPDIR)
+	@: > zlib/$(DEPDIR)/$(am__dirstamp)
+zlib/deflate.lo: zlib/$(am__dirstamp) zlib/$(DEPDIR)/$(am__dirstamp)
+openssl/$(am__dirstamp):
+	@$(MKDIR_P) openssl
+	@: > openssl/$(am__dirstamp)
+openssl/$(DEPDIR)/$(am__dirstamp):
+	@$(MKDIR_P) openssl/$(DEPDIR)
+	@: > openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/aescbch.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/aesgcm.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/aesgcmkw.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/aeskw.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/compat.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/dir.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/ec.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/ecdh.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/ecdhes.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/ecmr.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/ecdsa.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/hash.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/hmac.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/jwk.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/lock.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/misc.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/oct.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/pbes2.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/rsa.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/rsaes.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+openssl/rsassa.lo: openssl/$(am__dirstamp) \
+	openssl/$(DEPDIR)/$(am__dirstamp)
+
+libjose.la: $(libjose_la_OBJECTS) $(libjose_la_DEPENDENCIES) $(EXTRA_libjose_la_DEPENDENCIES) 
+	$(AM_V_CCLD)$(libjose_la_LINK) -rpath $(libdir) $(libjose_la_OBJECTS) $(libjose_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+	-rm -f openssl/*.$(OBJEXT)
+	-rm -f openssl/*.lo
+	-rm -f zlib/*.$(OBJEXT)
+	-rm -f zlib/*.lo
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/b64.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cfg.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hooks.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hsh.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/io.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jwe.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jwk.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/jws.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/aescbch.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/aesgcm.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/aesgcmkw.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/aeskw.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/compat.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/dir.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/ec.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/ecdh.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/ecdhes.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/ecdsa.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/ecmr.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/hash.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/hmac.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/jwk.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/lock.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/misc.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/oct.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/pbes2.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/rsa.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/rsaes.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/rsassa.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@zlib/$(DEPDIR)/deflate.Plo@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@	$(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+	-rm -rf openssl/.libs openssl/_libs
+	-rm -rf zlib/.libs zlib/_libs
+
+ID: $(am__tagged_files)
+	$(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	set x; \
+	here=`pwd`; \
+	$(am__define_uniq_tagged_files); \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	$(am__define_uniq_tagged_files); \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+	list='$(am__tagged_files)'; \
+	case "$(srcdir)" in \
+	  [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+	  *) sdir=$(subdir)/$(srcdir) ;; \
+	esac; \
+	for i in $$list; do \
+	  if test -f "$$i"; then \
+	    echo "$(subdir)/$$i"; \
+	  else \
+	    echo "$$sdir/$$i"; \
+	  fi; \
+	done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+	for dir in "$(DESTDIR)$(libdir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+	-rm -f openssl/$(DEPDIR)/$(am__dirstamp)
+	-rm -f openssl/$(am__dirstamp)
+	-rm -f zlib/$(DEPDIR)/$(am__dirstamp)
+	-rm -f zlib/$(am__dirstamp)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+	mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR) openssl/$(DEPDIR) zlib/$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-libLTLIBRARIES
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR) openssl/$(DEPDIR) zlib/$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+	mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-libLTLIBRARIES
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \
+	clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \
+	ctags-am distclean distclean-compile distclean-generic \
+	distclean-libtool distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-libLTLIBRARIES install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+	tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:

+ 390 - 0
lib/b64.c

@@ -0,0 +1,390 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jose/b64.h>
+#include "misc.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#define JOSE_B64_DEC_BLK 3
+#define JOSE_B64_ENC_BLK 4
+
+typedef struct {
+    jose_io_t io;
+    jose_io_t *next;
+    size_t len;
+    union {
+        uint8_t db[16 * JOSE_B64_DEC_BLK];
+        char    eb[16 * JOSE_B64_ENC_BLK];
+    };
+} io_t;
+
+static const char *map = JOSE_B64_MAP;
+
+static size_t
+b64_dlen(size_t elen)
+{
+    switch (elen % JOSE_B64_ENC_BLK) {
+    case 0: return elen / JOSE_B64_ENC_BLK * JOSE_B64_DEC_BLK;
+    case 2: return elen / JOSE_B64_ENC_BLK * JOSE_B64_DEC_BLK + 1;
+    case 3: return elen / JOSE_B64_ENC_BLK * JOSE_B64_DEC_BLK + 2;
+    default: return SIZE_MAX;
+    }
+}
+
+static size_t
+b64_elen(size_t dlen)
+{
+    switch (dlen % JOSE_B64_DEC_BLK) {
+    case 0: return dlen / JOSE_B64_DEC_BLK * JOSE_B64_ENC_BLK;
+    case 1: return dlen / JOSE_B64_DEC_BLK * JOSE_B64_ENC_BLK + 2;
+    case 2: return dlen / JOSE_B64_DEC_BLK * JOSE_B64_ENC_BLK + 3;
+    default: return SIZE_MAX;
+    }
+}
+
+static void
+io_free(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    jose_io_decref(i->next);
+    zero(i, sizeof(*i));
+    free(i);
+}
+
+static size_t
+min(size_t a, size_t b)
+{
+    return a > b ? b : a;
+}
+
+static bool
+dec_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_t *i = containerof(io, io_t, io);
+    const char *enc = in;
+
+    while (len > 0) {
+        uint8_t buf[sizeof(i->eb) / JOSE_B64_ENC_BLK * JOSE_B64_DEC_BLK];
+        size_t dl = 0;
+        size_t el = 0;
+
+        /* Copy input into our input buffer. */
+        el = min(sizeof(i->eb) - i->len, len);
+        memcpy(&i->eb[i->len], enc, el);
+        i->len += el;
+        enc += el;
+        len -= el;
+
+        /* Perform encoding into our output buffer. */
+        el = i->len - i->len % JOSE_B64_ENC_BLK;
+        dl = jose_b64_dec_buf(i->eb, el, buf, sizeof(buf));
+        if (dl == SIZE_MAX)
+            return false;
+
+        i->len -= el;
+        memmove(i->eb, &i->eb[el], i->len);
+
+        if (!i->next->feed(i->next, buf, dl))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+dec_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t buf[sizeof(i->eb) / JOSE_B64_ENC_BLK * JOSE_B64_DEC_BLK];
+    size_t dl = 0;
+
+    dl = jose_b64_dec_buf(i->eb, i->len, buf, sizeof(buf));
+    if (dl == SIZE_MAX)
+        return false;
+
+    i->len = 0;
+    if (!i->next->feed(i->next, buf, dl))
+        return false;
+
+    return i->next->done(i->next);
+}
+
+static bool
+enc_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_t *i = containerof(io, io_t, io);
+    const char *dec = in;
+
+    while (len > 0) {
+        uint8_t buf[sizeof(i->db) / JOSE_B64_DEC_BLK * JOSE_B64_ENC_BLK];
+        size_t dl = 0;
+        size_t el = 0;
+
+        /* Copy input into our input buffer. */
+        dl = min(sizeof(i->db) - i->len, len);
+        memcpy(&i->db[i->len], dec, dl);
+        i->len += dl;
+        dec += dl;
+        len -= dl;
+
+        /* Perform encoding into our output buffer. */
+        dl = i->len - i->len % JOSE_B64_DEC_BLK;
+        el = jose_b64_enc_buf(i->db, dl, buf, sizeof(buf));
+        if (el == SIZE_MAX)
+            return false;
+
+        i->len -= dl;
+        memmove(i->db, &i->db[dl], i->len);
+
+        if (!i->next->feed(i->next, buf, el))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+enc_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t buf[sizeof(i->db) / JOSE_B64_DEC_BLK * JOSE_B64_ENC_BLK];
+    size_t el = 0;
+
+    el = jose_b64_enc_buf(i->db, i->len, buf, sizeof(buf));
+    if (el == SIZE_MAX)
+        return false;
+
+    i->len = 0;
+    if (!i->next->feed(i->next, buf, el))
+        return false;
+
+    return i->next->done(i->next);
+}
+
+size_t
+jose_b64_dec(const json_t *i, void *o, size_t ol)
+{
+    const char *b64 = NULL;
+    size_t len = 0;
+
+    if (json_unpack((json_t *) i, "s%", &b64, &len) < 0)
+        return SIZE_MAX;
+
+    if (!o)
+        return b64_dlen(len);
+
+    return jose_b64_dec_buf(b64, len, o, ol);
+}
+
+jose_io_t *
+jose_b64_dec_io(jose_io_t *next)
+{
+    jose_io_auto_t *io = NULL;
+    io_t *i = NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = dec_feed;
+    io->done = dec_done;
+    io->free = io_free;
+
+    i->next = jose_io_incref(next);
+    return jose_io_incref(io);
+}
+
+size_t
+jose_b64_dec_buf(const void *i, size_t il, void *o, size_t ol)
+{
+    const size_t len = strlen(map);
+    const char *e = i;
+    uint8_t *d = o;
+    uint8_t rem = 0;
+    size_t oo = 0;
+
+    if (il == SIZE_MAX)
+        return SIZE_MAX;
+
+    if (!o)
+        return b64_dlen(il);
+
+    if (ol < b64_dlen(il))
+        return SIZE_MAX;
+
+    for (size_t io = 0; io < il; io++) {
+        uint8_t v = 0;
+
+        for (const char c = e[io]; v < len && c != map[v]; v++)
+            continue;
+
+        if (v >= len)
+            return SIZE_MAX;
+
+        switch (io % JOSE_B64_ENC_BLK) {
+        case 0:
+            if (!e[io+1] || rem > 0)
+                return SIZE_MAX;
+
+            rem = v << 2;
+            break;
+
+        case 1:
+            d[oo++] = rem | (v >> 4);
+            rem = v << 4;
+            break;
+
+        case 2:
+            d[oo++] = rem | (v >> 2);
+            rem = v << 6;
+            break;
+
+        case 3:
+            d[oo++] = rem | v;
+            rem = 0;
+            break;
+        }
+    }
+
+    return rem > 0 ? SIZE_MAX : oo;
+}
+
+json_t *
+jose_b64_dec_load(const json_t *i)
+{
+    uint8_t *buf = NULL;
+    json_t *out = NULL;
+    size_t size = 0;
+
+    size = jose_b64_dec(i, NULL, 0);
+    if (size == SIZE_MAX)
+        return NULL;
+
+    buf = calloc(1, size);
+    if (!buf)
+        return NULL;
+
+    if (jose_b64_dec(i, buf, size) != size) {
+        zero(buf, size);
+        free(buf);
+        return NULL;
+    }
+
+    out = json_loadb((char *) buf, size, JSON_DECODE_ANY, NULL);
+    zero(buf, size);
+    free(buf);
+    return out;
+}
+
+json_t *
+jose_b64_enc(const void *i, size_t il)
+{
+    json_t *out = NULL;
+    char *enc = NULL;
+    size_t elen = 0;
+
+    elen = b64_elen(il);
+    if (elen == SIZE_MAX)
+        return NULL;
+
+    enc = calloc(1, elen);
+    if (!enc)
+        return NULL;
+
+    if (jose_b64_enc_buf(i, il, enc, elen) == elen)
+        out = json_stringn(enc, elen);
+
+    zero(enc, elen);
+    free(enc);
+    return out;
+}
+
+jose_io_t *
+jose_b64_enc_io(jose_io_t *next)
+{
+    jose_io_auto_t *io = NULL;
+    io_t *i = NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = enc_feed;
+    io->done = enc_done;
+    io->free = io_free;
+
+    i->next = jose_io_incref(next);
+    return jose_io_incref(io);
+}
+
+size_t
+jose_b64_enc_buf(const void *i, size_t il, void *o, size_t ol)
+{
+    const uint8_t *ib = i;
+    uint8_t rem = 0;
+    size_t oo = 0;
+    char *ob = o;
+
+    if (!o)
+        return b64_elen(il);
+
+    if (ol < b64_elen(il))
+        return SIZE_MAX;
+
+    for (size_t io = 0; io < il; io++) {
+        uint8_t c = ib[io];
+
+        switch (io % 3) {
+        case 0:
+            ob[oo++] = map[c >> 2];
+            ob[oo++] = map[rem = (c & 0b11) << 4];
+            break;
+
+        case 1:
+            ob[oo-1] = map[rem | (c >> 4)];
+            ob[oo++] = map[rem = (c & 0b1111) << 2];
+            break;
+
+        case 2:
+            ob[oo-1] = map[rem | (c >> 6)];
+            ob[oo++] = map[c & 0b111111];
+            break;
+        }
+    }
+
+    return oo;
+}
+
+json_t *
+jose_b64_enc_dump(const json_t *i)
+{
+    json_t *out = NULL;
+    char *buf = NULL;
+
+    buf = json_dumps(i, JSON_COMPACT | JSON_SORT_KEYS);
+    if (!buf)
+        return NULL;
+
+    out = jose_b64_enc((const uint8_t *) buf, strlen(buf));
+    zero(buf, strlen(buf));
+    free(buf);
+    return out;
+}

+ 133 - 0
lib/cfg.c

@@ -0,0 +1,133 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jose/cfg.h>
+#undef jose_cfg_err
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+struct jose_cfg {
+    size_t refs;
+
+    jose_cfg_err_t *err;
+    void *misc;
+};
+
+static struct {
+    uint64_t nmbr;
+    const char *name;
+} errnames[] = {
+#define XX(n) { n, # n }
+    XX(JOSE_CFG_ERR_JWK_INVALID),
+    XX(JOSE_CFG_ERR_JWK_MISMATCH),
+    XX(JOSE_CFG_ERR_JWK_DENIED),
+    XX(JOSE_CFG_ERR_ALG_NOTSUP),
+    XX(JOSE_CFG_ERR_ALG_NOINFER),
+    XX(JOSE_CFG_ERR_JWS_INVALID),
+#undef XX
+    {}
+};
+
+static const char *
+getname(uint64_t err)
+{
+    if (err < _JOSE_CFG_ERR_BASE)
+        return strerror(err);
+
+    for (size_t i = 0; errnames[i].name; i++) {
+        if (errnames[i].nmbr == err)
+            return errnames[i].name;
+    }
+
+    return "UNKNOWN";
+}
+
+static void
+dflt_err(void *misc, const char *file, int line, uint64_t err,
+         const char *fmt, va_list ap)
+{
+    fprintf(stderr, "%s:%d:", file, line);
+
+    if (err != 0)
+        fprintf(stderr, "%s:", getname(err));
+
+    vfprintf(stderr, fmt, ap);
+    fprintf(stderr, "\n");
+}
+
+static const jose_cfg_t dflt = { .err = dflt_err };
+
+jose_cfg_t *
+jose_cfg(void)
+{
+    jose_cfg_t *cfg = NULL;
+    cfg = calloc(1, sizeof(*cfg));
+    if (cfg)
+        *cfg = dflt;
+    return jose_cfg_incref(cfg);
+}
+
+void
+jose_cfg_auto(jose_cfg_t **cfg)
+{
+    if (cfg)
+        jose_cfg_decref(*cfg);
+}
+
+jose_cfg_t *
+jose_cfg_incref(jose_cfg_t *cfg)
+{
+    if (cfg)
+        cfg->refs++;
+
+    return cfg;
+}
+
+void
+jose_cfg_decref(jose_cfg_t *cfg)
+{
+    if (cfg->refs-- == 1)
+        free(cfg);
+}
+
+void
+jose_cfg_set_err_func(jose_cfg_t *cfg, jose_cfg_err_t *err, void *misc)
+{
+    cfg->err = err ? err : dflt.err;
+    cfg->misc = misc;
+}
+
+void *
+jose_cfg_get_err_misc(jose_cfg_t *cfg)
+{
+    return cfg->err;
+}
+
+void
+jose_cfg_err(jose_cfg_t *cfg, const char *file, int line, uint64_t err,
+             const char *fmt, ...)
+{
+    const jose_cfg_t *c = cfg ? cfg : &dflt;
+    va_list ap;
+
+    va_start(ap, fmt);
+    c->err(c->misc, file, line, err, fmt, ap);
+    va_end(ap);
+}

+ 62 - 0
lib/hooks.c

@@ -0,0 +1,62 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hooks.h"
+#include <string.h>
+
+static const jose_hook_jwk_t *jwks;
+static const jose_hook_alg_t *algs;
+
+void
+jose_hook_jwk_push(jose_hook_jwk_t *jwk)
+{
+    jwk->next = jwks;
+    jwks = jwk;
+}
+
+const jose_hook_jwk_t *
+jose_hook_jwk_list(void)
+{
+    return jwks;
+}
+
+void
+jose_hook_alg_push(jose_hook_alg_t *alg)
+{
+    alg->next = algs;
+    algs = alg;
+}
+
+const jose_hook_alg_t *
+jose_hook_alg_list(void)
+{
+    return algs;
+}
+
+const jose_hook_alg_t *
+jose_hook_alg_find(jose_hook_alg_kind_t kind, const char *name)
+{
+    for (const jose_hook_alg_t *a = algs; a; a = a->next) {
+        if (a->kind != kind)
+            continue;
+
+        if (!name || strcmp(a->name, name) == 0)
+            return a;
+    }
+
+    return NULL;
+}

+ 186 - 0
lib/hooks.h

@@ -0,0 +1,186 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <jose/jws.h>
+#include <jose/jwe.h>
+
+typedef enum {
+    JOSE_HOOK_JWK_KIND_NONE = 0,
+    JOSE_HOOK_JWK_KIND_TYPE,
+    JOSE_HOOK_JWK_KIND_OPER,
+    JOSE_HOOK_JWK_KIND_PREP,
+    JOSE_HOOK_JWK_KIND_MAKE,
+    JOSE_HOOK_JWK_KIND_LAST = JOSE_HOOK_JWK_KIND_MAKE
+} jose_hook_jwk_kind_t;
+
+typedef enum {
+    JOSE_HOOK_ALG_KIND_NONE = 0,
+    JOSE_HOOK_ALG_KIND_HASH,
+    JOSE_HOOK_ALG_KIND_SIGN,
+    JOSE_HOOK_ALG_KIND_WRAP,
+    JOSE_HOOK_ALG_KIND_ENCR,
+    JOSE_HOOK_ALG_KIND_COMP,
+    JOSE_HOOK_ALG_KIND_EXCH,
+    JOSE_HOOK_ALG_KIND_LAST = JOSE_HOOK_ALG_KIND_EXCH
+} jose_hook_alg_kind_t;
+
+typedef struct jose_hook_jwk jose_hook_jwk_t;
+struct jose_hook_jwk {
+    const jose_hook_jwk_t *next;
+    jose_hook_jwk_kind_t kind;
+
+    union {
+        struct {
+            const char  *kty;
+            const char **req;
+            const char **pub;
+            const char **prv;
+        } type;
+
+        struct {
+            const char *pub;
+            const char *prv;
+            const char *use;
+        } oper;
+
+        struct {
+            bool
+            (*handles)(jose_cfg_t *cfg, const json_t *jwk);
+
+            json_t *
+            (*execute)(jose_cfg_t *cfg, const json_t *jwk);
+        } prep;
+
+        struct {
+            bool
+            (*handles)(jose_cfg_t *cfg, const json_t *jwk);
+
+            json_t *
+            (*execute)(jose_cfg_t *cfg, const json_t *jwk);
+        } make;
+    };
+};
+
+typedef struct jose_hook_alg jose_hook_alg_t;
+struct jose_hook_alg {
+    const jose_hook_alg_t *next;
+    jose_hook_alg_kind_t kind;
+    const char *name;
+
+    union {
+        struct {
+            size_t size;
+
+            jose_io_t *
+            (*hsh)(const jose_hook_alg_t *alg, jose_cfg_t *cfg, jose_io_t *next);
+        } hash;
+
+        struct {
+            const char *sprm;
+            const char *vprm;
+
+            const char *
+            (*sug)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *jwk);
+
+            jose_io_t *
+            (*sig)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   json_t *jws, json_t *sig, const json_t *jwk);
+
+            jose_io_t *
+            (*ver)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *jws, const json_t *sig, const json_t *jwk);
+        } sign;
+
+        struct {
+            const char *eprm;
+            const char *dprm;
+
+            const char *
+            (*alg)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *jwk);
+
+            const char *
+            (*enc)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *jwk);
+
+            bool
+            (*wrp)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   json_t *jwe, json_t *rcp,
+                   const json_t *jwk, json_t *cek);
+
+            bool
+            (*unw)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *jwe, const json_t *rcp,
+                   const json_t *jwk, json_t *cek);
+        } wrap;
+
+        struct {
+            const char *eprm;
+            const char *dprm;
+
+            const char *
+            (*sug)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *cek);
+
+            jose_io_t *
+            (*enc)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   json_t *jwe, const json_t *cek, jose_io_t *next);
+
+            jose_io_t *
+            (*dec)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *jwe, const json_t *cek, jose_io_t *next);
+        } encr;
+
+        struct {
+            jose_io_t *
+            (*def)(const jose_hook_alg_t *alg, jose_cfg_t *cfg, jose_io_t *next);
+
+            jose_io_t *
+            (*inf)(const jose_hook_alg_t *alg, jose_cfg_t *cfg, jose_io_t *next);
+        } comp;
+
+        struct {
+            const char *prm;
+
+            const char *
+            (*sug)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *prv, const json_t *pub);
+
+            json_t *
+            (*exc)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+                   const json_t *prv, const json_t *pub);
+        } exch;
+    };
+};
+
+void
+jose_hook_jwk_push(jose_hook_jwk_t *reg);
+
+const jose_hook_jwk_t *
+jose_hook_jwk_list(void);
+
+void
+jose_hook_alg_push(jose_hook_alg_t *alg);
+
+const jose_hook_alg_t *
+jose_hook_alg_list(void);
+
+const jose_hook_alg_t *
+jose_hook_alg_find(jose_hook_alg_kind_t kind, const char *name);

+ 79 - 0
lib/hsh.c

@@ -0,0 +1,79 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include "misc.h"
+#include "hsh.h"
+
+#include <jose/b64.h>
+#include "hooks.h"
+
+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 *enc = NULL;
+    jose_io_auto_t *buf = NULL;
+    char b[1024] = {};
+    size_t l = sizeof(b);
+
+    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))
+        return NULL;
+
+    return json_stringn(b, l);
+}
+
+jose_io_t *
+hsh_io(jose_cfg_t *cfg, const char *alg, jose_io_t *next)
+{
+    const jose_hook_alg_t *a = NULL;
+
+    a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, alg);
+    if (!a)
+        return NULL;
+
+    return a->hash.hsh(a, cfg, next);
+}
+
+size_t
+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 *buf = NULL;
+
+    a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, alg);
+    if (!a)
+        return SIZE_MAX;
+
+    if (!hash || hlen == 0)
+        return a->hash.size;
+
+    if (hlen < a->hash.size)
+        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))
+        return SIZE_MAX;
+
+    return hlen;
+}

+ 82 - 0
lib/hsh.h

@@ -0,0 +1,82 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * \brief Cryptographic Hashing
+ * \defgroup hsh Hash
+ * @{
+ */
+
+#pragma once
+
+#include <jose/cfg.h>
+#include <jose/io.h>
+#include <jansson.h>
+#include <stdint.h>
+
+/**
+ * Hashes data with the specified algorithm.
+ *
+ * This function hashes the first \p dlen bytes of \p data using the \p alg
+ * specified and returns the output as a URL-safe Base64 encoded JSON string.
+ *
+ * \param cfg   The configuration context (optional).
+ * \param alg   The hashing algorithm.
+ * \param data  The input data buffer.
+ * \param dlen  The length of the data in the input buffer.
+ * \return      The hash as a URL-safe Base64 encoded JSON string.
+ */
+json_t *
+hsh(jose_cfg_t *cfg, const char *alg, const void *data, size_t dlen);
+
+/**
+ * Hashes data with the specified algorithm using IO chaining.
+ *
+ * This function creates an IO chain filter that takes the data to be hashed
+ * as input and outputs a hash of the input data.
+ *
+ * \param cfg   The configuration context (optional).
+ * \param alg   The hashing algorithm.
+ * \param next  The size of the output hash buffer.
+ * \return      The number of bytes written to the hash buffer or SIZE_MAX on error.
+ */
+
+jose_io_t *
+hsh_io(jose_cfg_t *cfg, const char *alg, jose_io_t *next);
+
+
+/**
+ * Hashes data with the specified algorithm into a buffer.
+ *
+ * This function hashes the first \p dlen bytes of \p data using the \p alg
+ * specified and stores the output in \p hash (a buffer of size \p hlen).
+ *
+ * If \p hash is NULL, the required size of the output buffer is returned.
+ *
+ * \param cfg   The configuration context (optional).
+ * \param alg   The hashing algorithm.
+ * \param data  The input data buffer.
+ * \param dlen  The length of the data in the input buffer.
+ * \param hash  The output hash buffer.
+ * \param hlen  The size of the output hash buffer.
+ * \return      The number of bytes written to the hash buffer or SIZE_MAX on error.
+ */
+size_t
+hsh_buf(jose_cfg_t *cfg, const char *alg,
+        const void *data, size_t dlen, void *hash, size_t hlen);
+
+/** @} */

+ 332 - 0
lib/io.c

@@ -0,0 +1,332 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jose/io.h>
+#include "misc.h"
+#include <string.h>
+
+typedef struct {
+    jose_io_t io;
+    void **buf;
+    size_t *len;
+} io_malloc_t;
+
+typedef struct {
+    jose_io_t io;
+    uint8_t *buf;
+    size_t max;
+    size_t *len;
+} io_buffer_t;
+
+typedef struct {
+    jose_io_t io;
+    FILE *file;
+} io_file_t;
+
+typedef struct {
+    jose_io_t io;
+    bool all;
+    size_t nnexts;
+    jose_io_t *nexts[];
+} io_plex_t;
+
+void
+jose_io_auto(jose_io_t **io)
+{
+    if (!io || !*io)
+        return;
+
+    jose_io_decref(*io);
+    *io = NULL;
+}
+
+jose_io_t *
+jose_io_incref(jose_io_t *io)
+{
+    if (!io)
+        return NULL;
+
+    io->refs++;
+    return io;
+}
+
+void
+jose_io_decref(jose_io_t *io)
+{
+    if (!io)
+        return;
+
+    if (io->refs-- == 1)
+        io->free(io);
+}
+
+static bool
+malloc_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_malloc_t *i = containerof(io, io_malloc_t, io);
+    uint8_t *tmp = NULL;
+
+    if (len == 0)
+        return true;
+
+    tmp = realloc(*i->buf, *i->len + len);
+    if (!tmp)
+        return false;
+
+    memcpy(&tmp[*i->len], in, len);
+    *i->buf = tmp;
+    *i->len += len;
+    return true;
+}
+
+static bool
+malloc_done(jose_io_t *io)
+{
+    return true;
+}
+
+static void
+malloc_free(jose_io_t *io)
+{
+    io_malloc_t *i = containerof(io, io_malloc_t, io);
+
+    if (i->buf && *i->buf && i->len) {
+        zero(*i->buf, *i->len);
+        free(*i->buf);
+        *i->len = 0;
+    }
+
+    zero(i, sizeof(*i));
+    free(i);
+}
+
+jose_io_t *
+jose_io_malloc(jose_cfg_t *cfg, void **buf, size_t *len)
+{
+    jose_io_auto_t *io = NULL;
+    io_malloc_t *i = NULL;
+
+    if (!buf || !len)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = malloc_feed;
+    io->done = malloc_done;
+    io->free = malloc_free;
+
+    i->buf = buf;
+    i->len = len;
+    return jose_io_incref(io);
+}
+
+void *
+jose_io_malloc_steal(void **buf)
+{
+    if (!buf)
+        return NULL;
+
+    void *out = *buf;
+    *buf = NULL;
+    return out;
+}
+
+static bool
+buffer_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_buffer_t *i = containerof(io, io_buffer_t, io);
+
+    if (len > i->max - *i->len)
+        return false;
+
+    memcpy(&i->buf[*i->len], in, len);
+    *i->len += len;
+    return true;
+}
+
+static bool
+buffer_done(jose_io_t *io)
+{
+    return true;
+}
+
+static void
+buffer_free(jose_io_t *io)
+{
+    io_buffer_t *i = containerof(io, io_buffer_t, io);
+    zero(i, sizeof(*i));
+    free(i);
+}
+
+jose_io_t *
+jose_io_buffer(jose_cfg_t *cfg, void *buf, size_t *len)
+{
+    jose_io_auto_t *io = NULL;
+    io_buffer_t *i = NULL;
+
+    if (!buf || !len)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = buffer_feed;
+    io->done = buffer_done;
+    io->free = buffer_free;
+
+    i->buf = buf;
+    i->max = *len;
+    i->len = len;
+
+    *len = 0;
+    return jose_io_incref(io);
+}
+
+static bool
+file_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_file_t *i = containerof(io, io_file_t, io);
+    return fwrite(in, 1, len, i->file) == len;
+}
+
+static bool
+file_done(jose_io_t *io)
+{
+    return true;
+}
+
+static void
+file_free(jose_io_t *io)
+{
+    io_file_t *i = containerof(io, io_file_t, io);
+    zero(i, sizeof(*i));
+    free(i);
+}
+
+jose_io_t *
+jose_io_file(jose_cfg_t *cfg, FILE *file)
+{
+    jose_io_auto_t *io = NULL;
+    io_file_t *i = NULL;
+
+    if (!file)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = file_feed;
+    io->done = file_done;
+    io->free = file_free;
+
+    i->file = file;
+    return jose_io_incref(&i->io);
+}
+
+static bool
+plex_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_plex_t *i = containerof(io, io_plex_t, io);
+    bool status = false;
+
+    for (size_t j = 0; j < i->nnexts; j++) {
+        bool s = false;
+
+        if (!i->nexts[j])
+            continue;
+
+        s = i->nexts[j]->feed(i->nexts[j], in, len);
+        status |= s;
+        if (!s) {
+            jose_io_auto(&i->nexts[j]);
+            if (i->all)
+                return false;
+        }
+    }
+
+    return status;
+}
+
+static bool
+plex_done(jose_io_t *io)
+{
+    io_plex_t *i = containerof(io, io_plex_t, io);
+    bool status = false;
+
+    for (size_t j = 0; j < i->nnexts; j++) {
+        bool s = false;
+
+        if (!i->nexts[j])
+            continue;
+
+        s = i->nexts[j]->done(i->nexts[j]);
+        status |= s;
+        if (!s) {
+            jose_io_auto(&i->nexts[j]);
+            if (i->all)
+                return false;
+        }
+    }
+
+    return status;
+}
+
+static void
+plex_free(jose_io_t *io)
+{
+    io_plex_t *i = containerof(io, io_plex_t, io);
+
+    for (size_t j = 0; j < i->nnexts; j++)
+        jose_io_decref(i->nexts[j]);
+
+    zero(i, sizeof(*i));
+    free(i);
+}
+
+jose_io_t *
+jose_io_multiplex(jose_cfg_t *cfg, jose_io_t **nexts, bool all)
+{
+    jose_io_auto_t *io = NULL;
+    io_plex_t *i = NULL;
+    size_t nnexts = 0;
+
+    while (nexts && nexts[nnexts])
+        nnexts++;
+
+    i = calloc(1, sizeof(*i) + sizeof(jose_io_t *) * nnexts);
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = plex_feed;
+    io->done = plex_done;
+    io->free = plex_free;
+
+    i->all = all;
+    i->nnexts = nnexts;
+    for (size_t j = 0; nexts && j < nnexts; j++)
+        i->nexts[j] = jose_io_incref(nexts[j]);
+
+    return jose_io_incref(&i->io);
+}

+ 527 - 0
lib/jwe.c

@@ -0,0 +1,527 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include "misc.h"
+
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include <jose/jwe.h>
+#include "hooks.h"
+
+#include <errno.h>
+#include <string.h>
+
+#include <unistd.h>
+
+static bool
+jwe_hdr_set_new(json_t *jwe, const char *name, json_t *value)
+{
+    json_auto_t *v = value;
+    json_t *p = NULL;
+    json_t *u = NULL;
+
+    p = json_object_get(jwe, "protected");
+    if (p && !json_is_object(p) && !json_is_string(p))
+        return false;
+
+    u = json_object_get(jwe, "unprotected");
+    if (u && !json_is_object(u))
+        return false;
+
+    if (!u && json_is_string(p) &&
+        json_object_set_new(jwe, "unprotected", u = json_object()) < 0)
+        return false;
+
+    if (!u && !p &&
+        json_object_set_new(jwe, "protected", p = json_object()) < 0)
+        return false;
+
+    if (json_object_set(json_is_object(p) ? p : u, name, v) < 0)
+        return false;
+
+    return true;
+}
+
+json_t *
+jose_jwe_hdr(const json_t *jwe, const json_t *rcp)
+{
+    json_auto_t *p = NULL;
+    json_t *s = NULL;
+    json_t *h = NULL;
+
+    p = json_incref(json_object_get(jwe, "protected"));
+    if (!p) {
+        p = json_object();
+    } else if (json_is_object(p)) {
+        json_decref(p);
+        p = json_deep_copy(p);
+    } else if (json_is_string(p)) {
+        json_decref(p);
+        p = jose_b64_dec_load(p);
+    }
+
+    if (!json_is_object(p))
+        return NULL;
+
+    s = json_object_get(jwe, "unprotected");
+    if (s) {
+        if (json_object_update_missing(p, s) == -1)
+            return NULL;
+    }
+
+    h = json_object_get(rcp, "header");
+    if (h) {
+        if (json_object_update_missing(p, h) == -1)
+            return NULL;
+    }
+
+    return json_incref(p);
+}
+
+bool
+jose_jwe_enc(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *jwk,
+             const void *pt, size_t ptl)
+{
+    json_auto_t *cek = NULL;
+
+    cek = json_object();
+    if (!cek)
+        return NULL;
+
+    if (!jose_jwe_enc_jwk(cfg, jwe, rcp, jwk, cek))
+        return NULL;
+
+    return jose_jwe_enc_cek(cfg, jwe, cek, pt, ptl);
+}
+
+jose_io_t *
+jose_jwe_enc_io(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *jwk,
+                jose_io_t *next)
+{
+    json_auto_t *cek = NULL;
+
+    cek = json_object();
+    if (!cek)
+        return NULL;
+
+    if (!jose_jwe_enc_jwk(cfg, jwe, rcp, jwk, cek))
+        return NULL;
+
+    return jose_jwe_enc_cek_io(cfg, jwe, cek, next);
+}
+
+static const jose_hook_alg_t *
+find_alg(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *hdr,
+         const json_t *jwk)
+{
+    const jose_hook_alg_t *alg = NULL;
+    const char *name = NULL;
+    json_t *h = NULL;
+
+    if (json_unpack((json_t *) hdr, "{s:s}", "alg", &name) >= 0)
+        return jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, name);
+
+    for (alg = jose_hook_alg_list(); alg && !name; alg = alg->next) {
+        if (alg->kind != JOSE_HOOK_ALG_KIND_WRAP)
+            continue;
+        name = alg->wrap.alg(alg, cfg, jwk);
+    }
+
+    if (!name)
+        return NULL;
+
+    alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, name);
+    if (alg) {
+        h = json_object_get(rcp, "header");
+        if (!h && json_object_set_new(rcp, "header", h = json_object()) < 0)
+            return NULL;
+
+        if (json_object_set_new(h, "alg", json_string(alg->name)) < 0)
+            return NULL;
+    }
+
+    return alg;
+}
+
+static bool
+ensure_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+           const json_t *hdr, const json_t *jwk, json_t *cek)
+{
+    const char *enc = NULL;
+
+    enc = json_string_value(json_object_get(cek, "alg"));
+    if (enc)
+        return true;
+
+    if (json_unpack((json_t *) hdr, "{s?s}", "enc", &enc) < 0)
+        return false;
+
+    /* See if we can infer an enc from the CEK. */
+    for (const jose_hook_alg_t *a = jose_hook_alg_list();
+         a && !enc; a = a->next) {
+        if (a->kind != JOSE_HOOK_ALG_KIND_ENCR)
+            continue;
+        enc = a->encr.sug(a, cfg, cek);
+    }
+
+    /* See if we can infer an enc from the JWK. */
+    if (!enc)
+        enc = alg->wrap.enc(alg, cfg, jwk);
+
+    /* Just pick an enc. */
+    for (const jose_hook_alg_t *a = jose_hook_alg_list();
+         a && !enc; a = a->next) {
+        if (a->kind == JOSE_HOOK_ALG_KIND_ENCR)
+            enc = a->name;
+    }
+
+    return json_object_set_new(cek, "alg", json_string(enc)) >= 0;
+}
+
+bool
+jose_jwe_enc_jwk(jose_cfg_t *cfg, json_t *jwe, json_t *rcp, const json_t *jwk,
+                 json_t *cek)
+{
+    const jose_hook_alg_t *alg = NULL;
+    json_auto_t *hdr = NULL;
+    json_auto_t *r = NULL;
+
+    if (!cek)
+        return false;
+
+    if (json_is_array(jwk) || json_is_array(json_object_get(jwk, "keys"))) {
+        if (!json_is_array(jwk))
+            jwk = json_object_get(jwk, "keys");
+
+        if (json_is_array(rcp) && json_array_size(rcp) != json_array_size(jwk))
+            return NULL;
+
+        for (size_t i = 0; i < json_array_size(jwk); i++) {
+            json_auto_t *tmp = NULL;
+
+            if (json_is_array(rcp))
+                tmp = json_incref(json_array_get(rcp, i));
+            else
+                tmp = json_deep_copy(rcp);
+
+            if (!jose_jwe_enc_jwk(cfg, jwe, tmp, json_array_get(jwk, i), cek))
+                return false;
+        }
+
+        return json_array_size(jwk) > 0;
+    }
+
+    if (!rcp)
+        r = json_object();
+    else if (!json_is_object(rcp))
+        return false;
+    else
+        r = json_incref(rcp);
+
+    hdr = jose_jwe_hdr(jwe, r);
+    if (!hdr)
+        return false;
+
+    alg = find_alg(cfg, jwe, r, hdr, jwk);
+    if (!alg)
+        return false;
+
+    if (!ensure_enc(alg, cfg, jwe, hdr, jwk, cek))
+        return false;
+
+    if (!jose_jwk_prm(cfg, jwk, false, alg->wrap.eprm))
+        return false;
+
+    return alg->wrap.wrp(alg, cfg, jwe, r, jwk, cek);
+}
+
+bool
+jose_jwe_enc_cek(jose_cfg_t *cfg, json_t *jwe, const json_t *cek,
+                 const void *pt, size_t ptl)
+{
+    jose_io_auto_t *i = NULL;
+    jose_io_auto_t *o = NULL;
+    void *ct = NULL;
+    size_t ctl = 0;
+
+    o = jose_io_malloc(cfg, &ct, &ctl);
+    i = jose_jwe_enc_cek_io(cfg, jwe, cek, o);
+    if (!o || !i || !i->feed(i, pt, ptl) || !i->done(i))
+        return false;
+
+    if (json_object_set_new(jwe, "ciphertext", jose_b64_enc(ct, ctl)) < 0)
+        return false;
+
+    return true;
+}
+
+jose_io_t *
+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;
+
+    if (json_unpack(jwe, "{s?{s?s}}", "protected", "enc", &h) < 0)
+        return NULL;
+
+    if (json_unpack((json_t *) cek, "{s?s}", "alg", &k) < 0)
+        return NULL;
+
+    if (!h) {
+        h = k;
+
+        for (alg = jose_hook_alg_list(); alg && !h; alg = alg->next) {
+            if (alg->kind != JOSE_HOOK_ALG_KIND_ENCR)
+                continue;
+            h = alg->encr.sug(alg, cfg, cek);
+        }
+
+        if (!h) {
+            jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOINFER,
+                         "Unable to infer encryption algorithm");
+            return NULL;
+        }
+
+        alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_ENCR, h);
+        if (alg && !jwe_hdr_set_new(jwe, "enc", json_string(alg->name)))
+            return NULL;
+    } else {
+        if (k && strcmp(h, k) != 0) {
+            jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
+                         "Algorithm mismatch (%s != %s)", h, k);
+            return NULL;
+        }
+
+        alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_ENCR, h);
+    }
+
+    if (!alg) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOTSUP,
+                     "Unsupported encryption algorithm (%s)", h);
+        return NULL;
+    }
+
+    if (!jose_jwk_prm(cfg, cek, false, alg->encr.eprm)) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
+                     "CEK is not allowed to encrypt");
+        return NULL;
+    }
+
+    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);
+}
+
+void *
+jose_jwe_dec(jose_cfg_t *cfg, const json_t *jwe, const json_t *rcp,
+             const json_t *jwk, size_t *ptl)
+{
+    json_auto_t *cek = NULL;
+
+    cek = jose_jwe_dec_jwk(cfg, jwe, rcp, jwk);
+    if (!cek)
+        return NULL;
+
+    return jose_jwe_dec_cek(cfg, jwe, cek, ptl);
+}
+
+jose_io_t *
+jose_jwe_dec_io(jose_cfg_t *cfg, const json_t *jwe, const json_t *rcp,
+                const json_t *jwk, jose_io_t *next)
+{
+    json_auto_t *cek = NULL;
+
+    cek = jose_jwe_dec_jwk(cfg, jwe, rcp, jwk);
+    if (!cek)
+        return NULL;
+
+    return jose_jwe_dec_cek_io(cfg, jwe, cek, next);
+}
+
+json_t *
+jose_jwe_dec_jwk(jose_cfg_t *cfg, const json_t *jwe, const json_t *rcp,
+                 const json_t *jwk)
+{
+    const jose_hook_alg_t *alg = NULL;
+    const char *halg = NULL;
+    const char *henc = NULL;
+    const char *kalg = NULL;
+    json_auto_t *cek = NULL;
+    json_auto_t *hdr = NULL;
+
+    if (json_is_array(jwk) || json_is_array(json_object_get(jwk, "keys"))) {
+        if (!json_is_array(jwk))
+            jwk = json_object_get(jwk, "keys");
+
+        for (size_t i = 0; i < json_array_size(jwk) && !cek; i++)
+            cek = jose_jwe_dec_jwk(cfg, jwe, rcp, json_array_get(jwk, i));
+
+        return json_incref(cek);
+    }
+
+    if (!rcp) {
+        const json_t *rcps = NULL;
+
+        rcps = json_object_get(jwe, "recipients");
+        if (json_is_array(rcps)) {
+            for (size_t i = 0; i < json_array_size(rcps) && !cek; i++)
+                cek = jose_jwe_dec_jwk(cfg, jwe, json_array_get(rcps, i), jwk);
+        } else if (!rcps) {
+            cek = jose_jwe_dec_jwk(cfg, jwe, jwe, jwk);
+        }
+
+        return json_incref(cek);
+    }
+
+    hdr = jose_jwe_hdr(jwe, rcp);
+    if (!hdr)
+        return NULL;
+
+    if (json_unpack(hdr, "{s?s,s?s}", "alg", &halg, "enc", &henc) == -1)
+        return NULL;
+
+    kalg = json_string_value(json_object_get(jwk, "alg"));
+    if (!halg)
+        halg = kalg;
+    else if (kalg && strcmp(halg, kalg) != 0 &&
+             (!henc || strcmp(henc, kalg) != 0))
+        return NULL;
+
+    alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, halg);
+    if (!alg)
+        return NULL;
+
+    if (!jose_jwk_prm(cfg, jwk, false, alg->wrap.dprm))
+        return NULL;
+
+    cek = json_pack("{s:s,s:s,s:O,s:[ss]}",
+                    "kty", "oct", "use", "enc",
+                    "enc", json_object_get(hdr, "enc"),
+                    "key_ops", "encrypt", "decrypt");
+    if (!cek)
+        return NULL;
+
+    if (!alg->wrap.unw(alg, cfg, jwe, rcp, jwk, cek))
+        return NULL;
+
+    return json_incref(cek);
+}
+
+void *
+jose_jwe_dec_cek(jose_cfg_t *cfg, const json_t *jwe, const json_t *cek,
+                 size_t *ptl)
+{
+    jose_io_auto_t *d = NULL;
+    jose_io_auto_t *i = NULL;
+    jose_io_auto_t *o = NULL;
+    const char *ct = NULL;
+    void *pt = NULL;
+    size_t ctl = 0;
+
+    if (json_unpack((json_t *) jwe, "{s:s%}", "ciphertext", &ct, &ctl) < 0)
+        return NULL;
+
+    o = jose_io_malloc(cfg, &pt, ptl);
+    d = jose_jwe_dec_cek_io(cfg, jwe, cek, o);
+    i = jose_b64_dec_io(d);
+    if (!o || !d || !i || !i->feed(i, ct, ctl) || !i->done(i))
+        return NULL;
+
+    return jose_io_malloc_steal(&pt);
+}
+
+jose_io_t *
+jose_jwe_dec_cek_io(jose_cfg_t *cfg, const 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 *hdr = NULL;
+    json_auto_t *prt = NULL;
+    const char *kalg = NULL;
+    const char *halg = NULL;
+    const char *hzip = NULL;
+
+    prt = jose_b64_dec_load(json_object_get(jwe, "protected"));
+    (void) json_unpack(prt, "{s:s}", "zip", &hzip);
+
+    hdr = jose_jwe_hdr(jwe, NULL);
+    if (!hdr)
+        return NULL;
+
+    if (json_unpack(hdr, "{s?s}", "enc", &halg) < 0)
+        return NULL;
+
+    if (json_unpack((json_t *) cek, "{s?s}", "alg", &kalg) < 0)
+        return NULL;
+
+    if (!halg && !kalg) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOINFER,
+                     "Decryption algorithm cannot be inferred");
+        return NULL;
+    } else if (halg && kalg && strcmp(halg, kalg) != 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
+                     "Algorithm mismatch (%s != %s)", halg, kalg);
+        return NULL;
+    }
+
+    alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_ENCR, halg ? halg : kalg);
+    if (!alg)
+        return NULL;
+
+    if (!jose_jwk_prm(cfg, cek, false, alg->encr.dprm))
+        return NULL;
+
+    if (hzip) {
+        const jose_hook_alg_t *a = NULL;
+
+        a = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_COMP, hzip);
+        if (!a)
+            return NULL;
+
+        zip = a->comp.inf(a, cfg, next);
+        if (!zip)
+            return NULL;
+    }
+
+    return alg->encr.dec(alg, cfg, jwe, cek, zip ? zip : next);
+}

+ 490 - 0
lib/jwk.c

@@ -0,0 +1,490 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include "hooks.h"
+#include "misc.h"
+#include "hsh.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool
+jwk_hook(jose_cfg_t *cfg, json_t *jwk, jose_hook_jwk_kind_t kind, bool dflt)
+{
+    for (const jose_hook_jwk_t *j = jose_hook_jwk_list(); j; j = j->next) {
+        json_auto_t *upd = NULL;
+        const char *key = NULL;
+        json_t *val = NULL;
+        size_t i = 0;
+
+        if (j->kind != kind)
+            continue;
+
+        if (!j->prep.handles(cfg, jwk))
+            continue;
+
+        upd = j->prep.execute(cfg, jwk);
+        if (!json_is_object(upd))
+            return false;
+
+        json_array_foreach(json_object_get(upd, "del"), i, val) {
+            if (!json_object_get(jwk, json_string_value(val)))
+                continue;
+            if (json_object_del(jwk, json_string_value(val)) < 0)
+                return false;
+        }
+
+        json_object_foreach(json_object_get(upd, "upd"), key, val) {
+            json_t *src = json_object_get(jwk, key);
+
+            if (src && !json_equal(src, val))
+                return false;
+
+            if (json_object_set(jwk, key, val) < 0)
+                return false;
+        }
+
+        return true;
+    }
+
+    return dflt;
+}
+
+bool
+jose_jwk_gen(jose_cfg_t *cfg, json_t *jwk)
+{
+    const json_t *ko = NULL;
+    const char *alg = NULL;
+    const char *kty = NULL;
+    const char *use = NULL;
+
+    if (!jwk_hook(cfg, jwk, JOSE_HOOK_JWK_KIND_PREP, true))
+        return false;
+
+    if (!jwk_hook(cfg, jwk, JOSE_HOOK_JWK_KIND_MAKE, false))
+        return false;
+
+    if (json_unpack(jwk, "{s?s,s:s,s?s,s?o}",
+                    "alg", &alg, "kty", &kty, "use", &use, "key_ops", &ko) < 0)
+        return false;
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list();
+         a && alg && !use && !ko; a = a->next) {
+        json_auto_t *ops = NULL;
+
+        if (strcmp(alg, a->name) != 0)
+            continue;
+
+        ops = json_array();
+        if (!ops)
+            return false;
+
+        switch (a->kind) {
+        case JOSE_HOOK_ALG_KIND_SIGN:
+            if (json_array_append_new(ops, json_string("sign")) < 0)
+                return false;
+            if (json_array_append_new(ops, json_string("verify")) < 0)
+                return false;
+            break;
+        case JOSE_HOOK_ALG_KIND_WRAP:
+            if (json_array_append_new(ops, json_string("wrapKey")) < 0)
+                return false;
+            if (json_array_append_new(ops, json_string("unwrapKey")) < 0)
+                return false;
+            break;
+        case JOSE_HOOK_ALG_KIND_ENCR:
+            if (json_array_append_new(ops, json_string("encrypt")) < 0)
+                return false;
+            if (json_array_append_new(ops, json_string("decrypt")) < 0)
+                return false;
+            break;
+        case JOSE_HOOK_ALG_KIND_EXCH:
+            if (json_array_append_new(ops, json_string("deriveKey")) < 0)
+                return false;
+            break;
+        default:
+            break;
+        }
+
+        if (json_array_size(ops) > 0 &&
+            json_object_set(jwk, "key_ops", ops) < 0)
+            return false;
+
+        break;
+    }
+
+    for (const jose_hook_jwk_t *j = jose_hook_jwk_list(); j; j = j->next) {
+        if (j->kind != JOSE_HOOK_JWK_KIND_TYPE)
+            continue;
+
+        if (strcmp(j->type.kty, kty) == 0) {
+            for (size_t i = 0; j->type.req[i]; i++) {
+                if (!json_object_get(jwk, j->type.req[i]))
+                    return false;
+            }
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool
+jwk_clean(jose_cfg_t *cfg, json_t *jwk)
+{
+    const jose_hook_jwk_t *type = NULL;
+    const char *kty = NULL;
+    bool sym = false;
+
+    if (json_unpack(jwk, "{s:s}", "kty", &kty) == -1)
+        return false;
+
+    for (type = jose_hook_jwk_list(); type; type = type->next) {
+        if (type->kind != JOSE_HOOK_JWK_KIND_TYPE)
+            continue;
+
+        if (strcasecmp(kty, type->type.kty) == 0)
+            break;
+    }
+
+    if (!type)
+        return false;
+
+    sym = !type->type.pub || !type->type.pub[0];
+
+    for (size_t i = 0; type->type.prv[i]; i++) {
+        if (!json_object_get(jwk, type->type.prv[i]))
+            continue;
+
+        if (json_object_del(jwk, type->type.prv[i]) == -1)
+            return false;
+    }
+
+    for (const jose_hook_jwk_t *o = jose_hook_jwk_list(); o; o = o->next) {
+        json_t *arr = NULL;
+
+        if (o->kind != JOSE_HOOK_JWK_KIND_OPER)
+            continue;
+
+        if (!o->oper.prv && (!sym || !o->oper.pub))
+            continue;
+
+        arr = json_object_get(jwk, "key_ops");
+        for (size_t i = 0; i < json_array_size(arr); i++) {
+            const char *ko = NULL;
+
+            ko = json_string_value(json_array_get(arr, i));
+            if (!ko)
+                continue;
+
+            if ((!o->oper.prv || strcmp(o->oper.prv, ko) != 0) &&
+                (!sym || !o->oper.pub || strcmp(o->oper.pub, ko) != 0))
+                continue;
+
+            if (json_array_remove(arr, i--) == -1)
+                return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+jose_jwk_pub(jose_cfg_t *cfg, json_t *jwk)
+{
+    json_t *keys = NULL;
+
+    if (json_is_array(jwk))
+        keys = jwk;
+    else if (json_is_array(json_object_get(jwk, "keys")))
+        keys = json_object_get(jwk, "keys");
+
+    if (!keys)
+        return jwk_clean(cfg, jwk);
+
+    for (size_t i = 0; i < json_array_size(keys); i++) {
+        if (!jwk_clean(cfg, json_array_get(keys, i)))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+jose_jwk_prm(jose_cfg_t *cfg, const json_t *jwk, bool req, const char *op)
+{
+    const char *use = NULL;
+    json_t *ko = NULL;
+
+    if (!json_is_object(jwk))
+        return true;
+
+    if (!op)
+        return false;
+
+    if (json_unpack((json_t *) jwk, "{s?s,s?o}",
+                    "use", &use, "key_ops", &ko) != 0)
+        return false;
+
+    if (!use && !ko)
+        return !req;
+
+    for (size_t i = 0; i < json_array_size(ko); i++) {
+        json_t *v = json_array_get(ko, i);
+
+        if (json_is_string(v) && strcmp(op, json_string_value(v)) == 0)
+            return true;
+    }
+
+    for (const jose_hook_jwk_t *o = jose_hook_jwk_list(); use && o; o = o->next) {
+        if (o->kind != JOSE_HOOK_JWK_KIND_OPER)
+            continue;
+
+        if (!o->oper.use || strcmp(use, o->oper.use) != 0)
+            continue;
+
+        if (o->oper.pub && strcmp(op, o->oper.pub) == 0)
+            return true;
+
+        if (o->oper.prv && strcmp(op, o->oper.prv) == 0)
+            return true;
+    }
+
+    return false;
+}
+
+static const jose_hook_jwk_t *
+find_type(const json_t *jwk)
+{
+    const char *kty = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "kty", &kty) < 0)
+        return NULL;
+
+    for (const jose_hook_jwk_t *t = jose_hook_jwk_list(); t; t = t->next) {
+        if (t->kind != JOSE_HOOK_JWK_KIND_TYPE)
+            continue;
+        if (strcasecmp(kty, t->type.kty) == 0)
+            return t;
+    }
+
+    return NULL;
+}
+
+bool
+jose_jwk_eql(jose_cfg_t *cfg, const json_t *a, const json_t *b)
+{
+    const jose_hook_jwk_t *type = NULL;
+
+    type = find_type(a);
+    if (!type)
+        return false;
+
+    if (!json_equal(json_object_get(a, "kty"), json_object_get(b, "kty")))
+        return false;
+
+    for (size_t i = 0; type->type.req[i]; i++) {
+        json_t *aa = json_object_get(a, type->type.req[i]);
+        json_t *bb = json_object_get(b, type->type.req[i]);
+
+        if (!aa || !bb || !json_equal(aa, bb))
+            return false;
+    }
+
+    return true;
+}
+
+static char *
+jwk_str(const json_t *jwk)
+{
+    const jose_hook_jwk_t *type = NULL;
+    json_auto_t *key = NULL;
+
+    type = find_type(jwk);
+    if (!type)
+        return NULL;
+
+    key = json_object();
+    if (!key)
+        return NULL;
+
+    if (json_object_set(key, "kty", json_object_get(jwk, "kty")) < 0)
+        return NULL;
+
+    for (size_t i = 0; type->type.req[i]; i++) {
+        json_t *tmp = NULL;
+
+        tmp = json_object_get(jwk, type->type.req[i]);
+        if (!tmp)
+            return NULL;
+
+        if (json_object_set(key, type->type.req[i], tmp) < 0)
+            return NULL;
+    }
+
+    return json_dumps(key, JSON_SORT_KEYS | JSON_COMPACT);
+}
+
+json_t *
+jose_jwk_thp(jose_cfg_t *cfg, const json_t *jwk, const char *hash)
+{
+    json_t *thp = NULL;
+    char *str = NULL;
+
+    str = jwk_str(jwk);
+    if (!str)
+        return NULL;
+
+    thp = hsh(cfg, hash, str, strlen(str));
+    zero(str, strlen(str));
+    free(str);
+    return thp;
+}
+
+size_t
+jose_jwk_thp_buf(jose_cfg_t *cfg, const json_t *jwk,
+                 const char *alg, uint8_t *thp, size_t len)
+{
+    char *str = NULL;
+
+    if (!thp || len == 0)
+        return hsh_buf(cfg, alg, NULL, 0, NULL, 0);
+
+    str = jwk_str(jwk);
+    if (!str)
+        return SIZE_MAX;
+
+    len = hsh_buf(cfg, alg, str, strlen(str), thp, len);
+    zero(str, strlen(str));
+    free(str);
+    return len;
+}
+
+json_t *
+jose_jwk_exc(jose_cfg_t *cfg, const json_t *prv, const json_t *pub)
+{
+    const char *alga = NULL;
+    const char *algb = NULL;
+    const char *ktya = NULL;
+    const char *ktyb = NULL;
+
+    if (json_unpack((json_t *) prv, "{s:s,s?s}",
+                    "kty", &ktya, "alg", &alga) < 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_INVALID, "Private JWK is invalid");
+        return NULL;
+    }
+
+    if (json_unpack((json_t *) pub, "{s:s,s?s}",
+                    "kty", &ktyb, "alg", &algb) < 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_INVALID, "Public JWK is invalid");
+        return NULL;
+    }
+
+    if (strcmp(ktya, ktyb) != 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
+                     "Public and private JWKs are different types");
+        return NULL;
+    }
+
+    if (alga && algb && strcmp(alga, algb) != 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
+                     "Public and private JWKs have different algorithms");
+        return NULL;
+    }
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list();
+         !alga && !algb && a; a = a->next) {
+        if (a->kind != JOSE_HOOK_ALG_KIND_EXCH)
+            continue;
+
+        alga = a->exch.sug(a, cfg, prv, pub);
+    }
+
+    if (!alga && !algb) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOINFER,
+                     "Exchange algorithm cannot be inferred");
+        return NULL;
+    }
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+        if (a->kind != JOSE_HOOK_ALG_KIND_EXCH)
+            continue;
+
+        if (strcmp(alga ? alga : algb, a->name) != 0)
+            continue;
+
+        if (!jose_jwk_prm(cfg, prv, false, a->exch.prm)) {
+            jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
+                         "Private JWK cannot be used to derive keys");
+            return NULL;
+        }
+
+        if (!jose_jwk_prm(cfg, pub, false, a->exch.prm)) {
+            jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
+                         "Public JWK cannot be used to derive keys");
+            return NULL;
+        }
+
+        return a->exch.exc(a, cfg, prv, pub);
+    }
+
+    jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOTSUP,
+                 "Exchange algorithm %s is unsupported", alga ? alga : algb);
+    return NULL;
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static const char *oct_req[] = { "k", NULL };
+    static const char *oct_prv[] = { "k", NULL };
+
+    static const char *rsa_req[] = { "e", "n", NULL };
+    static const char *rsa_pub[] = { "e", "n", NULL };
+    static const char *rsa_prv[] = { "p", "d", "q", "dp", "dq", "qi", "oth", NULL };
+
+    static const char *ec_req[] = { "crv", "x", "y", NULL };
+    static const char *ec_pub[] = { "x", "y", NULL };
+    static const char *ec_prv[] = { "d", NULL };
+
+    static jose_hook_jwk_t hooks[] = {
+        { .kind = JOSE_HOOK_JWK_KIND_TYPE,
+          .type = { .kty = "oct", .req = oct_req, .prv = oct_prv } },
+        { .kind = JOSE_HOOK_JWK_KIND_TYPE,
+          .type = { .kty = "RSA", .req = rsa_req, .pub = rsa_pub, .prv = rsa_prv } },
+        { .kind = JOSE_HOOK_JWK_KIND_TYPE,
+          .type = { .kty = "EC", .req = ec_req, .pub = ec_pub, .prv = ec_prv } },
+        { .kind = JOSE_HOOK_JWK_KIND_OPER,
+          .oper = { .pub = "verify", .prv = "sign", .use = "sig" } },
+        { .kind = JOSE_HOOK_JWK_KIND_OPER,
+          .oper = { .pub = "encrypt", .prv = "decrypt", .use = "enc" } },
+        { .kind = JOSE_HOOK_JWK_KIND_OPER,
+          .oper = { .pub = "wrapKey", .prv = "unwrapKey", .use = "enc" } },
+        { .kind = JOSE_HOOK_JWK_KIND_OPER,
+          .oper = { .pub = "deriveKey" } },
+        { .kind = JOSE_HOOK_JWK_KIND_OPER,
+          .oper = { .pub = "deriveBits" } },
+        {}
+    };
+
+    for (size_t i = 0; hooks[i].kind != JOSE_HOOK_JWK_KIND_NONE; i++)
+        jose_hook_jwk_push(&hooks[i]);
+}

+ 317 - 0
lib/jws.c

@@ -0,0 +1,317 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#define _GNU_SOURCE
+#include "misc.h"
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include <jose/jws.h>
+#include "hooks.h"
+
+#include <errno.h>
+#include <string.h>
+
+static const jose_hook_alg_t *
+find_alg(jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk)
+{
+    const jose_hook_alg_t *alg = NULL;
+    const char *halg = NULL;
+    const char *kalg = NULL;
+    json_auto_t *hdr = NULL;
+
+    hdr = jose_jws_hdr(sig);
+    if (!hdr)
+        return NULL;
+
+    if (json_unpack(hdr, "{s:s}", "alg", &halg) < 0) {
+        for (alg = jose_hook_alg_list(); alg && !halg; alg = alg->next) {
+            if (alg->kind != JOSE_HOOK_ALG_KIND_SIGN)
+                continue;
+            halg = alg->sign.sug(alg, cfg, jwk);
+        }
+
+        if (!halg) {
+            jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOINFER,
+                         "Unable to infer signing algorithm");
+            return NULL;
+        }
+
+        alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_SIGN, halg);
+        if (alg) {
+            json_t *h = NULL;
+
+            h = json_object_get(sig, "protected");
+            if (!h && json_object_set_new(sig, "protected", h = json_object()) < 0)
+                return NULL;
+
+            if (json_object_set_new(h, "alg", json_string(alg->name)) < 0)
+                return NULL;
+        }
+    } else {
+        alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_SIGN, halg);
+    }
+
+    if (!alg) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOTSUP,
+                     "Signing algorithm (%s) is not supported", halg);
+        return NULL;
+    }
+
+    if (json_unpack((json_t *) jwk, "{s?s}", "alg", &kalg) < 0)
+        return NULL;
+
+    if (halg && kalg && strcmp(halg, kalg) != 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
+                     "Algorithm mismatch (%s != %s)", halg, kalg);
+        return NULL;
+    }
+
+    if (!jose_jwk_prm(cfg, jwk, false, alg->sign.sprm)) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
+                     "JWK cannot be used to sign");
+        return NULL;
+    }
+
+    return alg;
+}
+
+static void
+ios_auto(jose_io_t ***iosp)
+{
+    jose_io_t **ios = *iosp;
+
+    for (size_t i = 0; ios && ios[i]; i++)
+        jose_io_auto(&ios[i]);
+
+    free(ios);
+}
+
+json_t *
+jose_jws_hdr(const json_t *sig)
+{
+    json_auto_t *p = NULL;
+    json_t *h = NULL;
+
+    p = json_object_get(sig, "protected");
+    if (!p)
+        p = json_object();
+    else if (json_is_object(p))
+        p = json_deep_copy(p);
+    else if (json_is_string(p))
+        p = jose_b64_dec_load(p);
+
+    if (!json_is_object(p))
+        return NULL;
+
+    h = json_object_get(sig, "header");
+    if (h) {
+        if (json_object_update_missing(p, h) == -1)
+            return NULL;
+    }
+
+    return json_incref(p);
+}
+
+bool
+jose_jws_sig(jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk)
+{
+    jose_io_auto_t *io = NULL;
+    const char *pay = NULL;
+    size_t payl = 0;
+
+    if (json_unpack(jws, "{s:s%}", "payload", &pay, &payl) < 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWS_INVALID,
+                     "JWS missing payload attribute");
+        return false;
+    }
+
+    io = jose_jws_sig_io(cfg, jws, sig, jwk);
+    return io && io->feed(io, pay, payl) && io->done(io);
+}
+
+jose_io_t *
+jose_jws_sig_io(jose_cfg_t *cfg, json_t *jws, json_t *sig, const json_t *jwk)
+{
+    const jose_hook_alg_t *alg = NULL;
+    json_auto_t *s = NULL;
+
+    if (json_is_array(jwk) || json_is_array(json_object_get(jwk, "keys"))) {
+        jose_io_t __attribute__((cleanup(ios_auto))) **ios = NULL;
+        const json_t *key = NULL;
+        size_t i = 0;
+
+        if (!json_is_array(jwk))
+            jwk = json_object_get(jwk, "keys");
+
+        if (json_is_array(sig) && json_array_size(sig) != json_array_size(jwk))
+            return NULL;
+
+        ios = calloc(json_array_size(jwk) + 1, sizeof(*ios));
+        if (!ios)
+            return NULL;
+
+        json_array_foreach(jwk, i, key) {
+            json_auto_t *tmp = NULL;
+
+            if (json_is_array(sig))
+                tmp = json_incref(json_array_get(sig, i));
+            else
+                tmp = json_deep_copy(sig);
+
+            ios[i] = jose_jws_sig_io(cfg, jws, tmp, key);
+            if (!ios[i])
+                return NULL;
+        }
+
+        return jose_io_multiplex(cfg, ios, true);
+    }
+
+    s = sig ? json_incref(sig) : json_object();
+    if (!json_is_object(s)) {
+        jose_cfg_err(cfg, EINVAL, "Parameter sig MUST be an object or NULL");
+        return NULL;
+    }
+
+    alg = find_alg(cfg, jws, s, jwk);
+    if (!alg)
+        return NULL;
+
+    if (!encode_protected(s))
+        return NULL;
+
+    return alg->sign.sig(alg, cfg, jws, s, jwk);
+}
+
+bool
+jose_jws_ver(jose_cfg_t *cfg, const json_t *jws, const json_t *sig,
+             const json_t *jwk, bool all)
+{
+    jose_io_auto_t *io = NULL;
+    const char *pay = NULL;
+    size_t payl = 0;
+
+    if (json_unpack((json_t *) jws, "{s:s%}", "payload", &pay, &payl) < 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWS_INVALID,
+                     "JWS missing payload attribute");
+        return false;
+    }
+
+    io = jose_jws_ver_io(cfg, jws, sig, jwk, all);
+    return io && io->feed(io, pay, payl) && io->done(io);
+}
+
+jose_io_t *
+jose_jws_ver_io(jose_cfg_t *cfg, const json_t *jws, const json_t *sig,
+                const json_t *jwk, bool all)
+{
+    const jose_hook_alg_t *alg = NULL;
+    const char *kalg = NULL;
+    const char *halg = NULL;
+    json_auto_t *hdr = NULL;
+
+    if (json_is_array(jwk) || json_is_array(json_object_get(jwk, "keys"))) {
+        jose_io_t __attribute__((cleanup(ios_auto))) **ios = NULL;
+        size_t j = 0;
+
+        if (!json_is_array(jwk))
+            jwk = json_object_get(jwk, "keys");
+
+        if (json_is_array(sig) && json_array_size(sig) != json_array_size(jwk))
+            return NULL;
+
+        ios = calloc(json_array_size(jwk) + 1, sizeof(*ios));
+        if (!ios)
+            return NULL;
+
+        for (size_t i = 0; i < json_array_size(jwk); i++) {
+            const json_t *s = json_is_object(sig) ? sig : json_array_get(sig, i);
+            const json_t *k = json_array_get(jwk, i);
+            ios[j] = jose_jws_ver_io(cfg, jws, s, k, false);
+            if (ios[j])
+                j++;
+            else if (all)
+                return NULL;
+        }
+
+        return jose_io_multiplex(cfg, ios, all);
+    }
+
+    if (!sig) {
+        jose_io_t __attribute__((cleanup(ios_auto))) **ios = NULL;
+        const json_t *array = NULL;
+        const json_t *s = NULL;
+        size_t i = 0;
+        size_t j = 0;
+
+        array = json_object_get(jws, "signatures");
+        if (!json_is_array(array))
+            return jose_jws_ver_io(cfg, jws, jws, jwk, true);
+
+        ios = calloc(json_array_size(array) + 1, sizeof(*ios));
+        if (!ios)
+            return NULL;
+
+        json_array_foreach(array, i, s) {
+            ios[j] = jose_jws_ver_io(cfg, jws, s, jwk, true);
+            if (ios[j])
+                j++;
+        }
+
+        return jose_io_multiplex(cfg, ios, false);
+    } else if (!json_is_object(sig))
+        return NULL;
+
+    if (json_unpack((json_t *) jwk, "{s?s}", "alg", &kalg) < 0)
+        return NULL;
+
+    hdr = jose_jws_hdr(sig);
+    if (!hdr)
+        return NULL;
+
+    if (json_unpack(hdr, "{s?s}", "alg", &halg) < 0)
+        return NULL;
+
+    if (!halg) {
+        if (!kalg) {
+            jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOINFER,
+                         "Signature algorithm cannot be inferred");
+            return NULL;
+        }
+
+        halg = kalg;
+    } else if (kalg && strcmp(halg, kalg) < 0) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_MISMATCH,
+                     "Signing algorithm mismatch (%s != %s)", halg, kalg);
+        return NULL;
+    }
+
+    alg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_SIGN, halg);
+    if (!alg) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_ALG_NOTSUP,
+                     "Signing algorithm (%s) is not supported", halg);
+        return NULL;
+    }
+
+    if (!jose_jwk_prm(cfg, jwk, false, alg->sign.vprm)) {
+        jose_cfg_err(cfg, JOSE_CFG_ERR_JWK_DENIED,
+                     "JWK cannot be used to verify");
+        return false;
+    }
+
+    return alg->sign.ver(alg, cfg, jws, sig, jwk);
+}

+ 71 - 0
lib/libjose.map

@@ -0,0 +1,71 @@
+LIBJOSE_1.0 {
+	global:
+		jose_b64_dec;
+		jose_b64_dec_buf;
+		jose_b64_dec_io;
+		jose_b64_dec_load;
+		jose_b64_enc;
+		jose_b64_enc_buf;
+		jose_b64_enc_dump;
+		jose_b64_enc_io;
+		jose_cfg;
+		jose_cfg_auto;
+		jose_cfg_decref;
+		jose_cfg_err;
+		jose_cfg_get_err_misc;
+		jose_cfg_incref;
+		jose_cfg_set_err_func;
+		jose_hook_alg_find;
+		jose_hook_alg_list;
+		jose_hook_alg_push;
+		jose_hook_jwk_list;
+		jose_hook_jwk_push;
+		jose_io_auto;
+		jose_io_buffer;
+		jose_io_decref;
+		jose_io_file;
+		jose_io_incref;
+		jose_io_malloc;
+		jose_io_malloc_steal;
+		jose_io_multiplex;
+		jose_jwe_dec;
+		jose_jwe_dec_cek;
+		jose_jwe_dec_cek_io;
+		jose_jwe_dec_io;
+		jose_jwe_dec_jwk;
+		jose_jwe_enc;
+		jose_jwe_enc_cek;
+		jose_jwe_enc_cek_io;
+		jose_jwe_enc_io;
+		jose_jwe_enc_jwk;
+		jose_jwe_hdr;
+		jose_jwk_eql;
+		jose_jwk_exc;
+		jose_jwk_gen;
+		jose_jwk_prm;
+		jose_jwk_pub;
+		jose_jwk_thp;
+		jose_jwk_thp_buf;
+		jose_jws_hdr;
+		jose_jws_sig;
+		jose_jws_sig_io;
+		jose_jws_ver;
+		jose_jws_ver_io;
+
+	local:
+		*;
+};
+
+LIBJOSE_OPENSSL_1.0 {
+	global:
+		jose_openssl_jwk_from_EC_KEY;
+		jose_openssl_jwk_from_EC_POINT;
+		jose_openssl_jwk_from_EVP_PKEY;
+		jose_openssl_jwk_from_RSA;
+		jose_openssl_jwk_to_EC_KEY;
+		jose_openssl_jwk_to_EVP_PKEY;
+		jose_openssl_jwk_to_RSA;
+
+	local:
+		*;
+};

+ 49 - 0
lib/misc.c

@@ -0,0 +1,49 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include <jose/b64.h>
+#include <string.h>
+
+bool
+encode_protected(json_t *obj)
+{
+    json_t *p = NULL;
+
+    if (json_unpack(obj, "{s?o}", "protected", &p) == -1)
+        return false;
+
+    if (!p || json_is_string(p))
+        return true;
+
+    if (!json_is_object(p))
+        return false;
+
+    return json_object_set_new(obj, "protected", jose_b64_enc_dump(p)) == 0;
+}
+
+void
+zero(void *mem, size_t len)
+{
+    memset(mem, 0, len);
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    json_object_seed(0);
+}

+ 32 - 0
lib/misc.h

@@ -0,0 +1,32 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <jansson.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define containerof(ptr, type, member) \
+    ((type *)((char *) ptr - offsetof(type, member)))
+
+bool
+encode_protected(json_t *obj);
+
+void
+zero(void *mem, size_t len);

+ 443 - 0
lib/openssl/aescbch.c

@@ -0,0 +1,443 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include <jose/b64.h>
+#include "../hooks.h"
+
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
+#include <string.h>
+
+#define NAMES "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"
+
+typedef struct {
+    jose_io_t io;
+
+    EVP_CIPHER_CTX *cctx;
+    jose_io_t *next;
+    HMAC_CTX *hctx;
+    json_t *json;
+    uint64_t al;
+} io_t;
+
+static uint64_t
+h2be64(uint64_t x)
+{
+    union swap {
+        uint64_t i;
+        uint8_t  b[8];
+    } y;
+
+    y.b[0] = x >> 0x38;
+    y.b[1] = x >> 0x30;
+    y.b[2] = x >> 0x28;
+    y.b[3] = x >> 0x20;
+    y.b[4] = x >> 0x18;
+    y.b[5] = x >> 0x10;
+    y.b[6] = x >> 0x08;
+    y.b[7] = x >> 0x00;
+
+    return y.i;
+}
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return str2enum(alg, NAMES, NULL) != SIZE_MAX;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+    json_int_t len = 0;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return NULL;
+
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: len = 32; break;
+    case 1: len = 48; break;
+    case 2: len = 64; break;
+    default: return NULL;
+    }
+
+    return json_pack("{s{s:s,s:I}}", "upd", "kty", "oct", "bytes", len);
+}
+
+static const char *
+alg_encr_sug(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *cek)
+{
+    const char *name = NULL;
+    const char *type = NULL;
+    size_t len = 0;
+
+    if (json_unpack((json_t *) cek, "{s?s,s?s}",
+                    "alg", &name, "kty", &type) < 0)
+        return NULL;
+
+    if (name)
+        return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
+
+    if (!type || strcmp(type, "oct") != 0)
+        return NULL;
+
+    len = jose_b64_dec(json_object_get(cek, "k"), NULL, 0);
+
+    if (len >= SHA512_DIGEST_LENGTH)
+        return "A256CBC-HS512";
+    else if (len >= SHA384_DIGEST_LENGTH)
+        return "A192CBC-HS384";
+    else if (len >= SHA256_DIGEST_LENGTH)
+        return "A128CBC-HS256";
+
+    return NULL;
+}
+
+static void
+io_free(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    EVP_CIPHER_CTX_free(i->cctx);
+    jose_io_decref(i->next);
+    HMAC_CTX_free(i->hctx);
+    json_decref(i->json);
+    free(i);
+}
+
+static bool
+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;
+
+    for (size_t j = 0; j < len; j++) {
+        int l = 0;
+
+        if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0)
+            return false;
+
+        if (!i->next->feed(i->next, ct, l))
+            return false;
+
+        if (HMAC_Update(i->hctx, ct, l) <= 0)
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+enc_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+    uint8_t tg[EVP_MD_size(HMAC_CTX_get_md(i->hctx))];
+    int l = 0;
+
+    if (EVP_EncryptFinal(i->cctx, ct, &l) <= 0)
+        return false;
+
+    if (!i->next->feed(i->next, ct, l) || !i->next->done(i->next))
+        return false;
+
+    if (HMAC_Update(i->hctx, ct, l) <= 0)
+        return false;
+
+    if (HMAC_Update(i->hctx, (void *) &i->al, sizeof(i->al)) <= 0)
+        return false;
+
+    if (HMAC_Final(i->hctx, tg, NULL) <= 0)
+        return false;
+
+    if (json_object_set_new(i->json, "tag",
+                            jose_b64_enc(tg, sizeof(tg) / 2)) < 0)
+        return false;
+
+    return true;
+}
+
+static bool
+dec_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t pt[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+    const uint8_t *ct = in;
+    bool ret = false;
+    int l = 0;
+
+    if (HMAC_Update(i->hctx, in, len) <= 0)
+        return false;
+
+    for (size_t j = 0; j < len; j++) {
+        if (EVP_DecryptUpdate(i->cctx, pt, &l, &ct[j], 1) <= 0)
+            goto egress;
+
+        if (!i->next->feed(i->next, pt, l))
+            goto egress;
+    }
+
+    ret = true;
+
+egress:
+    OPENSSL_cleanse(pt, sizeof(pt));
+    return ret;
+}
+
+static bool
+dec_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t pt[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+    uint8_t tg[EVP_MD_size(HMAC_CTX_get_md(i->hctx))];
+    uint8_t bf[sizeof(tg) / 2];
+    json_t *tag = NULL;
+    int l = 0;
+
+    tag = json_object_get(i->json, "tag");
+    if (!tag)
+        return false;
+
+    if (jose_b64_dec(tag, NULL, 0) != sizeof(bf))
+        return false;
+
+    if (jose_b64_dec(tag, bf, sizeof(bf)) != sizeof(bf))
+        return false;
+
+    if (HMAC_Update(i->hctx, (void *) &i->al, sizeof(i->al)) <= 0)
+        return false;
+
+    if (HMAC_Final(i->hctx, tg, NULL) <= 0)
+        return false;
+
+    if (CRYPTO_memcmp(tg, bf, sizeof(bf)) != 0)
+        return false;
+
+    if (EVP_DecryptFinal(i->cctx, pt, &l) <= 0)
+        return false;
+
+    if (!i->next->feed(i->next, pt, l) || !i->next->done(i->next)) {
+        OPENSSL_cleanse(pt, sizeof(pt));
+        return false;
+    }
+
+    OPENSSL_cleanse(pt, sizeof(pt));
+    return true;
+}
+
+static bool
+setup(const EVP_CIPHER *cph, const EVP_MD *md, jose_cfg_t *cfg,
+      const json_t *jwe, const json_t *cek, uint8_t *iv,
+      typeof(EVP_EncryptInit) func, io_t *i)
+{
+    uint8_t key[EVP_CIPHER_key_length(cph) * 2];
+    const char *aad = NULL;
+    const char *prt = "";
+
+    if (jose_b64_dec(json_object_get(cek, "k"), NULL, 0) != sizeof(key))
+        return false;
+
+    if (json_unpack((json_t *) jwe, "{s?s,s?s}",
+                    "aad", &aad, "protected", &prt) < 0)
+        return false;
+
+    i->cctx = EVP_CIPHER_CTX_new();
+    if (!i->cctx)
+        return false;
+
+    i->hctx = HMAC_CTX_new();
+    if (!i->hctx)
+        return false;
+
+    if (jose_b64_dec(json_object_get(cek, "k"), NULL, 0) != sizeof(key))
+        return false;
+
+    if (jose_b64_dec(json_object_get(cek, "k"), key,
+                     sizeof(key)) != sizeof(key)) {
+        OPENSSL_cleanse(key, sizeof(key));
+        return false;
+    }
+
+    if (HMAC_Init_ex(i->hctx, key, sizeof(key) / 2, md, NULL) <= 0) {
+        OPENSSL_cleanse(key, sizeof(key));
+        return false;
+    }
+
+    if (func(i->cctx, cph, &key[sizeof(key) / 2], iv) <= 0) {
+        OPENSSL_cleanse(key, sizeof(key));
+        return false;
+    }
+
+    OPENSSL_cleanse(key, sizeof(key));
+
+    i->al += strlen(prt);
+    if (HMAC_Update(i->hctx, (void *) prt, strlen(prt)) <= 0)
+        return false;
+
+    if (aad) {
+        i->al += 1;
+        if (HMAC_Update(i->hctx, (void *) ".", 1) <= 0)
+            return false;
+
+        i->al += strlen(aad);
+        if (HMAC_Update(i->hctx, (void *) aad, strlen(aad)) <= 0)
+            return false;
+    }
+
+    i->al = h2be64(i->al * 8);
+
+    if (HMAC_Update(i->hctx, iv, EVP_CIPHER_iv_length(cph)) <= 0)
+        return false;
+
+    return true;
+}
+
+static jose_io_t *
+alg_encr_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+             const json_t *cek, jose_io_t *next)
+{
+    const EVP_CIPHER *cph = NULL;
+    jose_io_auto_t *io = NULL;
+    const EVP_MD *md = NULL;
+    io_t *i = NULL;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: cph = EVP_aes_128_cbc(); md = EVP_sha256(); break;
+    case 1: cph = EVP_aes_192_cbc(); md = EVP_sha384(); break;
+    case 2: cph = EVP_aes_256_cbc(); md = EVP_sha512(); break;
+    default: return NULL;
+    }
+
+    uint8_t iv[EVP_CIPHER_iv_length(cph)];
+
+    if (RAND_bytes(iv, sizeof(iv)) <= 0)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = enc_feed;
+    io->done = enc_done;
+    io->free = io_free;
+
+    i->json = json_incref(jwe);
+    i->next = jose_io_incref(next);
+    if (!i->json || !i->next)
+        return NULL;
+
+    if (!setup(cph, md, cfg, jwe, cek, iv, EVP_EncryptInit, i))
+        return NULL;
+
+    if (json_object_set_new(jwe, "iv", jose_b64_enc(iv, sizeof(iv))) < 0)
+        return NULL;
+
+    return jose_io_incref(io);
+}
+
+static jose_io_t *
+alg_encr_dec(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
+             const json_t *cek, jose_io_t *next)
+{
+    const EVP_CIPHER *cph = NULL;
+    jose_io_auto_t *io = NULL;
+    const EVP_MD *md = NULL;
+    io_t *i = NULL;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: cph = EVP_aes_128_cbc(); md = EVP_sha256(); break;
+    case 1: cph = EVP_aes_192_cbc(); md = EVP_sha384(); break;
+    case 2: cph = EVP_aes_256_cbc(); md = EVP_sha512(); break;
+    default: return NULL;
+    }
+
+    uint8_t iv[EVP_CIPHER_iv_length(cph)];
+
+    if (jose_b64_dec(json_object_get(jwe, "iv"), NULL, 0) != sizeof(iv))
+        return NULL;
+
+    if (jose_b64_dec(json_object_get(jwe, "iv"), iv, sizeof(iv)) != sizeof(iv))
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = dec_feed;
+    io->done = dec_done;
+    io->free = io_free;
+
+    i->json = json_incref((json_t *) jwe);
+    i->next = jose_io_incref(next);
+    if (!i->json || !i->next)
+        return NULL;
+
+    if (!setup(cph, md, cfg, jwe, cek, iv, EVP_DecryptInit, i))
+        return NULL;
+
+    return jose_io_incref(io);
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute,
+    };
+
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_ENCR,
+          .name = "A128CBC-HS256",
+          .encr.eprm = "encrypt",
+          .encr.dprm = "decrypt",
+          .encr.sug = alg_encr_sug,
+          .encr.enc = alg_encr_enc,
+          .encr.dec = alg_encr_dec },
+        { .kind = JOSE_HOOK_ALG_KIND_ENCR,
+          .name = "A192CBC-HS384",
+          .encr.eprm = "encrypt",
+          .encr.dprm = "decrypt",
+          .encr.sug = alg_encr_sug,
+          .encr.enc = alg_encr_enc,
+          .encr.dec = alg_encr_dec },
+        { .kind = JOSE_HOOK_ALG_KIND_ENCR,
+          .name = "A256CBC-HS512",
+          .encr.eprm = "encrypt",
+          .encr.dprm = "decrypt",
+          .encr.sug = alg_encr_sug,
+          .encr.enc = alg_encr_enc,
+          .encr.dec = alg_encr_dec },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 380 - 0
lib/openssl/aesgcm.c

@@ -0,0 +1,380 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include <jose/b64.h>
+#include "../hooks.h"
+
+#include <openssl/rand.h>
+
+#include <string.h>
+
+#define NAMES "A128GCM", "A192GCM", "A256GCM"
+
+typedef struct {
+    jose_io_t io;
+
+    EVP_CIPHER_CTX *cctx;
+    jose_io_t *next;
+    json_t *json;
+} io_t;
+
+static EVP_CIPHER_CTX *
+setup(const EVP_CIPHER *cph, jose_cfg_t *cfg, const json_t *jwe,
+      const json_t *cek, const uint8_t iv[],
+      typeof(EVP_EncryptInit_ex) *init, typeof(EVP_EncryptUpdate) *push)
+{
+    uint8_t key[EVP_CIPHER_key_length(cph)];
+    EVP_CIPHER_CTX *ecc = NULL;
+    const char *aad = NULL;
+    const char *prt = NULL;
+    size_t aadl = 0;
+    size_t prtl = 0;
+    int tmp;
+
+    if (json_unpack((json_t *) jwe, "{s?s%,s?s%}",
+                    "aad", &aad, &aadl, "protected", &prt, &prtl) < 0)
+        goto error;
+
+    ecc = EVP_CIPHER_CTX_new();
+    if (!ecc)
+        return NULL;
+
+    if (init(ecc, cph, NULL, NULL, NULL) <= 0)
+        goto error;
+
+    if (jose_b64_dec(json_object_get(cek, "k"), NULL, 0) != sizeof(key))
+        goto error;
+
+    if (jose_b64_dec(json_object_get(cek, "k"), key,
+                     sizeof(key)) != sizeof(key)) {
+        OPENSSL_cleanse(key, sizeof(key));
+        goto error;
+    }
+
+    tmp = init(ecc, NULL, NULL, key, iv);
+    OPENSSL_cleanse(key, sizeof(key));
+    if (tmp <= 0)
+        goto error;
+
+    if (prt && push(ecc, NULL, &tmp, (uint8_t *) prt, prtl) <= 0)
+        goto error;
+
+    if (aad) {
+        if (push(ecc, NULL, &tmp, (uint8_t *) ".", 1) <= 0)
+            goto error;
+
+        if (push(ecc, NULL, &tmp, (uint8_t *) aad, prtl) <= 0)
+            goto error;
+    }
+
+    return ecc;
+
+error:
+    EVP_CIPHER_CTX_free(ecc);
+    return NULL;
+}
+
+static void
+io_free(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    EVP_CIPHER_CTX_free(i->cctx);
+    jose_io_decref(i->next);
+    json_decref(i->json);
+    free(i);
+}
+
+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 ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+
+        if (EVP_EncryptUpdate(i->cctx, ct, &l, &pt[j], 1) <= 0)
+            return false;
+
+        if (!i->next->feed(i->next, ct, l))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+enc_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t ct[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+    uint8_t tg[EVP_GCM_TLS_TAG_LEN] = {};
+    int l = 0;
+
+    if (EVP_EncryptFinal(i->cctx, ct, &l) <= 0)
+        return false;
+
+    if (!i->next->feed(i->next, ct, l) || !i->next->done(i->next))
+        return false;
+
+    if (EVP_CIPHER_CTX_ctrl(i->cctx, EVP_CTRL_GCM_GET_TAG, sizeof(tg), tg) <= 0)
+        return false;
+
+    if (json_object_set_new(i->json, "tag",
+                            jose_b64_enc(tg, sizeof(tg))) < 0)
+        return false;
+
+    return true;
+}
+
+static bool
+dec_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t pt[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+    const uint8_t *ct = in;
+    bool ret = false;
+    int l = 0;
+
+    for (size_t j = 0; j < len; j++) {
+        if (EVP_DecryptUpdate(i->cctx, pt, &l, &ct[j], 1) <= 0)
+            goto egress;
+
+        if (i->next->feed(i->next, pt, l) != (size_t) l)
+            goto egress;
+    }
+
+    ret = true;
+
+egress:
+    OPENSSL_cleanse(pt, sizeof(pt));
+    return ret;
+}
+
+static bool
+dec_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t pt[EVP_CIPHER_CTX_block_size(i->cctx) + 1];
+    uint8_t tg[EVP_GCM_TLS_TAG_LEN] = {};
+    json_t *tag = NULL;
+    int l = 0;
+
+    tag = json_object_get(i->json, "tag");
+    if (!tag)
+        return false;
+
+    if (jose_b64_dec(tag, NULL, 0) != sizeof(tg))
+        return false;
+
+    if (jose_b64_dec(tag, tg, sizeof(tg)) != sizeof(tg))
+        return false;
+
+    if (EVP_CIPHER_CTX_ctrl(i->cctx, EVP_CTRL_GCM_SET_TAG,
+                            sizeof(tg), tg) <= 0)
+        return false;
+
+    if (EVP_DecryptFinal(i->cctx, pt, &l) <= 0)
+        return false;
+
+    if (!i->next->feed(i->next, pt, l) || !i->next->done(i->next)) {
+        OPENSSL_cleanse(pt, sizeof(pt));
+        return false;
+    }
+
+    OPENSSL_cleanse(pt, sizeof(pt));
+    return true;
+}
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return str2enum(alg, NAMES, NULL) != SIZE_MAX;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+    json_int_t len = 0;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return NULL;
+
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: len = 16; break;
+    case 1: len = 24; break;
+    case 2: len = 32; break;
+    default: return NULL;
+    }
+
+    return json_pack("{s:{s:s,s:I}}", "upd", "kty", "oct", "bytes", len);
+}
+
+static const char *
+alg_encr_sug(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *cek)
+{
+    const char *name = NULL;
+    const char *type = NULL;
+
+    if (json_unpack((json_t *) cek, "{s?s,s?s}",
+                    "alg", &name, "kty", &type) < 0)
+        return NULL;
+
+    if (name)
+        return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
+
+    if (!type || strcmp(type, "oct") != 0)
+        return NULL;
+
+    switch (jose_b64_dec(json_object_get(cek, "k"), NULL, 0)) {
+    case 16: return "A128GCM";
+    case 24: return "A192GCM";
+    case 32: return "A256GCM";
+    default: return NULL;
+    }
+}
+
+static jose_io_t *
+alg_encr_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+             const json_t *cek, jose_io_t *next)
+{
+    const EVP_CIPHER *cph = NULL;
+    jose_io_auto_t *io = NULL;
+    io_t *i = NULL;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: cph = EVP_aes_128_gcm(); break;
+    case 1: cph = EVP_aes_192_gcm(); break;
+    case 2: cph = EVP_aes_256_gcm(); break;
+    default: return NULL;
+    }
+
+    uint8_t iv[EVP_CIPHER_iv_length(cph)];
+
+    if (RAND_bytes(iv, sizeof(iv)) <= 0)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = enc_feed;
+    io->done = enc_done;
+    io->free = io_free;
+
+    i->json = json_incref(jwe);
+    i->next = jose_io_incref(next);
+    i->cctx = setup(cph, cfg, jwe, cek, iv,
+                      EVP_EncryptInit_ex, EVP_EncryptUpdate);
+    if (!i->json || !i->next || !i->cctx)
+        return NULL;
+
+    if (json_object_set_new(jwe, "iv", jose_b64_enc(iv, sizeof(iv))) < 0)
+        return NULL;
+
+    return jose_io_incref(io);
+}
+
+static jose_io_t *
+alg_encr_dec(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
+             const json_t *cek, jose_io_t *next)
+{
+    const EVP_CIPHER *cph = NULL;
+    jose_io_auto_t *io = NULL;
+    io_t *i = NULL;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: cph = EVP_aes_128_gcm(); break;
+    case 1: cph = EVP_aes_192_gcm(); break;
+    case 2: cph = EVP_aes_256_gcm(); break;
+    default: return NULL;
+    }
+
+    uint8_t iv[EVP_CIPHER_iv_length(cph)];
+
+    if (jose_b64_dec(json_object_get(jwe, "iv"), NULL, 0) != sizeof(iv))
+        return NULL;
+
+    if (jose_b64_dec(json_object_get(jwe, "iv"), iv, sizeof(iv)) != sizeof(iv))
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = dec_feed;
+    io->done = dec_done;
+    io->free = io_free;
+
+    i->json = json_incref((json_t *) jwe);
+    i->next = jose_io_incref(next);
+    i->cctx = setup(cph, cfg, jwe, cek, iv,
+                      EVP_DecryptInit_ex, EVP_DecryptUpdate);
+    if (!i->json || !i->next || !i->cctx)
+        return NULL;
+
+    return jose_io_incref(io);
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute,
+    };
+
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_ENCR,
+          .name = "A128GCM",
+          .encr.eprm = "encrypt",
+          .encr.dprm = "decrypt",
+          .encr.sug = alg_encr_sug,
+          .encr.enc = alg_encr_enc,
+          .encr.dec = alg_encr_dec },
+        { .kind = JOSE_HOOK_ALG_KIND_ENCR,
+          .name = "A192GCM",
+          .encr.eprm = "encrypt",
+          .encr.dprm = "decrypt",
+          .encr.sug = alg_encr_sug,
+          .encr.enc = alg_encr_enc,
+          .encr.dec = alg_encr_dec },
+        { .kind = JOSE_HOOK_ALG_KIND_ENCR,
+          .name = "A256GCM",
+          .encr.eprm = "encrypt",
+          .encr.dprm = "decrypt",
+          .encr.sug = alg_encr_sug,
+          .encr.enc = alg_encr_enc,
+          .encr.dec = alg_encr_dec },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 276 - 0
lib/openssl/aesgcmkw.c

@@ -0,0 +1,276 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include "../hooks.h"
+#include <string.h>
+
+#define NAMES "A128GCMKW", "A192GCMKW", "A256GCMKW"
+
+static inline const char *
+kw2enc(const jose_hook_alg_t *alg)
+{
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: return "A128GCM";
+    case 1: return "A192GCM";
+    case 2: return "A256GCM";
+    default: return NULL;
+    }
+}
+
+static inline const jose_hook_alg_t *
+kw2alg(const jose_hook_alg_t *alg)
+{
+    const char *enc = NULL;
+
+    enc = kw2enc(alg);
+    if (!enc)
+        return NULL;
+
+    return jose_hook_alg_find(JOSE_HOOK_ALG_KIND_ENCR, enc);
+}
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return str2enum(alg, NAMES, NULL) != SIZE_MAX;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+    json_int_t len = 0;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return NULL;
+
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: len = 16; break;
+    case 1: len = 24; break;
+    case 2: len = 32; break;
+    default: return NULL;
+    }
+
+    return json_pack("{s:{s:s,s:I}}", "upd", "kty", "oct", "bytes", len);
+}
+
+static const char *
+alg_wrap_alg(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *name = NULL;
+    const char *type = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s?s,s?s}",
+                    "alg", &name, "kty", &type) < 0)
+        return NULL;
+
+    if (name)
+        return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
+
+    if (!type || strcmp(type, "oct") != 0)
+        return NULL;
+
+    switch (jose_b64_dec(json_object_get(jwk, "k"), NULL, 0)) {
+    case 16: return "A128GCMKW";
+    case 24: return "A192GCMKW";
+    case 32: return "A256GCMKW";
+    default: break;
+    }
+
+    return NULL;
+}
+
+static const char *
+alg_wrap_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    return kw2enc(alg);
+}
+
+static bool
+alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+             json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    const jose_hook_alg_t *enc = NULL;
+    jose_io_auto_t *e = NULL;
+    jose_io_auto_t *d = NULL;
+    jose_io_auto_t *c = NULL;
+    jose_io_auto_t *p = NULL;
+    json_auto_t *tmp = NULL;
+    const char *k = NULL;
+    json_t *h = NULL;
+    void *ct = NULL;
+    void *pt = NULL;
+    size_t ptl = 0;
+    size_t ctl = 0;
+    size_t kl = 0;
+
+    if (!json_object_get(cek, "k") && !jose_jwk_gen(cfg, cek))
+        return false;
+
+    /* Obtain the plaintext to wrap. */
+    if (json_unpack(cek, "{s:s%}", "k", &k, &kl) < 0)
+        return false;
+
+    p = jose_io_malloc(cfg, &pt, &ptl);
+    if (!p)
+        return false;
+
+    d = jose_b64_dec_io(p);
+    if (!d || !d->feed(d, k, kl) || !d->done(d))
+        return false;
+
+    /* Perform the wrapping. */
+    enc = kw2alg(alg);
+    if (!enc)
+        return false;
+
+    tmp = json_object();
+    if (!tmp)
+        return false;
+
+    c = jose_io_malloc(cfg, &ct, &ctl);
+    if (!c)
+        return false;
+
+    e = enc->encr.enc(enc, cfg, tmp, jwk, c);
+    if (!e || !e->feed(e, pt, ptl) || !e->done(e))
+        return false;
+
+    /* Save the output. */
+    h = json_object_get(rcp, "header");
+    if (!h) {
+        if (json_object_set_new(rcp, "header", h = json_object()) < 0)
+            return false;
+    }
+    if (!json_is_object(h))
+        return false;
+
+    if (json_object_update(h, tmp) < 0)
+        return false;
+
+    if (json_object_set_new(rcp, "encrypted_key", jose_b64_enc(ct, ctl)) < 0)
+        return false;
+
+    return add_entity(jwe, rcp, "recipients", "header", "encrypted_key", NULL);
+}
+
+static bool
+alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
+             const json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    const jose_hook_alg_t *enc = NULL;
+    jose_io_auto_t *c = NULL;
+    jose_io_auto_t *d = NULL;
+    jose_io_auto_t *p = NULL;
+    json_auto_t *hdr = NULL;
+    json_auto_t *tmp = NULL;
+    const char *ct = NULL;
+    void *pt = NULL;
+    size_t ptl = 0;
+    size_t ctl = 0;
+
+    /* Prepare synthetic JWE */
+    hdr = jose_jwe_hdr(jwe, rcp);
+    if (!hdr)
+        return false;
+
+    tmp = json_object();
+    if (!tmp)
+        return false;
+
+    if (json_object_set(tmp, "iv", json_object_get(hdr, "iv")) < 0)
+        return false;
+
+    if (json_object_set(tmp, "tag", json_object_get(hdr, "tag")) < 0)
+        return false;
+
+    /* Perform the unwrap. */
+    if (json_unpack((json_t *) rcp, "{s:s%}", "encrypted_key", &ct, &ctl) < 0)
+        return false;
+
+    enc = kw2alg(alg);
+    if (!enc)
+        return false;
+
+    p = jose_io_malloc(cfg, &pt, &ptl);
+    if (!p)
+        return false;
+
+    c = enc->encr.dec(enc, cfg, tmp, jwk, p);
+    if (!c)
+        return false;
+
+    d = jose_b64_dec_io(c);
+    if (!d || !d->feed(d, ct, ctl) || !d->done(d))
+        return false;
+
+    /* Set the output value */
+    if (json_object_set_new(cek, "k", jose_b64_enc(pt, ptl)) < 0)
+        return false;
+
+    return true;
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute,
+    };
+
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "A128GCMKW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "A192GCMKW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "A256GCMKW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 268 - 0
lib/openssl/aeskw.c

@@ -0,0 +1,268 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include "../hooks.h"
+
+#include <openssl/rand.h>
+
+#include <string.h>
+
+#define NAMES "A128KW", "A192KW", "A256KW"
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return str2enum(alg, NAMES, NULL) != SIZE_MAX;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+    json_int_t len = 0;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) < 0)
+        return NULL;
+
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: len = 16; break;
+    case 1: len = 24; break;
+    case 2: len = 32; break;
+    default: return NULL;
+    }
+
+    return json_pack("{s:{s:s,s:I}}", "upd", "kty", "oct", "bytes", len);
+}
+
+static const char *
+alg_wrap_alg(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *name = NULL;
+    const char *type = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s?s,s?s}",
+                    "alg", &name, "kty", &type) < 0)
+        return NULL;
+
+    if (name)
+        return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
+
+    if (!type || strcmp(type, "oct") != 0)
+        return NULL;
+
+    switch (jose_b64_dec(json_object_get(jwk, "k"), NULL, 0)) {
+    case 16: return "A128KW";
+    case 24: return "A192KW";
+    case 32: return "A256KW";
+    default: return NULL;
+    }
+}
+
+static const char *
+alg_wrap_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: return "A128CBC-HS256";
+    case 1: return "A192CBC-HS384";
+    case 2: return "A256CBC-HS512";
+    default: return NULL;
+    }
+}
+
+static bool
+alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+             json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    const EVP_CIPHER *cph = NULL;
+    EVP_CIPHER_CTX *ecc = NULL;
+    bool ret = false;
+    size_t ptl = 0;
+    size_t ctl = 0;
+    int len = 0;
+
+    if (!json_object_get(cek, "k") && !jose_jwk_gen(cfg, cek))
+        return false;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: cph = EVP_aes_128_wrap(); break;
+    case 1: cph = EVP_aes_192_wrap(); break;
+    case 2: cph = EVP_aes_256_wrap(); break;
+    default: return false;
+    }
+
+    uint8_t ky[EVP_CIPHER_key_length(cph)];
+    uint8_t iv[EVP_CIPHER_iv_length(cph)];
+    uint8_t pt[KEYMAX];
+    uint8_t ct[sizeof(pt) + EVP_CIPHER_block_size(cph) * 2];
+
+    memset(iv, 0xA6, EVP_CIPHER_iv_length(cph));
+
+    if (jose_b64_dec(json_object_get(jwk, "k"), NULL, 0) != sizeof(ky))
+        goto egress;
+
+    if (jose_b64_dec(json_object_get(jwk, "k"), ky, sizeof(ky)) != sizeof(ky))
+        goto egress;
+
+    ptl = jose_b64_dec(json_object_get(cek, "k"), NULL, 0);
+    if (ptl > sizeof(pt))
+        goto egress;
+
+    if (jose_b64_dec(json_object_get(cek, "k"), pt, ptl) != ptl)
+        goto egress;
+
+    ecc = EVP_CIPHER_CTX_new();
+    if (!ecc)
+        goto egress;
+
+    EVP_CIPHER_CTX_set_flags(ecc, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+
+    if (EVP_EncryptInit_ex(ecc, cph, NULL, ky, iv) <= 0)
+        goto egress;
+
+    if (EVP_EncryptUpdate(ecc, ct, &len, pt, ptl) <= 0)
+        goto egress;
+    ctl = len;
+
+    if (EVP_EncryptFinal(ecc, &ct[len], &len) <= 0)
+        goto egress;
+    ctl += len;
+
+    if (json_object_set_new(rcp, "encrypted_key", jose_b64_enc(ct, ctl)) < 0)
+        goto egress;
+
+    ret = add_entity(jwe, rcp, "recipients", "header", "encrypted_key", NULL);
+
+egress:
+    OPENSSL_cleanse(ky, sizeof(ky));
+    OPENSSL_cleanse(pt, sizeof(pt));
+    EVP_CIPHER_CTX_free(ecc);
+    return ret;
+}
+
+static bool
+alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
+             const json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    const EVP_CIPHER *cph = NULL;
+    EVP_CIPHER_CTX *ecc = NULL;
+    bool ret = false;
+    size_t ctl = 0;
+    size_t ptl = 0;
+    int len = 0;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: cph = EVP_aes_128_wrap(); break;
+    case 1: cph = EVP_aes_192_wrap(); break;
+    case 2: cph = EVP_aes_256_wrap(); break;
+    default: return NULL;
+    }
+
+    uint8_t ky[EVP_CIPHER_key_length(cph)];
+    uint8_t iv[EVP_CIPHER_iv_length(cph)];
+    uint8_t ct[KEYMAX + EVP_CIPHER_block_size(cph) * 2];
+    uint8_t pt[sizeof(ct)];
+
+    memset(iv, 0xA6, sizeof(iv));
+
+    if (jose_b64_dec(json_object_get(jwk, "k"), NULL, 0) != sizeof(ky))
+        goto egress;
+
+    if (jose_b64_dec(json_object_get(jwk, "k"), ky, sizeof(ky)) != sizeof(ky))
+        goto egress;
+
+    ctl = jose_b64_dec(json_object_get(rcp, "encrypted_key"), NULL, 0);
+    if (ctl > sizeof(ct))
+        goto egress;
+
+    if (jose_b64_dec(json_object_get(rcp, "encrypted_key"), ct, ctl) != ctl)
+        goto egress;
+
+    ecc = EVP_CIPHER_CTX_new();
+    if (!ecc)
+        goto egress;
+
+    EVP_CIPHER_CTX_set_flags(ecc, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+
+    if (EVP_DecryptInit_ex(ecc, cph, NULL, ky, iv) <= 0)
+        goto egress;
+
+    if (EVP_DecryptUpdate(ecc, pt, &len, ct, ctl) <= 0)
+        goto egress;
+    ptl = len;
+
+    if (EVP_DecryptFinal(ecc, &pt[len], &len) <= 0)
+        goto egress;
+    ptl += len;
+
+    ret = json_object_set_new(cek, "k", jose_b64_enc(pt, ptl)) == 0;
+
+egress:
+    OPENSSL_cleanse(ky, sizeof(ky));
+    OPENSSL_cleanse(pt, sizeof(pt));
+    EVP_CIPHER_CTX_free(ecc);
+    return ret;
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute,
+    };
+
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "A128KW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "A192KW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "A256KW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 239 - 0
lib/openssl/compat.c

@@ -0,0 +1,239 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compat.h"
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+const unsigned char *
+EVP_PKEY_get0_hmac(EVP_PKEY *pkey, size_t *len)
+{
+    ASN1_OCTET_STRING *os = NULL;
+
+    if (EVP_PKEY_base_id(pkey) != EVP_PKEY_HMAC)
+        return NULL;
+
+    os = EVP_PKEY_get0(pkey);
+    *len = os->length;
+    return os->data;
+}
+
+void
+RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d)
+{
+    if (n)
+        *n = r->n;
+
+    if (e)
+        *e = r->e;
+
+    if (d)
+        *d = r->d;
+}
+
+void
+RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q)
+{
+    if (p)
+        *p = r->p;
+
+    if (q)
+        *q = r->q;
+}
+
+void
+RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1,
+                    const BIGNUM **iqmp)
+{
+    if (dmp1)
+        *dmp1 = r->dmp1;
+
+    if (dmq1)
+        *dmq1 = r->dmq1;
+
+    if (iqmp)
+        *iqmp = r->iqmp;
+}
+
+RSA *
+EVP_PKEY_get0_RSA(EVP_PKEY *pkey)
+{
+    if (pkey->type != EVP_PKEY_RSA)
+        return NULL;
+
+    return pkey->pkey.rsa;
+}
+
+EC_KEY *
+EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey)
+{
+    if (pkey->type != EVP_PKEY_EC)
+        return NULL;
+
+    return pkey->pkey.ec;
+}
+
+int
+RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d)
+{
+    if (!r->n && !n)
+        return 0;
+
+    if (!r->e && !e)
+        return 0;
+
+    if (n) {
+        BN_free(r->n);
+        r->n = n;
+    }
+
+    if (e) {
+        BN_free(r->e);
+        r->e = e;
+    }
+
+    if (d) {
+        BN_free(r->d);
+        r->d = d;
+    }
+
+    return 1;
+}
+
+int
+RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q)
+{
+    if (!r->p && !p)
+        return 0;
+
+    if (!r->q && !q)
+        return 0;
+
+    if (p) {
+        BN_free(r->p);
+        r->p = p;
+    }
+
+    if (q) {
+        BN_free(r->q);
+        r->q = q;
+    }
+
+    return 1;
+}
+
+int
+RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp)
+{
+    if (!r->dmp1 && !dmp1)
+        return 0;
+
+    if (!r->dmq1 && !dmq1)
+        return 0;
+
+    if (!r->iqmp && !iqmp)
+        return 0;
+
+    if (dmp1) {
+        BN_free(r->dmp1);
+        r->dmp1 = dmp1;
+    }
+
+    if (dmq1) {
+        BN_free(r->dmq1);
+        r->dmq1 = dmq1;
+    }
+
+    if (iqmp) {
+        BN_free(r->iqmp);
+        r->iqmp = iqmp;
+    }
+
+    return 1;
+}
+
+EVP_MD_CTX *
+EVP_MD_CTX_new(void)
+{
+    EVP_MD_CTX *cfg = OPENSSL_malloc(sizeof(EVP_MD_CTX));
+    if (!cfg)
+        return NULL;
+
+    EVP_MD_CTX_init(cfg);
+    return cfg;
+}
+
+void
+EVP_MD_CTX_free(EVP_MD_CTX *cfg)
+{
+    if (!cfg)
+        return;
+
+    EVP_MD_CTX_cleanup(cfg);
+    OPENSSL_free(cfg);
+}
+
+void
+ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
+{
+    if (pr)
+        *pr = sig->r;
+
+    if (ps)
+        *ps = sig->s;
+}
+
+int
+ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s)
+{
+    if (!r || !s)
+        return 0;
+
+    BN_clear_free(sig->r);
+    BN_clear_free(sig->s);
+    sig->r = r;
+    sig->s = s;
+    return 1;
+}
+
+HMAC_CTX *
+HMAC_CTX_new(void)
+{
+    HMAC_CTX *cfg = OPENSSL_malloc(sizeof(HMAC_CTX));
+
+    if (!cfg)
+        return NULL;
+
+    HMAC_CTX_init(cfg);
+    return cfg;
+}
+
+const EVP_MD *
+HMAC_CTX_get_md(const HMAC_CTX *ctx)
+{
+    return ctx->md;
+}
+
+void
+HMAC_CTX_free(HMAC_CTX *cfg)
+{
+    if (!cfg)
+        return;
+
+    HMAC_CTX_cleanup(cfg);
+    OPENSSL_free(cfg);
+}
+#endif

+ 75 - 0
lib/openssl/compat.h

@@ -0,0 +1,75 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <openssl/hmac.h>
+#include <openssl/ec.h>
+#include <openssl/ecdsa.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+const unsigned char *
+EVP_PKEY_get0_hmac(EVP_PKEY *pkey, size_t *len);
+
+void
+RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d);
+
+void
+RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q);
+
+void
+RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1,
+                    const BIGNUM **iqmp);
+
+RSA *
+EVP_PKEY_get0_RSA(EVP_PKEY *pkey);
+
+EC_KEY *
+EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey);
+
+int
+RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d);
+
+int
+RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q);
+
+int
+RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp);
+
+EVP_MD_CTX *
+EVP_MD_CTX_new(void);
+
+void
+EVP_MD_CTX_free(EVP_MD_CTX *ctx);
+
+void
+ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps);
+
+int
+ECDSA_SIG_set0(ECDSA_SIG *sig, BIGNUM *r, BIGNUM *s);
+
+HMAC_CTX *
+HMAC_CTX_new(void);
+
+const EVP_MD *
+HMAC_CTX_get_md(const HMAC_CTX *ctx);
+
+void
+HMAC_CTX_free(HMAC_CTX *ctx);
+#endif

+ 106 - 0
lib/openssl/dir.c

@@ -0,0 +1,106 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include "../hooks.h"
+
+#include <string.h>
+
+static const char *
+alg_wrap_alg(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *enc = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &enc) == -1)
+        return NULL;
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+        if (a->kind != JOSE_HOOK_ALG_KIND_ENCR)
+            continue;
+
+        if (strcmp(a->name, enc) == 0)
+            return "dir";
+    }
+
+    return NULL;
+}
+
+static const char *
+alg_wrap_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *enc = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &enc) == -1)
+        return NULL;
+
+    for (const jose_hook_alg_t *a = jose_hook_alg_list(); a; a = a->next) {
+        if (a->kind != JOSE_HOOK_ALG_KIND_ENCR)
+            continue;
+
+        if (strcmp(a->name, enc) == 0)
+            return a->name;
+    }
+
+    return NULL;
+}
+
+static bool
+copy(json_t *to, const json_t *from)
+{
+    json_auto_t *cpy = NULL;
+    cpy = json_deep_copy(from);
+    return json_object_update(to, cpy) == 0;
+}
+
+static bool
+alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+             json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    if (!json_object_get(cek, "k") && !copy(cek, jwk))
+        return false;
+
+    if (json_object_set_new(rcp, "encrypted_key", json_string("")) < 0)
+        return false;
+
+    return add_entity(jwe, rcp, "recipients", "header", "encrypted_key", NULL);
+}
+
+static bool
+alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
+             const json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    return copy(cek, jwk);
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "dir",
+          .wrap.eprm = "encrypt",
+          .wrap.dprm = "decrypt",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        {}
+    };
+
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 77 - 0
lib/openssl/ec.c

@@ -0,0 +1,77 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include "../hooks.h"
+#include <jose/openssl.h>
+
+#include <string.h>
+
+declare_cleanup(EC_KEY)
+
+static bool
+jwk_make_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *kty = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "kty", &kty) == -1)
+        return false;
+
+    return strcmp(kty, "EC") == 0;
+}
+
+static json_t *
+jwk_make_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    openssl_auto(EC_KEY) *key = NULL;
+    const char *crv = "P-256";
+    int nid = NID_undef;
+
+    if (!jwk_make_handles(cfg, jwk))
+        return false;
+
+    if (json_unpack((json_t *) jwk, "{s?s}", "crv", &crv) == -1)
+        return false;
+
+    switch (str2enum(crv, "P-256", "P-384", "P-521", NULL)) {
+    case 0: nid = NID_X9_62_prime256v1; break;
+    case 1: nid = NID_secp384r1; break;
+    case 2: nid = NID_secp521r1; break;
+    default: return false;
+    }
+
+    key = EC_KEY_new_by_curve_name(nid);
+    if (!key)
+        return false;
+
+    if (EC_KEY_generate_key(key) <= 0)
+        return false;
+
+    return json_pack("{s:o}", "upd", jose_openssl_jwk_from_EC_KEY(cfg, key));
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_MAKE,
+        .make.handles = jwk_make_handles,
+        .make.execute = jwk_make_execute
+    };
+
+    jose_hook_jwk_push(&jwk);
+}

+ 139 - 0
lib/openssl/ecdh.c

@@ -0,0 +1,139 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include "../hooks.h"
+#include <jose/openssl.h>
+
+#include <string.h>
+
+declare_cleanup(EC_POINT)
+declare_cleanup(EC_KEY)
+declare_cleanup(BN_CTX)
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return strcmp(alg, "ECDH") == 0;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *crv = "P-521";
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s,s?s}", "alg", &alg, "crv", &crv) < 0)
+        return false;
+
+    if (strcmp(alg, "ECDH") != 0)
+        return false;
+
+    return json_pack("{s:{s:s,s:s}}", "upd", "kty", "EC", "crv", crv);
+}
+
+static const char *
+alg_exch_sug(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+             const json_t *prv, const json_t *pub)
+{
+    const char *crva = NULL;
+    const char *crvb = NULL;
+    const char *ktya = NULL;
+    const char *ktyb = NULL;
+
+    if (json_unpack((json_t *) prv, "{s:s,s:s}", "kty", &ktya, "crv", &crva) < 0)
+        return NULL;
+
+    if (json_unpack((json_t *) pub, "{s:s,s:s}", "kty", &ktyb, "crv", &crvb) < 0)
+        return NULL;
+
+    if (strcmp("EC", ktya) != 0 || strcmp("EC", ktyb) != 0)
+        return NULL;
+
+    if (strcmp(crva, crvb) != 0)
+        return NULL;
+
+    if (str2enum(crva, "P-256", "P-384", "P-521", NULL) == SIZE_MAX)
+        return NULL;
+
+    return "ECDH";
+}
+
+static json_t *
+alg_exch_exc(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+             const json_t *prv, const json_t *pub)
+{
+    openssl_auto(EC_KEY) *lcl = NULL;
+    openssl_auto(EC_KEY) *rem = NULL;
+    openssl_auto(BN_CTX) *bnc = NULL;
+    openssl_auto(EC_POINT) *p = NULL;
+    const EC_GROUP *grp = NULL;
+
+    bnc = BN_CTX_new();
+    if (!bnc)
+        return NULL;
+
+    lcl = jose_openssl_jwk_to_EC_KEY(cfg, prv);
+    if (!lcl)
+        return NULL;
+
+    rem = jose_openssl_jwk_to_EC_KEY(cfg, pub);
+    if (!rem)
+        return NULL;
+
+    grp = EC_KEY_get0_group(lcl);
+    if (EC_GROUP_cmp(grp, EC_KEY_get0_group(rem), bnc) != 0)
+        return NULL;
+
+    p = EC_POINT_new(grp);
+    if (!p)
+        return NULL;
+
+    if (EC_POINT_mul(grp, p, NULL, EC_KEY_get0_public_key(rem),
+                     EC_KEY_get0_private_key(lcl), bnc) <= 0)
+        return NULL;
+
+    return jose_openssl_jwk_from_EC_POINT(cfg, EC_KEY_get0_group(rem), p, NULL);
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute
+    };
+
+    static jose_hook_alg_t ecdh[] = {
+        { .name = "ECDH",
+          .kind = JOSE_HOOK_ALG_KIND_EXCH,
+          .exch.prm = "deriveKey",
+          .exch.sug = alg_exch_sug,
+          .exch.exc = alg_exch_exc },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; ecdh[i].name; i++)
+        jose_hook_alg_push(&ecdh[i]);
+}

+ 483 - 0
lib/openssl/ecdhes.c

@@ -0,0 +1,483 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include "misc.h"
+#include <jose/b64.h>
+#include <jose/jwk.h>
+#include "../hooks.h"
+#include <jose/openssl.h>
+
+#include <openssl/rand.h>
+
+#include <string.h>
+
+#define NAMES "ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW"
+
+#include <assert.h>
+
+static uint32_t
+h2be32(uint32_t x)
+{
+    union swap {
+        uint32_t i;
+        uint8_t  b[8];
+    } y;
+
+    y.b[0] = x >> 0x18;
+    y.b[1] = x >> 0x10;
+    y.b[2] = x >> 0x08;
+    y.b[3] = x >> 0x00;
+
+    return y.i;
+}
+
+static bool
+concatkdf(const jose_hook_alg_t *alg, jose_cfg_t *cfg, uint8_t dk[], size_t dkl,
+          const uint8_t z[], size_t zl, ...)
+{
+    jose_io_auto_t *b = NULL;
+    uint8_t hsh[alg->hash.size];
+    size_t hshl = sizeof(hsh);
+    size_t reps = 0;
+    size_t left = 0;
+
+    reps = dkl / sizeof(hsh);
+    left = dkl % sizeof(hsh);
+
+    b = jose_io_buffer(cfg, &hsh, &hshl);
+    if (!b)
+        return false;
+
+    for (uint32_t c = 0; c <= reps; c++) {
+        uint32_t cnt = h2be32(c + 1);
+        jose_io_auto_t *h = NULL;
+        va_list ap;
+
+        h = alg->hash.hsh(alg, cfg, b);
+        if (!h)
+            return false;
+
+        if (!h->feed(h, &cnt, sizeof(cnt)))
+            return false;
+
+        if (!h->feed(h, z, zl))
+            return false;
+
+        va_start(ap, zl);
+        for (void *a = va_arg(ap, void *); a; a = va_arg(ap, void *)) {
+            size_t l = va_arg(ap, size_t);
+            uint32_t e = h2be32(l);
+
+            if (!h->feed(h, &e, sizeof(e))) {
+                va_end(ap);
+                return false;
+            }
+
+            if (!h->feed(h, a, l)) {
+                va_end(ap);
+                return false;
+            }
+        }
+        va_end(ap);
+
+        if (!h->feed(h, &(uint32_t) { h2be32(dkl * 8) }, 4))
+            return false;
+
+        if (!h->done(h))
+            return false;
+
+        assert(hshl == alg->hash.size);
+
+        memcpy(&dk[c * hshl], hsh, c == reps ? left : hshl);
+        OPENSSL_cleanse(hsh, sizeof(hsh));
+        hshl = 0;
+    }
+
+    return true;
+}
+
+static size_t
+encr_alg_keylen(jose_cfg_t *cfg, const char *enc)
+{
+    json_auto_t *tmpl = NULL;
+
+    if (!jose_hook_alg_find(JOSE_HOOK_ALG_KIND_ENCR, enc))
+        return SIZE_MAX;
+
+    tmpl = json_pack("{s:s}", "alg", enc);
+    if (!tmpl)
+        return SIZE_MAX;
+
+    for (const jose_hook_jwk_t *j = jose_hook_jwk_list(); j; j = j->next) {
+        json_auto_t *upd = NULL;
+        const char *kty = NULL;
+        json_int_t len = 0;
+
+        if (j->kind != JOSE_HOOK_JWK_KIND_PREP)
+            continue;
+
+        if (!j->prep.handles(cfg, tmpl))
+            continue;
+
+        upd = j->prep.execute(cfg, tmpl);
+        if (json_unpack(upd, "{s:{s:s,s:I}}",
+                        "upd", "kty", &kty, "bytes", &len) < 0)
+            return SIZE_MAX;
+
+        if (strcmp(kty, "oct") != 0)
+            return SIZE_MAX;
+
+        return len;
+    }
+
+    return SIZE_MAX;
+}
+
+static size_t
+decode(const json_t *obj, const char *name, uint8_t *buf, size_t len)
+{
+    const char *tmp = NULL;
+    size_t tmpl = 0;
+    size_t dlen = 0;
+
+    if (json_unpack((json_t *) obj, "{s?s%}", name, &tmp, &tmpl) < 0)
+        return SIZE_MAX;
+
+    if (!tmp)
+        return 0;
+
+    dlen = jose_b64_dec_buf(tmp, tmpl, NULL, 0);
+    if (dlen > len)
+        return dlen;
+
+    return jose_b64_dec_buf(tmp, tmpl, buf, len);
+}
+
+static json_t *
+derive(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
+       json_t *hdr, json_t *cek, const json_t *key)
+{
+    const jose_hook_alg_t *halg = NULL;
+    const char *name = alg->name;
+    uint8_t pu[KEYMAX] = {};
+    uint8_t pv[KEYMAX] = {};
+    uint8_t dk[KEYMAX] = {};
+    uint8_t ky[KEYMAX] = {};
+    const char *enc = NULL;
+    json_t *out = NULL;
+    size_t dkl = 0;
+    size_t pul = 0;
+    size_t pvl = 0;
+    size_t kyl = 0;
+
+    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, "S256");
+    if (!halg)
+        goto egress;
+
+    if (json_unpack(hdr, "{s?s}", "enc", &enc) < 0)
+        goto egress;
+
+    if (!enc && json_unpack(cek, "{s:s}", "alg", &enc) < 0)
+        goto egress;
+
+    switch (str2enum(alg->name, NAMES, NULL)) {
+    case 0: dkl = encr_alg_keylen(cfg, enc); name = enc; break;
+    case 1: dkl = 16; break;
+    case 2: dkl = 24; break;
+    case 3: dkl = 32; break;
+    default:
+        goto egress;
+    }
+
+    if (dkl < 16 || dkl > sizeof(dk))
+        goto egress;
+
+    pul = decode(hdr, "apu", pu, sizeof(pu));
+    if (pul > sizeof(pu))
+        goto egress;
+
+    pvl = decode(hdr, "apv", pv, sizeof(pv));
+    if (pvl > sizeof(pv))
+        goto egress;
+
+    kyl = decode(key, "x", ky, sizeof(ky));
+    if (kyl > sizeof(ky))
+        goto egress;
+
+    if (!concatkdf(halg, cfg,
+                   dk, dkl,
+                   ky, kyl,
+                   name, strlen(name),
+                   pu, pul,
+                   pv, pvl,
+                   NULL))
+        goto egress;
+
+    out = json_pack("{s:s,s:s,s:o}", "kty", "oct", "alg", enc,
+                    "k", jose_b64_enc(dk, dkl));
+
+egress:
+    OPENSSL_cleanse(ky, sizeof(ky));
+    OPENSSL_cleanse(pu, sizeof(pu));
+    OPENSSL_cleanse(pv, sizeof(pv));
+    OPENSSL_cleanse(dk, sizeof(dk));
+    return out;
+}
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return str2enum(alg, NAMES, NULL) != SIZE_MAX;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+    const char *grp = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return NULL;
+
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: grp = "P-521"; break;
+    case 1: grp = "P-256"; break;
+    case 2: grp = "P-384"; break;
+    case 3: grp = "P-521"; break;
+    default: return NULL;
+    }
+
+    return json_pack("{s:{s:s,s:s}}", "upd", "kty", "EC", "crv", grp);
+}
+
+static const char *
+alg_wrap_alg(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *name = NULL;
+    const char *type = NULL;
+    const char *curv = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s?s,s?s,s?s}",
+                    "alg", &name, "kty", &type, "crv", &curv) < 0)
+        return NULL;
+
+    if (name)
+        return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
+
+    if (!type || strcmp(type, "EC") != 0)
+        return NULL;
+
+    switch (str2enum(curv, "P-256", "P-384", "P-521", NULL)) {
+    case 0: return "ECDH-ES+A128KW";
+    case 1: return "ECDH-ES+A192KW";
+    case 2: return "ECDH-ES+A256KW";
+    default: return NULL;
+    }
+}
+
+static const char *
+alg_wrap_enc(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *crv = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s?s}", "crv", &crv) < 0)
+        return NULL;
+
+    switch (str2enum(crv, "P-256", "P-384", "P-521", NULL)) {
+    case 0: return "A128CBC-HS256";
+    case 1: return "A192CBC-HS384";
+    case 2: return "A256CBC-HS512";
+    default: return NULL;
+    }
+}
+
+static bool
+alg_wrap_wrp(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jwe,
+             json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    const jose_hook_alg_t *ecdh = NULL;
+    json_auto_t *exc = NULL;
+    json_auto_t *hdr = NULL;
+    json_auto_t *epk = NULL;
+    json_auto_t *der = NULL;
+    const char *wrap = NULL;
+    json_t *h = NULL;
+
+    if (json_object_get(cek, "k")) {
+        if (strcmp(alg->name, "ECDH-ES") == 0)
+            return false;
+    } else if (!jose_jwk_gen(cfg, cek)) {
+        return false;
+    }
+
+    hdr = jose_jwe_hdr(jwe, rcp);
+    if (!hdr)
+        return false;
+
+    h = json_object_get(rcp, "header");
+    if (!h && json_object_set_new(rcp, "header", h = json_object()) == -1)
+        return false;
+
+    epk = json_pack("{s:s,s:O}", "kty", "EC", "crv",
+                    json_object_get(jwk, "crv"));
+    if (!epk)
+        return false;
+
+    if (!jose_jwk_gen(cfg, epk))
+        return false;
+
+    ecdh = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_EXCH, "ECDH");
+    if (!ecdh)
+        return false;
+
+    exc = ecdh->exch.exc(ecdh, cfg, epk, jwk);
+    if (!exc)
+        return false;
+
+    if (!jose_jwk_pub(cfg, epk))
+        return false;
+
+    if (json_object_set(h, "epk", epk) == -1)
+        return false;
+
+    der = derive(alg, cfg, hdr, cek, exc);
+    if (!der)
+        return false;
+
+    wrap = strchr(alg->name, '+');
+    if (wrap) {
+        const jose_hook_alg_t *kw = NULL;
+
+        kw = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, &wrap[1]);
+        if (!kw)
+            return false;
+
+        return kw->wrap.wrp(kw, cfg, jwe, rcp, der, cek);
+    }
+
+    if (json_object_update(cek, der) < 0)
+        return false;
+
+    return add_entity(jwe, rcp, "recipients", "header", "encrypted_key", NULL);
+}
+
+static bool
+alg_wrap_unw(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwe,
+             const json_t *rcp, const json_t *jwk, json_t *cek)
+{
+    const json_t *epk = NULL;
+    json_auto_t *exc = NULL;
+    json_auto_t *der = NULL;
+    json_auto_t *hdr = NULL;
+    const char *wrap = NULL;
+
+    hdr = jose_jwe_hdr(jwe, rcp);
+    epk = json_object_get(hdr, "epk");
+    if (!hdr || !epk)
+        return false;
+
+    /* If the JWK has a private key, perform the normal exchange. */
+    if (json_object_get(jwk, "d")) {
+        const jose_hook_alg_t *ecdh = NULL;
+
+        ecdh = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_EXCH, "ECDH");
+        if (!ecdh)
+            return false;
+
+        exc = ecdh->exch.exc(ecdh, cfg, jwk, epk);
+
+    /* Otherwise, allow external exchanges. */
+    } else if (json_equal(json_object_get(jwk, "crv"),
+                          json_object_get(epk, "crv"))) {
+        exc = json_deep_copy(jwk);
+    }
+    if (!exc)
+        return false;
+
+    der = derive(alg, cfg, hdr, cek, exc);
+    if (!der)
+        return false;
+
+    wrap = strchr(alg->name, '+');
+    if (wrap) {
+        const jose_hook_alg_t *kw = NULL;
+
+        kw = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_WRAP, &wrap[1]);
+        if (!kw)
+            return false;
+
+        return kw->wrap.unw(kw, cfg, jwe, rcp, der, cek);
+    }
+
+    return json_object_update(cek, der) == 0;
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute
+    };
+
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "ECDH-ES",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "ECDH-ES+A128KW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "ECDH-ES+A192KW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        { .kind = JOSE_HOOK_ALG_KIND_WRAP,
+          .name = "ECDH-ES+A256KW",
+          .wrap.eprm = "wrapKey",
+          .wrap.dprm = "unwrapKey",
+          .wrap.alg = alg_wrap_alg,
+          .wrap.enc = alg_wrap_enc,
+          .wrap.wrp = alg_wrap_wrp,
+          .wrap.unw = alg_wrap_unw },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 310 - 0
lib/openssl/ecdsa.c

@@ -0,0 +1,310 @@
+/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
+/*
+ * Copyright 2016 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "misc.h"
+#include <jose/b64.h>
+#include "../hooks.h"
+#include <jose/openssl.h>
+
+#include <string.h>
+
+#define NAMES "ES256", "ES384", "ES512"
+
+typedef struct {
+    jose_io_t io;
+
+    jose_io_t *h;
+    jose_io_t *b;
+    EC_KEY *key;
+    json_t *obj;
+    json_t *sig;
+
+    size_t hshl;
+    void *hsh;
+} io_t;
+
+declare_cleanup(ECDSA_SIG)
+
+static void
+io_free(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    if (i->h)
+        i->h->free(i->h);
+    if (i->b)
+        i->b->free(i->b);
+    EC_KEY_free(i->key);
+    json_decref(i->obj);
+    json_decref(i->sig);
+    free(i);
+}
+
+static bool
+io_feed(jose_io_t *io, const void *in, size_t len)
+{
+    io_t *i = containerof(io, io_t, io);
+    return i->h->feed(i->h, in, len);
+}
+
+static bool
+sig_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t buf[(EC_GROUP_get_degree(EC_KEY_get0_group(i->key)) + 7) / 8 * 2];
+    openssl_auto(ECDSA_SIG) *ecdsa = NULL;
+    const BIGNUM *r = NULL;
+    const BIGNUM *s = NULL;
+
+    if (!i->h->done(i->h))
+        return false;
+
+    ecdsa = ECDSA_do_sign(i->hsh, i->hshl, i->key);
+    if (!ecdsa)
+        return false;
+
+    ECDSA_SIG_get0(ecdsa, &r, &s);
+
+    if (!bn_encode(r, buf, sizeof(buf) / 2))
+        return false;
+
+    if (!bn_encode(s, &buf[sizeof(buf) / 2], sizeof(buf) / 2))
+        return false;
+
+    if (json_object_set_new(i->sig, "signature",
+                            jose_b64_enc(buf, sizeof(buf))) < 0)
+        return false;
+
+    return add_entity(i->obj, i->sig,
+                      "signatures", "signature", "protected", "header", NULL);
+}
+
+static bool
+ver_done(jose_io_t *io)
+{
+    io_t *i = containerof(io, io_t, io);
+    uint8_t buf[(EC_GROUP_get_degree(EC_KEY_get0_group(i->key)) + 7) / 8 * 2];
+    openssl_auto(ECDSA_SIG) *ecdsa = NULL;
+    const json_t *sig = NULL;
+    BIGNUM *r = NULL;
+    BIGNUM *s = NULL;
+
+    sig = json_object_get(i->sig, "signature");
+    if (!sig)
+        return false;
+
+    if (jose_b64_dec(sig, NULL, 0) != sizeof(buf))
+        return false;
+
+    if (jose_b64_dec(sig, buf, sizeof(buf)) != sizeof(buf))
+        return false;
+
+    ecdsa = ECDSA_SIG_new();
+    if (!ecdsa)
+        return false;
+
+    r = bn_decode(buf, sizeof(buf) / 2);
+    s = bn_decode(&buf[sizeof(buf) / 2], sizeof(buf) / 2);
+    if (ECDSA_SIG_set0(ecdsa, r, s) <= 0) {
+        BN_free(r);
+        BN_free(s);
+        return false;
+    }
+
+    if (!i->h->done(i->h))
+        return false;
+
+    return ECDSA_do_verify(i->hsh, i->hshl, ecdsa, i->key) == 1;
+}
+
+static bool
+jwk_prep_handles(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    return str2enum(alg, NAMES, NULL) != SIZE_MAX;
+}
+
+static json_t *
+jwk_prep_execute(jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *alg = NULL;
+    const char *grp = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s:s}", "alg", &alg) == -1)
+        return false;
+
+    switch (str2enum(alg, NAMES, NULL)) {
+    case 0: grp = "P-256"; break;
+    case 1: grp = "P-384"; break;
+    case 2: grp = "P-521"; break;
+    default: return false;
+    }
+
+    return json_pack("{s:{s:s,s:s}}", "upd", "kty", "EC", "crv", grp);
+}
+
+static const char *
+alg_sign_sug(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jwk)
+{
+    const char *name = NULL;
+    const char *type = NULL;
+    const char *curv = NULL;
+
+    if (json_unpack((json_t *) jwk, "{s?s,s?s,s?s}",
+                    "alg", &name, "kty", &type, "crv", &curv) < 0)
+        return NULL;
+
+    if (name)
+        return str2enum(name, NAMES, NULL) != SIZE_MAX ? name : NULL;
+
+    if (!type || strcmp(type, "EC") != 0)
+        return NULL;
+
+    switch (str2enum(curv, "P-256", "P-384", "P-521", NULL)) {
+    case 0: return "ES256";
+    case 1: return "ES384";
+    case 2: return "ES512";
+    default: return NULL;
+    }
+}
+
+static jose_io_t *
+alg_sign_sig(const jose_hook_alg_t *alg, jose_cfg_t *cfg, json_t *jws,
+             json_t *sig, const json_t *jwk)
+{
+    const jose_hook_alg_t *halg = NULL;
+    jose_io_auto_t *io = NULL;
+    const char *prot = NULL;
+    io_t *i = NULL;
+    size_t plen = 0;
+
+    if (json_unpack(sig, "{s?s%}", "protected", &prot, &plen) < 0)
+        return NULL;
+
+    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, &alg->name[1]);
+    if (!halg)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = io_feed;
+    io->done = sig_done;
+    io->free = io_free;
+
+    i->b = jose_io_malloc(cfg, &i->hsh, &i->hshl);
+    i->h = halg->hash.hsh(halg, cfg, i->b);
+    i->obj = json_incref(jws);
+    i->sig = json_incref(sig);
+    i->key = jose_openssl_jwk_to_EC_KEY(cfg, jwk);
+    if (!i->b || !i->h || !i->obj || !i->sig || !i->key)
+        return NULL;
+
+    if (prot && !i->h->feed(i->h, prot, plen))
+        return NULL;
+
+    if (!i->h->feed(i->h, ".", 1))
+        return NULL;
+
+    return jose_io_incref(io);
+}
+
+static jose_io_t *
+alg_sign_ver(const jose_hook_alg_t *alg, jose_cfg_t *cfg, const json_t *jws,
+             const json_t *sig, const json_t *jwk)
+{
+    const jose_hook_alg_t *halg = NULL;
+    jose_io_auto_t *io = NULL;
+    const char *prot = NULL;
+    io_t *i = NULL;
+    size_t plen = 0;
+
+    if (json_unpack((json_t *) sig, "{s?s%}", "protected", &prot, &plen) < 0)
+        return NULL;
+
+    halg = jose_hook_alg_find(JOSE_HOOK_ALG_KIND_HASH, &alg->name[1]);
+    if (!halg)
+        return NULL;
+
+    i = calloc(1, sizeof(*i));
+    if (!i)
+        return NULL;
+
+    io = jose_io_incref(&i->io);
+    io->feed = io_feed;
+    io->done = ver_done;
+    io->free = io_free;
+
+    i->b = jose_io_malloc(cfg, &i->hsh, &i->hshl);
+    i->h = halg->hash.hsh(halg, cfg, i->b);
+    i->sig = json_incref((json_t *) sig);
+    i->key = jose_openssl_jwk_to_EC_KEY(cfg, jwk);
+    if (!i->b || !i->h || !i->sig || !i->key)
+        return NULL;
+
+    if (prot && !i->h->feed(i->h, prot, plen))
+        return NULL;
+
+    if (!i->h->feed(i->h, ".", 1))
+        return NULL;
+
+    return jose_io_incref(io);
+}
+
+static void __attribute__((constructor))
+constructor(void)
+{
+    static jose_hook_jwk_t jwk = {
+        .kind = JOSE_HOOK_JWK_KIND_PREP,
+        .prep.handles = jwk_prep_handles,
+        .prep.execute = jwk_prep_execute,
+    };
+
+    static jose_hook_alg_t algs[] = {
+        { .kind = JOSE_HOOK_ALG_KIND_SIGN,
+          .name = "ES256",
+          .sign.sprm = "sign",
+          .sign.vprm = "verify",
+          .sign.sug = alg_sign_sug,
+          .sign.sig = alg_sign_sig,
+          .sign.ver = alg_sign_ver },
+        { .kind = JOSE_HOOK_ALG_KIND_SIGN,
+          .name = "ES384",
+          .sign.sprm = "sign",
+          .sign.vprm = "verify",
+          .sign.sug = alg_sign_sug,
+          .sign.sig = alg_sign_sig,
+          .sign.ver = alg_sign_ver },
+        { .kind = JOSE_HOOK_ALG_KIND_SIGN,
+          .name = "ES512",
+          .sign.sprm = "sign",
+          .sign.vprm = "verify",
+          .sign.sug = alg_sign_sug,
+          .sign.sig = alg_sign_sig,
+          .sign.ver = alg_sign_ver },
+        {}
+    };
+
+    jose_hook_jwk_push(&jwk);
+    for (size_t i = 0; algs[i].name; i++)
+        jose_hook_alg_push(&algs[i]);
+}

+ 0 - 0
lib/openssl/ecmr.c


Some files were not shown because too many files changed in this diff