Skip to content

wiktor-k/tpm-openpgp

Repository files navigation

TPM for OpenPGP

This crate implements bindings so that TPM chips can be used with OpenPGP applications.

Basic key usage

First, we assume that we’ll use TPM 2 simulator package. If you want to test on real device set TCTI to device:/dev/tpmrm0.

set -e
set -o pipefail

tpm_server &

sleep 5

tpm2_startup -c -T mssim

TCTI=mssim:
PATH=$PATH:./target/debug

# Increase verbosity of commands
export RUST_LOG=info

To generate a number of random bytes using the specified TPM:

draw-bytes --tcti $TCTI
46d2f84712cefc51c8bc124354f7daa0fecd2f6066963ab15b6b50a63248dd90

Creating persistent keys

This crate uses descriptive documents for configuring key properties.

RSA

The following configuration creates RSA-2048 signing key and persists it at the handle 0x01000027. 123 is used as a sample auth value (PIN).

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000027
  algo:
    RSA:
      bits: 2048
  capabilities:
    - sign
  auth: 123

The key description (key.yml) is being read by the create-key binary that persists that key:

create-key -f key.yml

Presence of the key can be checked by using tpm2_getcap handles-persistent command from TSS suite of tools.

And the same file is used to retrieve it again using get-key binary:

get-key -f key.yml

Creating decryption key is just as strightforward:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000018
  algo:
    RSA:
      bits: 2048
  capabilities:
    - decrypt
  auth: 123
create-key -f decryption.yml

EC: NIST-P256

The following configuration creates NIST-P256 signing key and persists it at the handle 0x01000127. 123 is used as a sample auth value (PIN).

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000127
  algo:
    EC:
      curve: NIST-P256
  capabilities:
    - sign
  auth: 123

The key description (key-nist-p256.yml) is being read by the create-key binary that persists that key:

create-key -f key-nist-p256.yml

Presence of the key can be checked by using tpm2_getcap handles-persistent command from TSS suite of tools.

And the same file is used to retrieve it again using get-key binary:

get-key -f key-nist-p256.yml

Creating decryption key is just as strightforward:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000118
  algo:
    EC:
      curve: NIST-P256
  capabilities:
    - decrypt
  auth: 123
create-key -f decryption-nist-p256.yml

EC: NIST-P384

The following configuration creates NIST-P384 signing key and persists it at the handle 0x01000227. 123 is used as a sample auth value (PIN).

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000227
  algo:
    EC:
      curve: NIST-P384
  capabilities:
    - sign
  auth: 123

The key description (key-nist-p384.yml) is being read by the create-key binary that persists that key:

create-key -f key-nist-p384.yml

Presence of the key can be checked by using tpm2_getcap handles-persistent command from TSS suite of tools.

And the same file is used to retrieve it again using get-key binary:

get-key -f key-nist-p384.yml

Creating decryption key is just as strightforward:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000218
  algo:
    EC:
      curve: NIST-P384
  capabilities:
    - decrypt
  auth: 123
create-key -f decryption-nist-p384.yml

Creating non-persistent keys

Non persistent keys allow using unlimited number of keys that never use up TPM memory.

RSA

Keys need to be wrapped using a key parent that itself needs to be persistent:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000028
  algo:
    RSA:
      bits: 2048
  capabilities:
    - decrypt
    - restrict
  auth: 123
create-key -f parent.yml

Then, we can create non-persistent key:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      parent: 0x81000028
  algo:
    RSA:
      bits: 2048
  capabilities:
    - sign
  auth: 123
create-key -f child.yml | tee child-full.yml

Inspecting child-full.yml reveals that the tpm section has been extended with two new properties: private and unique. This is the private key wrapped (encrypted) using the parent key:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      parent: 0x81000028
      private: 002035531cd18d59c7e358b63b1f89ed3b2fdd12176ed5c02f5d68dfbf7f872c65ae00107170ee9bc217b4a7ed59ad11a1387aef195031690b01d6d3acd6b4f63d16006bb33737392dd1ba9753bcf81227e3dffecddc082821994e41c047e325d82ee2c3106e94d5f5bbcd935e6f80e2321f24012a24be73f231c9f6606d927016b3afd73b96df2e3f5a181cfbe436da9cf9bcefa1a1513cb63e8021fb9ad2cc81bce55d9651aa7ed8aeaccba7ba98834d759e9f3b30e21953e65a12742bc253dfbef1e8e158fcc9755acd08e3f4af4183b7b008c4ec0865b48315d346be
      unique:
        RSA:
          bytes: aa79ea1d9800af8b6556562c27dca2be827d7ca1facfd056c4effe79dca366e948e4b0f5253392ce4ea274c84f609e57edfd4848cf10e87e19b22e4bf27fc3560a8e6405a1a339969ce6d00bc4b32e1398be63f59af4c7337b4079817fd231d379dd437cb35910ce13337a6af0877c88ac2f8bc86dd902de3ffd10bdc6c5f284063f95c2c2487942472f34551691fdf8ae0f30a7a188bc73ecb776bef2a959be2cc89b425247030e2a921d505bb71e19100b17028b74e39e673dd1d35603fea424d44913e84c7744128ec2d82853d34062ea9476557a4458c70c05d7efd205ee6b89aa7b0b84daaecbf4075db8fcee2ed622dca2ee8e391e457cc88f3ac39b7d
    algo:
      RSA:
        bits: 2048
    capabilities:
      - sign
    auth: 123

