tink-go-tpm

module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2024 License: Apache-2.0

README

Tink Go Trusted Platform Module extension

Envelope encryption where the root Key Encryption Key (KEK) is bound to a Trusted Platform Module (TPM)

This library Envelope Encryption using Tink in a mode where the KEK wrapping key is encoded inside a Trusted Platform Module (TPM).

In other words, you must have access to the TPM that encrypted the data to decrypt the wrapping key

There are two modes to using this library:

  • Seal/Unseal

    To use this, you must have access to the same TPM for both encrypting and decrypting.

    When you encrypt data, it can ONLY get decrypted by that SAME TPM.

    see Seal/Unseal using a TPM's Storage Root Key (SRK)

  • Remote encryption

    To use this, you do not need a TPM to encrypt but you DO need the target TPM the encryption was tied to in order to decrypt.

    This mode utilizes a TPM Endorsement Public Key (EKPub) to wrap the encryption key which can ONLY get decrypted by the TPM that owns the EKPub

    see Remote Sealed TPM Import

note, the encryption used by the TPM in the above two modes is not aead though we use TINK AEAD constructs to do operations.

For full end-to-end, see examples/ folder

This library is NOT supported by google. its not reviewed; caveat emptor.


Essentially, the TINK Keykeyset is encrypted like so:

{
	"encryptedKeyset": "AAAFNAqgAQAgEFHs7/r2j13qJXdjdeJcKAgJRDBPa4TuTKwS....",
	"keysetInfo": {
		"primaryKeyId": 1807199571,
		"keyInfo": [
			{
				"typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
				"status": "ENABLED",
				"keyId": 1807199571,
				"outputPrefixType": "TINK"
			}
		]
	}
}

Usage Seal

To use, simply initialize a Tink client as shown below, specify a path to the TPM and optionally the PCR values to bind against

import (
	tpmkms "github.com/salrashid123/tink-go-tpm/integration/tpm"
	tpmkmspb "github.com/salrashid123/tink-go-tpm/proto"
	"github.com/tink-crypto/tink-go/v2/aead"

	"github.com/tink-crypto/tink-go/v2/keyset"
)

// create the client
client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)

// construct a keyURI  
ta, err := client.GetKeyURI(&tpmkmspb.TPMWrapped{
		Id: "123232",  // any identifier
		KeyType: &tpmkmspb.TPMWrapped_Sealed{
			Sealed: &tpmkmspb.Sealed{},
		},
	})

// save the url somewhere (you'll need this)
log.Printf("export TINK_URL=\"%s\"\n", ta)

// generate wrapping AEAD w/ KMS
kekAEAD, err := client.GetAEAD(ta)

// Get the KMS envelope AEAD primitive.
ra := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), kekAEAD)

// create a new key of any type
kh1, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())

// encrypt  the new key with the TPM/KMS wrapper
buf := new(bytes.Buffer)
w := keyset.NewJSONWriter(buf)
err := kh1.Write(w, ra)

// remember to write the keyset to file
var keysetBytes bytes.Buffer
err = json.Indent(&keysetBytes, buf.Bytes(), "", "\t")
log.Printf("Tink Keyset: %s\n", string(keysetBytes.Bytes()))
err = os.WriteFile(*keyset_file, keysetBytes.Bytes(), 0644)

// get AEAD off of the keyhandle
ekh, err := aead.New(kh1)

// encrypt
encryptedBlob, err := ekh.Encrypt([]byte(*data_to_encrypt), []byte("associated data"))

log.Printf("Cipher text: %s", base64.StdEncoding.EncodeToString(encryptedBlob))

Do decrypt,

// construct the client usign the TINK KEY_URI
client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)

a, err := client.GetAEAD(*tink_url)

// Get the KMS envelope AEAD primitive.
ra := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), a)

// read the keyset
keysetBytes, err := os.ReadFile(*keyset_file)
r := keyset.NewJSONReader(keysetBytes)

