├── .bazelrc ├── .bazelversion ├── .gitignore ├── BUILD ├── CONTRIBUTING.md ├── LICENSE ├── OWNERS ├── README.md ├── SECURITY.md ├── WORKSPACE ├── docker ├── Dockerfile-prow-env └── README ├── googletest.BUILD ├── jwt_verify_lib ├── check_audience.h ├── jwks.h ├── jwt.h ├── status.h ├── struct_utils.h └── verify.h ├── libprotobuf_mutator.BUILD ├── repositories.bzl ├── script ├── check-style └── ci.sh ├── simple_lru_cache ├── simple_lru_cache.h └── simple_lru_cache_inl.h ├── src ├── check_audience.cc ├── jwks.cc ├── jwt.cc ├── status.cc ├── struct_utils.cc └── verify.cc └── test ├── check_audience_test.cc ├── fuzz ├── BUILD ├── corpus │ └── jwt_verify_lib_fuzz_test │ │ ├── jwks_ec.txt │ │ ├── jwks_hmac.txt │ │ ├── jwks_okp.txt │ │ ├── jwks_pem.txt │ │ ├── jwks_rsa.txt │ │ └── jwks_x509.txt ├── corpus_format_test.cc ├── jwt_verify_lib_fuzz_input.proto └── jwt_verify_lib_fuzz_test.cc ├── jwks_test.cc ├── jwt_test.cc ├── jwt_time_test.cc ├── simple_lru_cache_test.cc ├── test_common.h ├── verify_audiences_test.cc ├── verify_jwk_ec_test.cc ├── verify_jwk_hmac_test.cc ├── verify_jwk_okp_test.cc ├── verify_jwk_rsa_pss_test.cc ├── verify_jwk_rsa_test.cc ├── verify_pem_ec_test.cc ├── verify_pem_okp_test.cc ├── verify_pem_rsa_test.cc └── verify_x509_test.cc /.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 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 5.3.2 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Google Inc. All rights reserved. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | approvers: 3 | - qiwzhang 4 | - nareddyt 5 | 6 | reviewers: 7 | - orionHong 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI Status](https://oss.gprow.dev/badge.svg?jobs=jwt-verify-lib-periodic)](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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/jwks.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/jwks.h" 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include "absl/strings/escaping.h" 22 | #include "absl/strings/match.h" 23 | #include "google/protobuf/struct.pb.h" 24 | #include "google/protobuf/util/json_util.h" 25 | #include "jwt_verify_lib/struct_utils.h" 26 | #include "openssl/bio.h" 27 | #include "openssl/bn.h" 28 | #include "openssl/curve25519.h" 29 | #include "openssl/ecdsa.h" 30 | #include "openssl/evp.h" 31 | #include "openssl/rsa.h" 32 | #include "openssl/sha.h" 33 | 34 | namespace google { 35 | namespace jwt_verify { 36 | 37 | namespace { 38 | 39 | // The x509 certificate prefix string 40 | const char kX509CertPrefix[] = "-----BEGIN CERTIFICATE-----\n"; 41 | // The x509 certificate suffix string 42 | const char kX509CertSuffix[] = "\n-----END CERTIFICATE-----\n"; 43 | 44 | // A convinence inline cast function. 45 | inline const uint8_t* castToUChar(const std::string& str) { 46 | return reinterpret_cast(str.c_str()); 47 | } 48 | 49 | /** Class to create key object from string of public key, formatted in PEM 50 | * or JWKs. 51 | * If it fails, status_ holds the failure reason. 52 | * 53 | * Usage example: 54 | * KeyGetter e; 55 | * bssl::UniquePtr pkey = e.createEcKeyFromJwkEC(...); 56 | */ 57 | class KeyGetter : public WithStatus { 58 | public: 59 | bssl::UniquePtr createEvpPkeyFromPem(const std::string& pkey_pem) { 60 | bssl::UniquePtr buf(BIO_new_mem_buf(pkey_pem.data(), pkey_pem.size())); 61 | if (buf == nullptr) { 62 | updateStatus(Status::JwksBioAllocError); 63 | return nullptr; 64 | } 65 | bssl::UniquePtr key( 66 | PEM_read_bio_PUBKEY(buf.get(), nullptr, nullptr, nullptr)); 67 | if (key == nullptr) { 68 | updateStatus(Status::JwksPemBadBase64); 69 | return nullptr; 70 | } 71 | return key; 72 | } 73 | 74 | bssl::UniquePtr createEcKeyFromJwkEC(int nid, const std::string& x, 75 | const std::string& y) { 76 | bssl::UniquePtr ec_key(EC_KEY_new_by_curve_name(nid)); 77 | if (!ec_key) { 78 | updateStatus(Status::JwksEcCreateKeyFail); 79 | return nullptr; 80 | } 81 | bssl::UniquePtr bn_x = createBigNumFromBase64UrlString(x); 82 | bssl::UniquePtr bn_y = createBigNumFromBase64UrlString(y); 83 | if (!bn_x || !bn_y) { 84 | // EC public key field x or y Base64 decode fail 85 | updateStatus(Status::JwksEcXorYBadBase64); 86 | return nullptr; 87 | } 88 | 89 | if (EC_KEY_set_public_key_affine_coordinates(ec_key.get(), bn_x.get(), 90 | bn_y.get()) == 0) { 91 | updateStatus(Status::JwksEcParseError); 92 | return nullptr; 93 | } 94 | return ec_key; 95 | } 96 | 97 | bssl::UniquePtr createRsaFromJwk(const std::string& n, 98 | const std::string& e) { 99 | bssl::UniquePtr n_bn = createBigNumFromBase64UrlString(n); 100 | bssl::UniquePtr e_bn = createBigNumFromBase64UrlString(e); 101 | if (n_bn == nullptr || e_bn == nullptr) { 102 | // RSA public key field is missing or has parse error. 103 | updateStatus(Status::JwksRsaParseError); 104 | return nullptr; 105 | } 106 | if (BN_cmp_word(e_bn.get(), 3) != 0 && 107 | BN_cmp_word(e_bn.get(), 65537) != 0) { 108 | // non-standard key; reject it early. 109 | updateStatus(Status::JwksRsaParseError); 110 | return nullptr; 111 | } 112 | // When jwt_verify_lib's minimum supported BoringSSL revision is past 113 | // https://boringssl-review.googlesource.com/c/boringssl/+/59386 (May 2023), 114 | // replace all this with `RSA_new_public_key` instead. 115 | bssl::UniquePtr rsa(RSA_new()); 116 | if (rsa == nullptr || 117 | !RSA_set0_key(rsa.get(), n_bn.get(), e_bn.get(), /*d=*/nullptr)) { 118 | // Allocation or programmer error. 119 | updateStatus(Status::JwksRsaParseError); 120 | return nullptr; 121 | } 122 | // `RSA_set0_key` takes ownership, but only on success. 123 | n_bn.release(); 124 | e_bn.release(); 125 | if (!RSA_check_key(rsa.get())) { 126 | // Not a valid RSA public key. 127 | updateStatus(Status::JwksRsaParseError); 128 | return nullptr; 129 | } 130 | return rsa; 131 | } 132 | 133 | std::string createRawKeyFromJwkOKP(int nid, size_t keylen, 134 | const std::string& x) { 135 | std::string x_decoded; 136 | if (!absl::WebSafeBase64Unescape(x, &x_decoded)) { 137 | updateStatus(Status::JwksOKPXBadBase64); 138 | } else if (x_decoded.length() != keylen) { 139 | updateStatus(Status::JwksOKPXWrongLength); 140 | } 141 | // For OKP the "x" value is the public key and can just be used as-is 142 | return x_decoded; 143 | } 144 | 145 | private: 146 | bssl::UniquePtr createBigNumFromBase64UrlString( 147 | const std::string& s) { 148 | std::string s_decoded; 149 | if (!absl::WebSafeBase64Unescape(s, &s_decoded)) { 150 | return nullptr; 151 | } 152 | return bssl::UniquePtr( 153 | BN_bin2bn(castToUChar(s_decoded), s_decoded.length(), NULL)); 154 | }; 155 | }; 156 | 157 | Status extractJwkFromJwkRSA(const ::google::protobuf::Struct& jwk_pb, 158 | Jwks::Pubkey* jwk) { 159 | if (!jwk->alg_.empty() && 160 | (jwk->alg_.size() < 2 || (jwk->alg_.compare(0, 2, "RS") != 0 && 161 | jwk->alg_.compare(0, 2, "PS") != 0))) { 162 | return Status::JwksRSAKeyBadAlg; 163 | } 164 | 165 | StructUtils jwk_getter(jwk_pb); 166 | std::string n_str; 167 | auto code = jwk_getter.GetString("n", &n_str); 168 | if (code == StructUtils::MISSING) { 169 | return Status::JwksRSAKeyMissingN; 170 | } 171 | if (code == StructUtils::WRONG_TYPE) { 172 | return Status::JwksRSAKeyBadN; 173 | } 174 | 175 | std::string e_str; 176 | code = jwk_getter.GetString("e", &e_str); 177 | if (code == StructUtils::MISSING) { 178 | return Status::JwksRSAKeyMissingE; 179 | } 180 | if (code == StructUtils::WRONG_TYPE) { 181 | return Status::JwksRSAKeyBadE; 182 | } 183 | 184 | KeyGetter e; 185 | jwk->rsa_ = e.createRsaFromJwk(n_str, e_str); 186 | return e.getStatus(); 187 | } 188 | 189 | Status extractJwkFromJwkEC(const ::google::protobuf::Struct& jwk_pb, 190 | Jwks::Pubkey* jwk) { 191 | if (!jwk->alg_.empty() && 192 | (jwk->alg_.size() < 2 || jwk->alg_.compare(0, 2, "ES") != 0)) { 193 | return Status::JwksECKeyBadAlg; 194 | } 195 | 196 | StructUtils jwk_getter(jwk_pb); 197 | std::string crv_str; 198 | auto code = jwk_getter.GetString("crv", &crv_str); 199 | if (code == StructUtils::MISSING) { 200 | crv_str = ""; 201 | } 202 | if (code == StructUtils::WRONG_TYPE) { 203 | return Status::JwksECKeyBadCrv; 204 | } 205 | jwk->crv_ = crv_str; 206 | 207 | // If both alg and crv specified, make sure they match 208 | if (!jwk->alg_.empty() && !jwk->crv_.empty()) { 209 | if (!((jwk->alg_ == "ES256" && jwk->crv_ == "P-256") || 210 | (jwk->alg_ == "ES384" && jwk->crv_ == "P-384") || 211 | (jwk->alg_ == "ES512" && jwk->crv_ == "P-521"))) { 212 | return Status::JwksECKeyAlgNotCompatibleWithCrv; 213 | } 214 | } 215 | 216 | // If neither alg or crv is set, assume P-256 217 | if (jwk->alg_.empty() && jwk->crv_.empty()) { 218 | jwk->crv_ = "P-256"; 219 | } 220 | 221 | int nid; 222 | if (jwk->alg_ == "ES256" || jwk->crv_ == "P-256") { 223 | nid = NID_X9_62_prime256v1; 224 | jwk->crv_ = "P-256"; 225 | } else if (jwk->alg_ == "ES384" || jwk->crv_ == "P-384") { 226 | nid = NID_secp384r1; 227 | jwk->crv_ = "P-384"; 228 | } else if (jwk->alg_ == "ES512" || jwk->crv_ == "P-521") { 229 | nid = NID_secp521r1; 230 | jwk->crv_ = "P-521"; 231 | } else { 232 | return Status::JwksECKeyAlgOrCrvUnsupported; 233 | } 234 | 235 | std::string x_str; 236 | code = jwk_getter.GetString("x", &x_str); 237 | if (code == StructUtils::MISSING) { 238 | return Status::JwksECKeyMissingX; 239 | } 240 | if (code == StructUtils::WRONG_TYPE) { 241 | return Status::JwksECKeyBadX; 242 | } 243 | 244 | std::string y_str; 245 | code = jwk_getter.GetString("y", &y_str); 246 | if (code == StructUtils::MISSING) { 247 | return Status::JwksECKeyMissingY; 248 | } 249 | if (code == StructUtils::WRONG_TYPE) { 250 | return Status::JwksECKeyBadY; 251 | } 252 | 253 | KeyGetter e; 254 | jwk->ec_key_ = e.createEcKeyFromJwkEC(nid, x_str, y_str); 255 | return e.getStatus(); 256 | } 257 | 258 | Status extractJwkFromJwkOct(const ::google::protobuf::Struct& jwk_pb, 259 | Jwks::Pubkey* jwk) { 260 | if (!jwk->alg_.empty() && jwk->alg_ != "HS256" && jwk->alg_ != "HS384" && 261 | jwk->alg_ != "HS512") { 262 | return Status::JwksHMACKeyBadAlg; 263 | } 264 | 265 | StructUtils jwk_getter(jwk_pb); 266 | std::string k_str; 267 | auto code = jwk_getter.GetString("k", &k_str); 268 | if (code == StructUtils::MISSING) { 269 | return Status::JwksHMACKeyMissingK; 270 | } 271 | if (code == StructUtils::WRONG_TYPE) { 272 | return Status::JwksHMACKeyBadK; 273 | } 274 | 275 | std::string key; 276 | if (!absl::WebSafeBase64Unescape(k_str, &key) || key.empty()) { 277 | return Status::JwksOctBadBase64; 278 | } 279 | 280 | jwk->hmac_key_ = key; 281 | return Status::Ok; 282 | } 283 | 284 | // The "OKP" key type is defined in https://tools.ietf.org/html/rfc8037 285 | Status extractJwkFromJwkOKP(const ::google::protobuf::Struct& jwk_pb, 286 | Jwks::Pubkey* jwk) { 287 | // alg is not required, but if present it must be EdDSA 288 | if (!jwk->alg_.empty() && jwk->alg_ != "EdDSA") { 289 | return Status::JwksOKPKeyBadAlg; 290 | } 291 | 292 | // crv is required per https://tools.ietf.org/html/rfc8037#section-2 293 | StructUtils jwk_getter(jwk_pb); 294 | std::string crv_str; 295 | auto code = jwk_getter.GetString("crv", &crv_str); 296 | if (code == StructUtils::MISSING) { 297 | return Status::JwksOKPKeyMissingCrv; 298 | } 299 | if (code == StructUtils::WRONG_TYPE) { 300 | return Status::JwksOKPKeyBadCrv; 301 | } 302 | jwk->crv_ = crv_str; 303 | 304 | // Valid crv values: 305 | // https://tools.ietf.org/html/rfc8037#section-3 306 | // https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve 307 | // In addition to Ed25519 there are: 308 | // X25519: Implemented in boringssl but not used for JWT and thus not 309 | // supported here 310 | // Ed448 and X448: Not implemented in boringssl 311 | int nid; 312 | size_t keylen; 313 | if (jwk->crv_ == "Ed25519") { 314 | nid = EVP_PKEY_ED25519; 315 | keylen = ED25519_PUBLIC_KEY_LEN; 316 | } else { 317 | return Status::JwksOKPKeyCrvUnsupported; 318 | } 319 | 320 | // x is required per https://tools.ietf.org/html/rfc8037#section-2 321 | std::string x_str; 322 | code = jwk_getter.GetString("x", &x_str); 323 | if (code == StructUtils::MISSING) { 324 | return Status::JwksOKPKeyMissingX; 325 | } 326 | if (code == StructUtils::WRONG_TYPE) { 327 | return Status::JwksOKPKeyBadX; 328 | } 329 | 330 | KeyGetter e; 331 | jwk->okp_key_raw_ = e.createRawKeyFromJwkOKP(nid, keylen, x_str); 332 | return e.getStatus(); 333 | } 334 | 335 | Status extractJwk(const ::google::protobuf::Struct& jwk_pb, Jwks::Pubkey* jwk) { 336 | StructUtils jwk_getter(jwk_pb); 337 | // Check "kty" parameter, it should exist. 338 | // https://tools.ietf.org/html/rfc7517#section-4.1 339 | auto code = jwk_getter.GetString("kty", &jwk->kty_); 340 | if (code == StructUtils::MISSING) { 341 | return Status::JwksMissingKty; 342 | } 343 | if (code == StructUtils::WRONG_TYPE) { 344 | return Status::JwksBadKty; 345 | } 346 | 347 | // "kid" and "alg" are optional, if they do not exist, set them to 348 | // empty. https://tools.ietf.org/html/rfc7517#page-8 349 | jwk_getter.GetString("kid", &jwk->kid_); 350 | jwk_getter.GetString("alg", &jwk->alg_); 351 | 352 | // Extract public key according to "kty" value. 353 | // https://tools.ietf.org/html/rfc7518#section-6.1 354 | if (jwk->kty_ == "EC") { 355 | return extractJwkFromJwkEC(jwk_pb, jwk); 356 | } else if (jwk->kty_ == "RSA") { 357 | return extractJwkFromJwkRSA(jwk_pb, jwk); 358 | } else if (jwk->kty_ == "oct") { 359 | return extractJwkFromJwkOct(jwk_pb, jwk); 360 | } else if (jwk->kty_ == "OKP") { 361 | return extractJwkFromJwkOKP(jwk_pb, jwk); 362 | } 363 | return Status::JwksNotImplementedKty; 364 | } 365 | 366 | Status extractX509(const std::string& key, Jwks::Pubkey* jwk) { 367 | jwk->bio_.reset(BIO_new(BIO_s_mem())); 368 | if (BIO_write(jwk->bio_.get(), key.c_str(), key.length()) <= 0) { 369 | return Status::JwksX509BioWriteError; 370 | } 371 | jwk->x509_.reset( 372 | PEM_read_bio_X509(jwk->bio_.get(), nullptr, nullptr, nullptr)); 373 | if (jwk->x509_ == nullptr) { 374 | return Status::JwksX509ParseError; 375 | } 376 | bssl::UniquePtr tmp_pkey(X509_get_pubkey(jwk->x509_.get())); 377 | if (tmp_pkey == nullptr) { 378 | return Status::JwksX509GetPubkeyError; 379 | } 380 | jwk->rsa_.reset(EVP_PKEY_get1_RSA(tmp_pkey.get())); 381 | if (jwk->rsa_ == nullptr) { 382 | return Status::JwksX509GetPubkeyError; 383 | } 384 | return Status::Ok; 385 | } 386 | 387 | bool shouldCheckX509(const ::google::protobuf::Struct& jwks_pb) { 388 | if (jwks_pb.fields().empty()) { 389 | return false; 390 | } 391 | 392 | for (const auto& kid : jwks_pb.fields()) { 393 | if (kid.first.empty() || 394 | kid.second.kind_case() != google::protobuf::Value::kStringValue) { 395 | return false; 396 | } 397 | const std::string& cert = kid.second.string_value(); 398 | if (!absl::StartsWith(cert, kX509CertPrefix) || 399 | !absl::EndsWith(cert, kX509CertSuffix)) { 400 | return false; 401 | } 402 | } 403 | return true; 404 | } 405 | 406 | Status createFromX509(const ::google::protobuf::Struct& jwks_pb, 407 | std::vector& keys) { 408 | for (const auto& kid : jwks_pb.fields()) { 409 | Jwks::PubkeyPtr key_ptr(new Jwks::Pubkey()); 410 | Status status = extractX509(kid.second.string_value(), key_ptr.get()); 411 | if (status != Status::Ok) { 412 | return status; 413 | } 414 | 415 | key_ptr->kid_ = kid.first; 416 | key_ptr->kty_ = "RSA"; 417 | keys.push_back(std::move(key_ptr)); 418 | } 419 | return Status::Ok; 420 | } 421 | 422 | } // namespace 423 | 424 | Status Jwks::addKeyFromPem(const std::string& pkey, const std::string& kid, 425 | const std::string& alg) { 426 | JwksPtr tmp = Jwks::createFromPem(pkey, kid, alg); 427 | if (tmp->getStatus() != Status::Ok) { 428 | return tmp->getStatus(); 429 | } 430 | keys_.insert(keys_.end(), std::make_move_iterator(tmp->keys_.begin()), 431 | std::make_move_iterator(tmp->keys_.end())); 432 | return Status::Ok; 433 | } 434 | 435 | JwksPtr Jwks::createFrom(const std::string& pkey, Type type) { 436 | JwksPtr keys(new Jwks()); 437 | switch (type) { 438 | case Type::JWKS: 439 | keys->createFromJwksCore(pkey); 440 | break; 441 | case Type::PEM: 442 | keys->createFromPemCore(pkey); 443 | break; 444 | } 445 | return keys; 446 | } 447 | 448 | JwksPtr Jwks::createFromPem(const std::string& pkey, const std::string& kid, 449 | const std::string& alg) { 450 | std::unique_ptr ret = Jwks::createFrom(pkey, Jwks::PEM); 451 | if (ret->getStatus() != Status::Ok) { 452 | return ret; 453 | } 454 | if (ret->keys_.size() != 1) { 455 | ret->updateStatus(Status::JwksPemBadBase64); 456 | return ret; 457 | } 458 | Pubkey* jwk = ret->keys_.at(0).get(); 459 | jwk->kid_ = kid; 460 | jwk->alg_ = alg; 461 | 462 | // If alg is a known EC algorithm, set the correct crv as well. 463 | if (jwk->alg_ == "ES256") { 464 | jwk->crv_ = "P-256"; 465 | } 466 | if (jwk->alg_ == "ES384") { 467 | jwk->crv_ = "P-384"; 468 | } 469 | if (jwk->alg_ == "ES512") { 470 | jwk->crv_ = "P-521"; 471 | } 472 | return ret; 473 | } 474 | 475 | // pkey_pem must be a PEM-encoded PKCS #8 public key. 476 | // This is the format that starts with -----BEGIN PUBLIC KEY-----. 477 | void Jwks::createFromPemCore(const std::string& pkey_pem) { 478 | keys_.clear(); 479 | PubkeyPtr key_ptr(new Pubkey()); 480 | KeyGetter e; 481 | bssl::UniquePtr evp_pkey(e.createEvpPkeyFromPem(pkey_pem)); 482 | updateStatus(e.getStatus()); 483 | 484 | if (evp_pkey == nullptr) { 485 | assert(e.getStatus() != Status::Ok); 486 | return; 487 | } 488 | assert(e.getStatus() == Status::Ok); 489 | 490 | switch (EVP_PKEY_id(evp_pkey.get())) { 491 | case EVP_PKEY_RSA: 492 | key_ptr->rsa_.reset(EVP_PKEY_get1_RSA(evp_pkey.get())); 493 | key_ptr->kty_ = "RSA"; 494 | break; 495 | case EVP_PKEY_EC: 496 | key_ptr->ec_key_.reset(EVP_PKEY_get1_EC_KEY(evp_pkey.get())); 497 | key_ptr->kty_ = "EC"; 498 | break; 499 | #ifndef BORINGSSL_FIPS 500 | case EVP_PKEY_ED25519: { 501 | uint8_t raw_key[ED25519_PUBLIC_KEY_LEN]; 502 | size_t out_len = ED25519_PUBLIC_KEY_LEN; 503 | if (EVP_PKEY_get_raw_public_key(evp_pkey.get(), raw_key, &out_len) != 1 || 504 | out_len != ED25519_PUBLIC_KEY_LEN) { 505 | updateStatus(Status::JwksPemGetRawEd25519Error); 506 | return; 507 | } 508 | key_ptr->okp_key_raw_ = 509 | std::string(reinterpret_cast(raw_key), out_len); 510 | key_ptr->kty_ = "OKP"; 511 | key_ptr->crv_ = "Ed25519"; 512 | break; 513 | } 514 | #endif 515 | default: 516 | updateStatus(Status::JwksPemNotImplementedKty); 517 | return; 518 | } 519 | 520 | keys_.push_back(std::move(key_ptr)); 521 | } 522 | 523 | void Jwks::createFromJwksCore(const std::string& jwks_json) { 524 | keys_.clear(); 525 | 526 | ::google::protobuf::util::JsonParseOptions options; 527 | ::google::protobuf::Struct jwks_pb; 528 | const auto status = ::google::protobuf::util::JsonStringToMessage( 529 | jwks_json, &jwks_pb, options); 530 | if (!status.ok()) { 531 | updateStatus(Status::JwksParseError); 532 | return; 533 | } 534 | 535 | const auto& fields = jwks_pb.fields(); 536 | const auto keys_it = fields.find("keys"); 537 | if (keys_it == fields.end()) { 538 | // X509 doesn't have "keys" field. 539 | if (shouldCheckX509(jwks_pb)) { 540 | updateStatus(createFromX509(jwks_pb, keys_)); 541 | return; 542 | } 543 | updateStatus(Status::JwksNoKeys); 544 | return; 545 | } 546 | if (keys_it->second.kind_case() != google::protobuf::Value::kListValue) { 547 | updateStatus(Status::JwksBadKeys); 548 | return; 549 | } 550 | 551 | for (const auto& key_value : keys_it->second.list_value().values()) { 552 | if (key_value.kind_case() != ::google::protobuf::Value::kStructValue) { 553 | continue; 554 | } 555 | PubkeyPtr key_ptr(new Pubkey()); 556 | Status status = extractJwk(key_value.struct_value(), key_ptr.get()); 557 | if (status == Status::Ok) { 558 | keys_.push_back(std::move(key_ptr)); 559 | resetStatus(status); 560 | } else { 561 | updateStatus(status); 562 | } 563 | } 564 | 565 | if (keys_.empty()) { 566 | updateStatus(Status::JwksNoValidKeys); 567 | } else { 568 | resetStatus(Status::Ok); 569 | } 570 | } 571 | 572 | } // namespace jwt_verify 573 | } // namespace google 574 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 19 | 20 | namespace google { 21 | namespace jwt_verify { 22 | 23 | std::string getStatusString(Status status) { 24 | switch (status) { 25 | case Status::Ok: 26 | return "OK"; 27 | case Status::JwtMissed: 28 | return "Jwt is missing"; 29 | case Status::JwtNotYetValid: 30 | return "Jwt not yet valid"; 31 | case Status::JwtExpired: 32 | return "Jwt is expired"; 33 | case Status::JwtBadFormat: 34 | return "Jwt is not in the form of Header.Payload.Signature with two dots " 35 | "and 3 sections"; 36 | case Status::JwtHeaderParseErrorBadBase64: 37 | return "Jwt header is an invalid Base64url encoded"; 38 | case Status::JwtHeaderParseErrorBadJson: 39 | return "Jwt header is an invalid JSON"; 40 | case Status::JwtHeaderBadAlg: 41 | return "Jwt header [alg] field is required and must be a string"; 42 | case Status::JwtHeaderNotImplementedAlg: 43 | return "Jwt header [alg] is not supported"; 44 | case Status::JwtHeaderBadKid: 45 | return "Jwt header [kid] field is not a string"; 46 | case Status::JwtPayloadParseErrorBadBase64: 47 | return "Jwt payload is an invalid Base64url encoded"; 48 | case Status::JwtEd25519SignatureWrongLength: 49 | return "Jwt ED25519 signature is wrong length"; 50 | case Status::JwtPayloadParseErrorBadJson: 51 | return "Jwt payload is an invalid JSON"; 52 | case Status::JwtPayloadParseErrorIssNotString: 53 | return "Jwt payload [iss] field is not a string"; 54 | case Status::JwtPayloadParseErrorSubNotString: 55 | return "Jwt payload [sub] field is not a string"; 56 | case Status::JwtPayloadParseErrorIatNotInteger: 57 | return "Jwt payload [iat] field is not an integer"; 58 | case Status::JwtPayloadParseErrorIatOutOfRange: 59 | return "Jwt payload [iat] field is not a positive 64 bit integer"; 60 | case Status::JwtPayloadParseErrorNbfNotInteger: 61 | return "Jwt payload [nbf] field is not an integer"; 62 | case Status::JwtPayloadParseErrorNbfOutOfRange: 63 | return "Jwt payload [nbf] field is not a positive 64 bit integer"; 64 | case Status::JwtPayloadParseErrorExpNotInteger: 65 | return "Jwt payload [exp] field is not an integer"; 66 | case Status::JwtPayloadParseErrorExpOutOfRange: 67 | return "Jwt payload [exp] field is not a positive 64 bit integer"; 68 | case Status::JwtPayloadParseErrorJtiNotString: 69 | return "Jwt payload [jti] field is not a string"; 70 | case Status::JwtPayloadParseErrorAudNotString: 71 | return "Jwt payload [aud] field is not a string or string list"; 72 | case Status::JwtSignatureParseErrorBadBase64: 73 | return "Jwt signature is an invalid Base64url encoded"; 74 | case Status::JwtUnknownIssuer: 75 | return "Jwt issuer is not configured"; 76 | case Status::JwtAudienceNotAllowed: 77 | return "Audiences in Jwt are not allowed"; 78 | case Status::JwtVerificationFail: 79 | return "Jwt verification fails"; 80 | case Status::JwtMultipleTokens: 81 | return "Found multiple Jwt tokens"; 82 | 83 | case Status::JwksParseError: 84 | return "Jwks is an invalid JSON"; 85 | case Status::JwksNoKeys: 86 | return "Jwks does not have [keys] field"; 87 | case Status::JwksBadKeys: 88 | return "[keys] in Jwks is not an array"; 89 | case Status::JwksNoValidKeys: 90 | return "Jwks doesn't have any valid public key"; 91 | case Status::JwksKidAlgMismatch: 92 | return "Jwks doesn't have key to match kid or alg from Jwt"; 93 | case Status::JwksRsaParseError: 94 | return "Jwks RSA [n] or [e] field is missing or has a parse error"; 95 | case Status::JwksEcCreateKeyFail: 96 | return "Jwks EC create key fail"; 97 | case Status::JwksEcXorYBadBase64: 98 | return "Jwks EC [x] or [y] field is an invalid Base64."; 99 | case Status::JwksEcParseError: 100 | return "Jwks EC [x] and [y] fields have a parse error."; 101 | case Status::JwksOctBadBase64: 102 | return "Jwks Oct key is an invalid Base64"; 103 | case Status::JwksOKPXBadBase64: 104 | return "Jwks OKP [x] field is an invalid Base64."; 105 | case Status::JwksOKPXWrongLength: 106 | return "Jwks OKP [x] field is wrong length."; 107 | case Status::JwksFetchFail: 108 | return "Jwks remote fetch is failed"; 109 | 110 | case Status::JwksMissingKty: 111 | return "[kty] is missing in [keys]"; 112 | case Status::JwksBadKty: 113 | return "[kty] is bad in [keys]"; 114 | case Status::JwksNotImplementedKty: 115 | return "[kty] is not supported in [keys]"; 116 | 117 | case Status::JwksRSAKeyBadAlg: 118 | return "[alg] is not started with [RS] or [PS] for an RSA key"; 119 | case Status::JwksRSAKeyMissingN: 120 | return "[n] field is missing for a RSA key"; 121 | case Status::JwksRSAKeyBadN: 122 | return "[n] field is not string for a RSA key"; 123 | case Status::JwksRSAKeyMissingE: 124 | return "[e] field is missing for a RSA key"; 125 | case Status::JwksRSAKeyBadE: 126 | return "[e] field is not string for a RSA key"; 127 | 128 | case Status::JwksECKeyBadAlg: 129 | return "[alg] is not started with [ES] for an EC key"; 130 | case Status::JwksECKeyBadCrv: 131 | return "[crv] field is not string for an EC key"; 132 | case Status::JwksECKeyAlgOrCrvUnsupported: 133 | return "[crv] or [alg] field is not supported for an EC key"; 134 | case Status::JwksECKeyAlgNotCompatibleWithCrv: 135 | return "[crv] field specified is not compatible with [alg] for an EC key"; 136 | case Status::JwksECKeyMissingX: 137 | return "[x] field is missing for an EC key"; 138 | case Status::JwksECKeyBadX: 139 | return "[x] field is not string for an EC key"; 140 | case Status::JwksECKeyMissingY: 141 | return "[y] field is missing for an EC key"; 142 | case Status::JwksECKeyBadY: 143 | return "[y] field is not string for an EC key"; 144 | 145 | case Status::JwksHMACKeyBadAlg: 146 | return "[alg] does not start with [HS] for an HMAC key"; 147 | case Status::JwksHMACKeyMissingK: 148 | return "[k] field is missing for an HMAC key"; 149 | case Status::JwksHMACKeyBadK: 150 | return "[k] field is not string for an HMAC key"; 151 | 152 | case Status::JwksOKPKeyBadAlg: 153 | return "[alg] is not [EdDSA] for an OKP key"; 154 | case Status::JwksOKPKeyMissingCrv: 155 | return "[crv] field is missing for an OKP key"; 156 | case Status::JwksOKPKeyBadCrv: 157 | return "[crv] field is not string for an OKP key"; 158 | case Status::JwksOKPKeyCrvUnsupported: 159 | return "[crv] field is not supported for an OKP key"; 160 | case Status::JwksOKPKeyMissingX: 161 | return "[x] field is missing for an OKP key"; 162 | case Status::JwksOKPKeyBadX: 163 | return "[x] field is not string for an OKP key"; 164 | 165 | case Status::JwksX509BioWriteError: 166 | return "X509 parse pubkey internal fails: memory allocation"; 167 | case Status::JwksX509ParseError: 168 | return "X509 parse pubkey fails"; 169 | case Status::JwksX509GetPubkeyError: 170 | return "X509 parse pubkey internal fails: get pubkey"; 171 | 172 | case Status::JwksPemNotImplementedKty: 173 | return "PEM Key type is not supported"; 174 | case Status::JwksPemBadBase64: 175 | return "PEM pubkey parse fails"; 176 | case Status::JwksPemGetRawEd25519Error: 177 | return "PEM failed to get raw ED25519 key"; 178 | 179 | case Status::JwksBioAllocError: 180 | return "Failed to create BIO due to memory allocation failure"; 181 | }; 182 | // Return empty string though switch-case is exhaustive. See issues/91. 183 | return ""; 184 | } 185 | 186 | } // namespace jwt_verify 187 | } // namespace google 188 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/verify.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/verify.h" 16 | 17 | #include "absl/strings/string_view.h" 18 | #include "absl/time/clock.h" 19 | #include "jwt_verify_lib/check_audience.h" 20 | #include "openssl/bn.h" 21 | #include "openssl/curve25519.h" 22 | #include "openssl/ecdsa.h" 23 | #include "openssl/err.h" 24 | #include "openssl/evp.h" 25 | #include "openssl/hmac.h" 26 | #include "openssl/mem.h" 27 | #include "openssl/rsa.h" 28 | #include "openssl/sha.h" 29 | 30 | namespace google { 31 | namespace jwt_verify { 32 | namespace { 33 | 34 | // A convenience inline cast function. 35 | inline const uint8_t* castToUChar(const absl::string_view& str) { 36 | return reinterpret_cast(str.data()); 37 | } 38 | 39 | bool verifySignatureRSA(RSA* key, const EVP_MD* md, const uint8_t* signature, 40 | size_t signature_len, const uint8_t* signed_data, 41 | size_t signed_data_len) { 42 | if (key == nullptr || md == nullptr || signature == nullptr || 43 | signed_data == nullptr) { 44 | return false; 45 | } 46 | bssl::UniquePtr evp_pkey(EVP_PKEY_new()); 47 | if (EVP_PKEY_set1_RSA(evp_pkey.get(), key) != 1) { 48 | return false; 49 | } 50 | 51 | bssl::UniquePtr md_ctx(EVP_MD_CTX_create()); 52 | if (EVP_DigestVerifyInit(md_ctx.get(), nullptr, md, nullptr, 53 | evp_pkey.get()) == 1) { 54 | if (EVP_DigestVerifyUpdate(md_ctx.get(), signed_data, signed_data_len) == 55 | 1) { 56 | if (EVP_DigestVerifyFinal(md_ctx.get(), signature, signature_len) == 1) { 57 | return true; 58 | } 59 | } 60 | } 61 | ERR_clear_error(); 62 | return false; 63 | } 64 | 65 | bool verifySignatureRSA(RSA* key, const EVP_MD* md, absl::string_view signature, 66 | absl::string_view signed_data) { 67 | return verifySignatureRSA(key, md, castToUChar(signature), signature.length(), 68 | castToUChar(signed_data), signed_data.length()); 69 | } 70 | 71 | bool verifySignatureRSAPSS(RSA* key, const EVP_MD* md, const uint8_t* signature, 72 | size_t signature_len, const uint8_t* signed_data, 73 | size_t signed_data_len) { 74 | if (key == nullptr || md == nullptr || signature == nullptr || 75 | signed_data == nullptr) { 76 | return false; 77 | } 78 | bssl::UniquePtr evp_pkey(EVP_PKEY_new()); 79 | if (EVP_PKEY_set1_RSA(evp_pkey.get(), key) != 1) { 80 | return false; 81 | } 82 | 83 | bssl::UniquePtr md_ctx(EVP_MD_CTX_create()); 84 | // pctx is owned by md_ctx, no need to free it separately. 85 | EVP_PKEY_CTX* pctx; 86 | if (EVP_DigestVerifyInit(md_ctx.get(), &pctx, md, nullptr, evp_pkey.get()) == 87 | 1 && 88 | EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) == 1 && 89 | EVP_PKEY_CTX_set_rsa_mgf1_md(pctx, md) == 1 && 90 | EVP_DigestVerify(md_ctx.get(), signature, signature_len, signed_data, 91 | signed_data_len) == 1) { 92 | return true; 93 | } 94 | 95 | ERR_clear_error(); 96 | return false; 97 | } 98 | 99 | bool verifySignatureRSAPSS(RSA* key, const EVP_MD* md, 100 | absl::string_view signature, 101 | absl::string_view signed_data) { 102 | return verifySignatureRSAPSS(key, md, castToUChar(signature), 103 | signature.length(), castToUChar(signed_data), 104 | signed_data.length()); 105 | } 106 | 107 | bool verifySignatureEC(EC_KEY* key, const EVP_MD* md, const uint8_t* signature, 108 | size_t signature_len, const uint8_t* signed_data, 109 | size_t signed_data_len) { 110 | if (key == nullptr || md == nullptr || signature == nullptr || 111 | signed_data == nullptr) { 112 | return false; 113 | } 114 | bssl::UniquePtr md_ctx(EVP_MD_CTX_create()); 115 | std::vector digest(EVP_MAX_MD_SIZE); 116 | unsigned int digest_len = 0; 117 | 118 | if (EVP_DigestInit(md_ctx.get(), md) == 0) { 119 | return false; 120 | } 121 | 122 | if (EVP_DigestUpdate(md_ctx.get(), signed_data, signed_data_len) == 0) { 123 | return false; 124 | } 125 | 126 | if (EVP_DigestFinal(md_ctx.get(), digest.data(), &digest_len) == 0) { 127 | return false; 128 | } 129 | 130 | bssl::UniquePtr ecdsa_sig(ECDSA_SIG_new()); 131 | if (!ecdsa_sig) { 132 | return false; 133 | } 134 | 135 | if (BN_bin2bn(signature, signature_len / 2, ecdsa_sig->r) == nullptr || 136 | BN_bin2bn(signature + (signature_len / 2), signature_len / 2, 137 | ecdsa_sig->s) == nullptr) { 138 | return false; 139 | } 140 | 141 | if (ECDSA_do_verify(digest.data(), digest_len, ecdsa_sig.get(), key) == 1) { 142 | return true; 143 | } 144 | 145 | ERR_clear_error(); 146 | return false; 147 | } 148 | 149 | bool verifySignatureEC(EC_KEY* key, const EVP_MD* md, 150 | absl::string_view signature, 151 | absl::string_view signed_data) { 152 | return verifySignatureEC(key, md, castToUChar(signature), signature.length(), 153 | castToUChar(signed_data), signed_data.length()); 154 | } 155 | 156 | bool verifySignatureOct(const uint8_t* key, size_t key_len, const EVP_MD* md, 157 | const uint8_t* signature, size_t signature_len, 158 | const uint8_t* signed_data, size_t signed_data_len) { 159 | if (key == nullptr || md == nullptr || signature == nullptr || 160 | signed_data == nullptr) { 161 | return false; 162 | } 163 | 164 | std::vector out(EVP_MAX_MD_SIZE); 165 | unsigned int out_len = 0; 166 | if (HMAC(md, key, key_len, signed_data, signed_data_len, out.data(), 167 | &out_len) == nullptr) { 168 | ERR_clear_error(); 169 | return false; 170 | } 171 | 172 | if (out_len != signature_len) { 173 | return false; 174 | } 175 | 176 | if (CRYPTO_memcmp(out.data(), signature, signature_len) == 0) { 177 | return true; 178 | } 179 | 180 | ERR_clear_error(); 181 | return false; 182 | } 183 | 184 | bool verifySignatureOct(absl::string_view key, const EVP_MD* md, 185 | absl::string_view signature, 186 | absl::string_view signed_data) { 187 | return verifySignatureOct(castToUChar(key), key.length(), md, 188 | castToUChar(signature), signature.length(), 189 | castToUChar(signed_data), signed_data.length()); 190 | } 191 | 192 | Status verifySignatureEd25519(absl::string_view key, 193 | absl::string_view signature, 194 | absl::string_view signed_data) { 195 | if (signature.length() != ED25519_SIGNATURE_LEN) { 196 | return Status::JwtEd25519SignatureWrongLength; 197 | } 198 | 199 | if (ED25519_verify(castToUChar(signed_data), signed_data.length(), 200 | castToUChar(signature), castToUChar(key.data())) == 1) { 201 | return Status::Ok; 202 | } 203 | 204 | ERR_clear_error(); 205 | return Status::JwtVerificationFail; 206 | } 207 | 208 | } // namespace 209 | 210 | Status verifyJwtWithoutTimeChecking(const Jwt& jwt, const Jwks& jwks) { 211 | // Verify signature 212 | std::string signed_data = 213 | jwt.header_str_base64url_ + '.' + jwt.payload_str_base64url_; 214 | bool kid_alg_matched = false; 215 | for (const auto& jwk : jwks.keys()) { 216 | // If kid is specified in JWT, JWK with the same kid is used for 217 | // verification. 218 | // If kid is not specified in JWT, try all JWK. 219 | if (!jwt.kid_.empty() && !jwk->kid_.empty() && jwk->kid_ != jwt.kid_) { 220 | continue; 221 | } 222 | 223 | // The same alg must be used. 224 | if (!jwk->alg_.empty() && jwk->alg_ != jwt.alg_) { 225 | continue; 226 | } 227 | kid_alg_matched = true; 228 | 229 | if (jwk->kty_ == "EC") { 230 | const EVP_MD* md; 231 | if (jwt.alg_ == "ES384") { 232 | md = EVP_sha384(); 233 | } else if (jwt.alg_ == "ES512") { 234 | md = EVP_sha512(); 235 | } else { 236 | // default to SHA256 237 | md = EVP_sha256(); 238 | } 239 | 240 | if (verifySignatureEC(jwk->ec_key_.get(), md, jwt.signature_, 241 | signed_data)) { 242 | // Verification succeeded. 243 | return Status::Ok; 244 | } 245 | } else if (jwk->kty_ == "RSA") { 246 | const EVP_MD* md; 247 | if (jwt.alg_ == "RS384" || jwt.alg_ == "PS384") { 248 | md = EVP_sha384(); 249 | } else if (jwt.alg_ == "RS512" || jwt.alg_ == "PS512") { 250 | md = EVP_sha512(); 251 | } else { 252 | // default to SHA256 253 | md = EVP_sha256(); 254 | } 255 | 256 | if (jwt.alg_.compare(0, 2, "RS") == 0) { 257 | if (verifySignatureRSA(jwk->rsa_.get(), md, jwt.signature_, 258 | signed_data)) { 259 | // Verification succeeded. 260 | return Status::Ok; 261 | } 262 | } else if (jwt.alg_.compare(0, 2, "PS") == 0) { 263 | if (verifySignatureRSAPSS(jwk->rsa_.get(), md, jwt.signature_, 264 | signed_data)) { 265 | // Verification succeeded. 266 | return Status::Ok; 267 | } 268 | } 269 | } else if (jwk->kty_ == "oct") { 270 | const EVP_MD* md; 271 | if (jwt.alg_ == "HS384") { 272 | md = EVP_sha384(); 273 | } else if (jwt.alg_ == "HS512") { 274 | md = EVP_sha512(); 275 | } else { 276 | // default to SHA256 277 | md = EVP_sha256(); 278 | } 279 | 280 | if (verifySignatureOct(jwk->hmac_key_, md, jwt.signature_, signed_data)) { 281 | // Verification succeeded. 282 | return Status::Ok; 283 | } 284 | } else if (jwk->kty_ == "OKP" && jwk->crv_ == "Ed25519") { 285 | Status status = verifySignatureEd25519(jwk->okp_key_raw_, jwt.signature_, 286 | signed_data); 287 | // For verification failures keep going and try the rest of the keys in 288 | // the JWKS. Otherwise status is either OK or an error with the JWT and we 289 | // can return immediately. 290 | if (status == Status::Ok || 291 | status == Status::JwtEd25519SignatureWrongLength) { 292 | return status; 293 | } 294 | } 295 | } 296 | 297 | // Verification failed. 298 | return kid_alg_matched ? Status::JwtVerificationFail 299 | : Status::JwksKidAlgMismatch; 300 | } 301 | 302 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks) { 303 | return verifyJwt(jwt, jwks, absl::ToUnixSeconds(absl::Now())); 304 | } 305 | 306 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks, uint64_t now, 307 | uint64_t clock_skew) { 308 | Status time_status = jwt.verifyTimeConstraint(now, clock_skew); 309 | if (time_status != Status::Ok) { 310 | return time_status; 311 | } 312 | 313 | return verifyJwtWithoutTimeChecking(jwt, jwks); 314 | } 315 | 316 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks, 317 | const std::vector& audiences) { 318 | return verifyJwt(jwt, jwks, audiences, absl::ToUnixSeconds(absl::Now())); 319 | } 320 | 321 | Status verifyJwt(const Jwt& jwt, const Jwks& jwks, 322 | const std::vector& audiences, uint64_t now) { 323 | CheckAudience checker(audiences); 324 | if (!checker.areAudiencesAllowed(jwt.audiences_)) { 325 | return Status::JwtAudienceNotAllowed; 326 | } 327 | return verifyJwt(jwt, jwks, now); 328 | } 329 | 330 | } // namespace jwt_verify 331 | } // namespace google 332 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/test_common.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/jwt.h" 21 | 22 | namespace google { 23 | namespace jwt_verify { 24 | 25 | void fuzzJwtSignatureBits(const Jwt& jwt, 26 | std::function test_fn) { 27 | // alter 1 bit 28 | for (size_t b = 0; b < jwt.signature_.size(); ++b) { 29 | for (int bit = 0; bit < 8; ++bit) { 30 | Jwt fuzz_jwt(jwt); 31 | unsigned char bb = fuzz_jwt.signature_[b]; 32 | bb ^= (unsigned char)(1 << bit); 33 | fuzz_jwt.signature_[b] = (char)bb; 34 | test_fn(fuzz_jwt); 35 | } 36 | } 37 | } 38 | 39 | void fuzzJwtSignatureLength(const Jwt& jwt, 40 | std::function test_fn) { 41 | // truncate bytes 42 | for (size_t count = 1; count < jwt.signature_.size(); ++count) { 43 | Jwt fuzz_jwt(jwt); 44 | fuzz_jwt.signature_ = jwt.signature_.substr(0, count); 45 | test_fn(fuzz_jwt); 46 | } 47 | } 48 | 49 | void fuzzJwtSignature(const Jwt& jwt, 50 | std::function test_fn) { 51 | fuzzJwtSignatureBits(jwt, test_fn); 52 | fuzzJwtSignatureLength(jwt, test_fn); 53 | } 54 | 55 | // copy from ESP: 56 | // https://github.com/cloudendpoints/esp/blob/master/src/api_manager/auth/lib/auth_jwt_validator_test.cc 57 | const char kPublicKeyX509[] = 58 | "{\"62a93512c9ee4c7f8067b5a216dade2763d32a47\": \"-----BEGIN " 59 | "CERTIFICATE-----" 60 | "\\nMIIDYDCCAkigAwIBAgIIEzRv3yOFGvcwDQYJKoZIhvcNAQEFBQAwUzFRME8GA1UE\\nAxNI" 61 | "NjI4NjQ1NzQxODgxLW5vYWJpdTIzZjVhOG04b3ZkOHVjdjY5OGxqNzh2djBs\\nLmFwcHMuZ29" 62 | "vZ2xldXNlcmNvbnRlbnQuY29tMB4XDTE1MDkxMTIzNDg0OVoXDTI1\\nMDkwODIzNDg0OVowUz" 63 | "FRME8GA1UEAxNINjI4NjQ1NzQxODgxLW5vYWJpdTIzZjVh\\nOG04b3ZkOHVjdjY5OGxqNzh2d" 64 | "jBsLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A" 65 | "MIIBCgKCAQEA0YWnm/eplO9BFtXszMRQ\\nNL5UtZ8HJdTH2jK7vjs4XdLkPW7YBkkm/" 66 | "2xNgcaVpkW0VT2l4mU3KftR+6s3Oa5R\\nnz5BrWEUkCTVVolR7VYksfqIB2I/" 67 | "x5yZHdOiomMTcm3DheUUCgbJRv5OKRnNqszA\\n4xHn3tA3Ry8VO3X7BgKZYAUh9fyZTFLlkeA" 68 | "h0+bLK5zvqCmKW5QgDIXSxUTJxPjZ\\nCgfx1vmAfGqaJb+" 69 | "nvmrORXQ6L284c73DUL7mnt6wj3H6tVqPKA27j56N0TB1Hfx4\\nja6Slr8S4EB3F1luYhATa1" 70 | "PKUSH8mYDW11HolzZmTQpRoLV8ZoHbHEaTfqX/" 71 | "aYah\\nIwIDAQABozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/" 72 | "wQEAwIHgDAWBgNVHSUB\\nAf8EDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAP4gk" 73 | "DCrPMI27/" 74 | "QdN\\nwW0mUSFeDuM8VOIdxu6d8kTHZiGa2h6nTz5E+" 75 | "twCdUuo6elGit3i5H93kFoaTpex\\nj/eDNoULdrzh+cxNAbYXd8XgDx788/" 76 | "jm06qkwXd0I5s9KtzDo7xxuBCyGea2LlpM\\n2HOI4qFunjPjFX5EFdaT/Rh+qafepTKrF/" 77 | "GQ7eGfWoFPbZ29Hs5y5zATJCDkstkY\\npnAya8O8I+" 78 | "tfKjOkcra9nOhtck8BK94tm3bHPdL0OoqKynnoRCJzN5KPlSGqR/h9\\nSMBZzGtDOzA2sX/" 79 | "8eyU6Rm4MV6/1/53+J6EIyarR5g3IK1dWmz/YT/YMCt6LhHTo\\n3yfXqQ==\\n-----END " 80 | "CERTIFICATE-----\\n\",\"b3319a147514df7ee5e4bcdee51350cc890cc89e\": " 81 | "\"-----BEGIN " 82 | "CERTIFICATE-----" 83 | "\\nMIIDYDCCAkigAwIBAgIICjE9gZxAlu8wDQYJKoZIhvcNAQEFBQAwUzFRME8GA1UE\\nAxNI" 84 | "NjI4NjQ1NzQxODgxLW5vYWJpdTIzZjVhOG04b3ZkOHVjdjY5OGxqNzh2djBs\\nLmFwcHMuZ29" 85 | "vZ2xldXNlcmNvbnRlbnQuY29tMB4XDTE1MDkxMzAwNTAyM1oXDTI1\\nMDkxMDAwNTAyM1owUz" 86 | "FRME8GA1UEAxNINjI4NjQ1NzQxODgxLW5vYWJpdTIzZjVh\\nOG04b3ZkOHVjdjY5OGxqNzh2d" 87 | "jBsLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A" 88 | "MIIBCgKCAQEAqDi7Tx4DhNvPQsl1ofxx\\nc2ePQFcs+L0mXYo6TGS64CY/" 89 | "2WmOtvYlcLNZjhuddZVV2X88m0MfwaSA16wE+" 90 | "RiK\\nM9hqo5EY8BPXj57CMiYAyiHuQPp1yayjMgoE1P2jvp4eqF+" 91 | "BTillGJt5W5RuXti9\\nuqfMtCQdagB8EC3MNRuU/" 92 | "KdeLgBy3lS3oo4LOYd+74kRBVZbk2wnmmb7IhP9OoLc\\n1+7+" 93 | "9qU1uhpDxmE6JwBau0mDSwMnYDS4G/" 94 | "ML17dC+ZDtLd1i24STUw39KH0pcSdf\\nFbL2NtEZdNeam1DDdk0iUtJSPZliUHJBI/" 95 | "pj8M+2Mn/" 96 | "oA8jBuI8YKwBqYkZCN1I9\\n5QIDAQABozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/" 97 | "wQEAwIHgDAWBgNVHSUB\\nAf8EDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAHSPR" 98 | "7fDAWyZ825IZ\\n86hEsQZCvmC0QbSzy62XisM/uHUO75BRFIAvC+zZAePCcNo/" 99 | "nh6FtEM19wZpxLiK\\n0m2nqDMpRdw3Qt6BNhjJMozTxA2Xdipnfq+fGpa+" 100 | "bMkVpnRZ53qAuwQpaKX6vagr\\nj83Bdx2b5WPQCg6xrQWsf79Vjj2U1hdw7+" 101 | "klcF7tLef1p8qA/ezcNXmcZ4BpbpaO\\nN9M4/kQOA3Y2F3ISAaOJzCB25F259whjW+Uuqd/" 102 | "L9Lb4gPPSUMSKy7Zy4Sn4il1U\\nFc94Mi9j13oeGvLOduNOStGu5XROIxDtCEjjn2y2SL2bPw" 103 | "0qAlIzBeniiApkmYw/\\no6OLrg==\\n-----END CERTIFICATE-----\\n\"}"; 104 | 105 | // A real X509 public key from 106 | // https://www.googleapis.com/service_accounts/v1/metadata/x509/[SERVICE_ACCOUNT]@[PROJECT_ID].iam.gserviceaccount.com 107 | const std::string kRealX509Jwks = R"( 108 | { 109 | "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", 110 | "08fcbb64ace10689705c063c0f5a165da5952acd": "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIILmYDGTHFClgwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UE\nAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MB4XDTE1MTExMTAwMjMyMVoXDTI1MTEw\nODAwMjMyMVowIDEeMBwGA1UEAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmixY2zuGgr6Lhed4knYbHNCGSj/t\n8uV1PDhJORUhHd0JnhAJNtRTU3KDYC88mQZqgcRmn2HCvmi5Koc2iMDLrBomBC8p\naFJn8Mo6ZPZNMY9rp0MUkY4IZgJ4I0LLl0fPmPwupkVMOXGIAvTCLfs/eibJJtgQ\nuxUZepMoiMxmBJL3iP7Bg3K6nKJVcalBpMfOibMKUMxk/J8/ud1IGQA0JhCo2WxS\nF7sQR7j5qC9R5x1OIZi0kpa/8WseBx2Y2oI6/EwHKM2glA/mkBqTIODT1sgCddGf\nBKY+yTfKw8ZNSqNRjg2tUYmxLkSSUELCyl4CSSFM2ZDo1LIzbtiKSZjOLQIDAQAB\nozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK\nBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAgChPhXk5vx3Ni8zcM5XU6pYv\nT7qxiYTHqt2ZTA63LA5k4jdCuaqQgJNZO8MmmlYzdihV2MrxTgUsq+GjPDfC8DX/\nGCKnBVBXBUxNJFQHs+D/o3twcoWKDE0/dmECWGhKuzyhs2CuE61mCphnvq/s0Cep\nYtwZTf9DzP0meHW6INXMxJaHbRjDJIC7Eg7F8JqAXsUi1LLt3xKiFuKApVNkWX+F\nEEOUPOkwLN/OTghJKG1xqR+OVs+9yniDflbW246tx+o+7eOJd0qk2GmMd9O3ZWwD\noynUQnXfZRpJZPTEfP5Z2XMMSfQBSB4zVgDFVhryD7X2C9lGUBymVEf2fZ4TFQ==\n-----END CERTIFICATE-----\n", 111 | "018cf5a21706e2b707e4104c33206c58e7262bf5": "-----BEGIN CERTIFICATE-----\nMIIDVDCCAjygAwIBAgIIPqS1LlT90NAwDQYJKoZIhvcNAQEFBQAwTTFLMEkGA1UE\nAxNCc2VydmljZS1jb250cm9sLWluLWxvYWQtdGVzdC5lc3AtbG9hZC10ZXN0Lmlh\nbS5nc2VydmljZWFjY291bnQuY29tMB4XDTIwMDEwNTA1MzAzNloXDTIwMDEyMTE3\nNDUzNlowTTFLMEkGA1UEAxNCc2VydmljZS1jb250cm9sLWluLWxvYWQtdGVzdC5l\nc3AtbG9hZC10ZXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyuGy/yh+zX1Prk7CPmTYOzXB6OdpBzw90nZV\nIbg7wY3cZ/ab82bHHwJqfRmbHzPFXg0OZ7mWtQJo292kcr+nAfQx0iRpsZkvcyyB\nV4o1IXWQNfpnWlJBmdp5AZZ2Ag3e6MzDAWLCUvYOnU2z8lhWzmrhhxPuToIkkSay\nKOrgThS2iGhHSyRl7lfCGG6/Ml0xnOKfb73pGPzRKAzHn3ML6rN2Z6/iznZZeFSS\n12SIpVa3wqOzACTyFuybAmcr6CI482FwWLnSabWqMBrQcOue2UbPtXF+faOUN9Dc\nlR3JiASOtfsWieYf/R2y1M5BuOffhcBIHzKEvNiHwJ7/8KZhnQIDAQABozgwNjAM\nBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEF\nBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEApz5t9q3aBD18M0pSmJMW+zndXXQP5lR8\nubKuUm8hJ96J2hq/udw9ngbbPkREFp23xqCZZtGqrUlwR9irnWG9fuJ2j4RXoUFy\nkf4L+vD1bXFxwj5p7AbX6V/ns5+JD4wlxAPcnbdeetwy7OIQowWWWjbFNgkASJ2U\nMOrO1Shd9GxbHX+I5KR8jvP1z7Ok+dIOe7oOtd7Bypz+OTnf7ZdCrSiUQ+OOc3X+\nHMxc9rtMt0c630x92oyZAtVYbeZN7InAB8aJjAc21zdfJ+hlmU5cdKYRqXmKYfEH\nEmrRygDj37XnUtQ1akkCvZzXt8Qd/IjEazpKaR9s1YLNTf+NwV94qQ==\n-----END CERTIFICATE-----\n", 112 | "0a03383fa5654d0e87ee6495a8500320522eb5c9": "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIILoG3DNlfB/QwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UE\nAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MB4XDTE5MDcwOTAxMzQyOVoXDTI5MDcw\nNjAxMzQyOVowIDEeMBwGA1UEAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsILt7Eo/k1nU6hWmWl8maMw42o5Z\n5Ajqop4rYAAbO7uaWo686KX3t3bBLyVSebCYU2+bmMzEr8uvOF6eu+1mgArUsw7F\n4jAFLK71hnwB8uXz14s7hWvIYvm5GVa4HDhOGZM5iMOsXzDjXWH9dB8u2+wWAp/5\nGtwse3mx89WOTcNiJkO/ZdekG18LXSidRQqROTYBmDMTy/FU/4xhPsqB4ZzH2ueG\norwJHLfZZTzPdrBmo+jGwoSMb069FGdUIP8RMnLCJUmEFdqT2cQn28nEXFMaTOI/\naWuuuM/5vopsKWXMVT3yjjDOXJbw1rar/zJoYOT9VnWAkkcNP5fY6tQbLwIDAQAB\nozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK\nBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEASJTehr0WmoSALKKDHza/vW5O\nOENvxjRaYiViM1lAKzPVxTBI/mdjCI+9k97ZfZuF2MTu41VWz9RcfjAvBsbkNS8f\n9IJnFU7N/gXKPM/gfG6GvFxr450fhFewcPIlutmFUrXkoJhXUhtb2nwxjTa0za1C\nIe8FqNo2/swL1cN5YqlO7eUk37g50etHHnWyFpHcPmQuKlJdSGfD5IT4/KE2Gi0T\nLVLcNWU/KUJEk62GziCDuWbLzcnynGjeMVzYOHN+4ZyWcuLZGsypBgCK0q3kFkkv\nu4uYcxJVI6J2nRTcMFC2zdqggmkAVTe0wX8HQ5Ns8IRHCUNNTu8jFrKj7IHp0w==\n-----END CERTIFICATE-----\n", 113 | "aeb0d379d4a71b561b77e1e05fe5a8a0ef2cf089": "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIIJWEK8Mx3fIgwDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UE\nAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MB4XDTE2MDMwMTE3NDExM1oXDTI2MDIy\nNzE3NDExM1owIDEeMBwGA1UEAxMVMTA2OTQ3MDEyMjYwNDg4NzM2MTU3MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt/D2PDPvrMb4KPJM712b9J3CQDLe\nBN5FxNh0/C72QngZFRyVSdarwen2MlddZGD6zsNSBwDVM0mkGi+fFHOgEe6WRlRT\nYnKUFibhR0Oh+G4jKVeMbed8wWGBhq4Fxo5i3230NMK0Iqkvc83wy1IpCHtK5dU1\natuOunh1DoKV9pM0bZwevHigLoNBjbyzpu6MaMh9WIGZ1JRTBtN1TdXI+yBvoNCA\nG284CjeTy1NTOynj11Nw8FevzeVWsOyKH5kzbvNZICfxeELD3GyQVF7WG5U80c8R\nxpcvm6wkYXoEoeQ9uOey9cJpXfvz1FLMts5IpafEf2STqjfl6vRdfhOOuQIDAQAB\nozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK\nBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAWTWrwEoWodbxXBtCrlHTAyrv\nigJio3mMZFVcEQJ23/H83R4+sk2Pri4QnR575Jyz/ddOn/b6QU7j/oYf5PUK93Gg\nTQ/5BZBXGTRno8YM2tO/joiIV3hwKtZNh51uiB+/0zkq76yEQ38lu9ZrRMMlsr85\nZ8tdMf6qlxIlbU46X1Jq0x8ETuhS5qtxKovqo6XwSAvcZaUNf5PgZ7lXEV3i/Og4\nQJGVjh9vuAfanZWMvoaMOtEiYyaRdpKVcXA+Tsrq13XUlk5cUgxrOj0gUO85WiPk\nFGSx3g1sIdsD/bUpy5XGV5CDFN4QUXDvTzwdeFPh8WH/alf1jp2xMoiYYx5HeQ==\n-----END CERTIFICATE-----\n" 114 | } 115 | )"; 116 | 117 | /** 118 | * Provide an overloaded << to output a Status to std::ostream for better error 119 | * messages in test output 120 | * @param os the std::ostream to write to 121 | * @param status is the enum status. 122 | * @return the std::ostream os 123 | */ 124 | std::ostream& operator<<(std::ostream& os, const Status& status) { 125 | return os << "Status(" << static_cast(status) << ", " 126 | << getStatusString(status) << ")"; 127 | } 128 | 129 | } // namespace jwt_verify 130 | } // namespace google 131 | -------------------------------------------------------------------------------- /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_jwk_ec_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 | // Please see jwt_generator.py and jwk_generator.py under 24 | // https://github.com/istio/proxy/tree/master/src/envoy/http/jwt_auth/tools 25 | // for ES{256,384,512}-signed jwt token and public jwk generation, respectively. 26 | // jwt_generator.py uses ES{256,384,512} private key file to generate JWT token. 27 | // ES256 private key file can be generated by: 28 | // $ openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem 29 | // ES384 private key file can be generated by: 30 | // $ openssl ecparam -genkey -name secp384r1 -noout -out private_key.pem 31 | // ES512 private key file can be generated by: 32 | // $ openssl ecparam -genkey -name secp521r1 -noout -out private_key.pem 33 | // jwk_generator.py uses ES{256.384,512} public key file to generate JWK. 34 | // ES256, ES384 and ES512 public key files can be generated by: 35 | // $ openssl ec -in private_key.pem -pubout -out public_key.pem 36 | 37 | // ES256 private key: 38 | // "-----BEGIN EC PRIVATE KEY-----" 39 | // "MHcCAQEEIOyf96eKdFeSFYeHiM09vGAylz+/auaXKEr+fBZssFsJoAoGCCqGSM49" 40 | // "AwEHoUQDQgAEEB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5n3ZsIFO8wV" 41 | // "DyUptLYxuCNPdh+Zijoec8QTa2wCpZQnDw==" 42 | // "-----END EC PRIVATE KEY-----" 43 | 44 | // ES256 public key: 45 | // "-----BEGIN PUBLIC KEY-----" 46 | // "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEB54wykhS7YJFD6RYJNnwbWEz3cI" 47 | // "7CF5bCDTXlrwI5n3ZsIFO8wVDyUptLYxuCNPdh+Zijoec8QTa2wCpZQnDw==" 48 | // "-----END PUBLIC KEY-----" 49 | 50 | // ES384 private key: 51 | // "-----BEGIN EC PRIVATE KEY-----" 52 | // "MIGkAgEBBDDqSPe2gvdUVMQcCxpr60rScFgjEQZeCYvZRq3oyY9mECVMK7nuRjLx" 53 | // "blWjf6DH9E+gBwYFK4EEACKhZANiAATJjwNZzJaWuv3cVOuxwjlh3PY0Lt6Z+gpg" 54 | // "cktfZ2vdxKB/DQa7ECS5DmcEwmZVXmACfnBXER+SwM5r/O9IccaR5glR+XzLXXBi" 55 | // "Q6UWMG32k4LDn5GV9mA85reluZSq7Fk=" 56 | // "-----END EC PRIVATE KEY-----" 57 | 58 | // ES384 public key: 59 | // "-----BEGIN PUBLIC KEY-----" 60 | // "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEyY8DWcyWlrr93FTrscI5Ydz2NC7emfoK" 61 | // "YHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5gAn5wVxEfksDOa/zvSHHGkeYJUfl8y11w" 62 | // "YkOlFjBt9pOCw5+RlfZgPOa3pbmUquxZ" 63 | // "-----END PUBLIC KEY-----" 64 | 65 | // ES512 private key: 66 | // "-----BEGIN EC PRIVATE KEY-----" 67 | // "MIHcAgEBBEIBKlG7GPIoqQujJHwe21rnsZePySFyd45HPe3FeldgZQEHqcUiZgpb" 68 | // "BgiuYMPHytEaohj1yC5gyOOsOfgsWY2qSsWgBwYFK4EEACOhgYkDgYYABAG4o4ns" 69 | // "e68+7fv2Y/xOjqNDl3vQv/jAkg/jloqNeQE0Box/VqW1ozetmaq61P58CYqqsMem" 70 | // "bGCoVHPydz0WjG3VQgAXFqWMIi6hUQDs8khoM8nl49e1nSGSKdPUH9tD3WZKEKJH" 71 | // "/jdaGyfU/sbPfRYScu4mzVIZXPWhPiUhFRieLY58iQ==" 72 | // "-----END EC PRIVATE KEY-----" 73 | 74 | // ES512 public key: 75 | // "-----BEGIN PUBLIC KEY-----" 76 | // "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBuKOJ7HuvPu379mP8To6jQ5d70L/4" 77 | // "wJIP45aKjXkBNAaMf1altaM3rZmqutT+fAmKqrDHpmxgqFRz8nc9Foxt1UIAFxal" 78 | // "jCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B/bQ91mShCiR/43Whsn1P7Gz30WEnLuJs1S" 79 | // "GVz1oT4lIRUYni2OfIk=" 80 | // "-----END PUBLIC KEY-----" 81 | const std::string PublicKeyJwkEC = R"( 82 | { 83 | "keys": [ 84 | { 85 | "kty": "EC", 86 | "crv": "P-256", 87 | "alg": "ES256", 88 | "kid": "abc", 89 | "x": "EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k", 90 | "y": "92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8" 91 | }, 92 | { 93 | "kty": "EC", 94 | "crv": "P-256", 95 | "alg": "ES256", 96 | "kid": "xyz", 97 | "x": "EB54wykhS7YJFD6RYJNnwbWEz3cI7CF5bCDTXlrwI5k", 98 | "y": "92bCBTvMFQ8lKbS2MbgjT3YfmYo6HnPEE2tsAqWUJw8" 99 | }, 100 | { 101 | "kty": "EC", 102 | "crv": "P-384", 103 | "alg": "ES384", 104 | "kid": "es384", 105 | "x": "yY8DWcyWlrr93FTrscI5Ydz2NC7emfoKYHJLX2dr3cSgfw0GuxAkuQ5nBMJmVV5g", 106 | "y": "An5wVxEfksDOa_zvSHHGkeYJUfl8y11wYkOlFjBt9pOCw5-RlfZgPOa3pbmUquxZ" 107 | }, 108 | { 109 | "kty": "EC", 110 | "crv": "P-521", 111 | "alg": "ES512", 112 | "kid": "es512", 113 | "x": "Abijiex7rz7t-_Zj_E6Oo0OXe9C_-MCSD-OWio15ATQGjH9WpbWjN62ZqrrU_nwJiqqwx6ZsYKhUc_J3PRaMbdVC", 114 | "y": "FxaljCIuoVEA7PJIaDPJ5ePXtZ0hkinT1B_bQ91mShCiR_43Whsn1P7Gz30WEnLuJs1SGVz1oT4lIRUYni2OfIk" 115 | } 116 | ] 117 | } 118 | )"; 119 | 120 | // "{"kid":"abc"}" 121 | const std::string JwtES256Text = 122 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYyJ9.eyJpc3MiOiI2Mj" 123 | "g2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvc" 124 | "GVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1" 125 | "MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3V" 126 | "udC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.T2KAwChqg" 127 | "o2ZSXyLh3IcMBQNSeRZRe5Z-MUDl-s-F99XGoyutqA6lq8bKZ6vmjZAlpVG8AGRZW9J" 128 | "Gp9lq3cbEw"; 129 | 130 | const std::string JwtTextEC = 131 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiYyJ9.eyJpc3MiOiI2Mj" 132 | "g2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvc" 133 | "GVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1" 134 | "MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3V" 135 | "udC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.T2KAwChqg" 136 | "o2ZSXyLh3IcMBQNSeRZRe5Z-MUDl-s-F99XGoyutqA6lq8bKZ6vmjZAlpVG8AGRZW9J" 137 | "Gp9lq3cbEw"; 138 | 139 | // "{"kid":"es384"}" 140 | const std::string JwtES384Text = 141 | "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6ImVzMzg0In0.eyJpc3MiOi" 142 | "I2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ" 143 | "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4" 144 | "ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmd" 145 | "zZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS" 146 | "9teWFwaSJ9.aKFxrqV4_rg1Zf2DamTU0D76hOq9-FYu-LNmpGPthjJKv31mOZ4t" 147 | "J40x2FVVJx5d8lntg3bsy1IN0z9C7MD_k10Y7Gea1YB7Jyi-DR68U5krJzzwKmD" 148 | "9ap1J7tb2UrzT"; 149 | 150 | // "{"kid":"es512"}" 151 | const std::string JwtES512Text = 152 | "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImVzNTEyIn0.eyJpc3MiOi" 153 | "I2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ" 154 | "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4" 155 | "ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmd" 156 | "zZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS" 157 | "9teWFwaSJ9.ATSReP9zpba6PRJZmlIEA78Ft-FZS1m_SpFLqfiNQNexaDaTmmVr" 158 | "IqD9X-krPxk0c8KSBeMlU-QLOsbh37coamruAPKoAODYWA-QKUN2a_xem8WrudK" 159 | "VXWsmQlZDOJA0lQWI-YGMEPrDr17mljMhZwSGbVVST9l-nZiMXyMK0z8hR9Mn"; 160 | 161 | // "{"kid":"abcdef"}" 162 | const std::string JwtTextWithNonExistKidEC = 163 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImFiY2RlZiJ9.eyJpc3MiOi" 164 | "I2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZ" 165 | "GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4" 166 | "ODEtbm9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmd" 167 | "zZXJ2aWNlYWNjb3VudC5jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS" 168 | "9teWFwaSJ9.rWSoOV5j7HxHc4yVgZEZYUSgY7AUarG3HxdfPON1mw6II_pNUsc8" 169 | "_sVf7Yv2-jeVhmf8BtR99wnOwEDhVYrVpQ"; 170 | 171 | const std::string JwtTextECNoKid = 172 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2Mjg2NDU3NDE4ODEtbm" 173 | "9hYml1MjNmNWE4bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2a" 174 | "WNlYWNjb3VudC5jb20iLCJzdWIiOiI2Mjg2NDU3NDE4ODEtbm9hYml1MjNmNWE4" 175 | "bThvdmQ4dWN2Njk4bGo3OHZ2MGxAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5" 176 | "jb20iLCJhdWQiOiJodHRwOi8vbXlzZXJ2aWNlLmNvbS9teWFwaSJ9.zlFcET8Fi" 177 | "OYcKe30A7qOD4TIBvtb9zIVhDcM8pievKs1Te-UOBcklQxhwXMnRSSEBY4P0pfZ" 178 | "qWJT_V5IVrKrdQ"; 179 | 180 | class VerifyJwkECTest : public testing::Test { 181 | protected: 182 | void SetUp() { 183 | jwks_ = Jwks::createFrom(PublicKeyJwkEC, Jwks::Type::JWKS); 184 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 185 | } 186 | 187 | JwksPtr jwks_; 188 | }; 189 | 190 | TEST_F(VerifyJwkECTest, KidOK) { 191 | Jwt jwt; 192 | EXPECT_EQ(jwt.parseFromString(JwtTextEC), Status::Ok); 193 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); 194 | 195 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 196 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 197 | }); 198 | } 199 | 200 | TEST_F(VerifyJwkECTest, KidES384OK) { 201 | Jwt jwt; 202 | EXPECT_EQ(jwt.parseFromString(JwtES384Text), Status::Ok); 203 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); 204 | 205 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 206 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 207 | }); 208 | } 209 | 210 | TEST_F(VerifyJwkECTest, KidES512OK) { 211 | Jwt jwt; 212 | EXPECT_EQ(jwt.parseFromString(JwtES512Text), Status::Ok); 213 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); 214 | 215 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 216 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 217 | }); 218 | } 219 | 220 | TEST_F(VerifyJwkECTest, NoKidOK) { 221 | Jwt jwt; 222 | EXPECT_EQ(jwt.parseFromString(JwtTextECNoKid), Status::Ok); 223 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); 224 | 225 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 226 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 227 | }); 228 | } 229 | 230 | TEST_F(VerifyJwkECTest, NonExistKidFail) { 231 | Jwt jwt; 232 | EXPECT_EQ(jwt.parseFromString(JwtTextWithNonExistKidEC), Status::Ok); 233 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwksKidAlgMismatch); 234 | } 235 | 236 | TEST_F(VerifyJwkECTest, PubkeyNoAlgOK) { 237 | // Remove "alg" claim from public key. 238 | std::string alg_claim = R"("alg": "ES256",)"; 239 | std::string pubkey_no_alg = PublicKeyJwkEC; 240 | std::size_t alg_pos = pubkey_no_alg.find(alg_claim); 241 | while (alg_pos != std::string::npos) { 242 | pubkey_no_alg.erase(alg_pos, alg_claim.length()); 243 | alg_pos = pubkey_no_alg.find(alg_claim); 244 | } 245 | 246 | jwks_ = Jwks::createFrom(pubkey_no_alg, Jwks::Type::JWKS); 247 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 248 | 249 | Jwt jwt; 250 | EXPECT_EQ(jwt.parseFromString(JwtES256Text), Status::Ok); 251 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 252 | } 253 | 254 | TEST_F(VerifyJwkECTest, PubkeyNoKidOK) { 255 | // Remove "kid" claim from public key. 256 | std::string kid_claim1 = R"("kid": "abc",)"; 257 | std::string kid_claim2 = R"("kid": "xyz",)"; 258 | std::string pubkey_no_kid = PublicKeyJwkEC; 259 | std::size_t kid_pos = pubkey_no_kid.find(kid_claim1); 260 | pubkey_no_kid.erase(kid_pos, kid_claim1.length()); 261 | kid_pos = pubkey_no_kid.find(kid_claim2); 262 | pubkey_no_kid.erase(kid_pos, kid_claim2.length()); 263 | 264 | jwks_ = Jwks::createFrom(pubkey_no_kid, Jwks::Type::JWKS); 265 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 266 | 267 | Jwt jwt; 268 | EXPECT_EQ(jwt.parseFromString(JwtES256Text), Status::Ok); 269 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 270 | } 271 | 272 | } // namespace 273 | } // namespace jwt_verify 274 | } // namespace google 275 | -------------------------------------------------------------------------------- /test/verify_jwk_hmac_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 | const std::string SymmetricKeyHMAC = R"( 24 | { 25 | "keys": [ 26 | { 27 | "kty": "oct", 28 | "alg": "HS256", 29 | "use": "sig", 30 | "kid": "62a93512c9ee4c7f8067b5a216dade2763d32a47", 31 | "k": "LcHQCLETtc_QO4D69zCnQEIAYaZ6BsldibDzuRHE5bI" 32 | }, 33 | { 34 | "kty": "oct", 35 | "alg": "HS256", 36 | "use": "sig", 37 | "kid": "b3319a147514df7ee5e4bcdee51350cc890cc89e", 38 | "k": "nyeGXUHngW64dyg2EuDs_8x6VGa14Bkrv1SFQwOzKfI" 39 | }, 40 | { 41 | "kty": "oct", 42 | "alg": "HS384", 43 | "use": "sig", 44 | "kid": "cda01077a6aa4b0088a6e959044977ef9e51c28b", 45 | "k": "5xYkMHiMVnCBbFEt0Uh1LhIbFB6yakzp2Mh7ESBMUCDq4zMO6WgCMaQwP332FH47" 46 | }, 47 | { 48 | "kty": "oct", 49 | "alg": "HS512", 50 | "use": "sig", 51 | "kid": "f6a7bd9ffd784388924f126280a746964ba61268", 52 | "k": "ID3awf7bo607gitUDWylMMhUyVFr4ZAmnysPw4675A1YmOaYajbqLmMA7fohGLYZdZyaluaiugKvnnGLYTDoUA" 53 | }, 54 | 55 | ] 56 | } 57 | )"; 58 | 59 | // JWT without kid 60 | // Header: {"alg":"HS256","typ":"JWT"} 61 | // Payload: 62 | // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} 63 | const std::string JwtTextNoKid = 64 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." 65 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 66 | "ImV4cCI6MTUwMTI4MTA1OH0." 67 | "_LY8Zz3ssG82v5-T8L2Hg1TsqzCEEKnYOxzrQpDTjwU"; 68 | 69 | // JWT without kid with long exp 70 | // Header: {"alg":"HS256","typ":"JWT"} 71 | // Payload: 72 | // {"iss":"https://example.com","sub":"test@example.com","aud":"example_service","exp":2001001001} 73 | const std::string JwtTextNoKidLongExp = 74 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." 75 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 76 | "ImF1ZCI6ImV4YW1wbGVfc2VydmljZSIsImV4cCI6MjAwMTAwMTAwMX0." 77 | "4tc7M-gJizpbB69_sQi7E0ym0np6uon4V41hVjYV2ic"; 78 | 79 | // JWT with correct kid 80 | // Header: 81 | // {"alg":"HS256","typ":"JWT","kid":"b3319a147514df7ee5e4bcdee51350cc890cc89e"} 82 | // Payload: 83 | // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} 84 | const std::string JwtHS256TextWithCorrectKid = 85 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImIzMzE5YTE0NzUxNGRmN2VlNWU0" 86 | "YmNkZWU1MTM1MGNjODkwY2M4OWUifQ." 87 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 88 | "ImV4cCI6MTUwMTI4MTA1OH0." 89 | "QqSMCAY5UDBvySx0VQhGqIvomZaSRUJOCT6ktV3BhL8"; 90 | 91 | // JWT with correct kid 92 | // Header: 93 | // {"alg":"HS384","typ":"JWT","kid":"cda01077a6aa4b0088a6e959044977ef9e51c28b"} 94 | // Payload: 95 | // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} 96 | const std::string JwtHS384TextWithCorrectKid = 97 | "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6ImNkYTAxMDc3YTZhYTRiMDA4OGE2" 98 | "ZTk1OTA0NDk3N2VmOWU1MWMyOGIifQ." 99 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 100 | "ImV4cCI6MTUwMTI4MTA1OH0." 101 | "F69ivpIRbgrmy1j6_MHl10xDW8iPdzsHAIgln3Z9PEemH9heiQoDUOgG91kA44fL"; 102 | 103 | // JWT with correct kid 104 | // Header: 105 | // {"alg":"HS512","typ":"JWT","kid":"f6a7bd9ffd784388924f126280a746964ba61268"} 106 | // Payload: 107 | // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} 108 | const std::string JwtHS512TextWithCorrectKid = 109 | "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImY2YTdiZDlmZmQ3ODQzODg5MjRm" 110 | "MTI2MjgwYTc0Njk2NGJhNjEyNjgifQ." 111 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 112 | "ImV4cCI6MTUwMTI4MTA1OH0." 113 | "YdILUM4zaeIRuxEMLV13qMX3d1sp63juPXwbpOp_HUjNdGGvocthipOxjQur6JtCLmIfvrI4" 114 | "XNrkxVWd-qS_3g"; 115 | 116 | // JWT with existing but incorrect kid 117 | // Header: 118 | // {"alg":"HS256","typ":"JWT","kid":"62a93512c9ee4c7f8067b5a216dade2763d32a47"} 119 | // Payload: 120 | // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} 121 | const std::string JwtTextWithIncorrectKid = 122 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjYyYTkzNTEyYzllZTRjN2Y4MDY3" 123 | "YjVhMjE2ZGFkZTI3NjNkMzJhNDcifQ." 124 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 125 | "ImV4cCI6MTUwMTI4MTA1OH0." 126 | "GRLODq7HrBduwUJEoJ3alWlXvxhCZZpFgvd1hYRDXa4"; 127 | 128 | // JWT with nonexist kid 129 | // Header: {"alg":"HS256","typ":"JWT","kid":"blahblahblah"} 130 | // Payload: 131 | // {"iss":"https://example.com","sub":"test@example.com","exp":1501281058} 132 | const std::string JwtTextWithNonExistKid = 133 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJsYWhibGFoYmxhaCJ9." 134 | "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidGVzdEBleGFtcGxlLmNvbSIs" 135 | "ImV4cCI6MTUwMTI4MTA1OH0." 136 | "WFHsFo29tA5_gT_rzm6WheQhCwwBPrRZWFEAWRF9Ym4"; 137 | 138 | class VerifyJwkHmacTest : public testing::Test { 139 | protected: 140 | void SetUp() { 141 | jwks_ = Jwks::createFrom(SymmetricKeyHMAC, Jwks::Type::JWKS); 142 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 143 | } 144 | 145 | JwksPtr jwks_; 146 | }; 147 | 148 | TEST_F(VerifyJwkHmacTest, NoKidOK) { 149 | Jwt jwt; 150 | EXPECT_EQ(jwt.parseFromString(JwtTextNoKid), Status::Ok); 151 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 152 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 153 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 154 | }); 155 | } 156 | 157 | TEST_F(VerifyJwkHmacTest, NoKidLongExpOK) { 158 | Jwt jwt; 159 | EXPECT_EQ(jwt.parseFromString(JwtTextNoKidLongExp), Status::Ok); 160 | EXPECT_EQ(verifyJwt(jwt, *jwks_), Status::Ok); 161 | 162 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 163 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 164 | }); 165 | } 166 | 167 | TEST_F(VerifyJwkHmacTest, CorrectKidHS256OK) { 168 | Jwt jwt; 169 | EXPECT_EQ(jwt.parseFromString(JwtHS256TextWithCorrectKid), Status::Ok); 170 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 171 | 172 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 173 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 174 | }); 175 | } 176 | 177 | TEST_F(VerifyJwkHmacTest, CorrectKidHS384OK) { 178 | Jwt jwt; 179 | EXPECT_EQ(jwt.parseFromString(JwtHS384TextWithCorrectKid), Status::Ok); 180 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 181 | 182 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 183 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 184 | }); 185 | } 186 | 187 | TEST_F(VerifyJwkHmacTest, CorrectKidHS512OK) { 188 | Jwt jwt; 189 | EXPECT_EQ(jwt.parseFromString(JwtHS512TextWithCorrectKid), Status::Ok); 190 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 191 | 192 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 193 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 194 | }); 195 | } 196 | 197 | TEST_F(VerifyJwkHmacTest, NonExistKidFail) { 198 | Jwt jwt; 199 | EXPECT_EQ(jwt.parseFromString(JwtTextWithNonExistKid), Status::Ok); 200 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwksKidAlgMismatch); 201 | } 202 | 203 | TEST_F(VerifyJwkHmacTest, OkSymmetricKeyNotAlg) { 204 | // Remove "alg" claim from symmetric key. 205 | std::string alg_claim = R"("alg": "HS256",)"; 206 | std::string symmkey_no_alg = SymmetricKeyHMAC; 207 | std::size_t alg_pos = symmkey_no_alg.find(alg_claim); 208 | while (alg_pos != std::string::npos) { 209 | symmkey_no_alg.erase(alg_pos, alg_claim.length()); 210 | alg_pos = symmkey_no_alg.find(alg_claim); 211 | } 212 | 213 | jwks_ = Jwks::createFrom(symmkey_no_alg, Jwks::Type::JWKS); 214 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 215 | 216 | Jwt jwt; 217 | EXPECT_EQ(jwt.parseFromString(JwtTextNoKid), Status::Ok); 218 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 219 | } 220 | 221 | TEST_F(VerifyJwkHmacTest, OkSymmetricKeyNotKid) { 222 | // Remove "kid" claim from symmetric key. 223 | std::string kid_claim1 = 224 | R"("kid": "62a93512c9ee4c7f8067b5a216dade2763d32a47",)"; 225 | std::string kid_claim2 = 226 | R"("kid": "b3319a147514df7ee5e4bcdee51350cc890cc89e",)"; 227 | std::string symmkey_no_kid = SymmetricKeyHMAC; 228 | std::size_t kid_pos = symmkey_no_kid.find(kid_claim1); 229 | symmkey_no_kid.erase(kid_pos, kid_claim1.length()); 230 | kid_pos = symmkey_no_kid.find(kid_claim2); 231 | symmkey_no_kid.erase(kid_pos, kid_claim2.length()); 232 | jwks_ = Jwks::createFrom(symmkey_no_kid, Jwks::Type::JWKS); 233 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 234 | 235 | Jwt jwt; 236 | EXPECT_EQ(jwt.parseFromString(JwtTextNoKid), Status::Ok); 237 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 238 | } 239 | 240 | } // namespace 241 | } // namespace jwt_verify 242 | } // namespace google 243 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/verify_jwk_rsa_pss_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 | // The following is the jwks from querying a private temporary instance of 24 | // keycloak at 25 | // https://keycloak.localhost/auth/realms/applications/protocol/openid-connect/certs 26 | 27 | const std::string PublicKeyRSAPSS = R"( 28 | { 29 | "keys": [ 30 | { 31 | "kid": "RGlV9a54XdAsuiYUDkQ0hDkiSZ92TJCgneh7-HvN-sk", 32 | "kty": "RSA", 33 | "alg": "PS384", 34 | "use": "sig", 35 | "n": "8logDcIilAXYJ2kNOrUIAVrWg3g-i1EUsWzEwAV3WT9NNwisUsljdyK3OOxy8yhbWyunxia-4Qo8nCIjURfLn0XoJyozCsruTWuvv2nvWx380zDD5gN-RK0kab_UWOV_zkr9YhBYd2PUB-sCcEwDKj8uHZrJ2CvXvxt2LV8_l_kwlCEDS_q97eEqvxhvYFF8DVo_AGABoK6fU1urn7X-GQcClgOEI8qKho-FU0RPJM80pnmCVds7oP2NYHSnAbkxltiB2cU1qazs21A52obU5zemUwJcdEGpykBKgc_aKaxkusLs2O0xWvnDbgXvboqb_0UhZPWNILZYK09jYCFobQ", 36 | "e": "AQAB", 37 | "x5c": [ 38 | "MIICpzCCAY8CBgFzHKZh6TANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxhcHBsaWNhdGlvbnMwHhcNMjAwNzA1MDE0MzUyWhcNMzAwNzA1MDE0NTMyWjAXMRUwEwYDVQQDDAxhcHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDyWiANwiKUBdgnaQ06tQgBWtaDeD6LURSxbMTABXdZP003CKxSyWN3Irc47HLzKFtbK6fGJr7hCjycIiNRF8ufRegnKjMKyu5Na6+/ae9bHfzTMMPmA35ErSRpv9RY5X/OSv1iEFh3Y9QH6wJwTAMqPy4dmsnYK9e/G3YtXz+X+TCUIQNL+r3t4Sq/GG9gUXwNWj8AYAGgrp9TW6uftf4ZBwKWA4QjyoqGj4VTRE8kzzSmeYJV2zug/Y1gdKcBuTGW2IHZxTWprOzbUDnahtTnN6ZTAlx0QanKQEqBz9oprGS6wuzY7TFa+cNuBe9uipv/RSFk9Y0gtlgrT2NgIWhtAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMaFjwzA+74wY+2YjsMk79IpvDV3Kke7hBThz+9+KT8u2cCX1fUucZemk5vNfLbv+Swhjs+Psuhim1mXxqfyNeSPrIznWAQDSUIW5c3SuJtIOXbfXjIoeK7QW4yhv4NsQBnXd0o6UncvlSZvFxQCMDqGrybOim2O93nM7p3udE2c08tAZ/XRFrxgENvuO3XGAg5EIiUEbHjtOgpjGwkxDfvOm0C4giaaHbUEarzK0olAExtKENwa9AKsxnckMH/kWNBY6ohYSJ7DojRUY84bKTWWFx8Krj0kzjNkbadrdAya8YoRp4IRqjZ9cA9i+yIlN1ulhL9GGq4JDHqTFaoBxiQ=" 39 | ], 40 | "x5t": "6mK6ZUgfCVv2sm7GVsDR_tdPjjE", 41 | "x5t#S256": "PJYSXCbyowmimYVC41vPKlZyUfmqcGNo6Cfba4y8pkE" 42 | }, 43 | { 44 | "kid": "u_ZZAorrQhtL2MA-bWkZ0qpzjia4D3u6QUvBRscHLrg", 45 | "kty": "RSA", 46 | "alg": "PS512", 47 | "use": "sig", 48 | "n": "0k2d9uo6k1luw7VpgeZuf4xIlhpp_pPndYjHCZBhSmXsXN7lV-HhYE3Vv2WurMT32HrOJVm4zJWbQOOFG2LD8Byw1sKzZWoS_wwFUWdeTzw43JniK-PYDY5sOM5sn6uGtfLNzm0fO0gkhLMf-dgodimA7dw_4kFqIYP9VNJOi3Pw3XI0uAuK1X7_eJ7mzWlCC8ERT0iJELKqC1Hx8Ub13SeTaFvPoguvx08END87WUbkdp4e4N16d_wVUWuutidY2HkjcklNhUWTc0BSST89TyKwwXwrXqY7_Ka14pjo8H-s6nT1ns80LiTjvjgzyeMRbptOYmgxlmYL0AXI07hbZw", 49 | "e": "AQAB", 50 | "x5c": [ 51 | "MIICpzCCAY8CBgFzHKaU5jANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxhcHBsaWNhdGlvbnMwHhcNMjAwNzA1MDE0NDA1WhcNMzAwNzA1MDE0NTQ1WjAXMRUwEwYDVQQDDAxhcHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSTZ326jqTWW7DtWmB5m5/jEiWGmn+k+d1iMcJkGFKZexc3uVX4eFgTdW/Za6sxPfYes4lWbjMlZtA44UbYsPwHLDWwrNlahL/DAVRZ15PPDjcmeIr49gNjmw4zmyfq4a18s3ObR87SCSEsx/52Ch2KYDt3D/iQWohg/1U0k6Lc/DdcjS4C4rVfv94nubNaUILwRFPSIkQsqoLUfHxRvXdJ5NoW8+iC6/HTwQ0PztZRuR2nh7g3Xp3/BVRa662J1jYeSNySU2FRZNzQFJJPz1PIrDBfCtepjv8prXimOjwf6zqdPWezzQuJOO+ODPJ4xFum05iaDGWZgvQBcjTuFtnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALyEXqK3BYwVU/7o8+wfDwOJ2nk9OmoGIu3hu6gwqC92DOXClus11UGHKsfhhYlQdzdBpNRD5hjabqaEpCoqF4HwzIWL+Pc08hnnP1IsxkAZdKicKLeFE6BwldK/RYK5vuNXjO824xGnTJuIEApWD2lWf7T3Ndyve14vx1B+6NPmazXPHcSbDN+06bXg8YeZVMnBqRYVBCxo5IoEwP2kJC/F3RbYJTF8QV2/AnwA/Bt1/rl6Y9MPqCwntyfrxq26Bwlpf9vC1dwRK45Tgv9c94/rD1Xax3MPQhhnCo+6H9UWSe/mIdPC2jPifcYJGujPpbbcp23fBOig+FwY6OZl1oo=" 52 | ], 53 | "x5t": "YVSZ0gbRsdQ2ItVwc00GynAyFwk", 54 | "x5t#S256": "ZOJz7HKW1fQVb46QI0Ymw7v4u1mfRmzDJmOp3zUMpt4" 55 | }, 56 | { 57 | "kid": "4hmO65bbc7IVI-3PfA2emAlO0qhv4rB__yw8BPQ58q8", 58 | "kty": "RSA", 59 | "alg": "PS256", 60 | "use": "sig", 61 | "n": "vz40nPlC2XsAGbqfp3S4nyl2G1iMFER1l_I4k7gfC-87UWu2-a7BZQHb646WmSXu8xFzu0x5FFTFmu_v3Aj1NAcdYbz09UypSxfH--aw7ATiSWL26jHixFP4l6miJxaXV-rlp9qFSO--1JRnlvYrt6M5mQI0ZvN8EahAVXIHNtDMZYu0HYwwL7j45gjF9o9kDbfMSPr8Oni0QC2tTcCg623OlNqrJZFT4YNJ8A1nRfwGwBLFp5pxpK9ZCekQVhBpZNUrlLB5uDaB5H9lwFKslbHC-HKlJbfZZg16j6tlQTgw6dnKNo5LPrZ4TeSUyuoudzZSpZo4dyFsasTfWYTSLQ", 62 | "e": "AQAB", 63 | "x5c": [ 64 | "MIICpzCCAY8CBgFzHIdU1jANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxhcHBsaWNhdGlvbnMwHhcNMjAwNzA1MDEwOTU3WhcNMzAwNzA1MDExMTM3WjAXMRUwEwYDVQQDDAxhcHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/PjSc+ULZewAZup+ndLifKXYbWIwURHWX8jiTuB8L7ztRa7b5rsFlAdvrjpaZJe7zEXO7THkUVMWa7+/cCPU0Bx1hvPT1TKlLF8f75rDsBOJJYvbqMeLEU/iXqaInFpdX6uWn2oVI777UlGeW9iu3ozmZAjRm83wRqEBVcgc20Mxli7QdjDAvuPjmCMX2j2QNt8xI+vw6eLRALa1NwKDrbc6U2qslkVPhg0nwDWdF/AbAEsWnmnGkr1kJ6RBWEGlk1SuUsHm4NoHkf2XAUqyVscL4cqUlt9lmDXqPq2VBODDp2co2jks+tnhN5JTK6i53NlKlmjh3IWxqxN9ZhNItAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEhiswSA4BBd9ka473JMX27y+4ZyxitUWi9ARhloiPtE7b+HVsRd6febjPlwMZJ/x7c5lRrQXEGCtJdHcVf2JybNo9bAPSsnmGAD9I+x5GyJljgRuItcfIJ3ALV7LqMbFPZ7cO6jB9hzYtjzECRN0+hJKSZm99kpau2sI8C1FkT+aSK7+j0jGagYwfI8hG7SV1IKQgTxtGZSpFgn2mi60TYsnLt2JYKSACq5hZykO7BPxnTK0sAK9ue34ddEuVe6L1wxDv44PME2dZwRmCRT5d7qj8lO4n2VYqBbc90ME6yAeRIhYRZSrHFTE2Wkufi+21HXIB63dKoYqiPe3y/GZno=" 65 | ], 66 | "x5t": "5lmEYc56y8EeBpHsP1-LO8M0W2c", 67 | "x5t#S256": "oC0EpmLVEv1CptAVxKT9uVpC975xKlu3xOrhh8RTNy4" 68 | } 69 | ] 70 | } 71 | )"; 72 | 73 | // PS256 JWT with correct kid 74 | // Header: 75 | // { 76 | // "alg": "PS256", 77 | // "typ": "JWT", 78 | // "kid": "4hmO65bbc7IVI-3PfA2emAlO0qhv4rB__yw8BPQ58q8" 79 | // } 80 | // Payload: 81 | // { 82 | // "exp": 1593912811, 83 | // "iat": 1593912511, 84 | // "jti": "3c9ee909-3ca5-4587-8c0b-700cb4cb8e62", 85 | // "iss": "https://keycloak.localhost/auth/realms/applications", 86 | // "sub": "c3cfd999-ca22-4080-9863-277427db4321", 87 | // "typ": "Bearer", 88 | // "azp": "foo", 89 | // "session_state": "de37ba9c-4b3a-4250-a89b-da81928fcf9b", 90 | // "acr": "1", 91 | // "scope": "email profile", 92 | // "email_verified": false, 93 | // "name": "User Zero", 94 | // "preferred_username": "user0", 95 | // "given_name": "User", 96 | // "family_name": "Zero", 97 | // "email": "user0@mail.com" 98 | // } 99 | 100 | const std::string Ps256JwtTextWithCorrectKid = 101 | "eyJhbGciOiJQUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0aG1PNjViYmM3SVZJLTNQ" 102 | "ZkEyZW1BbE8wcWh2NHJCX195dzhCUFE1OHE4In0." 103 | "eyJleHAiOjE1OTM5MTI4MTEsImlhdCI6MTU5MzkxMjUxMSwianRpIjoiM2M5ZWU5MDktM2Nh" 104 | "NS00NTg3LThjMGItNzAwY2I0Y2I4ZTYyIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5sb2Nh" 105 | "bGhvc3QvYXV0aC9yZWFsbXMvYXBwbGljYXRpb25zIiwic3ViIjoiYzNjZmQ5OTktY2EyMi00" 106 | "MDgwLTk4NjMtMjc3NDI3ZGI0MzIxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZm9vIiwic2Vz" 107 | "c2lvbl9zdGF0ZSI6ImRlMzdiYTljLTRiM2EtNDI1MC1hODliLWRhODE5MjhmY2Y5YiIsImFj" 108 | "ciI6IjEiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2Us" 109 | "Im5hbWUiOiJVc2VyIFplcm8iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMCIsImdpdmVu" 110 | "X25hbWUiOiJVc2VyIiwiZmFtaWx5X25hbWUiOiJaZXJvIiwiZW1haWwiOiJ1c2VyMEBtYWls" 111 | "LmNvbSJ9." 112 | "fas6TkXZ97K1d8tTMCEFDcG-MupI-BwGn0UZD8riwmbLf5xmDPaoZwmJ3k-szVo-oJMfMZbr" 113 | "VAI8xQwg4Z7bQvd3I9WM6XPsu1_gKnkc2EOATgkdpDg5rWOPSZCFLUD_bqsoPQrfc2C1-UKs" 114 | "VOwUkXEH6rEIlOvngqQWNJjtbkvsS2N_3kNAgaD8cELT5mxmM4vGZn14OHmXHJBIW9pHJU64" 115 | "tA0sDcexoylL7xB_E1XTs3St0sYyq_pz9920vHScr9KXQ3y9k-fbPvgBs2gGY0iK63E0lEwD" 116 | "fRWY4Za6RRqymammehv7ZiE4HjDy5Q_AdLGdRefrTxtiQrHIThLqAw"; 117 | 118 | // PS384 JWT with correct kid 119 | // Header: 120 | // { 121 | // "alg": "PS384", 122 | // "typ": "JWT", 123 | // "kid": "RGlV9a54XdAsuiYUDkQ0hDkiSZ92TJCgneh7-HvN-sk" 124 | // } 125 | // Payload: 126 | // { 127 | // "exp": 1593913901, 128 | // "iat": 1593913601, 129 | // "jti": "375242be-54c3-4c06-ad07-22457d493390", 130 | // "iss": "https://keycloak.localhost/auth/realms/applications", 131 | // "sub": "c3cfd999-ca22-4080-9863-277427db4321", 132 | // "typ": "Bearer", 133 | // "azp": "foo", 134 | // "session_state": "a0cc48a5-1eea-4078-b965-3f8edee8a15e", 135 | // "acr": "1", 136 | // "scope": "email profile", 137 | // "email_verified": false, 138 | // "name": "User Zero", 139 | // "preferred_username": "user0", 140 | // "given_name": "User", 141 | // "family_name": "Zero", 142 | // "email": "user0@mail.com" 143 | // } 144 | 145 | const std::string Ps384JwtTextWithCorrectKid = 146 | "eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSR2xWOWE1NFhkQXN1aVlV" 147 | "RGtRMGhEa2lTWjkyVEpDZ25laDctSHZOLXNrIn0." 148 | "eyJleHAiOjE1OTM5MTM5MDEsImlhdCI6MTU5MzkxMzYwMSwianRpIjoiMzc1MjQyYmUtNTRj" 149 | "My00YzA2LWFkMDctMjI0NTdkNDkzMzkwIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5sb2Nh" 150 | "bGhvc3QvYXV0aC9yZWFsbXMvYXBwbGljYXRpb25zIiwic3ViIjoiYzNjZmQ5OTktY2EyMi00" 151 | "MDgwLTk4NjMtMjc3NDI3ZGI0MzIxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZm9vIiwic2Vz" 152 | "c2lvbl9zdGF0ZSI6ImEwY2M0OGE1LTFlZWEtNDA3OC1iOTY1LTNmOGVkZWU4YTE1ZSIsImFj" 153 | "ciI6IjEiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2Us" 154 | "Im5hbWUiOiJVc2VyIFplcm8iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMCIsImdpdmVu" 155 | "X25hbWUiOiJVc2VyIiwiZmFtaWx5X25hbWUiOiJaZXJvIiwiZW1haWwiOiJ1c2VyMEBtYWls" 156 | "LmNvbSJ9." 157 | "lQdbyqQH0dBYA0yIMVmV-KMGOYc7-BuuQUggKqEi9kpmvZAeXaX1v04n6XkyZdIRMxLgxVoK" 158 | "LH3XJLg7zwW_luYR5ZlYj5SLYxUSkrlG3RfOvRpphXzhH-TcRQMdwSFEbNUiibZ6NkSmzMLi" 159 | "Weryi3JHCHAxt2e9Z6_dWlrKXXSvpmZgrn--NdU433TmePFdgoEGUH8F9q7T1Nd1S5FnsS2i" 160 | "-ywZzNMQIfQ59k_r1_WlH81bwoNgd4ffTlVsosZrw84UYBJdNt73-RWu1NNTXvIY2MiImods" 161 | "oo7DAD__ZDMgnJ8cpBmrq0YASz04SESNt1jiwCWbasJQx_B73hmd1A"; 162 | 163 | // PS512 JWT with correct kid 164 | // Header: 165 | // { 166 | // "alg": "PS512", 167 | // "typ": "JWT", 168 | // "kid": "u_ZZAorrQhtL2MA-bWkZ0qpzjia4D3u6QUvBRscHLrg" 169 | // } 170 | // Payload: 171 | // { 172 | // "exp": 1593913918, 173 | // "iat": 1593913618, 174 | // "jti": "7c1f8cba-7f7c-4e05-b02c-2a0a77914f5d", 175 | // "iss": "https://keycloak.localhost/auth/realms/applications", 176 | // "sub": "c3cfd999-ca22-4080-9863-277427db4321", 177 | // "typ": "Bearer", 178 | // "azp": "foo", 179 | // "session_state": "d8dbe685-cd10-42da-841c-f7ae6cd4d588", 180 | // "acr": "1", 181 | // "scope": "email profile", 182 | // "email_verified": false, 183 | // "name": "User Zero", 184 | // "preferred_username": "user0", 185 | // "given_name": "User", 186 | // "family_name": "Zero", 187 | // "email": "user0@mail.com" 188 | // } 189 | 190 | const std::string Ps512JwtTextWithCorrectKid = 191 | "eyJhbGciOiJQUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ1X1paQW9yclFodEwyTUEt" 192 | "YldrWjBxcHpqaWE0RDN1NlFVdkJSc2NITHJnIn0." 193 | "eyJleHAiOjE1OTM5MTM5MTgsImlhdCI6MTU5MzkxMzYxOCwianRpIjoiN2MxZjhjYmEtN2Y3" 194 | "Yy00ZTA1LWIwMmMtMmEwYTc3OTE0ZjVkIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5sb2Nh" 195 | "bGhvc3QvYXV0aC9yZWFsbXMvYXBwbGljYXRpb25zIiwic3ViIjoiYzNjZmQ5OTktY2EyMi00" 196 | "MDgwLTk4NjMtMjc3NDI3ZGI0MzIxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZm9vIiwic2Vz" 197 | "c2lvbl9zdGF0ZSI6ImQ4ZGJlNjg1LWNkMTAtNDJkYS04NDFjLWY3YWU2Y2Q0ZDU4OCIsImFj" 198 | "ciI6IjEiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2Us" 199 | "Im5hbWUiOiJVc2VyIFplcm8iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMCIsImdpdmVu" 200 | "X25hbWUiOiJVc2VyIiwiZmFtaWx5X25hbWUiOiJaZXJvIiwiZW1haWwiOiJ1c2VyMEBtYWls" 201 | "LmNvbSJ9." 202 | "p-NqE3q9BVakZNkKX3-X5FKIm64PloIjBjWfajQuRayHv4cj6xwvDve3uCuZa2oKyefJRNLy" 203 | "6rCJUGNsYM9Q-WRCtD6SuWLPkuqh-SUFtZqW7sWGOqTLKbMBx5StLZx7eEgdRWqzIxwLVLdF" 204 | "VuO-3L88qHFTU2Vv8UAu_nX-uyFKOV5bYgyFlxqgpSqvsbm6lZ0EZghPuidOmnMPQdS8-Evk" 205 | "jwSAYEgoQ1crXY8dEUc_AJfq84jtuMJMnFhfVQvk_8hN71wYWWYThXtEATFySUFrkoCvB-da" 206 | "Sl9FNeK5UPE9vYBi7QJ-Wt3Ikg7kEgPiuADlIao_ZxKdzoA51isGBg"; 207 | 208 | class VerifyJwkRsaPssTest : public testing::Test { 209 | protected: 210 | void SetUp() { 211 | jwks_ = Jwks::createFrom(PublicKeyRSAPSS, Jwks::Type::JWKS); 212 | EXPECT_EQ(jwks_->getStatus(), Status::Ok); 213 | } 214 | 215 | JwksPtr jwks_; 216 | }; 217 | 218 | TEST_F(VerifyJwkRsaPssTest, Ps256CorrectKidOK) { 219 | Jwt jwt; 220 | EXPECT_EQ(jwt.parseFromString(Ps256JwtTextWithCorrectKid), Status::Ok); 221 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 222 | 223 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 224 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 225 | }); 226 | } 227 | 228 | TEST_F(VerifyJwkRsaPssTest, Ps384CorrectKidOK) { 229 | Jwt jwt; 230 | EXPECT_EQ(jwt.parseFromString(Ps384JwtTextWithCorrectKid), Status::Ok); 231 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 232 | 233 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 234 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 235 | }); 236 | } 237 | 238 | TEST_F(VerifyJwkRsaPssTest, Ps512CorrectKidOK) { 239 | Jwt jwt; 240 | EXPECT_EQ(jwt.parseFromString(Ps512JwtTextWithCorrectKid), Status::Ok); 241 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::Ok); 242 | 243 | fuzzJwtSignature(jwt, [this](const Jwt& jwt) { 244 | EXPECT_EQ(verifyJwt(jwt, *jwks_, 1), Status::JwtVerificationFail); 245 | }); 246 | } 247 | 248 | // This set of keys and jwts were generated at https://jwt.io/ 249 | // public key: 250 | // "-----BEGIN PUBLIC KEY-----" 251 | // "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv" 252 | // "vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc" 253 | // "aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy" 254 | // "tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0" 255 | // "e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb" 256 | // "V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9" 257 | // "MwIDAQAB" 258 | // "-----END PUBLIC KEY-----" 259 | 260 | const std::string JwtIoPublicKeyRSAPSS = R"( 261 | { 262 | "keys": [ 263 | { 264 | "kty": "RSA", 265 | "kid": "f08a1cc9-d266-4049-9c22-f95260cbf5fd", 266 | "e": "AQAB", 267 | "n": "nzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA-kzeVOVpVWwkWdVha4s38XM_pa_yr47av7-z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr_Mrm_YtjCZVWgaOYIhwrXwKLqPr_11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e-lf4s4OxQawWD79J9_5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa-GSYOD2QU68Mb59oSk2OB-BtOLpJofmbGEGgvmwyCI9Mw" 268 | } 269 | ] 270 | } 271 | )"; 272 | 273 | // private key: 274 | // "-----BEGIN RSA PRIVATE KEY-----" 275 | // "MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw" 276 | // "kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr" 277 | // "m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi" 278 | // "NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV" 279 | // "3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2" 280 | // "QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs" 281 | // "kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go" 282 | // "amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM" 283 | // "+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9" 284 | // "D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC" 285 | // "0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y" 286 | // "lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+" 287 | // "hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp" 288 | // "bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X" 289 | // "+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B" 290 | // "BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC" 291 | // "2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx" 292 | // "QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz" 293 | // "5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9" 294 | // "Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0" 295 | // "NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j" 296 | // "8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma" 297 | // "3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K" 298 | // "y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB" 299 | // "jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=" 300 | // "-----END RSA PRIVATE KEY-----" 301 | 302 | const std::string JwtTextWithNoKid = 303 | "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9." 304 | "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlh" 305 | "dCI6MTUxNjIzOTAyMn0." 306 | "hZnl5amPk_I3tb4O-Otci_5XZdVWhPlFyVRvcqSwnDo_srcysDvhhKOD01DigPK1lJvTSTol" 307 | "yUgKGtpLqMfRDXQlekRsF4XhAjYZTmcynf-C-6wO5EI4wYewLNKFGGJzHAknMgotJFjDi_NC" 308 | "VSjHsW3a10nTao1lB82FRS305T226Q0VqNVJVWhE4G0JQvi2TssRtCxYTqzXVt22iDKkXeZJ" 309 | "ARZ1paXHGV5Kd1CljcZtkNZYIGcwnj65gvuCwohbkIxAnhZMJXCLaVvHqv9l-AAUV7esZvkQ" 310 | "R1IpwBAiDQJh4qxPjFGylyXrHMqh5NlT_pWL2ZoULWTg_TJjMO9TuQ"; 311 | 312 | const std::string JwtTextWithNonExistentKid = 313 | "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im5vbmV4aXN0ZW50In0." 314 | "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlh" 315 | "dCI6MTUxNjIzOTAyMn0." 316 | "USMoL8XwVl-sqtIl-VQr97oNr1XWbgnJnDJbi65ExV7IioYQ3cGfrpi9n2GxJOwuw6zU572l" 317 | "ME-wD9It-Q8H8eAOi83KoimQJmdzCGGUGTgwo3tZK5HV7W3srgP1_46-X43DYWOT6h1pIAE7" 318 | "7s23XuSKbq4rpp6cmbDODARfTj6OTQWTqwhOkX0Xo7i2q1foreKI8PnOyrvbs7oXrLJGZhg_" 319 | "6mRnP0wRJJFkIu2uYKcLDcgJ0OWXY6dQ-8agj-yjZ5ZUX8GUcy347P0UUpsGVNd1pUawLwTi" 320 | "kmNidJOxkGlawLtOwE7u0WtZdYmcppx99Qw5U4gYdQQx0wJqgj_d8g"; 321 | 322 | // Expected behavior for VerifyKidMatchingTest: 323 | // If kid is not specified in the jwt, allow verification as long as any of the 324 | // keys in the jwks are appropriate. 325 | // If kid is specified in the jwt, use only the requested key in the jwks for 326 | // verification. 327 | class VerifyKidMatchingTest : public testing::Test { 328 | protected: 329 | void SetUp() { 330 | correct_jwks_ = Jwks::createFrom(JwtIoPublicKeyRSAPSS, Jwks::Type::JWKS); 331 | EXPECT_EQ(correct_jwks_->getStatus(), Status::Ok); 332 | wrong_jwks_ = Jwks::createFrom(PublicKeyRSAPSS, Jwks::Type::JWKS); 333 | EXPECT_EQ(wrong_jwks_->getStatus(), Status::Ok); 334 | } 335 | 336 | // This jwks contains the appropriate key for signature verification 337 | JwksPtr correct_jwks_; 338 | // This jwks does not contain the appropriate key for signature verification 339 | JwksPtr wrong_jwks_; 340 | }; 341 | 342 | TEST_F(VerifyKidMatchingTest, JwtTextWithNoKidNoMatchingKey) { 343 | Jwt jwt; 344 | EXPECT_EQ(jwt.parseFromString(JwtTextWithNoKid), Status::Ok); 345 | // jwt has no kid, and none of the keys in the jwks can be used to verify, 346 | // hence verification fails 347 | EXPECT_EQ(verifyJwt(jwt, *wrong_jwks_), Status::JwtVerificationFail); 348 | } 349 | 350 | TEST_F(VerifyKidMatchingTest, JwtTextWithNoKidOk) { 351 | Jwt jwt; 352 | EXPECT_EQ(jwt.parseFromString(JwtTextWithNoKid), Status::Ok); 353 | // jwt has no kid, and one of the keys in the jwks can be used to verify, 354 | // hence verification is ok 355 | EXPECT_EQ(verifyJwt(jwt, *correct_jwks_, 1), Status::Ok); 356 | } 357 | 358 | TEST_F(VerifyKidMatchingTest, JwtTextWithNonExistentKid) { 359 | Jwt jwt; 360 | EXPECT_EQ(jwt.parseFromString(JwtTextWithNonExistentKid), Status::Ok); 361 | // jwt has a kid, which did not match any of the keys in the jwks (even 362 | // though the jwks does contain an appropriate key) 363 | EXPECT_EQ(verifyJwt(jwt, *correct_jwks_, 1), Status::JwksKidAlgMismatch); 364 | } 365 | 366 | } // namespace 367 | } // namespace jwt_verify 368 | } // namespace google 369 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------