├── .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 |
--------------------------------------------------------------------------------