Hello community, here is the log from the commit of package jwt_verify_lib for openSUSE:Factory checked in at 2020-01-17 16:07:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/jwt_verify_lib (Old) and /work/SRC/openSUSE:Factory/.jwt_verify_lib.new.26092 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "jwt_verify_lib" Fri Jan 17 16:07:38 2020 rev:6 rq:765233 version:20191024 Changes: -------- --- /work/SRC/openSUSE:Factory/jwt_verify_lib/jwt_verify_lib.changes 2019-11-11 21:24:53.719856777 +0100 +++ /work/SRC/openSUSE:Factory/.jwt_verify_lib.new.26092/jwt_verify_lib.changes 2020-01-17 16:07:59.420513935 +0100 @@ -1,0 +2,16 @@ +Tue Jan 14 10:43:14 UTC 2020 - [email protected] + +- Update to version 20191024: + * Add support for ES384 and ES512 + * Add HS384 and HS512 support + * Clear openssl error queue if HMAC() fails + * Add HS256 support +- Add source package, remove lib* and devel package - now it's not + possible to build jwt_verify_lib outside envoy-proxy's source + tree. +- Remove Maistra sources: + * jwt-verify-lib-openssl-20190806.tar.xz +- Add patch which adds compatibility with OpenSSL: + * jwt_verify-make-compatible-with-openssl.patch + +------------------------------------------------------------------- Old: ---- jwt-verify-lib-openssl-20190806.tar.xz jwt_verify_lib-20190909.tar.xz New: ---- jwt_verify-make-compatible-with-openssl.patch jwt_verify_lib-20191024.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ jwt_verify_lib.spec ++++++ --- /var/tmp/diff_new_pack.cmEtsx/_old 2020-01-17 16:07:59.928514175 +0100 +++ /var/tmp/diff_new_pack.cmEtsx/_new 2020-01-17 16:07:59.932514177 +0100 @@ -1,7 +1,7 @@ # # spec file for package jwt_verify_lib # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,117 +16,47 @@ # -%define sover 0 -%define libname lib%{name}%{sover} -%define maistra_name jwt-verify-lib-openssl -%define maistra_version 20190806 +%define src_install_dir /usr/src/%{name} Name: jwt_verify_lib -Version: 20190909 +Version: 20191024 Release: 0 Summary: JSON Web Tokens verification library for C++ License: Apache-2.0 Group: Development/Libraries/C and C++ Url: https://github.com/google/%{name} Source0: %{name}-%{version}.tar.xz -Source1: %{maistra_name}-%{maistra_version}.tar.xz -BuildRequires: abseil-cpp-source -BuildRequires: bazel-rules-cc-source -BuildRequires: bazel-rules-java-source -BuildRequires: bazel-rules-proto-source -BuildRequires: bazel-skylib-source -BuildRequires: bazel-workspaces -BuildRequires: bazel0.29 -BuildRequires: bssl_wrapper-devel -BuildRequires: gcc-c++ -BuildRequires: gmock -BuildRequires: gtest -BuildRequires: openssl-cbs-devel -BuildRequires: openssl-devel -BuildRequires: protobuf-devel -BuildRequires: protobuf-source -BuildRequires: rapidjson-devel -ExcludeArch: %ix86 +Patch0: jwt_verify-make-compatible-with-openssl.patch +BuildRequires: fdupes +BuildArch: noarch %description jwt_verify_lib is a library which verifies JSON Web Tokens. It does not provide any other features like signing or advanced checks. -%package -n %{libname} -Summary: JSON web token verification library for C++ -Group: System/Libraries +%package source +Summary: Source code of jwt_verify_lib +Group: Development/Sources -%description -n %{libname} +%description source jwt_verify_lib is a library which verifies JSON Web Tokens. It does not provide any other features like signing or advanced checks. -This package contains shared library for jwt_verify_lib. - -%package devel -Summary: Development files for jwt_verify_lib -Group: Development/Libraries/C and C++ -Requires: %{libname} = %{version} - -%description devel -jwt_verify_lib is a library which verifies JSON Web Tokens. It does not provide -any other features like signing or advanced checks. - -This package contains development files for jwt_verify_lib. +This package contains source code of jwt_verify_lib. %prep -%setup -q -D -b 1 -n %{maistra_name}-%{maistra_version} -%setup -q -pushd ../%{maistra_name}-%{maistra_version} -sed -i "s|\"src/struct_utils.h\"|\"jwt_verify_lib/struct_utils.h\"|g" src/jwks.cc -sed -i "s|\"src/struct_utils.h\"|\"jwt_verify_lib/struct_utils.h\"|g" src/jwt.cc -./openssl.sh ../%{name}-%{version} SSL -popd +%autosetup -p1 %build -# TODO(mrostecki): Create a macro in bazel package. -TARGETS=$(bazel query '//... except kind(.*test, //...)') -bazel build \ - -c dbg \ - --color=no \ - %(for opt in %{optflags}; do echo -e "--copt=${opt} \c"; done) \ - --curses=no \ - --distdir=%{_sourcedir} \ - --genrule_strategy=standalone \ - --host_javabase=@local_jdk//:jdk \ - --linkopt="-Wl,-soname,libjwt_verify_lib.so.%{sover}" \ - --override_repository="bazel_skylib=/usr/src/bazel-skylib" \ - --override_repository="com_google_absl=/usr/src/abseil-cpp" \ - --override_repository="com_google_protobuf=/usr/src/protobuf" \ - --override_repository="googletest_git=%{_datadir}/bazel-workspaces/googletest" \ - --override_repository="rules_cc=/usr/src/bazel-rules-cc" \ - --override_repository="rules_java=/usr/src/bazel-rules-java" \ - --override_repository="rules_proto=/usr/src/bazel-rules-proto" \ - --spawn_strategy=standalone \ - --strip=never \ - --verbose_failures \ - ${TARGETS} -bazel shutdown %install -install -D -m755 bazel-bin/libjwt_verify_lib.so %{buildroot}%{_libdir}/libjwt_verify_lib.so.%{sover} -ln -sf libjwt_verify_lib.so.%{sover} %{buildroot}%{_libdir}/libjwt_verify_lib.so -find jwt_verify_lib -type f -execdir install -D -m0644 "{}" "%{buildroot}%{_includedir}/jwt_verify_lib/{}" \; +mkdir -p %{buildroot}%{src_install_dir} +cp -r * %{buildroot}%{src_install_dir} +%fdupes %{buildroot}%{src_install_dir} -%post -n %{libname} -p /sbin/ldconfig -%postun -n %{libname} -p /sbin/ldconfig - -%files -n %{libname} +%files source %license LICENSE %doc README.md -%{_libdir}/libjwt_verify_lib.so.%{sover} - -%files devel -%{_includedir}/jwt_verify_lib -%{_includedir}/jwt_verify_lib/check_audience.h -%{_includedir}/jwt_verify_lib/jwks.h -%{_includedir}/jwt_verify_lib/jwt.h -%{_includedir}/jwt_verify_lib/status.h -%{_includedir}/jwt_verify_lib/verify.h -%{_libdir}/libjwt_verify_lib.so +%{src_install_dir} %changelog ++++++ _service ++++++ --- /var/tmp/diff_new_pack.cmEtsx/_old 2020-01-17 16:07:59.960514190 +0100 +++ /var/tmp/diff_new_pack.cmEtsx/_new 2020-01-17 16:07:59.960514190 +0100 @@ -5,15 +5,7 @@ <param name="changesgenerate">enable</param> <param name="filename">jwt_verify_lib</param> <param name="versionformat">%cd</param> - <param name="revision">2866385faa2508a11071d075f788172b3f3bd27f</param> - </service> - <service mode="disabled" name="tar_scm"> - <param name="url">https://github.com/Maistra/jwt-verify-lib-openssl</param> - <param name="scm">git</param> - <param name="changesgenerate">disable</param> - <param name="filename">jwt-verify-lib-openssl</param> - <param name="versionformat">%cd</param> - <param name="revision">maistra-1.1</param> + <param name="revision">9f10e2d60d42edeb6662e185707a7d6a4ebc5604</param> </service> <service mode="disabled" name="recompress"> <param name="file">*.tar</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.cmEtsx/_old 2020-01-17 16:07:59.976514197 +0100 +++ /var/tmp/diff_new_pack.cmEtsx/_new 2020-01-17 16:07:59.976514197 +0100 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/google/jwt_verify_lib</param> - <param name="changesrevision">5552dbc736a25da7e264b260d5ba3cedc33a71f8</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">4d9461344d9768c3cd05f8d90d4518b83d113f9d</param></service></servicedata> \ No newline at end of file ++++++ jwt_verify-make-compatible-with-openssl.patch ++++++ >From b0e4badb4158934c8ec102dccc26adf3b478e6e5 Mon Sep 17 00:00:00 2001 From: Venil Noronha <[email protected]> Date: Fri, 1 Nov 2019 10:10:10 -0700 Subject: [PATCH] make compatible with openssl Signed-off-by: Venil Noronha <[email protected]> --- BUILD | 4 +++- jwt_verify_lib/jwks.h | 4 ++++ src/jwks.cc | 20 ++++++++++++++++---- src/verify.cc | 13 +++++++++++-- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/BUILD b/BUILD index 60331dc..bd55255 100644 --- a/BUILD +++ b/BUILD @@ -27,6 +27,8 @@ cc_library( "//external:abseil_time", "//external:protobuf", "//external:ssl", + "@envoy_openssl//boringssl_compat:bssl_compat_cbs_lib", + "@envoy_openssl//boringssl_compat:bssl_compat_lib", ], ) diff --git a/jwt_verify_lib/jwks.h b/jwt_verify_lib/jwks.h index 24a18b7..80676da 100644 --- a/jwt_verify_lib/jwks.h +++ b/jwt_verify_lib/jwks.h @@ -22,6 +22,10 @@ #include "openssl/ec.h" #include "openssl/evp.h" +#ifndef OPENSSL_IS_BORINGSSL +#include "boringssl_compat/bssl.h" +#endif + namespace google { namespace jwt_verify { diff --git a/src/jwks.cc b/src/jwks.cc index 97b1ae8..9723c82 100644 --- a/src/jwks.cc +++ b/src/jwks.cc @@ -27,6 +27,11 @@ #include "openssl/rsa.h" #include "openssl/sha.h" +#ifndef OPENSSL_IS_BORINGSSL +#include "boringssl_compat/cbs.h" +using namespace Envoy::Extensions::Common::Cbs; +#endif + namespace google { namespace jwt_verify { @@ -118,18 +123,25 @@ class EvpPkeyGetter : public WithStatus { bssl::UniquePtr<RSA> createRsaFromJwk(const std::string& n, const std::string& e) { bssl::UniquePtr<RSA> rsa(RSA_new()); - rsa->n = createBigNumFromBase64UrlString(n).release(); - rsa->e = createBigNumFromBase64UrlString(e).release(); - if (rsa->n == nullptr || rsa->e == nullptr) { + BIGNUM* n_bn; + BIGNUM* e_bn; + n_bn = createBigNumFromBase64UrlString(n).release(); + e_bn = createBigNumFromBase64UrlString(e).release(); + if (n_bn == nullptr || e_bn == nullptr) { // RSA public key field is missing or has parse error. updateStatus(Status::JwksRsaParseError); return nullptr; } - if (BN_cmp_word(rsa->e, 3) != 0 && BN_cmp_word(rsa->e, 65537) != 0) { + if (BN_cmp_word(e_bn, 3) != 0 && BN_cmp_word(e_bn, 65537) != 0) { // non-standard key; reject it early. updateStatus(Status::JwksRsaParseError); return nullptr; } + if (!RSA_set0_key(rsa.get(), n_bn, e_bn, NULL)) { + // can't set RSA key; reject it early. + updateStatus(Status::JwksRsaParseError); + return nullptr; + } return rsa; } }; diff --git a/src/verify.cc b/src/verify.cc index 4d26c25..10fb175 100644 --- a/src/verify.cc +++ b/src/verify.cc @@ -22,7 +22,13 @@ #include "openssl/err.h" #include "openssl/evp.h" #include "openssl/hmac.h" + +#ifdef OPENSSL_IS_BORINGSSL #include "openssl/mem.h" +#else +#include "openssl/crypto.h" +#endif + #include "openssl/rsa.h" #include "openssl/sha.h" @@ -91,9 +97,12 @@ bool verifySignatureEC(EC_KEY* key, const EVP_MD* md, const uint8_t* signature, return false; } - if (BN_bin2bn(signature, signature_len / 2, ecdsa_sig->r) == nullptr || + const BIGNUM* r_bn; + const BIGNUM* s_bn; + ECDSA_SIG_get0(ecdsa_sig.get(), &r_bn, &s_bn); + if (BN_bin2bn(signature, signature_len / 2, const_cast<BIGNUM *>(r_bn)) == nullptr || BN_bin2bn(signature + (signature_len / 2), signature_len / 2, - ecdsa_sig->s) == nullptr) { + const_cast<BIGNUM *>(s_bn)) == nullptr) { return false; } -- 2.14.3 (Apple Git-98) ++++++ jwt_verify_lib-20190909.tar.xz -> jwt_verify_lib-20191024.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/BUILD new/jwt_verify_lib-20191024/BUILD --- old/jwt_verify_lib-20190909/BUILD 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/BUILD 2019-10-24 19:42:38.000000000 +0200 @@ -162,3 +162,20 @@ "//external:googletest_main", ], ) + +cc_test( + name = "verify_jwk_hmac_test", + srcs = [ + "src/test_common.h", + "src/verify_jwk_hmac_test.cc", + ], + linkopts = [ + "-lm", + "-lpthread", + ], + linkstatic = 1, + deps = [ + ":jwt_verify_lib", + "//external:googletest_main", + ], +) \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/jwt_verify_lib/jwks.h new/jwt_verify_lib-20191024/jwt_verify_lib/jwks.h --- old/jwt_verify_lib-20190909/jwt_verify_lib/jwks.h 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/jwt_verify_lib/jwks.h 2019-10-24 19:42:38.000000000 +0200 @@ -44,9 +44,11 @@ struct Pubkey { bssl::UniquePtr<EVP_PKEY> evp_pkey_; bssl::UniquePtr<EC_KEY> ec_key_; + std::string hmac_key_; std::string kid_; std::string kty_; std::string alg_; + std::string crv_; bool alg_specified_ = false; bool kid_specified_ = false; bool pem_format_ = false; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/jwt_verify_lib/status.h new/jwt_verify_lib-20191024/jwt_verify_lib/status.h --- old/jwt_verify_lib-20190909/jwt_verify_lib/status.h 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/jwt_verify_lib/status.h 2019-10-24 19:42:38.000000000 +0200 @@ -98,6 +98,9 @@ // "x" or "y" field of a Jwk EC is missing or has a parse error. JwksEcParseError, + // Jwks Oct key is an invalid Base64. + JwksOctBadBase64, + // Failed to fetch public key JwksFetchFail, @@ -119,8 +122,12 @@ // "e" field is not string for a RSA key JwksRSAKeyBadE, - // "alg" is not "ES256" for an EC key + // "alg" is not "ES256", "ES384" or "ES512" for an EC key JwksECKeyBadAlg, + // "crv" field is not string for an EC key + JwksECKeyBadCrv, + // "crv" is not compatible with "alg" for an EC key + JwksECKeyAlgNotCompatibleWithCrv, // "x" field is missing for an EC key JwksECKeyMissingX, // "x" field is not string for an EC key @@ -129,6 +136,13 @@ JwksECKeyMissingY, // "y" field is not string for an EC key JwksECKeyBadY, + + // "alg" is not "HS256", "HS384" or "HS512" for an HMAC key + JwksHMACKeyBadAlg, + // "k" field is missing for an HMAC key + JwksHMACKeyMissingK, + // "k" field is not string for an HMAC key + JwksHMACKeyBadK, }; /** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/jwks.cc new/jwt_verify_lib-20191024/src/jwks.cc --- old/jwt_verify_lib-20190909/src/jwks.cc 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/src/jwks.cc 2019-10-24 19:42:38.000000000 +0200 @@ -72,10 +72,9 @@ return createEvpPkeyFromRsa(createRsaFromJwk(n, e).get()); } - bssl::UniquePtr<EC_KEY> createEcKeyFromJwkEC(const std::string& x, + bssl::UniquePtr<EC_KEY> createEcKeyFromJwkEC(int nid, const std::string& x, const std::string& y) { - bssl::UniquePtr<EC_KEY> ec_key( - EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(nid)); if (!ec_key) { updateStatus(Status::JwksEcCreateKeyFail); return nullptr; @@ -168,13 +167,41 @@ Status extractJwkFromJwkEC(const ::google::protobuf::Struct& jwk_pb, Jwks::Pubkey* jwk) { - if (jwk->alg_specified_ && jwk->alg_ != "ES256") { + // since "alg" is optional, assume "ES256" if it was not specified + if (!jwk->alg_specified_) { + jwk->alg_ = "ES256"; + } else if (jwk->alg_.size() < 2 || jwk->alg_.compare(0, 2, "ES") != 0) { return Status::JwksECKeyBadAlg; } StructUtils jwk_getter(jwk_pb); + std::string crv_str; + auto code = jwk_getter.GetString("crv", &crv_str); + if (code == StructUtils::MISSING) { + crv_str = ""; + } + if (code == StructUtils::WRONG_TYPE) { + return Status::JwksECKeyBadCrv; + } + jwk->crv_ = crv_str; + int nid; + if (jwk->alg_ == "ES256" && (jwk->crv_ == "P-256" || jwk->crv_ == "")) { + nid = NID_X9_62_prime256v1; + jwk->crv_ = "P-256"; + } else if (jwk->alg_ == "ES384" && + (jwk->crv_ == "P-384" || jwk->crv_ == "")) { + nid = NID_secp384r1; + jwk->crv_ = "P-384"; + } else if (jwk->alg_ == "ES512" && + (jwk->crv_ == "P-521" || jwk->crv_ == "")) { + nid = NID_secp521r1; + jwk->crv_ = "P-521"; + } else { + return Status::JwksECKeyAlgNotCompatibleWithCrv; + } + std::string x_str; - auto code = jwk_getter.GetString("x", &x_str); + code = jwk_getter.GetString("x", &x_str); if (code == StructUtils::MISSING) { return Status::JwksECKeyMissingX; } @@ -192,10 +219,36 @@ } EvpPkeyGetter e; - jwk->ec_key_ = e.createEcKeyFromJwkEC(x_str, y_str); + jwk->ec_key_ = e.createEcKeyFromJwkEC(nid, x_str, y_str); return e.getStatus(); } +Status extractJwkFromJwkOct(const ::google::protobuf::Struct& jwk_pb, + Jwks::Pubkey* jwk) { + if (jwk->alg_specified_ && jwk->alg_ != "HS256" && jwk->alg_ != "HS384" && + jwk->alg_ != "HS512") { + return Status::JwksHMACKeyBadAlg; + } + + StructUtils jwk_getter(jwk_pb); + std::string k_str; + auto code = jwk_getter.GetString("k", &k_str); + if (code == StructUtils::MISSING) { + return Status::JwksHMACKeyMissingK; + } + if (code == StructUtils::WRONG_TYPE) { + return Status::JwksHMACKeyBadK; + } + + std::string key; + if (!absl::WebSafeBase64Unescape(k_str, &key) || key.empty()) { + return Status::JwksOctBadBase64; + } + + jwk->hmac_key_ = key; + return Status::Ok; +} + Status extractJwk(const ::google::protobuf::Struct& jwk_pb, Jwks::Pubkey* jwk) { StructUtils jwk_getter(jwk_pb); // Check "kty" parameter, it should exist. @@ -208,8 +261,8 @@ return Status::JwksBadKty; } - // "kid" and "alg" are optional, if they do not exist, set them to empty. - // https://tools.ietf.org/html/rfc7517#page-8 + // "kid", "alg" and "crv" are optional, if they do not exist, set them to + // empty. https://tools.ietf.org/html/rfc7517#page-8 code = jwk_getter.GetString("kid", &jwk->kid_); if (code == StructUtils::OK) { jwk->kid_specified_ = true; @@ -225,6 +278,8 @@ return extractJwkFromJwkEC(jwk_pb, jwk); } else if (jwk->kty_ == "RSA") { return extractJwkFromJwkRSA(jwk_pb, jwk); + } else if (jwk->kty_ == "oct") { + return extractJwkFromJwkOct(jwk_pb, jwk); } return Status::JwksNotImplementedKty; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/jwks_test.cc new/jwt_verify_lib-20191024/src/jwks_test.cc --- old/jwt_verify_lib-20190909/src/jwks_test.cc 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/src/jwks_test.cc 2019-10-24 19:42:38.000000000 +0200 @@ -112,17 +112,34 @@ "y": "92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8", "alg": "ES256", "kid": "xyz" + }, + { + "kty": "EC", + "crv": "P-384", + "x": "yY8DWcyWlrr93FTrscI5Ydz2NC7emfoKYHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5g", + "y": "An5wVxEfksDOa_zvSHHGkeYJUfl8y11wYkOlFjBt9pOCw5-RlfZgPOa3pbmUquxZ", + "alg": "ES384", + "kid": "es384" + }, + { + "kty": "EC", + "crv": "P-521", + "x": "Abijiex7rz7t-_Zj_E6Oo0OXe9C_-MCSD-OWio15ATQGjH9WpbWjN62ZqrrU_nwJiqqwx6ZsYKhUc_J3PRaMbdVC", + "y": "FxaljCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B_bQ91mShCiR_43Whsn1P7Gz30WEnLuJs1SGVz1oT4lIRUYni2OfIk", + "alg": "ES512", + "kid": "es512" } ] } )"; auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); EXPECT_EQ(jwks->getStatus(), Status::Ok); - EXPECT_EQ(jwks->keys().size(), 2); + EXPECT_EQ(jwks->keys().size(), 4); EXPECT_EQ(jwks->keys()[0]->alg_, "ES256"); EXPECT_EQ(jwks->keys()[0]->kid_, "abc"); EXPECT_EQ(jwks->keys()[0]->kty_, "EC"); + EXPECT_EQ(jwks->keys()[0]->crv_, "P-256"); EXPECT_TRUE(jwks->keys()[0]->alg_specified_); EXPECT_TRUE(jwks->keys()[0]->kid_specified_); EXPECT_FALSE(jwks->keys()[0]->pem_format_); @@ -130,9 +147,26 @@ EXPECT_EQ(jwks->keys()[1]->alg_, "ES256"); EXPECT_EQ(jwks->keys()[1]->kid_, "xyz"); EXPECT_EQ(jwks->keys()[1]->kty_, "EC"); + EXPECT_EQ(jwks->keys()[1]->crv_, "P-256"); EXPECT_TRUE(jwks->keys()[1]->alg_specified_); EXPECT_TRUE(jwks->keys()[1]->kid_specified_); EXPECT_FALSE(jwks->keys()[1]->pem_format_); + + EXPECT_EQ(jwks->keys()[2]->alg_, "ES384"); + EXPECT_EQ(jwks->keys()[2]->kid_, "es384"); + EXPECT_EQ(jwks->keys()[2]->kty_, "EC"); + EXPECT_EQ(jwks->keys()[2]->crv_, "P-384"); + EXPECT_TRUE(jwks->keys()[2]->alg_specified_); + EXPECT_TRUE(jwks->keys()[2]->kid_specified_); + EXPECT_FALSE(jwks->keys()[2]->pem_format_); + + EXPECT_EQ(jwks->keys()[3]->alg_, "ES512"); + EXPECT_EQ(jwks->keys()[3]->kid_, "es512"); + EXPECT_EQ(jwks->keys()[3]->kty_, "EC"); + EXPECT_EQ(jwks->keys()[3]->crv_, "P-521"); + EXPECT_TRUE(jwks->keys()[3]->alg_specified_); + EXPECT_TRUE(jwks->keys()[3]->kid_specified_); + EXPECT_FALSE(jwks->keys()[3]->pem_format_); } TEST(JwksParseTest, EmptyJwks) { @@ -316,6 +350,158 @@ EXPECT_EQ(jwks->getStatus(), Status::JwksRsaParseError); } +TEST(JwksParseTest, JwksECMatchAlgES256CrvP256) { + // alg matches crv + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES256", + "crv": "P-256", + "x": "EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k", + "y": "92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8" + } + ] + } +)"; + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::Ok); +} + +TEST(JwksParseTest, JwksECMatchAlgES384CrvP384) { + // alg matches crv + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES384", + "crv": "P-384", + "x": "yY8DWcyWlrr93FTrscI5Ydz2NC7emfoKYHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5g", + "y": "An5wVxEfksDOa_zvSHHGkeYJUfl8y11wYkOlFjBt9pOCw5-RlfZgPOa3pbmUquxZ" + } + ] + } +)"; + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::Ok); +} + +TEST(JwksParseTest, JwksECMatchAlgES512CrvP521) { + // alg matches crv + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES512", + "crv": "P-521", + "x": "Abijiex7rz7t-_Zj_E6Oo0OXe9C_-MCSD-OWio15ATQGjH9WpbWjN62ZqrrU_nwJiqqwx6ZsYKhUc_J3PRaMbdVC", + "y": "FxaljCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B_bQ91mShCiR_43Whsn1P7Gz30WEnLuJs1SGVz1oT4lIRUYni2OfIk" + } + ] + } +)"; + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::Ok); +} + +TEST(JwksParseTest, JwksECMismatchAlgCrv1) { + // alg doesn't match with crv + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES256", + "crv": "P-384" + } + ] + } +)"; + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::JwksECKeyAlgNotCompatibleWithCrv); +} + +TEST(JwksParseTest, JwksECMismatchAlgCrv2) { + // alg doesn't match with crv + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES384", + "crv": "P-521" + } + ] + } +)"; + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::JwksECKeyAlgNotCompatibleWithCrv); +} + +TEST(JwksParseTest, JwksECMismatchAlgCrv3) { + // alg doesn't match with crv + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES512", + "crv": "P-256" + } + ] + } +)"; + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::JwksECKeyAlgNotCompatibleWithCrv); +} + +TEST(JwksParseTest, JwksECUnspecifiedCrv) { + // crv determined from alg + const std::string jwks_text = R"( + { + "keys": [ + { + "kty": "EC", + "alg": "ES256", + "x": "EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k", + "y": "92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8" + }, + { + "kty": "EC", + "alg": "ES384", + "x": "yY8DWcyWlrr93FTrscI5Ydz2NC7emfoKYHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5g", + "y": "An5wVxEfksDOa_zvSHHGkeYJUfl8y11wYkOlFjBt9pOCw5-RlfZgPOa3pbmUquxZ" + }, + { + "kty": "EC", + "alg": "ES512", + "x": "Abijiex7rz7t-_Zj_E6Oo0OXe9C_-MCSD-OWio15ATQGjH9WpbWjN62ZqrrU_nwJiqqwx6ZsYKhUc_J3PRaMbdVC", + "y": "FxaljCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B_bQ91mShCiR_43Whsn1P7Gz30WEnLuJs1SGVz1oT4lIRUYni2OfIk" + } + ] + } +)"; + + auto jwks = Jwks::createFrom(jwks_text, Jwks::JWKS); + EXPECT_EQ(jwks->getStatus(), Status::Ok); + EXPECT_EQ(jwks->keys().size(), 3); + + EXPECT_EQ(jwks->keys()[0]->alg_, "ES256"); + EXPECT_EQ(jwks->keys()[0]->crv_, "P-256"); + EXPECT_TRUE(jwks->keys()[0]->alg_specified_); + + EXPECT_EQ(jwks->keys()[1]->alg_, "ES384"); + EXPECT_EQ(jwks->keys()[1]->crv_, "P-384"); + EXPECT_TRUE(jwks->keys()[1]->alg_specified_); + + EXPECT_EQ(jwks->keys()[2]->alg_, "ES512"); + EXPECT_EQ(jwks->keys()[2]->crv_, "P-521"); + EXPECT_TRUE(jwks->keys()[2]->alg_specified_); +} + } // namespace } // namespace jwt_verify } // namespace google diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/jwt.cc new/jwt_verify_lib-20191024/src/jwt.cc --- old/jwt_verify_lib-20190909/src/jwt.cc 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/src/jwt.cc 2019-10-24 19:42:38.000000000 +0200 @@ -61,8 +61,9 @@ return Status::JwtHeaderBadAlg; } - if (alg_ != "RS256" && alg_ != "ES256" && alg_ != "RS384" && - alg_ != "RS512") { + if (alg_ != "ES256" && alg_ != "ES384" && alg_ != "ES512" && + alg_ != "HS256" && alg_ != "HS384" && alg_ != "HS512" && + alg_ != "RS256" && alg_ != "RS384" && alg_ != "RS512") { return Status::JwtHeaderNotImplementedAlg; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/status.cc new/jwt_verify_lib-20191024/src/status.cc --- old/jwt_verify_lib-20190909/src/status.cc 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/src/status.cc 2019-10-24 19:42:38.000000000 +0200 @@ -70,6 +70,8 @@ return "Jwks EC create key fail"; case Status::JwksEcParseError: return "Jwks EC [x] or [y] field is missing or has a parse error."; + case Status::JwksOctBadBase64: + return "Jwks Oct key is an invalid Base64"; case Status::JwksFetchFail: return "Jwks remote fetch is failed"; @@ -92,7 +94,11 @@ return "[e] field is not string for a RSA key"; case Status::JwksECKeyBadAlg: - return "[alg] is not [ES256] for an EC key"; + return "[alg] is not started with [ES] for an EC key"; + case Status::JwksECKeyBadCrv: + return "[crv] field is not string for an EC key"; + case Status::JwksECKeyAlgNotCompatibleWithCrv: + return "[crv] field specified is not compatible with [alg] for an EC key"; case Status::JwksECKeyMissingX: return "[x] field is missing for an EC key"; case Status::JwksECKeyBadX: @@ -101,6 +107,13 @@ return "[y] field is missing for an EC key"; case Status::JwksECKeyBadY: return "[y] field is not string for an EC key"; + + case Status::JwksHMACKeyBadAlg: + return "[alg] does not start with [HS] for an HMAC key"; + case Status::JwksHMACKeyMissingK: + return "[k] field is missing for an HMAC key"; + case Status::JwksHMACKeyBadK: + return "[k] field is not string for an HMAC key"; }; return ""; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/verify.cc new/jwt_verify_lib-20191024/src/verify.cc --- old/jwt_verify_lib-20190909/src/verify.cc 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/src/verify.cc 2019-10-24 19:42:38.000000000 +0200 @@ -21,6 +21,8 @@ #include "openssl/ecdsa.h" #include "openssl/err.h" #include "openssl/evp.h" +#include "openssl/hmac.h" +#include "openssl/mem.h" #include "openssl/rsa.h" #include "openssl/sha.h" @@ -61,43 +63,91 @@ castToUChar(signed_data), signed_data.length()); } -bool verifySignatureEC(EC_KEY* key, const uint8_t* signature, +bool verifySignatureEC(EC_KEY* key, const EVP_MD* md, const uint8_t* signature, size_t signature_len, const uint8_t* signed_data, size_t signed_data_len) { - if (key == nullptr || signature == nullptr || signed_data == nullptr) { + if (key == nullptr || md == nullptr || signature == nullptr || + signed_data == nullptr) { return false; } - // ES256 signature should be 64 bytes. - if (signature_len != 2 * 32) { + bssl::UniquePtr<EVP_MD_CTX> md_ctx(EVP_MD_CTX_create()); + std::vector<uint8_t> digest(EVP_MAX_MD_SIZE); + unsigned int digest_len = 0; + + if (EVP_DigestInit(md_ctx.get(), md) == 0) { return false; } - uint8_t digest[SHA256_DIGEST_LENGTH]; - SHA256(signed_data, signed_data_len, digest); + if (EVP_DigestUpdate(md_ctx.get(), signed_data, signed_data_len) == 0) { + return false; + } + + if (EVP_DigestFinal(md_ctx.get(), digest.data(), &digest_len) == 0) { + return false; + } bssl::UniquePtr<ECDSA_SIG> ecdsa_sig(ECDSA_SIG_new()); if (!ecdsa_sig) { return false; } - if (BN_bin2bn(signature, 32, ecdsa_sig->r) == nullptr || - BN_bin2bn(signature + 32, 32, ecdsa_sig->s) == nullptr) { + if (BN_bin2bn(signature, signature_len / 2, ecdsa_sig->r) == nullptr || + BN_bin2bn(signature + (signature_len / 2), signature_len / 2, + ecdsa_sig->s) == nullptr) { return false; } - if (ECDSA_do_verify(digest, SHA256_DIGEST_LENGTH, ecdsa_sig.get(), key) == - 1) { + + if (ECDSA_do_verify(digest.data(), digest_len, ecdsa_sig.get(), key) == 1) { return true; } + ERR_clear_error(); return false; } -bool verifySignatureEC(EC_KEY* key, absl::string_view signature, +bool verifySignatureEC(EC_KEY* key, const EVP_MD* md, + absl::string_view signature, absl::string_view signed_data) { - return verifySignatureEC(key, castToUChar(signature), signature.length(), + return verifySignatureEC(key, md, castToUChar(signature), signature.length(), castToUChar(signed_data), signed_data.length()); } +bool verifySignatureOct(const uint8_t* key, size_t key_len, const EVP_MD* md, + const uint8_t* signature, size_t signature_len, + const uint8_t* signed_data, size_t signed_data_len) { + if (key == nullptr || md == nullptr || signature == nullptr || + signed_data == nullptr) { + return false; + } + + std::vector<uint8_t> out(EVP_MAX_MD_SIZE); + unsigned int out_len = 0; + if (HMAC(md, key, key_len, signed_data, signed_data_len, out.data(), + &out_len) == nullptr) { + ERR_clear_error(); + return false; + } + + if (out_len != signature_len) { + return false; + } + + if (CRYPTO_memcmp(out.data(), signature, signature_len) == 0) { + return true; + } + + ERR_clear_error(); + return false; +} + +bool verifySignatureOct(absl::string_view key, const EVP_MD* md, + absl::string_view signature, + absl::string_view signed_data) { + return verifySignatureOct(castToUChar(key), key.length(), md, + castToUChar(signature), signature.length(), + castToUChar(signed_data), signed_data.length()); +} + } // namespace Status verifyJwt(const Jwt& jwt, const Jwks& jwks) { @@ -131,10 +181,22 @@ } kid_alg_matched = true; - if (jwk->kty_ == "EC" && - verifySignatureEC(jwk->ec_key_.get(), jwt.signature_, signed_data)) { - // Verification succeeded. - return Status::Ok; + if (jwk->kty_ == "EC") { + const EVP_MD* md; + if (jwt.alg_ == "ES384") { + md = EVP_sha384(); + } else if (jwt.alg_ == "ES512") { + md = EVP_sha512(); + } else { + // default to SHA256 + md = EVP_sha256(); + } + + if (verifySignatureEC(jwk->ec_key_.get(), md, jwt.signature_, + signed_data)) { + // Verification succeeded. + return Status::Ok; + } } else if (jwk->pem_format_ || jwk->kty_ == "RSA") { const EVP_MD* md; if (jwt.alg_ == "RS384") { @@ -151,6 +213,21 @@ // Verification succeeded. return Status::Ok; } + } else if (jwk->kty_ == "oct") { + const EVP_MD* md; + if (jwt.alg_ == "HS384") { + md = EVP_sha384(); + } else if (jwt.alg_ == "HS512") { + md = EVP_sha512(); + } else { + // default to SHA256 + md = EVP_sha256(); + } + + if (verifySignatureOct(jwk->hmac_key_, md, jwt.signature_, signed_data)) { + // Verification succeeded. + return Status::Ok; + } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/verify_jwk_ec_test.cc new/jwt_verify_lib-20191024/src/verify_jwk_ec_test.cc --- old/jwt_verify_lib-20190909/src/verify_jwk_ec_test.cc 2019-09-09 18:18:10.000000000 +0200 +++ new/jwt_verify_lib-20191024/src/verify_jwk_ec_test.cc 2019-10-24 19:42:38.000000000 +0200 @@ -20,14 +20,19 @@ namespace jwt_verify { namespace { -// Please see jwt_generator.py and jwk_generator.py under /tools/. -// for ES256-signed jwt token and public jwk generation, respectively. -// jwt_generator.py uses ES256 private key file to generate JWT token. +// Please see jwt_generator.py and jwk_generator.py under +// https://github.com/istio/proxy/tree/master/src/envoy/http/jwt_auth/tools +// for ES{256,384,512}-signed jwt token and public jwk generation, respectively. +// jwt_generator.py uses ES{256,384,512} private key file to generate JWT token. // ES256 private key file can be generated by: // $ openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem -// jwk_generator.py uses ES256 public key file to generate JWK. ES256 -// public key file can be generated by: -// $ openssl ec -in private_key.pem -pubout -out public_key.pem. +// ES384 private key file can be generated by: +// $ openssl ecparam -genkey -name secp384r1 -noout -out private_key.pem +// ES512 private key file can be generated by: +// $ openssl ecparam -genkey -name secp521r1 -noout -out private_key.pem +// jwk_generator.py uses ES{256.384,512} public key file to generate JWK. +// ES256, ES384 and ES512 public key files can be generated by: +// $ openssl ec -in private_key.pem -pubout -out public_key.pem // ES256 private key: // "-----BEGIN EC PRIVATE KEY-----" @@ -42,6 +47,37 @@ // "7CF5bCDTXlrwI5n3ZsIFO8wVDyUptLYxuCNPdh+Zijoec8QTa2wCpZQnDw==" // "-----END PUBLIC KEY-----" +// ES384 private key: +// "-----BEGIN EC PRIVATE KEY-----" +// "MIGkAgEBBDDqSPe2gvdUVMQcCxpr60rScFgjEQZeCYvZRq3oyY9mECVMK7nuRjLx" +// "blWjf6DH9E+gBwYFK4EEACKhZANiAATJjwNZzJaWuv3cVOuxwjlh3PY0Lt6Z+gpg" +// "cktfZ2vdxKB/DQa7ECS5DmcEwmZVXmACfnBXER+SwM5r/O9IccaR5glR+XzLXXBi" +// "Q6UWMG32k4LDn5GV9mA85reluZSq7Fk=" +// "-----END EC PRIVATE KEY-----" + +// ES384 public key: +// "-----BEGIN PUBLIC KEY-----" +// "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEyY8DWcyWlrr93FTrscI5Ydz2NC7emfoK" +// "YHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5gAn5wVxEfksDOa/zvSHHGkeYJUfl8y11w" +// "YkOlFjBt9pOCw5+RlfZgPOa3pbmUquxZ" +// "-----END PUBLIC KEY-----" + +// ES512 private key: +// "-----BEGIN EC PRIVATE KEY-----" +// "MIHcAgEBBEIBKlG7GPIoqQujJHwe21rnsZePySFyd45HPe3FeldgZQEHqcUiZgpb" +// "BgiuYMPHytEaohj1yC5gyOOsOfgsWY2qSsWgBwYFK4EEACOhgYkDgYYABAG4o4ns" +// "e68+7fv2Y/xOjqNDl3vQv/jAkg/jloqNeQE0Box/VqW1ozetmaq61P58CYqqsMem" +// "bGCoVHPydz0WjG3VQgAXFqWMIi6hUQDs8khoM8nl49e1nSGSKdPUH9tD3WZKEKJH" +// "/jdaGyfU/sbPfRYScu4mzVIZXPWhPiUhFRieLY58iQ==" +// "-----END EC PRIVATE KEY-----" + +// ES512 public key: +// "-----BEGIN PUBLIC KEY-----" +// "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBuKOJ7HuvPu379mP8To6jQ5d70L/4" +// "wJIP45aKjXkBNAaMf1altaM3rZmqutT+fAmKqrDHpmxgqFRz8nc9Foxt1UIAFxal" +// "jCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B/bQ91mShCiR/43Whsn1P7Gz30WEnLuJs1S" +// "GVz1oT4lIRUYni2OfIk=" +// "-----END PUBLIC KEY-----" const std::string PublicKeyJwkEC = R"( { "keys": [ @@ -60,12 +96,37 @@ "kid": "xyz", "x": "EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k", "y": "92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8" + }, + { + "kty": "EC", + "crv": "P-384", + "alg": "ES384", + "kid": "es384", + "x": "yY8DWcyWlrr93FTrscI5Ydz2NC7emfoKYHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5g", + "y": "An5wVxEfksDOa_zvSHHGkeYJUfl8y11wYkOlFjBt9pOCw5-RlfZgPOa3pbmUquxZ" + }, + { + "kty": "EC", + "crv": "P-521", + "alg": "ES512", + "kid": "es512", + "x": "Abijiex7rz7t-_Zj_E6Oo0OXe9C_-MCSD-OWio15ATQGjH9WpbWjN62ZqrrU_nwJiqqwx6ZsYKhUc_J3PRaMbdVC", + "y": "FxaljCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B_bQ91mShCiR_43Whsn1P7Gz30WEnLuJs1SGVz1oT4lIRUYni2OfIk" } ] } )"; // "{"kid":"abc"}" +const std::string JwtES256Text = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYyJ9.eyJpc3MiOiI2Mj" + "g2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvc" + "GVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1" + "MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3V" + "udC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.T2KAwChqg" + "o2ZSXyLh3IcMBQNSeRZRe5Z-MUDl-s-F99XGoyutqA6lq8bKZ6vmjZAlpVG8AGRZW9J" + "Gp9lq3cbEw"; + const std::string JwtTextEC = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYyJ9.eyJpc3MiOiI2Mj" "g2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvc" @@ -75,6 +136,28 @@ "o2ZSXyLh3IcMBQNSeRZRe5Z-MUDl-s-F99XGoyutqA6lq8bKZ6vmjZAlpVG8AGRZW9J" "Gp9lq3cbEw"; +// "{"kid":"es384"}" +const std::string JwtES384Text = + "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6ImVzMzg0In0.eyJpc3MiOi" + "I2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ" + "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4" + "ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmd" + "zZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS" + "9teWFwaSJ9.aKFxrqV4_rg1Zf2DamTU0D76hOq9-FYu-LNmpGPthjJKv31mOZ4t" + "J40x2FVVJx5d8lntg3bsy1IN0z9C7MD_k10Y7Gea1YB7Jyi-DR68U5krJzzwKmD" + "9ap1J7tb2UrzT"; + +// "{"kid":"es512"}" +const std::string JwtES512Text = + "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImVzNTEyIn0.eyJpc3MiOi" + "I2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ" + "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4" + "ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmd" + "zZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS" + "9teWFwaSJ9.ATSReP9zpba6PRJZmlIEA78Ft-FZS1m_SpFLqfiNQNexaDaTmmVr" + "IqD9X-krPxk0c8KSBeMlU-QLOsbh37coamruAPKoAODYWA-QKUN2a_xem8WrudK" + "VXWsmQlZDOJA0lQWI-YGMEPrDr17mljMhZwSGbVVST9l-nZiMXyMK0z8hR9Mn"; + // "{"kid":"abcdef"}" const std::string JwtTextWithNonExistKidEC = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiY2RlZiJ9.eyJpc3MiOi" @@ -114,6 +197,26 @@ }); } +TEST_F(VerifyJwkECTest, KidES384OK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtES384Text), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); + + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + +TEST_F(VerifyJwkECTest, KidES512OK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtES512Text), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); + + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + TEST_F(VerifyJwkECTest, NoKidOK) { Jwt jwt; EXPECT_EQ(jwt.parseFromString(JwtTextECNoKid), Status::Ok); @@ -144,7 +247,7 @@ EXPECT_EQ(jwks_->getStatus(), Status::Ok); Jwt jwt; - EXPECT_EQ(jwt.parseFromString(JwtTextEC), Status::Ok); + EXPECT_EQ(jwt.parseFromString(JwtES256Text), Status::Ok); EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); } @@ -162,7 +265,7 @@ EXPECT_EQ(jwks_->getStatus(), Status::Ok); Jwt jwt; - EXPECT_EQ(jwt.parseFromString(JwtTextEC), Status::Ok); + EXPECT_EQ(jwt.parseFromString(JwtES256Text), Status::Ok); EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jwt_verify_lib-20190909/src/verify_jwk_hmac_test.cc new/jwt_verify_lib-20191024/src/verify_jwk_hmac_test.cc --- old/jwt_verify_lib-20190909/src/verify_jwk_hmac_test.cc 1970-01-01 01:00:00.000000000 +0100 +++ new/jwt_verify_lib-20191024/src/verify_jwk_hmac_test.cc 2019-10-24 19:42:38.000000000 +0200 @@ -0,0 +1,242 @@ +// Copyright 2018 Google LLC +// +// 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 +// +// https://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 "gtest/gtest.h" +#include "jwt_verify_lib/verify.h" +#include "src/test_common.h" + +namespace google { +namespace jwt_verify { +namespace { + +const std::string SymmetricKeyHMAC = R"( +{ + "keys": [ + { + "kty": "oct", + "alg": "HS256", + "use": "sig", + "kid": "62a93512c9ee4c7f8067b5a216dade2763d32a47", + "k": "LcHQCLETtc_QO4D69zCnQEIAYaZ6BsldibDzuRHE5bI" + }, + { + "kty": "oct", + "alg": "HS256", + "use": "sig", + "kid": "b3319a147514df7ee5e4bcdee51350cc890cc89e", + "k": "nyeGXUHngW64dyg2EuDs_8x6VGa14Bkrv1SFQwOzKfI" + }, + { + "kty": "oct", + "alg": "HS384", + "use": "sig", + "kid": "cda01077a6aa4b0088a6e959044977ef9e51c28b", + "k": "5xYkMHiMVnCBbFEt0Uh1LhIbFB6yakzp2Mh7ESBMUCDq4zMO6WgCMaQwP332FH47" + }, + { + "kty": "oct", + "alg": "HS512", + "use": "sig", + "kid": "f6a7bd9ffd784388924f126280a746964ba61268", + "k": "ID3awf7bo607gitUDWylMMhUyVFr4ZAmnysPw4675A1YmOaYajbqLmMA7fohGLYZdZyaluaiugKvnnGLYTDoUA" + }, + + ] +} +)"; + +// JWT without kid +// Header: {"alg":"HS256","typ":"JWT"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","exp":1501281058} +const std::string JwtTextNoKid = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImV4cCI6MTUwMTI4MTA1OH0." + "_LY8Zz3ssG82v5-T8L2Hg1TsqzCEEKnYOxzrQpDTjwU"; + +// JWT without kid with long exp +// Header: {"alg":"HS256","typ":"JWT"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","aud":"example_service","exp":2001001001} +const std::string JwtTextNoKidLongExp = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0." + "4tc7M-gJizpbB69_sQi7E0ym0np6uon4V41hVjYV2ic"; + +// JWT with correct kid +// Header: +// {"alg":"HS256","typ":"JWT","kid":"b3319a147514df7ee5e4bcdee51350cc890cc89e"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","exp":1501281058} +const std::string JwtHS256TextWithCorrectKid = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImIzMzE5YTE0NzUxNGRmN2VlNWU0" + "YmNkZWU1MTM1MGNjODkwY2M4OWUifQ." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImV4cCI6MTUwMTI4MTA1OH0." + "QqSMCAY5UDBvySx0VQhGqIvomZaSRUJOCT6ktV3BhL8"; + +// JWT with correct kid +// Header: +// {"alg":"HS384","typ":"JWT","kid":"cda01077a6aa4b0088a6e959044977ef9e51c28b"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","exp":1501281058} +const std::string JwtHS384TextWithCorrectKid = + "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6ImNkYTAxMDc3YTZhYTRiMDA4OGE2" + "ZTk1OTA0NDk3N2VmOWU1MWMyOGIifQ." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImV4cCI6MTUwMTI4MTA1OH0." + "F69ivpIRbgrmy1j6_MHl10xDW8iPdzsHAIgln3Z9PEemH9heiQoDUOgG91kA44fL"; + +// JWT with correct kid +// Header: +// {"alg":"HS512","typ":"JWT","kid":"f6a7bd9ffd784388924f126280a746964ba61268"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","exp":1501281058} +const std::string JwtHS512TextWithCorrectKid = + "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImY2YTdiZDlmZmQ3ODQzODg5MjRm" + "MTI2MjgwYTc0Njk2NGJhNjEyNjgifQ." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImV4cCI6MTUwMTI4MTA1OH0." + "YdILUM4zaeIRuxEMLV13qMX3d1sp63juPXwbpOp_HUjNdGGvocthipOxjQur6JtCLmIfvrI4" + "XNrkxVWd-qS_3g"; + +// JWT with existing but incorrect kid +// Header: +// {"alg":"HS256","typ":"JWT","kid":"62a93512c9ee4c7f8067b5a216dade2763d32a47"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","exp":1501281058} +const std::string JwtTextWithIncorrectKid = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjYyYTkzNTEyYzllZTRjN2Y4MDY3" + "YjVhMjE2ZGFkZTI3NjNkMzJhNDcifQ." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImV4cCI6MTUwMTI4MTA1OH0." + "GRLODq7HrBduwUJEoJ3alWlXvxhCZZpFgvd1hYRDXa4"; + +// JWT with nonexist kid +// Header: {"alg":"HS256","typ":"JWT","kid":"blahblahblah"} +// Payload: +// {"iss":"https://example.com","sub":"[email protected]","exp":1501281058} +const std::string JwtTextWithNonExistKid = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJsYWhibGFoYmxhaCJ9." + "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" + "ImV4cCI6MTUwMTI4MTA1OH0." + "WFHsFo29tA5_gT_rzm6WheQhCwwBPrRZWFEAWRF9Ym4"; + +class VerifyJwkHmacTest : public testing::Test { + protected: + void SetUp() { + jwks_ = Jwks::createFrom(SymmetricKeyHMAC, Jwks::Type::JWKS); + EXPECT_EQ(jwks_->getStatus(), Status::Ok); + } + + JwksPtr jwks_; +}; + +TEST_F(VerifyJwkHmacTest, NoKidOK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtTextNoKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + +TEST_F(VerifyJwkHmacTest, NoKidLongExpOK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtTextNoKidLongExp), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); + + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + +TEST_F(VerifyJwkHmacTest, CorrectKidHS256OK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtHS256TextWithCorrectKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); + + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + +TEST_F(VerifyJwkHmacTest, CorrectKidHS384OK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtHS384TextWithCorrectKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); + + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + +TEST_F(VerifyJwkHmacTest, CorrectKidHS512OK) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtHS512TextWithCorrectKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); + + fuzzJwtSignature(jwt, [this](const Jwt& jwt) { + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); + }); +} + +TEST_F(VerifyJwkHmacTest, NonExistKidFail) { + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtTextWithNonExistKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwksKidAlgMismatch); +} + +TEST_F(VerifyJwkHmacTest, OkSymmetricKeyNotAlg) { + // Remove "alg" claim from symmetric key. + std::string alg_claim = R"("alg": "HS256",)"; + std::string symmkey_no_alg = SymmetricKeyHMAC; + std::size_t alg_pos = symmkey_no_alg.find(alg_claim); + while (alg_pos != std::string::npos) { + symmkey_no_alg.erase(alg_pos, alg_claim.length()); + alg_pos = symmkey_no_alg.find(alg_claim); + } + + jwks_ = Jwks::createFrom(symmkey_no_alg, Jwks::Type::JWKS); + EXPECT_EQ(jwks_->getStatus(), Status::Ok); + + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtTextNoKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); +} + +TEST_F(VerifyJwkHmacTest, OkSymmetricKeyNotKid) { + // Remove "kid" claim from symmetric key. + std::string kid_claim1 = + R"("kid": "62a93512c9ee4c7f8067b5a216dade2763d32a47",)"; + std::string kid_claim2 = + R"("kid": "b3319a147514df7ee5e4bcdee51350cc890cc89e",)"; + std::string symmkey_no_kid = SymmetricKeyHMAC; + std::size_t kid_pos = symmkey_no_kid.find(kid_claim1); + symmkey_no_kid.erase(kid_pos, kid_claim1.length()); + kid_pos = symmkey_no_kid.find(kid_claim2); + symmkey_no_kid.erase(kid_pos, kid_claim2.length()); + jwks_ = Jwks::createFrom(symmkey_no_kid, Jwks::Type::JWKS); + EXPECT_EQ(jwks_->getStatus(), Status::Ok); + + Jwt jwt; + EXPECT_EQ(jwt.parseFromString(JwtTextNoKid), Status::Ok); + EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); +} + +} // namespace +} // namespace jwt_verify +} // namespace google
