Skip to content

Commit 8cfaced

Browse files
authored
Merge pull request #115 from fukamachi/woo-ssl
SSL support with CL+SSL.
2 parents 1991c70 + ceb9030 commit 8cfaced

File tree

9 files changed

+217
-59
lines changed

9 files changed

+217
-59
lines changed

.github/workflows/ci.yml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ jobs:
1414
- uses: actions/checkout@v4
1515
- name: Install dependencies from APT
1616
run: sudo apt-get install -y libev-dev gcc libc6-dev
17+
- name: Generate server certificates
18+
run: sh ./t/generate-certificates.sh
1719
- name: Install Roswell
1820
env:
1921
LISP: ${{ matrix.lisp }}
@@ -24,6 +26,8 @@ jobs:
2426
run: ros -e '(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)'
2527
- name: Install Rove
2628
run: ros install rove
29+
- name: Install the latest Clack (for HTTPS testing with clack-test)
30+
run: ros install fukamachi/clack
2731
- name: Run tests
2832
env:
2933
LISP: ${{ matrix.lisp }}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ benchmark/benchmark.log
1212
.qlot/
1313
qlfile
1414
qlfile.lock
15+
t/certs/

src/ev/socket.lisp

+43-19
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
:socket-data
5050
:socket-read-cb
5151
:socket-open-p
52+
:socket-ssl-handle
5253
:check-socket-open
5354

5455
:write-socket-data
@@ -74,6 +75,7 @@
7475
(tcp-read-cb nil :type symbol)
7576
(read-cb nil :type (or null function))
7677
(write-cb nil :type (or null function))
78+
(ssl-handle nil :type (or null cffi:foreign-pointer))
7779
(open-p t :type boolean)
7880

