Compare commits

...

125 Commits

Author SHA1 Message Date
Brandon Weeks
c5d6b1e758
Fix CodeSQL permissions take 2 (#413)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.24.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.24.x) (push) Has been cancelled
Test / test-macos (1.24.x) (push) Has been cancelled
Test / test-windows (1.24.x) (push) Has been cancelled
2025-03-05 20:25:46 +00:00
Brandon Weeks
7d4525c388
Fix CodeSQL permissions (#412) 2025-03-05 12:06:09 -08:00
dependabot[bot]
dce70c6163
Bump the go-modules group with 3 updates (#410)
Bumps the go-modules group with 3 updates: [github.com/google/go-cmp](https://github.com/google/go-cmp), [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) and [golang.org/x/sys](https://github.com/golang/sys).

Updates `github.com/google/go-cmp` from 0.6.0 to 0.7.0
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.6.0...v0.7.0)

Updates `github.com/google/go-tpm-tools` from 0.4.4 to 0.4.5
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Changelog](https://github.com/google/go-tpm-tools/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.4.4...v0.4.5)

Updates `golang.org/x/sys` from 0.30.0 to 0.31.0
- [Commits](https://github.com/golang/sys/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 11:52:46 -08:00
Brandon Weeks
51a20034c0
Bump Go version to 1.24 (#411) 2025-03-05 11:47:52 -08:00
dependabot[bot]
a94a8af69d
Bump the github-actions group with 4 updates (#409)
Bumps the github-actions group with 4 updates: [actions/checkout](https://github.com/actions/checkout), [github/codeql-action](https://github.com/github/codeql-action), [actions/setup-go](https://github.com/actions/setup-go) and [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `actions/checkout` from 2 to 4
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v4)

Updates `github/codeql-action` from 1 to 3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v3)

Updates `actions/setup-go` from 2 to 5
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v5)

Updates `golangci/golangci-lint-action` from 3 to 6
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 19:29:43 +00:00
Brandon Weeks
f37925d5d0
Add github-actions to Dependabot config (#408) 2025-03-05 11:26:41 -08:00
zhsh
f7a27487f1
TPM.PCRBanks() should ignore empty PCR banks. (#406)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
2025-02-24 08:53:28 -08:00
zhsh
d9d8fdc48e
attest: Implement discovery of supported PCR banks, rather than always blithely assuming we have exactly SHA1 and SHA256. (#404)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
To do this, add a function to attest.TPM called PCRBanks() which enumerates the available PCR banks on a TPM. This requires plumbing through tpmBase and its implementations; the TPM1.2 implementations statically return []HashAlg{HashSHA1}, as one might expect.

To accomplish all of this, the implementation of HashAlg needed to be rethought. Now, instead of a reimplementation of tpm2.Algorithm, it's a lightweight wrapper around it. Dependent methods -- like Hash() and String() -- no longer have case HashSHA1/case HashSHA256 blocks; instead, they simply delegate to go-tpm2 for their implementations. As a result, we should never need to do something like this again.

Also add convenience constants HashSHA384 and HashSHA512.
2025-02-13 23:38:32 -08:00
dependabot[bot]
f44f5ffe7e
Bump golang.org/x/sys from 0.29.0 to 0.30.0 in the go-modules group (#403)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
Bumps the go-modules group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.29.0 to 0.30.0
- [Commits](https://github.com/golang/sys/compare/v0.29.0...v0.30.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-10 09:54:13 -08:00
zhsh
9cdb0fcd55
attest: tpm.NewKeyCertifiedBy() method that does not need an entire attest.AK object. The new method only needs the AK handle and its algorithm. (#402)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
2025-02-03 14:24:55 +11:00
zhsh
dfabc9c919
attest: Support "qualifyingData" when creating a new key. (#401) 2025-02-03 13:43:19 +11:00
Evgeny Shatokhin
c7aee80c5d attest: Support certification by ECC AKs.
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
2025-01-22 08:27:10 -08:00
Evgeny Shatokhin
1b202b12e8 attest: Support creation of ECC AK. 2025-01-22 08:27:10 -08:00
dependabot[bot]
183ad1d5ad
Bump the go-modules group across 1 directory with 2 updates (#394)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
Bumps the go-modules group with 2 updates in the / directory: [github.com/google/go-tpm](https://github.com/google/go-tpm) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/google/go-tpm` from 0.9.1 to 0.9.3
- [Release notes](https://github.com/google/go-tpm/releases)
- [Commits](https://github.com/google/go-tpm/compare/v0.9.1...v0.9.3)

Updates `golang.org/x/sys` from 0.28.0 to 0.29.0
- [Commits](https://github.com/golang/sys/compare/v0.28.0...v0.29.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go-modules
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-06 17:03:05 -08:00
dependabot[bot]
9cc576ead1
Bump golang.org/x/crypto from 0.17.0 to 0.31.0 in the go_modules group (#390)
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Test / test-linux (1.22.x) (push) Has been cancelled
Test / test-linux-tpm12 (1.22.x) (push) Has been cancelled
Test / test-macos (1.22.x) (push) Has been cancelled
Test / test-windows (1.22.x) (push) Has been cancelled
Bumps the go_modules group with 1 update: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.17.0 to 0.31.0
- [Commits](https://github.com/golang/crypto/compare/v0.17.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 15:24:52 +01:00
dependabot[bot]
62f7ad0785
Bump golang.org/x/sys in the go-modules group across 1 directory (#385) 2024-11-11 07:32:39 -08:00
dependabot[bot]
f203ad3090
Bump golang.org/x/sys from 0.24.0 to 0.25.0 in the go-modules group (#383) 2024-09-09 16:25:25 +02:00
dependabot[bot]
72657612f0
Bump golang.org/x/sys from 0.23.0 to 0.24.0 in the go-modules group (#382)
Bumps the go-modules group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.23.0 to 0.24.0
- [Commits](https://github.com/golang/sys/compare/v0.23.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 09:17:08 -07:00
dependabot[bot]
ec740ef912
Bump golang.org/x/sys from 0.22.0 to 0.23.0 in the go-modules group (#381)
Bumps the go-modules group with 1 update: [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sys` from 0.22.0 to 0.23.0
- [Commits](https://github.com/golang/sys/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 10:41:44 -04:00
dependabot[bot]
51d1c6c3c5
Bump golang.org/x/sys from 0.21.0 to 0.22.0 in the go-modules group (#378) 2024-07-08 07:56:01 -07:00
Brandon Weeks
0c084813e6
Configured Dependabot grouped updates (#376) 2024-06-10 18:00:21 +00:00
dependabot[bot]
5d68dfee1b
Bump github.com/google/go-tpm from 0.9.0 to 0.9.1 (#375)
Bumps [github.com/google/go-tpm](https://github.com/google/go-tpm) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/google/go-tpm/releases)
- [Commits](https://github.com/google/go-tpm/compare/v0.9.0...v0.9.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 09:58:30 -07:00
dependabot[bot]
b7a5927d66
Bump golang.org/x/sys from 0.20.0 to 0.21.0 (#374)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/sys/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 08:58:12 -07:00
dependabot[bot]
b36ec6af0a
Bump golang.org/x/sys from 0.19.0 to 0.20.0 (#373)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/sys/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 08:34:28 -07:00
whongda
0722a4900b
Support sha384 RSA signature (#372)
Add signature algorithm details for sha384 RSA signature so certificates
using it can has its signature algorithm properly parsed
2024-04-15 15:05:48 -07:00
dependabot[bot]
545501297e
Bump golang.org/x/sys from 0.18.0 to 0.19.0 (#371)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/sys/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 15:29:20 -04:00
dependabot[bot]
5148956a0c
Bump github.com/google/go-tpm-tools from 0.4.3 to 0.4.4 (#369)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Changelog](https://github.com/google/go-tpm-tools/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 08:15:39 -07:00
zhsh
a9866d34bb
A note for Linux clients of tpm.EKs(). (#368)
Fix the doc for attest.ActivateCredentialWithEK() as well.
2024-03-27 09:09:08 -07:00
Liam Murphy
5b3763098f
Add critical bool arg to MarshalSubjectAltName to allow SANs to be critical (#367) 2024-03-26 23:34:46 +00:00
dependabot[bot]
e6ab626979
Bump github.com/google/go-tpm-tools from 0.4.2 to 0.4.3 (#364)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Changelog](https://github.com/google/go-tpm-tools/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.4.2...v0.4.3)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-26 16:55:42 +00:00
dependabot[bot]
52542411c5
Bump golang.org/x/sys from 0.16.0 to 0.18.0 (#365)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.16.0 to 0.18.0.
- [Commits](https://github.com/golang/sys/compare/v0.16.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-26 09:53:17 -07:00
dependabot[bot]
02cf9e2ddd
Bump golang.org/x/sys from 0.15.0 to 0.16.0 (#360)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/sys/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 09:25:02 -08:00
dependabot[bot]
8b301f2d45
Bump golang.org/x/crypto from 0.13.0 to 0.17.0 (#359)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.13.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.13.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 15:34:50 -08:00
dependabot[bot]
3d017c0234
Bump golang.org/x/sys from 0.14.0 to 0.15.0 (#358)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/sys/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 10:58:25 -08:00
Marcin Wielgoszewski
a3545dfc94
don't drop leading zeroes when performing generic ecdsa signing (#357)
call ret.FillBytes() instead of ret.Bytes() to preserve leading zeroes that may have been dropped when converting the digest to an integer
2023-11-09 08:39:58 -08:00
dependabot[bot]
93c5899459
Bump golang.org/x/sys from 0.13.0 to 0.14.0 (#356)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/sys/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 07:47:43 -08:00
dependabot[bot]
74a49366bd
Bump github.com/google/go-tpm-tools from 0.4.1 to 0.4.2 (#355)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Changelog](https://github.com/google/go-tpm-tools/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.4.1...v0.4.2)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 09:14:26 -07:00
dependabot[bot]
776dc3ac22
Bump golang.org/x/sys from 0.12.0 to 0.13.0 (#353)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/sys/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 16:58:28 +00:00
dependabot[bot]
136789e2e1
Bump github.com/google/go-cmp from 0.5.9 to 0.6.0 (#354)
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.9 to 0.6.0.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.9...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 09:55:31 -07:00
dependabot[bot]
82eb5d47a2
Bump github.com/google/go-tpm-tools from 0.4.0 to 0.4.1 (#352)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.4.0 to 0.4.1.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Changelog](https://github.com/google/go-tpm-tools/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.4.0...v0.4.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 08:32:24 -07:00
dependabot[bot]
f4ab877258
Bump github.com/google/go-tpm-tools (#351)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.3.13-0.20230620182252-4639ecce2aba to 0.4.0.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Changelog](https://github.com/google/go-tpm-tools/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/google/go-tpm-tools/commits/v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 08:03:38 -07:00
Herman Slatman
3d71f101b1
Fix Intel EK certificate URLs on Linux (#347) 2023-09-08 18:23:49 +00:00
Damien Miller
42c11fc152
Fix typo (#349) 2023-09-08 17:46:44 +00:00
dependabot[bot]
3c84bff65e
Bump golang.org/x/sys from 0.9.0 to 0.12.0 (#348)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.9.0 to 0.12.0.
- [Commits](https://github.com/golang/sys/compare/v0.9.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-08 17:42:41 +00:00
Brandon Weeks
ab5dee2ae5
ci: don't install OpenSSL 1.1 on macOS (#350)
GitHub actions runner macos-13 version 20230801.2 appears to include
this by default, causing a link failure.

da18545f2f
2023-09-08 10:33:41 -07:00
zhsh
046550658b
attest: Create keys under non-default SRKs (#342) 2023-07-11 10:14:13 -04:00
zhsh
310e2caafe
attest: Remove the EK field from AK struct (#341) 2023-06-29 07:53:53 -07:00
zhsh
60adf13bc0
attest: ActivateCredentialWithEK() method that can be used with non-default EKs. (#340) 2023-06-28 20:38:36 -07:00
zhsh
a56e8c4896
Activate AK with ECC EKs. (#339) 2023-06-27 20:02:47 -07:00
smo4201
8af5f4e7de
attest: Make PCRs included in quote configurable (#311)
Change the low-level Quote() functions so that the PCRs to be
included in the quote is selectable. Does not change the
high-level attestPlatform functions, which still retrieve
all PCRs.
2023-06-26 23:04:59 +00:00
zhsh
b92d1c69bf
Add TPM.EKCertificates() method, it returns all certificates from TPM's NVRAM (#333) 2023-06-23 15:10:34 -07:00
zhsh
d29df30553
Add EK as a field to AK struct. (#332)
The change is a no-op for existing clients, and it will simplify
adding the support for ECC EKs. The activation code no longer makes
assumptions about EK's type and handle (i.e. RSA and 0x81010001),
and instead relies on TPM.EKs() to provide the EK's details.
2023-06-22 13:17:47 -07:00
Brandon Weeks
63dd90f699
Bump github.com/google/go-tpm from 0.3.4 to 0.9.0 (#337) 2023-06-21 16:18:54 +02:00
dependabot[bot]
ac9aa2497f
Bump golang.org/x/sys from 0.8.0 to 0.9.0 (#335)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/sys/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-20 16:40:32 +00:00
Ludovic Fernandez
2788b541c7
Fix golangci-lint workflow (#336) 2023-06-20 09:35:01 -07:00
Chris Fenner
a9b6eb1eb8
use legacy tpm2 at its new path (#331) 2023-06-13 07:43:38 -07:00
zhsh
50c1e1e03b
Renamed some variables and methods to highlight that only RSA EKs are (#330)
currently supported.

This is the first step towards supporting ECC EKs.
2023-06-12 18:36:51 -07:00
juanvallejo
258084d04e Add support for generating TPM2.0 challenges using AttestedCertifyInfo
Fixes: issues/320.

Adds support for generating an activation challenge using
CertificationParameters.
Achieves symmetry with challenge-generation in
AttestationParameters, in order to provide a challenge to a
TPM to activate a TPM-certified key.

`attest.Activation` currently supports verifying and
generating a challenge given attestationData, an EK, an AK,
and a signature. In the attestationData, the CreationInfo
field is used to further validate and create the resulting
challenge.
In this change, `attest.Certification` will now support
generating a challenge given attestationData, an EK, a
TPM-certified public key, and a signature, in addition to
an AK used to verify the certification of the provided
public key we are generating an activation challenge for.
2023-06-06 10:46:12 -07:00
Herman Slatman
89884d0a74
Fix Intel EK certificate URL (#310)
* Fix Intel EK certificate URL

To download the certificate for an Intel TPM, the base64 padding
in the URL needs to be replaced with `%3D`. If it's not replaced,
requesting the URL will result in HTTP 403 Forbidden.

* Use `url.QueryEscape` to escape base64 padding
2023-06-02 09:17:59 -07:00
zhsh
b474b712d4
wrappedTPM20.ekTemplate() never returns an error. (#327) 2023-05-29 10:16:09 -07:00
dependabot[bot]
a4b579bcf0
Bump github.com/google/go-tpm-tools from 0.3.9 to 0.3.12 (#324)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.3.9 to 0.3.12.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.3.9...v0.3.12)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 11:14:50 -07:00
dependabot[bot]
62a036b369
Bump golang.org/x/sys from 0.0.0-20220209214540-3681064d5158 to 0.8.0 (#316)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20220209214540-3681064d5158 to 0.8.0.
- [Commits](https://github.com/golang/sys/commits/v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 11:03:00 -07:00
Brandon Weeks
10dd5f7a05
Bump Go version to 1.19 (#325) 2023-05-22 10:52:09 -07:00
Noah Stride
3ef3949b46 Fix comments referring to .Serialize() instead of .Marshal() 2023-05-15 10:56:35 -07:00
José Martínez
1f9c436d57 Parse TCG_PCR_EVENT2 structures with an eventSize of 0 2023-05-15 09:19:59 -07:00
dependabot[bot]
270ecbab1f
Bump github.com/google/go-tspi (#307)
Bumps [github.com/google/go-tspi](https://github.com/google/go-tspi) from 0.2.1-0.20190423175329-115dea689aad to 0.3.0.
- [Release notes](https://github.com/google/go-tspi/releases)
- [Commits](https://github.com/google/go-tspi/commits/v0.3.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-tspi
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-10 10:28:28 -08:00
Brandon Weeks
0ccbb50494
Handle multiple ELAM events (#309) 2023-03-08 13:32:50 -08:00
Mike Gerow
68deb4ce55 Use NV cert index as auth hierarchy for EK cert
This is the same approach tpm2_getekcertificate uses, with its
`TPM2_HANDLE_FLAGS_NV` flag.

The main impetus here is is ChromeOS's vtpm implementation[1], which
doesn't have a concept of an "owner" or "platform" password and expects
the NV index itself as the auth hierarchy. In either case, as this is
the same approach tpm2_getekcertificate uses this should provide a more
standard/common approach as opposed to relying on the owner password to
be empty.

Tested with both CrOS's vTPM and a real TPM on Debian.

b/258300352

[1]: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/vtpm/commands/nv_read_command.cc;l=64-68;drc=1efd0c8f36050d56b8550354a4c7af925e44118a
2023-01-05 12:25:14 -08:00
Marcin Wielgoszewski
5238453493 Truncate digests to the left most bits to match the bit-length of the order of the curve 2022-11-15 15:26:00 -08:00
Mike Gerow
b93151db1f
Preserve error logic in getPrimaryKeyHandle (#296)
In `wrappedTPM20.getPrimaryKeyHandle()`, preserve any error from the
short-circuit `tpm2.ReadPublic()` logic, so that we can return it
alongside any failure in `tpm2.CreatePrimary()`

Co-authored-by: Justin King-Lacroix <justinkl@google.com>
2022-11-04 14:57:37 -07:00
Brandon Weeks
0dc056af7d Fix golangci-lint findings 2022-11-01 13:38:49 -07:00
Brandon Weeks
19d3c4de97 Run golangci-lint as part of CI
https://golangci-lint.run/usage/install/#ci-installation
2022-11-01 13:38:49 -07:00
Brandon Weeks
438907edb0
Fix lints; run gofmt (#293)
$ gofmt -s -w .
2022-11-01 12:19:57 -07:00
hansinator
17f9c05652
fix returning wrong error in ParseWinEvents (#291)
Co-authored-by: Hans-Gert Dahmen <hans-gert.dahmen@immu.ne>
2022-10-11 09:22:10 -07:00
hansinator
d98599d257
Fix decoding of uints in windows events (#290)
Co-authored-by: Hans-Gert Dahmen <hans-gert.dahmen@immu.ne>
2022-10-07 13:01:04 -07:00
dependabot[bot]
053c50e8ad
Bump github.com/google/go-cmp from 0.5.8 to 0.5.9 (#286)
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.8 to 0.5.9.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.8...v0.5.9)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 11:14:05 -04:00
Malte Poll
e99c3e104e
Ignore MokListTrusted events in ParseUEFIVariableAuthority (#284) 2022-09-09 15:58:48 -07:00
dependabot[bot]
dff2daeaf0
Bump github.com/google/go-tpm-tools from 0.3.8 to 0.3.9 (#285) 2022-08-22 19:31:56 +00:00
Brandon Weeks
f5d560164e
Set NoDa flag on the AK template (#280)
Resolves an issue where a TPM in DA lockout mode cannot generate an AK.
2022-06-03 12:51:56 -07:00
Brandon Weeks
cb976082a3
x509ext: initial version of package (#279) 2022-06-02 15:05:51 -07:00
Brandon Weeks
50e72a4743
attest: fix OSS-Fuzz build (#278) 2022-05-31 21:50:58 -07:00
Brandon Weeks
f1ff544e51
attest: restore change from a35bd36 mistakingly removed in be496f1 (#277) 2022-05-31 13:12:21 -07:00
dependabot[bot]
e0bd974e4e
Bump github.com/google/go-cmp from 0.5.7 to 0.5.8 (#275)
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.7 to 0.5.8.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.7...v0.5.8)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-16 12:44:25 -07:00
dependabot[bot]
ad58dc770e
Bump github.com/google/go-tpm-tools from 0.3.7 to 0.3.8 (#276)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.3.7...v0.3.8)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-16 12:44:06 -07:00
dependabot[bot]
8235370483
Bump github.com/google/go-tpm-tools from 0.3.1 to 0.3.7 (#273)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.3.1 to 0.3.7.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.3.1...v0.3.7)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-18 08:39:55 -07:00
Joe Richey
8820d49b18 CI: Allow SHA1 on Go 1.18
Signed-off-by: Joe Richey <joerichey@google.com>
2022-04-04 13:48:39 -07:00
Joe Richey
0961a88d7c parseEfiSignature: Don't rely on type of error code
The specific error type is not part of x509.ParseCertificate documented
API. So we shouldn't rely on it for this workaround.

Signed-off-by: Joe Richey <joerichey@google.com>
2022-04-04 13:48:39 -07:00
Joe Richey
df6b91cbdb test: Use Fatalf instead of Errorf to prevent segfault
Signed-off-by: Joe Richey <joerichey@google.com>
2022-04-04 13:48:39 -07:00
Joe Richey
03018e6828 Remove certificate-transparency-go dependancy
Signed-off-by: Joe Richey <joerichey@google.com>
2022-04-04 13:48:39 -07:00
Joe Richey
0a9ecdcf7c Run CI for Go 1.18
Signed-off-by: Joe Richey <joerichey@google.com>
2022-03-25 13:55:33 -07:00
Joe Richey
4b44082d2c ci: ONly run on pushes to master
This prevents running the CI twice when opening a PR with a non-master
branch.

Signed-off-by: Joe Richey <joerichey@google.com>
2022-03-25 13:55:33 -07:00
dependabot[bot]
2a5dfec7cf
Bump github.com/google/go-cmp from 0.5.5 to 0.5.7 (#261)
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.5 to 0.5.7.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.5...v0.5.7)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-23 21:08:15 -07:00
Jiankun Lü
83d71b1c53
Bump go-tpm version (#264)
Certify now returns raw TPMT_SIGNATURE, so no need to pack it.
2022-02-14 16:31:48 -08:00
Tom D
277c40ca1d
AKPublic.VerifyAll: Additionally validate input parameters (#263) 2022-01-31 09:32:19 -08:00
Tom D
82f2c9c2c7
Merge pull request from GHSA-99cg-575x-774p
* AKPublic.Verify: Return an error if a provided PCR of the correct
   digest was not included in the quote.
 * AKPublic.VerifyAll: Implement VerifyAll method, which can cross-check
   that provided PCRs were covered by quotes across PCR banks.
 * PCR.QuoteVerified(): Introduce getter method to expose whether a
   PCR value was covered during quote verification.
2022-01-31 09:10:07 -08:00
dependabot[bot]
21f642c3c7 Copybara import of the project:
--
54a86af398f1d232ac8566821f9587b4ba896827 by dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>:

Bump github.com/google/go-tpm-tools from 0.2.1 to 0.3.1

Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.2.1 to 0.3.1.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.2.1...v0.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/go-attestation/pull/250 from google:dependabot/go_modules/github.com/google/go-tpm-tools-0.3.1 54a86af398f1d232ac8566821f9587b4ba896827
PiperOrigin-RevId: 415136926
2022-01-11 16:29:12 -08:00
Brandon Weeks
d114f3922f Copybara import of the project:
--
501de37b33eb18eb81a382b92c85fe3a65d0f9b3 by Brandon Weeks <bweeks@google.com>:

Restore changes accidentally reverted during reconciliation

COPYBARA_INTEGRATE_REVIEW=https://github.com/google/go-attestation/pull/256 from brandonweeks:fix_reconciliation 501de37b33eb18eb81a382b92c85fe3a65d0f9b3
PiperOrigin-RevId: 415128139
2022-01-11 16:29:01 -08:00
dependabot[bot]
b92e2746d6
Bump github.com/google/go-tpm-tools from 0.2.1 to 0.3.1 (#250)
Bumps [github.com/google/go-tpm-tools](https://github.com/google/go-tpm-tools) from 0.2.1 to 0.3.1.
- [Release notes](https://github.com/google/go-tpm-tools/releases)
- [Commits](https://github.com/google/go-tpm-tools/compare/v0.2.1...v0.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/go-tpm-tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-08 17:24:29 -08:00
Brandon Weeks
2f8dbfc94e
Restore changes accidentally reverted during reconciliation (#256) 2021-12-08 16:43:38 -08:00
copybara-service[bot]
f1f1b84491
Revert "Internal change"
PiperOrigin-RevId: 415106054

Co-authored-by: Brandon Weeks <bweeks@google.com>
2021-12-08 15:06:48 -08:00
Brandon Weeks
57a6cb587a Internal change
PiperOrigin-RevId: 415099842
2021-12-08 14:37:13 -08:00
Tom D'Netto
0393b91867 Implement CombineEventlogs().
PiperOrigin-RevId: 410914994
2021-11-18 15:36:36 -08:00
Brandon Weeks
be496f1149 Internal change
PiperOrigin-RevId: 394330027
2021-09-01 15:39:03 -07:00
Eric Chiang
a35bd36e42
attest: fix test build for MacOS (#241)
Windows still requires openssl due to tpm-tools simulator. Will try to
figure out that next.
2021-09-01 13:24:57 -07:00
Alex Wu
505680f536
Invert 'notspi' build tag to 'tspi' (#237)
This change allows users to specify TPM1.2 support rather than remove it.
go-attestation will build without needing Trousers/TSPI support.
The flip-side of this is that TPM1.2 does not just work; TPM1.2 users need to
include the `tspi` build tag.
2021-09-01 12:55:02 -07:00
Eric Chiang
7cf0af2beb
.github: add initial github action for CI (#239)
Goal is to switch current builder run internally by Google over to
GitHub Actions.
2021-09-01 11:15:26 -07:00
copybara-service[bot]
5410759ddc
Consider a nonce in NVRAM when computing the EK Template (Fixes #236). (#238)
PiperOrigin-RevId: 394112776

Co-authored-by: Tom D'Netto <jsonp@google.com>
2021-08-31 17:45:37 -07:00
Tom D
cc52e2d143
Handle EFI_ACTION events signalling DMA protection is disabled. (#235) 2021-08-23 14:03:58 -07:00
Timo Lindfors
7d128657ca Fix misleading comment 2021-08-10 12:18:55 -07:00
Timo Lindfors
e8c5dc4fd5 Fix minor spelling issues in comments 2021-08-10 12:18:55 -07:00
tracefinder
5df8a8e979
Add a build tag to turn off TPM12 support and avoid tspi dependency (#232)
* Add build tag to turn off TPM12 support and avoid tspi dependency

* Add notspi build flag related information in README.md
2021-07-30 12:26:45 -07:00
dependabot[bot]
9ff0d31d3c
Bump github.com/google/go-cmp from 0.5.5 to 0.5.6 (#221)
Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.5...v0.5.6)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-02 11:17:17 -07:00
Eric Chiang
fa6830fc2f
update go-tspi dependency (#231)
This fixes go-attestation builds on 32 bit architectures.
2021-07-02 10:45:08 -07:00
Brandon Weeks
7ec6228f59 Rollback using certificatetransparency/x509 for the CI code
PiperOrigin-RevId: 380897258
2021-06-22 14:48:41 -07:00
copybara-service[bot]
bec58f2406
Internal change (#227)
PiperOrigin-RevId: 380891920

Co-authored-by: Brandon Weeks <bweeks@google.com>
2021-06-22 14:33:47 -07:00
Go-Attestation Team
20a9e4b381 Internal change
PiperOrigin-RevId: 380881515
2021-06-22 20:41:11 +00:00
Tom D
1b4849d2c3
Make possibly-missing WBCL values ternary-typed (#226) 2021-06-21 14:10:45 -07:00
Alex Wu
0a3c6e82bf
Ignore SBAT events in ParseUEFIVariableAuthority (#222)
As part of the Boothole fixes, shim has introduced an
SBAT feature https://github.com/rhboot/shim/blob/main/SBAT.md.
SBAT configuration is configured to log to PCR7 using
EV_EFI_VARIABLE_AUTHORITY.
493bd940e5/mok.c (L228-L247)

This causes issue with ParseUEFIVariableAuthority, as
it asssumes that an event with type EV_EFI_VARIABLE_AUTHORITY
can be parsed as EFI_SIGNATURE_DATA, per section 3.3.4.8
of the TCG PC Client Platform Firmware Profile Specification.
2021-06-03 14:28:24 -07:00
Paweł Szałachowski
c4760bd1c6
Validate the RSA-PSS salt length argument. (#219) 2021-05-21 15:28:56 -07:00
Paweł Szałachowski
0b7298fb18
Support RSA application keys (#218) 2021-05-20 11:15:09 -07:00
Paweł Szałachowski
7f6fec6b36
add ecdsa configuration options (#217)
Add configuration options for ECDSA key generation.
2021-05-19 11:32:54 -07:00
Tom D
ee5bb94c43
WIP processing image load events (#216) 2021-05-10 12:11:58 -07:00
Paweł Szałachowski
9b857465d0
Handle to interface{} in *windowsKey12.certify() (#214) 2021-04-23 16:13:10 -07:00
Paweł Szałachowski
6848928436
Add AK.Certify() and use CertifyEx() for certification (#210)
* replace CertifyCreation() by CertifyEx() to handle certification of objects for which we cannot extract CreationData
* add AK.Certify(handle) allowing to certify externally-created keys
2021-04-23 14:41:30 -07:00
Tom D
e24a847d44
Add initial docs for attest-tool (#213) 2021-04-15 12:14:18 -07:00
52 changed files with 3825 additions and 751 deletions

View File

@ -4,3 +4,16 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
groups:
"Go modules":
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
github-actions:
patterns:
- "*"

View File

@ -1,5 +1,9 @@
name: "CodeQL" name: "CodeQL"
permissions:
contents: read
security-events: write
on: on:
push: push:
branches: [master] branches: [master]
@ -20,7 +24,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
@ -28,12 +32,12 @@ jobs:
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v3

18
.github/workflows/golangci-lint.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: golangci-lint
on:
pull_request:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.24.x
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.64.6

71
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,71 @@
on:
push:
branches: [master]
pull_request:
# Workaround for SHA1 on Go 1.18. There are some kinks to be worked out. See
# the tracking issue for more info: https://github.com/golang/go/issues/41682
env:
GODEBUG: x509sha1=1
name: Test
jobs:
test-linux:
strategy:
matrix:
go-version: [1.24.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: go test ./...
test-linux-tpm12:
strategy:
matrix:
go-version: [1.24.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Install libtspi
run: sudo apt-get install -y libtspi-dev
- name: Test
run: go test -tags tspi ./...
test-macos:
strategy:
matrix:
go-version: [1.24.x]
runs-on: macos-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
# See https://github.com/google/go-tpm-tools#macos-dev
- name: Test
run: C_INCLUDE_PATH="$(brew --prefix openssl@1.1)/include" LIBRARY_PATH="$(brew --prefix openssl@1.1)/lib" go test ./...
test-windows:
strategy:
matrix:
go-version: [1.24.x]
runs-on: windows-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: go build ./...

5
.golangci.yaml Normal file
View File

@ -0,0 +1,5 @@
linters:
enable:
- gofmt
disable:
- errcheck

View File

@ -15,16 +15,24 @@ Talks on this project:
## Status ## Status
Go-Attestation is under active development and **is not** ready for production use. Expect Go-Attestation is under active development. Expect
API changes at any time. API changes at any time.
Please note that this is not an official Google product. Please note that this is not an official Google product.
TPM 1.2 support is best effort, meaning we will accept fixes for TPM 1.2, but
testing is not covered by CI.
## Installation ## Installation
The go-attestation package is installable using go get: `go get github.com/google/go-attestation/attest` The go-attestation package is installable using go get: `go get github.com/google/go-attestation/attest`
Linux users must install `libtspi` and its headers. This can be installed on debian-based systems using: `sudo apt-get install libtspi-dev`. ### TPM1.2
By default, go-attestation does not build in TPM1.2 support on Linux.
Linux users must install [`libtspi`](http://trousers.sourceforge.net/) and its headers if they need TPM 1.2 support. This can be installed on debian-based systems using: `sudo apt-get install libtspi-dev`.
Then, build go-attestation with the `tspi` [build tag](https://pkg.go.dev/go/build#hdr-Build_Constraints) `go build --tags=tspi`.
Windows users can use go-attestation with TPM1.2 by default.
## Example: device identity ## Example: device identity
@ -73,7 +81,7 @@ if err != nil {
// handle error // handle error
} }
if err := ioutil.WriteFile("encrypted_aik.json", akBytes, 0600); err != nil { if err := os.WriteFile("encrypted_aik.json", akBytes, 0600); err != nil {
// handle error // handle error
} }
@ -107,7 +115,7 @@ returning the same secret to the server.
```go ```go
// Client decrypts the credential // Client decrypts the credential
akBytes, err := ioutil.ReadFile("encrypted_aik.json") akBytes, err := os.ReadFile("encrypted_aik.json")
if err != nil { if err != nil {
// handle error // handle error
} }

View File

@ -9,11 +9,11 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/google/go-tpm/legacy/tpm2"
tpm1 "github.com/google/go-tpm/tpm" tpm1 "github.com/google/go-tpm/tpm"
"github.com/google/go-tpm/tpm2"
// TODO(jsonp): Move activation generation code to internal package. // TODO(jsonp): Move activation generation code to internal package.
"github.com/google/go-tpm/tpm2/credactivation" "github.com/google/go-tpm/legacy/tpm2/credactivation"
"github.com/google/go-tspi/verification" "github.com/google/go-tspi/verification"
) )
@ -36,11 +36,11 @@ type ActivationParameters struct {
// TPMVersion holds the version of the TPM, either 1.2 or 2.0. // TPMVersion holds the version of the TPM, either 1.2 or 2.0.
TPMVersion TPMVersion TPMVersion TPMVersion
// EK, the endorsement key, describes an asymmetric key who's // EK, the endorsement key, describes an asymmetric key whose
// private key is permenantly bound to the TPM. // private key is permanently bound to the TPM.
// //
// Activation will verify that the provided EK is held on the same // Activation will verify that the provided EK is held on the same
// TPM as the AK. However, it is the callers responsibility to // TPM as the AK. However, it is the caller's responsibility to
// ensure the EK they provide corresponds to the the device which // ensure the EK they provide corresponds to the the device which
// they are trying to associate the AK with. // they are trying to associate the AK with.
EK crypto.PublicKey EK crypto.PublicKey

View File

@ -26,7 +26,7 @@ type key interface {
close(tpmBase) error close(tpmBase) error
marshal() ([]byte, error) marshal() ([]byte, error)
certificationParameters() CertificationParameters certificationParameters() CertificationParameters
sign(tpmBase, []byte) ([]byte, error) sign(tpmBase, []byte, crypto.PublicKey, crypto.SignerOpts) ([]byte, error)
decrypt(tpmBase, []byte) ([]byte, error) decrypt(tpmBase, []byte) ([]byte, error)
blobs() ([]byte, []byte, error) blobs() ([]byte, []byte, error)
} }
@ -48,7 +48,7 @@ type signer struct {
// Sign signs digest with the TPM-stored private signing key. // Sign signs digest with the TPM-stored private signing key.
func (s *signer) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { func (s *signer) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
return s.key.sign(s.tpm, digest) return s.key.sign(s.tpm, digest, s.pub, opts)
} }
// Public returns the public key corresponding to the private signing key. // Public returns the public key corresponding to the private signing key.
@ -56,9 +56,36 @@ func (s *signer) Public() crypto.PublicKey {
return s.pub return s.pub
} }
// KeyConfig encapsulates parameters for minting keys. This type is defined // Algorithm indicates an asymmetric algorithm to be used.
// now (despite being empty) for future interface compatibility. type Algorithm string
// Algorithm types supported.
const (
ECDSA Algorithm = "ECDSA"
RSA Algorithm = "RSA"
)
// KeyConfig encapsulates parameters for minting keys.
type KeyConfig struct { type KeyConfig struct {
// Algorithm to be used, either RSA or ECDSA.
Algorithm Algorithm
// Size is used to specify the bit size of the key or elliptic curve. For
// example, '256' is used to specify curve P-256.
Size int
// Parent describes the Storage Root Key that will be used as a parent.
// If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed.
// Supported only by TPM 2.0 on Linux.
Parent *ParentKeyConfig
// QualifyingData is an optional data that will be included into
// a TPM-generated signature of the minted key.
// It may contain any data chosen by the caller.
QualifyingData []byte
}
// defaultConfig is used when no other configuration is specified.
var defaultConfig = &KeyConfig{
Algorithm: ECDSA,
Size: 256,
} }
// Public returns the public key corresponding to the private key. // Public returns the public key corresponding to the private key.

View File

@ -12,8 +12,10 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build (!localtest || !tpm12) && cgo && !gofuzz
// +build !localtest !tpm12 // +build !localtest !tpm12
// +build cgo // +build cgo
// +build !gofuzz
// NOTE: simulator requires cgo, hence the build tag. // NOTE: simulator requires cgo, hence the build tag.
@ -23,7 +25,9 @@ import (
"bytes" "bytes"
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"math/big" "math/big"
@ -53,7 +57,68 @@ func testKeyCreateAndLoad(t *testing.T, tpm *TPM) {
if err != nil { if err != nil {
t.Fatalf("NewAK() failed: %v", err) t.Fatalf("NewAK() failed: %v", err)
} }
sk, err := tpm.NewKey(ak, nil) for _, test := range []struct {
name string
opts *KeyConfig
}{
{
name: "default",
opts: nil,
},
{
name: "ECDSAP256-SHA256",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 256,
},
},
{
name: "ECDSAP384-SHA384",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 384,
},
},
{
name: "ECDSAP521-SHA512",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 521,
},
},
{
name: "RSA-1024",
opts: &KeyConfig{
Algorithm: RSA,
Size: 1024,
},
},
{
name: "RSA-2048",
opts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
},
{
name: "QualifyingData-RSA",
opts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
QualifyingData: []byte("qualifying data"),
},
},
{
name: "QualifyingData-ECDSA",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 256,
QualifyingData: []byte("qualifying data"),
},
},
} {
t.Run(test.name, func(t *testing.T) {
sk, err := tpm.NewKey(ak, test.opts)
if err != nil { if err != nil {
t.Fatalf("NewKey() failed: %v", err) t.Fatalf("NewKey() failed: %v", err)
} }
@ -109,6 +174,8 @@ func testKeyCreateAndLoad(t *testing.T, tpm *TPM) {
if !bytes.Equal(pk1, pk2) { if !bytes.Equal(pk1, pk2) {
t.Error("public keys do not match") t.Error("public keys do not match")
} }
})
}
} }
func TestSimTPM20KeySign(t *testing.T) { func TestSimTPM20KeySign(t *testing.T) {
@ -134,7 +201,148 @@ func testKeySign(t *testing.T, tpm *TPM) {
if err != nil { if err != nil {
t.Fatalf("NewAK() failed: %v", err) t.Fatalf("NewAK() failed: %v", err)
} }
sk, err := tpm.NewKey(ak, nil)
for _, test := range []struct {
name string
keyOpts *KeyConfig
signOpts crypto.SignerOpts
digest []byte
}{
{
name: "default",
keyOpts: nil,
signOpts: nil,
digest: []byte("12345678901234567890123456789012"),
},
{
name: "ECDSAP256-SHA256",
keyOpts: &KeyConfig{
Algorithm: ECDSA,
Size: 256,
},
signOpts: nil,
digest: []byte("12345678901234567890123456789012"),
},
{
name: "ECDSAP384-SHA384",
keyOpts: &KeyConfig{
Algorithm: ECDSA,
Size: 384,
},
signOpts: nil,
digest: []byte("123456789012345678901234567890121234567890123456"),
},
{
name: "ECDSAP521-SHA512",
keyOpts: &KeyConfig{
Algorithm: ECDSA,
Size: 521,
},
signOpts: nil,
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
},
{
name: "RSA2048-PKCS1v15-SHA256",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: crypto.SHA256,
digest: []byte("12345678901234567890123456789012"),
},
{
name: "RSA2048-PKCS1v15-SHA384",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: crypto.SHA384,
digest: []byte("123456789012345678901234567890121234567890123456"),
},
{
name: "RSA2048-PKCS1v15-SHA512",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: crypto.SHA512,
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
},
{
name: "RSA2048-PSS-SHA256",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA256,
},
digest: []byte("12345678901234567890123456789012"),
},
{
name: "RSA2048-PSS-SHA384",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA384,
},
digest: []byte("123456789012345678901234567890121234567890123456"),
},
{
name: "RSA2048-PSS-SHA512",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
Hash: crypto.SHA512,
},
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
},
{
name: "RSA2048-PSS-SHA256, explicit salt len",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: &rsa.PSSOptions{
SaltLength: 32,
Hash: crypto.SHA256,
},
digest: []byte("12345678901234567890123456789012"),
},
{
name: "RSA2048-PSS-SHA384, explicit salt len",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: &rsa.PSSOptions{
SaltLength: 48,
Hash: crypto.SHA384,
},
digest: []byte("123456789012345678901234567890121234567890123456"),
},
{
name: "RSA2048-PSS-SHA512, explicit salt len",
keyOpts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
signOpts: &rsa.PSSOptions{
SaltLength: 64,
Hash: crypto.SHA512,
},
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
},
} {
t.Run(test.name, func(t *testing.T) {
sk, err := tpm.NewKey(ak, test.keyOpts)
if err != nil { if err != nil {
t.Fatalf("NewKey() failed: %v", err) t.Fatalf("NewKey() failed: %v", err)
} }
@ -149,18 +357,27 @@ func testKeySign(t *testing.T, tpm *TPM) {
if !ok { if !ok {
t.Fatalf("want crypto.Signer, got %T", priv) t.Fatalf("want crypto.Signer, got %T", priv)
} }
digest := []byte("12345678901234567890123456789012") sig, err := signer.Sign(rand.Reader, test.digest, test.signOpts)
sig, err := signer.Sign(rand.Reader, digest, nil)
if err != nil { if err != nil {
t.Fatalf("signer.Sign() failed: %v", err) t.Fatalf("signer.Sign() failed: %v", err)
} }
if test.keyOpts == nil || test.keyOpts.Algorithm == ECDSA {
verifyECDSA(t, pub, test.digest, sig)
} else {
verifyRSA(t, pub, test.digest, sig, test.signOpts)
}
})
}
}
func verifyECDSA(t *testing.T, pub crypto.PublicKey, digest, sig []byte) {
t.Helper()
parsed := struct{ R, S *big.Int }{} parsed := struct{ R, S *big.Int }{}
_, err = asn1.Unmarshal(sig, &parsed) _, err := asn1.Unmarshal(sig, &parsed)
if err != nil { if err != nil {
t.Fatalf("signature parsing failed: %v", err) t.Fatalf("signature parsing failed: %v", err)
} }
pubECDSA, ok := pub.(*ecdsa.PublicKey) pubECDSA, ok := pub.(*ecdsa.PublicKey)
if !ok { if !ok {
t.Fatalf("want *ecdsa.PublicKey, got %T", pub) t.Fatalf("want *ecdsa.PublicKey, got %T", pub)
@ -169,3 +386,159 @@ func testKeySign(t *testing.T, tpm *TPM) {
t.Fatalf("ecdsa.Verify() failed") t.Fatalf("ecdsa.Verify() failed")
} }
} }
func verifyRSA(t *testing.T, pub crypto.PublicKey, digest, sig []byte, opts crypto.SignerOpts) {
t.Helper()
pubRSA, ok := pub.(*rsa.PublicKey)
if !ok {
t.Fatalf("want *rsa.PublicKey, got %T", pub)
}
if pss, ok := opts.(*rsa.PSSOptions); ok {
if err := rsa.VerifyPSS(pubRSA, opts.HashFunc(), digest, sig, pss); err != nil {
t.Fatalf("rsa.VerifyPSS(): %v", err)
}
} else {
if err := rsa.VerifyPKCS1v15(pubRSA, opts.HashFunc(), digest, sig); err != nil {
t.Fatalf("signature verification failed: %v", err)
}
}
}
func TestSimTPM20KeyOpts(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
testKeyOpts(t, tpm)
}
func TestTPM20KeyOpts(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
testKeyOpts(t, tpm)
}
func testKeyOpts(t *testing.T, tpm *TPM) {
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("NewAK() failed: %v", err)
}
for _, test := range []struct {
name string
opts *KeyConfig
err bool
}{
{
name: "wrong alg",
opts: &KeyConfig{
Algorithm: "fake alg",
},
err: true,
},
{
name: "wrong size",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 1234,
},
err: true,
},
{
name: "default",
opts: nil,
err: false,
},
{
name: "ECDSAP256",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 256,
},
err: false,
},
{
name: "ECDSAP384",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 384,
},
err: false,
},
{
name: "ECDSAP521",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 521,
},
err: false,
},
{
name: "RSA-1024",
opts: &KeyConfig{
Algorithm: RSA,
Size: 1024,
},
err: false,
},
{
name: "RSA-2048",
opts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
err: false,
},
} {
t.Run(test.name, func(t *testing.T) {
sk, err := tpm.NewKey(ak, test.opts)
if !test.err && err != nil {
t.Fatalf("NewKey() failed: %v", err)
}
if test.err {
if err == nil {
sk.Close()
t.Fatalf("NewKey(): expected err != nil")
}
return
}
defer sk.Close()
expected := test.opts
if expected == nil {
expected = defaultConfig
}
switch pub := sk.Public().(type) {
case *ecdsa.PublicKey:
if expected.Algorithm != ECDSA {
t.Errorf("incorrect key type generated, expected %q, got EC", expected.Algorithm)
}
sizeToCurve := map[int]elliptic.Curve{
256: elliptic.P256(),
384: elliptic.P384(),
521: elliptic.P521(),
}
expectedCurve, ok := sizeToCurve[expected.Size]
if !ok {
t.Fatalf("cannot match curve to key size %d", expected.Size)
}
if expectedCurve != pub.Curve {
t.Errorf("incorrect curve, expected %v, got %v", expectedCurve, pub.Curve)
}
case *rsa.PublicKey:
if expected.Algorithm != RSA {
t.Errorf("incorrect key type, expected %q, got RSA", expected.Algorithm)
}
if pub.Size()*8 != expected.Size {
t.Errorf("incorrect key size, expected %d, got %d", expected.Size, pub.Size()*8)
}
default:
t.Errorf("unsupported key type: %T", pub)
}
})
}
}

View File

@ -0,0 +1,29 @@
# attest-tool
`attest-tool` is a simple utility to exercise attestation-related operations on your system.
## Building attest-tool
If your system has git and a [Go 1.15+ compiler](https://golang.org/dl/) installed, you can
install `attest-tool` from source by running the following commands:
```shell
git clone 'https://github.com/google/go-attestation' && cd go-attestation/attest/attest-tool
go build -o attest-tool ./ # compiled to ./attest-tool
```
## Testing attestation readiness
The main use-case of `attest-tool` is testing whether attestation works on the local system.
Once `attest-tool` has been built, you can run it in self-test mode like this:
```shell
./attest-tool self-test
```
After a few seconds, it should print out a 'PASS' message, or a 'FAIL' message with a
description of what went wrong.
On Linux, `attest-tool` either needs to be run as root, or granted access to the TPM (`/dev/tpmrm0`) device
& event log (`/sys/kernel/security/tpm0/binary_bios_measurements`)

View File

@ -6,16 +6,15 @@ import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-attestation/attest" "github.com/google/go-attestation/attest"
"github.com/google/go-attestation/attest/attest-tool/internal" "github.com/google/go-attestation/attest/attest-tool/internal"
) )
@ -83,7 +82,7 @@ func selftestCredentialActivation(tpm *attest.TPM, ak *attest.AK) error {
func selftestAttest(tpm *attest.TPM, ak *attest.AK) error { func selftestAttest(tpm *attest.TPM, ak *attest.AK) error {
// This nonce is used in generating the quote. As this is a selftest, // This nonce is used in generating the quote. As this is a selftest,
// its set to an arbitrary value. // it's set to an arbitrary value.
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}
pub, err := attest.ParseAKPublic(tpm.Version(), ak.AttestationParameters().Public) pub, err := attest.ParseAKPublic(tpm.Version(), ak.AttestationParameters().Public)
@ -141,7 +140,7 @@ func runCommand(tpm *attest.TPM) error {
fmt.Printf("Version: %d\n", info.Version) fmt.Printf("Version: %d\n", info.Version)
fmt.Printf("Interface: %d\n", info.Interface) fmt.Printf("Interface: %d\n", info.Interface)
fmt.Printf("VendorInfo: %x\n", info.VendorInfo) fmt.Printf("VendorInfo: %x\n", info.VendorInfo)
fmt.Printf("Manufactorer: %v\n", info.Manufacturer) fmt.Printf("Manufacturer: %v\n", info.Manufacturer)
case "make-ak", "make-aik": case "make-ak", "make-aik":
k, err := tpm.NewAK(nil) k, err := tpm.NewAK(nil)
@ -153,10 +152,10 @@ func runCommand(tpm *attest.TPM) error {
if err != nil { if err != nil {
return err return err
} }
return ioutil.WriteFile(*keyPath, b, 0644) return os.WriteFile(*keyPath, b, 0644)
case "quote": case "quote":
b, err := ioutil.ReadFile(*keyPath) b, err := os.ReadFile(*keyPath)
if err != nil { if err != nil {
return err return err
} }

View File

@ -16,7 +16,7 @@ package eventlog
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "os"
"testing" "testing"
"github.com/google/go-attestation/attest" "github.com/google/go-attestation/attest"
@ -24,7 +24,7 @@ import (
) )
func parseEvents(t *testing.T, testdata string) []attest.Event { func parseEvents(t *testing.T, testdata string) []attest.Event {
data, err := ioutil.ReadFile(testdata) data, err := os.ReadFile(testdata)
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }

View File

@ -3,7 +3,7 @@ package internal
import ( import (
"github.com/google/go-attestation/attest" "github.com/google/go-attestation/attest"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/legacy/tpm2"
) )
// Dump describes the layout of serialized information from the dump command. // Dump describes the layout of serialized information from the dump command.

View File

@ -17,13 +17,15 @@ package attest
import ( import (
"crypto" "crypto"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"strings"
"github.com/google/certificate-transparency-go/x509" "github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpm" "github.com/google/go-tpm/tpm"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/tpmutil"
) )
// TPMVersion is used to configure a preference in // TPMVersion is used to configure a preference in
@ -97,12 +99,25 @@ const (
keyEncodingParameterized keyEncodingParameterized
) )
// ParentKeyConfig describes the Storage Root Key that is used
// as a parent for new keys.
type ParentKeyConfig struct {
Algorithm Algorithm
Handle tpmutil.Handle
}
var defaultParentConfig = ParentKeyConfig{
Algorithm: RSA,
Handle: 0x81000001,
}
type ak interface { type ak interface {
close(tpmBase) error close(tpmBase) error
marshal() ([]byte, error) marshal() ([]byte, error)
activateCredential(tpm tpmBase, in EncryptedCredential) ([]byte, error) activateCredential(tpm tpmBase, in EncryptedCredential, ek *EK) ([]byte, error)
quote(t tpmBase, nonce []byte, alg HashAlg) (*Quote, error) quote(t tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error)
attestationParameters() AttestationParameters attestationParameters() AttestationParameters
certify(tb tpmBase, handle interface{}, opts CertifyOpts) (*CertificationParameters, error)
} }
// AK represents a key which can be used for attestation. // AK represents a key which can be used for attestation.
@ -124,11 +139,22 @@ func (k *AK) Marshal() ([]byte, error) {
} }
// ActivateCredential decrypts the secret using the key to prove that the AK // ActivateCredential decrypts the secret using the key to prove that the AK
// was generated on the same TPM as the EK. // was generated on the same TPM as the EK. This method can be used with TPMs
// that have the default EK, i.e. RSA EK with handle 0x81010001.
// //
// This operation is synonymous with TPM2_ActivateCredential. // This operation is synonymous with TPM2_ActivateCredential.
func (k *AK) ActivateCredential(tpm *TPM, in EncryptedCredential) (secret []byte, err error) { func (k *AK) ActivateCredential(tpm *TPM, in EncryptedCredential) (secret []byte, err error) {
return k.ak.activateCredential(tpm.tpm, in) return k.ak.activateCredential(tpm.tpm, in, nil)
}
// ActivateCredentialWithEK decrypts the secret using the key to prove that the AK
// was generated on the same TPM as the EK. This method can be used with TPMs
// that have an ECC EK. The 'ek' argument must be one of EKs returned from
// TPM.EKs() or TPM.EKCertificates().
//
// This operation is synonymous with TPM2_ActivateCredential.
func (k *AK) ActivateCredentialWithEK(tpm *TPM, in EncryptedCredential, ek EK) (secret []byte, err error) {
return k.ak.activateCredential(tpm.tpm, in, &ek)
} }
// Quote returns a quote over the platform state, signed by the AK. // Quote returns a quote over the platform state, signed by the AK.
@ -136,7 +162,16 @@ func (k *AK) ActivateCredential(tpm *TPM, in EncryptedCredential) (secret []byte
// This is a low-level API. Consumers seeking to attest the state of the // This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead. // platform should use tpm.AttestPlatform() instead.
func (k *AK) Quote(tpm *TPM, nonce []byte, alg HashAlg) (*Quote, error) { func (k *AK) Quote(tpm *TPM, nonce []byte, alg HashAlg) (*Quote, error) {
return k.ak.quote(tpm.tpm, nonce, alg) pcrs := make([]int, 24)
for pcr := range pcrs {
pcrs[pcr] = pcr
}
return k.ak.quote(tpm.tpm, nonce, alg, pcrs)
}
// QuotePCRs is like Quote() but allows the caller to select a subset of the PCRs.
func (k *AK) QuotePCRs(tpm *TPM, nonce []byte, alg HashAlg, pcrs []int) (*Quote, error) {
return k.ak.quote(tpm.tpm, nonce, alg, pcrs)
} }
// AttestationParameters returns information about the AK, typically used to // AttestationParameters returns information about the AK, typically used to
@ -145,9 +180,23 @@ func (k *AK) AttestationParameters() AttestationParameters {
return k.ak.attestationParameters() return k.ak.attestationParameters()
} }
// AKConfig encapsulates parameters for minting keys. This type is defined // Certify uses the attestation key to certify the key with `handle`. It returns
// now (despite being empty) for future interface compatibility. // certification parameters which allow to verify the properties of the attested
// key. Depending on the actual instantiation it can accept different handle
// types (e.g., tpmutil.Handle on Linux or uintptr on Windows).
func (k *AK) Certify(tpm *TPM, handle interface{}) (*CertificationParameters, error) {
return k.ak.certify(tpm.tpm, handle, CertifyOpts{})
}
// AKConfig encapsulates parameters for minting keys.
type AKConfig struct { type AKConfig struct {
// Parent describes the Storage Root Key that will be used as a parent.
// If nil, the default SRK (i.e. RSA with handle 0x81000001) is assumed.
// Supported only by TPM 2.0 on Linux.
Parent *ParentKeyConfig
// If not specified, the default algorithm (RSA) is assumed.
Algorithm Algorithm
} }
// EncryptedCredential represents encrypted parameters which must be activated // EncryptedCredential represents encrypted parameters which must be activated
@ -170,6 +219,16 @@ type PCR struct {
Index int Index int
Digest []byte Digest []byte
DigestAlg crypto.Hash DigestAlg crypto.Hash
// quoteVerified is true if the PCR was verified against a quote
// in a call to AKPublic.Verify or AKPublic.VerifyAll.
quoteVerified bool
}
// QuoteVerified returns true if the value of this PCR was previously
// verified against a Quote, in a call to AKPublic.Verify or AKPublic.VerifyAll.
func (p *PCR) QuoteVerified() bool {
return p.quoteVerified
} }
// EK is a burned-in endorcement key bound to a TPM. This optionally contains // EK is a burned-in endorcement key bound to a TPM. This optionally contains
@ -185,6 +244,9 @@ type EK struct {
// Public key. Clients or servers can perform an HTTP GET to this URL, and // Public key. Clients or servers can perform an HTTP GET to this URL, and
// use ParseEKCertificate on the response body. // use ParseEKCertificate on the response body.
CertificateURL string CertificateURL string
// The EK persistent handle.
handle tpmutil.Handle
} }
// AttestationParameters describes information about a key which is necessary // AttestationParameters describes information about a key which is necessary
@ -281,7 +343,12 @@ func ParseAKPublic(version TPMVersion, public []byte) (*AKPublic, error) {
// Verify is used to prove authenticity of the PCR measurements. It ensures that // Verify is used to prove authenticity of the PCR measurements. It ensures that
// the quote was signed by the AK, and that its contents matches the PCR and // the quote was signed by the AK, and that its contents matches the PCR and
// nonce combination. // nonce combination. An error is returned if a provided PCR index was not part
// of the quote. QuoteVerified() will return true on PCRs which were verified
// by a quote.
//
// Do NOT use this method if you have multiple quotes to verify: Use VerifyAll
// instead.
// //
// The nonce is used to prevent replays of Quote and PCRs and is signed by the // The nonce is used to prevent replays of Quote and PCRs and is signed by the
// quote. Some TPMs don't support nonces longer than 20 bytes, and if the // quote. Some TPMs don't support nonces longer than 20 bytes, and if the
@ -298,44 +365,64 @@ func (a *AKPublic) Verify(quote Quote, pcrs []PCR, nonce []byte) error {
} }
} }
// VerifyAll uses multiple quotes to verify the authenticity of all PCR
// measurements. See documentation on Verify() for semantics.
//
// An error is returned if any PCRs provided were not covered by a quote,
// or if no quote/nonce was provided.
func (a *AKPublic) VerifyAll(quotes []Quote, pcrs []PCR, nonce []byte) error {
if len(quotes) == 0 {
return errors.New("no quotes were provided")
}
if len(nonce) == 0 {
return errors.New("no nonce was provided")
}
for i, quote := range quotes {
if err := a.Verify(quote, pcrs, nonce); err != nil {
return fmt.Errorf("quote %d: %v", i, err)
}
}
var errPCRs []string
for _, p := range pcrs {
if !p.QuoteVerified() {
errPCRs = append(errPCRs, fmt.Sprintf("%d (%s)", p.Index, p.DigestAlg))
}
}
if len(errPCRs) > 0 {
return fmt.Errorf("some PCRs were not covered by a quote: %s", strings.Join(errPCRs, ", "))
}
return nil
}
// HashAlg identifies a hashing Algorithm. // HashAlg identifies a hashing Algorithm.
type HashAlg uint8 type HashAlg uint8
// Valid hash algorithms. // Known valid hash algorithms.
var ( var (
HashSHA1 = HashAlg(tpm2.AlgSHA1) HashSHA1 = HashAlg(tpm2.AlgSHA1)
HashSHA256 = HashAlg(tpm2.AlgSHA256) HashSHA256 = HashAlg(tpm2.AlgSHA256)
HashSHA384 = HashAlg(tpm2.AlgSHA384)
HashSHA512 = HashAlg(tpm2.AlgSHA512)
) )
func (a HashAlg) cryptoHash() crypto.Hash { func (a HashAlg) cryptoHash() crypto.Hash {
switch a { g := a.goTPMAlg()
case HashSHA1: h, err := g.Hash()
return crypto.SHA1 if err != nil {
case HashSHA256: panic(fmt.Sprintf("HashAlg %v (corresponding to TPM2.Algorithm %v) has no corresponding crypto.Hash", a, g))
return crypto.SHA256
} }
return 0 return h
} }
func (a HashAlg) goTPMAlg() tpm2.Algorithm { func (a HashAlg) goTPMAlg() tpm2.Algorithm {
switch a { return tpm2.Algorithm(a)
case HashSHA1:
return tpm2.AlgSHA1
case HashSHA256:
return tpm2.AlgSHA256
}
return 0
} }
// String returns a human-friendly representation of the hash algorithm. // String returns a human-friendly representation of the hash algorithm.
func (a HashAlg) String() string { func (a HashAlg) String() string {
switch a { return a.goTPMAlg().String()
case HashSHA1:
return "SHA1"
case HashSHA256:
return "SHA256"
}
return fmt.Sprintf("HashAlg<%d>", int(a))
} }
// PlatformParameters encapsulates the set of information necessary to attest // PlatformParameters encapsulates the set of information necessary to attest

View File

@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build gofuzz
// +build gofuzz // +build gofuzz
package attest package attest

View File

@ -12,8 +12,10 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build (!localtest || !tpm12) && cgo && !gofuzz
// +build !localtest !tpm12 // +build !localtest !tpm12
// +build cgo // +build cgo
// +build !gofuzz
// NOTE: simulator requires cgo, hence the build tag. // NOTE: simulator requires cgo, hence the build tag.
@ -33,7 +35,7 @@ func setupSimulatedTPM(t *testing.T) (*simulator.Simulator, *TPM) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
attestTPM, err := OpenTPM(&OpenConfig{CommandChannel: &linuxCmdChannel{tpm}}) attestTPM, err := OpenTPM(&OpenConfig{CommandChannel: &fakeCmdChannel{tpm}})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -65,8 +67,29 @@ func TestSimTPM20Info(t *testing.T) {
func TestSimTPM20AKCreateAndLoad(t *testing.T) { func TestSimTPM20AKCreateAndLoad(t *testing.T) {
sim, tpm := setupSimulatedTPM(t) sim, tpm := setupSimulatedTPM(t)
defer sim.Close() defer sim.Close()
for _, test := range []struct {
ak, err := tpm.NewAK(nil) name string
opts *AKConfig
}{
{
name: "NoConfig",
opts: nil,
},
{
name: "EmptyConfig",
opts: &AKConfig{},
},
{
name: "RSA",
opts: &AKConfig{Algorithm: RSA},
},
{
name: "ECDSA",
opts: &AKConfig{Algorithm: ECDSA},
},
} {
t.Run(test.name, func(t *testing.T) {
ak, err := tpm.NewAK(test.opts)
if err != nil { if err != nil {
t.Fatalf("NewAK() failed: %v", err) t.Fatalf("NewAK() failed: %v", err)
} }
@ -82,7 +105,7 @@ func TestSimTPM20AKCreateAndLoad(t *testing.T) {
loaded, err := tpm.LoadAK(enc) loaded, err := tpm.LoadAK(enc)
if err != nil { if err != nil {
t.Fatalf("LoadKey() failed: %v", err) t.Fatalf("LoadAK() failed: %v", err)
} }
defer loaded.Close(tpm) defer loaded.Close(tpm)
@ -93,35 +116,50 @@ func TestSimTPM20AKCreateAndLoad(t *testing.T) {
t.Logf("Original = %v", k1.public) t.Logf("Original = %v", k1.public)
t.Logf("Loaded = %v", k2.public) t.Logf("Loaded = %v", k2.public)
} }
})
}
} }
func TestSimTPM20ActivateCredential(t *testing.T) { func TestSimTPM20ActivateCredential(t *testing.T) {
testActivateCredential(t, false)
}
func TestSimTPM20ActivateCredentialWithEK(t *testing.T) {
testActivateCredential(t, true)
}
func testActivateCredential(t *testing.T, useEK bool) {
sim, tpm := setupSimulatedTPM(t) sim, tpm := setupSimulatedTPM(t)
defer sim.Close() defer sim.Close()
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("NewAK() failed: %v", err)
}
defer ak.Close(tpm)
EKs, err := tpm.EKs() EKs, err := tpm.EKs()
if err != nil { if err != nil {
t.Fatalf("EKs() failed: %v", err) t.Fatalf("EKs() failed: %v", err)
} }
ek := chooseEK(t, EKs) ek := chooseEK(t, EKs)
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("NewAK() failed: %v", err)
}
defer ak.Close(tpm)
ap := ActivationParameters{ ap := ActivationParameters{
TPMVersion: TPMVersion20, TPMVersion: TPMVersion20,
AK: ak.AttestationParameters(), AK: ak.AttestationParameters(),
EK: ek, EK: ek.Public,
} }
secret, challenge, err := ap.Generate() secret, challenge, err := ap.Generate()
if err != nil { if err != nil {
t.Fatalf("Generate() failed: %v", err) t.Fatalf("Generate() failed: %v", err)
} }
decryptedSecret, err := ak.ActivateCredential(tpm, *challenge) var decryptedSecret []byte
if useEK {
decryptedSecret, err = ak.ActivateCredentialWithEK(tpm, *challenge, ek)
} else {
decryptedSecret, err = ak.ActivateCredential(tpm, *challenge)
}
if err != nil { if err != nil {
t.Errorf("ak.ActivateCredential() failed: %v", err) t.Errorf("ak.ActivateCredential() failed: %v", err)
} }
@ -147,7 +185,7 @@ func TestParseAKPublic20(t *testing.T) {
} }
} }
func TestSimTPM20QuoteAndVerify(t *testing.T) { func TestSimTPM20QuoteAndVerifyAll(t *testing.T) {
sim, tpm := setupSimulatedTPM(t) sim, tpm := setupSimulatedTPM(t)
defer sim.Close() defer sim.Close()
@ -158,9 +196,13 @@ func TestSimTPM20QuoteAndVerify(t *testing.T) {
defer ak.Close(tpm) defer ak.Close(tpm)
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8} nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8}
quote, err := ak.Quote(tpm, nonce, HashSHA256) quote256, err := ak.Quote(tpm, nonce, HashSHA256)
if err != nil { if err != nil {
t.Fatalf("ak.Quote() failed: %v", err) t.Fatalf("ak.Quote(SHA256) failed: %v", err)
}
quote1, err := ak.Quote(tpm, nonce, HashSHA1)
if err != nil {
t.Fatalf("ak.Quote(SHA1) failed: %v", err)
} }
// Providing both PCR banks to AKPublic.Verify() ensures we can handle // Providing both PCR banks to AKPublic.Verify() ensures we can handle
@ -178,7 +220,14 @@ func TestSimTPM20QuoteAndVerify(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("ParseAKPublic() failed: %v", err) t.Fatalf("ParseAKPublic() failed: %v", err)
} }
if err := pub.Verify(*quote, pcrs, nonce); err != nil {
// Ensure VerifyAll fails if a quote is missing and hence not all PCR
// banks are covered.
if err := pub.VerifyAll([]Quote{*quote256}, pcrs, nonce); err == nil {
t.Error("VerifyAll().err returned nil, expected failure")
}
if err := pub.VerifyAll([]Quote{*quote256, *quote1}, pcrs, nonce); err != nil {
t.Errorf("quote verification failed: %v", err) t.Errorf("quote verification failed: %v", err)
} }
} }
@ -203,10 +252,8 @@ func TestSimTPM20AttestPlatform(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("ParseAKPublic() failed: %v", err) t.Fatalf("ParseAKPublic() failed: %v", err)
} }
for i, q := range attestation.Quotes { if err := pub.VerifyAll(attestation.Quotes, attestation.PCRs, nonce); err != nil {
if err := pub.Verify(q, attestation.PCRs, nonce); err != nil { t.Errorf("quote verification failed: %v", err)
t.Errorf("quote[%d] verification failed: %v", i, err)
}
} }
} }
@ -235,24 +282,69 @@ func TestSimTPM20PCRs(t *testing.T) {
} }
} }
func TestSimTPM20Persistence(t *testing.T) { func TestSimTPM20PersistenceSRK(t *testing.T) {
testPersistenceSRK(t, defaultParentConfig)
}
func TestSimTPM20PersistenceECCSRK(t *testing.T) {
parentConfig := ParentKeyConfig{
Algorithm: ECDSA,
Handle: 0x81000002,
}
testPersistenceSRK(t, parentConfig)
}
func testPersistenceSRK(t *testing.T, parentConfig ParentKeyConfig) {
sim, tpm := setupSimulatedTPM(t) sim, tpm := setupSimulatedTPM(t)
defer sim.Close() defer sim.Close()
ekHnd, _, err := tpm.tpm.(*wrappedTPM20).getPrimaryKeyHandle(commonEkEquivalentHandle) srkHnd, _, err := tpm.tpm.(*wrappedTPM20).getStorageRootKeyHandle(parentConfig)
if err != nil { if err != nil {
t.Fatalf("getPrimaryKeyHandle() failed: %v", err) t.Fatalf("getStorageRootKeyHandle() failed: %v", err)
} }
if ekHnd != commonEkEquivalentHandle { if srkHnd != parentConfig.Handle {
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, commonEkEquivalentHandle) t.Fatalf("bad SRK-equivalent handle: got 0x%x, wanted 0x%x", srkHnd, parentConfig.Handle)
} }
ekHnd, p, err := tpm.tpm.(*wrappedTPM20).getPrimaryKeyHandle(commonEkEquivalentHandle) srkHnd, p, err := tpm.tpm.(*wrappedTPM20).getStorageRootKeyHandle(parentConfig)
if err != nil { if err != nil {
t.Fatalf("second getPrimaryKeyHandle() failed: %v", err) t.Fatalf("second getStorageRootKeyHandle() failed: %v", err)
} }
if ekHnd != commonEkEquivalentHandle { if srkHnd != parentConfig.Handle {
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, commonEkEquivalentHandle) t.Fatalf("bad SRK-equivalent handle: got 0x%x, wanted 0x%x", srkHnd, parentConfig.Handle)
}
if p {
t.Fatalf("generated a new key the second time; that shouldn't happen")
}
}
func TestSimTPM20PersistenceEK(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
eks, err := tpm.EKs()
if err != nil {
t.Errorf("EKs() failed: %v", err)
}
if len(eks) == 0 || (eks[0].Public == nil) {
t.Errorf("EKs() = %v, want at least 1 EK with populated fields", eks)
}
ek := eks[0]
ekHnd, _, err := tpm.tpm.(*wrappedTPM20).getEndorsementKeyHandle(&ek)
if err != nil {
t.Fatalf("getStorageRootKeyHandle() failed: %v", err)
}
if ekHnd != ek.handle {
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, ek.handle)
}
ekHnd, p, err := tpm.tpm.(*wrappedTPM20).getEndorsementKeyHandle(&ek)
if err != nil {
t.Fatalf("second getEndorsementKeyHandle() failed: %v", err)
}
if ekHnd != ek.handle {
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, ek.handle)
} }
if p { if p {
t.Fatalf("generated a new key the second time; that shouldn't happen") t.Fatalf("generated a new key the second time; that shouldn't happen")

View File

@ -16,7 +16,6 @@ package attest
import ( import (
"bytes" "bytes"
"crypto"
"flag" "flag"
"fmt" "fmt"
"reflect" "reflect"
@ -84,13 +83,35 @@ func TestAKCreateAndLoad(t *testing.T) {
if !*testLocal { if !*testLocal {
t.SkipNow() t.SkipNow()
} }
for _, test := range []struct {
name string
opts *AKConfig
}{
{
name: "NoConfig",
opts: nil,
},
{
name: "EmptyConfig",
opts: &AKConfig{},
},
{
name: "RSA",
opts: &AKConfig{Algorithm: RSA},
},
{
name: "ECDSA",
opts: &AKConfig{Algorithm: ECDSA},
},
} {
t.Run(test.name, func(t *testing.T) {
tpm, err := OpenTPM(nil) tpm, err := OpenTPM(nil)
if err != nil { if err != nil {
t.Fatalf("OpenTPM() failed: %v", err) t.Fatalf("OpenTPM() failed: %v", err)
} }
defer tpm.Close() defer tpm.Close()
ak, err := tpm.NewAK(nil) ak, err := tpm.NewAK(test.opts)
if err != nil { if err != nil {
t.Fatalf("NewAK() failed: %v", err) t.Fatalf("NewAK() failed: %v", err)
} }
@ -106,7 +127,7 @@ func TestAKCreateAndLoad(t *testing.T) {
loaded, err := tpm.LoadAK(enc) loaded, err := tpm.LoadAK(enc)
if err != nil { if err != nil {
t.Fatalf("LoadKey() failed: %v", err) t.Fatalf("LoadAK() failed: %v", err)
} }
defer loaded.Close(tpm) defer loaded.Close(tpm)
@ -117,18 +138,20 @@ func TestAKCreateAndLoad(t *testing.T) {
t.Logf("Original = %v", k1.public) t.Logf("Original = %v", k1.public)
t.Logf("Loaded = %v", k2.public) t.Logf("Loaded = %v", k2.public)
} }
})
}
} }
// chooseEK selects the EK public which will be activated against. // chooseEK selects the EK which will be activated against.
func chooseEK(t *testing.T, eks []EK) crypto.PublicKey { func chooseEK(t *testing.T, eks []EK) EK {
t.Helper() t.Helper()
for _, ek := range eks { for _, ek := range eks {
return ek.Public return ek
} }
t.Fatalf("No suitable EK found") t.Fatalf("No suitable EK found")
return nil return EK{}
} }
func TestPCRs(t *testing.T) { func TestPCRs(t *testing.T) {

View File

@ -151,7 +151,7 @@ func TestTPMActivateCredential(t *testing.T) {
ap := ActivationParameters{ ap := ActivationParameters{
TPMVersion: TPMVersion12, TPMVersion: TPMVersion12,
AK: ak.AttestationParameters(), AK: ak.AttestationParameters(),
EK: ek, EK: ek.Public,
} }
secret, challenge, err := ap.Generate() secret, challenge, err := ap.Generate()
if err != nil { if err != nil {

View File

@ -17,11 +17,16 @@ package attest
import ( import (
"bytes" "bytes"
"crypto" "crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa" "crypto/rsa"
"errors" "errors"
"fmt" "fmt"
"io"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/legacy/tpm2/credactivation"
"github.com/google/go-tpm/tpmutil"
) )
// secureCurves represents a set of secure elliptic curves. For now, // secureCurves represents a set of secure elliptic curves. For now,
@ -32,7 +37,6 @@ var secureCurves = map[tpm2.EllipticCurve]bool{
tpm2.CurveNISTP521: true, tpm2.CurveNISTP521: true,
tpm2.CurveBNP256: true, tpm2.CurveBNP256: true,
tpm2.CurveBNP638: true, tpm2.CurveBNP638: true,
tpm2.CurveSM2P256: true,
} }
// CertificationParameters encapsulates the inputs for certifying an application key. // CertificationParameters encapsulates the inputs for certifying an application key.
@ -61,6 +65,44 @@ type VerifyOpts struct {
Hash crypto.Hash Hash crypto.Hash
} }
// ActivateOpts specifies options for the key certification's challenge generation.
type ActivateOpts struct {
// EK, the endorsement key, describes an asymmetric key whose
// private key is permanently bound to the TPM.
//
// Activation will verify that the provided EK is held on the same
// TPM as the key we're certifying. However, it is the caller's responsibility to
// ensure the EK they provide corresponds to the the device which
// they are trying to associate the certified key with.
EK crypto.PublicKey
// VerifierKeyNameDigest is the name digest of the public key we're using to
// verify the certification of the tpm-generated key being activated.
// The verifier key (usually the AK) that owns this digest should be the same
// key used in VerifyOpts.Public.
// Use tpm2.Public.Name() to produce the digest for a provided key.
VerifierKeyNameDigest *tpm2.HashValue
}
// CertifyOpts specifies options for the key's certification.
type CertifyOpts struct {
// QualifyingData is the user provided qualifying data.
QualifyingData []byte
}
// NewActivateOpts creates options for use in generating an activation challenge for a certified key.
// The computed hash is the name digest of the public key used to verify the certification of our key.
func NewActivateOpts(verifierPubKey tpm2.Public, ek crypto.PublicKey) (*ActivateOpts, error) {
pubName, err := verifierPubKey.Name()
if err != nil {
return nil, fmt.Errorf("unable to resolve a tpm2.Public Name struct from the given public key struct: %v", err)
}
return &ActivateOpts{
EK: ek,
VerifierKeyNameDigest: pubName.Digest,
}, nil
}
// Verify verifies the TPM2-produced certification parameters checking whether: // Verify verifies the TPM2-produced certification parameters checking whether:
// - the key length is secure // - the key length is secure
// - the attestation parameters matched the attested key // - the attestation parameters matched the attested key
@ -73,16 +115,12 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error {
if err != nil { if err != nil {
return fmt.Errorf("DecodePublic() failed: %v", err) return fmt.Errorf("DecodePublic() failed: %v", err)
} }
_, err = tpm2.DecodeCreationData(p.CreateData)
if err != nil {
return fmt.Errorf("DecodeCreationData() failed: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.CreateAttestation) att, err := tpm2.DecodeAttestationData(p.CreateAttestation)
if err != nil { if err != nil {
return fmt.Errorf("DecodeAttestationData() failed: %v", err) return fmt.Errorf("DecodeAttestationData() failed: %v", err)
} }
if att.Type != tpm2.TagAttestCreation { if att.Type != tpm2.TagAttestCertify {
return fmt.Errorf("attestation does not apply to creation data, got tag %x", att.Type) return fmt.Errorf("attestation does not apply to certification data, got tag %x", att.Type)
} }
switch pub.Type { switch pub.Type {
@ -98,18 +136,6 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error {
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type) return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
} }
// Compute & verify that the creation data matches the digest in the
// attestation structure.
nameHash, err := pub.NameAlg.Hash()
if err != nil {
return fmt.Errorf("HashConstructor() failed: %v", err)
}
h := nameHash.New()
h.Write(p.CreateData)
if !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) {
return errors.New("attestation refers to different public key")
}
// Make sure the key has sane parameters (e.g., attestation can be faked if an AK // Make sure the key has sane parameters (e.g., attestation can be faked if an AK
// can be used for arbitrary signatures). // can be used for arbitrary signatures).
// We verify the following: // We verify the following:
@ -136,20 +162,15 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error {
// Verify the attested creation name matches what is computed from // Verify the attested creation name matches what is computed from
// the public key. // the public key.
match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub) match, err := att.AttestedCertifyInfo.Name.MatchesPublic(pub)
if err != nil { if err != nil {
return err return err
} }
if !match { if !match {
return errors.New("creation attestation refers to a different key") return errors.New("certification refers to a different key")
} }
// Check the signature over the attestation data verifies correctly. // Check the signature over the attestation data verifies correctly.
// TODO: Support ECC certifying keys
pk, ok := opts.Public.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("Only RSA verification keys are supported")
}
if !opts.Hash.Available() { if !opts.Hash.Available() {
return fmt.Errorf("hash function is unavailable") return fmt.Errorf("hash function is unavailable")
} }
@ -165,9 +186,85 @@ func (p *CertificationParameters) Verify(opts VerifyOpts) error {
return fmt.Errorf("DecodeSignature() failed: %v", err) return fmt.Errorf("DecodeSignature() failed: %v", err)
} }
switch pk := opts.Public.(type) {
case *rsa.PublicKey:
if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil { if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
return fmt.Errorf("could not verify attestation: %v", err) return fmt.Errorf("could not verify attestation: %v", err)
} }
case *ecdsa.PublicKey:
if ok := ecdsa.Verify(pk, hsh.Sum(nil), sig.ECC.R, sig.ECC.S); !ok {
return fmt.Errorf("could not verify ECC attestation")
}
default:
return fmt.Errorf("unsupported public key type: %T", pub)
}
return nil return nil
} }
// Generate returns a credential activation challenge, which can be provided
// to the TPM to verify the AK parameters given are authentic & the AK
// is present on the same TPM as the EK.
//
// The caller is expected to verify the secret returned from the TPM as
// as result of calling ActivateCredential() matches the secret returned here.
// The caller should use subtle.ConstantTimeCompare to avoid potential
// timing attack vectors.
func (p *CertificationParameters) Generate(rnd io.Reader, verifyOpts VerifyOpts, activateOpts ActivateOpts) (secret []byte, ec *EncryptedCredential, err error) {
if err := p.Verify(verifyOpts); err != nil {
return nil, nil, err
}
if activateOpts.EK == nil {
return nil, nil, errors.New("no EK provided")
}
secret = make([]byte, activationSecretLen)
if rnd == nil {
rnd = rand.Reader
}
if _, err = io.ReadFull(rnd, secret); err != nil {
return nil, nil, fmt.Errorf("error generating activation secret: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.CreateAttestation)
if err != nil {
return nil, nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCertify {
return nil, nil, fmt.Errorf("attestation does not apply to certify data, got %x", att.Type)
}
cred, encSecret, err := credactivation.Generate(activateOpts.VerifierKeyNameDigest, activateOpts.EK, symBlockSize, secret)
if err != nil {
return nil, nil, fmt.Errorf("credactivation.Generate() failed: %v", err)
}
return secret, &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
// certify uses AK's handle, the passed user qualifying data, and the passed
// signature scheme to certify the key with the `hnd` handle.
func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, qualifyingData []byte, scheme tpm2.SigScheme) (*CertificationParameters, error) {
pub, _, _, err := tpm2.ReadPublic(tpm, hnd)
if err != nil {
return nil, fmt.Errorf("tpm2.ReadPublic() failed: %v", err)
}
public, err := pub.Encode()
if err != nil {
return nil, fmt.Errorf("could not encode public key: %v", err)
}
att, sig, err := tpm2.CertifyEx(tpm, "", "", hnd, akHnd, qualifyingData, scheme)
if err != nil {
return nil, fmt.Errorf("tpm2.Certify() failed: %v", err)
}
return &CertificationParameters{
Public: public,
CreateAttestation: att,
CreateSignature: sig,
}, nil
}

View File

@ -12,24 +12,64 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build (!localtest || !tpm12) && cgo && !gofuzz
// +build !localtest !tpm12
// +build cgo
// +build !gofuzz
package attest package attest
import ( import (
"bytes"
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"slices"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/legacy/tpm2"
) )
func TestCertificationParametersTPM20(t *testing.T) { func TestSimTPM20CertificationParametersRSA(t *testing.T) {
s, tpm := setupSimulatedTPM(t) sim, tpm := setupSimulatedTPM(t)
defer s.Close() defer sim.Close()
testCertificationParameters(t, tpm, RSA)
}
ak, err := tpm.NewAK(nil) func TestSimTPM20CertificationParametersECC(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
testCertificationParameters(t, tpm, ECDSA)
}
func TestTPM20CertificationParametersRSA(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
testCertificationParameters(t, tpm, RSA)
}
func TestTPM20CertificationParametersECC(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
testCertificationParameters(t, tpm, ECDSA)
}
func testCertificationParameters(t *testing.T, tpm *TPM, akAlg Algorithm) {
ak, err := tpm.NewAK(&AKConfig{Algorithm: akAlg})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -38,12 +78,12 @@ func TestCertificationParametersTPM20(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if pub.Type != tpm2.AlgRSA {
t.Fatal("non-RSA verifying key")
}
pk := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()} pk, err := pub.Key()
hash, err := pub.RSAParameters.Sign.Hash.Hash() if err != nil {
t.Fatal(err)
}
hash, err := pub.NameAlg.Hash()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -110,18 +150,6 @@ func TestCertificationParametersTPM20(t *testing.T) {
name: "modified Public", name: "modified Public",
p: &CertificationParameters{ p: &CertificationParameters{
Public: akAttestParams.Public, Public: akAttestParams.Public,
CreateData: skCertParams.CreateData,
CreateAttestation: skCertParams.CreateAttestation,
CreateSignature: skCertParams.CreateSignature,
},
opts: correctOpts,
err: cmpopts.AnyError,
},
{
name: "modified CreateData",
p: &CertificationParameters{
Public: skCertParams.Public,
CreateData: []byte("unparsable"),
CreateAttestation: skCertParams.CreateAttestation, CreateAttestation: skCertParams.CreateAttestation,
CreateSignature: skCertParams.CreateSignature, CreateSignature: skCertParams.CreateSignature,
}, },
@ -132,7 +160,6 @@ func TestCertificationParametersTPM20(t *testing.T) {
name: "modified CreateAttestation", name: "modified CreateAttestation",
p: &CertificationParameters{ p: &CertificationParameters{
Public: skCertParams.Public, Public: skCertParams.Public,
CreateData: skCertParams.CreateData,
CreateAttestation: akAttestParams.CreateAttestation, CreateAttestation: akAttestParams.CreateAttestation,
CreateSignature: skCertParams.CreateSignature, CreateSignature: skCertParams.CreateSignature,
}, },
@ -143,7 +170,6 @@ func TestCertificationParametersTPM20(t *testing.T) {
name: "modified CreateSignature", name: "modified CreateSignature",
p: &CertificationParameters{ p: &CertificationParameters{
Public: skCertParams.Public, Public: skCertParams.Public,
CreateData: skCertParams.CreateData,
CreateAttestation: skCertParams.CreateAttestation, CreateAttestation: skCertParams.CreateAttestation,
CreateSignature: akAttestParams.CreateSignature, CreateSignature: akAttestParams.CreateSignature,
}, },
@ -162,3 +188,285 @@ func TestCertificationParametersTPM20(t *testing.T) {
}) })
} }
} }
func TestSimTPM20KeyCertificationRSA(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
testKeyCertification(t, tpm, RSA)
}
func TestSimTPM20KeyCertificationECC(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
testKeyCertification(t, tpm, ECDSA)
}
func TestTPM20KeyCertificationRSA(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
testKeyCertification(t, tpm, RSA)
}
func TestTPM20KeyCertificationECC(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
testKeyCertification(t, tpm, ECDSA)
}
func extraData(t *testing.T, p CertificationParameters) []byte {
t.Helper()
ad, err := tpm2.DecodeAttestationData(p.CreateAttestation)
if err != nil {
t.Fatalf("failed to decode attestation data: %v", err)
}
return ad.ExtraData
}
func testKeyCertification(t *testing.T, tpm *TPM, akAlg Algorithm) {
ak, err := tpm.NewAK(&AKConfig{Algorithm: akAlg})
if err != nil {
t.Fatalf("NewAK() failed: %v", err)
}
akAttestParams := ak.AttestationParameters()
pub, err := tpm2.DecodePublic(akAttestParams.Public)
if err != nil {
t.Fatalf("DecodePublic() failed: %v", err)
}
pk, err := pub.Key()
if err != nil {
t.Fatalf("pub.Key() failed: %v", err)
}
hash, err := pub.NameAlg.Hash()
if err != nil {
t.Fatalf("cannot access AK's hash function: %v", err)
}
verifyOpts := VerifyOpts{
Public: pk,
Hash: hash,
}
for _, test := range []struct {
name string
opts *KeyConfig
wantExtraData []byte
err error
}{
{
name: "default",
opts: nil,
err: nil,
},
{
name: "ECDSAP256-SHA256",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 256,
},
err: nil,
},
{
name: "ECDSAP384-SHA384",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 384,
},
err: nil,
},
{
name: "ECDSAP521-SHA512",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 521,
},
err: nil,
},
{
name: "RSA-1024, key too short",
opts: &KeyConfig{
Algorithm: RSA,
Size: 1024,
},
err: cmpopts.AnyError,
},
{
name: "RSA-2048",
opts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
},
err: nil,
},
{
name: "QualifyingData-RSA",
opts: &KeyConfig{
Algorithm: RSA,
Size: 2048,
QualifyingData: []byte("qualifying data"),
},
wantExtraData: []byte("qualifying data"),
err: nil,
},
{
name: "QualifyingData-ECDSA",
opts: &KeyConfig{
Algorithm: ECDSA,
Size: 384,
QualifyingData: []byte("qualifying data"),
},
wantExtraData: []byte("qualifying data"),
err: nil,
},
} {
t.Run(test.name, func(t *testing.T) {
sk, err := tpm.NewKey(ak, test.opts)
if err != nil {
t.Fatalf("NewKey() failed: %v", err)
}
defer sk.Close()
p := sk.CertificationParameters()
if gotExtraData, wantExtraData := extraData(t, p), test.wantExtraData; !slices.Equal(gotExtraData, wantExtraData) {
t.Errorf("ExtraData got = %v, want = %v", gotExtraData, wantExtraData)
}
err = p.Verify(verifyOpts)
if test.err == nil && err == nil {
return
}
if got, want := err, test.err; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
t.Errorf("p.Verify() err = %v, want = %v", got, want)
}
})
}
}
func TestKeyActivationTPM20(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("error creating a new AK using simulated TPM: %v", err)
}
akAttestParams := ak.AttestationParameters()
pub, err := tpm2.DecodePublic(akAttestParams.Public)
if err != nil {
t.Fatalf("unable to decode public struct from AK attestation params: %v", err)
}
if pub.Type != tpm2.AlgRSA {
t.Fatal("non-RSA verifying key")
}
eks, err := tpm.EKs()
if err != nil {
t.Fatalf("unexpected error retrieving EK from tpm: %v", err)
}
if len(eks) == 0 {
t.Fatal("expected at least one EK from the simulated TPM")
}
pk := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
hash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
t.Fatalf("unable to compute hash signature from verifying key's RSA parameters: %v", err)
}
verifyOpts := VerifyOpts{
Public: pk,
Hash: hash,
}
sk, err := tpm.NewKey(ak, nil)
if err != nil {
t.Fatalf("unable to create a new TPM-backed key to certify: %v", err)
}
skCertParams := sk.CertificationParameters()
activateOpts, err := NewActivateOpts(pub, eks[0].Public)
if err != nil {
t.Fatalf("unable to create new ActivateOpts: %v", err)
}
wrongPub, err := tpm2.DecodePublic(skCertParams.Public)
if err != nil {
t.Fatalf("unable to decode public struct from CertificationParameters: %v", err)
}
wrongActivateOpts, err := NewActivateOpts(wrongPub, eks[0].Public)
if err != nil {
t.Fatalf("unable to create wrong ActivateOpts: %v", err)
}
for _, test := range []struct {
name string
p *CertificationParameters
verifyOpts VerifyOpts
activateOpts ActivateOpts
generateErr error
activateErr error
}{
{
name: "OK",
p: &skCertParams,
verifyOpts: verifyOpts,
activateOpts: *activateOpts,
generateErr: nil,
activateErr: nil,
},
{
name: "invalid verify opts",
p: &skCertParams,
verifyOpts: VerifyOpts{},
activateOpts: *activateOpts,
generateErr: cmpopts.AnyError,
activateErr: nil,
},
{
name: "invalid activate opts",
p: &skCertParams,
verifyOpts: verifyOpts,
activateOpts: *wrongActivateOpts,
generateErr: nil,
activateErr: cmpopts.AnyError,
},
} {
t.Run(test.name, func(t *testing.T) {
expectedSecret, encryptedCredentials, err := test.p.Generate(rand.Reader, test.verifyOpts, test.activateOpts)
if test.generateErr != nil {
if got, want := err, test.generateErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
t.Errorf("p.Generate() err = %v, want = %v", got, want)
}
return
} else if err != nil {
t.Errorf("unexpected p.Generate() error: %v", err)
return
}
actualSecret, err := ak.ActivateCredential(tpm, *encryptedCredentials)
if test.activateErr != nil {
if got, want := err, test.activateErr; !cmp.Equal(got, want, cmpopts.EquateErrors()) {
t.Errorf("p.ActivateCredential() err = %v, want = %v", got, want)
}
return
} else if err != nil {
t.Errorf("unexpected p.ActivateCredential() error: %v", err)
return
}
if !bytes.Equal(expectedSecret, actualSecret) {
t.Fatalf("Unexpected bytes decoded, expected %x, but got %x", expectedSecret, actualSecret)
}
})
}
}

View File

@ -30,7 +30,7 @@ import (
// Ensure hashes are available. // Ensure hashes are available.
_ "crypto/sha256" _ "crypto/sha256"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpmutil" "github.com/google/go-tpm/tpmutil"
) )
@ -56,14 +56,6 @@ func (e ReplayError) Error() string {
return fmt.Sprintf("event log failed to verify: the following registers failed to replay: %v", e.InvalidPCRs) return fmt.Sprintf("event log failed to verify: the following registers failed to replay: %v", e.InvalidPCRs)
} }
// TPM algorithms. See the TPM 2.0 specification section 6.3.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf#page=42
const (
algSHA1 uint16 = 0x0004
algSHA256 uint16 = 0x000B
)
// EventType indicates what kind of data an event is reporting. // EventType indicates what kind of data an event is reporting.
// //
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=103 // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=103
@ -115,10 +107,10 @@ func (e EventType) String() string {
} }
// Event is a single event from a TCG event log. This reports descrete items such // Event is a single event from a TCG event log. This reports descrete items such
// as BIOs measurements or EFI states. // as BIOS measurements or EFI states.
// //
// There are many pitfalls for using event log events correctly to determine the // There are many pitfalls for using event log events correctly to determine the
// state of a machine[1]. In general it's must safer to only rely on the raw PCR // state of a machine[1]. In general it's much safer to only rely on the raw PCR
// values and use the event log for debugging. // values and use the event log for debugging.
// //
// [1] https://github.com/google/go-attestation/blob/master/docs/event-log-disclosure.md // [1] https://github.com/google/go-attestation/blob/master/docs/event-log-disclosure.md
@ -174,6 +166,7 @@ type EventLog struct {
Algs []HashAlg Algs []HashAlg
rawEvents []rawEvent rawEvents []rawEvent
specIDEvent *specIDEvent
} }
func (e *EventLog) clone() *EventLog { func (e *EventLog) clone() *EventLog {
@ -183,6 +176,11 @@ func (e *EventLog) clone() *EventLog {
} }
copy(out.Algs, e.Algs) copy(out.Algs, e.Algs)
copy(out.rawEvents, e.rawEvents) copy(out.rawEvents, e.rawEvents)
if e.specIDEvent != nil {
dupe := *e.specIDEvent
out.specIDEvent = &dupe
}
return &out return &out
} }
@ -216,7 +214,7 @@ func (e *EventLog) Events(hash HashAlg) []Event {
// Verify replays the event log against a TPM's PCR values, returning the // Verify replays the event log against a TPM's PCR values, returning the
// events which could be matched to a provided PCR value. // events which could be matched to a provided PCR value.
// //
// PCRs provide no security guarentees unless they're attested to have been // PCRs provide no security guarantees unless they're attested to have been
// generated by a TPM. Verify does not perform these checks. // generated by a TPM. Verify does not perform these checks.
// //
// An error is returned if the replayed digest for events with a given PCR // An error is returned if the replayed digest for events with a given PCR
@ -314,6 +312,13 @@ func (a *AKPublic) validate12Quote(quote Quote, pcrs []PCR, nonce []byte) error
if att.Digest != sha1.Sum(composite) { if att.Digest != sha1.Sum(composite) {
return fmt.Errorf("PCRs passed didn't match quote: %v", err) return fmt.Errorf("PCRs passed didn't match quote: %v", err)
} }
// All provided PCRs are used to construct the composite hash which
// is verified against the quote (for TPM 1.2), so if we got this far,
// all PCR values are verified.
for i := range pcrs {
pcrs[i].quoteVerified = true
}
return nil return nil
} }
@ -348,7 +353,7 @@ func (a *AKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error
return fmt.Errorf("attestation isn't a quote, tag of type 0x%x", att.Type) return fmt.Errorf("attestation isn't a quote, tag of type 0x%x", att.Type)
} }
if !bytes.Equal([]byte(att.ExtraData), nonce) { if !bytes.Equal([]byte(att.ExtraData), nonce) {
return fmt.Errorf("nonce didn't match: %v", err) return fmt.Errorf("nonce = %#v, want %#v", []byte(att.ExtraData), nonce)
} }
pcrByIndex := map[int][]byte{} pcrByIndex := map[int][]byte{}
@ -360,17 +365,33 @@ func (a *AKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error
} }
sigHash.Reset() sigHash.Reset()
quotePCRs := make(map[int]struct{}, len(att.AttestedQuoteInfo.PCRSelection.PCRs))
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs { for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
digest, ok := pcrByIndex[index] digest, ok := pcrByIndex[index]
if !ok { if !ok {
return fmt.Errorf("quote was over PCR %d which wasn't provided", index) return fmt.Errorf("quote was over PCR %d which wasn't provided", index)
} }
quotePCRs[index] = struct{}{}
sigHash.Write(digest) sigHash.Write(digest)
} }
for index := range pcrByIndex {
if _, exists := quotePCRs[index]; !exists {
return fmt.Errorf("provided PCR %d was not included in quote", index)
}
}
if !bytes.Equal(sigHash.Sum(nil), att.AttestedQuoteInfo.PCRDigest) { if !bytes.Equal(sigHash.Sum(nil), att.AttestedQuoteInfo.PCRDigest) {
return fmt.Errorf("quote digest didn't match pcrs provided") return fmt.Errorf("quote digest didn't match pcrs provided")
} }
// If we got this far, all included PCRs with a digest algorithm matching that
// of the quote are verified. As such, we set their quoteVerified bit.
for i, pcr := range pcrs {
if _, exists := quotePCRs[pcr.Index]; exists && pcr.DigestAlg == pcrDigestAlg {
pcrs[i].quoteVerified = true
}
}
return nil return nil
} }
@ -401,7 +422,7 @@ func extend(pcr PCR, replay []byte, e rawEvent, locality byte) (pcrDigest []byte
// replayPCR replays the event log for a specific PCR, using pcr and // replayPCR replays the event log for a specific PCR, using pcr and
// event digests with the algorithm in pcr. An error is returned if the // event digests with the algorithm in pcr. An error is returned if the
// replayed values do not match the final PCR digest, or any event tagged // replayed values do not match the final PCR digest, or any event tagged
// with that PCR does not posess an event digest with the specified algorithm. // with that PCR does not possess an event digest with the specified algorithm.
func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) { func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
var ( var (
replay []byte replay []byte
@ -512,22 +533,15 @@ func ParseEventLog(measurementLog []byte) (*EventLog, error) {
return nil, fmt.Errorf("failed to parse spec ID event: %v", err) return nil, fmt.Errorf("failed to parse spec ID event: %v", err)
} }
for _, alg := range specID.algs { for _, alg := range specID.algs {
switch tpm2.Algorithm(alg.ID) { el.Algs = append(el.Algs, HashAlg(alg.ID))
case tpm2.AlgSHA1:
el.Algs = append(el.Algs, HashSHA1)
case tpm2.AlgSHA256:
el.Algs = append(el.Algs, HashSHA256)
}
}
if len(el.Algs) == 0 {
return nil, fmt.Errorf("measurement log didn't use sha1 or sha256 digests")
} }
// Switch to parsing crypto agile events. Don't include this in the // Switch to parsing crypto agile events. Don't include this in the
// replayed events since it intentionally doesn't extend the PCRs. // replayed events since it intentionally doesn't extend the PCRs.
// //
// Note that this doesn't actually guarentee that events have SHA256 // Note that this doesn't actually guarantee that events have SHA256
// digests. // digests.
parseFn = parseRawEvent2 parseFn = parseRawEvent2
el.specIDEvent = specID
} else { } else {
el.Algs = []HashAlg{HashSHA1} el.Algs = []HashAlg{HashSHA1}
el.rawEvents = append(el.rawEvents, e) el.rawEvents = append(el.rawEvents, e)
@ -729,9 +743,6 @@ func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err e
if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil { if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
return event, err return event, err
} }
if eventSize == 0 {
return event, errors.New("event data size is 0")
}
if eventSize > uint32(r.Len()) { if eventSize > uint32(r.Len()) {
return event, &eventSizeErr{eventSize, r.Len()} return event, &eventSizeErr{eventSize, r.Len()}
} }
@ -741,3 +752,73 @@ func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err e
} }
return event, err return event, err
} }
// AppendEvents takes a series of TPM 2.0 event logs and combines
// them into a single sequence of events with a single header.
//
// Additional logs must not use a digest algorithm which was not
// present in the original log.
func AppendEvents(base []byte, additional ...[]byte) ([]byte, error) {
baseLog, err := ParseEventLog(base)
if err != nil {
return nil, fmt.Errorf("base: %v", err)
}
if baseLog.specIDEvent == nil {
return nil, errors.New("tpm 1.2 event logs cannot be combined")
}
outBuff := make([]byte, len(base))
copy(outBuff, base)
out := bytes.NewBuffer(outBuff)
for i, l := range additional {
log, err := ParseEventLog(l)
if err != nil {
return nil, fmt.Errorf("log %d: %v", i, err)
}
if log.specIDEvent == nil {
return nil, fmt.Errorf("log %d: cannot use tpm 1.2 event log as a source", i)
}
algCheck:
for _, alg := range log.specIDEvent.algs {
for _, baseAlg := range baseLog.specIDEvent.algs {
if baseAlg == alg {
continue algCheck
}
}
return nil, fmt.Errorf("log %d: cannot use digest (%+v) not present in base log", i, alg)
}
for x, e := range log.rawEvents {
// Serialize header (PCR index, event type, number of digests)
binary.Write(out, binary.LittleEndian, rawEvent2Header{
PCRIndex: uint32(e.index),
Type: uint32(e.typ),
})
binary.Write(out, binary.LittleEndian, uint32(len(e.digests)))
// Serialize digests
for _, d := range e.digests {
var algID uint16
switch d.hash {
case crypto.SHA256:
algID = uint16(HashSHA256)
case crypto.SHA1:
algID = uint16(HashSHA1)
default:
return nil, fmt.Errorf("log %d: event %d: unhandled hash function %v", i, x, d.hash)
}
binary.Write(out, binary.LittleEndian, algID)
out.Write(d.data)
}
// Serialize event data
binary.Write(out, binary.LittleEndian, uint32(len(e.data)))
out.Write(e.data)
}
}
return out.Bytes(), nil
}

View File

@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build gofuzz
// +build gofuzz // +build gofuzz
package attest package attest

View File

@ -16,11 +16,12 @@ package attest
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"io/ioutil" "os"
"testing" "testing"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/legacy/tpm2"
) )
// Dump describes the layout of serialized information from the dump command. // Dump describes the layout of serialized information from the dump command.
@ -55,7 +56,7 @@ func TestParseEventLogLinux(t *testing.T) {
} }
func testParseEventLog(t *testing.T, testdata string) { func testParseEventLog(t *testing.T, testdata string) {
data, err := ioutil.ReadFile(testdata) data, err := os.ReadFile(testdata)
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -69,7 +70,7 @@ func testParseEventLog(t *testing.T, testdata string) {
} }
func TestParseCryptoAgileEventLog(t *testing.T) { func TestParseCryptoAgileEventLog(t *testing.T) {
data, err := ioutil.ReadFile("testdata/crypto_agile_eventlog") data, err := os.ReadFile("testdata/crypto_agile_eventlog")
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -87,7 +88,7 @@ func TestEventLog(t *testing.T) {
} }
func testEventLog(t *testing.T, testdata string) { func testEventLog(t *testing.T, testdata string) {
data, err := ioutil.ReadFile(testdata) data, err := os.ReadFile(testdata)
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -173,6 +174,43 @@ func TestParseEventLogEventSizeZero(t *testing.T) {
} }
} }
func TestParseEventLog2EventSizeZero(t *testing.T) {
data := []byte{
// PCR index
0x0, 0x0, 0x0, 0x0,
// type
0x7, 0x0, 0x0, 0x0,
// number of digests
0x1, 0x0, 0x0, 0x0,
// algorithm
0xb, 0x0,
// Digest
0xc8, 0xe3, 0x88, 0xb4, 0x79, 0x12, 0x86, 0x0c,
0x66, 0xa1, 0x5d, 0xad, 0xc4, 0x34, 0xf5, 0xdf,
0x73, 0x6c, 0x3a, 0xb4, 0xbe, 0x52, 0x07, 0x08,
0xdf, 0xac, 0x48, 0x2d, 0x71, 0xce, 0xa0, 0x73,
// Event size (0 B)
0x0, 0x0, 0x0, 0x0,
// no "event data"
}
specID := &specIDEvent{
algs: []specAlgSize{
{ID: uint16(tpm2.AlgSHA256), Size: 32},
},
}
if _, err := parseRawEvent2(bytes.NewBuffer(data), specID); err != nil {
t.Fatalf("parsing event log: %v", err)
}
}
func TestParseShortNoAction(t *testing.T) { func TestParseShortNoAction(t *testing.T) {
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110 // https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110
// says: "For EV_NO_ACTION events other than the EFI Specification ID event // says: "For EV_NO_ACTION events other than the EFI Specification ID event
@ -182,7 +220,7 @@ func TestParseShortNoAction(t *testing.T) {
// Currently we just assume that such events will have Data shorter than // Currently we just assume that such events will have Data shorter than
// "EFI Specification ID" field. // "EFI Specification ID" field.
data, err := ioutil.ReadFile("testdata/short_no_action_eventlog") data, err := os.ReadFile("testdata/short_no_action_eventlog")
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -325,7 +363,7 @@ func TestEBSVerifyWorkaround(t *testing.T) {
}, },
} }
elr, err := ioutil.ReadFile("testdata/ebs_event_missing_eventlog") elr, err := os.ReadFile("testdata/ebs_event_missing_eventlog")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -337,3 +375,45 @@ func TestEBSVerifyWorkaround(t *testing.T) {
t.Errorf("Verify() failed: %v", err) t.Errorf("Verify() failed: %v", err)
} }
} }
func TestAppendEvents(t *testing.T) {
base, err := os.ReadFile("testdata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog")
if err != nil {
t.Fatalf("reading test data: %v", err)
}
extraLog, err := base64.StdEncoding.DecodeString(`AAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUAAABTcGVjIElEIEV2ZW50MDMAAAAAAAACAAEC
AAAABAAUAAsAIAAACAAAAAYAAAACAAAABACX3UqVWDMNeg2Hkxyy6Q35wO4yBwsAVXbW4fKD8+xm
Kv75L4ecBpvSR4d6bz+A7z1prUcKPuMrAQAACAISpgJpbWFfaGFzaD1zaGEyNTYgYXBwYXJtb3I9
MSBwY2k9bm9hZXIsbm9hdHMgcHJpbnRrLmRldmttc2c9b24gc2xhYl9ub21lcmdlIGNvbnNvbGU9
dHR5UzAsMTE1MjAwbjggY29uc29sZT10dHkwIGdsaW51eC1ib290LWltYWdlPTIwMjExMDI3LjAy
LjAzIHF1aWV0IHNwbGFzaCBwbHltb3V0aC5pZ25vcmUtc2VyaWFsLWNvbnNvbGVzIGxzbT1sb2Nr
ZG93bix5YW1hLGxvYWRwaW4sc2FmZXNldGlkLGludGVncml0eSxhcHBhcm1vcixzZWxpbnV4LHNt
YWNrLHRvbW95byxicGYgcGFuaWM9MzAgaTkxNS5lbmFibGVfcHNyPTA=`)
if err != nil {
t.Fatal(err)
}
combined, err := AppendEvents(base, extraLog)
if err != nil {
t.Fatalf("CombineEventLogs() failed: %v", err)
}
// Make sure the combined log parses successfully and has one more
// event than the base log.
parsedBase, err := ParseEventLog(base)
if err != nil {
t.Fatal(err)
}
parsed, err := ParseEventLog(combined)
if err != nil {
t.Fatalf("ParseEventLog(combined_log) failed: %v", err)
}
if got, want := len(parsed.rawEvents), len(parsedBase.rawEvents)+1; got != want {
t.Errorf("unexpected number of events in combined log: got %d, want %d", got, want)
for i, e := range parsed.rawEvents {
t.Logf("logs[%d] = %+v", i, e)
}
}
}

View File

@ -91,12 +91,62 @@ func ExampleAK_credentialActivation() {
} }
} }
func ExampleAK_credentialActivationWithEK() {
tpm, err := attest.OpenTPM(nil)
if err != nil {
log.Fatalf("Failed to open TPM: %v", err)
}
defer tpm.Close()
// Create a new AK.
ak, err := tpm.NewAK(nil)
if err != nil {
log.Fatalf("Failed to create AK: %v", err)
}
defer ak.Close(tpm)
// Read the EK certificates.
ekCerts, err := tpm.EKCertificates()
if err != nil {
log.Fatalf("Failed to enumerate EKs: %v", err)
}
// Read parameters necessary to generate a challenge.
ap := ak.AttestationParameters()
// Try activating with each EK certificate.
for _, ek := range ekCerts {
// Generate a credential activation challenge (usually done on the server).
activation := attest.ActivationParameters{
TPMVersion: tpm.Version(),
EK: ek.Public,
AK: ap,
}
secret, challenge, err := activation.Generate()
if err != nil {
log.Fatalf("Failed to generate activation challenge: %v", err)
}
// Challenge the AK & EK properties to recieve the decrypted secret.
decrypted, err := ak.ActivateCredentialWithEK(tpm, *challenge, ek)
if err != nil {
log.Fatalf("Failed to activate credential: %v", err)
}
// Check that the AK completed the challenge (usually done on the server).
if subtle.ConstantTimeCompare(secret, decrypted) == 0 {
log.Fatal("Activation response did not match secret")
}
}
}
func TestExampleAK(t *testing.T) { func TestExampleAK(t *testing.T) {
if !*testExamples { if !*testExamples {
t.SkipNow() t.SkipNow()
} }
ExampleAK() ExampleAK()
ExampleAK_credentialActivation() ExampleAK_credentialActivation()
ExampleAK_credentialActivationWithEK()
} }
func TestExampleTPM(t *testing.T) { func TestExampleTPM(t *testing.T) {

View File

@ -2,14 +2,12 @@ package internal
import ( import (
"bytes" "bytes"
"crypto/x509"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"unicode/utf16" "unicode/utf16"
"github.com/google/certificate-transparency-go/asn1"
"github.com/google/certificate-transparency-go/x509"
) )
const ( const (
@ -37,6 +35,16 @@ var (
certHashSHA512SigGUID = efiGUID{0x446dbf63, 0x2502, 0x4cda, [8]byte{0xbc, 0xfa, 0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d}} certHashSHA512SigGUID = efiGUID{0x446dbf63, 0x2502, 0x4cda, [8]byte{0xbc, 0xfa, 0x24, 0x65, 0xd2, 0xb0, 0xfe, 0x9d}}
) )
var (
// https://github.com/rhboot/shim/blob/20e4d9486fcae54ee44d2323ae342ffe68c920e6/lib/guid.c#L36
// GUID used by the shim.
shimLockGUID = efiGUID{0x605dab50, 0xe046, 0x4300, [8]byte{0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23}}
// "SbatLevel" encoded as UCS-2.
shimSbatVarName = []uint16{0x53, 0x62, 0x61, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c}
// "MokListTrusted" encoded as UCS-2.
shimMokListTrustedVarName = []uint16{0x4d, 0x6f, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64}
)
// EventType describes the type of event signalled in the event log. // EventType describes the type of event signalled in the event log.
type EventType uint32 type EventType uint32
@ -79,6 +87,23 @@ const (
EFIVariableAuthority EventType = 0x800000e0 EFIVariableAuthority EventType = 0x800000e0
) )
// EFIDeviceType describes the type of a device specified by a device path.
type EFIDeviceType uint8
// "Device Path Protocol" type values.
//
// Section 9.3.2 of the UEFI specification, accessible at:
// https://uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf
const (
HardwareDevice EFIDeviceType = 0x01
ACPIDevice EFIDeviceType = 0x02
MessagingDevice EFIDeviceType = 0x03
MediaDevice EFIDeviceType = 0x04
BBSDevice EFIDeviceType = 0x05
EndDeviceArrayMarker EFIDeviceType = 0x7f
)
// ErrSigMissingGUID is returned if an EFI_SIGNATURE_DATA structure was parsed // ErrSigMissingGUID is returned if an EFI_SIGNATURE_DATA structure was parsed
// successfully, however was missing the SignatureOwner GUID. This case is // successfully, however was missing the SignatureOwner GUID. This case is
// handled specially as a workaround for a bug relating to authority events. // handled specially as a workaround for a bug relating to authority events.
@ -163,7 +188,7 @@ func (e EventType) String() string {
func UntrustedParseEventType(et uint32) (EventType, error) { func UntrustedParseEventType(et uint32) (EventType, error) {
// "The value associated with a UEFI specific platform event type MUST be in // "The value associated with a UEFI specific platform event type MUST be in
// the range between 0x80000000 and 0x800000FF, inclusive." // the range between 0x80000000 and 0x800000FF, inclusive."
if (et < 0x80000000 && et > 0x800000FF) || (et < 0x0 && et > 0x12) { if (et < 0x80000000 && et > 0x800000FF) || (et <= 0x0 && et > 0x12) {
return EventType(0), fmt.Errorf("event type not between [0x0, 0x12] or [0x80000000, 0x800000FF]: got %#x", et) return EventType(0), fmt.Errorf("event type not between [0x0, 0x12] or [0x80000000, 0x800000FF]: got %#x", et)
} }
if _, ok := eventTypeNames[EventType(et)]; !ok { if _, ok := eventTypeNames[EventType(et)]; !ok {
@ -250,15 +275,32 @@ type UEFIVariableAuthority struct {
// a UEFI variable authority. // a UEFI variable authority.
// //
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789 // https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789
func ParseUEFIVariableAuthority(r io.Reader) (UEFIVariableAuthority, error) { func ParseUEFIVariableAuthority(v UEFIVariableData) (UEFIVariableAuthority, error) {
v, err := ParseUEFIVariableData(r) if v.Header.VariableName == shimLockGUID && (
if err != nil { // Skip parsing new SBAT section logged by shim.
return UEFIVariableAuthority{}, err // See https://github.com/rhboot/shim/blob/main/SBAT.md for more.
unicodeNameEquals(v, shimSbatVarName) || //https://github.com/rhboot/shim/blob/20e4d9486fcae54ee44d2323ae342ffe68c920e6/include/sbat.h#L9-L12
// Skip parsing new MokListTrusted section logged by shim.
// See https://github.com/rhboot/shim/blob/main/MokVars.txt for more.
unicodeNameEquals(v, shimMokListTrustedVarName)) { //https://github.com/rhboot/shim/blob/4e513405b4f1641710115780d19dcec130c5208f/mok.c#L169-L182
return UEFIVariableAuthority{}, nil
} }
certs, err := parseEfiSignature(v.VariableData) certs, err := parseEfiSignature(v.VariableData)
return UEFIVariableAuthority{Certs: certs}, err return UEFIVariableAuthority{Certs: certs}, err
} }
func unicodeNameEquals(v UEFIVariableData, comp []uint16) bool {
if len(v.UnicodeName) != len(comp) {
return false
}
for i, v := range v.UnicodeName {
if v != comp[i] {
return false
}
}
return true
}
// efiSignatureData represents the EFI_SIGNATURE_DATA type. // efiSignatureData represents the EFI_SIGNATURE_DATA type.
// See section "31.4.1 Signature Database" in the specification for more information. // See section "31.4.1 Signature Database" in the specification for more information.
type efiSignatureData struct { type efiSignatureData struct {
@ -405,7 +447,6 @@ func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
} else { } else {
// A bug in shim may cause an event to be missing the SignatureOwner GUID. // A bug in shim may cause an event to be missing the SignatureOwner GUID.
// We handle this, but signal back to the caller using ErrSigMissingGUID. // We handle this, but signal back to the caller using ErrSigMissingGUID.
if _, isStructuralErr := err.(asn1.StructuralError); isStructuralErr {
var err2 error var err2 error
cert, err2 = x509.ParseCertificate(b) cert, err2 = x509.ParseCertificate(b)
if err2 == nil { if err2 == nil {
@ -413,6 +454,89 @@ func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
err = ErrSigMissingGUID err = ErrSigMissingGUID
} }
} }
}
return certificates, err return certificates, err
} }
type EFIDevicePathElement struct {
Type EFIDeviceType
Subtype uint8
Data []byte
}
// EFIImageLoad describes an EFI_IMAGE_LOAD_EVENT structure.
type EFIImageLoad struct {
Header EFIImageLoadHeader
DevPathData []byte
}
type EFIImageLoadHeader struct {
LoadAddr uint64
Length uint64
LinkAddr uint64
DevicePathLen uint64
}
func parseDevicePathElement(r io.Reader) (EFIDevicePathElement, error) {
var (
out EFIDevicePathElement
dataLen uint16
)
if err := binary.Read(r, binary.LittleEndian, &out.Type); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading type: %v", err)
}
if err := binary.Read(r, binary.LittleEndian, &out.Subtype); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading subtype: %v", err)
}
if err := binary.Read(r, binary.LittleEndian, &dataLen); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading data len: %v", err)
}
if dataLen > maxNameLen {
return EFIDevicePathElement{}, fmt.Errorf("device path data too long: %d > %d", dataLen, maxNameLen)
}
if dataLen < 4 {
return EFIDevicePathElement{}, fmt.Errorf("device path data too short: %d < %d", dataLen, 4)
}
out.Data = make([]byte, dataLen-4)
if err := binary.Read(r, binary.LittleEndian, &out.Data); err != nil {
return EFIDevicePathElement{}, fmt.Errorf("reading data: %v", err)
}
return out, nil
}
func (h *EFIImageLoad) DevicePath() ([]EFIDevicePathElement, error) {
var (
r = bytes.NewReader(h.DevPathData)
out []EFIDevicePathElement
)
for r.Len() > 0 {
e, err := parseDevicePathElement(r)
if err != nil {
return nil, err
}
if e.Type == EndDeviceArrayMarker {
return out, nil
}
out = append(out, e)
}
return out, nil
}
// ParseEFIImageLoad parses an EFI_IMAGE_LOAD_EVENT structure.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_EFI_Platform_1_22_Final_-v15.pdf#page=17
func ParseEFIImageLoad(r io.Reader) (ret EFIImageLoad, err error) {
err = binary.Read(r, binary.LittleEndian, &ret.Header)
if err != nil {
return
}
if ret.Header.DevicePathLen > maxNameLen {
return EFIImageLoad{}, fmt.Errorf("device path structure too long: %d > %d", ret.Header.DevicePathLen, maxNameLen)
}
ret.DevPathData = make([]byte, ret.Header.DevicePathLen)
err = binary.Read(r, binary.LittleEndian, &ret.DevPathData)
return
}

View File

@ -12,7 +12,8 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
// +build linux,!gofuzz,cgo //go:build linux && !gofuzz && cgo && tspi
// +build linux,!gofuzz,cgo,tspi
package attest package attest
@ -51,7 +52,7 @@ func (k *trousersKey12) close(tpm tpmBase) error {
return nil // No state for tpm 1.2. return nil // No state for tpm 1.2.
} }
func (k *trousersKey12) activateCredential(tb tpmBase, in EncryptedCredential) ([]byte, error) { func (k *trousersKey12) activateCredential(tb tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) {
t, ok := tb.(*trousersTPM) t, ok := tb.(*trousersTPM)
if !ok { if !ok {
return nil, fmt.Errorf("expected *linuxTPM, got %T", tb) return nil, fmt.Errorf("expected *linuxTPM, got %T", tb)
@ -64,7 +65,7 @@ func (k *trousersKey12) activateCredential(tb tpmBase, in EncryptedCredential) (
return cred, nil return cred, nil
} }
func (k *trousersKey12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { func (k *trousersKey12) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {
t, ok := tb.(*trousersTPM) t, ok := tb.(*trousersTPM)
if !ok { if !ok {
return nil, fmt.Errorf("expected *linuxTPM, got %T", tb) return nil, fmt.Errorf("expected *linuxTPM, got %T", tb)
@ -72,6 +73,9 @@ func (k *trousersKey12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, er
if alg != HashSHA1 { if alg != HashSHA1 {
return nil, fmt.Errorf("only SHA1 algorithms supported on TPM 1.2, not %v", alg) return nil, fmt.Errorf("only SHA1 algorithms supported on TPM 1.2, not %v", alg)
} }
if selectedPCRs != nil {
return nil, fmt.Errorf("selecting PCRs not supported on TPM 1.2 (parameter must be nil)")
}
quote, rawSig, err := attestation.GetQuote(t.ctx, k.blob, nonce) quote, rawSig, err := attestation.GetQuote(t.ctx, k.blob, nonce)
if err != nil { if err != nil {
@ -91,3 +95,7 @@ func (k *trousersKey12) attestationParameters() AttestationParameters {
UseTCSDActivationFormat: true, UseTCSDActivationFormat: true,
} }
} }
func (k *trousersKey12) certify(tb tpmBase, handle interface{}, _ CertifyOpts) (*CertificationParameters, error) {
return nil, fmt.Errorf("not implemented")
}

View File

@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build windows
// +build windows // +build windows
package attest package attest
@ -19,6 +20,7 @@ package attest
import ( import (
"fmt" "fmt"
"github.com/google/go-tpm/legacy/tpm2"
tpm1 "github.com/google/go-tpm/tpm" tpm1 "github.com/google/go-tpm/tpm"
) )
@ -47,7 +49,7 @@ func (k *windowsKey12) marshal() ([]byte, error) {
return out.Serialize() return out.Serialize()
} }
func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential) ([]byte, error) { func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) {
tpm, ok := t.(*windowsTPM) tpm, ok := t.(*windowsTPM)
if !ok { if !ok {
return nil, fmt.Errorf("expected *windowsTPM, got %T", t) return nil, fmt.Errorf("expected *windowsTPM, got %T", t)
@ -59,7 +61,7 @@ func (k *windowsKey12) activateCredential(t tpmBase, in EncryptedCredential) ([]
return decryptCredential(secretKey, in.Secret) return decryptCredential(secretKey, in.Secret)
} }
func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {
if alg != HashSHA1 { if alg != HashSHA1 {
return nil, fmt.Errorf("only SHA1 algorithms supported on TPM 1.2, not %v", alg) return nil, fmt.Errorf("only SHA1 algorithms supported on TPM 1.2, not %v", alg)
} }
@ -78,11 +80,6 @@ func (k *windowsKey12) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, err
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err) return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
} }
selectedPCRs := make([]int, 24)
for pcr, _ := range selectedPCRs {
selectedPCRs[pcr] = pcr
}
sig, pcrc, err := tpm1.Quote(tpm, tpmKeyHnd, nonce, selectedPCRs[:], wellKnownAuth[:]) sig, pcrc, err := tpm1.Quote(tpm, tpmKeyHnd, nonce, selectedPCRs[:], wellKnownAuth[:])
if err != nil { if err != nil {
return nil, fmt.Errorf("Quote() failed: %v", err) return nil, fmt.Errorf("Quote() failed: %v", err)
@ -110,6 +107,9 @@ func (k *windowsKey12) attestationParameters() AttestationParameters {
Public: k.public, Public: k.public,
} }
} }
func (k *windowsKey12) certify(tb tpmBase, handle interface{}, _ CertifyOpts) (*CertificationParameters, error) {
return nil, fmt.Errorf("not implemented")
}
// windowsKey20 represents a key bound to a TPM 2.0. // windowsKey20 represents a key bound to a TPM 2.0.
type windowsKey20 struct { type windowsKey20 struct {
@ -147,7 +147,7 @@ func (k *windowsKey20) marshal() ([]byte, error) {
return out.Serialize() return out.Serialize()
} }
func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential) ([]byte, error) { func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) {
tpm, ok := t.(*windowsTPM) tpm, ok := t.(*windowsTPM)
if !ok { if !ok {
return nil, fmt.Errorf("expected *windowsTPM, got %T", t) return nil, fmt.Errorf("expected *windowsTPM, got %T", t)
@ -155,7 +155,7 @@ func (k *windowsKey20) activateCredential(t tpmBase, in EncryptedCredential) ([]
return tpm.pcp.ActivateCredential(k.hnd, append(in.Credential, in.Secret...)) return tpm.pcp.ActivateCredential(k.hnd, append(in.Credential, in.Secret...))
} }
func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {
t, ok := tb.(*windowsTPM) t, ok := tb.(*windowsTPM)
if !ok { if !ok {
return nil, fmt.Errorf("expected *windowsTPM, got %T", tb) return nil, fmt.Errorf("expected *windowsTPM, got %T", tb)
@ -169,7 +169,7 @@ func (k *windowsKey20) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, err
if err != nil { if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err) return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
} }
return quote20(tpm, tpmKeyHnd, alg.goTPMAlg(), nonce) return quote20(tpm, tpmKeyHnd, alg.goTPMAlg(), nonce, selectedPCRs)
} }
func (k *windowsKey20) close(tpm tpmBase) error { func (k *windowsKey20) close(tpm tpmBase) error {
@ -184,3 +184,31 @@ func (k *windowsKey20) attestationParameters() AttestationParameters {
CreateSignature: k.createSignature, CreateSignature: k.createSignature,
} }
} }
func (k *windowsKey20) certify(tb tpmBase, handle interface{}, _ CertifyOpts) (*CertificationParameters, error) {
t, ok := tb.(*windowsTPM)
if !ok {
return nil, fmt.Errorf("expected *windowsTPM, got %T", tb)
}
h, ok := handle.(uintptr)
if !ok {
return nil, fmt.Errorf("expected uinptr, got %T", handle)
}
hnd, err := t.pcp.TPMKeyHandle(h)
if err != nil {
return nil, fmt.Errorf("TPMKeyHandle() failed: %v", err)
}
akHnd, err := t.pcp.TPMKeyHandle(k.hnd)
if err != nil {
return nil, fmt.Errorf("TPMKeyHandle() failed: %v", err)
}
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
scheme := tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: tpm2.AlgSHA1, // PCP-created AK uses SHA1
}
return certify(tpm, hnd, akHnd, nil, scheme)
}

View File

@ -12,20 +12,20 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build windows
// +build windows // +build windows
package attest package attest
import ( import (
"bytes" "bytes"
"crypto/x509"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm/tpmutil" "github.com/google/go-tpm/tpmutil"
tpmtbs "github.com/google/go-tpm/tpmutil/tbs" tpmtbs "github.com/google/go-tpm/tpmutil/tbs"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -369,8 +369,8 @@ func (h *winPCP) Close() error {
return closeNCryptObject(h.hProv) return closeNCryptObject(h.hProv)
} }
// DeleteKey permanently removes the key with the given handle // DeleteKey permanently removes the key with the given handle from the system,
// from the system, and frees its handle. // and frees its handle.
func (h *winPCP) DeleteKey(kh uintptr) error { func (h *winPCP) DeleteKey(kh uintptr) error {
r, _, msg := nCryptDeleteKey.Call(kh, 0) r, _, msg := nCryptDeleteKey.Call(kh, 0)
if r != 0 { if r != 0 {

View File

@ -16,10 +16,10 @@ package attest
import ( import (
"bytes" "bytes"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-attestation/attest/internal" "github.com/google/go-attestation/attest/internal"
) )
@ -56,8 +56,26 @@ type SecurebootState struct {
// PostSeparatorAuthority describes the use of a secure-boot key to authorize // PostSeparatorAuthority describes the use of a secure-boot key to authorize
// the execution of a binary after the separator. // the execution of a binary after the separator.
PostSeparatorAuthority []x509.Certificate PostSeparatorAuthority []x509.Certificate
// DriverLoadSourceHints describes the origin of boot services drivers.
// This data is not tamper-proof and must only be used as a hint.
DriverLoadSourceHints []DriverLoadSource
// DMAProtectionDisabled is true if the platform reports during boot that
// DMA protection is supported but disabled.
//
// See: https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/oem-kernel-dma-protection
DMAProtectionDisabled bool
} }
// DriverLoadSource describes the logical origin of a boot services driver.
type DriverLoadSource uint8
const (
UnknownSource DriverLoadSource = iota
PciMmioSource
)
// ParseSecurebootState parses a series of events to determine the // ParseSecurebootState parses a series of events to determine the
// configuration of secure boot on a device. An error is returned if // configuration of secure boot on a device. An error is returned if
// the state cannot be determined, or if the event log is structured // the state cannot be determined, or if the event log is structured
@ -79,13 +97,15 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
var ( var (
out SecurebootState out SecurebootState
seenSeparator bool seenSeparator7 bool
seenSeparator2 bool
seenAuthority bool seenAuthority bool
seenVars = map[string]bool{} seenVars = map[string]bool{}
driverSources [][]internal.EFIDevicePathElement
) )
for _, e := range events { for _, e := range events {
if e.Index != 7 { if e.Index != 7 && e.Index != 2 {
continue continue
} }
@ -93,14 +113,16 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("unrecognised event type: %v", err) return nil, fmt.Errorf("unrecognised event type: %v", err)
} }
digestVerify := e.digestEquals(e.Data) digestVerify := e.digestEquals(e.Data)
switch e.Index {
case 7:
switch et { switch et {
case internal.Separator: case internal.Separator:
if seenSeparator { if seenSeparator7 {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence) return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
} }
seenSeparator = true seenSeparator7 = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) { if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data) return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
} }
@ -109,10 +131,17 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
} }
case internal.EFIAction: case internal.EFIAction:
if string(e.Data) == "UEFI Debug Mode" { switch string(e.Data) {
case "UEFI Debug Mode":
return nil, errors.New("a UEFI debugger was present during boot") return nil, errors.New("a UEFI debugger was present during boot")
case "DMA Protection Disabled":
if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.sequence, digestVerify)
} }
out.DMAProtectionDisabled = true
default:
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence) return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
}
case internal.EFIVariableDriverConfig: case internal.EFIVariableDriverConfig:
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data)) v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
@ -123,7 +152,7 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence) return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
} }
seenVars[v.VarName()] = true seenVars[v.VarName()] = true
if seenSeparator { if seenSeparator7 {
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName()) return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
} }
@ -156,7 +185,12 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
} }
case internal.EFIVariableAuthority: case internal.EFIVariableAuthority:
a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data)) v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing UEFI variable data: %v", err)
}
a, err := internal.ParseUEFIVariableAuthority(v)
if err != nil { if err != nil {
// Workaround for: https://github.com/google/go-attestation/issues/157 // Workaround for: https://github.com/google/go-attestation/issues/157
if err == internal.ErrSigMissingGUID { if err == internal.ErrSigMissingGUID {
@ -175,15 +209,70 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
if digestVerify != nil { if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify) return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
} }
if !seenSeparator { if !seenSeparator7 {
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...) out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
} else { } else {
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...) out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
} }
default: default:
return nil, fmt.Errorf("unexpected event type: %v", et) return nil, fmt.Errorf("unexpected event type in PCR7: %v", et)
} }
case 2:
switch et {
case internal.Separator:
if seenSeparator2 {
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
}
seenSeparator2 = true
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
}
if digestVerify != nil {
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
}
case internal.EFIBootServicesDriver:
if !seenSeparator2 {
imgLoad, err := internal.ParseEFIImageLoad(bytes.NewReader(e.Data))
if err != nil {
return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.sequence, err)
}
dp, err := imgLoad.DevicePath()
if err != nil {
return nil, fmt.Errorf("failed to parse device path for driver load event %d: %v", e.sequence, err)
}
driverSources = append(driverSources, dp)
}
}
}
}
// Compute driver source hints based on the EFI device path observed in
// EFI Boot-services driver-load events.
sourceLoop:
for _, source := range driverSources {
// We consider a driver to have originated from PCI-MMIO if any number
// of elements in the device path [1] were PCI devices, and are followed by
// an element representing a "relative offset range" read.
// In the wild, we have typically observed 4-tuple device paths for such
// devices: ACPI device -> PCI device -> PCI device -> relative offset.
//
// [1]: See section 9 of the UEFI specification v2.6 or greater.
var seenPCI bool
for _, e := range source {
// subtype 0x1 corresponds to a PCI device (See: 9.3.2.1)
if e.Type == internal.HardwareDevice && e.Subtype == 0x1 {
seenPCI = true
}
// subtype 0x8 corresponds to "relative offset range" (See: 9.3.6.8)
if seenPCI && e.Type == internal.MediaDevice && e.Subtype == 0x8 {
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, PciMmioSource)
continue sourceLoop
}
}
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, UnknownSource)
} }
if !out.Enabled { if !out.Enabled {

View File

@ -15,13 +15,14 @@
package attest package attest
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"io/ioutil" "os"
"testing" "testing"
) )
func TestSecureBoot(t *testing.T) { func TestSecureBoot(t *testing.T) {
data, err := ioutil.ReadFile("testdata/windows_gcp_shielded_vm.json") data, err := os.ReadFile("testdata/windows_gcp_shielded_vm.json")
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -51,7 +52,7 @@ func TestSecureBoot(t *testing.T) {
// See: https://github.com/google/go-attestation/issues/157 // See: https://github.com/google/go-attestation/issues/157
func TestSecureBootBug157(t *testing.T) { func TestSecureBootBug157(t *testing.T) {
raw, err := ioutil.ReadFile("testdata/sb_cert_eventlog") raw, err := os.ReadFile("testdata/sb_cert_eventlog")
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -61,54 +62,97 @@ func TestSecureBootBug157(t *testing.T) {
} }
pcrs := []PCR{ pcrs := []PCR{
{'\x00', []byte("Q\xc3#\xde\f\fiOF\x01\xcd\xd0+\xebX\xff\x13b\x9ft"), '\x03'}, {'\x00', []byte("Q\xc3#\xde\f\fiOF\x01\xcd\xd0+\xebX\xff\x13b\x9ft"), '\x03', false},
{'\x01', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x01', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x02', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x02', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x03', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x03', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x04', []byte("\xb7q\x00\x8d\x17<\x02+\xc1oKM\x1a\u007f\x8b\x99\xed\x88\xee\xb1"), '\x03'}, {'\x04', []byte("\xb7q\x00\x8d\x17<\x02+\xc1oKM\x1a\u007f\x8b\x99\xed\x88\xee\xb1"), '\x03', false},
{'\x05', []byte("\xd79j\xc6\xe8\x87\xda\"ޠ;@\x95/p\xb8\xdbҩ\x96"), '\x03'}, {'\x05', []byte("\xd79j\xc6\xe8\x87\xda\"ޠ;@\x95/p\xb8\xdbҩ\x96"), '\x03', false},
{'\x06', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x06', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\a', []byte("E\xa8b\x1d4\xa5}\xf2\xb2\xe7\xf1L\x92\xb9\x9a\xc8\xde}X\x05"), '\x03'}, {'\a', []byte("E\xa8b\x1d4\xa5}\xf2\xb2\xe7\xf1L\x92\xb9\x9a\xc8\xde}X\x05"), '\x03', false},
{'\b', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\b', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\t', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\t', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\n', []byte("\x82\x84\x10>\x06\xd4\x01\"\xbcd\xa0䡉\x1a\xf9\xec\xd4\\\xf6"), '\x03'}, {'\n', []byte("\x82\x84\x10>\x06\xd4\x01\"\xbcd\xa0䡉\x1a\xf9\xec\xd4\\\xf6"), '\x03', false},
{'\v', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\v', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\r', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\r', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x0e', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x0e', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x0f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x0f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x10', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x10', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x11', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03'}, {'\x11', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03', false},
{'\x12', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03'}, {'\x12', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03', false},
{'\x13', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03'}, {'\x13', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03', false},
{'\x14', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03'}, {'\x14', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03', false},
{'\x15', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03'}, {'\x15', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03', false},
{'\x16', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03'}, {'\x16', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x03', false},
{'\x17', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'}, {'\x17', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03', false},
{'\x00', []byte("\xfc\xec\xb5j\xcc08b\xb3\x0e\xb3Bę\v\xebP\xb5ૉr$I\xc2٧?7\xb0\x19\xfe"), '\x05'}, {'\x00', []byte("\xfc\xec\xb5j\xcc08b\xb3\x0e\xb3Bę\v\xebP\xb5ૉr$I\xc2٧?7\xb0\x19\xfe"), '\x05', false},
{'\x01', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x01', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x02', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x02', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x03', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x03', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x04', []byte("\xa9)h\x80oy_\xa3D5\xd9\xf1\x18\x13hL\xa1\xe7\x05`w\xf7\x00\xbaI\xf2o\x99b\xf8m\x89"), '\x05'}, {'\x04', []byte("\xa9)h\x80oy_\xa3D5\xd9\xf1\x18\x13hL\xa1\xe7\x05`w\xf7\x00\xbaI\xf2o\x99b\xf8m\x89"), '\x05', false},
{'\x05', []byte("̆\x18\xb7y2\xb4\xef\xda\x12\xccX\xba\xd9>\xcdѕ\x9d\xea)\xe5\xabyE%\xa6\x19\xf5\xba\xab\xee"), '\x05'}, {'\x05', []byte("̆\x18\xb7y2\xb4\xef\xda\x12\xccX\xba\xd9>\xcdѕ\x9d\xea)\xe5\xabyE%\xa6\x19\xf5\xba\xab\xee"), '\x05', false},
{'\x06', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x06', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\a', []byte("Q\xb3\x04\x88\xc9\xe6%]\x82+\xdc\x1b ٩,2\xbd\xe6\xc3\xe7\xbc\x02\xbc\xdd2\x82^\xb5\xef\x06\x9a"), '\x05'}, {'\a', []byte("Q\xb3\x04\x88\xc9\xe6%]\x82+\xdc\x1b ٩,2\xbd\xe6\xc3\xe7\xbc\x02\xbc\xdd2\x82^\xb5\xef\x06\x9a"), '\x05', false},
{'\b', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\b', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\t', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\t', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\n', []byte("\xc3l\x9a\xb1\x10\x9b\xa0\x8a?dX!\x18\xf8G\x1a]i[\xc9#\xa0\xa2\xbd\x04]\xb1K\x97OB9"), '\x05'}, {'\n', []byte("\xc3l\x9a\xb1\x10\x9b\xa0\x8a?dX!\x18\xf8G\x1a]i[\xc9#\xa0\xa2\xbd\x04]\xb1K\x97OB9"), '\x05', false},
{'\v', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\v', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\r', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\r', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x0e', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x0e', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x0f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x0f', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x10', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x10', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
{'\x11', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05'}, {'\x11', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05', false},
{'\x12', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05'}, {'\x12', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05', false},
{'\x13', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05'}, {'\x13', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05', false},
{'\x14', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05'}, {'\x14', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05', false},
{'\x15', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05'}, {'\x15', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05', false},
{'\x16', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05'}, {'\x16', []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"), '\x05', false},
{'\x17', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05'}, {'\x17', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x05', false},
}
events, err := elr.Verify(pcrs)
if err != nil {
t.Fatalf("failed to verify log: %v", err)
}
sbs, err := ParseSecurebootState(events)
if err != nil {
t.Fatalf("failed parsing secureboot state: %v", err)
}
if got, want := len(sbs.PostSeparatorAuthority), 3; got != want {
t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want)
}
}
func b64MustDecode(input string) []byte {
b, err := base64.StdEncoding.DecodeString(input)
if err != nil {
panic(err)
}
return b
}
func TestSecureBootOptionRom(t *testing.T) {
raw, err := os.ReadFile("testdata/option_rom_eventlog")
if err != nil {
t.Fatalf("reading test data: %v", err)
}
elr, err := ParseEventLog(raw)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
pcrs := []PCR{
{'\x00', b64MustDecode("AVGK7ch6DvUF0nJh74NYCefaAIY="), '\x03', false},
{'\x01', b64MustDecode("vr/0wIpmd0c6tgTO3vuC+FDN6IM="), '\x03', false},
{'\x02', b64MustDecode("NmoxoMB1No8OEIVzM+ou1uigD9M="), '\x03', false},
{'\x03', b64MustDecode("sqg7Dr8vg3Qpmlsr38MeqVWtcjY="), '\x03', false},
{'\x04', b64MustDecode("OfOIw5WekEaUcm9MAVttzq4GgKE="), '\x03', false},
{'\x05', b64MustDecode("cjoFIM9/KXhUh0K9FUFwayRGRZ4="), '\x03', false},
{'\x06', b64MustDecode("sqg7Dr8vg3Qpmlsr38MeqVWtcjY="), '\x03', false},
{'\x07', b64MustDecode("IN59+6a838ytrX4+sJnJHU2Xxa0="), '\x03', false},
} }
events, err := elr.Verify(pcrs) events, err := elr.Verify(pcrs)
@ -120,7 +164,52 @@ func TestSecureBootBug157(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("failed parsing secureboot state: %v", err) t.Errorf("failed parsing secureboot state: %v", err)
} }
if got, want := len(sbs.PostSeparatorAuthority), 3; got != want { if got, want := len(sbs.PostSeparatorAuthority), 2; got != want {
t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want) t.Errorf("len(sbs.PostSeparatorAuthority) = %d, want %d", got, want)
} }
if got, want := len(sbs.DriverLoadSourceHints), 1; got != want {
t.Fatalf("len(sbs.DriverLoadSourceHints) = %d, want %d", got, want)
}
if got, want := sbs.DriverLoadSourceHints[0], PciMmioSource; got != want {
t.Errorf("sbs.DriverLoadSourceHints[0] = %v, want %v", got, want)
}
}
func TestSecureBootEventLogUbuntu(t *testing.T) {
data, err := os.ReadFile("testdata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog")
if err != nil {
t.Fatalf("reading test data: %v", err)
}
el, err := ParseEventLog(data)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
evts := el.Events(HashSHA256)
if err != nil {
t.Fatalf("verifying event log: %v", err)
}
_, err = ParseSecurebootState(evts)
if err != nil {
t.Errorf("parsing sb state: %v", err)
}
}
func TestSecureBootEventLogFedora36(t *testing.T) {
data, err := os.ReadFile("testdata/coreos_36_shielded_vm_no_secure_boot_eventlog")
if err != nil {
t.Fatalf("reading test data: %v", err)
}
el, err := ParseEventLog(data)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
evts := el.Events(HashSHA256)
if err != nil {
t.Fatalf("verifying event log: %v", err)
}
_, err = ParseSecurebootState(evts)
if err != nil {
t.Errorf("parsing sb state: %v", err)
}
} }

Binary file not shown.

BIN
attest/testdata/option_rom_eventlog vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@ -18,17 +18,18 @@ import (
"bytes" "bytes"
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"net/url"
"strings" "strings"
"github.com/google/certificate-transparency-go/asn1" "github.com/google/go-tpm/legacy/tpm2"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil" "github.com/google/go-tpm/tpmutil"
"go.uber.org/multierr"
) )
const ( const (
@ -37,18 +38,21 @@ const (
tpmPtFwVersion1 = 0x00000100 + 11 // PT_FIXED + offset of 11 tpmPtFwVersion1 = 0x00000100 + 11 // PT_FIXED + offset of 11
// Defined in "Registry of reserved TPM 2.0 handles and localities". // Defined in "Registry of reserved TPM 2.0 handles and localities".
nvramCertIndex = 0x1c00002 nvramRSACertIndex = 0x1c00002
nvramRSAEkNonceIndex = 0x1c00003
nvramECCCertIndex = 0x1c0000a
nvramECCEkNonceIndex = 0x1c0000b
// Defined in "Registry of reserved TPM 2.0 handles and localities", and checked on a glinux machine. // Defined in "Registry of reserved TPM 2.0 handles and localities", and checked on a glinux machine.
commonSrkEquivalentHandle = 0x81000001 commonRSAEkEquivalentHandle = 0x81010001
commonEkEquivalentHandle = 0x81010001 commonECCEkEquivalentHandle = 0x81010002
) )
var ( var (
akTemplate = tpm2.Public{ akTemplateRSA = tpm2.Public{
Type: tpm2.AlgRSA, Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256, NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault, Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA,
RSAParameters: &tpm2.RSAParams{ RSAParameters: &tpm2.RSAParams{
Sign: &tpm2.SigScheme{ Sign: &tpm2.SigScheme{
Alg: tpm2.AlgRSASSA, Alg: tpm2.AlgRSASSA,
@ -57,7 +61,23 @@ var (
KeyBits: 2048, KeyBits: 2048,
}, },
} }
defaultSRKTemplate = tpm2.Public{ akTemplateECC = tpm2.Public{
Type: tpm2.AlgECC,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA,
ECCParameters: &tpm2.ECCParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
Hash: tpm2.AlgSHA256,
},
CurveID: tpm2.CurveNISTP256,
Point: tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
},
},
}
defaultRSASRKTemplate = tpm2.Public{
Type: tpm2.AlgRSA, Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256, NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA, Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA,
@ -71,9 +91,26 @@ var (
KeyBits: 2048, KeyBits: 2048,
}, },
} }
// Default EK template defined in: defaultECCSRKTemplate = tpm2.Public{
Type: tpm2.AlgECC,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA,
ECCParameters: &tpm2.ECCParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB,
},
CurveID: tpm2.CurveNISTP256,
Point: tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
},
},
}
// Default RSA and ECC EK templates defined in:
// https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf // https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
defaultEKTemplate = tpm2.Public{ defaultRSAEKTemplate = tpm2.Public{
Type: tpm2.AlgRSA, Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256, NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
@ -96,15 +133,24 @@ var (
ModulusRaw: make([]byte, 256), ModulusRaw: make([]byte, 256),
}, },
} }
// Template for an ECC key for signing outside-TPM objects. defaultECCEKTemplate = tpm2.Public{
eccKeyTemplate = tpm2.Public{
Type: tpm2.AlgECC, Type: tpm2.AlgECC,
NameAlg: tpm2.AlgSHA256, NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted, Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt,
AuthPolicy: []byte{
0x83, 0x71, 0x97, 0x67, 0x44, 0x84,
0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D,
0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52,
0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64,
0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14,
0x69, 0xAA,
},
ECCParameters: &tpm2.ECCParams{ ECCParameters: &tpm2.ECCParams{
Sign: &tpm2.SigScheme{ Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgECDSA, Alg: tpm2.AlgAES,
Hash: tpm2.AlgSHA256, KeyBits: 128,
Mode: tpm2.AlgCFB,
}, },
CurveID: tpm2.CurveNISTP256, CurveID: tpm2.CurveNISTP256,
Point: tpm2.ECPoint{ Point: tpm2.ECPoint{
@ -113,6 +159,25 @@ var (
}, },
}, },
} }
// Basic template for an ECDSA key signing outside-TPM objects. Other
// fields are populated depending on the key creation options.
ecdsaKeyTemplate = tpm2.Public{
Type: tpm2.AlgECC,
Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted,
ECCParameters: &tpm2.ECCParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
},
},
}
// Basic template for an RSA key signing outside-TPM objects. Other
// fields are populated depending on the key creation options.
rsaKeyTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted,
RSAParameters: &tpm2.RSAParams{},
}
) )
type tpm20Info struct { type tpm20Info struct {
@ -196,12 +261,12 @@ func ParseEKCertificate(ekCert []byte) (*x509.Certificate, error) {
var cert struct { var cert struct {
Raw asn1.RawContent Raw asn1.RawContent
} }
if _, err := asn1.UnmarshalWithParams(ekCert, &cert, "lax"); err != nil && x509.IsFatal(err) { if _, err := asn1.UnmarshalWithParams(ekCert, &cert, "lax"); err != nil {
return nil, fmt.Errorf("asn1.Unmarshal() failed: %v, wasWrapped=%v", err, wasWrapped) return nil, fmt.Errorf("asn1.Unmarshal() failed: %v, wasWrapped=%v", err, wasWrapped)
} }
c, err := x509.ParseCertificate(cert.Raw) c, err := x509.ParseCertificate(cert.Raw)
if err != nil && x509.IsFatal(err) { if err != nil {
return nil, fmt.Errorf("x509.ParseCertificate() failed: %v", err) return nil, fmt.Errorf("x509.ParseCertificate() failed: %v", err)
} }
return c, nil return c, nil
@ -217,23 +282,23 @@ func intelEKURL(ekPub *rsa.PublicKey) string {
pubHash.Write(ekPub.N.Bytes()) pubHash.Write(ekPub.N.Bytes())
pubHash.Write([]byte{0x1, 0x00, 0x01}) pubHash.Write([]byte{0x1, 0x00, 0x01})
return intelEKCertServiceURL + base64.URLEncoding.EncodeToString(pubHash.Sum(nil)) return intelEKCertServiceURL + url.QueryEscape(base64.URLEncoding.EncodeToString(pubHash.Sum(nil)))
} }
func readEKCertFromNVRAM20(tpm io.ReadWriter) (*x509.Certificate, error) { func readEKCertFromNVRAM20(tpm io.ReadWriter, nvramCertIndex tpmutil.Handle) (*x509.Certificate, error) {
ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, tpm2.HandleOwner, "", 0) // By passing nvramCertIndex as our auth handle we're using the NV index
// itself as the auth hierarchy, which is the same approach
// tpm2_getekcertificate takes.
ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, nvramCertIndex, "", 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading EK cert: %v", err) return nil, fmt.Errorf("reading EK cert: %v", err)
} }
return ParseEKCertificate(ekCert) return ParseEKCertificate(ekCert)
} }
func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte) (*Quote, error) { func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte, selectedPCRs []int) (*Quote, error) {
sel := tpm2.PCRSelection{Hash: hashAlg} sel := tpm2.PCRSelection{Hash: hashAlg,
numPCRs := 24 PCRs: selectedPCRs}
for pcr := 0; pcr < numPCRs; pcr++ {
sel.PCRs = append(sel.PCRs, pcr)
}
quote, sig, err := tpm2.Quote(tpm, akHandle, "", "", nonce, sel, tpm2.AlgNull) quote, sig, err := tpm2.Quote(tpm, akHandle, "", "", nonce, sel, tpm2.AlgNull)
if err != nil { if err != nil {
@ -248,12 +313,37 @@ func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm,
}, err }, err
} }
func pcrbanks(tpm io.ReadWriter) ([]HashAlg, error) {
vals, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityPCRs, 1024, 0)
if err != nil {
return nil, fmt.Errorf("failed to get TPM available PCR banks: %w", err)
}
var hAlgs []HashAlg
var errs error
for i, v := range vals {
pcrb, ok := v.(tpm2.PCRSelection)
if !ok {
errs = multierr.Append(errs, fmt.Errorf("failed to convert value %d to tpm2.PCRSelection: %v", i, v))
continue
}
if len(pcrb.PCRs) == 0 {
// ignore empty PCR banks.
continue
}
hAlgs = append(hAlgs, HashAlg(pcrb.Hash))
}
return hAlgs, errs
}
func readAllPCRs20(tpm io.ReadWriter, alg tpm2.Algorithm) (map[uint32][]byte, error) { func readAllPCRs20(tpm io.ReadWriter, alg tpm2.Algorithm) (map[uint32][]byte, error) {
numPCRs := 24 numPCRs := 24
out := map[uint32][]byte{} out := map[uint32][]byte{}
// The TPM 2.0 spec says that the TPM can partially fulfill the // The TPM 2.0 spec says that the TPM can partially fulfill the
// request. As such, we repeat the command up to 8 times to get all // request. As such, we repeat the command up to 24 times to get all
// 24 PCRs. // 24 PCRs.
for i := 0; i < numPCRs; i++ { for i := 0; i < numPCRs; i++ {
// Build a selection structure, specifying all PCRs we do // Build a selection structure, specifying all PCRs we do
@ -291,17 +381,22 @@ type tpmBase interface {
close() error close() error
tpmVersion() TPMVersion tpmVersion() TPMVersion
eks() ([]EK, error) eks() ([]EK, error)
ekCertificates() ([]EK, error)
info() (*TPMInfo, error) info() (*TPMInfo, error)
pcrbanks() ([]HashAlg, error)
loadAK(opaqueBlob []byte) (*AK, error) loadAK(opaqueBlob []byte) (*AK, error)
loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error)
newAK(opts *AKConfig) (*AK, error) newAK(opts *AKConfig) (*AK, error)
loadKey(opaqueBlob []byte) (*Key, error) loadKey(opaqueBlob []byte) (*Key, error)
loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error)
newKey(ak *AK, opts *KeyConfig) (*Key, error) newKey(ak *AK, opts *KeyConfig) (*Key, error)
newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error)
pcrs(alg HashAlg) ([]PCR, error) pcrs(alg HashAlg) ([]PCR, error)
measurementLog() ([]byte, error) measurementLog() ([]byte, error)
} }
//TPM interfaces with a TPM device on the system. // TPM interfaces with a TPM device on the system.
type TPM struct { type TPM struct {
// tpm refers to a concrete implementation of TPM logic, based on the current // tpm refers to a concrete implementation of TPM logic, based on the current
// platform and TPM version. // platform and TPM version.
@ -314,10 +409,20 @@ func (t *TPM) Close() error {
} }
// EKs returns the endorsement keys burned-in to the platform. // EKs returns the endorsement keys burned-in to the platform.
// Note for Linux clients: for historical reasons, the method assumes that
// the TPM has a single EK, and the EK's type is RSA. If the EK's type is ECC
// and the TPM contains an ECC EK Certificate, the EKCertificates() method
// should be used to retrieve the EKs.
func (t *TPM) EKs() ([]EK, error) { func (t *TPM) EKs() ([]EK, error) {
return t.tpm.eks() return t.tpm.eks()
} }
// EKCertificates returns the endorsement key certificates burned-in to the platform.
// It is guaranteed that each EK.Certificate field will be populated.
func (t *TPM) EKCertificates() ([]EK, error) {
return t.tpm.ekCertificates()
}
// Info returns information about the TPM. // Info returns information about the TPM.
func (t *TPM) Info() (*TPMInfo, error) { func (t *TPM) Info() (*TPMInfo, error) {
return t.tpm.info() return t.tpm.info()
@ -325,12 +430,18 @@ func (t *TPM) Info() (*TPMInfo, error) {
// LoadAK loads a previously-created ak into the TPM for use. // LoadAK loads a previously-created ak into the TPM for use.
// A key loaded via this function needs to be closed with .Close(). // A key loaded via this function needs to be closed with .Close().
// Only blobs generated by calling AK.Serialize() are valid parameters // Only blobs generated by calling AK.Marshal() are valid parameters
// to this function. // to this function.
func (t *TPM) LoadAK(opaqueBlob []byte) (*AK, error) { func (t *TPM) LoadAK(opaqueBlob []byte) (*AK, error) {
return t.tpm.loadAK(opaqueBlob) return t.tpm.loadAK(opaqueBlob)
} }
// LoadAKWithParent loads a previously-created ak into the TPM
// under the given parent for use.
func (t *TPM) LoadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error) {
return t.tpm.loadAKWithParent(opaqueBlob, parent)
}
// MeasurementLog returns the present value of the System Measurement Log. // MeasurementLog returns the present value of the System Measurement Log.
// //
// This is a low-level API. Consumers seeking to attest the state of the // This is a low-level API. Consumers seeking to attest the state of the
@ -354,14 +465,37 @@ func (t *TPM) NewAK(opts *AKConfig) (*AK, error) {
return t.tpm.newAK(opts) return t.tpm.newAK(opts)
} }
// NewKey creates an application key certified by the attestation key. // NewKey creates an application key certified by the attestation key. If opts is nil
// then DefaultConfig is used.
func (t *TPM) NewKey(ak *AK, opts *KeyConfig) (*Key, error) { func (t *TPM) NewKey(ak *AK, opts *KeyConfig) (*Key, error) {
if opts == nil {
opts = defaultConfig
}
if opts.Algorithm == "" && opts.Size == 0 {
opts = defaultConfig
}
return t.tpm.newKey(ak, opts) return t.tpm.newKey(ak, opts)
} }
// NewKeyCertifiedByKey creates an application key certified by
// the attestation key. Unlike NewKey(), this method does not require
// an attest.AK object and only requires the AK handle and its algorithm.
// Thus it can be used in cases where the attestation key was not created
// by go-attestation library. If opts is nil then DefaultConfig is used.
func (t *TPM) NewKeyCertifiedByKey(akHandle tpmutil.Handle, akAlg Algorithm, opts *KeyConfig) (*Key, error) {
if opts == nil {
opts = defaultConfig
}
if opts.Algorithm == "" && opts.Size == 0 {
opts = defaultConfig
}
ck := certifyingKey{handle: akHandle, alg: akAlg}
return t.tpm.newKeyCertifiedByKey(ck, opts)
}
// LoadKey loads a previously-created application key into the TPM for use. // LoadKey loads a previously-created application key into the TPM for use.
// A key loaded via this function needs to be closed with .Close(). // A key loaded via this function needs to be closed with .Close().
// Only blobs generated by calling Key.Serialize() are valid parameters // Only blobs generated by calling Key.Marshal() are valid parameters
// to this function. // to this function.
func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) { func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) {
return t.tpm.loadKey(opaqueBlob) return t.tpm.loadKey(opaqueBlob)
@ -376,11 +510,20 @@ func (t *TPM) PCRs(alg HashAlg) ([]PCR, error) {
return t.tpm.pcrs(alg) return t.tpm.pcrs(alg)
} }
// PCRBanks returns the list of supported PCR banks on the TPM.
//
// This is a low-level API. Consumers seeking to attest the state of the
// platform should use tpm.AttestPlatform() instead.
func (t *TPM) PCRBanks() ([]HashAlg, error) {
return t.tpm.pcrbanks()
}
func (t *TPM) attestPCRs(ak *AK, nonce []byte, alg HashAlg) (*Quote, []PCR, error) { func (t *TPM) attestPCRs(ak *AK, nonce []byte, alg HashAlg) (*Quote, []PCR, error) {
pcrs, err := t.PCRs(alg) pcrs, err := t.PCRs(alg)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to read %v PCRs: %v", alg, err) return nil, nil, fmt.Errorf("failed to read %v PCRs: %v", alg, err)
} }
quote, err := ak.Quote(t, nonce, alg) quote, err := ak.Quote(t, nonce, alg)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to quote using %v: %v", alg, err) return nil, nil, fmt.Errorf("failed to quote using %v: %v", alg, err)
@ -406,9 +549,9 @@ func (t *TPM) attestPlatform(ak *AK, nonce []byte, eventLog []byte) (*PlatformPa
EventLog: eventLog, EventLog: eventLog,
} }
algs := []HashAlg{HashSHA1} algs, err := t.PCRBanks()
if t.Version() == TPMVersion20 { if err != nil {
algs = []HashAlg{HashSHA1, HashSHA256} return nil, fmt.Errorf("failed to get PCR banks: %w", err)
} }
var lastErr error var lastErr error

View File

@ -12,17 +12,18 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
// +build linux,!gofuzz,cgo //go:build linux && !gofuzz && cgo && tspi
// +build linux,!gofuzz,cgo,tspi
package attest package attest
import ( import (
"crypto" "crypto"
"crypto/x509"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io/ioutil" "os"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tspi/attestation" "github.com/google/go-tspi/attestation"
"github.com/google/go-tspi/tspi" "github.com/google/go-tspi/tspi"
"github.com/google/go-tspi/tspiconst" "github.com/google/go-tspi/tspiconst"
@ -93,7 +94,7 @@ func readEKCertFromNVRAM12(ctx *tspi.Context) (*x509.Certificate, error) {
return ParseEKCertificate(ekCert) return ParseEKCertificate(ekCert)
} }
func (t *trousersTPM) eks() ([]EK, error) { func (t *trousersTPM) ekCertificates() ([]EK, error) {
cert, err := readEKCertFromNVRAM12(t.ctx) cert, err := readEKCertFromNVRAM12(t.ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err) return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err)
@ -103,14 +104,26 @@ func (t *trousersTPM) eks() ([]EK, error) {
}, nil }, nil
} }
func (t *trousersTPM) eks() ([]EK, error) {
return t.ekCertificates()
}
func (t *trousersTPM) newKey(*AK, *KeyConfig) (*Key, error) { func (t *trousersTPM) newKey(*AK, *KeyConfig) (*Key, error) {
return nil, fmt.Errorf("not implemented") return nil, fmt.Errorf("not implemented")
} }
func (t *trousersTPM) newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error) {
return nil, fmt.Errorf("not implemented")
}
func (t *trousersTPM) loadKey(opaqueBlob []byte) (*Key, error) { func (t *trousersTPM) loadKey(opaqueBlob []byte) (*Key, error) {
return nil, fmt.Errorf("not implemented") return nil, fmt.Errorf("not implemented")
} }
func (t *trousersTPM) loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error) {
return nil, fmt.Errorf("not implemented")
}
func (t *trousersTPM) newAK(opts *AKConfig) (*AK, error) { func (t *trousersTPM) newAK(opts *AKConfig) (*AK, error) {
pub, blob, err := attestation.CreateAIK(t.ctx) pub, blob, err := attestation.CreateAIK(t.ctx)
if err != nil { if err != nil {
@ -131,6 +144,10 @@ func (t *trousersTPM) loadAK(opaqueBlob []byte) (*AK, error) {
return &AK{ak: newTrousersKey12(sKey.Blob, sKey.Public)}, nil return &AK{ak: newTrousersKey12(sKey.Blob, sKey.Public)}, nil
} }
func (t *trousersTPM) loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error) {
return nil, fmt.Errorf("not implemented")
}
// allPCRs12 returns a map of all the PCR values on the TPM // allPCRs12 returns a map of all the PCR values on the TPM
func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) { func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
tpm := ctx.GetTPM() tpm := ctx.GetTPM()
@ -146,6 +163,10 @@ func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
return PCRs, nil return PCRs, nil
} }
func (t *trousersTPM) pcrbanks() ([]HashAlg, error) {
return []HashAlg{HashSHA1}, nil
}
func (t *trousersTPM) pcrs(alg HashAlg) ([]PCR, error) { func (t *trousersTPM) pcrs(alg HashAlg) ([]PCR, error) {
if alg != HashSHA1 { if alg != HashSHA1 {
return nil, fmt.Errorf("non-SHA1 algorithm %v is not supported on TPM 1.2", alg) return nil, fmt.Errorf("non-SHA1 algorithm %v is not supported on TPM 1.2", alg)
@ -168,5 +189,5 @@ func (t *trousersTPM) pcrs(alg HashAlg) ([]PCR, error) {
} }
func (t *trousersTPM) measurementLog() ([]byte, error) { func (t *trousersTPM) measurementLog() ([]byte, error) {
return ioutil.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements") return os.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements")
} }

View File

@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build linux && !gofuzz
// +build linux,!gofuzz // +build linux,!gofuzz
package attest package attest
@ -20,12 +21,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path" "path"
"strings" "strings"
"github.com/google/go-tpm/tpm2" "github.com/google/go-tpm/legacy/tpm2"
) )
const ( const (
@ -48,7 +48,7 @@ func InjectSimulatedTPMForTest(rwc io.ReadWriteCloser) *TPM {
func probeSystemTPMs() ([]probedTPM, error) { func probeSystemTPMs() ([]probedTPM, error) {
var tpms []probedTPM var tpms []probedTPM
tpmDevs, err := ioutil.ReadDir(tpmRoot) tpmDevs, err := os.ReadDir(tpmRoot)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return nil, err return nil, err
} }
@ -81,7 +81,7 @@ type linuxCmdChannel struct {
// MeasurementLog implements CommandChannelTPM20. // MeasurementLog implements CommandChannelTPM20.
func (cc *linuxCmdChannel) MeasurementLog() ([]byte, error) { func (cc *linuxCmdChannel) MeasurementLog() ([]byte, error) {
return ioutil.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements") return os.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements")
} }
func openTPM(tpm probedTPM) (*TPM, error) { func openTPM(tpm probedTPM) (*TPM, error) {
@ -97,7 +97,7 @@ func openTPM(tpm probedTPM) (*TPM, error) {
// If the TPM has a kernel-provided resource manager, we should // If the TPM has a kernel-provided resource manager, we should
// use that instead of communicating directly. // use that instead of communicating directly.
devPath := path.Join("/dev", path.Base(tpm.Path)) devPath := path.Join("/dev", path.Base(tpm.Path))
f, err := ioutil.ReadDir(path.Join(tpm.Path, "device", "tpmrm")) f, err := os.ReadDir(path.Join(tpm.Path, "device", "tpmrm"))
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return nil, err return nil, err

View File

@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build gofuzz || (!linux && !windows)
// +build gofuzz !linux,!windows // +build gofuzz !linux,!windows
package attest package attest

View File

@ -8,18 +8,22 @@ import (
"testing" "testing"
) )
// Generated using the following command: // Created by downloading the base64-url encoded PEM data from
// https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D,
// extracting its public key, and formatting it to PEM using
// //
// openssl genrsa 2048|openssl rsa -outform PEM -pubout // openssl x509 -in ekcert.pem -pubkey
// //
// This is the public key from the EK cert that's used for testing tpm2-tools:
// https://github.com/tpm2-software/tpm2-tools/blob/master/test/integration/tests/getekcertificate.sh
var testRSAKey = mustParseRSAKey(`-----BEGIN PUBLIC KEY----- var testRSAKey = mustParseRSAKey(`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq8zyTXCjVALZzjS8wgNH MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwyDi8kSoYBqs8+AdJsZl
nAVdt4ZGM3N450xOnLplx/RbCVwXyu83SWh0B3Ka+92aocqcHzo+j6e6Urppre/I JJk1Vi3h2hl+nn8HbEaWE8+2U+mOwsOG/B0TPyyMbMM4tzLwsgi9g4qHej5bvD4d
+7VVKTdUAr8t5gxgSLGvo+ev+zv70GF4DmJthb8JNheHCmk3RnoSFs5TnDuSdvGb QIToNcfIkGocBbTS0w/b68HbrZUPprFlvUtqhkYDFGFkwMT1nUiQEe8fko3upukA
KcSzas0186LQyxvwfFjTxLweGrZKh/CTewD0/f5ozXmbTtJpl+qYrMi9GJamGlg6 YfPTdeVkYnMVHvYiJSCYvhpKsB3AoSInxgn9rOsRWvQI1Gk6b0mRl3RpWwwSvBih
N6EsWKh1xos8J/cEmS2vbyCGGADyBwRV8Zkto5EU1HJaEli10HVZf0D06vuKzzxM /3EgpzN7L7XxlR2Lt/CU1bVUwRyVI7MHKf5keH0KE7nmMEiNq039hmNKUnDscvzF
+6W7LzGqzAPeaWvHi07ezShqdr5q5y1KKhFJcy8HOpwN8iFfIw70y3FtMlrMprrU pE3GeajzKTjdgZfina6Dn1tMoPXeJ8lSLCPFThws5XhZUlEYvURwsYGA7veK5CZ7
twIDAQAB zQIDAQAB
-----END PUBLIC KEY-----`) -----END PUBLIC KEY-----`)
func mustParseRSAKey(data string) *rsa.PublicKey { func mustParseRSAKey(data string) *rsa.PublicKey {
@ -46,7 +50,7 @@ func parseRSAKey(data string) (*rsa.PublicKey, error) {
} }
func TestIntelEKURL(t *testing.T) { func TestIntelEKURL(t *testing.T) {
want := "https://ekop.intel.com/ekcertservice/7YtWV2nT3LpvSCfJt7ENIznN1R1jYkj_3S6mez3yyzg=" want := "https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D"
got := intelEKURL(testRSAKey) got := intelEKURL(testRSAKey)
if got != want { if got != want {
t.Fatalf("intelEKURL(), got=%q, want=%q", got, want) t.Fatalf("intelEKURL(), got=%q, want=%q", got, want)

View File

@ -12,6 +12,7 @@
// License for the specific language governing permissions and limitations under // License for the specific language governing permissions and limitations under
// the License. // the License.
//go:build windows
// +build windows // +build windows
package attest package attest
@ -151,6 +152,18 @@ func (t *windowsTPM) info() (*TPMInfo, error) {
return &tInfo, nil return &tInfo, nil
} }
func (t *windowsTPM) ekCertificates() ([]EK, error) {
ekCerts, err := t.pcp.EKCerts()
if err != nil {
return nil, fmt.Errorf("could not read EKCerts: %v", err)
}
var eks []EK
for _, cert := range ekCerts {
eks = append(eks, EK{Certificate: cert, Public: cert.PublicKey})
}
return eks, nil
}
func (t *windowsTPM) eks() ([]EK, error) { func (t *windowsTPM) eks() ([]EK, error) {
ekCerts, err := t.pcp.EKCerts() ekCerts, err := t.pcp.EKCerts()
if err != nil { if err != nil {
@ -324,14 +337,26 @@ func (t *windowsTPM) loadAK(opaqueBlob []byte) (*AK, error) {
} }
} }
func (t *windowsTPM) loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error) {
return nil, fmt.Errorf("not implemented")
}
func (t *windowsTPM) newKey(*AK, *KeyConfig) (*Key, error) { func (t *windowsTPM) newKey(*AK, *KeyConfig) (*Key, error) {
return nil, fmt.Errorf("not implemented") return nil, fmt.Errorf("not implemented")
} }
func (t *windowsTPM) newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error) {
return nil, fmt.Errorf("not implemented")
}
func (t *windowsTPM) loadKey(opaqueBlob []byte) (*Key, error) { func (t *windowsTPM) loadKey(opaqueBlob []byte) (*Key, error) {
return nil, fmt.Errorf("not implemented") return nil, fmt.Errorf("not implemented")
} }
func (t *windowsTPM) loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error) {
return nil, fmt.Errorf("not implemented")
}
func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) { func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) {
numPCRs := 24 numPCRs := 24
out := map[uint32][]byte{} out := map[uint32][]byte{}
@ -351,6 +376,23 @@ func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) {
return out, nil return out, nil
} }
func (t *windowsTPM) pcrbanks() ([]HashAlg, error) {
switch t.version {
case TPMVersion12:
return []HashAlg{HashSHA1}, nil
case TPMVersion20:
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
return pcrbanks(tpm)
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
}
func (t *windowsTPM) pcrs(alg HashAlg) ([]PCR, error) { func (t *windowsTPM) pcrs(alg HashAlg) ([]PCR, error) {
var PCRs map[uint32][]byte var PCRs map[uint32][]byte

View File

@ -146,6 +146,16 @@ const (
BitlockerStatusRecovery = 0x40 BitlockerStatusRecovery = 0x40
) )
// Ternary describes a boolean value that can additionally be unknown.
type Ternary uint8
// Valid Ternary values.
const (
TernaryUnknown Ternary = iota
TernaryTrue
TernaryFalse
)
// WinEvents describes information from the event log recorded during // WinEvents describes information from the event log recorded during
// bootup of Microsoft Windows. // bootup of Microsoft Windows.
type WinEvents struct { type WinEvents struct {
@ -154,7 +164,7 @@ type WinEvents struct {
// BootCount contains the value of the monotonic boot counter. This // BootCount contains the value of the monotonic boot counter. This
// value is not set for TPM 1.2 devices and some TPMs with buggy // value is not set for TPM 1.2 devices and some TPMs with buggy
// implementations of monotonic counters. // implementations of monotonic counters.
BootCount int BootCount uint64
// LoadedModules contains authenticode hashes for binaries which // LoadedModules contains authenticode hashes for binaries which
// were loaded during boot. // were loaded during boot.
LoadedModules map[string]WinModuleLoad LoadedModules map[string]WinModuleLoad
@ -169,19 +179,16 @@ type WinEvents struct {
KernelDebugEnabled bool KernelDebugEnabled bool
// DEPEnabled is true if NX (Data Execution Prevention) was consistently // DEPEnabled is true if NX (Data Execution Prevention) was consistently
// reported as enabled. // reported as enabled.
DEPEnabled bool DEPEnabled Ternary
// CodeIntegrityEnabled is true if code integrity was consistently // CodeIntegrityEnabled is true if code integrity was consistently
// reported as enabled. // reported as enabled.
CodeIntegrityEnabled bool CodeIntegrityEnabled Ternary
// TestSigningEnabled is true if test-mode signature verification was // TestSigningEnabled is true if test-mode signature verification was
// ever reported as enabled. // ever reported as enabled.
TestSigningEnabled bool TestSigningEnabled bool
// BitlockerUnlocks reports the bitlocker status for every instance of // BitlockerUnlocks reports the bitlocker status for every instance of
// a disk unlock, where bitlocker was used to secure the disk. // a disk unlock, where bitlocker was used to secure the disk.
BitlockerUnlocks []BitlockerStatus BitlockerUnlocks []BitlockerStatus
seenDep bool
seenCodeIntegrity bool
} }
// WinModuleLoad describes a module which was loaded while // WinModuleLoad describes a module which was loaded while
@ -265,7 +272,7 @@ func ParseWinEvents(events []Event) (*WinEvents, error) {
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err) return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
} }
if digestVerify != nil { if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, err) return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, digestVerify)
} }
if err := out.readWinEventBlock(s, e.Index); err != nil { if err := out.readWinEventBlock(s, e.Index); err != nil {
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err) return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
@ -296,7 +303,7 @@ func ParseWinEvents(events []Event) (*WinEvents, error) {
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err) return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
} }
if digestVerify != nil { if digestVerify != nil {
return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, err) return nil, fmt.Errorf("invalid digest for tagged event %d: %w", e.sequence, digestVerify)
} }
if err := out.readWinEventBlock(s, e.Index); err != nil { if err := out.readWinEventBlock(s, e.Index); err != nil {
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err) return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
@ -346,8 +353,11 @@ func (w *WinEvents) readBooleanInt64Event(header microsoftEventHeader, r *bytes.
// Boolean signals that latch off if the are ever false (ie: attributes // Boolean signals that latch off if the are ever false (ie: attributes
// that represent a stronger security state when set). // that represent a stronger security state when set).
case dataExecutionPrevention: case dataExecutionPrevention:
w.DEPEnabled = isSet && !(w.DEPEnabled != isSet && w.seenDep) if isSet && w.DEPEnabled == TernaryUnknown {
w.seenDep = true w.DEPEnabled = TernaryTrue
} else if !isSet {
w.DEPEnabled = TernaryFalse
}
} }
return nil return nil
} }
@ -375,44 +385,58 @@ func (w *WinEvents) readBooleanByteEvent(header microsoftEventHeader, r *bytes.R
// Boolean signals that latch off if the are ever false (ie: attributes // Boolean signals that latch off if the are ever false (ie: attributes
// that represent a stronger security state when set). // that represent a stronger security state when set).
case codeIntegrity: case codeIntegrity:
w.CodeIntegrityEnabled = isSet && !(w.CodeIntegrityEnabled != isSet && w.seenCodeIntegrity) if isSet && w.CodeIntegrityEnabled == TernaryUnknown {
w.seenCodeIntegrity = true w.CodeIntegrityEnabled = TernaryTrue
} else if !isSet {
w.CodeIntegrityEnabled = TernaryFalse
}
} }
return nil return nil
} }
func (w *WinEvents) readUint(header microsoftEventHeader, r io.Reader) (uint64, error) { func (w *WinEvents) readUint32(header microsoftEventHeader, r io.Reader) (uint32, error) {
if header.Size > 8 { if header.Size != 4 {
return 0, fmt.Errorf("integer too large (%d bytes)", header.Size) return 0, fmt.Errorf("integer size not uint32 (%d bytes)", header.Size)
} }
data := make([]uint8, header.Size) data := make([]uint8, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil { if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return 0, fmt.Errorf("reading u%d: %w", header.Size<<8, err) return 0, fmt.Errorf("reading u32: %w", err)
} }
i, n := binary.Uvarint(data) i := binary.LittleEndian.Uint32(data)
if n <= 0 {
return 0, fmt.Errorf("reading u%d: invalid varint", header.Size<<8) return i, nil
}
func (w *WinEvents) readUint64(header microsoftEventHeader, r io.Reader) (uint64, error) {
if header.Size != 8 {
return 0, fmt.Errorf("integer size not uint64 (%d bytes)", header.Size)
} }
data := make([]uint8, header.Size)
if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
return 0, fmt.Errorf("reading u64: %w", err)
}
i := binary.LittleEndian.Uint64(data)
return i, nil return i, nil
} }
func (w *WinEvents) readBootCounter(header microsoftEventHeader, r *bytes.Reader) error { func (w *WinEvents) readBootCounter(header microsoftEventHeader, r *bytes.Reader) error {
i, err := w.readUint(header, r) i, err := w.readUint64(header, r)
if err != nil { if err != nil {
return fmt.Errorf("boot counter: %v", err) return fmt.Errorf("boot counter: %v", err)
} }
if w.BootCount > 0 && w.BootCount != int(i) { if w.BootCount > 0 && w.BootCount != i {
return fmt.Errorf("conflicting values for boot counter: %d != %d", i, w.BootCount) return fmt.Errorf("conflicting values for boot counter: %d != %d", i, w.BootCount)
} }
w.BootCount = int(i) w.BootCount = i
return nil return nil
} }
func (w *WinEvents) readTransferControl(header microsoftEventHeader, r *bytes.Reader) error { func (w *WinEvents) readTransferControl(header microsoftEventHeader, r *bytes.Reader) error {
i, err := w.readUint(header, r) i, err := w.readUint32(header, r)
if err != nil { if err != nil {
return fmt.Errorf("transfer control: %v", err) return fmt.Errorf("transfer control: %v", err)
} }
@ -460,7 +484,7 @@ func (w *WinEvents) parseImageValidated(header microsoftEventHeader, r io.Reader
} }
func (w *WinEvents) parseHashAlgID(header microsoftEventHeader, r io.Reader) (WinCSPAlg, error) { func (w *WinEvents) parseHashAlgID(header microsoftEventHeader, r io.Reader) (WinCSPAlg, error) {
i, err := w.readUint(header, r) i, err := w.readUint32(header, r)
if err != nil { if err != nil {
return 0, fmt.Errorf("hash algorithm ID: %v", err) return 0, fmt.Errorf("hash algorithm ID: %v", err)
} }
@ -565,7 +589,7 @@ func (w *WinEvents) readLoadedModuleAggregation(rdr *bytes.Reader, header micros
if imgSize != 0 { if imgSize != 0 {
return errors.New("duplicate image size in LMA event") return errors.New("duplicate image size in LMA event")
} }
if imgSize, err = w.readUint(h, r); err != nil { if imgSize, err = w.readUint64(h, r); err != nil {
return err return err
} }
case hashAlgorithmID: case hashAlgorithmID:
@ -576,7 +600,7 @@ func (w *WinEvents) readLoadedModuleAggregation(rdr *bytes.Reader, header micros
return err return err
} }
case imageValidated: case imageValidated:
if imgValidated == true { if imgValidated {
return errors.New("duplicate image validated field in LMA event") return errors.New("duplicate image validated field in LMA event")
} }
if imgValidated, err = w.parseImageValidated(h, r); err != nil { if imgValidated, err = w.parseImageValidated(h, r); err != nil {
@ -654,7 +678,7 @@ func (w *WinEvents) parseUTF16(header microsoftEventHeader, r io.Reader) (string
return strings.TrimSuffix(string(utf16.Decode(data)), "\x00"), nil return strings.TrimSuffix(string(utf16.Decode(data)), "\x00"), nil
} }
func (w *WinEvents) readELAMAggregation(rdr *bytes.Reader, header microsoftEventHeader) error { func (w *WinEvents) readELAMAggregation(rdr io.Reader, header microsoftEventHeader) error {
var ( var (
r = &io.LimitedReader{R: rdr, N: int64(header.Size)} r = &io.LimitedReader{R: rdr, N: int64(header.Size)}
driverName string driverName string
@ -674,6 +698,11 @@ func (w *WinEvents) readELAMAggregation(rdr *bytes.Reader, header microsoftEvent
var err error var err error
switch h.Type { switch h.Type {
case elamAggregation:
w.readELAMAggregation(r, h)
if r.N == 0 {
return nil
}
case elamKeyname: case elamKeyname:
if driverName != "" { if driverName != "" {
return errors.New("duplicate driver name in ELAM aggregation event") return errors.New("duplicate driver name in ELAM aggregation event")

View File

@ -16,7 +16,7 @@ package attest
import ( import (
"encoding/json" "encoding/json"
"io/ioutil" "os"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -27,13 +27,14 @@ func TestParseWinEvents(t *testing.T) {
want := &WinEvents{ want := &WinEvents{
ColdBoot: true, ColdBoot: true,
BootCount: 4, BootCount: 4,
DEPEnabled: true, DEPEnabled: TernaryTrue,
CodeIntegrityEnabled: true, CodeIntegrityEnabled: TernaryTrue,
BitlockerUnlocks: []BitlockerStatus{0, 0}, BitlockerUnlocks: []BitlockerStatus{0, 0},
LoadedModules: map[string]WinModuleLoad{ LoadedModules: map[string]WinModuleLoad{
"0fdce7d71936f79445e7d2c84cbeb97c948d3730e0b839166b0a4e625c2d4547": WinModuleLoad{ "0fdce7d71936f79445e7d2c84cbeb97c948d3730e0b839166b0a4e625c2d4547": {
FilePath: `\Windows\System32\drivers\vioscsi.sys`, FilePath: `\Windows\System32\drivers\vioscsi.sys`,
ImageBase: []uint64{81416192}, ImageBase: []uint64{81416192},
ImageSize: uint64(86016),
HashAlgorithm: WinAlgSHA256, HashAlgorithm: WinAlgSHA256,
ImageValidated: true, ImageValidated: true,
AuthorityIssuer: "Microsoft Windows Third Party Component CA 2014", AuthorityIssuer: "Microsoft Windows Third Party Component CA 2014",
@ -48,9 +49,10 @@ func TestParseWinEvents(t *testing.T) {
}, },
AuthenticodeHash: []byte{15, 220, 231, 215, 25, 54, 247, 148, 69, 231, 210, 200, 76, 190, 185, 124, 148, 141, 55, 48, 224, 184, 57, 22, 107, 10, 78, 98, 92, 45, 69, 71}, AuthenticodeHash: []byte{15, 220, 231, 215, 25, 54, 247, 148, 69, 231, 210, 200, 76, 190, 185, 124, 148, 141, 55, 48, 224, 184, 57, 22, 107, 10, 78, 98, 92, 45, 69, 71},
}, },
"055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": WinModuleLoad{ "055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": {
FilePath: `\Windows\System32\Drivers\ksecpkg.sys`, FilePath: `\Windows\System32\Drivers\ksecpkg.sys`,
ImageBase: []uint64{82952192}, ImageBase: []uint64{82952192},
ImageSize: uint64(204800),
HashAlgorithm: WinAlgSHA256, HashAlgorithm: WinAlgSHA256,
ImageValidated: true, ImageValidated: true,
AuthorityIssuer: "Microsoft Windows Production PCA 2011", AuthorityIssuer: "Microsoft Windows Production PCA 2011",
@ -65,9 +67,10 @@ func TestParseWinEvents(t *testing.T) {
}, },
AuthenticodeHash: []byte{5, 90, 54, 169, 146, 27, 152, 204, 4, 4, 44, 169, 82, 73, 199, 236, 166, 85, 83, 104, 104, 218, 252, 236, 117, 8, 148, 126, 190, 94, 113, 244}, AuthenticodeHash: []byte{5, 90, 54, 169, 146, 27, 152, 204, 4, 4, 44, 169, 82, 73, 199, 236, 166, 85, 83, 104, 104, 218, 252, 236, 117, 8, 148, 126, 190, 94, 113, 244},
}, },
"2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": WinModuleLoad{ "2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": {
FilePath: `\Windows\System32\drivers\volmgrx.sys`, FilePath: `\Windows\System32\drivers\volmgrx.sys`,
ImageBase: []uint64{80875520}, ImageBase: []uint64{80875520},
ImageSize: uint64(405504),
HashAlgorithm: WinAlgSHA256, HashAlgorithm: WinAlgSHA256,
ImageValidated: true, ImageValidated: true,
AuthorityIssuer: "Microsoft Windows Production PCA 2011", AuthorityIssuer: "Microsoft Windows Production PCA 2011",
@ -84,11 +87,11 @@ func TestParseWinEvents(t *testing.T) {
}, },
}, },
ELAM: map[string]WinELAM{ ELAM: map[string]WinELAM{
"Windows Defender": WinELAM{Measured: []byte{0x06, 0x7d, 0x5b, 0x9d, 0xc5, 0x62, 0x7f, 0x97, 0xdc, 0xf3, 0xfe, 0xff, 0x60, 0x2a, 0x34, 0x2e, 0xd6, 0x98, 0xd2, 0xcc}}, "Windows Defender": {Measured: []byte{0x06, 0x7d, 0x5b, 0x9d, 0xc5, 0x62, 0x7f, 0x97, 0xdc, 0xf3, 0xfe, 0xff, 0x60, 0x2a, 0x34, 0x2e, 0xd6, 0x98, 0xd2, 0xcc}},
}, },
} }
data, err := ioutil.ReadFile("testdata/windows_gcp_shielded_vm.json") data, err := os.ReadFile("testdata/windows_gcp_shielded_vm.json")
if err != nil { if err != nil {
t.Fatalf("reading test data: %v", err) t.Fatalf("reading test data: %v", err)
} }
@ -118,7 +121,7 @@ func TestParseWinEvents(t *testing.T) {
"055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": true, "055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": true,
"2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": true, "2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": true,
} }
for k, _ := range winState.LoadedModules { for k := range winState.LoadedModules {
if _, keep := keep[k]; !keep { if _, keep := keep[k]; !keep {
delete(winState.LoadedModules, k) delete(winState.LoadedModules, k)
} }

View File

@ -15,14 +15,18 @@
package attest package attest
import ( import (
"bytes"
"crypto" "crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa" "crypto/rsa"
"encoding/asn1"
"errors" "errors"
"fmt" "fmt"
"io"
"math/big" "math/big"
"github.com/google/certificate-transparency-go/asn1" "github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil" "github.com/google/go-tpm/tpmutil"
) )
@ -30,9 +34,49 @@ import (
type wrappedTPM20 struct { type wrappedTPM20 struct {
interf TPMInterface interf TPMInterface
rwc CommandChannelTPM20 rwc CommandChannelTPM20
tpmRSAEkTemplate *tpm2.Public
tpmECCEkTemplate *tpm2.Public
} }
func (*wrappedTPM20) isTPMBase() {} // certifyingKey contains details of a TPM key that could certify other keys.
type certifyingKey struct {
handle tpmutil.Handle
alg Algorithm
}
func (t *wrappedTPM20) rsaEkTemplate() tpm2.Public {
if t.tpmRSAEkTemplate != nil {
return *t.tpmRSAEkTemplate
}
nonce, err := tpm2.NVReadEx(t.rwc, nvramRSAEkNonceIndex, tpm2.HandleOwner, "", 0)
if err != nil {
t.tpmRSAEkTemplate = &defaultRSAEKTemplate // No nonce, use the default template
} else {
template := defaultRSAEKTemplate
copy(template.RSAParameters.ModulusRaw, nonce)
t.tpmRSAEkTemplate = &template
}
return *t.tpmRSAEkTemplate
}
func (t *wrappedTPM20) eccEkTemplate() tpm2.Public {
if t.tpmECCEkTemplate != nil {
return *t.tpmECCEkTemplate
}
nonce, err := tpm2.NVReadEx(t.rwc, nvramECCEkNonceIndex, tpm2.HandleOwner, "", 0)
if err != nil {
t.tpmECCEkTemplate = &defaultECCEKTemplate // No nonce, use the default template
} else {
template := defaultECCEKTemplate
copy(template.ECCParameters.Point.XRaw, nonce)
t.tpmECCEkTemplate = &template
}
return *t.tpmECCEkTemplate
}
func (t *wrappedTPM20) tpmVersion() TPMVersion { func (t *wrappedTPM20) tpmVersion() TPMVersion {
return TPMVersion20 return TPMVersion20
@ -63,43 +107,105 @@ func (t *wrappedTPM20) info() (*TPMInfo, error) {
return &tInfo, nil return &tInfo, nil
} }
// Return value: handle, whether we generated a new one, error // Return value: handle, whether we generated a new one, error.
func (t *wrappedTPM20) getPrimaryKeyHandle(pHnd tpmutil.Handle) (tpmutil.Handle, bool, error) { func (t *wrappedTPM20) getEndorsementKeyHandle(ek *EK) (tpmutil.Handle, bool, error) {
_, _, _, err := tpm2.ReadPublic(t.rwc, pHnd) var ekHandle tpmutil.Handle
if err == nil { var ekTemplate tpm2.Public
// Found the persistent handle, assume it's the key we want.
return pHnd, false, nil if ek == nil {
// The default is RSA for backward compatibility.
ekHandle = commonRSAEkEquivalentHandle
ekTemplate = t.rsaEkTemplate()
} else {
ekHandle = ek.handle
if ekHandle == 0 {
// Assume RSA EK handle if it was not provided.
ekHandle = commonRSAEkEquivalentHandle
}
switch pub := ek.Public.(type) {
case *rsa.PublicKey:
ekTemplate = t.rsaEkTemplate()
case *ecdsa.PublicKey:
ekTemplate = t.eccEkTemplate()
default:
return 0, false, fmt.Errorf("unsupported public key type %T", pub)
}
} }
var keyHnd tpmutil.Handle _, _, _, err := tpm2.ReadPublic(t.rwc, ekHandle)
switch pHnd { if err == nil {
case commonSrkEquivalentHandle: // Found the persistent handle, assume it's the key we want.
keyHnd, _, err = tpm2.CreatePrimary(t.rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", defaultSRKTemplate) return ekHandle, false, nil
case commonEkEquivalentHandle:
keyHnd, _, err = tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
} }
rerr := err // Preserve this failure for later logging, if needed
keyHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", ekTemplate)
if err != nil { if err != nil {
return 0, false, fmt.Errorf("CreatePrimary failed: %v", err) return 0, false, fmt.Errorf("ReadPublic failed (%v), and then CreatePrimary failed: %v", rerr, err)
} }
defer tpm2.FlushContext(t.rwc, keyHnd) defer tpm2.FlushContext(t.rwc, keyHnd)
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, pHnd) err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, ekHandle)
if err != nil { if err != nil {
return 0, false, fmt.Errorf("EvictControl failed: %v", err) return 0, false, fmt.Errorf("EvictControl failed: %v", err)
} }
return pHnd, true, nil return ekHandle, true, nil
}
// Return value: handle, whether we generated a new one, error
func (t *wrappedTPM20) getStorageRootKeyHandle(parent ParentKeyConfig) (tpmutil.Handle, bool, error) {
srkHandle := parent.Handle
_, _, _, err := tpm2.ReadPublic(t.rwc, srkHandle)
if err == nil {
// Found the persistent handle, assume it's the key we want.
return srkHandle, false, nil
}
rerr := err // Preserve this failure for later logging, if needed
var srkTemplate tpm2.Public
switch parent.Algorithm {
case RSA:
srkTemplate = defaultRSASRKTemplate
case ECDSA:
srkTemplate = defaultECCSRKTemplate
default:
return 0, false, fmt.Errorf("unsupported SRK algorithm: %v", parent.Algorithm)
}
keyHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", srkTemplate)
if err != nil {
return 0, false, fmt.Errorf("ReadPublic failed (%v), and then CreatePrimary failed: %v", rerr, err)
}
defer tpm2.FlushContext(t.rwc, keyHnd)
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, srkHandle)
if err != nil {
return 0, false, fmt.Errorf("EvictControl failed: %v", err)
}
return srkHandle, true, nil
}
func (t *wrappedTPM20) ekCertificates() ([]EK, error) {
var res []EK
if rsaCert, err := readEKCertFromNVRAM20(t.rwc, nvramRSACertIndex); err == nil {
res = append(res, EK{Public: crypto.PublicKey(rsaCert.PublicKey), Certificate: rsaCert, handle: commonRSAEkEquivalentHandle})
}
if eccCert, err := readEKCertFromNVRAM20(t.rwc, nvramECCCertIndex); err == nil {
res = append(res, EK{Public: crypto.PublicKey(eccCert.PublicKey), Certificate: eccCert, handle: commonECCEkEquivalentHandle})
}
return res, nil
} }
func (t *wrappedTPM20) eks() ([]EK, error) { func (t *wrappedTPM20) eks() ([]EK, error) {
if cert, err := readEKCertFromNVRAM20(t.rwc); err == nil { if cert, err := readEKCertFromNVRAM20(t.rwc, nvramRSACertIndex); err == nil {
return []EK{ return []EK{
{Public: crypto.PublicKey(cert.PublicKey), Certificate: cert}, {Public: crypto.PublicKey(cert.PublicKey), Certificate: cert, handle: commonRSAEkEquivalentHandle},
}, nil }, nil
} }
// Attempt to create an EK. // Attempt to create an EK.
ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate) ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", t.rsaEkTemplate())
if err != nil { if err != nil {
return nil, fmt.Errorf("EK CreatePrimary failed: %v", err) return nil, fmt.Errorf("EK CreatePrimary failed: %v", err)
} }
@ -112,23 +218,50 @@ func (t *wrappedTPM20) eks() ([]EK, error) {
if pub.RSAParameters == nil { if pub.RSAParameters == nil {
return nil, errors.New("ECC EK not yet supported") return nil, errors.New("ECC EK not yet supported")
} }
return []EK{
{ i, err := t.info()
Public: &rsa.PublicKey{ if err != nil {
return nil, fmt.Errorf("Retrieving TPM info failed: %v", err)
}
ekPub := &rsa.PublicKey{
E: int(pub.RSAParameters.Exponent()), E: int(pub.RSAParameters.Exponent()),
N: pub.RSAParameters.Modulus(), N: pub.RSAParameters.Modulus(),
}, }
var certificateURL string
if i.Manufacturer.String() == manufacturerIntel {
certificateURL = intelEKURL(ekPub)
}
return []EK{
{
Public: ekPub,
CertificateURL: certificateURL,
handle: commonRSAEkEquivalentHandle,
}, },
}, nil }, nil
} }
func (t *wrappedTPM20) newAK(opts *AKConfig) (*AK, error) { func (t *wrappedTPM20) newAK(opts *AKConfig) (*AK, error) {
// TODO(jsonp): Abstract choice of hierarchy & parent. var parent ParentKeyConfig
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle) if opts != nil && opts.Parent != nil {
parent = *opts.Parent
} else {
parent = defaultParentConfig
}
srk, _, err := t.getStorageRootKeyHandle(parent)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get SRK handle: %v", err) return nil, fmt.Errorf("failed to get SRK handle: %v", err)
} }
var akTemplate tpm2.Public
var sigScheme *tpm2.SigScheme
// The default is RSA.
if opts != nil && opts.Algorithm == ECDSA {
akTemplate = akTemplateECC
sigScheme = akTemplateECC.ECCParameters.Sign
} else {
akTemplate = akTemplateRSA
sigScheme = akTemplateRSA.RSAParameters.Sign
}
blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", akTemplate) blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", akTemplate)
if err != nil { if err != nil {
return nil, fmt.Errorf("CreateKeyEx() failed: %v", err) return nil, fmt.Errorf("CreateKeyEx() failed: %v", err)
@ -145,35 +278,34 @@ func (t *wrappedTPM20) newAK(opts *AKConfig) (*AK, error) {
}() }()
// We can only certify the creation immediately afterwards, so we cache the result. // We can only certify the creation immediately afterwards, so we cache the result.
attestation, sig, err := tpm2.CertifyCreation(t.rwc, "", keyHandle, keyHandle, nil, creationHash, tpm2.SigScheme{tpm2.AlgRSASSA, tpm2.AlgSHA256, 0}, tix) attestation, sig, err := tpm2.CertifyCreation(t.rwc, "", keyHandle, keyHandle, nil, creationHash, *sigScheme, tix)
if err != nil { if err != nil {
return nil, fmt.Errorf("CertifyCreation failed: %v", err) return nil, fmt.Errorf("CertifyCreation failed: %v", err)
} }
// Pack the raw structure into a TPMU_SIGNATURE. return &AK{ak: newWrappedAK20(keyHandle, blob, pub, creationData, attestation, sig)}, nil
signature, err := tpmutil.Pack(tpm2.AlgRSASSA, tpm2.AlgSHA256, tpmutil.U16Bytes(sig))
if err != nil {
return nil, fmt.Errorf("failed to pack TPMT_SIGNATURE: %v", err)
}
return &AK{ak: newWrappedAK20(keyHandle, blob, pub, creationData, attestation, signature)}, nil
} }
func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) { func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) {
// TODO(szp): TODO(jsonp): Abstract choice of hierarchy & parent.
k, ok := ak.ak.(*wrappedKey20) k, ok := ak.ak.(*wrappedKey20)
if !ok { if !ok {
return nil, fmt.Errorf("expected *wrappedKey20, got: %T", k) return nil, fmt.Errorf("expected *wrappedKey20, got: %T", k)
} }
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle) kAlg, err := k.algorithm()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get SRK handle: %v", err) return nil, fmt.Errorf("get algorithm: %v", err)
}
ck := certifyingKey{handle: k.hnd, alg: kAlg}
return t.newKeyCertifiedByKey(ck, opts)
}
func (t *wrappedTPM20) newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error) {
parent, blob, pub, creationData, err := createKey(t, opts)
if err != nil {
return nil, fmt.Errorf("cannot create key: %v", err)
} }
blob, pub, creationData, creationHash, tix, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", eccKeyTemplate) keyHandle, _, err := tpm2.Load(t.rwc, parent, "", pub, blob)
if err != nil {
return nil, fmt.Errorf("CreateKey() failed: %v", err)
}
keyHandle, _, err := tpm2.Load(t.rwc, srk, "", pub, blob)
if err != nil { if err != nil {
return nil, fmt.Errorf("Load() failed: %v", err) return nil, fmt.Errorf("Load() failed: %v", err)
} }
@ -185,16 +317,16 @@ func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) {
}() }()
// Certify application key by AK // Certify application key by AK
attestation, sig, err := tpm2.CertifyCreation(t.rwc, "", keyHandle, k.hnd, nil, creationHash, tpm2.SigScheme{tpm2.AlgRSASSA, tpm2.AlgSHA256, 0}, tix) certifyOpts := CertifyOpts{QualifyingData: opts.QualifyingData}
cp, err := certifyByKey(t, keyHandle, ck, certifyOpts)
if err != nil { if err != nil {
return nil, fmt.Errorf("CertifyCreation failed: %v", err) return nil, fmt.Errorf("certifyByKey() failed: %v", err)
} }
// Pack the raw structure into a TPMU_SIGNATURE. if !bytes.Equal(pub, cp.Public) {
signature, err := tpmutil.Pack(tpm2.AlgRSASSA, tpm2.AlgSHA256, tpmutil.U16Bytes(sig)) return nil, fmt.Errorf("certified incorrect key, expected: %v, certified: %v", pub, cp.Public)
if err != nil {
return nil, fmt.Errorf("failed to pack TPMT_SIGNATURE: %v", err)
} }
// Pack the raw structure into a TPMU_SIGNATURE.
tpmPub, err := tpm2.DecodePublic(pub) tpmPub, err := tpm2.DecodePublic(pub)
if err != nil { if err != nil {
return nil, fmt.Errorf("decode public key: %v", err) return nil, fmt.Errorf("decode public key: %v", err)
@ -203,10 +335,82 @@ func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("access public key: %v", err) return nil, fmt.Errorf("access public key: %v", err)
} }
return &Key{key: newWrappedKey20(keyHandle, blob, pub, creationData, attestation, signature), pub: pubKey, tpm: t}, nil return &Key{key: newWrappedKey20(keyHandle, blob, pub, creationData, cp.CreateAttestation, cp.CreateSignature), pub: pubKey, tpm: t}, nil
} }
func (t *wrappedTPM20) deserializeAndLoad(opaqueBlob []byte) (tpmutil.Handle, *serializedKey, error) { func createKey(t *wrappedTPM20, opts *KeyConfig) (tpmutil.Handle, []byte, []byte, []byte, error) {
var parent ParentKeyConfig
if opts != nil && opts.Parent != nil {
parent = *opts.Parent
} else {
parent = defaultParentConfig
}
srk, _, err := t.getStorageRootKeyHandle(parent)
if err != nil {
return 0, nil, nil, nil, fmt.Errorf("failed to get SRK handle: %v", err)
}
tmpl, err := templateFromConfig(opts)
if err != nil {
return 0, nil, nil, nil, fmt.Errorf("incorrect key options: %v", err)
}
blob, pub, creationData, _, _, err := tpm2.CreateKey(t.rwc, srk, tpm2.PCRSelection{}, "", "", tmpl)
if err != nil {
return 0, nil, nil, nil, fmt.Errorf("CreateKey() failed: %v", err)
}
return srk, blob, pub, creationData, err
}
func templateFromConfig(opts *KeyConfig) (tpm2.Public, error) {
var tmpl tpm2.Public
switch opts.Algorithm {
case RSA:
tmpl = rsaKeyTemplate
if opts.Size < 0 || opts.Size > 65535 { // basic sanity check
return tmpl, fmt.Errorf("incorrect size parameter")
}
tmpl.RSAParameters.KeyBits = uint16(opts.Size)
case ECDSA:
tmpl = ecdsaKeyTemplate
switch opts.Size {
case 256:
tmpl.NameAlg = tpm2.AlgSHA256
tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA256
tmpl.ECCParameters.CurveID = tpm2.CurveNISTP256
tmpl.ECCParameters.Point = tpm2.ECPoint{
XRaw: make([]byte, 32),
YRaw: make([]byte, 32),
}
case 384:
tmpl.NameAlg = tpm2.AlgSHA384
tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA384
tmpl.ECCParameters.CurveID = tpm2.CurveNISTP384
tmpl.ECCParameters.Point = tpm2.ECPoint{
XRaw: make([]byte, 48),
YRaw: make([]byte, 48),
}
case 521:
tmpl.NameAlg = tpm2.AlgSHA512
tmpl.ECCParameters.Sign.Hash = tpm2.AlgSHA512
tmpl.ECCParameters.CurveID = tpm2.CurveNISTP521
tmpl.ECCParameters.Point = tpm2.ECPoint{
XRaw: make([]byte, 65),
YRaw: make([]byte, 65),
}
default:
return tmpl, fmt.Errorf("unsupported key size: %v", opts.Size)
}
default:
return tmpl, fmt.Errorf("unsupported algorithm type: %q", opts.Algorithm)
}
return tmpl, nil
}
func (t *wrappedTPM20) deserializeAndLoad(opaqueBlob []byte, parent ParentKeyConfig) (tpmutil.Handle, *serializedKey, error) {
sKey, err := deserializeKey(opaqueBlob, TPMVersion20) sKey, err := deserializeKey(opaqueBlob, TPMVersion20)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("deserializeKey() failed: %v", err) return 0, nil, fmt.Errorf("deserializeKey() failed: %v", err)
@ -215,7 +419,7 @@ func (t *wrappedTPM20) deserializeAndLoad(opaqueBlob []byte) (tpmutil.Handle, *s
return 0, nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding) return 0, nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding)
} }
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle) srk, _, err := t.getStorageRootKeyHandle(parent)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed to get SRK handle: %v", err) return 0, nil, fmt.Errorf("failed to get SRK handle: %v", err)
} }
@ -227,7 +431,11 @@ func (t *wrappedTPM20) deserializeAndLoad(opaqueBlob []byte) (tpmutil.Handle, *s
} }
func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) { func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) {
hnd, sKey, err := t.deserializeAndLoad(opaqueBlob) return t.loadAKWithParent(opaqueBlob, defaultParentConfig)
}
func (t *wrappedTPM20) loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error) {
hnd, sKey, err := t.deserializeAndLoad(opaqueBlob, parent)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot load attestation key: %v", err) return nil, fmt.Errorf("cannot load attestation key: %v", err)
} }
@ -235,7 +443,11 @@ func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) {
} }
func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) { func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) {
hnd, sKey, err := t.deserializeAndLoad(opaqueBlob) return t.loadKeyWithParent(opaqueBlob, defaultParentConfig)
}
func (t *wrappedTPM20) loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error) {
hnd, sKey, err := t.deserializeAndLoad(opaqueBlob, parent)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot load signing key: %v", err) return nil, fmt.Errorf("cannot load signing key: %v", err)
} }
@ -250,6 +462,10 @@ func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) {
return &Key{key: newWrappedKey20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature), pub: pub, tpm: t}, nil return &Key{key: newWrappedKey20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature), pub: pub, tpm: t}, nil
} }
func (t *wrappedTPM20) pcrbanks() ([]HashAlg, error) {
return pcrbanks(t.rwc)
}
func (t *wrappedTPM20) pcrs(alg HashAlg) ([]PCR, error) { func (t *wrappedTPM20) pcrs(alg HashAlg) ([]PCR, error) {
PCRs, err := readAllPCRs20(t.rwc, alg.goTPMAlg()) PCRs, err := readAllPCRs20(t.rwc, alg.goTPMAlg())
if err != nil { if err != nil {
@ -326,7 +542,7 @@ func (k *wrappedKey20) close(t tpmBase) error {
return tpm2.FlushContext(tpm.rwc, k.hnd) return tpm2.FlushContext(tpm.rwc, k.hnd)
} }
func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential) ([]byte, error) { func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential, ek *EK) ([]byte, error) {
t, ok := tb.(*wrappedTPM20) t, ok := tb.(*wrappedTPM20)
if !ok { if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb) return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
@ -341,7 +557,7 @@ func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential) ([
} }
secret := in.Secret[2:] secret := in.Secret[2:]
ekHnd, _, err := t.getPrimaryKeyHandle(commonEkEquivalentHandle) ekHnd, _, err := t.getEndorsementKeyHandle(ek)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -360,7 +576,7 @@ func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential) ([
} }
defer tpm2.FlushContext(t.rwc, sessHandle) defer tpm2.FlushContext(t.rwc, sessHandle)
if _, err := tpm2.PolicySecret(t.rwc, tpm2.HandleEndorsement, tpm2.AuthCommand{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession}, sessHandle, nil, nil, nil, 0); err != nil { if _, _, err := tpm2.PolicySecret(t.rwc, tpm2.HandleEndorsement, tpm2.AuthCommand{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession}, sessHandle, nil, nil, nil, 0); err != nil {
return nil, fmt.Errorf("tpm2.PolicySecret() failed: %v", err) return nil, fmt.Errorf("tpm2.PolicySecret() failed: %v", err)
} }
@ -370,12 +586,57 @@ func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential) ([
}, k.hnd, ekHnd, credential, secret) }, k.hnd, ekHnd, credential, secret)
} }
func (k *wrappedKey20) quote(tb tpmBase, nonce []byte, alg HashAlg) (*Quote, error) { func sigSchemeFromAlgorithm(alg Algorithm) (tpm2.SigScheme, error) {
switch alg {
case RSA:
return tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: tpm2.AlgSHA256,
}, nil
case ECDSA:
return tpm2.SigScheme{
Alg: tpm2.AlgECDSA,
Hash: tpm2.AlgSHA256,
}, nil
default:
return tpm2.SigScheme{}, fmt.Errorf("algorithm %v not supported", alg)
}
}
func (k *wrappedKey20) certify(tb tpmBase, handle interface{}, opts CertifyOpts) (*CertificationParameters, error) {
kAlg, err := k.algorithm()
if err != nil {
return nil, fmt.Errorf("unknown algorithm: %v", err)
}
ck := certifyingKey{
handle: k.hnd,
alg: kAlg,
}
return certifyByKey(tb, handle, ck, opts)
}
func certifyByKey(tb tpmBase, handle interface{}, ck certifyingKey, opts CertifyOpts) (*CertificationParameters, error) {
t, ok := tb.(*wrappedTPM20) t, ok := tb.(*wrappedTPM20)
if !ok { if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb) return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
} }
return quote20(t.rwc, k.hnd, tpm2.Algorithm(alg), nonce) hnd, ok := handle.(tpmutil.Handle)
if !ok {
return nil, fmt.Errorf("expected tpmutil.Handle, got %T", handle)
}
scheme, err := sigSchemeFromAlgorithm(ck.alg)
if err != nil {
return nil, fmt.Errorf("get signature scheme: %v", err)
}
return certify(t.rwc, hnd, ck.handle, opts.QualifyingData, scheme)
}
func (k *wrappedKey20) quote(tb tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error) {
t, ok := tb.(*wrappedTPM20)
if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
}
return quote20(t.rwc, k.hnd, tpm2.Algorithm(alg), nonce, selectedPCRs)
} }
func (k *wrappedKey20) attestationParameters() AttestationParameters { func (k *wrappedKey20) attestationParameters() AttestationParameters {
@ -390,31 +651,80 @@ func (k *wrappedKey20) attestationParameters() AttestationParameters {
func (k *wrappedKey20) certificationParameters() CertificationParameters { func (k *wrappedKey20) certificationParameters() CertificationParameters {
return CertificationParameters{ return CertificationParameters{
Public: k.public, Public: k.public,
CreateData: k.createData,
CreateAttestation: k.createAttestation, CreateAttestation: k.createAttestation,
CreateSignature: k.createSignature, CreateSignature: k.createSignature,
} }
} }
func (k *wrappedKey20) sign(tb tpmBase, digest []byte) ([]byte, error) { func (k *wrappedKey20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error) {
t, ok := tb.(*wrappedTPM20) t, ok := tb.(*wrappedTPM20)
if !ok { if !ok {
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb) return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
} }
sig, err := tpm2.Sign(t.rwc, k.hnd, "", digest, nil, nil) switch p := pub.(type) {
case *ecdsa.PublicKey:
return signECDSA(t.rwc, k.hnd, digest, p.Curve)
case *rsa.PublicKey:
return signRSA(t.rwc, k.hnd, digest, opts)
}
return nil, fmt.Errorf("unsupported signing key type: %T", pub)
}
func signECDSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, curve elliptic.Curve) ([]byte, error) {
// https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/crypto/ecdsa/ecdsa.go;l=181
orderBits := curve.Params().N.BitLen()
orderBytes := (orderBits + 7) / 8
if len(digest) > orderBytes {
digest = digest[:orderBytes]
}
ret := new(big.Int).SetBytes(digest)
excess := len(digest)*8 - orderBits
if excess > 0 {
ret.Rsh(ret, uint(excess))
}
// call ret.FillBytes() here instead of ret.Bytes() to preserve leading zeroes
// that may have been dropped when converting the digest to an integer
digest = ret.FillBytes(digest)
sig, err := tpm2.Sign(rw, key, "", digest, nil, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("signing data: %v", err) return nil, fmt.Errorf("cannot sign: %v", err)
} }
if sig.RSA != nil { if sig.ECC == nil {
return sig.RSA.Signature, nil return nil, fmt.Errorf("expected ECDSA signature, got: %v", sig.Alg)
} }
if sig.ECC != nil {
return asn1.Marshal(struct { return asn1.Marshal(struct {
R *big.Int R *big.Int
S *big.Int S *big.Int
}{sig.ECC.R, sig.ECC.S}) }{sig.ECC.R, sig.ECC.S})
}
func signRSA(rw io.ReadWriter, key tpmutil.Handle, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
h, err := tpm2.HashToAlgorithm(opts.HashFunc())
if err != nil {
return nil, fmt.Errorf("incorrect hash algorithm: %v", err)
} }
return nil, fmt.Errorf("unsupported signature type: %v", sig.Alg)
scheme := &tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: h,
}
if pss, ok := opts.(*rsa.PSSOptions); ok {
if pss.SaltLength != rsa.PSSSaltLengthAuto && pss.SaltLength != len(digest) {
return nil, fmt.Errorf("PSS salt length %d is incorrect, expected rsa.PSSSaltLengthAuto or %d", pss.SaltLength, len(digest))
}
scheme.Alg = tpm2.AlgRSAPSS
}
sig, err := tpm2.Sign(rw, key, "", digest, nil, scheme)
if err != nil {
return nil, fmt.Errorf("cannot sign: %v", err)
}
if sig.RSA == nil {
return nil, fmt.Errorf("expected RSA signature, got: %v", sig.Alg)
}
return sig.RSA.Signature, nil
} }
func (k *wrappedKey20) decrypt(tb tpmBase, ctxt []byte) ([]byte, error) { func (k *wrappedKey20) decrypt(tb tpmBase, ctxt []byte) ([]byte, error) {
@ -424,3 +734,18 @@ func (k *wrappedKey20) decrypt(tb tpmBase, ctxt []byte) ([]byte, error) {
func (k *wrappedKey20) blobs() ([]byte, []byte, error) { func (k *wrappedKey20) blobs() ([]byte, []byte, error) {
return k.public, k.blob, nil return k.public, k.blob, nil
} }
func (k *wrappedKey20) algorithm() (Algorithm, error) {
tpmPub, err := tpm2.DecodePublic(k.public)
if err != nil {
return "", fmt.Errorf("decode public key: %v", err)
}
switch tpmPub.Type {
case tpm2.AlgRSA:
return RSA, nil
case tpm2.AlgECC:
return ECDSA, nil
default:
return "", fmt.Errorf("unsupported key type: %v", tpmPub.Type)
}
}

View File

@ -37,6 +37,7 @@ var (
oidSignatureRSASha1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} oidSignatureRSASha1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
oidSignatureRSASha256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} oidSignatureRSASha256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
oidSignatureRSASha384 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112}
oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1}
@ -55,6 +56,7 @@ var signatureAlgorithmDetails = []struct {
}{ }{
{x509.SHA1WithRSA, "SHA1-RSA", oidSignatureRSASha1, x509.RSA, crypto.SHA1}, {x509.SHA1WithRSA, "SHA1-RSA", oidSignatureRSASha1, x509.RSA, crypto.SHA1},
{x509.SHA256WithRSA, "SHA256-RSA", oidSignatureRSASha256, x509.RSA, crypto.SHA256}, {x509.SHA256WithRSA, "SHA256-RSA", oidSignatureRSASha256, x509.RSA, crypto.SHA256},
{x509.SHA384WithRSA, "SHA384-RSA", oidSignatureRSASha384, x509.RSA, crypto.SHA384},
{x509.SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA256}, {x509.SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA256},
{x509.SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA384}, {x509.SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA384},
{x509.SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA512}, {x509.SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA512},
@ -129,50 +131,50 @@ func getSignatureAlgorithmFromAI(ai pkix.AlgorithmIdentifier) x509.SignatureAlgo
return x509.UnknownSignatureAlgorithm return x509.UnknownSignatureAlgorithm
} }
//RFC 5280 4.2.2.1 // RFC 5280 4.2.2.1
type authorityInfoAccess struct { type authorityInfoAccess struct {
Method asn1.ObjectIdentifier Method asn1.ObjectIdentifier
Location asn1.RawValue Location asn1.RawValue
} }
//RFC 5280 4.2.1.1 // RFC 5280 4.2.1.1
type authKeyID struct { type authKeyID struct {
ID []byte `asn1:"optional,tag:0"` ID []byte `asn1:"optional,tag:0"`
IssuerName asn1.RawValue `asn1:"set,optional,tag:1"` IssuerName asn1.RawValue `asn1:"set,optional,tag:1"`
SerialNumber *big.Int `asn1:"optional,tag:2"` SerialNumber *big.Int `asn1:"optional,tag:2"`
} }
//RFC 5280 4.2.1.4 // RFC 5280 4.2.1.4
type cpsPolicy struct { type cpsPolicy struct {
ID asn1.ObjectIdentifier ID asn1.ObjectIdentifier
Value string Value string
} }
//RFC 5280 4.2.1.4 // RFC 5280 4.2.1.4
type policyInformation struct { type policyInformation struct {
Raw asn1.RawContent Raw asn1.RawContent
ID asn1.ObjectIdentifier ID asn1.ObjectIdentifier
Policy asn1.RawValue Policy asn1.RawValue
} }
//RFC 5280 4.1.2.5 // RFC 5280 4.1.2.5
type validity struct { type validity struct {
NotBefore, NotAfter time.Time NotBefore, NotAfter time.Time
} }
//RFC 5280 4.2.1.4 // RFC 5280 4.2.1.4
type NoticeReference struct { type noticeReference struct {
Organization string Organization string
NoticeNumbers []int NoticeNumbers []int
} }
//RFC 5280 4.2.1.4 // RFC 5280 4.2.1.4
type userNotice struct { type userNotice struct {
NoticeRef NoticeReference `asn1:"optional"` NoticeRef noticeReference `asn1:"optional"`
ExplicitText string `asn1:"optional"` ExplicitText string `asn1:"optional"`
} }
//RFC 5755 4.1 // RFC 5755 4.1
type objectDigestInfo struct { type objectDigestInfo struct {
DigestedObjectType asn1.Enumerated DigestedObjectType asn1.Enumerated
OtherObjectTypeID asn1.ObjectIdentifier OtherObjectTypeID asn1.ObjectIdentifier
@ -180,14 +182,14 @@ type objectDigestInfo struct {
ObjectDigest asn1.BitString ObjectDigest asn1.BitString
} }
//RFC 5755 4.1 // RFC 5755 4.1
type attCertIssuer struct { type attCertIssuer struct {
IssuerName asn1.RawValue `asn1:"set,optional"` IssuerName asn1.RawValue `asn1:"set,optional"`
BaseCertificateID issuerSerial `asn1:"optional,tag:0"` BaseCertificateID issuerSerial `asn1:"optional,tag:0"`
ObjectDigestInfo objectDigestInfo `asn1:"optional,tag:1"` ObjectDigestInfo objectDigestInfo `asn1:"optional,tag:1"`
} }
//RFC 5755 4.1 // RFC 5755 4.1
type issuerSerial struct { type issuerSerial struct {
Raw asn1.RawContent Raw asn1.RawContent
Issuer asn1.RawValue Issuer asn1.RawValue
@ -195,7 +197,7 @@ type issuerSerial struct {
IssuerUID asn1.BitString `asn1:"optional"` IssuerUID asn1.BitString `asn1:"optional"`
} }
//RFC 5755 4.1 // RFC 5755 4.1
type holder struct { type holder struct {
Raw asn1.RawContent Raw asn1.RawContent
BaseCertificateID issuerSerial `asn1:"optional,tag:0"` BaseCertificateID issuerSerial `asn1:"optional,tag:0"`
@ -203,13 +205,13 @@ type holder struct {
ObjectDigestInfo objectDigestInfo `asn1:"optional,tag:2"` ObjectDigestInfo objectDigestInfo `asn1:"optional,tag:2"`
} }
//RFC 5755 4.1 // RFC 5755 4.1
type attribute struct { type attribute struct {
ID asn1.ObjectIdentifier ID asn1.ObjectIdentifier
RawValues []asn1.RawValue `asn1:"set"` RawValues []asn1.RawValue `asn1:"set"`
} }
//RFC 5755 4.1 // RFC 5755 4.1
type tbsAttributeCertificate struct { type tbsAttributeCertificate struct {
Raw asn1.RawContent Raw asn1.RawContent
Version int Version int

View File

@ -17,7 +17,7 @@ package attributecert
import ( import (
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"io/ioutil" "os"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -29,7 +29,7 @@ func TestVerifyAttributeCert(t *testing.T) {
"testdata/Intel_pc2.cer", "testdata/Intel_pc2.cer",
"testdata/Intel_pc3.cer", "testdata/Intel_pc3.cer",
} }
data, err := ioutil.ReadFile("testdata/IntelSigningKey_20April2017.cer") data, err := os.ReadFile("testdata/IntelSigningKey_20April2017.cer")
if err != nil { if err != nil {
t.Fatalf("failed to read Intel intermediate certificate: %v", err) t.Fatalf("failed to read Intel intermediate certificate: %v", err)
} }
@ -38,8 +38,8 @@ func TestVerifyAttributeCert(t *testing.T) {
t.Fatalf("failed to parse Intel intermediate certificate: %v", err) t.Fatalf("failed to parse Intel intermediate certificate: %v", err)
} }
for _, filename := range(testfiles) { for _, filename := range testfiles {
data, err = ioutil.ReadFile(filename) data, err = os.ReadFile(filename)
if err != nil { if err != nil {
t.Fatalf("failed to read %s: %v", filename, err) t.Fatalf("failed to read %s: %v", filename, err)
} }
@ -57,7 +57,7 @@ func TestVerifyAttributeCert(t *testing.T) {
} }
func TestParseAttributeCerts(t *testing.T) { func TestParseAttributeCerts(t *testing.T) {
files, err := ioutil.ReadDir("testdata") files, err := os.ReadDir("testdata")
if err != nil { if err != nil {
t.Fatalf("failed to read test dir: %v", err) t.Fatalf("failed to read test dir: %v", err)
} }
@ -70,7 +70,7 @@ func TestParseAttributeCerts(t *testing.T) {
} }
filename := "testdata/" + file.Name() filename := "testdata/" + file.Name()
jsonfile := filename + ".json" jsonfile := filename + ".json"
data, err := ioutil.ReadFile(filename) data, err := os.ReadFile(filename)
if err != nil { if err != nil {
t.Fatalf("failed to read test data %s: %v", filename, err) t.Fatalf("failed to read test data %s: %v", filename, err)
} }
@ -78,7 +78,7 @@ func TestParseAttributeCerts(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to parse test data %s: %v", filename, err) t.Fatalf("failed to parse test data %s: %v", filename, err)
} }
jsondata, err := ioutil.ReadFile(jsonfile) jsondata, err := os.ReadFile(jsonfile)
if err != nil { if err != nil {
t.Fatalf("failed to read json test data %s: %v", jsonfile, err) t.Fatalf("failed to read json test data %s: %v", jsonfile, err)
} }

0
attributecert/testdata/Intel_pc2.cer vendored Executable file → Normal file
View File

0
attributecert/testdata/Intel_pc3.cer vendored Executable file → Normal file
View File

View File

@ -16,8 +16,6 @@ import (
"time" "time"
) )
var simulatorStatePath = flag.String("state_path", "/tmp/sim/NVRAM/00.permall", "Path to ibmswtpm state file")
func ekPub() *rsa.PublicKey { func ekPub() *rsa.PublicKey {
out, err := exec.Command("tpm_getpubek", "-z").Output() out, err := exec.Command("tpm_getpubek", "-z").Output()
if err != nil { if err != nil {

21
ci/run.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash -e
1>&2 echo "-----
WARNING: The TPM 1.2 simulator no longer builds with newer versions of openssl.
These scripts are kept for posterity, but likely won't build on new OS
versions.
----"
export PROJECT_ROOT="$( pwd )"
TMPDIR="$( mktemp -d )"
SIM_DIR="${TMPDIR}/tpm12_sim"
TEST_ROOT="${TMPDIR}/tests_base"
mkdir -pv "${SIM_DIR}"
./ci/setup_tpm12_simulator.sh "${SIM_DIR}"
./ci/setup_tests_fs.sh "${TEST_ROOT}"
go test -v ./... -- --testTPM12
./ci/shutdown_tpm12_simulator.sh "${SIM_DIR}"

23
go.mod
View File

@ -1,14 +1,19 @@
module github.com/google/go-attestation module github.com/google/go-attestation
go 1.16 go 1.24
toolchain go1.24.1
require ( require (
github.com/google/certificate-transparency-go v1.1.1 github.com/google/go-cmp v0.7.0
github.com/google/go-cmp v0.5.5 github.com/google/go-tpm v0.9.3
github.com/google/go-tpm v0.3.2 github.com/google/go-tpm-tools v0.4.5
github.com/google/go-tpm-tools v0.2.1 github.com/google/go-tspi v0.3.0
github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b // indirect golang.org/x/sys v0.31.0
golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48 )
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
require (
github.com/google/certificate-transparency-go v1.1.2 // indirect
golang.org/x/crypto v0.31.0 // indirect
) )

745
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -59,4 +59,3 @@ var (
var ( var (
CloudComputeInstanceIdentifier = []int{1, 3, 6, 1, 4, 1, 11129, 2, 1, 21} CloudComputeInstanceIdentifier = []int{1, 3, 6, 1, 4, 1, 11129, 2, 1, 21}
) )

179
x509/x509ext.go Normal file
View File

@ -0,0 +1,179 @@
// Package x509ext provides functions for (un)marshalling X.509 extensions not
// supported by the crypto/x509 package.
package x509ext
import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"github.com/google/go-attestation/oid"
)
// RFC 4043
//
// https://tools.ietf.org/html/rfc4043
var (
oidPermanentIdentifier = []int{1, 3, 6, 1, 5, 5, 7, 8, 3}
)
// OtherName ::= SEQUENCE {
// type-id OBJECT IDENTIFIER,
// value [0] EXPLICIT ANY DEFINED BY type-id }
type otherName struct {
TypeID asn1.ObjectIdentifier
Value asn1.RawValue
}
func marshalOtherName(typeID asn1.ObjectIdentifier, value interface{}) (asn1.RawValue, error) {
valueBytes, err := asn1.MarshalWithParams(value, "explicit,tag:0")
if err != nil {
return asn1.RawValue{}, err
}
otherName := otherName{
TypeID: typeID,
Value: asn1.RawValue{FullBytes: valueBytes},
}
bytes, err := asn1.MarshalWithParams(otherName, "tag:0")
if err != nil {
return asn1.RawValue{}, err
}
return asn1.RawValue{FullBytes: bytes}, nil
}
// PermanentIdentifier represents an ASN.1 encoded "permanent identifier" as
// defined by RFC4043.
//
// PermanentIdentifier ::= SEQUENCE {
// identifierValue UTF8String OPTIONAL,
// assigner OBJECT IDENTIFIER OPTIONAL
// }
//
// https://datatracker.ietf.org/doc/html/rfc4043
type PermanentIdentifier struct {
IdentifierValue string `asn1:"utf8,optional"`
Assigner asn1.ObjectIdentifier `asn1:"optional"`
}
func parsePermanentIdentifier(der []byte) (PermanentIdentifier, error) {
var permID PermanentIdentifier
if _, err := asn1.UnmarshalWithParams(der, &permID, "explicit,tag:0"); err != nil {
return PermanentIdentifier{}, err
}
return permID, nil
}
// SubjectAltName contains GeneralName variations not supported by the
// crypto/x509 package.
//
// https://datatracker.ietf.org/doc/html/rfc5280
type SubjectAltName struct {
DirectoryNames []pkix.Name
PermanentIdentifiers []PermanentIdentifier
}
// ParseSubjectAltName parses a pkix.Extension into a SubjectAltName struct.
func ParseSubjectAltName(ext pkix.Extension) (*SubjectAltName, error) {
var out SubjectAltName
dirNames, otherNames, err := parseSubjectAltName(ext)
if err != nil {
return nil, fmt.Errorf("parseSubjectAltName: %v", err)
}
out.DirectoryNames = dirNames
for _, otherName := range otherNames {
if otherName.TypeID.Equal(oidPermanentIdentifier) {
permID, err := parsePermanentIdentifier(otherName.Value.FullBytes)
if err != nil {
return nil, fmt.Errorf("parsePermanentIdentifier: %v", err)
}
out.PermanentIdentifiers = append(out.PermanentIdentifiers, permID)
}
}
return &out, nil
}
// https://datatracker.ietf.org/doc/html/rfc5280#page-35
func parseSubjectAltName(ext pkix.Extension) (dirNames []pkix.Name, otherNames []otherName, err error) {
err = forEachSAN(ext.Value, func(generalName asn1.RawValue) error {
switch generalName.Tag {
case 0: // otherName
var otherName otherName
if _, err := asn1.UnmarshalWithParams(generalName.FullBytes, &otherName, "tag:0"); err != nil {
return fmt.Errorf("OtherName: asn1.UnmarshalWithParams: %v", err)
}
otherNames = append(otherNames, otherName)
case 4: // directoryName
var rdns pkix.RDNSequence
if _, err := asn1.Unmarshal(generalName.Bytes, &rdns); err != nil {
return fmt.Errorf("DirectoryName: asn1.Unmarshal: %v", err)
}
var dirName pkix.Name
dirName.FillFromRDNSequence(&rdns)
dirNames = append(dirNames, dirName)
default:
return fmt.Errorf("expected tag %d", generalName.Tag)
}
return nil
})
return
}
// Borrowed from the x509 package.
func forEachSAN(extension []byte, callback func(ext asn1.RawValue) error) error {
var seq asn1.RawValue
rest, err := asn1.Unmarshal(extension, &seq)
if err != nil {
return err
} else if len(rest) != 0 {
return errors.New("x509: trailing data after X.509 extension")
}
if !seq.IsCompound || seq.Tag != 16 || seq.Class != 0 {
return asn1.StructuralError{Msg: "bad SAN sequence"}
}
rest = seq.Bytes
for len(rest) > 0 {
var v asn1.RawValue
rest, err = asn1.Unmarshal(rest, &v)
if err != nil {
return err
}
if err := callback(v); err != nil {
return err
}
}
return nil
}
// MarshalSubjectAltName converts a SubjectAltName struct into a pkix.Extension,
// allowing callers to specify if the extension is critical.
func MarshalSubjectAltName(san *SubjectAltName, critical bool) (pkix.Extension, error) {
var generalNames []asn1.RawValue
for _, permID := range san.PermanentIdentifiers {
val, err := marshalOtherName(oidPermanentIdentifier, permID)
if err != nil {
return pkix.Extension{}, err
}
generalNames = append(generalNames, val)
}
for _, dirName := range san.DirectoryNames {
bytes, err := asn1.MarshalWithParams(dirName.ToRDNSequence(), "explicit,tag:4")
if err != nil {
return pkix.Extension{}, err
}
generalNames = append(generalNames, asn1.RawValue{FullBytes: bytes})
}
val, err := asn1.Marshal(generalNames)
if err != nil {
return pkix.Extension{}, err
}
return pkix.Extension{
Id: oid.SubjectAltName,
Critical: critical,
Value: val,
}, nil
}