Giter VIP home page Giter VIP logo

buddy-sign's Introduction

buddy-sign

Travis Badge

A library that provides a high level message signing api for Clojure.

Clojars Project

See the documentation for more detailed information.

buddy-sign's People

Contributors

camsaul avatar coltnz avatar ddeaguiar avatar delitescere avatar dijonkitchen avatar dottedmag avatar ducky427 avatar eddies avatar estsauver avatar freekpaans avatar greut avatar grotewold avatar jamiei avatar lvh avatar metametadata avatar niwinz avatar nloadholtes avatar pupeno avatar rlovtangen avatar shilder avatar theikkila avatar tobytripp avatar tvanhens avatar v-kolesnikov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

buddy-sign's Issues

Call resolve-key in sign function on pkey

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.

The JWT 'aud' claim can be a collection.

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.

Don't throw exception if 'unsign' fails, just return data

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

Buddy JWT doesn't support the NONE algorithm

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))})))

Ring cookie store based on buddy-sign

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?

buddy.sign.jwt/unsign fails when hs256 is not used and alg is unspecified

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))))

[PERFORMANCE] - avoid reflective calls

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... 👍

I got error when I try a minimal example of signing key

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)

compact unsign throwing exceptions

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.

Any interest or help in parsing keys encoded in JWK?

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?

buddy-sign 0.10.0 includes user.clj

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.

JWS ECDSA Signatures are generated in incorrect format

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

Nimbus ECDSA.java

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

using :now option in jwt/unsign requires timestamp rather than DateTime

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).

JWT signatures invalid according to jwt.io website

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?

Buddy 1.0.0 throws exception for previously working code

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:

  1. Unhandled java.lang.IllegalArgumentException
    No implementation of method: :-to-bytes of protocol:

    'buddy.core.codecs/IByteArray found for class:

    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
    

Errors in using :eddsa algo for claims signing

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.

New release?

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)

Wording, spelling, grammar

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.

Can't sign a claims map

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?

Missing Implementation of -to-bytes on PersistentArrayMap

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.

Suggestions for resolving lein deps :tree?

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]]

Exception on unsigning with RS256

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

EdDSA JWS support

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,

Not able to decode HS256 JWT?

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)

buddy-sign needs a hard dependency on nippy 2.11.1

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.

`iss` validation should be against a collection ("whitelist")

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.

Claims validation in JWS

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.

Handle java.security.SignatureException for JWS applications

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

  1. Catch java.security.SignatureException and wrap it in ex-info in buddy.sign.jws/unsign
  2. Call on-error on all errors in token backends

I've implemented 1st solution for my project and it works pretty well for now see #57

Supporting custom headers

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"}})

Support for looking up public keys via OIDC discovery

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.

Provide some leniency in validating time claims

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.

nbf rule correct?

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

:hs512 throws NullPointerException. :hs256 works fine.

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?

Cannot get "kid" of JWT token from decode-header

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.

Documentation is slightly misleading

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.

apply-jdk8-extensions macro is evaluated at compile-time, so JARs compiled with Java 8 JDK don't work on Java 7

It looks like the apply-jdk8-extensions macro is getting evaled 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 :/

Cats dependency issue

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.