7981
(buffer (make-output-buffer #+lispworks :output #+lispworks :static))
@@ -183,29 +185,51 @@
183185
(cffi:with-pointer-to-vector-data (data-sap data)
184186
(let* ((len (length data))
185187
(completedp nil)
186-
(n (wsys:write fd data-sap len)))
188+
(ssl-handle (socket-ssl-handle socket))
189+
(n
190+
#+woo-no-ssl
191+
(wsys:write fd data-sap len)
192+
#-woo-no-ssl
193+
(if ssl-handle
194+
(cl+ssl::ssl-write ssl-handle
195+
data-sap
196+
len)
197+
(wsys:write fd data-sap len))))
187198
(declare (type fixnum len)
188199
(type fixnum n))
189200
(case n
190201
(-1
191-
(let ((errno (wsys:errno)))
192-
(return-from flush-buffer
193-
(cond
194-
((or (= errno wsys:EWOULDBLOCK)
195-
(= errno wsys:EINTR))
196-
nil)
197-
((or (= errno wsys:ECONNABORTED)
198-
(= errno wsys:ECONNREFUSED)
199-
(= errno wsys:ECONNRESET)
200-
(= errno wsys:EPIPE)
201-
(= errno wsys:ENOTCONN))
202-
(vom:error "Connection is already closed (Code: ~D)" errno)
203-
(close-socket socket)
204-
t)
205-
(t
206-
(vom:error "Unexpected error (Code: ~D)" errno)
207-
(close-socket socket)
208-
t)))))
202+
(if ssl-handle
203+
#+woo-no-ssl (close-socket socket)
204+
#-woo-no-ssl
205+
(let ((errno (cl+ssl::ssl-get-error ssl-handle n)))
206+
(declare (type fixnum errno))
207+
(cond
208+
((or (= errno cl+ssl::+ssl-error-zero-return+)
209+
(= errno cl+ssl::+ssl-error-ssl+))
210+
(close-socket socket))
211+
((= errno cl+ssl::+ssl-error-want-write+))
212+
(t
213+
(vom:error "Unexpected error (Code: ~D)" errno)
214+
(close-socket socket))))
215+
(let ((errno (wsys:errno)))
216+
(return-from flush-buffer
217+
(cond
218+
((or (= errno wsys:EWOULDBLOCK)
219+
(= errno wsys:EINTR))
220+
nil)
221+
((or (= errno wsys:ECONNABORTED)
222+
(= errno wsys:ECONNREFUSED)
223+
(= errno wsys:ECONNRESET)
224+
(= errno wsys:EPIPE)
225+
(= errno wsys:ENOTCONN))
226+
(vom:error "Connection is already closed (Code: ~D)" errno)
227+
(close-socket socket)
228+
t)
229+
(t
230+
(vom:error "Unexpected error (Code: ~D)" errno)
231+
(close-socket socket)
232+
t))))))
209233
(otherwise
210234
(setf (socket-last-activity socket) (lev:ev-now *evloop*))
211235
(if (= n len)

src/ev/tcp.lisp

+39-17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
(:import-from :woo.ev.socket
1111
:make-socket
1212
:close-socket
13+
:socket-ssl-handle
1314
:socket-fd
1415
:socket-read-cb
1516
:socket-read-watcher
@@ -95,27 +96,48 @@
9596
(let* ((fd (io-fd watcher))
9697
(buffer-len (length *input-buffer*))
9798
(socket (deref-data-from-pointer fd))
98-
(read-cb (socket-read-cb socket)))
99+
(read-cb (socket-read-cb socket))
100+
(ssl-handle (socket-ssl-handle socket)))
99101
(loop
100-
(let ((n (wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len)))
102+
(let ((n
103+
#+woo-no-ssl
104+
(wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len)
105+
#-woo-no-ssl
106+
(if ssl-handle
107+
(cl+ssl::ssl-read ssl-handle (static-vectors:static-vector-pointer *input-buffer*) buffer-len)
108+
(wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len))))
101109
(declare (type fixnum n))
102110
(case n
103111
(-1
104-
(let ((errno (wsys:errno)))
105-
(cond
106-
((or (= errno wsys:EWOULDBLOCK)
107-
(= errno wsys:EINTR)))
108-
((or (= errno wsys:ECONNABORTED)
109-
(= errno wsys:ECONNREFUSED)
110-
(= errno wsys:ECONNRESET))
111-
(vom:error "Connection is already closed (Code: ~D)" errno)
112-
(close-socket socket))
113-
((= errno wsys:EAGAIN)
114-
;; Just to nothing
115-
)
116-
(t
117-
(vom:error "Unexpected error (Code: ~D)" errno)
118-
(close-socket socket))))
112+
(if ssl-handle
113+
#+woo-no-ssl (close-socket socket)
114+
#-woo-no-ssl
115+
(let ((errno (cl+ssl::ssl-get-error ssl-handle n)))
116+
(declare (type fixnum errno))
117+
(cond
118+
((or (= errno cl+ssl::+ssl-error-zero-return+)
119+
(= errno cl+ssl::+ssl-error-ssl+))
120+
(close-socket socket))
121+
((= errno cl+ssl::+ssl-error-want-read+))
122+
(t
123+
(vom:error "Unexpected error (Code: ~D)" errno)
124+
(close-socket socket))))
125+
(let ((errno (wsys:errno)))
126+
(declare (type fixnum errno))
127+
(cond
128+
((or (= errno wsys:EWOULDBLOCK)
129+
(= errno wsys:EINTR)))
130+
((or (= errno wsys:ECONNABORTED)
131+
(= errno wsys:ECONNREFUSED)
132+
(= errno wsys:ECONNRESET))
133+
(vom:error "Connection is already closed (Code: ~D)" errno)
134+
(close-socket socket))
135+
((= errno wsys:EAGAIN)
136+
;; Just to nothing
137+
)
138+
(t
139+
(vom:error "Unexpected error (Code: ~D)" errno)
140+
(close-socket socket)))))
119141
(return))
120142
(0
121143
;; EOF

src/ssl.lisp

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
(defpackage woo.ssl
2+
(:use :cl)
3+
(:import-from :cl+ssl
4+
:with-new-ssl
5+
:install-nonblock-flag
6+
:ssl-set-fd
7+
:ssl-set-accept-state
8+
:*default-cipher-list*
9+
:ssl-set-cipher-list
10+
:with-pem-password
11+
:install-key-and-cert)
12+
(:import-from :woo.ev.socket
13+
:socket-fd
14+
:socket-ssl-handle)
15+
(:export :init-ssl-handle))
16+
(in-package :woo.ssl)
17+
18+
(defun init-ssl-handle (socket ssl-cert-file ssl-key-file ssl-key-password)
19+
(let ((client-fd (socket-fd socket)))
20+
(with-new-ssl (handle)
21+
(install-nonblock-flag client-fd)
22+
(ssl-set-fd handle client-fd)
23+
(ssl-set-accept-state handle)
24+
(when *default-cipher-list*
25+
(ssl-set-cipher-list handle *default-cipher-list*))
26+
(setf (socket-ssl-handle socket) handle)
27+
(with-pem-password ((or ssl-key-password ""))
28+
(install-key-and-cert
29+
handle
30+
ssl-key-file
31+
ssl-cert-file))
32+
socket)))

src/woo.lisp

+66-18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
:socket-remote-addr
2121
:socket-remote-port
2222
:with-sockaddr)
23+
#-woo-no-ssl
24+
(:import-from :woo.ssl)
2325
(:import-from :woo.util
2426
:integer-string-p)
2527
(:import-from :quri
@@ -60,10 +62,14 @@
6062
(defvar *default-worker-num* nil)
6163

6264
(defun run (app &key (debug t)
63-
(port 5000) (address "127.0.0.1")
64-
listen ;; UNIX domain socket
65-
(backlog *default-backlog-size*) fd
66-
(worker-num *default-worker-num*))
65+
(port 5000) (address "127.0.0.1")
66+
listen ;; UNIX domain socket
67+
(backlog *default-backlog-size*) fd
68+
(worker-num *default-worker-num*)
69+
ssl-key-file
70+
ssl-cert-file
71+
ssl-key-password)
72+
(declare (ignorable ssl-key-password))
6773
(assert (and (integerp backlog)
6874
(plusp backlog)
6975
(<= backlog 128)))
@@ -76,8 +82,15 @@
7682

7783
(let ((*app* app)
7884
(*debug* debug)
79-
(*listener* nil))
85+
(*listener* nil)
86+
(ssl (or ssl-key-file ssl-cert-file)))
8087
(labels ((start-socket (socket)
88+
#-woo-no-ssl
89+
(when ssl
90+
(woo.ssl:init-ssl-handle socket
91+
ssl-cert-file
92+
ssl-key-file
93+
ssl-key-password))
8194
(setup-parser socket)
8295
(woo.ev.tcp:start-listening-socket socket))
8396
(start-multithread-server ()
@@ -120,6 +133,22 @@
120133
:fd fd
121134
:sockopt wsock:+SO-REUSEADDR+)))
122135
(wev:close-tcp-server *listener*))))))
136+
(when ssl
137+
#+woo-no-ssl
138+
(warn "SSL certificate is specified but Woo's SSL feature is off. Ignored.")
139+
#-woo-no-ssl
140+
(progn
141+
(cl+ssl::ensure-initialized)
142+
(when ssl-key-file
143+
(setf ssl-key-file
144+
(uiop:native-namestring
145+
(or (probe-file ssl-key-file)
146+
(error "SSL private key file '~A' does not exist." ssl-key-file)))))
147+
(when ssl-cert-file
148+
(setf ssl-cert-file
149+
(uiop:native-namestring
150+
(or (probe-file ssl-cert-file)
151+
(error "SSL certificate '~A' does not exist." ssl-cert-file)))))))
123152
(if worker-num
124153
(start-multithread-server)
125154
(start-singlethread-server)))))
@@ -346,19 +375,38 @@
346375
(setf (getf headers :content-length) 0))
347376
(write-response-headers socket status headers (not close))))
348377
(pathname
349-
(let* ((fd (wsys:open body))
350-
(size #+lispworks (sys:file-size body)
351-
#+(or sbcl ccl) (fd-file-size fd)
352-
#-(or sbcl ccl lispworks) (file-size body)))
353-
(unless (getf headers :content-length)
354-
(setf (getf headers :content-length) size))
355-
(unless (getf headers :content-type)
356-
(setf (getf headers :content-type) (mimes:mime body)))
357-
(wev:with-async-writing (socket :write-cb (and close
358-
(lambda (socket)
359-
(wev:close-socket socket))))
360-
(write-response-headers socket status headers (not close))
361-
(woo.ev.socket:send-static-file socket fd size))))
378+
(cond
379+
((woo.ev.socket:socket-ssl-handle socket)
380+
(with-open-file (in body :element-type '(unsigned-byte 8))
381+
(let ((size (file-length in)))
382+
(unless (getf headers :content-length)
383+
(setf (getf headers :content-length) size))
384+
(unless (getf headers :content-type)
385+
(setf (getf headers :content-type) (mimes:mime body)))
386+
(wev:with-async-writing (socket :write-cb (and close
387+
(lambda (socket)
388+
(wev:close-socket socket))))
389+
(write-response-headers socket status headers (not close))
390+
;; Future task: Use OpenSSL's SSL_sendfile which uses Kernel TLS.
391+
;; TODO: Stop allocating an input buffer every time
392+
(loop with buffer = (make-array 4096 :element-type '(unsigned-byte 8))
393+
for n = (read-sequence buffer in)
394+
do (wev:write-socket-data socket buffer :end n)
395+
while (= n 4096))))))
396+
(t
397+
(let* ((fd (wsys:open body))
398+
(size #+lispworks (sys:file-size body)
399+
#+(or sbcl ccl) (fd-file-size fd)
400+
#-(or sbcl ccl lispworks) (file-size body)))
401+
(unless (getf headers :content-length)
402+
(setf (getf headers :content-length) size))
403+
(unless (getf headers :content-type)
404+
(setf (getf headers :content-type) (mimes:mime body)))
405+
(wev:with-async-writing (socket :write-cb (and close
406+
(lambda (socket)
407+
(wev:close-socket socket))))
408+
(write-response-headers socket status headers (not close))
409+
(woo.ev.socket:send-static-file socket fd size))))))
362410
(list
363411
(wev:with-async-writing (socket :write-cb (and close
364412
(lambda (socket)

t/generate-certificates.sh

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
3+
mkdir t/certs
4+
cd t/certs
5+
6+
openssl genrsa -out localCA.key 2048
7+
openssl req -batch -new -key localCA.key -out localCA.csr \
8+
-subj "/C=JP/ST=Tokyo/L=Chuo-ku/O=\"Woo\"/OU=Development/CN=localhost"
9+
openssl x509 -req -days 3650 -signkey localCA.key -in localCA.csr -out localCA.crt
10+
openssl x509 -text -noout -in localCA.crt
11+
openssl genrsa -out localhost.key 2048
12+
openssl req -batch -new -key localhost.key -out localhost.csr \
13+
-subj "/C=JP/ST=Tokyo/L=Chuo-ku/O=\"Woo\"/OU=Development/CN=localhost"
14+
echo 'subjectAltName = DNS:localhost, DNS:localhost.localdomain, IP:127.0.0.1, DNS:app, DNS:app.localdomain' > localhost.csx
15+
openssl x509 -req -days 1825 -CA localCA.crt -CAkey localCA.key -CAcreateserial -in localhost.csr -extfile localhost.csx -out localhost.crt

t/woo.lisp

+8
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@
66

77
(deftest woo-server-tests
88
(clack.test.suite:run-server-tests :woo))
9+
10+
(deftest woo-ssl-server-tests
11+
(let ((clack.test:*clackup-additional-args*
12+
'(:ssl-cert-file #P"t/certs/localhost.crt"
13+
:ssl-key-file #P"t/certs/localhost.key"))
14+
(dex:*not-verify-ssl* t)
15+
(clack.test:*use-https* t))
16+
(clack.test.suite:run-server-tests :woo)))

woo.asd

+9-5
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
"trivial-mimes"
1818
"vom"
1919
"alexandria"
20-
#+sbcl "sb-posix"
21-
#+(and linux (not asdf3)) "uiop"
22-
#+sbcl "sb-concurrency"
23-
#-sbcl "cl-speedy-queue")
20+
(:feature :sbcl "sb-posix")
21+
(:feature (:and :linux (:not :asdf3)) "uiop")
22+
(:feature :sbcl "sb-concurrency")
23+
(:feature (:not :sbcl) "cl-speedy-queue")
24+
(:feature (:not :woo-no-ssl) "cl+ssl"))
2425
:components ((:module "src"
2526
:components
26-
((:file "woo" :depends-on ("ev" "response" "worker" "signal" "specials" "util"))
27+
((:file "woo" :depends-on ("ev" "response" "worker" "ssl" "signal" "specials" "util"))
2728
(:file "response" :depends-on ("ev"))
2829
(:file "ev" :depends-on ("ev-packages"))
2930
(:file "worker" :depends-on ("ev" "queue" "specials"))
@@ -37,6 +38,9 @@
3738
(:file "tcp" :depends-on ("event-loop" "socket" "util" "condition"))
3839
(:file "condition")
3940
(:file "util")))
41+
(:file "ssl"
42+
:depends-on ("ev-packages")
43+
:if-feature (:not :woo-no-ssl))
4044
(:module "llsocket"
4145
:depends-on ("syscall")
4246
:serial t

0 commit comments

Comments
 (0)