mirror of
https://github.com/google/go-attestation.git
synced 2025-03-14 16:26:41 +00:00
Compare commits
125 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c5d6b1e758 | ||
|
7d4525c388 | ||
|
dce70c6163 | ||
|
51a20034c0 | ||
|
a94a8af69d | ||
|
f37925d5d0 | ||
|
f7a27487f1 | ||
|
d9d8fdc48e | ||
|
f44f5ffe7e | ||
|
9cdb0fcd55 | ||
|
dfabc9c919 | ||
|
c7aee80c5d | ||
|
1b202b12e8 | ||
|
183ad1d5ad | ||
|
9cc576ead1 | ||
|
62f7ad0785 | ||
|
f203ad3090 | ||
|
72657612f0 | ||
|
ec740ef912 | ||
|
51d1c6c3c5 | ||
|
0c084813e6 | ||
|
5d68dfee1b | ||
|
b7a5927d66 | ||
|
b36ec6af0a | ||
|
0722a4900b | ||
|
545501297e | ||
|
5148956a0c | ||
|
a9866d34bb | ||
|
5b3763098f | ||
|
e6ab626979 | ||
|
52542411c5 | ||
|
02cf9e2ddd | ||
|
8b301f2d45 | ||
|
3d017c0234 | ||
|
a3545dfc94 | ||
|
93c5899459 | ||
|
74a49366bd | ||
|
776dc3ac22 | ||
|
136789e2e1 | ||
|
82eb5d47a2 | ||
|
f4ab877258 | ||
|
3d71f101b1 | ||
|
42c11fc152 | ||
|
3c84bff65e | ||
|
ab5dee2ae5 | ||
|
046550658b | ||
|
310e2caafe | ||
|
60adf13bc0 | ||
|
a56e8c4896 | ||
|
8af5f4e7de | ||
|
b92d1c69bf | ||
|
d29df30553 | ||
|
63dd90f699 | ||
|
ac9aa2497f | ||
|
2788b541c7 | ||
|
a9b6eb1eb8 | ||
|
50c1e1e03b | ||
|
258084d04e | ||
|
89884d0a74 | ||
|
b474b712d4 | ||
|
a4b579bcf0 | ||
|
62a036b369 | ||
|
10dd5f7a05 | ||
|
3ef3949b46 | ||
|
1f9c436d57 | ||
|
270ecbab1f | ||
|
0ccbb50494 | ||
|
68deb4ce55 | ||
|
5238453493 | ||
|
b93151db1f | ||
|
0dc056af7d | ||
|
19d3c4de97 | ||
|
438907edb0 | ||
|
17f9c05652 | ||
|
d98599d257 | ||
|
053c50e8ad | ||
|
e99c3e104e | ||
|
dff2daeaf0 | ||
|
f5d560164e | ||
|
cb976082a3 | ||
|
50e72a4743 | ||
|
f1ff544e51 | ||
|
e0bd974e4e | ||
|
ad58dc770e | ||
|
8235370483 | ||
|
8820d49b18 | ||
|
0961a88d7c | ||
|
df6b91cbdb | ||
|
03018e6828 | ||
|
0a9ecdcf7c | ||
|
4b44082d2c | ||
|
2a5dfec7cf | ||
|
83d71b1c53 | ||
|
277c40ca1d | ||
|
82f2c9c2c7 | ||
|
21f642c3c7 | ||
|
d114f3922f | ||
|
b92e2746d6 | ||
|
2f8dbfc94e | ||
|
f1f1b84491 | ||
|
57a6cb587a | ||
|
0393b91867 | ||
|
be496f1149 | ||
|
a35bd36e42 | ||
|
505680f536 | ||
|
7cf0af2beb | ||
|
5410759ddc | ||
|
cc52e2d143 | ||
|
7d128657ca | ||
|
e8c5dc4fd5 | ||
|
5df8a8e979 | ||
|
9ff0d31d3c | ||
|
fa6830fc2f | ||
|
7ec6228f59 | ||
|
bec58f2406 | ||
|
20a9e4b381 | ||
|
1b4849d2c3 | ||
|
0a3c6e82bf | ||
|
c4760bd1c6 | ||
|
0b7298fb18 | ||
|
7f6fec6b36 | ||
|
ee5bb94c43 | ||
|
9b857465d0 | ||
|
6848928436 | ||
|
e24a847d44 |
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
@ -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:
|
||||||
|
- "*"
|
||||||
|
12
.github/workflows/codeql-analysis.yml
vendored
12
.github/workflows/codeql-analysis.yml
vendored
@ -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
18
.github/workflows/golangci-lint.yml
vendored
Normal 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
71
.github/workflows/test.yml
vendored
Normal 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
5
.golangci.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
disable:
|
||||||
|
- errcheck
|
16
README.md
16
README.md
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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,61 +57,124 @@ 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 {
|
||||||
if err != nil {
|
name string
|
||||||
t.Fatalf("NewKey() failed: %v", err)
|
opts *KeyConfig
|
||||||
}
|
}{
|
||||||
defer sk.Close()
|
{
|
||||||
|
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 {
|
||||||
|
t.Fatalf("NewKey() failed: %v", err)
|
||||||
|
}
|
||||||
|
defer sk.Close()
|
||||||
|
|
||||||
enc, err := sk.Marshal()
|
enc, err := sk.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("sk.Marshal() failed: %v", err)
|
t.Fatalf("sk.Marshal() failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := sk.Close(); err != nil {
|
if err := sk.Close(); err != nil {
|
||||||
t.Fatalf("sk.Close() failed: %v", err)
|
t.Fatalf("sk.Close() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded, err := tpm.LoadKey(enc)
|
loaded, err := tpm.LoadKey(enc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("LoadKey() failed: %v", err)
|
t.Fatalf("LoadKey() failed: %v", err)
|
||||||
}
|
}
|
||||||
defer loaded.Close()
|
defer loaded.Close()
|
||||||
|
|
||||||
k1, k2 := sk.key.(*wrappedKey20), loaded.key.(*wrappedKey20)
|
k1, k2 := sk.key.(*wrappedKey20), loaded.key.(*wrappedKey20)
|
||||||
if !bytes.Equal(k1.public, k2.public) {
|
if !bytes.Equal(k1.public, k2.public) {
|
||||||
t.Error("Original & loaded Key public blobs did not match.")
|
t.Error("Original & loaded Key public blobs did not match.")
|
||||||
t.Logf("Original = %v", k1.public)
|
t.Logf("Original = %v", k1.public)
|
||||||
t.Logf("Loaded = %v", k2.public)
|
t.Logf("Loaded = %v", k2.public)
|
||||||
}
|
}
|
||||||
|
|
||||||
priv1, err := sk.Private(sk.Public())
|
priv1, err := sk.Private(sk.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("sk.Private() failed: %v", err)
|
t.Fatalf("sk.Private() failed: %v", err)
|
||||||
}
|
}
|
||||||
signer1, ok := priv1.(crypto.Signer)
|
signer1, ok := priv1.(crypto.Signer)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("want crypto.Signer, got %T", priv1)
|
t.Fatalf("want crypto.Signer, got %T", priv1)
|
||||||
}
|
}
|
||||||
pk1, err := x509.MarshalPKIXPublicKey(signer1.Public())
|
pk1, err := x509.MarshalPKIXPublicKey(signer1.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot marshal public key: %v", err)
|
t.Fatalf("cannot marshal public key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
priv2, err := loaded.Private(loaded.Public())
|
priv2, err := loaded.Private(loaded.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("loaded.Private() failed: %v", err)
|
t.Fatalf("loaded.Private() failed: %v", err)
|
||||||
}
|
}
|
||||||
signer2, ok := priv2.(crypto.Signer)
|
signer2, ok := priv2.(crypto.Signer)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("want crypto.Signer, got %T", priv2)
|
t.Fatalf("want crypto.Signer, got %T", priv2)
|
||||||
}
|
}
|
||||||
pk2, err := x509.MarshalPKIXPublicKey(signer2.Public())
|
pk2, err := x509.MarshalPKIXPublicKey(signer2.Public())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("cannot marshal public key: %v", err)
|
t.Fatalf("cannot marshal public key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(pk1, pk2) {
|
if !bytes.Equal(pk1, pk2) {
|
||||||
t.Error("public keys do not match")
|
t.Error("public keys do not match")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,33 +201,183 @@ 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)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewKey() failed: %v", err)
|
|
||||||
}
|
|
||||||
defer sk.Close()
|
|
||||||
|
|
||||||
pub := sk.Public()
|
for _, test := range []struct {
|
||||||
priv, err := sk.Private(pub)
|
name string
|
||||||
if err != nil {
|
keyOpts *KeyConfig
|
||||||
t.Fatalf("sk.Private() failed: %v", err)
|
signOpts crypto.SignerOpts
|
||||||
}
|
digest []byte
|
||||||
signer, ok := priv.(crypto.Signer)
|
}{
|
||||||
if !ok {
|
{
|
||||||
t.Fatalf("want crypto.Signer, got %T", priv)
|
name: "default",
|
||||||
}
|
keyOpts: nil,
|
||||||
digest := []byte("12345678901234567890123456789012")
|
signOpts: nil,
|
||||||
sig, err := signer.Sign(rand.Reader, digest, nil)
|
digest: []byte("12345678901234567890123456789012"),
|
||||||
if err != nil {
|
},
|
||||||
t.Fatalf("signer.Sign() failed: %v", err)
|
{
|
||||||
}
|
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 {
|
||||||
|
t.Fatalf("NewKey() failed: %v", err)
|
||||||
|
}
|
||||||
|
defer sk.Close()
|
||||||
|
|
||||||
|
pub := sk.Public()
|
||||||
|
priv, err := sk.Private(pub)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sk.Private() failed: %v", err)
|
||||||
|
}
|
||||||
|
signer, ok := priv.(crypto.Signer)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("want crypto.Signer, got %T", priv)
|
||||||
|
}
|
||||||
|
sig, err := signer.Sign(rand.Reader, test.digest, test.signOpts)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
29
attest/attest-tool/README.md
Normal file
29
attest/attest-tool/README.md
Normal 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`)
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
157
attest/attest.go
157
attest/attest.go
@ -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,54 +365,74 @@ 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
|
||||||
// the booted state of the machine the TPM is attached to.
|
// the booted state of the machine the TPM is attached to.
|
||||||
//
|
//
|
||||||
// The digests contained in the event log can be considered authentic if:
|
// The digests contained in the event log can be considered authentic if:
|
||||||
// - The AK public corresponds to the known AK for that platform.
|
// - The AK public corresponds to the known AK for that platform.
|
||||||
// - All quotes are verified with AKPublic.Verify(), and return no errors.
|
// - All quotes are verified with AKPublic.Verify(), and return no errors.
|
||||||
// - The event log parsed successfully using ParseEventLog(), and a call
|
// - The event log parsed successfully using ParseEventLog(), and a call
|
||||||
// to EventLog.Verify() with the full set of PCRs returned no error.
|
// to EventLog.Verify() with the full set of PCRs returned no error.
|
||||||
type PlatformParameters struct {
|
type PlatformParameters struct {
|
||||||
// The version of the TPM which generated this attestation.
|
// The version of the TPM which generated this attestation.
|
||||||
TPMVersion TPMVersion
|
TPMVersion TPMVersion
|
||||||
|
@ -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
|
||||||
|
@ -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,63 +67,99 @@ 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 {
|
||||||
|
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 {
|
||||||
|
t.Fatalf("NewAK() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
ak, err := tpm.NewAK(nil)
|
enc, err := ak.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("NewAK() failed: %v", err)
|
ak.Close(tpm)
|
||||||
}
|
t.Fatalf("ak.Marshal() failed: %v", err)
|
||||||
|
}
|
||||||
|
if err := ak.Close(tpm); err != nil {
|
||||||
|
t.Fatalf("ak.Close() failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
enc, err := ak.Marshal()
|
loaded, err := tpm.LoadAK(enc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ak.Close(tpm)
|
t.Fatalf("LoadAK() failed: %v", err)
|
||||||
t.Fatalf("ak.Marshal() failed: %v", err)
|
}
|
||||||
}
|
defer loaded.Close(tpm)
|
||||||
if err := ak.Close(tpm); err != nil {
|
|
||||||
t.Fatalf("ak.Close() failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
loaded, err := tpm.LoadAK(enc)
|
k1, k2 := ak.ak.(*wrappedKey20), loaded.ak.(*wrappedKey20)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("LoadKey() failed: %v", err)
|
|
||||||
}
|
|
||||||
defer loaded.Close(tpm)
|
|
||||||
|
|
||||||
k1, k2 := ak.ak.(*wrappedKey20), loaded.ak.(*wrappedKey20)
|
if !bytes.Equal(k1.public, k2.public) {
|
||||||
|
t.Error("Original & loaded AK public blobs did not match.")
|
||||||
if !bytes.Equal(k1.public, k2.public) {
|
t.Logf("Original = %v", k1.public)
|
||||||
t.Error("Original & loaded AK public blobs did not match.")
|
t.Logf("Loaded = %v", k2.public)
|
||||||
t.Logf("Original = %v", k1.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")
|
||||||
|
@ -16,7 +16,6 @@ package attest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -84,51 +83,75 @@ func TestAKCreateAndLoad(t *testing.T) {
|
|||||||
if !*testLocal {
|
if !*testLocal {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
tpm, err := OpenTPM(nil)
|
for _, test := range []struct {
|
||||||
if err != nil {
|
name string
|
||||||
t.Fatalf("OpenTPM() failed: %v", err)
|
opts *AKConfig
|
||||||
}
|
}{
|
||||||
defer tpm.Close()
|
{
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenTPM() failed: %v", err)
|
||||||
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
enc, err := ak.Marshal()
|
enc, err := ak.Marshal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ak.Close(tpm)
|
ak.Close(tpm)
|
||||||
t.Fatalf("ak.Marshal() failed: %v", err)
|
t.Fatalf("ak.Marshal() failed: %v", err)
|
||||||
}
|
}
|
||||||
if err := ak.Close(tpm); err != nil {
|
if err := ak.Close(tpm); err != nil {
|
||||||
t.Fatalf("ak.Close() failed: %v", err)
|
t.Fatalf("ak.Close() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
k1, k2 := ak.ak.(*wrappedKey20), loaded.ak.(*wrappedKey20)
|
k1, k2 := ak.ak.(*wrappedKey20), loaded.ak.(*wrappedKey20)
|
||||||
|
|
||||||
if !bytes.Equal(k1.public, k2.public) {
|
if !bytes.Equal(k1.public, k2.public) {
|
||||||
t.Error("Original & loaded AK public blobs did not match.")
|
t.Error("Original & loaded AK public blobs did not match.")
|
||||||
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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
|
switch pk := opts.Public.(type) {
|
||||||
return fmt.Errorf("could not verify attestation: %v", err)
|
case *rsa.PublicKey:
|
||||||
|
if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
@ -173,7 +165,8 @@ type EventLog struct {
|
|||||||
// Algs holds the set of algorithms that the event log uses.
|
// Algs holds the set of algorithms that the event log uses.
|
||||||
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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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,10 +35,20 @@ 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
|
||||||
|
|
||||||
// BIOS Events (TCG PC Client Specific Implementation Specification for Conventional BIOS 1.21)
|
// BIOS Events (TCG PC Client Specific Implementation Specification for Conventional BIOS 1.21)
|
||||||
const (
|
const (
|
||||||
PrebootCert EventType = 0x00000000
|
PrebootCert EventType = 0x00000000
|
||||||
PostCode EventType = 0x00000001
|
PostCode EventType = 0x00000001
|
||||||
@ -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,14 +447,96 @@ 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 {
|
certificates = append(certificates, *cert)
|
||||||
certificates = append(certificates, *cert)
|
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
|
||||||
|
}
|
||||||
|
@ -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")
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
@ -78,14 +96,16 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
|
|||||||
// - No UEFI debugger was attached.
|
// - No UEFI debugger was attached.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
out SecurebootState
|
out SecurebootState
|
||||||
seenSeparator bool
|
seenSeparator7 bool
|
||||||
seenAuthority bool
|
seenSeparator2 bool
|
||||||
seenVars = map[string]bool{}
|
seenAuthority 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,99 +113,168 @@ 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 et {
|
|
||||||
case internal.Separator:
|
|
||||||
if seenSeparator {
|
|
||||||
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
|
|
||||||
}
|
|
||||||
seenSeparator = 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.EFIAction:
|
switch e.Index {
|
||||||
if string(e.Data) == "UEFI Debug Mode" {
|
case 7:
|
||||||
return nil, errors.New("a UEFI debugger was present during boot")
|
switch et {
|
||||||
}
|
case internal.Separator:
|
||||||
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
|
if seenSeparator7 {
|
||||||
|
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
|
||||||
|
}
|
||||||
|
seenSeparator7 = 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.EFIVariableDriverConfig:
|
case internal.EFIAction:
|
||||||
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
|
switch string(e.Data) {
|
||||||
if err != nil {
|
case "UEFI Debug Mode":
|
||||||
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
|
return nil, errors.New("a UEFI debugger was present during boot")
|
||||||
}
|
case "DMA Protection Disabled":
|
||||||
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
|
if digestVerify != nil {
|
||||||
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
|
return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.sequence, digestVerify)
|
||||||
}
|
|
||||||
seenVars[v.VarName()] = true
|
|
||||||
if seenSeparator {
|
|
||||||
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
|
|
||||||
}
|
|
||||||
|
|
||||||
if digestVerify != nil {
|
|
||||||
return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.VarName() {
|
|
||||||
case "SecureBoot":
|
|
||||||
if len(v.VariableData) != 1 {
|
|
||||||
return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
|
|
||||||
}
|
|
||||||
out.Enabled = v.VariableData[0] == 1
|
|
||||||
case "PK":
|
|
||||||
if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
|
|
||||||
return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
|
|
||||||
}
|
|
||||||
case "KEK":
|
|
||||||
if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
|
|
||||||
return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
|
|
||||||
}
|
|
||||||
case "db":
|
|
||||||
if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
|
|
||||||
return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
|
|
||||||
}
|
|
||||||
case "dbx":
|
|
||||||
if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
|
|
||||||
return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case internal.EFIVariableAuthority:
|
|
||||||
a, err := internal.ParseUEFIVariableAuthority(bytes.NewReader(e.Data))
|
|
||||||
if err != nil {
|
|
||||||
// Workaround for: https://github.com/google/go-attestation/issues/157
|
|
||||||
if err == internal.ErrSigMissingGUID {
|
|
||||||
// Versions of shim which do not carry
|
|
||||||
// https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50
|
|
||||||
// have an erroneous additional byte in the event, which breaks digest
|
|
||||||
// verification. If verification failed, we try removing the last byte.
|
|
||||||
if digestVerify != nil && len(e.Data) > 0 {
|
|
||||||
digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
|
|
||||||
}
|
}
|
||||||
} else {
|
out.DMAProtectionDisabled = true
|
||||||
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
|
default:
|
||||||
|
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
seenAuthority = true
|
case internal.EFIVariableDriverConfig:
|
||||||
if digestVerify != nil {
|
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
|
||||||
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
|
||||||
if !seenSeparator {
|
}
|
||||||
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
|
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
|
||||||
} else {
|
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
|
||||||
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
|
}
|
||||||
|
seenVars[v.VarName()] = true
|
||||||
|
if seenSeparator7 {
|
||||||
|
return nil, fmt.Errorf("event %d: variable %q specified after separator", e.sequence, v.VarName())
|
||||||
|
}
|
||||||
|
|
||||||
|
if digestVerify != nil {
|
||||||
|
return nil, fmt.Errorf("invalid digest for variable %q on event %d: %v", v.VarName(), e.sequence, digestVerify)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.VarName() {
|
||||||
|
case "SecureBoot":
|
||||||
|
if len(v.VariableData) != 1 {
|
||||||
|
return nil, fmt.Errorf("event %d: SecureBoot data len is %d, expected 1", e.sequence, len(v.VariableData))
|
||||||
|
}
|
||||||
|
out.Enabled = v.VariableData[0] == 1
|
||||||
|
case "PK":
|
||||||
|
if out.PlatformKeys, out.PlatformKeyHashes, err = v.SignatureData(); err != nil {
|
||||||
|
return nil, fmt.Errorf("event %d: failed parsing platform keys: %v", e.sequence, err)
|
||||||
|
}
|
||||||
|
case "KEK":
|
||||||
|
if out.ExchangeKeys, out.ExchangeKeyHashes, err = v.SignatureData(); err != nil {
|
||||||
|
return nil, fmt.Errorf("event %d: failed parsing key exchange keys: %v", e.sequence, err)
|
||||||
|
}
|
||||||
|
case "db":
|
||||||
|
if out.PermittedKeys, out.PermittedHashes, err = v.SignatureData(); err != nil {
|
||||||
|
return nil, fmt.Errorf("event %d: failed parsing signature database: %v", e.sequence, err)
|
||||||
|
}
|
||||||
|
case "dbx":
|
||||||
|
if out.ForbiddenKeys, out.ForbiddenHashes, err = v.SignatureData(); err != nil {
|
||||||
|
return nil, fmt.Errorf("event %d: failed parsing forbidden signature database: %v", e.sequence, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case internal.EFIVariableAuthority:
|
||||||
|
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 {
|
||||||
|
// Workaround for: https://github.com/google/go-attestation/issues/157
|
||||||
|
if err == internal.ErrSigMissingGUID {
|
||||||
|
// Versions of shim which do not carry
|
||||||
|
// https://github.com/rhboot/shim/commit/8a27a4809a6a2b40fb6a4049071bf96d6ad71b50
|
||||||
|
// have an erroneous additional byte in the event, which breaks digest
|
||||||
|
// verification. If verification failed, we try removing the last byte.
|
||||||
|
if digestVerify != nil && len(e.Data) > 0 {
|
||||||
|
digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seenAuthority = true
|
||||||
|
if digestVerify != nil {
|
||||||
|
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
|
||||||
|
}
|
||||||
|
if !seenSeparator7 {
|
||||||
|
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
|
||||||
|
} else {
|
||||||
|
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected event type in PCR7: %v", et)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
case 2:
|
||||||
return nil, fmt.Errorf("unexpected event type: %v", et)
|
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 {
|
||||||
return &out, nil
|
return &out, nil
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BIN
attest/testdata/coreos_36_shielded_vm_no_secure_boot_eventlog
vendored
Normal file
BIN
attest/testdata/coreos_36_shielded_vm_no_secure_boot_eventlog
vendored
Normal file
Binary file not shown.
BIN
attest/testdata/option_rom_eventlog
vendored
Normal file
BIN
attest/testdata/option_rom_eventlog
vendored
Normal file
Binary file not shown.
BIN
attest/testdata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog
vendored
Normal file
BIN
attest/testdata/ubuntu_2104_shielded_vm_no_secure_boot_eventlog
vendored
Normal file
Binary file not shown.
221
attest/tpm.go
221
attest/tpm.go
@ -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.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
|
||||||
Attributes: tpm2.FlagSignerDefault ^ tpm2.FlagRestricted,
|
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
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -15,24 +15,68 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// wrappedTPM20 interfaces with a TPM 2.0 command channel.
|
// wrappedTPM20 interfaces with a TPM 2.0 command channel.
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i, err := t.info()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Retrieving TPM info failed: %v", err)
|
||||||
|
}
|
||||||
|
ekPub := &rsa.PublicKey{
|
||||||
|
E: int(pub.RSAParameters.Exponent()),
|
||||||
|
N: pub.RSAParameters.Modulus(),
|
||||||
|
}
|
||||||
|
var certificateURL string
|
||||||
|
if i.Manufacturer.String() == manufacturerIntel {
|
||||||
|
certificateURL = intelEKURL(ekPub)
|
||||||
|
}
|
||||||
return []EK{
|
return []EK{
|
||||||
{
|
{
|
||||||
Public: &rsa.PublicKey{
|
Public: ekPub,
|
||||||
E: int(pub.RSAParameters.Exponent()),
|
CertificateURL: certificateURL,
|
||||||
N: pub.RSAParameters.Modulus(),
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
0
attributecert/testdata/Intel_pc2.cer
vendored
Executable file → Normal file
0
attributecert/testdata/Intel_pc3.cer
vendored
Executable file → Normal file
0
attributecert/testdata/Intel_pc3.cer
vendored
Executable file → Normal 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
21
ci/run.sh
Executable 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
23
go.mod
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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
179
x509/x509ext.go
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user