Example: TLS echo server
This example shows how to run a TLS server in an enclave using the mbedtls crate. TLS provides a secure channel to exchange sensitive information between applications and between applications and users. Using EDP you can terminate the TLS connection inside the enclave to avail the security guarantees of the SGX architecture.
Best practices
Here are a few of the best practices you should follow while writing secure TLS applications:
- The application should not rely on untrusted sources for its key.
- A persistent key should be sealed.
- Along with normal certificate verification, applications should also verify attestation information before exchanging sensitive information.
Note, the example here is a basic prototype for a TLS echo server and does not demonstrate all of the points mentioned above.
Example code
Cargo
Add the following dependency in Cargo.toml
:
1
2
[dependencies]
mbedtls = {version="0.3.0", default-features = false, features = ["sgx"]}
Starting TLS server
To start a TLS server, the application has to first, bind to and listen on an IP socket.
1
2
3
4
5
6
7
8
9
10
fn main() {
let (mut key, mut cert) = get_key_and_cert();
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
let _ = serve(stream, &mut key, &mut cert).unwrap();
println!("Connection closed!");
}
}
The server will call the serve
for each incoming connection.
The server could optionally spawn a thread for each connection to handle the requests.
Establishing Connection
For each incoming stream, the server should build the mbedtls context with its TLS key and certificate, and then establish a connection as shown below. Once the connection is established, a connection handler can perform read/write IO operations on the session depending on the use case. Here, an echo server is implemented.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/// Establish a TLS connection with a randomly generated key and
/// a self signed certificate.
/// After a session is established, echo the incoming stream to the client
/// till EOF is detected.
fn serve(mut conn: TcpStream,
key: &mut Pk,
cert: &mut Certificate) -> TlsResult<()> {
let mut rng = Rdrand;
let mut config = Config::new(Endpoint::Server,
Transport::Stream,
Preset::Default);
config.set_rng(Some(&mut rng));
config.push_cert(&mut **cert, key)?;
let mut ctx = Context::new(&config)?;
let mut buf = String::new();
let session = ctx.establish(&mut conn, None)?;
println!("Connection established!");
let mut reader = BufReader::new(session);
while let Ok(1...std::usize::MAX) = reader.read_line(&mut buf) {
let session = reader.get_mut();
session.write_all(&buf.as_bytes()).unwrap();
buf.clear();
}
Ok(())
}
Generating certificates and keys.
Enclave application should not rely on untrusted sources of entropy for generating keys and signing certificates.
The mbedtls
crate provides the rdrand
feature (enabled as part of sgx
feature) to use x86 RDRAND
instruction as a source of entropy. The snippet demonstrates how to generate a random key and certificate using Rdrand
.
Note you can refer to the complete code for the implementation of get_validity()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn get_key_and_cert() -> (Pk, Certificate)
{
let mut rng = Rdrand;
let mut key = Pk::generate_rsa(&mut rng,
RSA_KEY_SIZE,
RSA_KEY_EXP).unwrap();
let mut key_i = Pk::generate_rsa(&mut rng,
RSA_KEY_SIZE,
RSA_KEY_EXP).unwrap();
let (not_before, not_after) = get_validity();
let cert = Certificate::from_der(&Builder::new()
.subject_key(&mut key)
.subject_with_nul("CN=mbedtls-server.example\0")
.unwrap()
.issuer_key(&mut key_i)
.issuer_with_nul("CN=mbedtls-server.example\0")
.unwrap()
.validity(
not_before,
not_after,
).unwrap()
.serial(&[5]).unwrap()
.signature_hash(Sha256)
.write_der_vec(&mut rng).unwrap()).unwrap();
(key, cert)
}
The complete code is available on GitHub