commit 343a215d31327870de4cc7cc0c2bda78b8457c48
Author: dlichteblau
Date: Wed Nov 9 22:10:44 2005 +0000
Initial revision
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bf7273a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,17 @@
+;; Copyright (C) 2001, 2003 Eric Marsden
+;; Copyright (C) 2005 David Lichteblau
+;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;
+;; This library is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU Library General Public
+;; License as published by the Free Software Foundation; either
+;; version 2 of the License, or (at your option) any later version.
+;;
+;; This library is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; Library General Public License for more details.
+;;
+;; You should have received a copy of the GNU Library General Public
+;; License along with this library; if not, write to the Free
+;; Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f6d297c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,3 @@
+.PHONY: clean
+clean:
+ rm -f *.fasl *.x86f *.fas *.ufsl *.lib *.pfsl
diff --git a/bio.lisp b/bio.lisp
new file mode 100644
index 0000000..1010342
--- /dev/null
+++ b/bio.lisp
@@ -0,0 +1,137 @@
+;;; Copyright (C) 2005 David Lichteblau
+;;;
+;;; See LICENSE for details.
+
+(in-package cl+ssl)
+
+(defconstant +bio-type-socket+ (logior 5 #x0400 #x0100))
+(defconstant +BIO_FLAGS_READ+ 1)
+(defconstant +BIO_FLAGS_WRITE+ 2)
+(defconstant +BIO_FLAGS_SHOULD_RETRY+ 8)
+(defconstant +BIO_CTRL_FLUSH+ 11)
+
+(cffi:defcstruct bio-method
+ (type :int)
+ (name :pointer)
+ (bwrite :pointer)
+ (bread :pointer)
+ (bputs :pointer)
+ (bgets :pointer)
+ (ctrl :pointer)
+ (create :pointer)
+ (destroy :pointer)
+ (callback-ctrl :pointer))
+
+(cffi:defcstruct bio
+ (method :pointer)
+ (callback :pointer)
+ (cb-arg :pointer)
+ (init :int)
+ (shutdown :int)
+ (flags :int)
+ (retry-reason :int)
+ (num :int)
+ (ptr :pointer)
+ (next-bio :pointer)
+ (prev-bio :pointer)
+ (references :int)
+ (num-read :unsigned-long)
+ (num-write :unsigned-long)
+ (crypto-ex-data-stack :pointer)
+ (crypto-ex-data-dummy :int))
+
+(defun make-bio-lisp-method ()
+ (let ((m (cffi:foreign-alloc 'bio-method)))
+ (setf (cffi:foreign-slot-value m 'bio-method 'type)
+ ;; fixme: this is wrong, but presumably still better than some
+ ;; random value here.
+ +bio-type-socket+)
+ (macrolet ((slot (name)
+ `(cffi:foreign-slot-value m 'bio-method ,name)))
+ (setf (slot 'name) (cffi:foreign-string-alloc "lisp"))
+ (setf (slot 'bwrite) (cffi:callback lisp-write))
+ (setf (slot 'bread) (cffi:callback lisp-read))
+ (setf (slot 'bputs) (cffi:callback lisp-puts))
+ (setf (slot 'bgets) (cffi:null-ptr))
+ (setf (slot 'ctrl) (cffi:callback lisp-ctrl))
+ (setf (slot 'create) (cffi:callback lisp-create))
+ (setf (slot 'destroy) (cffi:callback lisp-destroy))
+ (setf (slot 'callback-ctrl) (cffi:null-ptr)))
+ m))
+
+(defun bio-new-lisp ()
+ (bio-new *bio-lisp-method*))
+
+
+;;; "cargo cult"
+
+(cffi:defcallback lisp-write :int ((bio :pointer) (buf :pointer) (n :int))
+ bio
+ (dotimes (i n)
+ (write-byte (cffi:mem-ref buf :unsigned-char i) *socket*))
+ (finish-output *socket*)
+ n)
+
+(defun clear-retry-flags (bio)
+ (setf (cffi:foreign-slot-value bio 'bio 'flags)
+ (logandc2 (cffi:foreign-slot-value bio 'bio 'flags)
+ (logior +BIO_FLAGS_READ+
+ +BIO_FLAGS_WRITE+
+ +BIO_FLAGS_SHOULD_RETRY+))))
+
+(defun set-retry-read (bio)
+ (setf (cffi:foreign-slot-value bio 'bio 'flags)
+ (logior (cffi:foreign-slot-value bio 'bio 'flags)
+ +BIO_FLAGS_READ+
+ +BIO_FLAGS_SHOULD_RETRY+)))
+
+;; not sure whether we should block or not...
+(defvar *block* t)
+
+(cffi:defcallback lisp-read :int ((bio :pointer) (buf :pointer) (n :int))
+ bio buf n
+ (let ((i 0))
+ (handler-case
+ (unless (or (cffi:null-ptr-p buf) (null n))
+ (clear-retry-flags bio)
+ (setf (cffi:mem-ref buf :unsigned-char i) (read-byte *socket*))
+ (incf i)
+ (loop
+ while (and (< i n) (or *block* (listen *socket*)))
+ do
+ (setf (cffi:mem-ref buf :unsigned-char i) (read-byte *socket*))
+ (incf i))
+ #+(or)
+ (when (zerop i) (set-retry-read bio)))
+ (end-of-file ()))
+ i))
+
+(cffi:defcallback lisp-puts :int ((bio :pointer) (buf :string))
+ bio buf
+ (error "lisp-puts not implemented"))
+
+(cffi:defcallback lisp-ctrl :int
+ ((bio :pointer) (cmd :int) (larg :long) (parg :pointer))
+ bio larg parg
+ (cond
+ ((eql cmd +BIO_CTRL_FLUSH+) 1)
+ (t
+ ;; (warn "lisp-ctrl(~A,~A,~A)" cmd larg parg)
+ 0)))
+
+(cffi:defcallback lisp-create :int ((bio :pointer))
+ (setf (cffi:foreign-slot-value bio 'bio 'init) 1)
+ (setf (cffi:foreign-slot-value bio 'bio 'num) 0)
+ (setf (cffi:foreign-slot-value bio 'bio 'ptr) (cffi:null-ptr))
+ (setf (cffi:foreign-slot-value bio 'bio 'flags) 0)
+ 1)
+
+(cffi:defcallback lisp-destroy :int ((bio :pointer))
+ (cond
+ ((cffi:null-ptr-p bio) 0)
+ (t
+ (setf (cffi:foreign-slot-value bio 'bio 'init) 0)
+ (setf (cffi:foreign-slot-value bio 'bio 'flags) 0)
+ 1)))
+
+(setf *bio-lisp-method* nil) ;force reinit if anything changed here
diff --git a/cl+ssl.asd b/cl+ssl.asd
new file mode 100644
index 0000000..2b09589
--- /dev/null
+++ b/cl+ssl.asd
@@ -0,0 +1,26 @@
+;;; -*- mode: lisp -*-
+;;;
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+(defpackage :cl+ssl-system
+ (:use :cl :asdf)
+ (:export #:*libssl-pathname*))
+
+(in-package :cl+ssl-system)
+
+(defparameter *libssl-pathname* "/usr/lib/libssl.so")
+
+(defsystem :cl+ssl
+ :depends-on (:cffi :trivial-gray-streams)
+ :serial t
+ :components
+ ((:file "reload")
+ (:file "package")
+ (:file "conditions")
+ (:file "ffi")
+ (:file "streams")
+ (:file "bio")))
diff --git a/conditions.lisp b/conditions.lisp
new file mode 100644
index 0000000..dd44dc2
--- /dev/null
+++ b/conditions.lisp
@@ -0,0 +1,211 @@
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+(in-package :cl+ssl)
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+ (defconstant +ssl-error-none+ 0)
+ (defconstant +ssl-error-ssl+ 1)
+ (defconstant +ssl-error-want-read+ 2)
+ (defconstant +ssl-error-want-write+ 3)
+ (defconstant +ssl-error-want-x509-lookup+ 4)
+ (defconstant +ssl-error-syscall+ 5)
+ (defconstant +ssl-error-zero-return+ 6)
+ (defconstant +ssl-error-want-connect+ 7))
+
+
+;;; Condition hierarchy
+;;;
+(define-condition ssl-error (error)
+ ((queue :initform nil :initarg :queue :reader ssl-error-queue)))
+
+(define-condition ssl-error/handle (ssl-error)
+ ((ret :initarg :ret
+ :reader ssl-error-ret)
+ (handle :initarg :handle
+ :reader ssl-error-handle))
+ (:report (lambda (condition stream)
+ (format stream "Unspecified error ~A on handle ~A"
+ (ssl-error-ret condition)
+ (ssl-error-handle condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+(define-condition ssl-error-initialize (ssl-error)
+ ((reason :initarg :reason
+ :reader ssl-error-reason))
+ (:report (lambda (condition stream)
+ (format stream "SSL initialization error: ~A"
+ (ssl-error-reason condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+
+(define-condition ssl-error-want-something (ssl-error/handle)
+ ())
+
+;;;SSL_ERROR_NONE
+(define-condition ssl-error-none (ssl-error/handle)
+ ()
+ (:documentation
+ "The TLS/SSL I/O operation completed. This result code is returned if and
+ only if ret > 0.")
+ (:report (lambda (condition stream)
+ (format stream "The TLS/SSL operation on handle ~A completed. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_ZERO_RETURN
+(define-condition ssl-error-zero-return (ssl-error/handle)
+ ()
+ (:documentation
+ "The TLS/SSL connection has been closed. If the protocol version is SSL 3.0
+ or TLS 1.0, this result code is returned only if a closure alert has
+ occurred in the protocol, i.e. if the connection has been closed cleanly.
+ Note that in this case SSL_ERROR_ZERO_RETURN
+ does not necessarily indicate that the underlying transport has been
+ closed.")
+ (:report (lambda (condition stream)
+ (format stream "The TLS/SSL connection on handle ~A has been closed. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_WANT_READ
+(define-condition ssl-error-want-read (ssl-error-want-something)
+ ()
+ (:documentation
+ "The operation did not complete; the same TLS/SSL I/O function should be
+ called again later. If, by then, the underlying BIO has data available for
+ reading (if the result code is SSL_ERROR_WANT_READ) or allows writing data
+ (SSL_ERROR_WANT_WRITE), then some TLS/SSL protocol progress will take place,
+ i.e. at least part of an TLS/SSL record will be read or written. Note that
+ the retry may again lead to a SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
+ condition. There is no fixed upper limit for the number of iterations that
+ may be necessary until progress becomes visible at application protocol
+ level.")
+ (:report (lambda (condition stream)
+ (format stream "The TLS/SSL operation on handle ~A did not complete: It wants a READ. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_WANT_WRITE
+(define-condition ssl-error-want-write (ssl-error-want-something)
+ ()
+ (:documentation
+ "The operation did not complete; the same TLS/SSL I/O function should be
+ called again later. If, by then, the underlying BIO has data available for
+ reading (if the result code is SSL_ERROR_WANT_READ) or allows writing data
+ (SSL_ERROR_WANT_WRITE), then some TLS/SSL protocol progress will take place,
+ i.e. at least part of an TLS/SSL record will be read or written. Note that
+ the retry may again lead to a SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
+ condition. There is no fixed upper limit for the number of iterations that
+ may be necessary until progress becomes visible at application protocol
+ level.")
+ (:report (lambda (condition stream)
+ (format stream "The TLS/SSL operation on handle ~A did not complete: It wants a WRITE. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_WANT_CONNECT
+(define-condition ssl-error-want-connect (ssl-error-want-something)
+ ()
+ (:documentation
+ "The operation did not complete; the same TLS/SSL I/O function should be
+ called again later. The underlying BIO was not connected yet to the peer
+ and the call would block in connect()/accept(). The SSL
+ function should be called again when the connection is established. These
+ messages can only appear with a BIO_s_connect() or
+ BIO_s_accept() BIO, respectively. In order to find out, when
+ the connection has been successfully established, on many platforms
+ select() or poll() for writing on the socket file
+ descriptor can be used.")
+ (:report (lambda (condition stream)
+ (format stream "The TLS/SSL operation on handle ~A did not complete: It wants a connect first. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_WANT_X509_LOOKUP
+(define-condition ssl-error-want-x509-lookup (ssl-error-want-something)
+ ()
+ (:documentation
+ "The operation did not complete because an application callback set by
+ SSL_CTX_set_client_cert_cb() has asked to be called again. The
+ TLS/SSL I/O function should be called again later. Details depend on the
+ application.")
+ (:report (lambda (condition stream)
+ (format stream "The TLS/SSL operation on handle ~A did not complete: An application callback wants to be called again. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_SYSCALL
+(define-condition ssl-error-syscall (ssl-error/handle)
+ ((syscall :initarg :syscall))
+ (:documentation
+ "Some I/O error occurred. The OpenSSL error queue may contain more
+ information on the error. If the error queue is empty (i.e. ERR_get_error() returns 0),
+ ret can be used to find out more about the error: If ret == 0, an EOF was observed that
+ violates the protocol. If ret == -1, the underlying BIO reported an I/O error (for socket
+ I/O on Unix systems, consult errno for details).")
+ (:report (lambda (condition stream)
+ (if (zerop (err-get-error))
+ (case (ssl-error-ret condition)
+ (0 (format stream "An I/O error occurred: An unexpected EOF was observed on handle ~A. (return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition)))
+ (-1 (format stream "An I/O error occurred in the underlying BIO. (return code: ~A)"
+ (ssl-error-ret condition)))
+ (otherwise (format stream "An I/O error occurred: undocumented reason. (return code: ~A)"
+ (ssl-error-ret condition))))
+ (format stream "An UNKNOWN I/O error occurred in the underlying BIO. (return code: ~A)"
+ (ssl-error-ret condition)))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+;; SSL_ERROR_SSL
+(define-condition ssl-error-ssl (ssl-error/handle)
+ ()
+ (:documentation
+ "A failure in the SSL library occurred, usually a protocol error. The
+ OpenSSL error queue contains more information on the error.")
+ (:report (lambda (condition stream)
+ (format stream
+ "A failure in the SSL library occurred on handle ~A. (Return code: ~A)"
+ (ssl-error-handle condition)
+ (ssl-error-ret condition))
+ (write-sequence (ssl-error-queue condition) stream))))
+
+(defun write-ssl-error-queue (stream)
+ (format stream "SSL error queue: ~%")
+ (loop
+ for error-code = (err-get-error)
+ until (zerop error-code)
+ do (format stream "~a~%" (err-error-string error-code (cffi:null-ptr)))))
+
+(defun ssl-signal-error (handle syscall error-code original-error)
+ (let ((queue (with-output-to-string (s) (write-ssl-error-queue s))))
+ (if (and (eql error-code #.+ssl-error-syscall+)
+ (not (zerop original-error)))
+ (error 'ssl-error-syscall
+ :handle handle
+ :ret error-code
+ :queue queue
+ :syscall syscall)
+ (error (case error-code
+ (#.+ssl-error-none+ 'ssl-error-none)
+ (#.+ssl-error-ssl+ 'ssl-error-ssl)
+ (#.+ssl-error-want-read+ 'ssl-error-want-read)
+ (#.+ssl-error-want-write+ 'ssl-error-want-write)
+ (#.+ssl-error-want-x509-lookup+ 'ssl-error-want-x509-lookup)
+ (#.+ssl-error-zero-return+ 'ssl-error-zero-return)
+ (#.+ssl-error-want-connect+ 'ssl-error-want-connect)
+ (#.+ssl-error-syscall+ 'ssl-error-zero-return)
+ (t 'ssl-error/handle))
+ :handle handle
+ :ret error-code
+ :queue queue))))
diff --git a/ffi.lisp b/ffi.lisp
new file mode 100644
index 0000000..168d97b
--- /dev/null
+++ b/ffi.lisp
@@ -0,0 +1,234 @@
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+(declaim
+ (optimize (speed 3) (space 1) (safety 1) (debug 0) (compilation-speed 0)))
+
+(in-package :cl+ssl)
+
+
+;;; Global state
+;;;
+(defvar *ssl-global-context* nil)
+(defvar *ssl-global-method* nil)
+(defvar *bio-lisp-method* nil)
+
+(defun ssl-initialized-p ()
+ (and *ssl-global-context* *ssl-global-method*))
+
+
+;;; Constants
+;;;
+(defconstant +random-entropy+ 256)
+
+(defconstant +ssl-filetype-pem+ 1)
+(defconstant +ssl-filetype-asn1+ 2)
+(defconstant +ssl-filetype-default+ 3)
+
+(defconstant +SSL_CTRL_SET_SESS_CACHE_MODE+ 44)
+
+
+;;; Function definitions
+;;;
+(declaim (inline ssl-write ssl-read ssl-connect ssl-accept))
+
+(cffi:defctype ssl-method :pointer)
+(cffi:defctype ssl-ctx :pointer)
+(cffi:defctype ssl-pointer :pointer)
+
+(cffi:defcfun ("SSL_get_version" ssl-get-version)
+ :string
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_load_error_strings" ssl-load-error-strings)
+ :void)
+(cffi:defcfun ("SSL_library_init" ssl-library-init)
+ :int)
+(cffi:defcfun ("SSLv2_client_method" ssl-v2-client-method)
+ ssl-method)
+(cffi:defcfun ("SSLv23_client_method" ssl-v23-client-method)
+ ssl-method)
+(cffi:defcfun ("SSLv23_server_method" ssl-v23-server-method)
+ ssl-method)
+(cffi:defcfun ("SSLv23_method" ssl-v23-method)
+ ssl-method)
+(cffi:defcfun ("SSLv3_client_method" ssl-v3-client-method)
+ ssl-method)
+(cffi:defcfun ("SSLv3_server_method" ssl-v3-server-method)
+ ssl-method)
+(cffi:defcfun ("SSLv3_method" ssl-v3-method)
+ ssl-method)
+(cffi:defcfun ("TLSv1_client_method" ssl-TLSv1-client-method)
+ ssl-method)
+(cffi:defcfun ("TLSv1_server_method" ssl-TLSv1-server-method)
+ ssl-method)
+(cffi:defcfun ("TLSv1_method" ssl-TLSv1-method)
+ ssl-method)
+
+(cffi:defcfun ("SSL_CTX_new" ssl-ctx-new)
+ ssl-ctx
+ (method ssl-method))
+(cffi:defcfun ("SSL_new" ssl-new)
+ ssl-pointer
+ (ctx ssl-ctx))
+(cffi:defcfun ("SSL_set_fd" ssl-set-fd)
+ :int
+ (ssl ssl-pointer)
+ (fd :int))
+(cffi:defcfun ("SSL_set_bio" ssl-set-bio)
+ :void
+ (ssl ssl-pointer)
+ (rbio :pointer)
+ (wbio :pointer))
+(cffi:defcfun ("SSL_get_error" ssl-get-error)
+ :int
+ (ssl ssl-pointer)
+ (ret :int))
+(cffi:defcfun ("SSL_set_connect_state" ssl-set-connect-state)
+ :void
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_set_accept_state" ssl-set-accept-state)
+ :void
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_connect" ssl-connect)
+ :int
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_accept" ssl-accept)
+ :int
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_write" ssl-write)
+ :int
+ (ssl ssl-pointer)
+ (buf :pointer)
+ (num :int))
+(cffi:defcfun ("SSL_read" ssl-read)
+ :int
+ (ssl ssl-pointer)
+ (buf :pointer)
+ (num :int))
+(cffi:defcfun ("SSL_shutdown" ssh-shutdown)
+ :void
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_free" ssl-free)
+ :void
+ (ssl ssl-pointer))
+(cffi:defcfun ("SSL_CTX_free" ssl-ctx-free)
+ :void
+ (ctx ssl-ctx))
+(cffi:defcfun ("RAND_seed" rand-seed)
+ :void
+ (buf :pointer)
+ (num :int))
+(cffi:defcfun ("BIO_ctrl" bio-set-fd)
+ :long
+ (bio :pointer)
+ (cmd :int)
+ (larg :long)
+ (parg :pointer))
+(cffi:defcfun ("BIO_new_socket" bio-new-socket)
+ :pointer
+ (fd :int)
+ (close-flag :int))
+(cffi:defcfun ("BIO_new" bio-new)
+ :pointer
+ (method :pointer))
+
+(cffi:defcfun ("ERR_get_error" err-get-error)
+ :unsigned-long)
+(cffi:defcfun ("ERR_error_string" err-error-string)
+ :string
+ (e :unsigned-long)
+ (buf :pointer))
+
+(cffi:defcfun ("SSL_set_cipher_list" ssl-set-cipher-list)
+ :int
+ (ssl ssl-pointer)
+ (str :string))
+(cffi:defcfun ("SSL_use_RSAPrivateKey_file" ssl-use-rsa-privatekey-file)
+ :int
+ (ssl ssl-pointer)
+ (str :string)
+ ;; either +ssl-filetype-pem+ or +ssl-filetype-asn1+
+ (type :int))
+(cffi:defcfun
+ ("SSL_CTX_use_RSAPrivateKey_file" ssl-ctx-use-rsa-privatekey-file)
+ :int
+ (ctx ssl-ctx)
+ (type :int))
+(cffi:defcfun ("SSL_use_certificate_file" ssl-use-certificate-file)
+ :int
+ (ssl ssl-pointer)
+ (str :string)
+ (type :int))
+(cffi:defcfun ("SSL_CTX_load_verify_locations" ssl-ctx-load-verify-locations)
+ :int
+ (ctx ssl-ctx)
+ (CAfile :string)
+ (CApath :string))
+(cffi:defcfun ("SSL_CTX_set_client_CA_list" ssl-ctx-set-client-ca-list)
+ :void
+ (ctx ssl-ctx)
+ (list ssl-pointer))
+(cffi:defcfun ("SSL_load_client_CA_file" ssl-load-client-ca-file)
+ ssl-pointer
+ (file :string))
+
+(cffi:defcfun ("SSL_CTX_ctrl" ssl-ctx-ctrl)
+ :long
+ (ctx ssl-ctx)
+ (cmd :int)
+ (larg :long)
+ (parg :long))
+
+
+;;; Funcall wrapper
+;;;
+(defvar *socket*)
+
+(declaim (inline ensure-ssl-funcall))
+(defun ensure-ssl-funcall (*socket* handle func sleep-time &rest args)
+ (loop
+ (handler-case
+ (let ((rc (apply func args)))
+ (when (plusp rc)
+ (return rc))
+ (ssl-signal-error handle func (ssl-get-error handle rc) rc))
+ (ssl-error-want-something (condition)
+ (declare (ignore condition))
+ ;; FIXME: what is this SLEEP business for?
+ ;; Do we still need this?
+ (warn "sleeping in ensure-ssl-funcall")
+ (sleep sleep-time)))))
+
+
+;;; Initialization
+;;;
+(defun init-prng ()
+ ;; this initialization of random entropy is not necessary on
+ ;; Linux, since the OpenSSL library automatically reads from
+ ;; /dev/urandom if it exists. On Solaris it is necessary.
+ (let ((buf (cffi-sys::make-shareable-byte-vector +random-entropy+)))
+ (dotimes (i +random-entropy+)
+ (setf (elt buf i) (random 256)))
+ (cffi-sys::with-pointer-to-vector-data (ptr buf)
+ (rand-seed ptr +random-entropy+))))
+
+(defun ssl-ctx-set-session-cache-mode (ctx mode)
+ (ssl-ctx-ctrl ctx +SSL_CTRL_SET_SESS_CACHE_MODE+ mode 0))
+
+(defun initialize (&optional (method 'ssl-v23-method))
+ (setf *bio-lisp-method* (make-bio-lisp-method))
+ (ssl-load-error-strings)
+ (ssl-library-init)
+ (init-prng)
+ (setf *ssl-global-method* (funcall method))
+ (setf *ssl-global-context* (ssl-ctx-new *ssl-global-method*))
+ (ssl-ctx-set-session-cache-mode *ssl-global-context* 3))
+
+(defun ensure-initialized (&optional (method 'ssl-v23-method))
+ (unless (ssl-initialized-p)
+ (initialize method))
+ (unless *bio-lisp-method*
+ (setf *bio-lisp-method* (make-bio-lisp-method))))
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..e1633d5
--- /dev/null
+++ b/index.css
@@ -0,0 +1,66 @@
+div.sidebar {
+ float: right;
+ background-color: #eeeeee;
+ border: 2pt solid black;
+ margin: 0em 2pt 1em 2em;
+ min-width: 15%;
+ padding: 0pt 5pt 5pt 5pt;
+}
+
+div.sidebar ul {
+ padding: 0pt 0pt 0pt 1em;
+ margin: 0 0 1em;
+}
+
+body {
+ color: #000000;
+ background-color: #ffffff;
+ margin-right: 0pt;
+ margin-bottom: 10%;
+ padding-left: 30px;
+}
+
+h1,h2 {
+ background-color: darkred;
+ color: white;
+ margin-left: -30px;
+}
+
+th {
+ background-color: darkred;
+ color: white;
+ text-align: left;
+}
+
+pre {
+ background-color: #eeeeee;
+ border: solid 1px #d0d0d0;
+ padding: 1em;
+ margin-right: 10%;
+}
+
+.def {
+ background-color: #ddddff;
+ font-weight: bold;
+}
+
+.nomargin {
+ margin-bottom: 0;
+ margin-top: 0;
+}
+
+.working {
+ background-color: #60c060;
+}
+
+.broken {
+ background-color: #ff6060;
+}
+
+.incomplete {
+ background-color: #ffff60;
+}
+
+.unknown {
+ background-color: #cccccc;
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..841fbe1
--- /dev/null
+++ b/index.html
@@ -0,0 +1,199 @@
+
+
+
+
+ CL+SSL
+
+
+
+ CLplus SSL
+
+ Subprojects
+
+
+ Download
+ $ export CVSROOT=:pserver:anonymous@common-lisp.net:/project/cl-plus-ssl/cvsroot
+$ cvs login
+password: anonymous
+$ cvs co cl+ssl
+$ cvs co trivial-gray-streams
+$ cvs co trivial-https
+
+ Note that you need the libssl-dev package on Debian to
+ load this package without manual configuration.
+
+
+
+ Send bug reports to cl-plus-ssl-devel@common-lisp.net
+ (list
+ information )
+ or David Lichteblau .
+
+
+
+ CL+SSL
+
+ A simple Common Lisp interface to OpenSSL.
+
+ About
+
+
+ This library is a fork of SSL-CMUCL . The original
+ SSL-CMUCL source code was written by Eric Marsden and includes
+ contributions by Jochen Schmidt. License: LGPL.
+
+
+
+
+ CL+SSL is portable code based on CFFI and gray streams.
+
+
+ It defines its own libssl BIO method, so that SSL I/O is
+ actually written over portable Lisp streams instead of bypassing
+ the streams and sending data over Unix file descriptors directly.
+
+
+
+
+ Comparison chart:
+
+
+
+
+
+ FFI
+ Streams
+ Lisp-BIO
+
+
+
+ CL+SSL
+ CFFI
+ gray, non-buffering
+ yes
+
+
+ CL-SSL
+ UFFI
+ gray, buffering [part of ACL-COMPAT ]
+ no
+
+
+ SSL-CMUCL
+ CMUCL/ALIEN
+ CMUCL, non-buffering
+ no
+
+
+
+ API functions
+
+
Variable CL+SSL-SYSTEM:*LIBSSL-PATHNAME*
+ Full pathname of the SSL library. Defaults
+ to /usr/lib/libssl.so . If the default is not correct for
+ your system, set this variable between loading the .asd file and
+ load-op'ing the system.
+
+
+
Function CL+SSL:MAKE-SSL-CLIENT-STREAM (stream)
+ Return an SSL stream for the client socket stream .
+ All reads and writes to this SSL stream will be pushed through the
+ SSL connection can be closed using the standard close function.
+
+
+
Function CL+SSL:MAKE-SSL-SERVER-STREAM (stream &key certificate key)
+ Return an SSL stream for the server socket stream . All
+ reads and writes to this server stream will be pushed through the
+ OpenSSL library. The SSL connection can be closed using the
+ standard close function.
+
+
+ certificate is the path to a file containing the PEM-encoded
+ certificate for your server. key is the path to the PEM-encoded
+ key for the server, which must not be associated with a
+ passphrase.
+
+
+ Portability
+
+ CL+SSL requires CFFI with callback support.
+
+
+ Test results for Linux/x86, except OpenMCL which was tested on
+ Linux/PPC:
+
+
+
+
+ Lisp Implementation
+ Status
+ Comments
+
+
+ OpenMCL Working
+ SBCL Working
+ CMU CL Working
+
+ CLISP
+ Working
+ Extremely slow?
+
+ LispWorks Working
+
+ Allegro
+ Broken
+ segfault
+
+ Corman CL Unknown
+ Digitool MCL Unknown
+ Scieneer CL Unknown
+ ECL Unknown
+ GCL Unknown
+
+
+ TODO
+
+ Profile and optimize if needed. (CLISP?)
+ Implement remaining gray streams methods.
+ Add external format support on Unicode-capable Lisps.
+
+ Maybe
+
+ Add buffering to gray streams layer?
+ Add simple-streams layer instead of gray streams?
+
+
+
+ trivial-https
+
+
+ trivial-https is a fork of Brian
+ Mastenbrook's trivial-http adding
+ support for HTTPS using CL+SSL.
+
+
+
+ README
+
+
+
+ trivial-gray-streams
+
+
+ trivial-gray-streams provides an extremely thin compatibility
+ layer for gray streams.
+
+
+
+ README
+
+
+
diff --git a/package.lisp b/package.lisp
new file mode 100644
index 0000000..51b58ce
--- /dev/null
+++ b/package.lisp
@@ -0,0 +1,13 @@
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+(in-package :cl-user)
+
+(defpackage :cl+ssl
+ (:use :common-lisp :trivial-gray-streams)
+ (:export #:ensure-initialized
+ #:make-ssl-client-stream
+ #:make-ssl-server-stream))
diff --git a/reload.lisp b/reload.lisp
new file mode 100644
index 0000000..90e8f64
--- /dev/null
+++ b/reload.lisp
@@ -0,0 +1,17 @@
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+;;; We do this in an extra file so that it happens
+;;; - after the asd file has been loaded, so that users can
+;;; customize *libssl-pathname* between loading the asd and LOAD-OPing
+;;; the actual sources
+;;; - before ssl.lisp is loaded, which needs the library at compilation
+;;; time on some implemenations
+;;; - but not every time ssl.lisp is re-loaded as would happen if we
+;;; put this directly into ssl.lisp
+
+(in-package :cl+ssl-system)
+(cffi:load-foreign-library *libssl-pathname*)
diff --git a/streams.lisp b/streams.lisp
new file mode 100644
index 0000000..d78feea
--- /dev/null
+++ b/streams.lisp
@@ -0,0 +1,171 @@
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+(declaim
+ (optimize (speed 3) (space 1) (safety 1) (debug 0) (compilation-speed 0)))
+
+(in-package :cl+ssl)
+
+(defconstant +initial-buffer-size+ 2048)
+
+(defclass ssl-stream
+ (fundamental-binary-input-stream
+ fundamental-binary-output-stream
+ fundamental-character-input-stream
+ fundamental-character-output-stream
+ trivial-gray-stream-mixin)
+ ((ssl-stream-socket
+ :initarg :socket
+ :accessor ssl-stream-socket)
+ (handle
+ :initform nil
+ :accessor ssl-stream-handle)
+ (io-buffer
+ :initform (cffi-sys::make-shareable-byte-vector +initial-buffer-size+)
+ :accessor ssl-stream-io-buffer)))
+
+(defmethod print-object ((object ssl-stream) stream)
+ (print-unreadable-object (object stream :type t)
+ (format stream "for ~A" (ssl-stream-socket object))))
+
+(defclass ssl-server-stream (ssl-stream)
+ ((certificate
+ :initarg :certificate
+ :accessor ssl-stream-certificate)
+ (key
+ :initarg :key
+ :accessor ssl-stream-key)))
+
+
+;;; binary stream implementation
+;;;
+(defmethod close ((stream ssl-stream) &key abort)
+ (declare (ignore abort))
+ (ssl-free (ssl-stream-handle stream))
+ (close (ssl-stream-socket stream)))
+
+(defmethod stream-read-byte ((stream ssl-stream))
+ (let ((buf (ssl-stream-io-buffer stream)))
+ (handler-case
+ (cffi-sys::with-pointer-to-vector-data (ptr buf)
+ (ensure-ssl-funcall (ssl-stream-socket stream)
+ (ssl-stream-handle stream)
+ #'ssl-read
+ 5.5
+ (ssl-stream-handle stream)
+ ptr
+ 1)
+ (elt buf 0))
+ ;; SSL_read returns 0 on end-of-file
+ (ssl-error-zero-return ()
+ :eof))))
+
+(defmethod stream-write-byte ((stream ssl-stream) b)
+ (let ((buf (ssl-stream-io-buffer stream))
+ (handle (ssl-stream-handle stream))
+ (socket (ssl-stream-socket stream)))
+ (setf (elt buf 0) b)
+ (cffi-sys::with-pointer-to-vector-data (ptr buf)
+ (ensure-ssl-funcall socket handle #'ssl-write 0.5 handle ptr 1)))
+ b)
+
+(defmethod stream-write-sequence
+ ((stream ssl-stream) (thing array)
+ &optional (start 0) (end (length thing)))
+ (check-type thing (simple-array (unsigned-byte 8) (*)))
+ (let ((buf (ssl-stream-io-buffer stream))
+ (handle (ssl-stream-handle stream))
+ (socket (ssl-stream-socket stream))
+ (length (- end start)))
+ (when (> length (length buf))
+ (setf buf (cffi-sys::make-shareable-byte-vector (- end start)))
+ (setf (ssl-stream-io-buffer stream) buf))
+ ;; unfortunately, we cannot count on being able to use THING as an
+ ;; argument to WITH-POINTER-TO-VECTOR-DATA, so we need to copy all data:
+ (replace buf thing :start2 start :end2 end)
+ (cffi-sys::with-pointer-to-vector-data (ptr buf)
+ (ensure-ssl-funcall socket handle #'ssl-write 0.5 handle ptr length))))
+
+
+;;; minimal character stream implementation
+;;; no support for external formats, no support for unread-char
+;;;
+(defmethod stream-read-char ((stream ssl-stream))
+ (let ((b (stream-read-byte stream)))
+ (if (eql b :eof)
+ :eof
+ (code-char b))))
+
+(defmethod stream-write-char ((stream ssl-stream) char)
+ (stream-write-byte stream (char-code char))
+ char)
+
+(defmethod stream-write-sequence
+ ((stream ssl-stream) (thing string) &optional start end)
+ (let ((bytes (map '(simple-array (unsigned-byte 8) (*)) #'char-code thing)))
+ (stream-write-sequence stream bytes start end)))
+
+(defmethod stream-line-column ((stream ssl-stream))
+ nil)
+
+(defmethod stream-listen ((stream ssl-stream))
+ (warn "stream-listen")
+ (call-next-method))
+
+(defmethod stream-read-char-no-hang ((stream ssl-stream))
+ (warn "stream-read-char-no-hang")
+ (call-next-method))
+
+(defmethod stream-peek-char ((stream ssl-stream))
+ (warn "stream-peek-char")
+ (call-next-method))
+
+
+;;; interface functions
+;;;
+(defun make-ssl-client-stream (socket &key (method 'ssl-v23-method))
+ "Returns an SSL stream for the client socket descriptor SOCKET."
+ (ensure-initialized method)
+ (let ((stream (make-instance 'ssl-stream :socket socket))
+ (handle (ssl-new *ssl-global-context*)))
+ (setf (ssl-stream-handle stream) handle)
+ ;; (let ((bio (bio-new-socket socket 0))) (ssl-set-bio handle bio bio))
+ (ssl-set-bio handle (bio-new-lisp) (bio-new-lisp))
+ (ssl-set-connect-state handle)
+ (ensure-ssl-funcall socket handle #'ssl-connect 0.25 handle)
+ stream))
+
+(defun make-ssl-server-stream
+ (socket &key certificate key (method 'ssl-v23-method))
+ "Returns an SSL stream for the server socket descriptor SOCKET.
+CERTIFICATE is the path to a file containing the PEM-encoded certificate for
+ your server. KEY is the path to the PEM-encoded key for the server, which
+must not be associated with a passphrase."
+ (ensure-initialized method)
+ (let ((stream (make-instance 'ssl-server-stream
+ :socket socket
+ :certificate certificate
+ :key key))
+ (handle (ssl-new *ssl-global-context*))
+ (bio (bio-new-lisp)))
+ (setf (ssl-stream-handle stream) handle)
+ (ssl-set-bio handle bio bio)
+ (ssl-set-accept-state handle)
+ (when (zerop (ssl-set-cipher-list handle "ALL"))
+ (error 'ssl-error-initialize :reason "Can't set SSL cipher list"))
+ (when key
+ (unless (eql 1 (ssl-use-rsa-privatekey-file handle
+ key
+ +ssl-filetype-pem+))
+ (error 'ssl-error-initialize :reason "Can't load RSA private key ~A")))
+ (when certificate
+ (unless (eql 1 (ssl-use-certificate-file handle
+ certificate
+ +ssl-filetype-pem+))
+ (error 'ssl-error-initialize
+ :reason "Can't load certificate ~A" certificate)))
+ (ensure-ssl-funcall socket handle #'ssl-accept 0.25 handle)
+ stream))
diff --git a/test.lisp b/test.lisp
new file mode 100644
index 0000000..3b89f8b
--- /dev/null
+++ b/test.lisp
@@ -0,0 +1,95 @@
+;;; Copyright (C) 2001, 2003 Eric Marsden
+;;; Copyright (C) 2005 David Lichteblau
+;;; "the conditions and ENSURE-SSL-FUNCALL are by Jochen Schmidt."
+;;;
+;;; See LICENSE for details.
+
+#|
+(load "test.lisp")
+(ssl-test::test-https-client "www.google.com")
+(ssl-test::test-https-server)
+|#
+
+(defpackage :ssl-test
+ (:use :cl))
+(in-package :ssl-test)
+
+(eval-when (:compile-toplevel :load-toplevel :execute)
+ (asdf:operate 'asdf:load-op :trivial-sockets))
+
+(defun read-line-crlf (stream &optional eof-error-p)
+ (let ((s (make-string-output-stream)))
+ (loop
+ for empty = t then nil
+ for c = (read-char stream eof-error-p nil)
+ while (and c (not (eql c #\return)))
+ do
+ (unless (eql c #\newline)
+ (write-char c s))
+ finally
+ (return
+ (if empty nil (get-output-stream-string s))))))
+
+(defun test-nntps-client (&optional (host "snews.gmane.org") (port 563))
+ (let* ((fd (trivial-sockets:open-stream host port
+ :element-type '(unsigned-byte 8)))
+ (nntps (cl+ssl:make-ssl-client-stream fd)))
+ (format t "NNTPS> ~A~%" (read-line-crlf nntps))
+ (write-line "HELP" nntps)
+ (force-output nntps)
+ (loop :for line = (read-line-crlf nntps nil)
+ :until (string-equal "." line)
+ :do (format t "NNTPS> ~A~%" line))))
+
+
+;; open an HTTPS connection to a secure web server and make a
+;; HEAD request
+(defun test-https-client (host &optional (port 443))
+ (let* ((fd (trivial-sockets:open-stream host port
+ :element-type '(unsigned-byte 8)))
+ (https (cl+ssl:make-ssl-client-stream fd)))
+ (unwind-protect
+ (progn
+ (format https "HEAD / HTTP/1.0~%Host: ~a~%~%" host)
+ (force-output https)
+ (loop :for line = (read-line-crlf https nil)
+ :while line :do
+ (format t "HTTPS> ~a~%" line)))
+ (close https))))
+
+;; start a simple HTTPS server. See the mod_ssl documentation at
+;; for information on generating the
+;; server certificate and key
+;;
+;; You can stress-test the server with
+;;
+;; siege -c 10 -u https://host:8080/foobar
+;;
+(defun test-https-server
+ (&key (port 8080)
+ (cert "/home/david/newcert.pem")
+ (key "/home/david/newkey.pem"))
+ (format t "~&SSL server listening on port ~d~%" port)
+ (trivial-sockets:with-server (server (:port port))
+ (loop
+ (let ((client (cl+ssl:make-ssl-server-stream
+ (trivial-sockets:accept-connection
+ server
+ :element-type '(unsigned-byte 8))
+ :certificate cert
+ :key key)))
+ (unwind-protect
+ (progn
+ (loop :for line = (read-line-crlf client nil)
+ :while (> (length line) 1) :do
+ (format t "HTTPS> ~a~%" line))
+ (format client "HTTP/1.0 200 OK~%")
+ (format client "Server: SSL-CMUCL/1.1~%")
+ (format client "Content-Type: text/plain~%")
+ (terpri client)
+ (format client "G'day at ~A!~%"
+ (multiple-value-list (get-decoded-time)))
+ (format client "CL+SSL running in ~A ~A~%"
+ (lisp-implementation-type)
+ (lisp-implementation-version)))
+ (close client))))))