├── .bazelversion
├── .gitignore
├── OWNERS
├── test
├── fuzz
│ ├── jwt_verify_lib_fuzz_input.proto
│ ├── corpus
│ │ └── jwt_verify_lib_fuzz_test
│ │ │ ├── jwks_okp.txt
│ │ │ ├── jwks_pem.txt
│ │ │ ├── jwks_hmac.txt
│ │ │ ├── jwks_ec.txt
│ │ │ ├── jwks_rsa.txt
│ │ │ └── jwks_x509.txt
│ ├── BUILD
│ ├── jwt_verify_lib_fuzz_test.cc
│ └── corpus_format_test.cc
├── verify_pem_okp_test.cc
├── verify_x509_test.cc
├── check_audience_test.cc
├── jwt_time_test.cc
├── verify_audiences_test.cc
├── verify_pem_rsa_test.cc
├── verify_jwk_okp_test.cc
├── verify_pem_ec_test.cc
├── verify_jwk_hmac_test.cc
├── test_common.h
├── verify_jwk_ec_test.cc
└── verify_jwk_rsa_pss_test.cc
├── .bazelrc
├── libprotobuf_mutator.BUILD
├── SECURITY.md
├── docker
├── README
└── Dockerfile-prow-env
├── script
├── ci.sh
└── check-style
├── googletest.BUILD
├── CONTRIBUTING.md
├── WORKSPACE
├── README.md
├── simple_lru_cache
└── simple_lru_cache.h
├── jwt_verify_lib
├── check_audience.h
├── struct_utils.h
├── jwks.h
├── jwt.h
├── verify.h
└── status.h
├── src
├── check_audience.cc
├── struct_utils.cc
├── jwt.cc
├── status.cc
├── verify.cc
└── jwks.cc
├── repositories.bzl
├── BUILD
└── LICENSE
/.bazelversion:
--------------------------------------------------------------------------------
1 | 5.3.2
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bazel-*
2 | .vscode/
3 |
--------------------------------------------------------------------------------
/OWNERS:
--------------------------------------------------------------------------------
1 | # See the OWNERS docs at https://go.k8s.io/owners
2 | approvers:
3 | - qiwzhang
4 | - nareddyt
5 |
6 | reviewers:
7 | - orionHong
8 |
--------------------------------------------------------------------------------
/test/fuzz/jwt_verify_lib_fuzz_input.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package google.jwt_verify;
4 |
5 | message FuzzInput {
6 | string jwt = 1;
7 | string jwks = 2;
8 | }
9 |
--------------------------------------------------------------------------------
/test/fuzz/corpus/jwt_verify_lib_fuzz_test/jwks_okp.txt:
--------------------------------------------------------------------------------
1 | jwt: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImFiYyJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9.n7Jd_zwXE03FFDrjdxDP3CYJqAlFXCa3jbv8qER_Z5cmisGJ3_gEb2j1IALPtLA8TsYxQJ4Xxfucen9nFqxUBg"
2 | jwks: '{"keys":[{"kty":"OKP","crv":"Ed25519","alg":"EdDSA","kid":"abc","x":"6hH43mEbo-h7iigPm9zLKHH5oEc-bjIXD_t4PLPqHLQ"},{"kty":"OKP","crv":"Ed25519","alg":"EdDSA","kid":"xyz","x":"6hH43mEbo-h7iigPm9zLKHH5oEc-bjIXD_t4PLPqHLQ"}]}'
3 |
4 |
--------------------------------------------------------------------------------
/.bazelrc:
--------------------------------------------------------------------------------
1 | # Match Envoy's toolchain.
2 | build --cxxopt=-std=c++17 --host_cxxopt=-std=c++17
3 |
4 | # Force the use of Clang for C++ builds.
5 | build --action_env=CC=clang-13
6 | build --action_env=CXX=clang++-13
7 |
8 | # Define the --config=asan-libfuzzer configuration.
9 | build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
10 | build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
11 | build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
12 |
--------------------------------------------------------------------------------
/libprotobuf_mutator.BUILD:
--------------------------------------------------------------------------------
1 | licenses(["notice"]) # Apache 2
2 |
3 | cc_library(
4 | name = "libprotobuf_mutator",
5 | srcs = glob(
6 | [
7 | "src/**/*.cc",
8 | "src/**/*.h",
9 | "port/protobuf.h",
10 | ],
11 | exclude = ["**/*_test.cc"],
12 | ),
13 | hdrs = ["src/libfuzzer/libfuzzer_macro.h"],
14 | include_prefix = "libprotobuf_mutator",
15 | includes = ["."],
16 | visibility = ["//visibility:public"],
17 | deps = ["//external:protobuf"],
18 | )
19 |
--------------------------------------------------------------------------------
/test/fuzz/corpus/jwt_verify_lib_fuzz_test/jwks_pem.txt:
--------------------------------------------------------------------------------
1 | jwt: "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9.AMkxbTVhrtnX0Ylc8hI0nQFQkRhExqaQccHNJLL9aQd_0wlcZ8GHcXOaeKz8krRjxYw2kjHxg3Ng5Xtt7O_2AWN6AJ2FZ_742UKCFsCtCfZFP58d7UoTN7yZ8D4kmRCnh0GefX7z97eBCmMGmbSkCb87yGuDvxd1QlKiva1kkMGHCldt"
2 | jwks: "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBuKOJ7HuvPu379mP8To6jQ5d70L/4wJIP45aKjXkBNAaMf1altaM3rZmqutT+fAmKqrDHpmxgqFRz8nc9Foxt1UIAFxaljCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B/bQ91mShCiR/43Whsn1P7Gz30WEnLuJs1SGVz1oT4lIRUYni2OfIk=\n-----END PUBLIC KEY-----"
3 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Follow envoy security policy
4 |
5 | This code is mainly used by [envoy](https://github.com/envoyproxy/envoy). Please follow envoy security [policy](https://github.com/envoyproxy/envoy/security/policy).
6 |
7 |
8 | ## Supported Versions
9 |
10 | This repo is small, there is not versioned releases. Please always use top of the tree. If there is any bugs or security fixes, they will be fixed in the master branch.
11 |
12 |
13 | ## Reporting a Vulnerability
14 |
15 | Please report any vulnerability to envoy according to its security [policy](https://github.com/envoyproxy/envoy/security/policy).
16 |
--------------------------------------------------------------------------------
/docker/README:
--------------------------------------------------------------------------------
1 | ## Docker images for jwt_verify_lib
2 |
3 | ### How to build the image
4 | ```sh
5 | docker build -f ${DOCKER_FILE_NAME} -t ${IMAGE_TAG} .
6 | ```
7 | We can also inspect the image by running
8 | ```sh
9 | docker run -it --entrypoint /bin/sh ${IMAGE_TAG}
10 | ```
11 |
12 | ### How to push the image
13 | Please refer to [Docker official
14 | documentation](https://docs.docker.com/engine/reference/commandline/push/#push-a-new-image-to-a-registry).
15 |
16 | #### Dockerfile-prow-env
17 | This image is used to run OSS Prow CI jobs.
18 | ```sh
19 | # Build image
20 | docker build -f Dockerfile-prow-env -t gcr.io/cloudesf-testing/jwt-verify-lib-prow:v{YYYYMMDD} .
21 | # Push image to GCR
22 | docker image push gcr.io/cloudesf-testing/jwt-verify-lib-prow:v{YYYYMMDD}
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/test/fuzz/corpus/jwt_verify_lib_fuzz_test/jwks_hmac.txt:
--------------------------------------------------------------------------------
1 | jwt: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0.4tc7M-gJizpbB69_sQi7E0ym0np6uon4V41hVjYV2ic"
2 | jwks: '{"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"}]}'
3 |
--------------------------------------------------------------------------------
/script/ci.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2022 Google Inc.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | ################################################################################
18 |
19 | # Check code format before running build and test
20 | ./script/check-style
21 |
22 | # Build and test the entire repo
23 | bazel build //...
24 | bazel test //...
25 |
--------------------------------------------------------------------------------
/googletest.BUILD:
--------------------------------------------------------------------------------
1 |
2 | cc_library(
3 | name = "googletest",
4 | srcs = [
5 | "googletest/src/gtest-all.cc",
6 | "googlemock/src/gmock-all.cc",
7 | ],
8 | hdrs = glob([
9 | "googletest/include/**/*.h",
10 | "googlemock/include/**/*.h",
11 | "googletest/src/*.cc",
12 | "googletest/src/*.h",
13 | "googlemock/src/*.cc",
14 | ]),
15 | includes = [
16 | "googlemock",
17 | "googletest",
18 | "googletest/include",
19 | "googlemock/include",
20 | ],
21 | visibility = ["//visibility:public"],
22 | )
23 |
24 | cc_library(
25 | name = "googletest_main",
26 | srcs = ["googlemock/src/gmock_main.cc"],
27 | visibility = ["//visibility:public"],
28 | deps = [":googletest"],
29 | )
30 |
31 | cc_library(
32 | name = "googletest_prod",
33 | hdrs = [
34 | "googletest/include/gtest/gtest_prod.h",
35 | ],
36 | includes = [
37 | "googletest/include",
38 | ],
39 | visibility = ["//visibility:public"],
40 | )
41 |
--------------------------------------------------------------------------------
/test/fuzz/corpus/jwt_verify_lib_fuzz_test/jwks_ec.txt:
--------------------------------------------------------------------------------
1 | jwt: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYyJ9.eyJpc3MiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.T2KAwChqgo2ZSXyLh3IcMBQNSeRZRe5Z-MUDl-s-F99XGoyutqA6lq8bKZ6vmjZAlpVG8AGRZW9JGp9lq3cbEw"
2 | jwks: '{"keys":[{"kty":"EC","crv":"P-256","alg":"ES256","kid":"abc","x":"EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k","y":"92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8"},{"kty":"EC","crv":"P-256","alg":"ES256","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"}]}'
3 |
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/WORKSPACE:
--------------------------------------------------------------------------------
1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
2 |
3 | http_archive(
4 | name = "rules_fuzzing",
5 | sha256 = "d9002dd3cd6437017f08593124fdd1b13b3473c7b929ceb0e60d317cb9346118",
6 | strip_prefix = "rules_fuzzing-0.3.2",
7 | urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"],
8 | )
9 |
10 | load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
11 | rules_fuzzing_dependencies()
12 |
13 | load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
14 | rules_fuzzing_init()
15 |
16 | load(
17 | "//:repositories.bzl",
18 | "boringssl_repositories",
19 | "googletest_repositories",
20 | "abseil_repositories",
21 | "protobuf_repositories",
22 | "libprotobuf_mutator_repositories",
23 | )
24 |
25 | boringssl_repositories()
26 | googletest_repositories()
27 | abseil_repositories()
28 | protobuf_repositories()
29 | libprotobuf_mutator_repositories()
30 |
31 | load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
32 |
33 | protobuf_deps()
34 |
35 | load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
36 |
37 | rules_proto_dependencies()
38 |
39 | rules_proto_toolchains()
40 |
--------------------------------------------------------------------------------
/test/fuzz/BUILD:
--------------------------------------------------------------------------------
1 | load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
2 | # load cc_proto_library
3 |
4 | licenses(["notice"])
5 |
6 | package(default_visibility = ["//visibility:public"])
7 |
8 | proto_library(
9 | name = "jwt_verify_lib_fuzz_input_proto",
10 | testonly = 1,
11 | srcs = ["jwt_verify_lib_fuzz_input.proto"],
12 | )
13 |
14 | cc_proto_library(
15 | name = "jwt_verify_lib_fuzz_input_cc_proto",
16 | testonly = 1,
17 | deps = [":jwt_verify_lib_fuzz_input_proto"],
18 | )
19 |
20 | cc_fuzz_test(
21 | name = "jwt_verify_lib_fuzz_test",
22 | testonly = 1,
23 | srcs = [
24 | "jwt_verify_lib_fuzz_test.cc",
25 | ],
26 | corpus = glob(["corpus/jwt_verify_lib_fuzz_test/*"]),
27 | deps = [
28 | ":jwt_verify_lib_fuzz_input_cc_proto",
29 | "//:jwt_verify_lib",
30 | "//external:libprotobuf_mutator",
31 | ],
32 | )
33 |
34 | cc_test(
35 | name = "corpus_format_test",
36 | testonly = 1,
37 | srcs = [
38 | "corpus_format_test.cc",
39 | ],
40 | data = glob(["corpus/jwt_verify_lib_fuzz_test/*"]),
41 | deps = [
42 | ":jwt_verify_lib_fuzz_input_cc_proto",
43 | "//:jwt_verify_lib",
44 | "//external:abseil_strings",
45 | "//external:googletest_main",
46 | ],
47 | )
48 |
--------------------------------------------------------------------------------
/test/fuzz/corpus/jwt_verify_lib_fuzz_test/jwks_rsa.txt:
--------------------------------------------------------------------------------
1 | jwt: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0.n45uWZfIBZwCIPiL0K8Ca3tmm-ZlsDrC79_vXCspPwk5oxdSn983tuC9GfVWKXWUMHe11DsB02b19Ow-fmoEzooTFn65Ml7G34nW07amyM6lETiMhNzyiunctplOr6xKKJHmzTUhfTirvDeG-q9n24-8lH7GP8GgHvDlgSM9OY7TGp81bRcnZBmxim_UzHoYO3_c8OP4ZX3xG5PfihVk5G0g6wcHrO70w0_64JgkKRCrLHMJSrhIgp9NHel_CNOnL0AjQKe9IGblJrMuouqYYS0zEWwmOVUWUSxQkoLpldQUVefcfjQeGjz8IlvktRa77FYexfP590ACPyXrivtsxg"
2 | jwks: '{"keys":[{"kty":"RSA","alg":"RS256","use":"sig","kid":"62a93512c9ee4c7f8067b5a216dade2763d32a47","n":"0YWnm_eplO9BFtXszMRQNL5UtZ8HJdTH2jK7vjs4XdLkPW7YBkkm_2xNgcaVpkW0VT2l4mU3KftR-6s3Oa5Rnz5BrWEUkCTVVolR7VYksfqIB2I_x5yZHdOiomMTcm3DheUUCgbJRv5OKRnNqszA4xHn3tA3Ry8VO3X7BgKZYAUh9fyZTFLlkeAh0-bLK5zvqCmKW5QgDIXSxUTJxPjZCgfx1vmAfGqaJb-nvmrORXQ6L284c73DUL7mnt6wj3H6tVqPKA27j56N0TB1Hfx4ja6Slr8S4EB3F1luYhATa1PKUSH8mYDW11HolzZmTQpRoLV8ZoHbHEaTfqX_aYahIw","e":"AQAB"},{"kty":"RSA","alg":"RS256","use":"sig","kid":"b3319a147514df7ee5e4bcdee51350cc890cc89e","n":"qDi7Tx4DhNvPQsl1ofxxc2ePQFcs-L0mXYo6TGS64CY_2WmOtvYlcLNZjhuddZVV2X88m0MfwaSA16wE-RiKM9hqo5EY8BPXj57CMiYAyiHuQPp1yayjMgoE1P2jvp4eqF-BTillGJt5W5RuXti9uqfMtCQdagB8EC3MNRuU_KdeLgBy3lS3oo4LOYd-74kRBVZbk2wnmmb7IhP9OoLc1-7-9qU1uhpDxmE6JwBau0mDSwMnYDS4G_ML17dC-ZDtLd1i24STUw39KH0pcSdfFbL2NtEZdNeam1DDdk0iUtJSPZliUHJBI_pj8M-2Mn_oA8jBuI8YKwBqYkZCN1I95Q","e":"AQAB"}]}'
3 |
--------------------------------------------------------------------------------
/test/fuzz/jwt_verify_lib_fuzz_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2022 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.#pragma once
14 |
15 | #include
16 | #include
17 | #include
18 |
19 | #include "jwt_verify_lib/jwks.h"
20 | #include "jwt_verify_lib/jwt.h"
21 | #include "jwt_verify_lib/verify.h"
22 | #include "src/libfuzzer/libfuzzer_macro.h"
23 | #include "test/fuzz/jwt_verify_lib_fuzz_input.pb.h"
24 |
25 | namespace google {
26 | namespace jwt_verify {
27 | namespace {
28 |
29 | DEFINE_PROTO_FUZZER(const FuzzInput& input) {
30 | Jwt jwt;
31 | auto jwt_status = jwt.parseFromString(input.jwt());
32 |
33 | auto jwks1 = Jwks::createFrom(input.jwks(), Jwks::JWKS);
34 | auto jwks2 = Jwks::createFrom(input.jwks(), Jwks::PEM);
35 |
36 | if (jwt_status == Status::Ok) {
37 | if (jwks1->getStatus() == Status::Ok) {
38 | verifyJwt(jwt, *jwks1);
39 | }
40 | if (jwks2->getStatus() == Status::Ok) {
41 | verifyJwt(jwt, *jwks2);
42 | }
43 | }
44 | }
45 |
46 | } // namespace
47 | } // namespace jwt_verify
48 | } // namespace google
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://testgrid.k8s.io/googleoss-jwt-verify-lib#Summary)
2 |
3 | This repository stores JWT verification files for c++.
4 | These files are originally created in [istio/proxy jwt_auth folder](https://github.com/istio/proxy/blob/master/src/envoy/http/jwt_auth/jwt.h).
5 | The key reason to create a separate repo for them is that they can be used by other projects. For example, [envoyproxy](https://github.com/envoyproxy/envoy) likes to use these code to build a jwt_auth HTTP filter.
6 |
7 | This is not an officially supported Google product
8 |
9 | For contributors:
10 | If you make any changes, please make sure to use Bazel to pass all unit tests by running:
11 |
12 | ```
13 | bazel test //...
14 | ```
15 | Please format your codes by running:
16 |
17 | ```
18 | script/check-style
19 | ```
20 |
21 | ## Continuous Integration
22 | This repository is integreated with [OSS Prow](https://github.com/GoogleCloudPlatform/oss-test-infra), and the job setup is in the [OSS Prow repo](https://github.com/GoogleCloudPlatform/oss-test-infra/blob/master/prow/prowjobs/google/jwt_verify_lib/jwt-verify-lib-presubmit.yaml). Currently, Prow runs the [presubmit script](./script/ci.sh) on each Pull Request to verify tests pass. Note:
23 | - PR submission is only allowed if the job passes.
24 | - If you are an outside contributor, Prow may not run until a Googler LGTMs.
25 |
26 | The CI is running in a [docker image](./docker/Dockerfile-prow-env) that was pre-built and pushed to Google Cloud Registry. For future updates to the CI image, please refer to the commands listed in [./docker folder README](./docker/README).
27 |
--------------------------------------------------------------------------------
/test/fuzz/corpus/jwt_verify_lib_fuzz_test/jwks_x509.txt:
--------------------------------------------------------------------------------
1 | jwt: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjgyY2ZkNzk3OTAzMDYzYTBiNzhjZTFjYmY1ZTJmZTAzNmE2ZGUyNDIifQ.eyJpc3MiOiJmYWtlLmlzc3VlciIsImlhdCI6MTU3ODQ0ODEwNiwiYXVkIjoiZmFrZS5hdWRpZW5jZSIsImV4cCI6MTU3ODQ1MTcwNiwic3ViIjoiZmFrZS5pc3N1ZXIifQ.YeKxQvJurL2XHbLdV5pAYW1daumTyLS1ShtEqYJJvMV3kkAG0HRspxrUwkRqRsnchaWjkxzcj-cKg-38LM_hfRIAJ9kyjoiESmD8Oq2cMK_go8Ejq_6YS9CqEnqzR99RRL1iZRVgzj8Wgv7vL3sbbaOBANNmVLr9E5Y2_3_-SMYev586yQOkXkV3J7HaW5avCWl0bJAbR_-gB2Ku2-Em-12-Wh20_NuKIBje0OkkJm0YFHdtm_EBFoc54yQX9yrlyHCEYv7nLvoXZD268j7Xw5_FFhAuxeSS5FKFOxiBEEowSLFw3IgRqFaMfl_qvQQWNDBQBziIhbyLujpy4ZnmYw"
2 | jwks: '{"82cfd797903063a0b78ce1cbf5e2fe036a6de242": "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIIEN2Xgd3Y1CMwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UE\nAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MB4XDTE5MDIyNzE3NTA1N1oXDTI5MDIy\nNDE3NTA1N1owIDEeMBwGA1UEAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA00bLFfPv/jeyVU6xuStcwHdSBa+m\nlOX/9oWFwMsQucENe+QYKJmkAqdATz3BKJ354iknMy556Y8cBHbZa9X6gxi2BIPW\nzkuKTruDJrQrg6cgR6RHZ9WNoxGLRtyhq8PimV8DVtMSLYVy3p/gMwEtuQY4jiXS\nhhvCZxuJZIJnabNqTU5AGWfduQgDcLRd25cShKxDNOtfcBWQ+ZQWt5qkZGz5XFQ/\nt1+bND+hA3dC3bwLc9yFrgU+Z+XEDQErq4OG9MVezw6h6Imn6gkrdSyG1k9BjPsf\n4senqDXgtK2Iz9MuGIWcG62wV2a7qJYjnGBJfI4QKQBEdsYbuUel2wB0wQIDAQAB\nozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK\nBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEArrvMP0yrPQlCC/QB0iPxb4TY\nPPiDTuY4fPytUQgvSdQ4rMPSNZafe7tIS+0KDhZtblepaS5whVobVh9lS2bK+rDH\nRsM/H9XRGpyh2rJ6NYUbiyEMQ4jfNh99A02Nsz4Gaed3IE8Hml2pWLcCbp2VGDEN\nr6qrBVVWsaT736/kwVNp14S6FNhVIx1pZeKJrtOsJD+Y4f21WKlWdKdu4QVlxJoE\n9LtFur56aLhDA64D5GPjQnatRyShcWXvgEvUk5YUuBkjTDL1HSNTeqTdG6j8OEZo\nBuyfyPz4yV6BjnJWl2fk8v+9sB1B6m5LoR7ETHlWwh+elmaejFQCJN1+ED8k0w==\n-----END CERTIFICATE-----\n"}'
3 |
--------------------------------------------------------------------------------
/simple_lru_cache/simple_lru_cache.h:
--------------------------------------------------------------------------------
1 | /* Copyright 2016 Google Inc. All Rights Reserved.
2 | Licensed under the Apache License, Version 2.0 (the "License");
3 | you may not use this file except in compliance with the License.
4 | You may obtain a copy of the License at
5 | http://www.apache.org/licenses/LICENSE-2.0
6 | Unless required by applicable law or agreed to in writing, software
7 | distributed under the License is distributed on an "AS IS" BASIS,
8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | See the License for the specific language governing permissions and
10 | limitations under the License.
11 | ==============================================================================*/
12 |
13 | // For inclusion in .h files. The real class definition is in
14 | // simple_lru_cache_inl.h.
15 |
16 | #pragma once
17 |
18 | #include
19 |
20 | #include "absl/container/flat_hash_map.h" // for hash<>
21 |
22 | namespace google {
23 | namespace simple_lru_cache {
24 |
25 | namespace internal {
26 | template
27 | struct SimpleLRUHash : public std::hash {};
28 | } // namespace internal
29 |
30 | template ,
32 | typename EQ = std::equal_to>
33 | class SimpleLRUCache;
34 |
35 | // Deleter is a functor that defines how to delete a Value*. That is, it
36 | // contains a public method:
37 | // operator() (Value* value)
38 | // See example in the associated unittest.
39 | template ,
41 | typename EQ = std::equal_to>
42 | class SimpleLRUCacheWithDeleter;
43 |
44 | } // namespace simple_lru_cache
45 | } // namespace google
--------------------------------------------------------------------------------
/docker/Dockerfile-prow-env:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | FROM debian:bookworm
16 |
17 | LABEL maintainer="cloud-esf-dev@google.com"
18 |
19 | # add env we can debug with the image name:tag
20 | ARG IMAGE_ARG
21 | ENV IMAGE=${IMAGE_ARG}
22 |
23 |
24 | RUN apt-get update -y
25 | RUN apt-get -y install \
26 | wget make cmake python3 python3-pip pkg-config coreutils \
27 | zlib1g-dev curl libtool automake zip time rsync ninja-build \
28 | git bash-completion jq default-jdk python3-distutils libicu-dev libbrotli-dev
29 |
30 | # # install Bazelisk
31 | RUN wget -O /usr/local/bin/bazelisk https://github.com/bazelbuild/bazelisk/releases/download/v1.15.0/bazelisk-linux-amd64 && \
32 | chmod +x /usr/local/bin/bazelisk && \
33 | cp /usr/local/bin/bazelisk /usr/local/bin/bazel
34 |
35 | # install clang-13 and associated tools
36 | RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
37 | echo "deb https://apt.llvm.org/buster/ llvm-toolchain-buster-13 main" >> /etc/apt/sources.list && \
38 | apt-get update && \
39 | apt-get install -y llvm-13 llvm-13-dev libclang-13-dev clang-13 \
40 | lld-13 clang-tools-13 clang-format-13 libc++-dev xz-utils
41 |
42 | ENV CC clang-13
43 | ENV CXX clang++-13
44 |
45 |
--------------------------------------------------------------------------------
/jwt_verify_lib/check_audience.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.#pragma once
14 |
15 | #pragma once
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include "jwt_verify_lib/status.h"
23 |
24 | namespace google {
25 | namespace jwt_verify {
26 |
27 | /**
28 | * RFC for JWT `aud `_ only
29 | * specifies case sensitive comparison. But experiences showed that users
30 | * easily add wrong scheme and tailing slash to cause mis-match.
31 | * In this implemeation, scheme portion of URI and tailing slash is removed
32 | * before comparison.
33 | */
34 | class CheckAudience {
35 | public:
36 | // Construct the object with a list audiences from config.
37 | CheckAudience(const std::vector& config_audiences);
38 |
39 | // Check any of jwt_audiences is matched with one of configurated ones.
40 | bool areAudiencesAllowed(const std::vector& jwt_audiences) const;
41 |
42 | // check if config audiences is empty
43 | bool empty() const { return config_audiences_.empty(); }
44 |
45 | private:
46 | // configured audiences;
47 | std::set config_audiences_;
48 | };
49 |
50 | typedef std::unique_ptr CheckAudiencePtr;
51 |
52 | } // namespace jwt_verify
53 | } // namespace google
54 |
--------------------------------------------------------------------------------
/jwt_verify_lib/struct_utils.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.#pragma once
14 |
15 | #pragma once
16 |
17 | #include "google/protobuf/struct.pb.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 |
22 | class StructUtils {
23 | public:
24 | StructUtils(const ::google::protobuf::Struct& struct_pb);
25 |
26 | enum FindResult {
27 | OK = 0,
28 | MISSING,
29 | WRONG_TYPE,
30 | OUT_OF_RANGE,
31 | };
32 |
33 | FindResult GetString(const std::string& name, std::string* str_value);
34 |
35 | // Return error if the JSON value is not within a positive 64 bit integer
36 | // range. The decimals in the JSON value are dropped.
37 | FindResult GetUInt64(const std::string& name, uint64_t* int_value);
38 |
39 | FindResult GetDouble(const std::string& name, double* double_value);
40 |
41 | FindResult GetBoolean(const std::string& name, bool* bool_value);
42 |
43 | // Get string or list of string, designed to get "aud" field
44 | // "aud" can be either string array or string.
45 | // Try as string array, read it as empty array if doesn't exist.
46 | FindResult GetStringList(const std::string& name,
47 | std::vector* list);
48 |
49 | // Find the value with nested names.
50 | FindResult GetValue(const std::string& nested_names,
51 | const google::protobuf::Value*& found);
52 |
53 | private:
54 | const ::google::protobuf::Struct& struct_pb_;
55 | };
56 |
57 | } // namespace jwt_verify
58 | } // namespace google
59 |
--------------------------------------------------------------------------------
/src/check_audience.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "jwt_verify_lib/check_audience.h"
16 |
17 | #include "absl/strings/match.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // HTTP Protocol scheme prefix in JWT aud claim.
24 | constexpr absl::string_view HTTPSchemePrefix("http://");
25 |
26 | // HTTPS Protocol scheme prefix in JWT aud claim.
27 | constexpr absl::string_view HTTPSSchemePrefix("https://");
28 |
29 | std::string sanitizeAudience(const std::string& aud) {
30 | if (aud.empty()) {
31 | return aud;
32 | }
33 |
34 | size_t beg_pos = 0;
35 | bool sanitized = false;
36 | // Point beg to first character after protocol scheme prefix in audience.
37 | if (absl::StartsWith(aud, HTTPSchemePrefix)) {
38 | beg_pos = HTTPSchemePrefix.size();
39 | sanitized = true;
40 | } else if (absl::StartsWith(aud, HTTPSSchemePrefix)) {
41 | beg_pos = HTTPSSchemePrefix.size();
42 | sanitized = true;
43 | }
44 |
45 | // Point end to trailing slash in aud.
46 | size_t end_pos = aud.length();
47 | if (aud[end_pos - 1] == '/') {
48 | --end_pos;
49 | sanitized = true;
50 | }
51 | if (sanitized) {
52 | return aud.substr(beg_pos, end_pos - beg_pos);
53 | }
54 | return aud;
55 | }
56 |
57 | } // namespace
58 |
59 | CheckAudience::CheckAudience(const std::vector& config_audiences) {
60 | for (const auto& aud : config_audiences) {
61 | config_audiences_.insert(sanitizeAudience(aud));
62 | }
63 | }
64 |
65 | bool CheckAudience::areAudiencesAllowed(
66 | const std::vector& jwt_audiences) const {
67 | if (config_audiences_.empty()) {
68 | return true;
69 | }
70 | for (const auto& aud : jwt_audiences) {
71 | if (config_audiences_.find(sanitizeAudience(aud)) !=
72 | config_audiences_.end()) {
73 | return true;
74 | }
75 | }
76 | return false;
77 | }
78 |
79 | } // namespace jwt_verify
80 | } // namespace google
81 |
--------------------------------------------------------------------------------
/script/check-style:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | ################################################################################
18 | #
19 | ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
20 |
21 | CLANG_VERSION_REQUIRED="13.0.0"
22 | CLANG_FORMAT=$(which clang-format-${CLANG_VERSION_REQUIRED%%.*})
23 | if [[ ! -x "${CLANG_FORMAT}" ]]; then
24 | # Install required clang version to a folder and cache it.
25 | CLANG_DIRECTORY="${HOME}/clang"
26 | CLANG_FORMAT="${CLANG_DIRECTORY}/bin/clang-format"
27 |
28 | if [ "$(uname)" == "Darwin" ]; then
29 | CLANG_BIN="x86_64-apple-darwin.tar.xz"
30 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
31 | CLANG_BIN="x86_64-linux-gnu-ubuntu-16.04.tar.xz"
32 | else
33 | echo "Unsupported environment." ; exit 1 ;
34 | fi
35 |
36 | echo "clang-bin: https://github.com/llvm/llvm-project/releases/download/llvmorg-${CLANG_VERSION_REQUIRED}/clang+llvm-${CLANG_VERSION_REQUIRED}-${CLANG_BIN}"
37 |
38 | CLANG_VERSION="$(${CLANG_FORMAT} -version | cut -d ' ' -f 3)"
39 | if [[ "${CLANG_VERSION}" != "${CLANG_VERSION_REQUIRED}" ]]; then
40 | echo "Installing required clang-format ${CLANG_VERSION_REQUIRED} to ${CLANG_DIRECTORY}"
41 | mkdir -p ${CLANG_DIRECTORY}
42 | curl --show-error --retry 10 \
43 | -L "https://github.com/llvm/llvm-project/releases/download/llvmorg-${CLANG_VERSION_REQUIRED}/clang+llvm-${CLANG_VERSION_REQUIRED}-${CLANG_BIN}" \
44 | | tar Jx -C "${CLANG_DIRECTORY}" --strip=1 \
45 | || { echo "Could not install required clang-format. Skip formating." ; exit 0 ; }
46 | fi
47 | fi
48 |
49 | echo "Checking file format ..."
50 |
51 | pushd ${ROOT} > /dev/null
52 |
53 | SOURCE_FILES=($(git ls-tree -r HEAD --name-only | grep -E '\.(h|c|cc|proto)$'))
54 | "${CLANG_FORMAT}" -style=Google -i "${SOURCE_FILES[@]}" \
55 | || { echo "Could not run clang-format." ; exit 1 ; }
56 |
57 | CHANGED_FILES=($(git diff HEAD --name-only | grep -E '\.(h|c|cc|proto)$'))
58 |
59 | if [[ "${#CHANGED_FILES}" -ne 0 ]]; then
60 | echo "Files not formatted: ${CHANGED_FILES[@]}"
61 | exit 1
62 | fi
63 | echo "All files are properly formatted."
64 |
65 | popd
66 |
--------------------------------------------------------------------------------
/jwt_verify_lib/jwks.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.#pragma once
14 |
15 | #pragma once
16 |
17 | #include
18 | #include
19 |
20 | #include "jwt_verify_lib/status.h"
21 | #include "openssl/ec.h"
22 | #include "openssl/evp.h"
23 | #include "openssl/pem.h"
24 |
25 | namespace google {
26 | namespace jwt_verify {
27 |
28 | /**
29 | * Class to parse and a hold JSON Web Key Set.
30 | *
31 | * Usage example:
32 | * JwksPtr keys = Jwks::createFrom(jwks_string, type);
33 | * if (keys->getStatus() == Status::Ok) { ... }
34 | */
35 | class Jwks : public WithStatus {
36 | public:
37 | // Format of public key.
38 | enum Type { JWKS, PEM };
39 |
40 | // Create from string
41 | static std::unique_ptr createFrom(const std::string& pkey, Type type);
42 | // Executes to createFrom with type=PEM and sets additional JWKS paramaters
43 | // not specified within the PEM.
44 | static std::unique_ptr createFromPem(const std::string& pkey,
45 | const std::string& kid,
46 | const std::string& alg);
47 |
48 | // Adds a key to this keyset.
49 | Status addKeyFromPem(const std::string& pkey, const std::string& kid,
50 | const std::string& alg);
51 |
52 | // Struct for JSON Web Key
53 | struct Pubkey {
54 | std::string hmac_key_;
55 | std::string kid_;
56 | std::string kty_;
57 | std::string alg_;
58 | std::string crv_;
59 | bssl::UniquePtr rsa_;
60 | bssl::UniquePtr ec_key_;
61 | std::string okp_key_raw_;
62 | bssl::UniquePtr bio_;
63 | bssl::UniquePtr x509_;
64 | };
65 | typedef std::unique_ptr PubkeyPtr;
66 |
67 | // Access to list of Jwks
68 | const std::vector& keys() const { return keys_; }
69 |
70 | private:
71 | // Create Jwks
72 | void createFromJwksCore(const std::string& pkey_jwks);
73 | // Create PEM
74 | void createFromPemCore(const std::string& pkey_pem);
75 |
76 | // List of Jwks
77 | std::vector keys_;
78 | };
79 |
80 | typedef std::unique_ptr JwksPtr;
81 |
82 | } // namespace jwt_verify
83 | } // namespace google
84 |
--------------------------------------------------------------------------------
/test/fuzz/corpus_format_test.cc:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "absl/strings/str_cat.h"
4 | #include "google/protobuf/text_format.h"
5 | #include "gtest/gtest.h"
6 | #include "jwt_verify_lib/jwks.h"
7 | #include "jwt_verify_lib/jwt.h"
8 | #include "jwt_verify_lib/verify.h"
9 | #include "test/fuzz/jwt_verify_lib_fuzz_input.pb.h"
10 |
11 | namespace google {
12 | namespace jwt_verify {
13 | namespace {
14 |
15 | const absl::string_view kRunfilesDir = std::getenv("TEST_SRCDIR");
16 | const absl::string_view kWorkingDir = std::getenv("TEST_WORKSPACE");
17 | constexpr absl::string_view kDataDir =
18 | "test/fuzz/corpus/jwt_verify_lib_fuzz_test";
19 |
20 | std::string ReadTestBaseline(const std::string& input_file_name) {
21 | // Must reference testdata with an absolute path.
22 | std::string file_name = absl::StrCat(kRunfilesDir, "/", kWorkingDir, "/",
23 | kDataDir, "/", input_file_name);
24 |
25 | std::string contents;
26 | std::ifstream input_file;
27 | input_file.open(file_name, std::ifstream::in | std::ifstream::binary);
28 | EXPECT_TRUE(input_file.is_open()) << file_name;
29 | input_file.seekg(0, std::ios::end);
30 | contents.reserve(input_file.tellg());
31 | input_file.seekg(0, std::ios::beg);
32 | contents.assign((std::istreambuf_iterator(input_file)),
33 | (std::istreambuf_iterator()));
34 |
35 | return contents;
36 | }
37 |
38 | // Each corpus file has "jwt" and "jwks". If they are valid and
39 | // "jwks" can be used to verify "jwt", they will help fuzz engine
40 | // to be more efficient.
41 | // This test verifies the following corpus files satisfy above conditions.
42 | TEST(JwksParseTest, FuzzTestJwksCorpusFile) {
43 | std::vector files = {"jwks_ec.txt", "jwks_rsa.txt",
44 | "jwks_hmac.txt", "jwks_okp.txt",
45 | "jwks_x509.txt", "jwks_pem.txt"};
46 | for (const auto& file : files) {
47 | const std::string txt = ReadTestBaseline(file);
48 | FuzzInput input;
49 | EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(txt, &input))
50 | << "failed to parse corpus file: " << file;
51 |
52 | Jwt jwt;
53 | EXPECT_EQ(jwt.parseFromString(input.jwt()), Status::Ok)
54 | << "failed to parse jwt in corpus file: " << file;
55 |
56 | const Jwks::Type jwks_type =
57 | (file == "jwks_pem.txt") ? Jwks::PEM : Jwks::JWKS;
58 | auto jwks = Jwks::createFrom(input.jwks(), jwks_type);
59 | EXPECT_EQ(jwks->getStatus(), Status::Ok)
60 | << "failed to parse jwks in corpusfile: " << file;
61 |
62 | // Use to timestamp "1", not to verify expiration.
63 | EXPECT_EQ(getStatusString(verifyJwt(jwt, *jwks, 1)),
64 | getStatusString(Status::Ok))
65 | << "failed to verify in corpus file: " << file;
66 | }
67 | }
68 |
69 | } // namespace
70 | } // namespace jwt_verify
71 | } // namespace google
72 |
--------------------------------------------------------------------------------
/jwt_verify_lib/jwt.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.#pragma once
14 |
15 | #pragma once
16 |
17 | #include
18 | #include
19 |
20 | #include "google/protobuf/struct.pb.h"
21 | #include "jwt_verify_lib/status.h"
22 |
23 | namespace google {
24 | namespace jwt_verify {
25 |
26 | // Clock skew defaults to one minute.
27 | constexpr uint64_t kClockSkewInSecond = 60;
28 |
29 | /**
30 | * struct to hold a JWT data.
31 | */
32 | struct Jwt {
33 | // entire jwt
34 | std::string jwt_;
35 |
36 | // header string
37 | std::string header_str_;
38 | // header base64_url encoded
39 | std::string header_str_base64url_;
40 | // header in Struct protobuf
41 | ::google::protobuf::Struct header_pb_;
42 |
43 | // payload string
44 | std::string payload_str_;
45 | // payload base64_url encoded
46 | std::string payload_str_base64url_;
47 | // payload in Struct protobuf
48 | ::google::protobuf::Struct payload_pb_;
49 | // signature string
50 | std::string signature_;
51 | // alg
52 | std::string alg_;
53 | // kid
54 | std::string kid_;
55 | // iss
56 | std::string iss_;
57 | // audiences
58 | std::vector audiences_;
59 | // sub
60 | std::string sub_;
61 | // issued at
62 | uint64_t iat_ = 0;
63 | // not before
64 | uint64_t nbf_ = 0;
65 | // expiration
66 | uint64_t exp_ = 0;
67 | // JWT ID
68 | std::string jti_;
69 |
70 | /**
71 | * Standard constructor.
72 | */
73 | Jwt() {}
74 | /**
75 | * Copy constructor. The copy constructor is marked as explicit as the caller
76 | * should understand the copy operation is non-trivial as a complete
77 | * re-deserialization occurs.
78 | * @param rhs the instance to copy.
79 | */
80 | explicit Jwt(const Jwt& instance);
81 |
82 | /**
83 | * Copy Jwt instance.
84 | * @param rhs the instance to copy.
85 | * @return this
86 | */
87 | Jwt& operator=(const Jwt& rhs);
88 |
89 | /**
90 | * Parse Jwt from string text
91 | * @return the status.
92 | */
93 | Status parseFromString(const std::string& jwt);
94 |
95 | /*
96 | * Verify Jwt time constraint if specified
97 | * esp: expiration time, nbf: not before time.
98 | * @param now: is the current time in seconds since the unix epoch
99 | * @param clock_skew: the the clock skew in second.
100 | * @return the verification status.
101 | */
102 | Status verifyTimeConstraint(uint64_t now,
103 | uint64_t clock_skew = kClockSkewInSecond) const;
104 | };
105 |
106 | } // namespace jwt_verify
107 | } // namespace google
108 |
--------------------------------------------------------------------------------
/test/verify_pem_okp_test.cc:
--------------------------------------------------------------------------------
1 | // Licensed under the Apache License, Version 2.0 (the "License");
2 | // you may not use this file except in compliance with the License.
3 | // You may obtain a copy of the License at
4 | //
5 | // https://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to in writing, software
8 | // distributed under the License is distributed on an "AS IS" BASIS,
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | // See the License for the specific language governing permissions and
11 | // limitations under the License.
12 |
13 | #ifndef BORINGSSL_FIPS
14 |
15 | #include "gtest/gtest.h"
16 | #include "jwt_verify_lib/verify.h"
17 | #include "test/test_common.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // To generate new keys:
24 | // $ openssl genpkey -algorithm ed25519 -out ed_private.pem
25 | // $ openssl pkey -in ed_private.pem -pubout -out ed_public.pem
26 | // To generate new JWTs: I used https://pypi.org/project/privex-pyjwt/
27 |
28 | // ED25519 private key:
29 | // "-----BEGIN PRIVATE KEY-----"
30 | // "MC4CAQAwBQYDK2VwBCIEIHU2mIWEGpLJ4f6wz0+6DZOCpQ3c/HrqQP5i3LDi6BLe"
31 | // "-----END PRIVATE KEY-----"
32 |
33 | const std::string ed25519pubkey = R"(
34 | -----BEGIN PUBLIC KEY-----
35 | MCowBQYDK2VwAyEA6hH43mEbo+h7iigPm9zLKHH5oEc+bjIXD/t4PLPqHLQ=
36 | -----END PUBLIC KEY-----
37 | )";
38 |
39 | // JWT with
40 | // Header: {"alg": "EdDSA", "typ": "JWT"}
41 | // Payload: {"iss":"https://example.com", "sub":"test@example.com"}
42 | const std::string JwtPemEd25519 =
43 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9."
44 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
45 | "rn-h5xTejtilHiAG6aKJEQ3e5_"
46 | "aIKC7nwKUPOjBqN8df69JLiFtKxFCDINHtCNhoeLkgcDHHo2SJFincVH_OCg";
47 |
48 | // Header: {"alg": "EdDSA", typ": "JWT"}
49 | // Payload: {"iss":"https://example.com", "sub":"test@example.com"}
50 | // But signed by a different key
51 | const std::string JwtPemEd25519WrongSignature =
52 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9."
53 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
54 | "Ob8ljAEmEwAoJFEf_v0YZozGlLLPCLVL2C-6B20S8tVNHTzL1-"
55 | "ZiFENdpY53gGakwJ7mm7aLYFikPKUQ62bYCg";
56 |
57 | TEST(VerifyPEMTestOKP, VerifyOK) {
58 | Jwt jwt;
59 | EXPECT_EQ(jwt.parseFromString(JwtPemEd25519), Status::Ok);
60 | auto jwks = Jwks::createFrom(ed25519pubkey, Jwks::Type::PEM);
61 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
62 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
63 |
64 | fuzzJwtSignatureBits(jwt, [&jwks](const Jwt& jwt) {
65 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
66 | });
67 | fuzzJwtSignatureLength(jwt, [&jwks](const Jwt& jwt) {
68 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtEd25519SignatureWrongLength);
69 | });
70 | }
71 |
72 | TEST(VerifyPEMTestOKP, jwksIncorrectAlgSpecifiedFail) {
73 | Jwt jwt;
74 | EXPECT_EQ(jwt.parseFromString(JwtPemEd25519), Status::Ok);
75 | auto jwks = Jwks::createFrom(ed25519pubkey, Jwks::Type::PEM);
76 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
77 | // Add incorrect alg to jwks
78 | jwks->keys()[0]->alg_ = "RS256";
79 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwksKidAlgMismatch);
80 | }
81 |
82 | TEST(VerifyPEMTestOKP, WrongSignatureFail) {
83 | Jwt jwt;
84 | EXPECT_EQ(jwt.parseFromString(JwtPemEd25519WrongSignature), Status::Ok);
85 | auto jwks = Jwks::createFrom(ed25519pubkey, Jwks::Type::PEM);
86 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
87 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
88 | }
89 |
90 | } // namespace
91 | } // namespace jwt_verify
92 | } // namespace google
93 |
94 | #endif
95 |
--------------------------------------------------------------------------------
/jwt_verify_lib/verify.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #pragma once
16 |
17 | #include "jwt_verify_lib/jwks.h"
18 | #include "jwt_verify_lib/jwt.h"
19 | #include "jwt_verify_lib/status.h"
20 |
21 | namespace google {
22 | namespace jwt_verify {
23 |
24 | /**
25 | * This function verifies JWT signature is valid.
26 | * If verification failed, returns the failure reason.
27 | * Note this method does not verify the "aud" claim.
28 | * @param jwt is Jwt object
29 | * @param jwks is Jwks object
30 | * @return the verification status
31 | */
32 | Status verifyJwtWithoutTimeChecking(const Jwt& jwt, const Jwks& jwks);
33 |
34 | /**
35 | * This function verifies JWT signature is valid and that it has not expired
36 | * checking the "exp" and "nbf" claims against the system's current wall clock.
37 | * If verification failed, returns the failure reason.
38 | * Note this method does not verify the "aud" claim.
39 | * @param jwt is Jwt object
40 | * @param jwks is Jwks object
41 | * @return the verification status
42 | */
43 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks);
44 |
45 | /**
46 | * This function verifies JWT signature is valid and that it has not expired
47 | * checking the "exp" and "nbf" claims against the provided time. If
48 | * verification failed, returns the failure reason. Note this method does not
49 | * verify the "aud" claim.
50 | * @param jwt is Jwt object
51 | * @param jwks is Jwks object
52 | * @param now is the number of seconds since the unix epoch
53 | * @param clock_skew is the clock skew in second
54 | * @return the verification status
55 | */
56 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks, uint64_t now,
57 | uint64_t clock_skew = kClockSkewInSecond);
58 |
59 | /**
60 | * This function verifies JWT signature is valid, that it has not expired
61 | * checking the "exp" and "nbf" claims against the system's current wall clock
62 | * as well as validating that one of the entries in the audience list appears
63 | * as a member in the "aud" claim of the specified JWT. If the supplied
64 | * audience list is empty, no verification of the JWT's "aud" field is
65 | * performed. If verification failed, returns the failure reason.
66 | * @param jwt is Jwt object
67 | * @param jwks is Jwks object
68 | * @param audiences a list of audience by which to check against
69 | * @return the verification status
70 | */
71 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks,
72 | const std::vector& audiences);
73 |
74 | /**
75 | * This function verifies JWT signature is valid, that it has not expired
76 | * checking the "exp" and "nbf" claims against the provided time
77 | * as well as validating that one of the entries in the audience list appears
78 | * as a member in the "aud" claim of the specified JWT. If the supplied
79 | * audience list is empty, no verification of the JWT's "aud" field is
80 | * performed.
81 | * If verification failed,
82 | * returns the failure reason.
83 | * @param jwt is Jwt object
84 | * @param jwks is Jwks object
85 | * @param audiences a list of audience by which to check against.
86 | * @return the verification status
87 | */
88 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks,
89 | const std::vector& audiences, uint64_t now);
90 |
91 | } // namespace jwt_verify
92 | } // namespace google
93 |
--------------------------------------------------------------------------------
/test/verify_x509_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "gtest/gtest.h"
16 | #include "jwt_verify_lib/verify.h"
17 | #include "test/test_common.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // Token generated with the following header and payload and kOkPrivateKey.
24 | // Header (kid is not specified):
25 | // {
26 | // "alg": "RS256",
27 | // "typ": "JWT"
28 | // }
29 | // Payload:
30 | // {
31 | // "iss": "628645741881-"
32 | // "noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com",
33 | // "sub": "628645741881-"
34 | // "noabiu23f5a8m8ovd8ucv698lj78vv0l@developer.gserviceaccount.com",
35 | // "aud": "http://myservice.com/myapi"
36 | // }
37 | const std::string kTokenNoKid =
38 | "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2Mjg2NDU3NDE4ODEtbm9hYml1M"
39 | "jNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20"
40 | "iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ"
41 | "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmN"
42 | "vbS9teWFwaSJ9.gq_4ucjddQDjYK5FJr_kXmMo2fgSEB6Js1zopcQLVpCKFDNb-TQ97go0wuk5"
43 | "_vlSp_8I2ImrcdwYbAKqYCzcdyBXkAYoHCGgmY-v6MwZFUvrIaDzR_M3rmY8sQ8cdN3MN6ZRbB"
44 | "6opHwDP1lUEx4bZn_ZBjJMPgqbIqGmhoT1UpfPF6P1eI7sXYru-4KVna0STOynLl3d7JYb7E-8"
45 | "ifcjUJLhat8JR4zR8i4-zWjn6d6j_NI7ZvMROnao77D9YyhXv56zfsXRatKzzYtxPlQMz4AjP-"
46 | "bUHfbHmhiIOOAeEKFuIVUAwM17j54M6VQ5jnAabY5O-ermLfwPiXvNt2L2SA==";
47 |
48 | TEST(VerifyX509Test, NoKidToken) {
49 | Jwt jwt;
50 | EXPECT_EQ(jwt.parseFromString(kTokenNoKid), Status::Ok);
51 |
52 | auto jwks = Jwks::createFrom(kPublicKeyX509, Jwks::Type::JWKS);
53 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
54 |
55 | EXPECT_EQ(verifyJwt(jwt, *jwks), Status::Ok);
56 |
57 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
58 | EXPECT_EQ(verifyJwt(jwt, *jwks), Status::JwtVerificationFail);
59 | });
60 | }
61 |
62 | // A token generated by using
63 | // https://github.com/cloudendpoints/endpoints-tools/blob/master/auth/generate-jwt.py
64 | // ./generate-jwt.py --iss="fake.issuer" "fake.audience" sa_file
65 | const std::string kRealToken =
66 | "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjgyY2ZkNzk3OTAzMDYzYTBiNzhjZT"
67 | "FjYmY1ZTJmZTAzNmE2ZGUyNDIifQ."
68 | "eyJpc3MiOiJmYWtlLmlzc3VlciIsImlhdCI6MTU3ODQ0ODEwNiwiYXVkIjoiZmFrZS5hdWRpZW"
69 | "5jZSIsImV4cCI6MTU3ODQ1MTcwNiwic3ViIjoiZmFrZS5pc3N1ZXIifQ."
70 | "YeKxQvJurL2XHbLdV5pAYW1daumTyLS1ShtEqYJJvMV3kkAG0HRspxrUwkRqRsnchaWjkxzcj-"
71 | "cKg-38LM_hfRIAJ9kyjoiESmD8Oq2cMK_go8Ejq_"
72 | "6YS9CqEnqzR99RRL1iZRVgzj8Wgv7vL3sbbaOBANNmVLr9E5Y2_3_-"
73 | "SMYev586yQOkXkV3J7HaW5avCWl0bJAbR_-gB2Ku2-Em-12-Wh20_NuKIBje0OkkJm0YFHdtm_"
74 | "EBFoc54yQX9yrlyHCEYv7nLvoXZD268j7Xw5_FFhAuxeSS5FKFOxiBEEowSLFw3IgRqFaMfl_"
75 | "qvQQWNDBQBziIhbyLujpy4ZnmYw";
76 |
77 | TEST(VerifyX509Test, RealToken) {
78 | Jwt jwt;
79 | EXPECT_EQ(jwt.parseFromString(kRealToken), Status::Ok);
80 |
81 | auto jwks = Jwks::createFrom(kRealX509Jwks, Jwks::Type::JWKS);
82 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
83 |
84 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
85 |
86 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
87 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
88 | });
89 | }
90 |
91 | } // namespace
92 | } // namespace jwt_verify
93 | } // namespace google
94 |
--------------------------------------------------------------------------------
/test/check_audience_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "jwt_verify_lib/check_audience.h"
16 |
17 | #include "gtest/gtest.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | TEST(CheckAudienceTest, TestConfigNotPrefixNotTailing) {
24 | CheckAudience checker({"example_service"});
25 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service"}));
26 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service"}));
27 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service/"}));
28 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service/"}));
29 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service/"}));
30 | }
31 |
32 | TEST(CheckAudienceTest, TestConfigHttpPrefixNotTailing) {
33 | CheckAudience checker({"http://example_service"});
34 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service"}));
35 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service"}));
36 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service/"}));
37 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service/"}));
38 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service/"}));
39 | }
40 |
41 | TEST(CheckAudienceTest, TestConfigHttpsPrefixNotTailing) {
42 | CheckAudience checker({"https://example_service"});
43 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service"}));
44 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service"}));
45 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service/"}));
46 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service/"}));
47 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service/"}));
48 | }
49 |
50 | TEST(CheckAudienceTest, TestConfigNotPrefixWithTailing) {
51 | CheckAudience checker({"example_service/"});
52 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service"}));
53 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service"}));
54 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service"}));
55 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service/"}));
56 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service/"}));
57 | }
58 |
59 | TEST(CheckAudienceTest, TestConfigHttpPrefixWithTailing) {
60 | CheckAudience checker({"http://example_service/"});
61 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service"}));
62 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service"}));
63 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service"}));
64 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service/"}));
65 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service/"}));
66 | }
67 |
68 | TEST(CheckAudienceTest, TestConfigHttpsPrefixWithTailing) {
69 | CheckAudience checker({"https://example_service/"});
70 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service"}));
71 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service"}));
72 | EXPECT_TRUE(checker.areAudiencesAllowed({"https://example_service"}));
73 | EXPECT_TRUE(checker.areAudiencesAllowed({"example_service/"}));
74 | EXPECT_TRUE(checker.areAudiencesAllowed({"http://example_service/"}));
75 | }
76 |
77 | TEST(CheckAudienceTest, TestAudiencesAllowedWhenNoAudiencesConfigured) {
78 | CheckAudience checker({});
79 | EXPECT_TRUE(checker.areAudiencesAllowed({"foo", "bar"}));
80 | }
81 |
82 | TEST(CheckAudienceTest, TestAnyAudienceMatch) {
83 | CheckAudience checker({"bar", "quux", "foo"});
84 | EXPECT_TRUE(checker.areAudiencesAllowed({"quux"}));
85 | EXPECT_TRUE(checker.areAudiencesAllowed({"baz", "quux"}));
86 | }
87 |
88 | TEST(CheckAudienceTest, TestEmptyAudienceMatch) {
89 | CheckAudience checker({"bar", ""});
90 | EXPECT_TRUE(checker.areAudiencesAllowed({"bar"}));
91 | EXPECT_TRUE(checker.areAudiencesAllowed({""}));
92 | }
93 |
94 | } // namespace
95 | } // namespace jwt_verify
96 | } // namespace google
97 |
--------------------------------------------------------------------------------
/src/struct_utils.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "jwt_verify_lib/struct_utils.h"
16 |
17 | #include "absl/strings/str_split.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 |
22 | StructUtils::StructUtils(const ::google::protobuf::Struct& struct_pb)
23 | : struct_pb_(struct_pb) {}
24 |
25 | StructUtils::FindResult StructUtils::GetString(const std::string& name,
26 | std::string* str_value) {
27 | const ::google::protobuf::Value* found;
28 | FindResult result = GetValue(name, found);
29 | if (result != OK) {
30 | return result;
31 | }
32 | if (found->kind_case() != google::protobuf::Value::kStringValue) {
33 | return WRONG_TYPE;
34 | }
35 | *str_value = found->string_value();
36 | return OK;
37 | }
38 |
39 | StructUtils::FindResult StructUtils::GetDouble(const std::string& name,
40 | double* double_value) {
41 | const ::google::protobuf::Value* found;
42 | FindResult result = GetValue(name, found);
43 | if (result != OK) {
44 | return result;
45 | }
46 | if (found->kind_case() != google::protobuf::Value::kNumberValue) {
47 | return WRONG_TYPE;
48 | }
49 | *double_value = found->number_value();
50 | return OK;
51 | }
52 |
53 | StructUtils::FindResult StructUtils::GetUInt64(const std::string& name,
54 | uint64_t* int_value) {
55 | double double_value;
56 | FindResult result = GetDouble(name, &double_value);
57 | if (result != OK) {
58 | return result;
59 | }
60 | if (double_value < 0 ||
61 | double_value >=
62 | static_cast(std::numeric_limits::max())) {
63 | return OUT_OF_RANGE;
64 | }
65 | *int_value = static_cast(double_value);
66 | return OK;
67 | }
68 |
69 | StructUtils::FindResult StructUtils::GetBoolean(const std::string& name,
70 | bool* bool_value) {
71 | const ::google::protobuf::Value* found;
72 | FindResult result = GetValue(name, found);
73 | if (result != OK) {
74 | return result;
75 | }
76 | if (found->kind_case() != google::protobuf::Value::kBoolValue) {
77 | return WRONG_TYPE;
78 | }
79 | *bool_value = found->bool_value();
80 | return OK;
81 | }
82 |
83 | StructUtils::FindResult StructUtils::GetStringList(
84 | const std::string& name, std::vector* list) {
85 | const ::google::protobuf::Value* found;
86 | FindResult result = GetValue(name, found);
87 | if (result != OK) {
88 | return result;
89 | }
90 | if (found->kind_case() == google::protobuf::Value::kStringValue) {
91 | list->push_back(found->string_value());
92 | return OK;
93 | }
94 | if (found->kind_case() == google::protobuf::Value::kListValue) {
95 | for (const auto& v : found->list_value().values()) {
96 | if (v.kind_case() != google::protobuf::Value::kStringValue) {
97 | return WRONG_TYPE;
98 | }
99 | list->push_back(v.string_value());
100 | }
101 | return OK;
102 | }
103 | return WRONG_TYPE;
104 | }
105 |
106 | StructUtils::FindResult StructUtils::GetValue(
107 | const std::string& nested_names, const google::protobuf::Value*& found) {
108 | const std::vector name_vector =
109 | absl::StrSplit(nested_names, '.');
110 |
111 | const google::protobuf::Struct* current_struct = &struct_pb_;
112 | for (int i = 0; i < name_vector.size(); ++i) {
113 | const auto& fields = current_struct->fields();
114 | const auto it = fields.find(std::string(name_vector[i]));
115 | if (it == fields.end()) {
116 | return MISSING;
117 | }
118 | if (i == name_vector.size() - 1) {
119 | found = &it->second;
120 | return OK;
121 | }
122 | if (it->second.kind_case() != google::protobuf::Value::kStructValue) {
123 | return WRONG_TYPE;
124 | }
125 | current_struct = &it->second.struct_value();
126 | }
127 | return MISSING;
128 | }
129 |
130 | } // namespace jwt_verify
131 | } // namespace google
132 |
--------------------------------------------------------------------------------
/test/jwt_time_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "gtest/gtest.h"
16 | #include "jwt_verify_lib/jwt.h"
17 | #include "test/test_common.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // Header: {"alg":"RS256","typ":"JWT"}
24 | // Payload: {
25 | // "iss":"https://example.com",
26 | // "sub":"test@example.com",
27 | // "exp": 1605052800,
28 | // "nbf": 1605050800
29 | // }
30 | const std::string JwtText =
31 | "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
32 | "ewogICJpc3MiOiAiaHR0cHM6Ly9leGFtcGxlLmNvbSIsCiAgInN1YiI6ICJ0ZXN0QGV4YW1wbG"
33 | "UuY29tIiwKICAiZXhwIjogMTYwNTA1MjgwMCwKICAibmJmIjogMTYwNTA1MDgwMAp9."
34 | "digk0Fr_IdcWgJNVyeVDw2dC1cQG6LsHwg5pIN93L4";
35 |
36 | // The exp time for above Jwt
37 | constexpr uint64_t ExpTime = 1605052800U;
38 |
39 | // The nbf time for above Jwt.
40 | constexpr uint64_t NbfTime = 1605050800U;
41 |
42 | TEST(VerifyExpTest, BothNbfExp) {
43 | Jwt jwt;
44 | EXPECT_EQ(jwt.parseFromString(JwtText), Status::Ok);
45 |
46 | // 10s before exp
47 | EXPECT_EQ(jwt.verifyTimeConstraint(ExpTime + kClockSkewInSecond - 10),
48 | Status::Ok);
49 | // 10s after exp
50 | EXPECT_EQ(jwt.verifyTimeConstraint(ExpTime + kClockSkewInSecond + 10),
51 | Status::JwtExpired);
52 |
53 | // 10s after nbf
54 | EXPECT_EQ(jwt.verifyTimeConstraint(NbfTime - kClockSkewInSecond + 10),
55 | Status::Ok);
56 | // 10s befoe nbf
57 | EXPECT_EQ(jwt.verifyTimeConstraint(NbfTime - kClockSkewInSecond - 10),
58 | Status::JwtNotYetValid);
59 | }
60 |
61 | TEST(VerifyExpTest, BothNbfExpWithCustomClockSkew) {
62 | Jwt jwt;
63 | EXPECT_EQ(jwt.parseFromString(JwtText), Status::Ok);
64 |
65 | constexpr uint64_t kCustomClockSkew = 10;
66 | // 10s before exp
67 | EXPECT_EQ(jwt.verifyTimeConstraint(ExpTime + kCustomClockSkew - 1,
68 | kCustomClockSkew),
69 | Status::Ok);
70 | // 10s after exp
71 | EXPECT_EQ(jwt.verifyTimeConstraint(ExpTime + kCustomClockSkew + 1,
72 | kCustomClockSkew),
73 | Status::JwtExpired);
74 |
75 | // 10s after nbf
76 | EXPECT_EQ(jwt.verifyTimeConstraint(NbfTime - kCustomClockSkew + 1,
77 | kCustomClockSkew),
78 | Status::Ok);
79 | // 10s befoe nbf
80 | EXPECT_EQ(jwt.verifyTimeConstraint(NbfTime - kCustomClockSkew - 1,
81 | kCustomClockSkew),
82 | Status::JwtNotYetValid);
83 | }
84 |
85 | TEST(VerifyExpTest, OnlyExp) {
86 | Jwt jwt;
87 | EXPECT_EQ(jwt.parseFromString(JwtText), Status::Ok);
88 | // Reset nbf
89 | jwt.nbf_ = 0;
90 |
91 | // 10s before exp
92 | EXPECT_EQ(jwt.verifyTimeConstraint(ExpTime + kClockSkewInSecond - 10),
93 | Status::Ok);
94 | // 10s after exp
95 | EXPECT_EQ(jwt.verifyTimeConstraint(ExpTime + kClockSkewInSecond + 10),
96 | Status::JwtExpired);
97 |
98 | // `Now` can be 0,
99 | EXPECT_EQ(jwt.verifyTimeConstraint(0), Status::Ok);
100 | }
101 |
102 | TEST(VerifyExpTest, OnlyNbf) {
103 | Jwt jwt;
104 | EXPECT_EQ(jwt.parseFromString(JwtText), Status::Ok);
105 | // Reset exp
106 | jwt.exp_ = 0;
107 |
108 | // `Now` can be very large
109 | EXPECT_EQ(jwt.verifyTimeConstraint(9223372036854775810U), Status::Ok);
110 |
111 | // 10s after nbf
112 | EXPECT_EQ(jwt.verifyTimeConstraint(NbfTime - kClockSkewInSecond + 10),
113 | Status::Ok);
114 | // 10s befoe nbf
115 | EXPECT_EQ(jwt.verifyTimeConstraint(NbfTime - kClockSkewInSecond - 10),
116 | Status::JwtNotYetValid);
117 | }
118 |
119 | TEST(VerifyExpTest, NotTimeConstraint) {
120 | Jwt jwt;
121 | EXPECT_EQ(jwt.parseFromString(JwtText), Status::Ok);
122 | // Reset both exp and nbf
123 | jwt.exp_ = 0;
124 | jwt.nbf_ = 0;
125 |
126 | // `Now` can be very large
127 | EXPECT_EQ(jwt.verifyTimeConstraint(9223372036854775810U), Status::Ok);
128 |
129 | // `Now` can be 0,
130 | EXPECT_EQ(jwt.verifyTimeConstraint(0), Status::Ok);
131 | }
132 |
133 | } // namespace
134 | } // namespace jwt_verify
135 | } // namespace google
136 |
--------------------------------------------------------------------------------
/test/verify_audiences_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "gtest/gtest.h"
16 | #include "jwt_verify_lib/verify.h"
17 | #include "test/test_common.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // JWT with
24 | // Header: {"alg":"RS256","typ":"JWT"}
25 | // Payload:
26 | // {"iss":"https://example.com","sub":"test@example.com","aud":["aud1"]}
27 | const std::string JwtOneAudtext =
28 | "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
29 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIsIm"
30 | "F1ZCI6WyJhdWQxIl19Cg."
31 | "OldJUf2bMFr09QhqeJ3R6NJQSn22nFTcrsTCc7kVDsGWPPGe_-"
32 | "C7AyiPWQEzfc2wrwIVDsCrANtetlNOwBlUjQwQyCP90ByUlP9bt5TawHpl8iHXo3qs-"
33 | "WCJNT3yNlUnLa8sIBQWjVCE5NrWDirH755bESHzu25CEGwkISYyY5RU_wTA4_YoX-_"
34 | "TGgTs84fmCmgnpsqRLESXBAcOzO3Fo8y8Pz6IlHOWJAnxm2uRJm9Yko_6-"
35 | "Xz8OdDrJhH6iun44I_AC0qjj2bhyLRO4bli12U2Z5MRw9sXjCy55q43rYLAFt_"
36 | "hXFVY7vK9vU9a38gsFEGghv4oYCPWECp-xR5cHA";
37 | /*
38 | "-----BEGIN RSA PRIVATE KEY-----"
39 | "MIIEowIBAAKCAQEAtw7MNxUTxmzWROCD5BqJxmzT7xqc9KsnAjbXCoqEEHDx4WBl"
40 | "fcwkXHt9e/2+Uwi3Arz3FOMNKwGGlbr7clBY3utsjUs8BTF0kO/poAmSTdSuGeh2"
41 | "mSbcVHvmQ7X/kichWwx5Qj0Xj4REU3Gixu1gQIr3GATPAIULo5lj/ebOGAa+l0wI"
42 | "G80Nzz1pBtTIUx68xs5ZGe7cIJ7E8n4pMX10eeuh36h+aossePeuHulYmjr4N0/1"
43 | "jG7a+hHYL6nqwOR3ej0VqCTLS0OloC0LuCpLV7CnSpwbp2Qg/c+MDzQ0TH8g8drI"
44 | "zR5hFe9a3NlNRMXgUU5RqbLnR9zfXr7b9oEszQIDAQABAoIBAQCgQQ8cRZJrSkqG"
45 | "P7qWzXjBwfIDR1wSgWcD9DhrXPniXs4RzM7swvMuF1myW1/r1xxIBF+V5HNZq9tD"
46 | "Z07LM3WpqZX9V9iyfyoZ3D29QcPX6RGFUtHIn5GRUGoz6rdTHnh/+bqJ92uR02vx"
47 | "VPD4j0SNHFrWpxcE0HRxA07bLtxLgNbzXRNmzAB1eKMcrTu/W9Q1zI1opbsQbHbA"
48 | "CjbPEdt8INi9ij7d+XRO6xsnM20KgeuKx1lFebYN9TKGEEx8BCGINOEyWx1lLhsm"
49 | "V6S0XGVwWYdo2ulMWO9M0lNYPzX3AnluDVb3e1Yq2aZ1r7t/GrnGDILA1N2KrAEb"
50 | "AAKHmYNNAoGBAPAv9qJqf4CP3tVDdto9273DA4Mp4Kjd6lio5CaF8jd/4552T3UK"
51 | "N0Q7N6xaWbRYi6xsCZymC4/6DhmLG/vzZOOhHkTsvLshP81IYpWwjm4rF6BfCSl7"
52 | "ip+1z8qonrElxes68+vc1mNhor6GGsxyGe0C18+KzpQ0fEB5J4p0OHGnAoGBAMMb"
53 | "/fpr6FxXcjUgZzRlxHx1HriN6r8Jkzc+wAcQXWyPUOD8OFLcRuvikQ16sa+SlN4E"
54 | "HfhbFn17ABsikUAIVh0pPkHqMsrGFxDn9JrORXUpNhLdBHa6ZH+we8yUe4G0X4Mc"
55 | "R7c8OT26p2zMg5uqz7bQ1nJ/YWlP4nLqIytehnRrAoGAT6Rn0JUlsBiEmAylxVoL"
56 | "mhGnAYAKWZQ0F6/w7wEtPs/uRuYOFM4NY1eLb2AKLK3LqqGsUkAQx23v7PJelh2v"
57 | "z3bmVY52SkqNIGGnJuGDaO5rCCdbH2EypyCfRSDCdhUDWquSpBv3Dr8aOri2/CG9"
58 | "jQSLUOtC8ouww6Qow1UkPjMCgYB8kTicU5ysqCAAj0mVCIxkMZqFlgYUJhbZpLSR"
59 | "Tf93uiCXJDEJph2ZqLOXeYhMYjetb896qx02y/sLWAyIZ0ojoBthlhcLo2FCp/Vh"
60 | "iOSLot4lOPsKmoJji9fei8Y2z2RTnxCiik65fJw8OG6mSm4HeFoSDAWzaQ9Y8ue1"
61 | "XspVNQKBgAiHh4QfiFbgyFOlKdfcq7Scq98MA3mlmFeTx4Epe0A9xxhjbLrn362+"
62 | "ZSCUhkdYkVkly4QVYHJ6Idzk47uUfEC6WlLEAnjKf9LD8vMmZ14yWR2CingYTIY1"
63 | "LL2jMkSYEJx102t2088meCuJzEsF3BzEWOP8RfbFlciT7FFVeiM4"
64 | "-----END RSA PRIVATE KEY-----"
65 | */
66 | const std::string PublicKeyRSA = R"(
67 | {
68 | "keys":[
69 | {
70 | "kty":"RSA",
71 | "e":"AQAB",
72 | "n":"tw7MNxUTxmzWROCD5BqJxmzT7xqc9KsnAjbXCoqEEHDx4WBlfcwkXHt9e_2-Uwi3Arz3FOMNKwGGlbr7clBY3utsjUs8BTF0kO_poAmSTdSuGeh2mSbcVHvmQ7X_kichWwx5Qj0Xj4REU3Gixu1gQIr3GATPAIULo5lj_ebOGAa-l0wIG80Nzz1pBtTIUx68xs5ZGe7cIJ7E8n4pMX10eeuh36h-aossePeuHulYmjr4N0_1jG7a-hHYL6nqwOR3ej0VqCTLS0OloC0LuCpLV7CnSpwbp2Qg_c-MDzQ0TH8g8drIzR5hFe9a3NlNRMXgUU5RqbLnR9zfXr7b9oEszQ"
73 | }]
74 | }
75 | )";
76 |
77 | TEST(VerifyAudTest, MissingAudience) {
78 | Jwt jwt;
79 | Jwks jwks;
80 | std::vector audiences = {"aud2", "aud3"};
81 | EXPECT_EQ(jwt.parseFromString(JwtOneAudtext), Status::Ok);
82 | EXPECT_EQ(verifyJwt(jwt, jwks, audiences), Status::JwtAudienceNotAllowed);
83 | }
84 |
85 | TEST(VerifyAudTest, Success) {
86 | Jwt jwt;
87 | auto jwks = Jwks::createFrom(PublicKeyRSA, Jwks::Type::JWKS);
88 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
89 | EXPECT_EQ(jwt.parseFromString(JwtOneAudtext), Status::Ok);
90 |
91 | // Verify when at least one audience appears in both the JWT
92 | // and the allowed list.
93 | std::vector audiences = {"aud1", "aud3"};
94 | EXPECT_EQ(verifyJwt(jwt, *jwks, audiences), Status::Ok);
95 | // Verify that when the allowed list is empty, verification succeeds.
96 | EXPECT_EQ(verifyJwt(jwt, *jwks, std::vector{}), Status::Ok);
97 | }
98 |
99 | } // namespace
100 | } // namespace jwt_verify
101 | } // namespace google
102 |
--------------------------------------------------------------------------------
/test/verify_pem_rsa_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "gtest/gtest.h"
16 | #include "jwt_verify_lib/verify.h"
17 | #include "test/test_common.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // To generate new keys:
24 | // openssl req -x509 -nodes -newkey rsa:2048 -keyout rsa_private.pem -subj
25 | // "/CN=unused"
26 | // openssl rsa -in rsa_private.pem -pubout -out rsa_public.pem
27 | // To generate new JWTs: Use jwt.io with the generated private key.
28 |
29 | const std::string pubkey = R"(
30 | -----BEGIN PUBLIC KEY-----
31 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzgP9Xw2xvul2pZNjCpJD
32 | /L16FmKH/zt53seeo2/eBKzcUs3nDO33aYdjsCAAFaQfXSAe0PfmwbytmH9RMHOJ
33 | PUU2ApcEt63K5+3v5n+kqKfmym2lOebqpLgsdXIXvTsHYYy/10GGM+NPgyMUgU8q
34 | JSaPOOA/ZJ1eWQTyfgJCPeIarzcTaf+eSD3CQaDDpi488RFc3O86pho5x3KTHSg4
35 | CxHp0ua1RV2pNGJP1BqN0oX09Rgpjo7GE+ukpCMO7zOCwSeBjnqL/zdJ7pjo//u0
36 | dhGpdbcejNZhl1NN+0q1eogwJPM295/7xRSW77mmcUI8W4oLDHLz1zxRoX9yK9xv
37 | 3wIDAQAB
38 | -----END PUBLIC KEY-----
39 | )";
40 |
41 | // JWT with
42 | // Header: { "alg": "RS256", "typ": "JWT" }
43 | // Payload: {"iss":"https://example.com","sub":"test@example.com" }
44 | const std::string JwtPemRs256 =
45 | "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
46 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
47 | "FFSVKKWsuhwHaZDW-nsKcVY0_hEAHvfk7PNa-zRES9IPrhZU-dtZghhhP4xDdDc-"
48 | "w9Phg6Zy70JFOYBc7nvsaz4uRQg6YDYv5PEJaUcUnL4tu1_IK5KzuGnxLHJMVt-7F6EK_"
49 | "HVbvTgmTp6mruC1gvKr3aY3t9u_FQ6mSaziNecIlh9MzZlJ7MVQQhb9A047lbUtxGueGk0l3f-"
50 | "Idcg9idyIiBTqQuOfT1La088e4aCLQo6rCdAUsyeKaIjZyZmh-xK0-"
51 | "YMdobCyMBdEbeN5KWKv9kdSac0HaWbDNn_WKgtkmyIIv5iyPbCuo4vaZWwEQ7NSNsnQDe_"
52 | "BciDrX3npcg";
53 |
54 | // JWT with
55 | // Header: { "alg": "RS384", "typ": "JWT" }
56 | // Payload: {"iss":"https://example.com", "sub":"test@example.com" }
57 | const std::string JwtPemRs384 =
58 | "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9."
59 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
60 | "ec8gDlwNnT189m78UklZ239UcThdRUrlh3DICZcunjb0h6nRrn8xX1zhF9OWiDjIS6Cu7c6kzA"
61 | "OgWu2ZDNf7WSG0JjmcpLVw8W-Zxs0zs6ycxQETz5d_hxmV0kGNRF0nM1EC5DfhB_"
62 | "ByOVwRkaHcM-kpX6t_zvZoX_FGJTp51QzUeGHL1I3WxSVrsTBpBGY_qLGU0dEE9rXgLEEw5o_"
63 | "k05f92PTPBTwq7J3kUYzwxEI9dFb10q9wQYMn1lRL2-"
64 | "Tw0LpdYYKcE8TWVaoNHSAsQQqErMwggIrxW4bg7V66EUSzzFUO8etFs2NN0mWobBQYG7kaCLVS"
65 | "eHlbAyIQagmjMg";
66 |
67 | // JWT with
68 | // Header: { "alg": "RS512", "typ": "JWT" }
69 | // Payload: {"iss":"https://example.com", "sub":"test@example.com" }
70 | const std::string JwtPemRs512 =
71 | "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9."
72 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
73 | "daL48TAqnpXWRltqVZSSVXwRxTuaI1hL5FqdUKNuUUHgDP511EOb_"
74 | "DmsgajvwYs4EmrS2kDguhur0vDIV4RbW3EHMPz3ngMNbP56oMyXOaiXc4dbEGhJraxZ3Y7xh2f"
75 | "H_CNOiXkEuAJns6fCxKHk-"
76 | "Wl1fV36k4mmPFpuxiZqiuRCP6c6Vprt55HKmO3cipjR0wBGrQi07vBwe2uHcZ6R4I6klCgVchq"
77 | "Ms5qq2T1jSnLir6Z4YDgbw6L7lO_x9w2Rhw6R0impjDya2sBrQ-KdATaE5Zkyd5BU6L-"
78 | "IEqKrrJdVTr_rhBYMIMDjDk7ufioIY-6A0zBDQdM2xw3evwBE_w";
79 |
80 | TEST(VerifyPKCSTestRs256, OKPem) {
81 | Jwt jwt;
82 | EXPECT_EQ(jwt.parseFromString(JwtPemRs256), Status::Ok);
83 | auto jwks = Jwks::createFrom(pubkey, Jwks::Type::PEM);
84 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
85 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
86 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
87 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
88 | });
89 | }
90 |
91 | TEST(VerifyPKCSTestRs384, OKPem) {
92 | Jwt jwt;
93 | EXPECT_EQ(jwt.parseFromString(JwtPemRs384), Status::Ok);
94 | auto jwks = Jwks::createFrom(pubkey, Jwks::Type::PEM);
95 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
96 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
97 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
98 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
99 | });
100 | }
101 |
102 | TEST(VerifyPKCSTestRs512, OKPem) {
103 | Jwt jwt;
104 | EXPECT_EQ(jwt.parseFromString(JwtPemRs512), Status::Ok);
105 | auto jwks = Jwks::createFrom(pubkey, Jwks::Type::PEM);
106 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
107 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
108 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
109 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
110 | });
111 | }
112 |
113 | } // namespace
114 | } // namespace jwt_verify
115 | } // namespace google
116 |
--------------------------------------------------------------------------------
/src/jwt.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "jwt_verify_lib/jwt.h"
16 |
17 | #include
18 |
19 | #include "absl/container/flat_hash_set.h"
20 | #include "absl/strings/escaping.h"
21 | #include "absl/strings/str_split.h"
22 | #include "absl/time/clock.h"
23 | #include "google/protobuf/util/json_util.h"
24 | #include "jwt_verify_lib/struct_utils.h"
25 |
26 | namespace google {
27 | namespace jwt_verify {
28 |
29 | namespace {
30 |
31 | bool isImplemented(absl::string_view alg) {
32 | static const absl::flat_hash_set implemented_algs = {
33 | {"ES256"}, {"ES384"}, {"ES512"}, {"HS256"}, {"HS384"},
34 | {"HS512"}, {"RS256"}, {"RS384"}, {"RS512"}, {"PS256"},
35 | {"PS384"}, {"PS512"}, {"EdDSA"},
36 | };
37 |
38 | return implemented_algs.find(alg) != implemented_algs.end();
39 | }
40 |
41 | } // namespace
42 |
43 | Jwt::Jwt(const Jwt& instance) { *this = instance; }
44 |
45 | Jwt& Jwt::operator=(const Jwt& rhs) {
46 | parseFromString(rhs.jwt_);
47 | return *this;
48 | }
49 |
50 | Status Jwt::parseFromString(const std::string& jwt) {
51 | // jwt must have exactly 2 dots with 3 sections.
52 | jwt_ = jwt;
53 | std::vector jwt_split =
54 | absl::StrSplit(jwt, '.', absl::SkipEmpty());
55 | if (jwt_split.size() != 3) {
56 | return Status::JwtBadFormat;
57 | }
58 |
59 | // Parse header json
60 | header_str_base64url_ = std::string(jwt_split[0]);
61 | if (!absl::WebSafeBase64Unescape(header_str_base64url_, &header_str_)) {
62 | return Status::JwtHeaderParseErrorBadBase64;
63 | }
64 |
65 | ::google::protobuf::util::JsonParseOptions options;
66 | const auto header_status = ::google::protobuf::util::JsonStringToMessage(
67 | header_str_, &header_pb_, options);
68 | if (!header_status.ok()) {
69 | return Status::JwtHeaderParseErrorBadJson;
70 | }
71 |
72 | StructUtils header_getter(header_pb_);
73 | // Header should contain "alg" and should be a string.
74 | if (header_getter.GetString("alg", &alg_) != StructUtils::OK) {
75 | return Status::JwtHeaderBadAlg;
76 | }
77 |
78 | if (!isImplemented(alg_)) {
79 | return Status::JwtHeaderNotImplementedAlg;
80 | }
81 |
82 | // Header may contain "kid", should be a string if exists.
83 | if (header_getter.GetString("kid", &kid_) == StructUtils::WRONG_TYPE) {
84 | return Status::JwtHeaderBadKid;
85 | }
86 |
87 | // Parse payload json
88 | payload_str_base64url_ = std::string(jwt_split[1]);
89 | if (!absl::WebSafeBase64Unescape(payload_str_base64url_, &payload_str_)) {
90 | return Status::JwtPayloadParseErrorBadBase64;
91 | }
92 |
93 | const auto payload_status = ::google::protobuf::util::JsonStringToMessage(
94 | payload_str_, &payload_pb_, options);
95 | if (!payload_status.ok()) {
96 | return Status::JwtPayloadParseErrorBadJson;
97 | }
98 |
99 | StructUtils payload_getter(payload_pb_);
100 | if (payload_getter.GetString("iss", &iss_) == StructUtils::WRONG_TYPE) {
101 | return Status::JwtPayloadParseErrorIssNotString;
102 | }
103 | if (payload_getter.GetString("sub", &sub_) == StructUtils::WRONG_TYPE) {
104 | return Status::JwtPayloadParseErrorSubNotString;
105 | }
106 |
107 | auto result = payload_getter.GetUInt64("iat", &iat_);
108 | if (result == StructUtils::WRONG_TYPE) {
109 | return Status::JwtPayloadParseErrorIatNotInteger;
110 | } else if (result == StructUtils::OUT_OF_RANGE) {
111 | return Status::JwtPayloadParseErrorIatOutOfRange;
112 | }
113 |
114 | result = payload_getter.GetUInt64("nbf", &nbf_);
115 | if (result == StructUtils::WRONG_TYPE) {
116 | return Status::JwtPayloadParseErrorNbfNotInteger;
117 | } else if (result == StructUtils::OUT_OF_RANGE) {
118 | return Status::JwtPayloadParseErrorNbfOutOfRange;
119 | }
120 |
121 | result = payload_getter.GetUInt64("exp", &exp_);
122 | if (result == StructUtils::WRONG_TYPE) {
123 | return Status::JwtPayloadParseErrorExpNotInteger;
124 | } else if (result == StructUtils::OUT_OF_RANGE) {
125 | return Status::JwtPayloadParseErrorExpOutOfRange;
126 | }
127 |
128 | if (payload_getter.GetString("jti", &jti_) == StructUtils::WRONG_TYPE) {
129 | return Status::JwtPayloadParseErrorJtiNotString;
130 | }
131 |
132 | // "aud" can be either string array or string.
133 | // GetStringList function will try to read as string, if fails,
134 | // try to read as string array.
135 | if (payload_getter.GetStringList("aud", &audiences_) ==
136 | StructUtils::WRONG_TYPE) {
137 | return Status::JwtPayloadParseErrorAudNotString;
138 | }
139 |
140 | // Set up signature
141 | if (!absl::WebSafeBase64Unescape(jwt_split[2], &signature_)) {
142 | // Signature is a bad Base64url input.
143 | return Status::JwtSignatureParseErrorBadBase64;
144 | }
145 | return Status::Ok;
146 | }
147 |
148 | Status Jwt::verifyTimeConstraint(uint64_t now, uint64_t clock_skew) const {
149 | // Check Jwt is active (nbf).
150 | if (now + clock_skew < nbf_) {
151 | return Status::JwtNotYetValid;
152 | }
153 | // Check JWT has not expired (exp).
154 | if (exp_ && now > exp_ + clock_skew) {
155 | return Status::JwtExpired;
156 | }
157 | return Status::Ok;
158 | }
159 |
160 | } // namespace jwt_verify
161 | } // namespace google
162 |
--------------------------------------------------------------------------------
/repositories.bzl:
--------------------------------------------------------------------------------
1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
2 |
3 | BORINGSSL_COMMIT = "88d7a40bd06a34da6ee0d985545755199d047258" # 2023-05-17, same as Envoy
4 | BORINGSSL_SHA256 = "1e759891e168c5957f2f4d519929e2b4cef9303b7cf2049601081f4fca95bf21"
5 |
6 | def boringssl_repositories(bind = True):
7 | http_archive(
8 | name = "boringssl",
9 | strip_prefix = "boringssl-" + BORINGSSL_COMMIT,
10 | url = "https://github.com/google/boringssl/archive/" + BORINGSSL_COMMIT + ".tar.gz",
11 | sha256 = BORINGSSL_SHA256,
12 | )
13 |
14 | if bind:
15 | native.bind(
16 | name = "ssl",
17 | actual = "@boringssl//:ssl",
18 | )
19 |
20 | GOOGLETEST_COMMIT = "43863938377a9ea1399c0596269e0890b5c5515a"
21 | GOOGLETEST_SHA256 = "7c8ece456ad588c30160429498e108e2df6f42a30888b3ec0abf5d9792d9d3a0"
22 |
23 | def googletest_repositories(bind = True):
24 | http_archive(
25 | name = "googletest_git",
26 | build_file = "//:googletest.BUILD",
27 | strip_prefix = "googletest-" + GOOGLETEST_COMMIT,
28 | url = "https://github.com/google/googletest/archive/" + GOOGLETEST_COMMIT + ".tar.gz",
29 | sha256 = GOOGLETEST_SHA256,
30 | )
31 |
32 | if bind:
33 | native.bind(
34 | name = "googletest",
35 | actual = "@googletest_git//:googletest",
36 | )
37 |
38 | native.bind(
39 | name = "googletest_main",
40 | actual = "@googletest_git//:googletest_main",
41 | )
42 |
43 | native.bind(
44 | name = "googletest_prod",
45 | actual = "@googletest_git//:googletest_prod",
46 | )
47 |
48 | ABSEIL_COMMIT = "cc8dcd307b76a575d2e3e0958a4fe4c7193c2f68" # same as Envoy
49 | ABSEIL_SHA256 = "e35082e88b9da04f4d68094c05ba112502a5063712f3021adfa465306d238c76"
50 |
51 | def abseil_repositories(bind = True):
52 | http_archive(
53 | name = "com_google_absl",
54 | strip_prefix = "abseil-cpp-" + ABSEIL_COMMIT,
55 | url = "https://github.com/abseil/abseil-cpp/archive/" + ABSEIL_COMMIT + ".tar.gz",
56 | sha256 = ABSEIL_SHA256,
57 | )
58 |
59 | if bind:
60 | native.bind(
61 | name = "abseil_strings",
62 | actual = "@com_google_absl//absl/strings:strings",
63 | )
64 | native.bind(
65 | name = "abseil_time",
66 | actual = "@com_google_absl//absl/time:time",
67 | )
68 | native.bind(
69 | name = "abseil_flat_hash_set",
70 | actual = "@com_google_absl//absl/container:flat_hash_set",
71 | )
72 | native.bind(
73 | name = "abseil_flat_hash_map",
74 | actual = "@com_google_absl//absl/container:flat_hash_map",
75 | )
76 | _cctz_repositories(bind)
77 |
78 | CCTZ_COMMIT = "e19879df3a14791b7d483c359c4acd6b2a1cd96b"
79 | CCTZ_SHA256 = "35d2c6cf7ddef1cf7c1bb054bdf2e8d7778242f6d199591a834c14d224b80c39"
80 |
81 | def _cctz_repositories(bind = True):
82 | http_archive(
83 | name = "com_googlesource_code_cctz",
84 | url = "https://github.com/google/cctz/archive/" + CCTZ_COMMIT + ".tar.gz",
85 | sha256 = CCTZ_SHA256,
86 | )
87 |
88 |
89 | RULES_CC_COMMIT = "b7fe9697c0c76ab2fd431a891dbb9a6a32ed7c3e"
90 | RULES_CC_SHA256 = "29daf0159f0cf552fcff60b49d8bcd4f08f08506d2da6e41b07058ec50cfeaec"
91 |
92 | def _rules_cc_repositories():
93 | http_archive(
94 | name = "rules_cc",
95 | sha256 = RULES_CC_SHA256,
96 | strip_prefix = "rules_cc-" + RULES_CC_COMMIT,
97 | urls = ["https://github.com/bazelbuild/rules_cc/archive/" + RULES_CC_COMMIT + ".tar.gz"],
98 | )
99 |
100 | RULES_JAVA_COMMIT = "981f06c3d2bd10225e85209904090eb7b5fb26bd"
101 | RULES_JAVA_SHA256 = "f5a3e477e579231fca27bf202bb0e8fbe4fc6339d63b38ccb87c2760b533d1c3"
102 |
103 | def _rules_java_repositories():
104 | http_archive(
105 | name = "rules_java",
106 | sha256 = RULES_JAVA_SHA256,
107 | strip_prefix = "rules_java-" + RULES_JAVA_COMMIT,
108 | urls = ["https://github.com/bazelbuild/rules_java/archive/" + RULES_JAVA_COMMIT + ".tar.gz"],
109 | )
110 |
111 | RULES_PROTO_COMMIT = "97d8af4dc474595af3900dd85cb3a29ad28cc313" # Oct 31, 2019
112 | RULES_PROTO_SHA256 = "602e7161d9195e50246177e7c55b2f39950a9cf7366f74ed5f22fd45750cd208"
113 |
114 | def _rules_proto_repositories():
115 | http_archive(
116 | name = "rules_proto",
117 | sha256 = RULES_PROTO_SHA256,
118 | strip_prefix = "rules_proto-" + RULES_PROTO_COMMIT,
119 | urls = ["https://github.com/bazelbuild/rules_proto/archive/" + RULES_PROTO_COMMIT + ".tar.gz"],
120 | )
121 |
122 | ZLIB_RELEASE = "1.2.13"
123 | ZLIB_SHA256 = "b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30"
124 |
125 | def _zlib_repositories():
126 | http_archive(
127 | name = "zlib",
128 | build_file = "@com_google_protobuf//:third_party/zlib.BUILD",
129 | sha256 = ZLIB_SHA256,
130 | strip_prefix = "zlib-" + ZLIB_RELEASE,
131 | urls = ["https://zlib.net/zlib-" + ZLIB_RELEASE + ".tar.gz"],
132 | )
133 |
134 | PROTOBUF_RELEASE = "3.16.0" # Mar 04, 2021
135 | PROTOBUF_SHA256 = "7892a35d979304a404400a101c46ce90e85ec9e2a766a86041bb361f626247f5"
136 |
137 | def protobuf_repositories(bind = True):
138 | _rules_cc_repositories()
139 | _rules_java_repositories()
140 | _rules_proto_repositories()
141 | _zlib_repositories()
142 | http_archive(
143 | name = "com_google_protobuf",
144 | strip_prefix = "protobuf-" + PROTOBUF_RELEASE,
145 | url = "https://github.com/protocolbuffers/protobuf/archive/v" + PROTOBUF_RELEASE + ".tar.gz",
146 | sha256 = PROTOBUF_SHA256,
147 | )
148 |
149 | if bind:
150 | native.bind(
151 | name = "protobuf",
152 | actual = "@com_google_protobuf//:protobuf",
153 | )
154 |
155 | LIBPROTOBUF_MUTATOR_VERSION = "1.0"
156 | LIBPROTOBUF_MUTATOR_SHA256 = "792f250fb546bde8590e72d64311ea00a70c175fd77df6bb5e02328fa15fe28e"
157 |
158 | def libprotobuf_mutator_repositories(bind = True):
159 | http_archive(
160 | name = "com_google_libprotobuf_mutator",
161 | build_file = "//:libprotobuf_mutator.BUILD",
162 | strip_prefix = "libprotobuf-mutator-" + LIBPROTOBUF_MUTATOR_VERSION,
163 | url = "https://github.com/google/libprotobuf-mutator/archive/v" + LIBPROTOBUF_MUTATOR_VERSION + ".tar.gz",
164 | sha256 = LIBPROTOBUF_MUTATOR_SHA256,
165 | )
166 |
167 | if bind:
168 | native.bind(
169 | name = "libprotobuf_mutator",
170 | actual = "@com_google_libprotobuf_mutator//:libprotobuf_mutator",
171 | )
172 |
--------------------------------------------------------------------------------
/test/verify_jwk_okp_test.cc:
--------------------------------------------------------------------------------
1 | // Licensed under the Apache License, Version 2.0 (the "License");
2 | // you may not use this file except in compliance with the License.
3 | // You may obtain a copy of the License at
4 | //
5 | // https://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to in writing, software
8 | // distributed under the License is distributed on an "AS IS" BASIS,
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | // See the License for the specific language governing permissions and
11 | // limitations under the License.
12 |
13 | #include "gtest/gtest.h"
14 | #include "jwt_verify_lib/verify.h"
15 | #include "test/test_common.h"
16 |
17 | namespace google {
18 | namespace jwt_verify {
19 | namespace {
20 |
21 | // To generate new keys:
22 | // $ openssl genpkey -algorithm ed25519 -out ed_private.pem
23 | // To get the "x" value of the public key:
24 | // https://mta.openssl.org/pipermail/openssl-users/2018-March/007777.html
25 | // tr is used to convert to the URL-safe, no padding form of Base64 used by JWKS
26 | // $ openssl pkey -in ed_private.pem -pubout -outform DER | tail -c +13 | base64
27 | // | tr '+/' '-_' | tr -d '='
28 | // To generate new JWTs: I used https://pypi.org/project/privex-pyjwt/
29 |
30 | // Ed25519 private key
31 | // -----BEGIN PRIVATE KEY-----
32 | // MC4CAQAwBQYDK2VwBCIEIHU2mIWEGpLJ4f6wz0+6DZOCpQ3c/HrqQP5i3LDi6BLe
33 | // -----END PRIVATE KEY-----
34 |
35 | // Ed25519 public key
36 | // -----BEGIN PUBLIC KEY-----
37 | // MCowBQYDK2VwAyEA6hH43mEbo+h7iigPm9zLKHH5oEc+bjIXD/t4PLPqHLQ=
38 | // -----END PUBLIC KEY-----
39 |
40 | const std::string PublicKeyJwkOKP = R"(
41 | {
42 | "keys": [
43 | {
44 | "kty": "OKP",
45 | "crv": "Ed25519",
46 | "alg": "EdDSA",
47 | "kid": "abc",
48 | "x": "6hH43mEbo-h7iigPm9zLKHH5oEc-bjIXD_t4PLPqHLQ"
49 | },
50 | {
51 | "kty": "OKP",
52 | "crv": "Ed25519",
53 | "alg": "EdDSA",
54 | "kid": "xyz",
55 | "x": "6hH43mEbo-h7iigPm9zLKHH5oEc-bjIXD_t4PLPqHLQ"
56 | }
57 | ]
58 | }
59 | )";
60 |
61 | // Header: {"alg": "EdDSA", "kid": "abc", typ": "JWT"}
62 | // Payload: {"iss":"https://example.com", "sub":"test@example.com"}
63 | const std::string JwtJWKEd25519 =
64 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImFiYyJ9."
65 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
66 | "n7Jd_zwXE03FFDrjdxDP3CYJqAlFXCa3jbv8qER_Z5cmisGJ3_"
67 | "gEb2j1IALPtLA8TsYxQJ4Xxfucen9nFqxUBg";
68 |
69 | // Header: {"alg": "EdDSA", "typ": "JWT"}
70 | // Payload: {"iss":"https://example.com", "sub":"test@example.com"}
71 | const std::string JwtJWKEd25519NoKid =
72 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9."
73 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
74 | "rn-h5xTejtilHiAG6aKJEQ3e5_"
75 | "aIKC7nwKUPOjBqN8df69JLiFtKxFCDINHtCNhoeLkgcDHHo2SJFincVH_OCg";
76 |
77 | // Header: {"alg": "EdDSA", "kid": "abcdef", typ": "JWT"}
78 | // Payload: {"iss":"https://example.com", "sub":"test@example.com"}
79 | const std::string JwtJWKEd25519NonExistKid =
80 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImFiY2RlZiJ9."
81 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
82 | "dqLWFow63rL9VFsjtea60hZn5wMZJxNM6pGcVcOEOE38HrkY1miLj2ZIavd8P7NkkqEsuZMkZ4"
83 | "QHcZxm8qRiCA";
84 |
85 | // Header: {"alg": "EdDSA", "kid": "abc", typ": "JWT"}
86 | // Payload: {"iss":"https://example.com", "sub":"test@example.com"}
87 | // But signed by a different key
88 | const std::string JwtJWKEd25519WrongSignature =
89 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImFiYyJ9."
90 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
91 | "Y-Podsv8NFAQX07NbXAm6O8jD7KdzMpulh-kgDbuT-AspA_"
92 | "tT7aebOM2GHb2q6qex1O6BFkp5n8-2wrwKKE1BQ";
93 |
94 | class VerifyJwkOKPTest : public testing::Test {
95 | protected:
96 | void SetUp() {
97 | jwks_ = Jwks::createFrom(PublicKeyJwkOKP, Jwks::Type::JWKS);
98 | EXPECT_EQ(jwks_->getStatus(), Status::Ok);
99 | }
100 |
101 | JwksPtr jwks_;
102 | };
103 |
104 | TEST_F(VerifyJwkOKPTest, KidOK) {
105 | Jwt jwt;
106 | EXPECT_EQ(jwt.parseFromString(JwtJWKEd25519), Status::Ok);
107 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok);
108 |
109 | fuzzJwtSignatureBits(jwt, [this](const Jwt& jwt) {
110 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail);
111 | });
112 | fuzzJwtSignatureLength(jwt, [this](const Jwt& jwt) {
113 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1),
114 | Status::JwtEd25519SignatureWrongLength);
115 | });
116 | }
117 |
118 | TEST_F(VerifyJwkOKPTest, NoKidOK) {
119 | Jwt jwt;
120 | EXPECT_EQ(jwt.parseFromString(JwtJWKEd25519NoKid), Status::Ok);
121 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok);
122 |
123 | fuzzJwtSignatureBits(jwt, [this](const Jwt& jwt) {
124 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail);
125 | });
126 | fuzzJwtSignatureLength(jwt, [this](const Jwt& jwt) {
127 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1),
128 | Status::JwtEd25519SignatureWrongLength);
129 | });
130 | }
131 |
132 | TEST_F(VerifyJwkOKPTest, NonExistKidFail) {
133 | Jwt jwt;
134 | EXPECT_EQ(jwt.parseFromString(JwtJWKEd25519NonExistKid), Status::Ok);
135 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwksKidAlgMismatch);
136 | }
137 |
138 | TEST_F(VerifyJwkOKPTest, PubkeyNoAlgOK) {
139 | // Remove "alg" claim from public key.
140 | std::string alg_claim = R"("alg": "EdDSA",)";
141 | std::string pubkey_no_alg = PublicKeyJwkOKP;
142 | std::size_t alg_pos = pubkey_no_alg.find(alg_claim);
143 | while (alg_pos != std::string::npos) {
144 | pubkey_no_alg.erase(alg_pos, alg_claim.length());
145 | alg_pos = pubkey_no_alg.find(alg_claim);
146 | }
147 |
148 | jwks_ = Jwks::createFrom(pubkey_no_alg, Jwks::Type::JWKS);
149 | EXPECT_EQ(jwks_->getStatus(), Status::Ok);
150 |
151 | Jwt jwt;
152 | EXPECT_EQ(jwt.parseFromString(JwtJWKEd25519), Status::Ok);
153 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok);
154 | }
155 |
156 | TEST_F(VerifyJwkOKPTest, PubkeyNoKidOK) {
157 | // Remove "kid" claim from public key.
158 | std::string kid_claim1 = R"("kid": "abc",)";
159 | std::string kid_claim2 = R"("kid": "xyz",)";
160 | std::string pubkey_no_kid = PublicKeyJwkOKP;
161 | std::size_t kid_pos = pubkey_no_kid.find(kid_claim1);
162 | pubkey_no_kid.erase(kid_pos, kid_claim1.length());
163 | kid_pos = pubkey_no_kid.find(kid_claim2);
164 | pubkey_no_kid.erase(kid_pos, kid_claim2.length());
165 |
166 | jwks_ = Jwks::createFrom(pubkey_no_kid, Jwks::Type::JWKS);
167 | EXPECT_EQ(jwks_->getStatus(), Status::Ok);
168 |
169 | Jwt jwt;
170 | EXPECT_EQ(jwt.parseFromString(JwtJWKEd25519), Status::Ok);
171 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok);
172 | }
173 |
174 | TEST_F(VerifyJwkOKPTest, WrongSignatureFail) {
175 | Jwt jwt;
176 | EXPECT_EQ(jwt.parseFromString(JwtJWKEd25519WrongSignature), Status::Ok);
177 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::JwtVerificationFail);
178 | }
179 |
180 | } // namespace
181 | } // namespace jwt_verify
182 | } // namespace google
183 |
--------------------------------------------------------------------------------
/BUILD:
--------------------------------------------------------------------------------
1 | licenses(["notice"])
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | exports_files(["LICENSE"])
6 |
7 | cc_library(
8 | name = "jwt_verify_lib",
9 | srcs = [
10 | "src/check_audience.cc",
11 | "src/jwks.cc",
12 | "src/jwt.cc",
13 | "src/status.cc",
14 | "src/struct_utils.cc",
15 | "src/verify.cc",
16 | ],
17 | hdrs = [
18 | "jwt_verify_lib/check_audience.h",
19 | "jwt_verify_lib/jwks.h",
20 | "jwt_verify_lib/jwt.h",
21 | "jwt_verify_lib/status.h",
22 | "jwt_verify_lib/struct_utils.h",
23 | "jwt_verify_lib/verify.h",
24 | ],
25 | deps = [
26 | "//external:abseil_flat_hash_set",
27 | "//external:abseil_strings",
28 | "//external:abseil_time",
29 | "//external:protobuf",
30 | "//external:ssl",
31 | ],
32 | )
33 |
34 | cc_library(
35 | name = "simple_lru_cache_lib",
36 | hdrs = [
37 | "simple_lru_cache/simple_lru_cache.h",
38 | "simple_lru_cache/simple_lru_cache_inl.h",
39 | ],
40 | deps = [
41 | "//external:abseil_flat_hash_map",
42 | ],
43 | )
44 |
45 | cc_test(
46 | name = "check_audience_test",
47 | timeout = "short",
48 | srcs = [
49 | "test/check_audience_test.cc",
50 | ],
51 | linkopts = [
52 | "-lm",
53 | "-lpthread",
54 | ],
55 | linkstatic = 1,
56 | deps = [
57 | ":jwt_verify_lib",
58 | "//external:googletest_main",
59 | ],
60 | )
61 |
62 | cc_test(
63 | name = "jwt_test",
64 | timeout = "short",
65 | srcs = [
66 | "test/jwt_test.cc",
67 | ],
68 | linkopts = [
69 | "-lm",
70 | "-lpthread",
71 | ],
72 | linkstatic = 1,
73 | deps = [
74 | ":jwt_verify_lib",
75 | "//external:googletest_main",
76 | ],
77 | )
78 |
79 | cc_test(
80 | name = "jwks_test",
81 | timeout = "short",
82 | srcs = [
83 | "test/jwks_test.cc",
84 | "test/test_common.h",
85 | ],
86 | linkopts = [
87 | "-lm",
88 | "-lpthread",
89 | ],
90 | linkstatic = 1,
91 | deps = [
92 | ":jwt_verify_lib",
93 | "//external:googletest_main",
94 | ],
95 | )
96 |
97 | cc_test(
98 | name = "simple_lru_cache_test",
99 | timeout = "short",
100 | srcs = [
101 | "test/simple_lru_cache_test.cc",
102 | ],
103 | linkopts = [
104 | "-lm",
105 | "-lpthread",
106 | ],
107 | linkstatic = 1,
108 | deps = [
109 | ":simple_lru_cache_lib",
110 | "//external:googletest_main",
111 | ],
112 | )
113 |
114 | cc_test(
115 | name = "verify_x509_test",
116 | timeout = "short",
117 | srcs = [
118 | "test/test_common.h",
119 | "test/verify_x509_test.cc",
120 | ],
121 | linkopts = [
122 | "-lm",
123 | "-lpthread",
124 | ],
125 | linkstatic = 1,
126 | deps = [
127 | ":jwt_verify_lib",
128 | "//external:googletest_main",
129 | ],
130 | )
131 |
132 | cc_test(
133 | name = "verify_audiences_test",
134 | timeout = "short",
135 | srcs = [
136 | "test/test_common.h",
137 | "test/verify_audiences_test.cc",
138 | ],
139 | linkopts = [
140 | "-lm",
141 | "-lpthread",
142 | ],
143 | linkstatic = 1,
144 | deps = [
145 | ":jwt_verify_lib",
146 | "//external:googletest_main",
147 | ],
148 | )
149 |
150 | cc_test(
151 | name = "jwt_time_test",
152 | timeout = "short",
153 | srcs = [
154 | "test/jwt_time_test.cc",
155 | "test/test_common.h",
156 | ],
157 | linkopts = [
158 | "-lm",
159 | "-lpthread",
160 | ],
161 | linkstatic = 1,
162 | deps = [
163 | ":jwt_verify_lib",
164 | "//external:googletest_main",
165 | ],
166 | )
167 |
168 | cc_test(
169 | name = "verify_jwk_rsa_test",
170 | timeout = "short",
171 | srcs = [
172 | "test/test_common.h",
173 | "test/verify_jwk_rsa_test.cc",
174 | ],
175 | linkopts = [
176 | "-lm",
177 | "-lpthread",
178 | ],
179 | linkstatic = 1,
180 | deps = [
181 | ":jwt_verify_lib",
182 | "//external:googletest_main",
183 | ],
184 | )
185 |
186 | cc_test(
187 | name = "verify_jwk_rsa_pss_test",
188 | timeout = "short",
189 | srcs = [
190 | "test/test_common.h",
191 | "test/verify_jwk_rsa_pss_test.cc",
192 | ],
193 | linkopts = [
194 | "-lm",
195 | "-lpthread",
196 | ],
197 | linkstatic = 1,
198 | deps = [
199 | ":jwt_verify_lib",
200 | "//external:googletest_main",
201 | ],
202 | )
203 |
204 | cc_test(
205 | name = "verify_jwk_ec_test",
206 | timeout = "short",
207 | srcs = [
208 | "test/test_common.h",
209 | "test/verify_jwk_ec_test.cc",
210 | ],
211 | linkopts = [
212 | "-lm",
213 | "-lpthread",
214 | ],
215 | linkstatic = 1,
216 | deps = [
217 | ":jwt_verify_lib",
218 | "//external:googletest_main",
219 | ],
220 | )
221 |
222 | cc_test(
223 | name = "verify_jwk_hmac_test",
224 | timeout = "short",
225 | srcs = [
226 | "test/test_common.h",
227 | "test/verify_jwk_hmac_test.cc",
228 | ],
229 | linkopts = [
230 | "-lm",
231 | "-lpthread",
232 | ],
233 | linkstatic = 1,
234 | deps = [
235 | ":jwt_verify_lib",
236 | "//external:googletest_main",
237 | ],
238 | )
239 |
240 | cc_test(
241 | name = "verify_jwk_okp_test",
242 | timeout = "short",
243 | srcs = [
244 | "test/test_common.h",
245 | "test/verify_jwk_okp_test.cc",
246 | ],
247 | linkopts = [
248 | "-lm",
249 | "-lpthread",
250 | ],
251 | linkstatic = 1,
252 | deps = [
253 | ":jwt_verify_lib",
254 | "//external:googletest_main",
255 | ],
256 | )
257 |
258 | cc_test(
259 | name = "verify_pem_rsa_test",
260 | timeout = "short",
261 | srcs = [
262 | "test/test_common.h",
263 | "test/verify_pem_rsa_test.cc",
264 | ],
265 | linkopts = [
266 | "-lm",
267 | "-lpthread",
268 | ],
269 | linkstatic = 1,
270 | deps = [
271 | ":jwt_verify_lib",
272 | "//external:googletest_main",
273 | ],
274 | )
275 |
276 | cc_test(
277 | name = "verify_pem_ec_test",
278 | timeout = "short",
279 | srcs = [
280 | "test/test_common.h",
281 | "test/verify_pem_ec_test.cc",
282 | ],
283 | linkopts = [
284 | "-lm",
285 | "-lpthread",
286 | ],
287 | linkstatic = 1,
288 | deps = [
289 | ":jwt_verify_lib",
290 | "//external:googletest_main",
291 | ],
292 | )
293 |
294 | cc_test(
295 | name = "verify_pem_okp_test",
296 | timeout = "short",
297 | srcs = [
298 | "test/test_common.h",
299 | "test/verify_pem_okp_test.cc",
300 | ],
301 | linkopts = [
302 | "-lm",
303 | "-lpthread",
304 | ],
305 | linkstatic = 1,
306 | deps = [
307 | ":jwt_verify_lib",
308 | "//external:googletest_main",
309 | ],
310 | )
311 |
--------------------------------------------------------------------------------
/jwt_verify_lib/status.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #pragma once
16 |
17 | #include
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 |
22 | /**
23 | * Define the Jwt verification error status.
24 | */
25 | enum class Status {
26 | Ok = 0,
27 |
28 | // Jwt errors:
29 |
30 | // Jwt missing.
31 | JwtMissed,
32 |
33 | // Jwt not valid yet.
34 | JwtNotYetValid,
35 |
36 | // Jwt expired.
37 | JwtExpired,
38 |
39 | // JWT is not in the form of Header.Payload.Signature
40 | JwtBadFormat,
41 |
42 | // Jwt header is an invalid Base64url encoded.
43 | JwtHeaderParseErrorBadBase64,
44 |
45 | // Jwt header is an invalid JSON.
46 | JwtHeaderParseErrorBadJson,
47 |
48 | // "alg" in the header is not a string.
49 | JwtHeaderBadAlg,
50 |
51 | // Value of "alg" in the header is invalid.
52 | JwtHeaderNotImplementedAlg,
53 |
54 | // "kid" in the header is not a string.
55 | JwtHeaderBadKid,
56 |
57 | // Jwt payload is an invalid Base64url encoded.
58 | JwtPayloadParseErrorBadBase64,
59 |
60 | // Jwt payload is an invalid JSON.
61 | JwtPayloadParseErrorBadJson,
62 |
63 | // Jwt payload field [iss] must be string.
64 | JwtPayloadParseErrorIssNotString,
65 |
66 | // Jwt payload field [sub] must be string.
67 | JwtPayloadParseErrorSubNotString,
68 |
69 | // Jwt payload field [iat] must be integer.
70 | JwtPayloadParseErrorIatNotInteger,
71 |
72 | // Jwt payload field [iat] must be within a 64 bit positive integer range.
73 | JwtPayloadParseErrorIatOutOfRange,
74 |
75 | // Jwt payload field [nbf] must be integer.
76 | JwtPayloadParseErrorNbfNotInteger,
77 |
78 | // Jwt payload field [nbf] must be within a 64 bit positive integer range.
79 | JwtPayloadParseErrorNbfOutOfRange,
80 |
81 | // Jwt payload field [exp] must be integer.
82 | JwtPayloadParseErrorExpNotInteger,
83 |
84 | // Jwt payload field [exp] must be within a 64 bit positive integer range.
85 | JwtPayloadParseErrorExpOutOfRange,
86 |
87 | // Jwt payload field [jti] must be string.
88 | JwtPayloadParseErrorJtiNotString,
89 |
90 | // Jwt payload field [aud] must be string or string list.
91 | JwtPayloadParseErrorAudNotString,
92 |
93 | // Jwt signature is an invalid Base64url input.
94 | JwtSignatureParseErrorBadBase64,
95 |
96 | // Jwt ED25519 signature is wrong length
97 | JwtEd25519SignatureWrongLength,
98 |
99 | // Issuer is not configured.
100 | JwtUnknownIssuer,
101 |
102 | // Audience is not allowed.
103 | JwtAudienceNotAllowed,
104 |
105 | // Jwt verification fails.
106 | JwtVerificationFail,
107 |
108 | // Found multiple Jwt tokens.
109 | JwtMultipleTokens,
110 |
111 | // Jwks errors
112 |
113 | // Jwks is an invalid JSON.
114 | JwksParseError,
115 |
116 | // Jwks does not have "keys".
117 | JwksNoKeys,
118 |
119 | // "keys" in Jwks is not an array.
120 | JwksBadKeys,
121 |
122 | // Jwks doesn't have any valid public key.
123 | JwksNoValidKeys,
124 |
125 | // Jwks doesn't have key to match kid or alg from Jwt.
126 | JwksKidAlgMismatch,
127 |
128 | // "n" or "e" field of a Jwk RSA is missing or has a parse error.
129 | JwksRsaParseError,
130 |
131 | // Failed to create a EC_KEY object.
132 | JwksEcCreateKeyFail,
133 |
134 | // "x" or "y" field is an invalid Base64
135 | JwksEcXorYBadBase64,
136 |
137 | // "x" or "y" field of a Jwk EC is missing or has a parse error.
138 | JwksEcParseError,
139 |
140 | // Jwks Oct key is an invalid Base64.
141 | JwksOctBadBase64,
142 |
143 | // "x" field is invalid Base64
144 | JwksOKPXBadBase64,
145 | // "x" field is wrong length
146 | JwksOKPXWrongLength,
147 |
148 | // Failed to fetch public key
149 | JwksFetchFail,
150 |
151 | // "kty" is missing in "keys".
152 | JwksMissingKty,
153 | // "kty" is not string type in "keys".
154 | JwksBadKty,
155 | // "kty" is not supported in "keys".
156 | JwksNotImplementedKty,
157 |
158 | // "alg" is not started with "RS" for a RSA key
159 | JwksRSAKeyBadAlg,
160 | // "n" field is missing for a RSA key
161 | JwksRSAKeyMissingN,
162 | // "n" field is not string for a RSA key
163 | JwksRSAKeyBadN,
164 | // "e" field is missing for a RSA key
165 | JwksRSAKeyMissingE,
166 | // "e" field is not string for a RSA key
167 | JwksRSAKeyBadE,
168 |
169 | // "alg" is not "ES256", "ES384" or "ES512" for an EC key
170 | JwksECKeyBadAlg,
171 | // "crv" field is not string for an EC key
172 | JwksECKeyBadCrv,
173 | // "crv" or "alg" is not supported for an EC key
174 | JwksECKeyAlgOrCrvUnsupported,
175 | // "crv" is not compatible with "alg" for an EC key
176 | JwksECKeyAlgNotCompatibleWithCrv,
177 | // "x" field is missing for an EC key
178 | JwksECKeyMissingX,
179 | // "x" field is not string for an EC key
180 | JwksECKeyBadX,
181 | // "y" field is missing for an EC key
182 | JwksECKeyMissingY,
183 | // "y" field is not string for an EC key
184 | JwksECKeyBadY,
185 |
186 | // "alg" is not "HS256", "HS384" or "HS512" for an HMAC key
187 | JwksHMACKeyBadAlg,
188 | // "k" field is missing for an HMAC key
189 | JwksHMACKeyMissingK,
190 | // "k" field is not string for an HMAC key
191 | JwksHMACKeyBadK,
192 |
193 | // "alg" is not "EdDSA" for an OKP key
194 | JwksOKPKeyBadAlg,
195 | // "crv" field is missing for an OKP key
196 | JwksOKPKeyMissingCrv,
197 | // "crv" field is not string for an OKP key
198 | JwksOKPKeyBadCrv,
199 | // "crv" is not supported for an OKP key
200 | JwksOKPKeyCrvUnsupported,
201 | // "x" field is missing for an OKP key
202 | JwksOKPKeyMissingX,
203 | // "x" field is not string for an OKP key
204 | JwksOKPKeyBadX,
205 |
206 | // X509 BIO_Write function fails
207 | JwksX509BioWriteError,
208 | // X509 parse pubkey fails
209 | JwksX509ParseError,
210 | // X509 get pubkey fails
211 | JwksX509GetPubkeyError,
212 |
213 | // Key type is not supported.
214 | JwksPemNotImplementedKty,
215 | // Unable to parse public key
216 | JwksPemBadBase64,
217 | // Failed to get raw ED25519 key from PEM
218 | JwksPemGetRawEd25519Error,
219 |
220 | // Failed to create BIO
221 | JwksBioAllocError,
222 | };
223 |
224 | /**
225 | * Convert enum status to string.
226 | * @param status is the enum status.
227 | * @return the string status.
228 | */
229 | std::string getStatusString(Status status);
230 |
231 | /**
232 | * Base class to keep the status that represents "OK" or the first failure.
233 | */
234 | class WithStatus {
235 | public:
236 | WithStatus() : status_(Status::Ok) {}
237 |
238 | /**
239 | * Get the current status.
240 | * @return the enum status.
241 | */
242 | Status getStatus() const { return status_; }
243 |
244 | protected:
245 | void updateStatus(Status status) {
246 | // Only keep the first failure
247 | if (status_ == Status::Ok) {
248 | status_ = status;
249 | }
250 | }
251 |
252 | void resetStatus(Status status) { status_ = status; }
253 |
254 | private:
255 | // The internal status.
256 | Status status_;
257 | };
258 |
259 | } // namespace jwt_verify
260 | } // namespace google
261 |
--------------------------------------------------------------------------------
/test/verify_pem_ec_test.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "gtest/gtest.h"
16 | #include "jwt_verify_lib/verify.h"
17 | #include "test/test_common.h"
18 |
19 | namespace google {
20 | namespace jwt_verify {
21 | namespace {
22 |
23 | // To generate new keys:
24 | // $ openssl ecparam -name ${CurveName} -genkey -noout -out ec_private.pem
25 | // $ openssl ec -in ec_private.pem -pubout -out ec_public.pem
26 | // To generate new JWTs: Use jwt.io with the generated private key.
27 |
28 | // ES256 private key:
29 | // "-----BEGIN EC PRIVATE KEY-----"
30 | // "MHcCAQEEIOyf96eKdFeSFYeHiM09vGAylz+/auaXKEr+fBZssFsJoAoGCCqGSM49"
31 | // "AwEHoUQDQgAEEB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5n3ZsIFO8wV"
32 | // "DyUptLYxuCNPdh+Zijoec8QTa2wCpZQnDw=="
33 | // "-----END EC PRIVATE KEY-----"
34 |
35 | const std::string es256pubkey = R"(
36 | -----BEGIN PUBLIC KEY-----
37 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQ4x/MTt08crvf9NsENzTH+XT3QdI
38 | HCLizGaWwk3uaY7jx93jqFGY5z1xlXe3zyPgEZATV3IjloAkT6uxN6A2YA==
39 | -----END PUBLIC KEY-----
40 | )";
41 |
42 | // ES384 private key:
43 | // -----BEGIN EC PRIVATE KEY-----
44 | // MIGkAgEBBDDqSPe2gvdUVMQcCxpr60rScFgjEQZeCYvZRq3oyY9mECVMK7nuRjLx
45 | // blWjf6DH9E+gBwYFK4EEACKhZANiAATJjwNZzJaWuv3cVOuxwjlh3PY0Lt6Z+gpg
46 | // cktfZ2vdxKB/DQa7ECS5DmcEwmZVXmACfnBXER+SwM5r/O9IccaR5glR+XzLXXBi
47 | // Q6UWMG32k4LDn5GV9mA85reluZSq7Fk=
48 | // -----END EC PRIVATE KEY-----
49 |
50 | const std::string es384pubkey = R"(
51 | -----BEGIN PUBLIC KEY-----
52 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEyY8DWcyWlrr93FTrscI5Ydz2NC7emfoK
53 | YHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5gAn5wVxEfksDOa/zvSHHGkeYJUfl8y11w
54 | YkOlFjBt9pOCw5+RlfZgPOa3pbmUquxZ
55 | -----END PUBLIC KEY-----
56 | )";
57 |
58 | // ES512 private key:
59 | // -----BEGIN EC PRIVATE KEY-----
60 | // MIHcAgEBBEIBKlG7GPIoqQujJHwe21rnsZePySFyd45HPe3FeldgZQEHqcUiZgpb
61 | // BgiuYMPHytEaohj1yC5gyOOsOfgsWY2qSsWgBwYFK4EEACOhgYkDgYYABAG4o4ns
62 | // e68+7fv2Y/xOjqNDl3vQv/jAkg/jloqNeQE0Box/VqW1ozetmaq61P58CYqqsMem
63 | // bGCoVHPydz0WjG3VQgAXFqWMIi6hUQDs8khoM8nl49e1nSGSKdPUH9tD3WZKEKJH
64 | // /jdaGyfU/sbPfRYScu4mzVIZXPWhPiUhFRieLY58iQ==
65 | // -----END EC PRIVATE KEY-----
66 |
67 | const std::string es512pubkey = R"(
68 | -----BEGIN PUBLIC KEY-----
69 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBuKOJ7HuvPu379mP8To6jQ5d70L/4
70 | wJIP45aKjXkBNAaMf1altaM3rZmqutT+fAmKqrDHpmxgqFRz8nc9Foxt1UIAFxal
71 | jCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B/bQ91mShCiR/43Whsn1P7Gz30WEnLuJs1S
72 | GVz1oT4lIRUYni2OfIk=
73 | -----END PUBLIC KEY-----
74 | )";
75 |
76 | // JWT with
77 | // Header: { "alg": "ES256", "typ": "JWT" }
78 | // Payload: {"iss":"https://example.com","sub":"test@example.com" }
79 | const std::string JwtPemEs256 =
80 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9."
81 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
82 | "P2Ru0jfQrm4YgaN5aown5uf-LhV6QX6o-9eQ2D6TjWkJ62LxbIOu6eUnDYyn1QOaC6m2wdb-"
83 | "7NhcWG9DDijhiw";
84 |
85 | // JWT with
86 | // Header: { "alg": "ES384", "typ": "JWT" }
87 | // Payload: {"iss":"https://example.com","sub":"test@example.com" }
88 | const std::string JwtPemEs384 =
89 | "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9."
90 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
91 | "jE8oJhDNem-xMhmylecKVaYhHH_"
92 | "9qJsC3oPz0M35ECI5OHkSOmbnOKtZg1kKFGYzgHDcahq3w3WAD7jtp7TtZbcS8z7PjJvBYSk7r"
93 | "FlHNurxmqF8-f_A03w3F9Lr0rWO";
94 |
95 | // JWT with
96 | // Header: { "alg": "ES512", "typ": "JWT" }
97 | // Payload: {"iss":"https://example.com","sub":"test@example.com" }
98 | const std::string JwtPemEs512 =
99 | "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9."
100 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSJ9."
101 | "AMkxbTVhrtnX0Ylc8hI0nQFQkRhExqaQccHNJLL9aQd_"
102 | "0wlcZ8GHcXOaeKz8krRjxYw2kjHxg3Ng5Xtt7O_2AWN6AJ2FZ_"
103 | "742UKCFsCtCfZFP58d7UoTN7yZ8D4kmRCnh0GefX7z97eBCmMGmbSkCb87yGuDvxd1QlKiva1k"
104 | "kMGHCldt";
105 |
106 | TEST(VerifyPKCSTestRs256, OKPem) {
107 | Jwt jwt;
108 | EXPECT_EQ(jwt.parseFromString(JwtPemEs256), Status::Ok);
109 | auto jwks = Jwks::createFrom(es256pubkey, Jwks::Type::PEM);
110 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
111 | jwks->keys()[0]->alg_ = "ES256";
112 | jwks->keys()[0]->crv_ = "P-256";
113 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
114 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
115 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
116 | });
117 | }
118 |
119 | TEST(VerifyPKCSTestES384, OKPem) {
120 | Jwt jwt;
121 | EXPECT_EQ(jwt.parseFromString(JwtPemEs384), Status::Ok);
122 | auto jwks = Jwks::createFrom(es384pubkey, Jwks::Type::PEM);
123 | jwks->keys()[0]->alg_ = "ES384";
124 | jwks->keys()[0]->crv_ = "P-384";
125 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
126 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
127 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
128 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
129 | });
130 | }
131 |
132 | TEST(VerifyPKCSTestES512, OKPem) {
133 | Jwt jwt;
134 | EXPECT_EQ(jwt.parseFromString(JwtPemEs512), Status::Ok);
135 | auto jwks = Jwks::createFrom(es512pubkey, Jwks::Type::PEM);
136 | jwks->keys()[0]->alg_ = "ES512";
137 | jwks->keys()[0]->crv_ = "P-512";
138 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
139 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
140 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
141 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
142 | });
143 | }
144 |
145 | // If the JWKS does not specific crv or alg, it will be inferred from the JWT.
146 | TEST(VerifyPKCSTestES384, ES384CurveUnspecifiedOK) {
147 | Jwt jwt;
148 | EXPECT_EQ(jwt.parseFromString(JwtPemEs384), Status::Ok);
149 | auto jwks = Jwks::createFrom(es384pubkey, Jwks::Type::PEM);
150 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
151 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::Ok);
152 | }
153 |
154 | TEST(VerifyPKCSTestRs256, jwksAlgUnspecifiedDoesNotMatchJwtFail) {
155 | Jwt jwt;
156 | EXPECT_EQ(jwt.parseFromString(JwtPemEs256), Status::Ok);
157 | // Wrong public key, for a different algorithm.
158 | auto jwks = Jwks::createFrom(es384pubkey, Jwks::Type::PEM);
159 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
160 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
161 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
162 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwtVerificationFail);
163 | });
164 | }
165 |
166 | TEST(VerifyPKCSTestRs256, jwksIncorrectAlgSpecifiedFail) {
167 | Jwt jwt;
168 | EXPECT_EQ(jwt.parseFromString(JwtPemEs256), Status::Ok);
169 | auto jwks = Jwks::createFrom(es256pubkey, Jwks::Type::PEM);
170 | EXPECT_EQ(jwks->getStatus(), Status::Ok);
171 | // Add incorrect Alg to jwks.
172 | jwks->keys()[0]->alg_ = "ES512";
173 | jwks->keys()[0]->crv_ = "P-512";
174 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwksKidAlgMismatch);
175 | fuzzJwtSignature(jwt, [&jwks](const Jwt& jwt) {
176 | EXPECT_EQ(verifyJwt(jwt, *jwks, 1), Status::JwksKidAlgMismatch);
177 | });
178 | }
179 |
180 | } // namespace
181 | } // namespace jwt_verify
182 | } // namespace google
183 |
--------------------------------------------------------------------------------
/src/status.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #include "jwt_verify_lib/status.h"
16 |
17 | #include
18 | #include