Linux userland integration

This document gives pointers on how particular features of OP-TEE may be used from the Linux userland in typical application scenarios.

PKCS#11 driver

A common use-case is the integration of OP-TEE to securely store asymmetric keys inside the secure enclave. For example, when using TLS with client certificates, the corresponding private keys would reside securely within OP-TEE. If this client certificate is then used from within userspace, the corresponding cryptographic primitives are relayed to OP-TEE which establishes the connection using the requested client certificate on behalf of the application. However, the key itself never leaves secure storage (this is where it is created and resides).

The way this is done is via PKCS#11 (aka Cryptoki API). PKCS#11 specifies a number of standard calls to relay cryptographic requests (such as a signing operation) to a third party module. Such a module may be a smart card or, in the case of OP-TEE, it is a software PKCS#11 trusted application that appears to the userland as one. This trusted application is accessed using a shared object (dynamic library) which serves as the “glue” to translate cryptographic requests into OP-TEE calls. This shared object is libckteec.so which is part of the OP-TEE client tools.

Once OP-TEE has been compiled with the PKCS#11 TA, the client tools shared object has been built and the OP-TEE supplicant has been started, we can use pkcs11-tool of the OpenSC project to initiate first communication with the emulated smart card. In the following, we assume that libckteec.so has been installed in /usr/lib/libckteec.so. For simplicity reasons, we define an alias to call pkcs11-tool using the appropriate PKCS#11 module.

# alias p11="pkcs11-tool --module /usr/lib/libckteec.so"
# p11 --show-info
Cryptoki version 2.40
Manufacturer     Linaro
Library          OP-TEE PKCS11 Cryptoki library (ver 0.1)
Using slot 0 with a present token (0x0)

Hint

When testing OP-TEE under QEMU, OpenSC should be built by default as well and the pkcs11-tool should be available without modifications to the configuration. It can be explicitly requested by using the

make BR2_PACKAGE_OPENSC=y

build parameter when compiling OP-TEE.

This tells us the library code is already working. We can now display the different “slots”. You can think of them as different “card readers” for virtual smart cards. In a typical use case, only one slot is used for a single smart card.

# p11 --list-slots
Available slots:
Slot 0 (0x0): OP-TEE PKCS11 TA - TEE UUID 94e9ab89-4c43-56ea-8b35-45dc07226830
  token state:   uninitialized
Slot 1 (0x1): OP-TEE PKCS11 TA - TEE UUID 94e9ab89-4c43-56ea-8b35-45dc07226830
  token state:   uninitialized
Slot 2 (0x2): OP-TEE PKCS11 TA - TEE UUID 94e9ab89-4c43-56ea-8b35-45dc07226830
  token state:   uninitialized

Observe that the connection to the TA is also successfully working and it is showing three inserted (but “empty”, uninitialized) smart cards/tokens. Before we are able to create keys on these tokens, we need to initialize them with a SO-PIN and PIN. The SO-PIN is the “super pin”, while the PIN is the “user pin”. The concept is likely familiar to you from the SIM card of your phone, where the PUK acts as the “super pin”.

First, we initialize the SO-PIN of slot 0 and name our token “mytoken”:

# p11 --init-token --label mytoken --so-pin 1234567890
Using slot 0 with a present token (0x0)
Token successfully initialized

We have successfully initialized the SO-PIN to “1234567890”. Now we “log in” into the token using that SO-PIN and, using the SO-PIN authorization, initialize the PIN of the token to “12345”:

# p11 --label mytoken --login --so-pin 1234567890 --init-pin --pin 12345
Using slot 0 with a present token (0x0)
User PIN successfully initialized

We can now verify that the token has been successfully initialized:

# p11 --list-slots
Available slots:
Slot 0 (0x0): OP-TEE PKCS11 TA - TEE UUID 94e9ab89-4c43-56ea-8b35-45dc07226830
  token label        : mytoken
  token manufacturer : Linaro
  token model        : OP-TEE TA
  token flags        : login required, rng, token initialized, PIN initialized
  hardware version   : 0.0
  firmware version   : 0.1
  serial num         : 0000000000000000
  pin min/max        : 4/128
Slot 1 (0x1): OP-TEE PKCS11 TA - TEE UUID 94e9ab89-4c43-56ea-8b35-45dc07226830
  token state:   uninitialized
