-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
317 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
(ns com.palletops.docker.cert | ||
(:require | ||
[clj-time.core :as time] | ||
[clj-time.coerce :refer [to-date to-long]] | ||
[com.palletops.docker.jwk :refer [key-id]]) | ||
(:import | ||
[java.security PublicKey] | ||
[java.security.cert Certificate] | ||
[sun.security.util ObjectIdentifier] | ||
[sun.security.x509 | ||
AlgorithmId CertificateAlgorithmId CertificateExtensions | ||
CertificateIssuerName | ||
CertificateSerialNumber CertificateSubjectName CertificateValidity | ||
CertificateX509Key CertificateVersion | ||
BasicConstraintsExtension ExtendedKeyUsageExtension | ||
X500Name X509CertImpl X509CertInfo])) | ||
|
||
(def CRITICAL true) | ||
(def NON_CRITICAL false) | ||
(def server-auth-oid (ObjectIdentifier. "1.3.6.1.5.5.7.3.1")) | ||
(def client-auth-oid (ObjectIdentifier. "1.3.6.1.5.5.7.3.2")) | ||
|
||
(defn ^Certificate new-cert | ||
"Return a new X509 Certificate, signed by `key`." | ||
[{:keys [key ^PublicKey public-key | ||
client-auth server-auth | ||
ca max-path | ||
not-before not-after] | ||
:or {not-before (time/minus (time/now) (time/days 7)) | ||
not-after (time/plus (time/now) (time/years 10)) | ||
max-path 1024}}] | ||
(let [interval (CertificateValidity. (to-date not-before) (to-date not-after)) | ||
algo (AlgorithmId. AlgorithmId/sha256WithECDSA_oid) | ||
v (filter identity | ||
[(if server-auth server-auth-oid) | ||
(if client-auth client-auth-oid)]) | ||
_ (println "extensions" v) | ||
ext (doto (CertificateExtensions.) | ||
(.set ExtendedKeyUsageExtension/NAME | ||
(ExtendedKeyUsageExtension. | ||
NON_CRITICAL (java.util.Vector. ^java.util.List v)))) | ||
_ (when ca | ||
(.set BasicConstraintsExtension/NAME | ||
(BasicConstraintsExtension. CRITICAL ca max-path))) | ||
cert-info (doto (X509CertInfo.) | ||
(.set X509CertInfo/VERSION | ||
(CertificateVersion. CertificateVersion/V3)) | ||
(.set X509CertInfo/VALIDITY | ||
interval) | ||
(.set X509CertInfo/SERIAL_NUMBER | ||
(CertificateSerialNumber. 0)) | ||
(.set (str X509CertInfo/ISSUER | ||
"." CertificateSubjectName/DN_NAME) | ||
(X500Name. (str "CN=" (key-id public-key)))) | ||
(.set (str X509CertInfo/SUBJECT | ||
"." CertificateSubjectName/DN_NAME) | ||
(X500Name. (str "CN=" (key-id public-key)))) | ||
(.set X509CertInfo/KEY (CertificateX509Key. public-key)) | ||
(.set X509CertInfo/EXTENSIONS ext) | ||
(.set X509CertInfo/ALGORITHM_ID | ||
(CertificateAlgorithmId. algo)))] | ||
(doto (X509CertImpl. cert-info) | ||
(.sign key "SHA256WithECDSA")))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
(ns com.palletops.docker.identity-auth | ||
"Identity auth scheme as used by docker" | ||
(:import | ||
[java.net InetAddress Socket] | ||
[java.security KeyStore SecureRandom] | ||
[java.security.cert X509Certificate] | ||
[javax.net.ssl KeyManagerFactory SSLContext SSLSocket X509TrustManager])) | ||
|
||
(defn ^X509TrustManager accept-all-trust-manager | ||
[] | ||
(reify X509TrustManager | ||
(checkServerTrusted [_ chain autj-type]) | ||
(getAcceptedIssuers [_]))) | ||
|
||
(defn ^X509Certificate server-cert | ||
"Obtain a docker server's X509 Certificate using docker's identity | ||
auth scheme. This creates a connection to the docker server. In | ||
order to obtain the server certificate, it does not verify the | ||
server certificate. | ||
You can add the returned certificate to a trust store to verify further | ||
connections to the server." | ||
[^String host ^Integer port ^KeyStore keystore ^chars keystore-pw] | ||
(let [key-mgr-factory (doto (KeyManagerFactory/getInstance | ||
(KeyManagerFactory/getDefaultAlgorithm)) | ||
(.init keystore keystore-pw)) | ||
context (doto (SSLContext/getInstance "TLS") | ||
(.init (.getKeyManagers key-mgr-factory) | ||
(into-array X509TrustManager | ||
[(accept-all-trust-manager)]) | ||
(SecureRandom.))) | ||
socket-factory (.getSocketFactory context) | ||
addr (InetAddress/getByName host) | ||
^SSLSocket socket (.createSocket socket-factory addr port) | ||
server-cert (first (.getPeerCertificates (.getSession socket)))] | ||
(.close socket) | ||
server-cert)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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 ":"))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
(ns com.palletops.docker.keystore | ||
"Create keystore based on docker machine and boot2docker cert files." | ||
(:require | ||
[clojure.data.codec.base64 :refer [decode]] | ||
[clojure.java.io :as io] | ||
[clojure.string :as string] | ||
[com.palletops.docker.cert :refer [new-cert]] | ||
[com.palletops.docker.jwk :refer [load-jwk]]) | ||
(:import | ||
java.io.File | ||
[java.security Key KeyFactory KeyStore] | ||
[java.security.cert Certificate CertificateFactory X509Certificate] | ||
[net.oauth.signature.pem PKCS1EncodedKeySpec])) | ||
|
||
(defn ^Key load-key | ||
"Load a key from a PKCS#1 encoded pem file." | ||
[^File key-file] | ||
(let [der (with-open [s (java.io.BufferedInputStream. | ||
(io/input-stream key-file))] | ||
(let [chars (slurp s) | ||
b64chars (->> (string/split-lines chars) | ||
(remove #(re-matches #"---.*---" %)) | ||
string/join)] | ||
(decode (.getBytes b64chars)))) | ||
key-spec (PKCS1EncodedKeySpec. der) | ||
key-factory (KeyFactory/getInstance "RSA") | ||
key (.generatePrivate key-factory (.getKeySpec key-spec))] | ||
key)) | ||
|
||
(defn ^X509Certificate load-cert | ||
"Load a X509Certificate from a pem file." | ||
[^CertificateFactory cert-factory ^File path] | ||
(with-open [s (java.io.BufferedInputStream. (io/input-stream path))] | ||
(.generateCertificate cert-factory s))) | ||
|
||
(defn cert-path-key-and-certs | ||
"Return a key and certificate chain based on the files in cert-path." | ||
[cert-path] | ||
(let [ca-f (io/file cert-path "ca.pem")] | ||
(if (.exists ca-f) | ||
(let [cert-factory (CertificateFactory/getInstance "X.509") | ||
ca-cert (load-cert cert-factory ca-f) | ||
server-cert (load-cert cert-factory (io/file cert-path "cert.pem")) | ||
key (load-key (io/file cert-path "key.pem"))] | ||
[key [server-cert ca-cert]]) | ||
(throw (ex-info (str "No such file: " (str ca-f)) {:file (str ca-f)}))))) | ||
|
||
(defn ^String key-store | ||
"Return a JKS format keystore, containing the creds specified in | ||
files in cert-path." | ||
[cert-path] | ||
(let [ks-path (io/file cert-path "client.ks") | ||
ks (KeyStore/getInstance "JKS") | ||
pw (.toCharArray "")] | ||
(if (and (.exists ks-path) (pos? (.length ks-path))) | ||
[(str ks-path) (doto ks | ||
(.load (io/input-stream ks-path) pw))] | ||
(let [[key [server-cert ca-cert]] (cert-path-key-and-certs cert-path)] | ||
(doto ks | ||
(.load nil pw) | ||
(.setEntry | ||
"ca" | ||
(java.security.KeyStore$TrustedCertificateEntry. ca-cert) | ||
nil) | ||
(.setEntry | ||
"server" | ||
(java.security.KeyStore$TrustedCertificateEntry. server-cert) | ||
nil) | ||
(.setKeyEntry | ||
"client" key pw | ||
(into-array X509Certificate [server-cert ca-cert]))) | ||
(with-open [os (io/output-stream ks-path)] | ||
(.store ks os pw)) | ||
[(str ks-path) ks])))) | ||
|
||
(defn jwk-key-and-cert | ||
"Return the private key specified by jwk-path. The adjacent public | ||
key is also read and returned. A self-signed X509Certificate is | ||
create as a client certificate and returned." | ||
[jwk-path] | ||
(let [f (io/file jwk-path)] | ||
(if (.exists f) | ||
(let [key (load-jwk f) | ||
public-key (load-jwk | ||
(io/file (.getParent f) "public-key.json")) | ||
cert (new-cert {:key key | ||
:public-key public-key | ||
:client-auth true | ||
:server-auth false})] | ||
|
||
[key public-key cert]) | ||
(throw (ex-info (str "No such file: " (str f)) {:file (str f)}))))) | ||
|
||
|
||
(defn ^String key-store-jwk | ||
"Return a JKS format keystore, containing the orivate key specified | ||
by jwk-path. The adjacent public key is also read and a self-signed | ||
X509Certificate is create as a client certificate." | ||
[jwk-path] | ||
(let [ks-path (io/file (.getParentFile (io/file jwk-path)) "client.ks") | ||
ks (KeyStore/getInstance "JKS") | ||
pw (.toCharArray "")] | ||
(if (and (.exists ks-path) (pos? (.length ks-path))) | ||
[(str ks-path) (doto ks | ||
(.load (io/input-stream ks-path) pw))] | ||
(let [[key public-key cert] (jwk-key-and-cert jwk-path)] | ||
(doto ks | ||
(.load nil pw) | ||
(.setEntry | ||
"client" | ||
(java.security.KeyStore$TrustedCertificateEntry. cert) | ||
nil) | ||
(.setKeyEntry "clientkey" key pw | ||
(into-array X509Certificate [cert]))) | ||
(with-open [os (io/output-stream ks-path)] | ||
(.store ks os pw)) | ||
[(str ks-path) ks])))) | ||
|
||
(defn add-cert | ||
"Add a cert to the specified keystore" | ||
[ks-path ^KeyStore ks ^X509Certificate cert] | ||
(.setEntry | ||
ks "server" (java.security.KeyStore$TrustedCertificateEntry. cert) nil) | ||
(with-open [os (io/output-stream ks-path)] | ||
(.store ks os (.toCharArray "")))) |