Installing Dual TLS Certificates and Running TLS
Steps Toward The Goal
On earlier pages I showed how to set up a
Google Compute Engine
virtual machine, run FreeBSD,
and set up a basic Apache/PHP web server.
Now let's set up HTTPS!
On this page I will:
1: Set up both RSA and ECC key pairs.
2: Obtain Let's Encrypt certificates for both.
3: Set up automatic renewal.
On the final page I will tune the Apache HTTP headers.
Why TLS?
TLS, the successor to SSL, authenticates a web server and provides confidentiality and integrity of the data.
Try Google Cloud Platform and receive $50In July 2018, Google's Chrome browser began marking HTTP pages as Not Secure. Google had warned the previous year that Chrome would eventually do this. Once Chrome does something, the other browsers quickly follow.
Google also announced back in 2014 that HTTPS already had a slight effect on a page's rank in search results, and the weight would probably increase. In other words, if you don't run HTTPS, you will be ranked lower in search results. Similarly, once Google's search algorithm starts doing something, the other search engines follow.
So, even if a web site is purely informational, you still want to set up TLS.
Public-Key Security
How RSA WorksAsymmetric cryptography, also called public-key cryptography, bases its security on a trapdoor function. That's a function that is easy to compute in one direction but enormously difficult to compute in the opposite direction. RSA, which was developed in the late 1970s, relies on the difficulty of factoring the product of two very large prime numbers.
It is easy to multiply integers, even ones with hundreds of digits, but it is impractically difficult to start with such a product and figure out which two large prime numbers went into it.
Peter Shor's initial publication of his algorithm, 1994 An update to Shor's original paper, 1995 Increasing the speed of Shor's algorithm, 1998Then people worried: what if someone develops a general-purpose quantum computer? Shor's Algorithm could quickly factor very large numbers if you have such a platform on which to run it.
Around this time, people were starting to use mobile devices for Internet access, but smart phones with fast multi-core CPUs had not yet appeared.
How ECC WorksSo, Elliptic Curve Cryptography or ECC suddenly became popular. Its trapdoor function is based on a discrete logarithm, entirely different from RSA's factoring. Analysis by NSA and NIST showed that ECC provides equal security with much smaller keys than RSA, requiring much less computation.
So, there were two advantages for ECC: much higher performance, and expected resistance to sudden obsolescence when quantum computers appear. Certificate authorities began issuing dual certificates for sites: one based on ECC which newer clients would prefer for performance, and RSA as a fall-back.
Since then, cryptographers have discovered that ECC will be slightly more susceptible than RSA to attack by quantum computers. But ECC still has a performance advantage.
Let's Encrypt
Let's Encrypt is a certificate authority founded by the Electronic Frontier Foundation, the Mozilla Foundation, the University of Michigan, Akamai Technologies, and Cisco Corporation.
The Let's Encrypt CA gives you a certificate that's good for 90 days. The short certificate lifetime makes automated renewal important.
FreeBSDand Linux
Package
Management
ACME,
the Automated Certificate Management Environment,
is a protocol for interacting with the Let's Encrypt CA.
You use the certbot
program
to carry out the various steps.
Install the py37-certbot
package
to get it.
Preparing to Create Key Pairs and Certificates
First, edit the OpenSSL configuration.
Edit the file /etc/ssl/openssl.cnf
.
Search for the string v3_req
and make sure that this line isn't commented out:
req_extensions = v3_req
Search for the next instance of v3_req
,
which will be a section header.
Again, uncomment it if needed.
[ v3_req ]
Add or uncomment a following subjectAltName
line in that section, and add a tlsfeature
line.
Then, before the start of the next section, add a new
alt_name
section in which you define
the alternative names you want included in your certificate.
The result should be something like the following,
my additions are highlighted:
[... many lines deleted ...] # Extensions to add to any X.509v3 certificate request [ v3_req ] # This goes against PKIX guidelines but some CAs do it and some software # requires this to avoid interpreting an end user certificate as a CA. basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment # Support multiple alternative names. subjectAltName = @alt_names # Support OCSP Must-Staple. tlsfeature = status_request [ alt_names ] DNS.1 = www.cromwell-intl.com DNS.2 = cromwell-intl.com DNS.3 = ftp.cromwell-intl.com [... many lines deleted ...]
The old way of supporting OCSP Must-Staple required a
cryptic line:
1.3.6.1.5.5.7.1.24 = DER:30:03:02:01:05
See
Scott Helm's site
for an explanation of the security advantages of
OCSP Must-Staple, and the meaning of the old syntax.
Creating the RSA Key Pair and Certificate
You can use the certbot
command
to create an RSA key pair.
But it's unneccesarily confusing — the
renew
subcommand actually creates a new
key pair rather than renewing anything.
And, we'll need to use the openssl
command
to generate the elliptic-curve keys.
So, let's keep it simple.
Now, let's generate a 4096-bit RSA key pair.
The result goes into a file named rsa-privkey.pem
.
The actual file names, including any dotted suffix,
don't actually matter.
But settle on some pattern that makes sense to you.
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \ -out rsa-privkey.pem
That file is commonly called the "private key file", although it contains much more — the modulus or public key, the public exponent, the private exponent, both primes, two exponent terms, and a coefficient. Check the results with this command:
# openssl rsa -noout -text \ -in rsa-privkey.pem | less RSA Private-Key: (4096 bit, 2 primes) modulus: [... 35 lines omitted ...] publicExponent: 65537 (0x10001) privateExponent: [... 35 lines omitted ...] prime1 [... 18 lines omitted ...] prime2: [... 18 lines omitted ...] exponent1: [... 18 lines omitted ...] exponent2: [... 18 lines omitted ...] coefficient: [... 17 lines omitted ...]
Now we'll create a
certificate-signing request or CSR.
It will ask us for some metadata —
two-character country code, state or province,
locality, and so on.
The result goes into a file named rsa-csr.pem
.
# openssl req -new \ -key rsa-privkey.pem \ -out rsa-csr.pem
You could automate the process and avoid the interactive questions by defining a long Subject string on the command line. Note that you must escape embedded spaces. Here I'm leaving Organizational Unit empty, just "." for "OU".
# openssl req -new \ -key rsa-privkey.pem \ -subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=./CN=cromwell-intl.com/" -outform pem -out rsa-csr.pem
You can check the results with the following command.
Your should see what you intended in the Subject field
near the top, your multiple host names in the Subject
Alternative Name field, and the TLS Feature
status_request
.
# openssl req -noout -text \ -in rsa-csr.pem | less
The Let's Encrypt certbot
package creates a
directory structure.
On FreeBSD it's in /usr/local/etc/letsencrypt
,
let's not fight with it.
It includes a subdirectory accounts
where
you store your account information.
Let's put our private key file and certificate signing
request into their intended locations.
# mv rsa-privkey.pem /usr/local/etc/letsencrypt/keys # mv rsa-csr.pem /usr/local/etc/letsencrypt/csr
We'll get started on the elliptic-curve keys and then come back and ask for the certificates.
Creating the ECC Key Pairs
Now I will create an ECC private key and certificate.
You need a reasonably recent version of openssl
.
See what yours is capable of:
$ openssl ecparam -list_curves | lessUnderstanding
Elliptic-Curve
Cryptography
I will use
elliptic curve P-384,
designated secp384r1
.
That was the strongest elliptic curve included in
NSA Suite B cryptography, later renamed the
CNSA Suite,
and therefore many people interpret
that to mean they should not use secp521r1
.
Browsers support secp384r1
,
but some of them no longer support secp521r1
.
Also note that the NSA Suite B Cryptography and its
replacement the
Commercial National Security Algorithm Suite
specified that Elliptic-Curve Diffie-Hellman
and Elliptic-Curve Digital Signature Algorithm
(ECDH and ECDSA) with curve P-384, designated
secp384r1
, are adequate to protect
US National Security Systems information up to the
Top Secret level.
That's good enough for my purposes.
See the U.S. NIST SP 800-57
"Recommendation
for Key Management"
for the secp384r1
definition, and this
comparison of key lengths needed for approximately
equal resistance to brute-force attack:
Security Strength |
Symmetric (3DES, AES) |
Asymmetric (RSA, DSA) |
Elliptic Curve |
80 | 80 | 1024 | 160 |
112 | 112 | 2048 | 224 |
128 | 128 | 3072 | 256 |
192 | 192 | 7680 | 384 |
256 | 256 | 15,360 | 521 |
Yes, that's 521 in the above table. Earlier versions of the document explicitly said "521". Its revision 5 instead says "512+". US NIST SP 800-56A lists these curves, defined in RFC 4492:
FIPS 186-4 SP 800-56A |
TLS / RFC 4492 SP 800-52 |
IPsec with IKEv2 RFC 5903 |
Strength (bits) |
P-224 | secp224r1 | — | 112 |
P-256 | secp256r1 | secp256r1 | 112–128 |
P-384 | secp384r1 | secp384r1 | 112–192 |
P-521 | secp521r1 | secp521r1 | 112–256 |
K-233 | sect233k1 | — | 112–128 |
K-283 | sect283k1 | — | 112–128 |
K-409 | sect409k1 | — | 112–192 |
K-571 | sect571k1 | — | 112–256 |
B-233 | sect233r1 | — | 112–128 |
B-283 | sect283r1 | — | 112–128 |
B-409 | sect409r1 | — | 112–192 |
B-571 | sect571r1 | — | 112–256 |
Qualys, also known as SSL Labs, provides a
browser test.
Scroll down to the "Protocol Features" section,
then within "Protocol Details" look for
"Named Groups".
On Chrome I see the following.
GREASE is an extension intended to detect
server compatability issues and drive their repair.
It's intentionally not a defined group.
See its description in
RFC 8701.
It is an
IETF draft standard.
It will be reported as tls_grease_caca
or similar, the last four characters hexadecimal in the form
*a*a
.
Named Groups tls_grease_caca, x25519, secp256r1, secp384r1
On Firefox I see the following. Notice that Firefox still supports curve secp521r1:
Named Groups x25519, secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072
On the browser built into Android on my phone I see the same thing I see on Chrome:
Named Groups tls_grease_aaaa, x25519, secp256r1, secp384r1
On
lynx
I see:
Named Groups secp256r1, secp384r1, secp521r1, x25519, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192
On Safari you see secp521r1
similar to Firefox
and Lynx, and GREASE similar to Chrome.
Named Groups tls_grease_1a1a, x25519, secp256r1, secp384r1, secp521r1
secp384r1
is the strongest curve supported by
all modern browser clients.
My server prefers the order:
ssl_ecdh_curve X25519:secp521r1:secp384r1;
It seems that a fleet of *.search.msn.com
indexing bots are about the only clients that use
secp384r1
.
Let's generate a secp384r1 elliptic-curve key pair.
# openssl ecparam -genkey -name secp384r1 | \ openssl ec -out ecc-privkey.pem
You can check the results with this command:
# openssl ec -noout -text -in ecc-privkey.pem
Now I can generate the CSR:
# openssl req -new \ -key ecc-privkey.pem \ -subj "/C=US/ST=Indiana/L=West\ Lafayette/O=Cromwell\ International/OU=./CN=cromwell-intl.com/" -outform pem -out ecc-csr.pem
You can check the results.
Your should again see your intended Subject,
Alternative Names and status_request
.
# openssl req -noout -text -in ecc-csr.pem
Now we'll store those files.
# mv ecc-privkey.pem /usr/local/etc/letsencrypt/keys # mv ecc-csr.pem /usr/local/etc/letsencrypt/csr
Organizing the Files
The certbot
package created subdirectories
named archive
and live
,
and a subdirectory within each of those named
for my domain.
Some of that was created when I initially experimented
with the renew
subcommand.
Here's how I ended up renaming, duplicating, and
repurposing those directories.
All of this is under the letsencrypt
directory, subdirectories under
/usr/local/etc/letsencrypt
:
accounts/
My Let's Encrypt account definitions.
csrs/
Certificate Signing Request files.
keys/
Private key files.
ecc-live/cromwell-intl.com/
Elliptic-curve key and certificate files.
rsa-live/cromwell-intl.com/
RSA key and certificate files.
Plus several unused areas:
ecc-archive/
renewal/
renewal-hooks/
rsa-archive/
I copied the private key files into place.
# cp /usr/local/etc/letsencrypt/keys/ecc-privkey.pem \ /usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/privkey.pem # cp /usr/local/etc/letsencrypt/keys/rsa-privkey.pem \ /usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/privkey.pem
Now we're ready to create the certificates.
Creating the Certificates
I initially ran the certbot
commands
and moved files around by hand.
Once I had it working, I created the following
script that cron
runs every Friday morning:
#!/bin/sh LOGFILE=/root/certbot-output echo '' > $LOGFILE for keytype in ecc rsa do echo "$keytype renewal ====================================" >> $LOGFILE certbot certonly --non-interactive --webroot \ -w /usr/local/www/htdocs \ -d cromwell-intl.com -d www.cromwell-intl.com \ --email bob.cromwell@comcast.net \ --csr /usr/local/etc/letsencrypt/csr/${keytype}-csr.pem \ --agree-tos >> $LOGFILE 2>&1 echo "Installing files ===============================" >> $LOGFILE STORAGE=/usr/local/etc/letsencrypt/${keytype}-live/cromwell-intl.com # The certificate itself: mv -fv 0000_cert.pem $STORAGE/cert.pem >> $LOGFILE # The signing chain: mv -fv 0000_chain.pem $STORAGE/chain.pem >> $LOGFILE # The full chain including our certificate: mv -fv 0001_chain.pem $STORAGE/fullchain.pem >> $LOGFILE done echo "Nginx restart =================================" >> $LOGFILE pkill nginx ; sleep 3 ; pkill nginx /usr/local/etc/rc.d/php-fpm stop >> $LOGFILE 2>&1 /usr/local/etc/rc.d/php-fpm start >> $LOGFILE 2>&1 /usr/local/nginx/sbin/nginx -t >> $LOGFILE 2>&1 /usr/local/nginx/sbin/nginx >> $LOGFILE 2>&1
Be careful developing your solution
You can only request 5 duplicate certificates per week. So, develop any scripts with the certificate renewals commented out, until you're pretty sure that it's going to work. See the rate limit documentation for details.
Enabling HTTPS
httpd.conf
, the Apache configuration file,
contains the following:
# Put these directives at the global level: LoadModule ssl_module libexec/apache24/mod_ssl.so Listen 443 # Put these within individual VirtualHost stanzas # if you are hosting several sites on one server. <VirtualHost *:443> ServerName cromwell-intl.com SSLEngine on # ECC secp384r1 SSLCertificateFile "/usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/fullchain.pem" SSLCertificateKeyFile "/usr/local/etc/letsencrypt/ecc-live/cromwell-intl.com/privkey.pem" # RSA SSLCertificateFile "/usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/fullchain.pem" SSLCertificateKeyFile "/usr/local/etc/letsencrypt/rsa-live/cromwell-intl.com/privkey.pem" </VirtualHost>
Examining the Certificates
We can use the openssl
tool to parse
and display the certificates.
I use PHP to run the command on the actual file
and insert the output here:
# openssl x509 -noout -text -in ecc-live/cromwell-intl.com/cert.pem Certificate: Data: Version: 3 (0x2) Serial Number: 03:11:5c:b2:93:2a:16:8a:29:8a:99:89:d1:0c:98:56:2d:b1 Signature Algorithm: ecdsa-with-SHA384 Issuer: C = US, O = Let's Encrypt, CN = E5 Validity Not Before: Jan 7 07:41:45 2025 GMT Not After : Apr 7 07:41:44 2025 GMT Subject: CN = cromwell-intl.com Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (384 bit) pub: 04:d2:ef:ad:da:c1:7e:4d:4b:72:49:cd:e2:d5:94: 94:79:4a:81:09:63:cd:7d:44:07:4e:c3:54:48:fc: 4b:f8:5c:d3:46:3e:54:a1:9a:e3:03:68:24:39:61: 6c:ac:a8:b6:d1:64:fd:2a:b9:af:79:b8:d5:ef:c0: 37:b4:82:28:9a:26:2b:0f:24:85:1f:77:28:9f:e1: f3:ca:77:42:20:49:f7:8c:01:f1:aa:05:66:e4:99: 46:09:0a:ca:c4:7e:eb ASN1 OID: secp384r1 NIST CURVE: P-384 X509v3 extensions: X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: 1F:F4:A0:89:41:AA:65:09:49:F9:BF:AF:72:BF:7A:41:01:72:F9:7A X509v3 Authority Key Identifier: 9F:2B:5F:CF:3C:21:4F:9D:04:B7:ED:2B:2C:C4:C6:70:8B:D2:D7:0D Authority Information Access: OCSP - URI:http://e5.o.lencr.org CA Issuers - URI:http://e5.i.lencr.org/ X509v3 Subject Alternative Name: DNS:alt.cromwell-intl.com, DNS:cromwell-intl.com, DNS:www.cromwell-intl.com X509v3 Certificate Policies: Policy: 2.23.140.1.2.1 CT Precertificate SCTs: Signed Certificate Timestamp: Version : v1 (0x0) Log ID : DE:85:81:D7:50:24:7C:6B:CD:CB:AF:56:37:C5:E7:81: C6:4C:E4:6E:D6:17:63:9F:8F:34:A7:26:C9:E2:BD:37 Timestamp : Jan 7 08:40:15.727 2025 GMT Extensions: none Signature : ecdsa-with-SHA256 30:45:02:20:3A:58:D9:30:E9:B0:B6:63:EB:F0:B7:77: CF:FE:74:E6:21:C3:EB:68:16:B6:04:F5:A3:DE:62:B9: 87:F1:60:AC:02:21:00:CF:33:FA:7D:F1:C0:41:2B:01: F4:E7:DE:CE:4A:46:8C:69:54:92:46:2E:91:43:73:BB: 31:D4:7F:92:F9:2A:75 Signed Certificate Timestamp: Version : v1 (0x0) Log ID : 13:4A:DF:1A:B5:98:42:09:78:0C:6F:EF:4C:7A:91:A4: 16:B7:23:49:CE:58:57:6A:DF:AE:DA:A7:C2:AB:E0:22 Timestamp : Jan 7 08:40:16.096 2025 GMT Extensions: none Signature : ecdsa-with-SHA256 30:44:02:20:7C:B1:2C:22:F4:E0:B9:6E:31:23:FE:58: 00:E6:CE:92:C6:18:81:3A:A4:78:D8:30:97:BC:3F:E3: CD:AA:66:1C:02:20:37:97:85:59:9A:01:F3:98:14:BE: F8:EC:3F:2A:51:9B:CC:BA:46:4F:EB:E7:99:C3:1A:04: DD:CF:4C:26:7C:0A TLS Feature: status_request Signature Algorithm: ecdsa-with-SHA384 Signature Value: 30:64:02:30:7b:94:5f:ee:ca:3e:51:28:42:69:9a:a4:1c:f5: 4c:d7:27:15:09:bf:47:da:50:ed:e9:e3:cd:b8:eb:c2:d4:de: 57:5d:3f:35:1c:f9:06:1e:b7:bd:71:8c:8e:fa:ed:33:02:30: 2c:c5:4b:c3:63:e3:3b:3d:7a:f7:d0:a3:f1:ca:cc:44:af:8e: 77:ee:9d:ef:c7:bb:f9:e0:86:2e:ba:72:50:2b:96:25:f3:e4: 4b:6d:fa:d0:7a:9e:60:aa:6c:2e:19:60
# openssl x509 -noout -text -in rsa-live/cromwell-intl.com/cert.pem Certificate: Data: Version: 3 (0x2) Serial Number: 03:e9:08:73:a3:07:f2:72:ed:a0:6c:a6:1d:57:2b:fc:9e:93 Signature Algorithm: sha256WithRSAEncryption Issuer: C = US, O = Let's Encrypt, CN = R11 Validity Not Before: Jan 7 07:41:48 2025 GMT Not After : Apr 7 07:41:47 2025 GMT Subject: CN = cromwell-intl.com Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (4096 bit) Modulus: 00:af:55:1e:44:f3:ce:fc:53:19:61:e4:b9:c8:ef: 90:74:a6:67:ad:64:86:4d:f9:8b:2e:1b:33:0f:6f: 69:fc:0f:5f:f4:50:3c:12:17:9d:fd:82:8d:ce:f3: 65:31:b2:a0:bd:d8:2a:57:11:0f:6b:c7:9a:a8:c6: 1f:ff:e9:e5:63:0b:57:b4:b1:18:2f:d0:7c:36:12: 5a:e5:d0:88:34:be:ee:71:7c:e3:81:10:f5:6d:36: da:db:01:f0:4c:1e:bc:de:3c:a2:6b:7e:2b:62:c8: 8a:59:c8:6d:46:39:f4:61:6c:f8:05:c5:8f:65:2d: 60:f4:c2:94:0e:88:05:d2:82:d7:df:27:ad:cf:20: 11:54:75:53:67:b5:b7:b1:b2:05:a1:6f:9c:48:e2: 45:35:41:6f:c3:7c:1e:f3:a6:c2:0f:c7:bb:4c:7e: 57:11:88:b7:78:2d:f1:4c:67:93:2c:07:58:1f:07: 09:90:45:5a:d9:2c:29:4c:0e:4c:45:67:ca:f4:99: 59:17:92:82:78:af:61:44:22:fe:92:01:19:73:14: 87:7b:93:8b:cb:29:a7:9c:fa:31:e1:10:fa:b9:95: 5d:dc:74:81:cf:22:67:1a:72:d1:bd:6c:32:fb:db: 17:70:be:61:c0:3e:9e:4c:bb:da:e8:21:54:c4:6b: 21:e9:49:e7:d0:3b:b9:b8:ff:b6:ad:84:7f:86:63: 86:8e:31:55:2c:0a:3f:0b:cb:cc:b9:2c:01:49:bf: 16:27:97:b9:31:67:2d:93:40:2b:51:e7:d2:47:00: 11:88:0f:25:a9:8c:bb:96:c9:e0:8a:19:55:f7:f4: 2f:db:31:f8:9f:fc:9e:14:e2:62:6f:d8:c7:ea:2b: fd:db:17:a4:9d:7a:4f:d1:0f:88:be:c8:72:c0:be: 85:a2:d7:aa:cb:04:1d:41:25:96:3d:58:40:f1:0f: 77:54:5d:23:2a:16:b8:3d:b1:b3:06:18:3f:5e:d9: f4:a7:1d:9d:89:42:82:cc:e2:b1:44:26:7f:76:75: 15:d8:f0:a1:48:d2:b9:d7:df:d7:c4:79:1d:30:87: 11:22:a7:90:86:59:65:04:6d:fa:5c:1a:5b:a1:f9: 13:1f:33:53:18:4b:57:31:13:9e:37:56:97:38:8d: 2c:6f:ec:7e:a0:1c:3f:6e:63:de:c9:cf:dd:25:04: f7:24:3c:f9:22:98:67:e8:fe:b0:a0:2a:ca:60:47: 35:f9:3d:52:a7:65:20:45:b0:f1:51:a0:2d:27:6a: 9a:9d:c2:a4:d1:2d:4d:16:68:aa:1c:63:b1:0c:13: 0f:0b:16:11:f2:b6:5f:e1:70:73:ca:3c:e1:5a:f0: b5:18:8d Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: 93:E4:B4:2C:B7:D2:06:98:09:94:B1:99:F5:81:6F:F4:91:24:D0:19 X509v3 Authority Key Identifier: C5:CF:46:A4:EA:F4:C3:C0:7A:6C:95:C4:2D:B0:5E:92:2F:26:E3:B9 Authority Information Access: OCSP - URI:http://r11.o.lencr.org CA Issuers - URI:http://r11.i.lencr.org/ X509v3 Subject Alternative Name: DNS:alt.cromwell-intl.com, DNS:cromwell-intl.com, DNS:www.cromwell-intl.com X509v3 Certificate Policies: Policy: 2.23.140.1.2.1 CT Precertificate SCTs: Signed Certificate Timestamp: Version : v1 (0x0) Log ID : A2:E3:0A:E4:45:EF:BD:AD:9B:7E:38:ED:47:67:77:53: D7:82:5B:84:94:D7:2B:5E:1B:2C:C4:B9:50:A4:47:E7 Timestamp : Jan 7 08:40:18.519 2025 GMT Extensions: none Signature : ecdsa-with-SHA256 30:45:02:20:60:CB:40:11:0A:B3:3F:90:D6:85:71:CD: EF:10:AA:79:DD:B7:6C:FE:14:CE:DC:27:F0:4E:66:78: A1:89:BC:E1:02:21:00:DF:0C:1A:29:28:A9:C8:A6:24: 25:54:35:EA:38:C0:0D:71:11:D8:81:77:5B:AB:38:BA: 65:AA:14:AB:A0:35:1B Signed Certificate Timestamp: Version : v1 (0x0) Log ID : 4E:75:A3:27:5C:9A:10:C3:38:5B:6C:D4:DF:3F:52:EB: 1D:F0:E0:8E:1B:8D:69:C0:B1:FA:64:B1:62:9A:39:DF Timestamp : Jan 7 08:40:18.520 2025 GMT Extensions: none Signature : ecdsa-with-SHA256 30:45:02:20:6C:95:45:92:3A:3C:E2:D2:CE:E3:9F:46: 88:A8:D6:83:88:70:97:4C:65:96:BD:00:CA:D4:AE:5B: 84:C1:C1:DB:02:21:00:90:55:88:92:09:F0:FC:AF:DD: B6:9E:6F:D7:B9:01:0C:9B:96:F9:17:47:63:D9:28:48: 8A:11:80:41:B3:1F:07 TLS Feature: status_request Signature Algorithm: sha256WithRSAEncryption Signature Value: 2d:bb:41:83:83:25:44:12:ec:bc:25:57:2d:3c:9b:77:30:e4: b0:60:52:6c:6d:7c:46:54:33:7d:1e:d2:b9:39:55:25:5c:d2: 71:76:90:35:5b:a3:82:c8:1c:93:9b:29:a1:f6:68:73:7c:b2: bd:ed:49:b2:db:24:66:56:e5:fd:56:5c:e7:21:09:d6:a9:c7: 44:f0:c7:17:41:20:ee:b7:ae:2c:29:5a:c1:69:8f:ca:03:0e: 21:f9:5d:ab:d1:9a:78:6f:df:23:ca:9e:7c:7e:48:2e:77:a5: fc:da:bf:c5:f7:b7:79:68:64:cc:7b:77:ca:c7:79:5a:b4:2e: 54:43:78:4e:3e:c7:db:4a:b7:1a:54:7a:72:ca:34:fe:6c:f3: 17:fb:be:c1:b6:b4:50:22:dd:a4:b0:35:99:86:e5:95:14:f8: 6e:79:30:8c:5d:b3:c5:f5:25:13:ae:d5:9c:96:59:48:6a:85: e8:aa:5b:d7:d7:0d:ee:95:c0:a7:9e:e9:c6:32:d0:1a:1a:a8: 5b:a4:57:06:a2:94:42:48:65:53:b6:f1:24:83:32:35:34:c2: 0a:98:ca:9c:8c:11:48:ab:a3:9b:bb:32:87:a8:99:96:08:81: 87:d2:e7:63:da:5b:1b:a3:8d:ac:8d:f1:ce:88:4a:31:16:43: 50:68:ed:da
Initial Test
Restart Apache and verify that you can connect with both HTTP and HTTPS.
You could run a Qualys server test, but at this point it would probably get a "B" grade at best. We will tune the configuration, but first let's set up redirection.
Redirect to HTTPS and Remove "www."
The goal is to accept all connections, redirecting all
of these:
http://cromwell-intl.com/some/path/
http://www.cromwell-intl.com/some/path/
https://www.cromwell-intl.com/some/path/
to this:
https://cromwell-intl.com/some/path/
Add the following to your .htaccess
file
in the root of the web site.
Bold shows additions here.
Don't duplicate the RewriteEngine
line.
# Do NOT duplicate the following line if it # already exists earlier in the file. RewriteEngine on # Remove "www." and redirect HTTP to HTTPS # Use a standard variable and a tagged regular expression to # replace the URL with "https://", the host name, and the # path minus any leading "www.": RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC] RewriteRule ^(.*)$ https://%1/$1 [R=301,L] # If they asked for the non-www name but with HTTP, # build a new HTTPS URL with the host name and path: RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # We added the following in an earlier step. # If the client asks for a non-existent *.html file, rewrite it so # the bad name isn't passed to the PHP engine causing a "Primary script # unknown" log entry and a plain "File not found" page. RewriteCond %{REQUEST_FILENAME} \.html$ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI} !-f RewriteRule (.*) - [H=text/html]
Yes, I did the redirection with the site-specific
.htaccess
file.
I could have instead done it with slightly different syntax
in the server-wide httpd.conf
configuration file.
For the single site, given its size and traffic, I didn't
see a big advantage of one method over the other.
Now test all combinations of the redirections: http vs https, www. vs not, and include requests for nonexistent files and directories.
Improving the TLS Configuration
Mozilla Config Generator Apache SSL HowtoApache has a good SSL/TLS how-to document. Even more useful, Mozilla has a configuration generator. You select your server, its version, and your OpenSSL versions, and then you select the security profile.
Which security profile should you use? It depends...
Let's say you're setting up a server for use within your organization, and you have full control of the desktop systems and any portable laptops that could be used to connect in from outside. I recommend the strictest "Modern" profile for that case. All your client machines will need to be fairly current, but that should already be the case.
However, let's say that you want to be open to most all clients from the public. That's my situation. I have ads on my site. While it would be nice if everyone used up-to-date operating systems and browsers, I don't want to block or even inconvenience people with somewhat outdated platforms.
I used the "Intermediate" profile as a starting point.
Here is what I added toward the end of the
httpd.conf
configuration file,
before and outside the VirtualHost
stanza,
so it will apply to all virtually hosted web
sites I eventually set up on this server.
The cipher suite is usually one enormously long line
with no whitespace.
That's hard to read and much harder to modify,
so I prefer to split it across multiple lines.
Just make sure each backslash is the last character
on its line.
# TLS only, no SSL SSLProtocol all -SSLv2 -SSLv3 # Specify ciphers in a preferred order. I reordered what the configuration # generator gave me, putting better ciphers first. SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:\ ECDHE-RSA-CHACHA20-POLY1305:\ ECDHE-ECDSA-AES256-GCM-SHA384:\ ECDHE-RSA-AES256-GCM-SHA384:\ DHE-RSA-AES256-GCM-SHA384:\ ECDHE-ECDSA-AES128-GCM-SHA256:\ ECDHE-RSA-AES128-GCM-SHA256:\ DHE-RSA-AES128-GCM-SHA256:\ ECDHE-ECDSA-AES256-SHA384:\ ECDHE-ECDSA-AES128-SHA256:\ ECDHE-ECDSA-AES256-SHA:\ ECDHE-ECDSA-AES128-SHA:\ ECDHE-RSA-AES256-SHA384:\ ECDHE-RSA-AES128-SHA256:\ ECDHE-RSA-AES256-SHA:\ ECDHE-RSA-AES128-SHA:\ DHE-RSA-AES256-SHA256:\ DHE-RSA-AES128-SHA256:\ DHE-RSA-AES256-SHA:\ DHE-RSA-AES128-SHA:\ AES256-GCM-SHA384:\ AES128-GCM-SHA256:\ AES256-SHA256:\ AES128-SHA256:\ AES256-SHA:\ AES128-SHA:\ ECDHE-ECDSA-DES-CBC3-SHA:\ ECDHE-RSA-DES-CBC3-SHA:\ EDH-RSA-DES-CBC3-SHA:\ DES-CBC3-SHA:\ !DSS SSLHonorCipherOrder on # Disable compression and session tickets SSLCompression off SSLSessionTickets off # Enable OCSP Stapling LoadModule socache_shmcb_module libexec/apache24/mod_socache_shmcb.so SSLUseStapling On SSLStaplingCache "shmcb:logs/ssl_stapling(32768)" # Enable session resumption (caching). # However do not use large values. Large advertisers like # Google and Facebook use that to track users. Your site will # look suspicious if it does this. See, for example: # https://www.zdnet.com/article/advertisers-can-track-users-across-the-internet-via-tls-session-resumption/ # 300 seconds (5 minutes) seems reasonable. That's actually the # default, no need to specify that, but here's the syntax. SSLSessionCache "shmcb:logs/ssl_scache" SSLSessionCacheTimeout 300 # Insist on HSTS or HTTP Strict Transport Security Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
And with that, an A+ evaluation from the Qualys analyzer:
The server supports 3DES as a fallback position if none of the stronger ciphers are supported. That allows connections from IE 8 on XP. While 3DES is flagged in the report as weak, at least at the time that I did this it did not lower the score. Unlike RC4, which capped the grade at B at the time.
DNS CAA or Certification Authority Authorization provides a way to indicate in a DNS record just who is allowed to be a CA for the site. See this Qualys blog for details on what CAA is, and how the CA/Browser Forum has mandated its use.
Google Domains did not yet support CAA records when I started this project in July 2017, as you can see below. Google Cloud DNS, a different product, did.
By February 2018, they had added CAA records:
Let's test it:
$ dig @localhost cromwell-intl.com CAA
; <<>> DiG 9.10.6-P1 <<>> @localhost cromwell-intl.com CAA
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26186
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;cromwell-intl.com. IN CAA
;; ANSWER SECTION:
cromwell-intl.com. 3570 IN CAA 128 issue "letsencrypt.org"
;; AUTHORITY SECTION:
cromwell-intl.com. 78163 IN NS ns-cloud-c1.googledomains.com.
cromwell-intl.com. 78163 IN NS ns-cloud-c4.googledomains.com.
cromwell-intl.com. 78163 IN NS ns-cloud-c3.googledomains.com.
cromwell-intl.com. 78163 IN NS ns-cloud-c2.googledomains.com.
;; Query time: 0 msec
;; SERVER: ::1#53(::1)
;; WHEN: Wed Feb 14 14:44:24 EST 2018
;; MSG SIZE rcvd: 198
What About SMTP/S?
To test your SMTP/S server, use the excellent CheckTLS SMTP/S Test.
Final Step
I was very happy with this result! There are just a few more things to do.
Proceed to the final step to see how to tune the HTTP(S) headers.