Slot 2 (0x2): OP-TEE PKCS11 TA - TEE UUID 94e9ab89-4c43-56ea-8b35-45dc07226830
  token state:   uninitialized

Now we have a fully initialized token but it still contains no keys. To list what cryptographic primitives the particular OP-TEE version offers, you can query the supported mechanisms:

# p11 --list-mechanisms
Using slot 0 with a present token (0x0)
Supported mechanisms:
  SHA224-RSA-PKCS-PSS, keySize={256,4096}, sign, verify
  SHA224-RSA-PKCS, keySize={256,4096}, sign, verify
  SHA512-RSA-PKCS-PSS, keySize={256,4096}, sign, verify
  SHA384-RSA-PKCS-PSS, keySize={256,4096}, sign, verify
  SHA256-RSA-PKCS-PSS, keySize={256,4096}, sign, verify
  SHA512-RSA-PKCS, keySize={256,4096}, sign, verify
  SHA384-RSA-PKCS, keySize={256,4096}, sign, verify
  SHA256-RSA-PKCS, keySize={256,4096}, sign, verify
  SHA1-RSA-PKCS-PSS, keySize={256,4096}, sign, verify
  RSA-PKCS-OAEP, keySize={256,4096}, encrypt, decrypt
  SHA1-RSA-PKCS, keySize={256,4096}, sign, verify
  MD5-RSA-PKCS, keySize={256,4096}, sign, verify
  RSA-PKCS-PSS, sign, verify
  RSA-PKCS, keySize={256,4096}, encrypt, decrypt, sign, verify
  RSA-PKCS-KEY-PAIR-GEN, keySize={256,4096}, generate_key_pair
  ECDSA-SHA512, keySize={160,521}, sign, verify
  ECDSA-SHA384, keySize={160,521}, sign, verify
  ECDSA-SHA256, keySize={160,521}, sign, verify
  ECDSA-SHA224, keySize={160,521}, sign, verify
  ECDSA-SHA1, keySize={160,521}, sign, verify
  ECDSA, keySize={160,521}, sign, verify
  ECDSA-KEY-PAIR-GEN, keySize={160,521}, generate_key_pair
  mechtype-0x272, keySize={32,128}, sign, verify
  mechtype-0x262, keySize={32,128}, sign, verify
  mechtype-0x252, keySize={24,128}, sign, verify
  mechtype-0x257, keySize={14,64}, sign, verify
  SHA-1-HMAC-GENERAL, keySize={10,64}, sign, verify
  MD5-HMAC-GENERAL, keySize={8,64}, sign, verify
  SHA512-HMAC, keySize={32,128}, sign, verify
  SHA384-HMAC, keySize={32,128}, sign, verify
  SHA256-HMAC, keySize={24,128}, sign, verify
  SHA224-HMAC, keySize={14,64}, sign, verify
  SHA-1-HMAC, keySize={10,64}, sign, verify
  MD5-HMAC, keySize={8,64}, sign, verify
  SHA512, digest
  SHA384, digest
  SHA256, digest
  SHA224, digest
  SHA-1, digest
  MD5, digest
  GENERIC-SECRET-KEY-GEN, keySize={1,4096}, generate
  AES-KEY-GEN, keySize={16,32}, generate
  AES-CBC-ENCRYPT-DATA, derive
  AES-ECB-ENCRYPT-DATA, derive
  mechtype-0x108B, keySize={16,32}, sign, verify
  AES-CMAC, keySize={16,32}, sign, verify
  mechtype-0x1089, keySize={16,32}, encrypt, decrypt
  AES-CTR, keySize={16,32}, encrypt, decrypt
  AES-CBC-PAD, keySize={16,32}, encrypt, decrypt
  AES-CBC, keySize={16,32}, encrypt, decrypt, wrap, unwrap
  AES-ECB, keySize={16,32}, encrypt, decrypt, wrap, unwrap

In our case, we would want to create an elliptic curve keypair on P-256 (aka secp256r1 or prime256v1). As you can see, this is supported (“ECDSA-KEY-PAIR-GEN” supports between 160 and 521 bit curves).

# p11 -l --pin 12345 --keypairgen --key-type EC:prime256v1 --label mykey
Using slot 0 with a present token (0x0)
Key pair generated:
Private Key Object; EC
  label:      mykey
  Usage:      sign, derive
  Access:     sensitive, always sensitive, never extractable, local
