cl-tls

2023-10-21

An implementation of the Transport Layer Security Protocols

Upstream URL

github.com/shrdlu68/cl-tls

Author

Brian Kamotho

License

BSD-3-Clause
README

CL-TLS is a prototype Common Lisp implementation of TLS and related protocols and standards including:

  • RFC5246
  • ASN.1
  • x{501,509}
  • PKCS{1,3,5,8}

TLS is the IETF-standardized successor of Netscape's SSL protocol. Sometimes TLS/SSL are used interchangeably. At this point, there is no intention to support the older versions of the protocols (SSLv3 and below)

The project is currently in its early development phase and should only be used for experimental purposes.

CL-TLS uses Ironclad as its cryptographic back-end (at the time of writing, the ironclad in quicklisp has not yet been updated to the new maintained repo, use sharplispers/ironclad). See the system definition file for other dependencies

The project started as an attempt to create a network-based TLS fuzzer, but soon morphed of its own volition into a full-fledged TLS implementation.

Extensive testing, fuzzing, and code review is needed. Style guidelines, optimizations, feature-completion patches, bug fixes, and other contributions are welcome.

For an overview of known attacks against TLS and other issues relevant to implementors and users, see https://tools.ietf.org/html/rfc7457 At a minimum, CL-TLS will follow the recommendations and considerations in the aforementioned document before a proper alpha release is announced.

I also intend to fully document the internals of CL-TLS.

So far:

  • You can initiate sessions and exchange data through a TLS tunnel either as a server or a client, for RSA, DSA, and Diffie-Hellman cipher suites. There is no support for ECDSA suites yet.
  • Certificate path validation works for certificates signed using RSA or DSA. As above, no support for ECDSA signature verification yet.
  • Support for PEM-encoded certificate chains.
  • Support for most features of the TLS 1.2 spec such as fragmentation and session renegotiation.

Major TODOs before alpha release:

  • Finalizing work on hello extensions.
  • Fuzzing.
  • Speed/Memory optimizations.

Features that would be nice, but are not essential

  • Support for initializing with multiple certificates
  • ECDSA/ECDH/ECDHE support
  • Session resumption support
  • Support for post-RFC5246 cipher suites
  • rfc{4346,2246,6520}
  • pkcs#{7,12}
  • DTLS
  • Tools for the generation and management of public/private keys and certificates
  • DANE

CL-TLS does not offer gray streams, threading, sockets, event-loop, or compression functionality. This limits the code of CL-TLS to simply opening and managing a TLS tunnel through an octet stream and enhances portability and extensibility. However, libraries that offer this functionality (such as threaded or evented servers), can be built trivially on top of CL-TLS. See https://github.com/shrdlu68/secure-sockets as an example.

Here is an example of a simple text echo server that implements a simple one-thread-per-request model using usocket and bordeaux-threads:

(require :cl-tls)

(ql:quickload :bordeaux-threads)
(ql:quickload :usockets)
(ql:quickload :babel)

