SSL (Secure Socket Layer) is the technology behind the https
urls you can see when browsing secure sites (which is nearly all of them). It's a protocol that allows server and browser (or more generally, the "agent" or just the "client") to establish a "handshake" prior to start exchanging request and responses. Its two main purposes are to enable the client to reliably identify the server and allow both to communicate via an encrypted channel.
During this handshake, the server will present the client with its "certificate", which is a X501-formatted data structure. The way the client will verify that the server is who it claims to be is by checking that this certificate is signed by some organisation it trusts, a list of which it must have available on its side and also checking that the certificate is issued to the same domain we are accessing.
Additionally, the server can ask the client to provide him with a "client certificate" for identification purposes. The server can then use this information to grant or block the client from visiting certain areas of the site or executing certain functions.
We will see below how to
- activate SSL in our Apache Web Server,
- create a server certificate for our site,
- instruct the server to ask the client to provide a certificate,
- create and install a client certificate in our browser
Prerequisites
1) Verify Linux's builtin Apache Web Server is installed and running:
$ sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service;
enabled; vendor preset: enabled)
Active: active (running) since Sun 2025-09-21 12:43:12 CEST;
2) Although not mandatory, I find convenient to map the ip address of the Linux box containing our Apache server to a DNS name of our choosing, for example I will use:
test-980.com
In Windows, you can do this mapping by editing the file
C:\Windows\System32\drivers\etc\hosts
and adding your <Apache server ip> + blank-space + <domain name>:
# Added by me
172.31.36.52 test-980.com
Now when I call that domain from my Windows host, I will get the home page of my Apache Web Server:
#Before changing C:\Windows\System32\drivers\etc\hosts file:
PS> curl.exe -v http://test-980.com/
* Could not resolve host: test-980.com
* shutting down connection #0
curl: (6) Could not resolve host: test-980.com
#After changing C:\Windows\System32\drivers\etc\hosts file:
PS> curl.exe -v http://test-980.com/
* Host test-980.com:80 was resolved.
* IPv6: (none)
* IPv4: 172.31.36.52
* Trying 172.31.36.52:80...
* Connected to test-980.com (172.31.36.52) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: test-980.com
> User-Agent: curl/8.14.1
> Accept: */*
>
< HTTP/1.1 200 OK
... full content of the home page follows here ...
3) Create a virtual host for my test-980.com
site, so we don't mess around with default virtual host in apache:
$ sudo mkdir -p /var/sites/test-980.com/conf
$ sudo mkdir -p /var/sites/test-980.com/www/html
$ sudo vi /var/sites/test-980.com/www/html/index.html
#add the content shown in the cat command below
$ cat /var/sites/test-980.com/www/html/index.html
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>test-980.com: It works</title>
</head>
<body>
Welcome to test-980.com
</body>
</html>
$ sudo cp /etc/apache2/sites-available/000-default.conf \
/var/sites/test-980.com/conf/localhost.conf
$ sudo vi /var/sites/test-980.com/conf/localhost.conf
#add the content shown in the cat command below
$ cat /var/sites/test-980.com/conf/localhost.conf
<VirtualHost *:80>
ServerName test-980.com
ServerAdmin webmaster@test-980.com
DocumentRoot /var/sites/test-980.com/www/html
<Directory /var/sites/test-980.com/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
#Make our site available
$ sudo ln -s /var/sites/test-980.com/conf/localhost.conf \
/etc/apache2/sites-available/local-test-980.com.conf
#Maker our site enabled:
$ sudo a2ensite local-test-980.com
Enabling site local-test-980.com.
To activate the new configuration, you need to run:
systemctl reload apache2
$ sudo systemctl reload apache2
Now, curl
will return our new page:
PS> curl.exe http://test-980.com/
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>test-980.com: It works</title>
</head>
<body>
Welcome to test-980.com
</body>
</html>
However, we cannnot access our site via https
for the moment:
PS> curl.exe https://test-980.com/
curl: (35) schannel: next InitializeSecurityContext failed:
SEC_E_INVALID_TOKEN (0x80090308) -
El token proporcionado a la función no es válido
We'll see next how to secure our site via SSL.
Enable SSL on Apache
Run
$ sudo a2ensite default-ssl.conf
Enabling site default-ssl.
To activate the new configuration, you need to run:
systemctl reload apache2
$ sudo systemctl reload apache2
If we look at the content of /etc/apache2/sites-enabled/default-ssl.conf
, we will see that we have done the following:
- listen on port 443:
<VirtualHost _default_:443>
- serve the pages under
/var/www/html
:
DocumentRoot /var/www/html
- use SSL protocol on this virtual host:
SSLEngine on
- send the client the following server certificate:
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
So, if we now test https
, we will get a warning that the certificate is not trusted:
PS> curl.exe https://test-980.com/
curl: (60) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) -
La cadena de certificación fue emitida por una entidad en la que no se confía.
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.
However, we can force curl
to trust the certificate via the -k
option:
PS> curl.exe -k https://test-980.com/
... full content of page /var/www/html/index.html will follow ...
But we don't want the default page in /var/www/html/index.html
to be served, but the page in our test-980.com
site, so we have to create a virtual host for our site that speaks https
.
We do it as follows:
- copy default ssl conf:
$ sudo cp /etc/apache2/sites-available/default-ssl.conf \
/var/sites/test-980.com/conf/localhost-ssl.conf
- adapt it to our site by adding our domain name in
ServerName
and our html folder inDocumentRoot
:
$ cat /var/sites/test-980.com/conf/localhost-ssl.conf
...
ServerName test-980.com
...
DocumentRoot /var/sites/test-980.com/www/html
We set our site available, enable it and reload Apache:
$ sudo ln -s /var/sites/test-980.com/conf/localhost-ssl.conf \
/etc/apache2/sites-available/local-test-980.com-ssl.conf
$ sudo a2ensite local-test-980.com-ssl.conf
$ sudo systemctl reload apache2
If we try again we will get:
PS> curl.exe -k https://test-980.com/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.52 (Ubuntu) Server at test-980.com Port 443</address>
</body></html>
That's because we have to grant access to our home folder in our new ssl conf, in the same way we did in /var/sites/test-980.com/conf/localhost.conf
.
So, we have to modify localhost-ssl.conf
to add the following Directory
element:
$ more /var/sites/test-980.com/conf/localhost-ssl.conf
...
DocumentRoot /var/sites/test-980.com/www/html
<Directory /var/sites/test-980.com/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
...
and reload:
$ sudo systemctl reload apache2
Now, we can get our home page via https
:
PS> curl.exe -k https://test-980.com/
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>test-980.com: It works</title>
</head>
<body>
Welcome to test-980.com
</body>
</html>
Remember we are able to retrieve the page only because we have instructed curl
to ignore (via option k
) the fact that the certificate presented (or more precisely, the signer of the certificate) is not trusted:
PS> curl.exe -k https://test-980.com/
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>test-980.com: It works</title>
</head>
<body>
Welcome to test-980.com
</body>
</html>
#Removing the -k
PS> curl.exe https://test-980.com/
curl: (60) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - La cadena de certificación fue emitida por una entidad en la que no se confía.
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.
We can tell curl
to trust the certificate with the option cacert
:
PS> curl.exe https://test-980.com --cacert ^
\\wsl.localhost\Ubuntu-22.04\etc\ssl\certs\ssl-cert-snakeoil.pem
curl: (60) schannel: CertGetNameString() failed to match connection hostname (test-980.com) against server certificate names
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.
We're not there yet. The reason is that the certificate is issued for a domaine named "DESKTOP-IUMEIDO", which is my computer's name:
$ openssl x509 -in /etc/ssl/certs/ssl-cert-snakeoil.pem -noout -text|\
grep "Subject:"
Subject: CN = DESKTOP-IUMEID0.
, whereas our request is sent to a different domain (test-980.com
). The client is being presented a certificate signed by someone he now trusts, but that is issued to a domain different that the one we are trying to connect to. This is deemed dangerous, hence the message and the refusal to connect.
In next section, we'll see how to create a server certificate for our domain test-980
Create SSL server certificate
First, let's generate a key of length 2048:
$ openssl genrsa -out test-980.com.key 2048
Now we need to create a certificate sign request
or csr
. We will be asked to provide information about the owner of the certificate such as it's company name, it's country, etc... Only Common Name
is mandatory:
$ openssl req -new \
-key test-980.com.key \
-out test-980.com.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:test-980.com
Email Address []:.
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:.
An optional company name []:.
Finally, we sign the request with the key generated above. This will generate our certificate and place it in the file specified in out
:
$ openssl x509 -req -days 365 \
-in test-980.com.csr \
-signkey test-980.com.key \
-out test-980.com.crt
Certificate request self-signature ok
subject=CN = test-980.com
If we list our current folder, we will see the 3 files that we have just generated:
$ ls test-980*
test-980.com.crt test-980.com.csr test-980.com.key
Our certificate is the crt
file.
We will now copy the crt
and the key
files inside our test-980.com
's site configuration folder and reference them in our ssl conf file:
$ sudo cp test-980.com.crt /var/sites/test-980.com/conf
$ sudo cp test-980.com.key /var/sites/test-980.com/conf
#Modify localhost-ssl.conf to point to our new files:
$ cat /var/sites/test-980.com/conf/localhost-ssl.conf|grep SSLCertificate
SSLCertificateFile /var/sites/test-980.com/conf/test-980.com.crt
SSLCertificateKeyFile /var/sites/test-980.com/conf/test-980.com.key
#restart:
$ sudo systemctl stop apache2.service
$ sudo systemctl start apache2.service
Let's now copy the server certificate in our windows home folder (notice I've been using all this time my curl
tool in Windows) for the sake of legibility:
PS> cp \\wsl.localhost\Ubuntu-22.04\var\sites\test-980.com\conf\test-980.com.crt .
If we try again, it should work:
PS> curl.exe https://test-980.com --cacert test-980.com.crt
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>test-980.com: It works</title>
</head>
<body>
Welcome to test-980.com
</body>
</html>
Create SSL client certificate
We can instruct our server to require a certificate to the client by editing localhost-ssl.conf
as follows:
- uncomment the following line:
SSLVerifyClient require
- indicate where our signer certificate is with:
SSLCACertificateFile /var/sites/test-980.com/conf/test-980.com.crt
After restarting the server, we can't access our page anymore:
PS> curl.exe https://test-980.com --cacert test-980.com.crt
curl: (56) schannel: failed to read data from server: SEC_E_ILLEGAL_MESSAGE (0x80090326) - This error usually occurs when a fatal SSL/TLS alert is received (e.g. handshake failed). More detail may be available in the Windows System event log.
The message is quite cryptic but it is due to our curl
client not providing a certificate.
Let's then create a client certificate for James Bond
and sign it with our test-980.com
server certificate:
#Step 1: generate key
$ openssl genpkey -algorithm RSA -out james.bond.key
#Step 2: generate request
$ openssl req -new -key james.bond.key -out james.bond.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:.
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:BOND.James
Email Address []:.
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:.
An optional company name []:.
#Step 3: generate the certificate and sign it with our test-980.com server
#certificate (notice that all files in the command are assumed to be in
#current folder):
$ openssl x509 -req -in james.bond.csr -CA test-980.com.crt \
-CAkey test-980.com.key \
-CAcreateserial \
-out james.bond.crt \
-days 365 \
-sha256
Certificate request self-signature ok
subject=CN = BOND.James
#Step 4: optional
#If we want to import it in our browser or test it via curl in Windows,
#we might need to convert it to .pfx format:
$ openssl pkcs12 -export -out james.bond.pfx \
-inkey james.bond.key \
-in james.bond.crt
Enter Export Password: mypwd
Verifying - Enter Export Password: mypwd
Now let's copy the .pfx
in our Windows machine and try again by providing our client certificate and the password wiht the --cert
option:
PS> curl.exe https://test-980.com/ ^
--cacert ./test-980.com.crt ^
--cert james.bond.pfx:mypwd
curl: (35) schannel: next InitializeSecurityContext failed: SEC_E_INTERNAL_ERROR (0x80090304) - No es posible ponerse en contacto con la autoridad de seguridad local
It's not working but it should :( . You might want to check this article for the reasons behind and try your luck, but I rather have my balls torched than messing around with my Windows registry. I will use my Chrome browser instead.
To import the client certificate in Chrome, go to chrome://certificate-manager/clientcerts
and from there I'm sure you'll manage.
Now, when accessing our site via https, we will be asked to select a client certificate:
If you select it and click OK, you'll be granted access to the site:
Top comments (0)