/
jwk.clj
78 lines (69 loc) · 2.53 KB
/
jwk.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
(ns com.palletops.docker.jwk
"JSON Web Key Functions"
(:require
[clojure.data.codec.base64 :refer [decode]]
[clojure.java.io :as io]
[clojure.string :as string]
[cheshire.core :refer [parse-stream]])
(:import
java.io.File
[java.security AlgorithmParameters Key KeyFactory MessageDigest]
[java.security.spec
ECGenParameterSpec ECParameterSpec ECPoint ECPrivateKeySpec ECPublicKeySpec]
[org.apache.commons.codec.binary Base32]))
(defmulti curve-name
"Return a JSSE curve name for the given JWK curve name."
(fn [id] id))
(defmethod curve-name :default [id]
(throw (ex-info (str "Unimplemented elliptic curve " id) {:id id})))
(defmethod curve-name "P-256" [_] "secp256r1")
;; http://mail.openjdk.java.net/pipermail/security-dev/2013-October/009107.html
(defn named-curve-spec
[curve-name]
(let [parameters (AlgorithmParameters/getInstance "EC", "SunEC")
gen-spec (ECGenParameterSpec. curve-name)]
(.init parameters gen-spec)
(.getParameterSpec parameters ECParameterSpec)))
(defn base64url->base64
[^String s]
(let [s (-> s
(string/replace \- \+)
(string/replace \_ \/))]
(case (int (mod (count s) 4))
0 (str s "==")
2 (str s "==")
3 (str s "=")
(throw
(ex-info "Invalid base64 string" {:string s})))))
(def base64url->BigInteger
(comp
#(BigInteger. ^bytes %)
decode
#(.getBytes ^String %)
base64url->base64))
(defn ^Key jwk->key
"Convert a JWK map specifying an EC key to a Key"
[{:keys [crv d x y] :as jwk}]
(let [spec (named-curve-spec (curve-name crv))
kf (KeyFactory/getInstance "EC")]
(if d
(.generatePrivate kf (ECPrivateKeySpec. d spec))
(.generatePublic kf (ECPublicKeySpec. (ECPoint. x y) spec)))))
(defn ^Key load-jwk [^File key-file]
(let [{:keys [crv d kid kty x y] :as json} (with-open [r (io/reader key-file)]
(parse-stream r keyword))
json (-> json
(cond-> (:d json) (update-in [:d] base64url->BigInteger))
(cond-> (:m json) (update-in [:m] base64url->BigInteger))
(update-in [:x] base64url->BigInteger)
(update-in [:y] base64url->BigInteger))]
(when-not (= kty "EC")
(throw (ex-info "Unsupported key type" json)))
(jwk->key json)))
(defn key-id [^Key key]
(->> (.digest (MessageDigest/getInstance "SHA-256") (.getEncoded key))
(.encodeAsString (Base32.))
(partition 4)
(take 12)
(map #(apply str %))
(string/join ":")))