(defun echo-server (port &optional (host "localhost"))
  (let ((sock (usocket:socket-listen host port
				     :reuse-address t
				     :element-type '(unsigned-byte 8))))
    (cl-tls:initialize-listener
     :certificate "/path/to/cert.pem"
     :private-key "/path/to/key.pem")
    (loop
      for thread-id upfrom 0
      for new-sock = (usocket:socket-accept sock)
      for session-stream = (usocket:socket-stream new-sock)
      for handler = (multiple-value-bind (reader writer)
			(cl-tls:accept-tunnel :io-stream session-stream)
		      (lambda ()
			(loop
			  with id = thread-id
			  for in = (funcall reader)
			  for line = (and in
					  (babel:octets-to-string in))
			  if line do
			    (format t "~&~:R thread received data: ~A~%"
				    id line)
			    (funcall writer in)
			  else do
			    (format t
				    "~&Peer closed tunnel, ~:R thread exiting~%" id)
			    (return nil))))
      do
	 (bordeaux-threads:make-thread handler))))

And here is an example using client functionality:

(require :cl-tls)
(ql:quickload :babel)

(defun client-test (port &optional (host "localhost"))
  (let* ((sock (usocket:socket-connect
		host port
		:protocol :stream
		:element-type '(unsigned-byte 8))))
    (multiple-value-bind (reader writer close-callback)
	(cl-tls:request-tunnel
	 :certificate "/path/to/cert.pem"
	 :private-key "/path/to/key.pem"
	 :io-stream (usocket:socket-stream sock)
	 :peer-ip-addresses '((127 0 0 1))
	 :ca-certificates "/path/to/CA/ca-cert.pem")
      (loop
	for out = (read-line *standard-input* nil nil)
	for line = (and out
			(babel:string-to-octets out))
	if line do
	  (funcall writer line)
	  (let ((in (funcall reader)))
	    (cond (in
		   (format t "~&Received data: ~A~%"
			   (babel:octets-to-string in)))
		  (t
		   (funcall close-callback)
		   (return nil))))
	else do
	  (format t "~&Closing tunnel...~%")
	  (funcall close-callback)
	  (return nil)))))

Preliminary API documentation:

request-tunnel (&key certificate private-key ca-certificates
	       	     io-stream input-stream output-stream
		     include-ciphers exclude-ciphers
		     peer-dns-name peer-ip-addresses)

		Attempts to request a TLS tunnel from a server
		through an octet stream
		
		:io-stream A duplex octet stream
		
		:input-stream In the case where separate input and output
			      streams are used, an octet input stream
			      
		:output-stream An octet output stream

		:private-key A (DER/PEM-encoded) private key
			     (currently only DSA, RSA and DH private keys are supported)
			     If the private key is encrypted, you will be prompted for
			     the passphrase
			     
		:certificate A file containing a PEM-encoded list of one
			     or more cerificates
			     The file should consist of one or more
			     ----- BEGIN CERTIFICATE -----
			     ----- END CERTIFICATE -----
			     blocks. White space and other information around these blocks
			     is permitted and will be ignored. Support for PKCS#12-encoded
			     certificate chains is not yet implemented.
		:ca-certificates Either: 1. A directory to look for .crt, .pem, and .der
				 	    CA certificates in
					 2. A PEM-encoded file containing CA certificates,
					    encoded as explained above.
				 For servers, these are only needed if the server
				 needs to authenticate clients, i.e the clients are
				 expected to have client certificates issued to them.
				 This is not used very widely in practice.
				 For clients, CA certificates are used to authenticate
				 servers.
				 TLS clients such as browsers typically use a collection
				 of root certificates that are included by some means of
				 vetting, either provided by the operating system or
				 by the creators/maintainers of the browser.
				 You can use the CAs that your OS/browser has vetted,
				 or you can select which CAs to trust by yourself.
				 For example, most GNU/Linux systems have a package that
				 provides vetted root certificates, typically found in
				 the file "/etc/ssl/certs/ca-certificates.crt"
				 See also: https://curl.haxx.se/docs/sslcerts.html
				 
				 If you want to or have issued your own certificates,
				 include your CA certificate here, either in
				 addition to other CA certificates or as the sole
				 CA certificates. The client and server will only
				 be able to validate remote endpoints whose certificates
				 are signed by a CA certificate that is included,
				 there is no way to "accept" unvalidated certificates.

		
		:include-ciphers A list of symbols of cipher suites to add to the
				 default cipher list.
				 The symbols denote one of the cryptographic characteristics
				 of a cipher suite: key exchange, authentication
				 bulk encryption, mac, digest algorithms, prf, and key sizes.
				 Currently supported options:
				 :rsa-ke (RSA key exchange)
				 :rsa-auth (RSA authentication)
				 :dh (static dh) :dhe (ephemeral dh)
				 :dsa :anon (no authentication-vulnerable to MITM attacks!)
				 :rc4 (broken and prohibited) :3des (broken and prohibited)
				 :aes128 :aes256 :cbc (cipher block chaining mode)
				 :md5 :sha1 :sha256
				 
		:exclude-ciphers A list of symbols of cipher suites to exclude
				 from the default cipher list.
				 :anon, :rc4 and :3des should be excluded by default
				 
		:peer-dns-name DNS-name of the peer. This is checked against
			       the dns-name values in the subject alternative name extension
			       of the certificate presented by the peer.
			       It is also used in the SNI extension, which is important
			       for virtual servers to determine which server the client
			       wants to contact.
		
		:peer-ip-addresses List of IP addresses of the peer
				   This is checked against the ip-address values
				   in the subject alternative name extension
			       	   of the certificate presented by the peer.
initialize-listener (&key certificate private-key ca-certificates
				include-ciphers exclude-ciphers force-reinitialize
				authenticate-client-p require-authentication-p
				dh-params)
			
		Loads resources needed for a server session and
		sets configuration options
		Servers need to call this once when initializing.
						 
		:authenticate-client-p When true, an attempt to authenticate the client
				       will be made. However, the client may send back an
				       empty certificate if it does not have
				       (an appropriate) certificate. This option defaults
				       to nil
				       
		:require-authentication-p When true, the server will be asked for a
					  certificate, and the connection will
					  fail if the client fails to provide a certificate.
					  This option defaults to nil.

accept-tunnel (&key io-stream input-stream output-stream)
	      => read-callback,write-callback,close-callback

	      Attempts to accept a client's request for a
	      TLS tunnel through an octet stream

		read-callback A closure whose argument list is in the form
			      (&key (eof-error-p nil) (eof-value nil))
			      that returns the contents
			      of one TLS record (2^14 bytes or less).
			      If :eof-error-p is true, the condition cl:end-of-file
			      will be signaled if the TLS tunnel is closed properly
			      (i.e a close_notify alert is received)
			      If :eof-error-p is false, eof-value is returned rather
			      than isgnalling end-of-file.
			      If an error is encountered while attempting to read from
			      the underlying stream (for example is a socket connection
			      is terminated without properly closing the TLS tunnel),
			      the condition cl:stream-error will be signalled.
		write-callback A closure of one argument, an octet vector of arbitrary size,
			       to be sent down the tunnel
			       Attempting to write to a closed TLS tunnel will
			       signal the condition cl:stream-error
			       If an error is encountered while attempting to write to
			       the underlying stream (for example is a socket connection
			       is terminated without properly closing the TLS tunnel),
			       the condition cl:stream-error will be signalled.
		close-callback A closure of no arguments that politely closes the TLS
			       connection by sending a close_notify alert.
(defclass address ()
  ((host :initarg :host
	 :accessor host)
   (port :initarg :port
	 :accessor port)))
request-stream-to-address (address)
		 =>octet stream
	Specialize this generic function and provide socket functionality.
	This is needed by some of the functionality in cl-tls that may need
	to open sockets, such as contacting OCSP responders.
	For example, to if you are using usockets:
	(defmethod cl-tls:request-stream-to-address ((addr cl-tls:address))
	  (usocket:socket-stream (usocket:socket-connect
	  (cl-tls:host addr) (cl-tls:port addr)
	  :protocol :stream
	  :element-type '(unsigned-byte 8))))

This API will likely change as development continues.

Dependencies (5)

  • alexandria
  • babel
  • cl-base64
  • fast-io
  • ironclad
  • GitHub
  • Quicklisp