├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── lib.rs ├── tcp.rs └── types.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.bk 4 | build.sh 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "caesar" 3 | description = "A drop-in replacement for the Rust standard library TCP listener with TLSv1.2 enabled." 4 | documentation = "https://arturovm.me/rustdoc/caesar/index.html" 5 | repository = "https://github.com/Postage/caesar" 6 | readme = "README.md" 7 | keywords = ["tls", "ssl", "tcp", "crypto"] 8 | license = "MIT" 9 | 10 | version = "0.2.0" 11 | authors = ["Arturo Vergara "] 12 | 13 | [dependencies] 14 | openssl = "0.7.6" 15 | 16 | [features] 17 | default = ["tlsv1_2", "rfc5114"] 18 | 19 | tlsv1_2 = ["openssl/tlsv1_2"] 20 | rfc5114 = ["openssl/rfc5114"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Arturo Vergara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Caesar 2 | 3 | A drop-in replacement for the Rust standard library TCP listener with TLSv1.2 enabled. 4 | 5 | - [Documentation](https://arturovm.me/rustdoc/caesar/index.html) 6 | - [Crate](https://crates.io/crates/caesar) 7 | 8 | _Note: This library hasn't been tested._ 9 | 10 | ## Introduction 11 | 12 | This library abstracts over a regular TCP listener from the Rust standard library, and provides a drop-in* interface replacement that layers TLSv1.2 with a set of strong cipher suites on top of the connection. 13 | 14 | It uses the [OpenSSL library Rust bindings](https://github.com/sfackler/rust-openssl) by Steven Fackler for the underlying TLS functionality. If you don't trust OpenSSL (I don't), you can compile this crate against LibreSSL (instructions provided below). 15 | 16 | _* It's only necessary to prepend `Tls` to the regular types (e.g. `TlsTcpListener`), and write error handling code for the new error types._ 17 | 18 | ## Compiling 19 | 20 | ### Prerequisites 21 | 22 | - OpenSSL/LibreSSL headers 23 | 24 | How and where you install these headers depends on your platform. A quick Google search should do the trick. 25 | 26 | ### Building 27 | 28 | As described in the [README](https://github.com/sfackler/rust-openssl) for the Rust OpenSSL bindings, there are various ways of compiling this crate, depending on your platform. However, the most universal route, is to configure your build manually with environment variables (and this is required anyway for compiling against LibreSSL). There are only three: 29 | 30 | - `OPENSSL_LIB_DIR`: Use this to specify the path to the _lib_ dir of your SSL library of choice 31 | - `OPENSSL_INCLUDE_DIR`: Use this to specify the path to the _include_ dir of your SSL library of choice 32 | - `OPENSSL_STATIC`: [optional] This is a boolean variable specifying whether to statically link against your SSL library 33 | 34 | The complete command might look something like this: 35 | 36 | ```bash 37 | $ env OPENSSL_LIB_DIR="/usr/local/opt/libressl/lib" \ 38 | OPENSSL_INCLUDE_DIR="/usr/local/opt/libressl/include" \ 39 | OPENSSL_STATIC=true cargo build 40 | ``` 41 | ## Usage 42 | 43 | Caesar provides a `CaesarError` enum type, with a variant for I/O errors and another one for SSL errors. You can find the specifics in the library [docs](https://arturovm.me/rustdoc/caesar/index.html). 44 | 45 | ```rust 46 | extern crate caesar; 47 | 48 | use caesar::{TlsTcpListener, TlsTcpStream}; 49 | use std::thread; 50 | 51 | let key_path = "path_to_certs/key.pem"; 52 | let cert_path = "path_to_certs/cert.pem"; 53 | 54 | let listener = TlsTcpListener::bind("127.0.0.1:8080", key_path, cert_path).unwrap() 55 | 56 | fn handle_client(stream: TlsTcpStream) { 57 | // ... 58 | } 59 | 60 | // accept connections and process them, spawning a new thread for each one 61 | for stream in listener.incoming() { 62 | match stream { 63 | Ok(stream) => { 64 | thread::spawn(move || { 65 | // connection succeeded 66 | handle_client(stream) 67 | }); 68 | } 69 | Err(e) => { /* connection failed */ } 70 | } 71 | } 72 | 73 | // close the socket server 74 | drop(listener); 75 | ``` 76 | 77 | ## Notes 78 | 79 | This crate was written to satisfy my needs while writing [Postage](https://github.com/Postage/postage). As such, only a minimal set of functions was ported over from the `TcpListener` interface. I might continue to expand this set of functions as the need arises; if you need the functionality earlier, and I haven't implemented it yet, you are welcome to contribute. 80 | 81 | ## To Do 82 | 83 | - [ ] Write tests 84 | - [ ] Write proper documentation 85 | - [ ] Code review 86 | 87 | ## License 88 | 89 | This code is licensed under the MIT License. See `LICENSE`. 90 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A drop-in replacement for the Rust standard library TCP listener with TLSv1.2 enabled. 2 | //! 3 | //! _Note: This library hasn't been tested._ 4 | //! 5 | //! ## Introduction 6 | //! 7 | //! This library abstracts over a regular TCP listener from the Rust standard library, 8 | //! and provides a drop-in* interface replacement that layers TLSv1.2 with a set of 9 | //! strong cipher suites on top of the connection. 10 | //! 11 | //! It uses the [OpenSSL library Rust bindings](https://github.com/sfackler/rust-openssl) 12 | //! by Steven Fackler for the underlying TLS functionality. If you don't trust OpenSSL (I don't), 13 | //! you can compile this crate against LibreSSL (instructions provided below). 14 | //! 15 | //! _* It's only necessary to prepend `Tls` to the regular types (e.g. `TlsTcpListener`), 16 | //! and write error handling code for the new error types._ 17 | //! 18 | //! ## Usage 19 | //! 20 | //! Caesar provides a `CaesarError` enum type, with a variant for I/O errors and another one 21 | //! for SSL errors. 22 | //! 23 | //! ```rust 24 | //! extern crate caesar; 25 | //! 26 | //! use caesar::{TlsTcpListener, TlsTcpStream}; 27 | //! use std::thread; 28 | //! 29 | //! let key_path = "path_to_certs/key.pem"; 30 | //! let cert_path = "path_to_certs/cert.pem"; 31 | //! 32 | //! let listener = TlsTcpListener::bind("127.0.0.1:8080", key_path, cert_path).unwrap() 33 | //! 34 | //! fn handle_client(stream: TlsTcpStream) { 35 | //! // ... 36 | //! } 37 | //! 38 | //! // accept connections and process them, spawning a new thread for each one 39 | //! for stream in listener.incoming() { 40 | //! match stream { 41 | //! Ok(stream) => { 42 | //! thread::spawn(move || { 43 | //! // connection succeeded 44 | //! handle_client(stream) 45 | //! }); 46 | //! } 47 | //! Err(e) => { /* connection failed */ } 48 | //! } 49 | //! } 50 | //! 51 | //! close the socket server 52 | //! drop(listener); 53 | //! ``` 54 | 55 | extern crate openssl; 56 | 57 | mod types; 58 | mod tcp; 59 | 60 | pub use types::{Result, CaesarError}; 61 | pub use tcp::*; 62 | -------------------------------------------------------------------------------- /src/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::io::{self, Read, Write}; 3 | use std::net::{SocketAddr, ToSocketAddrs, TcpListener, TcpStream, Shutdown}; 4 | use std::time::Duration; 5 | 6 | use openssl::ssl::{self, SslContext, SslMethod, Ssl, SslStream}; 7 | use openssl::dh::DH; 8 | use openssl::x509::X509FileType; 9 | 10 | use super::types::Result; 11 | 12 | /// Abstracts over a `TcpListener`, and layers TLSv1.2 on top. 13 | #[derive(Debug)] 14 | pub struct TlsTcpListener { 15 | listener: TcpListener, 16 | ctx: SslContext, 17 | } 18 | 19 | /// Handles encrypted `TcpStream`s for you, and provides a 20 | /// simple read/write interface. 21 | #[derive(Debug)] 22 | pub struct TlsTcpStream(RefCell>); 23 | 24 | 25 | /// A re-implementation of the `std::net::Incoming` iterator type. 26 | #[derive(Debug)] 27 | pub struct Incoming<'a> { 28 | listener: &'a TlsTcpListener, 29 | } 30 | 31 | impl TlsTcpListener { 32 | /// Behaves exactly like `TcpStream::bind`. It expects paths to key and certificate files in 33 | /// PEM format. 34 | pub fn bind(addr: A, key: &str, cert: &str) -> Result { 35 | // create listener 36 | let listener = try!(TcpListener::bind(addr)); 37 | let ctx = try!(new_ssl_context(&key, &cert)); 38 | Ok(TlsTcpListener { 39 | listener: listener, 40 | ctx: ctx, 41 | }) 42 | } 43 | 44 | pub fn accept(&self) -> Result<(TlsTcpStream, SocketAddr)> { 45 | // acept from bare TCP stream 46 | let (stream, addr) = try!(self.listener.accept()); 47 | // create SSL object with stored context 48 | let ssl = try!(Ssl::new(&self.ctx)); 49 | // accept from encrypted stream 50 | let tls_stream = try!(SslStream::accept(ssl, stream)); 51 | Ok((TlsTcpStream(RefCell::new(tls_stream)), addr)) 52 | } 53 | 54 | pub fn incoming(&self) -> Incoming { 55 | Incoming { listener: self } 56 | } 57 | } 58 | 59 | impl<'a> Iterator for Incoming<'a> { 60 | type Item = Result; 61 | fn next(&mut self) -> Option> { 62 | Some(self.listener.accept().map(|p| p.0)) 63 | } 64 | } 65 | 66 | impl TlsTcpStream { 67 | pub fn peer_addr(&self) -> Result { 68 | Ok(try!(self.0.borrow().get_ref().peer_addr())) 69 | } 70 | 71 | pub fn local_addr(&self) -> Result { 72 | Ok(try!(self.0.borrow().get_ref().local_addr())) 73 | } 74 | 75 | pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { 76 | self.0.borrow().get_ref().shutdown(how) 77 | } 78 | 79 | pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { 80 | self.0.borrow().get_ref().set_read_timeout(dur) 81 | } 82 | } 83 | 84 | impl Read for TlsTcpStream { 85 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 86 | self.0.borrow_mut().read(buf) 87 | } 88 | } 89 | 90 | impl Write for TlsTcpStream { 91 | fn write(&mut self, buf: &[u8]) -> io::Result { 92 | self.0.borrow_mut().write(buf) 93 | } 94 | 95 | fn flush(&mut self) -> io::Result<()> { 96 | self.0.borrow_mut().flush() 97 | } 98 | } 99 | 100 | impl<'a> Read for &'a TlsTcpStream { 101 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 102 | self.0.borrow_mut().read(buf) 103 | } 104 | } 105 | 106 | impl<'a> Write for &'a TlsTcpStream { 107 | fn write(&mut self, buf: &[u8]) -> io::Result { 108 | self.0.borrow_mut().write(buf) 109 | } 110 | 111 | fn flush(&mut self) -> io::Result<()> { 112 | self.0.borrow_mut().flush() 113 | } 114 | } 115 | 116 | fn new_ssl_context(key: &str, cert: &str) -> Result { 117 | let mut ctx = try!(ssl::SslContext::new(SslMethod::Tlsv1_2)); 118 | // use recommended settings 119 | let opts = ssl::SSL_OP_CIPHER_SERVER_PREFERENCE | ssl::SSL_OP_NO_COMPRESSION | 120 | ssl::SSL_OP_NO_TICKET | ssl::SSL_OP_NO_SSLV2 | 121 | ssl::SSL_OP_NO_SSLV3 | ssl::SSL_OP_NO_TLSV1 | ssl::SSL_OP_NO_TLSV1_1; 122 | ctx.set_options(opts); 123 | // set strong cipher suites 124 | try!(ctx.set_cipher_list("ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\ 125 | ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\ 126 | ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\ 127 | DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:\ 128 | ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\ 129 | ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:\ 130 | ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\ 131 | ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:\ 132 | DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:\ 133 | ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\ 134 | EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:\ 135 | AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:\ 136 | !DSS")); 137 | // enable forward secrecy 138 | let dh = try!(DH::get_2048_256()); 139 | try!(ctx.set_tmp_dh(dh)); 140 | // set cert and key files 141 | try!(ctx.set_private_key_file(key, X509FileType::PEM)); 142 | try!(ctx.set_certificate_file(cert, X509FileType::PEM)); 143 | // check integrity 144 | try!(ctx.check_private_key()); 145 | Ok(ctx) 146 | } 147 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use std::{io, result, error, fmt}; 2 | use std::convert::From; 3 | 4 | use openssl::ssl::error::SslError; 5 | 6 | #[derive(Debug)] 7 | pub enum CaesarError { 8 | Ssl(SslError), 9 | Io(io::Error), 10 | } 11 | 12 | pub type Result = result::Result; 13 | 14 | impl fmt::Display for CaesarError { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | match *self { 17 | CaesarError::Ssl(ref e) => write!(f, "SSL error: {}", e), 18 | CaesarError::Io(ref e) => write!(f, "IO error: {}", e), 19 | } 20 | } 21 | } 22 | 23 | impl error::Error for CaesarError { 24 | fn description(&self) -> &str { 25 | match *self { 26 | CaesarError::Ssl(ref e) => e.description(), 27 | CaesarError::Io(ref e) => e.description(), 28 | } 29 | } 30 | 31 | fn cause(&self) -> Option<&error::Error> { 32 | match *self { 33 | CaesarError::Ssl(ref e) => Some(e), 34 | CaesarError::Io(ref e) => Some(e), 35 | } 36 | } 37 | } 38 | 39 | impl From for CaesarError { 40 | fn from(err: SslError) -> CaesarError { 41 | CaesarError::Ssl(err) 42 | } 43 | } 44 | 45 | impl From for CaesarError { 46 | fn from(err: io::Error) -> CaesarError { 47 | CaesarError::Io(err) 48 | } 49 | } 50 | --------------------------------------------------------------------------------