Except for the different configuration this key is perfectly usable in all operations:

get-key -f child-full.yml

EC: NIST-P256

Keys need to be wrapped using a key parent that itself needs to be persistent:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000328
  algo:
    EC:
      curve: NIST-P256
  capabilities:
    - decrypt
    - restrict
  auth: 123
create-key -f parent-nist-p256.yml

Then, we can create non-persistent key:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      parent: 0x81000328
  algo:
    EC:
      curve: NIST-P256
  capabilities:
    - sign
  auth: 123
create-key -f child-nist-p256.yml | tee child-nist-p256-complete.yml

Inspecting child-nist-p256-complete.yml reveals that the tpm section has been extended with two new properties: private and unique. This is the private key wrapped (encrypted) using the parent key:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      parent: 0x81000328
      private: 0020f02ddfa535dfae96031629c001868c0c28358df4d8d8784536a22faa7fea90020010a27a2d5a839d5bece4c50110e189dbf67d76f6f7f68a71301791fe0db8c187b1495621d1b4776fdc2f8b184451d16fd1aacc8261005df7c86058a7fa1609dce2e5a8ec7c631398b2e57e288dbe99059de30cfabdbcd057c53763
      unique:
        EC:
          x: 1f93e6eb830bfb22b6ac482f3c41770a65ab6478c5c0c4d0758b250289defc0b
          y: c211a231b6d5313a8a78af4a621ce7766ca1c000c59e904ed3f1fa38ff54cb72
  algo:
    EC:
      curve: NIST-P256
  capabilities:
    - sign
  auth: 123

Except for the different configuration this key is perfectly usable in all operations:

get-key -f child-nist-p256-complete.yml

EC: NIST-P384

Keys need to be wrapped using a key parent that itself needs to be persistent:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000428
  algo:
    EC:
      curve: NIST-P384
  capabilities:
    - decrypt
    - restrict
  auth: 123
create-key -f parent-nist-p384.yml

Then, we can create non-persistent key:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      parent: 0x81000428
  algo:
    EC:
      curve: NIST-P384
  capabilities:
    - sign
  auth: 123
create-key -f child-nist-p384.yml | tee child-nist-p384-complete.yml

Inspecting child-nist-p384-complete.yml reveals that the tpm section has been extended with two new properties: private and unique. This is the private key wrapped (encrypted) using the parent key:

spec:
  provider:
    tpm:
      tcti: "mssim:"
      parent: 0x81000428
      private: 0020fb1df208021cab7898d4edba2e87966a62e4e95820ad695b8d5af40351361f9100102e21b1075be238d84a9b98471879ec2cc415b4b0309edf82dd6b5a00fa557b9dc06ea752ea36fb4dea4f9a47c5884e1d43e0fcbd40db477e4147264f202145e1c995f411406c82d444a91d67edf69c824737e32057728d9f04193b469b3759bbb033673fafec5db9fa86
      unique:
        EC:
          x: cf159a49527490e60ba5cede361ca82a43d41e6754e8ddea1f57fdba9e05bd49ed62bb982994407801f95c366f85ef43
          y: 322ee52ffde0fe5f85367f801a0cbc5f05a772e6ac86027eed64a02303683b2caa1adb0674645533cb578284ee86eaab
  algo:
    EC:
      curve: NIST-P384
  capabilities:
    - sign
  auth: 123

Except for the different configuration this key is perfectly usable in all operations:

get-key -f child-nist-p384-complete.yml

Importing private keys

It is also possible to import already existing private keys into the TPM.

RSA

