pcpcrypto

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jan 26, 2025 License: Apache-2.0 Imports: 19 Imported by: 0

README

pcpCrypto

pcpCrypto implements the Go standard crypto.Signer interface for keys that live inside Microsoft's TPM Platform Crypto Provider (PCP). All operations are performed by calling PCP's Key Storage Provider (KSP), using CNG Key Storage Functions

This allows the use of TPM PCP keys in operations which require crypto.Signer keys (i.e. x509.CreateCertificateRequest)

For the moment, pcpCrypto only implements the following (more is yet to come) :

  • Generation and retrieval of RSA and ECDSA PCP keys.
  • RSA digest signing using PKCS#1 v1.5 & PSS schemes.
  • ECDSA digest signing.
  • TPM Sealing / Usealing using the TPM SRK.

Installation

The pcpCrypto package is installable using go get: go get github.com/ElMostafaIdrassi/pcpcrypto.

General trivia about PCP TPM keys

Each PCP-generated TPM key is persistent in regards to the PCP KSP : it has a name and persists through reboots.

In order to achieve this persistence, the PCP KSP creates, for each PCP-generated TPM key, a corresponding file which contains all the information about the public and private parts of the key. For a key that applies to the current user <username>, this file resides in C:\Users\<username>\AppData\Local\Microsoft\Crypto\PCPKSP\<SHA1DIGEST\, has a name that is the SHA-1 digest of the key's name and the extension PCPKEY. For a key that applies to the local machine, this file resides in C:\ProgramData\Microsoft\Crypto\PCPKSP\<SHA1DIGEST\, has a name that is the SHA-1 digest of the key's name and the extension PCPKEY.

This gives the PCP KSP the ability to actually load the key into the TPM's volatile memory at each NCryptOpenKey and unload it at each NCryptFreeObject, in the manner of a TPM2-TSS-ENGINE encrypted blob file.

Therefore, PCP KSP keys are not persistent in regards to the TPM chip itself. They are merely transient keys that are created inside of the TPM using NCryptCreatePersistedKey and exported immediately into the aforementioned PCP file when NCryptFinalizeKey is called. These PCP files will then allow the PCP KSP to reload the key into TPM's volatile memory whenever needed.

Known Limitations

  • The PCP KSP fails to sign using PKCS#1 PSS scheme if the passed salt length is not equal to the one supported by the TPM chip. The reason is that TPM chips come in 2 variations :

    • Chips that follow the Pre-TPM Spec-1.16 : these chips can only use a salt length that is always equal to the maximum allowed salt length, which is given by: keySizeInBytes - digestSizeInBytes - 2
    • Chips that follow the Post-TPM Spec-1.16 : these chips can only use a salt length that is always equal to the hash length.

    This means that the PCP KSP needs to be passed a salt length that is equal exactly to the value that the TPM chip supports. Therefore, if we pass a salt length that is equal to the hash length when using a Pre-TPM Spec-1.16 chip, the PCP KSP will either fail with the error TPM_E_PCP_UNSUPPORTED_PSS_SALT or return a zeroed signature. Same goes for Post-TPM Spec-1.16 chips if we pass a salt length that is equal to the maximum allowed salt length. Since the caller may not know the spec of the TPM chip they are using prior to calling the PCP KSP, this can be a problem. Therefore, the PCP KSP developers have given us the flag NCRYPT_TPM_PAD_PSS_IGNORE_SALT. If this option is set in the flags of the sign call, it tells the PCP KSP to ignore the passed salt length and to always use the one that is supported by the TPM chip. This effectively ensures that the PCP KSP will always sign using the correct salt length, regardless of the TPM chip's spec, and will return the correct signature. Note that this implicitly means that Pre-TPM Spec-1.16 chips do not support TLS 1.3, since the salt length in TLS 1.3 muist always be equal to the hash length.

  • Even with the NCRYPT_TPM_PAD_PSS_IGNORE_SALT hack, the PCP KSP can only sign SHA1 and SHA256 digests and fails to sign SHA384 and SHA512 digests using PKCS#1 PSS scheme with the error NTE_NOT_SUPPORTED.

  • The PCP KSP fails to sign SHA384 and SHA512 digests using an ECDSA NIST P256 key with error code TPM_20_E_SIZE. This means that the KSP does not truncate the digests that are longer than the curve's bit size before signing.

