Skip to content
This repository was archived by the owner on Jul 24, 2018. It is now read-only.

Support for specifying trust-store #16

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ More example requests:
;; Need to contact a server with an untrusted SSL cert?
(client/get "https://alioth.debian.org" {:insecure? true})

;; Need to specify a trust store?
(client/get "https://my.corp.com" {:trust-store "/path/to/trust-store.jks"
:trust-store-type "jks" ; default jks
:trust-store-pass "trustpass"
:security-protocol "TLS" ; default TLS})

;; If you don't want to follow-redirects automatically:
(client/get "http://site.come/redirects-somewhere" {:follow-redirects false})

Expand Down
40 changes: 32 additions & 8 deletions src/clj_http/lite/core.clj
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
(ns clj-http.lite.core
"Core HTTP request/response implementation."
(:require [clojure.java.io :as io])
(:import (java.io ByteArrayOutputStream InputStream IOException)
(java.net URI URL HttpURLConnection)))
(:import [java.io ByteArrayOutputStream InputStream IOException]
[java.net URI URL HttpURLConnection]
[javax.net.ssl HttpsURLConnection SSLContext TrustManagerFactory]
[java.security KeyStore]))

(defn parse-headers
"Takes a URLConnection and returns a map of names to values.
Expand Down Expand Up @@ -39,6 +41,25 @@
(.flush baos)
(.toByteArray baos)))))

(defn get-connection [url]
(.openConnection ^URL (URL. url)))

(defn set-trust-store
[^HttpsURLConnection conn
{:keys [trust-store trust-store-pass trust-store-type security-protocol]
:or {trust-store-type "jks" security-protocol "TLS"}}]
(let [ssl-context (SSLContext/getInstance security-protocol)
key-store (KeyStore/getInstance trust-store-type)
trust-manager-factory (TrustManagerFactory/getInstance "SunX509")]
(.load key-store (io/input-stream
(or (io/resource trust-store)
(io/file trust-store)))
(char-array trust-store-pass))
(.init trust-manager-factory key-store)
(.init ssl-context nil (.getTrustManagers trust-manager-factory) nil)
(.setSSLSocketFactory conn (.getSocketFactory ssl-context))
conn))

(defn request
"Executes the HTTP request corresponding to the given Ring request map and
returns the Ring response map corresponding to the resulting HTTP response.
Expand All @@ -48,12 +69,15 @@
[{:keys [request-method scheme server-name server-port uri query-string
headers content-type character-encoding body socket-timeout
conn-timeout multipart debug insecure? save-request? follow-redirects
chunk-size] :as req}]
chunk-size trust-store trust-store-pass] :as req}]
(let [http-url (str (name scheme) "://" server-name
(when server-port (str ":" server-port))
uri
(when query-string (str "?" query-string)))
conn (.openConnection ^URL (URL. http-url))]
^HttpURLConnection conn
(cond-> (get-connection http-url)
(and (= scheme :https) trust-store trust-store-pass)
(set-trust-store req))]
(when (and content-type character-encoding)
(.setRequestProperty conn "Content-Type" (str content-type
"; charset="
Expand All @@ -63,8 +87,8 @@
(doseq [[h v] headers]
(.setRequestProperty conn h v))
(when (false? follow-redirects)
(.setInstanceFollowRedirects ^HttpURLConnection conn false))
(.setRequestMethod ^HttpURLConnection conn (.toUpperCase (name request-method)))
(.setInstanceFollowRedirects conn false))
(.setRequestMethod conn (.toUpperCase (name request-method)))
(when body
(.setDoOutput conn true))
(when socket-timeout
Expand All @@ -78,9 +102,9 @@
(with-open [out (.getOutputStream conn)]
(io/copy body out)))
(merge {:headers (parse-headers conn)
:status (.getResponseCode ^HttpURLConnection conn)
:status (.getResponseCode conn)
:body (when-not (= request-method :head)
(coerce-body-entity req conn))}
(when save-request?
{:request (assoc (dissoc req :save-request?)
:http-url http-url)}))))
:http-url http-url)}))))
12 changes: 12 additions & 0 deletions test-resources/README.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
* Generate CA and import into keystore and truststore
openssl req -new -x509 -keyout ca-key.pem -out ca-cert.pem -days 3650
keytool -import -keystore truststore.jks -file ca-cert.pem
keytool -import -keystore keystore.jks -file ca-cert.pem

