├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── auth.go ├── auth_test.go ├── cmds_attestation.go ├── cmds_attestation_test.go ├── cmds_cap.go ├── cmds_cap_test.go ├── cmds_clock.go ├── cmds_command_audit.go ├── cmds_command_audit_test.go ├── cmds_context.go ├── cmds_context_example_test.go ├── cmds_context_test.go ├── cmds_da.go ├── cmds_da_test.go ├── cmds_duplication.go ├── cmds_duplication_test.go ├── cmds_ea.go ├── cmds_ea_test.go ├── cmds_hashhmac.go ├── cmds_hashhmac_test.go ├── cmds_hierarchy.go ├── cmds_hierarchy_example_test.go ├── cmds_hierarchy_test.go ├── cmds_nv.go ├── cmds_nv_test.go ├── cmds_object.go ├── cmds_object_example_test.go ├── cmds_object_test.go ├── cmds_pcr.go ├── cmds_pcr_test.go ├── cmds_rng.go ├── cmds_rng_test.go ├── cmds_session.go ├── cmds_session_test.go ├── cmds_signature.go ├── cmds_signature_test.go ├── cmds_startup.go ├── cmds_startup_test.go ├── cmds_symmetric.go ├── cmds_testing.go ├── command.go ├── command_test.go ├── constants.go ├── crypto.go ├── crypto ├── doc.go ├── kdf.go ├── secret.go ├── symmetric.go └── xor.go ├── cryptutil ├── crypt_test.go ├── doc.go ├── kdf.go ├── secret.go ├── signatures.go └── signatures_test.go ├── doc.go ├── errors.go ├── errors_test.go ├── export_test.go ├── go.mod ├── go.sum ├── internal ├── crypt │ ├── crypt_test.go │ ├── kdf.go │ ├── secret.go │ ├── symmetric.go │ ├── xor.go │ └── xor_test.go ├── ppi │ ├── ppi.go │ └── ppi_test.go ├── ppi_efi │ ├── export_test.go │ ├── ppi_efi.go │ └── ppi_efi_test.go ├── testutil │ ├── checkers.go │ ├── checkers_test.go │ ├── testutil_test.go │ └── util.go └── util │ └── object.go ├── linux ├── device.go ├── device_test.go ├── export_test.go ├── file.go ├── linux.go ├── linux_test.go ├── ppi.go ├── ppi_acpi.go ├── testdata │ ├── mixed-devices-tpm1-sysfs.tar │ ├── mixed-devices-tpm2-sysfs.tar │ ├── multiple-tpm2-devices-sysfs.tar │ ├── no-devices-sysfs.tar │ ├── tpm1-device-sysfs.tar │ ├── tpm2-device-no-rm-sysfs.tar │ ├── tpm2-device-old-kernel-sysfs.tar │ └── tpm2-device-sysfs.tar └── transport.go ├── mssim ├── cmds.go ├── device.go ├── device_test.go ├── export_test.go ├── mssim.go └── mssim_test.go ├── mu ├── doc.go ├── mu.go ├── mu_test.go └── mu_types_test.go ├── objectutil ├── credential.go ├── doc.go ├── duplication.go ├── duplication_test.go ├── export_test.go ├── keys.go ├── keys_test.go ├── named.go ├── object_test.go ├── qn.go ├── qn_test.go ├── templates.go └── templates_test.go ├── paramcrypt.go ├── paramcrypt_test.go ├── policyutil ├── auth.go ├── auth_test.go ├── builder.go ├── builder_test.go ├── cphash.go ├── cphash_test.go ├── doc.go ├── export_test.go ├── named.go ├── namehash.go ├── namehash_test.go ├── or_tree.go ├── or_tree_test.go ├── path_resolver.go ├── pcr_digest.go ├── pcr_digest_test.go ├── policy.go ├── policy_test.go ├── policyutil_test.go ├── resources.go ├── session.go ├── tpm.go └── walker.go ├── ppi ├── ppi.go └── ppi_test.go ├── ppi_efi ├── export_test.go ├── ppi_efi.go └── ppi_efi_test.go ├── resources.go ├── resources_test.go ├── run-tests ├── sealed_object_example_test.go ├── strings.go ├── strings_1_19.go ├── strings_1_20.go ├── strings_test.go ├── templates ├── templates.go └── templates_test.go ├── testutil ├── checkers.go ├── checkers_test.go ├── doc.go ├── export_test.go ├── keys.go ├── object.go ├── suites.go ├── suites_test.go ├── templates.go ├── testutil_test.go ├── tpm.go ├── tpm_test.go ├── transport.go └── transport_test.go ├── tpm.go ├── tpm2_test.go ├── tpm_example_test.go ├── transport.go ├── transportutil ├── buffer.go ├── buffer_test.go ├── mux.go ├── retrier.go ├── transportutil.go └── transportutil_test.go ├── types.go ├── types_algs.go ├── types_algs_test.go ├── types_attributes.go ├── types_attributes_test.go ├── types_constants.go ├── types_constants_test.go ├── types_context.go ├── types_creation.go ├── types_handles.go ├── types_handles_test.go ├── types_interface.go ├── types_nv.go ├── types_nv_test.go ├── types_objects.go ├── types_objects_test.go ├── types_structures.go ├── types_structures_test.go ├── types_test.go └── util ├── cphash.go ├── credential.go ├── crypto.go ├── doc.go ├── duplication.go ├── duplication_test.go ├── entity.go ├── export_test.go ├── keys.go ├── keys_test.go ├── pcr_digest.go ├── pcr_digest_test.go ├── policy.go ├── policy_test.go ├── qn.go ├── qn_test.go ├── signatures.go ├── signatures_test.go └── util_test.go /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | jobs: 8 | tests: 9 | runs-on: ubuntu-24.04 10 | strategy: 11 | matrix: 12 | goversion: 13 | - 1.18 14 | # TODO: enable stable, right now tests fails there, see 15 | # https://github.com/snapcore/secboot/pull/259 16 | # - stable 17 | steps: 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: ${{ matrix.goversion }} 22 | - name: Install dependencies 23 | run: | 24 | sudo snap install core core18 25 | sudo snap install tpm2-simulator-chrisccoulson 26 | - name: Checkout code 27 | uses: actions/checkout@v3 28 | - name: Cache Go dependencies 29 | id: cache-go-modules 30 | uses: actions/cache@v4 31 | with: 32 | path: ~/go/pkg/mod 33 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 34 | restore-keys: | 35 | ${{ runner.os }}-go- 36 | - name: Build 37 | run: go build -v -mod=readonly ./... 38 | - name: Test 39 | run: go test -v -race -p 1 -mod=readonly ./... -args -use-mssim 40 | - name: Go vet 41 | run: go vet ./... 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | go-tpm2.test 2 | NVChip 3 | vendor/*/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | dist: xenial 3 | install: 4 | - sudo snap install core core18 5 | - sudo snap install --edge tpm2-simulator-chrisccoulson 6 | - ./get-deps 7 | script: 8 | - ./run-tests --with-mssim 9 | - go vet ./... 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-tpm2 2 | 3 | [![Tests](https://github.com/canonical/go-tpm2/workflows/Tests/badge.svg)](https://github.com/canonical/go-tpm2/actions?query=workflow%3ATests) [![GoDoc](https://godoc.org/github.com/canonical/go-tpm2?status.svg)](https://godoc.org/github.com/canonical/go-tpm2) 4 | 5 | This repository contains a go library for interacting with TPM 2.0 devices. Some currently supported features are: 6 | 7 | - All authorization modes: cleartext password, HMAC session based and policy session based. 8 | - All session configurations: salted or unsalted + bound or unbound. 9 | - Session-based command and response parameter encryption using AES-CFB or XOR obfuscation. 10 | - Session-based command auditing. 11 | - Backends for Linux TPM character devices and TPM simulators implementing the Microsoft TPM 2.0 simulator interface. 12 | 13 | The current support status for each command group is detailed below. 14 | 15 | Command group | Support | Comment 16 | --- | --- | --- 17 | Start-up | Full | 18 | Testing | Full | 19 | Session Commands | Full | 20 | Object Commands | Full | 21 | Duplication Commands | Partial | TPM2_Duplicate and TPM2_Import are supported 22 | Asymmetric Primitives | None | 23 | Symmetric Primitives | None | 24 | Random Number Generator | Full | 25 | Hash/HMAC/Event Sequences | Full | 26 | Attestation Commands | Full | 27 | Ephemeral EC Keys | None | 28 | Signing and Signature Verification | Full | 29 | Command Audit | Full | 30 | Integrity Collection (PCR) | Partial | TPM2_PCR_Extend, TPM2_PCR_Event, TPM2_PCR_Read and TPM2_PCR_Reset are supported 31 | Enhanced Authorization (EA) Commands | Partial | All commands are supported except for TPM2_PolicyLocality, TPM2_PolicyPhysicalPresence, TPM2_PolicyTemplate and TPM2_PolicyAuthorizeNV 32 | Hierarchy Commands | Partial | TPM2_CreatePrimary, TPM2_HierarchyControl, TPM2_Clear, TPM2_ClearControl and TPM2_HierarchyChangeAuth are supported 33 | Dictionary Attack Functions | Full | 34 | Miscellaneous Management Functions | None | 35 | Field Upgrade | None | 36 | Context Management | Full | 37 | Clocks and Timers | Partial | TPM2_ReadClock is supported 38 | Capability Commands | Full | 39 | Non-Volatile Storage | Partial | All commands are supported except for TPM2_NV_Certify 40 | Vendor Specific | None | 41 | 42 | ## Relevant links 43 | - [TPM 2.0 Library Specification](https://trustedcomputinggroup.org/resource/tpm-library-specification/) 44 | - [IBM's Software TPM 2.0](https://sourceforge.net/projects/ibmswtpm2/) 45 | -------------------------------------------------------------------------------- /cmds_clock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 29 - Clocks and Timers 8 | 9 | // ReadClock executes the TPM2_ReadClock command. On succesful completion, it will return a 10 | // TimeInfo struct that contains the current value of time, clock, reset and restart counts. 11 | func (t *TPMContext) ReadClock(sessions ...SessionContext) (currentTime *TimeInfo, err error) { 12 | if err := t.StartCommand(CommandReadClock). 13 | AddExtraSessions(sessions...). 14 | Run(nil, ¤tTime); err != nil { 15 | return nil, err 16 | } 17 | return currentTime, nil 18 | } 19 | 20 | // func (t *TPMContext) ClockSet(auth Handle, newTime uint64, authAuth interface{}) error { 21 | // } 22 | 23 | // func (t *TPMContext) ClockRateAdjust(auth Handle, rateAdjust ClockAdjust, authAuth interface{}) error { 24 | // } 25 | -------------------------------------------------------------------------------- /cmds_command_audit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 21 - Command Audit 8 | 9 | // SetCommandCodeAuditStatus executes the TPM2_SetCommandCodeAuditStatus command to allow the 10 | // privacy administrator or platform to change the audit status of a command, or change the digest 11 | // algorithm used for command auditing (but not both at the same time). 12 | // 13 | // The auth parameter should be a ResourceContext corresponding to either [HandlePlatform] or 14 | // [HandleOwner]. This command requires authorization of auth with the user auth role, with session 15 | // based authorization provided via authAuthSession. 16 | // 17 | // The auditAlg argument specifies the digest algorithm for command auditing. The setList argument 18 | // is used to specify which commands should be added to the list of commands to be audited. The 19 | // clearList argument is used to specify which commands should be removed from the list of commands 20 | // to be audited. 21 | // 22 | // If auditAlg is not [HashAlgorithmNull] or the current audit digest algorithm, and the length of 23 | // setList or clearList is greater than zero, a *[TPMParameterError] error with an error code of 24 | // [ErrorValue] will be returned for parameter index 1. 25 | func (t *TPMContext) SetCommandCodeAuditStatus(auth ResourceContext, auditAlg HashAlgorithmId, setList, clearList CommandCodeList, authAuthSession SessionContext, sessions ...SessionContext) error { 26 | return t.StartCommand(CommandSetCommandCodeAuditStatus). 27 | AddHandles(UseResourceContextWithAuth(auth, authAuthSession)). 28 | AddParams(auditAlg, setList, clearList). 29 | Run(nil) 30 | } 31 | -------------------------------------------------------------------------------- /cmds_context_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/linux" 13 | "github.com/canonical/go-tpm2/objectutil" 14 | ) 15 | 16 | func ExampleTPMContext_EvictControl_persistTransientObject() { 17 | // Create a primary object and then use TPMContext.EvictControl to store it in NV memory 18 | // at handle 0x81000001. 19 | device, err := linux.DefaultTPM2Device() 20 | if err != nil { 21 | fmt.Fprintln(os.Stderr, err) 22 | return 23 | } 24 | tpm, err := tpm2.OpenTPMDevice(device) 25 | if err != nil { 26 | fmt.Fprintln(os.Stderr, err) 27 | return 28 | } 29 | defer tpm.Close() 30 | 31 | template := objectutil.NewRSAStorageKeyTemplate() 32 | 33 | transient, _, _, _, _, err := tpm.CreatePrimary(tpm.OwnerHandleContext(), nil, template, nil, nil, nil) 34 | if err != nil { 35 | fmt.Fprintln(os.Stderr, err) 36 | return 37 | } 38 | defer tpm.FlushContext(transient) 39 | 40 | persistent, err := tpm.EvictControl(tpm.OwnerHandleContext(), transient, 0x81000001, nil) 41 | if err != nil { 42 | fmt.Fprintln(os.Stderr, err) 43 | return 44 | } 45 | 46 | // persistent is the handle to the new persistent object 47 | _ = persistent 48 | } 49 | 50 | func ExampleTPMContext_EvictControl_evictPersistentObject() { 51 | // Use TPMContext.EvictControl to remove the object at handle 0x81000001 from NV memory. 52 | device, err := linux.DefaultTPM2Device() 53 | if err != nil { 54 | fmt.Fprintln(os.Stderr, err) 55 | return 56 | } 57 | tpm, err := tpm2.OpenTPMDevice(device) 58 | if err != nil { 59 | fmt.Fprintln(os.Stderr, err) 60 | return 61 | } 62 | defer tpm.Close() 63 | 64 | persistent, err := tpm.NewResourceContext(0x81000001) 65 | if err != nil { 66 | fmt.Fprintln(os.Stderr, err) 67 | return 68 | } 69 | 70 | if _, err := tpm.EvictControl(tpm.OwnerHandleContext(), persistent, persistent.Handle(), nil); err != nil { 71 | fmt.Fprintln(os.Stderr, err) 72 | return 73 | } 74 | 75 | // The resource associated with persistent is now unavailable. 76 | } 77 | -------------------------------------------------------------------------------- /cmds_da.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 25 - Dictionary Attack Functions 8 | 9 | // DictionaryAttackLockReset executes the TPM2_DictionaryAttackLockReset command to cancel the 10 | // effect of a TPM lockout. The lockContext parameter must always be a ResourceContext 11 | // corresponding to [HandleLockout]. The command requires authorization with the user auth role 12 | // for lockContext, with session based authorization provided via lockContextAuthSession. 13 | // 14 | // On successful completion, the lockout counter will be reset to zero. 15 | func (t *TPMContext) DictionaryAttackLockReset(lockContext ResourceContext, lockContextAuthSession SessionContext, sessions ...SessionContext) error { 16 | return t.StartCommand(CommandDictionaryAttackLockReset). 17 | AddHandles(UseResourceContextWithAuth(lockContext, lockContextAuthSession)). 18 | AddExtraSessions(sessions...). 19 | Run(nil) 20 | } 21 | 22 | // DictionaryAttackParameters executes the TPM2_DictionaryAttackParameters command to change the 23 | // dictionary attack lockout settings. The newMaxTries parameter sets the maximum value of the 24 | // lockout counter before the TPM enters lockout mode. If it is set to zero, then the TPM will 25 | // enter lockout mode and the use of dictionary attack protected entities will be disabled. 26 | // The newRecoveryTime parameter specifies the amount of time in seconds it takes for the lockout 27 | // counter to decrement by one. If it is set to zero, then dictionary attack protection is 28 | // disabled. The lockoutRecovery parameter specifies the amount of time in seconds that the lockout 29 | // hierarchy authorization cannot be used after an authorization failure. If it is set to zero, 30 | // then the lockout hierarchy can be used again after a TPM reset, restart or resume. The 31 | // newRecoveryTime and lockoutRecovery parameters are measured against powered on time rather than 32 | // clock time. 33 | // 34 | // The lockContext parameter must be a ResourceContext corresponding to [HandleLockout]. The 35 | // command requires authorization with the user auth role for lockContext, with session based 36 | // authorization provided via lockContextAuthSession. 37 | func (t *TPMContext) DictionaryAttackParameters(lockContext ResourceContext, newMaxTries, newRecoveryTime, lockoutRecovery uint32, lockContextAuthSession SessionContext, sessions ...SessionContext) error { 38 | return t.StartCommand(CommandDictionaryAttackParameters). 39 | AddHandles(UseResourceContextWithAuth(lockContext, lockContextAuthSession)). 40 | AddParams(newMaxTries, newRecoveryTime, lockoutRecovery). 41 | AddExtraSessions(sessions...). 42 | Run(nil) 43 | } 44 | -------------------------------------------------------------------------------- /cmds_hierarchy_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/linux" 13 | "github.com/canonical/go-tpm2/objectutil" 14 | ) 15 | 16 | func ExampleTPMContext_CreatePrimary_createPrimaryStorageKeyInStorageHierarchy() { 17 | // Use TPMContext.CreatePrimary to create a primary storage key in the storage hierarchy. 18 | device, err := linux.DefaultTPM2Device() 19 | if err != nil { 20 | fmt.Fprintln(os.Stderr, err) 21 | return 22 | } 23 | tpm, err := tpm2.OpenTPMDevice(device) 24 | if err != nil { 25 | fmt.Fprintln(os.Stderr, err) 26 | return 27 | } 28 | defer tpm.Close() 29 | 30 | template := objectutil.NewRSAStorageKeyTemplate() 31 | 32 | object, _, _, _, _, err := tpm.CreatePrimary(tpm.OwnerHandleContext(), nil, template, nil, nil, nil) 33 | if err != nil { 34 | fmt.Fprintln(os.Stderr, err) 35 | return 36 | } 37 | 38 | // object is the handle to the new transient primary object 39 | // ... 40 | // ... do something with object 41 | // ... 42 | 43 | tpm.FlushContext(object) 44 | } 45 | -------------------------------------------------------------------------------- /cmds_object_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/linux" 13 | "github.com/canonical/go-tpm2/objectutil" 14 | ) 15 | 16 | func ExampleTPMContext_Create_createPassphraseProtectedSealedObject() { 17 | // Use TPMContext.Create to seal some arbitrary data in a passphrase protected object. 18 | 19 | passphrase := []byte("passphrase") 20 | secret := []byte("secret data") 21 | 22 | device, err := linux.DefaultTPM2Device() 23 | if err != nil { 24 | fmt.Fprintln(os.Stderr, err) 25 | return 26 | } 27 | tpm, err := tpm2.OpenTPMDevice(device) 28 | if err != nil { 29 | fmt.Fprintln(os.Stderr, err) 30 | return 31 | } 32 | defer tpm.Close() 33 | 34 | // We need a storage parent, eg, the shared SRK. Assume it already exists. 35 | srk, err := tpm.NewResourceContext(0x81000001) 36 | if err != nil { 37 | fmt.Fprintln(os.Stderr, err) 38 | return 39 | } 40 | 41 | template := objectutil.NewSealedObjectTemplate() 42 | 43 | sensitive := &tpm2.SensitiveCreate{ 44 | UserAuth: passphrase, 45 | Data: secret} 46 | 47 | priv, pub, _, _, _, err := tpm.Create(srk, sensitive, template, nil, nil, nil) 48 | if err != nil { 49 | fmt.Fprintln(os.Stderr, err) 50 | return 51 | } 52 | 53 | // priv and pub contain the private and public parts of the sealed object, 54 | // and these can be serialized to persistent storage somewhere, or loaded in 55 | // to the TPM with the TPMContext.Load function. The mu/ subpackage can be used 56 | // to serialize them in the TPM wire format. 57 | _ = priv 58 | _ = pub 59 | } 60 | -------------------------------------------------------------------------------- /cmds_rng.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 16 - Random Number Generator 8 | 9 | // GetRandom executes the TPM2_GetRandom command to return the requested number of bytes from the 10 | // TPM's random number generator. 11 | func (t *TPMContext) GetRandom(bytesRequested uint16, sessions ...SessionContext) (randomBytes Digest, err error) { 12 | if err := t.StartCommand(CommandGetRandom). 13 | AddParams(bytesRequested). 14 | AddExtraSessions(sessions...). 15 | Run(nil, &randomBytes); err != nil { 16 | return nil, err 17 | } 18 | return randomBytes, nil 19 | } 20 | 21 | func (t *TPMContext) StirRandom(inData SensitiveData, sessions ...SessionContext) error { 22 | return t.StartCommand(CommandStirRandom). 23 | AddParams(inData). 24 | AddExtraSessions(sessions...). 25 | Run(nil) 26 | } 27 | -------------------------------------------------------------------------------- /cmds_rng_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "crypto/rand" 9 | 10 | . "gopkg.in/check.v1" 11 | 12 | . "github.com/canonical/go-tpm2" 13 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 14 | "github.com/canonical/go-tpm2/mu" 15 | "github.com/canonical/go-tpm2/testutil" 16 | ) 17 | 18 | type rngSuite struct { 19 | testutil.TPMTest 20 | } 21 | 22 | func (s *rngSuite) SetUpSuite(c *C) { 23 | s.TPMFeatures = testutil.TPMFeatureNV 24 | } 25 | 26 | var _ = Suite(&rngSuite{}) 27 | 28 | func (s *rngSuite) testGetRandom(c *C, bytesRequested uint16) { 29 | data, err := s.TPM.GetRandom(bytesRequested) 30 | c.Check(err, IsNil) 31 | c.Check(data, internal_testutil.LenEquals, int(bytesRequested)) 32 | 33 | _, _, rpBytes, _ := s.LastCommand(c).UnmarshalResponse(c) 34 | 35 | var expected Digest 36 | _, err = mu.UnmarshalFromBytes(rpBytes, &expected) 37 | c.Check(err, IsNil) 38 | 39 | c.Check(data, DeepEquals, expected) 40 | } 41 | 42 | func (s *rngSuite) TestGetRandom32(c *C) { 43 | s.testGetRandom(c, 32) 44 | } 45 | 46 | func (s *rngSuite) TestGetRandom20(c *C) { 47 | s.testGetRandom(c, 32) 48 | } 49 | 50 | func (s *rngSuite) TestStirRandom(c *C) { 51 | inData := make([]byte, 32) 52 | rand.Read(inData) 53 | 54 | c.Check(s.TPM.StirRandom(inData), IsNil) 55 | 56 | _, _, cpBytes := s.LastCommand(c).UnmarshalCommand(c) 57 | 58 | var expected []byte 59 | _, err := mu.UnmarshalFromBytes(cpBytes, &expected) 60 | c.Check(err, IsNil) 61 | 62 | c.Check(inData, DeepEquals, expected) 63 | } 64 | -------------------------------------------------------------------------------- /cmds_signature.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Secion 20 - Signing and Signature Verification 8 | 9 | // VerifySignature executes the TPM2_VerifySignature command to validate the provided signature 10 | // against a message with the provided digest, using the key associated with keyContext. If 11 | // keyContext corresponds to an object that isn't a signing key, a *[TPMHandleError] error with an 12 | // error code of [ErrorAttributes] will be returned. 13 | // 14 | // If the signature is invalid, a *[TPMParameterError] error with an error code of [ErrorSignature] 15 | // will be returned for parameter index 2. If the signature references an unsupported signature 16 | // scheme, a *[TPMParameterError] error with an error code of [ErrorScheme] will be returned for 17 | // parameter index 2. 18 | // 19 | // If keyContext corresponds to a HMAC key but only the public part is loaded, a 20 | // *[TPMParameterError] error with an error code of [ErrorHandle] will be returned for parameter 21 | // index 2. 22 | // 23 | // On success, a valid TkVerified structure will be returned. 24 | func (t *TPMContext) VerifySignature(keyContext ResourceContext, digest Digest, signature *Signature, sessions ...SessionContext) (validation *TkVerified, err error) { 25 | if err := t.StartCommand(CommandVerifySignature). 26 | AddHandles(UseHandleContext(keyContext)). 27 | AddParams(digest, signature). 28 | AddExtraSessions(sessions...). 29 | Run(nil, &validation); err != nil { 30 | return nil, err 31 | } 32 | 33 | return validation, nil 34 | } 35 | 36 | // Sign executes the TPM2_Sign command to sign the provided digest with the key associated with 37 | // keyContext. The function requires authorization with the user auth role for keyContext, with 38 | // session based authorization provided via keyContextAuthSession. 39 | // 40 | // If the object associated with keyContext is not a signing key, a *[TPMHandleError] error with an 41 | // error code of [ErrorKey] will be returned. 42 | // 43 | // If the scheme of the key associated with keyContext is [AsymSchemeNull], then inScheme must be 44 | // provided to specify a valid signing scheme for the key. If it isn't, a *[TPMParameterError] 45 | // error with an error code of [ErrorScheme] will be returned for parameter index 2. 46 | // 47 | // If the scheme of the key associated with keyContext is not [AsymSchemeNull], then inScheme may 48 | // be nil. If it is provided, then the specified scheme must match that of the signing key, else a 49 | // *[TPMParameterError] error with an error code of [ErrorScheme] will be returned for parameter 50 | // index 2. 51 | // 52 | // If the chosen scheme is unsupported, a *[TPMError] error with an error code of [ErrorScheme] 53 | // will be returned. 54 | // 55 | // If the length of digest does not match the size of the digest associated with the selected 56 | // signing scheme, a *[TPMParameterError] error with an error code of [ErrorSize] will be returned 57 | // for parameter index 1. 58 | // 59 | // If the key associated with keyContext has the [AttrRestricted] attribute, then the validation 60 | // parameter must be provided as proof that the supplied digest was created by the TPM. If the key 61 | // associated with keyContext does not have the [AttrRestricted] attribute, then validation may be 62 | // nil. If validation is not nil and doesn't correspond to a valid ticket, or it is nil and the 63 | // key associated with keyContext has the [AttrRestricted] attribute set, a *[TPMParameterError] 64 | // error with an error code of [ErrorTicket] will be returned for parameter index 3. 65 | func (t *TPMContext) Sign(keyContext ResourceContext, digest Digest, inScheme *SigScheme, validation *TkHashcheck, keyContextAuthSession SessionContext, sessions ...SessionContext) (signature *Signature, err error) { 66 | if inScheme == nil { 67 | inScheme = &SigScheme{Scheme: SigSchemeAlgNull} 68 | } 69 | if validation == nil { 70 | validation = &TkHashcheck{Tag: TagHashcheck, Hierarchy: HandleNull} 71 | } 72 | 73 | if err := t.StartCommand(CommandSign). 74 | AddHandles(UseResourceContextWithAuth(keyContext, keyContextAuthSession)). 75 | AddParams(digest, inScheme, validation). 76 | AddExtraSessions(sessions...). 77 | Run(nil, &signature); err != nil { 78 | return nil, err 79 | } 80 | 81 | return signature, nil 82 | } 83 | -------------------------------------------------------------------------------- /cmds_startup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 10 - Testing 8 | 9 | // Startup executes the TPM2_Startup command with the specified StartupType. If this isn't preceded 10 | // by _TPM_Init then it will return a *[TPMError] error with an error code of [ErrorInitialize]. 11 | // The shutdown and startup sequence determines how the TPM responds to this call: 12 | // - A call with startupType == [StartupClear] preceded by a call to [TPMContext.Shutdown] with 13 | // shutdownType == [StartupClear] or without a preceding call to [TPMContext.Shutdown] will 14 | // cause a TPM reset. 15 | // - A call with startupType == [StartupClear] preceded by a call to [TPMContext.Shutdown] with 16 | // shutdownType == [StartupState] will cause a TPM restart. 17 | // - A call with startupType == [StartupState] preceded by a call to [TPMContext.Shutdown] with 18 | // shutdownType == [StartupState] will cause a TPM resume. 19 | // - A call with startupType == [StartupState] that isn't preceded by a call to 20 | // [TPMContext.Shutdown] with shutdownType == [StartupState] will fail with a 21 | // *[TPMParameterError] error with an error code of [ErrorValue]. 22 | // 23 | // If called with startupType == [StartupState], a *[TPMError] error with an error code of 24 | // [ErrorNVUninitialized] will be returned if the saved state cannot be recovered. In this case, 25 | // the function must be called with startupType == [StartupClear]. 26 | // 27 | // Subsequent use of HandleContext instances corresponding to entities that are evicted as a 28 | // consequence of this function will no longer work. 29 | func (t *TPMContext) Startup(startupType StartupType) error { 30 | return t.StartCommand(CommandStartup).AddParams(startupType).Run(nil) 31 | } 32 | 33 | // Shutdown executes the TPM2_Shutdown command with the specified StartupType, and is used to 34 | // prepare the TPM for a power cycle. Calling this with shutdownType == [StartupClear] prepares the 35 | // TPM for a TPM reset. Calling it with shutdownType == [StartupState] prepares the TPM for either 36 | // a TPM restart or TPM resume, depending on how [TPMContext.Startup] is called. Some commands 37 | // executed after [TPMContext.Shutdown] but before a power cycle will nullify the effect of this 38 | // function. 39 | // 40 | // If a PCR bank has been reconfigured and shutdownType == [StartupState], a *[TPMParameterError] 41 | // error with an error code of [ErrorType] will be returned. 42 | func (t *TPMContext) Shutdown(shutdownType StartupType, sessions ...SessionContext) error { 43 | return t.StartCommand(CommandShutdown).AddParams(shutdownType).AddExtraSessions(sessions...).Run(nil) 44 | } 45 | -------------------------------------------------------------------------------- /cmds_startup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | . "github.com/canonical/go-tpm2" 9 | "github.com/canonical/go-tpm2/testutil" 10 | 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | type startupSuite struct { 15 | testutil.TPMSimulatorTest 16 | } 17 | 18 | var _ = Suite(&startupSuite{}) 19 | 20 | func (s *startupSuite) runStartupTest(c *C, shutdownType, startupType StartupType) (*TimeInfo, *TimeInfo) { 21 | timeBefore, err := s.TPM.ReadClock() 22 | c.Assert(err, IsNil) 23 | 24 | c.Check(s.TPM.Shutdown(shutdownType), IsNil) 25 | c.Check(s.Mssim(c).Reset(), IsNil) 26 | c.Check(s.TPM.Startup(startupType), IsNil) 27 | 28 | time, err := s.TPM.ReadClock() 29 | c.Assert(err, IsNil) 30 | 31 | return timeBefore, time 32 | } 33 | 34 | func (s *startupSuite) TestResume(c *C) { 35 | time1, time2 := s.runStartupTest(c, StartupState, StartupState) 36 | c.Check(time2.ClockInfo.ResetCount, Equals, time1.ClockInfo.ResetCount) 37 | c.Check(time2.ClockInfo.RestartCount, Equals, time1.ClockInfo.RestartCount+1) 38 | } 39 | 40 | func (s *startupSuite) TestRestart(c *C) { 41 | time1, time2 := s.runStartupTest(c, StartupState, StartupClear) 42 | c.Check(time2.ClockInfo.ResetCount, Equals, time1.ClockInfo.ResetCount) 43 | c.Check(time2.ClockInfo.RestartCount, Equals, time1.ClockInfo.RestartCount+1) 44 | } 45 | 46 | func (s *startupSuite) TestReset(c *C) { 47 | time1, time2 := s.runStartupTest(c, StartupClear, StartupClear) 48 | c.Check(time2.ClockInfo.ResetCount, Equals, time1.ClockInfo.ResetCount+1) 49 | c.Check(time2.ClockInfo.RestartCount, Equals, uint32(0)) 50 | } 51 | -------------------------------------------------------------------------------- /cmds_symmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 15 - Symmetric Primitives 8 | -------------------------------------------------------------------------------- /cmds_testing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // Section 9 - Start-up 8 | 9 | // SelfTest executes the TPM2_SelfTest command and causes the TPM to perform a self test 10 | // of its capabilities. If fullTest is true, it will test all functions. If fullTest is 11 | // false, it will only perform a test of functions that haven't already been tested. 12 | // 13 | // Some implementatons will return immediately and then begin testing. In this case, if 14 | // fullTest is true or fullTest is false and there are tests to perform, a *[TPMWarning] 15 | // with the warning code [WarningTesting] will be returned. If fullTest is false and 16 | // there are no tests to perform, this function will return with success. 17 | // 18 | // Some implementations will block and only return if all required tests have been completed. 19 | // In this case, if a failure occurs, a *[TPMError] with the error code [ErrorFailure] will 20 | // be returned. If all tests that execute complete successfully, the function will return 21 | // no error. 22 | func (t *TPMContext) SelfTest(fullTest bool, sessions ...SessionContext) error { 23 | return t.StartCommand(CommandSelfTest). 24 | AddParams(fullTest). 25 | AddExtraSessions(sessions...). 26 | Run(nil) 27 | } 28 | 29 | // IncrementalSelfTest executes the TPM2_IncrementalSelfTest command and causes the TPM to 30 | // perform a test of the selected algorithms. 31 | // 32 | // If toTest contains an algorithm that has already been tested, it won't be tested again. 33 | // 34 | // The TPM will return a todo list of algorithms that haven't been fully tested. Supplying 35 | // an empty toTest list is a way to determine which algorithms have not been fully tested 36 | // yet. 37 | // 38 | // If toTest is not an empty list, this command should respond with no error. Subsequent 39 | // calls to this command or any others will return a *[TPMWarning] with warning code 40 | // [WarningTesting] until the requested testing is complete. 41 | func (t *TPMContext) IncrementalSelfTest(toTest AlgorithmList, sessions ...SessionContext) (AlgorithmList, error) { 42 | var toDoList AlgorithmList 43 | if err := t.StartCommand(CommandIncrementalSelfTest). 44 | AddParams(toTest). 45 | AddExtraSessions(sessions...). 46 | Run(nil, &toDoList); err != nil { 47 | return nil, err 48 | } 49 | return toDoList, nil 50 | } 51 | 52 | // GetTestResult executes the TPM2_GetTestResult command and returns manufacturer-specific information 53 | // regarding the results of a self-test as well as the test status. 54 | // 55 | // If TPM2_SelfTest hasn't been executed and a testable function hasn't been tested, then testResult 56 | // will equal [ResponseNeedsTest]. If TPM2_SelfTest has been executed and testing is ongoing, then 57 | // testResult will equal [ResponseTesting]. 58 | // 59 | // If testing of all functions is complete without failure, testResult will be [ResponseSuccess]. If 60 | // any test failed, testResult will be [ResponseFailure]. 61 | func (t *TPMContext) GetTestResult(sessions ...SessionContext) (outData MaxBuffer, testResult ResponseCode, err error) { 62 | if err := t.StartCommand(CommandGetTestResult). 63 | AddExtraSessions(sessions...). 64 | Run(nil, &outData, &testResult); err != nil { 65 | return nil, 0, err 66 | } 67 | return outData, testResult, nil 68 | } 69 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | import ( 8 | "math" 9 | ) 10 | 11 | const ( 12 | DefaultRSAExponent = 65537 13 | ) 14 | 15 | const ( 16 | CapabilityMaxProperties uint32 = math.MaxUint32 17 | ) 18 | 19 | const ( 20 | // CFBKey is used as the label for the symmetric key derivation used in parameter encryption. 21 | CFBKey = "CFB" 22 | 23 | // DuplicateString is used as the label for key establishment for object duplication. 24 | DuplicateString = "DUPLICATE" 25 | 26 | // IdentityKey is used as the label for key establishment when issuing and using activation 27 | // credentials. 28 | IdentityKey = "IDENTITY" 29 | 30 | // IntegrityKey is used as the label for the HMAC key derivation used for outer wrappers. 31 | IntegrityKey = "INTEGRITY" 32 | 33 | // SecretKey is used as the label for salt establishment when starting a salted session. 34 | SecretKey = "SECRET" 35 | 36 | // SessionKey is used as the label for the session key derivation. 37 | SessionKey = "ATH" 38 | 39 | // StorageKey is used as the label for the symmetric key derivation used for encrypting and 40 | // decrypting outer wrappers. 41 | StorageKey = "STORAGE" 42 | ) 43 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | import ( 8 | "crypto/aes" 9 | "crypto/cipher" 10 | "crypto/ecdsa" 11 | "crypto/elliptic" 12 | "crypto/rand" 13 | "crypto/rsa" 14 | "encoding/binary" 15 | "fmt" 16 | 17 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 18 | ) 19 | 20 | type NewCipherFunc func([]byte) (cipher.Block, error) 21 | 22 | var ( 23 | eccCurves = map[ECCCurve]elliptic.Curve{ 24 | ECCCurveNIST_P224: elliptic.P224(), 25 | ECCCurveNIST_P256: elliptic.P256(), 26 | ECCCurveNIST_P384: elliptic.P384(), 27 | ECCCurveNIST_P521: elliptic.P521(), 28 | } 29 | 30 | symmetricAlgs = map[SymAlgorithmId]NewCipherFunc{ 31 | SymAlgorithmAES: aes.NewCipher, 32 | } 33 | ) 34 | 35 | // RegisterCipher allows a go block cipher implementation to be registered for the 36 | // specified algorithm, so binaries don't need to link against every implementation. 37 | func RegisterCipher(alg SymAlgorithmId, fn NewCipherFunc) { 38 | symmetricAlgs[alg] = fn 39 | } 40 | 41 | func cryptComputeCpHash(alg HashAlgorithmId, command CommandCode, handles []Name, parameters []byte) Digest { 42 | hash := alg.NewHash() 43 | 44 | binary.Write(hash, binary.BigEndian, command) 45 | for _, name := range handles { 46 | hash.Write([]byte(name)) 47 | } 48 | hash.Write(parameters) 49 | 50 | return hash.Sum(nil) 51 | } 52 | 53 | func cryptComputeRpHash(hashAlg HashAlgorithmId, responseCode ResponseCode, commandCode CommandCode, rpBytes []byte) []byte { 54 | hash := hashAlg.NewHash() 55 | 56 | binary.Write(hash, binary.BigEndian, responseCode) 57 | binary.Write(hash, binary.BigEndian, commandCode) 58 | hash.Write(rpBytes) 59 | 60 | return hash.Sum(nil) 61 | } 62 | 63 | func cryptComputeNonce(nonce []byte) error { 64 | _, err := rand.Read(nonce) 65 | return err 66 | } 67 | 68 | func cryptSecretEncrypt(public *Public, label []byte) (EncryptedSecret, []byte, error) { 69 | if !public.NameAlg.Available() { 70 | return nil, nil, fmt.Errorf("nameAlg %v is not available", public.NameAlg) 71 | } 72 | 73 | pub := public.Public() 74 | switch p := pub.(type) { 75 | case *rsa.PublicKey: 76 | if public.Params.RSADetail.Scheme.Scheme != RSASchemeNull && 77 | public.Params.RSADetail.Scheme.Scheme != RSASchemeOAEP { 78 | return nil, nil, fmt.Errorf("unsupported RSA scheme: %v", public.Params.RSADetail.Scheme.Scheme) 79 | } 80 | case *ecdsa.PublicKey: 81 | if p.Curve == nil { 82 | return nil, nil, fmt.Errorf("unsupported curve: %v", public.Params.ECCDetail.CurveID.GoCurve()) 83 | } 84 | } 85 | 86 | return internal_crypt.SecretEncrypt(rand.Reader, pub, public.NameAlg.GetHash(), label) 87 | } 88 | -------------------------------------------------------------------------------- /crypto/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | /* 6 | Package crypto is deprecated and should't be used - use cryptutil instead. 7 | */ 8 | package crypto 9 | -------------------------------------------------------------------------------- /crypto/kdf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypto 6 | 7 | import ( 8 | "crypto" 9 | 10 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 11 | ) 12 | 13 | // KDFa performs key derivation using the counter mode described in SP800-108 14 | // and HMAC as the PRF. 15 | // 16 | // This will panic if hashAlg is not available. 17 | // 18 | // Deprecated: Use [github.com/canonical/go-tpm2/cryptutil.KDFa]. 19 | func KDFa(hashAlg crypto.Hash, key, label, contextU, contextV []byte, sizeInBits int) []byte { 20 | return internal_crypt.KDFa(hashAlg, key, label, contextU, contextV, sizeInBits) 21 | } 22 | 23 | // KDFe performs key derivation using the "Concatenation Key Derivation Function 24 | // (Approved Alternative 1) in the original version of SP800-56A. 25 | // 26 | // This will panic if hashAlg is not available. 27 | // 28 | // Deprecated: Use [github.com/canonical/go-tpm2/cryptutil.KDFe]. 29 | func KDFe(hashAlg crypto.Hash, z, label, partyUInfo, partyVInfo []byte, sizeInBits int) []byte { 30 | return internal_crypt.KDFe(hashAlg, z, label, partyUInfo, partyVInfo, sizeInBits) 31 | } 32 | -------------------------------------------------------------------------------- /crypto/secret.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypto 6 | 7 | import ( 8 | "crypto" 9 | "crypto/rand" 10 | 11 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 12 | ) 13 | 14 | // SecretDecrypt recovers a seed from the supplied secret structure using the supplied private key. 15 | // It can be used to recover secrets created by the TPM, such as those created by the 16 | // TPM2_Duplicate command. 17 | // 18 | // If priv is a *[rsa.PrivateKey], this will recover the seed by decrypting the supplied secret 19 | // with RSA-OAEP. 20 | // 21 | // If priv is a *[ecdsa.PrivateKey], this uses ECDH to derive the seed using the supplied secret, 22 | // which will contain a serialized ephemeral peer key. 23 | // 24 | // The specified digest algorithm must match the name algorithm of the public area associated with 25 | // the supplied private key. 26 | // 27 | // This will panic if hashAlg is not available. 28 | // 29 | // Deprecated: Use [github.com/canonical/go-tpm2/cryptutil.SecretDecrypt]. 30 | func SecretDecrypt(priv crypto.PrivateKey, hashAlg crypto.Hash, label, secret []byte) (seed []byte, err error) { 31 | return internal_crypt.SecretDecrypt(priv, hashAlg, label, secret) 32 | } 33 | 34 | // SecretEncrypt establishes a seed and associated secret value using the supplied public key and 35 | // digest algorithm. The corresponding private key can recover the seed from the returned secret 36 | // value. This is useful for sharing secrets with the TPM via the TPM2_Import, 37 | // TPM2_ActivateCredential and TPM2_StartAuthSession commands. 38 | // 39 | // If public is a *[rsa.PublicKey], this will generate a random seed and then RSA-OAEP encrypt it 40 | // to create the secret. 41 | // 42 | // If public is a *[ecdsa.PublicKey], this uses ECDH to derive a seed value using an an ephemeral 43 | // key. The secret contains the serialized form of the public part of the ephemeral key. 44 | // 45 | // The supplied digest algorithm must match the name algorithm of the public area associated with 46 | // the supplied public key. 47 | // 48 | // This will panic if hashAlg is not available. 49 | // 50 | // Deprecated: Use [github.com/canonical/go-tpm2/cryptutil.SecretEncrypt]. 51 | func SecretEncrypt(public crypto.PublicKey, hashAlg crypto.Hash, label []byte) (secret []byte, seed []byte, err error) { 52 | return internal_crypt.SecretEncrypt(rand.Reader, public, hashAlg, label) 53 | } 54 | -------------------------------------------------------------------------------- /crypto/symmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypto 6 | 7 | import ( 8 | "crypto/cipher" 9 | 10 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 11 | ) 12 | 13 | type SymmetricAlgorithm interface { 14 | NewCipher(key []byte) (cipher.Block, error) 15 | } 16 | 17 | // SymmetricEncrypt performs in place symmetric encryption of the supplied 18 | // data with the supplied cipher using CFB mode. 19 | // 20 | // Deprecated: Use [cipher.Block] and [cipher.Stream] directly. 21 | func SymmetricEncrypt(alg SymmetricAlgorithm, key, iv, data []byte) error { 22 | return internal_crypt.SymmetricEncrypt(alg, key, iv, data) 23 | } 24 | 25 | // SymmetricDecrypt performs in place symmetric decryption of the supplied 26 | // data with the supplied cipher using CFB mode. 27 | // 28 | // Deprecated: Use [cipher.Block] and [cipher.Stream] directly. 29 | func SymmetricDecrypt(alg SymmetricAlgorithm, key, iv, data []byte) error { 30 | return internal_crypt.SymmetricDecrypt(alg, key, iv, data) 31 | } 32 | -------------------------------------------------------------------------------- /crypto/xor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypto 6 | 7 | import ( 8 | "crypto" 9 | 10 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 11 | ) 12 | 13 | // XORObfuscation performs XOR obfuscation as described in part 1 of the TPM 14 | // library specification. 15 | // 16 | // This will panic if hashAlg is not available. 17 | // 18 | // Deprecated: 19 | func XORObfuscation(hashAlg crypto.Hash, key []byte, contextU, contextV, data []byte) { 20 | internal_crypt.XORObfuscation(hashAlg, key, contextU, contextV, data) 21 | } 22 | -------------------------------------------------------------------------------- /cryptutil/crypt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package cryptutil_test 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "testing" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2/testutil" 16 | ) 17 | 18 | func init() { 19 | testutil.AddCommandLineFlags() 20 | } 21 | 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | func TestMain(m *testing.M) { 25 | flag.Parse() 26 | os.Exit(func() int { 27 | if testutil.TPMBackend == testutil.TPMBackendMssim { 28 | simulatorCleanup, err := testutil.LaunchTPMSimulator(nil) 29 | if err != nil { 30 | fmt.Fprintf(os.Stderr, "Cannot launch TPM simulator: %v\n", err) 31 | return 1 32 | } 33 | defer simulatorCleanup() 34 | } 35 | 36 | return m.Run() 37 | }()) 38 | } 39 | -------------------------------------------------------------------------------- /cryptutil/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | /* 6 | Package cryptutil contains some cryptographic functions that are useful when using go-tpm2. 7 | */ 8 | package cryptutil 9 | -------------------------------------------------------------------------------- /cryptutil/kdf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package cryptutil 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 10 | ) 11 | 12 | // KDFa performs key derivation using the counter mode described in SP800-108 13 | // and HMAC as the PRF. 14 | // 15 | // This will panic if hashAlg is not available. 16 | func KDFa(hashAlg tpm2.HashAlgorithmId, key, label, contextU, contextV []byte, sizeInBits int) []byte { 17 | return internal_crypt.KDFa(hashAlg.GetHash(), key, label, contextU, contextV, sizeInBits) 18 | } 19 | 20 | // KDFe performs key derivation using the "Concatenation Key Derivation Function 21 | // (Approved Alternative 1) in the original version of SP800-56A. 22 | // 23 | // This will panic if hashAlg is not available. 24 | func KDFe(hashAlg tpm2.HashAlgorithmId, z, label, partyUInfo, partyVInfo []byte, sizeInBits int) []byte { 25 | return internal_crypt.KDFe(hashAlg.GetHash(), z, label, partyUInfo, partyVInfo, sizeInBits) 26 | } 27 | -------------------------------------------------------------------------------- /cryptutil/secret.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package cryptutil 6 | 7 | import ( 8 | "crypto" 9 | "crypto/ecdsa" 10 | "crypto/rsa" 11 | "errors" 12 | "io" 13 | 14 | "github.com/canonical/go-tpm2" 15 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 16 | ) 17 | 18 | // SecretDecrypt recovers a seed from the supplied secret structure using the supplied private key. 19 | // It can be used to recover secrets created by the TPM, such as those created by the 20 | // TPM2_Duplicate command. 21 | // 22 | // If priv is a *[rsa.PrivateKey], this will recover the seed by decrypting the supplied secret 23 | // with RSA-OAEP. 24 | // 25 | // If priv is a *[ecdsa.PrivateKey], this uses ECDH to derive the seed using the supplied secret, 26 | // which will contain a serialized ephemeral peer key. 27 | // 28 | // The specified digest algorithm must match the name algorithm of the public area associated with 29 | // the supplied private key. 30 | func SecretDecrypt(priv crypto.PrivateKey, hashAlg tpm2.HashAlgorithmId, label, secret []byte) (seed []byte, err error) { 31 | if !hashAlg.Available() { 32 | return nil, errors.New("digest algorithm is not available") 33 | } 34 | return internal_crypt.SecretDecrypt(priv, hashAlg.GetHash(), label, secret) 35 | } 36 | 37 | // SecretEncrypt establishes a seed and associated secret value using the supplied public key. The 38 | // corresponding private key can recover the seed from the returned secret value. This is useful 39 | // for sharing secrets with the TPM via the TPM2_Import, TPM2_ActivateCredential and 40 | // TPM2_StartAuthSession commands. 41 | // 42 | // If public has the type [tpm2.ObjectTypeRSA], this will generate a random seed and then RSA-OAEP 43 | // encrypt it to create the secret. 44 | // 45 | // If public has the type [tpm2.ObjectTypeECC], this uses ECDH to derive a seed value using an an 46 | // ephemeral key. The secret contains the serialized form of the public part of the ephemeral key. 47 | func SecretEncrypt(rand io.Reader, public *tpm2.Public, label []byte) (secret tpm2.EncryptedSecret, seed []byte, err error) { 48 | if !public.NameAlg.Available() { 49 | return nil, nil, errors.New("digest algorithm is not available") 50 | } 51 | 52 | pub := public.Public() 53 | switch p := pub.(type) { 54 | case *rsa.PublicKey: 55 | if public.Params.RSADetail.Scheme.Scheme != tpm2.RSASchemeNull && 56 | public.Params.RSADetail.Scheme.Scheme != tpm2.RSASchemeOAEP { 57 | return nil, nil, errors.New("unsupported RSA scheme") 58 | } 59 | case *ecdsa.PublicKey: 60 | if p.Curve == nil { 61 | return nil, nil, errors.New("unsupported curve") 62 | } 63 | } 64 | 65 | return internal_crypt.SecretEncrypt(rand, pub, public.NameAlg.GetHash(), label) 66 | } 67 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package tpm2 implements an API for communicating with TPM 2.0 devices. 3 | 4 | This documentation refers to TPM commands and types that are described in 5 | more detail in the [TPM 2.0 Library Specification]. Knowledge of this 6 | specification is assumed in this documentation. 7 | 8 | Communication with Linux TPM character devices and TPM simulators 9 | implementing the Microsoft TPM2 simulator interface is supported. 10 | 11 | The core type by which consumers of this package communicate with a TPM is 12 | [TPMContext]. 13 | 14 | [TPM 2.0 Library Specification]: https://trustedcomputinggroup.org/resource/tpm-library-specification/ 15 | */ 16 | package tpm2 17 | -------------------------------------------------------------------------------- /export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | import ( 8 | "bytes" 9 | "crypto/rand" 10 | "io" 11 | 12 | "github.com/canonical/go-tpm2/mu" 13 | ) 14 | 15 | type CmdContext = cmdContext 16 | type NvIndexContextImpl = nvIndexContext 17 | type RspContext = rspContext 18 | type SessionContextData = sessionContextData 19 | type SessionContextImpl = sessionContext // We already have a SessionContext interface 20 | type SessionParam = sessionParam 21 | type SessionParams = sessionParams 22 | 23 | var ComputeBindName = computeBindName 24 | var FormatString = formatString 25 | var NewExtraSessionParam = newExtraSessionParam 26 | var NewSessionParamForAuth = newSessionParamForAuth 27 | var NewSessionParams = newSessionParams 28 | var NullResource = nullResource 29 | var PwSession = pwSession 30 | 31 | func (c *CommandContext) Cmd() *CmdContext { 32 | return &c.cmd 33 | } 34 | 35 | func (c *NvIndexContextImpl) Public() *NVPublic { 36 | return c.Data.NV 37 | } 38 | 39 | func (c *ResponseContext) Dispatcher() commandDispatcher { 40 | return c.dispatcher 41 | } 42 | 43 | func (c *ResponseContext) Rsp() *RspContext { 44 | return c.rsp 45 | } 46 | 47 | func Canonicalize(vals ...interface{}) error { 48 | b := new(bytes.Buffer) 49 | if _, err := mu.MarshalToWriter(b, vals...); err != nil { 50 | return err 51 | } 52 | _, err := mu.UnmarshalFromReader(b, vals...) 53 | return err 54 | } 55 | 56 | func MockRandReader(r io.Reader) (restore func()) { 57 | orig := rand.Reader 58 | rand.Reader = r 59 | return func() { 60 | rand.Reader = orig 61 | } 62 | } 63 | 64 | func NewMockCommandContext(dispatcher commandDispatcher, cmd *CmdContext) *CommandContext { 65 | c := &CommandContext{dispatcher: dispatcher} 66 | if cmd != nil { 67 | c.cmd = *cmd 68 | } 69 | return c 70 | } 71 | 72 | func NewMockResponseContext(dispatcher commandDispatcher, rsp *RspContext) *ResponseContext { 73 | return &ResponseContext{ 74 | dispatcher: dispatcher, 75 | rsp: rsp} 76 | } 77 | 78 | func NewResourceUnavailableError(handle Handle, err error) *ResourceUnavailableError { 79 | return &ResourceUnavailableError{Handle: handle, err: err} 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/canonical/go-tpm2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/canonical/go-efilib v1.6.0 7 | github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9 8 | github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e 9 | golang.org/x/crypto v0.9.0 10 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c 11 | gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 12 | ) 13 | 14 | require ( 15 | github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect 16 | github.com/kr/pretty v0.2.1 // indirect 17 | github.com/kr/text v0.1.0 // indirect 18 | github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 // indirect 19 | golang.org/x/net v0.10.0 // indirect 20 | golang.org/x/sys v0.8.0 // indirect 21 | gopkg.in/yaml.v2 v2.3.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/canonical/go-efilib v1.5.0 h1:6Igh42xng23i9IJ992KsOHf/TE74VGGPZ1iW0JeRxSA= 2 | github.com/canonical/go-efilib v1.5.0/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= 3 | github.com/canonical/go-efilib v1.6.0 h1:0ZFcIclzoMBl6sAaHrSO98YVPUvKAPfXm6kjofhlHHw= 4 | github.com/canonical/go-efilib v1.6.0/go.mod h1:n0Ttsy1JuHAvqaFbZBs6PAzoiiJdfkHsAmDOEbexYEQ= 5 | github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9 h1:Twk1ZSTWRClfGShP16ePf2JIiayqWS4ix1rkAR6baag= 6 | github.com/canonical/go-kbkdf v0.0.0-20250104172618-3b1308f9acf9/go.mod h1:IneQ5/yQcfPXrGekEXpR6yeea55ZD24N5+kHzeDseOM= 7 | github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= 8 | github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= 9 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 10 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraKSLxt2awQ42zofkP+NKh/VjQ0PjIMk/y4= 15 | github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= 16 | github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e h1:vqDZWKPBL9RKPA8KyOuTaSuXXlmRwn/ndvOkZaAmLJs= 17 | github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8= 18 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 19 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 20 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 21 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 22 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 23 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 26 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 27 | gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs= 28 | gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk= 29 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 30 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 31 | -------------------------------------------------------------------------------- /internal/crypt/crypt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypt_test 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2/testutil" 9 | ) 10 | 11 | func init() { 12 | testutil.AddCommandLineFlags() 13 | } 14 | -------------------------------------------------------------------------------- /internal/crypt/kdf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypt 6 | 7 | import ( 8 | "bytes" 9 | "crypto" 10 | "encoding/binary" 11 | 12 | kbkdf "github.com/canonical/go-kbkdf" 13 | "github.com/canonical/go-kbkdf/hmac_prf" 14 | ) 15 | 16 | // KDFa performs key derivation using the counter mode described in SP800-108 17 | // and HMAC as the PRF. 18 | // 19 | // This will panic if hashAlg is not available. 20 | func KDFa(hashAlg crypto.Hash, key, label, contextU, contextV []byte, sizeInBits int) []byte { 21 | context := make([]byte, len(contextU)+len(contextV)) 22 | copy(context, contextU) 23 | copy(context[len(contextU):], contextV) 24 | 25 | outKey := kbkdf.CounterModeKey(hmac_prf.From(hashAlg), key, label, context, uint32(sizeInBits)) 26 | 27 | if sizeInBits%8 != 0 { 28 | outKey[0] &= ((1 << uint(sizeInBits%8)) - 1) 29 | } 30 | return outKey 31 | } 32 | 33 | // KDFe performs key derivation using the "Concatenation Key Derivation Function 34 | // (Approved Alternative 1) in the original version of SP800-56A. 35 | // 36 | // This will panic if hashAlg is not available. 37 | func KDFe(hashAlg crypto.Hash, z, label, partyUInfo, partyVInfo []byte, sizeInBits int) []byte { 38 | digestSize := hashAlg.Size() 39 | 40 | counter := 0 41 | var res bytes.Buffer 42 | 43 | for bytes := (sizeInBits + 7) / 8; bytes > 0; bytes -= digestSize { 44 | if bytes < digestSize { 45 | digestSize = bytes 46 | } 47 | counter++ 48 | 49 | h := hashAlg.New() 50 | 51 | binary.Write(h, binary.BigEndian, uint32(counter)) 52 | h.Write(z) 53 | h.Write(label) 54 | h.Write([]byte{0}) 55 | h.Write(partyUInfo) 56 | h.Write(partyVInfo) 57 | 58 | res.Write(h.Sum(nil)[0:digestSize]) 59 | } 60 | 61 | outKey := res.Bytes() 62 | 63 | if sizeInBits%8 != 0 { 64 | outKey[0] &= ((1 << uint(sizeInBits%8)) - 1) 65 | } 66 | return outKey 67 | } 68 | -------------------------------------------------------------------------------- /internal/crypt/secret.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypt 6 | 7 | import ( 8 | "crypto" 9 | "crypto/ecdsa" 10 | "crypto/elliptic" 11 | "crypto/rsa" 12 | "errors" 13 | "fmt" 14 | "io" 15 | "math/big" 16 | 17 | "github.com/canonical/go-tpm2/mu" 18 | ) 19 | 20 | type eccPoint struct { 21 | X []byte 22 | Y []byte 23 | } 24 | 25 | func zeroExtendBytes(x *big.Int, l int) (out []byte) { 26 | out = make([]byte, l) 27 | tmp := x.Bytes() 28 | copy(out[len(out)-len(tmp):], tmp) 29 | return 30 | } 31 | 32 | // SecretDecrypt recovers a seed from the supplied secret structure using the supplied private key. 33 | // It can be used to recover secrets created by the TPM, such as those created by the 34 | // TPM2_Duplicate command. 35 | // 36 | // If priv is a *[rsa.PrivateKey], this will recover the seed by decrypting the supplied secret 37 | // with RSA-OAEP. 38 | // 39 | // If priv is a *[ecdsa.PrivateKey], this uses ECDH to derive the seed using the supplied secret, 40 | // which will contain a serialized ephemeral peer key. 41 | // 42 | // The specified digest algorithm must match the name algorithm of the public area associated with 43 | // the supplied private key. 44 | // 45 | // This will panic if hashAlg is not available. 46 | func SecretDecrypt(priv crypto.PrivateKey, hashAlg crypto.Hash, label, secret []byte) (seed []byte, err error) { 47 | switch p := priv.(type) { 48 | case *rsa.PrivateKey: 49 | h := hashAlg.New() 50 | label0 := make([]byte, len(label)+1) 51 | copy(label0, label) 52 | return rsa.DecryptOAEP(h, nil, p, secret, label0) 53 | case *ecdsa.PrivateKey: 54 | var ephPoint eccPoint 55 | if _, err := mu.UnmarshalFromBytes(secret, &ephPoint); err != nil { 56 | return nil, fmt.Errorf("cannot unmarshal ephemeral point: %w", err) 57 | } 58 | ephX := new(big.Int).SetBytes(ephPoint.X) 59 | ephY := new(big.Int).SetBytes(ephPoint.Y) 60 | 61 | if !p.Curve.IsOnCurve(ephX, ephY) { 62 | return nil, errors.New("ephemeral point is not on curve") 63 | } 64 | 65 | sz := p.Curve.Params().BitSize / 8 66 | 67 | mulX, _ := p.Curve.ScalarMult(ephX, ephY, p.D.Bytes()) 68 | return KDFe(hashAlg, zeroExtendBytes(mulX, sz), label, 69 | ephPoint.X, zeroExtendBytes(p.X, sz), hashAlg.Size()*8), nil 70 | default: 71 | return nil, errors.New("unsupported key type") 72 | } 73 | } 74 | 75 | // SecretEncrypt establishes a seed and associated secret value using the supplied public key and 76 | // digest algorithm. The corresponding private key can recover the seed from the returned secret 77 | // value. This is useful for sharing secrets with the TPM via the TPM2_Import, 78 | // TPM2_ActivateCredential and TPM2_StartAuthSession commands. 79 | // 80 | // If public is a *[rsa.PublicKey], this will generate a random seed and then RSA-OAEP encrypt it 81 | // to create the secret. 82 | // 83 | // If public is a *[ecdsa.PublicKey], this uses ECDH to derive a seed value using an an ephemeral 84 | // key. The secret contains the serialized form of the public part of the ephemeral key. 85 | // 86 | // The supplied digest algorithm must match the name algorithm of the public area associated with 87 | // the supplied public key. 88 | // 89 | // This will panic if hashAlg is not available. 90 | func SecretEncrypt(rand io.Reader, public crypto.PublicKey, hashAlg crypto.Hash, label []byte) (secret []byte, seed []byte, err error) { 91 | digestSize := hashAlg.Size() 92 | 93 | switch p := public.(type) { 94 | case *rsa.PublicKey: 95 | secret := make([]byte, digestSize) 96 | if _, err := rand.Read(secret); err != nil { 97 | return nil, nil, fmt.Errorf("cannot read random bytes for secret: %v", err) 98 | } 99 | 100 | h := hashAlg.New() 101 | label0 := make([]byte, len(label)+1) 102 | copy(label0, label) 103 | encryptedSecret, err := rsa.EncryptOAEP(h, rand, p, secret, label0) 104 | return encryptedSecret, secret, err 105 | case *ecdsa.PublicKey: 106 | if p.Curve == nil { 107 | return nil, nil, errors.New("no curve") 108 | } 109 | if !p.Curve.IsOnCurve(p.X, p.Y) { 110 | return nil, nil, errors.New("public key is not on curve") 111 | } 112 | 113 | ephPriv, ephX, ephY, err := elliptic.GenerateKey(p.Curve, rand) 114 | if err != nil { 115 | return nil, nil, fmt.Errorf("cannot generate ephemeral ECC key: %v", err) 116 | } 117 | 118 | sz := p.Curve.Params().BitSize / 8 119 | 120 | encryptedSecret := mu.MustMarshalToBytes(&eccPoint{ 121 | X: zeroExtendBytes(ephX, sz), 122 | Y: zeroExtendBytes(ephY, sz)}) 123 | 124 | mulX, _ := p.Curve.ScalarMult(p.X, p.Y, ephPriv) 125 | secret := KDFe(hashAlg, zeroExtendBytes(mulX, sz), label, zeroExtendBytes(ephX, sz), 126 | zeroExtendBytes(p.X, sz), digestSize*8) 127 | return encryptedSecret, secret, nil 128 | default: 129 | return nil, nil, errors.New("unsupported key type") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /internal/crypt/symmetric.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypt 6 | 7 | import ( 8 | "crypto/cipher" 9 | "fmt" 10 | ) 11 | 12 | type SymmetricAlgorithm interface { 13 | NewCipher(key []byte) (cipher.Block, error) 14 | } 15 | 16 | // SymmetricEncrypt performs in place symmetric encryption of the supplied 17 | // data with the supplied cipher using CFB mode. 18 | func SymmetricEncrypt(alg SymmetricAlgorithm, key, iv, data []byte) error { 19 | c, err := alg.NewCipher(key) 20 | if err != nil { 21 | return fmt.Errorf("cannot create cipher: %w", err) 22 | } 23 | // The TPM uses CFB cipher mode for all secret sharing 24 | s := cipher.NewCFBEncrypter(c, iv) 25 | s.XORKeyStream(data, data) 26 | return nil 27 | } 28 | 29 | // SymmetricDecrypt performs in place symmetric decryption of the supplied 30 | // data with the supplied cipher using CFB mode. 31 | func SymmetricDecrypt(alg SymmetricAlgorithm, key, iv, data []byte) error { 32 | c, err := alg.NewCipher(key) 33 | if err != nil { 34 | return fmt.Errorf("cannot create cipher: %w", err) 35 | } 36 | // The TPM uses CFB cipher mode for all secret sharing 37 | s := cipher.NewCFBDecrypter(c, iv) 38 | s.XORKeyStream(data, data) 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/crypt/xor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypt 6 | 7 | import ( 8 | "crypto" 9 | ) 10 | 11 | // XORObfuscation performs XOR obfuscation as described in part 1 of the TPM 12 | // library specification. 13 | // 14 | // This will panic if hashAlg is not available. 15 | func XORObfuscation(hashAlg crypto.Hash, key []byte, contextU, contextV, data []byte) { 16 | dataSize := len(data) 17 | mask := KDFa(hashAlg, key, []byte("XOR"), contextU, contextV, dataSize*8) 18 | for i := 0; i < dataSize; i++ { 19 | data[i] ^= mask[i] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal/crypt/xor_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package crypt_test 6 | 7 | import ( 8 | "bytes" 9 | "crypto" 10 | "crypto/rand" 11 | _ "crypto/sha1" 12 | _ "crypto/sha256" 13 | "testing" 14 | 15 | . "github.com/canonical/go-tpm2/internal/crypt" 16 | ) 17 | 18 | func TestXORObfuscation(t *testing.T) { 19 | for _, data := range []struct { 20 | desc string 21 | keyLength int 22 | alg crypto.Hash 23 | data []byte 24 | }{ 25 | { 26 | desc: "SHA256/1", 27 | keyLength: 32, 28 | alg: crypto.SHA256, 29 | data: []byte("secret data"), 30 | }, 31 | { 32 | desc: "SHA256/2", 33 | keyLength: 60, 34 | alg: crypto.SHA256, 35 | data: []byte("super secret data"), 36 | }, 37 | { 38 | desc: "SHA1/1", 39 | keyLength: 60, 40 | alg: crypto.SHA1, 41 | data: []byte("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"), 42 | }, 43 | } { 44 | t.Run(data.desc, func(t *testing.T) { 45 | key := make([]byte, data.keyLength) 46 | rand.Read(key) 47 | 48 | digestSize := data.alg.Size() 49 | 50 | contextU := make([]byte, digestSize) 51 | rand.Read(contextU) 52 | 53 | contextV := make([]byte, digestSize) 54 | rand.Read(contextV) 55 | 56 | var secret []byte 57 | secret = append(secret, data.data...) 58 | 59 | XORObfuscation(data.alg, key, contextU, contextV, secret) 60 | XORObfuscation(data.alg, key, contextU, contextV, secret) 61 | 62 | if !bytes.Equal(secret, data.data) { 63 | t.Errorf("Encrypt / decrypt with XOR obfuscation didn't produce the original data") 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /internal/ppi/ppi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package ppi 6 | 7 | import ( 8 | "sync" 9 | 10 | "github.com/canonical/go-tpm2" 11 | "github.com/canonical/go-tpm2/ppi" 12 | ) 13 | 14 | type PPIBackend interface { 15 | SubmitOperation(op ppi.OperationId, arg *uint32) error 16 | StateTransitionAction() (ppi.StateTransitionAction, error) 17 | OperationStatus(op ppi.OperationId) (ppi.OperationStatus, error) 18 | OperationResponse() (*ppi.OperationResponse, error) 19 | } 20 | 21 | type PPI struct { 22 | ppiType ppi.Type 23 | version ppi.Version 24 | functions PPIBackend 25 | 26 | staOnce sync.Once 27 | sta ppi.StateTransitionAction 28 | staError error 29 | 30 | ops map[ppi.OperationId]ppi.OperationStatus 31 | 32 | rspOnce sync.Once 33 | rsp *ppi.OperationResponse 34 | rspError error 35 | } 36 | 37 | func New(ppiType ppi.Type, version ppi.Version, functions PPIBackend) *PPI { 38 | return &PPI{ 39 | ppiType: ppiType, 40 | version: version, 41 | functions: functions, 42 | ops: make(map[ppi.OperationId]ppi.OperationStatus), 43 | } 44 | } 45 | 46 | func (p *PPI) submitOperation(op ppi.OperationId) error { 47 | return p.functions.SubmitOperation(op, nil) 48 | } 49 | 50 | func (p *PPI) Type() ppi.Type { 51 | return p.ppiType 52 | } 53 | 54 | func (p *PPI) Version() ppi.Version { 55 | return p.version 56 | } 57 | 58 | func (p *PPI) StateTransitionAction() (ppi.StateTransitionAction, error) { 59 | p.staOnce.Do(func() { 60 | p.sta, p.staError = p.functions.StateTransitionAction() 61 | }) 62 | return p.sta, p.staError 63 | } 64 | 65 | func (p *PPI) OperationStatus(op ppi.OperationId) (ppi.OperationStatus, error) { 66 | status, exists := p.ops[op] 67 | if exists { 68 | return status, nil 69 | } 70 | status, err := p.functions.OperationStatus(op) 71 | if err != nil { 72 | return 0, err 73 | } 74 | p.ops[op] = status 75 | return status, nil 76 | } 77 | 78 | func (p *PPI) EnableTPM() error { 79 | return p.submitOperation(ppi.OperationEnableTPM) 80 | } 81 | 82 | func (p *PPI) DisableTPM() error { 83 | return p.submitOperation(ppi.OperationDisableTPM) 84 | } 85 | 86 | func (p *PPI) ClearTPM() error { 87 | return p.submitOperation(ppi.OperationClearTPM) 88 | } 89 | 90 | func (p *PPI) EnableAndClearTPM() error { 91 | return p.submitOperation(ppi.OperationEnableAndClearTPM) 92 | } 93 | 94 | func (p *PPI) SetPCRBanks(algs ...tpm2.HashAlgorithmId) error { 95 | bits := ppi.MakeHashAlgorithms(algs...) 96 | return p.functions.SubmitOperation(ppi.OperationSetPCRBanks, (*uint32)(&bits)) 97 | } 98 | 99 | func (p *PPI) ChangeEPS() error { 100 | return p.submitOperation(ppi.OperationChangeEPS) 101 | } 102 | 103 | func (p *PPI) LogAllDigests() error { 104 | return p.submitOperation(ppi.OperationLogAllDigests) 105 | } 106 | 107 | func (p *PPI) DisableEndorsementAndEnableStorageHierarchy() error { 108 | return p.submitOperation(ppi.OperationDisableEndorsementEnableStorageHierarchy) 109 | } 110 | 111 | func (p *PPI) SetPPRequiredForOperation(op ppi.OperationId) error { 112 | op = op.SetPPRequiredOperationId() 113 | if op == ppi.NoOperation { 114 | return ppi.ErrOperationUnsupported 115 | } 116 | return p.submitOperation(op) 117 | } 118 | 119 | func (p *PPI) ClearPPRequiredForOperation(op ppi.OperationId) error { 120 | op = op.ClearPPRequiredOperationId() 121 | if op == ppi.NoOperation { 122 | return ppi.ErrOperationUnsupported 123 | } 124 | return p.submitOperation(op) 125 | } 126 | 127 | func (p *PPI) OperationResponse() (*ppi.OperationResponse, error) { 128 | p.rspOnce.Do(func() { 129 | p.rsp, p.rspError = p.functions.OperationResponse() 130 | }) 131 | return p.rsp, p.rspError 132 | } 133 | -------------------------------------------------------------------------------- /internal/ppi_efi/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package ppi_efi 6 | 7 | type ( 8 | EfiPpiImpl = efiPpiImpl 9 | PhysicalPresence = physicalPresence 10 | PhysicalPresenceConfig = physicalPresenceConfig 11 | PhysicalPresenceFlags = physicalPresenceFlags 12 | ) 13 | 14 | var ( 15 | ReadPhysicalPresence = readPhysicalPresence 16 | ) 17 | -------------------------------------------------------------------------------- /internal/testutil/testutil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/canonical/go-tpm2/testutil" 11 | 12 | . "gopkg.in/check.v1" 13 | ) 14 | 15 | func init() { 16 | testutil.AddCommandLineFlags() 17 | } 18 | 19 | func Test(t *testing.T) { TestingT(t) } 20 | -------------------------------------------------------------------------------- /internal/testutil/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil 6 | 7 | import ( 8 | "encoding/hex" 9 | "testing" 10 | 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | // DecodeHexString decodes the supplied hex string in to a byte slice. 15 | func DecodeHexString(c *C, s string) []byte { 16 | b, err := hex.DecodeString(s) 17 | c.Assert(err, IsNil) 18 | return b 19 | } 20 | 21 | // DecodeHexStringT decodes the supplied hex string in to a byte slice. 22 | func DecodeHexStringT(t *testing.T, s string) []byte { 23 | b, err := hex.DecodeString(s) 24 | if err != nil { 25 | t.Fatalf("%v", err) 26 | } 27 | return b 28 | } 29 | -------------------------------------------------------------------------------- /internal/util/object.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "bytes" 9 | "crypto/hmac" 10 | "crypto/rand" 11 | "errors" 12 | "fmt" 13 | "hash" 14 | "io" 15 | 16 | "github.com/canonical/go-tpm2" 17 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 18 | "github.com/canonical/go-tpm2/mu" 19 | ) 20 | 21 | // UnwrapOuter removes an outer wrapper from the supplied sensitive data blob. The 22 | // supplied name is associated with the data. 23 | // 24 | // It checks the integrity HMAC is valid using the specified digest algorithm and 25 | // a key derived from the supplied seed and returns an error if the check fails. 26 | // 27 | // It then decrypts the data blob using the specified symmetric algorithm and a 28 | // key derived from the supplied seed and name. 29 | func UnwrapOuter(hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, name tpm2.Name, seed []byte, useIV bool, data []byte) ([]byte, error) { 30 | if !hashAlg.Available() { 31 | return nil, errors.New("digest algorithm is not available") 32 | } 33 | if symmetricAlg == nil || !symmetricAlg.Algorithm.IsValidBlockCipher() { 34 | return nil, errors.New("symmetric algorithm is not a valid block cipher") 35 | } 36 | 37 | r := bytes.NewReader(data) 38 | 39 | var integrity []byte 40 | if _, err := mu.UnmarshalFromReader(r, &integrity); err != nil { 41 | return nil, fmt.Errorf("cannot unmarshal integrity digest: %w", err) 42 | } 43 | 44 | data, _ = io.ReadAll(r) 45 | 46 | hmacKey := internal_crypt.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.IntegrityKey), nil, nil, hashAlg.Size()*8) 47 | h := hmac.New(func() hash.Hash { return hashAlg.NewHash() }, hmacKey) 48 | h.Write(data) 49 | h.Write(name) 50 | 51 | if !bytes.Equal(h.Sum(nil), integrity) { 52 | return nil, errors.New("integrity digest is invalid") 53 | } 54 | 55 | r = bytes.NewReader(data) 56 | 57 | iv := make([]byte, symmetricAlg.Algorithm.BlockSize()) 58 | if useIV { 59 | if _, err := mu.UnmarshalFromReader(r, &iv); err != nil { 60 | return nil, fmt.Errorf("cannot unmarshal IV: %w", err) 61 | } 62 | if len(iv) != symmetricAlg.Algorithm.BlockSize() { 63 | return nil, errors.New("IV has the wrong size") 64 | } 65 | } 66 | 67 | data, _ = io.ReadAll(r) 68 | 69 | symKey := internal_crypt.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.StorageKey), name, nil, int(symmetricAlg.KeyBits.Sym)) 70 | 71 | if err := internal_crypt.SymmetricDecrypt(symmetricAlg.Algorithm, symKey, iv, data); err != nil { 72 | return nil, fmt.Errorf("cannot decrypt: %w", err) 73 | } 74 | 75 | return data, nil 76 | } 77 | 78 | // ProduceOuterWrap adds an outer wrapper to the supplied data. The supplied name 79 | // is associated with the data. 80 | // 81 | // It encrypts the data using the specified symmetric algorithm and a key derived 82 | // from the supplied seed and name. 83 | // 84 | // It then prepends an integrity HMAC of the encrypted data and the supplied 85 | // name using the specified digest algorithm and a key derived from the supplied 86 | // seed. 87 | func ProduceOuterWrap(hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, name tpm2.Name, seed []byte, useIV bool, data []byte) ([]byte, error) { 88 | if !hashAlg.Available() { 89 | return nil, errors.New("digest algorithm is not available") 90 | } 91 | if symmetricAlg == nil || !symmetricAlg.Algorithm.IsValidBlockCipher() { 92 | return nil, errors.New("symmetric algorithm is not a valid block cipher") 93 | } 94 | 95 | iv := make([]byte, symmetricAlg.Algorithm.BlockSize()) 96 | if useIV { 97 | if _, err := rand.Read(iv); err != nil { 98 | return nil, fmt.Errorf("cannot generate IV: %w", err) 99 | } 100 | } 101 | 102 | symKey := internal_crypt.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.StorageKey), name, nil, int(symmetricAlg.KeyBits.Sym)) 103 | 104 | if err := internal_crypt.SymmetricEncrypt(symmetricAlg.Algorithm, symKey, iv, data); err != nil { 105 | return nil, fmt.Errorf("cannot encrypt: %w", err) 106 | } 107 | 108 | if useIV { 109 | data = mu.MustMarshalToBytes(iv, mu.RawBytes(data)) 110 | } 111 | 112 | hmacKey := internal_crypt.KDFa(hashAlg.GetHash(), seed, []byte(tpm2.IntegrityKey), nil, nil, hashAlg.Size()*8) 113 | h := hmac.New(func() hash.Hash { return hashAlg.NewHash() }, hmacKey) 114 | h.Write(data) 115 | h.Write(name) 116 | 117 | integrity := h.Sum(nil) 118 | 119 | return mu.MustMarshalToBytes(integrity, mu.RawBytes(data)), nil 120 | } 121 | -------------------------------------------------------------------------------- /linux/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package linux 6 | 7 | import efi "github.com/canonical/go-efilib" 8 | 9 | var NewAcpiPpi = newAcpiPpi 10 | 11 | func MockSysfsPath(path string) (restore func()) { 12 | orig := sysfsPath 13 | sysfsPath = path 14 | return func() { 15 | sysfsPath = orig 16 | } 17 | } 18 | 19 | func MockEFIVars(vars efi.VarsBackend) (restore func()) { 20 | orig := customEfiVars 21 | customEfiVars = vars 22 | return func() { 23 | customEfiVars = orig 24 | } 25 | } 26 | 27 | func NewMockRawDevice(path, sysfsPath string, version TPMMajorVersion, devno int) *RawDevice { 28 | return &RawDevice{ 29 | Device: Device{ 30 | path: path, 31 | sysfsPath: sysfsPath, 32 | version: version}, 33 | devno: devno, 34 | } 35 | } 36 | 37 | func NewMockRMDevice(path, sysfsPath string, version TPMMajorVersion, raw *RawDevice) *RMDevice { 38 | return &RMDevice{ 39 | Device: Device{ 40 | path: path, 41 | sysfsPath: sysfsPath, 42 | version: version, 43 | }, 44 | raw: raw, 45 | } 46 | } 47 | 48 | func ResetDevices() { 49 | devices = tpmDevices{} 50 | } 51 | -------------------------------------------------------------------------------- /linux/linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | /* 6 | Package linux provides an interface for communicating with TPMs using a Linux TPM character device 7 | */ 8 | package linux 9 | -------------------------------------------------------------------------------- /linux/linux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package linux_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/canonical/go-tpm2/testutil" 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | func init() { 15 | testutil.AddCommandLineFlags() 16 | } 17 | 18 | func Test(t *testing.T) { TestingT(t) } 19 | -------------------------------------------------------------------------------- /linux/ppi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package linux 6 | 7 | import ( 8 | "sync/atomic" 9 | 10 | "github.com/canonical/go-tpm2/ppi" 11 | ) 12 | 13 | var ( 14 | forcedPpiType uint32 15 | ) 16 | 17 | const ( 18 | forcePpiTypeSet uint32 = 1 << 30 19 | forcePpiTypeLocked uint32 = 1 << 31 20 | ) 21 | 22 | // ForcePPIType can be used to force the PPI implementation that is returned from 23 | // [RawDevice.PhysicalPresenceInterface] on any device. This will panic if it is 24 | // called after [RawDevice.PhysicalPresenceInterface] has been called for any device. 25 | // If the forced PPI implementation isn't available, then any calls to 26 | // [RawDevice.PhysicalPresenceInterface] will return an error rather than falling 27 | // back to an available implementation. 28 | func ForcePPIType(ppiType ppi.Type) { 29 | for { 30 | val := atomic.LoadUint32(&forcedPpiType) 31 | if val&forcePpiTypeLocked > 0 { 32 | // This happens once loadForcedPpiType has been called. 33 | panic("cannot call ForcePPIType once RawDevice.PhysicalPresenceInterface has been called for any device") 34 | } 35 | 36 | // Update forcedPpiType atomically to reflect that this function 37 | // has been called and to store the type that was requested. 38 | newval := forcePpiTypeSet | uint32(ppiType) 39 | if atomic.CompareAndSwapUint32(&forcedPpiType, val, newval) { 40 | break 41 | } 42 | 43 | // We raced with another caller or a caller of loadForcedPpiType, 44 | // so try again. 45 | } 46 | } 47 | 48 | func loadForcedPpiType() (ppiType ppi.Type, set bool) { 49 | for { 50 | val := atomic.LoadUint32(&forcedPpiType) 51 | if val&forcePpiTypeSet > 0 { 52 | // ForcePPIType has been called. 53 | ppiType = ppi.Type(val & 0x3) 54 | set = true 55 | } 56 | 57 | if val&forcePpiTypeLocked > 0 { 58 | // This function has already been called, so there 59 | // will be no more updates to forcedPpiType. 60 | break 61 | } 62 | 63 | // Update forcedPpiType to indicate that it should no longer 64 | // be modified. 65 | newval := val | forcePpiTypeLocked 66 | if atomic.CompareAndSwapUint32(&forcedPpiType, val, newval) { 67 | break 68 | } 69 | 70 | // We raced with another caller or a caller of ForcePPIType, 71 | // so try again. 72 | } 73 | 74 | return ppiType, set 75 | } 76 | -------------------------------------------------------------------------------- /linux/ppi_acpi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package linux 6 | 7 | import ( 8 | "bufio" 9 | "errors" 10 | "fmt" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | 18 | "github.com/canonical/go-tpm2/ppi" 19 | ) 20 | 21 | type acpiPpiImpl struct { 22 | sysfsPath string 23 | Version ppi.Version 24 | 25 | opsOnce sync.Once 26 | ops map[ppi.OperationId]ppi.OperationStatus 27 | opsError error 28 | } 29 | 30 | func (p *acpiPpiImpl) SubmitOperation(op ppi.OperationId, arg *uint32) error { 31 | if arg != nil && p.Version.Compare(ppi.Version13) < 0 { 32 | return ppi.ErrOperationUnsupported 33 | } 34 | 35 | f, err := os.OpenFile(filepath.Join(p.sysfsPath, "request"), os.O_WRONLY, 0) 36 | switch { 37 | case errors.Is(err, os.ErrPermission): 38 | return ppi.ErrPermission 39 | case err != nil: 40 | return err 41 | } 42 | defer f.Close() 43 | 44 | cmd := strconv.FormatUint(uint64(op), 10) 45 | if arg != nil { 46 | cmd += " " + strconv.FormatUint(uint64(*arg), 10) 47 | } 48 | 49 | _, err = f.WriteString(cmd) 50 | switch { 51 | case errors.Is(err, os.ErrPermission): 52 | return ppi.ErrOperationUnsupported 53 | case errors.Is(err, syscall.EFAULT): 54 | return ppi.ErrOperationFailed 55 | default: 56 | return err 57 | } 58 | } 59 | 60 | func (p *acpiPpiImpl) StateTransitionAction() (ppi.StateTransitionAction, error) { 61 | actionBytes, err := os.ReadFile(filepath.Join(p.sysfsPath, "transition_action")) 62 | if err != nil { 63 | return 0, err 64 | } 65 | 66 | var action ppi.StateTransitionAction 67 | var dummy string 68 | if _, err := fmt.Sscanf(string(actionBytes), "%d:%s\n", &action, &dummy); err != nil { 69 | return 0, fmt.Errorf("cannot scan transition action %q: %w", string(actionBytes), err) 70 | } 71 | if action > ppi.StateTransitionActionOSVendorSpecific { 72 | return 0, fmt.Errorf("invalid transition action %d", action) 73 | } 74 | 75 | return action, nil 76 | } 77 | 78 | func (p *acpiPpiImpl) OperationStatus(op ppi.OperationId) (ppi.OperationStatus, error) { 79 | p.opsOnce.Do(func() { 80 | p.ops, p.opsError = func() (map[ppi.OperationId]ppi.OperationStatus, error) { 81 | opsFile, err := os.OpenFile(filepath.Join(p.sysfsPath, "tcg_operations"), os.O_RDONLY, 0) 82 | if err != nil { 83 | return nil, err 84 | } 85 | defer opsFile.Close() 86 | 87 | ops := make(map[ppi.OperationId]ppi.OperationStatus) 88 | 89 | scanner := bufio.NewScanner(opsFile) 90 | for scanner.Scan() { 91 | var op ppi.OperationId 92 | var status ppi.OperationStatus 93 | if _, err := fmt.Sscanf(scanner.Text(), "%d%d", &op, &status); err != nil { 94 | return nil, fmt.Errorf("cannot scan operation \"%s\": %w", scanner.Text(), err) 95 | } 96 | 97 | ops[op] = status 98 | } 99 | 100 | switch { 101 | case errors.Is(scanner.Err(), syscall.EPERM): 102 | return nil, ppi.ErrOperationUnsupported 103 | case scanner.Err() != nil: 104 | return nil, err 105 | } 106 | 107 | return ops, nil 108 | }() 109 | }) 110 | 111 | if p.opsError != nil { 112 | return 0, p.opsError 113 | } 114 | 115 | status, implemented := p.ops[op] 116 | if !implemented { 117 | return ppi.OperationNotImplemented, nil 118 | } 119 | if status > ppi.OperationPPNotRequired { 120 | return 0, fmt.Errorf("invalid operation status %d", status) 121 | } 122 | return status, nil 123 | } 124 | 125 | func (p *acpiPpiImpl) OperationResponse() (*ppi.OperationResponse, error) { 126 | rspBytes, err := os.ReadFile(filepath.Join(p.sysfsPath, "response")) 127 | switch { 128 | case errors.Is(err, syscall.EFAULT): 129 | return nil, ppi.ErrOperationFailed 130 | case err != nil: 131 | return nil, err 132 | } 133 | 134 | rsp := string(rspBytes) 135 | 136 | var arg1, arg2 uint32 137 | if _, err := fmt.Sscanf(rsp, "%d", &arg1); err != nil { 138 | return nil, fmt.Errorf("cannot scan response %q: %w", rsp, err) 139 | } 140 | if arg1 == 0 { 141 | return nil, nil 142 | } 143 | 144 | if _, err := fmt.Sscanf(rsp, "%d%v:", &arg1, &arg2); err != nil { 145 | return nil, fmt.Errorf("cannot scan response %q: %w", rsp, err) 146 | } 147 | 148 | r := &ppi.OperationResponse{Operation: ppi.OperationId(arg1)} 149 | if arg2 != 0 { 150 | r.Err = ppi.OperationError(arg2) 151 | } 152 | return r, nil 153 | } 154 | 155 | func newAcpiPpi(path string) (*acpiPpiImpl, error) { 156 | versionBytes, err := os.ReadFile(filepath.Join(path, "version")) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | version, err := ppi.ParseVersion(strings.TrimSpace(string(versionBytes))) 162 | if err != nil { 163 | return nil, fmt.Errorf("cannot parse version: %w", err) 164 | } 165 | 166 | return &acpiPpiImpl{ 167 | sysfsPath: path, 168 | Version: version, 169 | }, nil 170 | } 171 | -------------------------------------------------------------------------------- /linux/transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package linux 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "os" 11 | "syscall" 12 | 13 | "github.com/canonical/go-tpm2" 14 | "github.com/canonical/go-tpm2/transportutil" 15 | ) 16 | 17 | const ( 18 | maxCommandSize = 4096 19 | maxResponseSize = 4096 20 | ) 21 | 22 | type fileStatter interface { 23 | Stat() (os.FileInfo, error) 24 | } 25 | 26 | // Tcti represents a connection to a Linux TPM character device. 27 | // 28 | // Deprecated: Use [Transport]. 29 | type Tcti = Transport 30 | 31 | // Transport represents a connection to a Linux TPM character device. It is not intended to be 32 | // used from multiple goroutines simultaneously. 33 | type Transport struct { 34 | r transportutil.ResponseBuffer 35 | w io.Writer 36 | closer io.Closer 37 | statter fileStatter 38 | } 39 | 40 | func newTransport(file *tpmFile, partialReadSupported bool, maxResponseSize uint32) *Transport { 41 | var r transportutil.ResponseBuffer = file 42 | if !partialReadSupported { 43 | r = transportutil.BufferResponses(r, maxResponseSize) 44 | } 45 | return &Transport{ 46 | r: r, 47 | w: transportutil.BufferCommands(file, maxCommandSize), 48 | closer: file, 49 | statter: file, 50 | } 51 | } 52 | 53 | // Read implmements [tpm2.Transport]. 54 | func (d *Transport) Read(data []byte) (int, error) { 55 | n, err := d.r.Read(data) 56 | if err != nil && errors.Is(err, os.ErrClosed) { 57 | return n, transportutil.ErrClosed 58 | } 59 | return n, err 60 | } 61 | 62 | // Write implmements [tpm2.Transport]. 63 | func (d *Transport) Write(data []byte) (int, error) { 64 | if d.r.Len() > 0 { 65 | return 0, tpm2.ErrTransportBusy 66 | } 67 | 68 | n, err := d.w.Write(data) 69 | if err != nil { 70 | switch { 71 | case errors.Is(err, os.ErrClosed): 72 | return n, transportutil.ErrClosed 73 | case errors.Is(err, syscall.Errno(syscall.EBUSY)): 74 | return n, transportutil.ErrBusy 75 | } 76 | } 77 | return n, err 78 | } 79 | 80 | // Close implements [tpm2.Transport.Close]. 81 | func (d *Transport) Close() error { 82 | if err := d.closer.Close(); err != nil { 83 | if errors.Is(err, os.ErrClosed) { 84 | return transportutil.ErrClosed 85 | } 86 | return err 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /mssim/cmds.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package mssim 6 | 7 | const ( 8 | cmdPowerOn uint32 = 1 9 | cmdPowerOff uint32 = 2 10 | cmdPhysPresOn uint32 = 3 11 | cmdPhysPresOff uint32 = 4 12 | cmdHashStart uint32 = 5 13 | cmdHashData uint32 = 6 14 | cmdHashEnd uint32 = 7 15 | cmdTPMSendCommand uint32 = 8 16 | cmdCancelOn uint32 = 9 17 | cmdCancelOff uint32 = 10 18 | cmdNVOn uint32 = 11 19 | cmdNVOff uint32 = 12 20 | cmdRemoteHandshake uint32 = 15 21 | cmdReset uint32 = 17 22 | cmdRestart uint32 = 18 23 | cmdSessionEnd uint32 = 20 24 | cmdStop uint32 = 21 25 | cmdTestFailureMode uint32 = 30 26 | ) 27 | -------------------------------------------------------------------------------- /mssim/device_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2024 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package mssim_test 6 | 7 | import ( 8 | "time" 9 | 10 | . "github.com/canonical/go-tpm2/mssim" 11 | "github.com/canonical/go-tpm2/transportutil" 12 | . "gopkg.in/check.v1" 13 | ) 14 | 15 | type deviceSuite struct{} 16 | 17 | var _ = Suite(&deviceSuite{}) 18 | 19 | var ( 20 | defaultRetryParams = transportutil.RetryParams{ 21 | MaxRetries: 4, 22 | InitialBackoff: 20 * time.Millisecond, 23 | BackoffRate: 2, 24 | } 25 | ) 26 | 27 | func (s *deviceSuite) TestNewDevice(c *C) { 28 | dev := NewDevice() 29 | expectedDev := NewMockDevice(&DeviceAddr{Host: "localhost", Port: 2321}, &DeviceAddr{Host: "localhost", Port: 2322}, &defaultRetryParams) 30 | c.Check(dev, DeepEquals, expectedDev) 31 | } 32 | 33 | func (s *deviceSuite) TestNewDeviceDifferentHost(c *C) { 34 | dev := NewDevice(WithHost("192.18.1.50")) 35 | expectedDev := NewMockDevice(&DeviceAddr{Host: "192.18.1.50", Port: 2321}, &DeviceAddr{Host: "192.18.1.50", Port: 2322}, &defaultRetryParams) 36 | c.Check(dev, DeepEquals, expectedDev) 37 | } 38 | 39 | func (s *deviceSuite) TestNewDeviceDifferentPort(c *C) { 40 | dev := NewDevice(WithPort(4444)) 41 | expectedDev := NewMockDevice(&DeviceAddr{Host: "localhost", Port: 4444}, &DeviceAddr{Host: "localhost", Port: 4445}, &defaultRetryParams) 42 | c.Check(dev, DeepEquals, expectedDev) 43 | } 44 | 45 | func (s *deviceSuite) TestNewDeviceDifferentTPMPort(c *C) { 46 | dev := NewDevice(WithTPMPort(4444)) 47 | expectedDev := NewMockDevice(&DeviceAddr{Host: "localhost", Port: 4444}, &DeviceAddr{Host: "localhost", Port: 2322}, &defaultRetryParams) 48 | c.Check(dev, DeepEquals, expectedDev) 49 | } 50 | 51 | func (s *deviceSuite) TestNewDeviceDifferentPlatformPort(c *C) { 52 | dev := NewDevice(WithPlatformPort(4444)) 53 | expectedDev := NewMockDevice(&DeviceAddr{Host: "localhost", Port: 2321}, &DeviceAddr{Host: "localhost", Port: 4444}, &defaultRetryParams) 54 | c.Check(dev, DeepEquals, expectedDev) 55 | } 56 | 57 | func (s *deviceSuite) TestNewDeviceWithDifferentRetryParams(c *C) { 58 | dev := NewDevice(WithRetryParams(10, 10*time.Millisecond, 3)) 59 | expectedDev := NewMockDevice(&DeviceAddr{Host: "localhost", Port: 2321}, &DeviceAddr{Host: "localhost", Port: 2322}, &transportutil.RetryParams{ 60 | MaxRetries: 10, 61 | InitialBackoff: 10 * time.Millisecond, 62 | BackoffRate: 3, 63 | }) 64 | c.Check(dev, DeepEquals, expectedDev) 65 | } 66 | 67 | func (s *deviceSuite) TestInfoMethods(c *C) { 68 | dev := NewDevice() 69 | c.Check(dev.TPMAddr(), DeepEquals, DeviceAddr{Host: "localhost", Port: 2321}) 70 | c.Check(dev.PlatformAddr(), DeepEquals, DeviceAddr{Host: "localhost", Port: 2322}) 71 | c.Check(dev.RetryParams(), DeepEquals, defaultRetryParams) 72 | } 73 | -------------------------------------------------------------------------------- /mssim/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package mssim 6 | 7 | import ( 8 | "net" 9 | 10 | "github.com/canonical/go-tpm2/transportutil" 11 | ) 12 | 13 | type ( 14 | DeviceAddr = deviceAddr 15 | ) 16 | 17 | func MockNetDial(fn func(string, string) (net.Conn, error)) (restore func()) { 18 | orig := netDial 19 | netDial = fn 20 | return func() { 21 | netDial = orig 22 | } 23 | } 24 | 25 | func NewMockDevice(tpm, platform *DeviceAddr, retryParams *transportutil.RetryParams) *Device { 26 | return &Device{ 27 | tpm: *tpm, 28 | platform: *platform, 29 | retryParams: *retryParams, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mssim/mssim_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package mssim_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/canonical/go-tpm2/testutil" 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | func init() { 15 | testutil.AddCommandLineFlags() 16 | } 17 | 18 | func Test(t *testing.T) { TestingT(t) } 19 | -------------------------------------------------------------------------------- /mu/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package mu contains functions for marshalling go values to and unmarshalling them from the TPM wire format. 3 | 4 | Go types are marshalled to and from the TPM wire format according to the following rules: 5 | 6 | - UINT8 <-> uint8 7 | 8 | - BYTE <-> byte 9 | 10 | - INT8 <-> int8 11 | 12 | - BOOL <-> bool 13 | 14 | - UINT16 <-> uint16 15 | 16 | - INT16 <-> int16 17 | 18 | - UINT32 <-> uint32 19 | 20 | - INT32 <-> int32 21 | 22 | - UINT64 <-> uint64 23 | 24 | - INT64 <-> int64 25 | 26 | - TPM2B prefixed types (sized buffers with a 2-byte size field) have 2 representations 27 | in go: 28 | 29 | 1. []byte, or any type with an identical underlying type. A zero sized value is 30 | unmarshalled to nil. 31 | 32 | 2. Pointer to a struct, either referenced from a field with the `tpm2:"sized"` tag 33 | or wrapped with the Sized() function. A zero sized value is represented as a nil pointer. 34 | 35 | - TPMA prefixed types (attributes) <-> whichever go type corresponds to the underlying TPM 36 | type (UINT8, UINT16, or UINT32). 37 | 38 | - TPM_ALG_ID (algorithm enum) <-> uint16 39 | 40 | - TPML prefixed types (lists with a 4-byte length field) <-> slice of whichever go type 41 | corresponds to the underlying TPM type. Zero length lists are unmarshalled to nil. 42 | 43 | - TPMS prefixed types (structures) <-> struct 44 | 45 | - TPMT prefixed types (tagged union) <-> struct with at least one member that is a struct 46 | or pointer to a struct that represents a union. The first member is the selector field 47 | unless overridden on the union member with the `tpm2:"selector:"` tag. 48 | 49 | - TPMU prefixed types (unions) <-> struct which implements the Union interface. The default 50 | selector field can be overridden by using the `tpm2:"selector:"` tag. 51 | 52 | TPMI prefixed types (interface types) are generally not explicitly supported. These are used 53 | by the TPM for type checking during unmarshalling, but this package doesn't distinguish between 54 | TPMI prefixed types with the same underlying type. 55 | 56 | Byte array types are supported and are marshalled to and from a fixed size bytes sequence. 57 | No other array types are supported. 58 | 59 | Pointers are automatically dererenced during marshalling and unmarshalling. 60 | 61 | The marshalling code parses the "tpm2" tag on struct fields, the value of which is a comma 62 | separated list of options. These options are: 63 | - sized1 - the field is a variable sized buffer with a single byte size field, used 64 | to support the TPMS_PCR_SELECT type. This is only valid for byte slice fields. 65 | - ignore - the field is ignored by this package. 66 | - selector: - override the default selector field on a field to a structure 67 | that represents a union. The default behaviour without this option is to use the 68 | first field as the selector. It is invalid to use this on any field that isn't 69 | a structure or pointer to a structure that represents a union. 70 | - sized - turns a pointer to a structure into a sized (TPM2B) type. A zero sized 71 | structure is represented by a nil pointer. It is invalid to use this on any field 72 | that isn't a pointer to a structure. 73 | - raw - turns a slice into a raw type so that it is marshalled and unmarshalled without 74 | a size or length field. The slice must be pre-allocated to the correct length by the 75 | caller during unmarshalling. This is only valid for slice fields. 76 | */ 77 | package mu 78 | -------------------------------------------------------------------------------- /objectutil/credential.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package objectutil 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | 12 | "github.com/canonical/go-tpm2" 13 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 14 | internal_util "github.com/canonical/go-tpm2/internal/util" 15 | "github.com/canonical/go-tpm2/mu" 16 | ) 17 | 18 | // MakeCredential performs the duties of a certificate authority in order to create an activation 19 | // credential. It establishes a seed which is used to protect the activation credential (see 20 | // section 24 - "Credential Protection" of Part 1 of the Trusted Platform Module Library 21 | // specification). 22 | // 23 | // The encrypted and integrity protected credential blob and a secret are returned, and these can 24 | // be supplied to the TPM2_ActivateCredential command on the TPM on which both the private part of 25 | // key and the object associated with objectName are loaded in order to recover the activation 26 | // credential. 27 | func MakeCredential(rand io.Reader, key *tpm2.Public, credential tpm2.Digest, objectName tpm2.Name) (credentialBlob tpm2.IDObject, secret tpm2.EncryptedSecret, err error) { 28 | if !mu.IsValid(key) { 29 | return nil, nil, errors.New("key is not valid") 30 | } 31 | if !key.IsStorageParent() || !key.IsAsymmetric() { 32 | return nil, nil, errors.New("key must be an asymmetric storage parent") 33 | } 34 | if !key.NameAlg.Available() { 35 | return nil, nil, errors.New("name algorithm for key is not available") 36 | } 37 | 38 | credentialBlob, err = mu.MarshalToBytes(credential) 39 | if err != nil { 40 | return nil, nil, fmt.Errorf("cannot marshal credential: %w", err) 41 | } 42 | 43 | secret, seed, err := internal_crypt.SecretEncrypt(rand, key.Public(), key.NameAlg.GetHash(), []byte(tpm2.IdentityKey)) 44 | if err != nil { 45 | return nil, nil, fmt.Errorf("cannot create encrypted symmetric seed: %w", err) 46 | } 47 | 48 | credentialBlob, err = internal_util.ProduceOuterWrap(key.NameAlg, &key.AsymDetail().Symmetric, objectName, seed, false, credentialBlob) 49 | if err != nil { 50 | return nil, nil, fmt.Errorf("cannot apply outer wrapper: %w", err) 51 | } 52 | 53 | return credentialBlob, secret, nil 54 | } 55 | -------------------------------------------------------------------------------- /objectutil/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | /* 6 | Package objectutil contains utilities for creating and working with objects. 7 | */ 8 | package objectutil 9 | -------------------------------------------------------------------------------- /objectutil/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package objectutil 6 | 7 | var ZeroExtendBytes = zeroExtendBytes 8 | -------------------------------------------------------------------------------- /objectutil/named.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package objectutil 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | ) 10 | 11 | // Named is some type that represents an object. 12 | type Named interface { 13 | Name() tpm2.Name 14 | } 15 | -------------------------------------------------------------------------------- /objectutil/object_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package objectutil_test 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "testing" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2/testutil" 16 | ) 17 | 18 | func init() { 19 | testutil.AddCommandLineFlags() 20 | } 21 | 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | func TestMain(m *testing.M) { 25 | flag.Parse() 26 | os.Exit(func() int { 27 | if testutil.TPMBackend == testutil.TPMBackendMssim { 28 | simulatorCleanup, err := testutil.LaunchTPMSimulator(nil) 29 | if err != nil { 30 | fmt.Fprintf(os.Stderr, "Cannot launch TPM simulator: %v\n", err) 31 | return 1 32 | } 33 | defer simulatorCleanup() 34 | } 35 | 36 | return m.Run() 37 | }()) 38 | } 39 | -------------------------------------------------------------------------------- /objectutil/qn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package objectutil 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/mu" 13 | ) 14 | 15 | func computeOneQualifiedName(object Named, parentQn tpm2.Name) (tpm2.Name, error) { 16 | switch { 17 | case object.Name().Type() == tpm2.NameTypeNone: 18 | return nil, nil 19 | case object.Name().Type() != tpm2.NameTypeDigest: 20 | return nil, errors.New("invalid name") 21 | case !object.Name().Algorithm().Available(): 22 | return nil, errors.New("name algorithm is not available") 23 | case !parentQn.IsValid() || parentQn.Type() == tpm2.NameTypeNone: 24 | return nil, errors.New("invalid parent qualified name") 25 | case parentQn.Algorithm() != tpm2.HashAlgorithmNull && parentQn.Algorithm() != object.Name().Algorithm(): 26 | return nil, errors.New("name algorithm mismatch") 27 | } 28 | 29 | h := object.Name().Algorithm().NewHash() 30 | h.Write(parentQn) 31 | h.Write(object.Name()) 32 | 33 | return mu.MustMarshalToBytes(object.Name().Algorithm(), mu.RawBytes(h.Sum(nil))), nil 34 | } 35 | 36 | // ComputeQualifiedName computes the qualified name of an object from the specified qualified name 37 | // of a root object and a list of ancestor objects. The ancestor objects are ordered starting with 38 | // the immediate child of the object associated with the root qualified name. 39 | func ComputeQualifiedName(object Named, rootQn tpm2.Name, ancestors ...Named) (tpm2.Name, error) { 40 | lastQn := rootQn 41 | 42 | for i, ancestor := range ancestors { 43 | var err error 44 | lastQn, err = computeOneQualifiedName(ancestor, lastQn) 45 | if err != nil { 46 | return nil, fmt.Errorf("cannot compute intermediate QN for ancestor at index %d: %w", i, err) 47 | } 48 | } 49 | 50 | return computeOneQualifiedName(object, lastQn) 51 | } 52 | 53 | // ComputeQualifiedNameInHierarchy computes the qualified name of an object protected in the 54 | // specified hierarchy from a list of ancestor objects. The ancestor objects are ordered 55 | // starting from the primary object. 56 | func ComputeQualifiedNameInHierarchy(object Named, hierarchy tpm2.Handle, ancestors ...Named) (tpm2.Name, error) { 57 | switch hierarchy { 58 | case tpm2.HandleOwner, tpm2.HandleNull, tpm2.HandleEndorsement, tpm2.HandlePlatform: 59 | // Good! 60 | default: 61 | return nil, errors.New("invalid hierarchy") 62 | } 63 | return ComputeQualifiedName(object, mu.MustMarshalToBytes(hierarchy), ancestors...) 64 | } 65 | -------------------------------------------------------------------------------- /paramcrypt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | import ( 8 | "crypto/aes" 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | 13 | internal_crypt "github.com/canonical/go-tpm2/internal/crypt" 14 | "github.com/canonical/go-tpm2/mu" 15 | ) 16 | 17 | func isParamEncryptable(param interface{}) bool { 18 | return mu.DetermineTPMKind(param) == mu.TPMKindSized 19 | } 20 | 21 | func (s *sessionParam) ComputeSessionValue() []byte { 22 | var key []byte 23 | key = append(key, s.Session.Params().SessionKey...) 24 | if s.IsAuth() { 25 | key = append(key, trimAuthValue(s.AssociatedResource.AuthValue())...) 26 | } 27 | return key 28 | } 29 | 30 | func (p *sessionParams) decryptSession() (*sessionParam, int) { 31 | if p.DecryptSessionIndex == -1 { 32 | return nil, -1 33 | } 34 | return p.Sessions[p.DecryptSessionIndex], p.DecryptSessionIndex 35 | } 36 | 37 | func (p *sessionParams) encryptSession() (*sessionParam, int) { 38 | if p.EncryptSessionIndex == -1 { 39 | return nil, -1 40 | } 41 | return p.Sessions[p.EncryptSessionIndex], p.EncryptSessionIndex 42 | } 43 | 44 | func (p *sessionParams) hasDecryptSession() bool { 45 | return p.DecryptSessionIndex != -1 46 | } 47 | 48 | func (p *sessionParams) ComputeEncryptNonce() { 49 | s, i := p.encryptSession() 50 | if s == nil || i == 0 || !p.Sessions[0].IsAuth() { 51 | return 52 | } 53 | ds, di := p.decryptSession() 54 | if ds != nil && di == i { 55 | return 56 | } 57 | 58 | p.Sessions[0].EncryptNonce = s.Session.State().NonceTPM 59 | } 60 | 61 | func (p *sessionParams) EncryptCommandParameter(cpBytes []byte) error { 62 | s, i := p.decryptSession() 63 | if s == nil { 64 | return nil 65 | } 66 | 67 | hashAlg := s.Session.Params().HashAlg 68 | 69 | sessionValue := s.ComputeSessionValue() 70 | 71 | size := binary.BigEndian.Uint16(cpBytes) 72 | data := cpBytes[2 : size+2] 73 | 74 | symmetric := s.Session.Params().Symmetric 75 | 76 | switch symmetric.Algorithm { 77 | case SymAlgorithmAES: 78 | if symmetric.Mode.Sym != SymModeCFB { 79 | return errors.New("unsupported cipher mode") 80 | } 81 | k := internal_crypt.KDFa(hashAlg.GetHash(), sessionValue, []byte(CFBKey), s.NonceCaller, s.Session.State().NonceTPM, 82 | int(symmetric.KeyBits.Sym)+(aes.BlockSize*8)) 83 | offset := (symmetric.KeyBits.Sym + 7) / 8 84 | symKey := k[0:offset] 85 | iv := k[offset:] 86 | if err := internal_crypt.SymmetricEncrypt(symmetric.Algorithm, symKey, iv, data); err != nil { 87 | return fmt.Errorf("AES encryption failed: %v", err) 88 | } 89 | case SymAlgorithmXOR: 90 | internal_crypt.XORObfuscation(hashAlg.GetHash(), sessionValue, s.NonceCaller, s.Session.State().NonceTPM, data) 91 | default: 92 | return fmt.Errorf("unknown symmetric algorithm: %v", symmetric.Algorithm) 93 | } 94 | 95 | if i > 0 && p.Sessions[0].IsAuth() { 96 | p.Sessions[0].DecryptNonce = s.Session.State().NonceTPM 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func (p *sessionParams) DecryptResponseParameter(rpBytes []byte) error { 103 | s, _ := p.encryptSession() 104 | if s == nil { 105 | return nil 106 | } 107 | 108 | hashAlg := s.Session.Params().HashAlg 109 | 110 | sessionValue := s.ComputeSessionValue() 111 | 112 | size := binary.BigEndian.Uint16(rpBytes) 113 | data := rpBytes[2 : size+2] 114 | 115 | symmetric := s.Session.Params().Symmetric 116 | 117 | switch symmetric.Algorithm { 118 | case SymAlgorithmAES: 119 | if symmetric.Mode.Sym != SymModeCFB { 120 | return errors.New("unsupported cipher mode") 121 | } 122 | k := internal_crypt.KDFa(hashAlg.GetHash(), sessionValue, []byte(CFBKey), s.Session.State().NonceTPM, s.NonceCaller, 123 | int(symmetric.KeyBits.Sym)+(aes.BlockSize*8)) 124 | offset := (symmetric.KeyBits.Sym + 7) / 8 125 | symKey := k[0:offset] 126 | iv := k[offset:] 127 | if err := internal_crypt.SymmetricDecrypt(symmetric.Algorithm, symKey, iv, data); err != nil { 128 | return fmt.Errorf("AES encryption failed: %v", err) 129 | } 130 | case SymAlgorithmXOR: 131 | internal_crypt.XORObfuscation(hashAlg.GetHash(), sessionValue, s.Session.State().NonceTPM, s.NonceCaller, data) 132 | default: 133 | return fmt.Errorf("unknown symmetric algorithm: %v", symmetric.Algorithm) 134 | } 135 | 136 | return nil 137 | } 138 | -------------------------------------------------------------------------------- /policyutil/cphash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil 6 | 7 | import ( 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | 12 | "github.com/canonical/go-tpm2" 13 | "github.com/canonical/go-tpm2/mu" 14 | ) 15 | 16 | // CpHash provides a way to obtain a command parameter digest. 17 | type CpHash interface { 18 | // Digest returns the command parameter digest for the specified algorithm. 19 | Digest(alg tpm2.HashAlgorithmId) (tpm2.Digest, error) 20 | } 21 | 22 | func computeCpHash(alg tpm2.HashAlgorithmId, command tpm2.CommandCode, handles []tpm2.Name, cpBytes []byte) (tpm2.Digest, error) { 23 | if !alg.Available() { 24 | return nil, errors.New("algorithm is not available") 25 | } 26 | 27 | h := alg.NewHash() 28 | 29 | binary.Write(h, binary.BigEndian, command) 30 | for _, handle := range handles { 31 | h.Write(handle.Name()) 32 | } 33 | h.Write(cpBytes) 34 | 35 | return h.Sum(nil), nil 36 | } 37 | 38 | type commandParams struct { 39 | command tpm2.CommandCode 40 | handles []Named 41 | params []interface{} 42 | } 43 | 44 | func (c *commandParams) Digest(alg tpm2.HashAlgorithmId) (tpm2.Digest, error) { 45 | cpBytes, err := mu.MarshalToBytes(c.params...) 46 | if err != nil { 47 | return nil, err 48 | } 49 | var handles []tpm2.Name 50 | for i, handle := range c.handles { 51 | name := handle.Name() 52 | if !name.IsValid() { 53 | return nil, fmt.Errorf("invalid name for handle %d", i) 54 | } 55 | handles = append(handles, name) 56 | } 57 | return computeCpHash(alg, c.command, handles, cpBytes) 58 | } 59 | 60 | // CommandParameters returns a CpHash implementation for the specified command code, handles and 61 | // parameters. The required parameters are defined in part 3 of the TPM 2.0 Library Specification 62 | // for the specific command. 63 | func CommandParameters(command tpm2.CommandCode, handles []Named, params ...interface{}) CpHash { 64 | return &commandParams{ 65 | command: command, 66 | handles: handles, 67 | params: params} 68 | } 69 | 70 | type cpDigest tpm2.TaggedHash 71 | 72 | func (d *cpDigest) Digest(alg tpm2.HashAlgorithmId) (tpm2.Digest, error) { 73 | if alg != d.HashAlg { 74 | return nil, errors.New("no digest for algorithm") 75 | } 76 | return tpm2.Digest((*tpm2.TaggedHash)(d).Digest()), nil 77 | } 78 | 79 | // CommandParameterDigest returns a CpHash implementation for the specified algorithm and digest. 80 | func CommandParameterDigest(alg tpm2.HashAlgorithmId, digest tpm2.Digest) CpHash { 81 | d := tpm2.MakeTaggedHash(alg, digest) 82 | return (*cpDigest)(&d) 83 | } 84 | 85 | // ComputeCpHash computes a command parameter digest from the specified command code, the supplied 86 | // handles, and parameters using the specified digest algorithm. 87 | // 88 | // The required parameters is defined in part 3 of the TPM 2.0 Library Specification for the 89 | // specific command. 90 | // 91 | // The result of this is useful for extended authorization commands that bind an authorization to 92 | // a command and set of command parameters, such as [tpm2.TPMContext.PolicySigned], 93 | // [tpm2.TPMContext.PolicySecret], [tpm2.TPMContext.PolicyTicket] and 94 | // [tpm2.TPMContext.PolicyCpHash]. 95 | func ComputeCpHash(alg tpm2.HashAlgorithmId, command tpm2.CommandCode, handles []Named, params ...interface{}) (tpm2.Digest, error) { 96 | d := CommandParameters(command, handles, params...) 97 | return d.Digest(alg) 98 | } 99 | -------------------------------------------------------------------------------- /policyutil/cphash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil_test 6 | 7 | import ( 8 | "crypto" 9 | _ "crypto/sha1" 10 | _ "crypto/sha256" 11 | "io" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2" 16 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 17 | "github.com/canonical/go-tpm2/mu" 18 | "github.com/canonical/go-tpm2/objectutil" 19 | . "github.com/canonical/go-tpm2/policyutil" 20 | ) 21 | 22 | type cpHashSuite struct{} 23 | 24 | var _ = Suite(&cpHashSuite{}) 25 | 26 | func (s *cpHashSuite) TestCommandParameters(c *C) { 27 | cpHashA := CommandParameters(tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x01}}, tpm2.Private{1, 2, 3, 4}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 28 | digest, err := cpHashA.Digest(tpm2.HashAlgorithmSHA256) 29 | c.Check(err, IsNil) 30 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "0d5c70236d9181ea6b26fb203d8a45bbb3d982926d6cf4ba60ce0fe5d5717ac3"))) 31 | } 32 | 33 | func (s *cpHashSuite) TestCommandParametersDifferentParams(c *C) { 34 | cpHashA := CommandParameters(tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x01}}, tpm2.Private{1, 2, 3, 4, 5}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 35 | digest, err := cpHashA.Digest(tpm2.HashAlgorithmSHA256) 36 | c.Check(err, IsNil) 37 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "15fc1d7283e0f5f864651602c55f1d1dbebf7e573850bfae5235e94df0ac1fa1"))) 38 | } 39 | 40 | func (s *cpHashSuite) TestCommandParametersDifferentHandles(c *C) { 41 | cpHashA := CommandParameters(tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x0b}}, tpm2.Private{1, 2, 3, 4}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 42 | digest, err := cpHashA.Digest(tpm2.HashAlgorithmSHA256) 43 | c.Check(err, IsNil) 44 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "4facb677c43722471af5c535353911e4882d26aa58f4859562b6861476f4aca3"))) 45 | } 46 | 47 | func (s *cpHashSuite) TestCommandParametersSHA1(c *C) { 48 | cpHashA := CommandParameters(tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x01}}, tpm2.Private{1, 2, 3, 4}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 49 | digest, err := cpHashA.Digest(tpm2.HashAlgorithmSHA1) 50 | c.Check(err, IsNil) 51 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "d98ba8350f71c34132f62f50a6b9f21c4fa54f75"))) 52 | } 53 | 54 | func (s *cpHashSuite) TestCommandParameterDigestSHA256(c *C) { 55 | h := crypto.SHA256.New() 56 | io.WriteString(h, "params") 57 | 58 | cpHashA := CommandParameterDigest(tpm2.HashAlgorithmSHA256, h.Sum(nil)) 59 | digest, err := cpHashA.Digest(tpm2.HashAlgorithmSHA256) 60 | c.Check(err, IsNil) 61 | c.Check(digest, DeepEquals, tpm2.Digest(h.Sum(nil))) 62 | } 63 | 64 | func (s *cpHashSuite) TestCommandParameterDigestSHA1(c *C) { 65 | h := crypto.SHA1.New() 66 | io.WriteString(h, "params") 67 | 68 | cpHashA := CommandParameterDigest(tpm2.HashAlgorithmSHA1, h.Sum(nil)) 69 | digest, err := cpHashA.Digest(tpm2.HashAlgorithmSHA1) 70 | c.Check(err, IsNil) 71 | c.Check(digest, DeepEquals, tpm2.Digest(h.Sum(nil))) 72 | } 73 | 74 | func (s *cpHashSuite) TestCommandParameterDigestError(c *C) { 75 | h := crypto.SHA256.New() 76 | io.WriteString(h, "params") 77 | 78 | cpHashA := CommandParameterDigest(tpm2.HashAlgorithmSHA256, h.Sum(nil)) 79 | _, err := cpHashA.Digest(tpm2.HashAlgorithmSHA1) 80 | c.Check(err, ErrorMatches, "no digest for algorithm") 81 | } 82 | 83 | func (s *cpHashSuite) TestComputeCpHash(c *C) { 84 | cpHashA, err := ComputeCpHash(tpm2.HashAlgorithmSHA256, tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x01}}, tpm2.Private{1, 2, 3, 4}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 85 | c.Check(err, IsNil) 86 | c.Check(cpHashA, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "0d5c70236d9181ea6b26fb203d8a45bbb3d982926d6cf4ba60ce0fe5d5717ac3"))) 87 | } 88 | 89 | func (s *cpHashSuite) TestComputeCpHashDifferentParams(c *C) { 90 | cpHashA, err := ComputeCpHash(tpm2.HashAlgorithmSHA256, tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x01}}, tpm2.Private{1, 2, 3, 4, 5}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 91 | c.Check(err, IsNil) 92 | c.Check(cpHashA, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "15fc1d7283e0f5f864651602c55f1d1dbebf7e573850bfae5235e94df0ac1fa1"))) 93 | } 94 | 95 | func (s *cpHashSuite) TestComputeCpHashDifferentHandle(c *C) { 96 | cpHashA, err := ComputeCpHash(tpm2.HashAlgorithmSHA256, tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x0b}}, tpm2.Private{1, 2, 3, 4}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 97 | c.Check(err, IsNil) 98 | c.Check(cpHashA, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "4facb677c43722471af5c535353911e4882d26aa58f4859562b6861476f4aca3"))) 99 | } 100 | 101 | func (s *cpHashSuite) TestComputeCpHashSHA1(c *C) { 102 | cpHashA, err := ComputeCpHash(tpm2.HashAlgorithmSHA1, tpm2.CommandLoad, []Named{tpm2.Name{0x40, 0x00, 0x00, 0x01}}, tpm2.Private{1, 2, 3, 4}, mu.Sized(objectutil.NewRSAStorageKeyTemplate())) 103 | c.Check(err, IsNil) 104 | c.Check(cpHashA, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "d98ba8350f71c34132f62f50a6b9f21c4fa54f75"))) 105 | } 106 | -------------------------------------------------------------------------------- /policyutil/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | /* 6 | Package policyutil contains utilties for constructing and executing authorization policies. 7 | */ 8 | package policyutil 9 | -------------------------------------------------------------------------------- /policyutil/named.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | ) 10 | 11 | // Named is some resource that has a name. 12 | type Named interface { 13 | Name() tpm2.Name 14 | } 15 | 16 | // NamedHandle is some resource that has a name and a handle. 17 | type NamedHandle interface { 18 | Handle() tpm2.Handle 19 | Named 20 | } 21 | -------------------------------------------------------------------------------- /policyutil/namehash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | 11 | "github.com/canonical/go-tpm2" 12 | ) 13 | 14 | // NameHash provides a way to obtain a name digest. 15 | type NameHash interface { 16 | // Digest returns the name digest for the specified algorithm. 17 | Digest(alg tpm2.HashAlgorithmId) (tpm2.Digest, error) 18 | } 19 | 20 | type commandHandles []Named 21 | 22 | func (handles commandHandles) Digest(alg tpm2.HashAlgorithmId) (tpm2.Digest, error) { 23 | if !alg.Available() { 24 | return nil, errors.New("algorithm is not available") 25 | } 26 | 27 | h := alg.NewHash() 28 | 29 | for i, handle := range handles { 30 | if !handle.Name().IsValid() { 31 | return nil, fmt.Errorf("invalid name for handle %d", i) 32 | } 33 | h.Write(handle.Name()) 34 | } 35 | 36 | return h.Sum(nil), nil 37 | } 38 | 39 | // CommandHandles returns a NameHash implementation for the supplied command handles. 40 | func CommandHandles(handles ...Named) NameHash { 41 | return commandHandles(handles) 42 | } 43 | 44 | type nameDigest tpm2.TaggedHash 45 | 46 | func (d *nameDigest) Digest(alg tpm2.HashAlgorithmId) (tpm2.Digest, error) { 47 | if alg != d.HashAlg { 48 | return nil, errors.New("no digest for algorithm") 49 | } 50 | return tpm2.Digest((*tpm2.TaggedHash)(d).Digest()), nil 51 | } 52 | 53 | // CommandHandleDigest returns a NameHash implementation for the specified algorithm and digest. 54 | func CommandHandleDigest(alg tpm2.HashAlgorithmId, digest tpm2.Digest) NameHash { 55 | d := tpm2.MakeTaggedHash(alg, digest) 56 | return (*nameDigest)(&d) 57 | } 58 | 59 | // ComputeNameHash computes a digest from the supplied handles using the specified digest 60 | // algorithm. 61 | // 62 | // The result of this is useful with [tpm2.TPMContext.PolicyNameHash]. 63 | func ComputeNameHash(alg tpm2.HashAlgorithmId, handles ...Named) (tpm2.Digest, error) { 64 | d := CommandHandles(handles...) 65 | return d.Digest(alg) 66 | } 67 | -------------------------------------------------------------------------------- /policyutil/namehash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil_test 6 | 7 | import ( 8 | "crypto" 9 | _ "crypto/sha1" 10 | _ "crypto/sha256" 11 | "io" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2" 16 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 17 | "github.com/canonical/go-tpm2/objectutil" 18 | . "github.com/canonical/go-tpm2/policyutil" 19 | ) 20 | 21 | type nameHashSuite struct{} 22 | 23 | var _ = Suite(&nameHashSuite{}) 24 | 25 | func (s *nameHashSuite) TestCommandHandles1(c *C) { 26 | nameHash := CommandHandles(tpm2.MakeHandleName(tpm2.HandleOwner)) 27 | digest, err := nameHash.Digest(tpm2.HashAlgorithmSHA256) 28 | c.Check(err, IsNil) 29 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "16a3d3b482bb480394dfac704038a3708db2a77ccaa80ca419e91122406599ec"))) 30 | } 31 | 32 | func (s *nameHashSuite) TestCommandHandles2(c *C) { 33 | nameHash := CommandHandles(objectutil.NewRSAAttestationKeyTemplate(), tpm2.MakeHandleName(tpm2.HandleEndorsement)) 34 | digest, err := nameHash.Digest(tpm2.HashAlgorithmSHA256) 35 | c.Check(err, IsNil) 36 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "513623acd2967b65470ef1d0f31306a60099279e099b6428270af4e431be9cae"))) 37 | } 38 | 39 | func (s *nameHashSuite) TestCommandHandlesSHA1(c *C) { 40 | nameHash := CommandHandles(tpm2.MakeHandleName(tpm2.HandleOwner)) 41 | digest, err := nameHash.Digest(tpm2.HashAlgorithmSHA1) 42 | c.Check(err, IsNil) 43 | c.Check(digest, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "97d538cbfae3f530b934596ea99c19a9b5c06d03"))) 44 | } 45 | 46 | func (s *nameHashSuite) TestCommandHandleDigestSHA256(c *C) { 47 | h := crypto.SHA256.New() 48 | io.WriteString(h, "handles") 49 | 50 | nameHash := CommandHandleDigest(tpm2.HashAlgorithmSHA256, h.Sum(nil)) 51 | digest, err := nameHash.Digest(tpm2.HashAlgorithmSHA256) 52 | c.Check(err, IsNil) 53 | c.Check(digest, DeepEquals, tpm2.Digest(h.Sum(nil))) 54 | } 55 | 56 | func (s *nameHashSuite) TestCommandHandleDigestSHA1(c *C) { 57 | h := crypto.SHA1.New() 58 | io.WriteString(h, "handles") 59 | 60 | nameHash := CommandHandleDigest(tpm2.HashAlgorithmSHA1, h.Sum(nil)) 61 | digest, err := nameHash.Digest(tpm2.HashAlgorithmSHA1) 62 | c.Check(err, IsNil) 63 | c.Check(digest, DeepEquals, tpm2.Digest(h.Sum(nil))) 64 | } 65 | 66 | func (s *nameHashSuite) TestCommandHandleDigestError(c *C) { 67 | h := crypto.SHA256.New() 68 | io.WriteString(h, "handles") 69 | 70 | nameHash := CommandHandleDigest(tpm2.HashAlgorithmSHA256, h.Sum(nil)) 71 | _, err := nameHash.Digest(tpm2.HashAlgorithmSHA1) 72 | c.Check(err, ErrorMatches, "no digest for algorithm") 73 | } 74 | 75 | func (s *nameHashSuite) TestComputeNameHash1(c *C) { 76 | nameHash, err := ComputeNameHash(tpm2.HashAlgorithmSHA256, tpm2.MakeHandleName(tpm2.HandleOwner)) 77 | c.Check(err, IsNil) 78 | c.Check(nameHash, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "16a3d3b482bb480394dfac704038a3708db2a77ccaa80ca419e91122406599ec"))) 79 | } 80 | 81 | func (s *nameHashSuite) TestComputeNameHash2(c *C) { 82 | nameHash, err := ComputeNameHash(tpm2.HashAlgorithmSHA256, objectutil.NewRSAAttestationKeyTemplate(), tpm2.MakeHandleName(tpm2.HandleEndorsement)) 83 | c.Check(err, IsNil) 84 | c.Check(nameHash, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "513623acd2967b65470ef1d0f31306a60099279e099b6428270af4e431be9cae"))) 85 | } 86 | 87 | func (s *nameHashSuite) TestComputeNameHashSHA1(c *C) { 88 | nameHash, err := ComputeNameHash(tpm2.HashAlgorithmSHA1, tpm2.MakeHandleName(tpm2.HandleOwner)) 89 | c.Check(err, IsNil) 90 | c.Check(nameHash, DeepEquals, tpm2.Digest(internal_testutil.DecodeHexString(c, "97d538cbfae3f530b934596ea99c19a9b5c06d03"))) 91 | } 92 | -------------------------------------------------------------------------------- /policyutil/or_tree.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil 6 | 7 | import ( 8 | "errors" 9 | 10 | "github.com/canonical/go-tpm2" 11 | ) 12 | 13 | const ( 14 | // policyOrMaxDigests sets a reasonable limit on the maximum number of or 15 | // digests. 16 | policyOrMaxDigests = 4096 // equivalent to a depth of 4 17 | ) 18 | 19 | // ensureSufficientORDigests turns a single digest in to a pair of identical digests. 20 | // This is because TPM2_PolicyOR assertions require more than one digest. This avoids 21 | // having a separate policy sequence when there is only a single digest, without having 22 | // to store duplicate digests on disk. 23 | func ensureSufficientORDigests(digests tpm2.DigestList) tpm2.DigestList { 24 | if len(digests) == 1 { 25 | return tpm2.DigestList{digests[0], digests[0]} 26 | } 27 | return digests 28 | } 29 | 30 | type policyOrNode struct { 31 | parent *policyOrNode 32 | digests tpm2.DigestList 33 | } 34 | 35 | type policyOrTree struct { 36 | alg tpm2.HashAlgorithmId 37 | leafNodes []*policyOrNode 38 | } 39 | 40 | func newPolicyOrTree(alg tpm2.HashAlgorithmId, digests tpm2.DigestList) (out *policyOrTree, err error) { 41 | if len(digests) == 0 { 42 | return nil, errors.New("no digests") 43 | } 44 | if len(digests) > policyOrMaxDigests { 45 | return nil, errors.New("too many digests") 46 | } 47 | 48 | var prev []*policyOrNode 49 | 50 | for len(prev) != 1 { 51 | // The outer loop runs on each level of the tree. If 52 | // len(prev) == 1, then we have produced the root node 53 | // and the loop should not continue. 54 | 55 | var current []*policyOrNode 56 | var nextDigests tpm2.DigestList 57 | 58 | for len(digests) > 0 { 59 | // The inner loop runs on each sibling node within a level. 60 | 61 | n := len(digests) 62 | if n > 8 { 63 | // The TPM only supports 8 conditions in TPM2_PolicyOR. 64 | n = 8 65 | } 66 | 67 | // Create a new node with the next n digests and save it. 68 | node := &policyOrNode{digests: ensureSufficientORDigests(digests[:n])} 69 | current = append(current, node) 70 | 71 | // Consume the next n digests to fit in to this node and produce a single digest 72 | // that will go in to the parent node. 73 | trial := newComputePolicySession(alg, nil, true) 74 | trial.PolicyOR(node.digests) 75 | nextDigest, err := trial.PolicyGetDigest() 76 | if err != nil { 77 | return nil, err 78 | } 79 | nextDigests = append(nextDigests, nextDigest) 80 | 81 | // We've consumed n digests, so adjust the slice to point to the next ones to consume to 82 | // produce a sibling node. 83 | digests = digests[n:] 84 | } 85 | 86 | // There are no digests left to produce sibling nodes. 87 | // Link child nodes to parents. 88 | for i, child := range prev { 89 | child.parent = current[i>>3] 90 | } 91 | 92 | // Grab the digests for the nodes we've just produced to create the parent nodes. 93 | prev = current 94 | digests = nextDigests 95 | 96 | if out == nil { 97 | // Save the leaf nodes to return. 98 | out = &policyOrTree{ 99 | alg: alg, 100 | leafNodes: current, 101 | } 102 | } 103 | } 104 | 105 | return out, nil 106 | } 107 | 108 | func (t *policyOrTree) selectBranch(i int) (out []tpm2.DigestList) { 109 | node := t.leafNodes[i>>3] 110 | 111 | for node != nil { 112 | out = append(out, node.digests) 113 | node = node.parent 114 | } 115 | 116 | return out 117 | } 118 | -------------------------------------------------------------------------------- /policyutil/pcr_digest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "math" 11 | 12 | "github.com/canonical/go-tpm2" 13 | ) 14 | 15 | // ComputePCRDigest computes a digest using the specified algorithm from the provided set of PCR 16 | // values and the provided PCR selections. The digest is computed the same way as PCRComputeCurrentDigest 17 | // as defined in the TPM reference implementation. It is most useful for computing an input to 18 | // [tpm2.TPMContext.PolicyPCR] or [TrialAuthPolicy.PolicyPCR], and for validating quotes and creation data. 19 | func ComputePCRDigest(alg tpm2.HashAlgorithmId, pcrs tpm2.PCRSelectionList, values tpm2.PCRValues) (tpm2.Digest, error) { 20 | if !alg.Available() { 21 | return nil, errors.New("algorithm is not available") 22 | } 23 | h := alg.NewHash() 24 | 25 | for _, s := range pcrs { 26 | if _, ok := values[s.Hash]; !ok { 27 | return nil, fmt.Errorf("the provided values don't contain digests for the selected PCR bank %v", s.Hash) 28 | } 29 | 30 | bmp, err := s.Select.ToBitmap(math.MaxUint8) 31 | if err != nil { 32 | return nil, fmt.Errorf("invalid selection: %w", err) 33 | } 34 | sel := bmp.ToPCRs() 35 | 36 | for _, i := range sel { 37 | d, ok := values[s.Hash][i] 38 | if !ok { 39 | return nil, fmt.Errorf("the provided values don't contain a digest for PCR%d in bank %v", i, s.Hash) 40 | } 41 | h.Write(d) 42 | } 43 | } 44 | 45 | return h.Sum(nil), nil 46 | } 47 | 48 | // ComputePCRDigestFromAllValues computes a digest using the specified algorithm from all of the 49 | // provided set of PCR values. The digest is computed the same way as PCRComputeCurrentDigest as 50 | // defined in the TPM reference implementation. It returns the PCR selection associated with the 51 | // computed digest. 52 | func ComputePCRDigestFromAllValues(alg tpm2.HashAlgorithmId, values tpm2.PCRValues) (tpm2.PCRSelectionList, tpm2.Digest, error) { 53 | if !alg.Available() { 54 | return nil, nil, errors.New("algorithm is not available") 55 | } 56 | pcrs, err := values.SelectionList() 57 | if err != nil { 58 | return nil, nil, err 59 | } 60 | digest, err := ComputePCRDigest(alg, pcrs, values) 61 | if err != nil { 62 | return nil, nil, err 63 | } 64 | 65 | return pcrs, digest, nil 66 | } 67 | -------------------------------------------------------------------------------- /policyutil/policyutil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package policyutil_test 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "testing" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2" 16 | "github.com/canonical/go-tpm2/testutil" 17 | ) 18 | 19 | func init() { 20 | testutil.AddCommandLineFlags() 21 | } 22 | 23 | func authSessionHandle(sc tpm2.SessionContext) tpm2.Handle { 24 | if sc == nil { 25 | return tpm2.HandlePW 26 | } 27 | return sc.Handle() 28 | } 29 | 30 | func Test(t *testing.T) { TestingT(t) } 31 | 32 | func TestMain(m *testing.M) { 33 | flag.Parse() 34 | os.Exit(func() int { 35 | if testutil.TPMBackend == testutil.TPMBackendMssim { 36 | simulatorCleanup, err := testutil.LaunchTPMSimulator(nil) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "Cannot launch TPM simulator: %v\n", err) 39 | return 1 40 | } 41 | defer simulatorCleanup() 42 | } 43 | 44 | return m.Run() 45 | }()) 46 | } 47 | -------------------------------------------------------------------------------- /ppi/ppi_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package ppi_test 6 | 7 | import ( 8 | "io" 9 | "testing" 10 | 11 | . "gopkg.in/check.v1" 12 | 13 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 14 | . "github.com/canonical/go-tpm2/ppi" 15 | "github.com/canonical/go-tpm2/testutil" 16 | ) 17 | 18 | func init() { 19 | testutil.AddCommandLineFlags() 20 | } 21 | 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | type ppiSuite struct{} 25 | 26 | var _ = Suite(&ppiSuite{}) 27 | 28 | func (s *ppiSuite) TestOperationIdClearPPRequiredOperationIdEnableTPM(c *C) { 29 | c.Check(OperationEnableTPM.ClearPPRequiredOperationId(), Equals, OperationClearPPRequiredForEnableTPM) 30 | } 31 | 32 | func (s *ppiSuite) TestOperationIdSetPPRequiredOperationIdEnableTPM(c *C) { 33 | c.Check(OperationEnableTPM.SetPPRequiredOperationId(), Equals, OperationSetPPRequiredForEnableTPM) 34 | } 35 | 36 | func (s *ppiSuite) TestOperationIdClearPPRequiredOperationIdDisableTPM(c *C) { 37 | c.Check(OperationDisableTPM.ClearPPRequiredOperationId(), Equals, OperationClearPPRequiredForDisableTPM) 38 | } 39 | 40 | func (s *ppiSuite) TestOperationIdSetPPRequiredOperationIdDisableTPM(c *C) { 41 | c.Check(OperationDisableTPM.SetPPRequiredOperationId(), Equals, OperationSetPPRequiredForDisableTPM) 42 | } 43 | 44 | func (s *ppiSuite) TestOperationIdClearPPRequiredOperationIdClearTPM(c *C) { 45 | c.Check(OperationClearTPM.ClearPPRequiredOperationId(), Equals, OperationClearPPRequiredForClearTPM) 46 | } 47 | 48 | func (s *ppiSuite) TestOperationIdSetPPRequiredOperationIdClearTPM(c *C) { 49 | c.Check(OperationClearTPM.SetPPRequiredOperationId(), Equals, OperationSetPPRequiredForClearTPM) 50 | } 51 | 52 | func (s *ppiSuite) TestOperationIdClearPPRequiredOperationIdSetPCRBanks(c *C) { 53 | c.Check(OperationSetPCRBanks.ClearPPRequiredOperationId(), Equals, OperationClearPPRequiredForChangePCRs) 54 | } 55 | 56 | func (s *ppiSuite) TestOperationIdSetPPRequiredOperationIdSetPCRBanks(c *C) { 57 | c.Check(OperationSetPCRBanks.SetPPRequiredOperationId(), Equals, OperationSetPPRequiredForChangePCRs) 58 | } 59 | 60 | func (s *ppiSuite) TestOperationIdClearPPRequiredOperationIdChangeEPS(c *C) { 61 | c.Check(OperationChangeEPS.ClearPPRequiredOperationId(), Equals, OperationClearPPRequiredForChangeEPS) 62 | } 63 | 64 | func (s *ppiSuite) TestOperationIdSetPPRequiredOperationIdChangeEPS(c *C) { 65 | c.Check(OperationChangeEPS.SetPPRequiredOperationId(), Equals, OperationSetPPRequiredForChangeEPS) 66 | } 67 | 68 | func (s *ppiSuite) TestParseVersion13(c *C) { 69 | version, err := ParseVersion("1.3") 70 | c.Check(err, IsNil) 71 | c.Check(version.Major, internal_testutil.IntEqual, 1) 72 | c.Check(version.Minor, internal_testutil.IntEqual, 3) 73 | } 74 | 75 | func (s *ppiSuite) TestParseVersion12(c *C) { 76 | version, err := ParseVersion("1.2") 77 | c.Check(err, IsNil) 78 | c.Check(version.Major, internal_testutil.IntEqual, 1) 79 | c.Check(version.Minor, internal_testutil.IntEqual, 2) 80 | } 81 | 82 | func (s *ppiSuite) TestParseVersionInvalid1(c *C) { 83 | _, err := ParseVersion("1.") 84 | c.Check(err, Equals, io.EOF) 85 | } 86 | 87 | func (s *ppiSuite) TestParseVersionInvalid2(c *C) { 88 | _, err := ParseVersion("-1.3") 89 | c.Check(err, ErrorMatches, `expected integer`) 90 | } 91 | 92 | func (s *ppiSuite) TestVersionCompareEqual(c *C) { 93 | c.Check(Version13.Compare(Version13), Equals, 0) 94 | } 95 | 96 | func (s *ppiSuite) TestVersionCompareLT(c *C) { 97 | c.Check(Version12.Compare(Version13), Equals, -1) 98 | } 99 | 100 | func (s *ppiSuite) TestVersionCompareGT(c *C) { 101 | c.Check(Version13.Compare(Version12), Equals, 1) 102 | } 103 | -------------------------------------------------------------------------------- /ppi_efi/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package ppi_efi 6 | 7 | import ( 8 | "sync" 9 | 10 | efi "github.com/canonical/go-efilib" 11 | ) 12 | 13 | func MockVars(vars efi.VarsBackend) (restore func()) { 14 | orig := customVars 15 | customVars = vars 16 | return func() { 17 | customVars = orig 18 | } 19 | } 20 | 21 | func ResetPPI() { 22 | ppiOnce = sync.Once{} 23 | ppiInstance = nil 24 | ppiErr = nil 25 | } 26 | -------------------------------------------------------------------------------- /ppi_efi/ppi_efi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | /* 6 | Package ppi_efi provides a way of interacting with the TCG PC Client Physical Presence Interface via EFI variables 7 | */ 8 | package ppi_efi 9 | 10 | import ( 11 | "sync" 12 | 13 | efi "github.com/canonical/go-efilib" 14 | internal_ppi "github.com/canonical/go-tpm2/internal/ppi" 15 | internal_ppi_efi "github.com/canonical/go-tpm2/internal/ppi_efi" 16 | "github.com/canonical/go-tpm2/ppi" 17 | ) 18 | 19 | // ErrUnavailable indicates that the EFI based physical presence interface 20 | // is not available. 21 | var ErrUnavailable = internal_ppi_efi.ErrUnavailable 22 | 23 | var ( 24 | ppiOnce sync.Once 25 | ppiInstance ppi.PPI 26 | ppiErr error 27 | 28 | customVars efi.VarsBackend 29 | ) 30 | 31 | // PPI returns a global EFI based PPI instance. If no support is available, 32 | // an [ErrUnavailable] error is returned. Calling this function will always 33 | // return either a pointer to the same interface or the same error for the 34 | // lifetime of a process. 35 | func PPI() (ppi.PPI, error) { 36 | ppiOnce.Do(func() { 37 | var backend internal_ppi.PPIBackend 38 | var version ppi.Version 39 | backend, version, ppiErr = internal_ppi_efi.NewBackend(customVars) 40 | if ppiErr != nil { 41 | return 42 | } 43 | 44 | ppiInstance = internal_ppi.New(ppi.EFI, version, backend) 45 | }) 46 | 47 | return ppiInstance, ppiErr 48 | } 49 | -------------------------------------------------------------------------------- /run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | go test -v -race -p 1 ./... -check.v -args $@ 4 | -------------------------------------------------------------------------------- /sealed_object_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/linux" 13 | "github.com/canonical/go-tpm2/mu" 14 | "github.com/canonical/go-tpm2/objectutil" 15 | "github.com/canonical/go-tpm2/util" 16 | ) 17 | 18 | // srkHandle defines the handle for the SRK 19 | const srkHandle = 0x81000001 20 | 21 | // seal protects the supplied secret in the storage hierarchy of the TPM using 22 | // a simple authorization policy that is gated on the current values of the PCRs 23 | // included in the specified selection. The sealed object and metadata are 24 | // serialized and returned in a form that can be passed to the unseal function. 25 | func seal(secret []byte, pcrSelection tpm2.PCRSelectionList) ([]byte, error) { 26 | device, err := linux.DefaultTPM2Device() 27 | if err != nil { 28 | return nil, err 29 | } 30 | tpm, err := tpm2.OpenTPMDevice(device) 31 | if err != nil { 32 | return nil, err 33 | } 34 | defer tpm.Close() 35 | 36 | // Use the shared SRK as the storage object, and assume that it already exists. 37 | srk, err := tpm.NewResourceContext(srkHandle) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | // Build the sealed object template 43 | template := objectutil.NewSealedObjectTemplate( 44 | objectutil.WithUserAuthMode(objectutil.RequirePolicy)) 45 | 46 | // Compute a simple PCR policy using the TPM's current values 47 | _, values, err := tpm.PCRRead(pcrSelection) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | digest, err := util.ComputePCRDigest(tpm2.HashAlgorithmSHA256, pcrSelection, values) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | trial := util.ComputeAuthPolicy(tpm2.HashAlgorithmSHA256) 58 | trial.PolicyPCR(digest, pcrSelection) 59 | 60 | template.AuthPolicy = trial.GetDigest() 61 | 62 | sensitive := &tpm2.SensitiveCreate{Data: secret} 63 | 64 | // Create the sealed object 65 | priv, pub, _, _, _, err := tpm.Create(srk, sensitive, template, nil, nil, nil) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // Encode and return the sealed object 71 | return mu.MarshalToBytes(priv, pub, pcrSelection) 72 | } 73 | 74 | // unseal attempts to recover a secret from the supplied blob previously created by the seal 75 | // function. 76 | func unseal(data []byte) ([]byte, error) { 77 | // Decode the sealed object 78 | var priv tpm2.Private 79 | var pub *tpm2.Public 80 | var pcrSelection tpm2.PCRSelectionList 81 | if _, err := mu.UnmarshalFromBytes(data, &priv, &pub, &pcrSelection); err != nil { 82 | return nil, err 83 | } 84 | 85 | device, err := linux.DefaultTPM2Device() 86 | if err != nil { 87 | return nil, err 88 | } 89 | tpm, err := tpm2.OpenTPMDevice(device) 90 | if err != nil { 91 | return nil, err 92 | } 93 | defer tpm.Close() 94 | 95 | srk, err := tpm.NewResourceContext(srkHandle) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | // Load the sealed object into the TPM 101 | object, err := tpm.Load(srk, priv, pub, nil) 102 | if err != nil { 103 | return nil, err 104 | } 105 | defer tpm.FlushContext(object) 106 | 107 | // Run a policy session with the PCR assertion 108 | session, err := tpm.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256) 109 | if err != nil { 110 | return nil, err 111 | } 112 | defer tpm.FlushContext(session) 113 | 114 | if err := tpm.PolicyPCR(session, nil, pcrSelection); err != nil { 115 | return nil, err 116 | } 117 | 118 | return tpm.Unseal(object, session) 119 | } 120 | 121 | func Example_sealingASecret() { 122 | // Seal a secret to the storage hierarchy of the TPM using an authorization policy 123 | // that is gated on the current value of PCR7. 124 | // 125 | // Don't assume that this is a secure way to protect a key - it's just an example! 126 | 127 | secret := []byte("secret data") 128 | pcrSelection := tpm2.PCRSelectionList{{Hash: tpm2.HashAlgorithmSHA256, Select: []int{7}}} 129 | 130 | sealedData, err := seal(secret, pcrSelection) 131 | if err != nil { 132 | fmt.Fprintln(os.Stderr, "cannot seal:", err) 133 | return 134 | } 135 | 136 | // sealedData contains a serialized blob containing our secret that has been protected by the 137 | // TPM. It could be written somewhere to be read back later on. 138 | 139 | recoveredSecret, err := unseal(sealedData) 140 | if err != nil { 141 | fmt.Fprintln(os.Stderr, "cannot unseal:", err) 142 | return 143 | } 144 | 145 | fmt.Println("recovered secret:", recoveredSecret) 146 | } 147 | -------------------------------------------------------------------------------- /strings_1_19.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.20 2 | // +build !go1.20 3 | 4 | // Copyright 2025 Canonical Ltd. 5 | // Licensed under the LGPLv3 with static-linking exception. 6 | // See LICENCE file for details. 7 | 8 | package tpm2 9 | 10 | import ( 11 | "fmt" 12 | "strconv" 13 | "unicode/utf8" 14 | ) 15 | 16 | func formatString(state fmt.State, verb rune) string { 17 | // 1 byte for "%", 5 bytes for flags, 7 bytes for max width, 1 period, 7 bytes for max precision, 1 byte for verb. 18 | // Go's fmt package caps width and precision to 1e6. 19 | var tmp [1 + 5 + 7 + 1 + 7 + 1]byte 20 | 21 | b := append(tmp[:0], '%') 22 | for _, c := range []byte{'+', '-', '#', ' ', '0'} { 23 | if !state.Flag(int(c)) { 24 | continue 25 | } 26 | b = append(b, c) 27 | } 28 | if w, ok := state.Width(); ok { 29 | b = strconv.AppendInt(b, int64(w), 10) 30 | } 31 | if p, ok := state.Precision(); ok { 32 | b = append(b, '.') 33 | b = strconv.AppendInt(b, int64(p), 10) 34 | } 35 | b = utf8.AppendRune(b, verb) 36 | return string(b) 37 | } 38 | -------------------------------------------------------------------------------- /strings_1_20.go: -------------------------------------------------------------------------------- 1 | //go:build go1.20 2 | // +build go1.20 3 | 4 | // Copyright 2025 Canonical Ltd. 5 | // Licensed under the LGPLv3 with static-linking exception. 6 | // See LICENCE file for details. 7 | 8 | package tpm2 9 | 10 | import "fmt" 11 | 12 | var formatString = fmt.FormatString 13 | -------------------------------------------------------------------------------- /strings_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "io" 9 | 10 | . "gopkg.in/check.v1" 11 | 12 | . "github.com/canonical/go-tpm2" 13 | ) 14 | 15 | type mockFmtFlags int 16 | 17 | const ( 18 | fmtPlus mockFmtFlags = 1 << iota 19 | fmtMinus 20 | fmtHash 21 | fmtSpace 22 | fmtZero 23 | widthSet 24 | precisionSet 25 | ) 26 | 27 | type mockState struct { 28 | flags mockFmtFlags 29 | width int 30 | precision int 31 | } 32 | 33 | func (*mockState) Write(b []byte) (int, error) { 34 | return 0, io.ErrShortWrite 35 | } 36 | 37 | func (s *mockState) Width() (wid int, set bool) { 38 | if s.flags&widthSet == 0 { 39 | return 0, false 40 | } 41 | return s.width, true 42 | } 43 | 44 | func (s *mockState) Precision() (prec int, set bool) { 45 | if s.flags&precisionSet == 0 { 46 | return 0, false 47 | } 48 | return s.precision, true 49 | } 50 | 51 | func (s *mockState) Flag(c int) bool { 52 | switch c { 53 | case '+': 54 | return s.flags&fmtPlus != 0 55 | case '-': 56 | return s.flags&fmtMinus != 0 57 | case '#': 58 | return s.flags&fmtHash != 0 59 | case ' ': 60 | return s.flags&fmtSpace != 0 61 | case '0': 62 | return s.flags&fmtZero != 0 63 | default: 64 | return false 65 | } 66 | } 67 | 68 | type stringsSuite struct{} 69 | 70 | var _ = Suite(&stringsSuite{}) 71 | 72 | func (*stringsSuite) TestFormatStringS(c *C) { 73 | c.Check(FormatString(new(mockState), 's'), Equals, "%s") 74 | } 75 | 76 | func (*stringsSuite) TestFormatStringV(c *C) { 77 | c.Check(FormatString(new(mockState), 'v'), Equals, "%v") 78 | } 79 | 80 | func (*stringsSuite) TestFormatStringHashX(c *C) { 81 | c.Check(FormatString(&mockState{flags: fmtHash}, 'x'), Equals, "%#x") 82 | } 83 | 84 | func (*stringsSuite) TestFormatStringHashZeroPaddedX(c *C) { 85 | c.Check(FormatString(&mockState{flags: fmtHash | fmtZero | widthSet, width: 4}, 'x'), Equals, "%#04x") 86 | } 87 | 88 | func (*stringsSuite) TestFormatStringDWithWidthAndPrecision(c *C) { 89 | c.Check(FormatString(&mockState{flags: widthSet | precisionSet, width: 2, precision: 4}, 'd'), Equals, "%2.4d") 90 | } 91 | -------------------------------------------------------------------------------- /testutil/checkers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil 6 | 7 | import ( 8 | . "gopkg.in/check.v1" 9 | 10 | "github.com/canonical/go-tpm2/mu" 11 | ) 12 | 13 | type tpmValueDeepEqualsChecker struct { 14 | *CheckerInfo 15 | } 16 | 17 | // TPMValueDeepEquals checks that the obtained TPM value is deeply 18 | // equal to the expected TPM value. This works by first checking that 19 | // both values have the same type, and then serializing them both to 20 | // the TPM wire format and checking that they are bit-for-bit identical. 21 | // Both values need to be valid TPM types for this to work, and they 22 | // need to be representable by the TPM wire format. 23 | // 24 | // For example: 25 | // 26 | // expected := &tpm2.NVPublic{ 27 | // Index: 0x0180000, 28 | // NameAlg: tpm2.HashAlgorithmSHA256, 29 | // Attrs: tpm2.NVTypeOrdinary.WithAttrs(tpm2.AttrNVAuthWrite|tpm2.AttrNVAuthRead|tpm2.AttrNVWritten), 30 | // Size: 8} 31 | // c.Check(public, TPMValueDeepEquals, expected) 32 | var TPMValueDeepEquals Checker = &tpmValueDeepEqualsChecker{ 33 | &CheckerInfo{Name: "TPMValueDeepEquals", Params: []string{"obtained", "expected"}}} 34 | 35 | func (c *tpmValueDeepEqualsChecker) Check(params []interface{}, names []string) (result bool, err string) { 36 | if !mu.IsValid(params[0]) { 37 | return false, "obtained value is not a valid TPM value" 38 | } 39 | if !mu.IsValid(params[1]) { 40 | return false, "expected value is not a valid TPM value" 41 | } 42 | 43 | return mu.DeepEqual(params[0], params[1]), "" 44 | } 45 | -------------------------------------------------------------------------------- /testutil/checkers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil_test 6 | 7 | import ( 8 | "reflect" 9 | 10 | . "gopkg.in/check.v1" 11 | 12 | "github.com/canonical/go-tpm2" 13 | . "github.com/canonical/go-tpm2/testutil" 14 | ) 15 | 16 | func testInfo(c *C, checker Checker, name string, paramNames []string) { 17 | info := checker.Info() 18 | if info.Name != name { 19 | c.Fatalf("Got name %s, expected %s", info.Name, name) 20 | } 21 | if !reflect.DeepEqual(info.Params, paramNames) { 22 | c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames) 23 | } 24 | } 25 | 26 | func testCheck(c *C, checker Checker, result bool, error string, params ...interface{}) ([]interface{}, []string) { 27 | info := checker.Info() 28 | if len(params) != len(info.Params) { 29 | c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params)) 30 | } 31 | names := append([]string{}, info.Params...) 32 | resultActual, errorActual := checker.Check(params, names) 33 | if resultActual != result || errorActual != error { 34 | c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)", 35 | info.Name, params, resultActual, errorActual, result, error) 36 | } 37 | return params, names 38 | } 39 | 40 | type checkersSuite struct{} 41 | 42 | var _ = Suite(&checkersSuite{}) 43 | 44 | func (s *checkersSuite) TestTPMValueDeepEquals(c *C) { 45 | testInfo(c, TPMValueDeepEquals, "TPMValueDeepEquals", []string{"obtained", "expected"}) 46 | 47 | expected := tpm2.NVPublic{ 48 | Index: 0x0180000, 49 | NameAlg: tpm2.HashAlgorithmSHA256, 50 | Attrs: tpm2.NVTypeOrdinary.WithAttrs(tpm2.AttrNVAuthWrite | tpm2.AttrNVAuthRead | tpm2.AttrNVWritten), 51 | Size: 8} 52 | 53 | obtained := tpm2.NVPublic{ 54 | Index: 0x0180000, 55 | NameAlg: tpm2.HashAlgorithmSHA256, 56 | Attrs: tpm2.NVTypeOrdinary.WithAttrs(tpm2.AttrNVAuthWrite | tpm2.AttrNVAuthRead | tpm2.AttrNVWritten), 57 | Size: 8} 58 | 59 | c.Check(obtained, DeepEquals, expected) 60 | testCheck(c, TPMValueDeepEquals, true, "", obtained, expected) 61 | testCheck(c, TPMValueDeepEquals, true, "", &obtained, &expected) 62 | testCheck(c, TPMValueDeepEquals, false, "", obtained, &expected) 63 | testCheck(c, TPMValueDeepEquals, false, "", &obtained, expected) 64 | 65 | expected.AuthPolicy = tpm2.Digest{} 66 | 67 | c.Check(obtained, Not(DeepEquals), expected) 68 | testCheck(c, TPMValueDeepEquals, true, "", obtained, expected) 69 | 70 | obtained.AuthPolicy = make([]byte, 32) 71 | testCheck(c, TPMValueDeepEquals, false, "", obtained, expected) 72 | 73 | testCheck(c, TPMValueDeepEquals, false, "obtained value is not a valid TPM value", 1, uint16(1)) 74 | testCheck(c, TPMValueDeepEquals, false, "expected value is not a valid TPM value", uint16(1), 1) 75 | } 76 | -------------------------------------------------------------------------------- /testutil/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package testutil contains utilities for writing unit tests using go-tpm2. 3 | */ 4 | package testutil 5 | -------------------------------------------------------------------------------- /testutil/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | ) 10 | 11 | const ( 12 | TpmFeatureSimulatorOnlyPCRAllocation = tpmFeatureSimulatorOnlyPCRAllocation 13 | ) 14 | 15 | func MockWrapMssimTransport(fn func(tpm2.Transport, TPMFeatureFlags) (*Transport, error)) (restore func()) { 16 | origWrapMssimTransport := wrapMssimTransport 17 | wrapMssimTransport = fn 18 | return func() { 19 | wrapMssimTransport = origWrapMssimTransport 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /testutil/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/rsa" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/objectutil" 13 | ) 14 | 15 | // NewExternalSealedObject is a wrapper around [objectutil.NewSealedObject] that sets 16 | // the noDA attribute. 17 | // 18 | // Deprecated: use [objectutil.NewSealedObject] with the 19 | // [objectutil.WithoutDictionaryAttackProtection] option. 20 | func NewExternalSealedObject(authValue tpm2.Auth, data []byte) (*tpm2.Public, *tpm2.Sensitive) { 21 | pub, sensitive, err := objectutil.NewSealedObject(rand.Reader, data, authValue, 22 | objectutil.WithoutDictionaryAttackProtection()) 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | return pub, sensitive 28 | } 29 | 30 | // NewExternalRSAStoragePublicKey creates the public area for a RSA storage key from the supplied 31 | // key. 32 | func NewExternalRSAStoragePublicKey(key *rsa.PublicKey) *tpm2.Public { 33 | pub, err := objectutil.NewRSAPublicKey(key, objectutil.WithoutDictionaryAttackProtection()) 34 | if err != nil { 35 | panic(err) 36 | } 37 | pub.Attrs |= (tpm2.AttrRestricted | tpm2.AttrDecrypt) 38 | pub.Attrs &^= tpm2.AttrSign 39 | pub.Params.RSADetail.Symmetric = tpm2.SymDefObject{ 40 | Algorithm: tpm2.SymObjectAlgorithmAES, 41 | KeyBits: &tpm2.SymKeyBitsU{Sym: 128}, 42 | Mode: &tpm2.SymModeU{Sym: tpm2.SymModeCFB}} 43 | 44 | return pub 45 | } 46 | 47 | // NewExternalHMACKey is a wrapper around [objectutil.NewHMACKey] that sets the 48 | // noDA attribute. 49 | // 50 | // Deprecated: use [objectutil.NewHMACKey] with the 51 | // [objectutil.WithoutDictionaryAttackProtection] option. 52 | func NewExternalHMACKey(authValue tpm2.Auth, key []byte) (*tpm2.Public, *tpm2.Sensitive) { 53 | pub, sensitive, err := objectutil.NewHMACKey(rand.Reader, key, authValue, objectutil.WithoutDictionaryAttackProtection()) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | return pub, sensitive 59 | } 60 | -------------------------------------------------------------------------------- /testutil/object.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil 6 | 7 | import ( 8 | "fmt" 9 | 10 | "github.com/canonical/go-tpm2" 11 | internal_util "github.com/canonical/go-tpm2/internal/util" 12 | "github.com/canonical/go-tpm2/mu" 13 | ) 14 | 15 | // UnwrapOuter removes an outer wrapper from the supplied sensitive data blob. The 16 | // supplied name is associated with the data. 17 | // 18 | // It checks the integrity HMAC is valid using the specified digest algorithm and 19 | // a key derived from the supplied seed and returns an error if the check fails. 20 | // 21 | // It then decrypts the data blob using the specified symmetric algorithm and a 22 | // key derived from the supplied seed and name. 23 | func UnwrapOuter(hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, name tpm2.Name, seed []byte, useIV bool, data []byte) ([]byte, error) { 24 | return internal_util.UnwrapOuter(hashAlg, symmetricAlg, name, seed, useIV, data) 25 | } 26 | 27 | // ProduceOuterWrap adds an outer wrapper to the supplied data. The supplied name 28 | // is associated with the data. 29 | // 30 | // It encrypts the data using the specified symmetric algorithm and a key derived 31 | // from the supplied seed and name. 32 | // 33 | // It then prepends an integrity HMAC of the encrypted data and the supplied 34 | // name using the specified digest algorithm and a key derived from the supplied 35 | // seed. 36 | func ProduceOuterWrap(hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, name tpm2.Name, seed []byte, useIV bool, data []byte) ([]byte, error) { 37 | return internal_util.ProduceOuterWrap(hashAlg, symmetricAlg, name, seed, useIV, data) 38 | } 39 | 40 | // PrivateToSensitive unwraps a TPM private area into its corresponding 41 | // sensitive structure. The supplied name is the name of the object 42 | // associated with sensitive. 43 | // 44 | // The removes the outer wrapper from the private area using the specified 45 | // digest algorithm, symmetric algorithm and seed. These values are 46 | // associated with the parent storage key that that is used to load the 47 | // object into the TPM. The seed is part of the parent storage key's 48 | // sensitive area and will only be known for objects created outside of the 49 | // TPM and then imported, or objects created inside of the TPM that can be 50 | // duplicated and unwrapped outside of the TPM. 51 | func PrivateToSensitive(private tpm2.Private, name tpm2.Name, hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, seed []byte) (sensitive *tpm2.Sensitive, err error) { 52 | data, err := UnwrapOuter(hashAlg, symmetricAlg, name, seed, true, private) 53 | if err != nil { 54 | return nil, fmt.Errorf("cannot unwrap outer wrapper: %w", err) 55 | } 56 | 57 | if _, err := mu.UnmarshalFromBytes(data, mu.Sized(&sensitive)); err != nil { 58 | return nil, fmt.Errorf("cannot unmarhsal sensitive: %w", err) 59 | } 60 | 61 | return sensitive, nil 62 | } 63 | 64 | // SensitiveToPrivate creates a TPM private area from the supplied 65 | // sensitive structure. The supplied name is the name of the object 66 | // associated with sensitive. 67 | // 68 | // This applies an outer wrapper to the sensitive structure using the 69 | // specified digest algorithm, symmetric algorithm and seed. These values 70 | // are associated with the parent storage key that that will be used to 71 | // load the object into the TPM. The seed is part of the parent storage 72 | // key's sensitive area and will only be known for objects created outside 73 | // of the TPM and then imported, or objects created inside of the TPM that 74 | // can be duplicated and unwrapped outside of the TPM. 75 | func SensitiveToPrivate(sensitive *tpm2.Sensitive, name tpm2.Name, hashAlg tpm2.HashAlgorithmId, symmetricAlg *tpm2.SymDefObject, seed []byte) (tpm2.Private, error) { 76 | private, err := mu.MarshalToBytes(mu.Sized(sensitive)) 77 | if err != nil { 78 | return nil, fmt.Errorf("cannot marshal sensitive: %w", err) 79 | } 80 | 81 | private, err = ProduceOuterWrap(hashAlg, symmetricAlg, name, seed, true, private) 82 | if err != nil { 83 | return nil, fmt.Errorf("cannot apply outer wrapper: %w", err) 84 | } 85 | 86 | return private, nil 87 | } 88 | -------------------------------------------------------------------------------- /testutil/templates.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | "github.com/canonical/go-tpm2/objectutil" 10 | ) 11 | 12 | func rsaSchemeOption(scheme *tpm2.RSAScheme) objectutil.PublicTemplateOption { 13 | schemeId := tpm2.RSASchemeNull 14 | hashAlg := tpm2.HashAlgorithmNull 15 | if scheme != nil { 16 | schemeId = scheme.Scheme 17 | details := scheme.AnyDetails() 18 | if details != nil { 19 | hashAlg = details.HashAlg 20 | } 21 | } 22 | return objectutil.WithRSAScheme(schemeId, hashAlg) 23 | } 24 | 25 | func eccSchemeOption(scheme *tpm2.ECCScheme) objectutil.PublicTemplateOption { 26 | schemeId := tpm2.ECCSchemeNull 27 | hashAlg := tpm2.HashAlgorithmNull 28 | if scheme != nil { 29 | schemeId = scheme.Scheme 30 | details := scheme.AnyDetails() 31 | if details != nil { 32 | hashAlg = details.HashAlg 33 | } 34 | } 35 | return objectutil.WithECCScheme(schemeId, hashAlg) 36 | } 37 | 38 | // NewRSAStorageKeyTemplate is a wrapper around [objectutil.NewRSAStorageKeyTemplate] that defines the 39 | // noDA attribute. 40 | // 41 | // Deprecated: Use [objectutil.NewRSAStorageKeyTemplate] with the 42 | // [objectutil.WithoutDictionaryAttackProtection] option. 43 | func NewRSAStorageKeyTemplate() *tpm2.Public { 44 | return objectutil.NewRSAStorageKeyTemplate(objectutil.WithoutDictionaryAttackProtection()) 45 | } 46 | 47 | // NewRestrictedRSASigningKeyTemplate is a wrapper around [objectutil.NewRSAAttestationKeyTemplate] 48 | // that defines the noDA attribute. 49 | // 50 | // Deprecated: Use [objectutil.NewRSAAttestationKeyTemplate] with the 51 | // [objectutil.WithoutDictionaryAttackProtection] option. 52 | func NewRestrictedRSASigningKeyTemplate(scheme *tpm2.RSAScheme) *tpm2.Public { 53 | options := []objectutil.PublicTemplateOption{objectutil.WithoutDictionaryAttackProtection()} 54 | if scheme != nil { 55 | options = append(options, rsaSchemeOption(scheme)) 56 | } 57 | return objectutil.NewRSAAttestationKeyTemplate(options...) 58 | } 59 | 60 | // NewRSAKeyTemplate is a wrapper around [objectutil.NewRSAKeyTemplate] that defines the noDA 61 | // attribute. 62 | // 63 | // Deprecated: Use [objectutil.NewRSAKeyTemplate] with the 64 | // [objectutil.WithoutDictionaryAttackProtection] option. 65 | func NewRSAKeyTemplate(usage objectutil.Usage, scheme *tpm2.RSAScheme) *tpm2.Public { 66 | return objectutil.NewRSAKeyTemplate(usage, 67 | objectutil.WithoutDictionaryAttackProtection(), 68 | rsaSchemeOption(scheme)) 69 | } 70 | 71 | // NewSealedObject is a wrapper around [objectutil.NewSealedObjectTemplate] that defines the noDA 72 | // attribute. 73 | // 74 | // Deprecated: Use [objectutil.NewSealedObjectTemplate] with the 75 | // [objectutil.WithoutDictionaryAttackProtection] option. 76 | func NewSealedObjectTemplate() *tpm2.Public { 77 | return objectutil.NewSealedObjectTemplate(objectutil.WithoutDictionaryAttackProtection()) 78 | } 79 | 80 | // NewECCStorageKeyTemplate is a wrapper around [objectutil.NewECCStorageKeyTemplate] that defines the 81 | // noDA attribute. 82 | // 83 | // Deprecated: Use [objectutil.NewECCStorageKeyTemplate] with the 84 | // [objectutil.WithoutDictionaryAttackProtection] option. 85 | func NewECCStorageKeyTemplate() *tpm2.Public { 86 | return objectutil.NewECCStorageKeyTemplate(objectutil.WithoutDictionaryAttackProtection()) 87 | } 88 | 89 | // NewRestrictedECCSigningKeyTemplate is a wrapper around [objectutil.NewECCAttestationKeyTemplate] 90 | // that defines the noDA attribute. 91 | // 92 | // Deprecated: Use [objectutil.NewECCAttestationKeyTemplate] with the 93 | // [objectutil.WithoutDictionaryAttackProtection] option. 94 | func NewRestrictedECCSigningKeyTemplate(scheme *tpm2.ECCScheme) *tpm2.Public { 95 | options := []objectutil.PublicTemplateOption{objectutil.WithoutDictionaryAttackProtection()} 96 | if scheme != nil { 97 | options = append(options, eccSchemeOption(scheme)) 98 | } 99 | return objectutil.NewECCAttestationKeyTemplate(options...) 100 | } 101 | 102 | // NewECCKeyTemplate is a wrapper around [objectutil.NewECCKeyTemplate] that defines the noDA 103 | // attribute. 104 | // 105 | // Deprecated: Use [objectutil.NewECCKeyTemplate] with the 106 | // [objectutil.WithoutDictionaryAttackProtection] option. 107 | func NewECCKeyTemplate(usage objectutil.Usage, scheme *tpm2.ECCScheme) *tpm2.Public { 108 | return objectutil.NewECCKeyTemplate(usage, 109 | objectutil.WithoutDictionaryAttackProtection(), 110 | eccSchemeOption(scheme)) 111 | } 112 | -------------------------------------------------------------------------------- /testutil/testutil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil_test 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "testing" 12 | 13 | . "github.com/canonical/go-tpm2/testutil" 14 | 15 | . "gopkg.in/check.v1" 16 | ) 17 | 18 | func init() { 19 | AddCommandLineFlags() 20 | } 21 | 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | func TestMain(m *testing.M) { 25 | flag.Parse() 26 | os.Exit(func() int { 27 | if TPMBackend == TPMBackendMssim { 28 | simulatorCleanup, err := LaunchTPMSimulator(nil) 29 | if err != nil { 30 | fmt.Fprintf(os.Stderr, "Cannot launch TPM simulator: %v\n", err) 31 | return 1 32 | } 33 | defer simulatorCleanup() 34 | } 35 | 36 | return m.Run() 37 | }()) 38 | } 39 | -------------------------------------------------------------------------------- /testutil/tpm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package testutil_test 6 | 7 | import ( 8 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 9 | . "github.com/canonical/go-tpm2/testutil" 10 | . "gopkg.in/check.v1" 11 | ) 12 | 13 | type tpmSuite struct { 14 | TPMTest 15 | } 16 | 17 | var _ = Suite(&tpmSuite{}) 18 | 19 | func (s *tpmSuite) TestNewTransportBackedDeviceClosable(c *C) { 20 | device := NewTransportBackedDevice(s.Transport, true, -1) 21 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 22 | 23 | transport, err := device.Open() 24 | c.Assert(err, IsNil) 25 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 1) 26 | 27 | var tmpl TransportWrapper 28 | c.Check(transport, Implements, &tmpl) 29 | c.Check(transport.(TransportWrapper).Unwrap(), Equals, s.Transport) 30 | 31 | c.Check(transport.Close(), IsNil) 32 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 33 | c.Check(transport.Close(), ErrorMatches, `transport already closed`) 34 | 35 | tpm := s.TPM 36 | s.TPM = nil 37 | c.Check(tpm.Close(), internal_testutil.IsOneOf(ErrorMatches), []string{ 38 | `.*use of closed network connection$`, 39 | `.*file already closed$`, 40 | `.*transport already closed$`}) 41 | } 42 | 43 | func (s *tpmSuite) TestNewTransportBackedDeviceNotClosable(c *C) { 44 | device := NewTransportBackedDevice(s.Transport, false, -1) 45 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 46 | 47 | transport, err := device.Open() 48 | c.Assert(err, IsNil) 49 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 1) 50 | 51 | var tmpl TransportWrapper 52 | c.Check(transport, Implements, &tmpl) 53 | c.Check(transport.(TransportWrapper).Unwrap(), Equals, s.Transport) 54 | 55 | c.Check(transport.Close(), IsNil) 56 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 57 | c.Check(transport.Close(), ErrorMatches, `transport already closed`) 58 | 59 | // The test fixture will fail if the underlying transport was closed 60 | // unexpectedly 61 | } 62 | 63 | func (s *tpmSuite) TestNewTransportBackedDeviceMultipleOpen(c *C) { 64 | device := NewTransportBackedDevice(s.Transport, false, 2) 65 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 66 | 67 | transport1, err := device.Open() 68 | c.Assert(err, IsNil) 69 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 1) 70 | 71 | transport2, err := device.Open() 72 | c.Assert(err, IsNil) 73 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 2) 74 | 75 | c.Check(transport1.Close(), IsNil) 76 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 1) 77 | c.Check(transport2.Close(), IsNil) 78 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 79 | 80 | // The test fixture will fail if the underlying transport was closed 81 | // unexpectedly 82 | } 83 | 84 | func (s *tpmSuite) TestNewTransportBackedDeviceMaxOpen(c *C) { 85 | device := NewTransportBackedDevice(s.Transport, false, 2) 86 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 87 | 88 | transport1, err := device.Open() 89 | c.Assert(err, IsNil) 90 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 1) 91 | 92 | transport2, err := device.Open() 93 | c.Assert(err, IsNil) 94 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 2) 95 | 96 | _, err = device.Open() 97 | c.Check(err, Equals, ErrNoTPMDevice) 98 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 2) 99 | 100 | c.Check(transport1.Close(), IsNil) 101 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 1) 102 | c.Check(transport2.Close(), IsNil) 103 | c.Check(device.NumberOpen(), internal_testutil.IntEqual, 0) 104 | 105 | // The test fixture will fail if the underlying transport was closed 106 | // unexpectedly 107 | } 108 | -------------------------------------------------------------------------------- /tpm_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/linux" 13 | "github.com/canonical/go-tpm2/mssim" 14 | ) 15 | 16 | func ExampleOpenTPMDevice_linux() { 17 | // Open the default Linux TPM2 character device. 18 | 19 | device, err := linux.DefaultTPM2Device() 20 | if err != nil { 21 | fmt.Fprintln(os.Stderr, err) 22 | return 23 | } 24 | tpm, err := tpm2.OpenTPMDevice(device) 25 | if err != nil { 26 | fmt.Fprintln(os.Stderr, err) 27 | return 28 | } 29 | defer tpm.Close() 30 | 31 | // Use TPMContext 32 | // ... 33 | } 34 | 35 | func ExampleOpenTPMDevice_simulator() { 36 | // Open the TPM simulator on the default port (2321). 37 | 38 | tpm, err := tpm2.OpenTPMDevice(mssim.DefaultDevice) 39 | if err != nil { 40 | fmt.Fprintln(os.Stderr, err) 41 | return 42 | } 43 | defer tpm.Close() 44 | 45 | // Use TPMContext 46 | // ... 47 | } 48 | 49 | func ExampleTPMContext_cleartextPassphraseAuth() { 50 | // Change the authorization value for the storage hierarchy using 51 | // a cleartext passphrase for authorization. 52 | 53 | oldPassphrase := []byte("passphrase") 54 | newPassphrase := []byte("esarhpssap") 55 | 56 | device, err := linux.DefaultTPM2Device() 57 | if err != nil { 58 | fmt.Fprintln(os.Stderr, err) 59 | return 60 | } 61 | tpm, err := tpm2.OpenTPMDevice(device) 62 | if err != nil { 63 | fmt.Fprintln(os.Stderr, err) 64 | return 65 | } 66 | defer tpm.Close() 67 | 68 | tpm.OwnerHandleContext().SetAuthValue(oldPassphrase) 69 | 70 | // Change the new authorization value. Note that we don't pass 71 | // in a session argument - TPMContext creates a password session 72 | // automatically. Both the old and new passphrases are sent to the 73 | // TPM in cleartext. 74 | if err := tpm.HierarchyChangeAuth(tpm.OwnerHandleContext(), newPassphrase, nil); err != nil { 75 | fmt.Fprintln(os.Stderr, err) 76 | return 77 | } 78 | } 79 | 80 | func ExampleTPMContext_hMACSessionAuth() { 81 | // Change the authorization value for the storage hierarchy using 82 | // a HMAC session for authorization. 83 | 84 | oldPassphrase := []byte("passphrase") 85 | newPassphrase := []byte("esarhpssap") 86 | 87 | device, err := linux.DefaultTPM2Device() 88 | if err != nil { 89 | fmt.Fprintln(os.Stderr, err) 90 | return 91 | } 92 | tpm, err := tpm2.OpenTPMDevice(device) 93 | if err != nil { 94 | fmt.Fprintln(os.Stderr, err) 95 | return 96 | } 97 | defer tpm.Close() 98 | 99 | // Create an unbounded, unsalted HMAC session. 100 | session, err := tpm.StartAuthSession(nil, nil, tpm2.SessionTypeHMAC, nil, tpm2.HashAlgorithmSHA256) 101 | if err != nil { 102 | fmt.Fprintln(os.Stderr, err) 103 | return 104 | } 105 | defer tpm.FlushContext(session) 106 | 107 | tpm.OwnerHandleContext().SetAuthValue(oldPassphrase) 108 | 109 | // Change the authorization value. Note that we pass in the HMAC session 110 | // context. The current passphrase is not sent to the TPM - it is used to 111 | // derive the key used to create a command HMAC, which is then verified on 112 | // the TPM. The new passphrase is sent to the TPM in cleartext. 113 | if err := tpm.HierarchyChangeAuth(tpm.OwnerHandleContext(), newPassphrase, session); err != nil { 114 | fmt.Fprintln(os.Stderr, err) 115 | return 116 | } 117 | } 118 | 119 | func ExampleTPMContext_policySessionAuth() { 120 | // Change the authorization value of an existing NV index at handle 0x0180000 using a 121 | // policy session for authorization. The policy for the index asserts that the caller 122 | // must know the existing authorization value. 123 | 124 | handle := tpm2.Handle(0x01800000) 125 | oldPassphrase := []byte("passphrase") 126 | newPassphrase := []byte("esarhpssap") 127 | 128 | device, err := linux.DefaultTPM2Device() 129 | if err != nil { 130 | fmt.Fprintln(os.Stderr, err) 131 | return 132 | } 133 | tpm, err := tpm2.OpenTPMDevice(device) 134 | if err != nil { 135 | fmt.Fprintln(os.Stderr, err) 136 | return 137 | } 138 | defer tpm.Close() 139 | 140 | index, err := tpm.NewResourceContext(handle) 141 | if err != nil { 142 | fmt.Fprintln(os.Stderr, err) 143 | return 144 | } 145 | 146 | session, err := tpm.StartAuthSession(nil, nil, tpm2.SessionTypePolicy, nil, tpm2.HashAlgorithmSHA256) 147 | if err != nil { 148 | fmt.Fprintln(os.Stderr, err) 149 | return 150 | } 151 | defer tpm.FlushContext(session) 152 | 153 | if err := tpm.PolicyCommandCode(session, tpm2.CommandNVChangeAuth); err != nil { 154 | fmt.Fprintln(os.Stderr, err) 155 | return 156 | } 157 | if err := tpm.PolicyAuthValue(session); err != nil { 158 | fmt.Fprintln(os.Stderr, err) 159 | return 160 | } 161 | 162 | index.SetAuthValue(oldPassphrase) 163 | 164 | if err := tpm.NVChangeAuth(index, newPassphrase, session); err != nil { 165 | fmt.Fprintln(os.Stderr, err) 166 | return 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "time" 11 | ) 12 | 13 | // InfiniteTimeout can be used to configure an infinite timeout. 14 | const InfiniteTimeout = -1 * time.Millisecond 15 | 16 | var ( 17 | // ErrTimeoutNotSupported indicates that a [Transport] implementation does not support 18 | // configuring the command timeout. 19 | ErrTimeoutNotSupported = errors.New("configurable command timeouts are not supported") 20 | 21 | // ErrTransportBusy should be returned from calls to Write if a previously 22 | // submitted command has not finished or not all of its bytes have 23 | // been read back yet. 24 | ErrTransportBusy = errors.New("transport is busy") 25 | 26 | // ErrTransportClosed indicates that a transport is closed. 27 | ErrTransportClosed = errors.New("transport already closed") 28 | ) 29 | 30 | // TCTI represents a communication channel to a TPM implementation. 31 | // 32 | // Deprecated: use [Transport] instead. 33 | type TCTI = Transport 34 | 35 | // Transport represents a communication channel to a TPM implementation. 36 | // 37 | // Implementations of the [io.Reader] and [io.Writer] parts of this can expect that they 38 | // will be called from the same goroutine and that they won't be used from multiple 39 | // goroutines. 40 | // 41 | // Implementations should handle the [io.Closer] part being called from any goroutine, 42 | // even when a Read or Write is in progress on another goroutine. 43 | type Transport interface { 44 | // Read is used to receive a response to a previously transmitted command. 45 | // Implementations should support a response being read using multiple calls 46 | // to Read (partial reads). The transportutil.BufferResponses API can assist 47 | // for transports that don't support this. Implementations that support 48 | // partial reads should not return parts of more than one command in a single 49 | // call. 50 | // 51 | // Implementations should only use io.EOF to indicate that no more bytes will 52 | // ever be read from this transport. Callers should be able to identify the end 53 | // of a response based on the ResponseHeader itself and the number of bytes read. 54 | Read(p []byte) (int, error) 55 | 56 | // Write is used to transmit a serialized command to the TPM implementation. 57 | // Implementations should support a command being sent across multiple calls 58 | // to Write, and should be able to identify the end of a command based on the 59 | // CommandHeader itself. The transportutil.BufferCommands API can assist for 60 | // transports that don't support this. 61 | Write(p []byte) (int, error) 62 | 63 | // Close closes the transport. 64 | Close() error 65 | } 66 | 67 | type transportWriter struct { 68 | w io.Writer 69 | } 70 | 71 | func (w *transportWriter) Write(data []byte) (int, error) { 72 | n, err := w.w.Write(data) 73 | if err != nil { 74 | return n, &TransportError{"write", err} 75 | } 76 | return n, nil 77 | } 78 | 79 | func wrapTransportWriteErrors(w io.Writer) io.Writer { 80 | return &transportWriter{w: w} 81 | } 82 | 83 | type transportReader struct { 84 | r io.Reader 85 | } 86 | 87 | func (r *transportReader) Read(data []byte) (int, error) { 88 | n, err := r.r.Read(data) 89 | if err != nil { 90 | return n, &TransportError{"read", err} 91 | } 92 | return n, nil 93 | } 94 | 95 | func wrapTransportReadErrors(r io.Reader) io.Reader { 96 | return &transportReader{r: r} 97 | } 98 | -------------------------------------------------------------------------------- /transportutil/buffer_test.go: -------------------------------------------------------------------------------- 1 | package transportutil_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/canonical/go-tpm2" 8 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 9 | "github.com/canonical/go-tpm2/mu" 10 | . "github.com/canonical/go-tpm2/transportutil" 11 | . "gopkg.in/check.v1" 12 | ) 13 | 14 | type bufferSuite struct{} 15 | 16 | var _ = Suite(&bufferSuite{}) 17 | 18 | type countingWriter struct { 19 | buf *bytes.Buffer 20 | n int 21 | } 22 | 23 | func (w *countingWriter) Write(data []byte) (int, error) { 24 | w.n += 1 25 | return w.buf.Write(data) 26 | } 27 | 28 | func (s *bufferSuite) TestBufferCommands(c *C) { 29 | hdr := tpm2.CommandHeader{ 30 | Tag: tpm2.TagNoSessions, 31 | CommandSize: 12, 32 | CommandCode: tpm2.CommandStartup, 33 | } 34 | w := &countingWriter{buf: new(bytes.Buffer)} 35 | _, err := mu.MarshalToWriter(BufferCommands(w, 4096), hdr, mu.Raw(internal_testutil.DecodeHexString(c, "0000"))) 36 | c.Check(err, IsNil) 37 | c.Check(w.n, Equals, 1) 38 | c.Check(w.buf.Bytes(), DeepEquals, internal_testutil.DecodeHexString(c, "80010000000c000001440000")) 39 | } 40 | 41 | func (s *bufferSuite) TestBufferCommandsShortWrite(c *C) { 42 | hdr := tpm2.CommandHeader{ 43 | Tag: tpm2.TagNoSessions, 44 | CommandSize: 12, 45 | CommandCode: tpm2.CommandStartup, 46 | } 47 | w := &countingWriter{buf: new(bytes.Buffer)} 48 | _, err := mu.MarshalToWriter(BufferCommands(w, 4096), hdr, mu.Raw(internal_testutil.DecodeHexString(c, "00000000"))) 49 | c.Check(err, internal_testutil.ErrorIs, io.ErrShortWrite) 50 | c.Check(w.n, Equals, 1) 51 | c.Check(w.buf.Bytes(), DeepEquals, internal_testutil.DecodeHexString(c, "80010000000c000001440000")) 52 | } 53 | 54 | func (s *bufferSuite) TestBufferCommandsTooLarge(c *C) { 55 | w := &countingWriter{buf: new(bytes.Buffer)} 56 | _, err := BufferCommands(w, 4096).Write(internal_testutil.DecodeHexString(c, "800100001388000001440000")) 57 | c.Check(err, ErrorMatches, `invalid command size \(5000 bytes\)`) 58 | c.Check(w.n, Equals, 0) 59 | } 60 | 61 | type countingReader struct { 62 | buf io.Reader 63 | n int 64 | lastReadSize int 65 | } 66 | 67 | func (r *countingReader) Read(data []byte) (int, error) { 68 | r.n += 1 69 | r.lastReadSize = len(data) 70 | 71 | return r.buf.Read(data) 72 | } 73 | 74 | func (s *bufferSuite) TestBufferResponses(c *C) { 75 | r := &countingReader{buf: bytes.NewReader(internal_testutil.DecodeHexString(c, "80010000000a00000000"))} 76 | 77 | var hdr tpm2.ResponseHeader 78 | _, err := mu.UnmarshalFromReader(BufferResponses(r, 4096), &hdr) 79 | c.Check(err, IsNil) 80 | c.Check(r.n, Equals, 1) 81 | c.Check(r.lastReadSize, Equals, 4096) 82 | c.Check(hdr, DeepEquals, tpm2.ResponseHeader{ 83 | Tag: tpm2.TagNoSessions, 84 | ResponseSize: 10, 85 | ResponseCode: tpm2.ResponseSuccess, 86 | }) 87 | } 88 | 89 | func (s *bufferSuite) TestBufferResponsesEOF(c *C) { 90 | r := &countingReader{buf: bytes.NewReader(internal_testutil.DecodeHexString(c, "80010000000a00000000"))} 91 | 92 | data, err := io.ReadAll(BufferResponses(r, 4096)) 93 | c.Check(err, IsNil) 94 | c.Check(data, DeepEquals, internal_testutil.DecodeHexString(c, "80010000000a00000000")) 95 | } 96 | -------------------------------------------------------------------------------- /transportutil/transportutil.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package transportutil provides helpers when implementing tpm2.Transport 3 | */ 4 | package transportutil 5 | 6 | import "github.com/canonical/go-tpm2" 7 | 8 | var ( 9 | // ErrBusy should be returned from calls to Write if a previously 10 | // submitted command has not finished or not all of its bytes have 11 | // been read back yet. 12 | ErrBusy = tpm2.ErrTransportBusy 13 | 14 | // ErrClosed indicates that a transport is closed. 15 | ErrClosed = tpm2.ErrTransportClosed 16 | ) 17 | 18 | type transportResult struct { 19 | n int 20 | err error 21 | } 22 | 23 | type ( 24 | transportResultChan = chan transportResult 25 | transportResultSendChan = chan<- transportResult 26 | transportResultRecvChan = <-chan transportResult 27 | ) 28 | -------------------------------------------------------------------------------- /transportutil/transportutil_test.go: -------------------------------------------------------------------------------- 1 | package transportutil_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/canonical/go-tpm2/testutil" 7 | . "gopkg.in/check.v1" 8 | ) 9 | 10 | func init() { 11 | testutil.AddCommandLineFlags() 12 | } 13 | 14 | func Test(t *testing.T) { TestingT(t) } 15 | -------------------------------------------------------------------------------- /types_algs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "bytes" 9 | "reflect" 10 | "testing" 11 | 12 | . "github.com/canonical/go-tpm2" 13 | "github.com/canonical/go-tpm2/mu" 14 | ) 15 | 16 | type TestSchemeKeyedHashUContainer struct { 17 | Scheme KeyedHashSchemeId 18 | Details *SchemeKeyedHashU 19 | } 20 | 21 | func TestSchemeKeyedHashUnion(t *testing.T) { 22 | for _, data := range []struct { 23 | desc string 24 | in TestSchemeKeyedHashUContainer 25 | out []byte 26 | err string 27 | }{ 28 | { 29 | desc: "HMAC", 30 | in: TestSchemeKeyedHashUContainer{ 31 | Scheme: KeyedHashSchemeHMAC, 32 | Details: &SchemeKeyedHashU{HMAC: &SchemeHMAC{HashAlg: HashAlgorithmSHA256}}}, 33 | out: []byte{0x00, 0x05, 0x00, 0x0b}, 34 | }, 35 | { 36 | desc: "Null", 37 | in: TestSchemeKeyedHashUContainer{Scheme: KeyedHashSchemeNull, Details: &SchemeKeyedHashU{}}, 38 | out: []byte{0x00, 0x10}, 39 | }, 40 | { 41 | desc: "InvalidSelector", 42 | in: TestSchemeKeyedHashUContainer{Scheme: KeyedHashSchemeId(HashAlgorithmSHA256)}, 43 | out: []byte{0x00, 0x0b}, 44 | err: "cannot unmarshal argument 0 whilst processing element of type tpm2.SchemeKeyedHashU: invalid selector value: TPM_ALG_SHA256\n\n" + 45 | "=== BEGIN STACK ===\n" + 46 | "... tpm2_test.TestSchemeKeyedHashUContainer field Details\n" + 47 | "=== END STACK ===\n", 48 | }, 49 | } { 50 | t.Run(data.desc, func(t *testing.T) { 51 | out, err := mu.MarshalToBytes(data.in) 52 | if err != nil { 53 | t.Fatalf("MarshalToBytes failed: %v", err) 54 | } 55 | 56 | if !bytes.Equal(out, data.out) { 57 | t.Errorf("MarshalToBytes returned an unexpected sequence of bytes: %x", out) 58 | } 59 | 60 | var a TestSchemeKeyedHashUContainer 61 | n, err := mu.UnmarshalFromBytes(out, &a) 62 | if data.err != "" { 63 | if err == nil { 64 | t.Fatalf("UnmarshaFromBytes was expected to fail") 65 | } 66 | if err.Error() != data.err { 67 | t.Errorf("UnmarshalFromBytes returned an unexpected error: %v", err) 68 | } 69 | } else { 70 | if err != nil { 71 | t.Fatalf("UnmarshalFromBytes failed: %v", err) 72 | } 73 | if n != len(out) { 74 | t.Errorf("UnmarshalFromBytes consumed the wrong number of bytes (%d)", n) 75 | } 76 | 77 | if !reflect.DeepEqual(data.in, a) { 78 | t.Errorf("UnmarshalFromBytes didn't return the original data") 79 | } 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /types_attributes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | . "gopkg.in/check.v1" 9 | 10 | . "github.com/canonical/go-tpm2" 11 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 12 | ) 13 | 14 | type typesAttributesSuite struct{} 15 | 16 | var _ = Suite(&typesAttributesSuite{}) 17 | 18 | func (s *typesAttributesSuite) TestLocalityIsValidTrue1(c *C) { 19 | locality := LocalityZero 20 | c.Check(locality.IsValid(), internal_testutil.IsTrue) 21 | } 22 | 23 | func (s *typesAttributesSuite) TestLocalityIsValidTrue2(c *C) { 24 | locality := LocalityZero | LocalityThree 25 | c.Check(locality.IsValid(), internal_testutil.IsTrue) 26 | } 27 | 28 | func (s *typesAttributesSuite) TestLocalityIsValidTrue3(c *C) { 29 | locality := Locality(41) 30 | c.Check(locality.IsValid(), internal_testutil.IsTrue) 31 | } 32 | 33 | func (s *typesAttributesSuite) TestLocalityIsValidFalse(c *C) { 34 | var locality Locality 35 | c.Check(locality.IsValid(), internal_testutil.IsFalse) 36 | } 37 | 38 | func (s *typesAttributesSuite) TestLocalityIsExtendedTrue1(c *C) { 39 | locality := Locality(41) 40 | c.Check(locality.IsExtended(), internal_testutil.IsTrue) 41 | } 42 | 43 | func (s *typesAttributesSuite) TestLocalityIsExtendedTrue2(c *C) { 44 | locality := Locality(249) 45 | c.Check(locality.IsExtended(), internal_testutil.IsTrue) 46 | } 47 | 48 | func (s *typesAttributesSuite) TestLocalityIsExtendedFalse1(c *C) { 49 | locality := LocalityZero 50 | c.Check(locality.IsExtended(), internal_testutil.IsFalse) 51 | } 52 | 53 | func (s *typesAttributesSuite) TestLocalityIsExtendedFalse2(c *C) { 54 | locality := LocalityZero | LocalityThree 55 | c.Check(locality.IsExtended(), internal_testutil.IsFalse) 56 | } 57 | 58 | func (s *typesAttributesSuite) TestLocalityIsMultipleFalse1(c *C) { 59 | locality := Locality(41) 60 | c.Check(locality.IsMultiple(), internal_testutil.IsFalse) 61 | } 62 | 63 | func (s *typesAttributesSuite) TestLocalityIsMultipleFalse2(c *C) { 64 | locality := LocalityZero 65 | c.Check(locality.IsMultiple(), internal_testutil.IsFalse) 66 | } 67 | 68 | func (s *typesAttributesSuite) TestLocalityIsMultipleTrue1(c *C) { 69 | locality := LocalityZero | LocalityThree 70 | c.Check(locality.IsMultiple(), internal_testutil.IsTrue) 71 | } 72 | 73 | func (s *typesAttributesSuite) TestLocalityIsMultipleTrue2(c *C) { 74 | locality := LocalityZero | LocalityThree | LocalityFour 75 | c.Check(locality.IsMultiple(), internal_testutil.IsTrue) 76 | } 77 | 78 | func (s *typesAttributesSuite) TestLocalityValuesExtended(c *C) { 79 | locality := Locality(41) 80 | c.Check(locality.Values(), DeepEquals, []uint8{41}) 81 | } 82 | 83 | func (s *typesAttributesSuite) TestLocalityValuesSingle(c *C) { 84 | locality := LocalityZero 85 | c.Check(locality.Values(), DeepEquals, []uint8{0}) 86 | } 87 | 88 | func (s *typesAttributesSuite) TestLocalityValuesMultiple(c *C) { 89 | locality := LocalityZero | LocalityThree 90 | c.Check(locality.Values(), DeepEquals, []uint8{0, 3}) 91 | } 92 | 93 | func (s *typesAttributesSuite) TestLocalityValueExtended(c *C) { 94 | locality := Locality(41) 95 | c.Check(locality.Value(), internal_testutil.IntEqual, 41) 96 | } 97 | 98 | func (s *typesAttributesSuite) TestLocalityValueSingle1(c *C) { 99 | locality := LocalityZero 100 | c.Check(locality.Value(), internal_testutil.IntEqual, 0) 101 | } 102 | 103 | func (s *typesAttributesSuite) TestLocalityValueSingle3(c *C) { 104 | locality := LocalityThree 105 | c.Check(locality.Value(), internal_testutil.IntEqual, 3) 106 | } 107 | 108 | func (s *typesAttributesSuite) TestLocalityValueMultiplePanic(c *C) { 109 | locality := LocalityZero | LocalityThree 110 | c.Check(func() { locality.Value() }, PanicMatches, `unset or multiple localities are represented`) 111 | } 112 | 113 | func (s *typesAttributesSuite) TestLocalityValueZeroPanic(c *C) { 114 | var locality Locality 115 | c.Check(func() { locality.Value() }, PanicMatches, `unset or multiple localities are represented`) 116 | } 117 | -------------------------------------------------------------------------------- /types_context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // This file contains types defined in section 14 (Context Data) 8 | // in part 2 of the library spec. 9 | 10 | // ContextData corresponds to the TPM2B_CONTEXT_DATA type. 11 | type ContextData []byte 12 | 13 | // Context corresponds to the TPMS_CONTEXT type which represents a saved 14 | // object or session context. 15 | type Context struct { 16 | Sequence uint64 // Sequence number of the context 17 | SavedHandle Handle // Handle indicating if this is a session or object 18 | Hierarchy Handle // Hierarchy of the context 19 | Blob ContextData // Encrypted context data and integrity HMAC 20 | } 21 | -------------------------------------------------------------------------------- /types_creation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // This file contains types defined in section 15 (Creation Data) 8 | // in part 2 of the library spec. 9 | 10 | // CreationData corresponds to the TPMS_CREATION_DATA type, which provides 11 | // information about the creation environment of an object. 12 | type CreationData struct { 13 | PCRSelect PCRSelectionList // PCRs included in PCRDigest 14 | 15 | // Digest of the selected PCRs using the name algorithm of the object associated with this data. 16 | PCRDigest Digest 17 | Locality Locality // Locality at which the object was created 18 | ParentNameAlg AlgorithmId // Name algorithm of the parent 19 | ParentName Name // Name of the parent 20 | ParentQualifiedName Name // Qualified name of the parent 21 | OutsideInfo Data // External information provided by the caller 22 | } 23 | -------------------------------------------------------------------------------- /types_handles.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | // This file contains types defined in section 7 (Handles) in 8 | // part 2 of the library spec. 9 | 10 | // Handle corresponds to the TPM_HANDLE type, and is a numeric 11 | // identifier that references a resource on the TPM. 12 | type Handle uint32 13 | 14 | // Type returns the type of the handle. 15 | func (h Handle) Type() HandleType { 16 | return HandleType(h >> 24) 17 | } 18 | 19 | const ( 20 | HandleOwner Handle = 0x40000001 // TPM_RH_OWNER 21 | HandleNull Handle = 0x40000007 // TPM_RH_NULL 22 | HandleUnassigned Handle = 0x40000008 // TPM_RH_UNASSIGNED 23 | HandlePW Handle = 0x40000009 // TPM_RS_PW 24 | HandleLockout Handle = 0x4000000a // TPM_RH_LOCKOUT 25 | HandleEndorsement Handle = 0x4000000b // TPM_RH_ENDORSEMENT 26 | HandlePlatform Handle = 0x4000000c // TPM_RH_PLATFORM 27 | HandlePlatformNV Handle = 0x4000000d // TPM_RH_PLATFORM_NV 28 | HandleFWOwner Handle = 0x40000140 // TPM_RH_FW_OWNER 29 | HandleFWEndorsement Handle = 0x40000141 // TPM_RH_FW_ENDORSEMENT 30 | HandleFWPlatform Handle = 0x40000142 // TPM_RH_FW_PLATFORM 31 | HandleFWNull Handle = 0x40000143 // TPM_RH_FW_NULL 32 | HandleSVNOwnerBase Handle = 0x40010000 // TPM_RH_SVN_OWNER_BASE 33 | HandleSVNEndorsementBase Handle = 0x40020000 // TPM_RH_SVN_ENDORSEMENT_BASE 34 | HandleSVNPlatformBase Handle = 0x40030000 // TPM_RH_SVN_PLATFORM_BASE 35 | HandleSVNNullBase Handle = 0x40040000 // TPM_RH_SVN_NULL_BASE 36 | ) 37 | 38 | // HandleType corresponds to the TPM_HT type, and is used to 39 | // identify the type of a Handle. 40 | type HandleType uint8 41 | 42 | // BaseHandle returns the first handle for the handle type. 43 | func (h HandleType) BaseHandle() Handle { 44 | return Handle(h) << 24 45 | } 46 | 47 | const ( 48 | HandleTypePCR HandleType = 0x00 // TPM_HT_PCR 49 | HandleTypeNVIndex HandleType = 0x01 // TPM_HT_NV_INDEX 50 | HandleTypeHMACSession HandleType = 0x02 // TPM_HT_HMAC_SESSION 51 | HandleTypeLoadedSession HandleType = 0x02 // TPM_HT_LOADED_SESSION 52 | HandleTypePolicySession HandleType = 0x03 // TPM_HT_POLICY_SESSION 53 | HandleTypeSavedSession HandleType = 0x03 // TPM_HT_SAVED_SESSION 54 | HandleTypePermanent HandleType = 0x40 // TPM_HT_PERMANENT 55 | HandleTypeTransient HandleType = 0x80 // TPM_HT_TRANSIENT 56 | HandleTypePersistent HandleType = 0x81 // TPM_HT_PERSISTENT 57 | ) 58 | -------------------------------------------------------------------------------- /types_handles_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "testing" 9 | 10 | . "github.com/canonical/go-tpm2" 11 | ) 12 | 13 | func TestHandle(t *testing.T) { 14 | for _, data := range []struct { 15 | desc string 16 | handle Handle 17 | handleType HandleType 18 | }{ 19 | { 20 | desc: "PCR", 21 | handle: 0x0000000a, 22 | handleType: HandleTypePCR, 23 | }, 24 | { 25 | desc: "NVIndex", 26 | handle: 0x0180ff00, 27 | handleType: HandleTypeNVIndex, 28 | }, 29 | { 30 | desc: "HMACSession", 31 | handle: 0x02000001, 32 | handleType: HandleTypeHMACSession, 33 | }, 34 | { 35 | desc: "PolicySession", 36 | handle: 0x03000001, 37 | handleType: HandleTypePolicySession, 38 | }, 39 | { 40 | desc: "Permanent", 41 | handle: HandleOwner, 42 | handleType: HandleTypePermanent, 43 | }, 44 | { 45 | desc: "Transient", 46 | handle: 0x80000003, 47 | handleType: HandleTypeTransient, 48 | }, 49 | { 50 | desc: "Persistent", 51 | handle: 0x81000000, 52 | handleType: HandleTypePersistent, 53 | }, 54 | } { 55 | t.Run(data.desc, func(t *testing.T) { 56 | if data.handle.Type() != data.handleType { 57 | t.Errorf("Unexpected handle type (got %x, expected %x)", data.handle.Type(), data.handleType) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /types_nv.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | 11 | "github.com/canonical/go-tpm2/mu" 12 | ) 13 | 14 | // This file contains types defined in section 13 (NV Storage Structures) 15 | // in part 2 of the library spec. 16 | 17 | // NVType corresponds to the TPM_NT type. 18 | type NVType uint32 19 | 20 | // WithAttrs returns NVAttributes for this type with the specified attributes set. 21 | func (t NVType) WithAttrs(attrs NVAttributes) NVAttributes { 22 | return NVAttributes(t<<4) | attrs 23 | } 24 | 25 | const ( 26 | NVTypeOrdinary NVType = 0 // TPM_NT_ORDINARY 27 | NVTypeCounter NVType = 1 // TPM_NT_COUNTER 28 | NVTypeBits NVType = 2 // TPM_NT_BITS 29 | NVTypeExtend NVType = 4 // TPM_NT_EXTEND 30 | NVTypePinFail NVType = 8 // TPM_NT_PIN_FAIL 31 | NVTypePinPass NVType = 9 // TPM_NT_PIN_PASS 32 | ) 33 | 34 | // NVPinCounterParams corresponds to the TPMS_NV_PIN_COUNTER_PARAMETERS type. 35 | type NVPinCounterParams struct { 36 | Count uint32 37 | Limit uint32 38 | } 39 | 40 | // NVAttributes corresponds to the TPMA_NV type, and represents the 41 | // attributes of a NV index. When exchanged with the TPM, some bits 42 | // are reserved to encode the type of the NV index ([NVType]). 43 | type NVAttributes uint32 44 | 45 | // Type returns the NVType encoded in a NVAttributes value. 46 | func (a NVAttributes) Type() NVType { 47 | return NVType((a & attrNVType) >> attrNVTypeShift) 48 | } 49 | 50 | // AttrsOnly returns the NVAttributes without the encoded [NVType]. 51 | func (a NVAttributes) AttrsOnly() NVAttributes { 52 | return a & ^attrNVType 53 | } 54 | 55 | const ( 56 | AttrNVPPWrite NVAttributes = 1 << 0 // TPMA_NV_PPWRITE 57 | AttrNVOwnerWrite NVAttributes = 1 << 1 // TPMA_NV_OWNERWRITE 58 | AttrNVAuthWrite NVAttributes = 1 << 2 // TPMA_NV_AUTHWRITE 59 | AttrNVPolicyWrite NVAttributes = 1 << 3 // TPMA_NV_POLICY_WRITE 60 | attrNVTypeShift = 4 61 | attrNVType NVAttributes = 0xf << attrNVTypeShift 62 | AttrNVPolicyDelete NVAttributes = 1 << 10 // TPMA_NV_POLICY_DELETE 63 | AttrNVWriteLocked NVAttributes = 1 << 11 // TPMA_NV_WRITELOCKED 64 | AttrNVWriteAll NVAttributes = 1 << 12 // TPMA_NV_WRITEALL 65 | AttrNVWriteDefine NVAttributes = 1 << 13 // TPMA_NV_WRITEDEFINE 66 | AttrNVWriteStClear NVAttributes = 1 << 14 // TPMA_NV_WRITE_STCLEAR 67 | AttrNVGlobalLock NVAttributes = 1 << 15 // TPMA_NV_GLOBALLOCK 68 | AttrNVPPRead NVAttributes = 1 << 16 // TPMA_NV_PPREAD 69 | AttrNVOwnerRead NVAttributes = 1 << 17 // TPMA_NV_OWNERREAD 70 | AttrNVAuthRead NVAttributes = 1 << 18 // TPMA_NV_AUTHREAD 71 | AttrNVPolicyRead NVAttributes = 1 << 19 // TPMA_NV_POLICYREAD 72 | AttrNVNoDA NVAttributes = 1 << 25 // TPMA_NV_NO_DA 73 | AttrNVOrderly NVAttributes = 1 << 26 // TPMA_NV_ORDERLY 74 | AttrNVClearStClear NVAttributes = 1 << 27 // TPMA_NV_CLEAR_STCLEAR 75 | AttrNVReadLocked NVAttributes = 1 << 28 // TPMA_NV_READLOCKED 76 | AttrNVWritten NVAttributes = 1 << 29 // TPMA_NV_WRITTEN 77 | AttrNVPlatformCreate NVAttributes = 1 << 30 // TPMA_NV_PLATFORMCREATE 78 | AttrNVReadStClear NVAttributes = 1 << 31 // TPMA_NV_READ_STCLEAR 79 | ) 80 | 81 | // NVPublic corresponds to the TPMS_NV_PUBLIC type, which describes a NV index. 82 | type NVPublic struct { 83 | Index Handle // Handle of the NV index 84 | NameAlg HashAlgorithmId // NameAlg is the digest algorithm used to compute the name of the index 85 | Attrs NVAttributes // Attributes of this index 86 | AuthPolicy Digest // Authorization policy for this index 87 | Size uint16 // Size of this index 88 | } 89 | 90 | // Name computes the name of this NV index 91 | func (p *NVPublic) ComputeName() (Name, error) { 92 | if !p.NameAlg.Available() { 93 | return nil, fmt.Errorf("unsupported name algorithm or algorithm not linked into binary: %v", p.NameAlg) 94 | } 95 | h := p.NameAlg.NewHash() 96 | if _, err := mu.MarshalToWriter(h, p); err != nil { 97 | return nil, fmt.Errorf("cannot marshal public object: %v", err) 98 | } 99 | return mu.MustMarshalToBytes(p.NameAlg, mu.RawBytes(h.Sum(nil))), nil 100 | } 101 | 102 | func (p *NVPublic) compareName(name Name) bool { 103 | n, err := p.ComputeName() 104 | if err != nil { 105 | return false 106 | } 107 | return bytes.Equal(n, name) 108 | } 109 | 110 | // Name implements [github.com/canonical/go-tpm2/objectutil.Named] and 111 | // [github.com/canonical/go-tpm2/policyutil.Named]. 112 | // 113 | // This computes the name from the public area. If the name cannot be computed 114 | // then an invalid name is returned ([Name.Type] will return NameTypeInvalid). 115 | func (p *NVPublic) Name() Name { 116 | name, err := p.ComputeName() 117 | if err != nil { 118 | return Name{0, 0} 119 | } 120 | return name 121 | } 122 | 123 | // Deprecated: there is currently no use for this. 124 | func (p *NVPublic) Handle() Handle { 125 | return p.Index 126 | } 127 | -------------------------------------------------------------------------------- /types_nv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | 11 | . "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/testutil" 13 | ) 14 | 15 | func TestNVPublicName(t *testing.T) { 16 | tpm, _, closeTPM := testutil.NewTPMContextT(t, testutil.TPMFeatureOwnerHierarchy|testutil.TPMFeatureNV) 17 | defer closeTPM() 18 | 19 | owner := tpm.OwnerHandleContext() 20 | 21 | pub := NVPublic{ 22 | Index: Handle(0x0181ffff), 23 | NameAlg: HashAlgorithmSHA256, 24 | Attrs: NVTypeOrdinary.WithAttrs(AttrNVAuthWrite | AttrNVAuthRead), 25 | Size: 64} 26 | rc, err := tpm.NVDefineSpace(owner, nil, &pub, nil) 27 | if err != nil { 28 | t.Fatalf("NVDefineSpace failed: %v", err) 29 | } 30 | defer undefineNVSpace(t, tpm, rc, owner) 31 | 32 | name := pub.Name() 33 | 34 | // rc.Name() is what the TPM returned from NVReadPublic 35 | if !bytes.Equal(rc.Name(), name) { 36 | t.Errorf("NVPublic.Name() returned an unexpected name") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /types_objects_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | "bytes" 9 | "reflect" 10 | "testing" 11 | 12 | . "github.com/canonical/go-tpm2" 13 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 14 | "github.com/canonical/go-tpm2/mu" 15 | "github.com/canonical/go-tpm2/objectutil" 16 | "github.com/canonical/go-tpm2/testutil" 17 | 18 | . "gopkg.in/check.v1" 19 | ) 20 | 21 | type typesObjectsSuite struct{} 22 | 23 | var _ = Suite(&typesObjectsSuite{}) 24 | 25 | func (s *typesObjectsSuite) TestPublicIsStorageParentRSAValid(c *C) { 26 | pub := objectutil.NewRSAStorageKeyTemplate() 27 | c.Check(pub.IsStorageParent(), internal_testutil.IsTrue) 28 | } 29 | 30 | func (s *typesObjectsSuite) TestPublicIsStorageParentECCValid(c *C) { 31 | pub := objectutil.NewECCStorageKeyTemplate() 32 | c.Check(pub.IsStorageParent(), internal_testutil.IsTrue) 33 | } 34 | 35 | func (s *typesObjectsSuite) TestPublicIsStorageParentSymmetric(c *C) { 36 | pub := objectutil.NewSymmetricStorageKeyTemplate() 37 | c.Check(pub.IsStorageParent(), internal_testutil.IsTrue) 38 | } 39 | 40 | func (s *typesObjectsSuite) TestPublicIsStorageParentKeyedHash(c *C) { 41 | pub := objectutil.NewDerivationParentTemplate() 42 | c.Check(pub.IsStorageParent(), internal_testutil.IsFalse) 43 | } 44 | 45 | func (s *typesObjectsSuite) TestPublicIsStorageParentRSASign(c *C) { 46 | pub := objectutil.NewRSAAttestationKeyTemplate() 47 | c.Check(pub.IsStorageParent(), internal_testutil.IsFalse) 48 | } 49 | 50 | func (s *typesObjectsSuite) TestPublicIsStorageParentRSANoNameAlg(c *C) { 51 | pub := objectutil.NewRSAStorageKeyTemplate() 52 | pub.NameAlg = HashAlgorithmNull 53 | c.Check(pub.IsStorageParent(), internal_testutil.IsFalse) 54 | } 55 | 56 | type TestPublicIDUContainer struct { 57 | Alg ObjectTypeId 58 | Unique *PublicIDU 59 | } 60 | 61 | func TestPublicIDUnion(t *testing.T) { 62 | for _, data := range []struct { 63 | desc string 64 | in TestPublicIDUContainer 65 | out []byte 66 | err string 67 | }{ 68 | { 69 | desc: "RSA", 70 | in: TestPublicIDUContainer{Alg: ObjectTypeRSA, 71 | Unique: &PublicIDU{RSA: PublicKeyRSA{0x01, 0x02, 0x03}}}, 72 | out: []byte{0x00, 0x01, 0x00, 0x03, 0x01, 0x02, 0x03}, 73 | }, 74 | { 75 | desc: "KeyedHash", 76 | in: TestPublicIDUContainer{Alg: ObjectTypeKeyedHash, 77 | Unique: &PublicIDU{KeyedHash: Digest{0x04, 0x05, 0x06, 0x07}}}, 78 | out: []byte{0x00, 0x08, 0x00, 0x04, 0x04, 0x05, 0x06, 0x07}, 79 | }, 80 | { 81 | desc: "InvalidSelector", 82 | in: TestPublicIDUContainer{Alg: ObjectTypeId(AlgorithmNull), 83 | Unique: &PublicIDU{Sym: Digest{0x04, 0x05, 0x06, 0x07}}}, 84 | out: []byte{0x00, 0x10}, 85 | err: "cannot unmarshal argument 0 whilst processing element of type tpm2.PublicIDU: invalid selector value: TPM_ALG_NULL\n\n" + 86 | "=== BEGIN STACK ===\n" + 87 | "... tpm2_test.TestPublicIDUContainer field Unique\n" + 88 | "=== END STACK ===\n", 89 | }, 90 | } { 91 | t.Run(data.desc, func(t *testing.T) { 92 | out, err := mu.MarshalToBytes(data.in) 93 | if err != nil { 94 | t.Fatalf("MarshalToBytes failed: %v", err) 95 | } 96 | 97 | if !bytes.Equal(out, data.out) { 98 | t.Fatalf("MarshalToBytes returned an unexpected byte sequence: %x", out) 99 | } 100 | 101 | var a TestPublicIDUContainer 102 | n, err := mu.UnmarshalFromBytes(out, &a) 103 | if data.err != "" { 104 | if err == nil { 105 | t.Fatalf("UnmarshalFromBytes was expected to fail") 106 | } 107 | if err.Error() != data.err { 108 | t.Errorf("UnmarshalFromBytes returned an unexpected error: %v", err) 109 | } 110 | } else { 111 | if err != nil { 112 | t.Fatalf("UnmarshalFromBytes failed: %v", err) 113 | } 114 | if n != len(out) { 115 | t.Errorf("UnmarshalFromBytes consumed the wrong number of bytes (%d)", n) 116 | } 117 | 118 | if !reflect.DeepEqual(data.in, a) { 119 | t.Errorf("UnmarshalFromBytes didn't return the original data") 120 | } 121 | } 122 | }) 123 | } 124 | } 125 | 126 | func TestPublicName(t *testing.T) { 127 | tpm, _, closeTPM := testutil.NewTPMContextT(t, testutil.TPMFeatureOwnerHierarchy) 128 | defer closeTPM() 129 | 130 | primary := createRSASrkForTesting(t, tpm, nil) 131 | defer flushContext(t, tpm, primary) 132 | 133 | pub, _, _, err := tpm.ReadPublic(primary) 134 | if err != nil { 135 | t.Fatalf("ReadPublic failed: %v", err) 136 | } 137 | 138 | name, err := pub.ComputeName() 139 | if err != nil { 140 | t.Fatalf("Public.Name() failed: %v", err) 141 | } 142 | 143 | // primary.Name() is what the TPM returned at object creation 144 | if !bytes.Equal(primary.Name(), name) { 145 | t.Errorf("Public.Name() returned an unexpected name") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package tpm2_test 6 | 7 | import ( 8 | . "gopkg.in/check.v1" 9 | 10 | . "github.com/canonical/go-tpm2" 11 | internal_testutil "github.com/canonical/go-tpm2/internal/testutil" 12 | "github.com/canonical/go-tpm2/mu" 13 | ) 14 | 15 | type typesSuite struct{} 16 | 17 | var _ = Suite(&typesSuite{}) 18 | 19 | func (s *typesSuite) TestMarshalPCRValues(c *C) { 20 | values := make(PCRValues) 21 | c.Assert(values.SetValue(HashAlgorithmSHA256, 7, internal_testutil.DecodeHexString(c, "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865")), IsNil) 22 | c.Assert(values.SetValue(HashAlgorithmSHA1, 4, internal_testutil.DecodeHexString(c, "7448d8798a4380162d4b56f9b452e2f6f9e24e7a")), IsNil) 23 | c.Assert(values.SetValue(HashAlgorithmSHA256, 4, internal_testutil.DecodeHexString(c, "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3")), IsNil) 24 | 25 | b, err := mu.MarshalToBytes(values) 26 | c.Check(err, IsNil) 27 | c.Check(b, DeepEquals, internal_testutil.DecodeHexString(c, "00000002000403100000000b039000000000000300147448d8798a4380162d4b56f9b452e2f6f9e24e7a002053c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c300204355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865")) 28 | } 29 | 30 | func (s *typesSuite) TestUnmarshalPCRValues(c *C) { 31 | expected := make(PCRValues) 32 | c.Assert(expected.SetValue(HashAlgorithmSHA256, 7, internal_testutil.DecodeHexString(c, "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865")), IsNil) 33 | c.Assert(expected.SetValue(HashAlgorithmSHA1, 4, internal_testutil.DecodeHexString(c, "7448d8798a4380162d4b56f9b452e2f6f9e24e7a")), IsNil) 34 | c.Assert(expected.SetValue(HashAlgorithmSHA256, 4, internal_testutil.DecodeHexString(c, "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3")), IsNil) 35 | 36 | b := internal_testutil.DecodeHexString(c, "00000002000403100000000b039000000000000300147448d8798a4380162d4b56f9b452e2f6f9e24e7a002053c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c300204355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865") 37 | 38 | var values PCRValues 39 | _, err := mu.UnmarshalFromBytes(b, &values) 40 | c.Check(err, IsNil) 41 | c.Check(values, DeepEquals, expected) 42 | } 43 | 44 | func (s *typesSuite) TestMarshalPCRValuesInvalidPCR(c *C) { 45 | values := PCRValues{HashAlgorithmSHA256: {4000: internal_testutil.DecodeHexString(c, "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865")}} 46 | 47 | _, err := mu.MarshalToBytes(values) 48 | c.Check(err, ErrorMatches, "cannot marshal argument 0 whilst processing element of type tpm2.PCRValues: invalid selection list: cannot marshal argument 0 whilst processing element of type tpm2.PCRSelection: invalid PCR index \\(> 2040\\)\n\n"+ 49 | "=== BEGIN STACK ===\n"+ 50 | "... tpm2.PCRSelectionList index 0\n"+ 51 | "=== END STACK ===\n") 52 | } 53 | 54 | func (s *typesSuite) TestUnmarshalPCRValuesInvalidPayload(c *C) { 55 | b := internal_testutil.DecodeHexString(c, "00000002000403100000000b079000000000000300147448d8798a4380162d4b56f9b452e2f6f9e24e7a002053c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c300204355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865") 56 | 57 | var values PCRValues 58 | _, err := mu.UnmarshalFromBytes(b, &values) 59 | c.Check(err, ErrorMatches, `cannot unmarshal argument 0 whilst processing element of type tpm2.Digest: unexpected EOF 60 | 61 | === BEGIN STACK === 62 | ... tpm2.DigestList index 0 63 | ... tpm2.PCRValues location .*\/types\.go:[[:digit:]]*, argument 1 64 | === END STACK === 65 | `) 66 | } 67 | 68 | func (s *typesSuite) TestUnmarshalPCRValuesInvalidDigestAlgorithm(c *C) { 69 | b := internal_testutil.DecodeHexString(c, "00000002000403100000001b039000000000000300147448d8798a4380162d4b56f9b452e2f6f9e24e7a002053c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c300204355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865") 70 | 71 | var values PCRValues 72 | _, err := mu.UnmarshalFromBytes(b, &values) 73 | c.Check(err, ErrorMatches, "cannot unmarshal argument 0 whilst processing element of type tpm2.PCRValues: invalid digest algorithm") 74 | } 75 | 76 | func (s *typesSuite) TestUnmarshalPCRValuesInsufficientDigests(c *C) { 77 | b := internal_testutil.DecodeHexString(c, "00000002000403100000000b039000000000000200147448d8798a4380162d4b56f9b452e2f6f9e24e7a002053c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3") 78 | 79 | var values PCRValues 80 | _, err := mu.UnmarshalFromBytes(b, &values) 81 | c.Check(err, ErrorMatches, "cannot unmarshal argument 0 whilst processing element of type tpm2.PCRValues: insufficient digests") 82 | } 83 | 84 | func (s *typesSuite) TestUnmarshalPCRValuesInvalidDigestSize(c *C) { 85 | b := internal_testutil.DecodeHexString(c, "00000002000403100000000b039000000000000300147448d8798a4380162d4b56f9b452e2f6f9e24e7a0014cab3fe06fad053beb8ebfd8977b010655bfdd3c300204355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865") 86 | 87 | var values PCRValues 88 | _, err := mu.UnmarshalFromBytes(b, &values) 89 | c.Check(err, ErrorMatches, "cannot unmarshal argument 0 whilst processing element of type tpm2.PCRValues: invalid digest size") 90 | } 91 | -------------------------------------------------------------------------------- /util/cphash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "encoding/binary" 9 | "errors" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/mu" 13 | ) 14 | 15 | // ComputeCpHash computes a command parameter digest from the specified command code, the supplied 16 | // handles, and parameters using the specified digest algorithm. 17 | // 18 | // The required parameters is defined in part 3 of the TPM 2.0 Library Specification for the 19 | // specific command. 20 | // 21 | // The result of this is useful for extended authorization commands that bind an authorization to 22 | // a command and set of command parameters, such as [tpm2.TPMContext.PolicySigned], 23 | // [tpm2.TPMContext.PolicySecret], [tpm2.TPMContext.PolicyTicket] and 24 | // [tpm2.TPMContext.PolicyCpHash]. 25 | // 26 | // Deprecated: Use [policyutil.ComputeCpHash]. 27 | func ComputeCpHash(alg tpm2.HashAlgorithmId, command tpm2.CommandCode, handles []Entity, params ...interface{}) (tpm2.Digest, error) { 28 | if !alg.Available() { 29 | return nil, errors.New("algorithm is not available") 30 | } 31 | 32 | cpBytes, err := mu.MarshalToBytes(params...) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | h := alg.NewHash() 38 | 39 | binary.Write(h, binary.BigEndian, command) 40 | for _, handle := range handles { 41 | h.Write(handle.Name()) 42 | } 43 | h.Write(cpBytes) 44 | 45 | return h.Sum(nil), nil 46 | } 47 | -------------------------------------------------------------------------------- /util/credential.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "crypto/rand" 9 | 10 | "github.com/canonical/go-tpm2" 11 | "github.com/canonical/go-tpm2/objectutil" 12 | ) 13 | 14 | // MakeCredential performs the duties of a certificate authority in order to create an activation 15 | // credential. It establishes a seed which is used to protect the activation credential (see 16 | // section 24 - "Credential Protection" of Part 1 of the Trusted Platform Module Library 17 | // specification). 18 | // 19 | // The encrypted and integrity protected credential blob and a secret are returned, and these can 20 | // be supplied to the TPM2_ActivateCredential command on the TPM on which both the private part of 21 | // key and the object associated with objectName are loaded in order to recover the activation 22 | // credential. 23 | // 24 | // Deprecated: Use [objectutil.MakeCredential]. 25 | func MakeCredential(key *tpm2.Public, credential tpm2.Digest, objectName tpm2.Name) (credentialBlob tpm2.IDObjectRaw, secret tpm2.EncryptedSecret, err error) { 26 | return objectutil.MakeCredential(rand.Reader, key, credential, objectName) 27 | } 28 | -------------------------------------------------------------------------------- /util/crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "math/big" 9 | ) 10 | 11 | func zeroExtendBytes(x *big.Int, l int) (out []byte) { 12 | out = make([]byte, l) 13 | tmp := x.Bytes() 14 | copy(out[len(out)-len(tmp):], tmp) 15 | return 16 | } 17 | -------------------------------------------------------------------------------- /util/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package util contains helper functions that are useful when using go-tpm2. 3 | */ 4 | package util 5 | -------------------------------------------------------------------------------- /util/duplication.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "crypto" 9 | "crypto/rand" 10 | 11 | "github.com/canonical/go-tpm2" 12 | "github.com/canonical/go-tpm2/objectutil" 13 | ) 14 | 15 | // UnwrapDuplicationObject unwraps the supplied duplication object and returns the corresponding 16 | // sensitive area. The duplication object will normally be created by executing the 17 | // [tpm2.TPMContext.Duplicate] command. 18 | // 19 | // If outerSecret is supplied then it is assumed that the object has an outer duplication wrapper. 20 | // For an object duplicated with [tpm2.TPMContext.Duplicate], outerSecret is the secret structure 21 | // returned by this command. In this case, privKey, outerHashAlg and outerSymmetricAlg must be 22 | // supplied - privKey is the key that recovers the seed used to generate the outer wrapper (the new 23 | // parent when using [tpm2.TPMContext.Duplicate]), outerHashAlg is the algorithm used for integrity 24 | // checking and key derivation (the new parent's name algorithm when using 25 | // [tpm2.TPMContext.Duplicate]) and must not be [tpm2.HashAlgorithmNull], and outerSymmetricAlg 26 | // defines the symmetric algorithm for the outer wrapper (the new parent's symmetric algorithm when 27 | // using [tpm2.TPMContext.Duplicate]) and must not be [tpm2.SymObjectAlgorithmNull]). 28 | // 29 | // If innerSymmetricAlg is supplied and the Algorithm field is not [tpm2.SymObjectAlgorithmNull], 30 | // then it is assumed that the object has an inner duplication wrapper. In this case, the symmetric 31 | // key for the inner wrapper must be supplied using the innerSymmetricKey argument. 32 | // 33 | // Deprecated: Use [objectutil.UnwrapDuplicated]. 34 | func UnwrapDuplicationObject(duplicate tpm2.Private, public *tpm2.Public, privKey crypto.PrivateKey, outerHashAlg tpm2.HashAlgorithmId, outerSymmetricAlg *tpm2.SymDefObject, outerSecret tpm2.EncryptedSecret, innerSymmetricKey tpm2.Data, innerSymmetricAlg *tpm2.SymDefObject) (*tpm2.Sensitive, error) { 35 | return objectutil.UnwrapDuplicated(duplicate, public, privKey, outerHashAlg, outerSymmetricAlg, outerSecret, innerSymmetricKey, innerSymmetricAlg) 36 | } 37 | 38 | // CreateDuplicationObject creates a duplication object that can be imported in to a TPM with the 39 | // [tpm2.TPMContext.Import] command from the supplied sensitive area. 40 | // 41 | // If parentPublic is supplied, an outer duplication wrapper will be applied to the duplication 42 | // object. The parentPublic argument should correspond to the public area of the storage key to 43 | // which the duplication object will be imported. A secret structure will be returned as 44 | // [tpm2.EncryptedSecret] which can be used by the private part of parentPublic in order to 45 | // recover the seed used to generate the outer wrapper. 46 | // 47 | // If innerSymmetricAlg is supplied and the Algorithm field is not [tpm2.SymObjectAlgorithmNull], 48 | // this function will apply an inner duplication wrapper to the duplication object. If 49 | // innerSymmetricKey is supplied, it will be used as the symmetric key for the inner wrapper. It 50 | // must have a size appropriate for the selected symmetric algorithm. If innerSymmetricKey is not 51 | // supplied, a symmetric key will be created and returned as [tpm2.Data]. 52 | // 53 | // Deprecated: Use [objectutil.CreateImportable]. 54 | func CreateDuplicationObject(sensitive *tpm2.Sensitive, public, parentPublic *tpm2.Public, innerSymmetricKey tpm2.Data, innerSymmetricAlg *tpm2.SymDefObject) (innerSymmetricKeyOut tpm2.Data, duplicate tpm2.Private, outerSecret tpm2.EncryptedSecret, err error) { 55 | return objectutil.CreateImportable(rand.Reader, sensitive, public, parentPublic, innerSymmetricKey, innerSymmetricAlg) 56 | } 57 | -------------------------------------------------------------------------------- /util/entity.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import "github.com/canonical/go-tpm2/objectutil" 8 | 9 | // Entity is a type that has a name. 10 | type Entity = objectutil.Named 11 | -------------------------------------------------------------------------------- /util/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | var ZeroExtendBytes = zeroExtendBytes 8 | -------------------------------------------------------------------------------- /util/keys_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util_test 6 | 7 | import ( 8 | "crypto/ecdsa" 9 | "crypto/elliptic" 10 | "crypto/rand" 11 | "crypto/rsa" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2" 16 | "github.com/canonical/go-tpm2/testutil" 17 | . "github.com/canonical/go-tpm2/util" 18 | ) 19 | 20 | type keysSuite struct { 21 | testutil.TPMTest 22 | } 23 | 24 | var _ = Suite(&keysSuite{}) 25 | 26 | func (s *keysSuite) TestNewExternalRSAPublicKeyWithDefaults(c *C) { 27 | key, err := rsa.GenerateKey(rand.Reader, 2048) 28 | c.Assert(err, IsNil) 29 | 30 | pub := NewExternalRSAPublicKeyWithDefaults(0, &key.PublicKey) 31 | c.Check(pub, NotNil) 32 | c.Check(pub.Params.RSADetail.Exponent, Equals, uint32(key.E)) 33 | c.Check(pub.Unique.RSA, DeepEquals, tpm2.PublicKeyRSA(key.N.Bytes())) 34 | 35 | _, err = s.TPM.LoadExternal(nil, pub, tpm2.HandleOwner) 36 | c.Check(err, IsNil) 37 | } 38 | 39 | func (s *keysSuite) TestNewExternalECCPublicKeyWithDefaults(c *C) { 40 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 41 | c.Assert(err, IsNil) 42 | 43 | pub := NewExternalECCPublicKeyWithDefaults(0, &key.PublicKey) 44 | c.Check(pub, NotNil) 45 | c.Check(pub.Params.ECCDetail.CurveID, Equals, tpm2.ECCCurveNIST_P256) 46 | c.Check(pub.Unique.ECC.X, DeepEquals, tpm2.ECCParameter(ZeroExtendBytes(key.X, 32))) 47 | c.Check(pub.Unique.ECC.Y, DeepEquals, tpm2.ECCParameter(ZeroExtendBytes(key.Y, 32))) 48 | 49 | _, err = s.TPM.LoadExternal(nil, pub, tpm2.HandleOwner) 50 | c.Check(err, IsNil) 51 | } 52 | 53 | func (s *keysSuite) TestNewExternalSealedObject(c *C) { 54 | authValue := []byte("1234") 55 | data := []byte("secret data") 56 | 57 | pub, sensitive := NewExternalSealedObject(tpm2.HashAlgorithmSHA256, authValue, data) 58 | c.Check(pub, NotNil) 59 | c.Check(sensitive, NotNil) 60 | c.Check(sensitive.AuthValue, DeepEquals, tpm2.Auth(append(authValue, make([]byte, 28)...))) 61 | c.Check(sensitive.Sensitive.Bits, DeepEquals, tpm2.SensitiveData(data)) 62 | 63 | pub.Attrs |= tpm2.AttrNoDA 64 | 65 | object, err := s.TPM.LoadExternal(sensitive, pub, tpm2.HandleNull) 66 | c.Assert(err, IsNil) 67 | 68 | object.SetAuthValue(authValue) 69 | 70 | recoveredData, err := s.TPM.Unseal(object, nil) 71 | c.Check(err, IsNil) 72 | c.Check(recoveredData, DeepEquals, tpm2.SensitiveData(data)) 73 | } 74 | 75 | func (s *keysSuite) TestNewExternalHMACKeyWithDefaults(c *C) { 76 | authValue := []byte("1234") 77 | key := make([]byte, 32) 78 | rand.Read(key) 79 | 80 | pub, sensitive := NewExternalHMACKeyWithDefaults(authValue, key) 81 | c.Check(pub, NotNil) 82 | c.Check(sensitive, NotNil) 83 | c.Check(sensitive.AuthValue, DeepEquals, tpm2.Auth(append(authValue, make([]byte, 28)...))) 84 | c.Check(sensitive.Sensitive.Bits, DeepEquals, tpm2.SensitiveData(key)) 85 | 86 | _, err := s.TPM.LoadExternal(sensitive, pub, tpm2.HandleNull) 87 | c.Assert(err, IsNil) 88 | } 89 | -------------------------------------------------------------------------------- /util/pcr_digest.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | "github.com/canonical/go-tpm2/policyutil" 10 | ) 11 | 12 | // ComputePCRDigest computes a digest using the specified algorithm from the provided set of PCR 13 | // values and the provided PCR selections. The digest is computed the same way as PCRComputeCurrentDigest 14 | // as defined in the TPM reference implementation. It is most useful for computing an input to 15 | // [tpm2.TPMContext.PolicyPCR] or [TrialAuthPolicy.PolicyPCR], and for validating quotes and creation data. 16 | // 17 | // Deprecated: use [policyutil.ComputePCRDigest]. 18 | func ComputePCRDigest(alg tpm2.HashAlgorithmId, pcrs tpm2.PCRSelectionList, values tpm2.PCRValues) (tpm2.Digest, error) { 19 | return policyutil.ComputePCRDigest(alg, pcrs, values) 20 | } 21 | 22 | // ComputePCRDigestFromAllValues computes a digest using the specified algorithm from all of the 23 | // provided set of PCR values. The digest is computed the same way as PCRComputeCurrentDigest as 24 | // defined in the TPM reference implementation. It returns the PCR selection associated with the 25 | // computed digest. 26 | // 27 | // Deprecated: use [policyutil.ComputePCRDigestFromAllValues]. 28 | func ComputePCRDigestFromAllValues(alg tpm2.HashAlgorithmId, values tpm2.PCRValues) (tpm2.PCRSelectionList, tpm2.Digest, error) { 29 | return policyutil.ComputePCRDigestFromAllValues(alg, values) 30 | } 31 | -------------------------------------------------------------------------------- /util/qn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util 6 | 7 | import ( 8 | "github.com/canonical/go-tpm2" 9 | "github.com/canonical/go-tpm2/objectutil" 10 | ) 11 | 12 | // ComputeQualifiedName computes the qualified name of an object from the specified qualified name 13 | // of a root object and a list of ancestor object. The ancestor objects are ordered starting with 14 | // the immediate child of the object associated with the root qualified name. 15 | // 16 | // Deprecated: Use [objectutil.ComputeQualifiedName]. 17 | func ComputeQualifiedName(entity Entity, rootQn tpm2.Name, ancestors ...Entity) (tpm2.Name, error) { 18 | return objectutil.ComputeQualifiedName(entity, rootQn, ancestors...) 19 | } 20 | 21 | // ComputeQualifiedNameInHierarchy computes the qualified name of an object protected in the 22 | // specified hierarchy from a list of ancestor object. The ancestor objects are ordered 23 | // starting from the primary object. 24 | // 25 | // Deprecated: Use [objectutil.ComputeQualifiedNameInHierarchy]. 26 | func ComputeQualifiedNameInHierarchy(entity Entity, hierarchy tpm2.Handle, ancestors ...Entity) (tpm2.Name, error) { 27 | return objectutil.ComputeQualifiedNameInHierarchy(entity, hierarchy, ancestors...) 28 | } 29 | -------------------------------------------------------------------------------- /util/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Canonical Ltd. 2 | // Licensed under the LGPLv3 with static-linking exception. 3 | // See LICENCE file for details. 4 | 5 | package util_test 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "testing" 12 | 13 | . "gopkg.in/check.v1" 14 | 15 | "github.com/canonical/go-tpm2/testutil" 16 | ) 17 | 18 | func init() { 19 | testutil.AddCommandLineFlags() 20 | } 21 | 22 | func Test(t *testing.T) { TestingT(t) } 23 | 24 | func TestMain(m *testing.M) { 25 | flag.Parse() 26 | os.Exit(func() int { 27 | if testutil.TPMBackend == testutil.TPMBackendMssim { 28 | simulatorCleanup, err := testutil.LaunchTPMSimulator(nil) 29 | if err != nil { 30 | fmt.Fprintf(os.Stderr, "Cannot launch TPM simulator: %v\n", err) 31 | return 1 32 | } 33 | defer simulatorCleanup() 34 | } 35 | 36 | return m.Run() 37 | }()) 38 | } 39 | --------------------------------------------------------------------------------