// use the KMS/TPM envelope to decrypt the key
kh, err := keyset.Read(r, ra)

// get the key
ead, err := aead.New(kh)

// read the encrypteddata
encryptedBlob, err := os.ReadFile(*encrypted_blob)

// decrypt
pt, err := eaead.Decrypt(encryptedBlob, []byte("associated data"))
log.Printf("Plain text: %s", pt)

Usage Import

To use this mode, you must first acquire the Endorsement Public Key (ekPub).

The ekPub can be extracted from the Endorsement Certificate on a TPM or on GCE, via an API.

To use tpm2_tools on the target machine

$ tpm2_getekcertificate -X -o ECcert.bin

$ openssl x509 -in ECcert.bin -inform DER -noout -text

$ openssl  x509 -pubkey -noout -in ECcert.bin  -inform DER 

# or

$ tpm2_createek -c primary.ctx -G rsa -u ek.pub -Q

$ tpm2_readpublic -c primary.ctx -o ek.pem -f PEM -Q

Copy the public key (ek to a remote host and save as encrypting_public_key)

On a remote machine:

import (
	tpmkms "github.com/salrashid123/tink-go-tpm/integration/tpm"
	tpmkmspb "github.com/salrashid123/tink-go-tpm/proto"
	"github.com/tink-crypto/tink-go/v2/aead"
	"github.com/tink-crypto/tink-go/v2/keyset"
)

// create a client (the tpm isn't used here and the path is ignored)
client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)

// read the EkPub pem file
b, err := os.ReadFile(*encrypting_public_key)

// get the tink uri
ta, err := client.GetKeyURI(&tpmkmspb.TPMWrapped{
	Id: "abcd",
	KeyType: &tpmkmspb.TPMWrapped_Imported{
		Imported: &tpmkmspb.Imported{
			PublicKey: b,
		},
	},
})

log.Printf("export TINK_URL=\"%s\"\n", ta)

// get the KEK
kekAEAD, err := client.GetAEAD(ta)

// the template here isn't even used 
ra := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMSIVKeyTemplate(), kekAEAD)

// now encrypt
// create a new key of any time
kh1, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())

// encrypt  the new key with the TPM/KMS wrapper
buf := new(bytes.Buffer)
w := keyset.NewJSONWriter(buf)
err := kh1.Write(w, ra)

// you don't really need to write the keyset since we know to use the EKPub
var keysetBytes bytes.Buffer
err = json.Indent(&keysetBytes, buf.Bytes(), "", "\t")
log.Printf("Tink Keyset: %s\n", string(keysetBytes.Bytes()))
err = os.WriteFile(*keyset_file, keysetBytes.Bytes(), 0644)

// get AEAD off of the keyhandle
ekh, err := aead.New(kh1)

// encrypt
encryptedBlob, err := ekh.Encrypt([]byte(*data_to_encrypt), []byte("associated data"))

log.Printf("Cipher text: %s", base64.StdEncoding.EncodeToString(encryptedBlob))

At this point, copy encrypted_blob and keyset.json to the machine with the TPM

to decrypt

// create a client
client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)

// get the aead given the tink+url
a, err := client.GetAEAD(*tink_url)

ra := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMSIVKeyTemplate(), a)

// read the keyset
keysetBytes, err := os.ReadFile(*keyset_file)
r := keyset.NewJSONReader(keysetBytes)

// use the KMS/TPM envelope to decrypt the key
kh, err := keyset.Read(r, ra)

// get the key
ead, err := aead.New(kh)

// read the encrypteddata
encryptedBlob, err := os.ReadFile(*encrypted_blob)

// decrypt
pt, err := eaead.Decrypt(encryptedBlob, []byte("associated data"))
log.Printf("Plain text: %s", pt)

PCR Binding

Both seal and import allows you to encrypt data such that it is bound to a specific PCR value