* Generate key and CSR for server (Jetty running on localhost)
keytool -keystore keystore.jks -genkey -alias localhost
keytool -keystore keystore.jks -certreq -alias localhost -keyalg rsa -file localhost.csr

* Sign CSR with CA cert and import into keystore
openssl x509 -req -CA ca-cert.pem -CAkey ca-key.pem -in localhost.csr -out localhost.cer -days 3650 -CAcreateserial
keytool -import -keystore keystore.jks -file localhost.cer -alias localhost
21 changes: 21 additions & 0 deletions test-resources/ca-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDiDCCAnCgAwIBAgIJANa5pPKrnuBoMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xODA2MTQyMjM4
MjRaFw0yODA2MTEyMjM4MjRaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMtf
R79GuNTxPZcISS/iSSC0ejkUAhSysQb0a3zSvKCGZmxFtV0LtgIQFS/KlyTIKokr
BIV5ToOB6Zy7jc7bKoCaPvEPjT8i8rnjCUtFb+XFITdi0TI+macYb2/CCgrY7ePG
aewO+kK20eVeSjygPgi8svEsdVyIpH8PEuAxCdjsCo8F3EHQHhrokEqUh+rLA9AX
A0uhJAkiPUGdxozE5L68WrvUs8GD9I8QmCYBldhvjD5nI5NFVDF8yZduWX6NuX5g
1etxZ86ob1Tcy4BtVVYskCjHuT57mMFdspsC59cUiM1RUYOia/GkgDK38An2NJyN
OEr3MC77m1qP0XOvJ/0CAwEAAaNTMFEwHQYDVR0OBBYEFAvylOxjTNSDjCqBlncO
HBaDc5L5MB8GA1UdIwQYMBaAFAvylOxjTNSDjCqBlncOHBaDc5L5MA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFCVTNyT4RR64lEe03y487s8efyp
HX9kxbS2PyUx327PakqVhu/6EU1VWEI8FIwxFYQB+WpTjh5qB/8y1Y4TMWAD7VRH
MsVzf3k4Wf+jH0GaO9ZNI4NSXXlZHKjxCb+ahQOcnEFXRog6f1locJIcJlNZ6JEJ
Tncnehe5YH0YhBhRZuR46ACExMSjfF+6rymxJnQg1KkGQaA7ypu79PF9wcc/tzfR
RFMP8esEQARWDYQRuA9Ms2DDDYE5lZx0QYjh/ljnfNxmimmRtg7TFkpadkaAChHj
jEaoDHPe5y4ktrK6N5snKDX0XhLLTQptntYUOugT/NCylqaATRUQVTQNCSY=
-----END CERTIFICATE-----
1 change: 1 addition & 0 deletions test-resources/ca-cert.srl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
E9DE7EDC3590114D
30 changes: 30 additions & 0 deletions test-resources/ca-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQILkQiXuciC7QCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECBYw8AvZ5P56BIIEyB+Uh5zShr1o
fSL1oMlKOizKDkfNYNc2k/lV4I/X/vCARSAKa5/bU3dAh82p16Te3Z1N99X7LWTk
Bd2WrcIjZxm8fC20ew/zD4/fVKOI/t56WUV6tavNyEOfLhzgrtWv7eo5an65V1QW
eYS9syDCaHocQZzI96m+dyDKy7cJMUaVJIIlasGEuV0Lt2pkg6WfMHJbmd5wyqyM
/tsx8aIkZlDVvXdddI+OfQ7Pa0LlTqXfuYqmEuez4Uz6uP6EaRnOyXlbWhtJeMAG
NaRQ95Ew339k+4j2N/o45DLWXORiBZxCD0YmyioZiFEvA4BnzzJ/R2BtY7v3zj+X
S7puFG21Lwe2zb1PQoKljVeJVWwbCaOzL3E+oXHj7xz26OxYhGT6xn0YT6HJaOjE
1Np9Kv+UeZi+zw2C8s7ru1b0UaIoRpwNH4+d39N8lBEaBlW2byZ+hV51BwbnLVOs
t5G2P8ppKsg8hSlxAw6hgyiTqXvvo4BX1U9aBvznt8dUI35dzhBG4G0u3UrYLEw0
eYMecbSR12xQ1kRf+gO+g1uxsMY5odnz9I0WhwVGzEyjKdAXdQf/xZOtb+A2bUZx
RO/Ir1qSr58A1kUOyTWhv4EWxhviStqc9P0jQlA0olkkkZ2OULlXKh49Qemukbms
Og6jHIAljsbh4hZfyKFSUJJGC3GftoaMYErLcGJw3BmEavZcFM1z4FWu6HoQ5nEP
aDtGf0/ZN653VE0YRlmjxL85o7V3ZkOGakdTZnNaFi8wEv0nMCq+DbN5LghWokyM
bSdus2hmZr2j7fScYvdh+TBAOO73+ziVVM34SHqmcUYuuH4vv94GJwYLo3LUbDLL
uIQFJmBXekVpZ3l3HqSge5JYMXQ5vM11cJpjFrnyCNDISnunGpfDc2TGn4/CRQ4v
1EjwZLoWiQBsiw3FnKCqSLtl6pHcyMgWS/wkeQqHOcOyK4bFKBhtfe/Yp/xsTLt5
6n7Yo0VGe6qcRTA5H+fkMRzVlFmrmqB6eN9Md85YQ7bK6nfOg2jbUMejHJUTxPC3
BhRq0rMQXDhHvdomzxijWSznwtZs31UiG3Zis52ua4/Ux3K1yTKXVbfWqBhMNuVW
r7pvsTuLh+cdZQe4dxQgZXTNdVqrKa3yE0h6zbhBPlV1PXGzbSw8RSK0wYhJr167
MPN7S74YJTjV2ujhLD4cUEONACXbLI2FE3jx2lLKbFN/Ih421x1lJWtADWnT6CDB
19/kmNyI2bCkYGomoQ9qaVw7p7a0d9OV5VRXavEn7scAhOj4/cNrAukvedionpIx
autWzyZAyAOpmGCTtlRnImDBfs8tpWqf36qfAxIM3gZlKGB2cijNAKbYEld9XzFX
Kw0CWvZro3/QOkqabtcwvcoupxX2mZsju5uahlJwZ1xQFz4j6svtYwvpMRpySI+K
mzpRcDhNsY9vY3lKHHYw8ModxIZ1WlHz5xf9FsrQEsKpseZXy7ItxtIimu3saufj
6XHvQl4K88/W4d2Q+WoUIkhK1sU4mcad3WXjxhOEgboNqsV41U5QJTAYOTecdfTp
PcrHf/EFwwiePRruw90DiGB+fCqBGl5B3bpUORl0wjpSXHfxW3RBpA0KkXByeB4C
wm31ey9mnr8ylJ1p6chHog==
-----END ENCRYPTED PRIVATE KEY-----
Binary file removed test-resources/keystore
Binary file not shown.
Binary file added test-resources/keystore.jks
Binary file not shown.
31 changes: 31 additions & 0 deletions test-resources/localhost.cer
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFYzCCBEsCCQDp3n7cNZARTTANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwNjE0MjI1NjIwWhcN
MjgwNjExMjI1NjIwWjBuMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtu
b3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQL
EwdVbmtub3duMRIwEAYDVQQDEwlsb2NhbGhvc3QwggNCMIICNQYHKoZIzjgEATCC
AigCggEBAI95Ndm5qum/q+2Ies9JUbbzLsWeO683GOjqxJYfPv02BudDUanEGDM5
uAnnwq4cU5unR1uF0BGtuLR5h3VJhGlcrA6PFLM2CCiiL/onEQo9YqmTRTQJoP5p
bEZY+EvdIIGcNwmgEFexla3NACM9ulSEtikfnWSO+INEhneXnOwEtDSmrC516Zhd
4j2wKS/BEYyf+p2BgeczjbeStzDXueNJWS9oCZhyFTkV6j1ri0ZTxjNFj4A7MqTC
4PJykCVuTj+KOwg4ocRQ5OGMGimjfd9eoUPeS2b/BJA+1c8WI+FY1IfGCOl/IRzY
Hcojy244B2X4IuNCvkhMBXY5OWAc1mcCHQC69pamhXj3397n+mfJd8eF7zKyM7rl
gMC81WldAoIBABamXFggSFBwTnUCo5dXBA002jo0eMFU1OSlwC0kLuBPluYeS9CQ
Sr2sjzfuseCfMYLSPJBDy2QviABBYO35ygmzIHannDKmJ/JHPpGHm6LE50S9IIFU
TLVbgCw2jR+oPtSJ6U4PoGiOMkKKXHjEeMaNBSe3HJo6uwsL4SxEaJY559POdNsQ
GmWqK4f2TGgm2z7HL0tVmYNLtO2wL3yQ6aSW06VdU1vr/EXU9hn2Pz3tu4c5JcLy
JOB3MSltqIfsHkdI+H77X963VIQxayIy3uVT3a8CESsNHwLaMJcyJP4nrtqLnUsp
Itm6i+Oe2eEDpjxSgQvGiLfi7UMW4e8X294DggEFAAKCAQBj5iGy71+T7dyNUGYR
4tcboG43+q3i96dH+ft7opHNDBqPhAUo++YukC9Q9leMmyqjd8be5VGkPGxjePuS
wN6mQK2ilzAyTGUL2ICJdKE7U0b8AMy42rjoZVGwkRkpfahUkgfEB10TtS+AlbIZ
1azN98V+7yXwYPe1J5ioU9jDGMNC717ySBjORHIlOVmGOLK4Uylo2gW03he1nkAb
BxXoJATgV3VqwddJb5DMJoB0o5jwyMzL/cZHSABhukdrgK/KdAonVy0wzDQyScIj
3NIRCiDhFFGu0ZsruSG/vfNl1QLHToPzhgGIuGDO471hRP7Y1b/CE7eIzViyreTZ
LZW3MA0GCSqGSIb3DQEBCwUAA4IBAQB5cyUQA4maQogpheAwIsHD1R7TQFApu79N
1fKlvGGYutVo0EYv+FvIZsVmkoKKF725xXfjCfFOwIlGF6OrK/3xJNWBrSZr/jHs
1hOzdP0/Kmp63U2jfhBqn/7PiWYSvLUnwx7hj2qKhoGXhrXGTEs2kSyiRt92UFLM
9u+sajB2Wn0P3lxFHFI4Djv5uq4/Gb/Qjt2hJsB3W5yUdTgdpqyghDzrblXhrkhp
xQJhMb2pVLI7ZaJgBst0Ymllf4PHry1PF7WcgG0RlsTR8Xjh9YFcmnSAw2Hp/eDY
lsZBbkdZbjgqOifTGqBJ5RbTL3QFR3UFw3lMkTxDXYHzvwJ+iVhd
-----END CERTIFICATE-----
25 changes: 25 additions & 0 deletions test-resources/localhost.csr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-----BEGIN NEW CERTIFICATE REQUEST-----
MIIEQDCCA+sCAQAwbjEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93
bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMH
VW5rbm93bjESMBAGA1UEAxMJbG9jYWxob3N0MIIDQjCCAjUGByqGSM44BAEwggIo
AoIBAQCPeTXZuarpv6vtiHrPSVG28y7FnjuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ
58KuHFObp0dbhdARrbi0eYd1SYRpXKwOjxSzNggooi/6JxEKPWKpk0U0CaD+aWxG
WPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYpH51kjviDRIZ3l5zsBLQ0pqwudemYXeI9
sCkvwRGMn/qdgYHnM423krcw17njSVkvaAmYchU5Feo9a4tGU8YzRY+AOzKkwuDy
cpAlbk4/ijsIOKHEUOThjBopo33fXqFD3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3K
I8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0AuvaWpoV499/e5/pnyXfHhe8ysjO65YDA
vNVpXQKCAQAWplxYIEhQcE51AqOXVwQNNNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9
rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJsyB2p5wypifyRz6Rh5uixOdEvSCBVEy1
W4AsNo0fqD7UielOD6BojjJCilx4xHjGjQUntxyaOrsLC+EsRGiWOefTznTbEBpl
qiuH9kxoJts+xy9LVZmDS7TtsC98kOmkltOlXVNb6/xF1PYZ9j897buHOSXC8iTg
dzEpbaiH7B5HSPh++1/et1SEMWsiMt7lU92vAhErDR8C2jCXMiT+J67ai51LKSLZ
uovjntnhA6Y8UoELxoi34u1DFuHvF9veA4IBBQACggEAY+Yhsu9fk+3cjVBmEeLX
G6BuN/qt4venR/n7e6KRzQwaj4QFKPvmLpAvUPZXjJsqo3fG3uVRpDxsY3j7ksDe
pkCtopcwMkxlC9iAiXShO1NG/ADMuNq46GVRsJEZKX2oVJIHxAddE7UvgJWyGdWs
zffFfu8l8GD3tSeYqFPYwxjDQu9e8kgYzkRyJTlZhjiyuFMpaNoFtN4XtZ5AGwcV
6CQE4Fd1asHXSW+QzCaAdKOY8MjMy/3GR0gAYbpHa4CvynQKJ1ctMMw0MknCI9zS
EQog4RRRrtGbK7khv73zZdUCx06D84YBiLhgzuO9YUT+2NW/whO3iM1Ysq3k2S2V
t6AwMC4GCSqGSIb3DQEJDjEhMB8wHQYDVR0OBBYEFP+8yPg7WQpeC6R7LxYkW/FW
C21UMA0GCWCGSAFlAwQDAgUAA0AAMD0CHBBBmwajAf0D5p7cRxAj4iq/DUTMOyBu
JZBDhcQCHQCcd1nksVdQIHO7f27vJ4u0nC/1gm6IBuwhnocn
-----END NEW CERTIFICATE REQUEST-----
Binary file added test-resources/truststore.jks
Binary file not shown.
28 changes: 22 additions & 6 deletions test/clj_http/test/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -138,19 +138,35 @@
(deftest ^{:integration true} self-signed-ssl-get
(let [t (doto (Thread. #(ring/run-jetty handler
{:port 8081 :ssl-port 18082 :ssl? true
:keystore "test-resources/keystore"
:key-password "keykey"})) .start)]
:keystore "test-resources/keystore.jks"
:key-password "changeit"})) .start)]
(Thread/sleep 1000)
(try
(is (thrown? javax.net.ssl.SSLException
(request {:request-method :get :uri "/get"
:server-port 18082 :scheme :https})))
#_(let [resp (request {:request-method :get :uri "/get" :server-port 18082
:scheme :https :insecure? true})]
(is (= 200 (:status resp)))
(is (= "get" (slurp-body resp))))
:scheme :https :insecure? true})]
(is (= 200 (:status resp)))
(is (= "get" (slurp-body resp))))
(finally
(.stop t)))))
(.stop t)))))

(deftest ^{:integration true} self-signed-ssl-get-with-trust-store
(let [t (doto (Thread. #(ring/run-jetty handler
{:port 8082 :ssl-port 18083 :ssl? true
:keystore "test-resources/keystore.jks"
:key-password "changeit"})) .start)]
(Thread/sleep 1000)
(try
(let [resp (request {:request-method :get :uri "/get" :server-port 18083
:scheme :https
:trust-store "test-resources/truststore.jks"
:trust-store-pass "changeit"})]
(is (= 200 (:status resp)))
(is (= "get" (slurp-body resp))))
(finally
(.stop t)))))

;; (deftest ^{:integration true} multipart-form-uploads
;; (run-server)
Expand Down