SSL Certificates

Nisse

In the previous article, "C++ Sockets," I mentioned that thors-mongo had built-in support for SLL connections but glossed over the details. In this article, we will go over the additions needed. From the server side, you need an SSL certificate. But what is an SSL certificate, and how do you obtain one?

SSL Certificates

The SSL certificate has two main purposes.

  1. Securely encrypt all traffic between the client and server.
  2. Provides trust that you are talking to a server on the requested domain.

The Basics

SSL certificates are issued by trusted certificate authorities and contain information that validates the certificate cryptographically. When a browser connects to a website, the server returns the certificate, which includes crucial information such as the issuing company. Before establishing a secure connection, the browser validates the certificate against the issuing company’s SSL certificate to ensure it is not forged. If the issuing company is a subsidiary, it recursively looks up the parent company that issued the certificate and validates their certificates until it reaches a root certificate. This process creates a chain of trust leading to a known trusted root certificate.

All modern browsers know how to find and validate root certificates. They also have information about compromised certificate authorities whose certificates should no longer be trusted. Therefore, it is important to download only trusted browsers and keep them up to date.

Once a certificate has been validated, the browser knows it is communicating with a server for a specific domain (as the domain information is included in the certificate). Modern browsers indicate a secure connection, usually with a green padlock next to the URL. The browser can then use the public key in the certificate to establish a secure connection with the domain you are connecting to.

Where can you get a certificate

If you are a large company trusted by the Internet, you can create your own root certificate. While this is not difficult, it is of little value if no one trusts you. Therefore, the crucial step for a root certificate holder is becoming trusted by tools that validate certificates. Losing trust would mean having your root certificate removed from the list people use to validate certificates (i.e., the Chrome browser team would remove your certificate from the list of root certificates it trusts). This loss of trust would affect the root certificate authority and all downstream companies that hold certificates based on that root certificate; a browser would mark any sites using an untrusted root certificate as not being trusted.

Obtaining a certificate from a trusted root authority can be expensive and is typically only done by companies that issue certificates. However, you don't necessarily need a certificate from a root authority; you can get a certificate from a vendor that has a certificate issued by a root authority. Every trusted certificate authority has a process to validate that you own a domain, which allows them to create and deliver a signed certificate to you, but each authority's process is different. But for normal people (and small companies), you can get relatively cheap SSL certificates from most domain providers (like GoDaddy) that will create and manage your SSL Certificate for your domain.

Free Certificates

You can get Free SSL Certificates from a company called Let’s Encrypt.

Code

All the code for this article is in the directory V3 directory. It uses standard libraries and thors-mongo. If you have a Unix-like environment, this should be easy to build; if you use Windows, you may need to do some extra work. A “Makefile” is provided just as an example.

Build & Run

  > brew install thors-mongo              # A header-only version of thors-mongo can be alternatively installed.
  > git clone https://github.com/Loki-Astari/NisseBlogCode.git
  > cd NisseBlogCode/V3
  > make
  > ./NisseV3 8080 /Directory/You/Want/To/Server/On/Port/8080 /etc/letsencrypt/live/<MySite.com>/

How to Use ThorsSocket With an SSL Certificate

In ThorsSocket a normal socket is created with the following code:

    ThorsAnvil::ThorsSocket::Server   server(ServerInit{port});
    
    ThorsAnvil::ThorsSocket::Socket   socket = server.accept();          // A normal bi-direconal socket.