spec:
  provider:
    tpm:
      tcti: "mssim:"
  algo:
    RSA:
      bits: 1024
  private:
    rsa:
      prime: f69495352f2ab58db89a0a6ddb060ca0baa5ec190d1d61f0fae32cdfb7516fc9e4968b5c494c057f35dfe69136fe35434f0a3b8979551347c47a357abad0ad0b
      modulus:
        bytes: cd1abae5d734341ad373bae4f9ef46b1cf699d4054c859b9c0f0c811ca4d7b1cb03c66ea655156639b78c5db2c2fea42430f417ab3d4aee5f63b881dd106a3c60105bc46bb18c7a794a17f50392405551f77287e61b5f784354cd351021e1853b0cfd3470d4cc9bd9e39836b83c1be6bb200fef56786406e8cd45f73e4a9f523
      exponent: 65537
  capabilities:
    - sign
  auth: 123

Using these keys is the same as for any other type of key:

get-key -f private-key.yml

EC: NIST-P256

To generate a new P-256 key use the following openssl command:

openssl ecparam -name secp256r1 -genkey -noout -noout | openssl ec -in - -text -noout -conv_form uncompressed

The ec.parameter value should reflect the priv field with all bytes concatenated. The public part: ec.points would be two halves of the pub openssl value (omitting first 04 byte).

spec:
  provider:
    tpm:
      tcti: "mssim:"
  algo:
    EC:
      curve: NIST-P256
  private:
    ec:
      parameter: 98209eea87b063eca85109ef1b8a46c097ab252c59fba12f3899e38fe54ed41b
      points:
        x: 12b2a3f6deb274df7dfa6a9e6e13dea9b9515d590a666966defef5f86d1ecadf
        y: 4795cf10a894226f1778dcd4185db5c4d42a9c10591a2211812ef5b654094f81
  capabilities:
    - sign
  auth: 123

Using these keys is the same as for any other type of key:

get-key -f private-key-nist-p256.yml

EC: NIST-P384

To generate a new P-384 key use the following openssl command:

openssl ecparam -name secp384r1 -genkey -noout -noout | openssl ec -in - -text -noout -conv_form uncompressed

The ec.parameter value should reflect the priv field with all bytes concatenated. The public part: ec.points would be two halves of the pub openssl value (omitting first 04 byte).

spec:
  provider:
    tpm:
      tcti: "mssim:"
  algo:
    EC:
      curve: NIST-P384
  private:
    ec:
      parameter: 595e7774730018cc3942e4b713c2a288b8dbcec147ede1ed3c3760553bc39a7a092db968df4da71267c9586e69e6ffc7
      points:
        x: 88eae33668dfc22f1bec8ca87bef7dab67562b1b1bf10101b5a655212b31356d46963624e11f0b30ffb7bc60f315fb09
        y: 5c1ec2296140c2404a605e6c65b85c10d3e5807feb4d15f674e4318c7887e03408e98a348c413b16ad615484ed84cf2f
  capabilities:
    - sign
  auth: 123

Using these keys is the same as for any other type of key:

get-key -f private-key-nist-p384.yml

Signing digests

RSA

Signing uses raw RSA keys and produces raw PKCS1.5 signatures for now.

Ultimately these raw objects can be wrapped with protocol-specific structures e.g. certificates (for raw RSA keys) or OpenPGP signatures (for raw signatures).

Signing can use any key that has been defined previously:

echo -n foo | openssl dgst -binary -sha256 | sign-digest -f key.yml | xxd

EC: NIST-P256

Signing uses raw elliptic curve keys and produces a concatenation of R and S values.

Ultimately these raw objects can be wrapped with protocol-specific structures e.g. certificates (for raw RSA keys) or OpenPGP signatures (for raw signatures).

Signing can use any key that has been defined previously:

echo -n foo | openssl dgst -binary -sha256 | sign-digest -f key-nist-p256.yml | xxd

EC: NIST-P384

Signing uses raw elliptic curve keys and produces a concatenation of R and S values.

Ultimately these raw objects can be wrapped with protocol-specific structures e.g. certificates (for raw RSA keys) or OpenPGP signatures (for raw signatures).

Signing can use any key that has been defined previously:

echo -n foo | openssl dgst -binary -sha256 | sign-digest -f key-nist-p384.yml | xxd

Decryption

RSA

Encryption and decryption works similarily to signing. The plaintext is being passed as standard input to encrypt-raw commmand and it outputs the raw cipher text. decrypt-raw works in the other direction consuming the cipher text and producing the plain text.

Both of these take the key defintion as a sole argument.

echo this is a sample encryption message | encrypt-raw -f decryption.yml > encrypted
decrypt-raw -f decryption.yml < encrypted

ECDH

Encryption and decryption with EC keys works a little bit differently. EC key is used to generate two points: one is a public point that will be shared with the other party, the other is used as a symmetric key for encryption of the actual data.