Public Key Object; EC  EC_POINT 256 bits
  EC_POINT:   044104e3f89bd32ac8101ba675815fbaf34c4f34bb7bb2d233589983bad934cfa09795d56811747778d22b94e245028d3af6aff9e6abbbdb3a75fe1433182c605868c7
  EC_PARAMS:  06082a8648ce3d030107
  label:      mykey
  Usage:      verify, derive
  Access:     local

You can see the public key, which is a point on the elliptic curve. The byte 04 at byte offset 2 indicates that this point is represented in uncompressed affine representation, i.e., X and Y coordinates follow that byte directly. This format is not ideal to interface common libraries, however. Especially when using PKI with X.509 certificates, we typically want a PEM-formatted CSR to be able to create a certificate from.

For this, we create a small configuration file for OpenSSL and call it optee_hsm.conf. It references a library of libp11 which acts as a driver that enables OpenSSL to interface with a PKCS#11 library.

openssl_conf = openssl_conf

[openssl_conf]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/engines-1.1/libpkcs11.so
MODULE_PATH = /usr/lib/libckteec.so
PIN = 12345

[req]
distinguished_name = req_distinguished_name

[req_distinguished_name]

Hint

When testing OP-TEE under QEMU, libp11 is not compiled by default. For easy access to this library, you can build OP-TEE using the command

make BR2_PACKAGE_OPENSC=y BR2_PACKAGE_LIBOPENSSL=y BR2_PACKAGE_LIBOPENSSL_BIN=y BR2_PACKAGE_LIBP11=y

This will ensure that OpenSC (for the command line utility pkcs11-tool), OpenSSL, and libp11 are all built and installed in the QEMU environment. Note that in that environment, libpkcs11.so will reside at /usr/lib/engines-1.1/libpkcs11.so.

Then, we can ask OpenSSL to create a CSR from the key we have previously created:

# OPENSSL_CONF=optee_hsm.conf openssl req -new -engine pkcs11 -keyform engine -key label_mykey -subj "/CN=My CSR" -out mykey_csr.pem
engine "pkcs11" set.

We can then inspect said CSR:

$ openssl req -in mykey_csr.pem -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: CN = My CSR
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:e3:f8:9b:d3:2a:c8:10:1b:a6:75:81:5f:ba:f3:
                    4c:4f:34:bb:7b:b2:d2:33:58:99:83:ba:d9:34:cf:
                    a0:97:95:d5:68:11:74:77:78:d2:2b:94:e2:45:02:
                    8d:3a:f6:af:f9:e6:ab:bb:db:3a:75:fe:14:33:18:
                    2c:60:58:68:c7
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        Attributes:
            a0:00
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:20:61:7e:05:30:cf:4d:d0:93:22:78:9e:45:cf:af:
         3c:83:bb:04:c4:f0:81:f6:9a:5c:97:cd:ac:1e:94:cd:17:1b:
         02:21:00:e7:7f:88:1d:4f:56:b8:e2:87:be:76:de:28:b3:92:
         68:a7:16:3a:56:af:79:2f:98:bd:fd:6d:b3:82:e1:15:6c

Note that the public key matches exactly that which we have previously created (04 e3 f8...). This CSR could then be signed by a CA. For simplicity purposes, we can also use a self-signed certificate and sign with our own OP-TEE contained key:

# OPENSSL_CONF=optee_hsm.conf openssl req -new -engine pkcs11 -keyform engine -key label_mykey -subj "/CN=My CSR" -x509 -out mykey_selfsigned_cert.pem
engine "pkcs11" set.

Again we can review this self-signed certificate:

$ openssl x509 -in mykey_selfsigned_cert.pem -text
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            3f:8f:c8:c0:de:a8:75:ca:9d:62:79:31:c2:6c:48:f4:fd:50:22:1d
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = My CSR
        Validity
            Not Before: Mar 22 20:19:15 2023 GMT
            Not After : Apr 21 20:19:15 2023 GMT
        Subject: CN = My CSR
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:e3:f8:9b:d3:2a:c8:10:1b:a6:75:81:5f:ba:f3:
                    4c:4f:34:bb:7b:b2:d2:33:58:99:83:ba:d9:34:cf:
                    a0:97:95:d5:68:11:74:77:78:d2:2b:94:e2:45:02:
                    8d:3a:f6:af:f9:e6:ab:bb:db:3a:75:fe:14:33:18:
                    2c:60:58:68:c7
                ASN1 OID: prime256v1
                NIST CURVE: P-256
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:20:4a:9d:63:f2:e0:12:4b:46:eb:eb:62:34:9e:86:
         3d:d4:c8:cf:5f:c0:44:fe:8b:71:a0:b8:fa:41:d9:0b:60:3a:
         02:21:00:fb:c2:b3:0a:7b:54:e9:bb:66:7b:8e:f7:11:52:81:
         69:81:a6:cc:d0:bf:a2:7c:f7:2a:67:db:ab:f1:f3:2c:9f
