Go to file
occheung 10d9adaa8e smoltcp: bump to 0.7.0 2021-07-15 17:36:32 +08:00
src smoltcp: bump to 0.7.0 2021-07-15 17:36:32 +08:00
.gitignore update gitignore 2020-10-04 22:28:21 +08:00
Cargo.toml smoltcp: bump to 0.7.0 2021-07-15 17:36:32 +08:00
README.md readme: add verification details 2021-01-29 16:33:57 +08:00

README.md

SaiTLS

[no-std] Client & Server side TLS implementation on top of smoltcp, using allocator.

Features

This crate supports these algorithms:

  • Cipher suites:
    • TLS_AES_128_GCM_SHA256
    • TLS_AES_256_GCM_SHA384
    • TLS_CHACHA20_POLY1305_SHA256
    • TLS_AES_128_CCM_SHA256
  • Ecliptic curves:
    • X25519
    • secp256r1 (NIST P-256)
  • Digital signature algorithms:
    • ecdsa_secp256r1_sha256
    • ed25519
    • RSASSA-PKCS1-v1_5 algorithms
    • RSASSA-PSS algorithms

Implementation of all algorithms (except x25519 & ed25519) are directly sourced from the RustCrypto project.

Implementation of x25519 & ed25519 algorithms are directly sourced from the dalek-cryptography project.

Algorithms not listed above are NOT supported. Using certificates with unsupported algorithms will cause handshake/communication failure.

This crate supports these handshake features:

  • Certificate verification, with a single self-signed certificate
  • (Client) Client certificate verification, if requested by remote server
  • (Server) Send Client Certificate Request to remote client

These handshake features are NOT supported:

  • HelloRetryRequest
  • Pre-shared key & New session ticket
  • Early data
  • 0-RTT handshake
  • Verification of non-self-signed certificates, including certificate chain

TLS Socket

To create a TLS socket, the following items are needed:

  • TCP Socket from smoltcp
  • Random number generator (cryptographically secure RNG is recommended)
  • (Optional) Client certificates in x509 certificate & the associated private key

The RNG requires the TlsRng trait to be implemented, which further include these traits:

  • RngCore from rand_core
  • CryptoRng from rand_core

TLS socket can be instantiated by the following lines of code:

    // Typical TCP socket from smoltcp
    let mut tx_storage = [0; 4096];
    let mut rx_storage = [0; 4096];
    let tx_buffer = net::socket::TcpSocketBuffer::new(&mut tx_storage[..]);
    let rx_buffer = net::socket::TcpSocketBuffer::new(&mut rx_storage[..]);
    let mut tcp_socket = net::socket::TcpSocket::new(rx_buffer, tx_buffer);

    // TLS socket constructor
    let tls_socket = TlsSocket::new(
        tcp_socket,
        &mut rng,   // Assume rng is from a struct that implements TlsRng
        None
    );

Socket Storage & Access

Similar to smoltcp, a TlsSocketSet is needed to hold the sockets. Use TlsSocketHandle to gain access to a TLS Socket indirectly.

    // Prepare a socket set for TLS sockets
    let mut tls_socket_entries: [_; 1] = Default::default();
    let mut tls_socket_set = SaiTLS::set::TlsSocketSet::new(
        &mut tls_socket_entries[..]
    );
    // Use TLS socket set & handle to access TLS socket
    let tls_handle = tls_socket_set.add(tls_socket);
    {
        let mut tls_socket = tls_socket_set.get(tls_handle);
        /* Socket manipulations */
    }

Polling

The poll(..) function substitutes the EthernetInterface.poll(..) function in smoltcp.

    SaiTLS::poll(
        Some(&mut smoltcp_sockets),     // Optional socket set from smoltcp
        &mut tls_socket_set,
        &mut ethernet_interface,
        smoltcp::time::Instant::from_millis(time)
    );

Sockets in either smoltcp_sockets or tls_socket_set will be updated.

Authentication on SaiTLS side

If necessary, SaiTLS will send X.509 certificate to the remote side, designated in the constructor. This requires X.509 certificate and its associated private key to be loaded onto the socket. A X.509 self-signed certificate and the signature private key can be generated by OpenSSL using the following command:

openssl req -x509 -newkey rsa:1024 -sha256 -nodes -keyout key.der -out cert.der -days 30 -outform DER

This will generate a self-signed certificate in DER format as cert.der, signed by an RSA key with 1024 bits as key.der. The certificate supplied to SaiTLS must be in DER format.

