pcpcrypto

package module
v0.1.3-beta Latest Latest
Warning

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

Go to latest
Published: Dec 22, 2023 License: Apache-2.0 Imports: 17 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, whatever the passed salt length is. The reason is that TPM chips come in 2 variations :

    • Chips that follow the Pre-TPM Spec-1.16 : these chips 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 use a salt length that is always equal to the hash length.

    This means that the PCP KSP needs to use the salt length that the TPM chip supports instead of the one chosen by the caller. Therefore, the PCP KSP developers have given us the flag NCRYPT_TPM_PAD_PSS_IGNORE_SALT which needs to be passed during the signature. This option forces the PCP KSP to ignore the passed salt length and to always use the one that is supported by the TPM chip. This makes some TPMs incompatible with implementations that require the salt length to be equal to the hash length (i.e. TLS 1.3).

  • Even with the NCRYPT_TPM_PAD_PSS_IGNORE_SALT hack, PCP KSP can only sign SHA1 and SHA256 digests and fails to sign SHA384 and SHA512 digests using PKCS#1 PSS scheme with 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 the KSP does not truncate the digests that are longer than the curve's bit size before signing.

Documentation

Index

Constants

View Source
const (
	KeyUsageAllowDecrypt      = 0x00000001 // NcryptAllowDecryptFlag
	KeyUsageAllowSigning      = 0x00000002 // NcryptAllowSigningFlag
	KeyUsageAllowKeyAgreement = 0x00000004 // NcryptAllowKeyAgreementFlag
	KeyUsageAllowAllUsages    = 0x00ffffff // NcryptAllowAllUsages
)

Variables

This section is empty.

Functions

func Finalize

func Finalize()

func Initialize

func Initialize(customLogger goncrypt.Logger) (errRet 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, is it 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 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() uint32

	// 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

	// 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.

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 uint32, 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 :

  • KeyUsageAllowDecrypt
  • KeyUsageAllowSigning
  • KeyUsageAllowKeyAgreement
  • KeyUsageAllowAllUsages

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

TODO: Support UI Policies.

func GenerateRSAKey

func GenerateRSAKey(name string, password string, isUICompatible bool, isLocalMachine bool, bitLength uint32, keyUsage uint32, 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 :

  • KeyUsageAllowDecrypt
  • KeyUsageAllowSigning
  • KeyUsageAllowKeyAgreement
  • KeyUsageAllowAllUsages

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

TODO: Support UI Policies.

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.

Directories

Path Synopsis
examples
csr

Jump to

Keyboard shortcuts

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