pcpcrypto

package module
v0.1.0-beta Latest Latest
Warning

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

Go to latest
Published: Sep 10, 2022 License: Apache-2.0 Imports: 16 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.

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.

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 0x802801D5. 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

This section is empty.

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

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

At the time of writing, and even if we set the NCRYPT_MACHINE_KEY_FLAG flag during creation, the PCP KSP creates a key that applies to the Current User. Therefore, the search will always 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, 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).

At the time of writing, and even if we set the NCRYPT_MACHINE_KEY_FLAG flag during creation, the PCP KSP creates a key that applies to the Current User. Therefore, GenerateECDSAKey will always 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, 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.

At the time of writing, and even if we set the NCRYPT_MACHINE_KEY_FLAG flag during creation, the PCP KSP creates a key that applies to the Current User. Therefore, GenerateRSAKey will always 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() ([]Signer, error)

GetKeys tries to retrieve all existing PCP keys.

At the time of writing, and even if we set the NCRYPT_MACHINE_KEY_FLAG flag during creation, the PCP KSP creates a key that applies to the Current User. Therefore, GetKeys will always retrieve the keys that apply to 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