Documentation

Index

Constants

View Source
const BcryptPcpKeyMagic uint32 = 0x4D504350 // PCPM
View Source
const PCPTypeTpm12 uint32 = 0x00000001
View Source
const PCPTypeTpm20 uint32 = 0x00000002

Variables

This section is empty.

Functions

func Finalize

func Finalize()

func Initialize

func Initialize(customLogger goncrypt.Logger) (errRet error)

func ParsePCPKeyFile

func ParsePCPKeyFile(pcpKeyFilePath string) (
	public *tpm2.Public,
	private []byte,
	policyDigest *tpm2.TPMLDigest,
	keyName string,
	keyAlgorithm string,
	err error,
)

func SealDataWithTPM

func SealDataWithTPM(dataToSeal []byte, password string) ([]byte, error)

SealDataWithTPM seals the passed data using the TPM.

The data is sealed using the TPM's SRK (Storage Root Key) and can only be unsealed on the same machine, by any user.

If a password is provided, it is used as an additional TPM Sealing Password and the sealed data can only be unsealed if the same password is provided, on the same machine, by any user.

func UnsealDataWithTPM

func UnsealDataWithTPM(dataToUnseal []byte, password string) ([]byte, error)

UnsealDataWithTPM unseals the passed data using the TPM.

The data is unsealed using the TPM's SRK (Storage Root Key) on the same machine it was sealed on, by any user.

If a password was used to seal the data, it must be provided to unseal it.

Types

type KeyUsage

type KeyUsage uint32
const (
	KeyUsageDefault           KeyUsage = 0x00000000
	KeyUsageAllowDecrypt      KeyUsage = 0x00000001 // NcryptAllowDecryptFlag
	KeyUsageAllowSigning      KeyUsage = 0x00000002 // NcryptAllowSigningFlag
	KeyUsageAllowKeyAgreement KeyUsage = 0x00000004 // NcryptAllowKeyAgreementFlag
	KeyUsageAllowAllUsages    KeyUsage = 0x00ffffff // NcryptAllowAllUsages
)

func (*KeyUsage) Value

func (u *KeyUsage) Value() uint32

type PCP20KeyBlob

type PCP20KeyBlob struct {
	PCP20KeyBlobHeader PCP20KeyBlobHeader
	Public             []byte // TPM2B_PUBLIC
	Private            []byte // TPM2B_PRIVATE
	MigrationPublic    []byte
	MigrationPrivate   []byte
	PolicyDigestList   []byte // TPML_DIGEST
	PCRBinding         []byte
	PCRDigest          []byte
	EncryptedSecret    []byte
	Tpm12HostageBlob   []byte
}

type PCP20KeyBlobHeader

type PCP20KeyBlobHeader struct {
	Magic                  uint32
	HeaderLength           uint32
	PCPType                uint32
	Flags                  uint32
	PublicLength           uint32
	PrivateLength          uint32
	MigrationPublicLength  uint32
	MigrationPrivateLength uint32
	PolicyDigestListLength uint32
	PCRBindingLength       uint32
	PCRDigestLength        uint32
	EncryptedSecretLength  uint32
	Tpm12HostageBlobLength uint32
	PCRAlgId               uint16
}

type PCPKeyBlob

type PCPKeyBlob struct {
	PCPKeyBlobHeader PCPKeyBlobHeader
	TPMKey           []byte
}

type PCPKeyBlobHeader

type PCPKeyBlobHeader struct {
	Magic        uint32
	HeaderLength uint32
	PCPType      uint32
	Flags        uint32
	TPKKeyLength uint32
}

type PCPKeyBlobWin8

type PCPKeyBlobWin8 struct {
	PCPKeyBlobWin8Header PCPKeyBlobWin8Header
	Public               []byte // TPM2B_PUBLIC
	Private              []byte // TPM2B_PRIVATE
	MigrationPublic      []byte
	MigrationPrivate     []byte
	PolicyDigestList     []byte // TPML_DIGEST
	PCRBinding           []byte
	PCRDigest            []byte
	EncryptedSecret      []byte
	Tpm12HostageBlob     []byte
}