-----BEGIN CERTIFICATE-----
MIIBHDCBwwIUP4/IwN6odcqdYnkxwmxI9P1QIh0wCgYIKoZIzj0EAwIwETEPMA0G
A1UEAwwGTXkgQ1NSMB4XDTIzMDMyMjIwMTkxNVoXDTIzMDQyMTIwMTkxNVowETEP
MA0GA1UEAwwGTXkgQ1NSMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4/ib0yrI
EBumdYFfuvNMTzS7e7LSM1iZg7rZNM+gl5XVaBF0d3jSK5TiRQKNOvav+earu9s6
df4UMxgsYFhoxzAKBggqhkjOPQQDAgNIADBFAiBKnWPy4BJLRuvrYjSehj3UyM9f
wET+i3GguPpB2QtgOgIhAPvCswp7VOm7ZnuO9xFSgWmBpszQv6J89ypn26vx8yyf
-----END CERTIFICATE-----

To test our self-signed certificate as a client certificate, we first need to initialize a TLS server. This can either be done on a remote machine or locally. For the server we will again use a self-signed certificate (but simply store the corresponding private key in a file).

$ openssl ecparam -genkey -name prime256v1 -out server_key.pem
$ openssl req -new -x509 -key server_key.pem -subj '/CN=Server' -out server_cert.pem
$ openssl s_server -accept 9876 -cert server_cert.pem -key server_key.pem -www -Verify 1
verify depth is 1, must return a certificate
Using default temp DH parameters
ACCEPT

This starts a HTTPS server which listens at port 9876 and requires a TLS client certificate. We can validate that the connection to the server is refused if no client certificate is provided. Assume that 192.168.178.34 is the IPv4 address of the server:

$ curl -k https://192.168.178.34:9876
curl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0

Now on our OP-TEE device we can use OpenSSL to establish a connection using our OP-TEE stored client certificate:

# OPENSSL_CONF=optee_hsm.conf openssl s_client -engine pkcs11 -connect 192.168.178.34:9876 -cert mykey_selfsigned_cert.pem -keyform engine -key label_mykey
engine "pkcs11" set.
CONNECTED(00000004)
Can't use SSL_get_servername
depth=0 CN = Server
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = Server
verify return:1
---
Certificate chain
 0 s:CN = Server
   i:CN = Server
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIBeDCCAR2gAwIBAgIUDnUzOcNS9AgeJhvVmp73wF5DwxQwCgYIKoZIzj0EAwIw
ETEPMA0GA1UEAwwGU2VydmVyMB4XDTIzMDMyMjIwMjMwMloXDTIzMDQyMTIwMjMw
MlowETEPMA0GA1UEAwwGU2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
qnCvLjLa1XWBtY1OQjaHa60re5vnZ2WY555XSsFCe2RoF7wGBDDrdXKkQz9Vy0t4
d5OC6VMcFhia967nGa5zPqNTMFEwHQYDVR0OBBYEFBKTMLG057a/a2exmeF7dHVH
85D0MB8GA1UdIwQYMBaAFBKTMLG057a/a2exmeF7dHVH85D0MA8GA1UdEwEB/wQF
MAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAIeKwlghSkhA8zvpXsl9y6WSCXo9fRzt
DSl6myUsgac/AiEAhipKSjVQAvJAqXIecmMylqjY79XVzrbxKWYjsL1XdLw=
-----END CERTIFICATE-----
subject=CN = Server

issuer=CN = Server

---
No client certificate CA names sent
Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512:ECDSA+SHA224:RSA+SHA224
Shared Requested Signature Algorithms: ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512:Ed25519:Ed448:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA384:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 817 bytes and written 797 bytes
Verification error: self signed certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 18 (self signed certificate)
[...]

When connected, you can type “GET /” and press return to get a HTML response back from the HTTPS server, which will echo your client certificate inside a HTML page.