First, we need an EC key with decrypt capability. The key cannot be marked as restrict as that will prevent decryption.

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000147
  algo:
    EC:
      curve: NIST-P256
  capabilities:
    - decrypt
  auth: 123

Then we generate two points: public point and the shared secret:

create-key -f key-nist-p256-decryption.yml

# create a shared secret and public point
ecdh-key-gen -f key-nist-p256-decryption.yml --public-point public.bin > shared-secret.bin

echo "Public point:"
xxd public.bin

echo "Shared secret:"
xxd shared-secret.bin

And we use the shared secret to encrypt the message. Shared secret is removed as it is no longer necessary. The public point, along with the encrypted message, is transferred.

# encrypt the message using shared secret
echo this is a sample encryption message | openssl aes-256-cbc -e -kfile shared-secret.bin > encrypted

# remove shared secret, move public point and encrypted message to the other party
rm shared-secret.bin

The decrypting party first recovers the shared secret using the public point. Then the shared secret is passed to symmetric algorithm as a key (here using OpenSSL):

# recover shared secret using private key and the public point
ecdh-recover -f key-nist-p256-decryption.yml --public-point public.bin > shared-secret.bin

# decrypt the message
openssl aes-256-cbc -d -kfile shared-secret.bin -in encrypted

This concludes our basic TPM usage section. TPM will be cleared discarding all keys:

tpm2_clear

Key duplication

Key duplication allows secure private key material transfer from one machine (e.g. offline computer) to the other (e.g. online computer).

The main benefit is that the online computer never sees private key bits in plain. They are encrypted to the storage key that is stored in the TPM chip. The encrypted private key is decrypted by the TPM during import.

On online laptop, export the TPM key that will serve as parent for the imported key. This parent key needs to have decrypt and restrict capabilities.

spec:
  provider:
    tpm:
      tcti: "mssim:"
      handle: 0x81000027
  algo:
    RSA:
      bits: 2048
  capabilities:
    - decrypt
    - restrict
  auth: 123
create-key -f duplication-parent.yml

Retrieve the key’s public key bits and transfer them to the offline computer:

get-key -f duplication-parent.yml | tee duplication-parent-full.yml

Now, taking the private key and wrapping it with the parent’s key:

wrap --parent duplication-parent-full.yml  -f private-key.yml | tee key-to-import.yml

The duplicated key can then be imported:

create-key -f key-to-import.yml | tee duplicated-key.yml

Note that the imported key has wrapped key set. Import procedure checks the integrity of the key and if the encrypted seed can be successfully imported the wrapped key is removed and a regular private value is being inserted.

The duplicated key can be inspected for public key:

get-key -f duplicated-key.yml

It also works the same way as any other key:

echo -n foo | openssl dgst -binary -sha256 | sign-digest -f duplicated-key.yml | xxd

Work plan

Work on this project is being sponsored by NLnet. See https://nlnet.nl/project/Sequoia-TPM/ for details.

Signing and decryption using RSA keys [5/5]

  • [X] Creating new RSA keys and persisting them in TPM memory
  • [X] Using non-persistent RSA keys (that don’t use up TPM memory)
  • [X] Importing RSA private keys to TPM (for already existing keys)
  • [X] Signing using RSA keys in the TPM
  • [X] Decryption using RSA keys in the TPM

Support for Elliptic Curve algorithms [5/5]

  • [X] Creating new EC keys and persisting them in TPM memory
  • [X] Using non-persistent EC keys (that don’t use up TPM memory)
  • [X] Importing EC private keys to TPM (for already existing keys)
  • [X] Signing using EC keys in the TPM
  • [X] Decryption using EC keys in the TPM

Key migration support [4/4]

  • [X] Export of TPM encryption key
  • [X] Wrapping user’s private key using TPM encryption key
  • [X] Import of the wrapper private key to the TPM chip
  • [X] PR to the upstream rust-tss-esapi crate

Design and implementation of private key store crate [/]

  • [ ] Implementation of Sequoia’s Decryptor and Signer traits for TPM keys
  • [ ] API for managing TPM keys
  • [ ] API for key migration

Test harness using a TPM simulator [/]

  • [ ] Integration tests for creating, importing keys
  • [ ] Test cases for encryption (using Sequoia) and decryption (using TPM crate)
  • [ ] Tests for key migration

Extending Sequoia’s CLI to support private key store [/]

  • [ ] Extension to the CLI to allow specifying the location of the private key store
  • [ ] Modification to the sourec code not to rely on software private keys

Documentation for tools and the API [/]

  • [ ] Making sure all functions and items are documented
  • [ ] Including README and end-user documentation on how to use the project
  • [ ] Adding best practices document