type PCPKeyBlobWin8Header

type PCPKeyBlobWin8Header struct {
	Magic                  uint32
	HeaderLength           uint32
	PCPType                uint32
	Flags                  uint32
	PublicLength           uint32
	PrivateLength          uint32
	MigrationPublicLength  uint32
	MigrationPrivateLength uint32
	PolicyDigestListLength uint32
	PCRBindingLength       uint32
	PCRDigestLength        uint32
	EncryptedSecretLength  uint32
	Tpm12HostageBlobLength uint32
}

type Signer

type Signer interface {
	crypto.Signer

	// Name returns the PCP key name.
	Name() string

	// Size returns the PCP public key size.
	Size() uint32

	// KeyUsage returns the PCP key usage.
	KeyUsage() KeyUsage

	// IsLocalMachine returns whether the key applies to the Local Machine or to the Current User.
	IsLocalMachine() bool

	// Path returns the path to the PCP key file on disk.
	Path() string

	// TPMTPublicKey returns the TPMT_PUBLIC from the PCP key file
	TPMTPublicKey() *tpm2.Public

	// TPM2BPrivateKey returns the TPM2B_PRIVATE from the PCP key file
	TPM2BPrivateKey() []byte

	// TPMLDigestPolicy returns the TPML_DIGEST from the PCP key file
	TPMLDigestPolicy() *tpm2.TPMLDigest

	// Delete deletes the PCP key.
	Delete() error
}

Signer implements crypto.Signer and additional functions (i.e. Name()).

This allows a pcpPrivateKey to be usable whenever a crypto.Signer is expected, in addition to allowing the caller to perform additional actions on it that are not typically allowed / implemented by the crypto.Signer interface (i.e Name()).

func FindKey

func FindKey(name string, password string, isUICompatible bool, isLocalMachine bool) (Signer, error)

FindKey tries to open a handle to an existing PCP key by its name and read its public part before creating and returning either a pcpRSAPrivateKey or a pcpECDSAPrivateKey. If the PCP key does not exist, it returns nil.

If password is set, it will be saved in the private key and used before each signature, requiring no interaction from the user. Otherwise, if no password is set, a UI prompt might show up during the signature asking for the password / pin if the key needs one.

We differentiate between :

  • PCP keys created with a password set in the Windows UI,
  • PCP keys created with a password set programmatically using NCRYPT_PIN_PORPERTY.

A password set via the UI prompt is transformed internally into its SHA-1 digest, while a password set programmatically via NCRYPT_PIN_PROPERTY is transformed internally into its SHA-256 digest. Therefore, if isUICompatible is set to true, we will store the SHA-1 of the password, while we will store its SHA-256 if isUICompatible is set to false. Note that, if the key was created with a password set via the Windows UI prompt, isUICompatible should be set to true.

If isLocalMachine is set to true, the search will look for keys that apply to the Local Machine. Otherwise, it will look for keys that apply for the Current User.

After all operations are done on the resulting key, its handle should be freed by calling the Close() function on the key.

func GenerateECDSAKey

func GenerateECDSAKey(
	name string,
	password string,
	isUICompatible bool,
	isLocalMachine bool,
	curve elliptic.Curve,
	keyUsage KeyUsage,
	overwrite bool,
) (Signer, error)

GenerateECDSAKey generates a new signing ECDSA PCP Key with the specified name and curve, then returns its corresponding pcpECDSAPrivateKey instance.

If name is empty, it will generate a unique random name beforehand.

If password is empty, it will generate the key with no password / pin, making it usable with no authentication.

If isUICompatible is set to false, and if a password is set, the user will only be able to authenticate to the key programmatically, by setting either of the NCRYPT_PIN_PROPERTY or the NCRYPT_PCP_USAGEAUTH_PROPERTY properties, but never via the Windows UI. If isUICompatible is set to true, and if a password is set, the user will only be able to authenticate to the key via the Windows UI or by setting the NCRYPT_PCP_USAGEAUTH_PROPERTY property, but never by setting the NCRYPT_PIN_PROPERTY property.

If overwrite is set, and if a key with the same name already exists, it will be overwritten.

