This crate implements bindings so that TPM chips can be used with OpenPGP applications.
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
This crate uses descriptive documents for configuring key properties.
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
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
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
Non persistent keys allow using unlimited number of keys that never use up TPM memory.
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
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
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
It is also possible to import already existing private keys into the TPM.
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
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
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 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
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
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
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
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 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 on this project is being sponsored by NLnet. See https://nlnet.nl/project/Sequoia-TPM/ for details.
- [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
- [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
- [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
- [ ] Implementation of Sequoia’s Decryptor and Signer traits for TPM keys
- [ ] API for managing TPM keys
- [ ] API for key migration
- [ ] Integration tests for creating, importing keys
- [ ] Test cases for encryption (using Sequoia) and decryption (using TPM crate)
- [ ] Tests for key migration
- [ ] 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
- [ ] Making sure all functions and items are documented
- [ ] Including README and end-user documentation on how to use the project
- [ ] Adding best practices document