funcool / buddy-sign Goto Github PK
View Code? Open in Web Editor NEWHigh level message signing library.
Home Page: https://funcool.github.io/buddy-sign/latest/
License: Apache License 2.0
High level message signing library.
Home Page: https://funcool.github.io/buddy-sign/latest/
License: Apache License 2.0
I was working through this tutorial http://rundis.github.io/blog/2015/buddy_auth_part1.html and found that my require statement fails for buddy.sign.jws due to a failed dependency with cats:
CompilerException java.io.FileNotFoundException: Could not locate cats/monad/exception__init.class or cats/monad/exception.clj on classpath: , compiling:(buddy/sign/jws.clj:1:1)
Looking at my leiningen dependencies, I see cats 0.6.1. I am using Eclipse with CCW. Is there some other lein project parameter I need to get cljc files compiled correctly?
I just upgraded buddy-sign to 0.10.0 and noticed it broke my project’s uberjars. It turns out your release process accidentally included your user.clj
in the 0.10.0 JAR:
(ns user
(:require
[clojure.java.io :as io]
[clj-time.core :as t]
[buddy.sign.jws :as jws]
[buddy.core.keys :as keys]))
;; (def ec-privkey (keys/private-key "test/_files/privkey.ecdsa.pem" "secret"))
;; (def ec-pubkey (keys/public-key "test/_files/pubkey.ecdsa.pem"))
(defn- private-key []
(keys/private-key
(io/resource "_files/privkey.ecdsa.pem") "secret"))
(defn- public-key []
(keys/public-key
(io/resource "_files/pubkey.ecdsa.pem")))
(defn new-token
[res]
(let [exp (t/plus (t/now) (t/days 1))]
(jws/sign res (private-key) {:alg :es512 :exp exp})))
(defn from-token
[token]
(try
(jws/unsign token (public-key))
(catch Exception e
(do (prn e)
nil
))))
(comment
(def token (new-token {:foo "bar"}))
(jws/unsign token (pubkey))
)
As a result, my uberjars don’t contain clj-time
because it’s already loaded by the user
ns.
Would you be interested in a contribution of a Ring cookie store based on buddy-sign, both signing and encrypting cookies?
I have the feeling that would be more secure than the current Ring cookie encryption, but I'm not really sure. Do you know?
As per section 4.1.3 of the RFC,
In the general case, the "aud" value is an array of case-
sensitive strings, each containing a StringOrURI value. In the
special case when the JWT has one audience, the "aud" value MAY be a
single case-sensitive string containing a StringOrURI value.
I got this error while trying to sign resource with dsa and rsa keys:
backend.utils.jwt=> CompilerException java.lang.IllegalArgumentException: No implementation of method: :->byte-array of protocol: #'buddy.core.codecs/ByteArray found for class: org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey, compiling:(form-init4451766785895400468.clj:1:12)
Might be I'm missing something but I would expect this to work:
(require '[buddy.sign.jwt :as jwt])
(->
(jwt/sign {:foo 1} "test" {:alg :hs512})
(jwt/unsign "test"))
but it fails with
Message seems corrupt or manipulated.
{:type :validation, :cause :signature}
Looking at buddy.sign.jws/unsign
it sets the algorithm to hs256 unless specified. So this works:
(->
(jwt/sign {:foo 1} "test" {:alg :hs512})
(jwt/unsign "test" {:alg :hs512)) ; => {:foo 1}
Shouldn't jws/unsign read the signing algorithm from the header? Something like:
(defn unsign
"Given a signed message, verify it and return
the decoded payload."
([input pkey] (unsign input pkey nil))
([input pkey {:keys [alg] :or {alg :hs256}}]
(let [[header payload signature] (split-jws-message input)
header-data (parse-header header)]
(when-not
(try
(verify-signature {:key (util/resolve-key pkey header-data)
:signature signature
;; :alg alg <- Change this
:alg (:alg header-data) ;; <- To this
:header header
:payload payload})
(catch java.security.SignatureException se
(throw (ex-info "Message seems corrupt or manipulated."
{:type :validation :cause :signature}
se))))
(throw (ex-info "Message seems corrupt or manipulated."
{:type :validation :cause :signature})))
(decode-payload payload))))
The documentation describes a now option that can be used to unsign tokens that may have expired:
;; use timestamp in the past
(jwt/unsign token "key" {:now (time/minus (time/now) (time/seconds 5))})
;; => {:user 1}
However, I've found this results in the following error:
org.joda.time.DateTime cannot be cast to java.lang.Number
As a workaround I've done something like this:
(jwt/unsign token "key" {:now (buddy.sign.util/to-timestamp (time/minus (time/now) (time/seconds 5)))})
which works, but it would be more natural to be able to specify a DateTime per the documentation (and would be more consistent with the manner in which the :exp claim is specified during token signing anyway).
Hi there,
First of all many thanks for your work on this - hugely appreciated 👍 . Secondly observe the following output (per lein check
):
Reflection warning, buddy/core/keys/jwk/eddsa.clj:31:19 - reference to field getDeclaredConstructors can't be resolved.
Reflection warning, buddy/core/keys/jwk/eddsa.clj:33:37 - reference to field getParameterCount can't be resolved.
Reflection warning, buddy/core/keys/jwk/eddsa.clj:34:64 - reference to field getParameterTypes can't be resolved.
Reflection warning, buddy/core/keys/jwk/eddsa.clj:34:58 - call to static method aget on clojure.lang.RT can't be resolved (argument types: unknown, int).
Reflection warning, buddy/core/keys/jwk/eddsa.clj:38:5 - call to method setAccessible can't be resolved (target class is unknown).
Reflection warning, buddy/core/keys/jwk/eddsa.clj:39:5 - call to method newInstance can't be resolved (target class is unknown).
Reflection warning, buddy/core/keys/jwk/eddsa.clj:46:5 - call to static method copyOfRange on java.util.Arrays can't be resolved (argument types: unknown, long, java.lang.Number).
Reflection warning, buddy/core/keys/jwk/eddsa.clj:50:3 - call to static method copyOfRange on java.util.Arrays can't be resolved (argument types: unknown, long, java.lang.Number).
Reflection warning, buddy/core/keys/jwk/eddsa.clj:77:17 - reference to field getEncoded can't be resolved.
Reflection warning, buddy/core/keys/jwk/eddsa.clj:78:22 - reference to field getPublicKey can't be resolved.
Reflection warning, buddy/core/keys/jwk/eddsa.clj:78:43 - reference to field getEncoded can't be resolved.
Reflection warning, buddy/core/keys/jwk/eddsa.clj:79:15 - call to static method equals on java.util.Arrays can't be resolved (argument types: unknown, unknown).
Reflection warning, buddy/sign/jwe/cek.clj:42:5 - call to method init on javax.crypto.Cipher can't be resolved (argument types: int, unknown, java.security.SecureRandom).
Reflection warning, buddy/sign/jwe/cek.clj:49:5 - call to method init on javax.crypto.Cipher can't be resolved (argument types: int, unknown, java.security.SecureRandom).
Reflection warning, buddy/sign/jwe/cek.clj:56:5 - call to method init on javax.crypto.Cipher can't be resolved (argument types: int, unknown, java.security.SecureRandom).
Reflection warning, buddy/sign/jwe/cek.clj:63:5 - call to method init on javax.crypto.Cipher can't be resolved (argument types: int, unknown, java.security.SecureRandom).
Reflection warning, buddy/sign/jwe/cek.clj:70:5 - call to method init on javax.crypto.Cipher can't be resolved (argument types: int, unknown, java.security.SecureRandom).
Reflection warning, buddy/sign/jwe/cek.clj:77:5 - call to method init on javax.crypto.Cipher can't be resolved (argument types: int, unknown, java.security.SecureRandom).
A few (cleverly placed) type-hints should get rid of all of the above. I could put together a PR, but stuff like this is usually so trivial that the friction of opening the PR/reviewing dwarfs the time you would need to do it yourself. Let me know, if you'd like one regardless though... 👍
When verifying a JWT token from an external source (e.g., an ID token in an OpenID Connect flow), it is necessary to read the kid
of the signing key from the token header in order to fetch the correct signing key (e.g. from a JWK Key Set URL), before unsigning.
buddy.sign.jws/decode-header
seems to be perfect for this, but unfortunately it selectively extracts the typ
and alg
attributes instead of the full header (which includes kid
). It would be nice if either all header attributes would be returned, or if at least kid
is included to support the OpenID Connect use case.
I'm currently experiencing problems validating JWTs due to clock skew, the RFC specifies some leeway MAY be provided
Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew.
https://tools.ietf.org/html/rfc7519#section-4.1.4
Should we implement this? Any ideas what the API might look like?
Currently I can fix this by using a now
option that is a few minutes off, but this isn't really explicit.
This is most likely due to errors in my configuration but each time I attempt to execute the following:
(require '[buddy.sign.compact :as cm])
(-> (cm/sign {:test "value"}
;priv-key
{:alg :es256 :compress true})
(cm/unsign
;priv-key
{:alg :es256 :compress true :max-age (* 15 60)}))
unsign throws the following exception
#error {
:cause "No matching method found: initVerify for class java.security.Signature$Delegate"
:via
[{:type java.lang.IllegalArgumentException
:message "No matching method found: initVerify for class java.security.Signature$Delegate"
:at [clojure.lang.Reflector invokeMatchingMethod "Reflector.java" 80]}]
:trace
[[clojure.lang.Reflector invokeMatchingMethod "Reflector.java" 80]
[clojure.lang.Reflector invokeInstanceMethod "Reflector.java" 28]
[buddy.core.dsa$eval24478$fn__24479 invoke "dsa.clj" 72]
[buddy.core.dsa$eval24382$fn__24413$G__24369__24420 invoke "dsa.clj" 43]
[buddy.core.dsa$verify invoke "dsa.clj" 161]
[buddy.sign.compact$fn__25562 invoke "compact.clj" 73]
[buddy.sign.compact$verify_signature invoke "compact.clj" 96]
[buddy.sign.compact$unsign doInvoke "compact.clj" 139]
[clojure.lang.RestFn invoke "RestFn.java" 442]
I have included the nippy dependency in build.boot
[com.taoensso/nippy "2.11.1"]
I have also tried :rs256 with a different key pair and seen similar results. Please let me know if you need additional information or if you have any pointers on this issue.
Hello, i've added EdDSA signature algorithm for issuing JWS tokens
https://github.com/shilder/buddy-sign-eddsa
There's one issue with encoding of JWS header field - IANA lists algorithm for EdDSA as
EdDSA
string, but buddy.sign.jws/encode-header
converts it to EDSSA
, so i've monkey-patched this function to produce correct output
I can merge it with buddy-sign
, buddy-core
libraries, or leave at as separate library,
Here's one of the functions I'm using for authentication with Buddy:
(def encryption {:alg :rsa-oaep :enc :a192gcm})
(def pubkey (keys/public-key "pubkey.pem"))
(def exp-hours 3)
(defn make-id-token [id]
{:token
(jwe/encrypt {:user {:id id}
:exp (-> exp-hours hours from-now)}
pubkey encryption)})
This code apparently works fine with Buddy 0.13.0. However, with the recent Buddy 1.0.0, it throws this exception:
Unhandled java.lang.IllegalArgumentException
No implementation of method: :-to-bytes of protocol:
clojure.lang.PersistentArrayMap
core_deftype.clj: 568 clojure.core/-cache-protocol-fn
core_deftype.clj: 560 clojure.core/-cache-protocol-fn
codecs.clj: 60 buddy.core.codecs/eval20085/fn/G
codecs.clj: 69 buddy.core.codecs/to-bytes
codecs.clj: 66 buddy.core.codecs/to-bytes
jwe.clj: 77 buddy.sign.jwe/encode-payload
jwe.clj: 75 buddy.sign.jwe/encode-payload
jwe.clj: 224 buddy.sign.jwe/encrypt
jwe.clj: 214 buddy.sign.jwe/encrypt
RestFn.java: 442 clojure.lang.RestFn/invoke
In the "Decrypting Data" section, it says:
You do not need specify the encryption algorithm explicitly, it is automatically detected, because the incoming token will come with content encryption algorithm stored in its header part.
But with 0.9.0:
(def token (jwe/encrypt {} "ayellowsubmarine" {:alg :dir :enc :a128gcm}))
;; #'user/token
(jwe/decrypt token "ayellowsubmarine")
;; ExceptionInfo The `enc` param mismatch with header value. clojure.core/ex-info (core.clj:4617)
(jwe/decrypt token "ayellowsubmarine" {:alg :dir :enc :a128gcm})
;; {}
Not specifying the encryption algorithm only works for the default value of :a128cbc-hs256
.
Obviously not a big deal, but I figured I'd let you know.
See minimal project: https://github.com/metametadata/buddy-rs256-issue
In a nutshell, this code:
; example data is taken from https://jwt.io/
(jws/unsign "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE"
"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----"
{:alg :rs256})
causes the exception:
Hello, World!
Exception in thread "main" java.lang.IllegalArgumentException: No matching method found: initVerify for class java.security.Signature$Delegate, compiling:(/private/var/folders/7_/mhk7y9952n972b6kgt7_8l700000gn/T/form-init829514710048949323.clj:1:124)
at clojure.lang.Compiler.load(Compiler.java:7391)
at clojure.lang.Compiler.loadFile(Compiler.java:7317)
at clojure.main$load_script.invokeStatic(main.clj:275)
at clojure.main$init_opt.invokeStatic(main.clj:277)
at clojure.main$init_opt.invoke(main.clj:277)
at clojure.main$initialize.invokeStatic(main.clj:308)
at clojure.main$null_opt.invokeStatic(main.clj:342)
at clojure.main$null_opt.invoke(main.clj:339)
at clojure.main$main.invokeStatic(main.clj:421)
at clojure.main$main.doInvoke(main.clj:384)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Caused by: java.lang.IllegalArgumentException: No matching method found: initVerify for class java.security.Signature$Delegate
at clojure.lang.Reflector.invokeMatchingMethod(Reflector.java:80)
at clojure.lang.Reflector.invokeInstanceMethod(Reflector.java:28)
at buddy.core.dsa$eval737$fn__738.invoke(dsa.clj:72)
at buddy.core.dsa$eval641$fn__672$G__628__679.invoke(dsa.clj:43)
at buddy.core.dsa$verify.invokeStatic(dsa.clj:161)
at buddy.core.dsa$verify.invoke(dsa.clj:158)
at buddy.sign.jws$fn__1775.invokeStatic(jws.clj:41)
at buddy.sign.jws$fn__1775.invoke(jws.clj:30)
at buddy.sign.jws$verify_signature.invokeStatic(jws.clj:95)
at buddy.sign.jws$verify_signature.invoke(jws.clj:87)
at buddy.sign.jws$unsign.invokeStatic(jws.clj:133)
at buddy.sign.jws$unsign.invoke(jws.clj:127)
at buddy_rs256_issue.core$_main.invokeStatic(core.clj:6)
at buddy_rs256_issue.core$_main.doInvoke(core.clj:4)
at clojure.lang.RestFn.invoke(RestFn.java:397)
at clojure.lang.Var.invoke(Var.java:375)
at user$eval5.invokeStatic(form-init829514710048949323.clj:1)
at user$eval5.invoke(form-init829514710048949323.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6927)
at clojure.lang.Compiler.eval(Compiler.java:6917)
at clojure.lang.Compiler.load(Compiler.java:7379)
... 14 more
It should be called 3.5.351
The documentation has the following example:
(require '[buddy.sign.jws :as jws])
(jws/sign {:userid 1} "secret")
;; "eyJ0eXAiOiJKV1MiLCJhbGciOiJIU..."
If I run this, I get the following error:
CompilerException java.lang.IllegalArgumentException: No implementation of method: :-to-bytes of protocol: #'buddy.core.codecs/IByteArray found for class: clojure.lang.PersistentArrayMap
The implementation could look something like this (encoded as JSON for JWT):
(extend-protocol buddy.core.codecs/IByteArray
IPersistentMap
(-to-bytes [m]
(buddy.core.codecs/-to-bytes (cheshire.core/generate-string m))))
I'm not sure about the JSON part because I currently don't understand weather JWS has always a JSON payload. I suspect no.
Hey Buddy!
I'm not sure if this entirely qualifies as an issue, so close it if not, but I just discovered that you can sign tokens with nil
as the pkey/secret, but not unsign the same token with nil as there's no implementation of IKeyProvider (used by unsign ala resolve-key
here )
So, should there be a call to resolve-key in the sign
function? found here
Anyways, let me know if this was intended or not. If not, I'll open a PR.
It looks like the apply-jdk8-extensions
macro is getting eval
ed at compile-time, e.g. if you build a JAR the check is done whenm the JAR is built. This means if you build the JAR with a Java 8 JDK, the JAR won't work with Java 7.
e.g. if you build a JAR with Java 8 the macro is expanded at compile-time and (apply-jdk8-extensions)
becomes
(extend-protocol ITimestamp
java.time.Instant
(to-timestamp [obj__59919__auto__]
(.getEpochSecond obj__59919__auto__)))
This doesn't work on Java 7 :/
From the perspective of the caller of validating the incoming JWT, they allow themselves (a single audience) of possibly many audiences listed in the claim.
Conversely, they also may allow tokens generated a number of issuers, one of which is presented in the claim.
So, the validation logic should accept a single audience (to match against a list of audiences in the token, as it does now), and also one or a list of issuers (to match against the single issuer in the token).
Entails changing https://github.com/funcool/buddy-sign/blob/master/src/buddy/sign/jwt.clj#L26 to something that is similar to the aud
validation, but with inverse item-to-list matching.
I'm just new to buddy and found the two things in the headline while starting with the doc.
I am developing a tool that works with Let's Encrypt's ACME server. I would like to use buddy for this, but buddy.sign.jws/sign
does not support all the needed headers.
jwk
and kid
are easy enough since they're in the RFC, I could submit a PR for those.
I also need nonce
and url
, so how do you feel about modifying the API to allow arbitrary header values to be merged in? Would you accept a PR for this?
(buddy.sign.jws/sign "claims" "secret"
{:header {:url "https://example.com" :nonce "some_id"}})
Sometimes, when signature cannot be calculated (invalid signature length, key errors et cetera) java.security.Signature#verify
can throw java.security.SignatureException
which is not handled in buddy.sign.jws/unsign
The problem is that buddy.auth.backends.token/jws-backend
catches only clojure.lang.ExceptionInfo
exceptions and doesn't call on-error
for SignatureException
This problem can be solved in two ways
java.security.SignatureException
and wrap it in ex-info
in buddy.sign.jws/unsign
I've implemented 1st solution for my project and it works pretty well for now see #57
This is more of a feedback on the public API.
We quite like this little library and the fact that it has minimal dependencies.
However, using exceptions to report expected errors is an anti-pattern. There is simply no need to do this. Let the client code decide if they want to throw an exception on unsuccessful 'unsign', most of the time they wil not, they will just pick an alternative code path.
At this point, you may not want to break the public API, but providing alternative methods that return errors as data would be highly appreciated.
Thanks
The map defined here omits an entry for :ES384
. Is there a reason for this?
It doesn't appear difficult to add, assuming it's not omitted for a reason.
Due to this issue in nippy 2.11.0-alpha5, buddy-sign
should declare a hard dependency on Nippy 2.11.1, instead of listing it as "provided", which will use whatever version of Nippy happens to be available.
Our specific scenario:
We include carmine (2.12.2) in our project, which resolves to a dependency on Nippy 2.10.0. The result is that when trying to deserialize a JWT token with our claims, we get this error:
Bad reply data: Thaw failed: Decryption/decompression failure, or data unfrozen/damaged.
Is the following a result of anything under buddy-sign's control? I'm not clear yet on the general rule for resolving lein deps :tree
confusion; e.g. is it a downstream app's responsibility, or is it a symptom of a library needing to be more careful? Here is sample output from a project that uses buddy-sign:
❯ lein deps :tree
Possibly confusing dependencies found:
[ring/ring-core "1.3.2"] -> [org.clojure/tools.reader "0.8.1"]
overrides
[buddy/buddy-auth "0.5.0"] -> [buddy/buddy-sign "0.5.0"] -> [com.taoensso/nippy "2.8.0"] -> [com.taoensso/encore "1.21.0"] -> [org.clojure/tools.reader "0.8.13"]
and
[buddy/buddy-sign "0.5.0"] -> [com.taoensso/nippy "2.8.0"] -> [com.taoensso/encore "1.21.0"] -> [org.clojure/tools.reader "0.8.13"]
and
[buddy/buddy-auth "0.5.0"] -> [buddy/buddy-sign "0.5.0"] -> [com.taoensso/nippy "2.8.0"] -> [org.clojure/tools.reader "0.8.13"]
and
[buddy/buddy-sign "0.5.0"] -> [com.taoensso/nippy "2.8.0"] -> [org.clojure/tools.reader "0.8.13"]
and
[clj-http "1.1.0"] -> [org.clojure/tools.reader "0.8.16" :exclusions [org.clojure/clojure]]
Consider using these exclusions:
[buddy/buddy-auth "0.5.0" :exclusions [org.clojure/tools.reader]]
[buddy/buddy-sign "0.5.0" :exclusions [org.clojure/tools.reader]]
[buddy/buddy-auth "0.5.0" :exclusions [org.clojure/tools.reader]]
[buddy/buddy-sign "0.5.0" :exclusions [org.clojure/tools.reader]]
[clj-http "1.1.0" :exclusions [org.clojure/tools.reader]]
unsign requires you to know the alg before unsigning a JWS, however if you publish keys with different algorithms or you are not in control of the signing of a JWS you might not know the algorithm.
It should use the alg field from the JOSE header and only default to :hs256 if it is not present in opts or the header.
It would be nice if buddy supported the NONE algorithm to just implement the spec. I fully understand closing this since use of the NONE algorithm is discouraged and can lead to vulnerabilities if not careful though but I don't think the way buddy is currently implemented that it would be problematic as someone would explicitly need to pass {:alg :none}
into the options for both signing and "unsigning".
(-> {:foo "bar"}
(jwt/sign nil {:alg :none})
(jwt/unsign nil {:alg :none}))
=> {:foo "bar"}
Example workaround:
(extend-protocol buddy.sign.util/IKeyProvider
nil
(resolve-key [key header] nil))
(alter-var-root #'buddy.sign.jws/+signers-map+
(constantly (assoc buddy.sign.jws/+signers-map+
:none
{:signer (constantly (byte-array 0))
:verifier (fn [_authdata sig _key] (= (count sig) 0))})))
I'm using buddy "2.0.0"
and this little snippet of code:
(ns jwt-test.jwt
(:require [buddy.sign.jwt :as jwt]
[buddy.core.keys :as keys]))
(def ec-privkey (keys/str->private-key "-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----"))
(def ec-pubkey (keys/str->public-key "-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----"))
(def signed-data (jwt/sign {:sub "1234567890"
:name "John Doe"
:admin true
:iat 1516239022} ec-privkey {:alg :es256
:header {:typ "JWT"}
}))
(println "signed-data:" signed-data)
(def unsigned-data (jwt/unsign signed-data ec-pubkey {:alg :es256}))
(println "unsigned-data:" unsigned-data)
The keys are the default keys offered up to me by https://jwt.io
Now if you take the signed data, go over to https://jwt.io/
Select ES256 as the algorithm. Paste in the public and private key into the sections in 'Verify Signature' first, and then paste the signed-data from the code above in on the left. The webapp shows the signature as invalid.
Lets do the reverse...
In the jwt.io webapp, paste in header as:
{
"alg": "ES256",
"typ": "JWT"
}
Payload as:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
Verify Signature as:
ECDSASHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----
,

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----
)
And it will generate an encoded token. You will see it also says "Signature Verified".
Copy the token into the following code snippet:
(def signed-data "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.sx6_OV8d5Zzk8Hcd4nE2V1vy3Wo6Fc18i26_WRgHDBz68QytuBjhk90icuj6m737PnOikUfvK_0j0h5lGVmelA")
;; And unsign
(def unsigned-data (jwt/unsign signed-data ec-pubkey {:alg :es256}))
(println "unsigned-data:" unsigned-data)
And when I execute the unsign line I get a traceback with the exception java.security.SignatureException "error decoding signature bytes"
2. Unhandled clojure.lang.Compiler$CompilerException
Error compiling form-init6775515006125166539.clj at (28:20)
Compiler.java: 3700 clojure.lang.Compiler$InvokeExpr/eval
Compiler.java: 457 clojure.lang.Compiler$DefExpr/eval
Compiler.java: 7067 clojure.lang.Compiler/eval
Compiler.java: 7025 clojure.lang.Compiler/eval
core.clj: 3206 clojure.core/eval
core.clj: 3202 clojure.core/eval
main.clj: 243 clojure.main/repl/read-eval-print/fn
main.clj: 243 clojure.main/repl/read-eval-print
main.clj: 261 clojure.main/repl/fn
main.clj: 261 clojure.main/repl
main.clj: 177 clojure.main/repl
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 87 clojure.tools.nrepl.middleware.interruptible-eval/evaluate/fn
AFn.java: 152 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
core.clj: 657 clojure.core/apply
core.clj: 1965 clojure.core/with-bindings*
core.clj: 1965 clojure.core/with-bindings*
RestFn.java: 425 clojure.lang.RestFn/invoke
interruptible_eval.clj: 85 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 55 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 222 clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
interruptible_eval.clj: 190 clojure.tools.nrepl.middleware.interruptible-eval/run-next/fn
AFn.java: 22 clojure.lang.AFn/run
ThreadPoolExecutor.java: 1149 java.util.concurrent.ThreadPoolExecutor/runWorker
ThreadPoolExecutor.java: 624 java.util.concurrent.ThreadPoolExecutor$Worker/run
Thread.java: 748 java.lang.Thread/run
1. Caused by java.security.SignatureException
error decoding signature bytes.
nil: -1 org.bouncycastle.jcajce.provider.asymmetric.util.DSABase/engineVerify
Signature.java: 1219 java.security.Signature$Delegate/engineVerify
Signature.java: 652 java.security.Signature/verify
dsa.clj: 82 buddy.core.dsa/eval42257/fn
dsa.clj: 43 buddy.core.dsa/eval42161/fn/G
dsa.clj: 93 buddy.core.dsa/verify-signature-for-plain-data
dsa.clj: 90 buddy.core.dsa/verify-signature-for-plain-data
dsa.clj: 126 buddy.core.dsa/eval42276/fn
dsa.clj: 48 buddy.core.dsa/eval42221/fn/G
dsa.clj: 162 buddy.core.dsa/verify
dsa.clj: 158 buddy.core.dsa/verify
jws.clj: 51 buddy.sign.jws/fn
jws.clj: 30 buddy.sign.jws/fn
jws.clj: 104 buddy.sign.jws/verify-signature
jws.clj: 96 buddy.sign.jws/verify-signature
jws.clj: 143 buddy.sign.jws/unsign
jws.clj: 137 buddy.sign.jws/unsign
jwt.clj: 111 buddy.sign.jwt/unsign
jwt.clj: 107 buddy.sign.jwt/unsign
AFn.java: 160 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
Compiler.java: 3695 clojure.lang.Compiler$InvokeExpr/eval
Compiler.java: 457 clojure.lang.Compiler$DefExpr/eval
Compiler.java: 7067 clojure.lang.Compiler/eval
Compiler.java: 7025 clojure.lang.Compiler/eval
core.clj: 3206 clojure.core/eval
core.clj: 3202 clojure.core/eval
main.clj: 243 clojure.main/repl/read-eval-print/fn
main.clj: 243 clojure.main/repl/read-eval-print
main.clj: 261 clojure.main/repl/fn
main.clj: 261 clojure.main/repl
main.clj: 177 clojure.main/repl
RestFn.java: 1523 clojure.lang.RestFn/invoke
interruptible_eval.clj: 87 clojure.tools.nrepl.middleware.interruptible-eval/evaluate/fn
AFn.java: 152 clojure.lang.AFn/applyToHelper
AFn.java: 144 clojure.lang.AFn/applyTo
core.clj: 657 clojure.core/apply
core.clj: 1965 clojure.core/with-bindings*
core.clj: 1965 clojure.core/with-bindings*
RestFn.java: 425 clojure.lang.RestFn/invoke
interruptible_eval.clj: 85 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 55 clojure.tools.nrepl.middleware.interruptible-eval/evaluate
interruptible_eval.clj: 222 clojure.tools.nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
interruptible_eval.clj: 190 clojure.tools.nrepl.middleware.interruptible-eval/run-next/fn
AFn.java: 22 clojure.lang.AFn/run
ThreadPoolExecutor.java: 1149 java.util.concurrent.ThreadPoolExecutor/runWorker
ThreadPoolExecutor.java: 624 java.util.concurrent.ThreadPoolExecutor$Worker/run
Thread.java: 748 java.lang.Thread/run
What's going on here? Is jwt.io doing the wrong thing? Is buddy doing the wrong thing? Somewhere in between? Can anyone shed any light on this interoperability problem?
is this code right?
(when (and (:nbf claims) (> now (:nbf claims))) (throw (ex-info (format "Token is not yet valid (%s)" (:nbf claims)) {:type :validation :cause :nbf})))
This means that if now is greater than nbf, it'll throw an exception.
But nbf means "not before". So if my NBF is 11am, then I'm saying this token is invalid before 11am.
But the code does the reverse check... so is NBF broken?
The code lives in buddy.sign.jws
When I attempted to essentially implement the JWS example auth, from:
https://github.com/funcool/buddy-auth/blob/master/examples/token-jws/src/authexample/web.clj
from lines 63:
https://github.com/funcool/buddy-auth/blob/master/examples/token-jws/src/authexample/web.clj#L63
and 80:
https://github.com/funcool/buddy-auth/blob/master/examples/token-jws/src/authexample/web.clj#L80
using {:alg :hs512}
throws a NullPointerException
whereas when I make the only change to {:alg :hs256}
it works fine and request headers are authenticated properly.
Stacktrace for :hs512:
java.lang.NullPointerException
at clojure.string$lower_case.invoke(string.clj:215)
at buddy.sign.jws$parse_header.invoke(jws.clj:100)
at buddy.sign.jws$unsign.invoke(jws.clj:174)
at buddy.auth.backends.token$jws_backend$reify__571._authenticate(token.clj:53)
at buddy.auth.middleware$wrap_authentication$fn__1703$fn__1707.invoke(middleware.clj:36)
at buddy.auth.middleware$wrap_authentication$fn__1703.invoke(middleware.clj:31)
...
any thoughts on what's causing the difference in execution when using a different strength encryption scheme?
Hello! I'm having problems decoding a JWT. It's signed HMAC/256
I've verified that the signature is correct using http://jwt.io ..
However, when I try to decode and verify the token with buddy, I'm getting a "nil" (which I assume means it did not pass verification). I've attached an example secret and token. Am I doing something wrong, or is there a bug?
(def secret
"KdnIJv7h5r--N2Na7XfS0EiHUKrZm_qucUbF6PmE6FOMrelLwzBOGEmI17Uqmaeu"
)
(jws/unsign "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL29ycGhpZC10ZXN0LmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1NGNhZmM0YzJlMGU5YzZiMTQ5ZDMwY2QiLCJhdWQiOiJIdTRxd3FRaU9nVHJCNlliT2NHTXBUQTdpNjFSNVp1SCIsImV4cCI6MTQyMjYyNTA5MCwiaWF0IjoxNDIyNTg5MDkwfQ.3wKNZzgghjpsPBi5gDwv-RkbzvhG22Npcfyl8SUZriI" secret)
Hi,
Firstly, thanks for creating the Buddy security libraries for Clojure.
I am going through the code examples in your documentation, but did not see any specific examples for using ED25519 even though I see that it is supported using the :eddsa algorithm options.
I created a ED25519 keypair using OpenSSL v1.1.1g for testing buddy-sign claims signing using the :eddsa.
$ openssl genpkey -algorithm ED25519 > test-ed-privkey.pem
$ openssl pkey -in test-ed-privkey.pem -pubout > test-ed-pubkey.pem
And then wrote a very simple test on the Clojure repl:
(require '[buddy.sign.jwt :as jwt])
(require '[buddy.core.keys :as keys])
(def ed-privkey (keys/private-key "./test-ed-privkey.pem"))
(def ed-pubkey (keys/public-key "./test-ed-pubkey.pem"))
(jwt/sign {:userid 22} ed-privkey {:alg :eddsa})
That last line threw an InvalidKeyException
with the message
cannot identify EdDSA private key: class org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey
.
I am using these versions of Clojure and the buddy-sign libraries
[buddy/buddy-core "1.6.0"]
[buddy/buddy-sign "3.1.0"]
[org.clojure/clojure "1.10.0"]
on Mac OS v10.14.6.
I added to my project the dependency [buddy/buddy-sign "1.1.0"] to implement authentication features. However, for some reason this simple example doesn't work (from the repl):
(require '[buddy.sign.jws :as jws])
;; this work
(jws/sign "a" "secret")
;; => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.YQ.fbSW5D1FbwwcFijKn6aSghRHIwcpE102OKAvkfxsxls"
;; this doesn't work
(jws/sign {:a 100} "secret") ;; throws an error
And this is the error message:
IllegalArgumentException No implementation of method: :-to-bytes of protocol: #'buddy.core.codecs/IByteArray found for class: clojure.lang.PersistentArrayMap clojure.core/-cache-protocol-fn (core_deftype.clj:568)
I am getting a similar error if I try to sign an integer:
IllegalArgumentException No implementation of method: :-to-bytes of protocol: #'buddy.core.codecs/IByteArray found for class: java.lang.Long clojure.core/-cache-protocol-fn (core_deftype.clj:568)
Looking the sign implementation (from cursive), I see that it has a pre
clause to enforce the use of a map as the claims container, which contradicts the results I am getting.
(defn sign
"Sign arbitrary length string/byte array using
json web token/signature."
;; The exp nbf and iat keys in the options are deprecated
;; and will be removed in the next version.
[claims pkey & [{:keys [alg typ] :or {alg :hs256 typ :jws} :as opts}]]
{:pre [(map? claims)]}
(let [header (encode-header alg typ)
claims (encode-claims claims opts)
signature (calculate-signature {:key pkey
:alg alg
:header header
:claims claims})]
(str/join "." [header claims signature])))
Any idea of what could be the problem?
I suggest to improve validation for aud
so that we can use collection for “valid audience” but not a single value only.
buddy-sign/src/buddy/sign/jwt.clj
Lines 27 to 28 in 2009c4d
First: Thanks for this library! It's super easy and well documented! :)
Problem:
Compare:
(ex-data
(try
(cm/unsign "blah" "secret") (catch RuntimeException e e)))
(ex-data
(try
(jws/unsign "blah" "secret") (catch RuntimeException e e)))
Can't find information about this.
Google's OpenID Connect system provides signed JWS tokens with their authenticity asserted by keys available in JWK format:
{"kty" "RSA", "alg" "RS256", "use" "sig", "kid" "21ea36ce50d7665fc1d6b01dc726f5820c02c8df", "n" "n5NuAPSEeZcmbyIdChc29OHHieMEAIJRmNbtc6IOoutbva_JX-OOuW9EzAafCblQg6Eo5x3FVCMRCNCyxRJ4d9vktGqZvpiNFlEboidqLICxP-2luMmRRA6TYSFl2dFDYin4auoN0KjsV61-WpeDp0kuPEyvxW-C1arzNJYit8RaXc7lciKiCmp4yP_e4mwqd_48ChqzgVMD0f2caWQjxwfBIp3WWYJlsKNJ-gScQp1HEds9D_N6ELQJCDImF8f2fD1cMguu0W53tTahqubyILa_MZbmOiJLMbZLauFpZOAuyG_PfFOmGlQumL_v8NO_13H4hLMOfour_sAOHfH4Nw", "e" "AQAB"}
which is apparently part of the OpenID Connect standard. I don't know, I've been wading in acronym soup all day now.
I'm interested in validating these using buddy, but it's not clear how to convert the n
and e
values into a public key of the form buddy expects. Is this possible in buddy as is?
Is there any interest in seeing buddy extended to support OpenID Connect more systemically?
I was kind of surprised that jws/unsign
does claims validation since that's not part of JWS (I think). It is part of JWT, which happens to be my use case so it isn't actually a problem for me. The docs say
buddy-sign does not offers special api for Json Web Token because it is a subset of Json Web Signature (JWS) and Json Web Encryption (JSE) specifications.
But I don't think that's correct, I think it's the other way around.
Related: the JWS spec allows anything to act as the payload, not just JSON data. The current implementation requires the message to be a map though.
Let me know if you'd be willing to do these changes, I'd be happy to write a patch for them.
There are a lot of wording, spelling, and grammar mistakes throughout the documentation. I understand English isn't everyone's first language; I just wanted to make a note of this for future contributors.
Hello, i'm writing tests for JWK keys support and it seems that buddy-sign
JWS generator uses dsa/sign
output directly, which is DER-encoded format. but according to https://tools.ietf.org/html/rfc7515#appendix-A.3 JWS signature should be concatenation of R || S
Nimbus-JOSE-JWT library has pair of functions ECDSA/transcodeSignatureToConcat
and ECDSAPart/transcodeSignatureToDER
for conversions between these two
This can be solved either by copy/pasting (Nimbus is Apache 2.0 licensed, so it's possible with copyright noitices) these functions, by inclusion of nimbus in dependencies or by rewriting code in clojure (which will not be very nice due to low-level byte juggling)
Also this change will be breaking for anyone, who issued long-lived ECDSA JWS tokens using buddy-sign
Hi,
I noticed that some changes were made in December of 2017 to support the more standard JOSE format for JWT signatures when using ECDSA. Could you cut a new release version so that these can be consumed? Currently running into interop issues due to the DER format.
(talking about #62)
When using auth0 / google / etc... you can obtain public keys to assist with verifying JWTs via .well-known
endpoints.
The domain for the endpoints can be deduced from the JWT :iss
property and a map of the .well-known
paths for commonly used authentication providers (or provided by the client).
Are you considering support for this?
I have a rudimentary implementation so can create a PR if there is interest.
I think this article makes a good case for this: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/
I learn buddy by http://rundis.github.io/blog/2015/buddy_auth_part1.html series. And I don't see jws/to-timestamp function in new version of buddy-sign.
Where is it?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.