The include_bytes!() Rust macro can load the DER certificate as a binary slice. Private/Secrete key needs to be instantiated by the corresponding RustCrypto/Dalek library. The following is an example of loading cert.der and key.der into the constructor of TLSSocket.

// This line includes the certificate in DER format
const CERT: &'static [u8] = include_bytes!( `<path-to-certificate>` );
// These lines contains components of the RSA private key
const PRIVATE_KEY_MOD: &'static [u8] = ... ;
const PUBLIC_KEY_EXP: &'static [u8] = &[ 0x01, 0x00, 0x01 ];
const PRIVATE_KEY_EXP: &'static [u8] = ... ;
const CLIENT_PRIME_1: &'static [u8] = ... ;
const CLIENT_PRIME_2: &'static [u8] = ... ;

The exact values of these constants can be found by printing out the content using OpenSSL, such as the following command.

openssl rsa -in key.der -text

The private key can then be assembled as such:

let mut prime_vec = alloc::vec::Vec::new();
prime_vec.push(rsa::BigUint::from_bytes_be(&CLIENT_PRIME_1));
prime_vec.push(rsa::BigUint::from_bytes_be(&CLIENT_PRIME_2));

let rsa_key = rsa::RSAPrivateKey::from_components(
    rsa::BigUint::from_bytes_be(CLIENT_PRIVATE_KEY_MOD),
    rsa::BigUint::from_bytes_be(CLIENT_PUBLIC_KEY_EXP),
    rsa::BigUint::from_bytes_be(CLIENT_PRIVATE_KEY_EXP),
    prime_vec
);

Finally, the private key and the certificate are loaded onto the socket.

let mut tls_socket = TlsSocket::new(
    tcp_socket,
    &mut rng_struct,
    Some((cert_private_key, alloc::vec![CLIENT_CERT]))
);

Loading more than 1 certificate (i.e. a certificate chain) is experimental. Use at your own risk!

Note that the remote side may not necessarily accept the self-signed certificate. It is entirely up to the remote side to accept or reject your provided certificate. Designation of trusted files might be helpful (e.g. the --trustfile option in Ncat).

Authentication of certificate

TLS server can madate the connecting client to authenticate themselves. This is achieved by receiving a self-signed ASN.1 certificate. Relevant flow:

  • Signature extraction: get_cert_public_key() in certificate.rs.
    • The algorithm identifier field of the ASN.1 DER certificate is matched with OID of the supported algorithms to determine the type of signature algorithm.
    • Parse the certificate to extract the public key. Notabilly the parse_asn1_der_rsa_public_key() method was invoked to parse the components of a RSA public key.
  • Validation of certificate: validate_signature_with_trusted() in certificate.rs.
    • Again, the Algorithm ID is matched to determine the signature algorithm.
    • (RSA) Pass the to-be-signed certificate into a hash function (i.e. digest). The process is performed indirectly for ED25519 and ECDSA-P256.
    • (RSAPSS) Generate a determined value (FakeRandom) for PaddingScheme in the rsa crate. PaddingScheme does not use random during the verification of signature. It only use the digest.
    • Verify signature against to-be-signed certificate using the determined signature algorithm with the found certificate public key, make sure that it matches the provided signature from the certificate.

The following shows the list of certificate verification methods from external libraries:

Algorithms Methods Library (module)
RSA RSAPublicKey::verify() rsa
ECDSA-P256 VerifyingKey::verify() p256::ecdsa::verify
ED25519 PublicKey::verify_strict() ed25519_dalek

Authentication of client

When using TLS as the client side, SaiTLS compares the acceptable signature algorithms from the server, and the algorithm of the private key. If there are no conflict in signature algorithm usage, SaiTLS will send the certificate to the server on request. However, if the server requests a certificate while the supplied certificate is not eligible to be sent, SaiTLS will instead send an empty certificate to the server, without a follow up CertificateVerify. In both cases, it is still completely up to the server to accept your connection.

Authentication option of TLS server

When used as a server, TLS socket can be configured to request client authentication. The socket will expect a self-signed ASN.1 DER certificate to be received. To request client authentication:

tls_socket.listen(
    true,           // Enable Authentication
    ..              // port number
).unwrap();

Feature nal_tcp_stack

Implements TcpStack in embedded-nal (v0.1.0) for TlsSocket. This disguises TlsSocket as just another TCP socket, potentially useful for implementating application layer protocols (e.g. MQTT in minimq).

Usage:

let tls_socket_set = SaiTLS::set::TlsSocketSet::new( .. );
let tls_stack = NetworkStack::new(tls_socket_set);