├── .cirrus.yml ├── .gitallowed ├── .gitignore ├── .golangci.yml ├── CODEOWNERS ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── examples ├── README.md ├── tpm-clear │ └── clear.go ├── tpm-genaik │ └── genaik.go ├── tpm-keys │ └── tpm-keys.go ├── tpm-sign │ ├── README.md │ ├── common.go │ ├── extend_pcr.go │ ├── generate.go │ ├── sign.go │ ├── tpm-sign.go │ └── verify.go ├── tpm-takeownership │ └── own.go ├── tpm2-ekcert │ └── main.go ├── tpm2-nvread │ └── main.go └── tpm2-seal-unseal │ └── main.go ├── go.mod ├── go.sum ├── legacy └── tpm2 │ ├── README.md │ ├── constants.go │ ├── credactivation │ ├── credential_activation.go │ └── credential_activation_test.go │ ├── encoding_test.go │ ├── error.go │ ├── error_test.go │ ├── kdf.go │ ├── open_other.go │ ├── open_windows.go │ ├── structures.go │ ├── test │ ├── benchmark_test.go │ ├── kdf_test.go │ ├── tpm2_other_test.go │ ├── tpm2_test.go │ └── tpm2_windows_test.go │ └── tpm2.go ├── tpm ├── commands.go ├── constants.go ├── errors.go ├── open_other.go ├── open_windows.go ├── pcrs.go ├── pcrs_test.go ├── structures.go ├── testing.md ├── tpm.go ├── tpm_other_test.go ├── tpm_test.go ├── tpm_windows_test.go └── verify.go ├── tpm2 ├── audit.go ├── bitfield.go ├── child_object_crypto.go ├── constants.go ├── constants_internal.go ├── create_credential.go ├── create_duplicate.go ├── create_salt.go ├── crypto.go ├── crypto_test.go ├── errors.go ├── kdf.go ├── labeled_kem_convert.go ├── labeled_kem_ecc.go ├── labeled_kem_rsa.go ├── labeled_kem_test.go ├── marshalling.go ├── marshalling_test.go ├── names.go ├── pcrs.go ├── policy.go ├── reflect.go ├── sessions.go ├── structures.go ├── templates.go ├── test │ ├── activate_credential_test.go │ ├── audit_test.go │ ├── certify_test.go │ ├── clear_test.go │ ├── combined_context_test.go │ ├── commit_test.go │ ├── create_loaded_test.go │ ├── duplicate_test.go │ ├── ecdh_test.go │ ├── ek_test.go │ ├── evict_control_test.go │ ├── get_random_test.go │ ├── hash_sequence_hash_test.go │ ├── hierarchy_change_auth_test.go │ ├── hmac_start_test.go │ ├── hmac_test.go │ ├── import_test.go │ ├── load_external_test.go │ ├── names_test.go │ ├── nv_test.go │ ├── object_change_auth_test.go │ ├── pcr_test.go │ ├── policy_test.go │ ├── read_public_test.go │ ├── rsa_encryption_test.go │ ├── sealing_test.go │ ├── sign_test.go │ ├── symmetric_encryption_test.go │ ├── test_parms_test.go │ └── testvectors │ │ ├── ecc_labeled_encaps.json │ │ ├── kdfa.json │ │ ├── kdfe.json │ │ ├── rsa_labeled_encaps.json │ │ └── testvectors.go ├── tpm2.go ├── tpm2b.go └── transport │ ├── linuxtpm │ ├── linuxtpm.go │ └── linuxtpm_test.go │ ├── linuxudstpm │ ├── linuxudstpm.go │ └── linuxudstpm_test.go │ ├── open_other.go │ ├── open_windows.go │ ├── simulator │ └── simulator.go │ ├── tcp │ ├── tcp.go │ └── tcp_test.go │ ├── test │ └── helper.go │ ├── tpm.go │ └── windowstpm │ ├── windowstpm.go │ └── windowstpm_test.go └── tpmutil ├── emulator_read_write_closer_test.go ├── encoding.go ├── encoding_test.go ├── mssim └── mssim.go ├── poll_other.go ├── poll_unix.go ├── poll_unix_test.go ├── run.go ├── run_other.go ├── run_windows.go ├── structures.go └── tbs ├── tbs_windows.go └── tbs_windows_test.go /.cirrus.yml: -------------------------------------------------------------------------------- 1 | container: 2 | dockerfile: Dockerfile 3 | 4 | env: 5 | GOPROXY: https://proxy.golang.org 6 | GO111MODULE: on 7 | 8 | test_task: 9 | modules_cache: 10 | fingerprint_script: cat go.sum 11 | folder: $GOPATH/pkg/mod 12 | build_script: go build -v ./... 13 | test_script: go test -p 1 -v ./... 14 | 15 | lint_task: 16 | env: 17 | matrix: 18 | - GOOS: linux 19 | - GOOS: windows 20 | lint_script: 21 | golangci-lint run ./tpmutil/... ./tpm2/... 22 | -------------------------------------------------------------------------------- /.gitallowed: -------------------------------------------------------------------------------- 1 | tpm2/test/testvectors/* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | .vscode* 3 | 4 | # Swap 5 | [._]*.s[a-v][a-z] 6 | !*.svg # comment out if you don't need vector files 7 | [._]*.sw[a-p] 8 | [._]s[a-rt-v][a-z] 9 | [._]ss[a-gi-z] 10 | [._]sw[a-p] 11 | 12 | # Session 13 | Session.vim 14 | Sessionx.vim 15 | 16 | # Temporary 17 | .netrwhist 18 | *~ 19 | 20 | # Auto-generated tag files 21 | tags 22 | 23 | # Persistent undo 24 | [._]*.un~ 25 | 26 | # Generated files 27 | constants_string.go 28 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable: 3 | - errcheck 4 | enable: 5 | - stylecheck 6 | - goimports 7 | - misspell 8 | - revive 9 | linters-settings: 10 | revive: 11 | rules: 12 | - name: dot-imports 13 | disabled: true 14 | issues: 15 | exclude-use-default: false 16 | exclude: 17 | - stutters 18 | - underscores 19 | - unexported-return 20 | max-issues-per-linter: 0 21 | max-same-issues: 0 22 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Being an admin also gives you global code ownership. 2 | * @google/go-tpm-admin 3 | 4 | # The TPM 1.2 maintainers own the TPM 1.2 files. If this is updated, the access 5 | # permissions to the repository should also be updated. 6 | /tpm/ @zaolin @flanfly @ChriMarMe @google/go-tpm-admin 7 | /examples/tpm-*/ @zaolin @flanfly @ChriMarMe @google/go-tpm-admin 8 | /tpm2/ @alexmwu @jkl73 @google/go-tpm-admin 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute # 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | a just a few small guidelines you need to follow. 5 | 6 | 7 | ## Contributor License Agreement ## 8 | 9 | Contributions to any Google project must be accompanied by a Contributor 10 | License Agreement. This is not a copyright **assignment**, it simply gives 11 | Google permission to use and redistribute your contributions as part of the 12 | project. 13 | 14 | * If you are an individual writing original source code and you're sure you 15 | own the intellectual property, then you'll need to sign an [individual 16 | CLA][]. 17 | 18 | * If you work for a company that wants to allow you to contribute your work, 19 | then you'll need to sign a [corporate CLA][]. 20 | 21 | You generally only need to submit a CLA once, so if you've already submitted 22 | one (even if it was for a different project), you probably don't need to do it 23 | again. 24 | 25 | [individual CLA]: https://developers.google.com/open-source/cla/individual 26 | [corporate CLA]: https://developers.google.com/open-source/cla/corporate 27 | 28 | 29 | ## Submitting a patch ## 30 | 31 | 1. It's generally best to start by opening a new issue describing the bug or 32 | feature you're intending to fix. Even if you think it's relatively minor, 33 | it's helpful to know what people are working on. Mention in the initial 34 | issue that you are planning to work on that bug or feature so that it can 35 | be assigned to you. 36 | 37 | 1. Follow the normal process of [forking][] the project, and setup a new 38 | branch to work in. It's important that each group of changes be done in 39 | separate branches in order to ensure that a pull request only includes the 40 | commits related to that bug or feature. 41 | 42 | 1. Go makes it very simple to ensure properly formatted code, so always run 43 | `go fmt` on your code before committing it. You should also run 44 | [golint][] over your code. As noted in the [golint readme][], it's not 45 | strictly necessary that your code be completely "lint-free", but this will 46 | help you find common style issues. 47 | 48 | 1. Any significant changes should almost always be accompanied by tests. Look 49 | at some of the existing tests if you're unsure how to go about it. 50 | [gocov][] and [gocov-html][] are invaluable tools for seeing which parts of 51 | your code aren't being exercised by your tests. 52 | 53 | 1. Do your best to have [well-formed commit messages][] for each change. 54 | This provides consistency throughout the project, and ensures that commit 55 | messages are able to be formatted properly by various git tools. 56 | 57 | 1. Finally, push the commits to your fork and submit a [pull request][]. 58 | 59 | [forking]: https://help.github.com/articles/fork-a-repo 60 | [golint]: https://github.com/golang/lint 61 | [golint readme]: https://github.com/golang/lint/blob/master/README 62 | [gocov]: https://github.com/axw/gocov 63 | [gocov-html]: https://github.com/matm/gocov-html 64 | [well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 65 | [squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits 66 | [pull request]: https://help.github.com/articles/creating-a-pull-request 67 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 2 | # We need OpenSSL headers to build the simulator 3 | RUN apt-get update && apt-get install -y \ 4 | libssl-dev \ 5 | && rm -rf /var/lib/apt/lists/* 6 | # We need golangci-lint for linting 7 | ARG VERSION=1.56.2 8 | RUN curl -SL \ 9 | https://github.com/golangci/golangci-lint/releases/download/v${VERSION}/golangci-lint-${VERSION}-linux-amd64.tar.gz \ 10 | --output golangci.tar.gz \ 11 | && tar --extract --verbose \ 12 | --file=golangci.tar.gz \ 13 | --directory=/usr/local/bin \ 14 | --strip-components=1 \ 15 | --wildcards "*/golangci-lint" \ 16 | && rm golangci.tar.gz 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Go-TPM 2 | ====== 3 | 4 | Go-TPM is a Go library that communicates directly with a TPM device on Linux or 5 | Windows machines. 6 | 7 | The libraries don't implement the entire spec for neither 1.2 nor 2.0. **If you 8 | need a command that's missing, contributions are welcome!** 9 | 10 | Please note that this is not an official Google product. 11 | 12 | ## Structure 13 | 14 | The `tpm` directory contains TPM 1.2 client library. This library is in 15 | ["maintenance mode"](#tpm-1.2). 16 | 17 | The `legacy/tpm2` directory contains the legacy TPM 2.0 client library. 18 | 19 | The `tpm2` directory contains the prototype "TPMDirect" TPM 2.0 API, which is 20 | intended to (eventually) be 1:1 with the TPM 2.0 spec. Please report issues, 21 | complaints, or suggestions using the label 22 | https://github.com/google/go-tpm/labels/tpmdirect. 23 | 24 | The `examples` directory contains some simple examples for both legacy versions 25 | of the spec. 26 | 27 | ## TPM 1.2 28 | 29 | TPM 1.2 support currently has no maintainer. None of the TPM 2.0 maintainers 30 | have expertise on 1.2 either. 31 | 32 | As such, TPM 1.2 library is in "maintenance" mode - all PRs with new 33 | functionality or non-critical fixes will be rejected. 34 | 35 | **If you'd like to volunteer to maintain the TPM 1.2 library, you can do so via 36 | an [issue](https://github.com/google/go-tpm/issues).** You don't have to be a 37 | Googler to volunteer. 38 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # go-tpm usage examples 2 | 3 | This directory contains binaries that show how to use go-tpm. 4 | 5 | ## Versions 6 | 7 | Directories that start with `tpm-` are for TPM 1.x devices. 8 | 9 | Directories that start with `tpm2-` are for TPM 2.x devices. 10 | 11 | They are not compatible. For example, running `tpm-sign` against a TPM 2.x 12 | device will fail. 13 | -------------------------------------------------------------------------------- /examples/tpm-clear/clear.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2014, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "flag" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/google/go-tpm/tpm" 26 | ) 27 | 28 | var ownerAuthEnvVar = "TPM_OWNER_AUTH" 29 | 30 | func main() { 31 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 32 | flag.Parse() 33 | 34 | rwc, err := tpm.OpenTPM(*tpmname) 35 | if err != nil { 36 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 37 | return 38 | } 39 | 40 | var ownerAuth [20]byte 41 | ownerInput := os.Getenv(ownerAuthEnvVar) 42 | if ownerInput != "" { 43 | oa := sha1.Sum([]byte(ownerInput)) 44 | copy(ownerAuth[:], oa[:]) 45 | } 46 | if err := tpm.OwnerClear(rwc, ownerAuth); err != nil { 47 | fmt.Fprintf(os.Stderr, "Couldn't clear the TPM using owner auth: %s\n", err) 48 | return 49 | } 50 | 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /examples/tpm-genaik/genaik.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2014, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "flag" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/google/go-tpm/tpm" 26 | ) 27 | 28 | var ( 29 | ownerAuthEnvVar = "TPM_OWNER_AUTH" 30 | srkAuthEnvVar = "TPM_SRK_AUTH" 31 | aikAuthEnvVar = "TPM_AIK_AUTH" 32 | ) 33 | 34 | func main() { 35 | var blobname = flag.String("blob", "aikblob", "The name of the file to create") 36 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 37 | flag.Parse() 38 | 39 | rwc, err := tpm.OpenTPM(*tpmname) 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 42 | return 43 | } 44 | 45 | // Compute the auth values as needed. 46 | var ownerAuth [20]byte 47 | ownerInput := os.Getenv(ownerAuthEnvVar) 48 | if ownerInput != "" { 49 | oa := sha1.Sum([]byte(ownerInput)) 50 | copy(ownerAuth[:], oa[:]) 51 | } 52 | 53 | var srkAuth [20]byte 54 | srkInput := os.Getenv(srkAuthEnvVar) 55 | if srkInput != "" { 56 | sa := sha1.Sum([]byte(srkInput)) 57 | copy(srkAuth[:], sa[:]) 58 | } 59 | 60 | var aikAuth [20]byte 61 | aikInput := os.Getenv(aikAuthEnvVar) 62 | if aikInput != "" { 63 | aa := sha1.Sum([]byte(aikInput)) 64 | copy(aikAuth[:], aa[:]) 65 | } 66 | 67 | // TODO(tmroeder): add support for Privacy CAs. 68 | blob, err := tpm.MakeIdentity(rwc, srkAuth[:], ownerAuth[:], aikAuth[:], nil, nil) 69 | if err != nil { 70 | fmt.Fprintf(os.Stderr, "Couldn't make an new AIK: %s\n", err) 71 | return 72 | } 73 | 74 | if err := os.WriteFile(*blobname, blob, 0600); err != nil { 75 | fmt.Fprintf(os.Stderr, "Couldn't write to file %s: %s\n", *blobname, err) 76 | return 77 | } 78 | 79 | return 80 | } 81 | -------------------------------------------------------------------------------- /examples/tpm-keys/tpm-keys.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2016, Kevin Walsh. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | // Package main implements a program to clear key handles from a TPM. 18 | package main 19 | 20 | import ( 21 | "flag" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/google/go-tpm/tpm" 26 | ) 27 | 28 | func main() { 29 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 30 | var closekey = flag.Bool("close", false, "Close (unload) all existing key handles") 31 | flag.Parse() 32 | 33 | rwc, err := tpm.OpenTPM(*tpmname) 34 | if err != nil { 35 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 36 | return 37 | } 38 | 39 | handles, err := tpm.GetKeys(rwc) 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "Couldn't enumerate loaded TPM keys: %s\n", err) 42 | return 43 | } 44 | 45 | fmt.Printf("%d keys loaded in the TPM\n", len(handles)) 46 | for i, h := range handles { 47 | fmt.Printf(" (%d) Key handle %d\n", i+1, h) 48 | if *closekey { 49 | if err = tpm.CloseKey(rwc, h); err != nil { 50 | fmt.Fprintf(os.Stderr, "Couldn't close TPM key handle %d\n", h) 51 | } else { 52 | fmt.Printf(" Closed handle %d\n", h) 53 | } 54 | } 55 | } 56 | 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /examples/tpm-sign/README.md: -------------------------------------------------------------------------------- 1 | # tpm-sign 2 | 3 | This example shows how you can generate keys inside the TPM and use them for signature/verification operations. This utility supports `sign`, `verify`, `generate`, and `extendPcr` actions. Use `./tpm-sign --help` for advanced usage of each action. 4 | 5 | ## Basic Usage 6 | The following snippet shows how you can generate a key, sign data with it, and verify the signature. 7 | 8 | ``` 9 | $ ./tpm-sign generate 10 | Writing keyblob to keyblob 11 | Writing public key to publickey 12 | $ echo test_data | ./tpm-sign sign 13 | Writing signature to sig.data 14 | $ echo test_data | ./tpm-sign verify 15 | Signature valid. 16 | ``` 17 | 18 | ## Binding against PCRs 19 | This example shows how you can generate a key that is bound against PCR values. 20 | 21 | ``` 22 | $ ./tpm-sign extendPcr --reset --pcr 16 23 | $ ./tpm-sign generate --pcrs 0,16 24 | Writing keyblob to keyblob 25 | Writing public key to publickey 26 | $ echo test_data | ./tpm-sign sign 27 | Writing signature to sig.data 28 | $ echo test_measurement | ./tpm-sign extendPcr --pcr 16 29 | $ echo test_data | ./tpm-sign sign 30 | Could not perform sign operation: tpm: the named PCR value does not match the current PCR value 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /examples/tpm-sign/common.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package main 4 | 5 | import ( 6 | "crypto" 7 | ) 8 | 9 | const ( 10 | srkAuthEnvVar = "TPM_SRK_AUTH" 11 | usageAuthEnvVar = "TPM_USAGE_AUTH" 12 | migrationAuthEnvVar = "TPM_MIGRATION_AUTH" 13 | ) 14 | 15 | var hashNames = map[string]crypto.Hash{ 16 | "MD5": crypto.MD5, 17 | "SHA1": crypto.SHA1, 18 | "SHA224": crypto.SHA224, 19 | "SHA256": crypto.SHA256, 20 | "SHA384": crypto.SHA384, 21 | "SHA512": crypto.SHA512, 22 | "MD5SHA1": crypto.MD5SHA1, 23 | "RIPEMD160": crypto.RIPEMD160, 24 | } 25 | -------------------------------------------------------------------------------- /examples/tpm-sign/extend_pcr.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Ian Haken. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "flag" 22 | "fmt" 23 | "io" 24 | "os" 25 | 26 | "github.com/google/go-tpm/tpm" 27 | ) 28 | 29 | func extendPcrAction() { 30 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 31 | var pcrNum = flag.Int("pcr", 16, "PCR number to extend") 32 | var reset = flag.Bool("reset", false, "Reset the PCR rather than extending it") 33 | var dataPath = flag.String("data", "", "Path to the data that will be used to extend the PCR. If empty or omitted, the data will be read from stdin.") 34 | flag.CommandLine.Parse(os.Args[2:]) 35 | 36 | rwc, err := tpm.OpenTPM(*tpmname) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 39 | return 40 | } 41 | defer rwc.Close() 42 | 43 | if *reset { 44 | if err = tpm.PcrReset(rwc, []int{*pcrNum}); err != nil { 45 | fmt.Fprintf(os.Stderr, "Unable to reset PCR: %s\n", err) 46 | return 47 | } 48 | } else { 49 | var data []byte 50 | if *dataPath == "" { 51 | data, err = io.ReadAll(os.Stdin) 52 | } else { 53 | data, err = os.ReadFile(*dataPath) 54 | } 55 | if err != nil { 56 | fmt.Fprintf(os.Stderr, "Unable to read input: %s\n", err) 57 | return 58 | } 59 | if _, err = tpm.PcrExtend(rwc, uint32(*pcrNum), sha1.Sum(data)); err != nil { 60 | fmt.Fprintf(os.Stderr, "Error extending PCR: %s\n", err) 61 | return 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/tpm-sign/generate.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Ian Haken. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "crypto/x509" 22 | "flag" 23 | "fmt" 24 | "os" 25 | "strconv" 26 | "strings" 27 | 28 | "github.com/google/go-tpm/tpm" 29 | ) 30 | 31 | func generateAction() { 32 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 33 | var keyblobPath = flag.String("keyblob", "keyblob", "Output path of the generated keyblob") 34 | var pubKeyPath = flag.String("public-key", "publickey", "Output path of the generated keyblob's public key") 35 | var pcrsStr = flag.String("pcrs", "", "A comma-separated list of PCR numbers against which the generated key will be bound. If blank, it will not be bound to any PCR values.") 36 | flag.CommandLine.Parse(os.Args[2:]) 37 | 38 | var pcrs []int 39 | if *pcrsStr != "" { 40 | for _, pcr := range strings.Split(*pcrsStr, ",") { 41 | pcrNum, err := strconv.Atoi(pcr) 42 | if err != nil { 43 | fmt.Fprintf(os.Stderr, "Bad value in pcrs argument: %s\n", pcr) 44 | return 45 | } 46 | pcrs = append(pcrs, pcrNum) 47 | } 48 | } 49 | 50 | rwc, err := tpm.OpenTPM(*tpmname) 51 | if err != nil { 52 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 53 | return 54 | } 55 | defer rwc.Close() 56 | 57 | // Compute the auth values as needed. 58 | var srkAuth [20]byte 59 | srkInput := os.Getenv(srkAuthEnvVar) 60 | if srkInput != "" { 61 | sa := sha1.Sum([]byte(srkInput)) 62 | copy(srkAuth[:], sa[:]) 63 | } 64 | 65 | var usageAuth [20]byte 66 | usageInput := os.Getenv(usageAuthEnvVar) 67 | if usageInput != "" { 68 | ua := sha1.Sum([]byte(usageInput)) 69 | copy(usageAuth[:], ua[:]) 70 | } 71 | 72 | var migrationAuth [20]byte 73 | migrationInput := os.Getenv(migrationAuthEnvVar) 74 | if migrationInput != "" { 75 | ma := sha1.Sum([]byte(migrationInput)) 76 | copy(migrationAuth[:], ma[:]) 77 | } 78 | 79 | keyblob, err := tpm.CreateWrapKey(rwc, srkAuth[:], usageAuth, migrationAuth, pcrs) 80 | if err != nil { 81 | fmt.Fprintf(os.Stderr, "Couldn't make a new signing key: %s\n", err) 82 | return 83 | } 84 | fmt.Printf("Writing keyblob to %s\n", *keyblobPath) 85 | if err = os.WriteFile(*keyblobPath, keyblob, 0644); err != nil { 86 | fmt.Fprintf(os.Stderr, "Error writing keyblob file: %s\n", err) 87 | return 88 | } 89 | 90 | pubKey, err := tpm.UnmarshalRSAPublicKey(keyblob) 91 | if err != nil { 92 | fmt.Fprintf(os.Stderr, "Could not get public key: %s\n", err) 93 | return 94 | } 95 | 96 | pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey) 97 | if err != nil { 98 | fmt.Fprintf(os.Stderr, "Could not marshal public key: %s\n", err) 99 | return 100 | } 101 | fmt.Printf("Writing public key to %s\n", *pubKeyPath) 102 | if err = os.WriteFile(*pubKeyPath, pubKeyBytes, 0644); err != nil { 103 | fmt.Fprintf(os.Stderr, "Error writing public key file: %s\n", err) 104 | return 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/tpm-sign/sign.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Ian Haken. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "flag" 22 | "fmt" 23 | "io" 24 | "os" 25 | 26 | "github.com/google/go-tpm/tpm" 27 | ) 28 | 29 | func signAction() { 30 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 31 | var keyblobPath = flag.String("keyblob", "keyblob", "Input path of the keyblob to use") 32 | var signaturePath = flag.String("signature", "sig.data", "Output path of the signature") 33 | var hashAlgArg = flag.String("hash", "SHA256", "Hash algorithm to use when generating the signature") 34 | var dataPath = flag.String("data", "", "Path to the data that will be signed. If empty or omitted, the data will be read from stdin.") 35 | flag.CommandLine.Parse(os.Args[2:]) 36 | 37 | hashAlg, ok := hashNames[*hashAlgArg] 38 | if !ok { 39 | fmt.Fprintf(os.Stderr, "Invalid hash algorithm: %s\n", *hashAlgArg) 40 | return 41 | } 42 | 43 | rwc, err := tpm.OpenTPM(*tpmname) 44 | if err != nil { 45 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 46 | return 47 | } 48 | defer rwc.Close() 49 | 50 | // Compute the auth values as needed. 51 | var srkAuth [20]byte 52 | srkInput := os.Getenv(srkAuthEnvVar) 53 | if srkInput != "" { 54 | sa := sha1.Sum([]byte(srkInput)) 55 | copy(srkAuth[:], sa[:]) 56 | } 57 | 58 | var usageAuth [20]byte 59 | usageInput := os.Getenv(usageAuthEnvVar) 60 | if usageInput != "" { 61 | ua := sha1.Sum([]byte(usageInput)) 62 | copy(usageAuth[:], ua[:]) 63 | } 64 | 65 | var migrationAuth [20]byte 66 | migrationInput := os.Getenv(migrationAuthEnvVar) 67 | if migrationInput != "" { 68 | ma := sha1.Sum([]byte(migrationInput)) 69 | copy(migrationAuth[:], ma[:]) 70 | } 71 | 72 | keyblob, err := os.ReadFile(*keyblobPath) 73 | if err != nil { 74 | fmt.Fprintf(os.Stderr, "Error reading keyblob file: %s\n", err) 75 | return 76 | } 77 | keyHandle, err := tpm.LoadKey2(rwc, keyblob, srkAuth[:]) 78 | if err != nil { 79 | fmt.Fprintf(os.Stderr, "Could not load keyblob: %s\n", err) 80 | return 81 | } 82 | defer tpm.CloseKey(rwc, keyHandle) 83 | 84 | var data []byte 85 | if *dataPath == "" { 86 | data, err = io.ReadAll(os.Stdin) 87 | } else { 88 | data, err = os.ReadFile(*dataPath) 89 | } 90 | if err != nil { 91 | fmt.Fprintf(os.Stderr, "Error reading input data: %s\n", err) 92 | return 93 | } 94 | 95 | hash := hashAlg.New() 96 | if _, err = hash.Write(data); err != nil { 97 | fmt.Fprintf(os.Stderr, "Error building hash of data: %s\n", err) 98 | return 99 | } 100 | hashed := hash.Sum(nil) 101 | signature, err := tpm.Sign(rwc, usageAuth[:], keyHandle, hashAlg, hashed[:]) 102 | if err != nil { 103 | fmt.Fprintf(os.Stderr, "Could not perform sign operation: %s\n", err) 104 | return 105 | } 106 | fmt.Printf("Writing signature to %s\n", *signaturePath) 107 | if err = os.WriteFile(*signaturePath, signature, 0644); err != nil { 108 | fmt.Fprintf(os.Stderr, "Unable to write signature to file: %s\n", err) 109 | return 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/tpm-sign/tpm-sign.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Ian Haken. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | ) 23 | 24 | func showUsage() { 25 | fmt.Println("Usage: ./tpm-sign [...]") 26 | } 27 | 28 | func main() { 29 | if len(os.Args) < 2 { 30 | showUsage() 31 | } else { 32 | switch os.Args[1] { 33 | case "sign": 34 | signAction() 35 | case "verify": 36 | verifyAction() 37 | case "generate": 38 | generateAction() 39 | case "extendPcr": 40 | extendPcrAction() 41 | default: 42 | showUsage() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/tpm-sign/verify.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Ian Haken. All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/rsa" 21 | "crypto/x509" 22 | "flag" 23 | "fmt" 24 | "io" 25 | "os" 26 | ) 27 | 28 | func verifyAction() { 29 | var pubKeyPath = flag.String("public-key", "publickey", "Input path of public key file") 30 | var hashAlgArg = flag.String("hash", "SHA256", "Hash algorithm to use when verifying the signature") 31 | var signaturePath = flag.String("signature", "sig.data", "Input path of previously generated signature") 32 | var dataPath = flag.String("data", "", "Path to the data that was signed. If empty or omitted, the data will be read from stdin.") 33 | flag.CommandLine.Parse(os.Args[2:]) 34 | 35 | hashAlg, ok := hashNames[*hashAlgArg] 36 | if !ok { 37 | fmt.Fprintf(os.Stderr, "Invalid hash algorithm: %s\n", *hashAlgArg) 38 | return 39 | } 40 | 41 | var err error 42 | var data []byte 43 | if *dataPath == "" { 44 | data, err = io.ReadAll(os.Stdin) 45 | } else { 46 | data, err = os.ReadFile(*dataPath) 47 | } 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, "Error reading input data: %s\n", err) 50 | return 51 | } 52 | 53 | signature, err := os.ReadFile(*signaturePath) 54 | if err != nil { 55 | fmt.Fprintf(os.Stderr, "Error reading signature file: %s\n", err) 56 | return 57 | } 58 | 59 | pubKeyBytes, err := os.ReadFile(*pubKeyPath) 60 | if err != nil { 61 | fmt.Fprintf(os.Stderr, "Error reading public key file: %s\n", err) 62 | return 63 | } 64 | pubKey, err := x509.ParsePKIXPublicKey(pubKeyBytes) 65 | if err != nil { 66 | fmt.Fprintf(os.Stderr, "Error parsing public key: %s\n", err) 67 | return 68 | } 69 | rsaPubKey, ok := pubKey.(*rsa.PublicKey) 70 | if !ok { 71 | fmt.Fprintf(os.Stderr, "Expected public key to be an RSA key, but was %T\n", pubKey) 72 | return 73 | } 74 | 75 | hash := hashAlg.New() 76 | if _, err = hash.Write(data); err != nil { 77 | fmt.Fprintf(os.Stderr, "Error building hash of data: %s\n", err) 78 | return 79 | } 80 | hashed := hash.Sum(nil) 81 | if err = rsa.VerifyPKCS1v15(rsaPubKey, hashAlg, hashed[:], signature); err != nil { 82 | fmt.Fprintf(os.Stderr, "Error from verification: %s\n", err) 83 | return 84 | } 85 | fmt.Printf("Signature valid.\n") 86 | } 87 | -------------------------------------------------------------------------------- /examples/tpm-takeownership/own.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2014, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "crypto/sha1" 21 | "flag" 22 | "fmt" 23 | "os" 24 | 25 | "github.com/google/go-tpm/tpm" 26 | ) 27 | 28 | var ( 29 | ownerAuthEnvVar = "TPM_OWNER_AUTH" 30 | srkAuthEnvVar = "TPM_SRK_AUTH" 31 | ) 32 | 33 | func main() { 34 | var tpmname = flag.String("tpm", "/dev/tpm0", "The path to the TPM device to use") 35 | flag.Parse() 36 | 37 | rwc, err := tpm.OpenTPM(*tpmname) 38 | if err != nil { 39 | fmt.Fprintf(os.Stderr, "Couldn't open the TPM file %s: %s\n", *tpmname, err) 40 | return 41 | } 42 | 43 | // Compute the auth values as needed. 44 | var ownerAuth [20]byte 45 | ownerInput := os.Getenv(ownerAuthEnvVar) 46 | if ownerInput != "" { 47 | oa := sha1.Sum([]byte(ownerInput)) 48 | copy(ownerAuth[:], oa[:]) 49 | } 50 | 51 | var srkAuth [20]byte 52 | srkInput := os.Getenv(srkAuthEnvVar) 53 | if srkInput != "" { 54 | sa := sha1.Sum([]byte(srkInput)) 55 | copy(srkAuth[:], sa[:]) 56 | } 57 | 58 | pubEK, err := tpm.ReadPubEK(rwc) 59 | if err != nil { 60 | fmt.Fprintf(os.Stderr, "Couldn't read the endorsement key: %s\n", err) 61 | return 62 | } 63 | 64 | if err := tpm.TakeOwnership(rwc, ownerAuth, srkAuth, pubEK); err != nil { 65 | fmt.Fprintf(os.Stderr, "Couldn't take ownership of the TPM: %s\n", err) 66 | return 67 | } 68 | 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /examples/tpm2-ekcert/main.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Binary tpm2-ekcert reads an x509 certificate from a specific NVRAM index. 4 | package main 5 | 6 | import ( 7 | "crypto" 8 | "crypto/x509" 9 | "encoding/asn1" 10 | "errors" 11 | "flag" 12 | "fmt" 13 | "os" 14 | "reflect" 15 | 16 | "github.com/google/go-tpm/legacy/tpm2" 17 | "github.com/google/go-tpm/tpmutil" 18 | ) 19 | 20 | var ( 21 | tpmPath = flag.String("tpm-path", "/dev/tpm0", "Path to the TPM device (character device or a Unix socket)") 22 | // Default value is defined in section 7.8, "NV Memory" of the latest version pdf on: 23 | // https://trustedcomputinggroup.org/resource/tcg-tpm-v2-0-provisioning-guidance/ 24 | certIndex = flag.Uint("cert-index", 0x01C00002, "NVRAM index of the certificate file") 25 | tmplIndex = flag.Uint("template-index", 0, "NVRAM index of the EK template; if zero, default RSA EK template is used") 26 | outPath = flag.String("output", "", "File path for output; leave blank to write to stdout") 27 | 28 | // Default EK template defined in: 29 | // https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf 30 | defaultEKTemplate = tpm2.Public{ 31 | Type: tpm2.AlgRSA, 32 | NameAlg: tpm2.AlgSHA256, 33 | Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | 34 | tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt, 35 | AuthPolicy: []byte{ 36 | 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 37 | 0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D, 38 | 0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52, 39 | 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, 40 | 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 41 | 0x69, 0xAA, 42 | }, 43 | RSAParameters: &tpm2.RSAParams{ 44 | Symmetric: &tpm2.SymScheme{ 45 | Alg: tpm2.AlgAES, 46 | KeyBits: 128, 47 | Mode: tpm2.AlgCFB, 48 | }, 49 | KeyBits: 2048, 50 | ModulusRaw: make([]byte, 256), 51 | }, 52 | } 53 | ) 54 | 55 | func main() { 56 | flag.Parse() 57 | 58 | cert, err := readEKCert(*tpmPath, uint32(*certIndex), uint32(*tmplIndex)) 59 | if err != nil { 60 | fmt.Fprintln(os.Stderr, err) 61 | os.Exit(1) 62 | } 63 | if *outPath == "" { 64 | fmt.Println(string(cert)) 65 | return 66 | } 67 | if err := os.WriteFile(*outPath, cert, os.ModePerm); err != nil { 68 | fmt.Fprintln(os.Stderr, err) 69 | os.Exit(1) 70 | } 71 | } 72 | 73 | func readEKCert(path string, certIdx, tmplIdx uint32) ([]byte, error) { 74 | rwc, err := tpm2.OpenTPM(path) 75 | if err != nil { 76 | return nil, fmt.Errorf("can't open TPM at %q: %v", path, err) 77 | } 78 | defer rwc.Close() 79 | ekCert, err := tpm2.NVRead(rwc, tpmutil.Handle(certIdx)) 80 | if err != nil { 81 | return nil, fmt.Errorf("reading EK cert: %v", err) 82 | } 83 | 84 | // Identify if any `padding` exists in the EK cert that was read 85 | var raw asn1.RawValue 86 | paddingBytes, err := asn1.Unmarshal(ekCert, &raw) 87 | if err != nil { 88 | return nil, fmt.Errorf("ASN.1 Unmarshal failed: %v", err) 89 | } 90 | fmt.Printf("TPM NV Index bytes read: %d\n", len(ekCert)) 91 | fmt.Printf("Padding found from ASN.1 Unmarshal: %d\n", len(paddingBytes)) 92 | 93 | // Sanity-check that this is a valid certificate. 94 | cert, err := x509.ParseCertificate(ekCert[0 : len(ekCert)-len(paddingBytes)]) 95 | if err != nil { 96 | return nil, fmt.Errorf("parsing EK cert: %v", err) 97 | } 98 | 99 | // Initialize EK and compare public key to ekCert.PublicKey. 100 | var ekh tpmutil.Handle 101 | var ekPub crypto.PublicKey 102 | if tmplIdx != 0 { 103 | ekTemplate, err := tpm2.NVRead(rwc, tpmutil.Handle(tmplIdx)) 104 | if err != nil { 105 | return nil, fmt.Errorf("reading EK template: %v", err) 106 | } 107 | ekh, ekPub, err = tpm2.CreatePrimaryRawTemplate(rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", ekTemplate) 108 | if err != nil { 109 | return nil, fmt.Errorf("creating EK: %v", err) 110 | } 111 | } else { 112 | ekh, ekPub, err = tpm2.CreatePrimary(rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate) 113 | if err != nil { 114 | return nil, fmt.Errorf("creating EK: %v", err) 115 | } 116 | } 117 | defer tpm2.FlushContext(rwc, ekh) 118 | 119 | if !reflect.DeepEqual(ekPub, cert.PublicKey) { 120 | return nil, errors.New("public key in EK certificate differs from public key created via EK template") 121 | } 122 | 123 | return ekCert, nil 124 | } 125 | -------------------------------------------------------------------------------- /examples/tpm2-nvread/main.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Binary tpm2-nvread reads data from NVRAM at a specified index. The data is 4 | // printed out hex-encoded. 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/google/go-tpm/legacy/tpm2" 13 | "github.com/google/go-tpm/tpmutil" 14 | ) 15 | 16 | var ( 17 | tpmPath = flag.String("tpm-path", "/dev/tpm0", "Path to the TPM device (character device or a Unix socket)") 18 | index = flag.Uint("index", 0, "NVRAM index of read") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | if *index == 0 { 25 | fmt.Fprintln(os.Stderr, "--index must be set") 26 | os.Exit(1) 27 | } 28 | 29 | val, err := nvRead(*tpmPath, uint32(*index)) 30 | if err != nil { 31 | fmt.Fprintf(os.Stderr, "reading from index 0x%x: %v\n", *index, err) 32 | os.Exit(1) 33 | } 34 | fmt.Printf("NVRAM value at index 0x%x (hex encoded):\n%x\n", *index, val) 35 | } 36 | 37 | func nvRead(path string, index uint32) ([]byte, error) { 38 | rwc, err := tpm2.OpenTPM(path) 39 | if err != nil { 40 | return nil, fmt.Errorf("can't open TPM at %q: %v", path, err) 41 | } 42 | defer rwc.Close() 43 | return tpm2.NVRead(rwc, tpmutil.Handle(index)) 44 | } 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/google/go-tpm 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.9 7 | github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba 8 | golang.org/x/sys v0.8.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 2 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/google/go-sev-guest v0.6.1 h1:NajHkAaLqN9/aW7bCFSUplUMtDgk2+HcN7jC2btFtk0= 4 | github.com/google/go-sev-guest v0.6.1/go.mod h1:UEi9uwoPbLdKGl1QHaq1G8pfCbQ4QP0swWX4J0k6r+Q= 5 | github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba h1:qJEJcuLzH5KDR0gKc0zcktin6KSAwL7+jWKBYceddTc= 6 | github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc= 7 | github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ= 8 | github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ= 9 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 10 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= 12 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 13 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 14 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 15 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 16 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 17 | golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= 18 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 20 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 21 | -------------------------------------------------------------------------------- /legacy/tpm2/README.md: -------------------------------------------------------------------------------- 1 | # TPM 2.0 client library 2 | 3 | ## Tests 4 | 5 | This library contains unit tests in `github.com/google/go-tpm/tpm2`, which just 6 | tests that various encoding and error checking functions work correctly. It also 7 | contains more comprehensive integration tests in 8 | `github.com/google/go-tpm/tpm2/test`, which run actual commands on a TPM. 9 | 10 | By default, these integration tests are run against the 11 | [`go-tpm-tools`](https://github.com/google/go-tpm-tools) 12 | simulator, which is baesed on the 13 | [Microsoft Reference TPM2 code](https://github.com/microsoft/ms-tpm-20-ref). To 14 | run both the unit and integration tests, run (in this directory) 15 | ```bash 16 | go test . ./test 17 | ``` 18 | 19 | These integration tests can also be run against a real TPM device. This is 20 | slightly more complex as the tests often need to be built as a normal user and 21 | then executed as root. For example, 22 | ```bash 23 | # Build the test binary without running it 24 | go test -c github.com/google/go-tpm/tpm2/test 25 | # Execute the test binary as root 26 | sudo ./test.test --tpm-path=/dev/tpmrm0 27 | ``` 28 | On Linux, The `--tpm-path` causes the integration tests to be run against a 29 | real TPM located at that path (usually `/dev/tpmrm0` or `/dev/tpm0`). On Windows, the story is similar, execept that 30 | the `--use-tbs` flag is used instead. 31 | 32 | Tip: if your TPM host is remote and you don't want to install Go on it, this 33 | same two-step process can be used. The test binary can be copied to a remote 34 | host and run without extra installation (as the test binary has very few 35 | *runtime* dependancies). 36 | -------------------------------------------------------------------------------- /legacy/tpm2/credactivation/credential_activation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package credactivation 16 | 17 | import ( 18 | "bytes" 19 | "crypto/x509" 20 | "encoding/base64" 21 | "encoding/pem" 22 | "testing" 23 | 24 | "github.com/google/go-tpm/legacy/tpm2" 25 | ) 26 | 27 | var ( 28 | eccEKPub = []byte(`-----BEGIN PUBLIC KEY----- 29 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsIfixqsUp8cJBSeYDhJKCZ32eAHF 30 | 3rS7HdTMOnoFj1MrX+PutPTxa6SFdhWGLnhEQyfcwRni8veQX/dSP2on2w== 31 | -----END PUBLIC KEY-----`) 32 | rsaEKPub = []byte(`-----BEGIN PUBLIC KEY----- 33 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArIqmuAvuIcakIEPd2hZl 34 | avob21ehQ7zaHduJQNbNuKVSc1HTlvw9DkWN03b0SktcRIfsjw/omqPl60RhCx0j 35 | qxsYnf5Gk4jhfCnUeQVicAqHnUGrKjMkLIGTZVOpyqBEXHsdhugw6M5HVIKyfwNO 36 | KhvLZKRH8JkvtElVhLQ6E2+H83XoSpkt9oCnGPyN2Z5qRP+fhQiRylMCD8Rz8ABn 37 | YVqGBBrG+2cBt/0uFLjxHx2mm/4sI/1scG5xrcrDLva9WZB40MehW5VlS6Fwqq05 38 | dKtLGpk7ludjH38m2zhM5/UdKZ34skJaS/Aiyj+P5AT1BpJL2ZtjCbBdnMDUSbRF 39 | 1QIDAQAB 40 | -----END PUBLIC KEY-----`) 41 | ) 42 | 43 | type zeroReader struct{} 44 | 45 | func (zeroReader) Read(b []byte) (int, error) { 46 | for i := range b { 47 | b[i] = 0 48 | } 49 | return len(b), nil 50 | } 51 | 52 | func mustDecodeBase64(in string, t *testing.T) []byte { 53 | d, err := base64.StdEncoding.DecodeString(in) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | return d 58 | } 59 | 60 | func TestCredentialActivation(t *testing.T) { 61 | var activateTests = []struct { 62 | ekPub []byte 63 | expected string 64 | }{ 65 | { 66 | eccEKPub, 67 | "AEQAIE4SquOcMAzLi7f3ru6P8nuIpSmzr8OTACXpzLo3PTOe/oBazv+fZF2JiKDZqNPNeHISCsZMdEtEfvyYjqmzZ/CB7ABEACAeGDfvDRlRiDV1cbXlVFsSLo8JZ/2nJCA+slYczpcoXgAg+CstT57xB59sS1uDVuIyQulYttdJprVoGkEDVmvcWok=", 68 | }, 69 | { 70 | rsaEKPub, 71 | "AEQAIFjKZAUo3Wmgxu+CqHFzsQZr7BqawtprBmpXpZa77nb5S+iN6IcSPQLCKPZMNuunv7BIb4/VJA/xjMrj8RnQbjspCwEAJcQogjACOfStYTVjmR4p61ZbTTRt7ZNG5nc6iifq+TfnyfoU+E3T6Kount4M8fSUdMWlKx5A24Ms4ndi1VYOA+s4inPusyn1X1ZCHe5tNwT1E9jpVxc0jaUAVad6Q5cOgUyAp4qvc8wmaYXcIa/PzVfa6teF4iXxNqVDAYqpdmbP68v0Hk5gRqCa/tHAdg5avE3C20DP1SSvPitumWROL6mHMooVxjsyjPnHEBLo7y/BKwezEO/15xnBvPOvWs7ARIu1KdER+zrCJX9SMCPbn4cVMfLdrX70xko7XjdhV7pXtAeUeKmmKSYE45m5ZN0h83YgHXGDjf+ynWse10okyA==", 72 | }, 73 | } 74 | 75 | for _, test := range activateTests { 76 | p, _ := pem.Decode(test.ekPub) 77 | public, err := x509.ParsePKIXPublicKey(p.Bytes) 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | 82 | aikDigest := mustDecodeBase64("5snpf9qRfKD2Tb72eLAZqC/a/MyUhg+IvdwDZkTJK9w=", t) 83 | expected := mustDecodeBase64(test.expected, t) 84 | secret := mustDecodeBase64("AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=", t) 85 | 86 | aikName := &tpm2.HashValue{ 87 | Alg: tpm2.AlgSHA256, 88 | Value: aikDigest, 89 | } 90 | 91 | idObject, wrappedCredential, err := generate(aikName, public, 16, secret, zeroReader{}) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | activationBlob := append(idObject, wrappedCredential...) 96 | 97 | if !bytes.Equal(expected, activationBlob) { 98 | t.Errorf("generate(%v, %v, %v) returned incorrect result", aikName, public, secret) 99 | t.Logf(" Got: %v", base64.StdEncoding.EncodeToString(activationBlob)) 100 | t.Logf(" Want: %v", base64.StdEncoding.EncodeToString(expected)) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /legacy/tpm2/error_test.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/google/go-tpm/tpmutil" 8 | ) 9 | 10 | func TestError(t *testing.T) { 11 | tests := []struct { 12 | response tpmutil.ResponseCode 13 | expected error 14 | }{ 15 | {0x501, VendorError{Code: 0x501}}, 16 | {0x922, Warning{Code: RCRetry}}, 17 | {0x100, Error{Code: RCInitialize}}, 18 | {0xfc1, ParameterError{Code: RCAsymmetric, Parameter: RCF}}, 19 | {0x7a3, HandleError{Code: RCExpired, Handle: RC7}}, 20 | {0xfa2, SessionError{Code: RCBadAuth, Session: RC7}}, 21 | } 22 | 23 | for _, test := range tests { 24 | err := decodeResponse(test.response) 25 | if !reflect.DeepEqual(err, test.expected) { 26 | t.Fatalf("decodeResponse(0x%x) = %#v, want %#v", test.response, err, test.expected) 27 | } 28 | } 29 | } 30 | 31 | // nil ReadWriter handle causes tpmutil.RunCommand to return an error. 32 | func TestRunCommandErr(t *testing.T) { 33 | if _, err := runCommand(nil, TagSessions, CmdSign); err == nil { 34 | t.Error("runCommand returned nil error on error from tpmutil.RunCommand") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /legacy/tpm2/kdf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm2 16 | 17 | import ( 18 | "crypto" 19 | "crypto/hmac" 20 | "encoding/binary" 21 | "hash" 22 | ) 23 | 24 | // KDFa implements TPM 2.0's default key derivation function, as defined in 25 | // section 11.4.9.2 of the TPM revision 2 specification part 1. 26 | // See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ 27 | // The key & label parameters must not be zero length. 28 | // The label parameter is a non-null-terminated string. 29 | // The contextU & contextV parameters are optional. 30 | // Deprecated: Use KDFaHash. 31 | func KDFa(hashAlg Algorithm, key []byte, label string, contextU, contextV []byte, bits int) ([]byte, error) { 32 | h, err := hashAlg.Hash() 33 | if err != nil { 34 | return nil, err 35 | } 36 | return KDFaHash(h, key, label, contextU, contextV, bits), nil 37 | } 38 | 39 | // KDFe implements TPM 2.0's ECDH key derivation function, as defined in 40 | // section 11.4.9.3 of the TPM revision 2 specification part 1. 41 | // See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ 42 | // The z parameter is the x coordinate of one party's private ECC key multiplied 43 | // by the other party's public ECC point. 44 | // The use parameter is a non-null-terminated string. 45 | // The partyUInfo and partyVInfo are the x coordinates of the initiator's and 46 | // Deprecated: Use KDFeHash. 47 | func KDFe(hashAlg Algorithm, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) ([]byte, error) { 48 | h, err := hashAlg.Hash() 49 | if err != nil { 50 | return nil, err 51 | } 52 | return KDFeHash(h, z, use, partyUInfo, partyVInfo, bits), nil 53 | } 54 | 55 | // KDFaHash implements TPM 2.0's default key derivation function, as defined in 56 | // section 11.4.9.2 of the TPM revision 2 specification part 1. 57 | // See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ 58 | // The key & label parameters must not be zero length. 59 | // The label parameter is a non-null-terminated string. 60 | // The contextU & contextV parameters are optional. 61 | func KDFaHash(h crypto.Hash, key []byte, label string, contextU, contextV []byte, bits int) []byte { 62 | mac := hmac.New(h.New, key) 63 | 64 | out := kdf(mac, bits, func() { 65 | mac.Write([]byte(label)) 66 | mac.Write([]byte{0}) // Terminating null character for C-string. 67 | mac.Write(contextU) 68 | mac.Write(contextV) 69 | binary.Write(mac, binary.BigEndian, uint32(bits)) 70 | }) 71 | return out 72 | } 73 | 74 | // KDFeHash implements TPM 2.0's ECDH key derivation function, as defined in 75 | // section 11.4.9.3 of the TPM revision 2 specification part 1. 76 | // See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ 77 | // The z parameter is the x coordinate of one party's private ECC key multiplied 78 | // by the other party's public ECC point. 79 | // The use parameter is a non-null-terminated string. 80 | // The partyUInfo and partyVInfo are the x coordinates of the initiator's and 81 | // the responder's ECC points, respectively. 82 | func KDFeHash(h crypto.Hash, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) []byte { 83 | hash := h.New() 84 | 85 | out := kdf(hash, bits, func() { 86 | hash.Write(z) 87 | hash.Write([]byte(use)) 88 | hash.Write([]byte{0}) // Terminating null character for C-string. 89 | hash.Write(partyUInfo) 90 | hash.Write(partyVInfo) 91 | }) 92 | return out 93 | } 94 | 95 | func kdf(h hash.Hash, bits int, update func()) []byte { 96 | bytes := (bits + 7) / 8 97 | out := []byte{} 98 | 99 | for counter := 1; len(out) < bytes; counter++ { 100 | h.Reset() 101 | binary.Write(h, binary.BigEndian, uint32(counter)) 102 | update() 103 | 104 | out = h.Sum(out) 105 | } 106 | // out's length is a multiple of hash size, so there will be excess 107 | // bytes if bytes isn't a multiple of hash size. 108 | out = out[:bytes] 109 | 110 | // As mentioned in the KDFa and KDFe specs mentioned above, 111 | // the unused bits of the most significant octet are masked off. 112 | if maskBits := uint8(bits % 8); maskBits > 0 { 113 | out[0] &= (1 << maskBits) - 1 114 | } 115 | return out 116 | } 117 | -------------------------------------------------------------------------------- /legacy/tpm2/open_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2019, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tpm2 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "io" 23 | "os" 24 | 25 | "github.com/google/go-tpm/tpmutil" 26 | ) 27 | 28 | // OpenTPM opens a channel to the TPM at the given path. If the file is a 29 | // device, then it treats it like a normal TPM device, and if the file is a 30 | // Unix domain socket, then it opens a connection to the socket. 31 | // 32 | // This function may also be invoked with no paths, as tpm2.OpenTPM(). In this 33 | // case, the default paths on Linux (/dev/tpmrm0 then /dev/tpm0), will be used. 34 | func OpenTPM(path ...string) (tpm io.ReadWriteCloser, err error) { 35 | switch len(path) { 36 | case 0: 37 | tpm, err = tpmutil.OpenTPM("/dev/tpmrm0") 38 | if errors.Is(err, os.ErrNotExist) { 39 | tpm, err = tpmutil.OpenTPM("/dev/tpm0") 40 | } 41 | case 1: 42 | tpm, err = tpmutil.OpenTPM(path[0]) 43 | default: 44 | return nil, errors.New("cannot specify multiple paths to tpm2.OpenTPM") 45 | } 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | // Make sure this is a TPM 2.0 51 | _, err = GetManufacturer(tpm) 52 | if err != nil { 53 | tpm.Close() 54 | return nil, fmt.Errorf("open %s: device is not a TPM 2.0", path) 55 | } 56 | return tpm, nil 57 | } 58 | -------------------------------------------------------------------------------- /legacy/tpm2/open_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | // Copyright (c) 2018, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tpm2 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | 23 | "github.com/google/go-tpm/tpmutil" 24 | "github.com/google/go-tpm/tpmutil/tbs" 25 | ) 26 | 27 | // OpenTPM opens a channel to the TPM. 28 | func OpenTPM() (io.ReadWriteCloser, error) { 29 | info, err := tbs.GetDeviceInfo() 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | if info.TPMVersion != tbs.TPMVersion20 { 35 | return nil, fmt.Errorf("openTPM: device is not a TPM 2.0") 36 | } 37 | 38 | return tpmutil.OpenTPM() 39 | } 40 | -------------------------------------------------------------------------------- /legacy/tpm2/test/benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm2 16 | 17 | import ( 18 | "crypto/sha256" 19 | "testing" 20 | 21 | . "github.com/google/go-tpm/legacy/tpm2" 22 | ) 23 | 24 | func BenchmarkRSA2048Signing(b *testing.B) { 25 | b.StopTimer() 26 | rw := openTPM(b) 27 | defer rw.Close() 28 | 29 | pub := Public{ 30 | Type: AlgRSA, 31 | NameAlg: AlgSHA256, 32 | Attributes: FlagSign | FlagSensitiveDataOrigin | FlagUserWithAuth, 33 | RSAParameters: &RSAParams{ 34 | Sign: &SigScheme{ 35 | Alg: AlgRSASSA, 36 | Hash: AlgSHA256, 37 | }, 38 | KeyBits: 2048, 39 | }, 40 | } 41 | 42 | signerHandle, _, err := CreatePrimary(rw, HandleOwner, pcrSelection7, emptyPassword, defaultPassword, pub) 43 | if err != nil { 44 | b.Fatalf("CreatePrimary failed: %v", err) 45 | } 46 | defer FlushContext(rw, signerHandle) 47 | 48 | digest := sha256.Sum256([]byte("randomString")) 49 | 50 | b.StartTimer() 51 | for i := 0; i < b.N; i++ { 52 | if _, err := Sign(rw, signerHandle, defaultPassword, digest[:], nil, pub.RSAParameters.Sign); err != nil { 53 | b.Fatalf("Signing failed: %v", err) 54 | } 55 | } 56 | } 57 | 58 | func BenchmarkECCNISTP256Signing(b *testing.B) { 59 | b.StopTimer() 60 | rw := openTPM(b) 61 | defer rw.Close() 62 | skipOnUnsupportedAlg(b, rw, AlgECC) 63 | 64 | pub := Public{ 65 | Type: AlgECC, 66 | NameAlg: AlgSHA256, 67 | Attributes: FlagSign | FlagSensitiveDataOrigin | FlagUserWithAuth, 68 | ECCParameters: &ECCParams{ 69 | Sign: &SigScheme{ 70 | Alg: AlgECDSA, 71 | Hash: AlgSHA256, 72 | }, 73 | CurveID: CurveNISTP256, 74 | }, 75 | } 76 | 77 | signerHandle, _, err := CreatePrimary(rw, HandleOwner, pcrSelection7, emptyPassword, defaultPassword, pub) 78 | if err != nil { 79 | b.Fatalf("CreatePrimary failed: %v", err) 80 | } 81 | defer FlushContext(rw, signerHandle) 82 | 83 | digest := sha256.Sum256([]byte("randomString")) 84 | 85 | b.StartTimer() 86 | for i := 0; i < b.N; i++ { 87 | if _, err := Sign(rw, signerHandle, defaultPassword, digest[:], nil, pub.ECCParameters.Sign); err != nil { 88 | b.Fatalf("Signing failed: %v", err) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /legacy/tpm2/test/tpm2_other_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tpm2 18 | 19 | import ( 20 | "flag" 21 | "io" 22 | "testing" 23 | 24 | . "github.com/google/go-tpm/legacy/tpm2" 25 | ) 26 | 27 | var tpmPath = flag.String("tpm-path", "", "Path to TPM character device. Most Linux systems expose it under /dev/tpm0. Empty value (default) will disable all integration tests.") 28 | 29 | func useDeviceTPM() bool { return *tpmPath != "" } 30 | 31 | func openDeviceTPM(tb testing.TB) io.ReadWriteCloser { 32 | tb.Helper() 33 | rw, err := OpenTPM(*tpmPath) 34 | if err != nil { 35 | tb.Fatalf("Open TPM at %s failed: %s\n", *tpmPath, err) 36 | } 37 | return rw 38 | } 39 | -------------------------------------------------------------------------------- /legacy/tpm2/test/tpm2_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm2 16 | 17 | import ( 18 | "flag" 19 | "io" 20 | "testing" 21 | 22 | . "github.com/google/go-tpm/legacy/tpm2" 23 | ) 24 | 25 | var runTPMTests = flag.Bool("use-tbs", false, "Run integration tests against Windows TPM Base Services (TBS). Defaults to false.") 26 | 27 | func useDeviceTPM() bool { return *runTPMTests } 28 | 29 | func openDeviceTPM(tb testing.TB) io.ReadWriteCloser { 30 | tb.Helper() 31 | rw, err := OpenTPM() 32 | if err != nil { 33 | tb.Fatalf("Open TPM failed: %s\n", err) 34 | } 35 | return rw 36 | } 37 | -------------------------------------------------------------------------------- /tpm/open_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2019, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tpm 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | 23 | "github.com/google/go-tpm/tpmutil" 24 | ) 25 | 26 | // OpenTPM opens a channel to the TPM at the given path. If the file is a 27 | // device, then it treats it like a normal TPM device, and if the file is a 28 | // Unix domain socket, then it opens a connection to the socket. 29 | func OpenTPM(path string) (io.ReadWriteCloser, error) { 30 | return openAndStartupTPM(path, false) 31 | } 32 | 33 | // openAndStartupTPM opens the TPM and optionally runs TPM_Startup if needed. 34 | // This feature is implemented only for testing. 35 | func openAndStartupTPM(path string, doStartup bool) (io.ReadWriteCloser, error) { 36 | rwc, err := tpmutil.OpenTPM(path) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | // Make sure this is a TPM 1.2 42 | _, err = GetManufacturer(rwc) 43 | if doStartup && err == tpmError(errInvalidPostInit) { 44 | if err = startup(rwc); err == nil { 45 | _, err = GetManufacturer(rwc) 46 | } 47 | } 48 | if err != nil { 49 | rwc.Close() 50 | return nil, fmt.Errorf("open %s: device is not a TPM 1.2: %v", path, err) 51 | } 52 | return rwc, nil 53 | } 54 | -------------------------------------------------------------------------------- /tpm/open_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | 21 | "github.com/google/go-tpm/tpmutil" 22 | "github.com/google/go-tpm/tpmutil/tbs" 23 | ) 24 | 25 | // OpenTPM opens a channel to the TPM. 26 | func OpenTPM() (io.ReadWriteCloser, error) { 27 | info, err := tbs.GetDeviceInfo() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | if info.TPMVersion != tbs.TPMVersion12 { 33 | return nil, fmt.Errorf("openTPM: device is not a TPM 1.2") 34 | } 35 | 36 | return tpmutil.OpenTPM() 37 | } 38 | -------------------------------------------------------------------------------- /tpm/pcrs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestPCRMask(t *testing.T) { 22 | var mask pcrMask 23 | if err := mask.setPCR(-1); err == nil { 24 | t.Fatal("Incorrectly allowed non-existent PCR -1 to be set") 25 | } 26 | 27 | if err := mask.setPCR(24); err == nil { 28 | t.Fatal("Incorrectly allowed non-existent PCR 24 to be set") 29 | } 30 | 31 | if err := mask.setPCR(0); err != nil { 32 | t.Fatal("Couldn't set PCR 0 in the mask:", err) 33 | } 34 | 35 | set, err := mask.isPCRSet(0) 36 | if err != nil { 37 | t.Fatal("Couldn't check to see if PCR 0 was set:", err) 38 | } 39 | 40 | if !set { 41 | t.Fatal("Incorrectly said PCR wasn't set when it should have been") 42 | } 43 | 44 | if err := mask.setPCR(18); err != nil { 45 | t.Fatal("Couldn't set PCR 18 in the mask:", err) 46 | } 47 | 48 | set, err = mask.isPCRSet(18) 49 | if err != nil { 50 | t.Fatal("Couldn't check to see if PCR 18 was set:", err) 51 | } 52 | 53 | if !set { 54 | t.Fatal("Incorrectly said PCR wasn't set when it should have been") 55 | } 56 | 57 | if _, err := mask.isPCRSet(-1); err == nil { 58 | t.Fatal("Incorrectly permitted a check for PCR -1") 59 | } 60 | 61 | if _, err := mask.isPCRSet(400); err == nil { 62 | t.Fatal("Incorrectly permitted a check for PCR 400") 63 | } 64 | } 65 | 66 | func TestNewPCRSelection(t *testing.T) { 67 | pcrs, err := newPCRSelection([]int{17, 18}) 68 | if err != nil { 69 | t.Fatal("Couldn't set up a PCR selection with PCRs 17 and 18") 70 | } 71 | 72 | if pcrs.Size != 3 { 73 | t.Fatal("Incorrectly size in a PCR selection") 74 | } 75 | 76 | set, err := pcrs.Mask.isPCRSet(17) 77 | if err != nil { 78 | t.Fatal("Couldn't check a PCR on a mask in a PCR selection") 79 | } 80 | 81 | if !set { 82 | t.Fatal("PCR 17 wasn't set in a PCR selection after setting it") 83 | } 84 | 85 | set, err = pcrs.Mask.isPCRSet(20) 86 | if err != nil { 87 | t.Fatal("Couldn't check an unset PCR on a mask in a PCR selection") 88 | } 89 | 90 | if set { 91 | t.Fatal("PCR 20 was incorrectly set in a PCR mask in a PCR selection") 92 | } 93 | } 94 | 95 | func TestIncorrectCreatePCRComposite(t *testing.T) { 96 | pcrs, err := newPCRSelection([]int{17, 18}) 97 | if err != nil { 98 | t.Fatal("Couldn't set up a PCR selection with PCRs 17 and 18") 99 | } 100 | 101 | // This byte array is far too long and isn't a multiple of PCRSize, since it 102 | // is of prime size. 103 | pcrValues := make([]byte, 541) 104 | if _, err := createPCRComposite(pcrs.Mask, pcrValues); err == nil { 105 | t.Fatal("Incorrectly created a PCR composite with wrong PCR length") 106 | } 107 | } 108 | 109 | func TestWrongCreatePCRInfoLong(t *testing.T) { 110 | pcrs, err := newPCRSelection([]int{17, 18}) 111 | if err != nil { 112 | t.Fatal("Couldn't set up a PCR selection with PCRs 17 and 18") 113 | } 114 | 115 | // This byte array is far too long and isn't a multiple of PCRSize, since it 116 | // is of prime size. 117 | pcrValues := make([]byte, 541) 118 | if _, err := createPCRInfoLong(0, pcrs.Mask, pcrValues); err == nil { 119 | t.Fatal("Incorrectly created a PCR composite with wrong PCR length") 120 | } 121 | } 122 | 123 | func TestWrongNewPCRInfoLong(t *testing.T) { 124 | rwc := openTPMOrSkip(t) 125 | defer rwc.Close() 126 | 127 | if _, err := newPCRInfoLong(rwc, 0, []int{400}); err == nil { 128 | t.Fatal("Incorrectly created a pcrInfoLong for PCR 400") 129 | } 130 | 131 | // This case uses a reasonable PCR value but a nil file. 132 | if _, err := newPCRInfoLong(nil, 0, []int{17}); err == nil { 133 | t.Fatal("Incorrectly created a pcrInfoLong using a nil file") 134 | } 135 | } 136 | 137 | func TestNewPCRInfoLongWithHashes(t *testing.T) { 138 | pcrMap := make(map[int][]byte) 139 | pcrMap[23] = make([]byte, 20) 140 | pcrMap[16] = make([]byte, 20) 141 | 142 | if _, err := newPCRInfoLongWithHashes(LocZero, pcrMap); err != nil { 143 | t.Fatal("Couldn't create pcrInfoLong structure") 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tpm/testing.md: -------------------------------------------------------------------------------- 1 | # Testing TPM 1.2 Functionality 2 | 3 | **TODO(https://github.com/google/go-tpm/issues/91):** Support for testing the TPM 1.2 stack against 4 | a simulator is a work in progress. Today, it requires several manual steps. 5 | 6 | ## Overview 7 | 8 | As TPM 1.2s are phased out of common developer devices, testing changes to the TPM 1.2 stack is 9 | difficult without special hardware. To support development on the TPM 1.2 stack without special 10 | hardware, a TPM 1.2 simulator or emulator may be used. This document discusses how to use 11 | [IBM's TPM 1.2 simulator](http://ibmswtpm.sourceforge.net) (on a Linux or Mac OS device, Windows is 12 | not yet supported) to run the go-tpm TPM 1.2 tests (in the `go-tpm/tpm/` directory). 13 | 14 | ## Downloading, building, and using the IBM TPM 1.2 Simulator 15 | 16 | * Download the latest release of the 17 | [IBM TPM 1.2 Simulator](https://sourceforge.net/projects/ibmswtpm/), unpack the tarball, and `cd` 18 | into it. 19 | * Add `-DTPM_UNIX_DOMAIN_SOCKET` to `tpm/makefile-en-ac`. 20 | * Build the simulator with `make -f tpm/makefile-en-ac` 21 | * Set `TEMP_TPM=/tmp/tpm` or some other suitable temporary location for the TPM state files and Unix 22 | domain socket. 23 | * Start the simulator with `TPM_PATH=${TEMP_TPM} TPM_PORT=${TEMP_TPM}/tpm.sock` 24 | 25 | ## Running the TPM 1.2 tests against the IBM TPM 1.2 Simulator 26 | 27 | * Comment out the line `t.Skip()` in `TestTakeOwnership`. This test normally does not work on 28 | physical TPMs, so it is normally disabled. 29 | * Use `TestTakeOwnership` to take ownership of the simulated TPM with `TPM_PATH=${TEMP_TPM}/tpm.sock 30 | go test -v ./tpm/... -run TestTakeOwnership -count=1` 31 | * Run the full test suite with `TPM_PATH=${TEMP_TPM}/tpm.sock go test -v ./tpm/...` 32 | 33 | ## Future Improvements 34 | 35 | * Add setup logic to the TPM 1.2 tests to take ownership of an unowned TPM under test. 36 | * Wrap a TPM 1.2 simulator somewhere (possibly in https://github.com/google/go-tpm-tools) and 37 | integrate it into test setup for the TPM 1.2 tests. 38 | * Resolve issues that necessitated the use of `t.Skip()` in current tests. 39 | * Either add an informative comment along with a skip when a test fails for an expected reason, or 40 | remove the test. 41 | * Resolve issues with current tests that fail on the simulator (such as `TestGetAlgs`). 42 | * Automate the use of a simulator in a Continuous Integration environment that is accessible to 43 | GitHub. -------------------------------------------------------------------------------- /tpm/tpm_other_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2014, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tpm 18 | 19 | import ( 20 | "io" 21 | "os" 22 | "testing" 23 | ) 24 | 25 | // Skip the test if we can't open the TPM. 26 | func openTPMOrSkip(t *testing.T) io.ReadWriteCloser { 27 | tpmPath := os.Getenv(tpmPathEnvVar) 28 | if tpmPath == "" { 29 | tpmPath = "/dev/tpm0" 30 | } 31 | 32 | rwc, err := openAndStartupTPM(tpmPath, true) 33 | if err != nil { 34 | t.Skipf("Skipping test, since we can't open %s for read/write: %s\n", tpmPath, err) 35 | } 36 | 37 | return rwc 38 | } 39 | -------------------------------------------------------------------------------- /tpm/tpm_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm 16 | 17 | import ( 18 | "io" 19 | "testing" 20 | ) 21 | 22 | // Skip the test if we can't open the TPM. 23 | func openTPMOrSkip(t *testing.T) io.ReadWriteCloser { 24 | rwc, err := OpenTPM() 25 | if err != nil { 26 | t.Skipf("Skipping test, since we can't access the TPM: %s\n", err) 27 | } 28 | 29 | return rwc 30 | } 31 | -------------------------------------------------------------------------------- /tpm/verify.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpm 16 | 17 | import ( 18 | "crypto" 19 | "crypto/rsa" 20 | "crypto/sha1" 21 | "errors" 22 | "math/big" 23 | 24 | "github.com/google/go-tpm/tpmutil" 25 | ) 26 | 27 | // This file provides functions to extract a crypto/rsa public key from a key 28 | // blob or a TPM_KEY of the right type. It also provides a function for 29 | // verifying a quote value given a public key for the key it was signed with. 30 | 31 | // UnmarshalRSAPublicKey takes in a blob containing a serialized RSA TPM_KEY and 32 | // converts it to a crypto/rsa.PublicKey. 33 | func UnmarshalRSAPublicKey(keyBlob []byte) (*rsa.PublicKey, error) { 34 | // Parse the blob as a key. 35 | var k key 36 | if _, err := tpmutil.Unpack(keyBlob, &k); err != nil { 37 | return nil, err 38 | } 39 | 40 | return k.unmarshalRSAPublicKey() 41 | } 42 | 43 | // unmarshalRSAPublicKey unmarshals a TPM key into a crypto/rsa.PublicKey. 44 | func (k *key) unmarshalRSAPublicKey() (*rsa.PublicKey, error) { 45 | // Currently, we only support algRSA 46 | if k.AlgorithmParams.AlgID != AlgRSA { 47 | return nil, errors.New("only TPM_ALG_RSA is supported") 48 | } 49 | 50 | // This means that k.AlgorithmsParams.Params is an rsaKeyParams, which is 51 | // enough to create the exponent, and k.PubKey contains the key. 52 | var rsakp rsaKeyParams 53 | if _, err := tpmutil.Unpack(k.AlgorithmParams.Params, &rsakp); err != nil { 54 | return nil, err 55 | } 56 | 57 | // Make sure that the exponent will fit into an int before using it blindly. 58 | if len(rsakp.Exponent) > 4 { 59 | return nil, errors.New("exponent value doesn't fit into an int") 60 | } 61 | pk := &rsa.PublicKey{ 62 | N: new(big.Int).SetBytes(k.PubKey), 63 | // The exponent isn't set here, but it's fixed to 0x10001 64 | E: 0x10001, 65 | } 66 | return pk, nil 67 | } 68 | 69 | // UnmarshalPubRSAPublicKey takes in a blob containing a serialized RSA 70 | // TPM_PUBKEY and converts it to a crypto/rsa.PublicKey. 71 | func UnmarshalPubRSAPublicKey(keyBlob []byte) (*rsa.PublicKey, error) { 72 | // Parse the blob as a key. 73 | var pk pubKey 74 | if _, err := tpmutil.Unpack(keyBlob, &pk); err != nil { 75 | return nil, err 76 | } 77 | 78 | return pk.unmarshalRSAPublicKey() 79 | } 80 | 81 | // unmarshalRSAPublicKey unmarshals a TPM pub key into a crypto/rsa.PublicKey. 82 | // This is almost identical to the identically named function for a TPM key. 83 | func (pk *pubKey) unmarshalRSAPublicKey() (*rsa.PublicKey, error) { 84 | // Currently, we only support AlgRSA 85 | if pk.AlgorithmParams.AlgID != AlgRSA { 86 | return nil, errors.New("only TPM_ALG_RSA is supported") 87 | } 88 | 89 | // This means that pk.AlgorithmsParams.Params is an rsaKeyParams, which is 90 | // enough to create the exponent, and pk.Key contains the key. 91 | var rsakp rsaKeyParams 92 | if _, err := tpmutil.Unpack(pk.AlgorithmParams.Params, &rsakp); err != nil { 93 | return nil, err 94 | } 95 | 96 | // Make sure that the exponent will fit into an int before using it blindly. 97 | if len(rsakp.Exponent) > 4 { 98 | return nil, errors.New("exponent value doesn't fit into an int") 99 | } 100 | rsapk := &rsa.PublicKey{ 101 | N: new(big.Int).SetBytes(pk.Key), 102 | // The exponent isn't set here, but it's fixed to 0x10001 103 | E: 0x10001, 104 | } 105 | return rsapk, nil 106 | } 107 | 108 | // NewQuoteInfo computes a quoteInfo structure for a given pair of data and PCR 109 | // values. 110 | func NewQuoteInfo(data []byte, pcrNums []int, pcrs []byte) ([]byte, error) { 111 | // Compute the composite hash for these PCRs. 112 | pcrSel, err := newPCRSelection(pcrNums) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | comp, err := createPCRComposite(pcrSel.Mask, pcrs) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | qi := "eInfo{ 123 | Version: quoteVersion, 124 | Fixed: fixedQuote, 125 | Nonce: sha1.Sum(data), 126 | } 127 | copy(qi.CompositeDigest[:], comp) 128 | 129 | return tpmutil.Pack(qi) 130 | } 131 | 132 | // VerifyQuote verifies a quote against a given set of PCRs. 133 | func VerifyQuote(pk *rsa.PublicKey, data []byte, quote []byte, pcrNums []int, pcrs []byte) error { 134 | p, err := NewQuoteInfo(data, pcrNums, pcrs) 135 | if err != nil { 136 | return err 137 | } 138 | 139 | s := sha1.Sum(p) 140 | 141 | // Try to do a direct encryption to reverse the value and see if it's padded 142 | // with PKCS1v1.5. 143 | return rsa.VerifyPKCS1v15(pk, crypto.SHA1, s[:], quote) 144 | } 145 | 146 | // TODO(tmroeder): add VerifyQuote2 instead of VerifyQuote. This means I'll 147 | // probably have to look at the signature scheme and use that to choose how to 148 | // verify the signature, whether PKCS1v1.5 or OAEP. And this will have to be set 149 | // on the key before it's passed to ordQuote2 150 | // TODO(tmroeder): handle key12 151 | -------------------------------------------------------------------------------- /tpm2/audit.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // CommandAudit represents an audit session for attesting the execution of a 10 | // series of commands in the TPM. It is useful for both command and session 11 | // auditing. 12 | type CommandAudit struct { 13 | hash TPMIAlgHash 14 | digest []byte 15 | } 16 | 17 | // NewAudit initializes a new CommandAudit with the specified hash algorithm. 18 | func NewAudit(hash TPMIAlgHash) (*CommandAudit, error) { 19 | h, err := hash.Hash() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &CommandAudit{ 24 | hash: hash, 25 | digest: make([]byte, h.Size()), 26 | }, nil 27 | } 28 | 29 | // AuditCommand extends the audit digest with the given command and response. 30 | // Go Generics do not allow type parameters on methods, otherwise this would be 31 | // a method on CommandAudit. 32 | // See https://github.com/golang/go/issues/49085 for more information. 33 | func AuditCommand[C Command[R, *R], R any](a *CommandAudit, cmd C, rsp *R) error { 34 | cc := cmd.Command() 35 | cpHash, err := auditCPHash[R](cc, a.hash, cmd) 36 | if err != nil { 37 | return err 38 | } 39 | rpHash, err := auditRPHash(cc, a.hash, rsp) 40 | if err != nil { 41 | return err 42 | } 43 | ha, err := a.hash.Hash() 44 | if err != nil { 45 | return err 46 | } 47 | h := ha.New() 48 | h.Write(a.digest) 49 | h.Write(cpHash) 50 | h.Write(rpHash) 51 | a.digest = h.Sum(nil) 52 | return nil 53 | } 54 | 55 | // Digest returns the current digest of the audit. 56 | func (a *CommandAudit) Digest() []byte { 57 | return a.digest 58 | } 59 | 60 | // auditCPHash calculates the command parameter hash for a given command with 61 | // the given hash algorithm. The command is assumed to not have any decrypt 62 | // sessions. 63 | func auditCPHash[R any](cc TPMCC, h TPMIAlgHash, c Command[R, *R]) ([]byte, error) { 64 | names, err := cmdNames(c) 65 | if err != nil { 66 | return nil, err 67 | } 68 | parms, err := cmdParameters(c, nil) 69 | if err != nil { 70 | return nil, err 71 | } 72 | return cpHash(h, cc, names, parms) 73 | } 74 | 75 | // auditRPHash calculates the response parameter hash for a given response with 76 | // the given hash algorithm. The command is assumed to be successful and to not 77 | // have any encrypt sessions. 78 | func auditRPHash(cc TPMCC, h TPMIAlgHash, r any) ([]byte, error) { 79 | var parms bytes.Buffer 80 | parameters := taggedMembers(reflect.ValueOf(r).Elem(), "handle", true) 81 | for i, parameter := range parameters { 82 | if err := marshal(&parms, parameter); err != nil { 83 | return nil, fmt.Errorf("marshalling parameter %v: %w", i+1, err) 84 | } 85 | } 86 | return rpHash(h, TPMRCSuccess, cc, parms.Bytes()) 87 | } 88 | -------------------------------------------------------------------------------- /tpm2/bitfield.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Bitfield represents a TPM bitfield (i.e., TPMA_*) type. 8 | type Bitfield interface { 9 | // Length returns the length of the bitfield. 10 | Length() int 11 | } 12 | 13 | // BitGetter represents a TPM bitfield (i.e., TPMA_*) type that can be read. 14 | type BitGetter interface { 15 | Bitfield 16 | // GetReservedBit returns the value of the given reserved bit. 17 | // If the bit is not reserved, returns false. 18 | GetReservedBit(pos int) bool 19 | } 20 | 21 | // BitSetter represents a TPM bitfield (i.e., TPMA_*) type that can be written. 22 | type BitSetter interface { 23 | Bitfield 24 | // GetReservedBit sets the value of the given reserved bit. 25 | SetReservedBit(pos int, val bool) 26 | } 27 | 28 | func checkPos(pos int, len int) { 29 | if pos >= len || pos < 0 { 30 | panic(fmt.Errorf("bit %d out of range for %d-bit field", pos, len)) 31 | } 32 | } 33 | 34 | // bitfield8 represents an 8-bit bitfield which may have reserved bits. 35 | // 8-bit TPMA_* types embed this one, and the reserved bits are stored in it. 36 | type bitfield8 uint8 37 | 38 | // Length implements the Bitfield interface. 39 | func (bitfield8) Length() int { 40 | return 8 41 | } 42 | 43 | // GetReservedBit implements the BitGetter interface. 44 | func (r bitfield8) GetReservedBit(pos int) bool { 45 | checkPos(pos, 8) 46 | return r&(1< maxPCR { 29 | maxPCR = pcr 30 | } 31 | } 32 | selectionSize := maxPCR/8 + 1 33 | 34 | // Enforce the minimum PCR selection size. 35 | if selectionSize < (pcClientMinimumPCRCount / 8) { 36 | selectionSize = (pcClientMinimumPCRCount / 8) 37 | } 38 | 39 | // Allocate a byte array to store the bitfield, that has at least 40 | // enough bits to store our selections. 41 | selection := make([]byte, selectionSize) 42 | for _, pcr := range pcrs { 43 | // The PCR selection mask is byte-wise little-endian: 44 | // select[0] contains bits representing the selection of PCRs 0 through 7 45 | // select[1] contains PCRs 8 through 15, and so on. 46 | byteIdx := pcr / 8 47 | // Within the byte, the PCR selection is bit-wise big-endian: 48 | // bit 0 of select[0] contains the selection of PCR 0 49 | // bit 1 of select[0] contains the selection of PCR 1, and so on. 50 | bitIdx := pcr % 8 51 | 52 | selection[byteIdx] |= (1 << bitIdx) 53 | } 54 | return selection 55 | } 56 | -------------------------------------------------------------------------------- /tpm2/policy.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "reflect" 7 | ) 8 | 9 | // PolicyCalculator represents a TPM 2.0 policy that needs to be calculated 10 | // synthetically (i.e., without a TPM). 11 | type PolicyCalculator struct { 12 | alg TPMIAlgHash 13 | hash crypto.Hash 14 | state []byte 15 | } 16 | 17 | // NewPolicyCalculator creates a fresh policy using the given hash algorithm. 18 | func NewPolicyCalculator(alg TPMIAlgHash) (*PolicyCalculator, error) { 19 | hash, err := alg.Hash() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &PolicyCalculator{ 24 | alg: alg, 25 | hash: hash, 26 | state: make([]byte, hash.Size()), 27 | }, nil 28 | } 29 | 30 | // Reset resets the internal state of the policy hash to all 0x00. 31 | func (p *PolicyCalculator) Reset() { 32 | p.state = make([]byte, p.hash.Size()) 33 | } 34 | 35 | // Update updates the internal state of the policy hash by appending the 36 | // current state with the given contents, and updating the new state to the 37 | // hash of that. 38 | func (p *PolicyCalculator) Update(data ...interface{}) error { 39 | hash := p.hash.New() 40 | hash.Write(p.state) 41 | var buf bytes.Buffer 42 | for _, d := range data { 43 | if err := marshal(&buf, reflect.ValueOf(d)); err != nil { 44 | return err 45 | } 46 | } 47 | hash.Write(buf.Bytes()) 48 | p.state = hash.Sum(nil) 49 | return nil 50 | } 51 | 52 | // Hash returns the current state of the policy hash. 53 | func (p *PolicyCalculator) Hash() *TPMTHA { 54 | result := TPMTHA{ 55 | HashAlg: p.alg, 56 | Digest: make([]byte, len(p.state)), 57 | } 58 | copy(result.Digest, p.state) 59 | return &result 60 | } 61 | -------------------------------------------------------------------------------- /tpm2/templates.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | var ( 4 | // RSASRKTemplate contains the TCG reference RSA-2048 SRK template. 5 | // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf 6 | RSASRKTemplate = TPMTPublic{ 7 | Type: TPMAlgRSA, 8 | NameAlg: TPMAlgSHA256, 9 | ObjectAttributes: TPMAObject{ 10 | FixedTPM: true, 11 | STClear: false, 12 | FixedParent: true, 13 | SensitiveDataOrigin: true, 14 | UserWithAuth: true, 15 | AdminWithPolicy: false, 16 | NoDA: true, 17 | EncryptedDuplication: false, 18 | Restricted: true, 19 | Decrypt: true, 20 | SignEncrypt: false, 21 | }, 22 | Parameters: NewTPMUPublicParms( 23 | TPMAlgRSA, 24 | &TPMSRSAParms{ 25 | Symmetric: TPMTSymDefObject{ 26 | Algorithm: TPMAlgAES, 27 | KeyBits: NewTPMUSymKeyBits( 28 | TPMAlgAES, 29 | TPMKeyBits(128), 30 | ), 31 | Mode: NewTPMUSymMode( 32 | TPMAlgAES, 33 | TPMAlgCFB, 34 | ), 35 | }, 36 | KeyBits: 2048, 37 | }, 38 | ), 39 | Unique: NewTPMUPublicID( 40 | TPMAlgRSA, 41 | &TPM2BPublicKeyRSA{ 42 | Buffer: make([]byte, 256), 43 | }, 44 | ), 45 | } 46 | // RSAEKTemplate contains the TCG reference RSA-2048 EK template. 47 | RSAEKTemplate = TPMTPublic{ 48 | Type: TPMAlgRSA, 49 | NameAlg: TPMAlgSHA256, 50 | ObjectAttributes: TPMAObject{ 51 | FixedTPM: true, 52 | STClear: false, 53 | FixedParent: true, 54 | SensitiveDataOrigin: true, 55 | UserWithAuth: false, 56 | AdminWithPolicy: true, 57 | NoDA: false, 58 | EncryptedDuplication: false, 59 | Restricted: true, 60 | Decrypt: true, 61 | SignEncrypt: false, 62 | }, 63 | AuthPolicy: TPM2BDigest{ 64 | Buffer: []byte{ 65 | // TPM2_PolicySecret(RH_ENDORSEMENT) 66 | 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8, 67 | 0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24, 68 | 0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, 69 | 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA, 70 | }, 71 | }, 72 | Parameters: NewTPMUPublicParms( 73 | TPMAlgRSA, 74 | &TPMSRSAParms{ 75 | Symmetric: TPMTSymDefObject{ 76 | Algorithm: TPMAlgAES, 77 | KeyBits: NewTPMUSymKeyBits( 78 | TPMAlgAES, 79 | TPMKeyBits(128), 80 | ), 81 | Mode: NewTPMUSymMode( 82 | TPMAlgAES, 83 | TPMAlgCFB, 84 | ), 85 | }, 86 | KeyBits: 2048, 87 | }, 88 | ), 89 | Unique: NewTPMUPublicID( 90 | TPMAlgRSA, 91 | &TPM2BPublicKeyRSA{ 92 | Buffer: make([]byte, 256), 93 | }, 94 | ), 95 | } 96 | 97 | // ECCSRKTemplate contains the TCG reference ECC-P256 SRK template. 98 | // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf 99 | ECCSRKTemplate = TPMTPublic{ 100 | Type: TPMAlgECC, 101 | NameAlg: TPMAlgSHA256, 102 | ObjectAttributes: TPMAObject{ 103 | FixedTPM: true, 104 | STClear: false, 105 | FixedParent: true, 106 | SensitiveDataOrigin: true, 107 | UserWithAuth: true, 108 | AdminWithPolicy: false, 109 | NoDA: true, 110 | EncryptedDuplication: false, 111 | Restricted: true, 112 | Decrypt: true, 113 | SignEncrypt: false, 114 | }, 115 | Parameters: NewTPMUPublicParms( 116 | TPMAlgECC, 117 | &TPMSECCParms{ 118 | Symmetric: TPMTSymDefObject{ 119 | Algorithm: TPMAlgAES, 120 | KeyBits: NewTPMUSymKeyBits( 121 | TPMAlgAES, 122 | TPMKeyBits(128), 123 | ), 124 | Mode: NewTPMUSymMode( 125 | TPMAlgAES, 126 | TPMAlgCFB, 127 | ), 128 | }, 129 | CurveID: TPMECCNistP256, 130 | }, 131 | ), 132 | Unique: NewTPMUPublicID( 133 | TPMAlgECC, 134 | &TPMSECCPoint{ 135 | X: TPM2BECCParameter{ 136 | Buffer: make([]byte, 32), 137 | }, 138 | Y: TPM2BECCParameter{ 139 | Buffer: make([]byte, 32), 140 | }, 141 | }, 142 | ), 143 | } 144 | 145 | // ECCEKTemplate contains the TCG reference ECC-P256 EK template. 146 | ECCEKTemplate = TPMTPublic{ 147 | Type: TPMAlgECC, 148 | NameAlg: TPMAlgSHA256, 149 | ObjectAttributes: TPMAObject{ 150 | FixedTPM: true, 151 | STClear: false, 152 | FixedParent: true, 153 | SensitiveDataOrigin: true, 154 | UserWithAuth: false, 155 | AdminWithPolicy: true, 156 | NoDA: false, 157 | EncryptedDuplication: false, 158 | Restricted: true, 159 | Decrypt: true, 160 | SignEncrypt: false, 161 | }, 162 | AuthPolicy: TPM2BDigest{ 163 | Buffer: []byte{ 164 | // TPM2_PolicySecret(RH_ENDORSEMENT) 165 | 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8, 166 | 0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24, 167 | 0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64, 168 | 0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA, 169 | }, 170 | }, 171 | Parameters: NewTPMUPublicParms( 172 | TPMAlgECC, 173 | &TPMSECCParms{ 174 | Symmetric: TPMTSymDefObject{ 175 | Algorithm: TPMAlgAES, 176 | KeyBits: NewTPMUSymKeyBits( 177 | TPMAlgAES, 178 | TPMKeyBits(128), 179 | ), 180 | Mode: NewTPMUSymMode( 181 | TPMAlgAES, 182 | TPMAlgCFB, 183 | ), 184 | }, 185 | CurveID: TPMECCNistP256, 186 | }, 187 | ), 188 | Unique: NewTPMUPublicID( 189 | TPMAlgECC, 190 | &TPMSECCPoint{ 191 | X: TPM2BECCParameter{ 192 | Buffer: make([]byte, 32), 193 | }, 194 | Y: TPM2BECCParameter{ 195 | Buffer: make([]byte, 32), 196 | }, 197 | }, 198 | ), 199 | } 200 | ) 201 | -------------------------------------------------------------------------------- /tpm2/test/audit_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func TestAuditSession(t *testing.T) { 12 | thetpm, err := simulator.OpenSimulator() 13 | if err != nil { 14 | t.Fatalf("could not connect to TPM simulator: %v", err) 15 | } 16 | defer thetpm.Close() 17 | 18 | // Create the audit session 19 | sess, cleanup, err := HMACSession(thetpm, TPMAlgSHA256, 16, Audit()) 20 | if err != nil { 21 | t.Fatalf("%v", err) 22 | } 23 | defer cleanup() 24 | 25 | // Create the AK for audit 26 | createAKCmd := CreatePrimary{ 27 | PrimaryHandle: TPMRHOwner, 28 | InPublic: New2B(TPMTPublic{ 29 | Type: TPMAlgECC, 30 | NameAlg: TPMAlgSHA256, 31 | ObjectAttributes: TPMAObject{ 32 | FixedTPM: true, 33 | STClear: false, 34 | FixedParent: true, 35 | SensitiveDataOrigin: true, 36 | UserWithAuth: true, 37 | AdminWithPolicy: false, 38 | NoDA: true, 39 | EncryptedDuplication: false, 40 | Restricted: true, 41 | Decrypt: false, 42 | SignEncrypt: true, 43 | }, 44 | Parameters: NewTPMUPublicParms( 45 | TPMAlgECC, 46 | &TPMSECCParms{ 47 | Scheme: TPMTECCScheme{ 48 | Scheme: TPMAlgECDSA, 49 | Details: NewTPMUAsymScheme( 50 | TPMAlgECDSA, 51 | &TPMSSigSchemeECDSA{ 52 | HashAlg: TPMAlgSHA256, 53 | }, 54 | ), 55 | }, 56 | CurveID: TPMECCNistP256, 57 | }, 58 | ), 59 | }, 60 | ), 61 | } 62 | createAKRsp, err := createAKCmd.Execute(thetpm) 63 | if err != nil { 64 | t.Fatalf("%v", err) 65 | } 66 | defer func() { 67 | // Flush the AK 68 | flush := FlushContext{FlushHandle: createAKRsp.ObjectHandle} 69 | if _, err := flush.Execute(thetpm); err != nil { 70 | t.Errorf("%v", err) 71 | } 72 | }() 73 | 74 | audit, err := NewAudit(TPMAlgSHA256) 75 | if err != nil { 76 | t.Fatalf("%v", err) 77 | } 78 | // Call GetCapability a bunch of times with the audit session and make sure it extends like 79 | // we expect it to. 80 | props := []TPMPT{ 81 | TPMPTFamilyIndicator, 82 | TPMPTLevel, 83 | TPMPTRevision, 84 | TPMPTDayofYear, 85 | TPMPTYear, 86 | TPMPTManufacturer, 87 | } 88 | for _, prop := range props { 89 | getCmd := GetCapability{ 90 | Capability: TPMCapTPMProperties, 91 | Property: uint32(prop), 92 | PropertyCount: 1, 93 | } 94 | getRsp, err := getCmd.Execute(thetpm, sess) 95 | if err != nil { 96 | t.Fatalf("%v", err) 97 | } 98 | if err := AuditCommand(audit, getCmd, getRsp); err != nil { 99 | t.Fatalf("%v", err) 100 | } 101 | // Get the audit digest signed by the AK 102 | getAuditCmd := GetSessionAuditDigest{ 103 | PrivacyAdminHandle: TPMRHEndorsement, 104 | SignHandle: NamedHandle{ 105 | Handle: createAKRsp.ObjectHandle, 106 | Name: createAKRsp.Name, 107 | }, 108 | SessionHandle: sess.Handle(), 109 | QualifyingData: TPM2BData{Buffer: []byte("foobar")}, 110 | } 111 | getAuditRsp, err := getAuditCmd.Execute(thetpm) 112 | if err != nil { 113 | t.Fatalf("%v", err) 114 | } 115 | // TODO check the signature with the AK pub 116 | attest, err := getAuditRsp.AuditInfo.Contents() 117 | if err != nil { 118 | t.Fatalf("%v", err) 119 | } 120 | aud, err := attest.Attested.SessionAudit() 121 | if err != nil { 122 | t.Fatalf("%v", err) 123 | } 124 | want := audit.Digest() 125 | got := aud.SessionDigest.Buffer 126 | if !bytes.Equal(want, got) { 127 | t.Errorf("unexpected audit value:\ngot %x\nwant %x", got, want) 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /tpm2/test/clear_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func TestClear(t *testing.T) { 12 | thetpm, err := simulator.OpenSimulator() 13 | if err != nil { 14 | t.Fatalf("could not connect to TPM simulator: %v", err) 15 | } 16 | defer thetpm.Close() 17 | 18 | srkCreate := CreatePrimary{ 19 | PrimaryHandle: TPMRHOwner, 20 | InPublic: New2B(ECCSRKTemplate), 21 | } 22 | 23 | srkCreateRsp, err := srkCreate.Execute(thetpm) 24 | if err != nil { 25 | t.Fatalf("could not generate SRK: %v", err) 26 | } 27 | 28 | srkName1 := srkCreateRsp.Name 29 | 30 | clear := Clear{ 31 | AuthHandle: AuthHandle{ 32 | Handle: TPMRHLockout, 33 | Auth: PasswordAuth(nil), 34 | }, 35 | } 36 | _, err = clear.Execute(thetpm) 37 | if err != nil { 38 | t.Fatalf("could not clear TPM: %v", err) 39 | } 40 | 41 | srkCreateRsp, err = srkCreate.Execute(thetpm) 42 | if err != nil { 43 | t.Fatalf("could not generate SRK: %v", err) 44 | } 45 | defer func() { 46 | flush := FlushContext{ 47 | FlushHandle: srkCreateRsp.ObjectHandle, 48 | } 49 | _, err := flush.Execute(thetpm) 50 | if err != nil { 51 | t.Fatalf("could not flush SRK: %v", err) 52 | } 53 | }() 54 | 55 | srkName2 := srkCreateRsp.Name 56 | 57 | if bytes.Equal(srkName1.Buffer, srkName2.Buffer) { 58 | t.Errorf("SRK Name did not change across clear, was %x both times", srkName1.Buffer) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tpm2/test/combined_context_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "github.com/google/go-cmp/cmp/cmpopts" 8 | . "github.com/google/go-tpm/tpm2" 9 | "github.com/google/go-tpm/tpm2/transport" 10 | "github.com/google/go-tpm/tpm2/transport/simulator" 11 | ) 12 | 13 | func ReadPublicName(t *testing.T, handle TPMHandle, thetpm transport.TPM) TPM2BName { 14 | readPublic := ReadPublic{ 15 | ObjectHandle: handle, 16 | } 17 | 18 | rspRP, err := readPublic.Execute(thetpm) 19 | if err != nil { 20 | t.Fatalf("Failed to read public: %v", err) 21 | } 22 | 23 | return rspRP.Name 24 | } 25 | 26 | func TestCombinedContext(t *testing.T) { 27 | thetpm, err := simulator.OpenSimulator() 28 | if err != nil { 29 | t.Fatalf("could not connect to TPM simulator: %v", err) 30 | } 31 | defer thetpm.Close() 32 | 33 | createPrimary := CreatePrimary{ 34 | PrimaryHandle: TPMRHOwner, 35 | 36 | InPublic: New2B(TPMTPublic{ 37 | Type: TPMAlgRSA, 38 | NameAlg: TPMAlgSHA256, 39 | ObjectAttributes: TPMAObject{ 40 | SignEncrypt: true, 41 | FixedTPM: true, 42 | FixedParent: true, 43 | SensitiveDataOrigin: true, 44 | UserWithAuth: true, 45 | }, 46 | Parameters: NewTPMUPublicParms( 47 | TPMAlgRSA, 48 | &TPMSRSAParms{ 49 | Scheme: TPMTRSAScheme{ 50 | Scheme: TPMAlgRSASSA, 51 | Details: NewTPMUAsymScheme( 52 | TPMAlgRSASSA, &TPMSSigSchemeRSASSA{ 53 | HashAlg: TPMAlgSHA256, 54 | }, 55 | ), 56 | }, 57 | KeyBits: 2048, 58 | }, 59 | ), 60 | }), 61 | CreationPCR: TPMLPCRSelection{ 62 | PCRSelections: []TPMSPCRSelection{ 63 | { 64 | Hash: TPMAlgSHA1, 65 | PCRSelect: PCClientCompatible.PCRs(7), 66 | }, 67 | }, 68 | }, 69 | } 70 | 71 | rspCP, err := createPrimary.Execute(thetpm) 72 | if err != nil { 73 | t.Fatalf("could not create key: %v", err) 74 | } 75 | 76 | flushContextObject := FlushContext{FlushHandle: rspCP.ObjectHandle} 77 | defer flushContextObject.Execute(thetpm) 78 | 79 | contextSave := ContextSave{ 80 | SaveHandle: rspCP.ObjectHandle, 81 | } 82 | 83 | rspCS, err := contextSave.Execute(thetpm) 84 | if err != nil { 85 | t.Fatalf("ContextSave failed: %v", err) 86 | } 87 | 88 | contextLoad := ContextLoad{ 89 | Context: rspCS.Context, 90 | } 91 | 92 | rspCL, err := contextLoad.Execute(thetpm) 93 | if err != nil { 94 | t.Fatalf("ContextLoad failed: %v", err) 95 | } 96 | 97 | flushContextLoaded := FlushContext{FlushHandle: rspCL.LoadedHandle} 98 | defer flushContextLoaded.Execute(thetpm) 99 | 100 | rspCLName := ReadPublicName(t, rspCL.LoadedHandle, thetpm) 101 | rspCPName := ReadPublicName(t, rspCP.ObjectHandle, thetpm) 102 | 103 | if !cmp.Equal(rspCLName, rspCPName, cmpopts.IgnoreUnexported(rspCLName)) { 104 | t.Error("Mismatch between public returned from ContextLoad & CreateLoaded") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tpm2/test/commit_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/google/go-tpm/tpm2" 7 | "github.com/google/go-tpm/tpm2/transport/simulator" 8 | ) 9 | 10 | func TestCommit(t *testing.T) { 11 | thetpm, err := simulator.OpenSimulator() 12 | if err != nil { 13 | t.Fatalf("could not connect to TPM simulator: %v", err) 14 | } 15 | 16 | defer thetpm.Close() 17 | 18 | password := []byte("hello") 19 | 20 | create := CreateLoaded{ 21 | ParentHandle: TPMRHOwner, 22 | InSensitive: TPM2BSensitiveCreate{ 23 | Sensitive: &TPMSSensitiveCreate{ 24 | UserAuth: TPM2BAuth{ 25 | Buffer: password, 26 | }, 27 | }, 28 | }, 29 | InPublic: New2BTemplate( 30 | &TPMTPublic{ 31 | Type: TPMAlgECC, 32 | NameAlg: TPMAlgSHA256, 33 | ObjectAttributes: TPMAObject{ 34 | FixedTPM: true, 35 | FixedParent: true, 36 | UserWithAuth: true, 37 | SensitiveDataOrigin: true, 38 | SignEncrypt: true, 39 | }, 40 | Parameters: NewTPMUPublicParms( 41 | TPMAlgECC, 42 | &TPMSECCParms{ 43 | Symmetric: TPMTSymDefObject{ 44 | Algorithm: TPMAlgNull, 45 | }, 46 | Scheme: TPMTECCScheme{ 47 | Scheme: TPMAlgECDAA, 48 | Details: NewTPMUAsymScheme( 49 | TPMAlgECDAA, 50 | &TPMSSchemeECDAA{ 51 | HashAlg: TPMAlgSHA256, 52 | }, 53 | ), 54 | }, 55 | CurveID: TPMECCBNP256, 56 | KDF: TPMTKDFScheme{ 57 | Scheme: TPMAlgNull, 58 | }, 59 | }, 60 | ), 61 | }), 62 | } 63 | 64 | rspCP, err := create.Execute(thetpm) 65 | if err != nil { 66 | t.Fatalf("could not create key: %v", err) 67 | } 68 | 69 | flushContextCP := FlushContext{FlushHandle: rspCP.ObjectHandle} 70 | defer flushContextCP.Execute(thetpm) 71 | 72 | commit := Commit{ 73 | SignHandle: AuthHandle{ 74 | Handle: rspCP.ObjectHandle, 75 | Name: rspCP.Name, 76 | Auth: PasswordAuth(password), 77 | }, 78 | P1: New2B( 79 | TPMSECCPoint{ 80 | X: TPM2BECCParameter{ 81 | Buffer: []byte{1}, 82 | }, 83 | Y: TPM2BECCParameter{ 84 | Buffer: []byte{2}, 85 | }, 86 | }), 87 | S2: TPM2BSensitiveData{ 88 | Buffer: []byte{}, 89 | }, 90 | Y2: TPM2BECCParameter{ 91 | Buffer: []byte{}, 92 | }, 93 | } 94 | 95 | resp, err := commit.Execute(thetpm) 96 | if err != nil { 97 | t.Fatalf("could not commit: %v", err) 98 | } 99 | 100 | firstCounter := resp.Counter 101 | 102 | resp, err = commit.Execute(thetpm) 103 | if err != nil { 104 | t.Fatalf("could not commit: %v", err) 105 | } 106 | 107 | secondCounter := resp.Counter 108 | 109 | if firstCounter+1 != secondCounter { 110 | t.Fatalf("counter did not increment") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tpm2/test/create_loaded_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/google/go-tpm/tpm2" 7 | "github.com/google/go-tpm/tpm2/transport" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func getDeriver(t *testing.T, thetpm transport.TPM) NamedHandle { 12 | t.Helper() 13 | 14 | cl := CreateLoaded{ 15 | ParentHandle: TPMRHOwner, 16 | InPublic: New2BTemplate( 17 | &TPMTPublic{ 18 | Type: TPMAlgKeyedHash, 19 | NameAlg: TPMAlgSHA256, 20 | ObjectAttributes: TPMAObject{ 21 | SensitiveDataOrigin: true, 22 | UserWithAuth: true, 23 | Decrypt: true, 24 | Restricted: true, 25 | }, 26 | Parameters: NewTPMUPublicParms( 27 | TPMAlgKeyedHash, 28 | &TPMSKeyedHashParms{ 29 | Scheme: TPMTKeyedHashScheme{ 30 | Scheme: TPMAlgXOR, 31 | Details: NewTPMUSchemeKeyedHash( 32 | TPMAlgXOR, 33 | &TPMSSchemeXOR{ 34 | HashAlg: TPMAlgSHA256, 35 | KDF: TPMAlgKDF1SP800108, 36 | }, 37 | ), 38 | }, 39 | }, 40 | ), 41 | }), 42 | } 43 | rsp, err := cl.Execute(thetpm) 44 | if err != nil { 45 | t.Fatalf("could not create derivation parent: %v:", err) 46 | } 47 | return NamedHandle{ 48 | Handle: rsp.ObjectHandle, 49 | Name: rsp.Name, 50 | } 51 | } 52 | 53 | func TestCreateLoaded(t *testing.T) { 54 | thetpm, err := simulator.OpenSimulator() 55 | if err != nil { 56 | t.Fatalf("could not connect to TPM simulator: %v", err) 57 | } 58 | defer thetpm.Close() 59 | 60 | deriver := getDeriver(t, thetpm) 61 | 62 | derive := New2B( 63 | TPMSDerive{ 64 | Label: TPM2BLabel{ 65 | Buffer: []byte("label"), 66 | }, 67 | Context: TPM2BLabel{ 68 | Buffer: []byte("context"), 69 | }, 70 | }) 71 | 72 | createLoadeds := map[string]*CreateLoaded{ 73 | "PrimaryKey": { 74 | ParentHandle: TPMRHEndorsement, 75 | InPublic: New2BTemplate(&ECCEKTemplate), 76 | }, 77 | "NoParentPrimaryKey": { 78 | // Make the object in the null hierarchy and ensure that go-tpm supports not providing the parent handle at all. 79 | InPublic: New2BTemplate(&ECCEKTemplate), 80 | }, 81 | "OrdinaryKey": { 82 | ParentHandle: TPMRHOwner, 83 | InSensitive: TPM2BSensitiveCreate{ 84 | Sensitive: &TPMSSensitiveCreate{ 85 | UserAuth: TPM2BAuth{ 86 | Buffer: []byte("p@ssw0rd"), 87 | }, 88 | }, 89 | }, 90 | InPublic: New2BTemplate( 91 | &TPMTPublic{ 92 | Type: TPMAlgECC, 93 | NameAlg: TPMAlgSHA256, 94 | ObjectAttributes: TPMAObject{ 95 | SensitiveDataOrigin: true, 96 | UserWithAuth: true, 97 | SignEncrypt: true, 98 | }, 99 | Parameters: NewTPMUPublicParms( 100 | TPMAlgECC, 101 | &TPMSECCParms{ 102 | CurveID: TPMECCNistP256, 103 | }, 104 | ), 105 | }), 106 | }, 107 | "DataBlob": { 108 | ParentHandle: TPMRHOwner, 109 | InSensitive: TPM2BSensitiveCreate{ 110 | Sensitive: &TPMSSensitiveCreate{ 111 | UserAuth: TPM2BAuth{ 112 | Buffer: []byte("p@ssw0rd"), 113 | }, 114 | Data: NewTPMUSensitiveCreate(&TPM2BSensitiveData{ 115 | Buffer: []byte("secrets"), 116 | }), 117 | }, 118 | }, 119 | InPublic: New2BTemplate( 120 | &TPMTPublic{ 121 | Type: TPMAlgKeyedHash, 122 | NameAlg: TPMAlgSHA256, 123 | ObjectAttributes: TPMAObject{ 124 | UserWithAuth: true, 125 | }, 126 | }), 127 | }, 128 | "Derived": { 129 | ParentHandle: deriver, 130 | InSensitive: TPM2BSensitiveCreate{ 131 | Sensitive: &TPMSSensitiveCreate{ 132 | UserAuth: TPM2BAuth{ 133 | Buffer: []byte("p@ssw0rd"), 134 | }, 135 | Data: NewTPMUSensitiveCreate(&derive), 136 | }, 137 | }, 138 | InPublic: New2BTemplate( 139 | &TPMTPublic{ 140 | Type: TPMAlgECC, 141 | NameAlg: TPMAlgSHA256, 142 | ObjectAttributes: TPMAObject{ 143 | FixedParent: true, 144 | UserWithAuth: true, 145 | SignEncrypt: true, 146 | }, 147 | Parameters: NewTPMUPublicParms( 148 | TPMAlgECC, 149 | &TPMSECCParms{ 150 | CurveID: TPMECCNistP256, 151 | }, 152 | ), 153 | }), 154 | }, 155 | } 156 | 157 | for name, createLoaded := range createLoadeds { 158 | t.Run(name, func(t *testing.T) { 159 | rsp, err := createLoaded.Execute(thetpm) 160 | if err != nil { 161 | t.Fatalf("error from CreateLoaded: %v", err) 162 | } 163 | if _, err = (FlushContext{FlushHandle: rsp.ObjectHandle}).Execute(thetpm); err != nil { 164 | t.Errorf("error from FlushContext: %v", err) 165 | } 166 | }) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /tpm2/test/duplicate_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/google/go-tpm/tpm2" 7 | "github.com/google/go-tpm/tpm2/transport" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | // TestDuplicate creates an object under Owner->SRK and duplicates it to 12 | // Endorsement->SRK. 13 | func TestDuplicate(t *testing.T) { 14 | thetpm, err := simulator.OpenSimulator() 15 | if err != nil { 16 | t.Fatalf("could not connect to TPM simulator: %v", err) 17 | } 18 | defer thetpm.Close() 19 | 20 | t.Log("### Create Owner SRK") 21 | srkCreateResp, err := CreatePrimary{ 22 | PrimaryHandle: TPMRHOwner, 23 | InPublic: New2B(ECCSRKTemplate), 24 | }.Execute(thetpm) 25 | if err != nil { 26 | t.Fatalf("could not generate SRK: %v", err) 27 | } 28 | 29 | srk := NamedHandle{ 30 | Handle: srkCreateResp.ObjectHandle, 31 | Name: srkCreateResp.Name, 32 | } 33 | 34 | policy, err := dupPolicyDigest(thetpm) 35 | if err != nil { 36 | t.Fatalf("dupPolicyDigest: %v", err) 37 | } 38 | 39 | keyPass := []byte("foo") 40 | 41 | t.Log("### Create Object to be duplicated") 42 | objectCreateLoadedResp, err := CreateLoaded{ 43 | ParentHandle: srk, 44 | InSensitive: TPM2BSensitiveCreate{ 45 | Sensitive: &TPMSSensitiveCreate{ 46 | UserAuth: TPM2BAuth{ 47 | Buffer: keyPass, 48 | }, 49 | }, 50 | }, 51 | InPublic: New2BTemplate(&TPMTPublic{ 52 | Type: TPMAlgECC, 53 | NameAlg: TPMAlgSHA256, 54 | ObjectAttributes: TPMAObject{ 55 | FixedTPM: false, 56 | FixedParent: false, 57 | SensitiveDataOrigin: true, 58 | UserWithAuth: true, 59 | Decrypt: true, 60 | SignEncrypt: true, 61 | }, 62 | AuthPolicy: TPM2BDigest{Buffer: policy}, 63 | Parameters: NewTPMUPublicParms( 64 | TPMAlgECC, 65 | &TPMSECCParms{ 66 | CurveID: TPMECCNistP256, 67 | }, 68 | ), 69 | Unique: NewTPMUPublicID( 70 | TPMAlgECC, 71 | &TPMSECCPoint{ 72 | X: TPM2BECCParameter{Buffer: make([]byte, 32)}, 73 | Y: TPM2BECCParameter{Buffer: make([]byte, 32)}, 74 | }, 75 | ), 76 | }), 77 | }.Execute(thetpm) 78 | if err != nil { 79 | t.Fatalf("TPM2_CreateLoaded: %v", err) 80 | } 81 | 82 | // We don't need the owner SRK handle anymore. 83 | FlushContext{FlushHandle: srkCreateResp.ObjectHandle}.Execute(thetpm) 84 | 85 | t.Log("### Create Endorsement SRK (New Parent)") 86 | srk2CreateResp, err := CreatePrimary{ 87 | PrimaryHandle: TPMRHEndorsement, 88 | InPublic: New2B(ECCSRKTemplate), 89 | }.Execute(thetpm) 90 | if err != nil { 91 | t.Fatalf("could not generate SRK: %v", err) 92 | } 93 | defer FlushContext{FlushHandle: srk2CreateResp.ObjectHandle}.Execute(thetpm) 94 | 95 | srk2 := NamedHandle{ 96 | Handle: srk2CreateResp.ObjectHandle, 97 | Name: srk2CreateResp.Name, 98 | } 99 | 100 | t.Log("### Duplicate Object") 101 | duplicateResp, err := Duplicate{ 102 | ObjectHandle: AuthHandle{ 103 | Handle: objectCreateLoadedResp.ObjectHandle, 104 | Name: objectCreateLoadedResp.Name, 105 | Auth: Policy(TPMAlgSHA256, 16, PolicyCallback(func(tpm transport.TPM, handle TPMISHPolicy, _ TPM2BNonce) error { 106 | _, err := PolicyCommandCode{ 107 | PolicySession: handle, 108 | Code: TPMCCDuplicate, 109 | }.Execute(tpm) 110 | return err 111 | })), 112 | }, 113 | NewParentHandle: srk2, 114 | Symmetric: TPMTSymDef{ 115 | Algorithm: TPMAlgNull, 116 | }, 117 | }.Execute(thetpm) 118 | if err != nil { 119 | t.Fatalf("TPM2_Duplicate: %v", err) 120 | } 121 | 122 | // We don't need the original object handle anymore. 123 | FlushContext{FlushHandle: objectCreateLoadedResp.ObjectHandle}.Execute(thetpm) 124 | 125 | t.Log("### Import Object") 126 | importResp, err := Import{ 127 | ParentHandle: AuthHandle{ 128 | Handle: srk2.Handle, 129 | Name: srk2.Name, 130 | Auth: PasswordAuth(nil), 131 | }, 132 | ObjectPublic: objectCreateLoadedResp.OutPublic, 133 | Duplicate: duplicateResp.Duplicate, 134 | InSymSeed: duplicateResp.OutSymSeed, 135 | Symmetric: TPMTSymDef{ 136 | Algorithm: TPMAlgNull, 137 | }, 138 | }.Execute(thetpm) 139 | if err != nil { 140 | t.Fatalf("TPM2_Import: %v", err) 141 | } 142 | 143 | t.Log("### Load Imported Object") 144 | loadResp, err := Load{ 145 | ParentHandle: srk2, 146 | InPrivate: importResp.OutPrivate, 147 | InPublic: objectCreateLoadedResp.OutPublic, 148 | }.Execute(thetpm) 149 | if err != nil { 150 | t.Fatalf("TPM2_Load: %v", err) 151 | } 152 | defer FlushContext{FlushHandle: loadResp.ObjectHandle}.Execute(thetpm) 153 | } 154 | 155 | func dupPolicyDigest(thetpm transport.TPM) ([]byte, error) { 156 | sess, cleanup, err := PolicySession(thetpm, TPMAlgSHA256, 16, Trial()) 157 | if err != nil { 158 | return nil, err 159 | } 160 | defer cleanup() 161 | 162 | _, err = PolicyCommandCode{ 163 | PolicySession: sess.Handle(), 164 | Code: TPMCCDuplicate, 165 | }.Execute(thetpm) 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | pgd, err := PolicyGetDigest{ 171 | PolicySession: sess.Handle(), 172 | }.Execute(thetpm) 173 | if err != nil { 174 | return nil, err 175 | } 176 | _, err = FlushContext{FlushHandle: sess.Handle()}.Execute(thetpm) 177 | if err != nil { 178 | return nil, err 179 | } 180 | return pgd.PolicyDigest.Buffer, nil 181 | } 182 | -------------------------------------------------------------------------------- /tpm2/test/ecdh_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "crypto/ecdh" 5 | "crypto/rand" 6 | "testing" 7 | 8 | "github.com/google/go-cmp/cmp" 9 | "github.com/google/go-cmp/cmp/cmpopts" 10 | . "github.com/google/go-tpm/tpm2" 11 | "github.com/google/go-tpm/tpm2/transport/simulator" 12 | ) 13 | 14 | func TestECDH(t *testing.T) { 15 | thetpm, err := simulator.OpenSimulator() 16 | if err != nil { 17 | t.Fatalf("could not connect to TPM simulator: %v", err) 18 | } 19 | defer thetpm.Close() 20 | 21 | // Create a TPM ECDH key 22 | tpmCreate := CreatePrimary{ 23 | PrimaryHandle: TPMRHOwner, 24 | InPublic: New2B(TPMTPublic{ 25 | Type: TPMAlgECC, 26 | NameAlg: TPMAlgSHA256, 27 | ObjectAttributes: TPMAObject{ 28 | FixedTPM: true, 29 | STClear: false, 30 | FixedParent: true, 31 | SensitiveDataOrigin: true, 32 | UserWithAuth: true, 33 | AdminWithPolicy: false, 34 | NoDA: true, 35 | EncryptedDuplication: false, 36 | Restricted: false, 37 | Decrypt: true, 38 | SignEncrypt: false, 39 | X509Sign: false, 40 | }, 41 | Parameters: NewTPMUPublicParms( 42 | TPMAlgECC, 43 | &TPMSECCParms{ 44 | CurveID: TPMECCNistP256, 45 | Scheme: TPMTECCScheme{ 46 | Scheme: TPMAlgECDH, 47 | Details: NewTPMUAsymScheme( 48 | TPMAlgECDH, 49 | &TPMSKeySchemeECDH{ 50 | HashAlg: TPMAlgSHA256, 51 | }, 52 | ), 53 | }, 54 | }, 55 | ), 56 | }), 57 | } 58 | 59 | // Use NIST P-256 60 | curve := ecdh.P256() 61 | 62 | tpmCreateRsp, err := tpmCreate.Execute(thetpm) 63 | if err != nil { 64 | t.Fatalf("could not create the TPM key: %v", err) 65 | } 66 | outPub, err := tpmCreateRsp.OutPublic.Contents() 67 | if err != nil { 68 | t.Fatalf("%v", err) 69 | } 70 | tpmPub, err := outPub.Unique.ECC() 71 | if err != nil { 72 | t.Fatalf("%v", err) 73 | } 74 | eccParms, err := outPub.Parameters.ECCDetail() 75 | if err != nil { 76 | t.Fatalf("%v", err) 77 | } 78 | tpmPubKey, err := ECDHPub(eccParms, tpmPub) 79 | if err != nil { 80 | t.Fatalf("could not unmarshal pubkey: %v", err) 81 | } 82 | 83 | // Create a SW ECDH key 84 | swPriv, err := curve.GenerateKey(rand.Reader) 85 | if err != nil { 86 | t.Fatalf("could not create the SW key: %v", err) 87 | } 88 | x, y, err := ECCPoint(swPriv.PublicKey()) 89 | if err != nil { 90 | t.Fatalf("could not get SW key point: %v", err) 91 | } 92 | swPub := TPMSECCPoint{ 93 | X: TPM2BECCParameter{Buffer: x.FillBytes(make([]byte, 32))}, 94 | Y: TPM2BECCParameter{Buffer: y.FillBytes(make([]byte, 32))}, 95 | } 96 | 97 | // Calculate Z based on the SW priv * TPM pub 98 | zx, err := swPriv.ECDH(tpmPubKey) 99 | if err != nil { 100 | t.Fatalf("ecdh exchange: %v", err) 101 | } 102 | 103 | z := TPMSECCPoint{ 104 | X: TPM2BECCParameter{Buffer: zx}, 105 | } 106 | 107 | // Calculate Z based on TPM priv * SW pub 108 | ecdh := ECDHZGen{ 109 | KeyHandle: AuthHandle{ 110 | Handle: tpmCreateRsp.ObjectHandle, 111 | Name: tpmCreateRsp.Name, 112 | Auth: PasswordAuth(nil), 113 | }, 114 | InPoint: New2B(swPub), 115 | } 116 | ecdhRsp, err := ecdh.Execute(thetpm) 117 | if err != nil { 118 | t.Fatalf("ECDH_ZGen failed: %v", err) 119 | } 120 | 121 | outPoint, err := ecdhRsp.OutPoint.Contents() 122 | if err != nil { 123 | t.Fatalf("%v", err) 124 | } 125 | if !cmp.Equal(z.X, outPoint.X, cmpopts.IgnoreUnexported(z.X)) { 126 | t.Errorf("want %x got %x", z, outPoint) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tpm2/test/evict_control_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/google/go-tpm/tpm2" 7 | "github.com/google/go-tpm/tpm2/transport/simulator" 8 | ) 9 | 10 | func TestEvictControl(t *testing.T) { 11 | thetpm, err := simulator.OpenSimulator() 12 | if err != nil { 13 | t.Fatalf("could not connect to TPM simulator: %v", err) 14 | } 15 | defer thetpm.Close() 16 | 17 | srkCreate := CreatePrimary{ 18 | PrimaryHandle: TPMRHOwner, 19 | InPublic: New2B(ECCSRKTemplate), 20 | } 21 | 22 | srkCreateRsp, err := srkCreate.Execute(thetpm) 23 | if err != nil { 24 | t.Fatalf("could not generate SRK: %v", err) 25 | } 26 | 27 | _, err = EvictControl{ 28 | Auth: TPMRHOwner, 29 | ObjectHandle: &NamedHandle{ 30 | Handle: srkCreateRsp.ObjectHandle, 31 | Name: srkCreateRsp.Name, 32 | }, 33 | PersistentHandle: 0x81000000, 34 | }.Execute(thetpm) 35 | if err != nil { 36 | t.Fatalf("could not persist: %v", err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tpm2/test/get_random_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/google/go-tpm/tpm2" 7 | "github.com/google/go-tpm/tpm2/transport/simulator" 8 | ) 9 | 10 | func TestGetRandom(t *testing.T) { 11 | thetpm, err := simulator.OpenSimulator() 12 | if err != nil { 13 | t.Fatalf("could not connect to TPM simulator: %v", err) 14 | } 15 | defer thetpm.Close() 16 | 17 | grc := GetRandom{ 18 | BytesRequested: 16, 19 | } 20 | 21 | if _, err := grc.Execute(thetpm); err != nil { 22 | t.Fatalf("GetRandom failed: %v", err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tpm2/test/hierarchy_change_auth_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func TestHierarchyChangeAuth(t *testing.T) { 12 | thetpm, err := simulator.OpenSimulator() 13 | if err != nil { 14 | t.Fatalf("could not connect to TPM simulator: %v", err) 15 | } 16 | defer thetpm.Close() 17 | 18 | authKey := []byte("authkey") 19 | newAuthKey := []byte("newAuthKey") 20 | 21 | t.Run("HierarchyChangeAuthOwner", func(t *testing.T) { 22 | hca := HierarchyChangeAuth{ 23 | AuthHandle: TPMRHOwner, 24 | NewAuth: TPM2BAuth{ 25 | Buffer: authKey, 26 | }, 27 | } 28 | 29 | _, err := hca.Execute(thetpm) 30 | if err != nil { 31 | t.Errorf("failed HierarchyChangeAuth: %v", err) 32 | } 33 | }) 34 | 35 | t.Run("HierarchyChangeAuthOwnerUnauth", func(t *testing.T) { 36 | hca := HierarchyChangeAuth{ 37 | AuthHandle: TPMRHOwner, 38 | NewAuth: TPM2BAuth{ 39 | Buffer: newAuthKey, 40 | }, 41 | } 42 | 43 | _, err := hca.Execute(thetpm) 44 | if !errors.Is(err, TPMRCBadAuth) { 45 | t.Errorf("failed HierarchyChangeAuthWithoutAuth: want TPM_RC_BAD_AUTH, got %v", err) 46 | } 47 | }) 48 | 49 | t.Run("HierarchyChangeAuthOwnerAuth", func(t *testing.T) { 50 | hca := HierarchyChangeAuth{ 51 | AuthHandle: AuthHandle{ 52 | Handle: TPMRHOwner, 53 | Auth: PasswordAuth(authKey), 54 | }, 55 | NewAuth: TPM2BAuth{ 56 | Buffer: newAuthKey, 57 | }, 58 | } 59 | 60 | _, err := hca.Execute(thetpm) 61 | if err != nil { 62 | t.Errorf("failed HierarchyChangeAuthWithAuth: %v", err) 63 | } 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /tpm2/test/load_external_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func decodeHex(t *testing.T, h string) []byte { 12 | t.Helper() 13 | data, err := hex.DecodeString(h) 14 | if err != nil { 15 | t.Fatalf("could not decode '%v' as hex data: %v", h, err) 16 | } 17 | return data 18 | } 19 | 20 | func TestLoadExternal(t *testing.T) { 21 | loads := map[string]*LoadExternal{ 22 | "ECCNoSensitive": { 23 | InPublic: New2B(TPMTPublic{ 24 | Type: TPMAlgECC, 25 | NameAlg: TPMAlgSHA256, 26 | ObjectAttributes: TPMAObject{ 27 | SignEncrypt: true, 28 | }, 29 | Parameters: NewTPMUPublicParms( 30 | TPMAlgECC, 31 | &TPMSECCParms{ 32 | CurveID: TPMECCNistP256, 33 | }, 34 | ), 35 | Unique: NewTPMUPublicID( 36 | // This happens to be a P256 EKpub from the simulator 37 | TPMAlgECC, 38 | &TPMSECCPoint{ 39 | X: TPM2BECCParameter{Buffer: decodeHex(t, "9855efa3514873b88067ab127b2d4692864a395db3d9e4ccad0592478a245c16")}, 40 | Y: TPM2BECCParameter{Buffer: decodeHex(t, "e802a26649839a2d7b13c812a5dc0b61c110cbe62db784d96e60a823448c8993")}, 41 | }, 42 | ), 43 | }), 44 | }, 45 | "KeyedHashSensitive": { 46 | InPrivate: New2B( 47 | TPMTSensitive{ 48 | SensitiveType: TPMAlgKeyedHash, 49 | SeedValue: TPM2BDigest{ 50 | Buffer: []byte("obfuscation is my middle name!!!"), 51 | }, 52 | Sensitive: NewTPMUSensitiveComposite( 53 | TPMAlgKeyedHash, 54 | &TPM2BSensitiveData{ 55 | Buffer: []byte("secrets"), 56 | }, 57 | ), 58 | }), 59 | InPublic: New2B( 60 | TPMTPublic{ 61 | Type: TPMAlgKeyedHash, 62 | NameAlg: TPMAlgSHA256, 63 | Unique: NewTPMUPublicID( 64 | TPMAlgKeyedHash, 65 | &TPM2BDigest{ 66 | // SHA256("obfuscation is my middle name!!!secrets") 67 | Buffer: decodeHex(t, "ed4fe8e2bff97665e7bfbe27c2365d07a9be91dd92d997cd91cc706b6074eb08"), 68 | }, 69 | ), 70 | }), 71 | }, 72 | } 73 | 74 | thetpm, err := simulator.OpenSimulator() 75 | if err != nil { 76 | t.Fatalf("could not connect to TPM simulator: %v", err) 77 | } 78 | defer thetpm.Close() 79 | 80 | for name, load := range loads { 81 | t.Run(name, func(t *testing.T) { 82 | rsp, err := load.Execute(thetpm) 83 | if err != nil { 84 | t.Fatalf("error from LoadExternal: %v", err) 85 | } 86 | if _, err = (FlushContext{FlushHandle: rsp.ObjectHandle}).Execute(thetpm); err != nil { 87 | t.Errorf("error from FlushContext: %v", err) 88 | } 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tpm2/test/names_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func TestHandleName(t *testing.T) { 12 | want := []byte{0x40, 0x00, 0x00, 0x0B} 13 | name := HandleName(TPMRHEndorsement) 14 | if !bytes.Equal(want, name.Buffer) { 15 | t.Errorf("Incorrect name for RH_ENDORSEMENT (want %x got %x)", want, name.Buffer) 16 | } 17 | } 18 | 19 | func TestObjectName(t *testing.T) { 20 | thetpm, err := simulator.OpenSimulator() 21 | if err != nil { 22 | t.Fatalf("could not connect to TPM simulator: %v", err) 23 | } 24 | defer thetpm.Close() 25 | 26 | createPrimary := CreatePrimary{ 27 | PrimaryHandle: TPMRHEndorsement, 28 | InPublic: New2B(ECCEKTemplate), 29 | } 30 | rsp, err := createPrimary.Execute(thetpm) 31 | if err != nil { 32 | t.Fatalf("could not call TPM2_CreatePrimary: %v", err) 33 | } 34 | flush := FlushContext{FlushHandle: rsp.ObjectHandle} 35 | defer flush.Execute(thetpm) 36 | public := rsp.OutPublic 37 | 38 | want := rsp.Name 39 | pub, err := public.Contents() 40 | if err != nil { 41 | t.Fatalf("%v", err) 42 | } 43 | name, err := ObjectName(pub) 44 | if err != nil { 45 | t.Fatalf("error from ObjectName: %v", err) 46 | } 47 | if !bytes.Equal(want.Buffer, name.Buffer) { 48 | t.Errorf("Incorrect name for ECC EK (want %x got %x)", want.Buffer, name.Buffer) 49 | } 50 | } 51 | 52 | func TestNVName(t *testing.T) { 53 | thetpm, err := simulator.OpenSimulator() 54 | if err != nil { 55 | t.Fatalf("could not connect to TPM simulator: %v", err) 56 | } 57 | defer thetpm.Close() 58 | 59 | public := New2B( 60 | TPMSNVPublic{ 61 | NVIndex: TPMHandle(0x0180000F), 62 | NameAlg: TPMAlgSHA256, 63 | Attributes: TPMANV{ 64 | OwnerWrite: true, 65 | OwnerRead: true, 66 | NT: TPMNTOrdinary, 67 | }, 68 | DataSize: 4, 69 | }) 70 | 71 | defineSpace := NVDefineSpace{ 72 | AuthHandle: TPMRHOwner, 73 | PublicInfo: public, 74 | } 75 | if _, err := defineSpace.Execute(thetpm); err != nil { 76 | t.Fatalf("could not call TPM2_DefineSpace: %v", err) 77 | } 78 | 79 | pub, err := public.Contents() 80 | if err != nil { 81 | t.Fatalf("%v", err) 82 | } 83 | readPublic := NVReadPublic{ 84 | NVIndex: pub.NVIndex, 85 | } 86 | rsp, err := readPublic.Execute(thetpm) 87 | if err != nil { 88 | t.Fatalf("could not call TPM2_ReadPublic: %v", err) 89 | } 90 | 91 | want := rsp.NVName 92 | name, err := NVName(pub) 93 | if err != nil { 94 | t.Fatalf("error from NVIndexName: %v", err) 95 | } 96 | if !bytes.Equal(want.Buffer, name.Buffer) { 97 | t.Errorf("Incorrect name for NV index (want %x got %x)", want.Buffer, name.Buffer) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tpm2/test/nv_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "testing" 7 | 8 | . "github.com/google/go-tpm/tpm2" 9 | "github.com/google/go-tpm/tpm2/transport/simulator" 10 | ) 11 | 12 | func TestNVAuthWrite(t *testing.T) { 13 | thetpm, err := simulator.OpenSimulator() 14 | if err != nil { 15 | t.Fatalf("could not connect to TPM simulator: %v", err) 16 | } 17 | defer thetpm.Close() 18 | 19 | def := NVDefineSpace{ 20 | AuthHandle: TPMRHOwner, 21 | Auth: TPM2BAuth{ 22 | Buffer: []byte("p@ssw0rd"), 23 | }, 24 | PublicInfo: New2B( 25 | TPMSNVPublic{ 26 | NVIndex: TPMHandle(0x0180000F), 27 | NameAlg: TPMAlgSHA256, 28 | Attributes: TPMANV{ 29 | OwnerWrite: true, 30 | OwnerRead: true, 31 | AuthWrite: true, 32 | AuthRead: true, 33 | NT: TPMNTOrdinary, 34 | NoDA: true, 35 | }, 36 | DataSize: 4, 37 | }), 38 | } 39 | if _, err := def.Execute(thetpm); err != nil { 40 | t.Fatalf("Calling TPM2_NV_DefineSpace: %v", err) 41 | } 42 | 43 | pub, err := def.PublicInfo.Contents() 44 | if err != nil { 45 | t.Fatalf("%v", err) 46 | } 47 | nvName, err := NVName(pub) 48 | if err != nil { 49 | t.Fatalf("Calculating name of NV index: %v", err) 50 | } 51 | 52 | prewrite := NVWrite{ 53 | AuthHandle: AuthHandle{ 54 | Handle: pub.NVIndex, 55 | Name: *nvName, 56 | Auth: PasswordAuth([]byte("p@ssw0rd")), 57 | }, 58 | NVIndex: NamedHandle{ 59 | Handle: pub.NVIndex, 60 | Name: *nvName, 61 | }, 62 | Data: TPM2BMaxNVBuffer{ 63 | Buffer: []byte{0x01, 0x02, 0x03, 0x04}, 64 | }, 65 | Offset: 0, 66 | } 67 | if _, err := prewrite.Execute(thetpm); err != nil { 68 | t.Errorf("Calling TPM2_NV_Write: %v", err) 69 | } 70 | 71 | read := NVReadPublic{ 72 | NVIndex: pub.NVIndex, 73 | } 74 | readRsp, err := read.Execute(thetpm) 75 | if err != nil { 76 | t.Fatalf("Calling TPM2_NV_ReadPublic: %v", err) 77 | } 78 | t.Logf("Name: %x", readRsp.NVName.Buffer) 79 | 80 | write := NVWrite{ 81 | AuthHandle: AuthHandle{ 82 | Handle: TPMRHOwner, 83 | Auth: HMAC(TPMAlgSHA256, 16, Auth([]byte{})), 84 | }, 85 | NVIndex: NamedHandle{ 86 | Handle: pub.NVIndex, 87 | Name: readRsp.NVName, 88 | }, 89 | Data: TPM2BMaxNVBuffer{ 90 | Buffer: []byte{0x01, 0x02, 0x03, 0x04}, 91 | }, 92 | Offset: 0, 93 | } 94 | if _, err := write.Execute(thetpm); err != nil { 95 | t.Errorf("Calling TPM2_NV_Write: %v", err) 96 | } 97 | } 98 | 99 | func TestNVAuthIncrement(t *testing.T) { 100 | thetpm, err := simulator.OpenSimulator() 101 | if err != nil { 102 | t.Fatalf("could not connect to TPM simulator: %v", err) 103 | } 104 | defer thetpm.Close() 105 | 106 | // Define the counter space 107 | def := NVDefineSpace{ 108 | AuthHandle: TPMRHOwner, 109 | Auth: TPM2BAuth{ 110 | Buffer: []byte("p@ssw0rd"), 111 | }, 112 | PublicInfo: New2B( 113 | TPMSNVPublic{ 114 | NVIndex: TPMHandle(0x0180000F), 115 | NameAlg: TPMAlgSHA256, 116 | Attributes: TPMANV{ 117 | OwnerWrite: true, 118 | OwnerRead: true, 119 | AuthWrite: true, 120 | AuthRead: true, 121 | NT: TPMNTCounter, 122 | NoDA: true, 123 | }, 124 | DataSize: 8, 125 | }), 126 | } 127 | if _, err := def.Execute(thetpm); err != nil { 128 | t.Fatalf("Calling TPM2_NV_DefineSpace: %v", err) 129 | } 130 | 131 | pub, err := def.PublicInfo.Contents() 132 | if err != nil { 133 | t.Fatalf("%v", err) 134 | } 135 | // Calculate the Name of the index as of its creation 136 | // (i.e., without NV_WRITTEN set). 137 | nvName, err := NVName(pub) 138 | if err != nil { 139 | t.Fatalf("Calculating name of NV index: %v", err) 140 | } 141 | 142 | incr := NVIncrement{ 143 | AuthHandle: AuthHandle{ 144 | Handle: TPMRHOwner, 145 | Auth: HMAC(TPMAlgSHA256, 16, Auth([]byte{})), 146 | }, 147 | NVIndex: NamedHandle{ 148 | Handle: pub.NVIndex, 149 | Name: *nvName, 150 | }, 151 | } 152 | if _, err := incr.Execute(thetpm); err != nil { 153 | t.Errorf("Calling TPM2_NV_Increment: %v", err) 154 | } 155 | 156 | // The NV index's Name has changed. Ask the TPM for it. 157 | readPub := NVReadPublic{ 158 | NVIndex: pub.NVIndex, 159 | } 160 | readPubRsp, err := readPub.Execute(thetpm) 161 | if err != nil { 162 | t.Fatalf("Calling TPM2_NV_ReadPublic: %v", err) 163 | } 164 | incr.NVIndex = NamedHandle{ 165 | Handle: pub.NVIndex, 166 | Name: readPubRsp.NVName, 167 | } 168 | 169 | read := NVRead{ 170 | AuthHandle: AuthHandle{ 171 | Handle: TPMRHOwner, 172 | Auth: HMAC(TPMAlgSHA256, 16, Auth([]byte{})), 173 | }, 174 | NVIndex: NamedHandle{ 175 | Handle: pub.NVIndex, 176 | Name: readPubRsp.NVName, 177 | }, 178 | Size: 8, 179 | } 180 | readRsp, err := read.Execute(thetpm) 181 | if err != nil { 182 | t.Fatalf("Calling TPM2_NV_Read: %v", err) 183 | } 184 | 185 | if _, err := incr.Execute(thetpm); err != nil { 186 | t.Errorf("Calling TPM2_NV_Increment: %v", err) 187 | } 188 | 189 | var val1 uint64 190 | err = binary.Read(bytes.NewReader(readRsp.Data.Buffer), binary.BigEndian, &val1) 191 | if err != nil { 192 | t.Fatalf("Parsing counter: %v", err) 193 | } 194 | 195 | readRsp, err = read.Execute(thetpm) 196 | if err != nil { 197 | t.Fatalf("Calling TPM2_NV_Read: %v", err) 198 | } 199 | 200 | var val2 uint64 201 | err = binary.Read(bytes.NewReader(readRsp.Data.Buffer), binary.BigEndian, &val2) 202 | if err != nil { 203 | t.Fatalf("Parsing counter: %v", err) 204 | } 205 | 206 | if val2 != (val1 + 1) { 207 | t.Errorf("want %v got %v", val1+1, val2) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /tpm2/test/read_public_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "github.com/google/go-cmp/cmp/cmpopts" 8 | . "github.com/google/go-tpm/tpm2" 9 | "github.com/google/go-tpm/tpm2/transport/simulator" 10 | ) 11 | 12 | // TestReadPublicKey compares the CreatePrimary response parameter outPublic with the output of ReadPublic outPublic. 13 | func TestReadPublicKey(t *testing.T) { 14 | // Open simulated TPM for testing. 15 | thetpm, err := simulator.OpenSimulator() 16 | if err != nil { 17 | t.Fatalf("could not connect to TPM simulator: %v", err) 18 | } 19 | 20 | // Defer the close of the simulated TPM to after use. 21 | // Without this, other programs/tests may not be able to get a handle to the TPM. 22 | defer thetpm.Close() 23 | 24 | // Fill in the CreatePrimary struct. 25 | // See definition in Part 3, Commands, section 24.1. 26 | // See tpm2/templates/go for more TPMTPublic examples. 27 | createPrimary := CreatePrimary{ 28 | PrimaryHandle: TPMRHOwner, 29 | InPublic: New2B(TPMTPublic{ 30 | Type: TPMAlgECC, 31 | NameAlg: TPMAlgSHA256, 32 | ObjectAttributes: TPMAObject{ 33 | FixedTPM: true, 34 | FixedParent: true, 35 | SensitiveDataOrigin: true, 36 | UserWithAuth: true, 37 | SignEncrypt: true, 38 | }, 39 | Parameters: NewTPMUPublicParms( 40 | TPMAlgECC, 41 | &TPMSECCParms{ 42 | Scheme: TPMTECCScheme{ 43 | Scheme: TPMAlgECDSA, 44 | Details: NewTPMUAsymScheme( 45 | TPMAlgECDSA, 46 | &TPMSSigSchemeECDSA{ 47 | HashAlg: TPMAlgSHA256, 48 | }, 49 | ), 50 | }, 51 | CurveID: TPMECCNistP256, 52 | }, 53 | ), 54 | }), 55 | } 56 | 57 | // Executing the command uses reflection to pack the bytes into a 58 | // TPM2_CreatePrimary command, returns a TPM2_CreatePrimary Response. 59 | // This response is also decoded so you are again working with structs 60 | // that can be found in Part 3, Commands, section 24.1. 61 | rspCP, err := createPrimary.Execute(thetpm) 62 | if err != nil { 63 | t.Fatalf("CreatePrimary failed: %v", err) 64 | } 65 | 66 | // The TPM can only hold so much in nonvolatile memory, thus we must 67 | // flush the handle after we are done using it to prevent overloading. 68 | // Again we defer the flush to after we are done using the object. 69 | // It is generally good practice to defer the cleanup immediately 70 | // after loading an object or creating an Authorization Session. 71 | // See Part 1, Architecture, section 30.4 72 | flushContext := FlushContext{FlushHandle: rspCP.ObjectHandle} 73 | defer flushContext.Execute(thetpm) 74 | 75 | // Fill in the ReadPublic struct. 76 | // See definition in Part 3, Commands, section 12.4. 77 | readPublic := ReadPublic{ 78 | ObjectHandle: rspCP.ObjectHandle, 79 | } 80 | 81 | // Executing the command uses reflection to pack the bytes into a 82 | // TPM2_ReadPublic command, returns a TPM2_ReadPublic Response. 83 | // This response is also decoded so you are again working with structs 84 | // that can be found in Part 3, Commands, section 12.4. 85 | rspRP, err := readPublic.Execute(thetpm) 86 | if err != nil { 87 | t.Fatalf("ReadPublic failed: %v", err) 88 | } 89 | 90 | // PublicArea.Unique represents the unique identifier of the TPMTPublic. 91 | // Notice how this test uses verification of another TPM command that is 92 | // able to produce similar results to validate the response. 93 | pubCreate, err := rspCP.OutPublic.Contents() 94 | if err != nil { 95 | t.Fatalf("%v", err) 96 | } 97 | pubRead, err := rspRP.OutPublic.Contents() 98 | if err != nil { 99 | t.Fatalf("%v", err) 100 | } 101 | eccCreate, err := pubCreate.Unique.ECC() 102 | if err != nil { 103 | t.Fatalf("%v", err) 104 | } 105 | eccRead, err := pubRead.Unique.ECC() 106 | if err != nil { 107 | t.Fatalf("%v", err) 108 | } 109 | if !cmp.Equal(eccCreate.X, eccRead.X, cmpopts.IgnoreUnexported(eccCreate.X)) { 110 | t.Error("Mismatch between public returned from CreatePrimary & ReadPublic") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tpm2/test/rsa_encryption_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func TestRSAEncryption(t *testing.T) { 12 | theTpm, err := simulator.OpenSimulator() 13 | if err != nil { 14 | t.Fatalf("could not connect to TPM simulator: %v", err) 15 | } 16 | t.Cleanup(func() { 17 | if err := theTpm.Close(); err != nil { 18 | t.Errorf("%v", err) 19 | } 20 | }) 21 | 22 | createPrimaryCmd := CreatePrimary{ 23 | PrimaryHandle: TPMRHOwner, 24 | InPublic: New2B(RSASRKTemplate), 25 | } 26 | createPrimaryRsp, err := createPrimaryCmd.Execute(theTpm) 27 | if err != nil { 28 | t.Fatalf("%v", err) 29 | } 30 | t.Cleanup(func() { 31 | flushContextCmd := FlushContext{FlushHandle: createPrimaryRsp.ObjectHandle} 32 | if _, err := flushContextCmd.Execute(theTpm); err != nil { 33 | t.Errorf("%v", err) 34 | } 35 | }) 36 | 37 | createCmd := Create{ 38 | ParentHandle: NamedHandle{ 39 | Handle: createPrimaryRsp.ObjectHandle, 40 | Name: createPrimaryRsp.Name, 41 | }, 42 | InPublic: New2B(TPMTPublic{ 43 | Type: TPMAlgRSA, 44 | NameAlg: TPMAlgSHA256, 45 | ObjectAttributes: TPMAObject{ 46 | FixedTPM: true, 47 | STClear: false, 48 | FixedParent: true, 49 | SensitiveDataOrigin: true, 50 | UserWithAuth: true, 51 | AdminWithPolicy: false, 52 | NoDA: true, 53 | EncryptedDuplication: false, 54 | Restricted: false, 55 | Decrypt: true, 56 | SignEncrypt: true, 57 | }, 58 | Parameters: NewTPMUPublicParms( 59 | TPMAlgRSA, 60 | &TPMSRSAParms{ 61 | KeyBits: 2048, 62 | }, 63 | ), 64 | Unique: NewTPMUPublicID( 65 | TPMAlgRSA, 66 | &TPM2BPublicKeyRSA{ 67 | Buffer: make([]byte, 256), 68 | }, 69 | ), 70 | }), 71 | } 72 | createRsp, err := createCmd.Execute(theTpm) 73 | if err != nil { 74 | t.Fatalf("%v", err) 75 | } 76 | 77 | loadCmd := Load{ 78 | ParentHandle: NamedHandle{ 79 | Handle: createPrimaryRsp.ObjectHandle, 80 | Name: createPrimaryRsp.Name, 81 | }, 82 | InPrivate: createRsp.OutPrivate, 83 | InPublic: createRsp.OutPublic, 84 | } 85 | loadRsp, err := loadCmd.Execute(theTpm) 86 | if err != nil { 87 | t.Fatalf("%v", err) 88 | } 89 | t.Cleanup(func() { 90 | flushContextCmd := FlushContext{FlushHandle: loadRsp.ObjectHandle} 91 | if _, err := flushContextCmd.Execute(theTpm); err != nil { 92 | t.Errorf("%v", err) 93 | } 94 | }) 95 | 96 | message := []byte("secret") 97 | 98 | encryptCmd := RSAEncrypt{ 99 | KeyHandle: loadRsp.ObjectHandle, 100 | Message: TPM2BPublicKeyRSA{Buffer: message}, 101 | InScheme: TPMTRSADecrypt{ 102 | Scheme: TPMAlgOAEP, 103 | Details: NewTPMUAsymScheme( 104 | TPMAlgOAEP, 105 | &TPMSEncSchemeOAEP{ 106 | HashAlg: TPMAlgSHA256, 107 | }, 108 | ), 109 | }, 110 | } 111 | encryptRsp, err := encryptCmd.Execute(theTpm) 112 | if err != nil { 113 | t.Fatalf("%v", err) 114 | } 115 | 116 | decryptCmd := RSADecrypt{ 117 | KeyHandle: NamedHandle{ 118 | Handle: loadRsp.ObjectHandle, 119 | Name: loadRsp.Name, 120 | }, 121 | CipherText: TPM2BPublicKeyRSA{Buffer: encryptRsp.OutData.Buffer}, 122 | InScheme: TPMTRSADecrypt{ 123 | Scheme: TPMAlgOAEP, 124 | Details: NewTPMUAsymScheme( 125 | TPMAlgOAEP, 126 | &TPMSEncSchemeOAEP{ 127 | HashAlg: TPMAlgSHA256, 128 | }, 129 | ), 130 | }, 131 | } 132 | decryptRsp, err := decryptCmd.Execute(theTpm) 133 | if err != nil { 134 | t.Fatalf("%v", err) 135 | } 136 | 137 | if !bytes.Equal(message, decryptRsp.Message.Buffer) { 138 | t.Errorf("want %x got %x", message, decryptRsp.Message.Buffer) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tpm2/test/sign_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "crypto/sha256" 7 | "testing" 8 | 9 | . "github.com/google/go-tpm/tpm2" 10 | "github.com/google/go-tpm/tpm2/transport/simulator" 11 | ) 12 | 13 | func TestSign(t *testing.T) { 14 | 15 | thetpm, err := simulator.OpenSimulator() 16 | if err != nil { 17 | t.Fatalf("could not connect to TPM simulator: %v", err) 18 | } 19 | defer thetpm.Close() 20 | 21 | createPrimary := CreatePrimary{ 22 | PrimaryHandle: TPMRHOwner, 23 | 24 | InPublic: New2B(TPMTPublic{ 25 | Type: TPMAlgRSA, 26 | NameAlg: TPMAlgSHA256, 27 | ObjectAttributes: TPMAObject{ 28 | SignEncrypt: true, 29 | FixedTPM: true, 30 | FixedParent: true, 31 | SensitiveDataOrigin: true, 32 | UserWithAuth: true, 33 | }, 34 | Parameters: NewTPMUPublicParms( 35 | TPMAlgRSA, 36 | &TPMSRSAParms{ 37 | Scheme: TPMTRSAScheme{ 38 | Scheme: TPMAlgRSASSA, 39 | Details: NewTPMUAsymScheme( 40 | TPMAlgRSASSA, 41 | &TPMSSigSchemeRSASSA{ 42 | HashAlg: TPMAlgSHA256, 43 | }, 44 | ), 45 | }, 46 | KeyBits: 2048, 47 | }, 48 | ), 49 | }), 50 | CreationPCR: TPMLPCRSelection{ 51 | PCRSelections: []TPMSPCRSelection{ 52 | { 53 | Hash: TPMAlgSHA1, 54 | PCRSelect: PCClientCompatible.PCRs(7), 55 | }, 56 | }, 57 | }, 58 | } 59 | 60 | rspCP, err := createPrimary.Execute(thetpm) 61 | if err != nil { 62 | t.Fatalf("could not create key: %v", err) 63 | } 64 | 65 | flushContext := FlushContext{FlushHandle: rspCP.ObjectHandle} 66 | defer flushContext.Execute(thetpm) 67 | 68 | digest := sha256.Sum256([]byte("migrationpains")) 69 | 70 | sign := Sign{ 71 | KeyHandle: NamedHandle{ 72 | Handle: rspCP.ObjectHandle, 73 | Name: rspCP.Name, 74 | }, 75 | Digest: TPM2BDigest{ 76 | Buffer: digest[:], 77 | }, 78 | InScheme: TPMTSigScheme{ 79 | Scheme: TPMAlgRSASSA, 80 | Details: NewTPMUSigScheme( 81 | TPMAlgRSASSA, 82 | &TPMSSchemeHash{ 83 | HashAlg: TPMAlgSHA256, 84 | }, 85 | ), 86 | }, 87 | Validation: TPMTTKHashCheck{ 88 | Tag: TPMSTHashCheck, 89 | }, 90 | } 91 | 92 | rspSign, err := sign.Execute(thetpm) 93 | if err != nil { 94 | t.Fatalf("Failed to Sign Digest: %v", err) 95 | } 96 | 97 | pub, err := rspCP.OutPublic.Contents() 98 | if err != nil { 99 | t.Fatalf("%v", err) 100 | } 101 | rsaDetail, err := pub.Parameters.RSADetail() 102 | if err != nil { 103 | t.Fatalf("%v", err) 104 | } 105 | rsaUnique, err := pub.Unique.RSA() 106 | if err != nil { 107 | t.Fatalf("%v", err) 108 | } 109 | 110 | rsaPub, err := RSAPub(rsaDetail, rsaUnique) 111 | if err != nil { 112 | t.Fatalf("Failed to retrieve Public Key: %v", err) 113 | } 114 | 115 | rsassa, err := rspSign.Signature.Signature.RSASSA() 116 | if err != nil { 117 | t.Fatalf("%v", err) 118 | } 119 | if err := rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], rsassa.Sig.Buffer); err != nil { 120 | t.Errorf("Signature verification failed: %v", err) 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /tpm2/test/test_parms_test.go: -------------------------------------------------------------------------------- 1 | package tpm2test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | . "github.com/google/go-tpm/tpm2" 8 | "github.com/google/go-tpm/tpm2/transport/simulator" 9 | ) 10 | 11 | func TestTestParms(t *testing.T) { 12 | thetpm, err := simulator.OpenSimulator() 13 | if err != nil { 14 | t.Fatalf("could not connect to TPM simulator: %v", err) 15 | } 16 | defer thetpm.Close() 17 | 18 | for _, tt := range []struct { 19 | name string 20 | parms TPMTPublicParms 21 | wantErr error 22 | }{ 23 | { 24 | "p256", 25 | TPMTPublicParms{ 26 | Type: TPMAlgECC, 27 | Parameters: NewTPMUPublicParms( 28 | TPMAlgECC, 29 | &TPMSECCParms{ 30 | CurveID: TPMECCNistP256, 31 | }, 32 | ), 33 | }, 34 | nil, 35 | }, 36 | { 37 | "p364", 38 | TPMTPublicParms{ 39 | Type: TPMAlgECC, 40 | Parameters: NewTPMUPublicParms( 41 | TPMAlgECC, 42 | &TPMSECCParms{ 43 | CurveID: TPMECCNistP384, 44 | }, 45 | ), 46 | }, 47 | nil, 48 | }, 49 | { 50 | "p521", 51 | TPMTPublicParms{ 52 | Type: TPMAlgECC, 53 | Parameters: NewTPMUPublicParms( 54 | TPMAlgECC, 55 | &TPMSECCParms{ 56 | CurveID: TPMECCNistP521, 57 | }, 58 | ), 59 | }, 60 | nil, 61 | }, 62 | { 63 | "rsa2048", 64 | TPMTPublicParms{ 65 | Type: TPMAlgRSA, 66 | Parameters: NewTPMUPublicParms( 67 | TPMAlgRSA, 68 | &TPMSRSAParms{ 69 | KeyBits: 2048, 70 | }, 71 | ), 72 | }, 73 | nil, 74 | }, 75 | { 76 | "rsa3072 - unsupported", 77 | TPMTPublicParms{ 78 | Type: TPMAlgRSA, 79 | Parameters: NewTPMUPublicParms( 80 | TPMAlgRSA, 81 | &TPMSRSAParms{ 82 | KeyBits: 3072, 83 | }, 84 | ), 85 | }, 86 | TPMRCValue, 87 | }, 88 | { 89 | "rsa4096 - unsupported", 90 | TPMTPublicParms{ 91 | Type: TPMAlgRSA, 92 | Parameters: NewTPMUPublicParms( 93 | TPMAlgRSA, 94 | &TPMSRSAParms{ 95 | KeyBits: 4096, 96 | }, 97 | ), 98 | }, 99 | TPMRCValue, 100 | }, 101 | } { 102 | t.Run(tt.name, func(t *testing.T) { 103 | grc := TestParms{Parameters: tt.parms} 104 | _, err := grc.Execute(thetpm) 105 | if !errors.Is(err, tt.wantErr) { 106 | t.Fatalf("TestParms failed failed. Expecting err %v got %v", tt.wantErr, err) 107 | } 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tpm2/test/testvectors/testvectors.go: -------------------------------------------------------------------------------- 1 | // Package testvectors contains test vectors for TPM crypto. 2 | package testvectors 3 | 4 | import ( 5 | _ "embed" 6 | "encoding/hex" 7 | "encoding/json" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | // This package contains test vectors from https://github.com/chrisfenner/tpm-test-vectors. 13 | // These test vectors were validated against the TCG Reference Implementation. 14 | 15 | type hexBytes []byte 16 | 17 | func (h *hexBytes) UnmarshalJSON(data []byte) error { 18 | var err error 19 | *h, err = hex.DecodeString(strings.Trim(string(data), "\"")) 20 | return err 21 | } 22 | 23 | //go:embed ecc_labeled_encaps.json 24 | var eccLabeledEncapsJSON []byte 25 | 26 | // ECCLabeledEncapsTestCase is a test case for ECC Labeled Encapsulation (Secret Sharing). 27 | type ECCLabeledEncapsTestCase struct { 28 | Name string 29 | Description string 30 | Label string 31 | EphemeralPrivate hexBytes 32 | PublicKey hexBytes 33 | Secret hexBytes 34 | Ciphertext hexBytes 35 | } 36 | 37 | // ECCLabeledEncapsulation iterates the ECC Labeled Encapsulation test cases. 38 | func ECCLabeledEncapsulation(t *testing.T) []ECCLabeledEncapsTestCase { 39 | t.Helper() 40 | 41 | var testCases []ECCLabeledEncapsTestCase 42 | if err := json.Unmarshal(eccLabeledEncapsJSON, &testCases); err != nil { 43 | t.Fatalf("could not unmarshal JSON: %v", err) 44 | } 45 | 46 | return testCases 47 | } 48 | 49 | //go:embed rsa_labeled_encaps.json 50 | var rsaLabeledEncapsJSON []byte 51 | 52 | // RSALabeledEncapsTestCase is a test case for RSA Labeled Encapsulation (Secret Sharing). 53 | type RSALabeledEncapsTestCase struct { 54 | Name string 55 | Description string 56 | Label string 57 | OAEPSalt hexBytes 58 | PublicKey hexBytes 59 | Secret hexBytes 60 | Ciphertext hexBytes 61 | } 62 | 63 | // RSALabeledEncapsulation iterates the RSA Labeled Encapsulation test cases. 64 | func RSALabeledEncapsulation(t *testing.T) []RSALabeledEncapsTestCase { 65 | t.Helper() 66 | 67 | var testCases []RSALabeledEncapsTestCase 68 | if err := json.Unmarshal(rsaLabeledEncapsJSON, &testCases); err != nil { 69 | t.Fatalf("could not unmarshal JSON: %v", err) 70 | } 71 | 72 | return testCases 73 | } 74 | -------------------------------------------------------------------------------- /tpm2/tpm2b.go: -------------------------------------------------------------------------------- 1 | package tpm2 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | // TPM2B is a helper type for all sized TPM structures. It can be instantiated with either a raw byte buffer or the actual struct. 11 | type TPM2B[T Marshallable, P interface { 12 | *T 13 | Unmarshallable 14 | }] struct { 15 | contents *T 16 | buffer []byte 17 | } 18 | 19 | // New2B creates a new TPM2B containing the given contents. 20 | func New2B[T Marshallable, P interface { 21 | *T 22 | Unmarshallable 23 | }](t T) TPM2B[T, P] { 24 | return TPM2B[T, P]{contents: &t} 25 | } 26 | 27 | // BytesAs2B creates a new TPM2B containing the given byte array. 28 | func BytesAs2B[T Marshallable, P interface { 29 | *T 30 | Unmarshallable 31 | }](b []byte) TPM2B[T, P] { 32 | return TPM2B[T, P]{buffer: b} 33 | } 34 | 35 | // Contents returns the structured contents of the TPM2B. 36 | // It can fail if the TPM2B was instantiated with an invalid byte buffer. 37 | func (value *TPM2B[T, P]) Contents() (*T, error) { 38 | if value.contents != nil { 39 | return value.contents, nil 40 | } 41 | if value.buffer == nil { 42 | return nil, fmt.Errorf("TPMB had no contents or buffer") 43 | } 44 | contents, err := Unmarshal[T, P](value.buffer) 45 | if err != nil { 46 | return nil, err 47 | } 48 | // Cache the result 49 | value.contents = (*T)(contents) 50 | return value.contents, nil 51 | } 52 | 53 | // Bytes returns the inner contents of the TPM2B as a byte array, not including the length field. 54 | func (value *TPM2B[T, P]) Bytes() []byte { 55 | if value.buffer != nil { 56 | return value.buffer 57 | } 58 | if value.contents == nil { 59 | return []byte{} 60 | } 61 | 62 | // Cache the result 63 | value.buffer = Marshal(*value.contents) 64 | return value.buffer 65 | } 66 | 67 | // marshal implements the tpm2.Marshallable interface. 68 | func (value TPM2B[T, P]) marshal(buf *bytes.Buffer) { 69 | b := value.Bytes() 70 | binary.Write(buf, binary.BigEndian, uint16(len(b))) 71 | buf.Write(b) 72 | } 73 | 74 | // unmarshal implements the tpm2.Unmarshallable interface. 75 | // Note: the structure contents are not validated during unmarshalling. 76 | func (value *TPM2B[T, P]) unmarshal(buf *bytes.Buffer) error { 77 | var size uint16 78 | binary.Read(buf, binary.BigEndian, &size) 79 | value.contents = nil 80 | value.buffer = make([]byte, size) 81 | _, err := io.ReadAtLeast(buf, value.buffer, int(size)) 82 | return err 83 | } 84 | -------------------------------------------------------------------------------- /tpm2/transport/linuxtpm/linuxtpm.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Package linuxtpm provides access to a physical TPM device via the device file. 4 | package linuxtpm 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/google/go-tpm/tpm2/transport" 12 | ) 13 | 14 | var ( 15 | // ErrFileIsNotDevice indicates that the TPM file mode was not a device. 16 | ErrFileIsNotDevice = errors.New("TPM file is not a device") 17 | ) 18 | 19 | // Open opens the TPM device file at the given path. 20 | func Open(path string) (transport.TPMCloser, error) { 21 | fi, err := os.Stat(path) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if fi.Mode()&os.ModeDevice == 0 { 27 | return nil, fmt.Errorf("%w: %s (%s)", ErrFileIsNotDevice, fi.Mode().String(), path) 28 | } 29 | var f *os.File 30 | f, err = os.OpenFile(path, os.O_RDWR, 0600) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return transport.FromReadWriteCloser(f), nil 36 | } 37 | -------------------------------------------------------------------------------- /tpm2/transport/linuxtpm/linuxtpm_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package linuxtpm 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "github.com/google/go-tpm/tpm2/transport" 10 | testhelper "github.com/google/go-tpm/tpm2/transport/test" 11 | ) 12 | 13 | func open(path string) func() (transport.TPMCloser, error) { 14 | return func() (transport.TPMCloser, error) { 15 | return Open(path) 16 | } 17 | } 18 | 19 | func TestLocalTPM(t *testing.T) { 20 | testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrFileIsNotDevice}, open("/dev/tpm0")) 21 | } 22 | 23 | func TestLocalResourceManagedTPM(t *testing.T) { 24 | testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrFileIsNotDevice}, open("/dev/tpmrm0")) 25 | } 26 | -------------------------------------------------------------------------------- /tpm2/transport/linuxudstpm/linuxudstpm.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Package linuxudstpm provides access to a TPM device via a Unix domain socket. 4 | package linuxudstpm 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "net" 10 | "os" 11 | 12 | "github.com/google/go-tpm/tpm2/transport" 13 | ) 14 | 15 | var ( 16 | // ErrFileIsNotSocket indicates that the TPM file is not a socket. 17 | ErrFileIsNotSocket = errors.New("TPM file is not a socket") 18 | // ErrMustCallWriteThenRead indicates that the file was not written-then-read in the expected pattern. 19 | ErrMustCallWriteThenRead = errors.New("must call Write then Read in an alternating sequence") 20 | // ErrNotOpen indicates that the TPM file is not currently open. 21 | ErrNotOpen = errors.New("no connection is open") 22 | ) 23 | 24 | // Open opens the TPM socket at the given path. 25 | func Open(path string) (transport.TPMCloser, error) { 26 | fi, err := os.Stat(path) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if fi.Mode()&os.ModeSocket == 0 { 32 | return nil, fmt.Errorf("%w: %s (%s)", ErrFileIsNotSocket, fi.Mode().String(), path) 33 | } 34 | return transport.FromReadWriteCloser(newEmulatorReadWriteCloser(path)), nil 35 | } 36 | 37 | // dialer abstracts the net.Dial call so test code can provide its own net.Conn 38 | // implementation. 39 | type dialer func(network, path string) (net.Conn, error) 40 | 41 | // emulatorReadWriteCloser manages connections with a TPM emulator over a Unix 42 | // domain socket. These emulators often operate in a write/read/disconnect 43 | // sequence, so the Write method always connects, and the Read method always 44 | // closes. emulatorReadWriteCloser is not thread safe. 45 | type emulatorReadWriteCloser struct { 46 | path string 47 | conn net.Conn 48 | dialer dialer 49 | } 50 | 51 | // newEmulatorReadWriteCloser stores information about a Unix domain socket to 52 | // write to and read from. 53 | func newEmulatorReadWriteCloser(path string) *emulatorReadWriteCloser { 54 | return &emulatorReadWriteCloser{ 55 | path: path, 56 | dialer: net.Dial, 57 | } 58 | } 59 | 60 | // Read implements the io.Reader interface. 61 | func (erw *emulatorReadWriteCloser) Read(p []byte) (int, error) { 62 | // Read is always the second operation in a Write/Read sequence. 63 | if erw.conn == nil { 64 | return 0, ErrMustCallWriteThenRead 65 | } 66 | n, err := erw.conn.Read(p) 67 | erw.conn.Close() 68 | erw.conn = nil 69 | return n, err 70 | } 71 | 72 | // Write implements the io.Writer interface. 73 | func (erw *emulatorReadWriteCloser) Write(p []byte) (int, error) { 74 | if erw.conn != nil { 75 | return 0, ErrMustCallWriteThenRead 76 | } 77 | var err error 78 | erw.conn, err = erw.dialer("unix", erw.path) 79 | if err != nil { 80 | return 0, err 81 | } 82 | return erw.conn.Write(p) 83 | } 84 | 85 | // Close implements the io.Closer interface. 86 | func (erw *emulatorReadWriteCloser) Close() error { 87 | if erw.conn == nil { 88 | // This is an expected possible state, e.g., if someone sent the TPM a command and didn't read the response. 89 | return nil 90 | } 91 | err := erw.conn.Close() 92 | erw.conn = nil 93 | return err 94 | } 95 | -------------------------------------------------------------------------------- /tpm2/transport/linuxudstpm/linuxudstpm_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package linuxudstpm 4 | 5 | import ( 6 | "flag" 7 | "os" 8 | "syscall" 9 | "testing" 10 | 11 | "github.com/google/go-tpm/tpm2/transport" 12 | testhelper "github.com/google/go-tpm/tpm2/transport/test" 13 | ) 14 | 15 | var tpmSocket = flag.String("tpm_socket", "/dev/tpm0", "path to the TPM simulator UDS") 16 | 17 | func TestMain(m *testing.M) { 18 | flag.Parse() 19 | os.Exit(m.Run()) 20 | } 21 | 22 | func open() func() (transport.TPMCloser, error) { 23 | return func() (transport.TPMCloser, error) { 24 | return Open(*tpmSocket) 25 | } 26 | } 27 | 28 | func TestLocalUDSTPM(t *testing.T) { 29 | testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrFileIsNotSocket, syscall.ECONNREFUSED}, open()) 30 | } 31 | -------------------------------------------------------------------------------- /tpm2/transport/open_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package transport 4 | 5 | import ( 6 | legacy "github.com/google/go-tpm/legacy/tpm2" 7 | ) 8 | 9 | // OpenTPM opens the TPM at the given path. If no path is provided, it will 10 | // attempt to use reasonable defaults. 11 | // 12 | // Deprecated: Please use the individual transport packages (e.g., 13 | // go-tpm/tpm2/transport/linuxtpm). 14 | func OpenTPM(path ...string) (TPMCloser, error) { 15 | rwc, err := legacy.OpenTPM(path...) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return &wrappedRWC{ 20 | transport: rwc, 21 | }, nil 22 | } 23 | -------------------------------------------------------------------------------- /tpm2/transport/open_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package transport 4 | 5 | import ( 6 | legacy "github.com/google/go-tpm/legacy/tpm2" 7 | ) 8 | 9 | // OpenTPM opens the local system TPM. 10 | // 11 | // Deprecated: Please use the individual transport packages (e.g., 12 | // go-tpm/tpm2/transport/windowstpm). 13 | func OpenTPM() (TPMCloser, error) { 14 | rwc, err := legacy.OpenTPM() 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &wrappedRWC{ 19 | transport: rwc, 20 | }, nil 21 | } 22 | -------------------------------------------------------------------------------- /tpm2/transport/simulator/simulator.go: -------------------------------------------------------------------------------- 1 | // Package simulator provides access to a local simulator for TPM testing. 2 | package simulator 3 | 4 | import ( 5 | "io" 6 | 7 | "github.com/google/go-tpm-tools/simulator" 8 | "github.com/google/go-tpm/tpm2/transport" 9 | "github.com/google/go-tpm/tpmutil" 10 | ) 11 | 12 | // TPM represents a connection to a TPM simulator. 13 | type TPM struct { 14 | transport io.ReadWriteCloser 15 | } 16 | 17 | // Send implements the TPM interface. 18 | func (t *TPM) Send(input []byte) ([]byte, error) { 19 | return tpmutil.RunCommandRaw(t.transport, input) 20 | } 21 | 22 | // OpenSimulator starts and opens a TPM simulator. 23 | func OpenSimulator() (transport.TPMCloser, error) { 24 | sim, err := simulator.Get() 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &TPM{ 29 | transport: sim, 30 | }, nil 31 | } 32 | 33 | // Close implements the TPM interface. 34 | func (t *TPM) Close() error { 35 | return t.transport.Close() 36 | } 37 | -------------------------------------------------------------------------------- /tpm2/transport/test/helper.go: -------------------------------------------------------------------------------- 1 | // Package testhelper provides some helper code for TPM transport tests. 2 | package testhelper 3 | 4 | import ( 5 | "bytes" 6 | "encoding/binary" 7 | "errors" 8 | "testing" 9 | 10 | "github.com/google/go-tpm/tpm2" 11 | "github.com/google/go-tpm/tpm2/transport" 12 | ) 13 | 14 | // RunTest checks that the connection to the given TPM seems to be working. 15 | func RunTest(t *testing.T, skipErrs []error, tpmOpener func() (transport.TPMCloser, error)) { 16 | tpm, err := tpmOpener() 17 | for _, skipErr := range skipErrs { 18 | if errors.Is(err, skipErr) { 19 | t.Skipf("%v", err) 20 | } 21 | } 22 | if err != nil { 23 | t.Fatalf("Failed to open TPM: %v", err) 24 | } 25 | defer func(tpm transport.TPMCloser) { 26 | if err := tpm.Close(); err != nil { 27 | t.Fatalf("tpm.Close() = %v", err) 28 | } 29 | }(tpm) 30 | 31 | // Ping the TPM to ask it what the manufacturer is, as a basic consistency check. 32 | cap, err := tpm2.GetCapability{ 33 | Capability: tpm2.TPMCapTPMProperties, 34 | Property: uint32(tpm2.TPMPTManufacturer), 35 | PropertyCount: 1, 36 | }.Execute(tpm) 37 | 38 | // We might run into one of the known "skip if this error" cases. 39 | for _, skipErr := range skipErrs { 40 | if errors.Is(err, skipErr) { 41 | t.Skipf("%v", err) 42 | } 43 | } 44 | if err != nil { 45 | t.Fatalf("GetCapability() = %v", err) 46 | } 47 | props, err := cap.CapabilityData.Data.TPMProperties() 48 | if err != nil { 49 | t.Fatalf("cap.TPMProperties() = %v", err) 50 | } 51 | if len(props.TPMProperty) != 1 { 52 | t.Fatalf("GetCapability() = %v properties, want 1", len(props.TPMProperty)) 53 | } 54 | 55 | var idBuf bytes.Buffer 56 | idBuf.Grow(4) 57 | binary.Write(&idBuf, binary.BigEndian, props.TPMProperty[0].Value) 58 | t.Logf("Manufacturer ID: %q", idBuf.String()) 59 | } 60 | -------------------------------------------------------------------------------- /tpm2/transport/tpm.go: -------------------------------------------------------------------------------- 1 | // Package transport implements types for physically talking to TPMs. 2 | package transport 3 | 4 | import ( 5 | "io" 6 | 7 | "github.com/google/go-tpm/tpmutil" 8 | ) 9 | 10 | // TPM represents a logical connection to a TPM. 11 | type TPM interface { 12 | Send(input []byte) ([]byte, error) 13 | } 14 | 15 | // TPMCloser represents a logical connection to a TPM and you can close it. 16 | type TPMCloser interface { 17 | TPM 18 | io.Closer 19 | } 20 | 21 | // wrappedRW represents a struct that wraps an io.ReadWriter 22 | // to a transport.TPM to be compatible with tpmdirect. 23 | type wrappedRW struct { 24 | transport io.ReadWriter 25 | } 26 | 27 | // wrappedRWC represents a struct that wraps an io.ReadWriteCloser 28 | // to a transport.TPM to be compatible with tpmdirect. 29 | type wrappedRWC struct { 30 | transport io.ReadWriteCloser 31 | } 32 | 33 | // wrappedTPM represents a struct that wraps a transport.TPM's underlying 34 | // transport to use with legacy code that expects an io.ReadWriter. 35 | type wrappedTPM struct { 36 | response []byte 37 | tpm TPM 38 | } 39 | 40 | // FromReadWriter takes in a io.ReadWriter and returns a 41 | // transport.TPM wrapping the io.ReadWriter. 42 | func FromReadWriter(rw io.ReadWriter) TPM { 43 | return &wrappedRW{transport: rw} 44 | } 45 | 46 | // FromReadWriteCloser takes in a io.ReadWriteCloser and returns a 47 | // transport.TPMCloser wrapping the io.ReadWriteCloser. 48 | func FromReadWriteCloser(rwc io.ReadWriteCloser) TPMCloser { 49 | return &wrappedRWC{transport: rwc} 50 | } 51 | 52 | // ToReadWriter takes in a transport TPM and returns an 53 | // io.ReadWriter wrapping the transport TPM. 54 | func ToReadWriter(tpm TPM) io.ReadWriter { 55 | return &wrappedTPM{tpm: tpm} 56 | } 57 | 58 | // Read copies t.response into the p buffer and return the appropriate length. 59 | func (t *wrappedTPM) Read(p []byte) (int, error) { 60 | n := copy(p, t.response) 61 | t.response = t.response[n:] 62 | if len(t.response) == 0 { 63 | return n, io.EOF 64 | } 65 | return n, nil 66 | } 67 | 68 | // Write implements the io.ReadWriter interface. 69 | func (t *wrappedTPM) Write(p []byte) (n int, err error) { 70 | t.response, err = t.tpm.Send(p) 71 | if err != nil { 72 | return 0, err 73 | } 74 | return len(p), nil 75 | } 76 | 77 | // Send implements the TPM interface. 78 | func (t *wrappedRW) Send(input []byte) ([]byte, error) { 79 | return tpmutil.RunCommandRaw(t.transport, input) 80 | } 81 | 82 | // Send implements the TPM interface. 83 | func (t *wrappedRWC) Send(input []byte) ([]byte, error) { 84 | return tpmutil.RunCommandRaw(t.transport, input) 85 | } 86 | 87 | // Close implements the TPM interface. 88 | func (t *wrappedRWC) Close() error { 89 | return t.transport.Close() 90 | } 91 | -------------------------------------------------------------------------------- /tpm2/transport/windowstpm/windowstpm.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | // Package windowstpm implements the TPM transport on Windows using tbs.dll. 4 | package windowstpm 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io" 10 | 11 | "github.com/google/go-tpm/tpm2/transport" 12 | "github.com/google/go-tpm/tpmutil/tbs" 13 | ) 14 | 15 | var ( 16 | // ErrNotTPM20 indicates that a TPM 2.0 was not found. 17 | ErrNotTPM20 = errors.New("device is not a TPM 2.0") 18 | ) 19 | 20 | const ( 21 | maxTPMResponse = 4096 22 | ) 23 | 24 | // Open opens a channel to the TPM via TBS. 25 | func Open() (transport.TPMCloser, error) { 26 | info, err := tbs.GetDeviceInfo() 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if info.TPMVersion != tbs.TPMVersion20 { 32 | return nil, fmt.Errorf("%w: %v", ErrNotTPM20, info.TPMVersion) 33 | } 34 | 35 | tpmContext, err := tbs.CreateContext(tbs.TPMVersion20, tbs.IncludeTPM20) 36 | rwc := &winTPMBuffer{ 37 | context: tpmContext, 38 | outBuffer: make([]byte, 0, maxTPMResponse), 39 | } 40 | return transport.FromReadWriteCloser(rwc), err 41 | } 42 | 43 | // winTPMBuffer is a ReadWriteCloser to access the TPM in Windows. 44 | type winTPMBuffer struct { 45 | context tbs.Context 46 | outBuffer []byte 47 | } 48 | 49 | // Write implements the io.Writer interface. 50 | // 51 | // Executes the TPM command specified by commandBuffer (at Normal Priority), returning the number 52 | // of bytes in the command and any error code returned by executing the TPM command. Command 53 | // response can be read by calling Read(). 54 | func (rwc *winTPMBuffer) Write(commandBuffer []byte) (int, error) { 55 | // TPM spec defines longest possible response to be maxTPMResponse. 56 | rwc.outBuffer = rwc.outBuffer[:maxTPMResponse] 57 | 58 | outBufferLen, err := rwc.context.SubmitCommand( 59 | tbs.NormalPriority, 60 | commandBuffer, 61 | rwc.outBuffer, 62 | ) 63 | 64 | if err != nil { 65 | rwc.outBuffer = rwc.outBuffer[:0] 66 | return 0, err 67 | } 68 | // Shrink outBuffer so it is length of response. 69 | rwc.outBuffer = rwc.outBuffer[:outBufferLen] 70 | return len(commandBuffer), nil 71 | } 72 | 73 | // Read implements the io.Reader interface. 74 | // 75 | // Provides TPM response from the command called in the last Write call. 76 | func (rwc *winTPMBuffer) Read(responseBuffer []byte) (int, error) { 77 | if len(rwc.outBuffer) == 0 { 78 | return 0, io.EOF 79 | } 80 | lenCopied := copy(responseBuffer, rwc.outBuffer) 81 | // Cut out the piece of slice which was just read out, maintaining original slice capacity. 82 | rwc.outBuffer = append(rwc.outBuffer[:0], rwc.outBuffer[lenCopied:]...) 83 | return lenCopied, nil 84 | } 85 | 86 | // Close implements the io.Closer interface. 87 | func (rwc *winTPMBuffer) Close() error { 88 | return rwc.context.Close() 89 | } 90 | -------------------------------------------------------------------------------- /tpm2/transport/windowstpm/windowstpm_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package windowstpm 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | testhelper "github.com/google/go-tpm/tpm2/transport/test" 10 | ) 11 | 12 | func TestLocalTPM(t *testing.T) { 13 | testhelper.RunTest(t, []error{os.ErrNotExist, os.ErrPermission, ErrNotTPM20}, Open) 14 | } 15 | -------------------------------------------------------------------------------- /tpmutil/poll_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin 2 | 3 | package tpmutil 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | // Not implemented on Windows. 10 | func poll(_ *os.File) error { return nil } 11 | -------------------------------------------------------------------------------- /tpmutil/poll_unix.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package tpmutil 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // poll blocks until the file descriptor is ready for reading or an error occurs. 13 | func poll(f *os.File) error { 14 | var ( 15 | fds = []unix.PollFd{{ 16 | Fd: int32(f.Fd()), 17 | Events: 0x1, // POLLIN 18 | }} 19 | timeout = -1 // Indefinite timeout 20 | ) 21 | 22 | if _, err := unix.Poll(fds, timeout); err != nil { 23 | return err 24 | } 25 | 26 | // Revents is filled in by the kernel. 27 | // If the expected event happened, Revents should match Events. 28 | if fds[0].Revents != fds[0].Events { 29 | return fmt.Errorf("unexpected poll Revents 0x%x", fds[0].Revents) 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /tpmutil/poll_unix_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package tpmutil 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestPoll(t *testing.T) { 11 | r, w, err := os.Pipe() 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | defer r.Close() 16 | defer w.Close() 17 | 18 | if _, err := w.Write([]byte("hi")); err != nil { 19 | t.Fatalf("error writing to pipe: %v", err) 20 | } 21 | if err := poll(r); err != nil { 22 | t.Errorf("error polling reader side of the pipe: %v", err) 23 | } 24 | if err := r.Close(); err != nil { 25 | t.Fatalf("error closing reader side of the pipe: %v", err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tpmutil/run.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package tpmutil provides common utility functions for both TPM 1.2 and TPM 16 | // 2.0 devices. 17 | package tpmutil 18 | 19 | import ( 20 | "errors" 21 | "io" 22 | "os" 23 | "time" 24 | ) 25 | 26 | // maxTPMResponse is the largest possible response from the TPM. We need to know 27 | // this because we don't always know the length of the TPM response, and 28 | // /dev/tpm insists on giving it all back in a single value rather than 29 | // returning a header and a body in separate responses. 30 | const maxTPMResponse = 4096 31 | 32 | // RunCommandRaw executes the given raw command and returns the raw response. 33 | // Does not check the response code except to execute retry logic. 34 | func RunCommandRaw(rw io.ReadWriter, inb []byte) ([]byte, error) { 35 | if rw == nil { 36 | return nil, errors.New("nil TPM handle") 37 | } 38 | 39 | // f(t) = (2^t)ms, up to 2s 40 | var backoffFac uint 41 | var rh responseHeader 42 | var outb []byte 43 | 44 | for { 45 | if _, err := rw.Write(inb); err != nil { 46 | return nil, err 47 | } 48 | 49 | // If the TPM is a real device, it may not be ready for reading 50 | // immediately after writing the command. Wait until the file 51 | // descriptor is ready to be read from. 52 | if f, ok := rw.(*os.File); ok { 53 | if err := poll(f); err != nil { 54 | return nil, err 55 | } 56 | } 57 | 58 | outb = make([]byte, maxTPMResponse) 59 | outlen, err := rw.Read(outb) 60 | if err != nil { 61 | return nil, err 62 | } 63 | // Resize the buffer to match the amount read from the TPM. 64 | outb = outb[:outlen] 65 | 66 | _, err = Unpack(outb, &rh) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | // If TPM is busy, retry the command after waiting a few ms. 72 | if rh.Res == RCRetry { 73 | if backoffFac < 11 { 74 | dur := (1 << backoffFac) * time.Millisecond 75 | time.Sleep(dur) 76 | backoffFac++ 77 | } else { 78 | return nil, err 79 | } 80 | } else { 81 | break 82 | } 83 | } 84 | 85 | return outb, nil 86 | } 87 | 88 | // RunCommand executes cmd with given tag and arguments. Returns TPM response 89 | // body (without response header) and response code from the header. Returned 90 | // error may be nil if response code is not RCSuccess; caller should check 91 | // both. 92 | func RunCommand(rw io.ReadWriter, tag Tag, cmd Command, in ...interface{}) ([]byte, ResponseCode, error) { 93 | inb, err := packWithHeader(commandHeader{tag, 0, cmd}, in...) 94 | if err != nil { 95 | return nil, 0, err 96 | } 97 | 98 | outb, err := RunCommandRaw(rw, inb) 99 | if err != nil { 100 | return nil, 0, err 101 | } 102 | 103 | var rh responseHeader 104 | read, err := Unpack(outb, &rh) 105 | if err != nil { 106 | return nil, 0, err 107 | } 108 | if rh.Res != RCSuccess { 109 | return nil, rh.Res, nil 110 | } 111 | 112 | return outb[read:], rh.Res, nil 113 | } 114 | -------------------------------------------------------------------------------- /tpmutil/run_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | // Copyright (c) 2018, Google LLC All rights reserved. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tpmutil 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "net" 23 | "os" 24 | ) 25 | 26 | // OpenTPM opens a channel to the TPM at the given path. If the file is a 27 | // device, then it treats it like a normal TPM device, and if the file is a 28 | // Unix domain socket, then it opens a connection to the socket. 29 | func OpenTPM(path string) (io.ReadWriteCloser, error) { 30 | // If it's a regular file, then open it 31 | var rwc io.ReadWriteCloser 32 | fi, err := os.Stat(path) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | if fi.Mode()&os.ModeDevice != 0 { 38 | var f *os.File 39 | f, err = os.OpenFile(path, os.O_RDWR, 0600) 40 | if err != nil { 41 | return nil, err 42 | } 43 | rwc = io.ReadWriteCloser(f) 44 | } else if fi.Mode()&os.ModeSocket != 0 { 45 | rwc = NewEmulatorReadWriteCloser(path) 46 | } else { 47 | return nil, fmt.Errorf("unsupported TPM file mode %s", fi.Mode().String()) 48 | } 49 | 50 | return rwc, nil 51 | } 52 | 53 | // dialer abstracts the net.Dial call so test code can provide its own net.Conn 54 | // implementation. 55 | type dialer func(network, path string) (net.Conn, error) 56 | 57 | // EmulatorReadWriteCloser manages connections with a TPM emulator over a Unix 58 | // domain socket. These emulators often operate in a write/read/disconnect 59 | // sequence, so the Write method always connects, and the Read method always 60 | // closes. EmulatorReadWriteCloser is not thread safe. 61 | type EmulatorReadWriteCloser struct { 62 | path string 63 | conn net.Conn 64 | dialer dialer 65 | } 66 | 67 | // NewEmulatorReadWriteCloser stores information about a Unix domain socket to 68 | // write to and read from. 69 | func NewEmulatorReadWriteCloser(path string) *EmulatorReadWriteCloser { 70 | return &EmulatorReadWriteCloser{ 71 | path: path, 72 | dialer: net.Dial, 73 | } 74 | } 75 | 76 | // Read implements io.Reader by reading from the Unix domain socket and closing 77 | // it. 78 | func (erw *EmulatorReadWriteCloser) Read(p []byte) (int, error) { 79 | // Read is always the second operation in a Write/Read sequence. 80 | if erw.conn == nil { 81 | return 0, fmt.Errorf("must call Write then Read in an alternating sequence") 82 | } 83 | n, err := erw.conn.Read(p) 84 | erw.conn.Close() 85 | erw.conn = nil 86 | return n, err 87 | } 88 | 89 | // Write implements io.Writer by connecting to the Unix domain socket and 90 | // writing. 91 | func (erw *EmulatorReadWriteCloser) Write(p []byte) (int, error) { 92 | if erw.conn != nil { 93 | return 0, fmt.Errorf("must call Write then Read in an alternating sequence") 94 | } 95 | var err error 96 | erw.conn, err = erw.dialer("unix", erw.path) 97 | if err != nil { 98 | return 0, err 99 | } 100 | return erw.conn.Write(p) 101 | } 102 | 103 | // Close implements io.Closer by closing the Unix domain socket if one is open. 104 | func (erw *EmulatorReadWriteCloser) Close() error { 105 | if erw.conn == nil { 106 | return fmt.Errorf("cannot call Close when no connection is open") 107 | } 108 | err := erw.conn.Close() 109 | erw.conn = nil 110 | return err 111 | } 112 | -------------------------------------------------------------------------------- /tpmutil/run_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpmutil 16 | 17 | import ( 18 | "io" 19 | 20 | "github.com/google/go-tpm/tpmutil/tbs" 21 | ) 22 | 23 | // winTPMBuffer is a ReadWriteCloser to access the TPM in Windows. 24 | type winTPMBuffer struct { 25 | context tbs.Context 26 | outBuffer []byte 27 | } 28 | 29 | // Executes the TPM command specified by commandBuffer (at Normal Priority), returning the number 30 | // of bytes in the command and any error code returned by executing the TPM command. Command 31 | // response can be read by calling Read(). 32 | func (rwc *winTPMBuffer) Write(commandBuffer []byte) (int, error) { 33 | // TPM spec defines longest possible response to be maxTPMResponse. 34 | rwc.outBuffer = rwc.outBuffer[:maxTPMResponse] 35 | 36 | outBufferLen, err := rwc.context.SubmitCommand( 37 | tbs.NormalPriority, 38 | commandBuffer, 39 | rwc.outBuffer, 40 | ) 41 | 42 | if err != nil { 43 | rwc.outBuffer = rwc.outBuffer[:0] 44 | return 0, err 45 | } 46 | // Shrink outBuffer so it is length of response. 47 | rwc.outBuffer = rwc.outBuffer[:outBufferLen] 48 | return len(commandBuffer), nil 49 | } 50 | 51 | // Provides TPM response from the command called in the last Write call. 52 | func (rwc *winTPMBuffer) Read(responseBuffer []byte) (int, error) { 53 | if len(rwc.outBuffer) == 0 { 54 | return 0, io.EOF 55 | } 56 | lenCopied := copy(responseBuffer, rwc.outBuffer) 57 | // Cut out the piece of slice which was just read out, maintaining original slice capacity. 58 | rwc.outBuffer = append(rwc.outBuffer[:0], rwc.outBuffer[lenCopied:]...) 59 | return lenCopied, nil 60 | } 61 | 62 | func (rwc *winTPMBuffer) Close() error { 63 | return rwc.context.Close() 64 | } 65 | 66 | // OpenTPM creates a new instance of a ReadWriteCloser which can interact with a 67 | // Windows TPM. 68 | func OpenTPM() (io.ReadWriteCloser, error) { 69 | tpmContext, err := tbs.CreateContext(tbs.TPMVersion20, tbs.IncludeTPM12|tbs.IncludeTPM20) 70 | rwc := &winTPMBuffer{ 71 | context: tpmContext, 72 | outBuffer: make([]byte, 0, maxTPMResponse), 73 | } 74 | return rwc, err 75 | } 76 | 77 | // FromContext creates a new instance of a ReadWriteCloser which can 78 | // interact with a Windows TPM, using the specified TBS handle. 79 | func FromContext(ctx tbs.Context) io.ReadWriteCloser { 80 | return &winTPMBuffer{ 81 | context: ctx, 82 | outBuffer: make([]byte, 0, maxTPMResponse), 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tpmutil/structures.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Google LLC All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tpmutil 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "fmt" 21 | "io" 22 | ) 23 | 24 | // maxBytesBufferSize sets a sane upper bound on the size of a U32Bytes 25 | // buffer. This limit exists to prevent a maliciously large size prefix 26 | // from resulting in a massive memory allocation, potentially causing 27 | // an OOM condition on the system. 28 | // We expect no buffer from a TPM to approach 1Mb in size. 29 | const maxBytesBufferSize uint32 = 1024 * 1024 // 1Mb. 30 | 31 | // RawBytes is for Pack and RunCommand arguments that are already encoded. 32 | // Compared to []byte, RawBytes will not be prepended with slice length during 33 | // encoding. 34 | type RawBytes []byte 35 | 36 | // U16Bytes is a byte slice with a 16-bit header 37 | type U16Bytes []byte 38 | 39 | // TPMMarshal packs U16Bytes 40 | func (b *U16Bytes) TPMMarshal(out io.Writer) error { 41 | size := len([]byte(*b)) 42 | if err := binary.Write(out, binary.BigEndian, uint16(size)); err != nil { 43 | return err 44 | } 45 | 46 | n, err := out.Write(*b) 47 | if err != nil { 48 | return err 49 | } 50 | if n != size { 51 | return fmt.Errorf("unable to write all contents of U16Bytes") 52 | } 53 | return nil 54 | } 55 | 56 | // TPMUnmarshal unpacks a U16Bytes 57 | func (b *U16Bytes) TPMUnmarshal(in io.Reader) error { 58 | var tmpSize uint16 59 | if err := binary.Read(in, binary.BigEndian, &tmpSize); err != nil { 60 | return err 61 | } 62 | size := int(tmpSize) 63 | 64 | if len(*b) >= size { 65 | *b = (*b)[:size] 66 | } else { 67 | *b = append(*b, make([]byte, size-len(*b))...) 68 | } 69 | 70 | n, err := in.Read(*b) 71 | if err != nil { 72 | return err 73 | } 74 | if n != size { 75 | return io.ErrUnexpectedEOF 76 | } 77 | return nil 78 | } 79 | 80 | // U32Bytes is a byte slice with a 32-bit header 81 | type U32Bytes []byte 82 | 83 | // TPMMarshal packs U32Bytes 84 | func (b *U32Bytes) TPMMarshal(out io.Writer) error { 85 | size := len([]byte(*b)) 86 | if err := binary.Write(out, binary.BigEndian, uint32(size)); err != nil { 87 | return err 88 | } 89 | 90 | n, err := out.Write(*b) 91 | if err != nil { 92 | return err 93 | } 94 | if n != size { 95 | return fmt.Errorf("unable to write all contents of U32Bytes") 96 | } 97 | return nil 98 | } 99 | 100 | // TPMUnmarshal unpacks a U32Bytes 101 | func (b *U32Bytes) TPMUnmarshal(in io.Reader) error { 102 | var tmpSize uint32 103 | if err := binary.Read(in, binary.BigEndian, &tmpSize); err != nil { 104 | return err 105 | } 106 | 107 | if tmpSize > maxBytesBufferSize { 108 | return bytes.ErrTooLarge 109 | } 110 | // We can now safely cast to an int on 32-bit or 64-bit machines 111 | size := int(tmpSize) 112 | 113 | if len(*b) >= size { 114 | *b = (*b)[:size] 115 | } else { 116 | *b = append(*b, make([]byte, size-len(*b))...) 117 | } 118 | 119 | n, err := in.Read(*b) 120 | if err != nil { 121 | return err 122 | } 123 | if n != size { 124 | return fmt.Errorf("unable to read all contents in to U32Bytes") 125 | } 126 | return nil 127 | } 128 | 129 | // Tag is a command tag. 130 | type Tag uint16 131 | 132 | // Command is an identifier of a TPM command. 133 | type Command uint32 134 | 135 | // A commandHeader is the header for a TPM command. 136 | type commandHeader struct { 137 | Tag Tag 138 | Size uint32 139 | Cmd Command 140 | } 141 | 142 | // ResponseCode is a response code returned by TPM. 143 | type ResponseCode uint32 144 | 145 | // RCSuccess is response code for successful command. Identical for TPM 1.2 and 146 | // 2.0. 147 | const RCSuccess ResponseCode = 0x000 148 | 149 | // RCRetry is response code for TPM is busy. 150 | const RCRetry ResponseCode = 0x922 151 | 152 | // A responseHeader is a header for TPM responses. 153 | type responseHeader struct { 154 | Tag Tag 155 | Size uint32 156 | Res ResponseCode 157 | } 158 | 159 | // A Handle is a reference to a TPM object. 160 | type Handle uint32 161 | 162 | // HandleValue returns the handle value. This behavior is intended to satisfy 163 | // an interface that can be implemented by other, more complex types as well. 164 | func (h Handle) HandleValue() uint32 { 165 | return uint32(h) 166 | } 167 | 168 | type handleList []Handle 169 | 170 | func (l *handleList) TPMMarshal(_ io.Writer) error { 171 | return fmt.Errorf("TPMMarhsal on []Handle is not supported yet") 172 | } 173 | 174 | func (l *handleList) TPMUnmarshal(in io.Reader) error { 175 | var numHandles uint16 176 | if err := binary.Read(in, binary.BigEndian, &numHandles); err != nil { 177 | return err 178 | } 179 | 180 | // Make len(e) match size exactly. 181 | size := int(numHandles) 182 | if len(*l) >= size { 183 | *l = (*l)[:size] 184 | } else { 185 | *l = append(*l, make([]Handle, size-len(*l))...) 186 | } 187 | return binary.Read(in, binary.BigEndian, *l) 188 | } 189 | 190 | // SelfMarshaler allows custom types to override default encoding/decoding 191 | // behavior in Pack, Unpack and UnpackBuf. 192 | type SelfMarshaler interface { 193 | TPMMarshal(out io.Writer) error 194 | TPMUnmarshal(in io.Reader) error 195 | } 196 | -------------------------------------------------------------------------------- /tpmutil/tbs/tbs_windows_test.go: -------------------------------------------------------------------------------- 1 | package tbs 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | // Encodes a call to Getrandom() with a buffer of length zero, making this 10 | // command an effective no-op. 11 | var getRandomRawCommand = []byte{128, 1, 0, 0, 0, 12, 0, 0, 1, 123, 0, 0} 12 | 13 | func getContext(t *testing.T) Context { 14 | ctx, err := CreateContext(TPMVersion20, IncludeTPM12|IncludeTPM20) 15 | if err != nil { 16 | t.Skipf("Skipping test as we couldn't access the TPM: %v", err) 17 | } 18 | return ctx 19 | } 20 | 21 | // Get the log by passing in progressively larger buffers 22 | func TestGetLogLargeBuffer(t *testing.T) { 23 | ctx := getContext(t) 24 | defer ctx.Close() 25 | 26 | log := make([]byte, os.Getpagesize()) 27 | for { 28 | logLen, err := ctx.GetTCGLog(log) 29 | if err == nil { 30 | if logLen == 0 { 31 | t.Fatal("Expected positive TCGLog length") 32 | } 33 | return 34 | } 35 | if err != ErrInsufficientBuffer { 36 | t.Fatalf("GetTCGLog failed: %v", err) 37 | } 38 | log = make([]byte, 2*len(log)) 39 | } 40 | } 41 | 42 | // Get the log by passing in nil, checking the size, and then getting the log. 43 | func TestGetLogWithNilSlice(t *testing.T) { 44 | ctx := getContext(t) 45 | defer ctx.Close() 46 | 47 | logLen, err := ctx.GetTCGLog(nil) 48 | if err != nil { 49 | t.Fatalf("First GetTCGLog failed: %v", err) 50 | } 51 | if logLen == 0 { 52 | t.Fatal("Expected positive TCGLog length") 53 | } 54 | 55 | log := make([]byte, logLen) 56 | if _, err := ctx.GetTCGLog(log); err != nil { 57 | t.Fatalf("Second GetTCGLog failed: %v", err) 58 | } 59 | } 60 | 61 | // SubmitCommand can handle a nil command buffer. 62 | func TestSubmitCommandNilCommand(t *testing.T) { 63 | ctx := getContext(t) 64 | defer ctx.Close() 65 | 66 | rawResponse := make([]byte, os.Getpagesize()) 67 | _, err := ctx.SubmitCommand(NormalPriority, nil, rawResponse) 68 | if err != ErrBadParameter { 69 | t.Fatalf("SubmitCommand failed with %v: expected ErrBadParameter", err) 70 | } 71 | } 72 | 73 | // SubmitCommand can handle a nil response buffer. 74 | func TestSubmitCommandNilResponse(t *testing.T) { 75 | ctx := getContext(t) 76 | defer ctx.Close() 77 | 78 | _, err := ctx.SubmitCommand(NormalPriority, getRandomRawCommand, nil) 79 | if err != ErrInvalidOutputPointer { 80 | t.Fatalf("SubmitCommand failed with %v: expected ErrInvalidOutputPointer", err) 81 | } 82 | } 83 | 84 | // SubmitCommand can handle a response buffer that is shorter than necessary. 85 | func TestSubmitCommandShortResponse(t *testing.T) { 86 | ctx := getContext(t) 87 | defer ctx.Close() 88 | 89 | rawResponse := make([]byte, 1) 90 | _, err := ctx.SubmitCommand(NormalPriority, getRandomRawCommand, rawResponse) 91 | if err != ErrInsufficientBuffer { 92 | t.Fatalf("SubmitCommand failed with %v: expected ErrInsufficientBuffer", err) 93 | } 94 | } 95 | 96 | // SubmitCommand can handle a response buffer that is longer than necessary. 97 | func TestSubmitCommandLongResponse(t *testing.T) { 98 | ctx := getContext(t) 99 | defer ctx.Close() 100 | 101 | rawResponse := make([]byte, os.Getpagesize()) 102 | responseLen, err := ctx.SubmitCommand(NormalPriority, getRandomRawCommand, rawResponse) 103 | if err != nil { 104 | t.Fatalf("SubmitCommand failed: %v", err) 105 | } 106 | rawResponse = rawResponse[:responseLen] 107 | 108 | // Expected response buffer for getRandomRawCommand 109 | expectedGetRandomRawResponse := []byte{128, 1, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0} 110 | if !bytes.Equal(rawResponse, expectedGetRandomRawResponse) { 111 | t.Fatalf("Got response of %v, expected %v", rawResponse, expectedGetRandomRawResponse) 112 | } 113 | } 114 | --------------------------------------------------------------------------------