To create a secure connection, specify the location of the SSL certificate file on the host file system. If you use Let’s Encrypt, the default location for the SSL certificate is /etc/letsencrypt/live/<domainName>/fullchain.pem, and the private key is located at /etc/letsencrypt/live/<domainName>/privkey.pem. You can then create a secure SSL connection with:

    // The path where the certificates for “thorsanvil.dev” are stored.
    std::string   certPath = "/etc/letsencrypt/live/thorsanvil.dev”;
    
    // Create a certificate object that contains the SSL Certificate and private key.
    // Note: Some files require you to provide a password to access the certificate, please see the documentation
    // on how to add appropriate lambda’s to retrieve the password from secure storage (as they should not be in the code)
    ThorsAnvil::ThorsSocket::CertificateInfo     certificate{std::filesystem::canonical(std::filesystem::path(certPath) /= "fullchain.pem”,
                                                             std::filesystem::canonical(std::filesystem::path(certPath) /= "privkey.pem”
                                                            };
    ThorsAnvil::ThorsSocket::SSLctx              ctx{ThorsAnvil::ThorsSocket::SSLMethodType::Server, certificate};
    ThorsAnvil::ThorsSocket::Server              server(SServerInit{port, std::move(ctx)});
    
    ThorsAnvil::ThorsSocket::Socket   socket = server.accept();          // A secure bi-direconal SSL socket.

How to handle SSL or normal connections

The ThorsAnvil::ThorsSocket::Socket class can be constructed with ServerInit or SServerInit object. To simplify this, the constructor also takes a std::varient (ServerInit) that can contain either of these objects. This allows us to write a function like this to initialize a server connection to initialize either type.

namespace TASock = ThorsAnvil::ThorsSocket;

// Always need a port to listen on.
// If we have an SSL certificate then pass its location in `certPath` (which may be empty)
TASock::ServerInit getServerInit(int port, std::optional<std::filesystem::path> certPath)
{
    // If there is only a port.
    // i.e. The user did not provide a certificate path return a `ServerInfo` object.
    // This will create a normal listening socket.
    if (!certPath.has_value()) {
        return TASock::ServerInfo{port};
    }
    
    // If we have a certificate path.
    // Use this to create a certificate object.
    // This assumes the standard names for these files as provided by "Let's encrypt".
    TASock::CertificateInfo     certificate{std::filesystem::canonical(std::filesystem::path(*certPath) /= "fullchain.pem"),
                                            std::filesystem::canonical(std::filesystem::path(*certPath) /= "privkey.pem")
                                           };
    TASock::SSLctx              ctx{TASock::SSLMethodType::Server, certificate};
    
    // Now that we have created the appropriate SSL objects needed.
    // We return an SServierInfo object.
    // Please Note: This is a different type to the ServerInfo returned above (one less S in the name).
    return TASock::SServerInfo{port, std::move(ctx)};
    
    // We can return these two two different types because
    // ServerInit is actually a std::variant<ServerInfo, SServerInfo>
}

To use this we just need to adjust main() slightly.

The only change in Nisse’s API from V1 is that it allows the user to provide a certificate and key file.

```C++
int main(int argc, char* argv[])
{
    if (argc != 4 && argc != 3)
    {
        std::cerr << "Usage: NisseV3 <port> <documentPath> [<SSL Certificate Path>]" << "\n";
        return 1;
    }
    ...
        std::optional<std::filesystem::path>    certDir;
        if (argc == 4) {
            certDir = std::filesystem::canonical(argv[3]);
        }
    ...
        WebServer   server(getServerInit(port, certDir), contentDir);
    ...
}

What is the next step

We have a Web Server that can connect over SSL. But the server only handles request serially. So the next article looks at how to add some basic parallelism to support multiple simultaneous connections.

Related Posts

C++ Wrapper for Socket

The last two articles examined the "C Socket" interface that is provided by OS. In this article I wrap this functionality in a very simple C++ class to provide guaranteed closing and apply a consisten

Read More

Common Mistakes

### 1: using namespace Every new developer that comes to C++ always starts writing code like this: myfirstprog.cpp ```c #include <iostream> using namespace std; ``` It seems reasonable and every

Read More

Control Flow

So far we have demonstrated basic programs that just do a single task without making any decisions. Most (all but the most trivial) programming languages provide constructs for decision making (Condi

Read More