DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Strengthening Cybersecurity: The Role of Digital Certificates and PKI in Authentication
  • Power BI Embedded Analytics — Part 2: Power BI Embedded Overview
  • Simplify Authorization in Ruby on Rails With the Power of Pundit Gem
  • Segmentation Violation and How Rust Helps Overcome It

Trending

  • Issue and Present Verifiable Credentials With Spring Boot and Android
  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  • Java Virtual Threads and Scaling
  • Unlocking AI Coding Assistants Part 2: Generating Code
  1. DZone
  2. Coding
  3. Languages
  4. Using TLS With Rust: Authentication

Using TLS With Rust: Authentication

Learn how to implement the authentication portion of your network protocol in Rust.

By 
Oren Eini user avatar
Oren Eini
·
Feb. 12, 19 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
7.9K Views

Join the DZone community and get the full member experience.

Join For Free

After running into a few hurdles, I managed to get rust openssl bindings to work, which means that this is now the time to actually wire things properly in my network protocol. Let’s see how that works, shall we?

First, we have the OpenSSL setup:

struct Server {
    tls_config: Arc<openssl::ssl::SslAcceptor>,
    tcp_listener: TcpListener,
    allowed_certs_thumbprints: HashSet<String>
}

impl Server {

    fn new(cert_path: &str, key_path: &str, listen_uri: &str, allowed_certs_thumbprints: &[&str]) -> Result<Server, ConnectionError> {

        let mut allowed = HashSet::new();

        for thumbprint in allowed_certs_thumbprints {
            allowed.insert(thumbprint.to_lowercase());
        }

        let mut sslb = openssl::ssl::SslAcceptor::mozilla_modern(openssl::ssl::SslMethod::tls())?;
        sslb.set_private_key_file(key_path, openssl::ssl::SslFiletype::PEM)?;
        sslb.set_certificate_chain_file(cert_path)?;
        sslb.check_private_key()?;
        // accept all certificates, we'll do our own validation on them
        sslb.set_verify_callback(openssl::ssl::SslVerifyMode::PEER, |_, _| true);

        let listener = TcpListener::bind(listen_uri)?;

        Ok(Server { tls_config: Arc::new(sslb.build()), tcp_listener: listener, allowed_certs_thumbprints: allowed })
    }

}


As you can see, this is pretty easy and there isn’t really anything there that is of actual interest. It does feel a whole lot easier than dealing with OpenSSL directly in C, though.

That said, when I started actually dealing with the client certificate, things got a lot more complicated. The first thing that I wanted to do is to do my authentication, which is defined as:

  • Client present a client certificate (can be any client certificate).
  • If a client doesn’t give a certificate, we accept the connection, send a message (using the encrypted tunnel), and abort.
  • If the client provide an certificate, it must be one that was previously registered in the server. That is what allowed_certs_thumbprints is for. If it isn’t, we accept the connection, write an error, and abort.
  • If the client certificate has expired or is not yet valid, accept, write error, and abort.

You get the gist. Here is what I had to do to implement the first part:

fn authenticate_certificate(stream: &mut openssl::ssl::SslStream<TcpStream>, server: &Server) -> Result<bool, ConnectionError> {
        fn get_friendly_name(peer: &openssl::x509::X509) -> String {
            peer.subject_name() // can't figure out how to get the real friendly name
                .entries()
                .last()
                .map( |it| it.data()
                        .as_utf8()
                        .and_then(|s| Ok(s.to_string()))
                        .unwrap_or("".to_string()) 
                )
                .unwrap_or("<Unknown>".to_string()) 
        }

        match stream.ssl().peer_certificate() {
             None => {
                stream.write(b"ERR No certificate was provided\r\n")?;
                return Ok(false);
            }
            Some(peer) => {
                let thumbprint = hex::encode(peer.digest(openssl::hash::MessageDigest::sha1())?);
                if server.allowed_certs_thumbprints.contains(&thumbprint) == false {
                    let msg = format!("ERR certificate ({}) thumbprint '{}' is unknown\r\n",
                        get_friendly_name(&peer),
                        thumbprint);
                    stream.write(msg.as_bytes())?;
                    return Ok(false);
                }
            }
        };
        return Ok(true);
}