Supported EC curves are dictated by the (usually at least NIST-P256) and by the PCP KSP. GenerateECDSAKey only supports NIST-P256/P384/P521 which are the only curves supported by the PCP KSP (at the time of writing).

If isLocalMachine is set to true, GenerateRSAKey will generate keys that apply to the Local Machine. Otherwise, it will generate keys that apply for the Current User.

The key usage can be set by combining the following flags using the OR operation :

  • AllowDecrypt
  • AllowSigning
  • AllowKeyAgreement
  • AllowAllUsages

If keyUsage is set to Default instead, the default key usage will be used, which is SignOnly for ECDSA keys.

func GenerateECKeyWithUIPolicy

func GenerateECKeyWithUIPolicy(
	name string,
	uiPolicy UIPolicy,
	isLocalMachine bool,
	curve elliptic.Curve,
	keyUsage KeyUsage,
	overwrite bool,
) (Signer, error)

GenerateECKeyWithUIPolicy is a variant of GenerateECKey that allows to specify the key's UI policy instead of the key's password.

func GenerateRSAKey

func GenerateRSAKey(
	name string,
	password string,
	isUICompatible bool,
	isLocalMachine bool,
	bitLength uint32,
	keyUsage KeyUsage,
	overwrite bool,
) (Signer, error)

GenerateRSAKey generates a new signing RSA PCP Key with the specified name and bit length, then returns its corresponding pcpRSAPrivateKey instance.

If name is empty, it will generate a unique random name beforehand.

If password is empty, it will generate the key with no password / pin, making it usable with no authentication.

If isUICompatible is set to false, and if a password is set, the user will only be able to authenticate to the key programmatically, by setting either of the NCRYPT_PIN_PROPERTY or the NCRYPT_PCP_USAGEAUTH_PROPERTY properties, but never via the Windows UI. If isUICompatible is set to true, and if a password is set, the user will only be able to authenticate to the key via the Windows UI or by setting the NCRYPT_PCP_USAGEAUTH_PROPERTY property, but never by setting the NCRYPT_PIN_PROPERTY property.

If overwrite is set, and if a key with the same name already exists, it will be overwritten.

Supported RSA bit lengths are dictated by the TPM chip (usually 1024 and 2048) and by the PCP KSP. Therefore, there is no restriction on bitLength by GenerateRSAKey.

If isLocalMachine is set to true, GenerateRSAKey will generate keys that apply to the Local Machine. Otherwise, it will generate keys that apply for the Current User.

The key usage can be set by combining the following flags using the OR operation :

  • AllowDecrypt
  • AllowSigning
  • AllowKeyAgreement
  • AllowAllUsages

If keyUsage is set to Default instead, the default key usage will be used, which is Sign + Decrypt for RSA keys.

func GenerateRSAKeyWithUIPolicy

func GenerateRSAKeyWithUIPolicy(
	name string,
	uiPolicy UIPolicy,
	isLocalMachine bool,
	bitLength uint32,
	keyUsage KeyUsage,
	overwrite bool,
) (Signer, error)

GenerateRSAKeyWithUIPolicy is a variant of GenerateRSAKey that allows to specify the key's UI policy instead of the key's password.

func GetKeys

func GetKeys(isLocalMachine bool) ([]Signer, error)

GetKeys tries to retrieve all existing PCP keys.

If isLocalMachine is set to true, the search will retrieve the keys that apply to the Local Machine. Otherwise, it will retrieve the keys that apply for the Current User.

type TlvRecord

type TlvRecord struct {
	Tag   uint32
	Len   uint32
	Value []byte
}

func GetPCPKeyFileTLVRecords

func GetPCPKeyFileTLVRecords(pcpKeyFilePath string) ([]TlvRecord, error)

type UIPolicy

type UIPolicy uint32
const (
	UIPolicyNoConsent                       UIPolicy = 0x00000000
	UIPolicyConsentWithOptionalPIN          UIPolicy = 0x00000001
	UIPolicyConsentWithMandatoryPIN         UIPolicy = 0x00000002
	UIPolicyConsentWithMandatoryFingerprint UIPolicy = 0x00000004
)

func (*UIPolicy) Value

func (p *UIPolicy) Value() uint32

Directories

Path Synopsis
examples
csr command
seal command

Jump to

Keyboard shortcuts

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