From 223191ed62a35a7536eed06ef1786a1ad0900aa8 Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sat, 10 Aug 2024 17:25:22 +0000 Subject: [PATCH 1/9] [WIP] SSL support with CL+SSL. --- src/ev/socket.lisp | 25 +++++++++++++++++-------- src/ev/tcp.lisp | 33 ++++++++++++++++++++++++++++++--- src/woo.lisp | 41 +++++++++++++++++++++++++++++++++-------- woo.asd | 3 ++- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/ev/socket.lisp b/src/ev/socket.lisp index 5a4bc04..ce053a4 100644 --- a/src/ev/socket.lisp +++ b/src/ev/socket.lisp @@ -49,6 +49,7 @@ :socket-data :socket-read-cb :socket-open-p + :socket-ssl-stream :check-socket-open :write-socket-data @@ -74,6 +75,7 @@ (tcp-read-cb nil :type symbol) (read-cb nil :type (or null function)) (write-cb nil :type (or null function)) + (ssl-stream nil :type (or null stream)) (open-p t :type boolean) (buffer (make-output-buffer #+lispworks :output #+lispworks :static)) @@ -148,13 +150,18 @@ (when (socket-open-p socket) (when write-cb-specified-p (setf (socket-write-cb socket) write-cb)) - (if (typep data '(simple-array (unsigned-byte 8) (*))) - (fast-write-sequence data - (socket-buffer socket) - start end) - (loop for i from start upto (1- end) - for byte of-type (unsigned-byte 8) = (aref data i) - do (fast-write-byte byte (socket-buffer socket)))))) + (let ((ssl-stream (socket-ssl-stream socket))) + (if ssl-stream + (progn + (write-sequence data ssl-stream :start start :end end) + (force-output ssl-stream)) + (if (typep data '(simple-array (unsigned-byte 8) (*))) + (fast-write-sequence data + (socket-buffer socket) + start end) + (loop for i from start upto (1- end) + for byte of-type (unsigned-byte 8) = (aref data i) + do (fast-write-byte byte (socket-buffer socket)))))))) (defun write-socket-byte (socket byte &key (write-cb nil write-cb-specified-p)) (declare (optimize speed) @@ -162,7 +169,9 @@ (when (socket-open-p socket) (when write-cb-specified-p (setf (socket-write-cb socket) write-cb)) - (fast-write-byte byte (socket-buffer socket)))) + (if (socket-ssl-stream socket) + (write-byte byte (socket-ssl-stream socket)) + (fast-write-byte byte (socket-buffer socket))))) (declaim (inline reset-buffer)) (defun reset-buffer (socket) diff --git a/src/ev/tcp.lisp b/src/ev/tcp.lisp index 2c7f8c7..508d290 100644 --- a/src/ev/tcp.lisp +++ b/src/ev/tcp.lisp @@ -10,6 +10,7 @@ (:import-from :woo.ev.socket :make-socket :close-socket + :socket-ssl-stream :socket-fd :socket-read-cb :socket-read-watcher @@ -95,9 +96,18 @@ (let* ((fd (io-fd watcher)) (buffer-len (length *input-buffer*)) (socket (deref-data-from-pointer fd)) - (read-cb (socket-read-cb socket))) + (read-cb (socket-read-cb socket)) + (ssl-stream (socket-ssl-stream socket))) (loop - (let ((n (wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len))) + (let ((n + (if ssl-stream + (let ((handle (cl+ssl::ssl-stream-handle ssl-stream)) + (cl+ssl::*bio-blockp* nil)) + (cl+ssl::nonblocking-ssl-funcall + ssl-stream #'integerp #'cl+ssl::ssl-read handle + (static-vectors:static-vector-pointer *input-buffer*) + buffer-len)) + (wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len)))) (declare (type fixnum n)) (case n (-1 @@ -184,6 +194,22 @@ (cffi:foreign-slot-value *dummy-sockaddr* '(:struct wsock:sockaddr-in) 'wsock::port))) (t (values nil nil))))) +(defun make-ssl-stream (client-fd) + (cl+ssl::ensure-initialized) + (let ((stream + (make-instance 'cl+ssl::ssl-server-stream + :socket client-fd + :input-buffer-size 0 + :output-buffer-size cl+ssl::*default-buffer-size*))) + (cl+ssl::with-new-ssl (handle) + (setf (cl+ssl::ssl-stream-handle stream) handle) + (cl+ssl::install-nonblock-flag client-fd) + (cl+ssl::ssl-set-fd handle client-fd) + (cl+ssl::ssl-set-accept-state handle) + (when cl+ssl:*default-cipher-list* + (cl+ssl::ssl-set-cipher-list handle cl+ssl:*default-cipher-list*)) + stream))) + (define-c-callback tcp-accept-cb :void ((evloop :pointer) (listener :pointer) (events :int)) (declare (ignore evloop events)) (let* ((fd (io-fd listener)) @@ -215,7 +241,8 @@ (multiple-value-bind (remote-addr remote-port) (get-remote-addr-and-port) (let ((socket (make-socket :fd client-fd :tcp-read-cb 'tcp-read-cb - :remote-addr remote-addr :remote-port remote-port))) + :remote-addr remote-addr :remote-port remote-port + :ssl-stream (make-ssl-stream client-fd)))) (let* ((callbacks (callbacks fd)) (read-cb (getf callbacks :read-cb)) (connect-cb (getf callbacks :connect-cb))) diff --git a/src/woo.lisp b/src/woo.lisp index 16ef062..9037145 100644 --- a/src/woo.lisp +++ b/src/woo.lisp @@ -60,10 +60,13 @@ (defvar *default-worker-num* nil) (defun run (app &key (debug t) - (port 5000) (address "127.0.0.1") - listen ;; UNIX domain socket - (backlog *default-backlog-size*) fd - (worker-num *default-worker-num*)) + (port 5000) (address "127.0.0.1") + listen ;; UNIX domain socket + (backlog *default-backlog-size*) fd + (worker-num *default-worker-num*) + ssl-key-file + ssl-cert-file + ssl-key-password) (assert (and (integerp backlog) (plusp backlog) (<= backlog 128))) @@ -78,6 +81,16 @@ (*debug* debug) (*listener* nil)) (labels ((start-socket (socket) + (when (and ssl-key-file ssl-cert-file) + (let ((ssl-stream (woo.ev.tcp::make-ssl-stream (woo.ev.socket::socket-fd socket)))) + (setf (woo.ev.socket:socket-ssl-stream socket) ssl-stream) + (setf (cl+ssl::ssl-stream-certificate ssl-stream) ssl-cert-file + (cl+ssl::ssl-stream-key ssl-stream) ssl-key-file) + (cl+ssl::with-pem-password ((or ssl-key-password "")) + (cl+ssl::install-key-and-cert + (cl+ssl::ssl-stream-handle ssl-stream) + ssl-key-file + ssl-cert-file)))) (setup-parser socket) (woo.ev.tcp:start-listening-socket socket)) (start-multithread-server () @@ -119,10 +132,22 @@ :backlog backlog :fd fd :sockopt wsock:+SO-REUSEADDR+))) - (wev:close-tcp-server *listener*)))))) - (if worker-num - (start-multithread-server) - (start-singlethread-server))))) + (wev:close-tcp-server *listener*))))) + (main () + (if worker-num + (start-multithread-server) + (start-singlethread-server)))) + (when ssl-key-file + (setf ssl-key-file + (uiop:native-namestring + (or (probe-file ssl-key-file) + (error "SSL private key file '~A' does not exist." ssl-key-file))))) + (when ssl-cert-file + (setf ssl-cert-file + (uiop:native-namestring + (or (probe-file ssl-cert-file) + (error "SSL certificate '~A' does not exist." ssl-cert-file))))) + (main)))) (defun read-cb (socket data &key (start 0) (end (length data))) (let ((parser (wev:socket-data socket))) diff --git a/woo.asd b/woo.asd index 73526a0..ce50f70 100644 --- a/woo.asd +++ b/woo.asd @@ -20,7 +20,8 @@ #+sbcl "sb-posix" #+(and linux (not asdf3)) "uiop" #+sbcl "sb-concurrency" - #-sbcl "cl-speedy-queue") + #-sbcl "cl-speedy-queue" + "cl+ssl") :components ((:module "src" :components ((:file "woo" :depends-on ("ev" "response" "worker" "signal" "specials" "util")) From 752b6dfb973b29689ea21a56b41ec4879fcc4b2a Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sat, 10 Aug 2024 17:44:59 +0000 Subject: [PATCH 2/9] Use Ultralisp for CI. --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 528ea90..fda98ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,8 @@ jobs: ROSWELL_INSTALL_DIR: /usr run: | curl -L https://raw.githubusercontent.com/roswell/roswell/master/scripts/install-for-ci.sh | sh + - name: Install Ultralisp + run: ros -e '(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)' - name: Install Rove run: ros install rove - name: Run tests From 74ef1dfc4aad7cbd34f757ca8a9710402a21cc8f Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sat, 10 Aug 2024 17:56:44 +0000 Subject: [PATCH 3/9] Fix. --- src/ev/tcp.lisp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ev/tcp.lisp b/src/ev/tcp.lisp index 508d290..05b289f 100644 --- a/src/ev/tcp.lisp +++ b/src/ev/tcp.lisp @@ -241,8 +241,7 @@ (multiple-value-bind (remote-addr remote-port) (get-remote-addr-and-port) (let ((socket (make-socket :fd client-fd :tcp-read-cb 'tcp-read-cb - :remote-addr remote-addr :remote-port remote-port - :ssl-stream (make-ssl-stream client-fd)))) + :remote-addr remote-addr :remote-port remote-port))) (let* ((callbacks (callbacks fd)) (read-cb (getf callbacks :read-cb)) (connect-cb (getf callbacks :connect-cb))) From 168c5841c20d0b072265c3a63e4f79e56d9f72f8 Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sun, 11 Aug 2024 12:36:09 +0000 Subject: [PATCH 4/9] HTTPS support for static files. --- src/ev/socket.lisp | 33 +++++++++++++---------------- src/ev/tcp.lisp | 35 +++++++++++------------------- src/woo.lisp | 53 ++++++++++++++++++++++++++++++---------------- 3 files changed, 62 insertions(+), 59 deletions(-) diff --git a/src/ev/socket.lisp b/src/ev/socket.lisp index ce053a4..a7e56ee 100644 --- a/src/ev/socket.lisp +++ b/src/ev/socket.lisp @@ -49,7 +49,7 @@ :socket-data :socket-read-cb :socket-open-p - :socket-ssl-stream + :socket-ssl-handle :check-socket-open :write-socket-data @@ -75,7 +75,7 @@ (tcp-read-cb nil :type symbol) (read-cb nil :type (or null function)) (write-cb nil :type (or null function)) - (ssl-stream nil :type (or null stream)) + (ssl-handle nil :type (or null cffi:foreign-pointer)) (open-p t :type boolean) (buffer (make-output-buffer #+lispworks :output #+lispworks :static)) @@ -150,18 +150,13 @@ (when (socket-open-p socket) (when write-cb-specified-p (setf (socket-write-cb socket) write-cb)) - (let ((ssl-stream (socket-ssl-stream socket))) - (if ssl-stream - (progn - (write-sequence data ssl-stream :start start :end end) - (force-output ssl-stream)) - (if (typep data '(simple-array (unsigned-byte 8) (*))) - (fast-write-sequence data - (socket-buffer socket) - start end) - (loop for i from start upto (1- end) - for byte of-type (unsigned-byte 8) = (aref data i) - do (fast-write-byte byte (socket-buffer socket)))))))) + (if (typep data '(simple-array (unsigned-byte 8) (*))) + (fast-write-sequence data + (socket-buffer socket) + start end) + (loop for i from start upto (1- end) + for byte of-type (unsigned-byte 8) = (aref data i) + do (fast-write-byte byte (socket-buffer socket)))))) (defun write-socket-byte (socket byte &key (write-cb nil write-cb-specified-p)) (declare (optimize speed) @@ -169,9 +164,7 @@ (when (socket-open-p socket) (when write-cb-specified-p (setf (socket-write-cb socket) write-cb)) - (if (socket-ssl-stream socket) - (write-byte byte (socket-ssl-stream socket)) - (fast-write-byte byte (socket-buffer socket))))) + (fast-write-byte byte (socket-buffer socket)))) (declaim (inline reset-buffer)) (defun reset-buffer (socket) @@ -192,7 +185,11 @@ (cffi:with-pointer-to-vector-data (data-sap data) (let* ((len (length data)) (completedp nil) - (n (wsys:write fd data-sap len))) + (n (if (socket-ssl-handle socket) + (cl+ssl::ssl-write (socket-ssl-handle socket) + data-sap + len) + (wsys:write fd data-sap len)))) (declare (type fixnum len) (type fixnum n)) (case n diff --git a/src/ev/tcp.lisp b/src/ev/tcp.lisp index 05b289f..5d378df 100644 --- a/src/ev/tcp.lisp +++ b/src/ev/tcp.lisp @@ -10,7 +10,7 @@ (:import-from :woo.ev.socket :make-socket :close-socket - :socket-ssl-stream + :socket-ssl-handle :socket-fd :socket-read-cb :socket-read-watcher @@ -97,16 +97,11 @@ (buffer-len (length *input-buffer*)) (socket (deref-data-from-pointer fd)) (read-cb (socket-read-cb socket)) - (ssl-stream (socket-ssl-stream socket))) + (ssl-handle (socket-ssl-handle socket))) (loop (let ((n - (if ssl-stream - (let ((handle (cl+ssl::ssl-stream-handle ssl-stream)) - (cl+ssl::*bio-blockp* nil)) - (cl+ssl::nonblocking-ssl-funcall - ssl-stream #'integerp #'cl+ssl::ssl-read handle - (static-vectors:static-vector-pointer *input-buffer*) - buffer-len)) + (if ssl-handle + (cl+ssl::ssl-read ssl-handle (static-vectors:static-vector-pointer *input-buffer*) buffer-len) (wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len)))) (declare (type fixnum n)) (case n @@ -194,21 +189,15 @@ (cffi:foreign-slot-value *dummy-sockaddr* '(:struct wsock:sockaddr-in) 'wsock::port))) (t (values nil nil))))) -(defun make-ssl-stream (client-fd) +(defun make-ssl-handle (client-fd) (cl+ssl::ensure-initialized) - (let ((stream - (make-instance 'cl+ssl::ssl-server-stream - :socket client-fd - :input-buffer-size 0 - :output-buffer-size cl+ssl::*default-buffer-size*))) - (cl+ssl::with-new-ssl (handle) - (setf (cl+ssl::ssl-stream-handle stream) handle) - (cl+ssl::install-nonblock-flag client-fd) - (cl+ssl::ssl-set-fd handle client-fd) - (cl+ssl::ssl-set-accept-state handle) - (when cl+ssl:*default-cipher-list* - (cl+ssl::ssl-set-cipher-list handle cl+ssl:*default-cipher-list*)) - stream))) + (cl+ssl::with-new-ssl (handle) + (cl+ssl::install-nonblock-flag client-fd) + (cl+ssl::ssl-set-fd handle client-fd) + (cl+ssl::ssl-set-accept-state handle) + (when cl+ssl:*default-cipher-list* + (cl+ssl::ssl-set-cipher-list handle cl+ssl:*default-cipher-list*)) + handle)) (define-c-callback tcp-accept-cb :void ((evloop :pointer) (listener :pointer) (events :int)) (declare (ignore evloop events)) diff --git a/src/woo.lisp b/src/woo.lisp index 9037145..c0e08d0 100644 --- a/src/woo.lisp +++ b/src/woo.lisp @@ -82,13 +82,11 @@ (*listener* nil)) (labels ((start-socket (socket) (when (and ssl-key-file ssl-cert-file) - (let ((ssl-stream (woo.ev.tcp::make-ssl-stream (woo.ev.socket::socket-fd socket)))) - (setf (woo.ev.socket:socket-ssl-stream socket) ssl-stream) - (setf (cl+ssl::ssl-stream-certificate ssl-stream) ssl-cert-file - (cl+ssl::ssl-stream-key ssl-stream) ssl-key-file) + (let ((ssl-handle (woo.ev.tcp::make-ssl-handle (woo.ev.socket::socket-fd socket)))) + (setf (woo.ev.socket:socket-ssl-handle socket) ssl-handle) (cl+ssl::with-pem-password ((or ssl-key-password "")) (cl+ssl::install-key-and-cert - (cl+ssl::ssl-stream-handle ssl-stream) + ssl-handle ssl-key-file ssl-cert-file)))) (setup-parser socket) @@ -371,19 +369,38 @@ (setf (getf headers :content-length) 0)) (write-response-headers socket status headers (not close)))) (pathname - (let* ((fd (wsys:open body)) - (size #+lispworks (sys:file-size body) - #+(or sbcl ccl) (fd-file-size fd) - #-(or sbcl ccl lispworks) (file-size body))) - (unless (getf headers :content-length) - (setf (getf headers :content-length) size)) - (unless (getf headers :content-type) - (setf (getf headers :content-type) (mimes:mime body))) - (wev:with-async-writing (socket :write-cb (and close - (lambda (socket) - (wev:close-socket socket)))) - (write-response-headers socket status headers (not close)) - (woo.ev.socket:send-static-file socket fd size)))) + (cond + ((woo.ev.socket:socket-ssl-handle socket) + (with-open-file (in body :element-type '(unsigned-byte 8)) + (let ((size (file-length in))) + (unless (getf headers :content-length) + (setf (getf headers :content-length) size)) + (unless (getf headers :content-type) + (setf (getf headers :content-type) (mimes:mime body))) + (wev:with-async-writing (socket :write-cb (and close + (lambda (socket) + (wev:close-socket socket)))) + (write-response-headers socket status headers (not close)) + ;; Future task: Use OpenSSL's SSL_sendfile which uses Kernel TLS. + ;; TODO: Stop allocating an input buffer every time + (loop with buffer = (make-array 4096 :element-type '(unsigned-byte 8)) + for n = (read-sequence buffer in) + do (wev:write-socket-data socket buffer :end n) + while (= n 4096)))))) + (t + (let* ((fd (wsys:open body)) + (size #+lispworks (sys:file-size body) + #+(or sbcl ccl) (fd-file-size fd) + #-(or sbcl ccl lispworks) (file-size body))) + (unless (getf headers :content-length) + (setf (getf headers :content-length) size)) + (unless (getf headers :content-type) + (setf (getf headers :content-type) (mimes:mime body))) + (wev:with-async-writing (socket :write-cb (and close + (lambda (socket) + (wev:close-socket socket)))) + (write-response-headers socket status headers (not close)) + (woo.ev.socket:send-static-file socket fd size)))))) (list (wev:with-async-writing (socket :write-cb (and close (lambda (socket) From d49dbb7ca4bec0d9a4bc04269f180d5f9599d30a Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sun, 11 Aug 2024 12:57:00 +0000 Subject: [PATCH 5/9] Refactor. --- src/ev/tcp.lisp | 10 ---------- src/ssl.lisp | 32 ++++++++++++++++++++++++++++++++ src/woo.lisp | 48 +++++++++++++++++++++++------------------------- woo.asd | 3 ++- 4 files changed, 57 insertions(+), 36 deletions(-) create mode 100644 src/ssl.lisp diff --git a/src/ev/tcp.lisp b/src/ev/tcp.lisp index 5d378df..1b0f34e 100644 --- a/src/ev/tcp.lisp +++ b/src/ev/tcp.lisp @@ -189,16 +189,6 @@ (cffi:foreign-slot-value *dummy-sockaddr* '(:struct wsock:sockaddr-in) 'wsock::port))) (t (values nil nil))))) -(defun make-ssl-handle (client-fd) - (cl+ssl::ensure-initialized) - (cl+ssl::with-new-ssl (handle) - (cl+ssl::install-nonblock-flag client-fd) - (cl+ssl::ssl-set-fd handle client-fd) - (cl+ssl::ssl-set-accept-state handle) - (when cl+ssl:*default-cipher-list* - (cl+ssl::ssl-set-cipher-list handle cl+ssl:*default-cipher-list*)) - handle)) - (define-c-callback tcp-accept-cb :void ((evloop :pointer) (listener :pointer) (events :int)) (declare (ignore evloop events)) (let* ((fd (io-fd listener)) diff --git a/src/ssl.lisp b/src/ssl.lisp new file mode 100644 index 0000000..4034dd0 --- /dev/null +++ b/src/ssl.lisp @@ -0,0 +1,32 @@ +(defpackage woo.ssl + (:use :cl) + (:import-from :cl+ssl + :with-new-ssl + :install-nonblock-flag + :ssl-set-fd + :ssl-set-accept-state + :*default-cipher-list* + :ssl-set-cipher-list + :with-pem-password + :install-key-and-cert) + (:import-from :woo.ev.socket + :socket-fd + :socket-ssl-handle) + (:export :init-ssl-handle)) +(in-package :woo.ssl) + +(defun init-ssl-handle (socket ssl-cert-file ssl-key-file ssl-key-password) + (let ((client-fd (socket-fd socket))) + (with-new-ssl (handle) + (install-nonblock-flag client-fd) + (ssl-set-fd handle client-fd) + (ssl-set-accept-state handle) + (when *default-cipher-list* + (ssl-set-cipher-list handle *default-cipher-list*)) + (setf (socket-ssl-handle socket) handle) + (with-pem-password ((or ssl-key-password "")) + (install-key-and-cert + handle + ssl-key-file + ssl-cert-file)) + socket))) diff --git a/src/woo.lisp b/src/woo.lisp index c0e08d0..5c61888 100644 --- a/src/woo.lisp +++ b/src/woo.lisp @@ -79,16 +79,14 @@ (let ((*app* app) (*debug* debug) - (*listener* nil)) + (*listener* nil) + (ssl (or ssl-key-file ssl-cert-file))) (labels ((start-socket (socket) - (when (and ssl-key-file ssl-cert-file) - (let ((ssl-handle (woo.ev.tcp::make-ssl-handle (woo.ev.socket::socket-fd socket)))) - (setf (woo.ev.socket:socket-ssl-handle socket) ssl-handle) - (cl+ssl::with-pem-password ((or ssl-key-password "")) - (cl+ssl::install-key-and-cert - ssl-handle - ssl-key-file - ssl-cert-file)))) + (when ssl + (woo.ssl:init-ssl-handle socket + ssl-cert-file + ssl-key-file + ssl-key-password)) (setup-parser socket) (woo.ev.tcp:start-listening-socket socket)) (start-multithread-server () @@ -130,22 +128,22 @@ :backlog backlog :fd fd :sockopt wsock:+SO-REUSEADDR+))) - (wev:close-tcp-server *listener*))))) - (main () - (if worker-num - (start-multithread-server) - (start-singlethread-server)))) - (when ssl-key-file - (setf ssl-key-file - (uiop:native-namestring - (or (probe-file ssl-key-file) - (error "SSL private key file '~A' does not exist." ssl-key-file))))) - (when ssl-cert-file - (setf ssl-cert-file - (uiop:native-namestring - (or (probe-file ssl-cert-file) - (error "SSL certificate '~A' does not exist." ssl-cert-file))))) - (main)))) + (wev:close-tcp-server *listener*)))))) + (when ssl + (cl+ssl::ensure-initialized) + (when ssl-key-file + (setf ssl-key-file + (uiop:native-namestring + (or (probe-file ssl-key-file) + (error "SSL private key file '~A' does not exist." ssl-key-file))))) + (when ssl-cert-file + (setf ssl-cert-file + (uiop:native-namestring + (or (probe-file ssl-cert-file) + (error "SSL certificate '~A' does not exist." ssl-cert-file)))))) + (if worker-num + (start-multithread-server) + (start-singlethread-server))))) (defun read-cb (socket data &key (start 0) (end (length data))) (let ((parser (wev:socket-data socket))) diff --git a/woo.asd b/woo.asd index ce50f70..2d1eb72 100644 --- a/woo.asd +++ b/woo.asd @@ -24,7 +24,7 @@ "cl+ssl") :components ((:module "src" :components - ((:file "woo" :depends-on ("ev" "response" "worker" "signal" "specials" "util")) + ((:file "woo" :depends-on ("ev" "response" "worker" "ssl" "signal" "specials" "util")) (:file "response" :depends-on ("ev")) (:file "ev" :depends-on ("ev-packages")) (:file "worker" :depends-on ("ev" "queue" "specials")) @@ -38,6 +38,7 @@ (:file "tcp" :depends-on ("event-loop" "socket" "util" "condition")) (:file "condition") (:file "util"))) + (:file "ssl" :depends-on ("ev-packages")) (:module "llsocket" :depends-on ("syscall") :serial t From 438e2e94b90067bd7eeb3a9b31eb234371fdc74e Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sun, 11 Aug 2024 13:10:02 +0000 Subject: [PATCH 6/9] Add :woo-no-ssl to disable SSL feature and omit a dependency on CL+SSL. --- src/ev/socket.lisp | 14 +++++++++----- src/ev/tcp.lisp | 3 +++ src/woo.lisp | 29 ++++++++++++++++++----------- woo.asd | 14 ++++++++------ 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/ev/socket.lisp b/src/ev/socket.lisp index a7e56ee..31afee1 100644 --- a/src/ev/socket.lisp +++ b/src/ev/socket.lisp @@ -185,11 +185,15 @@ (cffi:with-pointer-to-vector-data (data-sap data) (let* ((len (length data)) (completedp nil) - (n (if (socket-ssl-handle socket) - (cl+ssl::ssl-write (socket-ssl-handle socket) - data-sap - len) - (wsys:write fd data-sap len)))) + (n + #+woo-no-ssl + (wsys:write fd data-sap len) + #-woo-no-ssl + (if (socket-ssl-handle socket) + (cl+ssl::ssl-write (socket-ssl-handle socket) + data-sap + len) + (wsys:write fd data-sap len)))) (declare (type fixnum len) (type fixnum n)) (case n diff --git a/src/ev/tcp.lisp b/src/ev/tcp.lisp index 1b0f34e..13376f3 100644 --- a/src/ev/tcp.lisp +++ b/src/ev/tcp.lisp @@ -100,6 +100,9 @@ (ssl-handle (socket-ssl-handle socket))) (loop (let ((n + #+woo-no-ssl + (wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len) + #-woo-no-ssl (if ssl-handle (cl+ssl::ssl-read ssl-handle (static-vectors:static-vector-pointer *input-buffer*) buffer-len) (wsys:read fd (static-vectors:static-vector-pointer *input-buffer*) buffer-len)))) diff --git a/src/woo.lisp b/src/woo.lisp index 5c61888..07f9e5d 100644 --- a/src/woo.lisp +++ b/src/woo.lisp @@ -20,6 +20,8 @@ :socket-remote-addr :socket-remote-port :with-sockaddr) + #-woo-no-ssl + (:import-from :woo.ssl) (:import-from :woo.util :integer-string-p) (:import-from :quri @@ -82,6 +84,7 @@ (*listener* nil) (ssl (or ssl-key-file ssl-cert-file))) (labels ((start-socket (socket) + #-woo-no-ssl (when ssl (woo.ssl:init-ssl-handle socket ssl-cert-file @@ -130,17 +133,21 @@ :sockopt wsock:+SO-REUSEADDR+))) (wev:close-tcp-server *listener*)))))) (when ssl - (cl+ssl::ensure-initialized) - (when ssl-key-file - (setf ssl-key-file - (uiop:native-namestring - (or (probe-file ssl-key-file) - (error "SSL private key file '~A' does not exist." ssl-key-file))))) - (when ssl-cert-file - (setf ssl-cert-file - (uiop:native-namestring - (or (probe-file ssl-cert-file) - (error "SSL certificate '~A' does not exist." ssl-cert-file)))))) + #+woo-no-ssl + (warn "SSL certificate is specified but Woo's SSL feature is off. Ignored.") + #-woo-no-ssl + (progn + (cl+ssl::ensure-initialized) + (when ssl-key-file + (setf ssl-key-file + (uiop:native-namestring + (or (probe-file ssl-key-file) + (error "SSL private key file '~A' does not exist." ssl-key-file))))) + (when ssl-cert-file + (setf ssl-cert-file + (uiop:native-namestring + (or (probe-file ssl-cert-file) + (error "SSL certificate '~A' does not exist." ssl-cert-file))))))) (if worker-num (start-multithread-server) (start-singlethread-server))))) diff --git a/woo.asd b/woo.asd index 2d1eb72..dd32052 100644 --- a/woo.asd +++ b/woo.asd @@ -17,11 +17,11 @@ "trivial-mimes" "vom" "alexandria" - #+sbcl "sb-posix" - #+(and linux (not asdf3)) "uiop" - #+sbcl "sb-concurrency" - #-sbcl "cl-speedy-queue" - "cl+ssl") + (:feature :sbcl "sb-posix") + (:feature (:and :linux (:not :asdf3)) "uiop") + (:feature :sbcl "sb-concurrency") + (:feature (:not :sbcl) "cl-speedy-queue") + (:feature (:not :woo-no-ssl) "cl+ssl")) :components ((:module "src" :components ((:file "woo" :depends-on ("ev" "response" "worker" "ssl" "signal" "specials" "util")) @@ -38,7 +38,9 @@ (:file "tcp" :depends-on ("event-loop" "socket" "util" "condition")) (:file "condition") (:file "util"))) - (:file "ssl" :depends-on ("ev-packages")) + (:file "ssl" + :depends-on ("ev-packages") + :if-feature (:not :woo-no-ssl)) (:module "llsocket" :depends-on ("syscall") :serial t From 621ab7c4888e8a2d289beaadbeed9aea14709abb Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sun, 11 Aug 2024 14:18:33 +0000 Subject: [PATCH 7/9] Run testing also for HTTPS. --- .github/workflows/ci.yml | 4 ++++ .gitignore | 1 + t/generate-certificates.sh | 15 +++++++++++++++ t/woo.lisp | 8 ++++++++ 4 files changed, 28 insertions(+) create mode 100755 t/generate-certificates.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fda98ca..1d85415 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,8 @@ jobs: - uses: actions/checkout@v4 - name: Install dependencies from APT run: sudo apt-get install -y libev-dev gcc libc6-dev + - name: Generate server certificates + run: sh ./t/generate-certificates.sh - name: Install Roswell env: LISP: ${{ matrix.lisp }} @@ -24,6 +26,8 @@ jobs: run: ros -e '(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)' - name: Install Rove run: ros install rove + - name: Install the latest Clack (for HTTPS testing with clack-test) + run: ros install fukamachi/clack - name: Run tests env: LISP: ${{ matrix.lisp }} diff --git a/.gitignore b/.gitignore index 571c025..d80ccf3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ benchmark/benchmark.log .qlot/ qlfile qlfile.lock +t/certs/ diff --git a/t/generate-certificates.sh b/t/generate-certificates.sh new file mode 100755 index 0000000..0a2a22f --- /dev/null +++ b/t/generate-certificates.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +mkdir t/certs +cd t/certs + +openssl genrsa -out localCA.key 2048 +openssl req -batch -new -key localCA.key -out localCA.csr \ + -subj "/C=JP/ST=Tokyo/L=Chuo-ku/O=\"Woo\"/OU=Development/CN=localhost" +openssl x509 -req -days 3650 -signkey localCA.key -in localCA.csr -out localCA.crt +openssl x509 -text -noout -in localCA.crt +openssl genrsa -out localhost.key 2048 +openssl req -batch -new -key localhost.key -out localhost.csr \ + -subj "/C=JP/ST=Tokyo/L=Chuo-ku/O=\"Woo\"/OU=Development/CN=localhost" +echo 'subjectAltName = DNS:localhost, DNS:localhost.localdomain, IP:127.0.0.1, DNS:app, DNS:app.localdomain' > localhost.csx +openssl x509 -req -days 1825 -CA localCA.crt -CAkey localCA.key -CAcreateserial -in localhost.csr -extfile localhost.csx -out localhost.crt diff --git a/t/woo.lisp b/t/woo.lisp index 04b4202..cb2a3f3 100644 --- a/t/woo.lisp +++ b/t/woo.lisp @@ -6,3 +6,11 @@ (deftest woo-server-tests (clack.test.suite:run-server-tests :woo)) + +(deftest woo-ssl-server-tests + (let ((clack.test:*clackup-additional-args* + '(:ssl-cert-file #P"t/certs/localhost.crt" + :ssl-key-file #P"t/certs/localhost.key")) + (dex:*not-verify-ssl* t) + (clack.test:*use-https* t)) + (clack.test.suite:run-server-tests :woo))) From b91e8e4570a85d094b6c3a476cf688d753329e66 Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sun, 11 Aug 2024 15:01:23 +0000 Subject: [PATCH 8/9] Fix style-warning. --- src/woo.lisp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/woo.lisp b/src/woo.lisp index 07f9e5d..fcb561c 100644 --- a/src/woo.lisp +++ b/src/woo.lisp @@ -69,6 +69,7 @@ ssl-key-file ssl-cert-file ssl-key-password) + (declare (ignorable ssl-key-password)) (assert (and (integerp backlog) (plusp backlog) (<= backlog 128))) From ceb90308a86c3dbe619c9a971e8806c3e66eb155 Mon Sep 17 00:00:00 2001 From: Eitaro Fukamachi Date: Sun, 11 Aug 2024 15:01:28 +0000 Subject: [PATCH 9/9] Handle SSL errors properly. --- src/ev/socket.lisp | 54 +++++++++++++++++++++++++++++----------------- src/ev/tcp.lisp | 44 ++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/src/ev/socket.lisp b/src/ev/socket.lisp index 31afee1..7504533 100644 --- a/src/ev/socket.lisp +++ b/src/ev/socket.lisp @@ -185,12 +185,13 @@ (cffi:with-pointer-to-vector-data (data-sap data) (let* ((len (length data)) (completedp nil) + (ssl-handle (socket-ssl-handle socket)) (n #+woo-no-ssl (wsys:write fd data-sap len) #-woo-no-ssl - (if (socket-ssl-handle socket) - (cl+ssl::ssl-write (socket-ssl-handle socket) + (if ssl-handle + (cl+ssl::ssl-write ssl-handle data-sap len) (wsys:write fd data-sap len)))) @@ -198,24 +199,37 @@ (type fixnum n)) (case n (-1 - (let ((errno (wsys:errno))) - (return-from flush-buffer - (cond - ((or (= errno wsys:EWOULDBLOCK) - (= errno wsys:EINTR)) - nil) - ((or (= errno wsys:ECONNABORTED) - (= errno wsys:ECONNREFUSED) - (= errno wsys:ECONNRESET) - (= errno wsys:EPIPE) - (= errno wsys:ENOTCONN)) - (vom:error "Connection is already closed (Code: ~D)" errno) - (close-socket socket) - t) - (t - (vom:error "Unexpected error (Code: ~D)" errno) - (close-socket socket) - t))))) + (if ssl-handle + #+woo-no-ssl (close-socket socket) + #-woo-no-ssl + (let ((errno (cl+ssl::ssl-get-error ssl-handle n))) + (declare (type fixnum errno)) + (cond + ((or (= errno cl+ssl::+ssl-error-zero-return+) + (= errno cl+ssl::+ssl-error-ssl+)) + (close-socket socket)) + ((= errno cl+ssl::+ssl-error-want-write+)) + (t + (vom:error "Unexpected error (Code: ~D)" errno) + (close-socket socket)))) + (let ((errno (wsys:errno))) + (return-from flush-buffer + (cond + ((or (= errno wsys:EWOULDBLOCK) + (= errno wsys:EINTR)) + nil) + ((or (= errno wsys:ECONNABORTED) + (= errno wsys:ECONNREFUSED) + (= errno wsys:ECONNRESET) + (= errno wsys:EPIPE) + (= errno wsys:ENOTCONN)) + (vom:error "Connection is already closed (Code: ~D)" errno) + (close-socket socket) + t) + (t + (vom:error "Unexpected error (Code: ~D)" errno) + (close-socket socket) + t)))))) (otherwise (setf (socket-last-activity socket) (lev:ev-now *evloop*)) (if (= n len) diff --git a/src/ev/tcp.lisp b/src/ev/tcp.lisp index 13376f3..a384365 100644 --- a/src/ev/tcp.lisp +++ b/src/ev/tcp.lisp @@ -109,21 +109,35 @@ (declare (type fixnum n)) (case n (-1 - (let ((errno (wsys:errno))) - (cond - ((or (= errno wsys:EWOULDBLOCK) - (= errno wsys:EINTR))) - ((or (= errno wsys:ECONNABORTED) - (= errno wsys:ECONNREFUSED) - (= errno wsys:ECONNRESET)) - (vom:error "Connection is already closed (Code: ~D)" errno) - (close-socket socket)) - ((= errno wsys:EAGAIN) - ;; Just to nothing - ) - (t - (vom:error "Unexpected error (Code: ~D)" errno) - (close-socket socket)))) + (if ssl-handle + #+woo-no-ssl (close-socket socket) + #-woo-no-ssl + (let ((errno (cl+ssl::ssl-get-error ssl-handle n))) + (declare (type fixnum errno)) + (cond + ((or (= errno cl+ssl::+ssl-error-zero-return+) + (= errno cl+ssl::+ssl-error-ssl+)) + (close-socket socket)) + ((= errno cl+ssl::+ssl-error-want-read+)) + (t + (vom:error "Unexpected error (Code: ~D)" errno) + (close-socket socket)))) + (let ((errno (wsys:errno))) + (declare (type fixnum errno)) + (cond + ((or (= errno wsys:EWOULDBLOCK) + (= errno wsys:EINTR))) + ((or (= errno wsys:ECONNABORTED) + (= errno wsys:ECONNREFUSED) + (= errno wsys:ECONNRESET)) + (vom:error "Connection is already closed (Code: ~D)" errno) + (close-socket socket)) + ((= errno wsys:EAGAIN) + ;; Just to nothing + ) + (t + (vom:error "Unexpected error (Code: ~D)" errno) + (close-socket socket))))) (return)) (0 ;; EOF