fn handle_connection(socket: TcpStream, server: &Server) -> Result<(), ConnectionError> {

    let acceptor = server.tls_config.clone();
    let mut stream = acceptor.accept(socket)?;

    if authenticate_certificate(&mut stream, server)? == false{
        return Ok(());// error already sent to client
    }

    stream.write(b"OK\r\n")?;

    // connection established, do the rest from here
}


Most of the code, actually, is about generating proper and clear error messages more than anything else. I’m not sure how to get the friendly name from the certificate, but this seems to be a good enough stand-in for now.

We validate that we have a certificate or send an error back. We validate that the certificate presented is known to us, or we send an error back.

The next part I wanted to implement was… really far too hard than it should be. I just wanted to verify that the certificate not before/not after dates are valid. And the problem is that the rust bindings for OpenSSL do not expose that information. Luckily, because it is using OpenSSL, I can just call to OpenSSL directly. That led me to some interesting search into how Rust calls out to C, how foreign types work, and a lot of “fun” like that. Given that I’m doing this to learn, I suppose that this is a good thing, though.

Here is what I ended up with (take a deep breath):

fn authenticate_certificate(stream: &mut openssl::ssl::SslStream<TcpStream>, server: &Server) -> Result<bool, ConnectionError> {
        fn get_friendly_name(peer: &openssl::x509::X509) -> String {
            // like before
        }

        extern "C" {
            fn ASN1_TIME_diff(
                    pday: *mut std::os::raw::c_int, 
                    psec: *mut std::os::raw::c_int, 
                    from: *const openssl_sys::ASN1_TIME, 
                    to: *const openssl_sys::ASN1_TIME) -> std::os::raw::c_int;
        }

        fn is_before(x: &openssl::asn1::Asn1TimeRef, y: &openssl::asn1::Asn1TimeRef) -> Result<bool, ConnectionError> {
            unsafe {
                let mut day : std::os::raw::c_int = 0;
                let mut sec : std::os::raw::c_int = 0;
                match ASN1_TIME_diff(&mut day, &mut sec,  x.as_ptr(), y.as_ptr() ) {
                    0 => Err(ConnectionError::InvalidTimeFormat),
                    _ => Ok(day > 0 || sec > 0)
                }
            }
        }

        fn is_valid_time(peer: &openssl::x509::X509) -> Result<(), ConnectionError> {
            let now = openssl::asn1::Asn1Time::days_from_now(0)?;

            if is_before(&now, peer.not_before())? {
                return Err(ConnectionError::ClientCertNotYetValid { date: peer.not_before().to_string() });
            }
            if is_before(peer.not_after(), &now)? {
                return Err(ConnectionError::ClientCertExpired { date: peer.not_after().to_string() } );   
            }

            Ok(())
        }

        match stream.ssl().peer_certificate() {
             None => {
                stream.write(b"ERR No certificate was provided\r\n")?;
                return Ok(false);
            }
            Some(peer) => {
                let thumbprint = hex::encode(peer.digest(openssl::hash::MessageDigest::sha1())?);
                if server.allowed_certs_thumbprints.contains(&thumbprint) == false {
                    let msg = format!("ERR certificate ({}) thumbprint '{}' is unknown\r\n",
                        get_friendly_name(&peer),
                        thumbprint);
                    stream.write(msg.as_bytes())?;
                    return Ok(false);
                }

                if let Err(e) =  is_valid_time(&peer) {
                    let msg = format!("ERR certificate ({}) thumbprint '{}' cannot be used: {}\r\n",
                        get_friendly_name(&peer),
                        thumbprint,
                        e);
                    stream.write(msg.as_bytes())?;
                    return Ok(false);
                }
            }
        };
        return Ok(true);
}


Notice that I’m doing all of this (defining external function, defining helper functions) inside the authenticate_certificate function. Coming up with that was harder than expected, but I really liked the fact that it was possible and that I can just shove this into a corner of my code and not have to make a Big Thing out of it.

And with that, I the authentication portion of my network protocol in Rust done.

The next stage is going to be implementing a server that can handle more than a single connection at a time

TLS Rust (programming language) authentication

Published at DZone with permission of Oren Eini, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Strengthening Cybersecurity: The Role of Digital Certificates and PKI in Authentication
  • Power BI Embedded Analytics — Part 2: Power BI Embedded Overview
  • Simplify Authorization in Ruby on Rails With the Power of Pundit Gem
  • Segmentation Violation and How Rust Helps Overcome It

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!