For example, if in any of the examples you extend the PCR bank 23 to the next value:

$ tpm2_pcrread sha256:23 
  sha256:
    23: 0xF5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B
$  tpm2_pcrextend 23:sha256=0xF5A5FD42D16A20302798EF6ED309979B43003D2320D9F0E8EA9831A92759FB4B

$ tpm2_pcrread sha256:23 
  sha256:
    23: 0xDB56114E00FDD4C1F85C892BF35AC9A89289AAECB1EBD0A96CDE606A748B5D71

you will not be able to decrypt or unseal:

2024/04/12 20:57:15 Could not create TINK keyHandle keyset.Handle: 
     decryption failed: unseal failed: session 1, error code 0x1d : a policy check failed
EKPub on GCP

As mentioned, you can extract the ekPub via an API on GCP

gcloud compute  instances create   tpm-device  \
      --zone=us-central1-a --machine-type=n1-standard-1    --tags tpm  \
	   --no-service-account  --no-scopes  \
	   --shielded-secure-boot --shielded-vtpm --shielded-integrity-monitoring \
	   --image-family=debian-11 --image-project=debian-cloud

gcloud compute instances get-shielded-identity  tpm-device --format="value(encryptionKey.ekPub)" > /tmp/ek.pem
TINK URL Format

The specific TINK URLs is just a json protocolbuffer which includes specifications for the key that was used and the PCR banks to apply

for example, for

  • Seal:
{
  "id": "abcd",
  "Sealed": {
    "tpmpublic": "AAEACwADBHIAAAAGAIAAQwAQCAAAAAAAAQDTaYD4pDJLNAXm6d1JkpCwNuNFLkLM6TtdBzTgeyQS6BIsCkbDU1ZZKvKZoDBNTDoOGKIaUEo/4upk3ASTvciOHWm8twaYy7tyLMhabJsYkxtMVDdiJGSY1f1XHETdyCJZcPEYrVt4aeiOkUMla4T9ghCI3tpSs2jLP1KEbGX7kD21VfOu0+Wn+VSWEHAUTZ4GR33GQvSYh6hxb5UKaonUPUply50X2omS1LeYl+OR33JVpdQSkT7NiZi/05ePVq79o3DQ1eQJw8UhghjiZyXm/TdpSv4tJ1RAeb2QOr4Y5o9yje8m70fFPguG5o5bSvWzIj0dbMQQssPX57opZwKB",
    "pcrvalues": [
      {
        "pcr": 23,
        "value": "ZjVhNWZkNDJkMTZhMjAzMDI3OThlZjZlZDMwOTk3OWI0MzAwM2QyMzIwZDlmMGU4ZWE5ODMxYTkyNzU5ZmI0Yg=="
      }
    ]
  }
}
  • Import:
{
  "id": "abcd",
  "Imported": {
    "publicKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF3bG9sQ0lPemFVOGpsekd6T3k0SQp6elcrNmEzclNVUW8vSnluUW1NRGs0WGNIU3Jaa3FVSVN6d2FlRWNRUXNGa29NRjl4RmtNc3R0R0doQ0JJTHpGCm5rYlY3bjYvaVNMTjlCQ2YzNWVNYnRmRzF1SFpxbTNaZGdNQVpiU3lERTNzeGhIRTIzSWFOTW5zVU9UcnRwMFkKYUNBRis4N3NrelVlTjN1WHg5RE5Ed2dIdEZTZ3hvTGd5cnJuNGJsekswV3RvVkhIb3dwOXVFclZaVExYekk0RQorYlVwRXFtU1htdVJPQUpGYmtmZWtERkFpSk1oeU1JeWJwbFE0eXMxVFI1ZlZmTHMzUFhQRExGakJjM1NPV3lrCmhLbTZ0Q1dLcWR6a0tXMkp2SG1qcGVjZXpZdWF5bmc3aHJFdDFtREJYbGxSRXB3VlUrMHpMb1RBRUdQTG9EQzIKYlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCgo=",
    "pcrvalues": [
      {
        "pcr": 23,
        "value": "ZjVhNWZkNDJkMTZhMjAzMDI3OThlZjZlZDMwOTk3OWI0MzAwM2QyMzIwZDlmMGU4ZWE5ODMxYTkyNzU5ZmI0Yg=="
      }
    ]
  }
}
/usr/local/bin/protoc -I ./ --include_imports  \
   --experimental_allow_proto3_optional --include_source_info \
    --descriptor_set_out=proto/tinktpm.proto.pb --go_out=paths=source_relative:. proto/tinktpm.proto

