This article focuses on enabling the usage of the keys stored in the TPM2 by various tools using the PKCS#11 interface. The module should be configured to allow the access to these keys only after some measurement of the state of the system is done, to guarantee that it was not tampered. This part is outside of the scope of these notes. Here the protections offered to the user are the physical possession and the requirement to enter a PIN.
PKCS#11 exposes N separate slots, each containing a token. We will used a separate slot/token for each application. For the basic support under Arch Linux, the following packages need to be installed:
tpm2-tss. The latest package also sets up udev rules to allow the abrmd broker daemon to access the device
/dev/tpm0. You can test if the TPM2 module is working by printing some random characters by using its random number generator:
$ tpm2_getrandom --hex 16
tpm-pkcs11 needs to save some information on disk, I've set the
TPM2_PKCS11_STORE environment variable to a suitable directory in my
SSH in slot 1
Using the TPM2 is pretty straightforward. From here on [PIN] denotes the user PIN. Avoid saving the command lines containing it in the bash history, for example by prepending the line with a space.
$ tpm2_ptool init action: Created id: 1 $ tpm2_ptool addtoken --pid=1 --sopin=[PIN] --userpin=[PIN] --label=ssh_token $ tpm2_ptool addkey --algorithm=rsa2048 --label=ssh_token --key-label=ssh_token --userpin=[PIN] action: add private: CKA_ID: '33333334333132626661393734663161' public: CKA_ID: '33333334333132626661393734663161'
To output the public key (to be added to the
authorized_keys file on the remote system):
$ ssh-keygen -D /usr/lib/pkcs11/libtpm2_pkcs11.so ssh-rsa AAAAB3NzaC1yc2EAAAADAQA...
And you can connect to a system by offering the key from the TPM2:
$ ssh -I /usr/lib/pkcs11/libtpm2_pkcs11.so [destination host]
GnuPG in slot 2
Supporting GnuPG is more complicated. You will need
openssl for creating a certificate from the public key and the package
gnupg-pkcs11-scd to interface GnuPG with the TPM2 via the PKCS#11 API. Unfortunately, there is a bug at the moment in the upstream program which doesn't correctly inform the GnuPG agent daemon about the key padding. I prepared a Pull Request for the upstream, until it is merged, you need to use the version from my github repository.
The initial part is similar to the previous case, but then you need to create a certificate from the public key to allow GnuPG to use it:
$ tpm2_ptool init action: Created id: 2 $ tpm2_ptool addtoken --pid=2 --label=gpg_token --sopin=[PIN] --userpin=[PIN] $ tpm2_ptool addkey --algorithm=rsa2048 --label=gpg_token --key-label=gpg_token --userpin=[PIN] $ openssl <<EOF req -engine pkcs11 -new -key pkcs11:model=rls;manufacturer=Nuvoton;serial=0000000000000000;token=gpg_token;type=private;pin-value=[PIN] -keyform engine -out req.pem -text -x509 -subj /CN=[Your Common Name] x509 -engine pkcs11 -signkey pkcs11:model=rls;manufacturer=Nuvoton;serial=0000000000000000;token=gpg_token;type=private;pin-value=[PIN] -keyform engine -in req.pem -out cert.pem EOF $ tpm2_ptool addcert --label gpg_token --key-label gpg_token cert.pem
Afterwards, we need to configure the SCD (smart card driver) for the GnuPG Agent which uses the PKCS#11 to work with the private key. Unfortunately, GnuPG Agent supports only a single SCD, so you cannot use both the TPM2 and a normal smart card at the same time. Also, note that the patched version of gnupg-pkcs11-scd needs to be used until upstream is fixed. Two configuration files need changes:
has_padding providers tpm provider-tpm-library /usr/lib/pkcs11/libtpm2_pkcs11.so
- for the latter, the following debug directives might be useful if you encounter any problems:
verbose debug-all log-file /tmp/scd.log
GnuPG Agent has to be restarted and you need to trigger a reload of the smart card:
$ systemctl --user restart gpg-agent.service $ gpg --card-status
The next step consist in identifying the keygrip for the RSA keypair stored in the TPM2 module. The procedure for creating a keypair should support automatically identifying the smartcard. Unfortunately, this doesn't seem working, so you need to manually list the available keygrips:
$ gpg-agent --server gpg-connect-agent << EOF SCD LEARN EOF ... gnupg-pkcs11-scd: S KEY-FRIEDNLY 01B4D88E8F24441E1773472EFAD1CFE020072CF2 /CN=[The CN you used] on gpg_tokenchan_0 ... ...
Look for a 40 characters long string of letters and numbers after the words
KEY-FRIENDLY. With this information you can proceed with the normal generation of a keypair using option 13 Existing key and entering the keygrip when prompted:
$ gpg --expert --full-generate-key ... Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) (7) DSA (set your own capabilities) (8) RSA (set your own capabilities) (9) ECC and ECC (10) ECC (sign only) (11) ECC (set your own capabilities) (13) Existing key (14) Existing key from card Your selection? 13 Enter the keygrip: 01B4D88E8F24441E1773472EFAD1CFE020072CF2 ...
And that's it, you should have a keypair based on the RSA keys in the TPM2. Please test it by encrypting/decrypting/signing some text.
OpenVPN in slot 3
OpenVPN is similar to GPG because it needs a certificate. However, usually an OpenVPN is deployed by defining a custom CA which signs the certificates. The easiest way for doing it is by using EasyRSA. The set up of a CA is outside the scope of this article, there are many good tutorials to get started.
The initial steps are the same as the GPG case:
$ tpm2_ptool init action: Created id: 3 $ tpm2_ptool addtoken --pid=3 --label=openvpn_token --sopin=[Your PIN] --userpin=[Your PIN] $ tpm2_ptool addkey --algorithm=rsa2048 --label=openvpn_token --key-label=openvpn_token --userpin=[Your PIN] action: add private: CKA_ID: '33313665373362353539386632666162' public: CKA_ID: '33313665373362353539386632666162'
Afterwards, you need to create a Certificate Sign Request with
openssl (mind the missing
x509 compared to the previous invocation of
$ openssl req -engine pkcs11 -new -key 'pkcs11:model=rls;manufacturer=Nuvoton;serial=0000000000000000;token=openvpn_token;type=private;pin-value=[Your PIN]' -keyform engine -out req.csr -subj /CN=chri_tpm2_1
Customize the Common Name according to your needs (I used
chri_tpm2_1 to identify the client in this case)`. Now you can import and sign the CSR with EasyRSA:
$ easyrsa import-req req.csr chri_tpm_1 $ easyrsa sign-req client chri_tpm_1
and the certificate signed by your OpenVPN deployment's CA can be imported back into the TPM2:
$ tpm2_ptool addcert --label openvpn_token --key-label openvpn_token pki/issued/chri_tpm_1.crt action: add cert: CKA_ID: '33313665373362353539386632666162'
You can check with the
openvpn command itself that the certificate is visible:
$ openvpn --show-pkcs11-ids /usr/lib/pkcs11/libtpm2_pkcs11.so Certificate DN: CN=chri_tpm2_1 Serial: 2C6524CBF1845D6A662A3E40285FCEF5 Serialized id: Nuvoton/rls/0000000000000000/openvpn_token/33313665373362353539386632666162
Take note of the
Serialized id because it needs to be used in the configuration file for the client. Here is the configuration that I use,
chri_tpm_1.conf, as an example (look for the options starting with pkcs11):
dev tun client remote [Your OpenVPN server] [Your OpenVPN server port] udp <ca> -----BEGIN CERTIFICATE----- ... CA certificate from EasyRSA -----END CERTIFICATE----- </ca> pkcs11-providers /usr/lib/pkcs11/libtpm2_pkcs11.so pkcs11-id 'Nuvoton/rls/0000000000000000/openvpn_token/33313665373362353539386632666162' nobind persist-key persist-tun remote-cert-tls server key-direction 1 <tls-auth> -----BEGIN OpenVPN Static key V1----- ... MAC Static key, recommended but not mandatory -----END OpenVPN Static key V1----- </tls-auth> verb 3
And here you can see an example of a successful connection:
$ openvpn --config chri_tpm_1.conf ... Mon Jun 1 20:42:22 2020 TLS: Initial packet from [AF_INET][your server IP:port], sid=381bca9d 47aaf107 Mon Jun 1 20:42:22 2020 VERIFY OK: depth=1, CN=[the CA for your openvpn] Mon Jun 1 20:42:22 2020 VERIFY KU OK Mon Jun 1 20:42:22 2020 Validating certificate extended key usage Mon Jun 1 20:42:22 2020 ++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication Mon Jun 1 20:42:22 2020 VERIFY EKU OK Mon Jun 1 20:42:22 2020 VERIFY OK: depth=0, CN=server_1 Enter openvpn_token token Password: **************** Mon Jun 1 20:42:41 2020 Control Channel: TLSv1.3, cipher TLSv1.3 TLS_AES_256_GCM_SHA384, 2048 bit RSA Mon Jun 1 20:42:41 2020 [server_1] Peer Connection Initiated with [AF_INET][your server IP:port] ...
Note that you will be asked for the PIN. If you start
openvpn via some other tool (
NetworManager for example), the right way to provide the PIN is via the management interface (look for the
management-query-passwords option in the
openvpn's manual page). This function should be provided by the network utility starting