Ed25519 and rustls (TLS client/server)

Hello.
How to use Ed25519 keys with tokio rustls?
I would like to use that examples but with Ed25519 keys.
I need help with writing examples (client and server with Ed25519).

Server:

use rustls_pemfile::{certs, pkcs8_private_keys};
use std::fs::File;
use std::io::{self, BufReader};
use std::path::{Path};
use std::sync::Arc;
use tokio::io::{copy, sink, split, AsyncWriteExt};
use tokio::net::TcpListener;
use tokio_rustls::rustls::{self, Certificate, PrivateKey};
use tokio_rustls::TlsAcceptor;

fn load_certs(path: &Path) -> io::Result<Vec<Certificate>> {
    certs(&mut BufReader::new(File::open(path)?))
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
        .map(|mut certs| certs.drain(..).map(Certificate).collect())
}

fn load_keys(path: &Path) -> io::Result<Vec<PrivateKey>> {
    pkcs8_private_keys(&mut BufReader::new(File::open(path)?))
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))
        .map(|mut keys| keys.drain(..).map(PrivateKey).collect())
}

#[tokio::main]
async fn main() -> io::Result<()> {

    // Generate cert from private_key:
    //
    // openssl req -nodes \
    // -x509 \
    // -key  Ed25519_private_key.pem \
    // -out eddsa-ca.cert \
    // -sha256 \
    // -batch \
    // -days 3650 \
    // -subj "/CN=ponytown EdDSA CA"

    let private_key_path = Path::new("Ed25519_private_key.pem");
    let cert_path = Path::new("eddsa-ca.cert");
    
    let certs = load_certs(&cert_path)?;
    let keys = load_keys(&private_key_path)?;

    println!("{:?}", certs);
    println!("{:?}", keys);

    let flag_echo = true;

    let private_key = keys.get(0).unwrap().clone();

    let config = rustls::ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(certs, private_key)
        .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;

    let acceptor = TlsAcceptor::from(Arc::new(config));

    let listener = TcpListener::bind("127.0.0.1:8888").await?;

    loop {
        let (stream, peer_addr) = listener.accept().await?;
        let acceptor = acceptor.clone();

        let fut = async move {
            let mut stream = acceptor.accept(stream).await?;

            if flag_echo {
                let (mut reader, mut writer) = split(stream);
                let n = copy(&mut reader, &mut writer).await?;
                writer.flush().await?;
                println!("Echo: {} - {}", peer_addr, n);
            } 
            else 
            {
                let mut output = sink();
                stream
                    .write_all(
                        &b"HTTP/1.0 200 ok\r\n\
                    Connection: close\r\n\
                    Content-length: 12\r\n\
                    \r\n\
                    Hello world!"[..],
                    )
                    .await?;
                stream.shutdown().await?;
                copy(&mut stream, &mut output).await?;
                println!("Hello: {}", peer_addr);
            }

            Ok(()) as io::Result<()>
        };

        tokio::spawn(async move {
            if let Err(err) = fut.await {
                eprintln!("{:?}", err);
            }
        });
    }
}

Client:

use std::fs::File;
use std::io;
use std::io::BufReader;
use std::sync::Arc;
use tokio::io::{copy, split, stdin as tokio_stdin, stdout as tokio_stdout, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio_rustls::{TlsConnector};
use rustls_pemfile::{certs};
use tokio_rustls::rustls::{Certificate};
use std::path::{Path};
use tokio_rustls::rustls::{ClientConfig, ServerName, RootCertStore};
use std::convert::TryFrom;

fn load_certs(path: &Path) -> io::Result<Vec<Certificate>> {
    certs(&mut BufReader::new(File::open(path)?))
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
        .map(|mut certs| certs.drain(..).map(Certificate).collect())
}

#[tokio::main]
async fn main() -> io::Result<()> {

    let host = "127.0.0.1";
    let port = "8888";
    let addr = format!("{}:{}", host, port);

    let cert_path = Path::new("eddsa-ca.cert");
    let certs = load_certs(&cert_path)?;
    println!("{:?}", certs);

    let cert = certs.get(0).unwrap().clone();

    let mut root_store = RootCertStore::empty();
    root_store.add(&cert).unwrap();

    let config = ClientConfig::builder()
        .with_safe_default_cipher_suites()
        .with_safe_default_kx_groups()
        .with_safe_default_protocol_versions()
        .unwrap()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let connector = TlsConnector::from(Arc::new(config));

    let stream = TcpStream::connect(&addr).await?;

    let (mut stdin, mut stdout) = (tokio_stdin(), tokio_stdout());

    let domain = ServerName::try_from(host)
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?;

    let content = "HELLO WORLD";
    let mut stream = connector.connect(domain, stream).await?;
    stream.write_all(content.as_bytes()).await?;

    let (mut reader, mut writer) = split(stream);

    tokio::select! {
        ret = copy(&mut reader, &mut stdout) => {
            ret?;
        },
        ret = copy(&mut stdin, &mut writer) => {
            ret?;
            writer.shutdown().await?
        }
    }

    Ok(())
}

Server output:

[Certificate(b"0\x82\x01L0\x81\xff\xa0\x03\x02\x01\x02\x02\x14\x17\xbc\xb7\xf2\x9b\xeb\x9a jI\x88O\xd20q\xe2{\xc0\xff\xa10\x05\x06\x03+ep0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA0\x1e\x17\r220820130336Z\x17\r320817130336Z0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA0*0\x05\x06\x03+ep\x03!\0s\x7fSA_\xe4`\xd2\xda\xda\xb9ghV>\xfba\xb6!E\x93b\x96\xe0\xfeMc\xf57\x82\x82W\xa3S0Q0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xf5\x14\xfe\x05\xc2\xe1_\xff\xe8\xacFx\x1f\xf8\x97Y,\xbb\xc5\xcf0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xf5\x14\xfe\x05\xc2\xe1_\xff\xe8\xacFx\x1f\xf8\x97Y,\xbb\xc5\xcf0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\x01\xff0\x05\x06\x03+ep\x03A\0\xb7\x0e\xac\xbbE\x8c\x04\xbd\xbe\x8e\x02\xb3B7\xf8\xfb8\xef\xfe8\xf19\x9a 2g\xe9$\xe2\xe9s&\xd5\xbc\x8b\x9b.\xdf\xbcW4)\x1e\xa9\xd0K\xb4\x82\xa8\x1d\x01\x1f44!\xdb\xa0!61\xa1\x01y\x02")]
[PrivateKey([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 45, 107, 131, 28, 150, 11, 0, 12, 157, 125, 138, 145, 239, 74, 76, 190, 103, 162, 38, 81, 180, 157, 179, 29, 83, 83, 254, 194, 36, 252, 180, 7])]
Custom { kind: InvalidData, error: AlertReceived(BadCertificate) }

Client output:

[Certificate(b"0\x82\x01L0\x81\xff\xa0\x03\x02\x01\x02\x02\x14\x17\xbc\xb7\xf2\x9b\xeb\x9a jI\x88O\xd20q\xe2{\xc0\xff\xa10\x05\x06\x03+ep0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA0\x1e\x17\r220820130336Z\x17\r320817130336Z0\x1c1\x1a0\x18\x06\x03U\x04\x03\x0c\x11ponytown EdDSA CA0*0\x05\x06\x03+ep\x03!\0s\x7fSA_\xe4`\xd2\xda\xda\xb9ghV>\xfba\xb6!E\x93b\x96\xe0\xfeMc\xf57\x82\x82W\xa3S0Q0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xf5\x14\xfe\x05\xc2\xe1_\xff\xe8\xacFx\x1f\xf8\x97Y,\xbb\xc5\xcf0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xf5\x14\xfe\x05\xc2\xe1_\xff\xe8\xacFx\x1f\xf8\x97Y,\xbb\xc5\xcf0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\x01\xff0\x05\x06\x03+ep\x03A\0\xb7\x0e\xac\xbbE\x8c\x04\xbd\xbe\x8e\x02\xb3B7\xf8\xfb8\xef\xfe8\xf19\x9a 2g\xe9$\xe2\xe9s&\xd5\xbc\x8b\x9b.\xdf\xbcW4)\x1e\xa9\xd0K\xb4\x82\xa8\x1d\x01\x1f44!\xdb\xa0!61\xa1\x01y\x02")]
Error: Custom { kind: InvalidData, error: UnsupportedNameType }

Ed25519 Keys (generated by jwt_simple)
Ed25519_pair_key.pem:

-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIC1rgxyWCwAMnX2Kke9KTL5noiZRtJ2zHVNT/sIk/LQH
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAc39TQV/kYNLa2rlnaFY++2G2IUWTYpbg/k1j9TeCglc=
-----END PUBLIC KEY-----

Ed25519_public_key.pem:

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAc39TQV/kYNLa2rlnaFY++2G2IUWTYpbg/k1j9TeCglc=
-----END PUBLIC KEY-----

eddsa-ca.cert:
Generated by: openssl req -nodes -x509 -key Ed25519_private_key.pem -out eddsa-ca.cert -sha256 -batch -days 3650 -subj "/CN=ponytown EdDSA CA"

-----BEGIN CERTIFICATE-----
MIIBTDCB/6ADAgECAhQXvLfym+uaIGpJiE/SMHHie8D/oTAFBgMrZXAwHDEaMBgG
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMjIwODIwMTMwMzM2WhcNMzIwODE3
MTMwMzM2WjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
AHN/U0Ff5GDS2tq5Z2hWPvthtiFFk2KW4P5NY/U3goJXo1MwUTAdBgNVHQ4EFgQU
9RT+BcLhX//orEZ4H/iXWSy7xc8wHwYDVR0jBBgwFoAU9RT+BcLhX//orEZ4H/iX
WSy7xc8wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQC3Dqy7RYwEvb6OArNCN/j7
OO/+OPE5miAyZ+kk4ulzJtW8i5su37xXNCkeqdBLtIKoHQEfNDQh26AhNjGhAXkC
-----END CERTIFICATE-----

What is the question?

How to fix it?

This is entirely unrelated to Ed25519 usage. This error indicates that the server name is an ip address rather than a domain name. This is not yet supported by rustls. See Can't connect to a server presenting a certificate with an IP address in its common name · Issue #184 · rustls/rustls · GitHub Also the certificate has a CN of ponytown, so you need to pass ponytown as server name. Of course before you actually deploy it, you will need to give it the actual domain name as server name.

2 Likes

Thanks for your reply. I haven't a domain name/DNS. I want to use only the IP address.
Can I do TLS client/server communication without cert (only pub/priv keys)?

No, the certificate itself is sent to the client when establishing the connection, so there needs to be certificate. You can fill in a dummy domain name though and then set this dummy domain name when passing the server name to rustls on the client side.

I found this.