Key Usage and AEAD

It is critical to point out the encryption used here by the TPM is IS NOT AEAD and does not accept additional data: all this does is uses TPM constructs to encrypt the key primitive that actually encrypts your data.

Ofcourse the DEK you choose does follow AEAD constructs and the additional data applies.

You should not use the AEAD derived from the TPM directly but use it to create a NewKMSEnvelopeAEAD2 and then use that to wrap the DEK. The two methods of encryption with KMS backend is described here:

The recommended way is to "encrypt the keyset with KMS" as described above but to use the KmsEnvelopeAead primitive from the TPM.

do this (preferred):

// create the client
client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)
// construct a keyURI  
ta, err := client.GetKeyURI(&tpmkmspb.TPMWrapped{
		Id: "123232",  // any identifier
		KeyType: &tpmkmspb.TPMWrapped_Sealed{},
	})
// get kms remote
kekAEAD, err := gcpClient.GetAEAD(ta)
ra := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), kekAEAD)

// encrypt a new key with the remote and save it (you need to save the keyset in this case)
kh1, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())
buf := new(bytes.Buffer)
w := keyset.NewJSONWriter(buf)
err = kh1.Write(w, kekAEAD)

// encrypt the data
ekh, err := aead.New(kh1)
ct, err := ekh.Encrypt([]byte(*data_to_encrypt), []byte("associated data"))
log.Printf("Cipher text: %s", base64.StdEncoding.EncodeToString(ct))

do not do either:

// create the client
client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)
// construct a keyURI  
ta, err := client.GetKeyURI(&tpmkmspb.TPMWrapped{
		Id: "123232",  // any identifier
		KeyType: &tpmkmspb.TPMWrapped_Sealed{},
	})
// get kms remote
kekAEAD, err := gcpClient.GetAEAD(ta)
// create key
kh1, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())
// encrypt the key with remote and write to file 
buf := new(bytes.Buffer)
w := keyset.NewJSONWriter(buf)
// if you try to wrap the keyset using the TPM directly like this, the "kek key associated data" is  not included
err = kh1.WriteWithAssociatedData(w, kekAEAD, []byte("kek key associated data"))

primitive, err := aead.New(kh1)
ct, err := primitive.Encrypt([]byte(*data_to_encrypt), []byte("associated data"))

or

client, err := tpmkms.NewClient(ctx, "tpm://", *tpmPath)
// construct a keyURI  
ta, err := client.GetKeyURI(&tpmkmspb.TPMWrapped{
		Id: "123232",  // any identifier
		KeyType: &tpmkmspb.TPMWrapped_Sealed{},
	})
// get kms remote
kekAEAD, err := gcpClient.GetAEAD(ta)

// construct the envelopeAEAD
primitive := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), kekAEAD)
// encrypt the data
ct, err := primitive.Encrypt([]byte(*data_to_encrypt), []byte("associated data"))
log.Printf("Cipher text: %s", base64.StdEncoding.EncodeToString(ct))

finally, KMSEnvelopeAEAD2 does not utilize the provided additional data as shown here...meaning kh1.Write(w,a) is same as kh1.WriteWithAssociatedData(w, kekAEAD, []byte("")) as far as the remote is concerned


Directories

Path Synopsis
integration
tpm
proto module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL