mirror of
https://github.com/google/go-attestation.git
synced 2025-03-14 00:06:31 +00:00
Compare commits
137 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 | ||
|
b6c6a0c365 | ||
|
31ad4f57fd | ||
|
b89180c3eb | ||
|
1ceeedc8dc | ||
|
1bbba0bdfd | ||
|
611c6598b2 | ||
|
9fc6c7504a | ||
|
1379a4f766 | ||
|
440d34a877 | ||
|
328912c0ae | ||
|
d436f3c9c5 | ||
|
339bdb245a |
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
@ -4,3 +4,16 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
"Go modules":
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
14
.github/workflows/codeql-analysis.yml
vendored
14
.github/workflows/codeql-analysis.yml
vendored
@ -1,5 +1,9 @@
|
||||
name: "CodeQL"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
@ -20,20 +24,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- 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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@ -73,7 +81,7 @@ if err != nil {
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -107,7 +115,7 @@ returning the same secret to the server.
|
||||
```go
|
||||
// Client decrypts the credential
|
||||
|
||||
akBytes, err := ioutil.ReadFile("encrypted_aik.json")
|
||||
akBytes, err := os.ReadFile("encrypted_aik.json")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
tpm1 "github.com/google/go-tpm/tpm"
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
@ -36,11 +36,11 @@ type ActivationParameters struct {
|
||||
// TPMVersion holds the version of the TPM, either 1.2 or 2.0.
|
||||
TPMVersion TPMVersion
|
||||
|
||||
// EK, the endorsement key, describes an asymmetric key who's
|
||||
// private key is permenantly bound to the TPM.
|
||||
// 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 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
|
||||
// they are trying to associate the AK with.
|
||||
EK crypto.PublicKey
|
||||
@ -233,6 +233,12 @@ func (p *ActivationParameters) generateChallengeTPM20(secret []byte) (*Encrypted
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DecodeAttestationData() failed: %v", err)
|
||||
}
|
||||
if att.AttestedCreationInfo == nil {
|
||||
return nil, fmt.Errorf("attestation was not for a creation event")
|
||||
}
|
||||
if att.AttestedCreationInfo.Name.Digest == nil {
|
||||
return nil, fmt.Errorf("attestation creation info name has no digest")
|
||||
}
|
||||
cred, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, p.EK, symBlockSize, secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credactivation.Generate() failed: %v", err)
|
||||
|
136
attest/application_key.go
Normal file
136
attest/application_key.go
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2021 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy of
|
||||
// the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type key interface {
|
||||
close(tpmBase) error
|
||||
marshal() ([]byte, error)
|
||||
certificationParameters() CertificationParameters
|
||||
sign(tpmBase, []byte, crypto.PublicKey, crypto.SignerOpts) ([]byte, error)
|
||||
decrypt(tpmBase, []byte) ([]byte, error)
|
||||
blobs() ([]byte, []byte, error)
|
||||
}
|
||||
|
||||
// Key represents a key which can be used for signing and decrypting
|
||||
// outside-TPM objects.
|
||||
type Key struct {
|
||||
key key
|
||||
pub crypto.PublicKey
|
||||
tpm tpmBase
|
||||
}
|
||||
|
||||
// signer implements crypto.Signer returned by Key.Private().
|
||||
type signer struct {
|
||||
key key
|
||||
pub crypto.PublicKey
|
||||
tpm tpmBase
|
||||
}
|
||||
|
||||
// Sign signs digest with the TPM-stored private signing key.
|
||||
func (s *signer) Sign(r io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
return s.key.sign(s.tpm, digest, s.pub, opts)
|
||||
}
|
||||
|
||||
// Public returns the public key corresponding to the private signing key.
|
||||
func (s *signer) Public() crypto.PublicKey {
|
||||
return s.pub
|
||||
}
|
||||
|
||||
// Algorithm indicates an asymmetric algorithm to be used.
|
||||
type Algorithm string
|
||||
|
||||
// Algorithm types supported.
|
||||
const (
|
||||
ECDSA Algorithm = "ECDSA"
|
||||
RSA Algorithm = "RSA"
|
||||
)
|
||||
|
||||
// KeyConfig encapsulates parameters for minting keys.
|
||||
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.
|
||||
func (k *Key) Public() crypto.PublicKey {
|
||||
return k.pub
|
||||
}
|
||||
|
||||
// Private returns an object allowing to use the TPM-backed private key.
|
||||
// For now it implements only crypto.Signer.
|
||||
func (k *Key) Private(pub crypto.PublicKey) (crypto.PrivateKey, error) {
|
||||
switch pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
if _, ok := k.pub.(*rsa.PublicKey); !ok {
|
||||
return nil, fmt.Errorf("incompatible public key types: %T != %T", pub, k.pub)
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
if _, ok := k.pub.(*ecdsa.PublicKey); !ok {
|
||||
return nil, fmt.Errorf("incompatible public key types: %T != %T", pub, k.pub)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported public key type: %T", pub)
|
||||
}
|
||||
return &signer{k.key, k.pub, k.tpm}, nil
|
||||
}
|
||||
|
||||
// Close unloads the key from the system.
|
||||
func (k *Key) Close() error {
|
||||
return k.key.close(k.tpm)
|
||||
}
|
||||
|
||||
// Marshal encodes the key in a format that can be loaded with tpm.LoadKey().
|
||||
// This method exists to allow consumers to store the key persistently and load
|
||||
// it as a later time. Users SHOULD NOT attempt to interpret or extract values
|
||||
// from this blob.
|
||||
func (k *Key) Marshal() ([]byte, error) {
|
||||
return k.key.marshal()
|
||||
}
|
||||
|
||||
// CertificationParameters returns information about the key required to
|
||||
// verify key certification.
|
||||
func (k *Key) CertificationParameters() CertificationParameters {
|
||||
return k.key.certificationParameters()
|
||||
}
|
||||
|
||||
// Blobs returns public and private blobs to be used by tpm2.Load().
|
||||
func (k *Key) Blobs() (pub, priv []byte, err error) {
|
||||
return k.key.blobs()
|
||||
}
|
544
attest/application_key_test.go
Normal file
544
attest/application_key_test.go
Normal file
@ -0,0 +1,544 @@
|
||||
// Copyright 2021 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy of
|
||||
// the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
//go:build (!localtest || !tpm12) && cgo && !gofuzz
|
||||
// +build !localtest !tpm12
|
||||
// +build cgo
|
||||
// +build !gofuzz
|
||||
|
||||
// NOTE: simulator requires cgo, hence the build tag.
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimTPM20KeyCreateAndLoad(t *testing.T) {
|
||||
sim, tpm := setupSimulatedTPM(t)
|
||||
defer sim.Close()
|
||||
testKeyCreateAndLoad(t, tpm)
|
||||
}
|
||||
|
||||
func TestTPM20KeyCreateAndLoad(t *testing.T) {
|
||||
if !*testLocal {
|
||||
t.SkipNow()
|
||||
}
|
||||
tpm, err := OpenTPM(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenTPM() failed: %v", err)
|
||||
}
|
||||
defer tpm.Close()
|
||||
testKeyCreateAndLoad(t, tpm)
|
||||
}
|
||||
|
||||
func testKeyCreateAndLoad(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
|
||||
}{
|
||||
{
|
||||
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()
|
||||
if err != nil {
|
||||
t.Fatalf("sk.Marshal() failed: %v", err)
|
||||
}
|
||||
if err := sk.Close(); err != nil {
|
||||
t.Fatalf("sk.Close() failed: %v", err)
|
||||
}
|
||||
|
||||
loaded, err := tpm.LoadKey(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadKey() failed: %v", err)
|
||||
}
|
||||
defer loaded.Close()
|
||||
|
||||
k1, k2 := sk.key.(*wrappedKey20), loaded.key.(*wrappedKey20)
|
||||
if !bytes.Equal(k1.public, k2.public) {
|
||||
t.Error("Original & loaded Key public blobs did not match.")
|
||||
t.Logf("Original = %v", k1.public)
|
||||
t.Logf("Loaded = %v", k2.public)
|
||||
}
|
||||
|
||||
priv1, err := sk.Private(sk.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("sk.Private() failed: %v", err)
|
||||
}
|
||||
signer1, ok := priv1.(crypto.Signer)
|
||||
if !ok {
|
||||
t.Fatalf("want crypto.Signer, got %T", priv1)
|
||||
}
|
||||
pk1, err := x509.MarshalPKIXPublicKey(signer1.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("cannot marshal public key: %v", err)
|
||||
}
|
||||
|
||||
priv2, err := loaded.Private(loaded.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("loaded.Private() failed: %v", err)
|
||||
}
|
||||
signer2, ok := priv2.(crypto.Signer)
|
||||
if !ok {
|
||||
t.Fatalf("want crypto.Signer, got %T", priv2)
|
||||
}
|
||||
pk2, err := x509.MarshalPKIXPublicKey(signer2.Public())
|
||||
if err != nil {
|
||||
t.Fatalf("cannot marshal public key: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(pk1, pk2) {
|
||||
t.Error("public keys do not match")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimTPM20KeySign(t *testing.T) {
|
||||
sim, tpm := setupSimulatedTPM(t)
|
||||
defer sim.Close()
|
||||
testKeySign(t, tpm)
|
||||
}
|
||||
|
||||
func TestTPM20KeySign(t *testing.T) {
|
||||
if !*testLocal {
|
||||
t.SkipNow()
|
||||
}
|
||||
tpm, err := OpenTPM(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenTPM() failed: %v", err)
|
||||
}
|
||||
defer tpm.Close()
|
||||
testKeySign(t, tpm)
|
||||
}
|
||||
|
||||
func testKeySign(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
|
||||
keyOpts *KeyConfig
|
||||
signOpts crypto.SignerOpts
|
||||
digest []byte
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
keyOpts: nil,
|
||||
signOpts: nil,
|
||||
digest: []byte("12345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "ECDSAP256-SHA256",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: ECDSA,
|
||||
Size: 256,
|
||||
},
|
||||
signOpts: nil,
|
||||
digest: []byte("12345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "ECDSAP384-SHA384",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: ECDSA,
|
||||
Size: 384,
|
||||
},
|
||||
signOpts: nil,
|
||||
digest: []byte("123456789012345678901234567890121234567890123456"),
|
||||
},
|
||||
{
|
||||
name: "ECDSAP521-SHA512",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: ECDSA,
|
||||
Size: 521,
|
||||
},
|
||||
signOpts: nil,
|
||||
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PKCS1v15-SHA256",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: crypto.SHA256,
|
||||
digest: []byte("12345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PKCS1v15-SHA384",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: crypto.SHA384,
|
||||
digest: []byte("123456789012345678901234567890121234567890123456"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PKCS1v15-SHA512",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: crypto.SHA512,
|
||||
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PSS-SHA256",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
digest: []byte("12345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PSS-SHA384",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA384,
|
||||
},
|
||||
digest: []byte("123456789012345678901234567890121234567890123456"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PSS-SHA512",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA512,
|
||||
},
|
||||
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PSS-SHA256, explicit salt len",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: &rsa.PSSOptions{
|
||||
SaltLength: 32,
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
digest: []byte("12345678901234567890123456789012"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PSS-SHA384, explicit salt len",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: &rsa.PSSOptions{
|
||||
SaltLength: 48,
|
||||
Hash: crypto.SHA384,
|
||||
},
|
||||
digest: []byte("123456789012345678901234567890121234567890123456"),
|
||||
},
|
||||
{
|
||||
name: "RSA2048-PSS-SHA512, explicit salt len",
|
||||
keyOpts: &KeyConfig{
|
||||
Algorithm: RSA,
|
||||
Size: 2048,
|
||||
},
|
||||
signOpts: &rsa.PSSOptions{
|
||||
SaltLength: 64,
|
||||
Hash: crypto.SHA512,
|
||||
},
|
||||
digest: []byte("1234567890123456789012345678901212345678901234567890123456789012"),
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sk, err := tpm.NewKey(ak, test.keyOpts)
|
||||
if err != nil {
|
||||
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 }{}
|
||||
_, err := asn1.Unmarshal(sig, &parsed)
|
||||
if err != nil {
|
||||
t.Fatalf("signature parsing failed: %v", err)
|
||||
}
|
||||
pubECDSA, ok := pub.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("want *ecdsa.PublicKey, got %T", pub)
|
||||
}
|
||||
if !ecdsa.Verify(pubECDSA, digest[:], parsed.R, parsed.S) {
|
||||
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/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/google/go-attestation/attest"
|
||||
"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 {
|
||||
// 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}
|
||||
|
||||
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("Interface: %d\n", info.Interface)
|
||||
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":
|
||||
k, err := tpm.NewAK(nil)
|
||||
@ -153,10 +152,10 @@ func runCommand(tpm *attest.TPM) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(*keyPath, b, 0644)
|
||||
return os.WriteFile(*keyPath, b, 0644)
|
||||
|
||||
case "quote":
|
||||
b, err := ioutil.ReadFile(*keyPath)
|
||||
b, err := os.ReadFile(*keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ package eventlog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-attestation/attest"
|
||||
@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
func parseEvents(t *testing.T, testdata string) []attest.Event {
|
||||
data, err := ioutil.ReadFile(testdata)
|
||||
data, err := os.ReadFile(testdata)
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package internal
|
||||
|
||||
import (
|
||||
"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.
|
||||
|
157
attest/attest.go
157
attest/attest.go
@ -17,13 +17,15 @@ package attest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
)
|
||||
|
||||
// TPMVersion is used to configure a preference in
|
||||
@ -97,12 +99,25 @@ const (
|
||||
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 {
|
||||
close(tpmBase) error
|
||||
marshal() ([]byte, error)
|
||||
activateCredential(tpm tpmBase, in EncryptedCredential) ([]byte, error)
|
||||
quote(t tpmBase, nonce []byte, alg HashAlg) (*Quote, error)
|
||||
activateCredential(tpm tpmBase, in EncryptedCredential, ek *EK) ([]byte, error)
|
||||
quote(t tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error)
|
||||
attestationParameters() AttestationParameters
|
||||
certify(tb tpmBase, handle interface{}, opts CertifyOpts) (*CertificationParameters, error)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
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.
|
||||
@ -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
|
||||
// platform should use tpm.AttestPlatform() instead.
|
||||
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
|
||||
@ -145,9 +180,23 @@ func (k *AK) AttestationParameters() AttestationParameters {
|
||||
return k.ak.attestationParameters()
|
||||
}
|
||||
|
||||
// AKConfig encapsulates parameters for minting keys. This type is defined
|
||||
// now (despite being empty) for future interface compatibility.
|
||||
// Certify uses the attestation key to certify the key with `handle`. It returns
|
||||
// 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 {
|
||||
// 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
|
||||
@ -170,6 +219,16 @@ type PCR struct {
|
||||
Index int
|
||||
Digest []byte
|
||||
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
|
||||
@ -185,6 +244,9 @@ type EK struct {
|
||||
// Public key. Clients or servers can perform an HTTP GET to this URL, and
|
||||
// use ParseEKCertificate on the response body.
|
||||
CertificateURL string
|
||||
|
||||
// The EK persistent handle.
|
||||
handle tpmutil.Handle
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
type HashAlg uint8
|
||||
|
||||
// Valid hash algorithms.
|
||||
// Known valid hash algorithms.
|
||||
var (
|
||||
HashSHA1 = HashAlg(tpm2.AlgSHA1)
|
||||
HashSHA256 = HashAlg(tpm2.AlgSHA256)
|
||||
HashSHA384 = HashAlg(tpm2.AlgSHA384)
|
||||
HashSHA512 = HashAlg(tpm2.AlgSHA512)
|
||||
)
|
||||
|
||||
func (a HashAlg) cryptoHash() crypto.Hash {
|
||||
switch a {
|
||||
case HashSHA1:
|
||||
return crypto.SHA1
|
||||
case HashSHA256:
|
||||
return crypto.SHA256
|
||||
g := a.goTPMAlg()
|
||||
h, err := g.Hash()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("HashAlg %v (corresponding to TPM2.Algorithm %v) has no corresponding crypto.Hash", a, g))
|
||||
}
|
||||
return 0
|
||||
return h
|
||||
}
|
||||
|
||||
func (a HashAlg) goTPMAlg() tpm2.Algorithm {
|
||||
switch a {
|
||||
case HashSHA1:
|
||||
return tpm2.AlgSHA1
|
||||
case HashSHA256:
|
||||
return tpm2.AlgSHA256
|
||||
}
|
||||
return 0
|
||||
return tpm2.Algorithm(a)
|
||||
}
|
||||
|
||||
// String returns a human-friendly representation of the hash algorithm.
|
||||
func (a HashAlg) String() string {
|
||||
switch a {
|
||||
case HashSHA1:
|
||||
return "SHA1"
|
||||
case HashSHA256:
|
||||
return "SHA256"
|
||||
}
|
||||
return fmt.Sprintf("HashAlg<%d>", int(a))
|
||||
return a.goTPMAlg().String()
|
||||
}
|
||||
|
||||
// PlatformParameters encapsulates the set of information necessary to attest
|
||||
// the booted state of the machine the TPM is attached to.
|
||||
//
|
||||
// The digests contained in the event log can be considered authentic if:
|
||||
// - The AK public corresponds to the known AK for that platform.
|
||||
// - All quotes are verified with AKPublic.Verify(), and return no errors.
|
||||
// - The event log parsed successfully using ParseEventLog(), and a call
|
||||
// to EventLog.Verify() with the full set of PCRs returned no error.
|
||||
// - The AK public corresponds to the known AK for that platform.
|
||||
// - All quotes are verified with AKPublic.Verify(), and return no errors.
|
||||
// - The event log parsed successfully using ParseEventLog(), and a call
|
||||
// to EventLog.Verify() with the full set of PCRs returned no error.
|
||||
type PlatformParameters struct {
|
||||
// The version of the TPM which generated this attestation.
|
||||
TPMVersion TPMVersion
|
||||
|
@ -12,6 +12,7 @@
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package attest
|
||||
|
@ -12,8 +12,10 @@
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
//go:build (!localtest || !tpm12) && cgo && !gofuzz
|
||||
// +build !localtest !tpm12
|
||||
// +build cgo
|
||||
// +build !gofuzz
|
||||
|
||||
// NOTE: simulator requires cgo, hence the build tag.
|
||||
|
||||
@ -33,7 +35,7 @@ func setupSimulatedTPM(t *testing.T) (*simulator.Simulator, *TPM) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
attestTPM, err := OpenTPM(&OpenConfig{CommandChannel: &linuxCmdChannel{tpm}})
|
||||
attestTPM, err := OpenTPM(&OpenConfig{CommandChannel: &fakeCmdChannel{tpm}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -65,63 +67,99 @@ func TestSimTPM20Info(t *testing.T) {
|
||||
func TestSimTPM20AKCreateAndLoad(t *testing.T) {
|
||||
sim, tpm := setupSimulatedTPM(t)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAK() failed: %v", err)
|
||||
}
|
||||
enc, err := ak.Marshal()
|
||||
if err != nil {
|
||||
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()
|
||||
if err != nil {
|
||||
ak.Close(tpm)
|
||||
t.Fatalf("ak.Marshal() failed: %v", err)
|
||||
}
|
||||
if err := ak.Close(tpm); err != nil {
|
||||
t.Fatalf("ak.Close() failed: %v", err)
|
||||
}
|
||||
loaded, err := tpm.LoadAK(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAK() failed: %v", err)
|
||||
}
|
||||
defer loaded.Close(tpm)
|
||||
|
||||
loaded, err := tpm.LoadAK(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadKey() failed: %v", err)
|
||||
}
|
||||
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) {
|
||||
t.Error("Original & loaded AK public blobs did not match.")
|
||||
t.Logf("Original = %v", k1.public)
|
||||
t.Logf("Loaded = %v", k2.public)
|
||||
if !bytes.Equal(k1.public, k2.public) {
|
||||
t.Error("Original & loaded AK public blobs did not match.")
|
||||
t.Logf("Original = %v", k1.public)
|
||||
t.Logf("Loaded = %v", k2.public)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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()
|
||||
if err != nil {
|
||||
t.Fatalf("EKs() failed: %v", err)
|
||||
}
|
||||
ek := chooseEK(t, EKs)
|
||||
|
||||
ak, err := tpm.NewAK(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAK() failed: %v", err)
|
||||
}
|
||||
defer ak.Close(tpm)
|
||||
|
||||
ap := ActivationParameters{
|
||||
TPMVersion: TPMVersion20,
|
||||
AK: ak.AttestationParameters(),
|
||||
EK: ek,
|
||||
EK: ek.Public,
|
||||
}
|
||||
secret, challenge, err := ap.Generate()
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
defer sim.Close()
|
||||
|
||||
@ -158,9 +196,13 @@ func TestSimTPM20QuoteAndVerify(t *testing.T) {
|
||||
defer ak.Close(tpm)
|
||||
|
||||
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 {
|
||||
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
|
||||
@ -178,7 +220,14 @@ func TestSimTPM20QuoteAndVerify(t *testing.T) {
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -203,10 +252,8 @@ func TestSimTPM20AttestPlatform(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("ParseAKPublic() failed: %v", err)
|
||||
}
|
||||
for i, q := range attestation.Quotes {
|
||||
if err := pub.Verify(q, attestation.PCRs, nonce); err != nil {
|
||||
t.Errorf("quote[%d] verification failed: %v", i, err)
|
||||
}
|
||||
if err := pub.VerifyAll(attestation.Quotes, attestation.PCRs, nonce); err != nil {
|
||||
t.Errorf("quote verification failed: %v", 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)
|
||||
defer sim.Close()
|
||||
|
||||
ekHnd, _, err := tpm.tpm.(*wrappedTPM20).getPrimaryKeyHandle(commonEkEquivalentHandle)
|
||||
srkHnd, _, err := tpm.tpm.(*wrappedTPM20).getStorageRootKeyHandle(parentConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("getPrimaryKeyHandle() failed: %v", err)
|
||||
t.Fatalf("getStorageRootKeyHandle() failed: %v", err)
|
||||
}
|
||||
if ekHnd != commonEkEquivalentHandle {
|
||||
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, commonEkEquivalentHandle)
|
||||
if srkHnd != parentConfig.Handle {
|
||||
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 {
|
||||
t.Fatalf("second getPrimaryKeyHandle() failed: %v", err)
|
||||
t.Fatalf("second getStorageRootKeyHandle() failed: %v", err)
|
||||
}
|
||||
if ekHnd != commonEkEquivalentHandle {
|
||||
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, commonEkEquivalentHandle)
|
||||
if srkHnd != parentConfig.Handle {
|
||||
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 {
|
||||
t.Fatalf("generated a new key the second time; that shouldn't happen")
|
||||
|
@ -16,7 +16,6 @@ package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"flag"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@ -84,51 +83,75 @@ func TestAKCreateAndLoad(t *testing.T) {
|
||||
if !*testLocal {
|
||||
t.SkipNow()
|
||||
}
|
||||
tpm, err := OpenTPM(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenTPM() failed: %v", err)
|
||||
}
|
||||
defer tpm.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) {
|
||||
tpm, err := OpenTPM(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("OpenTPM() failed: %v", err)
|
||||
}
|
||||
defer tpm.Close()
|
||||
|
||||
ak, err := tpm.NewAK(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAK() failed: %v", err)
|
||||
}
|
||||
ak, err := tpm.NewAK(test.opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAK() failed: %v", err)
|
||||
}
|
||||
|
||||
enc, err := ak.Marshal()
|
||||
if err != nil {
|
||||
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()
|
||||
if err != nil {
|
||||
ak.Close(tpm)
|
||||
t.Fatalf("ak.Marshal() failed: %v", err)
|
||||
}
|
||||
if err := ak.Close(tpm); err != nil {
|
||||
t.Fatalf("ak.Close() failed: %v", err)
|
||||
}
|
||||
|
||||
loaded, err := tpm.LoadAK(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadKey() failed: %v", err)
|
||||
}
|
||||
defer loaded.Close(tpm)
|
||||
loaded, err := tpm.LoadAK(enc)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAK() failed: %v", err)
|
||||
}
|
||||
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) {
|
||||
t.Error("Original & loaded AK public blobs did not match.")
|
||||
t.Logf("Original = %v", k1.public)
|
||||
t.Logf("Loaded = %v", k2.public)
|
||||
if !bytes.Equal(k1.public, k2.public) {
|
||||
t.Error("Original & loaded AK public blobs did not match.")
|
||||
t.Logf("Original = %v", k1.public)
|
||||
t.Logf("Loaded = %v", k2.public)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// chooseEK selects the EK public which will be activated against.
|
||||
func chooseEK(t *testing.T, eks []EK) crypto.PublicKey {
|
||||
// chooseEK selects the EK which will be activated against.
|
||||
func chooseEK(t *testing.T, eks []EK) EK {
|
||||
t.Helper()
|
||||
|
||||
for _, ek := range eks {
|
||||
return ek.Public
|
||||
return ek
|
||||
}
|
||||
|
||||
t.Fatalf("No suitable EK found")
|
||||
return nil
|
||||
return EK{}
|
||||
}
|
||||
|
||||
func TestPCRs(t *testing.T) {
|
||||
|
@ -151,7 +151,7 @@ func TestTPMActivateCredential(t *testing.T) {
|
||||
ap := ActivationParameters{
|
||||
TPMVersion: TPMVersion12,
|
||||
AK: ak.AttestationParameters(),
|
||||
EK: ek,
|
||||
EK: ek.Public,
|
||||
}
|
||||
secret, challenge, err := ap.Generate()
|
||||
if err != nil {
|
||||
|
270
attest/certification.go
Normal file
270
attest/certification.go
Normal file
@ -0,0 +1,270 @@
|
||||
// Copyright 2021 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy of
|
||||
// the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"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,
|
||||
// the selection is based on the key size only.
|
||||
var secureCurves = map[tpm2.EllipticCurve]bool{
|
||||
tpm2.CurveNISTP256: true,
|
||||
tpm2.CurveNISTP384: true,
|
||||
tpm2.CurveNISTP521: true,
|
||||
tpm2.CurveBNP256: true,
|
||||
tpm2.CurveBNP638: true,
|
||||
}
|
||||
|
||||
// CertificationParameters encapsulates the inputs for certifying an application key.
|
||||
// Only TPM 2.0 is supported at this point.
|
||||
type CertificationParameters struct {
|
||||
// Public represents the key's canonical encoding (a TPMT_PUBLIC structure).
|
||||
// It includes the public key and signing parameters.
|
||||
Public []byte
|
||||
// CreateData represents the properties of a TPM 2.0 key. It is encoded
|
||||
// as a TPMS_CREATION_DATA structure.
|
||||
CreateData []byte
|
||||
// CreateAttestation represents an assertion as to the details of the key.
|
||||
// It is encoded as a TPMS_ATTEST structure.
|
||||
CreateAttestation []byte
|
||||
// CreateSignature represents a signature of the CreateAttestation structure.
|
||||
// It is encoded as a TPMT_SIGNATURE structure.
|
||||
CreateSignature []byte
|
||||
}
|
||||
|
||||
// VerifyOpts specifies options for the key certification's verification.
|
||||
type VerifyOpts struct {
|
||||
// Public is the public key used to verify key ceritification.
|
||||
Public crypto.PublicKey
|
||||
// Hash is the hash function used for signature verification. It can be
|
||||
// extracted from the properties of the certifying key.
|
||||
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:
|
||||
// - the key length is secure
|
||||
// - the attestation parameters matched the attested key
|
||||
// - the key was TPM-generated and resides within TPM
|
||||
// - the key can sign/decrypt outside-TPM objects
|
||||
// - the signature is successfuly verified against the passed public key
|
||||
// For now, it accepts only RSA verification keys.
|
||||
func (p *CertificationParameters) Verify(opts VerifyOpts) error {
|
||||
pub, err := tpm2.DecodePublic(p.Public)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DecodePublic() failed: %v", err)
|
||||
}
|
||||
att, err := tpm2.DecodeAttestationData(p.CreateAttestation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DecodeAttestationData() failed: %v", err)
|
||||
}
|
||||
if att.Type != tpm2.TagAttestCertify {
|
||||
return fmt.Errorf("attestation does not apply to certification data, got tag %x", att.Type)
|
||||
}
|
||||
|
||||
switch pub.Type {
|
||||
case tpm2.AlgRSA:
|
||||
if pub.RSAParameters.KeyBits < minRSABits {
|
||||
return fmt.Errorf("attested key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits)
|
||||
}
|
||||
case tpm2.AlgECC:
|
||||
if !secureCurves[pub.ECCParameters.CurveID] {
|
||||
return fmt.Errorf("attested key uses insecure curve")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
|
||||
}
|
||||
|
||||
// Make sure the key has sane parameters (e.g., attestation can be faked if an AK
|
||||
// can be used for arbitrary signatures).
|
||||
// We verify the following:
|
||||
// - Key is TPM backed.
|
||||
// - Key is TPM generated.
|
||||
// - Key is not restricted (means it can do arbitrary signing/decrypt ops).
|
||||
// - Key cannot be duplicated.
|
||||
// - Key was generated by a call to TPM_Create*.
|
||||
if att.Magic != tpm20GeneratedMagic {
|
||||
return errors.New("creation attestation was not produced by a TPM")
|
||||
}
|
||||
if (pub.Attributes & tpm2.FlagFixedTPM) == 0 {
|
||||
return errors.New("provided key is exportable")
|
||||
}
|
||||
if (pub.Attributes & tpm2.FlagRestricted) != 0 {
|
||||
return errors.New("provided key is restricted")
|
||||
}
|
||||
if (pub.Attributes & tpm2.FlagFixedParent) == 0 {
|
||||
return errors.New("provided key can be duplicated to a different parent")
|
||||
}
|
||||
if (pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0 {
|
||||
return errors.New("provided key is not created by TPM")
|
||||
}
|
||||
|
||||
// Verify the attested creation name matches what is computed from
|
||||
// the public key.
|
||||
match, err := att.AttestedCertifyInfo.Name.MatchesPublic(pub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !match {
|
||||
return errors.New("certification refers to a different key")
|
||||
}
|
||||
|
||||
// Check the signature over the attestation data verifies correctly.
|
||||
if !opts.Hash.Available() {
|
||||
return fmt.Errorf("hash function is unavailable")
|
||||
}
|
||||
hsh := opts.Hash.New()
|
||||
hsh.Write(p.CreateAttestation)
|
||||
|
||||
if len(p.CreateSignature) < 8 {
|
||||
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.CreateSignature))
|
||||
}
|
||||
|
||||
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.CreateSignature))
|
||||
if err != nil {
|
||||
return fmt.Errorf("DecodeSignature() failed: %v", err)
|
||||
}
|
||||
|
||||
switch pk := opts.Public.(type) {
|
||||
case *rsa.PublicKey:
|
||||
if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
472
attest/certification_test.go
Normal file
472
attest/certification_test.go
Normal file
@ -0,0 +1,472 @@
|
||||
// Copyright 2021 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy of
|
||||
// the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
//go:build (!localtest || !tpm12) && cgo && !gofuzz
|
||||
// +build !localtest !tpm12
|
||||
// +build cgo
|
||||
// +build !gofuzz
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
)
|
||||
|
||||
func TestSimTPM20CertificationParametersRSA(t *testing.T) {
|
||||
sim, tpm := setupSimulatedTPM(t)
|
||||
defer sim.Close()
|
||||
testCertificationParameters(t, tpm, RSA)
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
akAttestParams := ak.AttestationParameters()
|
||||
pub, err := tpm2.DecodePublic(akAttestParams.Public)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pk, err := pub.Key()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hash, err := pub.NameAlg.Hash()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
correctOpts := VerifyOpts{
|
||||
Public: pk,
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
wrongKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wrongHash := crypto.SHA512_256
|
||||
if wrongHash == correctOpts.Hash {
|
||||
wrongHash = crypto.SHA256
|
||||
}
|
||||
|
||||
sk, err := tpm.NewKey(ak, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
skCertParams := sk.CertificationParameters()
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
p *CertificationParameters
|
||||
opts VerifyOpts
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "OK",
|
||||
p: &skCertParams,
|
||||
opts: correctOpts,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "wrong public key",
|
||||
p: &skCertParams,
|
||||
opts: VerifyOpts{
|
||||
Public: wrongKey.Public,
|
||||
Hash: correctOpts.Hash,
|
||||
},
|
||||
err: cmpopts.AnyError,
|
||||
},
|
||||
{
|
||||
name: "wrong hash function",
|
||||
p: &skCertParams,
|
||||
opts: VerifyOpts{
|
||||
Public: correctOpts.Public,
|
||||
Hash: wrongHash,
|
||||
},
|
||||
err: cmpopts.AnyError,
|
||||
},
|
||||
{
|
||||
name: "unavailable hash function",
|
||||
p: &skCertParams,
|
||||
opts: VerifyOpts{
|
||||
Public: correctOpts.Public,
|
||||
Hash: crypto.BLAKE2b_384,
|
||||
},
|
||||
err: cmpopts.AnyError,
|
||||
},
|
||||
{
|
||||
name: "modified Public",
|
||||
p: &CertificationParameters{
|
||||
Public: akAttestParams.Public,
|
||||
CreateAttestation: skCertParams.CreateAttestation,
|
||||
CreateSignature: skCertParams.CreateSignature,
|
||||
},
|
||||
opts: correctOpts,
|
||||
err: cmpopts.AnyError,
|
||||
},
|
||||
{
|
||||
name: "modified CreateAttestation",
|
||||
p: &CertificationParameters{
|
||||
Public: skCertParams.Public,
|
||||
CreateAttestation: akAttestParams.CreateAttestation,
|
||||
CreateSignature: skCertParams.CreateSignature,
|
||||
},
|
||||
opts: correctOpts,
|
||||
err: cmpopts.AnyError,
|
||||
},
|
||||
{
|
||||
name: "modified CreateSignature",
|
||||
p: &CertificationParameters{
|
||||
Public: skCertParams.Public,
|
||||
CreateAttestation: skCertParams.CreateAttestation,
|
||||
CreateSignature: akAttestParams.CreateSignature,
|
||||
},
|
||||
opts: correctOpts,
|
||||
err: cmpopts.AnyError,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.p.Verify(test.opts)
|
||||
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 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,14 +30,14 @@ import (
|
||||
// Ensure hashes are available.
|
||||
_ "crypto/sha256"
|
||||
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
)
|
||||
|
||||
// ReplayError describes the parsed events that failed to verify against
|
||||
// a particular PCR.
|
||||
type ReplayError struct {
|
||||
Events []Event
|
||||
Events []Event
|
||||
// InvalidPCRs reports the set of PCRs where the event log replay failed.
|
||||
InvalidPCRs []int
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
//
|
||||
// [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 []HashAlg
|
||||
|
||||
rawEvents []rawEvent
|
||||
rawEvents []rawEvent
|
||||
specIDEvent *specIDEvent
|
||||
}
|
||||
|
||||
func (e *EventLog) clone() *EventLog {
|
||||
@ -183,6 +176,11 @@ func (e *EventLog) clone() *EventLog {
|
||||
}
|
||||
copy(out.Algs, e.Algs)
|
||||
copy(out.rawEvents, e.rawEvents)
|
||||
if e.specIDEvent != nil {
|
||||
dupe := *e.specIDEvent
|
||||
out.specIDEvent = &dupe
|
||||
}
|
||||
|
||||
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
|
||||
// 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.
|
||||
//
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
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{}
|
||||
@ -360,17 +365,33 @@ func (a *AKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error
|
||||
}
|
||||
|
||||
sigHash.Reset()
|
||||
quotePCRs := make(map[int]struct{}, len(att.AttestedQuoteInfo.PCRSelection.PCRs))
|
||||
for _, index := range att.AttestedQuoteInfo.PCRSelection.PCRs {
|
||||
digest, ok := pcrByIndex[index]
|
||||
if !ok {
|
||||
return fmt.Errorf("quote was over PCR %d which wasn't provided", index)
|
||||
}
|
||||
quotePCRs[index] = struct{}{}
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 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
|
||||
// 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) {
|
||||
var (
|
||||
replay []byte
|
||||
@ -506,28 +527,21 @@ func ParseEventLog(measurementLog []byte) (*EventLog, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse first event: %v", err)
|
||||
}
|
||||
if e.typ == eventTypeNoAction {
|
||||
if e.typ == eventTypeNoAction && len(e.data) >= binary.Size(specIDEventHeader{}) {
|
||||
specID, err = parseSpecIDEvent(e.data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse spec ID event: %v", err)
|
||||
}
|
||||
for _, alg := range specID.algs {
|
||||
switch tpm2.Algorithm(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")
|
||||
el.Algs = append(el.Algs, HashAlg(alg.ID))
|
||||
}
|
||||
// Switch to parsing crypto agile events. Don't include this in the
|
||||
// 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.
|
||||
parseFn = parseRawEvent2
|
||||
el.specIDEvent = specID
|
||||
} else {
|
||||
el.Algs = []HashAlg{HashSHA1}
|
||||
el.rawEvents = append(el.rawEvents, e)
|
||||
@ -568,22 +582,24 @@ const (
|
||||
wantErrata = 0
|
||||
)
|
||||
|
||||
type specIDEventHeader struct {
|
||||
Signature [16]byte
|
||||
PlatformClass uint32
|
||||
VersionMinor uint8
|
||||
VersionMajor uint8
|
||||
Errata uint8
|
||||
UintnSize uint8
|
||||
NumAlgs uint32
|
||||
}
|
||||
|
||||
// parseSpecIDEvent parses a TCG_EfiSpecIDEventStruct structure from the reader.
|
||||
//
|
||||
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=18
|
||||
func parseSpecIDEvent(b []byte) (*specIDEvent, error) {
|
||||
r := bytes.NewReader(b)
|
||||
var header struct {
|
||||
Signature [16]byte
|
||||
PlatformClass uint32
|
||||
VersionMinor uint8
|
||||
VersionMajor uint8
|
||||
Errata uint8
|
||||
UintnSize uint8
|
||||
NumAlgs uint32
|
||||
}
|
||||
var header specIDEventHeader
|
||||
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
|
||||
return nil, fmt.Errorf("reading event header: %v", err)
|
||||
return nil, fmt.Errorf("reading event header: %w: %X", err, b)
|
||||
}
|
||||
if header.Signature != wantSignature {
|
||||
return nil, fmt.Errorf("invalid spec id signature: %x", header.Signature)
|
||||
@ -653,10 +669,7 @@ func (e *eventSizeErr) Error() string {
|
||||
func parseRawEvent(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
|
||||
var h rawEventHeader
|
||||
if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
|
||||
return event, err
|
||||
}
|
||||
if h.EventSize == 0 {
|
||||
return event, errors.New("event data size is 0")
|
||||
return event, fmt.Errorf("header deserialization error: %w", err)
|
||||
}
|
||||
if h.EventSize > uint32(r.Len()) {
|
||||
return event, &eventSizeErr{h.EventSize, r.Len()}
|
||||
@ -664,7 +677,7 @@ func parseRawEvent(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err er
|
||||
|
||||
data := make([]byte, int(h.EventSize))
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
return event, err
|
||||
return event, fmt.Errorf("reading data error: %w", err)
|
||||
}
|
||||
|
||||
digests := []digest{{hash: crypto.SHA1, data: h.Digest[:]}}
|
||||
@ -710,7 +723,7 @@ func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err e
|
||||
if alg.ID != algID {
|
||||
continue
|
||||
}
|
||||
if uint16(r.Len()) < alg.Size {
|
||||
if r.Len() < int(alg.Size) {
|
||||
return event, fmt.Errorf("reading digest: %v", io.ErrUnexpectedEOF)
|
||||
}
|
||||
digest.data = make([]byte, alg.Size)
|
||||
@ -730,9 +743,6 @@ func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err e
|
||||
if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
|
||||
return event, err
|
||||
}
|
||||
if eventSize == 0 {
|
||||
return event, errors.New("event data size is 0")
|
||||
}
|
||||
if eventSize > uint32(r.Len()) {
|
||||
return event, &eventSizeErr{eventSize, r.Len()}
|
||||
}
|
||||
@ -742,3 +752,73 @@ func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err e
|
||||
}
|
||||
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
|
||||
// the License.
|
||||
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package attest
|
||||
|
@ -15,11 +15,13 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"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.
|
||||
@ -54,7 +56,7 @@ func TestParseEventLogLinux(t *testing.T) {
|
||||
}
|
||||
|
||||
func testParseEventLog(t *testing.T, testdata string) {
|
||||
data, err := ioutil.ReadFile(testdata)
|
||||
data, err := os.ReadFile(testdata)
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
@ -68,7 +70,7 @@ func testParseEventLog(t *testing.T, testdata string) {
|
||||
}
|
||||
|
||||
func TestParseCryptoAgileEventLog(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("testdata/crypto_agile_eventlog")
|
||||
data, err := os.ReadFile("testdata/crypto_agile_eventlog")
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
@ -86,7 +88,7 @@ func TestEventLog(t *testing.T) {
|
||||
}
|
||||
|
||||
func testEventLog(t *testing.T, testdata string) {
|
||||
data, err := ioutil.ReadFile(testdata)
|
||||
data, err := os.ReadFile(testdata)
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
@ -149,6 +151,84 @@ func TestParseEventLogEventSizeTooLarge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEventLogEventSizeZero(t *testing.T) {
|
||||
data := []byte{
|
||||
// PCR index
|
||||
0x4, 0x0, 0x0, 0x0,
|
||||
|
||||
// type
|
||||
0xd, 0x0, 0x0, 0x0,
|
||||
|
||||
// Digest
|
||||
0x94, 0x2d, 0xb7, 0x4a, 0xa7, 0x37, 0x5b, 0x23, 0xea, 0x23,
|
||||
0x58, 0xeb, 0x3b, 0x31, 0x59, 0x88, 0x60, 0xf6, 0x90, 0x59,
|
||||
|
||||
// Event size (0 B)
|
||||
0x0, 0x0, 0x0, 0x0,
|
||||
|
||||
// no "event data"
|
||||
}
|
||||
|
||||
if _, err := parseRawEvent(bytes.NewBuffer(data), nil); err != nil {
|
||||
t.Fatalf("parsing event log: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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
|
||||
// (Section 9.4.5.1) the log will ...". Thus it is concluded other
|
||||
// than "EFI Specification ID" events are also valid as NO_ACTION events.
|
||||
//
|
||||
// Currently we just assume that such events will have Data shorter than
|
||||
// "EFI Specification ID" field.
|
||||
|
||||
data, err := os.ReadFile("testdata/short_no_action_eventlog")
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
if _, err := ParseEventLog(data); err != nil {
|
||||
t.Fatalf("parsing event log: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSpecIDEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -283,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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -295,3 +375,45 @@ func TestEBSVerifyWorkaround(t *testing.T) {
|
||||
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) {
|
||||
if !*testExamples {
|
||||
t.SkipNow()
|
||||
}
|
||||
ExampleAK()
|
||||
ExampleAK_credentialActivation()
|
||||
ExampleAK_credentialActivationWithEK()
|
||||
}
|
||||
|
||||
func TestExampleTPM(t *testing.T) {
|
||||
|
@ -2,14 +2,12 @@ package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/google/certificate-transparency-go/asn1"
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -37,10 +35,20 @@ var (
|
||||
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.
|
||||
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 (
|
||||
PrebootCert EventType = 0x00000000
|
||||
PostCode EventType = 0x00000001
|
||||
@ -79,6 +87,23 @@ const (
|
||||
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
|
||||
// successfully, however was missing the SignatureOwner GUID. This case is
|
||||
// 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) {
|
||||
// "The value associated with a UEFI specific platform event type MUST be in
|
||||
// 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)
|
||||
}
|
||||
if _, ok := eventTypeNames[EventType(et)]; !ok {
|
||||
@ -250,15 +275,32 @@ type UEFIVariableAuthority struct {
|
||||
// a UEFI variable authority.
|
||||
//
|
||||
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789
|
||||
func ParseUEFIVariableAuthority(r io.Reader) (UEFIVariableAuthority, error) {
|
||||
v, err := ParseUEFIVariableData(r)
|
||||
if err != nil {
|
||||
return UEFIVariableAuthority{}, err
|
||||
func ParseUEFIVariableAuthority(v UEFIVariableData) (UEFIVariableAuthority, error) {
|
||||
if v.Header.VariableName == shimLockGUID && (
|
||||
// Skip parsing new SBAT section logged by shim.
|
||||
// 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)
|
||||
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.
|
||||
// See section "31.4.1 Signature Database" in the specification for more information.
|
||||
type efiSignatureData struct {
|
||||
@ -405,14 +447,96 @@ func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
|
||||
} else {
|
||||
// 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.
|
||||
if _, isStructuralErr := err.(asn1.StructuralError); isStructuralErr {
|
||||
var err2 error
|
||||
cert, err2 = x509.ParseCertificate(b)
|
||||
if err2 == nil {
|
||||
certificates = append(certificates, *cert)
|
||||
err = ErrSigMissingGUID
|
||||
}
|
||||
var err2 error
|
||||
cert, err2 = x509.ParseCertificate(b)
|
||||
if err2 == nil {
|
||||
certificates = append(certificates, *cert)
|
||||
err = ErrSigMissingGUID
|
||||
}
|
||||
}
|
||||
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
|
||||
// the License.
|
||||
|
||||
// +build linux,!gofuzz,cgo
|
||||
//go:build linux && !gofuzz && cgo && tspi
|
||||
// +build linux,!gofuzz,cgo,tspi
|
||||
|
||||
package attest
|
||||
|
||||
@ -51,7 +52,7 @@ func (k *trousersKey12) close(tpm tpmBase) error {
|
||||
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)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *linuxTPM, got %T", tb)
|
||||
@ -64,7 +65,7 @@ func (k *trousersKey12) activateCredential(tb tpmBase, in EncryptedCredential) (
|
||||
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)
|
||||
if !ok {
|
||||
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 {
|
||||
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)
|
||||
if err != nil {
|
||||
@ -91,3 +95,7 @@ func (k *trousersKey12) attestationParameters() AttestationParameters {
|
||||
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
|
||||
// the License.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package attest
|
||||
@ -19,6 +20,7 @@ package attest
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
tpm1 "github.com/google/go-tpm/tpm"
|
||||
)
|
||||
|
||||
@ -47,7 +49,7 @@ func (k *windowsKey12) marshal() ([]byte, error) {
|
||||
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)
|
||||
if !ok {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
selectedPCRs := make([]int, 24)
|
||||
for pcr, _ := range selectedPCRs {
|
||||
selectedPCRs[pcr] = pcr
|
||||
}
|
||||
|
||||
sig, pcrc, err := tpm1.Quote(tpm, tpmKeyHnd, nonce, selectedPCRs[:], wellKnownAuth[:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Quote() failed: %v", err)
|
||||
@ -110,6 +107,9 @@ func (k *windowsKey12) attestationParameters() AttestationParameters {
|
||||
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.
|
||||
type windowsKey20 struct {
|
||||
@ -147,7 +147,7 @@ func (k *windowsKey20) marshal() ([]byte, error) {
|
||||
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)
|
||||
if !ok {
|
||||
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...))
|
||||
}
|
||||
|
||||
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)
|
||||
if !ok {
|
||||
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 {
|
||||
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 {
|
||||
@ -184,3 +184,31 @@ func (k *windowsKey20) attestationParameters() AttestationParameters {
|
||||
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
|
||||
// the License.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
tpmtbs "github.com/google/go-tpm/tpmutil/tbs"
|
||||
"golang.org/x/sys/windows"
|
||||
@ -369,8 +369,8 @@ func (h *winPCP) Close() error {
|
||||
return closeNCryptObject(h.hProv)
|
||||
}
|
||||
|
||||
// DeleteKey permanently removes the key with the given handle
|
||||
// from the system, and frees its handle.
|
||||
// DeleteKey permanently removes the key with the given handle from the system,
|
||||
// and frees its handle.
|
||||
func (h *winPCP) DeleteKey(kh uintptr) error {
|
||||
r, _, msg := nCryptDeleteKey.Call(kh, 0)
|
||||
if r != 0 {
|
||||
|
@ -16,10 +16,10 @@ package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"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
|
||||
// the execution of a binary after the separator.
|
||||
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
|
||||
// configuration of secure boot on a device. An error is returned if
|
||||
// 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.
|
||||
|
||||
var (
|
||||
out SecurebootState
|
||||
seenSeparator bool
|
||||
seenAuthority bool
|
||||
seenVars = map[string]bool{}
|
||||
out SecurebootState
|
||||
seenSeparator7 bool
|
||||
seenSeparator2 bool
|
||||
seenAuthority bool
|
||||
seenVars = map[string]bool{}
|
||||
driverSources [][]internal.EFIDevicePathElement
|
||||
)
|
||||
|
||||
for _, e := range events {
|
||||
if e.Index != 7 {
|
||||
if e.Index != 7 && e.Index != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -93,99 +113,168 @@ func ParseSecurebootState(events []Event) (*SecurebootState, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unrecognised event type: %v", err)
|
||||
}
|
||||
|
||||
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:
|
||||
if string(e.Data) == "UEFI Debug Mode" {
|
||||
return nil, errors.New("a UEFI debugger was present during boot")
|
||||
}
|
||||
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
|
||||
switch e.Index {
|
||||
case 7:
|
||||
switch et {
|
||||
case internal.Separator:
|
||||
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:
|
||||
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
|
||||
}
|
||||
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
|
||||
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
|
||||
}
|
||||
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.
|
||||
case internal.EFIAction:
|
||||
switch string(e.Data) {
|
||||
case "UEFI Debug Mode":
|
||||
return nil, errors.New("a UEFI debugger was present during boot")
|
||||
case "DMA Protection Disabled":
|
||||
if digestVerify != nil {
|
||||
digestVerify = e.digestEquals(e.Data[:len(e.Data)-1])
|
||||
return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.sequence, digestVerify)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed parsing EFI variable authority at event %d: %v", e.sequence, err)
|
||||
out.DMAProtectionDisabled = true
|
||||
default:
|
||||
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
|
||||
}
|
||||
}
|
||||
seenAuthority = true
|
||||
if digestVerify != nil {
|
||||
return nil, fmt.Errorf("invalid digest for authority on event %d: %v", e.sequence, digestVerify)
|
||||
}
|
||||
if !seenSeparator {
|
||||
out.PreSeparatorAuthority = append(out.PreSeparatorAuthority, a.Certs...)
|
||||
} else {
|
||||
out.PostSeparatorAuthority = append(out.PostSeparatorAuthority, a.Certs...)
|
||||
|
||||
case internal.EFIVariableDriverConfig:
|
||||
v, err := internal.ParseUEFIVariableData(bytes.NewReader(e.Data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing EFI variable at event %d: %v", e.sequence, err)
|
||||
}
|
||||
if _, seenBefore := seenVars[v.VarName()]; seenBefore {
|
||||
return nil, fmt.Errorf("duplicate EFI variable %q at event %d", v.VarName(), e.sequence)
|
||||
}
|
||||
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:
|
||||
return nil, fmt.Errorf("unexpected event type: %v", et)
|
||||
case 2:
|
||||
switch et {
|
||||
case internal.Separator:
|
||||
if seenSeparator2 {
|
||||
return nil, fmt.Errorf("duplicate separator at event %d", e.sequence)
|
||||
}
|
||||
seenSeparator2 = true
|
||||
if !bytes.Equal(e.Data, []byte{0, 0, 0, 0}) {
|
||||
return nil, fmt.Errorf("invalid separator data at event %d: %v", e.sequence, e.Data)
|
||||
}
|
||||
if digestVerify != nil {
|
||||
return nil, fmt.Errorf("invalid separator digest at event %d: %v", e.sequence, digestVerify)
|
||||
}
|
||||
|
||||
case internal.EFIBootServicesDriver:
|
||||
if !seenSeparator2 {
|
||||
imgLoad, err := internal.ParseEFIImageLoad(bytes.NewReader(e.Data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed parsing EFI image load at boot services driver event %d: %v", e.sequence, err)
|
||||
}
|
||||
dp, err := imgLoad.DevicePath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse device path for driver load event %d: %v", e.sequence, err)
|
||||
}
|
||||
driverSources = append(driverSources, dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute driver source hints based on the EFI device path observed in
|
||||
// EFI Boot-services driver-load events.
|
||||
sourceLoop:
|
||||
for _, source := range driverSources {
|
||||
// We consider a driver to have originated from PCI-MMIO if any number
|
||||
// of elements in the device path [1] were PCI devices, and are followed by
|
||||
// an element representing a "relative offset range" read.
|
||||
// In the wild, we have typically observed 4-tuple device paths for such
|
||||
// devices: ACPI device -> PCI device -> PCI device -> relative offset.
|
||||
//
|
||||
// [1]: See section 9 of the UEFI specification v2.6 or greater.
|
||||
var seenPCI bool
|
||||
for _, e := range source {
|
||||
// subtype 0x1 corresponds to a PCI device (See: 9.3.2.1)
|
||||
if e.Type == internal.HardwareDevice && e.Subtype == 0x1 {
|
||||
seenPCI = true
|
||||
}
|
||||
// subtype 0x8 corresponds to "relative offset range" (See: 9.3.6.8)
|
||||
if seenPCI && e.Type == internal.MediaDevice && e.Subtype == 0x8 {
|
||||
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, PciMmioSource)
|
||||
continue sourceLoop
|
||||
}
|
||||
}
|
||||
out.DriverLoadSourceHints = append(out.DriverLoadSourceHints, UnknownSource)
|
||||
}
|
||||
|
||||
if !out.Enabled {
|
||||
return &out, nil
|
||||
}
|
||||
|
@ -15,13 +15,14 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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 {
|
||||
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
|
||||
func TestSecureBootBug157(t *testing.T) {
|
||||
raw, err := ioutil.ReadFile("testdata/sb_cert_eventlog")
|
||||
raw, err := os.ReadFile("testdata/sb_cert_eventlog")
|
||||
if err != nil {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
@ -61,54 +62,97 @@ func TestSecureBootBug157(t *testing.T) {
|
||||
}
|
||||
|
||||
pcrs := []PCR{
|
||||
{'\x00', []byte("Q\xc3#\xde\f\fiOF\x01\xcd\xd0+\xebX\xff\x13b\x9ft"), '\x03'},
|
||||
{'\x01', []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'},
|
||||
{'\x03', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'},
|
||||
{'\x04', []byte("\xb7q\x00\x8d\x17<\x02+\xc1oKM\x1a\u007f\x8b\x99\xed\x88\xee\xb1"), '\x03'},
|
||||
{'\x05', []byte("\xd79j\xc6\xe8\x87\xda\"ޠ;@\x95/p\xb8\xdbҩ\x96"), '\x03'},
|
||||
{'\x06', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'},
|
||||
{'\a', []byte("E\xa8b\x1d4\xa5}\xf2\xb2\xe7\xf1L\x92\xb9\x9a\xc8\xde}X\x05"), '\x03'},
|
||||
{'\b', []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'},
|
||||
{'\n', []byte("\x82\x84\x10>\x06\xd4\x01\"\xbcd\xa0䡉\x1a\xf9\xec\xd4\\\xf6"), '\x03'},
|
||||
{'\v', []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'},
|
||||
{'\r', []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'},
|
||||
{'\x0f', []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'},
|
||||
{'\x11', []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'},
|
||||
{'\x13', []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'},
|
||||
{'\x15', []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'},
|
||||
{'\x17', []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), '\x03'},
|
||||
{'\x00', []byte("\xfc\xec\xb5j\xcc08b\xb3\x0e\xb3Bę\v\xebP\xb5ૉr$I\xc2٧?7\xb0\x19\xfe"), '\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\x04', []byte("\xa9)h\x80oy_\xa3D5\xd9\xf1\x18\x13hL\xa1\xe7\x05`w\xf7\x00\xbaI\xf2o\x99b\xf8m\x89"), '\x05'},
|
||||
{'\x05', []byte("̆\x18\xb7y2\xb4\xef\xda\x12\xccX\xba\xd9>\xcdѕ\x9d\xea)\xe5\xabyE%\xa6\x19\xf5\xba\xab\xee"), '\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\n', []byte("\xc3l\x9a\xb1\x10\x9b\xa0\x8a?dX!\x18\xf8G\x1a]i[\xc9#\xa0\xa2\xbd\x04]\xb1K\x97OB9"), '\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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'},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', false},
|
||||
{'\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', 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', 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', false},
|
||||
{'\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', 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', 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', 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', 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', false},
|
||||
{'\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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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)
|
||||
@ -120,7 +164,52 @@ func TestSecureBootBug157(t *testing.T) {
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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/short_no_action_eventlog
vendored
Normal file
BIN
attest/testdata/short_no_action_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.
244
attest/tpm.go
244
attest/tpm.go
@ -18,17 +18,18 @@ import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/certificate-transparency-go/asn1"
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
"go.uber.org/multierr"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -37,18 +38,21 @@ const (
|
||||
tpmPtFwVersion1 = 0x00000100 + 11 // PT_FIXED + offset of 11
|
||||
|
||||
// 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.
|
||||
commonSrkEquivalentHandle = 0x81000001
|
||||
commonEkEquivalentHandle = 0x81010001
|
||||
commonRSAEkEquivalentHandle = 0x81010001
|
||||
commonECCEkEquivalentHandle = 0x81010002
|
||||
)
|
||||
|
||||
var (
|
||||
akTemplate = tpm2.Public{
|
||||
akTemplateRSA = tpm2.Public{
|
||||
Type: tpm2.AlgRSA,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagSignerDefault,
|
||||
Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA,
|
||||
RSAParameters: &tpm2.RSAParams{
|
||||
Sign: &tpm2.SigScheme{
|
||||
Alg: tpm2.AlgRSASSA,
|
||||
@ -57,7 +61,23 @@ var (
|
||||
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,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagStorageDefault | tpm2.FlagNoDA,
|
||||
@ -71,9 +91,26 @@ var (
|
||||
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
|
||||
defaultEKTemplate = tpm2.Public{
|
||||
defaultRSAEKTemplate = tpm2.Public{
|
||||
Type: tpm2.AlgRSA,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
|
||||
@ -96,6 +133,51 @@ var (
|
||||
ModulusRaw: make([]byte, 256),
|
||||
},
|
||||
}
|
||||
defaultECCEKTemplate = tpm2.Public{
|
||||
Type: tpm2.AlgECC,
|
||||
NameAlg: tpm2.AlgSHA256,
|
||||
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
|
||||
tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt,
|
||||
AuthPolicy: []byte{
|
||||
0x83, 0x71, 0x97, 0x67, 0x44, 0x84,
|
||||
0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D,
|
||||
0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52,
|
||||
0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64,
|
||||
0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14,
|
||||
0x69, 0xAA,
|
||||
},
|
||||
ECCParameters: &tpm2.ECCParams{
|
||||
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),
|
||||
},
|
||||
},
|
||||
}
|
||||
// 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 {
|
||||
@ -179,12 +261,12 @@ func ParseEKCertificate(ekCert []byte) (*x509.Certificate, error) {
|
||||
var cert struct {
|
||||
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)
|
||||
}
|
||||
|
||||
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 c, nil
|
||||
@ -200,23 +282,23 @@ func intelEKURL(ekPub *rsa.PublicKey) string {
|
||||
pubHash.Write(ekPub.N.Bytes())
|
||||
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) {
|
||||
ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, tpm2.HandleOwner, "", 0)
|
||||
func readEKCertFromNVRAM20(tpm io.ReadWriter, nvramCertIndex tpmutil.Handle) (*x509.Certificate, error) {
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("reading EK cert: %v", err)
|
||||
}
|
||||
return ParseEKCertificate(ekCert)
|
||||
}
|
||||
|
||||
func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte) (*Quote, error) {
|
||||
sel := tpm2.PCRSelection{Hash: hashAlg}
|
||||
numPCRs := 24
|
||||
for pcr := 0; pcr < numPCRs; pcr++ {
|
||||
sel.PCRs = append(sel.PCRs, pcr)
|
||||
}
|
||||
func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte, selectedPCRs []int) (*Quote, error) {
|
||||
sel := tpm2.PCRSelection{Hash: hashAlg,
|
||||
PCRs: selectedPCRs}
|
||||
|
||||
quote, sig, err := tpm2.Quote(tpm, akHandle, "", "", nonce, sel, tpm2.AlgNull)
|
||||
if err != nil {
|
||||
@ -231,12 +313,37 @@ func quote20(tpm io.ReadWriter, akHandle tpmutil.Handle, hashAlg tpm2.Algorithm,
|
||||
}, 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) {
|
||||
numPCRs := 24
|
||||
out := map[uint32][]byte{}
|
||||
|
||||
// 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.
|
||||
for i := 0; i < numPCRs; i++ {
|
||||
// Build a selection structure, specifying all PCRs we do
|
||||
@ -274,15 +381,22 @@ type tpmBase interface {
|
||||
close() error
|
||||
tpmVersion() TPMVersion
|
||||
eks() ([]EK, error)
|
||||
ekCertificates() ([]EK, error)
|
||||
info() (*TPMInfo, error)
|
||||
pcrbanks() ([]HashAlg, error)
|
||||
|
||||
loadAK(opaqueBlob []byte) (*AK, error)
|
||||
loadAKWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*AK, error)
|
||||
newAK(opts *AKConfig) (*AK, error)
|
||||
loadKey(opaqueBlob []byte) (*Key, error)
|
||||
loadKeyWithParent(opaqueBlob []byte, parent ParentKeyConfig) (*Key, error)
|
||||
newKey(ak *AK, opts *KeyConfig) (*Key, error)
|
||||
newKeyCertifiedByKey(ck certifyingKey, opts *KeyConfig) (*Key, error)
|
||||
pcrs(alg HashAlg) ([]PCR, 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 {
|
||||
// tpm refers to a concrete implementation of TPM logic, based on the current
|
||||
// platform and TPM version.
|
||||
@ -295,10 +409,20 @@ func (t *TPM) Close() error {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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.
|
||||
func (t *TPM) Info() (*TPMInfo, error) {
|
||||
return t.tpm.info()
|
||||
@ -306,12 +430,18 @@ func (t *TPM) Info() (*TPMInfo, error) {
|
||||
|
||||
// LoadAK loads a previously-created ak into the TPM for use.
|
||||
// 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.
|
||||
func (t *TPM) LoadAK(opaqueBlob []byte) (*AK, error) {
|
||||
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.
|
||||
//
|
||||
// This is a low-level API. Consumers seeking to attest the state of the
|
||||
@ -335,6 +465,42 @@ func (t *TPM) NewAK(opts *AKConfig) (*AK, error) {
|
||||
return t.tpm.newAK(opts)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if opts == nil {
|
||||
opts = defaultConfig
|
||||
}
|
||||
if opts.Algorithm == "" && opts.Size == 0 {
|
||||
opts = defaultConfig
|
||||
}
|
||||
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.
|
||||
// A key loaded via this function needs to be closed with .Close().
|
||||
// Only blobs generated by calling Key.Marshal() are valid parameters
|
||||
// to this function.
|
||||
func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) {
|
||||
return t.tpm.loadKey(opaqueBlob)
|
||||
}
|
||||
|
||||
// PCRs returns the present value of Platform Configuration Registers with
|
||||
// the given digest algorithm.
|
||||
//
|
||||
@ -344,15 +510,35 @@ func (t *TPM) PCRs(alg HashAlg) ([]PCR, error) {
|
||||
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) {
|
||||
pcrs, err := t.PCRs(alg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read %v PCRs: %v", alg, err)
|
||||
}
|
||||
|
||||
quote, err := ak.Quote(t, nonce, alg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to quote using %v: %v", alg, err)
|
||||
}
|
||||
|
||||
// Make sure that the pcrs and quote values are consistent. See details in Section 17.6.2 of
|
||||
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_TPM2_r1p59_Part1_Architecture_pub.pdf
|
||||
pub, err := ParseAKPublic(t.Version(), ak.AttestationParameters().Public)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse AK public: %v", err)
|
||||
}
|
||||
if err := pub.Verify(*quote, pcrs, nonce); err != nil {
|
||||
return nil, nil, fmt.Errorf("local quote verification failed: %v", err)
|
||||
}
|
||||
|
||||
return quote, pcrs, nil
|
||||
}
|
||||
|
||||
@ -363,9 +549,9 @@ func (t *TPM) attestPlatform(ak *AK, nonce []byte, eventLog []byte) (*PlatformPa
|
||||
EventLog: eventLog,
|
||||
}
|
||||
|
||||
algs := []HashAlg{HashSHA1}
|
||||
if t.Version() == TPMVersion20 {
|
||||
algs = []HashAlg{HashSHA1, HashSHA256}
|
||||
algs, err := t.PCRBanks()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get PCR banks: %w", err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
|
@ -12,17 +12,18 @@
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
// +build linux,!gofuzz,cgo
|
||||
//go:build linux && !gofuzz && cgo && tspi
|
||||
// +build linux,!gofuzz,cgo,tspi
|
||||
|
||||
package attest
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/google/certificate-transparency-go/x509"
|
||||
"github.com/google/go-tspi/attestation"
|
||||
"github.com/google/go-tspi/tspi"
|
||||
"github.com/google/go-tspi/tspiconst"
|
||||
@ -93,7 +94,7 @@ func readEKCertFromNVRAM12(ctx *tspi.Context) (*x509.Certificate, error) {
|
||||
return ParseEKCertificate(ekCert)
|
||||
}
|
||||
|
||||
func (t *trousersTPM) eks() ([]EK, error) {
|
||||
func (t *trousersTPM) ekCertificates() ([]EK, error) {
|
||||
cert, err := readEKCertFromNVRAM12(t.ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err)
|
||||
@ -103,6 +104,26 @@ func (t *trousersTPM) eks() ([]EK, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *trousersTPM) eks() ([]EK, error) {
|
||||
return t.ekCertificates()
|
||||
}
|
||||
|
||||
func (t *trousersTPM) newKey(*AK, *KeyConfig) (*Key, error) {
|
||||
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) {
|
||||
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) {
|
||||
pub, blob, err := attestation.CreateAIK(t.ctx)
|
||||
if err != nil {
|
||||
@ -123,6 +144,10 @@ func (t *trousersTPM) loadAK(opaqueBlob []byte) (*AK, error) {
|
||||
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
|
||||
func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
|
||||
tpm := ctx.GetTPM()
|
||||
@ -138,6 +163,10 @@ func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
|
||||
return PCRs, nil
|
||||
}
|
||||
|
||||
func (t *trousersTPM) pcrbanks() ([]HashAlg, error) {
|
||||
return []HashAlg{HashSHA1}, nil
|
||||
}
|
||||
|
||||
func (t *trousersTPM) pcrs(alg HashAlg) ([]PCR, error) {
|
||||
if alg != HashSHA1 {
|
||||
return nil, fmt.Errorf("non-SHA1 algorithm %v is not supported on TPM 1.2", alg)
|
||||
@ -160,5 +189,5 @@ func (t *trousersTPM) pcrs(alg HashAlg) ([]PCR, 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
|
||||
// the License.
|
||||
|
||||
//go:build linux && !gofuzz
|
||||
// +build linux,!gofuzz
|
||||
|
||||
package attest
|
||||
@ -20,12 +21,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,7 +48,7 @@ func InjectSimulatedTPMForTest(rwc io.ReadWriteCloser) *TPM {
|
||||
func probeSystemTPMs() ([]probedTPM, error) {
|
||||
var tpms []probedTPM
|
||||
|
||||
tpmDevs, err := ioutil.ReadDir(tpmRoot)
|
||||
tpmDevs, err := os.ReadDir(tpmRoot)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
@ -81,7 +81,7 @@ type linuxCmdChannel struct {
|
||||
|
||||
// MeasurementLog implements CommandChannelTPM20.
|
||||
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) {
|
||||
@ -97,7 +97,7 @@ func openTPM(tpm probedTPM) (*TPM, error) {
|
||||
// If the TPM has a kernel-provided resource manager, we should
|
||||
// use that instead of communicating directly.
|
||||
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 !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
|
@ -12,6 +12,7 @@
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
//go:build gofuzz || (!linux && !windows)
|
||||
// +build gofuzz !linux,!windows
|
||||
|
||||
package attest
|
||||
|
@ -8,18 +8,22 @@ import (
|
||||
"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-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq8zyTXCjVALZzjS8wgNH
|
||||
nAVdt4ZGM3N450xOnLplx/RbCVwXyu83SWh0B3Ka+92aocqcHzo+j6e6Urppre/I
|
||||
+7VVKTdUAr8t5gxgSLGvo+ev+zv70GF4DmJthb8JNheHCmk3RnoSFs5TnDuSdvGb
|
||||
KcSzas0186LQyxvwfFjTxLweGrZKh/CTewD0/f5ozXmbTtJpl+qYrMi9GJamGlg6
|
||||
N6EsWKh1xos8J/cEmS2vbyCGGADyBwRV8Zkto5EU1HJaEli10HVZf0D06vuKzzxM
|
||||
+6W7LzGqzAPeaWvHi07ezShqdr5q5y1KKhFJcy8HOpwN8iFfIw70y3FtMlrMprrU
|
||||
twIDAQAB
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwyDi8kSoYBqs8+AdJsZl
|
||||
JJk1Vi3h2hl+nn8HbEaWE8+2U+mOwsOG/B0TPyyMbMM4tzLwsgi9g4qHej5bvD4d
|
||||
QIToNcfIkGocBbTS0w/b68HbrZUPprFlvUtqhkYDFGFkwMT1nUiQEe8fko3upukA
|
||||
YfPTdeVkYnMVHvYiJSCYvhpKsB3AoSInxgn9rOsRWvQI1Gk6b0mRl3RpWwwSvBih
|
||||
/3EgpzN7L7XxlR2Lt/CU1bVUwRyVI7MHKf5keH0KE7nmMEiNq039hmNKUnDscvzF
|
||||
pE3GeajzKTjdgZfina6Dn1tMoPXeJ8lSLCPFThws5XhZUlEYvURwsYGA7veK5CZ7
|
||||
zQIDAQAB
|
||||
-----END PUBLIC KEY-----`)
|
||||
|
||||
func mustParseRSAKey(data string) *rsa.PublicKey {
|
||||
@ -46,7 +50,7 @@ func parseRSAKey(data string) (*rsa.PublicKey, error) {
|
||||
}
|
||||
|
||||
func TestIntelEKURL(t *testing.T) {
|
||||
want := "https://ekop.intel.com/ekcertservice/7YtWV2nT3LpvSCfJt7ENIznN1R1jYkj_3S6mez3yyzg="
|
||||
want := "https://ekop.intel.com/ekcertservice/WVEG2rRwkQ7m3RpXlUphgo6Y2HLxl18h6ZZkkOAdnBE%3D"
|
||||
got := intelEKURL(testRSAKey)
|
||||
if 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
|
||||
// the License.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package attest
|
||||
@ -48,7 +49,7 @@ func probeSystemTPMs() ([]probedTPM, error) {
|
||||
tbs = windows.MustLoadDLL("Tbs.dll")
|
||||
tbsGetDeviceInfo = tbs.MustFindProc("Tbsi_GetDeviceInfo")
|
||||
}
|
||||
|
||||
|
||||
// Windows systems appear to only support a single abstracted TPM.
|
||||
// If we fail to initialize the Platform Crypto Provider, we assume
|
||||
// a TPM is not present.
|
||||
@ -151,6 +152,18 @@ func (t *windowsTPM) info() (*TPMInfo, error) {
|
||||
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) {
|
||||
ekCerts, err := t.pcp.EKCerts()
|
||||
if err != nil {
|
||||
@ -324,6 +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) {
|
||||
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) {
|
||||
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) {
|
||||
numPCRs := 24
|
||||
out := map[uint32][]byte{}
|
||||
@ -343,6 +376,23 @@ func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) {
|
||||
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) {
|
||||
var PCRs map[uint32][]byte
|
||||
|
||||
|
@ -146,13 +146,25 @@ const (
|
||||
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
|
||||
// bootup of Microsoft Windows.
|
||||
type WinEvents struct {
|
||||
// ColdBoot is set to true if the system was not resuming from hibernation.
|
||||
ColdBoot bool
|
||||
// BootCount contains the value of the monotonic boot counter. This
|
||||
// value is not set for TPM 1.2 devices and some TPMs with buggy
|
||||
// implementations of monotonic counters.
|
||||
BootCount int
|
||||
BootCount uint64
|
||||
// LoadedModules contains authenticode hashes for binaries which
|
||||
// were loaded during boot.
|
||||
LoadedModules map[string]WinModuleLoad
|
||||
@ -167,19 +179,16 @@ type WinEvents struct {
|
||||
KernelDebugEnabled bool
|
||||
// DEPEnabled is true if NX (Data Execution Prevention) was consistently
|
||||
// reported as enabled.
|
||||
DEPEnabled bool
|
||||
DEPEnabled Ternary
|
||||
// CodeIntegrityEnabled is true if code integrity was consistently
|
||||
// reported as enabled.
|
||||
CodeIntegrityEnabled bool
|
||||
CodeIntegrityEnabled Ternary
|
||||
// TestSigningEnabled is true if test-mode signature verification was
|
||||
// ever reported as enabled.
|
||||
TestSigningEnabled bool
|
||||
// BitlockerUnlocks reports the bitlocker status for every instance of
|
||||
// a disk unlock, where bitlocker was used to secure the disk.
|
||||
BitlockerUnlocks []BitlockerStatus
|
||||
|
||||
seenDep bool
|
||||
seenCodeIntegrity bool
|
||||
}
|
||||
|
||||
// WinModuleLoad describes a module which was loaded while
|
||||
@ -263,7 +272,7 @@ func ParseWinEvents(events []Event) (*WinEvents, error) {
|
||||
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
|
||||
}
|
||||
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 {
|
||||
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
|
||||
@ -294,7 +303,7 @@ func ParseWinEvents(events []Event) (*WinEvents, error) {
|
||||
return nil, fmt.Errorf("invalid tagged event structure at event %d: %w", e.sequence, err)
|
||||
}
|
||||
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 {
|
||||
return nil, fmt.Errorf("invalid SIPA events in event %d: %w", e.sequence, err)
|
||||
@ -344,8 +353,11 @@ func (w *WinEvents) readBooleanInt64Event(header microsoftEventHeader, r *bytes.
|
||||
// Boolean signals that latch off if the are ever false (ie: attributes
|
||||
// that represent a stronger security state when set).
|
||||
case dataExecutionPrevention:
|
||||
w.DEPEnabled = isSet && !(w.DEPEnabled != isSet && w.seenDep)
|
||||
w.seenDep = true
|
||||
if isSet && w.DEPEnabled == TernaryUnknown {
|
||||
w.DEPEnabled = TernaryTrue
|
||||
} else if !isSet {
|
||||
w.DEPEnabled = TernaryFalse
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -373,39 +385,66 @@ func (w *WinEvents) readBooleanByteEvent(header microsoftEventHeader, r *bytes.R
|
||||
// Boolean signals that latch off if the are ever false (ie: attributes
|
||||
// that represent a stronger security state when set).
|
||||
case codeIntegrity:
|
||||
w.CodeIntegrityEnabled = isSet && !(w.CodeIntegrityEnabled != isSet && w.seenCodeIntegrity)
|
||||
w.seenCodeIntegrity = true
|
||||
if isSet && w.CodeIntegrityEnabled == TernaryUnknown {
|
||||
w.CodeIntegrityEnabled = TernaryTrue
|
||||
} else if !isSet {
|
||||
w.CodeIntegrityEnabled = TernaryFalse
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WinEvents) readUint(header microsoftEventHeader, r io.Reader) (uint64, error) {
|
||||
if header.Size > 8 {
|
||||
return 0, fmt.Errorf("integer too large (%d bytes)", header.Size)
|
||||
func (w *WinEvents) readUint32(header microsoftEventHeader, r io.Reader) (uint32, error) {
|
||||
if header.Size != 4 {
|
||||
return 0, fmt.Errorf("integer size not uint32 (%d bytes)", header.Size)
|
||||
}
|
||||
|
||||
data := make([]uint8, header.Size)
|
||||
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)
|
||||
if n <= 0 {
|
||||
return 0, fmt.Errorf("reading u%d: invalid varint", header.Size<<8)
|
||||
i := binary.LittleEndian.Uint32(data)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
w.BootCount = int(i)
|
||||
w.BootCount = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WinEvents) readTransferControl(header microsoftEventHeader, r *bytes.Reader) error {
|
||||
i, err := w.readUint32(header, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("transfer control: %v", err)
|
||||
}
|
||||
|
||||
// A transferControl event with a value of 1 indicates that bootmngr
|
||||
// launched WinLoad. A different (unknown) value is set if WinResume
|
||||
// is launched.
|
||||
w.ColdBoot = i == 0x1
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -445,7 +484,7 @@ func (w *WinEvents) parseImageValidated(header microsoftEventHeader, r io.Reader
|
||||
}
|
||||
|
||||
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 {
|
||||
return 0, fmt.Errorf("hash algorithm ID: %v", err)
|
||||
}
|
||||
@ -550,7 +589,7 @@ func (w *WinEvents) readLoadedModuleAggregation(rdr *bytes.Reader, header micros
|
||||
if imgSize != 0 {
|
||||
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
|
||||
}
|
||||
case hashAlgorithmID:
|
||||
@ -561,7 +600,7 @@ func (w *WinEvents) readLoadedModuleAggregation(rdr *bytes.Reader, header micros
|
||||
return err
|
||||
}
|
||||
case imageValidated:
|
||||
if imgValidated == true {
|
||||
if imgValidated {
|
||||
return errors.New("duplicate image validated field in LMA event")
|
||||
}
|
||||
if imgValidated, err = w.parseImageValidated(h, r); err != nil {
|
||||
@ -639,7 +678,7 @@ func (w *WinEvents) parseUTF16(header microsoftEventHeader, r io.Reader) (string
|
||||
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 (
|
||||
r = &io.LimitedReader{R: rdr, N: int64(header.Size)}
|
||||
driverName string
|
||||
@ -659,6 +698,11 @@ func (w *WinEvents) readELAMAggregation(rdr *bytes.Reader, header microsoftEvent
|
||||
|
||||
var err error
|
||||
switch h.Type {
|
||||
case elamAggregation:
|
||||
w.readELAMAggregation(r, h)
|
||||
if r.N == 0 {
|
||||
return nil
|
||||
}
|
||||
case elamKeyname:
|
||||
if driverName != "" {
|
||||
return errors.New("duplicate driver name in ELAM aggregation event")
|
||||
@ -721,6 +765,8 @@ func (w *WinEvents) readSIPAEvent(r *bytes.Reader, pcr int) error {
|
||||
return w.readBootCounter(header, r)
|
||||
case bitlockerUnlock:
|
||||
return w.readBitlockerUnlock(header, r, pcr)
|
||||
case transferControl:
|
||||
return w.readTransferControl(header, r)
|
||||
|
||||
case osKernelDebug, codeIntegrity, bootDebugging, testSigning: // Parse boolean values.
|
||||
return w.readBooleanByteEvent(header, r)
|
||||
|
@ -16,7 +16,7 @@ package attest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -25,14 +25,16 @@ import (
|
||||
|
||||
func TestParseWinEvents(t *testing.T) {
|
||||
want := &WinEvents{
|
||||
ColdBoot: true,
|
||||
BootCount: 4,
|
||||
DEPEnabled: true,
|
||||
CodeIntegrityEnabled: true,
|
||||
DEPEnabled: TernaryTrue,
|
||||
CodeIntegrityEnabled: TernaryTrue,
|
||||
BitlockerUnlocks: []BitlockerStatus{0, 0},
|
||||
LoadedModules: map[string]WinModuleLoad{
|
||||
"0fdce7d71936f79445e7d2c84cbeb97c948d3730e0b839166b0a4e625c2d4547": WinModuleLoad{
|
||||
"0fdce7d71936f79445e7d2c84cbeb97c948d3730e0b839166b0a4e625c2d4547": {
|
||||
FilePath: `\Windows\System32\drivers\vioscsi.sys`,
|
||||
ImageBase: []uint64{81416192},
|
||||
ImageSize: uint64(86016),
|
||||
HashAlgorithm: WinAlgSHA256,
|
||||
ImageValidated: true,
|
||||
AuthorityIssuer: "Microsoft Windows Third Party Component CA 2014",
|
||||
@ -47,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},
|
||||
},
|
||||
"055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": WinModuleLoad{
|
||||
"055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": {
|
||||
FilePath: `\Windows\System32\Drivers\ksecpkg.sys`,
|
||||
ImageBase: []uint64{82952192},
|
||||
ImageSize: uint64(204800),
|
||||
HashAlgorithm: WinAlgSHA256,
|
||||
ImageValidated: true,
|
||||
AuthorityIssuer: "Microsoft Windows Production PCA 2011",
|
||||
@ -64,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},
|
||||
},
|
||||
"2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": WinModuleLoad{
|
||||
"2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": {
|
||||
FilePath: `\Windows\System32\drivers\volmgrx.sys`,
|
||||
ImageBase: []uint64{80875520},
|
||||
ImageSize: uint64(405504),
|
||||
HashAlgorithm: WinAlgSHA256,
|
||||
ImageValidated: true,
|
||||
AuthorityIssuer: "Microsoft Windows Production PCA 2011",
|
||||
@ -83,11 +87,11 @@ func TestParseWinEvents(t *testing.T) {
|
||||
},
|
||||
},
|
||||
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 {
|
||||
t.Fatalf("reading test data: %v", err)
|
||||
}
|
||||
@ -117,7 +121,7 @@ func TestParseWinEvents(t *testing.T) {
|
||||
"055a36a9921b98cc04042ca95249c7eca655536868dafcec7508947ebe5e71f4": true,
|
||||
"2bedd1589410b6fa13c82f35db735025b6a160595922750248771f5abd0fee58": true,
|
||||
}
|
||||
for k, _ := range winState.LoadedModules {
|
||||
for k := range winState.LoadedModules {
|
||||
if _, keep := keep[k]; !keep {
|
||||
delete(winState.LoadedModules, k)
|
||||
}
|
||||
|
@ -15,22 +15,68 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/asn1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/google/go-tpm/tpm2"
|
||||
"github.com/google/go-tpm/legacy/tpm2"
|
||||
"github.com/google/go-tpm/tpmutil"
|
||||
)
|
||||
|
||||
// wrappedTPM20 interfaces with a TPM 2.0 command channel.
|
||||
type wrappedTPM20 struct {
|
||||
interf TPMInterface
|
||||
rwc CommandChannelTPM20
|
||||
interf TPMInterface
|
||||
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 {
|
||||
return TPMVersion20
|
||||
@ -61,43 +107,105 @@ func (t *wrappedTPM20) info() (*TPMInfo, error) {
|
||||
return &tInfo, nil
|
||||
}
|
||||
|
||||
// Return value: handle, whether we generated a new one, error
|
||||
func (t *wrappedTPM20) getPrimaryKeyHandle(pHnd tpmutil.Handle) (tpmutil.Handle, bool, error) {
|
||||
_, _, _, err := tpm2.ReadPublic(t.rwc, pHnd)
|
||||
if err == nil {
|
||||
// Found the persistent handle, assume it's the key we want.
|
||||
return pHnd, false, nil
|
||||
// Return value: handle, whether we generated a new one, error.
|
||||
func (t *wrappedTPM20) getEndorsementKeyHandle(ek *EK) (tpmutil.Handle, bool, error) {
|
||||
var ekHandle tpmutil.Handle
|
||||
var ekTemplate tpm2.Public
|
||||
|
||||
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
|
||||
switch pHnd {
|
||||
case commonSrkEquivalentHandle:
|
||||
keyHnd, _, err = tpm2.CreatePrimary(t.rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", defaultSRKTemplate)
|
||||
case commonEkEquivalentHandle:
|
||||
keyHnd, _, err = tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
|
||||
_, _, _, err := tpm2.ReadPublic(t.rwc, ekHandle)
|
||||
if err == nil {
|
||||
// Found the persistent handle, assume it's the key we want.
|
||||
return ekHandle, false, nil
|
||||
}
|
||||
rerr := err // Preserve this failure for later logging, if needed
|
||||
|
||||
keyHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", ekTemplate)
|
||||
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)
|
||||
|
||||
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, pHnd)
|
||||
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, keyHnd, ekHandle)
|
||||
if err != nil {
|
||||
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) {
|
||||
if cert, err := readEKCertFromNVRAM20(t.rwc); err == nil {
|
||||
if cert, err := readEKCertFromNVRAM20(t.rwc, nvramRSACertIndex); err == nil {
|
||||
return []EK{
|
||||
{Public: crypto.PublicKey(cert.PublicKey), Certificate: cert},
|
||||
{Public: crypto.PublicKey(cert.PublicKey), Certificate: cert, handle: commonRSAEkEquivalentHandle},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("EK CreatePrimary failed: %v", err)
|
||||
}
|
||||
@ -110,23 +218,50 @@ func (t *wrappedTPM20) eks() ([]EK, error) {
|
||||
if pub.RSAParameters == nil {
|
||||
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{
|
||||
{
|
||||
Public: &rsa.PublicKey{
|
||||
E: int(pub.RSAParameters.Exponent()),
|
||||
N: pub.RSAParameters.Modulus(),
|
||||
},
|
||||
Public: ekPub,
|
||||
CertificateURL: certificateURL,
|
||||
handle: commonRSAEkEquivalentHandle,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *wrappedTPM20) newAK(opts *AKConfig) (*AK, error) {
|
||||
// TODO(jsonp): Abstract choice of hierarchy & parent.
|
||||
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle)
|
||||
var parent ParentKeyConfig
|
||||
if opts != nil && opts.Parent != nil {
|
||||
parent = *opts.Parent
|
||||
} else {
|
||||
parent = defaultParentConfig
|
||||
}
|
||||
srk, _, err := t.getStorageRootKeyHandle(parent)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CreateKeyEx() failed: %v", err)
|
||||
@ -143,36 +278,192 @@ func (t *wrappedTPM20) newAK(opts *AKConfig) (*AK, error) {
|
||||
}()
|
||||
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("CertifyCreation failed: %v", err)
|
||||
}
|
||||
// Pack the raw structure into a TPMU_SIGNATURE.
|
||||
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: newWrappedKey20(keyHandle, blob, pub, creationData, attestation, signature)}, nil
|
||||
return &AK{ak: newWrappedAK20(keyHandle, blob, pub, creationData, attestation, sig)}, nil
|
||||
}
|
||||
|
||||
func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) {
|
||||
sKey, err := deserializeKey(opaqueBlob, TPMVersion20)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("deserializeKey() failed: %v", err)
|
||||
}
|
||||
if sKey.Encoding != keyEncodingEncrypted {
|
||||
return nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding)
|
||||
func (t *wrappedTPM20) newKey(ak *AK, opts *KeyConfig) (*Key, error) {
|
||||
k, ok := ak.ak.(*wrappedKey20)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *wrappedKey20, got: %T", k)
|
||||
}
|
||||
|
||||
srk, _, err := t.getPrimaryKeyHandle(commonSrkEquivalentHandle)
|
||||
kAlg, err := k.algorithm()
|
||||
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)
|
||||
}
|
||||
|
||||
keyHandle, _, err := tpm2.Load(t.rwc, parent, "", pub, blob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Load() failed: %v", err)
|
||||
}
|
||||
// If any errors occur, free the handle.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tpm2.FlushContext(t.rwc, keyHandle)
|
||||
}
|
||||
}()
|
||||
|
||||
// Certify application key by AK
|
||||
certifyOpts := CertifyOpts{QualifyingData: opts.QualifyingData}
|
||||
cp, err := certifyByKey(t, keyHandle, ck, certifyOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("certifyByKey() failed: %v", err)
|
||||
}
|
||||
if !bytes.Equal(pub, cp.Public) {
|
||||
return nil, fmt.Errorf("certified incorrect key, expected: %v, certified: %v", pub, cp.Public)
|
||||
}
|
||||
|
||||
// Pack the raw structure into a TPMU_SIGNATURE.
|
||||
tpmPub, err := tpm2.DecodePublic(pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode public key: %v", err)
|
||||
}
|
||||
pubKey, err := tpmPub.Key()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("access public key: %v", err)
|
||||
}
|
||||
return &Key{key: newWrappedKey20(keyHandle, blob, pub, creationData, cp.CreateAttestation, cp.CreateSignature), pub: pubKey, tpm: t}, nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("deserializeKey() failed: %v", err)
|
||||
}
|
||||
if sKey.Encoding != keyEncodingEncrypted {
|
||||
return 0, nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding)
|
||||
}
|
||||
|
||||
srk, _, err := t.getStorageRootKeyHandle(parent)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("failed to get SRK handle: %v", err)
|
||||
}
|
||||
var hnd tpmutil.Handle
|
||||
if hnd, _, err = tpm2.Load(t.rwc, srk, "", sKey.Public, sKey.Blob); err != nil {
|
||||
return nil, fmt.Errorf("Load() failed: %v", err)
|
||||
return 0, nil, fmt.Errorf("Load() failed: %v", err)
|
||||
}
|
||||
return &AK{ak: newWrappedKey20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil
|
||||
return hnd, sKey, nil
|
||||
}
|
||||
|
||||
func (t *wrappedTPM20) loadAK(opaqueBlob []byte) (*AK, error) {
|
||||
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 {
|
||||
return nil, fmt.Errorf("cannot load attestation key: %v", err)
|
||||
}
|
||||
return &AK{ak: newWrappedAK20(hnd, sKey.Blob, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil
|
||||
}
|
||||
|
||||
func (t *wrappedTPM20) loadKey(opaqueBlob []byte) (*Key, error) {
|
||||
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 {
|
||||
return nil, fmt.Errorf("cannot load signing key: %v", err)
|
||||
}
|
||||
tpmPub, err := tpm2.DecodePublic(sKey.Public)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode public blob: %v", err)
|
||||
}
|
||||
pub, err := tpmPub.Key()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("access public key: %v", err)
|
||||
}
|
||||
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) {
|
||||
@ -208,7 +499,18 @@ type wrappedKey20 struct {
|
||||
createSignature []byte
|
||||
}
|
||||
|
||||
func newWrappedKey20(hnd tpmutil.Handle, blob, public, createData, createAttestation, createSig []byte) ak {
|
||||
func newWrappedAK20(hnd tpmutil.Handle, blob, public, createData, createAttestation, createSig []byte) ak {
|
||||
return &wrappedKey20{
|
||||
hnd: hnd,
|
||||
blob: blob,
|
||||
public: public,
|
||||
createData: createData,
|
||||
createAttestation: createAttestation,
|
||||
createSignature: createSig,
|
||||
}
|
||||
}
|
||||
|
||||
func newWrappedKey20(hnd tpmutil.Handle, blob, public, createData, createAttestation, createSig []byte) key {
|
||||
return &wrappedKey20{
|
||||
hnd: hnd,
|
||||
blob: blob,
|
||||
@ -240,13 +542,22 @@ func (k *wrappedKey20) close(t tpmBase) error {
|
||||
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)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
|
||||
}
|
||||
|
||||
ekHnd, _, err := t.getPrimaryKeyHandle(commonEkEquivalentHandle)
|
||||
if len(in.Credential) < 2 {
|
||||
return nil, fmt.Errorf("malformed credential blob")
|
||||
}
|
||||
credential := in.Credential[2:]
|
||||
if len(in.Secret) < 2 {
|
||||
return nil, fmt.Errorf("malformed encrypted secret")
|
||||
}
|
||||
secret := in.Secret[2:]
|
||||
|
||||
ekHnd, _, err := t.getEndorsementKeyHandle(ek)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -265,22 +576,67 @@ func (k *wrappedKey20) activateCredential(tb tpmBase, in EncryptedCredential) ([
|
||||
}
|
||||
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 tpm2.ActivateCredentialUsingAuth(t.rwc, []tpm2.AuthCommand{
|
||||
{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession},
|
||||
{Session: sessHandle, Attributes: tpm2.AttrContinueSession},
|
||||
}, k.hnd, ekHnd, in.Credential[2:], in.Secret[2:])
|
||||
}, 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)
|
||||
if !ok {
|
||||
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 {
|
||||
@ -291,3 +647,105 @@ func (k *wrappedKey20) attestationParameters() AttestationParameters {
|
||||
CreateSignature: k.createSignature,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *wrappedKey20) certificationParameters() CertificationParameters {
|
||||
return CertificationParameters{
|
||||
Public: k.public,
|
||||
CreateAttestation: k.createAttestation,
|
||||
CreateSignature: k.createSignature,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *wrappedKey20) sign(tb tpmBase, digest []byte, pub crypto.PublicKey, opts crypto.SignerOpts) ([]byte, error) {
|
||||
t, ok := tb.(*wrappedTPM20)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *wrappedTPM20, got %T", tb)
|
||||
}
|
||||
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 {
|
||||
return nil, fmt.Errorf("cannot sign: %v", err)
|
||||
}
|
||||
if sig.ECC == nil {
|
||||
return nil, fmt.Errorf("expected ECDSA signature, got: %v", sig.Alg)
|
||||
}
|
||||
return asn1.Marshal(struct {
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}{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)
|
||||
}
|
||||
|
||||
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) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (k *wrappedKey20) blobs() ([]byte, []byte, error) {
|
||||
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}
|
||||
oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
|
||||
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}
|
||||
|
||||
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.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.SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA384},
|
||||
{x509.SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA512},
|
||||
@ -129,50 +131,50 @@ func getSignatureAlgorithmFromAI(ai pkix.AlgorithmIdentifier) x509.SignatureAlgo
|
||||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
||||
|
||||
//RFC 5280 4.2.2.1
|
||||
// RFC 5280 4.2.2.1
|
||||
type authorityInfoAccess struct {
|
||||
Method asn1.ObjectIdentifier
|
||||
Location asn1.RawValue
|
||||
}
|
||||
|
||||
//RFC 5280 4.2.1.1
|
||||
// RFC 5280 4.2.1.1
|
||||
type authKeyID struct {
|
||||
ID []byte `asn1:"optional,tag:0"`
|
||||
IssuerName asn1.RawValue `asn1:"set,optional,tag:1"`
|
||||
SerialNumber *big.Int `asn1:"optional,tag:2"`
|
||||
}
|
||||
|
||||
//RFC 5280 4.2.1.4
|
||||
// RFC 5280 4.2.1.4
|
||||
type cpsPolicy struct {
|
||||
ID asn1.ObjectIdentifier
|
||||
Value string
|
||||
}
|
||||
|
||||
//RFC 5280 4.2.1.4
|
||||
// RFC 5280 4.2.1.4
|
||||
type policyInformation struct {
|
||||
Raw asn1.RawContent
|
||||
ID asn1.ObjectIdentifier
|
||||
Policy asn1.RawValue
|
||||
}
|
||||
|
||||
//RFC 5280 4.1.2.5
|
||||
// RFC 5280 4.1.2.5
|
||||
type validity struct {
|
||||
NotBefore, NotAfter time.Time
|
||||
}
|
||||
|
||||
//RFC 5280 4.2.1.4
|
||||
type NoticeReference struct {
|
||||
// RFC 5280 4.2.1.4
|
||||
type noticeReference struct {
|
||||
Organization string
|
||||
NoticeNumbers []int
|
||||
}
|
||||
|
||||
//RFC 5280 4.2.1.4
|
||||
// RFC 5280 4.2.1.4
|
||||
type userNotice struct {
|
||||
NoticeRef NoticeReference `asn1:"optional"`
|
||||
NoticeRef noticeReference `asn1:"optional"`
|
||||
ExplicitText string `asn1:"optional"`
|
||||
}
|
||||
|
||||
//RFC 5755 4.1
|
||||
// RFC 5755 4.1
|
||||
type objectDigestInfo struct {
|
||||
DigestedObjectType asn1.Enumerated
|
||||
OtherObjectTypeID asn1.ObjectIdentifier
|
||||
@ -180,14 +182,14 @@ type objectDigestInfo struct {
|
||||
ObjectDigest asn1.BitString
|
||||
}
|
||||
|
||||
//RFC 5755 4.1
|
||||
// RFC 5755 4.1
|
||||
type attCertIssuer struct {
|
||||
IssuerName asn1.RawValue `asn1:"set,optional"`
|
||||
BaseCertificateID issuerSerial `asn1:"optional,tag:0"`
|
||||
ObjectDigestInfo objectDigestInfo `asn1:"optional,tag:1"`
|
||||
}
|
||||
|
||||
//RFC 5755 4.1
|
||||
// RFC 5755 4.1
|
||||
type issuerSerial struct {
|
||||
Raw asn1.RawContent
|
||||
Issuer asn1.RawValue
|
||||
@ -195,7 +197,7 @@ type issuerSerial struct {
|
||||
IssuerUID asn1.BitString `asn1:"optional"`
|
||||
}
|
||||
|
||||
//RFC 5755 4.1
|
||||
// RFC 5755 4.1
|
||||
type holder struct {
|
||||
Raw asn1.RawContent
|
||||
BaseCertificateID issuerSerial `asn1:"optional,tag:0"`
|
||||
@ -203,13 +205,13 @@ type holder struct {
|
||||
ObjectDigestInfo objectDigestInfo `asn1:"optional,tag:2"`
|
||||
}
|
||||
|
||||
//RFC 5755 4.1
|
||||
// RFC 5755 4.1
|
||||
type attribute struct {
|
||||
ID asn1.ObjectIdentifier
|
||||
RawValues []asn1.RawValue `asn1:"set"`
|
||||
}
|
||||
|
||||
//RFC 5755 4.1
|
||||
// RFC 5755 4.1
|
||||
type tbsAttributeCertificate struct {
|
||||
Raw asn1.RawContent
|
||||
Version int
|
||||
|
@ -17,7 +17,7 @@ package attributecert
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -29,7 +29,7 @@ func TestVerifyAttributeCert(t *testing.T) {
|
||||
"testdata/Intel_pc2.cer",
|
||||
"testdata/Intel_pc3.cer",
|
||||
}
|
||||
data, err := ioutil.ReadFile("testdata/IntelSigningKey_20April2017.cer")
|
||||
data, err := os.ReadFile("testdata/IntelSigningKey_20April2017.cer")
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
for _, filename := range(testfiles) {
|
||||
data, err = ioutil.ReadFile(filename)
|
||||
for _, filename := range testfiles {
|
||||
data, err = os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read %s: %v", filename, err)
|
||||
}
|
||||
@ -57,7 +57,7 @@ func TestVerifyAttributeCert(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseAttributeCerts(t *testing.T) {
|
||||
files, err := ioutil.ReadDir("testdata")
|
||||
files, err := os.ReadDir("testdata")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read test dir: %v", err)
|
||||
}
|
||||
@ -70,7 +70,7 @@ func TestParseAttributeCerts(t *testing.T) {
|
||||
}
|
||||
filename := "testdata/" + file.Name()
|
||||
jsonfile := filename + ".json"
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read test data %s: %v", filename, err)
|
||||
}
|
||||
@ -78,7 +78,7 @@ func TestParseAttributeCerts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse test data %s: %v", filename, err)
|
||||
}
|
||||
jsondata, err := ioutil.ReadFile(jsonfile)
|
||||
jsondata, err := os.ReadFile(jsonfile)
|
||||
if err != nil {
|
||||
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"
|
||||
)
|
||||
|
||||
var simulatorStatePath = flag.String("state_path", "/tmp/sim/NVRAM/00.permall", "Path to ibmswtpm state file")
|
||||
|
||||
func ekPub() *rsa.PublicKey {
|
||||
out, err := exec.Command("tpm_getpubek", "-z").Output()
|
||||
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
|
||||
|
||||
go 1.13
|
||||
go 1.24
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
github.com/google/certificate-transparency-go v1.1.1
|
||||
github.com/google/go-cmp v0.5.4
|
||||
github.com/google/go-tpm v0.3.2
|
||||
github.com/google/go-tpm-tools v0.2.1
|
||||
github.com/google/go-tspi v0.2.1-0.20190423175329-115dea689aad
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-tpm v0.9.3
|
||||
github.com/google/go-tpm-tools v0.4.5
|
||||
github.com/google/go-tspi v0.3.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/sys v0.31.0
|
||||
)
|
||||
|
||||
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 (
|
||||
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