├── .github └── FUNDING.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── build.clj ├── deps.edn ├── doc.clj ├── doc ├── 00-intro.md ├── 01-jwt.md ├── 02-jws.md ├── 03-jwe.md ├── 04-jwk.md ├── 05-cms.md ├── 06-faq.md └── Makefile ├── mvn-upload.sh ├── pom.xml ├── scripts └── repl ├── src └── buddy │ └── sign │ ├── compact.clj │ ├── jwe.clj │ ├── jwe │ └── cek.clj │ ├── jwk.clj │ ├── jws.clj │ ├── jwt.clj │ └── util.clj └── test ├── _files ├── privkey.3des.dsa.pem ├── privkey.3des.rsa.pem ├── privkey.ecdsa.pem ├── pubkey.3des.dsa.pem ├── pubkey.3des.rsa.pem └── pubkey.ecdsa.pem ├── buddy └── sign │ ├── compact_tests.clj │ ├── interop_tests.clj │ ├── jwe_tests.clj │ ├── jwk_tests.clj │ ├── jws_tests.clj │ └── jwt_tests.clj └── user.clj /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: niwinz 2 | patreon: niwinz 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | .cpcache 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | *.swp 10 | /.lein-* 11 | /.nrepl-port 12 | /doc/dist 13 | \#*\# 14 | *~ 15 | .\#* 16 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 3.6.1-359 4 | 5 | Date: 2024-07-19 6 | 7 | - Add missing resolve-key call on sign method 8 | 9 | 10 | ## Version 3.6.0-357 11 | 12 | Date: 2024-07-19 13 | 14 | - Update buddy-core to 1.12.0-430 15 | 16 | 17 | ## Version 3.5.351 18 | 19 | Date: 2023-06-30 20 | 21 | - Update buddy-core to 1.11.423 22 | - Improve input validation on JWT/JWE/JWS 23 | - Add convencience helper `decode-header` to JWT ns 24 | - Add `buddy.sign.jwk` namespace with convenience helpers for creating 25 | public and private key instances from JWKs 26 | 27 | 28 | ## Version 3.5.346 29 | 30 | Date: 2023-05-20 31 | 32 | - Fix reflection warnings 33 | - Update dependencies 34 | 35 | ## Version 3.4.333 36 | 37 | Date: 2022-01-14 38 | 39 | - Update dependencies. 40 | 41 | 42 | ## Version 3.4.1 43 | 44 | Date: 2021-05-02 45 | 46 | - Update buddy-core to 1.10.1 47 | 48 | 49 | ## Version 3.4.0 50 | 51 | Date: 2021-05-01 52 | 53 | - Update buddy-core to 1.10.0 54 | 55 | 56 | ## Version 3.3.0 57 | 58 | Date: 2020-12-03 59 | 60 | - Update buddy-core to 1.9.0 61 | - Update nippy to 3.1.1 (provided) 62 | 63 | 64 | ## Version 3.2.0 65 | 66 | Date: 2020-09-15 67 | 68 | - Update buddy-core to 1.8.0 69 | 70 | 71 | ## Version 3.1.0 72 | 73 | Date: 2019-06-28 74 | 75 | - Update buddy-core to 1.6.0 76 | 77 | 78 | ## Version 3.0.0 79 | 80 | Date: 2018-06-02 81 | 82 | - Update buddy-core to 1.5.0 83 | - Proper handling of SignatureException (jws). 84 | - Add EdDSA signer. 85 | - Add KeyProvider abstraction for enably dynamic selection 86 | of key on JWS. 87 | 88 | 89 | ## Version 2.2.0 90 | 91 | Date: 2017-08-29 92 | 93 | - Update buddy-core to 1.4.0 94 | 95 | 96 | ## Version 2.1.0 97 | 98 | Date: 2017-08-28 99 | 100 | - Add `:skip-validation` option to **jwt** functions, for allow inspect invalid tokens. 101 | - Add support for custom header through `:header` option on jws, jwe and jwt. 102 | - The `typ` header is no longer set by default (is optional on the RFC 103 | and is removed for save some bytes on all tokens) 104 | - Now only the `:alg` and `:enc` headers are treated specially 105 | (keywordized on header decoding), tha rest are returned as is. 106 | 107 | 108 | ## Version 2.0.0 109 | 110 | Date: 2017-08-08 111 | 112 | - Stop rejecting tokens with future `:iat` claim values. (BACKWARD 113 | INCOMPATIBLE CHANGE, more info in [#49](https://github.com/funcool/buddy-sign/pull/49)) 114 | - Fix unexpected exception in some malformed tokens. 115 | - Add `:leeway` option to `jwt/unsign` function. 116 | - Update buddy-core to 1.3.0 117 | 118 | 119 | ## Version 1.6.0 120 | 121 | Date: 2017-07-28 122 | 123 | - Update to use Clojure 1.9-alpha17 124 | - Update cheshire to 5.7.1 125 | - Update nippy to 2.13.0 126 | - Allows not just a single issuer, but also a collection of issuers, 127 | to be provided for validating the `iss` claim in a token 128 | 129 | ## Version 1.5.0 130 | 131 | Date: 2017-03-30 132 | 133 | - Evaluate jdk8 extensions at runtime. It allow build jdk7 compatible 134 | jars using JDK8. 135 | 136 | 137 | ## Version 1.4.0 138 | 139 | Date: 2017-01-24 140 | 141 | - Update buddy-core to 1.2.0 142 | - Update cheshire to 5.7.0 143 | 144 | 145 | ## Version 1.3.0 146 | 147 | Date: 2016-11-15 148 | 149 | - Update buddy-core to 1.1.1 150 | 151 | 152 | ## Version 1.2.0 153 | 154 | Date: 2016-08-28 155 | 156 | - Update buddy-core to 1.0.0 157 | - Update cheshire to 5.6.3 158 | - Update nippy to 2.12.2 159 | - Add the ability to pass any type that implements ITimestamp protocol 160 | as value to the `:now` parameter on JWT api. 161 | 162 | 163 | ## Version 1.1.0 164 | 165 | Date: 2016-06-10 166 | 167 | - Test everything with generative tests (with test.check) 168 | - Drop direct support to clojure 1.5 and 1.6 169 | (because test.check has hard dependency with clojure ># 1.7) 170 | - Update to buddy 0.13.0 that fixes some bugs that affects 171 | to JWE when compressed tokens are used. 172 | - Fix varios jwe/jws/jwt validations bugs found thanks to using 173 | generative tests with test.check. 174 | - The `aud` claim validation can be a set. 175 | 176 | 177 | ## Version 1.0.0 178 | 179 | Date: 2016-05-20 180 | 181 | **Important**: This is an major release beacause it includes breaking api changes. 182 | 183 | Important changes: 184 | 185 | **JWS and JWE becomes a more low level function** for sign or encrypt arbitrary 186 | data (as RFC specifies) and all claims and json related stuff is moved into 187 | specific **JWT** related namespace. 188 | 189 | The api is preserved so, the migration is pretty easy; just replace your `jws` or 190 | `jwe` import with `jwt`. 191 | 192 | [source, clojure] 193 | ---- 194 | ;; Old imports: 195 | (require '[buddy.sign.jws :as jws]) 196 | (require '[buddy.sign.jwe :as jwe]) 197 | 198 | ;; New import: 199 | (require '[buddy.sign.jwt :as jwt]) 200 | ---- 201 | 202 | Many thanks to @FreekPaans for the initial work on split JWS and JWT. 203 | 204 | The **clj-time** dependency is removed. JodaTime directly is used if it is 205 | available in the classpath. 206 | 207 | Add jdk8 java.time.Instant support for time related claims. 208 | 209 | Removed hardcoded dependency to `nippy` for compact signing ns. Now the user 210 | should specify their own dependency in order to be able use the compact message 211 | signing implementation. 212 | 213 | 214 | ## Version 0.13.0 215 | 216 | Date: 2016-04-24 217 | 218 | - Fix unexpected NPE on header parsing on jws/jwe. 219 | - Fixed `:exp` claim validation (thanks @dottedmag) for JWS/JWE. 220 | - Fixed `:nbf` claim validation on JWE. 221 | - Add improved `:iat` validation (thanks @dottedmag) for JWS/JWE. 222 | 223 | 224 | ## Version 0.12.0 225 | 226 | Date: 2016-04-08 227 | 228 | - Fix compliance with RFC bug in JWE implementation (header was improperly encoded 229 | before passed as aad that causes incompatibilities with other implementations). 230 | WARNING: will invalidate all your tokens. 231 | - Adapt to buddy-core api changes. 232 | 233 | 234 | ## Version 0.11.0 235 | 236 | Date: 2016-03-27 237 | 238 | - Update buddy-core dependency to 0.11.0 239 | - Remove user.clj accindentally pulled into the jar. 240 | 241 | 242 | ## Version 0.10.0 243 | 244 | Date: 2016-03-26 245 | 246 | - Update buddy-core dependency to 0.10.0 247 | - Update nippy dependency to 2.11.1. 248 | - Fix exception data inconsistency with jwt on compact impl. 249 | - Fix wrong documentation about auto detection of the alg. 250 | 251 | 252 | ## Version 0.9.0 253 | 254 | Date: 2016-01-06 255 | 256 | - Update buddy-core dependency to 0.9.0 257 | - Minor cosmetic changes. 258 | 259 | 260 | ## Version 0.8.1 261 | 262 | Date: 2015-11-17 263 | 264 | - Properly remove cats dependency. 265 | - Fix wrong arguments on jws and compact sign methods. 266 | 267 | 268 | ## Version 0.8.0 269 | 270 | Date: 2015-11-15 271 | 272 | - Adapt to buddy-core 0.8.x changes. 273 | - BREAKING CHANGE: Remove cats dependency. 274 | The jws/encode, jws/decode and respectivelly functions 275 | in the jwe namespace are now simple alias to the main 276 | api on the each ns. 277 | 278 | 279 | ## Version 0.7.1 280 | 281 | Date: 2015-09-23 282 | 283 | - Fix broken nbf claim validation. 284 | (thanks to @jonpither for report it) 285 | 286 | 287 | ## Version 0.7.0 288 | 289 | Date: 2015-09-19 290 | 291 | - Update cats to 1.0.0 292 | - Update clj-time to 0.11.0 293 | - Update nippy to 2.9.1 294 | - Update buddy-core to 0.7.0 295 | - Remove slingshot usage and start using plain 296 | clojure.lang.ExceptionInfo exceptions. 297 | (maybe breaking change) 298 | 299 | 300 | ## Version 0.6.1 301 | 302 | Date: 2015-08-02 303 | 304 | * Set default clojure version to 1.7.0 305 | * Update cats version to 0.6.1 306 | 307 | 308 | ## Version 0.6.0 309 | 310 | Date: 2015-06-28 311 | 312 | * Replace cryptographic primitives used in jwe implementation 313 | with buddy-core new implementation that fixes few bugs realted 314 | to wrong padding management. 315 | * Update buddy-core to 0.6.0 316 | * Remove direct slingshot dependency because is not transitive 317 | from the new buddy-core version. 318 | * Update cheshire dependency to 5.5.0 319 | 320 | 321 | ## Version 0.5.1 322 | 323 | Date: 2015-05-09 324 | 325 | * Improved error reporting when validating wrong jwe/jws tokens. 326 | 327 | 328 | ## Version 0.5.0 329 | 330 | Date: 2015-04-03 331 | 332 | * Add Jsen Web Encryption support. With key encryption algorithms: `DIR`, `A128KW`, `A192KW`, `A256KW`, 333 | `RSA1_5`, `RSA-OAEP`, `RSA-OAEP-256`. and content encryption algorithms: `A128CBC-HS256`, 334 | `A192CBC-HS384`, `A256CBC-HS512`, `A128GCM`, `A192GCM`, `A256GCM`. 335 | * The encode and decode functions now returns instances of success or failure of exception monad 336 | instead of instances of either monad (maybe breaking change). 337 | * The sign and unsign functions now raises exceptions instead of simply return nil. This allows 338 | libraries and applications that does not works with monads workis like a usual, using jvm 339 | exceptions and know the specific error instead of useless nil (maybe breaking change). 340 | * Add the ability to specify the `:typ` header value in JWS. 341 | * Add :iss (issuer) and :aud (audience) claims validation to JWS. 342 | * Add explicit alg validation in JWS (the previous behavior that only checks the header alg without 343 | matching it with user provided value has security flaws: 344 | https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ 345 | 346 | 347 | ## Version 0.4.2 348 | 349 | Date: 2015-03-29 350 | 351 | * Bug fix related to :iat param validating on jws. (thanks to @tvanhens) 352 | 353 | 354 | ## Version 0.4.1 355 | 356 | Date: 2015-03-14 357 | 358 | * Update nippy version from 2.7.1 to 2.8.0 359 | * Update buddy-core from 0.4.0 to 0.4.2 360 | * Update cats from 0.3.2 to 0.3.4 361 | 362 | 363 | ## Version 0.4.0 364 | 365 | Date: 2015-02-22 366 | 367 | * Add encode/decode functions to JWS/JWT implementation. Them instead of return 368 | plain value, return a monadic either. That allows granular error reporting 369 | instead something like nil that not very useful. The previous sign/unsign 370 | are conserved for backward compatibility but maybe in future will be removed. 371 | * Rename parameter `maxage` to `max-age` on jws implementation. This change 372 | introduces a little backward incompatibility. 373 | * Add "compact" signing implementation as replacemen of django based one. 374 | * Django based generic signing is removed. 375 | * Update buddy-core version to 0.4.0 376 | 377 | 378 | ## Version 0.3.0 379 | 380 | Date: 2014-01-18 381 | 382 | * First version splitted from monolitic buddy package. 383 | * No changes from original version. 384 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # buddy-sign 2 | 3 | A library that provides a high level message signing api for Clojure. 4 | 5 | See the [documentation](https://funcool.github.io/buddy-sign/latest/) for more detailed 6 | information. 7 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:refer-clojure :exclude [compile]) 3 | (:require [clojure.tools.build.api :as b])) 4 | 5 | (def lib 'buddy/buddy-sign) 6 | (def version (format "3.6.1-%s" (b/git-count-revs nil))) 7 | (def class-dir "target/classes") 8 | (def basis (b/create-basis {:project "deps.edn"})) 9 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 10 | 11 | (defn clean [_] 12 | (b/delete {:path "target"})) 13 | 14 | (defn jar [_] 15 | (b/write-pom 16 | {:class-dir class-dir 17 | :lib lib 18 | :version version 19 | :basis basis 20 | :src-dirs ["src"]}) 21 | 22 | (b/copy-dir 23 | {:src-dirs ["src" "resources"] 24 | :target-dir class-dir}) 25 | 26 | (b/jar 27 | {:class-dir class-dir 28 | :jar-file jar-file})) 29 | 30 | (defn clojars [_] 31 | (b/process 32 | {:command-args ["mvn" 33 | "deploy:deploy-file" 34 | (str "-Dfile=" jar-file) 35 | "-DpomFile=target/classes/META-INF/maven/buddy/buddy-sign/pom.xml" 36 | "-DrepositoryId=clojars" 37 | "-Durl=https://clojars.org/repo/"]})) 38 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {buddy/buddy-core 3 | {:mvn/version "1.12.0-430" 4 | ;; :local/root "../buddy-core" 5 | }} 6 | :paths ["src" "resources" "target/classes"] 7 | :aliases 8 | {:dev 9 | {:extra-deps 10 | {org.clojure/tools.namespace {:mvn/version "RELEASE"} 11 | org.clojure/test.check {:mvn/version "RELEASE"} 12 | org.clojure/tools.deps.alpha {:mvn/version "RELEASE"} 13 | org.clojure/clojure {:mvn/version "1.11.3"} 14 | com.bhauman/rebel-readline {:mvn/version "RELEASE"} 15 | criterium/criterium {:mvn/version "RELEASE"} 16 | com.taoensso/nippy {:mvn/version "3.4.2"} 17 | com.nimbusds/nimbus-jose-jwt {:mvn/version "9.40"} 18 | } 19 | :extra-paths ["test" "dev"]} 20 | 21 | :test 22 | {:extra-paths ["test"] 23 | :extra-deps 24 | {io.github.cognitect-labs/test-runner 25 | {:git/tag "v0.5.1" :git/sha "dfb30dd"}} 26 | :exec-fn cognitect.test-runner.api/test 27 | :exec-args {:patterns [".*-test.*"]}} 28 | 29 | :build 30 | {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.10.5" :git/sha "2a21b7a"}} 31 | :ns-default build} 32 | 33 | :codox 34 | {:extra-deps 35 | {codox/codox {:mvn/version "RELEASE"} 36 | org.clojure/tools.reader {:mvn/version "RELEASE"} 37 | codox-theme-rdash/codox-theme-rdash {:mvn/version "RELEASE"}}} 38 | 39 | :outdated 40 | {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"} 41 | org.slf4j/slf4j-nop {:mvn/version "RELEASE"}} 42 | :main-opts ["-m" "antq.core"]}}} 43 | -------------------------------------------------------------------------------- /doc.clj: -------------------------------------------------------------------------------- 1 | (require '[codox.main :as codox]) 2 | 3 | (codox/generate-docs 4 | {:output-path "doc/dist/latest" 5 | :metadata {:doc/format :markdown} 6 | :language :clojure 7 | :name "buddy/buddy-sign" 8 | :themes [:rdash] 9 | :source-paths ["src"] 10 | :namespaces [#"^buddy\."] 11 | :source-uri "https://github.com/funcool/buddy-sign/blob/master/{filepath}#L{line}"}) 12 | -------------------------------------------------------------------------------- /doc/00-intro.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Buddy *sign* module is dedicated to provide a high level abstraction 4 | for web ready message signing and encryption. 5 | 6 | It can be used for several purposes: 7 | 8 | * You can serialize and sign or encrypt a user ID for unsubscribing of 9 | newsletters into URLs. This way you don't need to generate one-time 10 | tokens and store them in the database. 11 | * Same thing with any kind of activation link for accounts and similar things. 12 | * Signed or encrypted objects can be stored in cookies or other 13 | untrusted sources which means you don't need to have sessions stored 14 | on the server, which reduces the number of necessary database 15 | queries. 16 | * Signed information can safely do a roundtrip between server and 17 | client in general which makes them useful for passing server-side 18 | state to a client and then back. 19 | * Safely send and receve signed or encrypted messages between 20 | components or microservices. 21 | * Self contained token generation for use with completely stateless 22 | token based authentication. 23 | 24 | 25 | ## Install 26 | 27 | The simplest way to use _buddy-sign_ in a clojure project, is by including it in the 28 | dependency vector on your *_project.clj_* file: 29 | 30 | ```clojure 31 | [buddy/buddy-sign "3.6.1-359"] 32 | ``` 33 | 34 | Or deps.edn: 35 | 36 | ```clojure 37 | buddy/buddy-sign {:mvn/version "3.6.1-359"} 38 | ``` 39 | 40 | And is tested under JDK >= 8 41 | 42 | 43 | ## Involved RFC's? 44 | 45 | * https://tools.ietf.org/html/rfc7519 46 | * https://tools.ietf.org/html/rfc7518 47 | * https://tools.ietf.org/html/rfc7516 48 | * https://tools.ietf.org/html/rfc7515 49 | * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05 50 | * https://tools.ietf.org/html/rfc3394 51 | * https://tools.ietf.org/html/rfc7517 52 | * https://tools.ietf.org/html/rfc7638 53 | * https://tools.ietf.org/html/rfc8037 54 | 55 | ## Source Code 56 | 57 | _buddy-sign_ is open source and can be found on 58 | [github](https://github.com/funcool/buddy-sign). 59 | 60 | You can clone the public repository with this command: 61 | 62 | ```clojure 63 | git clone https://github.com/funcool/buddy-sign 64 | ``` 65 | 66 | ## Run tests 67 | 68 | For running tests just execute this: 69 | 70 | ```clojure 71 | lein test 72 | ``` 73 | 74 | ## License 75 | 76 | _buddy-sign_ is licensed under Apache 2.0 License. You can see the 77 | complete text of the license on the root of the repository on 78 | `LICENSE` file. 79 | -------------------------------------------------------------------------------- /doc/01-jwt.md: -------------------------------------------------------------------------------- 1 | # JWT (Json Web Token) 2 | 3 | JSON Web Token (JWT) is a compact claims representation format 4 | intended for space constrained environments such as HTTP Authorization 5 | headers and URI query parameters. JWTs encode claims to be 6 | transmitted as a JavaScript Object Notation (JSON) object that is used 7 | as the payload of a JSON Web Signature (JWS) structure or as the 8 | plaintext of a JSON Web Encryption (JWE) structure, enabling the 9 | claims to be digitally signed or MACed and/or encrypted. 10 | 11 | 12 | ## Supported algorithms 13 | 14 | Here is a table of supported algorithms for signing JWT claims using JWS 15 | (Json Web Signature): 16 | 17 | |Algorithm name | Hash algorithms | Keywords | Priv/Pub Key? | 18 | |---|---|---|---| 19 | |Elliptic Curve DSA | sha256, sha512 | `:es256`, `:es512` | Yes | 20 | |Edwards Curve DSA | sha512 | `:eddsa` | Yes | 21 | |RSASSA PSS | sha256, sha512 | `:ps256`, `:ps512` | Yes | 22 | |RSASSA PKCS1 v1_5 | sha256, sha512 | `:rs256`, `:rs512` | Yes | 23 | |HMAC | sha256*, sha512 | `:hs256`, `:hs512` | No | 24 | 25 | The JWE (Json Web Encryption) in difference to JWS uses two types of 26 | algoritms: key encryption algorithms and content encryption 27 | algorithms. 28 | 29 | The *key encryption algorithms* are responsible to encrypt the key 30 | that will be used for encrypt the content. This is a table that 31 | exposes the currently supported _Key Encryption Algorithms_ (specified 32 | in JWA RFC): 33 | 34 | | Algorithm name | Decription | Keyword | Shared Key Size | 35 | |----------------|------------|---------------|-----------------| 36 | | DIR | Direct use of a shared symmetric key | `:dir` | (depends on content encryption algorithm) | 37 | | A128KW | AES128 Key Wrap | `:a128kw` | 16 bytes | 38 | | A192KW | AES192 Key Wrap | `:a192kw` | 24 bytes | 39 | | A256KW | AES256 Key Wrap | `:a256kw` | 32 bytes | 40 | | RSA1_5 | RSA PKCS1 V1_5 | `:rsa1_5` | Asymetric key pair | 41 | | RSA-OAEP | RSA OAEP with SHA1 | `:rsa-oaep` | Asymetric key pair | 42 | | RSA-OAEP-256 | RSA OAEP with SHA256 | `:rsa-oaep-256` | Asymetric key pair | 43 | 44 | 45 | The *content encryption algoritms* are responsible to encrypt the 46 | content. This is a table that exposes the currently supported _Content 47 | Encryption Algorithms_ (all specified in the JWA RFC): 48 | 49 | | Algorithm name | Description | Keyword | Shared Key Size | 50 | |----------------|-------------|---------|-----------------| 51 | | A128CBC-HS256 | AES128 with CBC mode and HMAC-SHA256 | `:a128cbc-hs256` | 32 bytes | 52 | | A192CBC-HS384 | AES192 with CBC mode and HMAC-SHA384 | `:a192cbc-hs384` | 48 bytes | 53 | | A256CBC-HS512 | AES256 with CBC mode and HMAC-SHA512 | `:a256cbc-hs512` | 64 bytes | 54 | | A128GCM | AES128 with GCM mode | `:a128gcm` | 16 bytes | 55 | | A192GCM | AES192 with GCM mode | `:a192gcm` | 24 bytes | 56 | | A256GCM | AES256 with GCM mode | `:a256gcm` | 32 bytes | 57 | 58 | 59 | ## Signing data 60 | 61 | Let's start with signing data. For it we will use the `sign` function 62 | from `buddy.sign.jws` namespace, and the `hs256` algorithm for 63 | signing: 64 | 65 | ```clojure 66 | (require '[buddy.sign.jwt :as jwt]) 67 | 68 | (jwt/sign {:userid 1} "secret") 69 | ;; "eyJ0eXAiOiJKV1MiLCJhbGciOiJIU..." 70 | ``` 71 | 72 | The `sign` function return a encoded and signed token as plain 73 | `String` instance or an exception in case of something goes wrong. As 74 | you can observe, no algorithm is passed as parameter. In this 75 | situation the default one will be used, and in this case is `:hs256`. 76 | 77 | **NOTE**: Due to the nature of the storage format, the input is 78 | restricted mainly to json objects in the current version. 79 | 80 | 81 | ## Unsigning data 82 | 83 | It's time to unsign data. That process consists on verify the 84 | signature of incoming data and return the plain data (without 85 | signature). For it we will use the `unsign` function from 86 | `buddy.sign.jwt` namespace: 87 | 88 | ```clojure 89 | (jwt/unsign data "secret") 90 | ;; => {:userid 1} 91 | ``` 92 | 93 | ## Claims validation 94 | 95 | _buddy-sign_ JWT implements validation of a concrete subset of claims: 96 | *iat* (issue time), *exp* (expiration time), *nbf* (not before), *iss* 97 | (issuer) and *aud* (audience). 98 | 99 | The validation is performed on decoding the token. If `:exp` claim is 100 | found and is posterior to the current date time (UTC) an validation 101 | exception will be raised. Alternatively, the time to validate token 102 | against can be specified as `:now` option to `unsign`. 103 | 104 | Additionally, if you want to provide some leeway for the claims 105 | validation, you can pass the `:leeway` option to the `unsign` 106 | function. 107 | 108 | Let's see an example using direct api: 109 | 110 | ```clojure 111 | (require '[clj-time.core :as time]) 112 | 113 | ;; Define claims with `:exp` key 114 | (def claims 115 | {:user 1 :exp (time/plus (time/now) (time/seconds 5))}) 116 | 117 | ;; Serialize and sign a token with previously defined claims 118 | (def token (jwt/sign claims "key")) 119 | 120 | ;; wait 5 seconds and try unsign it 121 | 122 | (jwt/unsign token "key") 123 | ;; => ExceptionInfo "Token is older than :exp (1427836475)" 124 | 125 | ;; use timestamp in the past 126 | (jwt/unsign token "key" {:now (time/minus (time/now) (time/seconds 5))}) 127 | ;; => {:user 1} 128 | ``` 129 | 130 | ## Encrypting data 131 | 132 | Let's start with encrypting data. For it we will use the `encrypt` 133 | function from the `buddy.sign.jwt` namespace: 134 | 135 | ```clojure 136 | (require '[buddy.sign.jwt :as jwt]) 137 | (require '[buddy.core.hash :as hash]) 138 | 139 | ;; Hash your secret key with sha256 for 140 | ;; create a byte array of 32 bytes because 141 | ;; is a requirement for default content 142 | ;; encryption algorithm 143 | 144 | (def secret (hash/sha256 "mysecret")) 145 | 146 | ;; Encrypt it using the previously 147 | ;; hashed key 148 | 149 | (jwt/encrypt {:userid 1} secret {:alg :dir :enc :a128cbc-hs256}) 150 | ;; "eyJ0eXAiOiJKV1MiLCJhbGciOiJIU..." 151 | ``` 152 | 153 | The `encrypt` function, like `sign` from *JWT*, returns a plain string 154 | with encrypted and encoded content using a provided algorithm and 155 | shared secret key. 156 | 157 | 158 | ## Decrypting Data 159 | 160 | The decrypt is a inverse process, that takes encrypted data and the 161 | shared key, and returns the plain data. For it, _buddy-sign_ exposes 162 | the `decrypt` function. Let see how you can use it: 163 | 164 | ```clojure 165 | (jwt/decrypt incoming-data secret) 166 | ;; => {:userid 1} 167 | ``` 168 | 169 | ## Digital signature algorithms 170 | 171 | In order to use any of digital signature algorithms you must have a private/public 172 | key. If you don't have one, don't worry, it is very easy to generate it using 173 | *openssl* ([look on FAQ](./05-faq.md)). 174 | 175 | Having generated a key pair, you can sign your messages using one of 176 | supported digital signature algorithms. 177 | 178 | Example of signing a string using _es256_ (eliptic curve dsa) algorithm: 179 | 180 | ```clojure 181 | (require '[buddy.core.keys :as keys]) 182 | 183 | ;; Create keys instances 184 | (def ec-privkey (keys/private-key "ecprivkey.pem")) 185 | (def ec-pubkey (keys/public-key "ecpubkey.pem")) 186 | 187 | ;; Use them like plain secret password with hmac algorithms for sign 188 | (def signed-data (jwt/sign {:foo "bar"} ec-privkey {:alg :es256})) 189 | 190 | ;; And unsign 191 | (def unsigned-data (jwt/unsign signed-data ec-pubkey {:alg :es256})) 192 | ``` 193 | 194 | ## Asymetric encryption 195 | 196 | In order to use any asymetric encryption algorithm, you should have 197 | private/public key pair. If you don't have one, don't worry, it is 198 | very easy to generate it using *openssl* ([look on FAQ](./05-faq.md)). 199 | 200 | Then, having ready the key pair, you can start using one of the supported 201 | key encryption algorithms in the JWE specification such as `:rsa1_5`, `:rsa-oaep` 202 | or `:rsa-oaep-256`. 203 | 204 | Let see an demonstration example: 205 | 206 | 207 | ```clojure 208 | (require '[buddy.core.keys :as keys]) 209 | 210 | ;; Create keys instances 211 | (def privkey (keys/private-key "privkey.pem")) 212 | (def pubkey (keys/public-key "pubkey.pem")) 213 | 214 | ;; Encrypt data 215 | (def encrypted-data (jwt/encrypt {:foo "bar"} pubkey 216 | {:alg :rsa-oaep 217 | :enc :a128cbc-hs256}) 218 | 219 | ;; Decrypted 220 | (def decrypted-data (jwt/decrypt encrypted-data privkey 221 | {:alg :rsa-oaep 222 | :enc :a128cbc-hs256})) 223 | ``` 224 | -------------------------------------------------------------------------------- /doc/02-jws.md: -------------------------------------------------------------------------------- 1 | # JWS (Json Web Signature) 2 | 3 | JSON Web Signature (JWS) is a signing part of Json Web Token (JWT) 4 | specification and represents content secured with digital signatures 5 | or Message Authentication Codes (MACs) using JavaScript Object 6 | Notation (JSON) as serialization format. 7 | 8 | In difference to JWT, this is more lowlevel signing primitive and 9 | allows signing arbitrary binary data (instead of json formated 10 | claims): 11 | 12 | ```clojure 13 | (require '[buddy.sign.jws :as jws]) 14 | (require '[buddy.core.nonce :as nonce]) 15 | (require '[buddy.core.bytes :as bytes]) 16 | 17 | (def data (nonce/random-bytes 1024)) 18 | (def message (jws/sign data "secret")) 19 | 20 | (bytes/equals? (jws/unsign message "secret") data) 21 | ;; => true 22 | ``` 23 | 24 | The supported algorithms are documented on the [jwt 25 | document](01-jwt.md). 26 | -------------------------------------------------------------------------------- /doc/03-jwe.md: -------------------------------------------------------------------------------- 1 | # JWE (Json Web Encryption) 2 | 3 | JSON Web Encryption (JWE) is a encryption part of Json Web Token (JWT) 4 | specification and represents encrypted content using JavaScript 5 | Object Notation (JSON) based data structures. 6 | 7 | In same way as JWS, this is a low level primitive that allows creating 8 | fully encrypted messages of arbitrary data: 9 | 10 | ```clojure 11 | (require '[buddy.sign.jws :as jws]) 12 | (require '[buddy.core.nonce :as nonce]) 13 | (require '[buddy.core.bytes :as bytes]) 14 | 15 | (def key32 (nonce/random-bytes 32)) 16 | (def data (nonce/random-bytes 1024)) 17 | 18 | (def message (jwt/encrypt data key32)) 19 | (bytes/equals? (jws/decrypt message key32) data) 20 | ;; => true 21 | ``` 22 | 23 | The supported algorithms are documented on the [jwt 24 | document](01-jwt.md). 25 | -------------------------------------------------------------------------------- /doc/04-jwk.md: -------------------------------------------------------------------------------- 1 | # JWK (Json Web key) 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /doc/05-cms.md: -------------------------------------------------------------------------------- 1 | # CMS (Compact Message Signing) 2 | 3 | CMS is a high influence by django's cryptographic library and json web 4 | signature/encryption signing algorithm with focus on have a compact 5 | representation. It's build on top of fantastic `ptaoussanis/nippy` 6 | serialization library. 7 | 8 | In order to use this you shall include the concrete `nippy` library because 9 | **buddy-sign** does not have a hardcoded dependency to it: 10 | 11 | ```clojure 12 | ;; project.clj 13 | [com.taoensso/nippy "3.1.1"] 14 | 15 | ;; deps.edn 16 | com.taoensso/nippy {:mvn/version "3.1.1"} 17 | ``` 18 | 19 | In the same way as JWS, it support a great number of different signing 20 | algorithms that can be used for sign your messages: 21 | 22 | | Algorithm name | Hash algorithms | Keywords | Priv/Pub Key? | 23 | |--------------------|-------------------|--------------------|---------------| 24 | | Elliptic Curve DSA | sha256, sha512 | `:es256`, `:es512` | Yes | 25 | | RSASSA PSS | sha256, sha512 | `:ps256`, `:ps512` | Yes | 26 | | RSASSA PKCS1 v1_5 | sha256, sha512 | `:rs256`, `:rs512` | Yes | 27 | | Poly1305 | aes, twofish, serpent | `:poly1305-aes`, `:poly1305-serpent`, `:poly1305-twofish` | No | 28 | | HMAC | sha256*, sha512 | `:hs256`, `:hs512` | No | 29 | 30 | In difference with jwt, this implementation is not limited to hash-map 31 | like objects, and you can sign any clojure valid type. 32 | 33 | Let's see an example: 34 | 35 | ```clojure 36 | (require '[buddy.sign.compact :as cms]) 37 | 38 | (def data (cms/sign #{:foo :bar} "secret") 39 | 40 | (cms/unsign data "secret") 41 | ;; => #{:foo :bar} 42 | ``` 43 | 44 | Then, you also will be able validate the signed message based on its age: 45 | 46 | ```clojure 47 | (cm/unsign data "secret" {:max-age (* 15 60)}) 48 | ;; => ExceptionInfo: "Token is older than 1427836475" 49 | ``` 50 | 51 | **NOTE:** Only `:max-age` validation is bundled all other validation 52 | is delegated to the user code. 53 | -------------------------------------------------------------------------------- /doc/06-faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## When I should use JWE and when JWS? 4 | 5 | The main difference between JWS and JWE, is that JWE encrypts the claims with 6 | an algorithm that uses a one time key. Both provides good security, but JWE also 7 | provides privacy of the data. 8 | 9 | If you only stores the userid or something similar, JWS is recommended, because 10 | it has less overhead. But if you are storing in the token claims that require 11 | privacy, JWE is the solution that should be used. 12 | 13 | ## ECDSA vs EdDSA ? 14 | 15 | ECDSA algorithm has one very weak point - it requires cryptographically secure 16 | random numbers not only for key generation but also for EVERY signature creation. 17 | 18 | If attacker has two signatures for same data and can guess random number used 19 | for their creation, then she can calculate private key (see PS3 ECDSA exploit 20 | for example). 21 | 22 | Ed25519 on the other hand is specifically designed to avoid this kind of errors, 23 | it also has very good performance characteristics both for signing and verification, 24 | see https://tools.ietf.org/html/rfc8032[RFC8032] for details 25 | 26 | ## How I can generate keypairs? 27 | 28 | Example on how to generate one Elliptic Curve DSA keypair: 29 | 30 | ```bash 31 | # Generating params file 32 | openssl ecparam -name prime256v1 -out ecparams.pem 33 | 34 | # Generate a private key from params file 35 | openssl ecparam -in ecparams.pem -genkey -noout -out ecprivkey.pem 36 | 37 | # Generate a public key from private key 38 | openssl ec -in ecprivkey.pem -pubout -out ecpubkey.pem 39 | ``` 40 | 41 | Example on how to generate one RSA keypair: 42 | 43 | ```bash 44 | # Generate aes256 encrypted private key 45 | openssl genrsa -aes256 -out privkey.pem 2048 46 | 47 | # Generate public key from previously created private key. 48 | openssl rsa -pubout -in privkey.pem -out pubkey.pem 49 | ``` 50 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | all: doc 2 | 3 | doc: 4 | mkdir -p dist/latest/ 5 | cd ..; clojure -A:dev:codox -M doc.clj; 6 | 7 | github: doc 8 | ghp-import -m "Generate documentation" -b gh-pages dist/ 9 | git push origin gh-pages 10 | -------------------------------------------------------------------------------- /mvn-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mvn deploy:deploy-file -Dfile=target/buddy-sign.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/ 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | jar 5 | buddy-sign 6 | buddy-sign 7 | 3.5 8 | buddy-sign 9 | 10 | 11 | Apache-2.0 12 | https://www.apache.org/licenses/LICENSE-2.0.txt 13 | 14 | 15 | 16 | 17 | buddy 18 | buddy-core 19 | 1.12.0-430 20 | 21 | 22 | org.clojure 23 | clojure 24 | 1.11.1 25 | 26 | 27 | 28 | src 29 | 30 | 31 | 32 | clojars 33 | https://repo.clojars.org/ 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /scripts/repl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export OPTIONS=" 4 | -A:dev \ 5 | -J-XX:-OmitStackTraceInFastThrow \ 6 | -J-Xms50m \ 7 | -J-Xmx512m \ 8 | -J-Djdk.attach.allowAttachSelf \ 9 | -J-XX:+UnlockDiagnosticVMOptions \ 10 | -J-XX:+DebugNonSafepoints"; 11 | 12 | # Disable C2 Compiler 13 | # export OPTIONS="$OPTIONS -J-XX:TieredStopAtLevel=1" 14 | 15 | # Disable all compilers 16 | # export OPTIONS="$OPTIONS -J-Xint" 17 | 18 | # export OPTIONS_EVAL="nil" 19 | export OPTIONS_EVAL="(set! *warn-on-reflection* true)" 20 | 21 | set -ex 22 | exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main 23 | -------------------------------------------------------------------------------- /src/buddy/sign/compact.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 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 | (ns buddy.sign.compact 16 | "Compact high level message signing implementation. 17 | 18 | It has high influence by django's cryptographic library 19 | and json web signature/encryption but with focus on have 20 | a compact representation. It's build on top of fantastic 21 | ptaoussanis/nippy serialization library. 22 | 23 | This singing implementation is not very efficient with 24 | small messages, but is very space efficient with big 25 | messages. 26 | 27 | The purpose of this implementation is for secure message 28 | transfer, it is not really good candidate for auth token 29 | because of not good space efficiency for small messages." 30 | (:require [buddy.core.codecs :as codecs] 31 | [buddy.core.codecs.base64 :as b64] 32 | [buddy.core.bytes :as bytes] 33 | [buddy.core.keys :as keys] 34 | [buddy.core.mac :as mac] 35 | [buddy.core.dsa :as dsa] 36 | [buddy.core.nonce :as nonce] 37 | [buddy.sign.util :as util] 38 | [clojure.string :as str] 39 | [taoensso.nippy :as nippy] 40 | [taoensso.nippy.compression :as nippycompress]) 41 | (:import clojure.lang.Keyword)) 42 | 43 | (defn- sign-poly 44 | [input options] 45 | (let [iv (or (:iv options) 46 | (nonce/random-bytes 16))] 47 | (-> (mac/hash input (assoc options :iv iv)) 48 | (bytes/concat iv)))) 49 | 50 | (defn- verify-poly 51 | [input signature options] 52 | (let [iv (bytes/slice signature 16 (count signature)) 53 | signature' (bytes/slice signature 0 16)] 54 | (mac/verify input signature' (assoc options :iv iv)))) 55 | 56 | (def ^{:doc "List of supported signing algorithms" 57 | :dynamic true} 58 | *signers-map* 59 | {:hs256 {:signer #(mac/hash %1 {:alg :hmac+sha256 :key %2}) 60 | :verifier #(mac/verify %1 %2 {:alg :hmac+sha256 :key %3})} 61 | :hs512 {:signer #(mac/hash %1 {:alg :hmac+sha512 :key %2}) 62 | :verifier #(mac/verify %1 %2 {:alg :hmac+sha512 :key %3})} 63 | :rs256 {:signer #(dsa/sign %1 {:alg :rsassa-pkcs15+sha256 :key %2}) 64 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pkcs15+sha256 :key %3})} 65 | :rs512 {:signer #(dsa/sign %1 {:alg :rsassa-pkcs15+sha512 :key %2}) 66 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pkcs15+sha512 :key %3})} 67 | :ps256 {:signer #(dsa/sign %1 {:alg :rsassa-pss+sha256 :key %2}) 68 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pss+sha256 :key %3})} 69 | :ps512 {:signer #(dsa/sign %1 {:alg :rsassa-pss+sha512 :key %2}) 70 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pss+sha512 :key %3})} 71 | :es256 {:signer #(dsa/sign %1 {:alg :ecdsa+sha256 :key %2}) 72 | :verifier #(dsa/verify %1 %2 {:alg :ecdsa+sha256 :key %3 })} 73 | :es512 {:signer #(dsa/sign %1 {:alg :ecdsa+sha512 :key %2}) 74 | :verifier #(dsa/verify %1 %2 {:alg :ecdsa+sha512 :key %3})} 75 | :poly1305-aes {:signer #(sign-poly %1 {:alg :poly1305+aes :key %2}) 76 | :verifier #(verify-poly %1 %2 {:alg :poly1305+aes :key %3})} 77 | :poly1305-serpent {:signer #(sign-poly %1 {:alg :poly1305+serpent :key %2}) 78 | :verifier #(verify-poly %1 %2 {:alg :poly1305+serpent :key %3})} 79 | :poly1305-twofish {:signer #(sign-poly %1 {:alg :poly1305+twofish :key %2}) 80 | :verifier #(verify-poly %1 %2 {:alg :poly1305+twofish :key %3})}}) 81 | 82 | (defn- calculate-signature 83 | "Given the bunch of bytes, a private key and algorithm, 84 | return a calculated signature as byte array." 85 | [^bytes input ^bytes key ^Keyword alg] 86 | (let [signer (get-in *signers-map* [alg :signer])] 87 | (signer input key))) 88 | 89 | (defn- verify-signature 90 | "Given a bunch of bytes, a previously generated 91 | signature, the private key and algorithm, return 92 | signature matches or not." 93 | [^bytes input ^bytes signature ^bytes key ^Keyword alg] 94 | (let [verifier (get-in *signers-map* [alg :verifier])] 95 | (verifier input signature key))) 96 | 97 | (defn- serialize 98 | [data compress] 99 | (cond 100 | (true? compress) 101 | (nippy/freeze data {:compressor nippy/snappy-compressor}) 102 | 103 | (satisfies? nippycompress/ICompressor compress) 104 | (nippy/freeze data {:compressor compress}) 105 | 106 | :else 107 | (nippy/freeze data))) 108 | 109 | (def ^:private bytes->base64str 110 | (comp codecs/bytes->str #(b64/encode % true))) 111 | 112 | (defn sign 113 | "Sign arbitrary length string/byte array using 114 | compact sigining method." 115 | [data key & [{:keys [alg compress] 116 | :or {alg :hs256 compress true}}]] 117 | (let [input (serialize data compress) 118 | salt (nonce/random-bytes 12) 119 | stamp (codecs/long->bytes (util/now)) 120 | signature (-> (bytes/concat input salt stamp) 121 | (calculate-signature key alg))] 122 | (str/join "." [(bytes->base64str input) 123 | (bytes->base64str signature) 124 | (bytes->base64str salt) 125 | (bytes->base64str stamp)]))) 126 | 127 | (defn unsign 128 | "Given a signed message, verify it and return 129 | the decoded data." 130 | [data key & [{:keys [alg compress max-age] 131 | :or {alg :hs256 compress true}}]] 132 | (let [[input signature salt stamp] (str/split data #"\." 4) 133 | input (b64/decode input) 134 | signature (b64/decode signature) 135 | salt (b64/decode salt) 136 | stamp (b64/decode stamp) 137 | candidate (bytes/concat input salt stamp)] 138 | (when-not (verify-signature candidate signature key alg) 139 | (throw (ex-info "Message seems corrupt or manipulated." 140 | {:type :validation :cause :signature}))) 141 | (let [now (util/now) 142 | stamp (codecs/bytes->long stamp)] 143 | (when (and (number? max-age) (> (- now stamp) max-age)) 144 | (throw (ex-info (format "Token is older than %s" max-age) 145 | {:type :validation :cause :max-age}))) 146 | (nippy/thaw input {:v1-compatibility? false})))) 147 | 148 | (util/defalias encode sign) 149 | (util/defalias decode unsign) 150 | -------------------------------------------------------------------------------- /src/buddy/sign/jwe.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 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 | ;; Links to rfcs: 16 | ;; - http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32 17 | ;; - http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 18 | ;; - http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-40 19 | ;; - http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05 20 | ;; - http://tools.ietf.org/html/rfc3394 (AES Key Wrap Algorithm) 21 | ;; - http://tools.ietf.org/html/rfc3447 (RSA OAEP) 22 | 23 | (ns buddy.sign.jwe 24 | "Json Web Encryption" 25 | (:require 26 | [buddy.core.bytes :as bytes] 27 | [buddy.core.codecs :as bc] 28 | [buddy.core.codecs.base64 :as b64] 29 | [buddy.core.crypto :as crypto] 30 | [buddy.core.keys :as keys] 31 | [buddy.core.nonce :as nonce] 32 | [buddy.sign.jwe.cek :as cek] 33 | [buddy.sign.jws :as jws] 34 | [buddy.sign.util :as util] 35 | [buddy.util.deflate :as deflate] 36 | [cheshire.core :as json] 37 | [clojure.string :as str]) 38 | (:import 39 | org.bouncycastle.crypto.InvalidCipherTextException)) 40 | 41 | ;; --- Implementation details 42 | 43 | (defn- generate-iv 44 | [{:keys [enc]}] 45 | (case enc 46 | :a128cbc-hs256 (nonce/random-bytes 16) 47 | :a192cbc-hs384 (nonce/random-bytes 16) 48 | :a256cbc-hs512 (nonce/random-bytes 16) 49 | :a128gcm (nonce/random-bytes 12) 50 | :a192gcm (nonce/random-bytes 12) 51 | :a256gcm (nonce/random-bytes 12))) 52 | 53 | (defn- encode-payload 54 | [input zip] 55 | (cond-> (bc/to-bytes input) 56 | zip (deflate/compress))) 57 | 58 | (defn- decode-payload 59 | [payload header] 60 | (let [{:keys [zip]} (util/parse-jose-header header)] 61 | (cond-> payload 62 | zip (deflate/uncompress payload)))) 63 | 64 | (defmulti aead-encrypt :alg) 65 | (defmulti aead-decrypt :alg) 66 | 67 | (defmethod aead-encrypt :a128cbc-hs256 68 | [{:keys [alg plaintext secret iv aad] :as params}] 69 | (let [result (crypto/-encrypt {:alg :aes128-cbc-hmac-sha256 :input plaintext 70 | :key secret :iv iv :aad aad}) 71 | resultlen (count result) 72 | ciphertext (bytes/slice result 0 (- resultlen 16)) 73 | tag (bytes/slice result (- resultlen 16) resultlen)] 74 | [ciphertext tag])) 75 | 76 | (defmethod aead-decrypt :a128cbc-hs256 77 | [{:keys [alg authtag ciphertext secret iv aad] :as params}] 78 | (crypto/-decrypt {:alg :aes128-cbc-hmac-sha256 79 | :input (bytes/concat ciphertext authtag) 80 | :key secret 81 | :iv iv 82 | :aad aad})) 83 | 84 | (defmethod aead-encrypt :a192cbc-hs384 85 | [{:keys [alg plaintext secret iv aad] :as params}] 86 | (let [result (crypto/-encrypt {:alg :aes192-cbc-hmac-sha384 :input plaintext 87 | :key secret :iv iv :aad aad}) 88 | resultlen (count result) 89 | ciphertext (bytes/slice result 0 (- resultlen 24)) 90 | tag (bytes/slice result (- resultlen 24) resultlen)] 91 | [ciphertext tag])) 92 | 93 | (defmethod aead-decrypt :a192cbc-hs384 94 | [{:keys [alg authtag ciphertext secret iv aad] :as params}] 95 | (crypto/-decrypt {:alg :aes192-cbc-hmac-sha384 96 | :input (bytes/concat ciphertext authtag) 97 | :key secret 98 | :iv iv 99 | :aad aad})) 100 | 101 | (defmethod aead-encrypt :a256cbc-hs512 102 | [{:keys [alg plaintext secret iv aad] :as params}] 103 | (let [result (crypto/-encrypt {:alg :aes256-cbc-hmac-sha512 :input plaintext 104 | :key secret :iv iv :aad aad}) 105 | resultlen (count result) 106 | ciphertext (bytes/slice result 0 (- resultlen 32)) 107 | tag (bytes/slice result (- resultlen 32) resultlen)] 108 | [ciphertext tag])) 109 | 110 | (defmethod aead-decrypt :a256cbc-hs512 111 | [{:keys [alg authtag ciphertext secret iv aad] :as params}] 112 | (crypto/-decrypt {:alg :aes256-cbc-hmac-sha512 113 | :input (bytes/concat ciphertext authtag) 114 | :key secret 115 | :iv iv 116 | :aad aad})) 117 | 118 | (defmethod aead-encrypt :a128gcm 119 | [{:keys [alg plaintext secret iv aad] :as params}] 120 | (let [result (crypto/-encrypt {:alg :aes128-gcm 121 | :input plaintext 122 | :key secret 123 | :iv iv 124 | :aad aad}) 125 | resultlen (alength ^bytes result) 126 | ciphertext (bytes/slice result 0 (- resultlen 16)) 127 | tag (bytes/slice result (- resultlen 16) resultlen)] 128 | [ciphertext tag])) 129 | 130 | (defmethod aead-decrypt :a128gcm 131 | [{:keys [alg authtag ciphertext secret iv aad] :as params}] 132 | (crypto/-decrypt {:alg :aes128-gcm 133 | :input (bytes/concat ciphertext authtag) 134 | :key secret 135 | :iv iv 136 | :aad aad})) 137 | 138 | (defmethod aead-encrypt :a192gcm 139 | [{:keys [alg plaintext secret iv aad] :as params}] 140 | (let [result (crypto/-encrypt {:alg :aes192-gcm 141 | :input plaintext 142 | :key secret 143 | :iv iv 144 | :aad aad}) 145 | resultlen (count result) 146 | ciphertext (bytes/slice result 0 (- resultlen 16)) 147 | tag (bytes/slice result (- resultlen 16) resultlen)] 148 | [ciphertext tag])) 149 | 150 | (defmethod aead-decrypt :a192gcm 151 | [{:keys [alg authtag ciphertext secret iv aad] :as params}] 152 | (crypto/-decrypt {:alg :aes192-gcm 153 | :input (bytes/concat ciphertext authtag) 154 | :key secret 155 | :iv iv 156 | :aad aad})) 157 | 158 | (defmethod aead-encrypt :a256gcm 159 | [{:keys [alg plaintext secret iv aad] :as params}] 160 | (let [result (crypto/-encrypt {:alg :aes256-gcm :input plaintext 161 | :key secret :iv iv :aad aad}) 162 | resultlen (count result) 163 | ciphertext (bytes/slice result 0 (- resultlen 16)) 164 | tag (bytes/slice result (- resultlen 16) resultlen)] 165 | [ciphertext tag])) 166 | 167 | (defmethod aead-decrypt :a256gcm 168 | [{:keys [alg authtag ciphertext secret iv aad] :as params}] 169 | (crypto/-decrypt {:alg :aes256-gcm 170 | :input (bytes/concat ciphertext authtag) 171 | :key secret 172 | :iv iv 173 | :aad aad})) 174 | 175 | ;; --- Public Api 176 | 177 | (def ^:private bytes->base64 178 | (comp bc/bytes->str #(bc/bytes->b64 % true))) 179 | 180 | (defn decode-header 181 | "Given a message, decode the header. 182 | WARNING: This does not perform any signature validation" 183 | [input] 184 | (let [[header] (str/split input #"\." 2)] 185 | (util/parse-jose-header (bc/str->bytes header)))) 186 | 187 | (defn encrypt 188 | "Encrypt then sign arbitrary length string/byte array using 189 | json web encryption" 190 | ([payload key] (encrypt payload key nil)) 191 | ([payload key {:keys [alg enc zip header] 192 | :or {alg :dir enc :a128cbc-hs256 zip false}}] 193 | (let [scek (cek/generate {:key key :alg alg :enc enc}) 194 | ecek (cek/encrypt {:key key :cek scek :alg alg :enc enc}) 195 | iv (generate-iv {:enc enc}) 196 | header (cond-> (into {:alg alg :enc enc} header) 197 | zip (assoc :zip "DEF")) 198 | header (util/encode-jose-header header) 199 | payload (encode-payload payload zip) 200 | 201 | [ciphertext authtag] 202 | (aead-encrypt {:alg enc 203 | :plaintext payload 204 | :secret scek 205 | :aad header 206 | :iv iv})] 207 | 208 | (str (bc/bytes->str header) "." 209 | (bytes->base64 ecek) "." 210 | (bytes->base64 iv) "." 211 | (bytes->base64 ciphertext) "." 212 | (bytes->base64 authtag))))) 213 | 214 | (defn decrypt 215 | "Decrypt the jwe compliant message and return its payload" 216 | ([input key] (decrypt input key nil)) 217 | ([input key {:keys [alg enc] :or {alg :dir enc :a128cbc-hs256} :as opts}] 218 | (let [[header ecek iv ciphertext authtag] (some-> input (str/split #"\." 5))] 219 | (when (or (nil? ecek) (nil? iv) (nil? ciphertext) (nil? authtag)) 220 | (throw (ex-info "Message seems corrupt or manipulated" 221 | {:type :validation :cause :signature}))) 222 | (try 223 | (let [ecek (-> ecek 224 | (bc/str->bytes) 225 | (bc/b64->bytes true)) 226 | iv (-> iv 227 | (bc/str->bytes) 228 | (bc/b64->bytes true)) 229 | ctxt (-> ciphertext 230 | (bc/str->bytes) 231 | (bc/b64->bytes true)) 232 | authtag (-> authtag 233 | (bc/str->bytes) 234 | (bc/b64->bytes true)) 235 | 236 | header (bc/str->bytes header) 237 | 238 | scek (cek/decrypt {:key key :ecek ecek :alg alg :enc enc}) 239 | 240 | payload (aead-decrypt {:ciphertext ctxt 241 | :authtag authtag 242 | :alg enc 243 | :aad header 244 | :secret scek 245 | :iv iv})] 246 | (decode-payload payload header)) 247 | 248 | (catch java.lang.IllegalArgumentException e 249 | (throw (ex-info "Message seems corrupt or manipulated" 250 | {:type :validation :cause :token} 251 | e))) 252 | (catch java.lang.AssertionError e 253 | (throw (ex-info "Message seems corrupt or manipulated" 254 | {:type :validation :cause :token} 255 | e))) 256 | (catch com.fasterxml.jackson.core.JsonParseException e 257 | (throw (ex-info "Message seems corrupt or manipulated" 258 | {:type :validation :cause :signature} 259 | e))) 260 | (catch org.bouncycastle.crypto.InvalidCipherTextException e 261 | (throw (ex-info "Message seems corrupt or manipulated" 262 | {:type :validation :cause :signature} 263 | e))))))) 264 | 265 | (util/defalias encode encrypt) 266 | (util/defalias decode decrypt) 267 | -------------------------------------------------------------------------------- /src/buddy/sign/jwe/cek.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 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 | (ns buddy.sign.jwe.cek 16 | "Json Web Encryption Content Encryption Key utilities." 17 | (:require [buddy.core.codecs :as codecs] 18 | [buddy.core.nonce :as nonce] 19 | [buddy.core.keys :as keys]) 20 | (:import javax.crypto.Cipher 21 | java.security.Key 22 | java.security.SecureRandom)) 23 | 24 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 25 | ;; Implementation: Content Encryption Keys 26 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 27 | 28 | (defn- validate-keylength-for-algorithm 29 | [key algorithn] 30 | (case algorithn 31 | :dir true 32 | :rsa-oaep true 33 | :rsa-oaep-256 true 34 | :rsa1_5 true 35 | :a128kw (= (count key) 16) 36 | :a192kw (= (count key) 24) 37 | :a256kw (= (count key) 32))) 38 | 39 | (defn- encrypt-with-rsaaoep 40 | [^bytes cek ^Key pubkey] 41 | (let [cipher (Cipher/getInstance "RSA/ECB/OAEPWithSHA-1AndMGF1Padding" "BC") 42 | sr (SecureRandom.)] 43 | (.init ^Cipher cipher Cipher/ENCRYPT_MODE pubkey sr) 44 | (.doFinal cipher cek))) 45 | 46 | (defn- decrypt-with-rsaaoep 47 | [^bytes ecek ^Key privkey] 48 | (let [cipher (Cipher/getInstance "RSA/ECB/OAEPWithSHA-1AndMGF1Padding" "BC") 49 | sr (SecureRandom.)] 50 | (.init cipher Cipher/DECRYPT_MODE privkey sr) 51 | (.doFinal cipher ecek))) 52 | 53 | (defn- encrypt-with-rsaaoep-sha256 54 | [^bytes cek ^Key pubkey] 55 | (let [cipher (Cipher/getInstance "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" "BC") 56 | sr (SecureRandom.)] 57 | (.init cipher Cipher/ENCRYPT_MODE pubkey sr) 58 | (.doFinal cipher cek))) 59 | 60 | (defn- decrypt-with-rsaaoep-sha256 61 | [^bytes ecek ^Key privkey] 62 | (let [cipher (Cipher/getInstance "RSA/ECB/OAEPWithSHA-256AndMGF1Padding" "BC") 63 | sr (SecureRandom.)] 64 | (.init cipher Cipher/DECRYPT_MODE privkey sr) 65 | (.doFinal cipher ecek))) 66 | 67 | (defn- encrypt-with-rsa-pkcs15 68 | [^bytes cek ^Key pubkey] 69 | (let [cipher (Cipher/getInstance "RSA/ECB/PKCS1Padding" "BC") 70 | sr (SecureRandom.)] 71 | (.init cipher Cipher/ENCRYPT_MODE pubkey sr) 72 | (.doFinal cipher cek))) 73 | 74 | (defn- decrypt-with-rsa-pkcs15 75 | [^bytes ecek ^Key privkey] 76 | (let [cipher (Cipher/getInstance "RSA/ECB/PKCS1Padding" "BC") 77 | sr (SecureRandom.)] 78 | (.init cipher Cipher/DECRYPT_MODE privkey sr) 79 | (.doFinal cipher ecek))) 80 | 81 | (def ^:private 82 | aeskw? #{:a128kw :a192kw :a256kw}) 83 | 84 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 85 | ;; Public Api 86 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 87 | 88 | (defn generate 89 | [{:keys [key alg enc] :as options}] 90 | (case alg 91 | :dir (codecs/to-bytes key) 92 | (case enc 93 | :a128cbc-hs256 (nonce/random-bytes 32) 94 | :a192cbc-hs384 (nonce/random-bytes 48) 95 | :a256cbc-hs512 (nonce/random-bytes 64) 96 | :a128gcm (nonce/random-bytes 16) 97 | :a192gcm (nonce/random-bytes 24) 98 | :a256gcm (nonce/random-bytes 32)))) 99 | 100 | (defn encrypt 101 | [{:keys [key alg enc cek] :as options}] 102 | {:pre [(validate-keylength-for-algorithm key alg)]} 103 | (cond 104 | (= alg :dir) 105 | (byte-array 0) 106 | 107 | (= alg :rsa-oaep) 108 | (encrypt-with-rsaaoep cek key) 109 | 110 | (= alg :rsa-oaep-256) 111 | (encrypt-with-rsaaoep-sha256 cek key) 112 | 113 | (= alg :rsa1_5) 114 | (encrypt-with-rsa-pkcs15 cek key) 115 | 116 | (aeskw? alg) 117 | (let [secret (codecs/to-bytes key)] 118 | (keys/wrap cek secret :aes)))) 119 | 120 | (defn decrypt 121 | [{:keys [key alg enc ecek] :as options}] 122 | (cond 123 | (= alg :dir) 124 | (codecs/to-bytes key) 125 | 126 | (= alg :rsa-oaep) 127 | (decrypt-with-rsaaoep ecek key) 128 | 129 | (= alg :rsa-oaep-256) 130 | (decrypt-with-rsaaoep-sha256 ecek key) 131 | 132 | (= alg :rsa1_5) 133 | (decrypt-with-rsa-pkcs15 ecek key) 134 | 135 | (aeskw? alg) 136 | (let [secret (codecs/to-bytes key)] 137 | (keys/unwrap ecek secret :aes)))) 138 | -------------------------------------------------------------------------------- /src/buddy/sign/jwk.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 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 | (ns buddy.sign.jwk 16 | "A JWK key reading functions" 17 | (:require 18 | [cheshire.core :as json] 19 | [buddy.core.keys :as bk])) 20 | 21 | (defn public-key 22 | "Creates a PublicKey instance from JWK. This function 23 | accepts JSON formatted string or a clojure map." 24 | [input] 25 | (let [input (cond 26 | (string? input) (json/parse-string input true) 27 | (map? input) input 28 | :else (throw (IllegalArgumentException. "expected json string or map")))] 29 | (bk/jwk->public-key input))) 30 | 31 | (defn private-key 32 | "Creates a PublicKey instance from JWK. This function 33 | accepts JSON formatted string or a clojure map." 34 | [input] 35 | (let [input (cond 36 | (string? input) (json/parse-string input true) 37 | (map? input) input 38 | :else (throw (IllegalArgumentException. "expected json string or map")))] 39 | (bk/jwk->private-key input))) 40 | 41 | (defn thumbprint 42 | [input] 43 | (let [input (cond 44 | (string? input) (json/parse-string input true) 45 | (map? input) input 46 | :else (throw (IllegalArgumentException. "expected json string or map")))] 47 | (bk/jwk-thumbprint input))) 48 | -------------------------------------------------------------------------------- /src/buddy/sign/jws.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 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 | ;; Links to rfcs: 16 | ;; - http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32 17 | ;; - http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-40 18 | ;; - https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 19 | 20 | (ns buddy.sign.jws 21 | "Json Web Signature implementation" 22 | (:require 23 | [buddy.core.codecs :as bc] 24 | [buddy.core.dsa :as dsa] 25 | [buddy.core.mac :as mac] 26 | [buddy.sign.util :as util] 27 | [buddy.util.ecdsa :refer [transcode-to-der transcode-to-concat]] 28 | [cheshire.core :as json] 29 | [clojure.string :as str])) 30 | 31 | (def +signers-map+ 32 | "Supported algorithms" 33 | {:hs256 {:signer #(mac/hash %1 {:alg :hmac+sha256 :key %2}) 34 | :verifier #(mac/verify %1 %2 {:alg :hmac+sha256 :key %3})} 35 | :hs384 {:signer #(mac/hash %1 {:alg :hmac+sha384 :key %2}) 36 | :verifier #(mac/verify %1 %2 {:alg :hmac+sha384 :key %3})} 37 | :hs512 {:signer #(mac/hash %1 {:alg :hmac+sha512 :key %2}) 38 | :verifier #(mac/verify %1 %2 {:alg :hmac+sha512 :key %3})} 39 | 40 | ;; NOT RECOMMENDED 41 | :rs256 {:signer #(dsa/sign %1 {:alg :rsassa-pkcs15+sha256 :key %2}) 42 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pkcs15+sha256 :key %3})} 43 | :rs384 {:signer #(dsa/sign %1 {:alg :rsassa-pkcs15+sha384 :key %2}) 44 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pkcs15+sha384 :key %3})} 45 | :rs512 {:signer #(dsa/sign %1 {:alg :rsassa-pkcs15+sha512 :key %2}) 46 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pkcs15+sha512 :key %3})} 47 | 48 | :ps256 {:signer #(dsa/sign %1 {:alg :rsassa-pss+sha256 :key %2}) 49 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pss+sha256 :key %3})} 50 | :ps384 {:signer #(dsa/sign %1 {:alg :rsassa-pss+sha384 :key %2}) 51 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pss+sha384 :key %3})} 52 | :ps512 {:signer #(dsa/sign %1 {:alg :rsassa-pss+sha512 :key %2}) 53 | :verifier #(dsa/verify %1 %2 {:alg :rsassa-pss+sha512 :key %3})} 54 | 55 | ;; ECDSA with signature conversions 56 | :es256 {:signer #(-> (dsa/sign %1 {:alg :ecdsa+sha256 :key %2}) 57 | (transcode-to-concat 64)) 58 | :verifier #(dsa/verify %1 (transcode-to-der %2) {:alg :ecdsa+sha256 :key %3})} 59 | :es384 {:signer #(-> (dsa/sign %1 {:alg :ecdsa+sha384 :key %2}) 60 | (transcode-to-concat 96)) 61 | :verifier #(dsa/verify %1 (transcode-to-der %2) {:alg :ecdsa+sha384 :key %3})} 62 | :es512 {:signer #(-> (dsa/sign %1 {:alg :ecdsa+sha512 :key %2}) 63 | (transcode-to-concat 132)) 64 | :verifier #(dsa/verify %1 (transcode-to-der %2) {:alg :ecdsa+sha512 :key %3})} 65 | 66 | :eddsa {:signer #(dsa/sign %1 {:alg :eddsa :key %2}) 67 | :verifier #(dsa/verify %1 %2 {:alg :eddsa :key %3})}}) 68 | 69 | ;; --- Implementation 70 | 71 | (defn- calculate-signature 72 | "Given the bunch of bytes, a private key and algorithm, 73 | return a calculated signature as byte array" 74 | [{:keys [key alg header payload]}] 75 | (let [signer (get-in +signers-map+ [alg :signer]) 76 | authdata (str header "." payload)] 77 | (-> (signer authdata key) 78 | (bc/bytes->b64 true) 79 | (bc/bytes->str)))) 80 | 81 | (defn- verify-signature 82 | "Given a bunch of bytes, a previously generated 83 | signature, the private key and algorithm, return 84 | signature matches or not" 85 | [{:keys [alg signature key header payload]}] 86 | (try 87 | (let [verifier (get-in +signers-map+ [alg :verifier]) 88 | authdata (str header "." payload) 89 | signature (-> signature 90 | (bc/str->bytes) 91 | (bc/b64->bytes true))] 92 | (verifier authdata signature key)) 93 | 94 | (catch java.lang.IllegalArgumentException e 95 | (throw (ex-info "Message seems corrupt or manipulated" 96 | {:type :validation :cause :signature} 97 | e))) 98 | (catch java.security.SignatureException e 99 | (throw (ex-info "Message seems corrupt or manipulated" 100 | {:type :validation :cause :signature} 101 | e))))) 102 | 103 | ;; --- Public Api 104 | 105 | (defn decode-header 106 | "Given a message, decode the header 107 | 108 | WARNING: This does not perform any signature validation" 109 | [input] 110 | (let [[header] (str/split input #"\." 2)] 111 | (util/parse-jose-header header))) 112 | 113 | (defn sign 114 | "Sign arbitrary length string/byte array using json web 115 | token/signature" 116 | ([payload pkey] (sign payload pkey nil)) 117 | ([payload pkey {:keys [alg header] :or {alg :hs256} :as opts}] 118 | (assert (some? payload) "expected payload to be provided") 119 | (assert (some? pkey) "expected pkey to be provided") 120 | (let [header (into {:alg alg} header) 121 | pkey (util/resolve-key pkey header) 122 | header (-> header 123 | (util/encode-jose-header) 124 | (bc/bytes->str)) 125 | payload (-> (bc/->bytes payload) 126 | (bc/bytes->b64 true) 127 | (bc/bytes->str)) 128 | signature (calculate-signature {:key pkey 129 | :alg alg 130 | :header header 131 | :payload payload})] 132 | (str header "." payload "." signature)))) 133 | 134 | (defn unsign 135 | "Given a signed message, verify it and return the decoded payload" 136 | ([input pkey] (unsign input pkey nil)) 137 | ([input pkey {:keys [alg] :or {alg :hs256}}] 138 | (let [[header payload signature] (some-> input (str/split #"\." 3))] 139 | (when (or (nil? header) (nil? payload) (nil? signature)) 140 | (throw (ex-info "Message seems corrupt or manipulated" 141 | {:type :validation :cause :signature}))) 142 | (let [header-data (util/parse-jose-header header) 143 | params {:key (util/resolve-key pkey header-data) 144 | :signature signature 145 | :alg alg 146 | :header header 147 | :payload payload}] 148 | (when-not (verify-signature params) 149 | (throw (ex-info "Message seems corrupt or manipulated" 150 | {:type :validation :cause :signature}))) 151 | 152 | (-> payload 153 | (bc/str->bytes) 154 | (bc/b64->bytes true)))))) 155 | 156 | (util/defalias encode sign) 157 | (util/defalias decode unsign) 158 | -------------------------------------------------------------------------------- /src/buddy/sign/jwt.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright Andrey Antukh 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 | (ns buddy.sign.jwt 16 | (:require 17 | [buddy.core.codecs :as bc] 18 | [buddy.sign.jwe :as jwe] 19 | [buddy.sign.jws :as jws] 20 | [buddy.sign.util :as util] 21 | [cheshire.core :as json] 22 | [clojure.string :as str])) 23 | 24 | (defn decode-header 25 | "Given a message, decode the header. 26 | WARNING: This does not perform any signature validation" 27 | [input] 28 | (let [[header] (str/split input #"\." 2)] 29 | (util/parse-jose-header (bc/str->bytes header)))) 30 | 31 | (defn- validate-claims 32 | "Checks the issuer in the `:iss` claim against one of the allowed 33 | issuers in the passed `:iss`. Passed `:iss` may be a string or a 34 | vector of strings. If no `:iss` is passed, this check is not 35 | performed. 36 | 37 | Checks one or more audiences in the `:aud` claim against the single 38 | valid audience in the passed `:aud`. If no `:aud` is passed, this 39 | check is not performed. 40 | 41 | Checks the subject in the `:sub` claim. If no `:sub` is passed, 42 | this check is not performed. 43 | 44 | Checks the `:exp` claim is not less than the passed `:now`, with a 45 | leeway of the passed `:leeway`. If no `:exp` claim exists, this 46 | check is not performed. 47 | 48 | Checks the `:nbf` claim is less than the passed `:now`, with a 49 | leeway of the passed `:leeway`. If no `:nbf` claim exists, this 50 | check is not performed. 51 | 52 | Checks the passed `:now` is greater than the `:iat` claim plus the 53 | passed `:max-age`. If no `:iat` claim exists, this check is not 54 | performed. 55 | 56 | A check that fails raises an exception with `:type` of `:validation` 57 | and `:cause` indicating which check failed. 58 | 59 | `:now` is an integer POSIX time and defaults to the current time. 60 | `:leeway` is an integer number of seconds and defaults to zero." 61 | [claims {:keys [max-age iss aud sub now leeway] 62 | :or {now (util/now) leeway 0}}] 63 | (let [now (util/to-timestamp now)] 64 | 65 | ;; Check the `:iss` claim. 66 | (when (and iss (let [iss-claim (:iss claims)] 67 | (if (coll? iss) 68 | (not-any? #{iss-claim} iss) 69 | (not= iss-claim iss)))) 70 | (throw (ex-info (str "Issuer does not match " iss) 71 | {:type :validation :cause :iss}))) 72 | 73 | ;; Check the `:aud` claim. 74 | (when (and aud (let [aud-claim (:aud claims)] 75 | (if (coll? aud-claim) 76 | (not-any? #{aud} aud-claim) 77 | (not= aud aud-claim)))) 78 | (throw (ex-info (str "Audience does not match " aud) 79 | {:type :validation :cause :aud}))) 80 | 81 | ;; Check the `:exp` claim. 82 | (when (and (:exp claims) (<= (:exp claims) (- now leeway))) 83 | (throw (ex-info (format "Token is expired (%s)" (:exp claims)) 84 | {:type :validation :cause :exp}))) 85 | 86 | ;; Check the `:nbf` claim. 87 | (when (and (:nbf claims) (> (:nbf claims) (+ now leeway))) 88 | (throw (ex-info (format "Token is not yet valid (%s)" (:nbf claims)) 89 | {:type :validation :cause :nbf}))) 90 | 91 | ;; Check the `:max-age` option. 92 | (when (and (:iat claims) (number? max-age) (> (- now (:iat claims)) max-age)) 93 | (throw (ex-info (format "Token is older than max-age (%s)" max-age) 94 | {:type :validation :cause :max-age}))) 95 | 96 | ;; Check the `:sub` claim. 97 | (when (and sub (let [sub-claim (:sub claims)] 98 | (if (coll? sub-claim) 99 | (not-any? #{sub} sub-claim) 100 | (not= sub sub-claim)))) 101 | (throw (ex-info (str "The subject does not match " sub) 102 | {:type :validation :cause :sub}))) 103 | claims)) 104 | 105 | (defn- normalize-date-claims 106 | "Normalize date related claims and return transformed object." 107 | [data] 108 | (into {} (map (fn [[key val]] 109 | (if (satisfies? util/ITimestamp val) 110 | [key (util/to-timestamp val)] 111 | [key val])) data))) 112 | 113 | (defn- normalize-nil-claims 114 | "Given a raw headers, try normalize it removing any 115 | key with null values." 116 | [data] 117 | (into {} (remove (comp nil? val) data))) 118 | 119 | (defn- prepare-claims [claims opts] 120 | (let [additionalclaims (-> (select-keys opts [:exp :nbf :iat :iss :aud]) 121 | (normalize-nil-claims) 122 | (normalize-date-claims))] 123 | (-> (normalize-date-claims claims) 124 | (merge additionalclaims)))) 125 | 126 | (defn sign 127 | ([claims pkey] (sign claims pkey {})) 128 | ([claims pkey opts] 129 | {:pre [(map? claims)]} 130 | (let [payload (-> (prepare-claims claims opts) 131 | (json/generate-string))] 132 | (jws/sign payload pkey opts)))) 133 | 134 | (defn unsign 135 | ([message pkey] (unsign message pkey {})) 136 | ([message pkey {:keys [skip-validation] :or {skip-validation false} :as opts}] 137 | (try 138 | (let [claims (-> (jws/unsign message pkey opts) 139 | (bc/bytes->str) 140 | (json/parse-string true))] 141 | (if skip-validation 142 | claims 143 | (validate-claims claims opts))) 144 | (catch com.fasterxml.jackson.core.JsonParseException e 145 | (throw (ex-info "Message seems corrupt or manipulated." 146 | {:type :validation :cause :signature})))))) 147 | 148 | (defn encrypt 149 | ([claims pkey] (encrypt claims pkey nil)) 150 | ([claims pkey opts] 151 | {:pre [(map? claims)]} 152 | (let [payload (-> (prepare-claims claims opts) 153 | (json/generate-string))] 154 | (jwe/encrypt payload pkey opts)))) 155 | 156 | (defn decrypt 157 | ([message pkey] (decrypt message pkey nil)) 158 | ([message pkey {:keys [skip-validation] :or {skip-validation false} :as opts}] 159 | (try 160 | (let [claims (-> (jwe/decrypt message pkey opts) 161 | (bc/bytes->str) 162 | (json/parse-string true))] 163 | (if skip-validation 164 | claims 165 | (validate-claims claims opts))) 166 | (catch com.fasterxml.jackson.core.JsonParseException e 167 | (throw (ex-info "Message seems corrupt or manipulated." 168 | {:type :validation :cause :signature})))))) 169 | -------------------------------------------------------------------------------- /src/buddy/sign/util.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 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 | (ns buddy.sign.util 16 | (:require 17 | [buddy.core.codecs :as bc] 18 | [cheshire.core :as json] 19 | [clojure.string :as str]) 20 | (:import 21 | java.lang.reflect.Method 22 | clojure.lang.Reflector)) 23 | 24 | (defprotocol IKeyProvider 25 | (resolve-key [key header] "Resolve a key")) 26 | 27 | (defprotocol ITimestamp 28 | "Default protocol for convert any type to 29 | unix timestamp." 30 | (to-timestamp [obj] "Covert to timestamp")) 31 | 32 | ;; Default impl for the key provider 33 | 34 | (extend-protocol IKeyProvider 35 | (Class/forName "[B") 36 | (resolve-key [key header] key) 37 | 38 | String 39 | (resolve-key [key header] key) 40 | 41 | clojure.lang.IFn 42 | (resolve-key [key header] (key header)) 43 | 44 | java.security.Key 45 | (resolve-key [key header] key)) 46 | 47 | (extend-protocol ITimestamp 48 | java.util.Date 49 | (to-timestamp [obj] 50 | (-> (.getTime ^java.util.Date obj) 51 | (quot 1000))) 52 | 53 | java.lang.Long 54 | (to-timestamp [obj] obj)) 55 | 56 | ;; apply Joda-Time extensions. DateTime and Instant implement ReadableInstant so this works for both 57 | (when-let [klass (try (Class/forName "org.joda.time.ReadableInstant") 58 | (catch ClassNotFoundException _))] 59 | (let [[^Method method] (Reflector/getMethods klass 0 "getMillis" false)] 60 | (extend klass 61 | ITimestamp 62 | {:to-timestamp (fn [this] 63 | (-> (.invoke method this (make-array Object 0)) 64 | (quot 1000)))}))) 65 | 66 | ;; apply Java 8 extensions 67 | (when-let [klass (try (Class/forName "java.time.Instant") 68 | (catch ClassNotFoundException _))] 69 | (let [[^Method method] (Reflector/getMethods klass 0 "getEpochSecond" false)] 70 | (extend klass 71 | ITimestamp 72 | {:to-timestamp (fn [this] 73 | (.invoke method this (make-array Object 0)))}))) 74 | 75 | (defn now 76 | "Get a current timestamp in seconds." 77 | [] 78 | (quot (System/currentTimeMillis) 1000)) 79 | 80 | (def ^:deprecated timestamp 81 | "Alias to `now`." 82 | now) 83 | 84 | (defmacro defalias 85 | [name orig] 86 | `(do 87 | (alter-meta! 88 | (if (.hasRoot (var ~orig)) 89 | (def ~name (.getRawRoot (var ~orig))) 90 | (def ~name)) 91 | #(conj (dissoc % :macro) 92 | (apply dissoc (meta (var ~orig)) (remove #{:macro} (keys %))))) 93 | (var ~name))) 94 | 95 | (defn parse-jose-header 96 | [^bytes data] 97 | (try 98 | (let [{:keys [alg enc] :as header} (-> data 99 | (bc/b64->bytes true) 100 | (bc/bytes->str) 101 | (json/parse-string true))] 102 | (when-not (map? header) 103 | (throw (ex-info "Message seems corrupt or manipulated" 104 | {:type :validation :cause :header}))) 105 | (cond-> header 106 | (string? alg) (assoc :alg (keyword (str/lower-case alg))) 107 | (string? enc) (assoc :enc (keyword (str/lower-case enc))))) 108 | 109 | (catch java.lang.IllegalArgumentException e 110 | (throw (ex-info "Message seems corrupt or manipulated" 111 | {:type :validation :cause :header}))) 112 | 113 | (catch java.lang.NullPointerException e 114 | (throw (ex-info "Message seems corrupt or manipulated" 115 | {:type :validation :cause :header}))) 116 | 117 | (catch com.fasterxml.jackson.core.JsonParseException e 118 | (throw (ex-info "Message seems corrupt or manipulated" 119 | {:type :validation :cause :header}))))) 120 | 121 | (defn encode-jose-header 122 | [{:keys [alg enc] :as header}] 123 | (let [header (cond-> header 124 | (keyword? alg) 125 | (assoc :alg (case alg 126 | :eddsa "EdDSA" 127 | :dir "dir" 128 | (-> alg name str/upper-case))) 129 | (keyword? enc) 130 | (assoc :enc (str/upper-case (name enc))))] 131 | (-> header 132 | (json/generate-string) 133 | (bc/str->bytes) 134 | (bc/bytes->b64 true)))) 135 | -------------------------------------------------------------------------------- /test/_files/privkey.3des.dsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,A31C28E0698981D8 4 | 5 | BtDyvLjxvkLfHHvtX8Pe7w9fvCZNXnio6A1YR/cSurVU8HclUnSyILB6AMPEyrdf 6 | Cksb39jQHLPFm72wwhmeb1UUuISnoTRZxefUYshzljWUvYp1Gj2jmEO66QLutsgE 7 | Cgq27ZpZTE5oqfYvEjM8kxie/TiT0ZpHWuPXrezRHeDUV+hQAHU2QimyvXVGRLHg 8 | zpPdstwVdaesNCuoFqqkeVeum2O5OPz+0Z+TbMmDfXrCnK3y0nP6hS4pJcyjcH0B 9 | KejAC+r8IxGaMuyKpSG9PyX8AT0apgPvpl3SnalPF2+jUkFHIHQNEGRYdFiLuUTm 10 | idpMuq6rEhswUG0P7TITUizML3U+ObvmgzoZju13OEsLj45UamBd4mwIzwZZZPU6 11 | /t77uajpqPSJmo70+RnBHDArS1XIZtAx6d9BQN49Eu6rmnMgCW49TkIUeGwniRgx 12 | KhbghJVEpvTSSQoWMzaZpaP6BQmJCOd532upCrTHjA8rR5diTzEc7g6SjhJYW4Z5 13 | sHiCdV/++2EsqSyvzuC3yAdrWGZmLzxTAbI/E81U77UuHVAjkaKVwySH68SezRZy 14 | 22GDGdpXhgPQChZoUvtJMhexFNIZRM0tKG+9wySZA4PrY6WRkzxaaV0p68YwWCRY 15 | 4oWN6pwJuC6X7+9rCCCsuZU6VKHbqDE7UtCRZ5BzMca+g9Iv0Nhn3FHk2qzbgE74 16 | w0hzDYV1EuGZjIMYRvrf/djejNHgTem1REPg0WLNrXVmCr5s2LptnnnB3ixiLMRU 17 | vKguW4eI7WC2Ny820sqEuoHhTEoequnBcdUacqLCnJEtEkwZTfdc5ZTZtFHfLP5a 18 | T0qsSxxpQB+3lQlqqHIF7z3p0hdr3vmwpZSUTqojBZrH3Ot6Z+b9ugSwdHvbbRNt 19 | ozNHbffyBtJ63yhAfIjX0rZnOiLFsgDWns74/RrMJERY+w3FzNCDgLsJ7IF6tuQ9 20 | 74DZfuGjioWyWFM8N9vX6YC5HoNAOEqj8FF5wjyaxIejbZ7zj0yY//nE9Y/F2KUO 21 | k0Q+iWHtBHp+IAa+Ecic/UHVAQ4QsmNqBu+ZmVufGSm8uNDx6od9fElU4hth3rLT 22 | u3N2XxajcEfbIbWYZ/l/2Pyny7Zt0kdc5logB79b359MCtCVheNdrBtxRNa78Udn 23 | -----END DSA PRIVATE KEY----- 24 | -------------------------------------------------------------------------------- /test/_files/privkey.3des.rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,BE7DFD7A51E401E4 4 | 5 | huDKvjpMKFsmxp+XyiLUE8H75osPq9azTWZkH/Q/EmtIeKIoJQj1vlxJEDgw9PBu 6 | M2S/Uig9B3S6LuosqX2auZhyjBmMq+O0DcqNrsdZi9E4VkfeRaJyAfqiuLDB8GaZ 7 | i0gv7Z7JK2x34e7PfPXqRWkOTNt0KIcClqFEs37XwzE9jrOctaqP1EEeLdwxOizh 8 | T/5gy81YEfE8HK5Z4kQKX8SaA0MwFZIiFy0F6bPLv3gjX2ld4/L+bZOLLlN43kRO 9 | u01TEy0SFQp2Ga4BQYNuVuccUpfhrL6RaUD5HNyHdpsmmxQ99Ae201SkZOyQkzOz 10 | dLUN5L+HnLszSMoCyYcH/a+5kMTk5PtPNhmY0ZWE2D63e2cRiutCZvrBzIO6AEXK 11 | V5Wb9qM4BYY7YGLHSWk6YhvtI2U2p2k0Vpg9EeDJHsu9xaNJlQUa+KhVzbwtMKi+ 12 | kITGSY2Tvoqfn2j7Ek1MJlSYHICTVyh4peTA2q3Y3swQSWgiZfpXEailxz8c3uRC 13 | CzOLDscLCi4j3Edb3ssaik1DHiwVtSiT8aIMSqvoklp6W733Wzls2HST6aiFkvVP 14 | DOqX00xdeRaANVzL+/isn70p5SOCnwa71n9iNcL9q+3irmkpAZwi3zfNG35+WGBA 15 | 1muFJvYJf3MrlsoEDAVlmHzHv022OI817DgNcuc3oyB3OtKqiG/FJZQtrBs9tGvJ 16 | O/qp7tNeFt3Hyu+8OHNBVqbdsR0G5blWcDyvMkiJjaurcHmKp3jgv5XYHbEwzW2U 17 | hSFR++ZZjGy3xLQzMbEpjPBxPhENtrk2Tc6eiyW5f7imMY8Y/HusfpFgbKDJcRKi 18 | ndjqiUqwDdyrG2tYAjxzjaZ5/TFKgpS7mkuQtInMaCFhym/BbnYcriwKlk3PlUoU 19 | iCbKKBTqCwbhVxOwDQVOw6xC5uOFMtVMlsGGs7sFgkxFLcsZk3NswokjYFXO/rKs 20 | DLHVwCZLfAkgafw0gLV74VTQpgcWoSikZqNnj+SA+mFEw+R4VqdpL6i2M6a/Pewl 21 | k/yymf2hzmhlLcf2XnonsMHxT2mz6R29XytByeJJ5yrP/kVZKCDAu+HzqFVGRd/P 22 | 2nUvUAru0SxsbbAbeHbwL39J8ZbwO5K6i8oDW17y4f/xPShF8KbATgcaM0+KSjQC 23 | jzeeKh2TfwATA8XoQkjpQXz+Ob/vfsdnFUv0mKwZDz9v4Y1gS6CoWsPWLgFAL/g8 24 | qqYaE5mxifOQBJtfCUjsAhDTBw4x7OphWja/ozzXjSr94R7cmIoT8PzuqxKE55+W 25 | Du0zS6GYTl24dvo/eoAVy3CujidUDM7w5FHWWJwXdX+wOmzTzQ17HWi0RU9tkhlh 26 | fAUr9YhD9rQYlanPNxfPMzDvL83wI+NzeGzO5DF/s/KEe/of6gd5X23nAPApcTA7 27 | mvuEpGYABKvhXqw5X9YeHQ2cu4Ef2Qtw666/amDu+7J0DWv28/f5bgQ0kb1IdJR8 28 | 44nHu6pGZLe7O3SRDT1PLg/nkddogIXIMeX0PoGjl9yFzn2akKhcccOZmmnVbE3B 29 | 5YpbYX4UhgdK5K17KaX3g2P80Z9i/tGw/+klqVY64Bxhs8YOibWt7vlqLpUAYElI 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /test/_files/privkey.ecdsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHQCAQEEIOWRHY5IzngV6VPtZdaTppvr6olKi4QYKsGNnR0Am7OFoAcGBSuBBAAK 3 | oUQDQgAEl22Tjui9EvTe/bmRuEzdLPDYKbCkLhd4KjcE01C4NvLTArVjT8EiLq9d 4 | ztE6wPRHbvhBaHPETf3ieVcLeSQqeA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /test/_files/pubkey.3des.dsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIDSDCCAjoGByqGSM44BAEwggItAoIBAQDKfZUHqyXbL097Okme9U0nZTzBXrtf 3 | /+J5IzMkGVggfu1k6e5VvwFBSUc+hLFZ99TBuKOxKanu4yX/rYphYXunJPynO5Ht 4 | 8KDxmdmdjFrPR6yxTyRygBB5AHnUXp3e3PDjDC2jc2TCHgbP44ifpG3HcwHgzwKn 5 | dvRlIunTWq3QVweLm4RCcrbIVdNjq9alj7qlv3dgahwLEVqoTSWQQsxSlk7xW+QD 6 | goPWq609XoqPcazDHtlk5f5sRjsx/+9seGEXP1OJAqxzl/+KD6CpyPv5ZxO8P62Z 7 | xzyn+bgLjvAwQBbYsnu4knrPR27HPrLeM7BREt7BIqcEhVy2Bo6814trAiEA6RE4 8 | VS5F2Nf6Hfp6yZZLjIOui7tRMD1I1iVanDC3Z58CggEBALwT+UbaVXNk7NllIjEF 9 | +aK+7+hFHylz2sVq+n22pA8Wpaqjmk3Rg3crMIdWIFBHdRWJNTz7KTfUU6JQ/kmh 10 | qj6zWy4aXznxOD9H1EC5tzq1flhuUTNOGauOod+ppHozkoa5o8JxWw7rXj2Wqq9V 11 | K2tTDITncddQaWj7zdOY34Wb51pm+Adxt7vitORXRHWEpUSTNhy81JJBOkPCBm45 12 | xXr9QB0AMTGHIF2uycBi7FGPrNTCdxz/ve/2lZ21aSC/oEabnmEX6qF0JDuicHd4 13 | fnhvH4aH/CpDyUMpxDK69weiUpeAcZJQ8QjOa3/OPwZDjxlntOSNhmUVPS5gp49x 14 | VS8DggEGAAKCAQEAiU0vd1RovhsryNyPZvNSfTj0C7xng8s7UuAlEnmEmwTE98sC 15 | EBO8TeAvnqYPcNjTDZWnLtJ4vevISamrFH4yZOMfLYfcmKVipQpxfJm7ocJ+iVFR 16 | FTeueIOO5KvPsSk9z0DJyCZL++Pb4DBip+Gdf71BLCHX4aQPTvV8xkTopxQA5lsE 17 | oQueBHBJQ0nFz9e8oCiXxUdT3/5tKW9Y35lolN8eAU4M19U6x3GOPobZgVcXF6z+ 18 | r7LhaJXLrjF4LlNmXItuvdGcp/BBo7oZ0IfqpLDkRAuFlwjC6CKHZVW5Nob1CNK+ 19 | U+vPkJpMPsSr0UhaySCFn+4KBrGe5v9Dd8g9lA== 20 | -----END PUBLIC KEY----- 21 | -------------------------------------------------------------------------------- /test/_files/pubkey.3des.rsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuyriayUirpQv+4MhsKMr 3 | /1DHwz2XSkXk8GgeJBSHW1RpjWTQOSmQWYj9D+k5KRqwvs7I5OQYLLve+eRZNvwR 4 | 2OlPPLGs+qEukY1eH5FFav8LNgcxbo0pAX7IgYqQC3fH2RKarppLGBc7Jm/O0S4D 5 | 1ENtLansjz1qwrMBvmWW51YTYw5h59RHiR/85jlMCC1kkHuE/00EtfXzlcXYLMAc 6 | f7ep7Wjozl9q2E7huHNTWNLtuQmVpDEr1i6LhVKLJRxofSMZn+SRcvn/pNxQrOQ5 7 | ivfCjWz87EOh4va4N1yWVzXwAbAy9ob75WUu7OAHTiRhHNjZiRaZgrmc2XKwyOpW 8 | mwIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /test/_files/pubkey.ecdsa.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEl22Tjui9EvTe/bmRuEzdLPDYKbCkLhd4 3 | KjcE01C4NvLTArVjT8EiLq9dztE6wPRHbvhBaHPETf3ieVcLeSQqeA== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/buddy/sign/compact_tests.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2014-2015 Andrey Antukh 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 | (ns buddy.sign.compact-tests 16 | (:require [clojure.test :refer :all] 17 | [clojure.test.check.clojure-test :refer (defspec)] 18 | [clojure.test.check.generators :as gen] 19 | [clojure.test.check.properties :as props] 20 | [buddy.core.codecs :as codecs] 21 | [buddy.core.crypto :as crypto] 22 | [buddy.core.hash :as hash] 23 | [buddy.core.keys :as keys] 24 | [buddy.core.bytes :as bytes] 25 | [buddy.core.nonce :as nonce] 26 | [buddy.sign.compact :as compact] 27 | [buddy.sign.util :as util])) 28 | 29 | (def secret (hash/sha256 "secret")) 30 | (def rsa-privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret")) 31 | (def rsa-pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem")) 32 | (def ec-privkey (keys/private-key "test/_files/privkey.ecdsa.pem" "secret")) 33 | (def ec-pubkey (keys/public-key "test/_files/pubkey.ecdsa.pem")) 34 | 35 | (def not-nan? (complement #(Double/isNaN %))) 36 | 37 | (def map-gen 38 | (gen/map gen/keyword 39 | (gen/one-of [gen/string-alphanumeric 40 | gen/symbol 41 | gen/keyword 42 | (gen/such-that not-nan? gen/double) 43 | gen/small-integer]))) 44 | 45 | 46 | (defspec compact-spec-alg-hs 50 47 | (props/for-all 48 | [key (gen/one-of [gen/bytes gen/string]) 49 | alg (gen/elements [:hs512 :hs256]) 50 | data map-gen] 51 | (let [res1 (compact/sign data key {:alg alg}) 52 | res2 (compact/unsign res1 key {:alg alg})] 53 | (is (= res2 data))))) 54 | 55 | (defspec compact-spec-alg-poly 50 56 | (props/for-all 57 | [alg (gen/elements [:poly1305-aes :poly1305-serpent :poly1305-twofish]) 58 | data map-gen] 59 | (let [res1 (compact/sign data secret {:alg alg}) 60 | res2 (compact/unsign res1 secret {:alg alg})] 61 | (is (= res2 data))))) 62 | 63 | (defspec compact-spec-alg-rsa 50 64 | (props/for-all 65 | [alg (gen/elements [:rs256 :rs512 :ps512 :ps256]) 66 | data map-gen] 67 | (let [res1 (compact/sign data rsa-privkey {:alg alg}) 68 | res2 (compact/unsign res1 rsa-pubkey {:alg alg})] 69 | (is (= res2 data))))) 70 | 71 | (defspec compact-spec-alg-ec 50 72 | (props/for-all 73 | [alg (gen/elements [:es512 :es256]) 74 | data map-gen] 75 | (let [res1 (compact/sign data ec-privkey {:alg alg}) 76 | res2 (compact/unsign res1 ec-pubkey {:alg alg})] 77 | (is (= res2 data))))) 78 | 79 | (deftest compact-test-validation 80 | (let [candidate {:foo "bar"} 81 | signed (compact/sign candidate secret) 82 | unsigned1 (compact/decode signed secret {:max-age 1})] 83 | (Thread/sleep 2000) 84 | (is (= unsigned1 candidate)) 85 | (try 86 | (compact/decode signed secret {:max-age 1}) 87 | (catch clojure.lang.ExceptionInfo e 88 | (let [data (ex-data e)] 89 | (is (= (:cause data) :max-age))))))) 90 | -------------------------------------------------------------------------------- /test/buddy/sign/interop_tests.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2014-2015 Andrey Antukh 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 | (ns buddy.sign.interop-tests 16 | (:require [clojure.test :refer :all] 17 | [clojure.string :as str] 18 | [buddy.core.codecs :as codecs] 19 | [buddy.core.crypto :as crypto] 20 | [buddy.core.bytes :as bytes] 21 | [buddy.core.nonce :as nonce] 22 | [buddy.core.keys :as keys] 23 | [buddy.sign.jwe :as jwe] 24 | [buddy.sign.jws :as jws] 25 | [buddy.sign.jwt :as jwt] 26 | [buddy.sign.util :as util] 27 | [cheshire.core :as json]) 28 | (:import com.nimbusds.jose.JWEHeader 29 | com.nimbusds.jose.JWSHeader 30 | com.nimbusds.jose.JWEAlgorithm 31 | com.nimbusds.jose.JWSAlgorithm 32 | com.nimbusds.jose.EncryptionMethod 33 | com.nimbusds.jose.Payload 34 | com.nimbusds.jose.JWEObject 35 | com.nimbusds.jwt.EncryptedJWT 36 | com.nimbusds.jwt.SignedJWT 37 | com.nimbusds.jwt.JWTClaimsSet 38 | com.nimbusds.jwt.JWTClaimsSet$Builder 39 | com.nimbusds.jose.crypto.MACSigner 40 | com.nimbusds.jose.crypto.MACVerifier 41 | com.nimbusds.jose.crypto.DirectEncrypter 42 | com.nimbusds.jose.crypto.DirectDecrypter 43 | com.nimbusds.jose.crypto.ECDSAVerifier 44 | (javax.crypto KeyGenerator) 45 | (java.security SecureRandom KeyPairGenerator) 46 | (java.security.spec ECGenParameterSpec))) 47 | 48 | (def secret (codecs/hex->bytes (str "000102030405060708090a0b0c0d0e0f" 49 | "101112131415161718191a1b1c1d1e1f"))) 50 | 51 | (def data {:userid 1 :scope "auth"}) 52 | (def key16 (nonce/random-bytes 16)) 53 | (def key24 (nonce/random-bytes 24)) 54 | (def key32 (nonce/random-bytes 32)) 55 | (def key32' (nonce/random-bytes 32)) 56 | (def key48 (nonce/random-bytes 48)) 57 | (def key64 (nonce/random-bytes 64)) 58 | (def rsa-privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret")) 59 | (def rsa-pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem")) 60 | (def ec-privkey (keys/private-key "test/_files/privkey.ecdsa.pem" "secret")) 61 | (def ec-pubkey (keys/public-key "test/_files/pubkey.ecdsa.pem")) 62 | 63 | (deftest interoperability-test-1 64 | (let [header (JWEHeader. JWEAlgorithm/DIR EncryptionMethod/A128GCM) 65 | claimsbuilder (doto (JWTClaimsSet$Builder.) 66 | (.claim "test1" "test")) 67 | claims (.build claimsbuilder) 68 | 69 | jwt (doto (EncryptedJWT. header claims) 70 | (.encrypt (DirectEncrypter. key16))) 71 | 72 | result (.serialize jwt)] 73 | (let [data (jwt/decrypt result key16 {:alg :dir :enc :a128gcm})] 74 | (is (= data {:test1 "test"}))))) 75 | 76 | (deftest interoperability-test-2 77 | (let [token (jwt/encrypt {:test1 "test"} key16 {:alg :dir :enc :a128gcm}) 78 | jwt (doto (EncryptedJWT/parse token) 79 | (.decrypt (DirectDecrypter. key16)))] 80 | (is (= "test" (.. jwt getJWTClaimsSet (getClaim "test1")))))) 81 | 82 | (deftest interoperability-test-3 83 | (let [header (JWEHeader. JWEAlgorithm/DIR EncryptionMethod/A128CBC_HS256) 84 | claimsbuilder (doto (JWTClaimsSet$Builder.) 85 | (.claim "test1" "test")) 86 | claims (.build claimsbuilder) 87 | 88 | jwt (doto (EncryptedJWT. header claims) 89 | (.encrypt (DirectEncrypter. key32))) 90 | 91 | result (.serialize jwt)] 92 | (let [data (jwt/decrypt result key32 {:alg :dir :enc :a128cbc-hs256})] 93 | (is (= data {:test1 "test"}))))) 94 | 95 | (deftest interoperability-test-4 96 | (let [token (jwt/encrypt {:test1 "test"} key32 {:alg :dir :enc :a128cbc-hs256}) 97 | jwt (doto (EncryptedJWT/parse token) 98 | (.decrypt (DirectDecrypter. key32)))] 99 | (is (= "test" (.. jwt getJWTClaimsSet (getClaim "test1")))))) 100 | 101 | (deftest interoperability-test-5 102 | (let [header (JWSHeader. JWSAlgorithm/HS256) 103 | claimsbuilder (doto (JWTClaimsSet$Builder.) 104 | (.claim "test1" "test")) 105 | claims (.build claimsbuilder) 106 | 107 | jwt (doto (SignedJWT. header claims) 108 | (.sign (MACSigner. key32))) 109 | 110 | result (.serialize jwt)] 111 | (let [data (-> (jws/unsign result key32 {:alg :hs256}) 112 | (codecs/bytes->str) 113 | (json/parse-string true))] 114 | (is (= data {:test1 "test"}))))) 115 | 116 | (deftest interoperability-test-6 117 | (let [token (jws/sign (json/generate-string {:test1 "test"}) key32 {:alg :hs256}) 118 | jwt (SignedJWT/parse token)] 119 | (is (.verify jwt (MACVerifier. key32))) 120 | (is (= "test" (.. jwt getJWTClaimsSet (getClaim "test1")))))) 121 | 122 | (defn generate-ecdsa-pair [curvename] 123 | (let [kg (KeyPairGenerator/getInstance "EC" "BC") 124 | _ (.initialize kg (ECGenParameterSpec. curvename) (SecureRandom/getInstance "SHA1PRNG")) 125 | pair (.generateKeyPair kg) 126 | public (.getPublic pair) 127 | private (.getPrivate pair)] 128 | [public private])) 129 | 130 | (deftest interoperability-test-es256 131 | (let [[public private] (generate-ecdsa-pair "P-256") 132 | token (jws/sign (json/generate-string {:test1 "test"}) private {:alg :es256}) 133 | jwt (SignedJWT/parse token)] 134 | (is (.verify jwt (ECDSAVerifier. public))) 135 | (is (= "test" (.. jwt getJWTClaimsSet (getClaim "test1")))))) 136 | 137 | (deftest interoperability-test-es384 138 | (let [[public private] (generate-ecdsa-pair "P-384") 139 | token (jws/sign (json/generate-string {:test1 "test"}) private {:alg :es384}) 140 | jwt (SignedJWT/parse token)] 141 | (is (.verify jwt (ECDSAVerifier. public))) 142 | (is (= "test" (.. jwt getJWTClaimsSet (getClaim "test1")))))) 143 | 144 | (deftest interoperability-test-es512 145 | (let [[public private] (generate-ecdsa-pair "P-521") 146 | token (jws/sign (json/generate-string {:test1 "test"}) private {:alg :es512}) 147 | jwt (SignedJWT/parse token)] 148 | (is (.verify jwt (ECDSAVerifier. public))) 149 | (is (= "test" (.. jwt getJWTClaimsSet (getClaim "test1")))))) 150 | -------------------------------------------------------------------------------- /test/buddy/sign/jwe_tests.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2014-2016 Andrey Antukh 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 | (ns buddy.sign.jwe-tests 16 | (:require [clojure.test :refer :all] 17 | [clojure.test.check.clojure-test :refer (defspec)] 18 | [clojure.test.check.generators :as gen] 19 | [clojure.test.check.properties :as props] 20 | [clojure.string :as str] 21 | [buddy.core.codecs :as codecs] 22 | [buddy.core.crypto :as crypto] 23 | [buddy.core.bytes :as bytes] 24 | [buddy.core.nonce :as nonce] 25 | [buddy.core.keys :as keys] 26 | [buddy.sign.jwe :as jwe] 27 | [buddy.sign.util :as util])) 28 | 29 | (def secret (codecs/hex->bytes (str "000102030405060708090a0b0c0d0e0f" 30 | "101112131415161718191a1b1c1d1e1f"))) 31 | 32 | (def data (codecs/to-bytes "test-data")) 33 | (def key16 (nonce/random-bytes 16)) 34 | (def key24 (nonce/random-bytes 24)) 35 | (def key32 (nonce/random-bytes 32)) 36 | (def key32' (nonce/random-bytes 32)) 37 | (def key48 (nonce/random-bytes 48)) 38 | (def key64 (nonce/random-bytes 64)) 39 | (def rsa-privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret")) 40 | (def rsa-pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem")) 41 | (def ec-privkey (keys/private-key "test/_files/privkey.ecdsa.pem" "secret")) 42 | (def ec-pubkey (keys/public-key "test/_files/pubkey.ecdsa.pem")) 43 | 44 | (def rsa-algs 45 | [:rsa-oaep :rsa-oaep-256 :rsa1_5]) 46 | 47 | (def encs 48 | [:a128gcm :a192gcm :a256gcm :a128cbc-hs256 49 | :a192cbc-hs384 :a256cbc-hs512]) 50 | 51 | ;; --- Tests 52 | 53 | (deftest jwe-decode-header 54 | (let [candidate "foo bar" 55 | encrypted (jwe/encrypt candidate secret) 56 | header (jwe/decode-header encrypted)] 57 | (is (= {:alg :dir, :enc :a128cbc-hs256} header)))) 58 | 59 | (deftest jwe-wrong-date-specific-test 60 | (let [token (str "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.." 61 | "zkV7_0---NDlvQYfpNDfqw.hECYr8zURDvz9hdjz6s-O0HNF2" 62 | "MhgHgXjnQN6KuUcgE.eXYr6ybqAYcQkkkuGNcNKA")] 63 | (try 64 | (jwe/decrypt token key32 {:enc :a128cbc-hs256}) 65 | (throw (Exception. "unexpected")) 66 | (catch clojure.lang.ExceptionInfo e 67 | (let [cause (:cause (ex-data e))] 68 | (is (= cause :authtag))))))) 69 | 70 | (deftest wrong-key-for-enc 71 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a256gcm}))) 72 | (is (thrown? AssertionError (jwe/encrypt data key48 {:enc :a256gcm}))) 73 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a192gcm}))) 74 | (is (thrown? AssertionError (jwe/encrypt data key32 {:enc :a192gcm}))) 75 | (is (thrown? AssertionError (jwe/encrypt data key32 {:enc :a128gcm}))) 76 | (is (thrown? AssertionError (jwe/encrypt data key48 {:enc :a128gcm}))) 77 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a256cbc-hs512}))) 78 | (is (thrown? AssertionError (jwe/encrypt data key32 {:enc :a256cbc-hs512}))) 79 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a192cbc-hs384}))) 80 | (is (thrown? AssertionError (jwe/encrypt data key32 {:enc :a192cbc-hs384}))) 81 | (is (thrown? AssertionError (jwe/encrypt data key64 {:enc :a192cbc-hs384}))) 82 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a128cbc-hs256}))) 83 | (is (thrown? AssertionError (jwe/encrypt data key48 {:enc :a128cbc-hs256}))) 84 | (is (thrown? AssertionError (jwe/encrypt data key32 {:enc :a128gcm :alg :a128kw}))) 85 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a128gcm :alg :a192kw}))) 86 | (is (thrown? AssertionError (jwe/encrypt data key16 {:enc :a128gcm :alg :a256kw}))) 87 | ) 88 | 89 | (defspec jwe-spec-alg-dir-enc-a256gcm 1000 90 | (props/for-all 91 | [zip gen/boolean 92 | data gen/bytes] 93 | (let [res1 (jwe/encrypt data key32 {:enc :a256gcm :alg :dir :zip zip}) 94 | res2 (jwe/decrypt res1 key32 {:enc :a256gcm :alg :dir :zip zip})] 95 | (is (bytes/equals? res2 data))))) 96 | 97 | (defspec jwe-spec-alg-dir-enc-a192gcm 1000 98 | (props/for-all 99 | [zip gen/boolean 100 | data gen/bytes] 101 | (let [res1 (jwe/encrypt data key24 {:enc :a192gcm :alg :dir :zip zip}) 102 | res2 (jwe/decrypt res1 key24 {:enc :a192gcm :alg :dir :zip zip})] 103 | (is (bytes/equals? res2 data))))) 104 | 105 | (defspec jwe-spec-alg-dir-enc-a128gcm 1000 106 | (props/for-all 107 | [zip gen/boolean 108 | data gen/bytes] 109 | (let [res1 (jwe/encrypt data key16 {:enc :a128gcm :alg :dir :zip zip}) 110 | res2 (jwe/decrypt res1 key16 {:enc :a128gcm :alg :dir :zip zip})] 111 | (is (bytes/equals? res2 data))))) 112 | 113 | (defspec jwe-spec-alg-dir-enc-a256cbc-hs512 1000 114 | (props/for-all 115 | [zip gen/boolean 116 | data gen/bytes] 117 | (let [res1 (jwe/encrypt data key64 {:enc :a256cbc-hs512 :zip zip}) 118 | res2 (jwe/decrypt res1 key64 {:enc :a256cbc-hs512 :zip zip})] 119 | (is (bytes/equals? res2 data))))) 120 | 121 | (defspec jwe-spec-alg-dir-enc-a192cbc-hs384 1000 122 | (props/for-all 123 | [zip gen/boolean 124 | data gen/bytes] 125 | (let [res1 (jwe/encrypt data key48 {:enc :a192cbc-hs384 :zip zip}) 126 | res2 (jwe/decrypt res1 key48 {:enc :a192cbc-hs384 :zip zip})] 127 | (is (bytes/equals? res2 data))))) 128 | 129 | (defspec jwe-spec-alg-dir-enc-a128cbc-hs256 1000 130 | (props/for-all 131 | [zip gen/boolean 132 | data gen/bytes] 133 | (let [res1 (jwe/encrypt data key32 {:enc :a128cbc-hs256 :zip zip}) 134 | res2 (jwe/decrypt res1 key32 {:enc :a128cbc-hs256 :zip zip})] 135 | (is (bytes/equals? res2 data))))) 136 | 137 | (defspec jwe-spec-wrong-data 1000 138 | (props/for-all 139 | [data gen/string-ascii] 140 | (try 141 | (jwe/decrypt data secret) 142 | (throw (Exception. "unexpected")) 143 | (catch clojure.lang.ExceptionInfo e 144 | (let [cause (:cause (ex-data e))] 145 | (is (or (= cause :signature) 146 | (= cause :token) 147 | (= cause :authtag) 148 | (= cause :header)))))))) 149 | 150 | (defspec jwe-spec-wrong-token 1000 151 | (props/for-all 152 | [data1 gen/string-alphanumeric 153 | data2 gen/string-alphanumeric 154 | data3 gen/string-alphanumeric 155 | data4 gen/string-alphanumeric 156 | data5 gen/string-alphanumeric] 157 | (let [data (str data1 "." data2 "." data3 "." data4 "." data5)] 158 | (try 159 | (jwe/decrypt data secret) 160 | (throw (Exception. "unexpected")) 161 | (catch clojure.lang.ExceptionInfo e 162 | (let [cause (:cause (ex-data e))] 163 | (is (or (= cause :signature) 164 | (= cause :token) 165 | (= cause :authtag) 166 | (= cause :header))))))))) 167 | 168 | (defspec jwe-spec-alg-rsa 500 169 | (props/for-all 170 | [enc (gen/elements encs) 171 | alg (gen/elements rsa-algs) 172 | zip gen/boolean 173 | data gen/bytes] 174 | (let [res1 (jwe/encrypt data rsa-pubkey {:enc enc :alg alg :zip zip}) 175 | res2 (jwe/decrypt res1 rsa-privkey {:enc enc :alg alg :zip zip})] 176 | (is (bytes/equals? res2 data))))) 177 | 178 | (defspec jwe-spec-alg-a128kw 1000 179 | (props/for-all 180 | [enc (gen/elements encs) 181 | zip gen/boolean 182 | data gen/bytes] 183 | (let [res1 (jwe/encrypt data key16 {:enc enc :alg :a128kw :zip zip}) 184 | res2 (jwe/decrypt res1 key16 {:enc enc :alg :a128kw :zip zip})] 185 | (is (bytes/equals? res2 data))))) 186 | 187 | (defspec jwe-spec-alg-a192kw 1000 188 | (props/for-all 189 | [enc (gen/elements encs) 190 | zip gen/boolean 191 | data gen/bytes] 192 | (let [res1 (jwe/encrypt data key24 {:enc enc :alg :a192kw :zip zip}) 193 | res2 (jwe/decrypt res1 key24 {:enc enc :alg :a192kw :zip zip})] 194 | (is (bytes/equals? res2 data))))) 195 | 196 | (defspec jwe-spec-alg-a256kw 1000 197 | (props/for-all 198 | [enc (gen/elements encs) 199 | zip gen/boolean 200 | data gen/bytes] 201 | (let [res1 (jwe/encrypt data key32 {:enc enc :alg :a256kw :zip zip}) 202 | res2 (jwe/decrypt res1 key32 {:enc enc :alg :a256kw :zip zip})] 203 | (is (bytes/equals? res2 data))))) 204 | -------------------------------------------------------------------------------- /test/buddy/sign/jwk_tests.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2014-2016 Andrey Antukh 2 | ;; Copyright (c) 2017 Denis Shilov 3 | ;; 4 | ;; Licensed under the Apache License, Version 2.0 (the "License") 5 | ;; you may not use this file except in compliance with the License. 6 | ;; You may obtain a copy of the License at 7 | ;; 8 | ;; http://www.apache.org/licenses/LICENSE-2.0 9 | ;; 10 | ;; Unless required by applicable law or agreed to in writing, software 11 | ;; distributed under the License is distributed on an "AS IS" BASIS, 12 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ;; See the License for the specific language governing permissions and 14 | ;; limitations under the License. 15 | 16 | (ns buddy.sign.jwk-tests 17 | (:require [clojure.test :refer :all] 18 | [buddy.core.codecs.base64 :as b64] 19 | [buddy.core.codecs :as codecs] 20 | [buddy.core.keys :as keys] 21 | [buddy.sign.jws :as jws])) 22 | 23 | (defn- load-pair [jwk] 24 | [(keys/jwk->public-key jwk) 25 | (keys/jwk->private-key jwk)]) 26 | 27 | (def ed25519-jwk-key 28 | {:kty "OKP" 29 | :crv "Ed25519" 30 | :d "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", 31 | :x "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}) 32 | 33 | (deftest ed25519-jws-sign-unsign 34 | (let [[public private] (load-pair ed25519-jwk-key) 35 | ;; Example from RFC 36 | payload "Example of Ed25519 signing" 37 | token (jws/sign payload private {:alg :eddsa :key private})] 38 | 39 | (is (= "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg" 40 | token)) 41 | (is (= "Example of Ed25519 signing" 42 | (codecs/bytes->str (jws/unsign token public {:alg :eddsa})))))) 43 | 44 | (def rsa2048-jwk-key 45 | {:kty "RSA", 46 | :n "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ" 47 | :e "AQAB" 48 | :d "Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97IjlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYTCBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLhBOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ"}) 49 | 50 | 51 | (deftest rsa-jws-sign-unsign 52 | (let [[public private] (load-pair rsa2048-jwk-key) 53 | ;; Example from RFC 54 | payload "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}" 55 | token (jws/sign payload private {:alg :rs256 56 | :key private})] 57 | 58 | (is (= "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" 59 | token)) 60 | (is (= payload 61 | (codecs/bytes->str (jws/unsign token public {:alg :rs256})))))) 62 | 63 | (def ec256-jwk-key 64 | {:kty "EC", 65 | :crv "P-256", 66 | :x "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", 67 | :y "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", 68 | :d "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"}) 69 | 70 | (deftest ec256-jws-sign-unsign 71 | (let [[public private] (load-pair ec256-jwk-key) 72 | payload "{\"iss\":\"joe\",\r\n \"exp\":1300819380,\r\n \"http://example.com/is_root\":true}" 73 | token (jws/sign payload private {:alg :es256 74 | :key private}) 75 | rfctoken "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"] 76 | 77 | ;; unsign using our token 78 | (is (= payload 79 | (codecs/bytes->str (jws/unsign token public {:alg :es256})))) 80 | 81 | ;; unsign using RFC reference token 82 | (is (= payload 83 | (codecs/bytes->str (jws/unsign rfctoken public {:alg :es256})))))) 84 | 85 | 86 | (def ec521-jwk-key 87 | {:kty "EC", 88 | :crv "P-521", 89 | :x "AekpBQ8ST8a8VcfVOTNl353vSrDCLLJXmPk06wTjxrrjcBpXp5EOnYG_NjFZ6OvLFV1jSfS9tsz4qUxcWceqwQGk", 90 | :y "ADSmRA43Z1DSNx_RvcLI87cdL07l6jQyyBXMoxVg_l2Th-x3S1WDhjDly79ajL4Kkd0AZMaZmh9ubmf63e3kyMj2", 91 | :d "AY5pb7A0UFiB3RELSD64fTLOSV_jazdF7fLYyuTw8lOfRhWg6Y6rUrPAxerEzgdRhajnu0ferB0d53vM9mE15j2C"}) 92 | 93 | (deftest ec521-jws-sign-unsign 94 | (let [[public private] (load-pair ec521-jwk-key) 95 | payload "Payload" 96 | token (jws/sign payload private {:alg :es512 97 | :key private}) 98 | rfctoken "eyJhbGciOiJFUzUxMiJ9.UGF5bG9hZA.AdwMgeerwtHoh-l192l60hp9wAHZFVJbLfD_UxMi70cwnZOYaRI1bKPWROc-mZZqwqT2SI-KGDKB34XO0aw_7XdtAG8GaSwFKdCAPZgoXD2YBJZCPEX3xKpRwcdOO8KpEHwJjyqOgzDO7iKvU8vcnwNrmxYbSW9ERBXukOXolLzeO_Jn"] 99 | 100 | ;; unsign using our token 101 | (is (= payload 102 | (codecs/bytes->str (jws/unsign token public {:alg :es512})))) 103 | ;; unsign using RFC reference token 104 | (is (= payload 105 | (codecs/bytes->str (jws/unsign rfctoken public {:alg :es512})))))) 106 | -------------------------------------------------------------------------------- /test/buddy/sign/jws_tests.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2014-2016 Andrey Antukh 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 | (ns buddy.sign.jws-tests 16 | (:require [clojure.test :refer :all] 17 | [clojure.test.check.clojure-test :refer (defspec)] 18 | [clojure.test.check.generators :as gen] 19 | [clojure.test.check.properties :as props] 20 | [buddy.core.codecs :as codecs] 21 | [buddy.core.crypto :as crypto] 22 | [buddy.core.keys :as keys] 23 | [buddy.core.bytes :as bytes] 24 | [buddy.core.nonce :as nonce] 25 | [buddy.sign.jws :as jws] 26 | [buddy.sign.util :as util])) 27 | 28 | (def secret "test") 29 | (def rsa-privkey (keys/private-key "test/_files/privkey.3des.rsa.pem" "secret")) 30 | (def rsa-pubkey (keys/public-key "test/_files/pubkey.3des.rsa.pem")) 31 | (def ec-privkey (keys/private-key "test/_files/privkey.ecdsa.pem" "secret")) 32 | (def ec-pubkey (keys/public-key "test/_files/pubkey.ecdsa.pem")) 33 | 34 | (defn- unsign-exp-succ 35 | ([signed candidate] 36 | (unsign-exp-succ signed candidate nil)) 37 | ([signed candidate opts] 38 | (is (bytes/equals? (jws/unsign signed secret opts) 39 | (codecs/to-bytes candidate))))) 40 | 41 | (defn- unsign-exp-fail 42 | ([signed cause] 43 | (unsign-exp-fail signed cause nil)) 44 | ([signed cause opts] 45 | (try 46 | (jws/unsign signed secret opts) 47 | (throw (Exception. "unexpected")) 48 | (catch clojure.lang.ExceptionInfo e 49 | (is (= cause (:cause (ex-data e)))))))) 50 | 51 | (deftest jws-wrong-key 52 | (let [candidate "foo bar " 53 | result (jws/sign candidate ec-privkey {:alg :es512})] 54 | (unsign-exp-fail result :signature))) 55 | 56 | (defspec jws-spec-alg-hs 500 57 | (props/for-all 58 | [key (gen/one-of [gen/bytes gen/string]) 59 | data (gen/one-of [gen/bytes gen/string]) 60 | alg (gen/elements [:hs512 :hs384 :hs256])] 61 | (let [res1 (jws/sign data key {:alg alg}) 62 | res2 (jws/unsign res1 key {:alg alg})] 63 | (is (bytes/equals? res2 (codecs/to-bytes data)))))) 64 | 65 | (defspec jws-spec-alg-ps-and-rs 500 66 | (props/for-all 67 | [data (gen/one-of [gen/bytes gen/string]) 68 | alg (gen/elements [:ps512 :ps384 :ps256 :rs512 :rs384 :rs256])] 69 | (let [res1 (jws/sign data rsa-privkey {:alg alg}) 70 | res2 (jws/unsign res1 rsa-pubkey {:alg alg})] 71 | (is (bytes/equals? res2 (codecs/to-bytes data)))))) 72 | 73 | (defspec jws-spec-custom-headers 500 74 | (props/for-all 75 | [data (gen/one-of [gen/bytes gen/string]) 76 | nonce (gen/one-of [gen/string]) 77 | alg (gen/elements [:ps512 :ps384 :ps256 :rs512 :rs384 :rs256])] 78 | (let [header-data {:url "https://example.com" :nonce nonce} 79 | res1 (jws/sign data rsa-privkey {:alg alg :header header-data}) 80 | res2 (jws/unsign res1 rsa-pubkey {:alg alg}) 81 | header (jws/decode-header res1)] 82 | (is (bytes/equals? res2 (codecs/to-bytes data))) 83 | (is (= header (merge header-data {:alg alg})))))) 84 | 85 | (defspec jws-spec-alg-es 500 86 | (props/for-all 87 | [data (gen/one-of [gen/bytes gen/string]) 88 | alg (gen/elements [:es512 :es384 :es256])] 89 | (let [res1 (jws/sign data ec-privkey {:alg alg}) 90 | res2 (jws/unsign res1 ec-pubkey {:alg alg})] 91 | (is (bytes/equals? res2 (codecs/to-bytes data)))))) 92 | 93 | (defspec jwe-spec-wrong-data 500 94 | (props/for-all 95 | [data gen/string-ascii] 96 | (try 97 | (jws/unsign data secret) 98 | (throw (Exception. "unexpected")) 99 | (catch clojure.lang.ExceptionInfo e 100 | (let [cause (:cause (ex-data e))] 101 | (is (or (= cause :signature) 102 | (= cause :header)))))))) 103 | 104 | (defspec jwe-spec-wrong-token 500 105 | (props/for-all 106 | [data1 gen/string-alphanumeric 107 | data2 gen/string-alphanumeric 108 | data3 gen/string-alphanumeric] 109 | (let [data (str data1 "." data2 "." data3)] 110 | (try 111 | (jws/unsign data secret) 112 | (throw (Exception. "unexpected")) 113 | (catch clojure.lang.ExceptionInfo e 114 | (let [cause (:cause (ex-data e))] 115 | (is (or (= cause :signature) 116 | (= cause :header))))))))) 117 | -------------------------------------------------------------------------------- /test/buddy/sign/jwt_tests.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright 2014-2016 Andrey Antukh 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 | (ns buddy.sign.jwt-tests 16 | (:require [clojure.test :refer :all] 17 | [clojure.test.check.clojure-test :refer (defspec)] 18 | [clojure.test.check.generators :as gen] 19 | [clojure.test.check.properties :as props] 20 | [buddy.core.codecs :as codecs] 21 | [buddy.core.nonce :as nonce] 22 | [buddy.core.keys :as keys] 23 | [buddy.core.bytes :as bytes] 24 | [buddy.sign.jwe :as jwe] 25 | [buddy.sign.jws :as jws] 26 | [buddy.sign.jwt :as jwt] 27 | [buddy.sign.util :as util] 28 | [cheshire.core :as json])) 29 | 30 | (def secret (codecs/hex->bytes (str "000102030405060708090a0b0c0d0e0f" 31 | "101112131415161718191a1b1c1d1e1f"))) 32 | 33 | (def key16 (nonce/random-bytes 16)) 34 | 35 | (defn- unsign-exp-succ 36 | ([get-claims-fn signed claims] 37 | (unsign-exp-succ get-claims-fn signed claims {})) 38 | ([get-claims-fn signed claims opts] 39 | (is (= (get-claims-fn signed opts) claims)))) 40 | 41 | (defn- unsign-exp-fail 42 | ([get-claims-fn signed cause] 43 | (unsign-exp-fail get-claims-fn signed cause {})) 44 | ([get-claims-fn signed cause opts] 45 | (try 46 | (get-claims-fn signed opts) 47 | (is false "get-claims-fn should throw") 48 | (catch clojure.lang.ExceptionInfo e 49 | (is (= (:cause (ex-data e)) cause)))))) 50 | 51 | (defspec jwt-spec-encode-decode-jws 100 52 | (props/for-all 53 | [key (gen/one-of [gen/bytes gen/string]) 54 | alg (gen/elements [:hs512 :hs256]) 55 | data (gen/map (gen/resize 4 gen/keyword) 56 | (gen/one-of [gen/string-alphanumeric gen/small-integer]))] 57 | (let [res1 (jwt/sign data key {:alg alg}) 58 | res2 (jwt/unsign res1 key {:alg alg})] 59 | (is (= res2 data))))) 60 | 61 | (defspec jwt-spec-encode-decode-jwe 100 62 | (props/for-all 63 | [enc (gen/elements [:a128gcm :a192gcm :a256gcm :a128cbc-hs256 64 | :a192cbc-hs384 :a256cbc-hs512]) 65 | zip gen/boolean 66 | data (gen/map (gen/resize 4 gen/keyword) 67 | (gen/one-of [gen/string-alphanumeric gen/int]))] 68 | (let [res1 (jwt/encrypt data key16 {:alg :a128kw :enc enc :zip zip}) 69 | res2 (jwt/decrypt res1 key16 {:alg :a128kw :enc enc :zip zip})] 70 | (is (= res2 data))))) 71 | 72 | (defn jwt-claims-validation 73 | [make-jwt-fn get-claims-fn] 74 | (let [unsign-exp-succ (partial unsign-exp-succ get-claims-fn) 75 | unsign-exp-fail (partial unsign-exp-fail get-claims-fn)] 76 | 77 | (testing "current time claims validation" 78 | (let [now (util/timestamp) 79 | candidate {:foo "bar" :iat now :nbf now :exp (+ now 60)} 80 | signed (make-jwt-fn candidate)] 81 | (unsign-exp-succ signed candidate))) 82 | 83 | (testing ":exp claim validation" 84 | (let [candidate {:foo "bar" :exp 10} 85 | signed (make-jwt-fn candidate)] 86 | (unsign-exp-succ signed candidate {:now 0}) 87 | (unsign-exp-succ signed candidate {:now 9}) 88 | (unsign-exp-succ signed candidate {:now 10 :leeway 1}) 89 | (unsign-exp-fail signed :exp {:now 10}) 90 | (unsign-exp-fail signed :exp {:now 11}) 91 | (unsign-exp-fail signed :exp {:now 12 :leeway 1}) 92 | (unsign-exp-succ signed candidate {:now 11 :skip-validation true}))) 93 | 94 | (testing ":nbf claim validation" 95 | (let [candidate {:foo "bar" :nbf 10} 96 | signed (make-jwt-fn candidate)] 97 | (unsign-exp-fail signed :nbf {:now 0}) 98 | (unsign-exp-fail signed :nbf {:now 8 :leeway 1}) 99 | (unsign-exp-fail signed :nbf {:now 9}) 100 | (unsign-exp-succ signed candidate {:now 9 :leeway 1}) 101 | (unsign-exp-succ signed candidate {:now 10}) 102 | (unsign-exp-succ signed candidate {:now 11}))) 103 | 104 | (testing ":iss claim validation" 105 | (testing "single issuer special case" 106 | (let [candidate {:foo "bar" :iss "foo:bar"} 107 | signed (make-jwt-fn candidate)] 108 | (unsign-exp-succ signed candidate) 109 | (unsign-exp-fail signed :iss {:iss "bar:foo"}))) 110 | 111 | (testing "multi-issuers case" 112 | (let [issuers ["foo:bar" "bar:baz"] 113 | candidate {:foo "bar" :iss "foo:bar"} 114 | signed (make-jwt-fn candidate)] 115 | (unsign-exp-succ signed candidate {:iss issuers}) 116 | (unsign-exp-fail signed :iss {:iss ["bar:foo" "baz:bar"]})))) 117 | 118 | (testing ":sub claim validation" 119 | (testing "subject claim presention case" 120 | (let [candidate {:foo "bar" :sub "foo:bar"} 121 | signed (make-jwt-fn candidate)] 122 | (unsign-exp-succ signed candidate) 123 | (unsign-exp-fail signed :sub {:sub "bar:foo"})))) 124 | 125 | (testing ":aud claim validation" 126 | (testing "single audience special case" 127 | (let [candidate {:foo "bar" :aud "foo:bar"} 128 | signed (make-jwt-fn candidate)] 129 | (unsign-exp-succ signed candidate) 130 | (unsign-exp-fail signed :aud {:aud "bar:foo"}))) 131 | 132 | (testing "multi-audience case" 133 | (let [audience ["foo:bar" "bar:baz"] 134 | candidate {:foo "bar" :aud audience} 135 | signed (make-jwt-fn candidate)] 136 | (doseq [aud audience] 137 | (unsign-exp-succ signed candidate {:aud aud})) 138 | (unsign-exp-fail signed :aud {:aud "bar:foo"})))))) 139 | 140 | (deftest jwt-jws-claims-validation 141 | (jwt-claims-validation 142 | #(jwt/sign % secret {:alg :hs256}) 143 | #(jwt/unsign %1 secret (merge {:alg :hs256} %2)))) 144 | 145 | (deftest jwt-jwe-claims-validation 146 | (jwt-claims-validation 147 | #(jwt/encrypt % key16 {:alg :a128kw :enc :a128gcm}) 148 | #(jwt/decrypt %1 key16 (merge {:alg :a128kw :enc :a128gcm} %2)))) 149 | 150 | (deftest jwt-jwtio-example 151 | (let [jwt (str "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." 152 | "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I" 153 | "kpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA9" 154 | "5OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ") 155 | claims (jwt/unsign jwt "secret" {:alg :hs256})] 156 | (is (= claims {:sub "1234567890" 157 | :name "John Doe" 158 | :admin true}) "jwt.io example"))) 159 | 160 | (deftest jwt-claims-must-be-map 161 | (is (thrown? AssertionError (jwt/sign "qwe" secret {:alg :hs256})) 162 | "claims should be a map")) 163 | 164 | (deftest jwt-no-json-payload 165 | (let [jws (jws/sign "foobar" secret {:alg :hs256})] 166 | (try 167 | (jwt/unsign jws secret {:alg :hs256}) 168 | (is false "unsign should throw") 169 | (catch clojure.lang.ExceptionInfo e 170 | (is (= (:cause (ex-data e)) :signature)))))) 171 | -------------------------------------------------------------------------------- /test/user.clj: -------------------------------------------------------------------------------- 1 | ;; This Source Code Form is subject to the terms of the Mozilla Public 2 | ;; License, v. 2.0. If a copy of the MPL was not distributed with this 3 | ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | ;; 5 | ;; Copyright (c) 2016-2022 Andrey Antukh 6 | 7 | (ns user 8 | (:require 9 | [clojure.spec.alpha :as s] 10 | [clojure.tools.namespace.repl :as repl] 11 | [clojure.walk :refer [macroexpand-all]] 12 | [clojure.pprint :refer [pprint]] 13 | [clojure.test :as test] 14 | [clojure.java.io :as io] 15 | [clojure.repl :refer :all] 16 | [criterium.core :refer [quick-bench bench with-progress-reporting]])) 17 | 18 | (defmacro run-quick-bench 19 | [& exprs] 20 | `(with-progress-reporting (quick-bench (do ~@exprs) :verbose))) 21 | 22 | (defmacro run-quick-bench' 23 | [& exprs] 24 | `(quick-bench (do ~@exprs))) 25 | 26 | (defmacro run-bench 27 | [& exprs] 28 | `(with-progress-reporting (bench (do ~@exprs) :verbose))) 29 | 30 | (defmacro run-bench' 31 | [& exprs] 32 | `(bench (do ~@exprs))) 33 | 34 | (defn run-tests 35 | ([] (run-tests #".*-tests$")) 36 | ([o] 37 | (repl/refresh) 38 | (cond 39 | (instance? java.util.regex.Pattern o) 40 | (test/run-all-tests o) 41 | 42 | (symbol? o) 43 | (if-let [sns (namespace o)] 44 | (do (require (symbol sns)) 45 | (test/test-vars [(resolve o)])) 46 | (test/test-ns o))))) 47 | 48 | --------------------------------------------------------------------------------