Skip to content

Commit

Permalink
Re-included support for local machine keys, and added some notes abou…
Browse files Browse the repository at this point in the history
…t them.
  • Loading branch information
ElMostafaIdrassi committed Oct 17, 2022
1 parent cd31a1e commit 79ac1b2
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 66 deletions.
23 changes: 8 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,14 @@ The pcpCrypto package is installable using go get: `go get github.com/ElMostafaI
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.
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

Expand Down
14 changes: 10 additions & 4 deletions ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ func (k *pcpECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.Signer
if k.passwordDigest != nil {
openFlags |= internal.NcryptSilentFlag
}
if k.isLocalMachine {
openFlags |= internal.NcryptMachineKeyFlag
}

// Set the other flags
// If a password is set for the key, set the flag NCRYPT_SILENT_FLAG, meaning
Expand Down Expand Up @@ -144,9 +147,8 @@ func (k *pcpECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.Signer
// 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.
// 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
Expand All @@ -157,7 +159,7 @@ func (k *pcpECDSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.Signer
// SignOnly for ECDSA keys.
//
// TODO: Support UI Policies.
func GenerateECDSAKey(name string, password string, isUICompatible bool, curve elliptic.Curve, keyUsage uint32, overwrite bool) (Signer, error) {
func GenerateECDSAKey(name string, password string, isUICompatible bool, isLocalMachine bool, curve elliptic.Curve, keyUsage uint32, overwrite bool) (Signer, error) {
var hProvider uintptr
var hKey uintptr
var creationFlags uint32
Expand All @@ -181,6 +183,9 @@ func GenerateECDSAKey(name string, password string, isUICompatible bool, curve e
if overwrite {
creationFlags |= internal.NcryptOverwriteKeyFlag
}
if isLocalMachine {
creationFlags |= internal.NcryptMachineKeyFlag
}

// Set the other flags
if len(password) != 0 {
Expand Down Expand Up @@ -299,6 +304,7 @@ func GenerateECDSAKey(name string, password string, isUICompatible bool, curve e
passwordDigest: passwordDigest,
pubKey: publicKey,
keyUsage: keyUsage,
isLocalMachine: isLocalMachine,
},
}, nil
}
10 changes: 5 additions & 5 deletions ecdsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
func testECDSAGenerateAndFindKey(t *testing.T, name string, password string, isUICompatible bool, curve elliptic.Curve, toBeDeleted bool) {

// Generate key
key, err := GenerateECDSAKey(name, password, isUICompatible, curve, 0, true)
key, err := GenerateECDSAKey(name, password, isUICompatible, false, curve, 0, true)
require.NoError(t, err)
require.NotNil(t, key)
if toBeDeleted {
Expand All @@ -40,7 +40,7 @@ func testECDSAGenerateAndFindKey(t *testing.T, name string, password string, isU
require.Equal(t, key.Size(), uint32((curve.Params().BitSize+7)/8))

// Find the key
keyBis, err := FindKey(key.Name(), password, isUICompatible)
keyBis, err := FindKey(key.Name(), password, isUICompatible, false)
require.NoError(t, err)
require.NotNil(t, keyBis)
require.Equal(t, key.Name(), keyBis.Name())
Expand Down Expand Up @@ -96,7 +96,7 @@ func TestECDSAGenerateKey(t *testing.T) {
func TestECDSASignWithPassNotUICompatible(t *testing.T) {

// Generate key
key, err := GenerateECDSAKey("", "password123", false, elliptic.P256(), 0, true)
key, err := GenerateECDSAKey("", "password123", false, false, elliptic.P256(), 0, true)
require.NoError(t, err)
require.NotNil(t, key)
defer func() {
Expand All @@ -111,7 +111,7 @@ func TestECDSASignWithPassNotUICompatible(t *testing.T) {
func TestECDSASignWithPassUICompatible(t *testing.T) {

// Generate key
key, err := GenerateECDSAKey("", "password123", true, elliptic.P256(), 0, true)
key, err := GenerateECDSAKey("", "password123", true, false, elliptic.P256(), 0, true)
require.NoError(t, err)
require.NotNil(t, key)
defer func() {
Expand Down Expand Up @@ -159,7 +159,7 @@ func TestECDSASignWithPassUICompatiblePrompt(t *testing.T) {
func TestECDSASignWithoutPass(t *testing.T) {

// Generate key
key, err := GenerateECDSAKey("", "", false, elliptic.P256(), 0, true)
key, err := GenerateECDSAKey("", "", false, false, elliptic.P256(), 0, true)
require.NoError(t, err)
require.NotNil(t, key)
defer func() {
Expand Down
2 changes: 1 addition & 1 deletion examples/csr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
)

func main() {
key, err := pcpcrypto.GenerateRSAKey("", "", false, 1024, 0, true)
key, err := pcpcrypto.GenerateRSAKey("", "", false, false, 1024, 0, true)
if err != nil {
fmt.Fprintf(os.Stderr, "pcpcrypto.GenerateRSAKey() failed: %v\n", err)
return
Expand Down
53 changes: 36 additions & 17 deletions pcpcrypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ type Signer interface {
// 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

// Delete deletes the PCP key.
Delete() error
}
Expand All @@ -72,6 +75,7 @@ type pcpPrivateKey struct {
passwordDigest []byte // passwordDigest is the PCP key password / pin digest (SHA-1 if UI compatible, SHA-256 otherwise)
pubKey crypto.PublicKey // pubKey is the public part of the PCP key.
keyUsage uint32 // keyUsage is the PCP key usage.
isLocalMachine bool // isLocalMachine determines whether the key applies to the Local Machine or to the Current User.
}

// Public is a required method of the crypto.Signer interface.
Expand All @@ -89,6 +93,11 @@ func (k pcpPrivateKey) KeyUsage() uint32 {
return k.keyUsage
}

// IsLocalMachine returns whether the key applies to the Local Machine or to the Current User.
func (k pcpPrivateKey) IsLocalMachine() bool {
return k.isLocalMachine
}

// Delete deletes the PCP key.
func (k pcpPrivateKey) Delete() error {
var hProvider uintptr
Expand All @@ -104,6 +113,9 @@ func (k pcpPrivateKey) Delete() error {

// Set the flags
flags = internal.NcryptSilentFlag
if k.isLocalMachine {
flags |= internal.NcryptMachineKeyFlag
}

// Try to get a handle to the key by its name
_, err = internal.NCryptOpenKey(hProvider, &hKey, k.name, 0, flags)
Expand Down Expand Up @@ -140,13 +152,12 @@ func (k pcpPrivateKey) Delete() error {
// 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.
// 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 FindKey(name string, password string, isUICompatible bool) (Signer, error) {
func FindKey(name string, password string, isUICompatible bool, isLocalMachine bool) (Signer, error) {
var hProvider uintptr
var hKey uintptr
var flags uint32
Expand All @@ -162,6 +173,9 @@ func FindKey(name string, password string, isUICompatible bool) (Signer, error)

// Set the flags
flags = internal.NcryptSilentFlag
if isLocalMachine {
flags |= internal.NcryptMachineKeyFlag
}

// Try to get a handle to the key by its name
_, err = internal.NCryptOpenKey(hProvider, &hKey, name, 0, flags)
Expand All @@ -171,7 +185,7 @@ func FindKey(name string, password string, isUICompatible bool) (Signer, error)
defer internal.NCryptFreeObject(hKey)

// Get key's algorithm
algBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptAlgorithmGroupProperty, flags)
algBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptAlgorithmGroupProperty, internal.NcryptSilentFlag)
if err != nil {
return nil, err
}
Expand All @@ -181,7 +195,7 @@ func FindKey(name string, password string, isUICompatible bool) (Signer, error)
}

// Get key's usage
usageBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptKeyUsageProperty, flags)
usageBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptKeyUsageProperty, internal.NcryptSilentFlag)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -212,10 +226,10 @@ func FindKey(name string, password string, isUICompatible bool) (Signer, error)
var pubkeyBytes []byte
var isRSA bool
if alg == internal.NcryptRsaAlgorithm {
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptRsapublicBlob, nil, flags)
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptRsapublicBlob, nil, 0)
isRSA = true
} else if alg == internal.NcryptEcdsaAlgorithm {
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptEccpublicBlob, nil, flags)
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptEccpublicBlob, nil, 0)
} else {
return nil, fmt.Errorf("unsupported algo: only RSA and ECDSA keys are supported")
}
Expand Down Expand Up @@ -244,6 +258,7 @@ func FindKey(name string, password string, isUICompatible bool) (Signer, error)
passwordDigest: passwordDigest,
pubKey: publicKey,
keyUsage: usage,
isLocalMachine: isLocalMachine,
},
}, nil
} else {
Expand Down Expand Up @@ -282,18 +297,17 @@ func FindKey(name string, password string, isUICompatible bool) (Signer, error)
passwordDigest: passwordDigest,
pubKey: publicKey,
keyUsage: usage,
isLocalMachine: isLocalMachine,
},
}, nil
}
}

// 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.
func GetKeys() ([]Signer, error) {
// 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.
func GetKeys(isLocalMachine bool) ([]Signer, error) {
var hProvider uintptr
var pState unsafe.Pointer
var pKeyName unsafe.Pointer
Expand All @@ -316,6 +330,9 @@ func GetKeys() ([]Signer, error) {

// Set the flags
flags = internal.NcryptSilentFlag
if isLocalMachine {
flags |= internal.NcryptMachineKeyFlag
}

// Retrieve 1 key item at a time.
for {
Expand Down Expand Up @@ -349,7 +366,7 @@ func GetKeys() ([]Signer, error) {
defer internal.NCryptFreeObject(hKey)

// Get key's algorithm
algBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptAlgorithmGroupProperty, flags)
algBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptAlgorithmGroupProperty, internal.NcryptSilentFlag)
if err != nil {
return nil, err
}
Expand All @@ -359,7 +376,7 @@ func GetKeys() ([]Signer, error) {
}

// Get key's usage
usageBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptKeyUsageProperty, flags)
usageBytes, _, err := internal.NCryptGetProperty(hKey, internal.NcryptKeyUsageProperty, internal.NcryptSilentFlag)
if err != nil {
return nil, err
}
Expand All @@ -370,10 +387,10 @@ func GetKeys() ([]Signer, error) {

// Read key's public part
if alg == internal.NcryptRsaAlgorithm {
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptRsapublicBlob, nil, flags)
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptRsapublicBlob, nil, 0)
isRSA = true
} else if alg == internal.NcryptEcdsaAlgorithm {
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptEccpublicBlob, nil, flags)
pubkeyBytes, _, err = internal.NCryptExportKey(hKey, 0, internal.BcryptEccpublicBlob, nil, 0)
} else {
continue
}
Expand Down Expand Up @@ -403,6 +420,7 @@ func GetKeys() ([]Signer, error) {
passwordDigest: nil,
pubKey: publicKey,
keyUsage: keyUsage,
isLocalMachine: isLocalMachine,
},
})
} else {
Expand Down Expand Up @@ -441,6 +459,7 @@ func GetKeys() ([]Signer, error) {
passwordDigest: nil,
pubKey: publicKey,
keyUsage: keyUsage,
isLocalMachine: isLocalMachine,
},
})
}
Expand Down
Loading

0 comments on commit 79ac1b2

Please sign in to comment.