diff --git a/go.mod b/go.mod index 2c00e9fd..de128572 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/peterbourgon/diskv/v3 v3.0.1 github.com/pkg/errors v0.9.1 github.com/schollz/jsonstore v1.1.0 - github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca + github.com/smallstep/go-attestation v0.4.9 github.com/stretchr/testify v1.11.1 go.uber.org/mock v0.6.0 golang.org/x/crypto v0.50.0 diff --git a/go.sum b/go.sum index 3db95143..d76c8ffb 100644 --- a/go.sum +++ b/go.sum @@ -758,8 +758,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4= -github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/go-attestation v0.4.8 h1:yX7oiDFYlXywl+9Hss1RvhcSyPB7q9mFmGZSNhEfi1Q= +github.com/smallstep/go-attestation v0.4.8/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= +github.com/smallstep/go-attestation v0.4.9 h1:/fVmzB8A8tk7B2PCGPlszWzyl/GDcEQbynqcg2PMaZ0= +github.com/smallstep/go-attestation v0.4.9/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= diff --git a/kms/tpmkms/tpmkms.go b/kms/tpmkms/tpmkms.go index f766e4e0..08f982cf 100644 --- a/kms/tpmkms/tpmkms.go +++ b/kms/tpmkms/tpmkms.go @@ -195,6 +195,9 @@ func ParseTPMOptions(u *uri.URI) []tpm.NewTPMOption { if storageDirectory := u.Get("storage-directory"); storageDirectory != "" { opts = append(opts, tpm.WithStore(storage.NewDirstore(storageDirectory))) } + if u.Get("store-location") == "machine" { + opts = append(opts, tpm.WithMachineKey()) + } return opts } diff --git a/tpm/ak.go b/tpm/ak.go index 82903b51..e450eb6e 100644 --- a/tpm/ak.go +++ b/tpm/ak.go @@ -286,8 +286,10 @@ func (t *TPM) DeleteAK(ctx context.Context, name string) (err error) { return fmt.Errorf("failed deleting AK %q because %d key(s) exist that were attested by it", name, len(keys)) } + var attestErr error if err := t.attestTPM.DeleteKey(ak.Data); err != nil { // TODO: we could add a DeleteAK to go-attestation; under the hood it's loaded the same as a key though. - return fmt.Errorf("failed deleting AK %q: %w", name, err) + // Preserve the PCP error but still clean up file-store metadata below. + attestErr = fmt.Errorf("failed deleting AK %q: %w", name, err) } if err := t.store.DeleteAK(name); err != nil { @@ -298,7 +300,7 @@ func (t *TPM) DeleteAK(ctx context.Context, name string) (err error) { return fmt.Errorf("failed persisting storage: %w", err) } - return + return attestErr } // AttestationParameters returns information about the AK, typically used to diff --git a/tpm/internal/key/key.go b/tpm/internal/key/key.go index cd9141d1..a3a11ead 100644 --- a/tpm/internal/key/key.go +++ b/tpm/internal/key/key.go @@ -106,6 +106,10 @@ type CreateConfig struct { // Size is used to specify the bit size of the key or elliptic curve. For // example, '256' is used to specify curve P-256. Size int + // MachineKey instructs the Windows NCrypt PCP provider to create the key in + // the machine (local machine) key store rather than the current user store. + // Only relevant on Windows; ignored on other platforms. + MachineKey bool } func (c *CreateConfig) Validate() error { diff --git a/tpm/internal/key/key_windows.go b/tpm/internal/key/key_windows.go index 65636a6d..7c2bda7d 100644 --- a/tpm/internal/key/key_windows.go +++ b/tpm/internal/key/key_windows.go @@ -8,7 +8,7 @@ import ( ) func create(_ io.ReadWriteCloser, keyName string, config CreateConfig) ([]byte, error) { - pcp, err := openPCP() + pcp, err := openPCP(config.MachineKey) if err != nil { return nil, fmt.Errorf("failed to open PCP: %w", err) } diff --git a/tpm/internal/key/pcp_windows.go b/tpm/internal/key/pcp_windows.go index 3626d9a6..455d2711 100644 --- a/tpm/internal/key/pcp_windows.go +++ b/tpm/internal/key/pcp_windows.go @@ -35,6 +35,9 @@ const ( // The below is documented in this Microsoft whitepaper: // https://github.com/Microsoft/TSS.MSR/blob/master/PCPTool.v11/Using%20the%20Windows%208%20Platform%20Crypto%20Provider%20and%20Associated%20TPM%20Functionality.pdf ncryptOverwriteKeyFlag = 0x80 + // ncryptMachineKeyFlag instructs NCrypt to create or open a key in the + // machine (local machine) key store rather than the current user key store. + ncryptMachineKeyFlag uint32 = 0x00000020 // Key usage value for generic keys nCryptPropertyPCPKeyUsagePolicyGeneric = 0x3 // Key usage value for AKs. @@ -282,7 +285,8 @@ func getNCryptBufferProperty(hnd uintptr, field string) ([]byte, error) { // winPCP represents a reference to the Platform Crypto Provider. type winPCP struct { - hProv uintptr + hProv uintptr + machineKey bool } // Close releases all resources managed by the Handle. @@ -301,8 +305,13 @@ func (h *winPCP) newKey(name string, alg string, length uint32, policy uint32) ( return 0, nil, nil, err } + var flags uint32 + if h.machineKey { + flags = ncryptMachineKeyFlag + } + // Create a persistent RSA key of the specified name. - r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16RSA[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0) + r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16RSA[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, uintptr(flags)) if r != 0 { if tpmErr := maybeWinErr(r); tpmErr != nil { msg = tpmErr @@ -491,10 +500,13 @@ func decodeKeyBlob(keyBlob []byte) ([]byte, []byte, error) { } // openPCP initializes a reference to the Microsoft PCP provider. +// Pass machineKey=true to create and open keys in the machine (local machine) +// key store rather than the current user key store. // The Caller is expected to call Close() when they are done. -func openPCP() (*winPCP, error) { +func openPCP(machineKey bool) (*winPCP, error) { var err error var h winPCP + h.machineKey = machineKey pname, err := windows.UTF16FromString(pcpProviderName) if err != nil { return nil, err diff --git a/tpm/key.go b/tpm/key.go index b8c267b3..45b7f54c 100644 --- a/tpm/key.go +++ b/tpm/key.go @@ -186,8 +186,9 @@ func (t *TPM) CreateKey(ctx context.Context, name string, config CreateKeyConfig } createConfig := internalkey.CreateConfig{ - Algorithm: config.Algorithm, - Size: config.Size, + Algorithm: config.Algorithm, + Size: config.Size, + MachineKey: t.options.attestConfig.MachineKey, } if err := t.validate(&createConfig); err != nil { return nil, fmt.Errorf("invalid key creation parameters: %w", err) @@ -386,8 +387,12 @@ func (t *TPM) DeleteKey(ctx context.Context, name string) (err error) { return fmt.Errorf("failed getting key %q: %w", name, err) } + var attestErr error if err := t.attestTPM.DeleteKey(key.Data); err != nil { - return fmt.Errorf("failed deleting key %q: %w", name, err) + // Preserve the PCP error but still clean up file-store metadata below. + // On Windows the PCP key may be inaccessible when the reset command runs + // in a different session than the agent service that created the key. + attestErr = fmt.Errorf("failed deleting key %q: %w", name, err) } if err := t.store.DeleteKey(name); err != nil { @@ -398,7 +403,7 @@ func (t *TPM) DeleteKey(ctx context.Context, name string) (err error) { return fmt.Errorf("failed persisting storage: %w", err) } - return + return attestErr } // Signer returns a crypto.Signer backed by the Key. diff --git a/tpm/tpm.go b/tpm/tpm.go index bd0dde01..a3c1de7b 100644 --- a/tpm/tpm.go +++ b/tpm/tpm.go @@ -96,6 +96,17 @@ func WithCommandChannel(commandChannel CommandChannel) NewTPMOption { } } +// WithMachineKey configures the TPM to create and open keys in the machine +// (local machine) key store rather than the current user key store. On Windows +// this causes NCRYPT_MACHINE_KEY_FLAG to be passed to NCrypt key operations. +// This option has no effect on non-Windows platforms. +func WithMachineKey() NewTPMOption { + return func(o *options) error { + o.attestConfig.MachineKey = true + return nil + } +} + // WithCapabilities explicitly sets the capabilities rather // than acquiring them from the TPM directly. The primary use // for this option is to ease testing different TPM capabilities. @@ -241,6 +252,7 @@ func (t *TPM) initializeCommandChannel() error { t.attestConfig = &attest.OpenConfig{ TPMVersion: t.options.attestConfig.TPMVersion, CommandChannel: t.commandChannel, + MachineKey: t.options.attestConfig.MachineKey, } return nil } @@ -275,6 +287,7 @@ func (t *TPM) initializeCommandChannel() error { t.attestConfig = &attest.OpenConfig{ TPMVersion: t.options.attestConfig.TPMVersion, CommandChannel: t.commandChannel, + MachineKey: t.options.attestConfig.MachineKey, } return nil