How to configure nginx
to use a certificate generated on a Trusted Platform Module (TPM)
… and in my case, on a raspberry pi
with one of these:
Essentially, we will
Install the tpm2-tss Engine for Openssl which allow for nginx to talk to the tpm using openssl’s Engine system.
Generate an RSA key on the TPM
Use openssl to generate a Certificate Signing Request (CSR)
Use our own CA to sign that CSR and generate an x509
certificate suitable for a webserver
Configure nginx to read the private key on the TPM and the certificate
access site!
Some notes…
/dev/tpm0
) and has limited throughput.Yep, you need to install a bunch of stuff…i did this using the sources here but you can also just package install
make sure you install in sequence
tpm2-tss
tpm2-tools
(you don’t need this but its good to have around)tpm2-tss-engine
Verify that the engine is available by generating some random bytes
$ openssl engine -t
(dynamic) Dynamic engine loading support
[ unavailable ]
(tpm2tss) TPM2-TSS engine for OpenSSL
[ available ]
$ openssl rand -hex -engine tpm2tss 32
engine "tpm2tss" set.
2798d69532b579b9db8bb2bb5d0f6c76fcea162ca243b01bfacb679bfcdb3431
Use the tpm2tss-genkey utility to create the key
tpm2tss-genkey -a rsa -s 2048 tpm.key
openssl rsa -engine tpm2tss -inform engine -in tpm.key -pubout -outform pem -out tpm.pem
In my case, the public key was the following…but the private key is of course on the TPM
$ more tpm.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNEgTnW2qXSVjza5M84y
cvdrXh3VGIqWFWSLi5qcdWriWo3S3aVjDuN4c42uF5xhqx71PRCI5bGYqkG4U6Y7
WB/6jrbKIrTuOTWOfwnTqXelbahRHWWVq+z8P/Z1ABNVS8kxJQEz6zOVgF9rh+oK
XHteMwFjt/368cTShpoSq3CUQcOybP6B/pHyMibBz1jkLfCfWc7u+VvZQAsINAtB
oucV/aRbLtkMjRXdiOe/fQNsAqtKacpZCtqClSoRyg0rBjwMuKZpXiZZ3rvIzpjt
ToFimCaZJUlf+wV1pdHB29Xcblv6JmPcUw61BM4d9iQXPtZn4p7R+nypUn2vJQ7P
LwIDAQAB
-----END PUBLIC KEY-----
Just for fun, create a file, sign and verify with the public key:
echo -n "foo" > mydata
openssl pkeyutl -engine tpm2tss -keyform engine -inkey tpm.key -sign -in mydata -out mysig
openssl pkeyutl -pubin -inkey tpm.pem -verify -in mydata -sigfile mysig
Create an openssl config file (shown in the appendix). We will use this to set the specifications for the CSR
req.conf
At this point, you could just take the easy route and create a self-signed key
# to use self signed
# openssl req -config req.conf -new -x509 -engine tpm2tss -key tpm.key -keyform engine -out tpm.crt -days 1000
# openssl x509 -in tpm.crt -text -noout
but what what i did is created a CSR instead
# to generate csr to sign
openssl req -config req.conf -new -engine tpm2tss -key tpm.key -keyform engine -out tpm.csr
My CSR looked like this
tpm.csr
-----BEGIN CERTIFICATE REQUEST-----
MIIDBjCCAe4CAQAwcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQH
DA1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQKDAZHb29nbGUxEzARBgNVBAsMCkVudGVy
cHJpc2UxGDAWBgNVBAMMD2Jsb2cuNmVxdWo1LmRldjCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAJjRIE51tql0lY82uTPOMnL3a14d1RiKlhVki4uanHVq
4lqN0t2lYw7jeHONrhecYase9T0QiOWxmKpBuFOmO1gf+o62yiK07jk1jn8J06l3
pW2oUR1llavs/D/2dQATVUvJMSUBM+szlYBfa4fqClx7XjMBY7f9+vHE0oaaEqtw
lEHDsmz+gf6R8jImwc9Y5C3wn1nO7vlb2UALCDQLQaLnFf2kWy7ZDI0V3Yjnv30D
bAKrSmnKWQragpUqEcoNKwY8DLimaV4mWd67yM6Y7U6BYpgmmSVJX/sFdaXRwdvV
3G5b+iZj3FMOtQTOHfYkFz7WZ+Ke0fp8qVJ9ryUOzy8CAwEAAaBPME0GCSqGSIb3
DQEJDjFAMD4wCwYDVR0PBAQDAgQwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1Ud
EQQTMBGCD2Jsb2cuNmVxdWo1LmRldjANBgkqhkiG9w0BAQsFAAOCAQEAlcjgjL5E
QwhrrkFbq9WqlwKkNeg4zfgawxo93Tw2vupsQifHaqXP3CT/RypzCIFjSr2YGZWR
DCSdg2MtbEo4nT9/N5Iv1rl/lMBDwFO44nfV0qy5LKGybMc6CgPt1fIcp0XSmX7C
WInqfiplGhGH43WPDAV7Mf/0GB42AefUJTDVz98flriIaDteyID5CJI4oARTYcuQ
umhuJLb+BVloDY100vpF0b6Wt7vyCuLxHeO94rQqoByB/Ol8yA+magn8JAqOvBDN
LTHWp87MfTepm13YQOXLgh7Y1T5aRmv8ftYx4bd3hymsafZ/8l1Kuz3JsGSLF2xN
OEJnj6exde18GA==
-----END CERTIFICATE REQUEST-----
I then took the CSR to my own CA and signed it (you can create your own CA CA ScratchPad ) if you want
Sign the CSR.
The cert that i got back was:
tpm.crt
-----BEGIN CERTIFICATE-----
MIIEVzCCAz+gAwIBAgIBQTANBgkqhkiG9w0BAQUFADBXMQswCQYDVQQGEwJVUzEP
MA0GA1UECgwGR29vZ2xlMRMwEQYDVQQLDApFbnRlcnByaXNlMSIwIAYDVQQDDBlF
bnRlcnByaXNlIFN1Ym9yZGluYXRlIENBMB4XDTIxMTIxNTE2NDY1NVoXDTI3MDYw
NzE2NDY1NVowcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1N
b3VudGFpbiBWaWV3MQ8wDQYDVQQKDAZHb29nbGUxEzARBgNVBAsMCkVudGVycHJp
c2UxGDAWBgNVBAMMD2Jsb2cuNmVxdWo1LmRldjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJjRIE51tql0lY82uTPOMnL3a14d1RiKlhVki4uanHVq4lqN
0t2lYw7jeHONrhecYase9T0QiOWxmKpBuFOmO1gf+o62yiK07jk1jn8J06l3pW2o
UR1llavs/D/2dQATVUvJMSUBM+szlYBfa4fqClx7XjMBY7f9+vHE0oaaEqtwlEHD
smz+gf6R8jImwc9Y5C3wn1nO7vlb2UALCDQLQaLnFf2kWy7ZDI0V3Yjnv30DbAKr
SmnKWQragpUqEcoNKwY8DLimaV4mWd67yM6Y7U6BYpgmmSVJX/sFdaXRwdvV3G5b
+iZj3FMOtQTOHfYkFz7WZ+Ke0fp8qVJ9ryUOzy8CAwEAAaOCAREwggENMA4GA1Ud
DwEB/wQEAwIHgDAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1Ud
DgQWBBTaJ8JibkMFng0gmp4Q0ZbzuD7SwzAfBgNVHSMEGDAWgBS/4RzwIkiP/DvP
XdntrohwId/dhjBEBggrBgEFBQcBAQQ4MDYwNAYIKwYBBQUHMAKGKGh0dHA6Ly9w
a2kuZXNvZGVtb2FwcDIuY29tL2NhL3Rscy1jYS5jZXIwOQYDVR0fBDIwMDAuoCyg
KoYoaHR0cDovL3BraS5lc29kZW1vYXBwMi5jb20vY2EvdGxzLWNhLmNybDAaBgNV
HREEEzARgg9ibG9nLjZlcXVqNS5kZXYwDQYJKoZIhvcNAQEFBQADggEBAIJ289RE
WQ/40Oa3b5YG/3RVt2Wmqo0+xNwmT0TG/gDsfNeA3QYjAiT7iNfx79s2ElDjDayG
IQrv3PWkdurIGt+3KNzaaBDkgztYs+WJKA04YeFVThs1dMk0sjM7VHBlehyjXDHy
pScPY7dQcu1yEBZjh0o1Ju/n0I+yAfYRg6P13+r+KexkAtEDaYE6a98ySIc0JBUI
flagANLd7k1g/QzvCRk7BaRbMpHmqY9NmeOMedixE90Yd2z5s5m965u9AfV8MO+E
DKEdWo/4pstiJ0xAMCmIW3ziFbUtog0/BKRUYXPR1r3iw6ALNsX9cua5OveO7pQn
+6MIh8d/BAaaSOg=
-----END CERTIFICATE-----
$ openssl x509 -in tpm.crt -inform PEM -noout -fingerprint
SHA1 Fingerprint=D1:31:4D:67:8A:F5:F6:19:9E:D9:0B:3E:DF:65:D9:F3:F7:ED:91:9A
$ openssl x509 -inform PEM -in tpm.crt -pubkey -noout
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNEgTnW2qXSVjza5M84y
cvdrXh3VGIqWFWSLi5qcdWriWo3S3aVjDuN4c42uF5xhqx71PRCI5bGYqkG4U6Y7
WB/6jrbKIrTuOTWOfwnTqXelbahRHWWVq+z8P/Z1ABNVS8kxJQEz6zOVgF9rh+oK
XHteMwFjt/368cTShpoSq3CUQcOybP6B/pHyMibBz1jkLfCfWc7u+VvZQAsINAtB
oucV/aRbLtkMjRXdiOe/fQNsAqtKacpZCtqClSoRyg0rBjwMuKZpXiZZ3rvIzpjt
ToFimCaZJUlf+wV1pdHB29Xcblv6JmPcUw61BM4d9iQXPtZn4p7R+nypUn2vJQ7P
LwIDAQAB
-----END PUBLIC KEY-----
of course the public keys match! Copy the tpm.crt
file to your webserver (needless to say, don’t copy this file since you will have your own TPM on your own device!)
openssl.cnf
The location of the openssl file for me was at /usr/lib/ssl/openssl.cnf
(for ref, i modified the sample here to account for the .so
path).
The libtpm2tss.so
file for me was here (yep, arm-linux
)
/usr/lib/arm-linux-gnueabihf/engines-1.1/libtpm2tss.so
Edit the openssl config file and point it to the .so
above
/usr/lib/ssl/openssl.cnf
openssl_conf = openssl_init
[openssl_init]
engines = engine_section
[engine_section]
tpm2tss = tpm2tss_section
[tpm2tss_section]
engine_id = tpm2tss
dynamic_path = /usr/lib/arm-linux-gnueabihf/engines-1.1/libtpm2tss.so
default_algorithms = RSA,ECDSA
init = 1
We now need to tell nginx about the engine’s it should use.
Edit /etc/nginx/nginx.conf
and specify the ssl_engine tpm2tss
line as shown below
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
ssl_engine tpm2tss;
Now edit your nginx server file at /etc/nginx/sites-enabled/default
and specify the paths to the TPM based private key reference and the certificate you just uploaded
ssl_certificate /root/tpm_certs/tpm.crt;
ssl_certificate_key engine:tpm2tss:/root/tpm_certs/tpm.key;
the full structure is:
server {
listen 443 ssl http2;
server_name blog.6equj5.dev;
root /usr/share/nginx/blog;
index index.html index.htm;
access_log /var/log/nginx/access_6equj5.log;
error_log /var/log/nginx/error_6equj5.log;
ssl on;
ssl_certificate /root/tpm_certs/tpm.crt;
ssl_certificate_key engine:tpm2tss:/root/tpm_certs/tpm.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
try_files $uri $uri/ =404;
}
}
In my case…the server is currently running at: https://blog.6equj5.dev
the x509
is what we setup earlier
$ openssl s_client --connect blog.6equj5.dev:443
CONNECTED(00000003)
depth=0 C = US, ST = CA, L = Mountain View, O = Google, OU = Enterprise, CN = blog.6equj5.dev
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = US, ST = CA, L = Mountain View, O = Google, OU = Enterprise, CN = blog.6equj5.dev
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 C = US, ST = CA, L = Mountain View, O = Google, OU = Enterprise, CN = blog.6equj5.dev
verify return:1
---
Certificate chain
0 s:C = US, ST = CA, L = Mountain View, O = Google, OU = Enterprise, CN = blog.6equj5.dev
i:C = US, O = Google, OU = Enterprise, CN = Enterprise Subordinate CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEVzCCAz+gAwIBAgIBQTANBgkqhkiG9w0BAQUFADBXMQswCQYDVQQGEwJVUzEP
MA0GA1UECgwGR29vZ2xlMRMwEQYDVQQLDApFbnRlcnByaXNlMSIwIAYDVQQDDBlF
bnRlcnByaXNlIFN1Ym9yZGluYXRlIENBMB4XDTIxMTIxNTE2NDY1NVoXDTI3MDYw
NzE2NDY1NVowcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1N
b3VudGFpbiBWaWV3MQ8wDQYDVQQKDAZHb29nbGUxEzARBgNVBAsMCkVudGVycHJp
c2UxGDAWBgNVBAMMD2Jsb2cuNmVxdWo1LmRldjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJjRIE51tql0lY82uTPOMnL3a14d1RiKlhVki4uanHVq4lqN
0t2lYw7jeHONrhecYase9T0QiOWxmKpBuFOmO1gf+o62yiK07jk1jn8J06l3pW2o
UR1llavs/D/2dQATVUvJMSUBM+szlYBfa4fqClx7XjMBY7f9+vHE0oaaEqtwlEHD
smz+gf6R8jImwc9Y5C3wn1nO7vlb2UALCDQLQaLnFf2kWy7ZDI0V3Yjnv30DbAKr
SmnKWQragpUqEcoNKwY8DLimaV4mWd67yM6Y7U6BYpgmmSVJX/sFdaXRwdvV3G5b
+iZj3FMOtQTOHfYkFz7WZ+Ke0fp8qVJ9ryUOzy8CAwEAAaOCAREwggENMA4GA1Ud
DwEB/wQEAwIHgDAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1Ud
DgQWBBTaJ8JibkMFng0gmp4Q0ZbzuD7SwzAfBgNVHSMEGDAWgBS/4RzwIkiP/DvP
XdntrohwId/dhjBEBggrBgEFBQcBAQQ4MDYwNAYIKwYBBQUHMAKGKGh0dHA6Ly9w
a2kuZXNvZGVtb2FwcDIuY29tL2NhL3Rscy1jYS5jZXIwOQYDVR0fBDIwMDAuoCyg
KoYoaHR0cDovL3BraS5lc29kZW1vYXBwMi5jb20vY2EvdGxzLWNhLmNybDAaBgNV
HREEEzARgg9ibG9nLjZlcXVqNS5kZXYwDQYJKoZIhvcNAQEFBQADggEBAIJ289RE
WQ/40Oa3b5YG/3RVt2Wmqo0+xNwmT0TG/gDsfNeA3QYjAiT7iNfx79s2ElDjDayG
IQrv3PWkdurIGt+3KNzaaBDkgztYs+WJKA04YeFVThs1dMk0sjM7VHBlehyjXDHy
pScPY7dQcu1yEBZjh0o1Ju/n0I+yAfYRg6P13+r+KexkAtEDaYE6a98ySIc0JBUI
flagANLd7k1g/QzvCRk7BaRbMpHmqY9NmeOMedixE90Yd2z5s5m965u9AfV8MO+E
DKEdWo/4pstiJ0xAMCmIW3ziFbUtog0/BKRUYXPR1r3iw6ALNsX9cua5OveO7pQn
+6MIh8d/BAaaSOg=
-----END CERTIFICATE-----
subject=C = US, ST = CA, L = Mountain View, O = Google, OU = Enterprise, CN = blog.6equj5.dev
issuer=C = US, O = Google, OU = Enterprise, CN = Enterprise Subordinate CA
req.conf
:[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = US
ST = CA
L = Mountain View
O = Google
OU = Enterprise
CN = blog.6equj5.dev
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = blog.6equj5.dev
Note, nginx initially failed to start with errors like this
ERROR:tcti:src/tss2-tcti/tcti-device.c:443:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpm0: Permission denied
ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0
What that means is of course a permissions issue
# ls -lart /dev/tpm*
crw-rw---- 1 tss tss 240, 65536 Dec 12 18:17 /dev/tpmrm0
crw-rw---- 1 tss root 10, 224 Dec 12 18:17 /dev/tpm0
since nginx runs as www-data
for me
# ps -ef|grep nginx
root 899 1 0 10:35 ? 00:00:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data 900 899 0 10:35 ? 00:00:00 nginx: worker process
www-data 901 899 0 10:35 ? 00:00:00 nginx: worker process
www-data 902 899 0 10:35 ? 00:00:00 nginx: worker process
www-data 903 899 0 10:35 ? 00:00:00 nginx: worker process
root 907 732 0 10:36 pts/0 00:00:00 grep nginx
All you should do is add the user to that group and restart nginx
usermod -a -G tss www-data
This site supports webmentions. Send me a mention via this form.