pax_global_header 0000666 0000000 0000000 00000000064 14523205536 0014517 g ustar 00root root 0000000 0000000 52 comment=a3545dfc9422d450aff99000607f8e1fc561bbd7
go-attestation-0.5.1/ 0000775 0000000 0000000 00000000000 14523205536 0014464 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/.github/ 0000775 0000000 0000000 00000000000 14523205536 0016024 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/.github/dependabot.yml 0000664 0000000 0000000 00000000155 14523205536 0020655 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
go-attestation-0.5.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14523205536 0020061 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/.github/workflows/codeql-analysis.yml 0000664 0000000 0000000 00000001343 14523205536 0023675 0 ustar 00root root 0000000 0000000 name: "CodeQL"
on:
push:
branches: [master]
pull_request:
branches: [master]
schedule:
- cron: '0 14 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['go']
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
go-attestation-0.5.1/.github/workflows/golangci-lint.yml 0000664 0000000 0000000 00000000560 14523205536 0023334 0 ustar 00root root 0000000 0000000 name: golangci-lint
on:
pull_request:
permissions:
contents: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.20.x
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.53.3
go-attestation-0.5.1/.github/workflows/test.yml 0000664 0000000 0000000 00000003402 14523205536 0021562 0 ustar 00root root 0000000 0000000 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.20.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test ./...
test-linux-tpm12:
strategy:
matrix:
go-version: [1.20.x]
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- 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.20.x]
runs-on: macos-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
# 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.20.x]
runs-on: windows-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go build ./...
go-attestation-0.5.1/.golangci.yaml 0000664 0000000 0000000 00000000071 14523205536 0017207 0 ustar 00root root 0000000 0000000 linters:
enable:
- gofmt
disable:
- errcheck
go-attestation-0.5.1/CONTRIBUTING.md 0000664 0000000 0000000 00000002115 14523205536 0016714 0 ustar 00root root 0000000 0000000 # How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
## Community Guidelines
This project follows
[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
go-attestation-0.5.1/LICENSE 0000664 0000000 0000000 00000026136 14523205536 0015501 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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-attestation-0.5.1/README.md 0000664 0000000 0000000 00000010635 14523205536 0015750 0 ustar 00root root 0000000 0000000 Go-Attestation
==============
[](https://godoc.org/github.com/google/go-attestation/attest)
Go-Attestation abstracts remote attestation operations across a variety of platforms
and TPMs, enabling remote validation of machine identity and state. This project
attempts to provide high level primitives for both client and server logic.
Talks on this project:
* _"Making Device Identity Trustworthy"_ - Open Source Summit Europe - October 2019 - ([Slides](https://static.sched.com/hosted_files/osseu19/ec/Device%20Identity.pdf))
* _"Using TPMs to Cryptographically Verify Devices at Scale"_ - Open Source Summit North America - September 2019 - ([Video](https://www.youtube.com/watch?v=EmEymlA5Q5Q) 39min)
* _"Making Remote Attestation Useful on Linux"_ - Linux Security Summit - September 2019 - ([Video](https://www.youtube.com/watch?v=TKva_h66Ptc) 26min)
## Status
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`
### 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
TPMs can be used to identify a device remotely and provision unique per-device
hardware-bound keys.
TPMs are provisioned with a set of Endorsement Keys (EKs) by the manufacturer.
These optionally include a certificate signed by the manufacturer and act as a
TPM's identity. For privacy reasons the EK can't be used to sign or encrypt data
directly, and is instead used to attest to the presence of a signing key, an
Attestation Key (AK), on the same TPM. (Newer versions of the spec may allow the
EK to sign directly.)
During attestation, a TPM generates an AK and proves to a certificate authority
that the AK is on the same TPM as a EK. If the certificate authority trusts the
EK, it can transitively trust the AK, for example by issuing a certificate for
the AK.
To perform attestation, the client generates an AK and sends the EK and AK
parameters to the server:
```go
// Client generates an AK and sends it to the server
config := &attest.OpenConfig{}
tpm, err := attest.OpenTPM(config)
if err != nil {
// handle error
}
eks, err := tpm.EKs()
if err != nil {
// handle error
}
ek := eks[0]
akConfig := &attest.AKConfig{}
ak, err := tpm.NewAK(akConfig)
if err != nil {
// handle error
}
attestParams := ak.AttestationParameters()
akBytes, err := ak.Marshal()
if err != nil {
// handle error
}
if err := os.WriteFile("encrypted_aik.json", akBytes, 0600); err != nil {
// handle error
}
// send TPM version, EK, and attestParams to the server
```
The server uses the EK and AK parameters to generate a challenge encrypted to
the EK, returning the challenge to the client. During this phase, the server
determines if it trusts the EK, either by chaining its certificate to a known
manufacturer and/or querying an inventory system.
```go
// Server validates EK and/or EK certificate
params := attest.ActivationParameters{
TPMVersion: tpmVersion,
EK: ek.Public,
AK: attestParams,
}
secret, encryptedCredentials, err := params.Generate()
if err != nil {
// handle error
}
// return encrypted credentials to client
```
The client proves possession of the AK by decrypting the challenge and
returning the same secret to the server.
```go
// Client decrypts the credential
akBytes, err := os.ReadFile("encrypted_aik.json")
if err != nil {
// handle error
}
ak, err := tpm.LoadAK(akBytes)
if err != nil {
// handle error
}
secret, err := ak.ActivateCredential(tpm, encryptedCredentials)
if err != nil {
// handle error
}
// return secret to server
```
At this point, the server records the AK and EK association and allows the client
to use its AK as a credential (e.g. by issuing it a client certificate).
go-attestation-0.5.1/attest/ 0000775 0000000 0000000 00000000000 14523205536 0015770 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/attest/activation.go 0000664 0000000 0000000 00000021265 14523205536 0020466 0 ustar 00root root 0000000 0000000 package attest
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"errors"
"fmt"
"io"
"github.com/google/go-tpm/legacy/tpm2"
tpm1 "github.com/google/go-tpm/tpm"
// TODO(jsonp): Move activation generation code to internal package.
"github.com/google/go-tpm/legacy/tpm2/credactivation"
"github.com/google/go-tspi/verification"
)
const (
// minRSABits is the minimum accepted bit size of an RSA key.
minRSABits = 2048
// activationSecretLen is the size in bytes of the generated secret
// which is generated for credential activation.
activationSecretLen = 32
// symBlockSize is the block size used for symmetric ciphers used
// when generating the credential activation challenge.
symBlockSize = 16
// tpm20GeneratedMagic is a magic tag when can only be present on a
// TPM structure if the structure was generated wholly by the TPM.
tpm20GeneratedMagic = 0xff544347
)
// ActivationParameters encapsulates the inputs for activating an AK.
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 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 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
// AK, the Attestation Key, describes the properties of
// an asymmetric key (managed by the TPM) which signs attestation
// structures.
// The values from this structure can be obtained by calling
// Parameters() on an attest.AK.
AK AttestationParameters
// Rand is a source of randomness to generate a seed and secret for the
// challenge.
//
// If nil, this defaults to crypto.Rand.
Rand io.Reader
}
// checkAKParameters examines properties of an AK and a creation
// attestation, to determine if it is suitable for use as an attestation key.
func (p *ActivationParameters) checkAKParameters() error {
switch p.TPMVersion {
case TPMVersion12:
return p.checkTPM12AKParameters()
case TPMVersion20:
return p.checkTPM20AKParameters()
default:
return fmt.Errorf("TPM version %d not supported", p.TPMVersion)
}
}
func (p *ActivationParameters) checkTPM12AKParameters() error {
// TODO(jsonp): Implement helper to parse public blobs, ie:
// func ParsePublic(publicBlob []byte) (crypto.Public, error)
pub, err := tpm1.UnmarshalPubRSAPublicKey(p.AK.Public)
if err != nil {
return fmt.Errorf("unmarshalling public key: %v", err)
}
if bits := pub.Size() * 8; bits < minRSABits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, bits)
}
return nil
}
func (p *ActivationParameters) checkTPM20AKParameters() error {
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature is too short to be valid: only %d bytes", len(p.AK.CreateSignature))
}
pub, err := tpm2.DecodePublic(p.AK.Public)
if err != nil {
return fmt.Errorf("DecodePublic() failed: %v", err)
}
_, err = tpm2.DecodeCreationData(p.AK.CreateData)
if err != nil {
return fmt.Errorf("DecodeCreationData() failed: %v", err)
}
att, err := tpm2.DecodeAttestationData(p.AK.CreateAttestation)
if err != nil {
return fmt.Errorf("DecodeAttestationData() failed: %v", err)
}
if att.Type != tpm2.TagAttestCreation {
return fmt.Errorf("attestation does not apply to creation data, got tag %x", att.Type)
}
// TODO: Support ECC AKs.
switch pub.Type {
case tpm2.AlgRSA:
if pub.RSAParameters.KeyBits < minRSABits {
return fmt.Errorf("attestation key too small: must be at least %d bits but was %d bits", minRSABits, pub.RSAParameters.KeyBits)
}
default:
return fmt.Errorf("public key of alg 0x%x not supported", pub.Type)
}
// Compute & verify that the creation data matches the digest in the
// attestation structure.
nameHash, err := pub.NameAlg.Hash()
if err != nil {
return fmt.Errorf("HashConstructor() failed: %v", err)
}
h := nameHash.New()
h.Write(p.AK.CreateData)
if !bytes.Equal(att.AttestedCreationInfo.OpaqueDigest, h.Sum(nil)) {
return errors.New("attestation refers to different public key")
}
// Make sure the AK has sane key parameters (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 a restricted key (means it cannot 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("AK is exportable")
}
if ((pub.Attributes & tpm2.FlagRestricted) == 0) || ((pub.Attributes & tpm2.FlagFixedParent) == 0) || ((pub.Attributes & tpm2.FlagSensitiveDataOrigin) == 0) {
return errors.New("provided key is not limited to attestation")
}
// Verify the attested creation name matches what is computed from
// the public key.
match, err := att.AttestedCreationInfo.Name.MatchesPublic(pub)
if err != nil {
return err
}
if !match {
return errors.New("creation attestation refers to a different key")
}
// Check the signature over the attestation data verifies correctly.
pk := rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
signHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
return err
}
hsh := signHash.New()
hsh.Write(p.AK.CreateAttestation)
verifyHash, err := pub.RSAParameters.Sign.Hash.Hash()
if err != nil {
return err
}
if len(p.AK.CreateSignature) < 8 {
return fmt.Errorf("signature invalid: length of %d is shorter than 8", len(p.AK.CreateSignature))
}
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(p.AK.CreateSignature))
if err != nil {
return fmt.Errorf("DecodeSignature() failed: %v", err)
}
if err := rsa.VerifyPKCS1v15(&pk, verifyHash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
return fmt.Errorf("could not verify attestation: %v", err)
}
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 *ActivationParameters) Generate() (secret []byte, ec *EncryptedCredential, err error) {
if err := p.checkAKParameters(); err != nil {
return nil, nil, err
}
if p.EK == nil {
return nil, nil, errors.New("no EK provided")
}
rnd, secret := p.Rand, 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)
}
switch p.TPMVersion {
case TPMVersion12:
ec, err = p.generateChallengeTPM12(rnd, secret)
case TPMVersion20:
ec, err = p.generateChallengeTPM20(secret)
default:
return nil, nil, fmt.Errorf("unrecognised TPM version: %v", p.TPMVersion)
}
if err != nil {
return nil, nil, err
}
return secret, ec, nil
}
func (p *ActivationParameters) generateChallengeTPM20(secret []byte) (*EncryptedCredential, error) {
att, err := tpm2.DecodeAttestationData(p.AK.CreateAttestation)
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)
}
return &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
func (p *ActivationParameters) generateChallengeTPM12(rand io.Reader, secret []byte) (*EncryptedCredential, error) {
pk, ok := p.EK.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("got EK of type %T, want an RSA key", p.EK)
}
var (
cred, encSecret []byte
err error
)
if p.AK.UseTCSDActivationFormat {
cred, encSecret, err = verification.GenerateChallengeEx(pk, p.AK.Public, secret)
} else {
cred, encSecret, err = generateChallenge12(rand, pk, p.AK.Public, secret)
}
if err != nil {
return nil, fmt.Errorf("challenge generation failed: %v", err)
}
return &EncryptedCredential{
Credential: cred,
Secret: encSecret,
}, nil
}
go-attestation-0.5.1/attest/activation_test.go 0000664 0000000 0000000 00000010540 14523205536 0021517 0 ustar 00root root 0000000 0000000 package attest
import (
"bytes"
"crypto/rsa"
"encoding/base64"
"math/big"
"math/rand"
"testing"
)
func decodeBase10(base10 string, t *testing.T) *big.Int {
i, ok := new(big.Int).SetString(base10, 10)
if !ok {
t.Fatalf("failed decode of base10: %q", base10)
}
return i
}
func decodeBase64(in string, t *testing.T) []byte {
out, err := base64.StdEncoding.DecodeString(in)
if err != nil {
t.Fatal(err)
}
return out
}
func ekCertSigner(t *testing.T) *rsa.PrivateKey {
return &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: decodeBase10("14314132931241006650998084889274020608918049032671858325988396851334124245188214251956198731333464217832226406088020736932173064754214329009979944037640912127943488972644697423190955557435910767690712778463524983667852819010259499695177313115447116110358524558307947613422897787329221478860907963827160223559690523660574329011927531289655711860504630573766609239332569210831325633840174683944553667352219670930408593321661375473885147973879086994006440025257225431977751512374815915392249179976902953721486040787792801849818254465486633791826766873076617116727073077821584676715609985777563958286637185868165868520557", t),
E: 3,
},
D: decodeBase10("9542755287494004433998723259516013739278699355114572217325597900889416163458809501304132487555642811888150937392013824621448709836142886006653296025093941418628992648429798282127303704957273845127141852309016655778568546006839666463451542076964744073572349705538631742281931858219480985907271975884773482372966847639853897890615456605598071088189838676728836833012254065983259638538107719766738032720239892094196108713378822882383694456030043492571063441943847195939549773271694647657549658603365629458610273821292232646334717612674519997533901052790334279661754176490593041941863932308687197618671528035670452762731", t),
Primes: []*big.Int{
decodeBase10("130903255182996722426771613606077755295583329135067340152947172868415809027537376306193179624298874215608270802054347609836776473930072411958753044562214537013874103802006369634761074377213995983876788718033850153719421695468704276694983032644416930879093914927146648402139231293035971427838068945045019075433", t),
decodeBase10("109348945610485453577574767652527472924289229538286649661240938988020367005475727988253438647560958573506159449538793540472829815903949343191091817779240101054552748665267574271163617694640513549693841337820602726596756351006149518830932261246698766355347898158548465400674856021497190430791824869615170301029", t),
},
}
}
func TestActivationTPM20(t *testing.T) {
priv := ekCertSigner(t)
rand := rand.New(rand.NewSource(123456))
// These parameters represent an AK generated on a real-world, infineon TPM.
params := ActivationParameters{
TPMVersion: TPMVersion20,
AK: AttestationParameters{
Public: decodeBase64("AAEACwAFBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAFAAECAAAAAAAAQC/08gj/04z4xGMIVTmr02lzhI5epufXgU831xEpf2qpXfvtNGUfqTcgWF2EUux2HDPqgcj59dtXRobQdlr4uCGNzfZIGAej4JusLa4MjpG6W2DtJPot6F1Mry63talzJ36U47niy9Iesd34CO2p9Xk3+86ZmBnQ6PQ2roUNK3l7bKz6cFLM9drOLwCqU0AUl6pHvzYPPz+xXsPl3iaA2cM97oneUiJNmJM7wtR9OcaKyIA4wVlX5TndB9NwWq5Iuj8q2Sp40Dg0noXXGSPliAtVD8flkXtAcuI9UHkQbzu9cGPRdSJPMn743GONg3bYalFtcgh2VpACXkPbXB32J7B", t),
CreateData: decodeBase64("AAAAAAAg47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFUBAAsAIgALWI9hwDRB3zYSkannqM5z0J1coQNA1Jz/oCRxJQwTaNwAIgALmyFYBhHeIU3FUKIAPgXFD3NXyasP3siQviDEyH7avu4AAA==", t),
CreateAttestation: decodeBase64("/1RDR4AaACIAC41+jhmEOue1MZhJjIk79ENar6i15rBvamXLpQnGTBCOAAAAAAAAD3GRNfU4syzJ1jQGATDCDteFC5C4ACIAC3ToMYGy9GXxcf8A0HvOuLOHbU7HPEppM47C7CMcU8TtACBDmJFUFO1f5+BYevaYdd3VtfMCsxIuHhoTZJczzLP2BA==", t),
CreateSignature: decodeBase64("ABQABAEALVzJSnKRJU39gHjETaI89/sM1L6HwBPGNekw6NojSW8bwD5/W1cLRDakCsYKUQu68mmbjs8xaIVBRvVM2YWP10tbTWNB0iJc9b8rERhkk3QIIFm/XsiVZsb0mysTxfeh8zygaAKQ/50sYyzp+raD0Ho0mYIRKJOEdQ6chsBflM3eB8mCXGTugUfrET80q3iu0gncaKWbfxQaQUb9ZTPSJrTN64HQ9tlOfnGT+8++WA3hV0NqKMnoAqiI9GZnI5MPXs6XxEncu/GJLJpAYZakBiS74Jvlr34Pur32B4xjm1M25AUGHEIgb6r49S0sV+hzaKu45858lQRMXj01GcyBhw==", t),
},
EK: &rsa.PublicKey{
E: priv.E,
N: priv.N,
},
Rand: rand,
}
secret, _, err := params.Generate()
if err != nil {
t.Fatalf("Generate() returned err: %v", err)
}
if got, want := secret, decodeBase64("0vhS7HtORX9uf/iyQ8Sf9WkpJuoJ1olCfTjSZuyNNxY=", t); !bytes.Equal(got, want) {
t.Fatalf("secret = %v, want %v", got, want)
}
}
go-attestation-0.5.1/attest/application_key.go 0000664 0000000 0000000 00000007541 14523205536 0021501 0 ustar 00root root 0000000 0000000 // 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
}
// 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()
}
go-attestation-0.5.1/attest/application_key_test.go 0000664 0000000 0000000 00000026612 14523205536 0022540 0 ustar 00root root 0000000 0000000 // 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,
},
},
} {
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)
}
})
}
}
go-attestation-0.5.1/attest/attest-tool/ 0000775 0000000 0000000 00000000000 14523205536 0020247 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/attest/attest-tool/README.md 0000664 0000000 0000000 00000001746 14523205536 0021536 0 ustar 00root root 0000000 0000000 # 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`)
go-attestation-0.5.1/attest/attest-tool/attest-tool.go 0000664 0000000 0000000 00000017504 14523205536 0023064 0 ustar 00root root 0000000 0000000 // Binary attest-tool performs attestation operations on the local system.
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"flag"
"fmt"
"os"
"github.com/google/go-attestation/attest"
"github.com/google/go-attestation/attest/attest-tool/internal"
)
var (
keyPath = flag.String("key", "ak.json", "Path to the key file")
nonceHex = flag.String("nonce", "", "Hex string to use as nonce when quoting")
randomNonce = flag.Bool("random-nonce", false, "Generate a random nonce instead of using one provided")
useSHA256 = flag.Bool("sha256", false, "Use SHA256 for quote operatons")
)
func main() {
flag.Parse()
if *randomNonce {
n := make([]byte, 8)
rand.Read(n)
*nonceHex = hex.EncodeToString(n)
}
tpm, err := attest.OpenTPM(nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening the TPM: %v\n", err)
os.Exit(1)
}
err = runCommand(tpm)
tpm.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func selftestCredentialActivation(tpm *attest.TPM, ak *attest.AK) error {
eks, err := tpm.EKs()
if err != nil {
return fmt.Errorf("EKs() failed: %v", err)
}
if len(eks) == 0 {
return errors.New("no EK present")
}
ek := eks[0].Public
// Test credential activation.
ap := attest.ActivationParameters{
TPMVersion: tpm.Version(),
EK: ek,
AK: ak.AttestationParameters(),
}
secret, ec, err := ap.Generate()
if err != nil {
return fmt.Errorf("failed to generate activation challenge: %v", err)
}
decryptedSecret, err := ak.ActivateCredential(tpm, *ec)
if err != nil {
return fmt.Errorf("failed to generate activate credential: %v", err)
}
if !bytes.Equal(secret, decryptedSecret) {
return errors.New("credential activation produced incorrect secret")
}
return nil
}
func selftestAttest(tpm *attest.TPM, ak *attest.AK) error {
// This nonce is used in generating the quote. As this is a selftest,
// 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)
if err != nil {
return fmt.Errorf("failed to parse ak public: %v", err)
}
if _, err := tpm.MeasurementLog(); err != nil {
return fmt.Errorf("no event log available: %v", err)
}
attestation, err := tpm.AttestPlatform(ak, nonce, nil)
if err != nil {
return fmt.Errorf("failed to attest: %v", err)
}
for i, quote := range attestation.Quotes {
if err := pub.Verify(quote, attestation.PCRs, nonce); err != nil {
return fmt.Errorf("failed to verify quote[%d]: %v", i, err)
}
}
el, err := attest.ParseEventLog(attestation.EventLog)
if err != nil {
return fmt.Errorf("failed to parse event log: %v", err)
}
if _, err := el.Verify(attestation.PCRs); err != nil {
return fmt.Errorf("event log failed to verify: %v", err)
}
return nil
}
func selftest(tpm *attest.TPM) error {
ak, err := tpm.NewAK(nil)
if err != nil {
return fmt.Errorf("NewAK() failed: %v", err)
}
defer ak.Close(tpm)
if err := selftestCredentialActivation(tpm, ak); err != nil {
return fmt.Errorf("credential activation failed: %v", err)
}
if err := selftestAttest(tpm, ak); err != nil {
return fmt.Errorf("state attestation failed: %v", err)
}
return nil
}
func runCommand(tpm *attest.TPM) error {
switch flag.Arg(0) {
case "info":
info, err := tpm.Info()
if err != nil {
return err
}
fmt.Printf("Version: %d\n", info.Version)
fmt.Printf("Interface: %d\n", info.Interface)
fmt.Printf("VendorInfo: %x\n", info.VendorInfo)
fmt.Printf("Manufacturer: %v\n", info.Manufacturer)
case "make-ak", "make-aik":
k, err := tpm.NewAK(nil)
if err != nil {
return fmt.Errorf("failed to mint an AK: %v", err)
}
defer k.Close(tpm)
b, err := k.Marshal()
if err != nil {
return err
}
return os.WriteFile(*keyPath, b, 0644)
case "quote":
b, err := os.ReadFile(*keyPath)
if err != nil {
return err
}
k, err := tpm.LoadAK(b)
if err != nil {
return fmt.Errorf("failed to load AK: %v", err)
}
defer k.Close(tpm)
nonce, err := hex.DecodeString(*nonceHex)
if err != nil {
return err
}
alg := attest.HashSHA1
if *useSHA256 {
alg = attest.HashSHA256
}
q, err := k.Quote(tpm, nonce, alg)
if err != nil {
return fmt.Errorf("failed to generate quote: %v", err)
}
fmt.Printf("Quote: %x\n", q.Quote)
fmt.Printf("Signature: %x\n", q.Signature)
case "list-eks":
eks, err := tpm.EKs()
if err != nil {
return fmt.Errorf("failed to read EKs: %v", err)
}
for _, ek := range eks {
data, err := encodeEK(ek)
if err != nil {
return fmt.Errorf("encoding ek: %v", err)
}
fmt.Printf("%s\n", data)
}
case "list-pcrs":
alg := attest.HashSHA1
if *useSHA256 {
alg = attest.HashSHA256
}
pcrs, err := tpm.PCRs(alg)
if err != nil {
return fmt.Errorf("failed to read PCRs: %v", err)
}
for _, pcr := range pcrs {
fmt.Printf("PCR[%d]: %x\n", pcr.Index, pcr.Digest)
}
case "measurement-log":
b, err := tpm.MeasurementLog()
if err != nil {
return fmt.Errorf("failed to read the measurement log: %v", err)
}
fmt.Printf("%x\n", b)
case "dump":
dumpData, err := runDump(tpm)
if err != nil {
return err
}
return json.NewEncoder(os.Stdout).Encode(dumpData)
case "self-test":
err := selftest(tpm)
if err != nil {
fmt.Println("FAIL")
return err
} else {
fmt.Println("PASS")
}
default:
return fmt.Errorf("no such command %q", flag.Arg(0))
}
return nil
}
func encodeEK(ek attest.EK) ([]byte, error) {
if ek.Certificate != nil {
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: ek.Certificate.Raw,
}), nil
}
switch pub := ek.Public.(type) {
case *ecdsa.PublicKey:
data, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, fmt.Errorf("marshaling ec public key: %v", err)
}
return pem.EncodeToMemory(&pem.Block{
Type: "EC PUBLIC KEY",
Bytes: data,
}), nil
case *rsa.PublicKey:
return pem.EncodeToMemory(&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(pub),
}), nil
default:
return nil, fmt.Errorf("unsupported public key type %T", pub)
}
}
func runDump(tpm *attest.TPM) (*internal.Dump, error) {
var (
out internal.Dump
err error
)
out.Static.TPMVersion = tpm.Version()
if out.Static.EKPem, err = rsaEKPEM(tpm); err != nil {
return nil, err
}
k, err := tpm.NewAK(nil)
if err != nil {
return nil, fmt.Errorf("failed to mint an AK: %v", err)
}
defer k.Close(tpm)
out.AK = k.AttestationParameters()
// Get a quote.
if out.Quote.Nonce, err = hex.DecodeString(*nonceHex); err != nil {
return nil, fmt.Errorf("failed decoding nonce hex: %v", err)
}
out.Quote.Alg = attest.HashSHA1
if *useSHA256 {
out.Quote.Alg = attest.HashSHA256
}
q, err := k.Quote(tpm, out.Quote.Nonce, out.Quote.Alg)
if err != nil {
return nil, fmt.Errorf("failed to generate quote: %v", err)
}
out.Quote.Quote = q.Quote
out.Quote.Signature = q.Signature
// Get log information.
if out.Log.Raw, err = tpm.MeasurementLog(); err != nil {
return nil, fmt.Errorf("failed to read measurement log: %v", err)
}
// Get PCR values.
if out.Log.PCRs, err = tpm.PCRs(out.Quote.Alg); err != nil {
return nil, fmt.Errorf("failed to read PCRs: %v", err)
}
return &out, nil
}
func rsaEKPEM(tpm *attest.TPM) ([]byte, error) {
eks, err := tpm.EKs()
if err != nil {
return nil, fmt.Errorf("failed to read EKs: %v", err)
}
var (
pk *rsa.PublicKey
buf bytes.Buffer
)
for _, ek := range eks {
if pub, ok := ek.Public.(*rsa.PublicKey); ok {
pk = pub
break
}
}
if pk == nil {
return nil, errors.New("no EK available")
}
if err := pem.Encode(&buf, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(pk)}); err != nil {
return nil, fmt.Errorf("failed to PEM encode: %v", err)
}
return buf.Bytes(), nil
}
go-attestation-0.5.1/attest/attest-tool/internal/ 0000775 0000000 0000000 00000000000 14523205536 0022063 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/attest/attest-tool/internal/eventlog/ 0000775 0000000 0000000 00000000000 14523205536 0023706 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/attest/attest-tool/internal/eventlog/eventlog.go 0000664 0000000 0000000 00000015341 14523205536 0026064 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 eventlog implements experimental logic for parsing the TCG event log format.
package eventlog
import "fmt"
// eventType indicates what kind of data an event is reporting.
type eventType uint32
func isReserved(t eventType) bool {
if 0x00000013 <= t && t <= 0x0000FFFF {
return true
}
if 0x800000E1 <= t && t <= 0x8000FFFF {
return true
}
return false
}
// String returns the name as defined by the TCG specification.
func (e eventType) String() string {
if s, ok := eventTypeNames[e]; ok {
return s
}
s := fmt.Sprintf("eventType(0x%08x)", int(e))
if isReserved(e) {
s += " (reserved)"
}
return s
}
const (
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=103
// Reserved for future use.
evPrebootCert eventType = 0x00000000
// Host platform trust chain measurements. The event data can contain one of
// the following, indicating different points of boot: "POST CODE", "SMM CODE",
// "ACPI DATA", "BIS CODE", "Embedded UEFI Driver".
//
// PCR[0] MUST be extended with this event type.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=38
evPostCode eventType = 0x00000001
// The event type was never used and is considered reserved.
evUnused eventType = 0x00000002
// Used for PCRs[0,6]. This event type doesn't extend the PCR, the digest MUST
// be all zeros, and the data holds information intended for parsers such as
// delimiting a switch to the agile crypto event format.
//
// This event MUST NOT extend any PCR
evNoAction eventType = 0x00000003
// Delineates the point where the Platform Firmware relinquishes control of TPM
// measurements to the operating system.
//
// Event data size MUST contain either 0x00000000 or 0xFFFFFFFF, the digest MUST
// match the data.
//
// This event MUST extend the PCRs 0 through 7 inclusive.
evSeparator eventType = 0x00000004
// An event indicating a particular action in the boot sequence, for example
// "User Password Entered" or "Booting BCV Device s".
//
// The digests field contains the tagged hash of the event field for each PCR bank.
//
// Used for PCRs [1, 2, 3, 4, 5, and 6].
evAction eventType = 0x00000005
// Used for PCRs defined for OS and application usage. The digest field MUST
// contain a hash of the data. The data contains a TCG_PCClientTaggedEvent
// sructure.
evEventTag eventType = 0x00000006
// Used for PCR[0] only. The digest contains the hash of the SRTM for each PCR
// bank. The data is informative and not expected to match the digest.
evSCRTMContents eventType = 0x00000007
evSCRTMVersion eventType = 0x00000008
// The digests field contains the tagged hash of the microcode patch applied for
// each PCR bank. The data is informative and not expected to match the digest.
evCUPMicrocode eventType = 0x00000009
// TODO(ericchiang): explain these events
evPlatformConfigFiles eventType = 0x0000000A
evTableOfDevices eventType = 0x0000000B
// Can be used for any PCRs except 0, 1, 2, or 3.
evCompactHash eventType = 0x0000000C
// IPL events are deprecated
evIPL eventType = 0x0000000D
evIPLPartitionData eventType = 0x0000000E
// Used for PCR[0] only.
//
// TODO(ericchiang): explain these events
evNonhostCode eventType = 0x0000000F
evNonhostConfig eventType = 0x00000010
evNonhostInfo eventType = 0x00000011
evOmitBootDeviceEvents eventType = 0x00000012
// The following events are UEFI specific.
// Data contains a UEFI_VARIABLE_DATA structure.
evEFIVariableDriverConfig eventType = 0x80000001 // PCR[1,3,5]
evEFIVariableBoot eventType = 0x80000002 // PCR[1]
// Data contains a UEFI_IMAGE_LOAD_EVENT structure.
evEFIBootServicesApplication eventType = 0x80000003 // PCR[2,4]
evEFIBootServicesDriver eventType = 0x80000004 // PCR[0,2]
evEFIRuntimeServicesDriver eventType = 0x80000005 // PCR[2,4]
// Data contains a UEFI_GPT_DATA structure.
evEFIGPTEvent eventType = 0x80000006 // PCR[5]
evEFIAction eventType = 0x80000007 // PCR[1,2,3,4,5,6,7]
// Data contains a UEFI_PLATFORM_FIRMWARE_BLOB structure.
evEFIPlatformFirmwareBlob eventType = 0x80000008 // PCR[0,2,4]
// Data contains a UEFI_HANDOFF_TABLE_POINTERS structure.
evEFIHandoffTables eventType = 0x80000009 // PCR[1]
// The digests field contains the tagged hash of the H-CRTM event
// data for each PCR bank.
//
// The Event Data MUST be the string: “HCRTMâ€.
evEFIHCRTMEvent eventType = 0x80000010 // PCR[0]
// Data contains a UEFI_VARIABLE_DATA structure.
evEFIVariableAuthority eventType = 0x800000E0 // PCR[7]
)
var eventTypeNames = map[eventType]string{
evPrebootCert: "EV_PREBOOT_CERT",
evPostCode: "EV_POST_CODE",
evUnused: "EV_UNUSED",
evNoAction: "EV_NO_ACTION",
evSeparator: "EV_SEPARATOR",
evAction: "EV_ACTION",
evEventTag: "EV_EVENT_TAG",
evSCRTMContents: "EV_S_CRTM_CONTENTS",
evSCRTMVersion: "EV_S_CRTM_VERSION",
evCUPMicrocode: "EV_CPU_MICROCODE",
evPlatformConfigFiles: "EV_PLATFORM_CONFIG_FLAGS",
evTableOfDevices: "EV_TABLE_OF_DEVICES",
evCompactHash: "EV_COMPACT_HASH",
evIPL: "EV_IPL (deprecated)",
evIPLPartitionData: "EV_IPL_PARTITION_DATA (deprecated)",
evNonhostCode: "EV_NONHOST_CODE",
evNonhostConfig: "EV_NONHOST_CONFIG",
evNonhostInfo: "EV_NONHOST_INFO",
evOmitBootDeviceEvents: "EV_OMIT_BOOT_DEVICE_EVENTS",
// UEFI events
evEFIVariableDriverConfig: "EV_EFI_VARIABLE_DRIVER_CONFIG",
evEFIVariableBoot: "EV_EFI_VARIABLE_BOOT",
evEFIBootServicesApplication: "EV_EFI_BOOT_SERVICES_APPLICATION",
evEFIBootServicesDriver: "EV_EFI_BOOT_SERVICES_DRIVER",
evEFIRuntimeServicesDriver: "EV_EFI_RUNTIME_SERVICES_DRIVER",
evEFIGPTEvent: "EV_EFI_GPT_EVENT",
evEFIAction: "EV_EFI_ACTION",
evEFIPlatformFirmwareBlob: "EV_EFI_PLATFORM_FIRMWARE_BLOB",
evEFIHandoffTables: "EV_EFI_HANDOFF_TABLES",
evEFIHCRTMEvent: "EV_EFI_HCRTM_EVENT",
evEFIVariableAuthority: "EV_EFI_VARIABLE_AUTHORITY",
}
go-attestation-0.5.1/attest/attest-tool/internal/eventlog/secureboot.go 0000664 0000000 0000000 00000015076 14523205536 0026420 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 eventlog
import (
"bytes"
"crypto"
"encoding/binary"
"fmt"
"io"
"unicode/utf16"
"github.com/google/go-attestation/attest"
)
var (
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=153
efiGlobalVariable = efiGUID{
0x8BE4DF61, 0x93CA, 0x11d2, [8]uint8{0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C}}
efiGlobalVariableSecureBoot = "SecureBoot"
efiGlobalVariablePlatformKey = "PK"
efiGlobalVariableKeyExchangeKey = "KEK"
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1804
efiImageSecurityDatabaseGUID = efiGUID{
0xd719b2cb, 0x3d3a, 0x4596, [8]uint8{0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f}}
efiImageSecurityDatabase = "db"
efiImageSecurityDatabase1 = "dbx"
efiImageSecurityDatabase2 = "dbt"
efiImageSecurityDatabase3 = "dbr"
)
type efiGUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]uint8
}
func (e efiGUID) String() string {
if s, ok := efiGUIDString[e]; ok {
return s
}
return fmt.Sprintf("{0x%x,0x%x,0x%x,{%x}}", e.Data1, e.Data2, e.Data3, e.Data4)
}
var efiGUIDString = map[efiGUID]string{
efiGlobalVariable: "EFI_GLOBAL_VARIABLE",
efiImageSecurityDatabaseGUID: "EFI_IMAGE_SECURITY_DATABASE_GUID",
}
type uefiVariableData struct {
id efiGUID
name string
data []byte
}
func (d *uefiVariableData) String() string {
return fmt.Sprintf("%s %s data length %d", d.id, d.name, len(d.data))
}
// SecureBoot holds parsed PCR 7 values representing secure boot settings for
// the device.
type SecureBoot struct {
Enabled bool
// TODO(ericchiang): parse these as EFI_SIGNATURE_LIST
//
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1788
PK []byte
KEK []byte
DB []byte
DBX []byte
DBT []byte
DBR []byte
// Authority is the set of certificate that were used during secure boot
// validation. This will be a subset of the certifiates in DB.
Authority []byte
}
// ParseSecureBoot parses UEFI secure boot variables (PCR[7) from a verified event log.
//
// See https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=56
func ParseSecureBoot(events []attest.Event) (*SecureBoot, error) {
var sb SecureBoot
seenSep := false
for i, e := range events {
if e.Index != 7 {
continue
}
t := eventType(e.Type)
switch t {
case evEFIVariableDriverConfig:
if seenSep {
return nil, fmt.Errorf("event %d %s after %s", i, t, evSeparator)
}
data, err := parseUEFIVariableData(e.Data, e.Digest)
if err != nil {
return nil, fmt.Errorf("parsing event %d, PCR[%02d] %s: %v", i, e.Index, t, err)
}
switch data.id {
case efiGlobalVariable:
switch data.name {
case efiGlobalVariableSecureBoot:
if len(data.data) != 1 {
return nil, fmt.Errorf("%s/%s was %d bytes", data.id, data.name, len(data.data))
}
switch data.data[0] {
case 0x0:
sb.Enabled = false
case 0x1:
sb.Enabled = true
default:
return nil, fmt.Errorf("invalid %s/%s value 0x%x", data.id, data.name, data.data)
}
case efiGlobalVariablePlatformKey:
sb.PK = data.data
case efiGlobalVariableKeyExchangeKey:
sb.KEK = data.data
}
case efiImageSecurityDatabaseGUID:
switch data.name {
case efiImageSecurityDatabase:
sb.DB = data.data
case efiImageSecurityDatabase1:
sb.DBX = data.data
case efiImageSecurityDatabase2:
sb.DBT = data.data
case efiImageSecurityDatabase3:
sb.DBR = data.data
}
}
case evEFIVariableAuthority:
if !seenSep {
return nil, fmt.Errorf("event %d %s before %s", i, t, evSeparator)
}
data, err := parseUEFIVariableData(e.Data, e.Digest)
if err != nil {
return nil, fmt.Errorf("parsing event %d, PCR[%02d] %s: %v", i, e.Index, t, err)
}
switch data.id {
case efiImageSecurityDatabaseGUID:
switch data.name {
case efiImageSecurityDatabase:
if !sb.Enabled {
return nil, fmt.Errorf("%s/%s present when secure boot wasn't enabled", t, data.name)
}
if len(sb.Authority) != 0 {
// If a malicious value is appended to the eventlog,
// ensure we only trust the first value written by
// the UEFI firmware.
return nil, fmt.Errorf("%s/%s was already present earlier in the event log", t, data.name)
}
sb.Authority = data.data
}
}
case evSeparator:
seenSep = true
}
}
return &sb, nil
}
func binaryRead(r io.Reader, i interface{}) error {
return binary.Read(r, binary.LittleEndian, i)
}
var hashBySize = map[int]crypto.Hash{
crypto.SHA1.Size(): crypto.SHA1,
crypto.SHA256.Size(): crypto.SHA256,
}
func verifyDigest(digest, data []byte) bool {
h, ok := hashBySize[len(digest)]
if !ok {
return false
}
hash := h.New()
hash.Write(data)
return bytes.Equal(digest, hash.Sum(nil))
}
// parseUEFIVariableData parses a UEFI_VARIABLE_DATA struct and validates the
// digest of an event entry.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100
func parseUEFIVariableData(b, digest []byte) (*uefiVariableData, error) {
r := bytes.NewBuffer(b)
var hdr struct {
ID efiGUID
NameLength uint64
DataLength uint64
}
if err := binaryRead(r, &hdr); err != nil {
return nil, err
}
name := make([]uint16, hdr.NameLength)
if err := binaryRead(r, &name); err != nil {
return nil, fmt.Errorf("parsing name: %v", err)
}
if r.Len() != int(hdr.DataLength) {
return nil, fmt.Errorf("remaining bytes %d doesn't match data length %d", r.Len(), hdr.DataLength)
}
data := r.Bytes()
// TODO(ericchiang): older UEFI firmware (Lenovo Bios version 1.17) logs the
// digest of the data, which doesn't encapsulate the ID or name. This lets
// attackers alter keys and we should determine if this is an acceptable risk.
if !verifyDigest(digest, b) && !verifyDigest(digest, data) {
return nil, fmt.Errorf("digest didn't match data")
}
return &uefiVariableData{id: hdr.ID, name: string(utf16.Decode(name)), data: r.Bytes()}, nil
}
go-attestation-0.5.1/attest/attest-tool/internal/eventlog/secureboot_test.go 0000664 0000000 0000000 00000005446 14523205536 0027457 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 eventlog
import (
"encoding/json"
"os"
"testing"
"github.com/google/go-attestation/attest"
"github.com/google/go-attestation/attest/attest-tool/internal"
)
func parseEvents(t *testing.T, testdata string) []attest.Event {
data, err := os.ReadFile(testdata)
if err != nil {
t.Fatalf("reading test data: %v", err)
}
var dump internal.Dump
if err := json.Unmarshal(data, &dump); err != nil {
t.Fatalf("parsing test data: %v", err)
}
ak, err := attest.ParseAKPublic(dump.Static.TPMVersion, dump.AK.Public)
if err != nil {
t.Fatalf("parsing AK: %v", err)
}
if err := ak.Verify(attest.Quote{
Version: dump.Static.TPMVersion,
Quote: dump.Quote.Quote,
Signature: dump.Quote.Signature,
}, dump.Log.PCRs, dump.Quote.Nonce); err != nil {
t.Fatalf("verifying quote: %v", err)
}
el, err := attest.ParseEventLog(dump.Log.Raw)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
events, err := el.Verify(dump.Log.PCRs)
if err != nil {
t.Fatalf("validating event log: %v", err)
}
return events
}
func notEmpty(t *testing.T, name string, field []byte) {
t.Helper()
if len(field) == 0 {
t.Errorf("field %s wasn't populated", name)
}
}
func isEmpty(t *testing.T, name string, field []byte) {
t.Helper()
if len(field) != 0 {
t.Errorf("expected field %s not to be populated", name)
}
}
func TestParseSecureBootWindows(t *testing.T) {
events := parseEvents(t, "../../../testdata/windows_gcp_shielded_vm.json")
sb, err := ParseSecureBoot(events)
if err != nil {
t.Fatalf("parse secure boot: %v", err)
}
if !sb.Enabled {
t.Errorf("expected secure boot to be enabled")
}
notEmpty(t, "db", sb.DB)
notEmpty(t, "dbx", sb.DBX)
notEmpty(t, "pk", sb.PK)
notEmpty(t, "kek", sb.KEK)
isEmpty(t, "dbt", sb.DBT)
isEmpty(t, "dbr", sb.DBR)
notEmpty(t, "Authority", sb.Authority)
}
func TestParseSecureBootLinux(t *testing.T) {
events := parseEvents(t, "../../../testdata/linux_tpm12.json")
sb, err := ParseSecureBoot(events)
if err != nil {
t.Errorf("parse secure boot: %v", err)
}
if sb.Enabled {
t.Errorf("expected secure boot to be disabled")
}
notEmpty(t, "db", sb.DB)
notEmpty(t, "dbx", sb.DBX)
isEmpty(t, "dbt", sb.DBT)
isEmpty(t, "dbr", sb.DBR)
isEmpty(t, "Authority", sb.Authority)
}
go-attestation-0.5.1/attest/attest-tool/internal/internal.go 0000664 0000000 0000000 00000001155 14523205536 0024230 0 ustar 00root root 0000000 0000000 // Package internal contains marshalling structures for attest-tool and tests.
package internal
import (
"github.com/google/go-attestation/attest"
"github.com/google/go-tpm/legacy/tpm2"
)
// Dump describes the layout of serialized information from the dump command.
type Dump struct {
Static struct {
TPMVersion attest.TPMVersion
EKPem []byte
}
AK attest.AttestationParameters
Quote struct {
Nonce []byte
Alg attest.HashAlg
Quote []byte
Signature []byte
}
Log struct {
PCRs []attest.PCR
PCRAlg tpm2.Algorithm
Raw []byte // The measured boot log in binary form.
}
}
go-attestation-0.5.1/attest/attest.go 0000664 0000000 0000000 00000043002 14523205536 0017622 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 abstracts TPM attestation operations.
package attest
import (
"crypto"
"crypto/x509"
"errors"
"fmt"
"io"
"strings"
"github.com/google/go-tpm/legacy/tpm2"
"github.com/google/go-tpm/tpm"
"github.com/google/go-tpm/tpmutil"
)
// TPMVersion is used to configure a preference in
// which TPM to use, if multiple are available.
type TPMVersion uint8
// TPM versions
const (
TPMVersionAgnostic TPMVersion = iota
TPMVersion12
TPMVersion20
)
// TPMInterface indicates how the client communicates
// with the TPM.
type TPMInterface uint8
// TPM interfaces
const (
TPMInterfaceDirect TPMInterface = iota
TPMInterfaceKernelManaged
TPMInterfaceDaemonManaged
TPMInterfaceCommandChannel
)
// CommandChannelTPM20 represents a pipe along which TPM 2.0 commands
// can be issued, and measurement logs read.
type CommandChannelTPM20 interface {
io.ReadWriteCloser
MeasurementLog() ([]byte, error)
}
// OpenConfig encapsulates settings passed to OpenTPM().
type OpenConfig struct {
// TPMVersion indicates which TPM version the library should
// attempt to use. If the specified version is not available,
// ErrTPMNotAvailable is returned. Defaults to TPMVersionAgnostic.
TPMVersion TPMVersion
// CommandChannel provides a TPM 2.0 command channel, which can be
// used in-lieu of any TPM present on the platform.
CommandChannel CommandChannelTPM20
}
// keyEncoding indicates how an exported TPM key is represented.
type keyEncoding uint8
func (e keyEncoding) String() string {
switch e {
case keyEncodingInvalid:
return "invalid"
case keyEncodingOSManaged:
return "os-managed"
case keyEncodingEncrypted:
return "encrypted"
case keyEncodingParameterized:
return "parameterized"
default:
return fmt.Sprintf("keyEncoding<%d>", int(e))
}
}
// Key encodings
const (
keyEncodingInvalid keyEncoding = iota
// Managed by the OS but loadable by name.
keyEncodingOSManaged
// Key fully represented but in encrypted form.
keyEncodingEncrypted
// Parameters stored, but key must be regenerated before use.
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, ek *EK) ([]byte, error)
quote(t tpmBase, nonce []byte, alg HashAlg, selectedPCRs []int) (*Quote, error)
attestationParameters() AttestationParameters
certify(tb tpmBase, handle interface{}) (*CertificationParameters, error)
}
// AK represents a key which can be used for attestation.
type AK struct {
ak ak
}
// Close unloads the AK from the system.
func (k *AK) Close(t *TPM) error {
return k.ak.close(t.tpm)
}
// Marshal encodes the AK in a format that can be reloaded with tpm.LoadAK().
// 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 *AK) Marshal() ([]byte, error) {
return k.ak.marshal()
}
// ActivateCredential 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 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, nil)
}
// ActivateCredential 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.
//
// 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) {
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
// generate a credential activation challenge.
func (k *AK) AttestationParameters() AttestationParameters {
return k.ak.attestationParameters()
}
// 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)
}
// 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
}
// EncryptedCredential represents encrypted parameters which must be activated
// against a key.
type EncryptedCredential struct {
Credential []byte
Secret []byte
}
// Quote encapsulates the results of a Quote operation against the TPM,
// using an attestation key.
type Quote struct {
Version TPMVersion
Quote []byte
Signature []byte
}
// PCR encapsulates the value of a PCR at a point in time.
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
// a certificate that can chain to the TPM manufacturer.
type EK struct {
// Public key of the EK.
Public crypto.PublicKey
// Certificate is the EK certificate for TPMs that provide it.
Certificate *x509.Certificate
// For Intel TPMs, Intel hosts certificates at a public URL derived from the
// 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
// for verifying its properties remotely.
type AttestationParameters struct {
// Public represents the AK's canonical encoding. This blob includes the
// public key, as well as signing parameters such as the hash algorithm
// used to generate quotes.
//
// Use ParseAKPublic to access the key's data.
Public []byte
// For TPM 2.0 devices, Public is encoded as a TPMT_PUBLIC structure.
// For TPM 1.2 devices, Public is a TPM_PUBKEY structure, as defined in
// the TPM Part 2 Structures specification, available at
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011.pdf
// UseTCSDActivationFormat is set when tcsd (trousers daemon) is operating
// as an intermediary between this library and the TPM. A value of true
// indicates that activation challenges should use the TCSD-specific format.
UseTCSDActivationFormat bool
// Subsequent fields are only populated for AKs generated on a TPM
// implementing version 2.0 of the specification. The specific structures
// referenced for each field are defined in the TPM Revision 2, Part 2 -
// Structures specification, available here:
// https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
// 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
}
// AKPublic holds structured information about an AK's public key.
type AKPublic struct {
// Public is the public part of the AK. This can either be an *rsa.PublicKey or
// and *ecdsa.PublicKey.
Public crypto.PublicKey
// Hash is the hashing algorithm the AK will use when signing quotes.
Hash crypto.Hash
}
// ParseAKPublic parses the Public blob from the AttestationParameters,
// returning the public key and signing parameters for the key.
func ParseAKPublic(version TPMVersion, public []byte) (*AKPublic, error) {
switch version {
case TPMVersion12:
rsaPub, err := tpm.UnmarshalPubRSAPublicKey(public)
if err != nil {
return nil, fmt.Errorf("parsing public key: %v", err)
}
return &AKPublic{Public: rsaPub, Hash: crypto.SHA1}, nil
case TPMVersion20:
pub, err := tpm2.DecodePublic(public)
if err != nil {
return nil, fmt.Errorf("parsing TPM public key structure: %v", err)
}
switch {
case pub.RSAParameters == nil && pub.ECCParameters == nil:
return nil, errors.New("parsing public key: missing asymmetric parameters")
case pub.RSAParameters != nil && pub.RSAParameters.Sign == nil:
return nil, errors.New("parsing public key: missing rsa signature scheme")
case pub.ECCParameters != nil && pub.ECCParameters.Sign == nil:
return nil, errors.New("parsing public key: missing ecc signature scheme")
}
pubKey, err := pub.Key()
if err != nil {
return nil, fmt.Errorf("parsing public key: %v", err)
}
var h crypto.Hash
switch pub.Type {
case tpm2.AlgRSA:
h, err = pub.RSAParameters.Sign.Hash.Hash()
case tpm2.AlgECC:
h, err = pub.ECCParameters.Sign.Hash.Hash()
default:
return nil, fmt.Errorf("unsupported public key type 0x%x", pub.Type)
}
if err != nil {
return nil, fmt.Errorf("invalid public key hash: %v", err)
}
return &AKPublic{Public: pubKey, Hash: h}, nil
default:
return nil, fmt.Errorf("unknown tpm version 0x%x", version)
}
}
// 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. 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
// nonce is used to tie additional data to the quote, the additional data should be
// hashed to construct the nonce.
func (a *AKPublic) Verify(quote Quote, pcrs []PCR, nonce []byte) error {
switch quote.Version {
case TPMVersion12:
return a.validate12Quote(quote, pcrs, nonce)
case TPMVersion20:
return a.validate20Quote(quote, pcrs, nonce)
default:
return fmt.Errorf("quote used unknown tpm version 0x%x", quote.Version)
}
}
// 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.
var (
HashSHA1 = HashAlg(tpm2.AlgSHA1)
HashSHA256 = HashAlg(tpm2.AlgSHA256)
)
func (a HashAlg) cryptoHash() crypto.Hash {
switch a {
case HashSHA1:
return crypto.SHA1
case HashSHA256:
return crypto.SHA256
}
return 0
}
func (a HashAlg) goTPMAlg() tpm2.Algorithm {
switch a {
case HashSHA1:
return tpm2.AlgSHA1
case HashSHA256:
return tpm2.AlgSHA256
}
return 0
}
// 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))
}
// 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.
type PlatformParameters struct {
// The version of the TPM which generated this attestation.
TPMVersion TPMVersion
// The public blob of the AK which endorsed the platform state. This can
// be decoded to verify the adjacent quotes using ParseAKPublic().
Public []byte
// The set of quotes which endorse the state of the PCRs.
Quotes []Quote
// The set of expected PCR values, which are used in replaying the event log
// to verify digests were not tampered with.
PCRs []PCR
// The raw event log provided by the platform. This can be processed with
// ParseEventLog().
EventLog []byte
}
var (
defaultOpenConfig = &OpenConfig{}
// ErrTPMNotAvailable is returned in response to OpenTPM() when
// either no TPM is available, or a TPM of the requested version
// is not available (if TPMVersion was set in the provided config).
ErrTPMNotAvailable = errors.New("TPM device not available")
// ErrTPM12NotImplemented is returned in response to methods which
// need to interact with the TPM1.2 device in ways that have not
// yet been implemented.
ErrTPM12NotImplemented = errors.New("TPM 1.2 support not yet implemented")
)
// TPMInfo contains information about the version & interface
// of an open TPM.
type TPMInfo struct {
Version TPMVersion
Interface TPMInterface
VendorInfo string
Manufacturer TCGVendorID
// FirmwareVersionMajor and FirmwareVersionMinor describe
// the firmware version of the TPM, but are only available
// for TPM 2.0 devices.
FirmwareVersionMajor int
FirmwareVersionMinor int
}
// probedTPM identifies a TPM device on the system, which
// is a candidate for being used.
type probedTPM struct {
Version TPMVersion
Path string
}
// MatchesConfig returns true if the TPM satisfies the constraints
// specified by the given config.
func (t *probedTPM) MatchesConfig(config OpenConfig) bool {
return config.TPMVersion == TPMVersionAgnostic || t.Version == config.TPMVersion
}
// OpenTPM initializes access to the TPM based on the
// config provided.
func OpenTPM(config *OpenConfig) (*TPM, error) {
if config == nil {
config = defaultOpenConfig
}
// As a special case, if the user provided us with a command channel,
// we should use that.
if config.CommandChannel != nil {
if config.TPMVersion > TPMVersionAgnostic && config.TPMVersion != TPMVersion20 {
return nil, errors.New("command channel can only be used as a TPM 2.0 device")
}
return &TPM{&wrappedTPM20{
interf: TPMInterfaceCommandChannel,
rwc: config.CommandChannel,
}}, nil
}
candidateTPMs, err := probeSystemTPMs()
if err != nil {
return nil, err
}
for _, tpm := range candidateTPMs {
if tpm.MatchesConfig(*config) {
return openTPM(tpm)
}
}
return nil, ErrTPMNotAvailable
}
// AvailableTPMs returns information about available TPMs matching
// the given config, without opening the devices.
func AvailableTPMs(config *OpenConfig) ([]TPMInfo, error) {
if config == nil {
config = defaultOpenConfig
}
candidateTPMs, err := probeSystemTPMs()
if err != nil {
return nil, err
}
var out []TPMInfo
for _, tpm := range candidateTPMs {
if tpm.MatchesConfig(*config) {
t, err := openTPM(tpm)
if err != nil {
return nil, err
}
defer t.Close()
i, err := t.Info()
if err != nil {
return nil, err
}
out = append(out, *i)
}
}
return out, nil
}
go-attestation-0.5.1/attest/attest_fuzz.go 0000664 0000000 0000000 00000002732 14523205536 0020705 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 gofuzz
// +build gofuzz
package attest
// FuzzParseAKPublic12 is an exported entrypoint for fuzzers to test
// ParseAKPublic TPM 1.2 blobs. This method should not be used for any
// other purpose.
func FuzzParseAKPublic12(data []byte) int {
_, err := ParseAKPublic(TPMVersion12, data)
if err != nil {
return 0
}
return 1
}
// FuzzParseAKPublic20 is an exported entrypoint for fuzzers to test
// ParseAKPublic TPM 2.0 blobs. This method should not be used for any
// other purpose.
func FuzzParseAKPublic20(data []byte) int {
_, err := ParseAKPublic(TPMVersion20, data)
if err != nil {
return 0
}
return 1
}
// FuzzParseEKCertificate is an exported entrypoint for fuzzers to test
// ParseEKCertificate. This method should not be used for any other purpose.
func FuzzParseEKCertificate(data []byte) int {
_, err := ParseEKCertificate(data)
if err != nil {
return 0
}
return 1
}
go-attestation-0.5.1/attest/attest_simulated_tpm20_test.go 0000664 0000000 0000000 00000020573 14523205536 0023762 0 ustar 00root root 0000000 0000000 // Copyright 2019 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"
"testing"
"github.com/google/go-tpm-tools/simulator"
)
func setupSimulatedTPM(t *testing.T) (*simulator.Simulator, *TPM) {
t.Helper()
tpm, err := simulator.Get()
if err != nil {
t.Fatal(err)
}
attestTPM, err := OpenTPM(&OpenConfig{CommandChannel: &fakeCmdChannel{tpm}})
if err != nil {
t.Fatal(err)
}
return tpm, attestTPM
}
func TestSimTPM20EK(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)
}
}
func TestSimTPM20Info(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
if _, err := tpm.Info(); err != nil {
t.Errorf("tpm.Info() failed: %v", err)
}
}
func TestSimTPM20AKCreateAndLoad(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
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)
}
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)
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()
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.Public,
}
secret, challenge, err := ap.Generate()
if err != nil {
t.Fatalf("Generate() failed: %v", err)
}
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)
}
if !bytes.Equal(secret, decryptedSecret) {
t.Error("secret does not match decrypted secret")
t.Logf("Secret = %v", secret)
t.Logf("Decrypted secret = %v", decryptedSecret)
}
}
func TestParseAKPublic20(t *testing.T) {
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)
params := ak.AttestationParameters()
if _, err := ParseAKPublic(TPMVersion20, params.Public); err != nil {
t.Errorf("parsing AK public blob: %v", err)
}
}
func TestSimTPM20QuoteAndVerifyAll(t *testing.T) {
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)
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8}
quote256, err := ak.Quote(tpm, nonce, HashSHA256)
if err != nil {
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
// the case where extra PCRs of a different digest algorithm are provided.
var pcrs []PCR
for _, alg := range []HashAlg{HashSHA256, HashSHA1} {
p, err := tpm.PCRs(alg)
if err != nil {
t.Fatalf("tpm.PCRs(%v) failed: %v", alg, err)
}
pcrs = append(pcrs, p...)
}
pub, err := ParseAKPublic(tpm.Version(), ak.AttestationParameters().Public)
if err != nil {
t.Fatalf("ParseAKPublic() failed: %v", err)
}
// 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)
}
}
func TestSimTPM20AttestPlatform(t *testing.T) {
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)
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8}
attestation, err := tpm.attestPlatform(ak, nonce, nil)
if err != nil {
t.Fatalf("AttestPlatform() failed: %v", err)
}
pub, err := ParseAKPublic(attestation.TPMVersion, attestation.Public)
if err != nil {
t.Fatalf("ParseAKPublic() failed: %v", err)
}
if err := pub.VerifyAll(attestation.Quotes, attestation.PCRs, nonce); err != nil {
t.Errorf("quote verification failed: %v", err)
}
}
func TestSimTPM20PCRs(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
PCRs, err := tpm.PCRs(HashSHA256)
if err != nil {
t.Fatalf("PCRs() failed: %v", err)
}
if len(PCRs) != 24 {
t.Errorf("len(PCRs) = %d, want %d", len(PCRs), 24)
}
for i, pcr := range PCRs {
if len(pcr.Digest) != pcr.DigestAlg.Size() {
t.Errorf("PCR %d len(digest) = %d, expected match with digest algorithm size (%d)", pcr.Index, len(pcr.Digest), pcr.DigestAlg.Size())
}
if pcr.Index != i {
t.Errorf("PCR index %d does not match map index %d", pcr.Index, i)
}
if pcr.DigestAlg != crypto.SHA256 {
t.Errorf("pcr.DigestAlg = %v, expected crypto.SHA256", pcr.DigestAlg)
}
}
}
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()
srkHnd, _, err := tpm.tpm.(*wrappedTPM20).getStorageRootKeyHandle(parentConfig)
if err != nil {
t.Fatalf("getStorageRootKeyHandle() failed: %v", err)
}
if srkHnd != parentConfig.Handle {
t.Fatalf("bad SRK-equivalent handle: got 0x%x, wanted 0x%x", srkHnd, parentConfig.Handle)
}
srkHnd, p, err := tpm.tpm.(*wrappedTPM20).getStorageRootKeyHandle(parentConfig)
if err != nil {
t.Fatalf("second getStorageRootKeyHandle() failed: %v", err)
}
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")
}
}
go-attestation-0.5.1/attest/attest_test.go 0000664 0000000 0000000 00000010261 14523205536 0020662 0 ustar 00root root 0000000 0000000 // Copyright 2019 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"
"flag"
"fmt"
"reflect"
"testing"
)
var (
testLocal = flag.Bool("testLocal", false, "run tests against local hardware")
)
func TestOpen(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
if tpm == nil {
t.Fatalf("Expected non-nil tpm struct")
}
defer tpm.Close()
}
func TestInfo(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
info, err := tpm.Info()
if err != nil {
t.Errorf("tpm.Info() failed: %v", err)
}
if info.Manufacturer.String() == "" {
t.Error("Expected info.Manufacturer.String() != ''")
}
t.Logf("TPM Info = %+v", info)
}
func TestEKs(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
eks, err := tpm.EKs()
if err != nil {
t.Errorf("EKs() failed: %v", err)
}
if len(eks) == 0 {
t.Log("EKs() did not return anything. This could be an issue if an EK is present.")
}
}
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()
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)
}
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)
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 which will be activated against.
func chooseEK(t *testing.T, eks []EK) EK {
t.Helper()
for _, ek := range eks {
return ek
}
t.Fatalf("No suitable EK found")
return EK{}
}
func TestPCRs(t *testing.T) {
if !*testLocal {
t.SkipNow()
}
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
PCRs, err := tpm.PCRs(HashSHA1)
if err != nil {
t.Fatalf("PCRs() failed: %v", err)
}
if len(PCRs) != 24 {
t.Errorf("len(PCRs) = %d, want %d", len(PCRs), 24)
}
for i, pcr := range PCRs {
if pcr.Index != i {
t.Errorf("PCR index %d does not match map index %d", pcr.Index, i)
}
}
}
func TestBug139(t *testing.T) {
// Tests ParseAKPublic() with known parseable but non-spec-compliant blobs, and ensure
// an error is returned rather than a segfault.
// https://github.com/google/go-attestation/issues/139
badBlob := []byte{0, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 6, 0, 128, 0, 67, 0, 16, 8, 0, 0, 1, 0, 1, 0, 0}
msg := "parsing public key: missing rsa signature scheme"
if _, err := ParseAKPublic(TPMVersion20, badBlob); err == nil || err.Error() != msg {
t.Errorf("ParseAKPublic() err = %v, want %v", err, msg)
}
}
func TestBug142(t *testing.T) {
// Tests ParseEKCertificate() with a malformed size prefix which would overflow
// an int16, ensuring an error is returned rather than a panic occurring.
input := []byte{0x10, 0x01, 0x00, 0xff, 0xff, 0x20}
wantErr := fmt.Errorf("parsing nvram header: ekCert size %d smaller than specified cert length %d", len(input), 65535)
if _, err := ParseEKCertificate(input); !reflect.DeepEqual(err, wantErr) {
t.Errorf("ParseEKCertificate() = %v, want %v", err, wantErr)
}
}
go-attestation-0.5.1/attest/attest_tpm12_test.go 0000664 0000000 0000000 00000007263 14523205536 0021715 0 ustar 00root root 0000000 0000000 // Copyright 2019 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/rand"
"flag"
"sort"
"testing"
)
var (
testTPM12 = flag.Bool("testTPM12", false, "run tests for TPM1.2")
tpm12config = &OpenConfig{TPMVersion: TPMVersion12}
)
func openTPM12(t *testing.T) *TPM {
if !*testTPM12 {
t.SkipNow()
}
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("Failed to open tpm 1.2: %v", err)
}
return tpm
}
func TestTPM12Info(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
Info, err := tpm.Info()
if err != nil {
t.Fatalf("Failed to get Vendor info: %v", err)
}
t.Logf("Vendor info: %s\n", Info.VendorInfo)
}
func TestTPM12PCRs(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
PCRs, err := tpm.PCRs(HashSHA1)
if err != nil {
t.Fatalf("Failed to get PCR values: %v", err)
}
var indices []int
for i, PCR := range PCRs {
if i != PCR.Index {
t.Errorf("Index %d does not match the PCRindex %d\n", i, PCR.Index)
}
indices = append(indices, i)
}
sort.Ints(indices)
for i := range indices {
PCR := PCRs[i]
t.Logf("PCR %v contains value 0x%x, which was caculated using alg %v\n", PCR.Index, bytes.NewBuffer(PCR.Digest), PCR.DigestAlg)
}
}
func TestTPM12EKs(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
eks, err := tpm.EKs()
if err != nil {
t.Fatalf("Failed to get EKs: %v", err)
}
if len(eks) == 0 {
t.Fatalf("EKs returned nothing")
}
}
func TestNewAK(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
if _, err := tpm.NewAK(nil); err != nil {
t.Fatalf("NewAK failed: %v", err)
}
}
func TestTPMQuote(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
nonce := make([]byte, 20)
if _, err := rand.Read(nonce); err != nil {
t.Fatalf("reading nonce: %v", err)
}
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("NewAK failed: %v", err)
}
quote, err := ak.Quote(tpm, nonce, HashSHA1)
if err != nil {
t.Fatalf("Quote failed: %v", err)
}
t.Logf("Quote{version: %v, quote: %x, signature: %x}\n", quote.Version, quote.Quote, quote.Signature)
}
func TestParseAKPublic12(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("NewAK() failed: %v", err)
}
defer ak.Close(tpm)
params := ak.AttestationParameters()
if _, err := ParseAKPublic(TPMVersion12, params.Public); err != nil {
t.Errorf("parsing AK public blob: %v", err)
}
}
func TestTPMActivateCredential(t *testing.T) {
tpm := openTPM12(t)
defer tpm.Close()
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatalf("NewAK failed: %v", err)
}
EKs, err := tpm.EKs()
if err != nil {
t.Fatalf("failed to read EKs: %v", err)
}
ek := chooseEK(t, EKs)
ap := ActivationParameters{
TPMVersion: TPMVersion12,
AK: ak.AttestationParameters(),
EK: ek.Public,
}
secret, challenge, err := ap.Generate()
if err != nil {
t.Fatalf("Generate() failed: %v", err)
}
validation, err := ak.ActivateCredential(tpm, *challenge)
if err != nil {
t.Fatalf("ActivateCredential failed: %v", err)
}
if !bytes.Equal(validation, secret) {
t.Errorf("secret mismatch: expected %x, got %x", secret, validation)
}
t.Logf("validation: %x", validation)
}
go-attestation-0.5.1/attest/certification.go 0000664 0000000 0000000 00000022143 14523205536 0021144 0 ustar 00root root 0000000 0000000 // 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/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
}
// 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.
// TODO: Support ECC certifying keys
pk, ok := opts.Public.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("only RSA verification keys are supported")
}
if !opts.Hash.Available() {
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)
}
if err := rsa.VerifyPKCS1v15(pk, opts.Hash, hsh.Sum(nil), sig.RSA.Signature); err != nil {
return fmt.Errorf("could not verify attestation: %v", err)
}
return 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 and the passed signature scheme to certify the key
// with the `hnd` handle.
func certify(tpm io.ReadWriteCloser, hnd, akHnd tpmutil.Handle, 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, nil, scheme)
if err != nil {
return nil, fmt.Errorf("tpm2.Certify() failed: %v", err)
}
return &CertificationParameters{
Public: public,
CreateAttestation: att,
CreateSignature: sig,
}, nil
}
go-attestation-0.5.1/attest/certification_test.go 0000664 0000000 0000000 00000022514 14523205536 0022205 0 ustar 00root root 0000000 0000000 // 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"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/go-tpm/legacy/tpm2"
)
func TestSimTPM20CertificationParameters(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
testCertificationParameters(t, tpm)
}
func TestTPM20CertificationParameters(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)
}
func testCertificationParameters(t *testing.T, tpm *TPM) {
ak, err := tpm.NewAK(nil)
if err != nil {
t.Fatal(err)
}
akAttestParams := ak.AttestationParameters()
pub, err := tpm2.DecodePublic(akAttestParams.Public)
if err != nil {
t.Fatal(err)
}
if pub.Type != tpm2.AlgRSA {
t.Fatal("non-RSA verifying key")
}
pk := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
hash, err := pub.RSAParameters.Sign.Hash.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 TestSimTPM20KeyCertification(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
testKeyCertification(t, tpm)
}
func TestTPM20KeyCertification(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)
}
func testKeyCertification(t *testing.T, tpm *TPM) {
ak, err := tpm.NewAK(nil)
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 := &rsa.PublicKey{E: int(pub.RSAParameters.Exponent()), N: pub.RSAParameters.Modulus()}
hash, err := pub.RSAParameters.Sign.Hash.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
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,
},
} {
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()
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)
}
})
}
}
go-attestation-0.5.1/attest/challenge.go 0000664 0000000 0000000 00000007100 14523205536 0020237 0 ustar 00root root 0000000 0000000 package attest
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rsa"
"crypto/sha1"
"encoding/binary"
"fmt"
"io"
)
const (
ekBlobTag = 0x000c
ekBlobActivateTag = 0x002b
ekTypeActivate = 0x0001
algXOR = 0x0000000a
schemeESNone = 0x0001
)
type symKeyHeader struct {
Alg uint32
Scheme uint16
KeySize uint16
}
type activationBlobHeader struct {
Tag uint16
KeyHeader symKeyHeader
}
func makeEmptyPCRInfo() []byte {
var b bytes.Buffer
binary.Write(&b, binary.BigEndian, uint16(3)) // SIZE_OF_SELECT
b.Write([]byte{0x00, 0x00, 0x00}) // empty bitfield for 3 PCRs
b.Write([]byte{0x01}) // TPM_LOCALITY_SELECTION = TPM_LOC_ZERO
b.Write(bytes.Repeat([]byte{0}, sha1.Size)) // TPM_COMPOSITE_HASH
return b.Bytes()
}
func makeActivationBlob(symKey, akpub []byte) (blob []byte, err error) {
akHash := sha1.Sum(akpub)
var out bytes.Buffer
if err := binary.Write(&out, binary.BigEndian, activationBlobHeader{
Tag: ekBlobActivateTag,
KeyHeader: symKeyHeader{
Alg: algXOR,
Scheme: schemeESNone,
KeySize: uint16(len(symKey)),
},
}); err != nil {
return nil, err
}
out.Write(symKey)
out.Write(akHash[:])
out.Write(makeEmptyPCRInfo())
return out.Bytes(), nil
}
type ekBlobHeader struct {
Tag uint16
EkType uint16
BlobLen uint32
}
func makeEkBlob(activationBlob []byte) []byte {
var out bytes.Buffer
binary.Write(&out, binary.BigEndian, ekBlobHeader{
Tag: ekBlobTag,
EkType: ekTypeActivate,
BlobLen: uint32(len(activationBlob)),
})
out.Write(activationBlob)
return out.Bytes()
}
func pad(plaintext []byte, bsize int) []byte {
pad := bsize - (len(plaintext) % bsize)
if pad == 0 {
pad = bsize
}
for i := 0; i < pad; i++ {
plaintext = append(plaintext, byte(pad))
}
return plaintext
}
// generateChallenge12 generates a TPM_EK_BLOB challenge for a TPM 1.2 device.
// This process is defined in section 15.1 of the TPM 1.2 commands spec,
// available at: https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-3-Commands_v1.2_rev116_01032011.pdf
//
// asymenc is a TPM_EK_BLOB structure containing a TPM_EK_BLOB_ACTIVATE structure,
// encrypted with the EK of the TPM. The contained credential is the aes key
// for symenc.
// symenc is a structure with TPM_SYM_MODE_CBC leading, then the IV, and then
// the secret encrypted with the session key credential contained in asymenc.
// To use this, pass asymenc as the input to the TPM_ActivateIdentity command.
// Use the returned credential as the aes key to decode the secret in symenc.
func generateChallenge12(rand io.Reader, pubkey *rsa.PublicKey, akpub, secret []byte) (asymenc []byte, symenc []byte, err error) {
aeskey := make([]byte, 16)
iv := make([]byte, 16)
if _, err = io.ReadFull(rand, aeskey); err != nil {
return nil, nil, err
}
if _, err = io.ReadFull(rand, iv); err != nil {
return nil, nil, err
}
activationBlob, err := makeActivationBlob(aeskey, akpub)
if err != nil {
return nil, nil, err
}
label := []byte{'T', 'C', 'P', 'A'}
asymenc, err = rsa.EncryptOAEP(sha1.New(), rand, pubkey, makeEkBlob(activationBlob), label)
if err != nil {
return nil, nil, fmt.Errorf("EncryptOAEP() failed: %v", err)
}
block, err := aes.NewCipher(aeskey)
if err != nil {
return nil, nil, err
}
cbc := cipher.NewCBCEncrypter(block, iv)
secret = pad(secret, len(iv))
symenc = make([]byte, len(secret))
cbc.CryptBlocks(symenc, secret)
var symOut bytes.Buffer
binary.Write(&symOut, binary.BigEndian, uint32(0x02)) // TPM_SYM_MODE_CBC
symOut.Write(iv)
symOut.Write(symenc)
return asymenc, symOut.Bytes(), nil
}
go-attestation-0.5.1/attest/challenge_test.go 0000664 0000000 0000000 00000006545 14523205536 0021312 0 ustar 00root root 0000000 0000000 package attest
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"testing"
)
func TestMakeActivationBlob(t *testing.T) {
blob, err := makeActivationBlob([]byte{1, 2, 3, 4, 5}, []byte{5, 6, 7, 8})
if err != nil {
t.Fatal(err)
}
if got, want := blob[0:2], []byte{0, 0x2b}; !bytes.Equal(got, want) {
t.Errorf("tag = %v, want %v", got, want)
}
if got, want := blob[2:6], []byte{0, 0, 0, 0x0a}; !bytes.Equal(got, want) {
t.Errorf("alg = %v, want %v", got, want)
}
if got, want := blob[6:8], []byte{0, 1}; !bytes.Equal(got, want) {
t.Errorf("scheme = %v, want %v", got, want)
}
if got, want := blob[8:10], []byte{0, 5}; !bytes.Equal(got, want) {
t.Errorf("len = %v, want %v", got, want)
}
if got, want := blob[10:15], []byte{1, 2, 3, 4, 5}; !bytes.Equal(got, want) {
t.Errorf("symKey = %v, want %v", got, want)
}
if got, want := blob[15:35], []byte{133, 217, 101, 29, 154, 57, 154, 103, 224, 21, 208, 71, 253, 158, 106, 148, 30, 107, 32, 187}; !bytes.Equal(got, want) {
t.Errorf("ak digest = %v, want %v", got, want)
}
if got, want := blob[35:37], []byte{0, 3}; !bytes.Equal(got, want) {
t.Errorf("size of select = %v, want %v", got, want)
}
if got, want := blob[37:40], []byte{0, 0, 0}; !bytes.Equal(got, want) {
t.Errorf("select bitfield = %v, want %v", got, want)
}
if got, want := blob[40:41], []byte{1}; !bytes.Equal(got, want) {
t.Errorf("locality = %v, want %v", got, want)
}
if got, want := blob[41:61], bytes.Repeat([]byte{0}, 20); !bytes.Equal(got, want) {
t.Errorf("select digest = %v, want %v", got, want)
}
}
func TestGenerateChallengeSymHeader(t *testing.T) {
cert, err := x509.ParseCertificate(decodeBase64("MIID2jCCA4CgAwIBAgIKFsBPsR6KEUuzHjAKBggqhkjOPQQDAjBVMVMwHwYDVQQDExhOdXZvdG9uIFRQTSBSb290IENBIDIxMTAwJQYDVQQKEx5OdXZvdG9uIFRlY2hub2xvZ3kgQ29ycG9yYXRpb24wCQYDVQQGEwJUVzAeFw0xNzEwMTgyMzQ5MjBaFw0zNzEwMTQyMzQ5MjBaMAAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsiqa4C+4hxqQgQ93aFmVq+hvbV6FDvNod24lA1s24pVJzUdOW/D0ORY3TdvRKS1xEh+yPD+iao+XrRGELHSOrGxid/kaTiOF8KdR5BWJwCoedQasqMyQsgZNlU6nKoERcex2G6DDozkdUgrJ/A04qG8tkpEfwmS+0SVWEtDoTb4fzdehKmS32gKcY/I3ZnmpE/5+FCJHKUwIPxHPwAGdhWoYEGsb7ZwG3/S4UuPEfHaab/iwj/WxwbnGtysMu9r1ZkHjQx6FblWVLoXCqrTl0q0samTuW52MffybbOEzn9R0pnfiyQlpL8CLKP4/kBPUGkkvZm2MJsF2cwNRJtEXVAgMBAAGjggHAMIIBvDBKBgNVHREBAf8EQDA+pDwwOjE4MBQGBWeBBQIBEwtpZDo0RTU0NDMwMDAQBgVngQUCAhMHTlBDVDZ4eDAOBgVngQUCAxMFaWQ6MTMwDAYDVR0TAQH/BAIwADAQBgNVHSUECTAHBgVngQUIATAfBgNVHSMEGDAWgBSfu3mqD1JieL7RUJKacXHpajW+9zAOBgNVHQ8BAf8EBAMCBSAwcAYDVR0JBGkwZzAWBgVngQUCEDENMAsMAzIuMAIBAAIBdDBNBgVngQUCEjFEMEICAQABAf+gAwoBAaEDCgEAogMKAQCjFTATFgMzLjEKAQQKAQEBAf+gAwoBAqQPMA0WBTE0MC0yCgECAQEApQMBAQAwQQYDVR0gBDowODA2BgRVHSAAMC4wLAYIKwYBBQUHAgEWIGh0dHA6Ly93d3cubnV2b3Rvbi5jb20vc2VjdXJpdHkvMGgGCCsGAQUFBwEBBFwwWjBYBggrBgEFBQcwAoZMaHR0cDovL3d3dy5udXZvdG9uLmNvbS9zZWN1cml0eS9OVEMtVFBNLUVLLUNlcnQvTnV2b3RvbiBUUE0gUm9vdCBDQSAyMTEwLmNlcjAKBggqhkjOPQQDAgNIADBFAiEAtct+vD/l1Vv9TJOl6oRSI+IZk+k31YIqcscDZEGpZI0CIFFAsVKlFnQnXKTxo7sx9dGOio92Bschl0TQLQhVv0K7", t))
if err != nil {
t.Fatal(err)
}
_, sym, err := generateChallenge12(rand.Reader, cert.PublicKey.(*rsa.PublicKey), []byte("pubkey yo"), []byte("secretz"))
if err != nil {
t.Fatal(err)
}
if got, want := len(sym), 36; got != want {
t.Errorf("len(sym) = %v, want %v", got, want)
}
if got, want := sym[0:4], []byte{0, 0, 0, 2}; !bytes.Equal(got, want) {
t.Errorf("symmetric mode = %v, want %v", got, want)
}
}
go-attestation-0.5.1/attest/eventlog.go 0000664 0000000 0000000 00000057677 14523205536 0020170 0 ustar 00root root 0000000 0000000 // Copyright 2019 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/rsa"
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"sort"
"strings"
// Ensure hashes are available.
_ "crypto/sha256"
"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
// InvalidPCRs reports the set of PCRs where the event log replay failed.
InvalidPCRs []int
}
func (e ReplayError) affected(pcr int) bool {
for _, p := range e.InvalidPCRs {
if p == pcr {
return true
}
}
return false
}
// Error returns a human-friendly description of replay failures.
func (e ReplayError) Error() string {
return fmt.Sprintf("event log failed to verify: the following registers failed to replay: %v", e.InvalidPCRs)
}
// 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
type EventType uint32
var eventTypeStrings = map[uint32]string{
0x00000000: "EV_PREBOOT_CERT",
0x00000001: "EV_POST_CODE",
0x00000002: "EV_UNUSED",
0x00000003: "EV_NO_ACTION",
0x00000004: "EV_SEPARATOR",
0x00000005: "EV_ACTION",
0x00000006: "EV_EVENT_TAG",
0x00000007: "EV_S_CRTM_CONTENTS",
0x00000008: "EV_S_CRTM_VERSION",
0x00000009: "EV_CPU_MICROCODE",
0x0000000A: "EV_PLATFORM_CONFIG_FLAGS",
0x0000000B: "EV_TABLE_OF_DEVICES",
0x0000000C: "EV_COMPACT_HASH",
0x0000000D: "EV_IPL",
0x0000000E: "EV_IPL_PARTITION_DATA",
0x0000000F: "EV_NONHOST_CODE",
0x00000010: "EV_NONHOST_CONFIG",
0x00000011: "EV_NONHOST_INFO",
0x00000012: "EV_OMIT_BOOT_DEVICE_EVENTS",
0x80000000: "EV_EFI_EVENT_BASE",
0x80000001: "EV_EFI_VARIABLE_DRIVER_CONFIG",
0x80000002: "EV_EFI_VARIABLE_BOOT",
0x80000003: "EV_EFI_BOOT_SERVICES_APPLICATION",
0x80000004: "EV_EFI_BOOT_SERVICES_DRIVER",
0x80000005: "EV_EFI_RUNTIME_SERVICES_DRIVER",
0x80000006: "EV_EFI_GPT_EVENT",
0x80000007: "EV_EFI_ACTION",
0x80000008: "EV_EFI_PLATFORM_FIRMWARE_BLOB",
0x80000009: "EV_EFI_HANDOFF_TABLES",
0x80000010: "EV_EFI_HCRTM_EVENT",
0x800000E0: "EV_EFI_VARIABLE_AUTHORITY",
}
// String returns the Spec name of the EventType, for example "EV_ACTION". If
// unknown, it returns a formatted string of the EventType value.
func (e EventType) String() string {
if s, ok := eventTypeStrings[uint32(e)]; ok {
return s
}
// NOTE: 0x00000013-0x0000FFFF are reserverd. Should we include that
// information in the formatting?
return fmt.Sprintf("EventType(0x%08x)", uint32(e))
}
// Event is a single event from a TCG event log. This reports descrete items such
// 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 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
type Event struct {
// order of the event in the event log.
sequence int
// Index of the PCR that this event was replayed against.
Index int
// Untrusted type of the event. This value is not verified by event log replays
// and can be tampered with. It should NOT be used without additional context,
// and unrecognized event types should result in errors.
Type EventType
// Data of the event. For certain kinds of events, this must match the event
// digest to be valid.
Data []byte
// Digest is the verified digest of the event data. While an event can have
// multiple for different hash values, this is the one that was matched to the
// PCR value.
Digest []byte
// TODO(ericchiang): Provide examples or links for which event types must
// match their data to their digest.
}
func (e *Event) digestEquals(b []byte) error {
if len(e.Digest) == 0 {
return errors.New("no digests present")
}
switch len(e.Digest) {
case crypto.SHA256.Size():
s := sha256.Sum256(b)
if bytes.Equal(s[:], e.Digest) {
return nil
}
case crypto.SHA1.Size():
s := sha1.Sum(b)
if bytes.Equal(s[:], e.Digest) {
return nil
}
default:
return fmt.Errorf("cannot compare hash of length %d", len(e.Digest))
}
return fmt.Errorf("digest (len %d) does not match", len(e.Digest))
}
// EventLog is a parsed measurement log. This contains unverified data representing
// boot events that must be replayed against PCR values to determine authenticity.
type EventLog struct {
// Algs holds the set of algorithms that the event log uses.
Algs []HashAlg
rawEvents []rawEvent
specIDEvent *specIDEvent
}
func (e *EventLog) clone() *EventLog {
out := EventLog{
Algs: make([]HashAlg, len(e.Algs)),
rawEvents: make([]rawEvent, len(e.rawEvents)),
}
copy(out.Algs, e.Algs)
copy(out.rawEvents, e.rawEvents)
if e.specIDEvent != nil {
dupe := *e.specIDEvent
out.specIDEvent = &dupe
}
return &out
}
// Events returns events that have not been replayed against the PCR values and
// are therefore unverified. The returned events contain the digest that matches
// the provided hash algorithm, or are empty if that event didn't contain a
// digest for that hash.
//
// This method is insecure and should only be used for debugging.
func (e *EventLog) Events(hash HashAlg) []Event {
var events []Event
for _, re := range e.rawEvents {
ev := Event{
Index: re.index,
Type: re.typ,
Data: re.data,
}
for _, digest := range re.digests {
if hash.cryptoHash() != digest.hash {
continue
}
ev.Digest = digest.data
break
}
events = append(events, ev)
}
return events
}
// 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 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
// index do not match any provided value for that PCR index.
func (e *EventLog) Verify(pcrs []PCR) ([]Event, error) {
events, err := e.verify(pcrs)
// If there were any issues replaying the PCRs, try each of the workarounds
// in turn.
// TODO(jsonp): Allow workarounds to be combined.
if rErr, isReplayErr := err.(ReplayError); isReplayErr {
for _, wkrd := range eventlogWorkarounds {
if !rErr.affected(wkrd.affectedPCR) {
continue
}
el := e.clone()
if err := wkrd.apply(el); err != nil {
return nil, fmt.Errorf("failed applying workaround %q: %v", wkrd.id, err)
}
if events, err := el.verify(pcrs); err == nil {
return events, nil
}
}
}
return events, err
}
func (e *EventLog) verify(pcrs []PCR) ([]Event, error) {
events, err := replayEvents(e.rawEvents, pcrs)
if err != nil {
if _, isReplayErr := err.(ReplayError); isReplayErr {
return nil, err
}
return nil, fmt.Errorf("pcrs failed to replay: %v", err)
}
return events, nil
}
type rawAttestationData struct {
Version [4]byte // This MUST be 1.1.0.0
Fixed [4]byte // This SHALL always be the string ‘QUOT’
Digest [20]byte // PCR Composite Hash
Nonce [20]byte // Nonce Hash
}
var (
fixedQuote = [4]byte{'Q', 'U', 'O', 'T'}
)
type rawPCRComposite struct {
Size uint16 // always 3
PCRMask [3]byte
Values tpmutil.U32Bytes
}
func (a *AKPublic) validate12Quote(quote Quote, pcrs []PCR, nonce []byte) error {
pub, ok := a.Public.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("unsupported public key type: %T", a.Public)
}
qHash := sha1.Sum(quote.Quote)
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA1, qHash[:], quote.Signature); err != nil {
return fmt.Errorf("invalid quote signature: %v", err)
}
var att rawAttestationData
if _, err := tpmutil.Unpack(quote.Quote, &att); err != nil {
return fmt.Errorf("parsing quote: %v", err)
}
// TODO(ericchiang): validate Version field.
if att.Nonce != sha1.Sum(nonce) {
return fmt.Errorf("invalid nonce")
}
if att.Fixed != fixedQuote {
return fmt.Errorf("quote wasn't a QUOT object: %x", att.Fixed)
}
// See 5.4.1 Creating a PCR composite hash
sort.Slice(pcrs, func(i, j int) bool { return pcrs[i].Index < pcrs[j].Index })
var (
pcrMask [3]byte // bitmap indicating which PCRs are active
values []byte // appended values of all PCRs
)
for _, pcr := range pcrs {
if pcr.Index < 0 || pcr.Index >= 24 {
return fmt.Errorf("invalid PCR index: %d", pcr.Index)
}
pcrMask[pcr.Index/8] |= 1 << uint(pcr.Index%8)
values = append(values, pcr.Digest...)
}
composite, err := tpmutil.Pack(rawPCRComposite{3, pcrMask, values})
if err != nil {
return fmt.Errorf("marshaling PCRs: %v", err)
}
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
}
func (a *AKPublic) validate20Quote(quote Quote, pcrs []PCR, nonce []byte) error {
sig, err := tpm2.DecodeSignature(bytes.NewBuffer(quote.Signature))
if err != nil {
return fmt.Errorf("parse quote signature: %v", err)
}
sigHash := a.Hash.New()
sigHash.Write(quote.Quote)
switch pub := a.Public.(type) {
case *rsa.PublicKey:
if sig.RSA == nil {
return fmt.Errorf("rsa public key provided for ec signature")
}
sigBytes := []byte(sig.RSA.Signature)
if err := rsa.VerifyPKCS1v15(pub, a.Hash, sigHash.Sum(nil), sigBytes); err != nil {
return fmt.Errorf("invalid quote signature: %v", err)
}
default:
// TODO(ericchiang): support ecdsa
return fmt.Errorf("unsupported public key type %T", pub)
}
att, err := tpm2.DecodeAttestationData(quote.Quote)
if err != nil {
return fmt.Errorf("parsing quote signature: %v", err)
}
if att.Type != tpm2.TagAttestQuote {
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 = %#v, want %#v", []byte(att.ExtraData), nonce)
}
pcrByIndex := map[int][]byte{}
pcrDigestAlg := HashAlg(att.AttestedQuoteInfo.PCRSelection.Hash).cryptoHash()
for _, pcr := range pcrs {
if pcr.DigestAlg == pcrDigestAlg {
pcrByIndex[pcr.Index] = pcr.Digest
}
}
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
}
func extend(pcr PCR, replay []byte, e rawEvent, locality byte) (pcrDigest []byte, eventDigest []byte, err error) {
h := pcr.DigestAlg
for _, digest := range e.digests {
if digest.hash != pcr.DigestAlg {
continue
}
if len(digest.data) != len(pcr.Digest) {
return nil, nil, fmt.Errorf("digest data length (%d) doesn't match PCR digest length (%d)", len(digest.data), len(pcr.Digest))
}
hash := h.New()
if len(replay) != 0 {
hash.Write(replay)
} else {
b := make([]byte, h.Size())
b[h.Size()-1] = locality
hash.Write(b)
}
hash.Write(digest.data)
return hash.Sum(nil), digest.data, nil
}
return nil, nil, fmt.Errorf("no event digest matches pcr algorithm: %v", pcr.DigestAlg)
}
// 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 possess an event digest with the specified algorithm.
func replayPCR(rawEvents []rawEvent, pcr PCR) ([]Event, bool) {
var (
replay []byte
outEvents []Event
locality byte
)
for _, e := range rawEvents {
if e.index != pcr.Index {
continue
}
// If TXT is enabled then the first event for PCR0
// should be a StartupLocality event. The final byte
// of this event indicates the locality from which
// TPM2_Startup() was issued. The initial value of
// PCR0 is equal to the locality.
if e.typ == eventTypeNoAction {
if pcr.Index == 0 && len(e.data) == 17 && strings.HasPrefix(string(e.data), "StartupLocality") {
locality = e.data[len(e.data)-1]
}
continue
}
replayValue, digest, err := extend(pcr, replay, e, locality)
if err != nil {
return nil, false
}
replay = replayValue
outEvents = append(outEvents, Event{sequence: e.sequence, Data: e.data, Digest: digest, Index: pcr.Index, Type: e.typ})
}
if len(outEvents) > 0 && !bytes.Equal(replay, pcr.Digest) {
return nil, false
}
return outEvents, true
}
type pcrReplayResult struct {
events []Event
successful bool
}
func replayEvents(rawEvents []rawEvent, pcrs []PCR) ([]Event, error) {
var (
invalidReplays []int
verifiedEvents []Event
allPCRReplays = map[int][]pcrReplayResult{}
)
// Replay the event log for every PCR and digest algorithm combination.
for _, pcr := range pcrs {
events, ok := replayPCR(rawEvents, pcr)
allPCRReplays[pcr.Index] = append(allPCRReplays[pcr.Index], pcrReplayResult{events, ok})
}
// Record PCR indices which do not have any successful replay. Record the
// events for a successful replay.
pcrLoop:
for i, replaysForPCR := range allPCRReplays {
for _, replay := range replaysForPCR {
if replay.successful {
// We consider the PCR verified at this stage: The replay of values with
// one digest algorithm matched a provided value.
// As such, we save the PCR's events, and proceed to the next PCR.
verifiedEvents = append(verifiedEvents, replay.events...)
continue pcrLoop
}
}
invalidReplays = append(invalidReplays, i)
}
if len(invalidReplays) > 0 {
events := make([]Event, 0, len(rawEvents))
for _, e := range rawEvents {
events = append(events, Event{e.sequence, e.index, e.typ, e.data, nil})
}
return nil, ReplayError{
Events: events,
InvalidPCRs: invalidReplays,
}
}
sort.Slice(verifiedEvents, func(i int, j int) bool {
return verifiedEvents[i].sequence < verifiedEvents[j].sequence
})
return verifiedEvents, nil
}
// EV_NO_ACTION is a special event type that indicates information to the parser
// instead of holding a measurement. For TPM 2.0, this event type is used to signal
// switching from SHA1 format to a variable length digest.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClientSpecPlat_TPM_2p0_1p04_pub.pdf#page=110
const eventTypeNoAction = 0x03
// ParseEventLog parses an unverified measurement log.
func ParseEventLog(measurementLog []byte) (*EventLog, error) {
var specID *specIDEvent
r := bytes.NewBuffer(measurementLog)
parseFn := parseRawEvent
var el EventLog
e, err := parseFn(r, specID)
if err != nil {
return nil, fmt.Errorf("parse first event: %v", err)
}
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")
}
// 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 guarantee that events have SHA256
// digests.
parseFn = parseRawEvent2
el.specIDEvent = specID
} else {
el.Algs = []HashAlg{HashSHA1}
el.rawEvents = append(el.rawEvents, e)
}
sequence := 1
for r.Len() != 0 {
e, err := parseFn(r, specID)
if err != nil {
return nil, err
}
e.sequence = sequence
sequence++
el.rawEvents = append(el.rawEvents, e)
}
return &el, nil
}
type specIDEvent struct {
algs []specAlgSize
}
type specAlgSize struct {
ID uint16
Size uint16
}
// Expected values for various Spec ID Event fields.
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=19
var wantSignature = [16]byte{0x53, 0x70,
0x65, 0x63, 0x20, 0x49,
0x44, 0x20, 0x45, 0x76,
0x65, 0x6e, 0x74, 0x30,
0x33, 0x00} // "Spec ID Event03\0"
const (
wantMajor = 2
wantMinor = 0
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 specIDEventHeader
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
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)
}
if header.VersionMajor != wantMajor {
return nil, fmt.Errorf("invalid spec major version, got %02x, wanted %02x",
header.VersionMajor, wantMajor)
}
if header.VersionMinor != wantMinor {
return nil, fmt.Errorf("invalid spec minor version, got %02x, wanted %02x",
header.VersionMajor, wantMinor)
}
// TODO(ericchiang): Check errata? Or do we expect that to change in ways
// we're okay with?
specAlg := specAlgSize{}
e := specIDEvent{}
for i := 0; i < int(header.NumAlgs); i++ {
if err := binary.Read(r, binary.LittleEndian, &specAlg); err != nil {
return nil, fmt.Errorf("reading algorithm: %v", err)
}
e.algs = append(e.algs, specAlg)
}
var vendorInfoSize uint8
if err := binary.Read(r, binary.LittleEndian, &vendorInfoSize); err != nil {
return nil, fmt.Errorf("reading vender info size: %v", err)
}
if r.Len() != int(vendorInfoSize) {
return nil, fmt.Errorf("reading vendor info, expected %d remaining bytes, got %d", vendorInfoSize, r.Len())
}
return &e, nil
}
type digest struct {
hash crypto.Hash
data []byte
}
type rawEvent struct {
sequence int
index int
typ EventType
data []byte
digests []digest
}
// TPM 1.2 event log format. See "5.1 SHA1 Event Log Entry Format"
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
type rawEventHeader struct {
PCRIndex uint32
Type uint32
Digest [20]byte
EventSize uint32
}
type eventSizeErr struct {
eventSize uint32
logSize int
}
func (e *eventSizeErr) Error() string {
return fmt.Sprintf("event data size (%d bytes) is greater than remaining measurement log (%d bytes)", e.eventSize, e.logSize)
}
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, fmt.Errorf("header deserialization error: %w", err)
}
if h.EventSize > uint32(r.Len()) {
return event, &eventSizeErr{h.EventSize, r.Len()}
}
data := make([]byte, int(h.EventSize))
if _, err := io.ReadFull(r, data); err != nil {
return event, fmt.Errorf("reading data error: %w", err)
}
digests := []digest{{hash: crypto.SHA1, data: h.Digest[:]}}
return rawEvent{
typ: EventType(h.Type),
data: data,
index: int(h.PCRIndex),
digests: digests,
}, nil
}
// TPM 2.0 event log format. See "5.2 Crypto Agile Log Entry Format"
// https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf#page=15
type rawEvent2Header struct {
PCRIndex uint32
Type uint32
}
func parseRawEvent2(r *bytes.Buffer, specID *specIDEvent) (event rawEvent, err error) {
var h rawEvent2Header
if err = binary.Read(r, binary.LittleEndian, &h); err != nil {
return event, err
}
event.typ = EventType(h.Type)
event.index = int(h.PCRIndex)
// parse the event digests
var numDigests uint32
if err := binary.Read(r, binary.LittleEndian, &numDigests); err != nil {
return event, err
}
for i := 0; i < int(numDigests); i++ {
var algID uint16
if err := binary.Read(r, binary.LittleEndian, &algID); err != nil {
return event, err
}
var digest digest
for _, alg := range specID.algs {
if alg.ID != algID {
continue
}
if r.Len() < int(alg.Size) {
return event, fmt.Errorf("reading digest: %v", io.ErrUnexpectedEOF)
}
digest.data = make([]byte, alg.Size)
digest.hash = HashAlg(alg.ID).cryptoHash()
}
if len(digest.data) == 0 {
return event, fmt.Errorf("unknown algorithm ID %x", algID)
}
if _, err := io.ReadFull(r, digest.data); err != nil {
return event, err
}
event.digests = append(event.digests, digest)
}
// parse event data
var eventSize uint32
if err = binary.Read(r, binary.LittleEndian, &eventSize); err != nil {
return event, err
}
if eventSize > uint32(r.Len()) {
return event, &eventSizeErr{eventSize, r.Len()}
}
event.data = make([]byte, int(eventSize))
if _, err := io.ReadFull(r, event.data); err != nil {
return event, err
}
return event, err
}
// AppendEvents takes a series of TPM 2.0 event logs and combines
// them into a single sequence of events with a single header.
//
// Additional logs must not use a digest algorithm which was not
// present in the original log.
func AppendEvents(base []byte, additional ...[]byte) ([]byte, error) {
baseLog, err := ParseEventLog(base)
if err != nil {
return nil, fmt.Errorf("base: %v", err)
}
if baseLog.specIDEvent == nil {
return nil, errors.New("tpm 1.2 event logs cannot be combined")
}
outBuff := make([]byte, len(base))
copy(outBuff, base)
out := bytes.NewBuffer(outBuff)
for i, l := range additional {
log, err := ParseEventLog(l)
if err != nil {
return nil, fmt.Errorf("log %d: %v", i, err)
}
if log.specIDEvent == nil {
return nil, fmt.Errorf("log %d: cannot use tpm 1.2 event log as a source", i)
}
algCheck:
for _, alg := range log.specIDEvent.algs {
for _, baseAlg := range baseLog.specIDEvent.algs {
if baseAlg == alg {
continue algCheck
}
}
return nil, fmt.Errorf("log %d: cannot use digest (%+v) not present in base log", i, alg)
}
for x, e := range log.rawEvents {
// Serialize header (PCR index, event type, number of digests)
binary.Write(out, binary.LittleEndian, rawEvent2Header{
PCRIndex: uint32(e.index),
Type: uint32(e.typ),
})
binary.Write(out, binary.LittleEndian, uint32(len(e.digests)))
// Serialize digests
for _, d := range e.digests {
var algID uint16
switch d.hash {
case crypto.SHA256:
algID = uint16(HashSHA256)
case crypto.SHA1:
algID = uint16(HashSHA1)
default:
return nil, fmt.Errorf("log %d: event %d: unhandled hash function %v", i, x, d.hash)
}
binary.Write(out, binary.LittleEndian, algID)
out.Write(d.data)
}
// Serialize event data
binary.Write(out, binary.LittleEndian, uint32(len(e.data)))
out.Write(e.data)
}
}
return out.Bytes(), nil
}
go-attestation-0.5.1/attest/eventlog_fuzz.go 0000664 0000000 0000000 00000001606 14523205536 0021223 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 gofuzz
// +build gofuzz
package attest
// FuzzParseEventLog is an exported entrypoint for fuzzers to test the eventlog
// parser. This method should not be used for any other purpose.
func FuzzParseEventLog(data []byte) int {
_, err := ParseEventLog(data)
if err != nil {
return 0
}
return 1
}
go-attestation-0.5.1/attest/eventlog_test.go 0000664 0000000 0000000 00000024707 14523205536 0021213 0 ustar 00root root 0000000 0000000 // Copyright 2019 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"
"encoding/base64"
"encoding/json"
"os"
"testing"
"github.com/google/go-tpm/legacy/tpm2"
)
// Dump describes the layout of serialized information from the dump command.
type Dump struct {
Static struct {
TPMVersion TPMVersion
EKPem []byte
}
AK AttestationParameters
Quote struct {
Nonce []byte
Alg HashAlg
Quote []byte
Signature []byte
}
Log struct {
PCRs []PCR
PCRAlg tpm2.Algorithm
Raw []byte // The measured boot log in binary form.
}
}
func TestParseEventLogWindows(t *testing.T) {
testParseEventLog(t, "testdata/windows_gcp_shielded_vm.json")
}
func TestParseEventLogLinux(t *testing.T) {
testParseEventLog(t, "testdata/linux_tpm12.json")
}
func testParseEventLog(t *testing.T, testdata string) {
data, err := os.ReadFile(testdata)
if err != nil {
t.Fatalf("reading test data: %v", err)
}
var dump Dump
if err := json.Unmarshal(data, &dump); err != nil {
t.Fatalf("parsing test data: %v", err)
}
if _, err := ParseEventLog(dump.Log.Raw); err != nil {
t.Fatalf("parsing event log: %v", err)
}
}
func TestParseCryptoAgileEventLog(t *testing.T) {
data, err := os.ReadFile("testdata/crypto_agile_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 TestEventLogLinux(t *testing.T) {
testEventLog(t, "testdata/linux_tpm12.json")
}
func TestEventLog(t *testing.T) {
testEventLog(t, "testdata/windows_gcp_shielded_vm.json")
}
func testEventLog(t *testing.T, testdata string) {
data, err := os.ReadFile(testdata)
if err != nil {
t.Fatalf("reading test data: %v", err)
}
var dump Dump
if err := json.Unmarshal(data, &dump); err != nil {
t.Fatalf("parsing test data: %v", err)
}
ak, err := ParseAKPublic(dump.Static.TPMVersion, dump.AK.Public)
if err != nil {
t.Fatalf("parsing AK: %v", err)
}
if err := ak.Verify(Quote{
Version: dump.Static.TPMVersion,
Quote: dump.Quote.Quote,
Signature: dump.Quote.Signature,
}, dump.Log.PCRs, dump.Quote.Nonce); err != nil {
t.Fatalf("verifying quote: %v", err)
}
el, err := ParseEventLog(dump.Log.Raw)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
events, err := el.Verify(dump.Log.PCRs)
if err != nil {
t.Fatalf("validating event log: %v", err)
}
for i, e := range events {
if e.sequence != i {
t.Errorf("event out of order: events[%d].sequence = %d, want %d", i, e.sequence, i)
}
}
}
func TestParseEventLogEventSizeTooLarge(t *testing.T) {
data := []byte{
// PCR index
0x30, 0x34, 0x39, 0x33,
// type
0x36, 0x30, 0x30, 0x32,
// Digest
0x31, 0x39, 0x36, 0x33, 0x39, 0x34, 0x34, 0x37, 0x39, 0x32,
0x31, 0x32, 0x32, 0x37, 0x39, 0x30, 0x34, 0x30, 0x31, 0x6d,
// Event size (3.183 GB)
0xbd, 0xbf, 0xef, 0x47,
// "event data"
0x00, 0x00, 0x00, 0x00,
}
// If this doesn't panic, the test passed
// TODO(ericchiang): use errors.As once go-attestation switches to Go 1.13.
_, err := ParseEventLog(data)
if err == nil {
t.Fatalf("expected parsing invalid event log to fail")
}
}
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
data []byte
want []uint16
wantErr bool
}{
{
name: "sha1",
data: append(
[]byte("Spec ID Event03"), 0x0,
0x0, 0x0, 0x0, 0x0, // platform class
0x0, // version minor
0x2, // version major
0x0, // errata
0x8, // uintn size
0x1, 0x0, 0x0, 0x0, // num algs
0x04, 0x0, // SHA1
0x14, 0x0, // size
0x2, // vendor info size
0x0, 0x0,
),
want: []uint16{0x0004},
},
{
name: "sha1_and_sha256",
data: append(
[]byte("Spec ID Event03"), 0x0,
0x0, 0x0, 0x0, 0x0, // platform class
0x0, // version minor
0x2, // version major
0x0, // errata
0x8, // uintn size
0x2, 0x0, 0x0, 0x0, // num algs
0x04, 0x0, // SHA1
0x14, 0x0, // size
0x0B, 0x0, // SHA256
0x20, 0x0, // size
0x2, // vendor info size
0x0, 0x0,
),
want: []uint16{0x0004, 0x000B},
},
{
name: "invalid_version",
data: append(
[]byte("Spec ID Event03"), 0x0,
0x0, 0x0, 0x0, 0x0, // platform class
0x2, // version minor
0x1, // version major
0x0, // errata
0x8, // uintn size
0x2, 0x0, 0x0, 0x0, // num algs
0x04, 0x0, // SHA1
0x14, 0x0, // size
0x0B, 0x0, // SHA256
0x20, 0x0, // size
0x2, // vendor info size
0x0, 0x0,
),
wantErr: true,
},
{
name: "malicious_number_of_algs",
data: append(
[]byte("Spec ID Event03"), 0x0,
0x0, 0x0, 0x0, 0x0, // platform class
0x0, // version minor
0x2, // version major
0x0, // errata
0x8, // uintn size
0xff, 0xff, 0xff, 0xff, // num algs
0x04, 0x0, // SHA1
0x14, 0x0, // size
0x2, // vendor info size
0x0, 0x0,
),
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
spec, err := parseSpecIDEvent(test.data)
var algs []uint16
if (err != nil) != test.wantErr {
t.Fatalf("parsing spec, wantErr=%t, got=%v", test.wantErr, err)
}
if err != nil {
return
}
algsEq := func(want, got []uint16) bool {
if len(got) != len(want) {
return false
}
for i, alg := range got {
if want[i] != alg {
return false
}
}
return true
}
for _, alg := range spec.algs {
algs = append(algs, alg.ID)
}
if !algsEq(test.want, algs) {
t.Errorf("algorithms, got=%x, want=%x", spec.algs, test.want)
}
})
}
}
func TestEBSVerifyWorkaround(t *testing.T) {
pcr5 := []PCR{
{
Index: 5,
Digest: []byte{
0x31, 0x24, 0x58, 0x08, 0xd6, 0xd3, 0x58, 0x49, 0xbc, 0x39,
0x4f, 0x63, 0x43, 0xf2, 0xb3, 0xff, 0x90, 0x8e, 0xd5, 0xe3,
},
DigestAlg: HashSHA1.cryptoHash(),
},
{
Index: 5,
Digest: []byte{
0x6c, 0xae, 0xa1, 0x23, 0xfa, 0x61, 0x11, 0x30, 0x5e, 0xe6, 0x24,
0xe4, 0x52, 0xe2, 0x69, 0xad, 0x14, 0xac, 0x52, 0x2a, 0xb8, 0xbf,
0x0c, 0x88, 0xe1, 0x16, 0x16, 0xde, 0x4c, 0x22, 0x2f, 0x7d,
},
DigestAlg: HashSHA256.cryptoHash(),
},
}
elr, err := os.ReadFile("testdata/ebs_event_missing_eventlog")
if err != nil {
t.Fatal(err)
}
el, err := ParseEventLog(elr)
if err != nil {
t.Fatalf("ParseEventLog() failed: %v", err)
}
if _, err := el.Verify(pcr5); err != nil {
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)
}
}
}
go-attestation-0.5.1/attest/eventlog_workarounds.go 0000664 0000000 0000000 00000004521 14523205536 0022602 0 ustar 00root root 0000000 0000000 // Copyright 2020 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
type elWorkaround struct {
id string
affectedPCR int
apply func(e *EventLog) error
}
// inject3 appends two new events into the event log.
func inject3(e *EventLog, pcr int, data1, data2, data3 string) error {
if err := inject(e, pcr, data1); err != nil {
return err
}
if err := inject(e, pcr, data2); err != nil {
return err
}
return inject(e, pcr, data3)
}
// inject2 appends two new events into the event log.
func inject2(e *EventLog, pcr int, data1, data2 string) error {
if err := inject(e, pcr, data1); err != nil {
return err
}
return inject(e, pcr, data2)
}
// inject appends a new event into the event log.
func inject(e *EventLog, pcr int, data string) error {
evt := rawEvent{
data: []byte(data),
index: pcr,
sequence: e.rawEvents[len(e.rawEvents)-1].sequence + 1,
}
for _, alg := range e.Algs {
h := alg.cryptoHash().New()
h.Write([]byte(data))
evt.digests = append(evt.digests, digest{hash: alg.cryptoHash(), data: h.Sum(nil)})
}
e.rawEvents = append(e.rawEvents, evt)
return nil
}
const (
ebsInvocation = "Exit Boot Services Invocation"
ebsSuccess = "Exit Boot Services Returned with Success"
ebsFailure = "Exit Boot Services Returned with Failure"
)
var eventlogWorkarounds = []elWorkaround{
{
id: "EBS Invocation + Success",
affectedPCR: 5,
apply: func(e *EventLog) error {
return inject2(e, 5, ebsInvocation, ebsSuccess)
},
},
{
id: "EBS Invocation + Failure",
affectedPCR: 5,
apply: func(e *EventLog) error {
return inject2(e, 5, ebsInvocation, ebsFailure)
},
},
{
id: "EBS Invocation + Failure + Success",
affectedPCR: 5,
apply: func(e *EventLog) error {
return inject3(e, 5, ebsInvocation, ebsFailure, ebsSuccess)
},
},
}
go-attestation-0.5.1/attest/example_test.go 0000664 0000000 0000000 00000011676 14523205536 0021024 0 ustar 00root root 0000000 0000000 package attest_test
import (
"crypto/subtle"
"flag"
"log"
"testing"
"github.com/google/go-attestation/attest"
)
var (
testExamples = flag.Bool("test-examples", false, "Enable tests for examples.")
)
func ExampleAK() {
tpm, err := attest.OpenTPM(nil)
if err != nil {
log.Fatalf("Failed to open the 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)
}
// Save a re-loadable representation to blob.
blob, err := ak.Marshal()
if err != nil {
log.Fatalf("Failed to marshal AK: %v", err)
}
// Close our handle to the AK.
if err := ak.Close(tpm); err != nil {
log.Fatalf("Failed to close AK: %v", err)
}
// Re-load the created AK from the blob.
ak, err = tpm.LoadAK(blob)
if err != nil {
log.Fatalf("Failed to load AK: %v", err)
}
if err := ak.Close(tpm); err != nil {
log.Fatalf("Failed to close AK: %v", err)
}
}
func ExampleAK_credentialActivation() {
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.
ek, err := tpm.EKs()
if err != nil {
log.Fatalf("Failed to enumerate EKs: %v", err)
}
// Read parameters necessary to generate a challenge.
ap := ak.AttestationParameters()
// Generate a credential activation challenge (usually done on the server).
activation := attest.ActivationParameters{
TPMVersion: tpm.Version(),
EK: ek[0].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.ActivateCredential(tpm, *challenge)
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 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) {
if !*testExamples {
t.SkipNow()
}
ExampleTPM_AttestPlatform()
}
func ExampleTPM_AttestPlatform() {
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)
// The nonce would typically be provided by the server.
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8}
// Perform an attestation against the state of the plaform. Usually, you
// would pass a nil config, and the event log would be read from the
// platform. To ensure this example runs on platforms without event logs,
// we pass a fake EventLog value.
att, err := tpm.AttestPlatform(ak, nonce, &attest.PlatformAttestConfig{
EventLog: []byte{0},
})
if err != nil {
log.Fatalf("Failed to attest the platform state: %v", err)
}
// Construct an AKPublic struct from the parameters of the key. This
// will be used to verify the quote signatures.
pub, err := attest.ParseAKPublic(tpm.Version(), ak.AttestationParameters().Public)
if err != nil {
log.Fatalf("Failed to parse AK public: %v", err)
}
for i, q := range att.Quotes {
if err := pub.Verify(q, att.PCRs, nonce); err != nil {
log.Fatalf("quote[%d] verification failed: %v", i, err)
}
}
}
go-attestation-0.5.1/attest/internal/ 0000775 0000000 0000000 00000000000 14523205536 0017604 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/attest/internal/events.go 0000664 0000000 0000000 00000045740 14523205536 0021451 0 ustar 00root root 0000000 0000000 package internal
import (
"bytes"
"crypto/x509"
"encoding/binary"
"errors"
"fmt"
"io"
"unicode/utf16"
)
const (
// maxNameLen is the maximum accepted byte length for a name field.
// This value should be larger than any reasonable value.
maxNameLen = 2048
// maxDataLen is the maximum size in bytes of a variable data field.
// This value should be larger than any reasonable value.
maxDataLen = 1024 * 1024 // 1 Megabyte.
)
// GUIDs representing the contents of an UEFI_SIGNATURE_LIST.
var (
hashSHA256SigGUID = efiGUID{0xc1c41626, 0x504c, 0x4092, [8]byte{0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28}}
hashSHA1SigGUID = efiGUID{0x826ca512, 0xcf10, 0x4ac9, [8]byte{0xb1, 0x87, 0xbe, 0x01, 0x49, 0x66, 0x31, 0xbd}}
hashSHA224SigGUID = efiGUID{0x0b6e5233, 0xa65c, 0x44c9, [8]byte{0x94, 0x07, 0xd9, 0xab, 0x83, 0xbf, 0xc8, 0xbd}}
hashSHA384SigGUID = efiGUID{0xff3e5307, 0x9fd0, 0x48c9, [8]byte{0x85, 0xf1, 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x01}}
hashSHA512SigGUID = efiGUID{0x093e0fae, 0xa6c4, 0x4f50, [8]byte{0x9f, 0x1b, 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a}}
keyRSA2048SigGUID = efiGUID{0x3c5766e8, 0x269c, 0x4e34, [8]byte{0xaa, 0x14, 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6}}
certRSA2048SHA256SigGUID = efiGUID{0xe2b36190, 0x879b, 0x4a3d, [8]byte{0xad, 0x8d, 0xf2, 0xe7, 0xbb, 0xa3, 0x27, 0x84}}
certRSA2048SHA1SigGUID = efiGUID{0x67f8444f, 0x8743, 0x48f1, [8]byte{0xa3, 0x28, 0x1e, 0xaa, 0xb8, 0x73, 0x60, 0x80}}
certX509SigGUID = efiGUID{0xa5c059a1, 0x94e4, 0x4aa7, [8]byte{0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72}}
certHashSHA256SigGUID = efiGUID{0x3bd2a492, 0x96c0, 0x4079, [8]byte{0xb4, 0x20, 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed}}
certHashSHA384SigGUID = efiGUID{0x7076876e, 0x80c2, 0x4ee6, [8]byte{0xaa, 0xd2, 0x28, 0xb3, 0x49, 0xa6, 0x86, 0x5b}}
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)
const (
PrebootCert EventType = 0x00000000
PostCode EventType = 0x00000001
unused EventType = 0x00000002
NoAction EventType = 0x00000003
Separator EventType = 0x00000004
Action EventType = 0x00000005
EventTag EventType = 0x00000006
SCRTMContents EventType = 0x00000007
SCRTMVersion EventType = 0x00000008
CpuMicrocode EventType = 0x00000009
PlatformConfigFlags EventType = 0x0000000A
TableOfDevices EventType = 0x0000000B
CompactHash EventType = 0x0000000C
Ipl EventType = 0x0000000D
IplPartitionData EventType = 0x0000000E
NonhostCode EventType = 0x0000000F
NonhostConfig EventType = 0x00000010
NonhostInfo EventType = 0x00000011
OmitBootDeviceEvents EventType = 0x00000012
)
// EFI Events (TCG EFI Platform Specification Version 1.22)
const (
EFIEventBase EventType = 0x80000000
EFIVariableDriverConfig EventType = 0x80000001
EFIVariableBoot EventType = 0x80000002
EFIBootServicesApplication EventType = 0x80000003
EFIBootServicesDriver EventType = 0x80000004
EFIRuntimeServicesDriver EventType = 0x80000005
EFIGPTEvent EventType = 0x80000006
EFIAction EventType = 0x80000007
EFIPlatformFirmwareBlob EventType = 0x80000008
EFIHandoffTables EventType = 0x80000009
EFIHCRTMEvent EventType = 0x80000010
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.
var ErrSigMissingGUID = errors.New("signature data was missing owner GUID")
var eventTypeNames = map[EventType]string{
PrebootCert: "Preboot Cert",
PostCode: "POST Code",
unused: "Unused",
NoAction: "No Action",
Separator: "Separator",
Action: "Action",
EventTag: "Event Tag",
SCRTMContents: "S-CRTM Contents",
SCRTMVersion: "S-CRTM Version",
CpuMicrocode: "CPU Microcode",
PlatformConfigFlags: "Platform Config Flags",
TableOfDevices: "Table of Devices",
CompactHash: "Compact Hash",
Ipl: "IPL",
IplPartitionData: "IPL Partition Data",
NonhostCode: "Non-Host Code",
NonhostConfig: "Non-HostConfig",
NonhostInfo: "Non-Host Info",
OmitBootDeviceEvents: "Omit Boot Device Events",
EFIEventBase: "EFI Event Base",
EFIVariableDriverConfig: "EFI Variable Driver Config",
EFIVariableBoot: "EFI Variable Boot",
EFIBootServicesApplication: "EFI Boot Services Application",
EFIBootServicesDriver: "EFI Boot Services Driver",
EFIRuntimeServicesDriver: "EFI Runtime Services Driver",
EFIGPTEvent: "EFI GPT Event",
EFIAction: "EFI Action",
EFIPlatformFirmwareBlob: "EFI Platform Firmware Blob",
EFIVariableAuthority: "EFI Variable Authority",
EFIHandoffTables: "EFI Handoff Tables",
EFIHCRTMEvent: "EFI H-CRTM Event",
}
// TaggedEventData represents the TCG_PCClientTaggedEventStruct structure,
// as defined by 11.3.2.1 in the "TCG PC Client Specific Implementation
// Specification for Conventional BIOS", version 1.21.
type TaggedEventData struct {
ID uint32
Data []byte
}
// ParseTaggedEventData parses a TCG_PCClientTaggedEventStruct structure.
func ParseTaggedEventData(d []byte) (*TaggedEventData, error) {
var (
r = bytes.NewReader(d)
header struct {
ID uint32
DataLen uint32
}
)
if err := binary.Read(r, binary.LittleEndian, &header); err != nil {
return nil, fmt.Errorf("reading header: %w", err)
}
if int(header.DataLen) > len(d) {
return nil, fmt.Errorf("tagged event len (%d bytes) larger than data length (%d bytes)", header.DataLen, len(d))
}
out := TaggedEventData{
ID: header.ID,
Data: make([]byte, header.DataLen),
}
return &out, binary.Read(r, binary.LittleEndian, &out.Data)
}
func (e EventType) String() string {
if s, ok := eventTypeNames[e]; ok {
return s
}
return fmt.Sprintf("EventType(0x%x)", uint32(e))
}
// UntrustedParseEventType returns the event type indicated by
// the provided value.
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) {
return EventType(0), fmt.Errorf("event type not between [0x0, 0x12] or [0x80000000, 0x800000FF]: got %#x", et)
}
if _, ok := eventTypeNames[EventType(et)]; !ok {
return EventType(0), fmt.Errorf("unknown event type %#x", et)
}
return EventType(et), nil
}
// efiGUID represents the EFI_GUID type.
// See section "2.3.1 Data Types" in the specification for more information.
// type efiGUID [16]byte
type efiGUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
func (d efiGUID) String() string {
var u [8]byte
binary.BigEndian.PutUint32(u[:4], d.Data1)
binary.BigEndian.PutUint16(u[4:6], d.Data2)
binary.BigEndian.PutUint16(u[6:8], d.Data3)
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], d.Data4[:2], d.Data4[2:])
}
// UEFIVariableDataHeader represents the leading fixed-size fields
// within UEFI_VARIABLE_DATA.
type UEFIVariableDataHeader struct {
VariableName efiGUID
UnicodeNameLength uint64 // uintN
VariableDataLength uint64 // uintN
}
// UEFIVariableData represents the UEFI_VARIABLE_DATA structure.
type UEFIVariableData struct {
Header UEFIVariableDataHeader
UnicodeName []uint16
VariableData []byte // []int8
}
// ParseUEFIVariableData parses the data section of an event structured as
// a UEFI variable.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100
func ParseUEFIVariableData(r io.Reader) (ret UEFIVariableData, err error) {
err = binary.Read(r, binary.LittleEndian, &ret.Header)
if err != nil {
return
}
if ret.Header.UnicodeNameLength > maxNameLen {
return UEFIVariableData{}, fmt.Errorf("unicode name too long: %d > %d", ret.Header.UnicodeNameLength, maxNameLen)
}
ret.UnicodeName = make([]uint16, ret.Header.UnicodeNameLength)
for i := 0; uint64(i) < ret.Header.UnicodeNameLength; i++ {
err = binary.Read(r, binary.LittleEndian, &ret.UnicodeName[i])
if err != nil {
return
}
}
if ret.Header.VariableDataLength > maxDataLen {
return UEFIVariableData{}, fmt.Errorf("variable data too long: %d > %d", ret.Header.VariableDataLength, maxDataLen)
}
ret.VariableData = make([]byte, ret.Header.VariableDataLength)
_, err = io.ReadFull(r, ret.VariableData)
return
}
func (v *UEFIVariableData) VarName() string {
return string(utf16.Decode(v.UnicodeName))
}
func (v *UEFIVariableData) SignatureData() (certs []x509.Certificate, hashes [][]byte, err error) {
return parseEfiSignatureList(v.VariableData)
}
// UEFIVariableAuthority describes the contents of a UEFI variable authority
// event.
type UEFIVariableAuthority struct {
Certs []x509.Certificate
}
// ParseUEFIVariableAuthority parses the data section of an event structured as
// a UEFI variable authority.
//
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1789
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 {
SignatureOwner efiGUID
SignatureData []byte // []int8
}
// efiSignatureList represents the EFI_SIGNATURE_LIST type.
// See section "31.4.1 Signature Database" in the specification for more information.
type efiSignatureListHeader struct {
SignatureType efiGUID
SignatureListSize uint32
SignatureHeaderSize uint32
SignatureSize uint32
}
type efiSignatureList struct {
Header efiSignatureListHeader
SignatureData []byte
Signatures []byte
}
// parseEfiSignatureList parses a EFI_SIGNATURE_LIST structure.
// The structure and related GUIDs are defined at:
// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1790
func parseEfiSignatureList(b []byte) ([]x509.Certificate, [][]byte, error) {
if len(b) < 28 {
// Being passed an empty signature list here appears to be valid
return nil, nil, nil
}
signatures := efiSignatureList{}
buf := bytes.NewReader(b)
certificates := []x509.Certificate{}
hashes := [][]byte{}
for buf.Len() > 0 {
err := binary.Read(buf, binary.LittleEndian, &signatures.Header)
if err != nil {
return nil, nil, err
}
if signatures.Header.SignatureHeaderSize > maxDataLen {
return nil, nil, fmt.Errorf("signature header too large: %d > %d", signatures.Header.SignatureHeaderSize, maxDataLen)
}
if signatures.Header.SignatureListSize > maxDataLen {
return nil, nil, fmt.Errorf("signature list too large: %d > %d", signatures.Header.SignatureListSize, maxDataLen)
}
signatureType := signatures.Header.SignatureType
switch signatureType {
case certX509SigGUID: // X509 certificate
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
signature := efiSignatureData{}
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
if err != nil {
return nil, nil, err
}
err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(signature.SignatureData)
if err != nil {
return nil, nil, err
}
sigOffset += int(signatures.Header.SignatureSize)
certificates = append(certificates, *cert)
}
case hashSHA256SigGUID: // SHA256
for sigOffset := 0; uint32(sigOffset) < signatures.Header.SignatureListSize-28; {
signature := efiSignatureData{}
signature.SignatureData = make([]byte, signatures.Header.SignatureSize-16)
err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner)
if err != nil {
return nil, nil, err
}
err = binary.Read(buf, binary.LittleEndian, &signature.SignatureData)
if err != nil {
return nil, nil, err
}
hashes = append(hashes, signature.SignatureData)
sigOffset += int(signatures.Header.SignatureSize)
}
case keyRSA2048SigGUID:
err = errors.New("unhandled RSA2048 key")
case certRSA2048SHA256SigGUID:
err = errors.New("unhandled RSA2048-SHA256 key")
case hashSHA1SigGUID:
err = errors.New("unhandled SHA1 hash")
case certRSA2048SHA1SigGUID:
err = errors.New("unhandled RSA2048-SHA1 key")
case hashSHA224SigGUID:
err = errors.New("unhandled SHA224 hash")
case hashSHA384SigGUID:
err = errors.New("unhandled SHA384 hash")
case hashSHA512SigGUID:
err = errors.New("unhandled SHA512 hash")
case certHashSHA256SigGUID:
err = errors.New("unhandled X509-SHA256 hash metadata")
case certHashSHA384SigGUID:
err = errors.New("unhandled X509-SHA384 hash metadata")
case certHashSHA512SigGUID:
err = errors.New("unhandled X509-SHA512 hash metadata")
default:
err = fmt.Errorf("unhandled signature type %s", signatureType)
}
if err != nil {
return nil, nil, err
}
}
return certificates, hashes, nil
}
// EFISignatureData represents the EFI_SIGNATURE_DATA type.
// See section "31.4.1 Signature Database" in the specification
// for more information.
type EFISignatureData struct {
SignatureOwner efiGUID
SignatureData []byte // []int8
}
func parseEfiSignature(b []byte) ([]x509.Certificate, error) {
certificates := []x509.Certificate{}
if len(b) < 16 {
return nil, fmt.Errorf("invalid signature: buffer smaller than header (%d < %d)", len(b), 16)
}
buf := bytes.NewReader(b)
signature := EFISignatureData{}
signature.SignatureData = make([]byte, len(b)-16)
if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureOwner); err != nil {
return certificates, err
}
if err := binary.Read(buf, binary.LittleEndian, &signature.SignatureData); err != nil {
return certificates, err
}
cert, err := x509.ParseCertificate(signature.SignatureData)
if err == nil {
certificates = append(certificates, *cert)
} 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.
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
}
go-attestation-0.5.1/attest/internal/events_test.go 0000664 0000000 0000000 00000002066 14523205536 0022502 0 ustar 00root root 0000000 0000000 package internal
import (
"bytes"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestParseUEFIVariableData(t *testing.T) {
data := []byte{0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0xd, 0x0, 0xe0, 0x98,
0x3, 0x2b, 0x8c, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x53, 0x0, 0x65, 0x0, 0x63, 0x0, 0x75, 0x0, 0x72, 0x0,
0x65, 0x0, 0x42, 0x0, 0x6f, 0x0, 0x6f, 0x0, 0x74, 0x0, 0x1}
want := UEFIVariableData{
Header: UEFIVariableDataHeader{
VariableName: efiGUID{Data1: 0x8be4df61, Data2: 0x93ca, Data3: 0x11d2, Data4: [8]uint8{0xaa, 0xd, 0x0, 0xe0, 0x98, 0x3, 0x2b, 0x8c}},
UnicodeNameLength: 0xa,
VariableDataLength: 0x1,
},
UnicodeName: []uint16{0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x42, 0x6f, 0x6f, 0x74},
VariableData: []uint8{0x1},
}
got, err := ParseUEFIVariableData(bytes.NewReader(data))
if err != nil {
t.Fatalf("ParseEFIVariableData() failed: %v", err)
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("ParseUEFIVariableData() mismatch (-want +got):\n%s", diff)
}
}
go-attestation-0.5.1/attest/key_linux.go 0000664 0000000 0000000 00000005352 14523205536 0020333 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 linux && !gofuzz && cgo && tspi
// +build linux,!gofuzz,cgo,tspi
package attest
import (
"fmt"
"github.com/google/go-tspi/attestation"
)
// trousersKey12 represents a key bound to a TPM 1.2 device via tcsd.
type trousersKey12 struct {
blob []byte
public []byte
}
func newTrousersKey12(blob, public []byte) ak {
return &trousersKey12{
blob: blob,
public: public,
}
}
// Marshal represents the key in a persistent format which may be
// loaded at a later time using tpm.LoadKey().
func (k *trousersKey12) marshal() ([]byte, error) {
out := serializedKey{
Encoding: keyEncodingEncrypted,
TPMVersion: TPMVersion12,
Blob: k.blob,
Public: k.public,
}
return out.Serialize()
}
func (k *trousersKey12) close(tpm tpmBase) error {
return nil // No state for tpm 1.2.
}
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)
}
cred, err := attestation.AIKChallengeResponse(t.ctx, k.blob, in.Credential, in.Secret)
if err != nil {
return nil, fmt.Errorf("failed to activate ak: %v", err)
}
return cred, nil
}
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)
}
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 {
return nil, fmt.Errorf("Quote() failed: %v", err)
}
return &Quote{
Version: TPMVersion12,
Quote: quote,
Signature: rawSig,
}, nil
}
func (k *trousersKey12) attestationParameters() AttestationParameters {
return AttestationParameters{
Public: k.public,
UseTCSDActivationFormat: true,
}
}
func (k *trousersKey12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) {
return nil, fmt.Errorf("not implemented")
}
go-attestation-0.5.1/attest/key_windows.go 0000664 0000000 0000000 00000013560 14523205536 0020666 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 windows
// +build windows
package attest
import (
"fmt"
"github.com/google/go-tpm/legacy/tpm2"
tpm1 "github.com/google/go-tpm/tpm"
)
// windowsKey12 represents a Windows-managed key on a TPM1.2 TPM.
type windowsKey12 struct {
hnd uintptr
pcpKeyName string
public []byte
}
func newWindowsKey12(hnd uintptr, pcpKeyName string, public []byte) ak {
return &windowsKey12{
hnd: hnd,
pcpKeyName: pcpKeyName,
public: public,
}
}
func (k *windowsKey12) marshal() ([]byte, error) {
out := serializedKey{
Encoding: keyEncodingOSManaged,
TPMVersion: TPMVersion12,
Name: k.pcpKeyName,
Public: k.public,
}
return out.Serialize()
}
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)
}
secretKey, err := tpm.pcp.ActivateCredential(k.hnd, in.Credential)
if err != nil {
return nil, err
}
return decryptCredential(secretKey, in.Secret)
}
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)
}
t, ok := tb.(*windowsTPM)
if !ok {
return nil, fmt.Errorf("expected *windowsTPM, got %T", tb)
}
tpmKeyHnd, 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)
}
sig, pcrc, err := tpm1.Quote(tpm, tpmKeyHnd, nonce, selectedPCRs[:], wellKnownAuth[:])
if err != nil {
return nil, fmt.Errorf("Quote() failed: %v", err)
}
// Construct and return TPM_QUOTE_INFO
// Returning TPM_QUOTE_INFO allows us to verify the Quote at a higher resolution
// and matches what go-tspi returns.
quote, err := tpm1.NewQuoteInfo(nonce, selectedPCRs[:], pcrc)
if err != nil {
return nil, fmt.Errorf("failed to construct Quote Info: %v", err)
}
return &Quote{
Version: TPMVersion12,
Quote: quote,
Signature: sig,
}, nil
}
func (k *windowsKey12) close(tpm tpmBase) error {
return closeNCryptObject(k.hnd)
}
func (k *windowsKey12) attestationParameters() AttestationParameters {
return AttestationParameters{
Public: k.public,
}
}
func (k *windowsKey12) certify(tb tpmBase, handle interface{}) (*CertificationParameters, error) {
return nil, fmt.Errorf("not implemented")
}
// windowsKey20 represents a key bound to a TPM 2.0.
type windowsKey20 struct {
hnd uintptr
pcpKeyName string
public []byte
createData []byte
createAttestation []byte
createSignature []byte
}
func newWindowsKey20(hnd uintptr, pcpKeyName string, public, createData, createAttest, createSig []byte) ak {
return &windowsKey20{
hnd: hnd,
pcpKeyName: pcpKeyName,
public: public,
createData: createData,
createAttestation: createAttest,
createSignature: createSig,
}
}
func (k *windowsKey20) marshal() ([]byte, error) {
out := serializedKey{
Encoding: keyEncodingOSManaged,
TPMVersion: TPMVersion20,
Name: k.pcpKeyName,
Public: k.public,
CreateData: k.createData,
CreateAttestation: k.createAttestation,
CreateSignature: k.createSignature,
}
return out.Serialize()
}
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)
}
return tpm.pcp.ActivateCredential(k.hnd, append(in.Credential, in.Secret...))
}
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)
}
tpmKeyHnd, 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)
}
return quote20(tpm, tpmKeyHnd, alg.goTPMAlg(), nonce, selectedPCRs)
}
func (k *windowsKey20) close(tpm tpmBase) error {
return closeNCryptObject(k.hnd)
}
func (k *windowsKey20) attestationParameters() AttestationParameters {
return AttestationParameters{
Public: k.public,
CreateData: k.createData,
CreateAttestation: k.createAttestation,
CreateSignature: k.createSignature,
}
}
func (k *windowsKey20) certify(tb tpmBase, handle interface{}) (*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, scheme)
}
go-attestation-0.5.1/attest/pcp_windows.go 0000664 0000000 0000000 00000060462 14523205536 0020663 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 windows
// +build windows
package attest
import (
"bytes"
"crypto/x509"
"encoding/binary"
"fmt"
"io"
"syscall"
"unsafe"
"github.com/google/go-tpm/tpmutil"
tpmtbs "github.com/google/go-tpm/tpmutil/tbs"
"golang.org/x/sys/windows"
)
const (
pcpProviderName = "Microsoft Platform Crypto Provider"
cryptENotFound = 0x80092004 // From winerror.h.
// The below is documented in this Microsoft whitepaper:
// https://github.com/Microsoft/TSS.MSR/blob/master/PCPTool.v11/Using%20the%20Windows%208%20Platform%20Crypto%20Provider%20and%20Associated%20TPM%20Functionality.pdf
ncryptOverwriteKeyFlag = 0x80
// Key usage value for AKs.
nCryptPropertyPCPKeyUsagePolicyIdentity = 0x8
)
// DLL references.
var (
nCrypt = windows.MustLoadDLL("ncrypt.dll")
nCryptOpenStorageProvider = nCrypt.MustFindProc("NCryptOpenStorageProvider")
nCryptFreeObject = nCrypt.MustFindProc("NCryptFreeObject")
nCryptGetProperty = nCrypt.MustFindProc("NCryptGetProperty")
nCryptSetProperty = nCrypt.MustFindProc("NCryptSetProperty")
nCryptOpenKey = nCrypt.MustFindProc("NCryptOpenKey")
nCryptCreatePersistedKey = nCrypt.MustFindProc("NCryptCreatePersistedKey")
nCryptFinalizeKey = nCrypt.MustFindProc("NCryptFinalizeKey")
nCryptDeleteKey = nCrypt.MustFindProc("NCryptDeleteKey")
crypt32 = windows.MustLoadDLL("crypt32.dll")
crypt32CertEnumCertificatesInStore = crypt32.MustFindProc("CertEnumCertificatesInStore")
crypt32CertCloseStore = crypt32.MustFindProc("CertCloseStore")
tbs *windows.DLL
tbsGetDeviceInfo *windows.Proc
)
// Error codes.
var (
isReadyErrors = map[uint32]string{
0x00000002: "Platform restart is required (shutdown).",
0x00000004: "Platform restart is required (reboot).",
0x00000008: "The TPM is already owned.",
0x00000010: "Physical presence is required to provision the TPM.",
0x00000020: "The TPM is disabled or deactivated.",
0x00000040: "TPM ownership was taken.",
0x00000080: "An endorsement key exists in the TPM.",
0x00000100: "The TPM owner authorization is not properly stored in the registry.",
0x00000200: "The Storage Root Key (SRK) authorization value is not all zeros.",
0x00000800: "The operating system's registry information about the TPM’s Storage Root Key does not match the TPM Storage Root Key.",
0x00001000: "The TPM permanent flag to allow reading of the Storage Root Key public value is not set.",
0x00002000: "The monotonic counter incremented during boot has not been created.",
0x00020000: "Windows Group Policy is configured to not store any TPM owner authorization so the TPM cannot be fully ready.",
0x00040000: "The EK Certificate was not read from the TPM NV Ram and stored in the registry.",
0x00080000: "The TCG event log is empty or cannot be read.",
0x00100000: "The TPM is not owned.",
0x00200000: "An error occurred, but not specific to a particular task.",
0x00400000: "The device lock counter has not been created.",
0x00800000: "The device identifier has not been created.",
}
tpmErrNums = map[uint32]string{
0x80280001: "TPM_E_AUTHFAIL",
0x80280002: "TPM_E_BADINDEX",
0x80280003: "TPM_E_BAD_PARAMETER",
0x80280004: "TPM_E_AUDITFAILURE",
0x80280005: "TPM_E_CLEAR_DISABLED",
0x80280006: "TPM_E_DEACTIVATED",
0x80280007: "TPM_E_DISABLED",
0x80280008: "TPM_E_DISABLED_CMD",
0x80280009: "TPM_E_FAIL",
0x8028000A: "TPM_E_BAD_ORDINAL",
0x8028000B: "TPM_E_INSTALL_DISABLED",
0x8028000C: "TPM_E_INVALID_KEYHANDLE",
0x8028000D: "TPM_E_KEYNOTFOUND",
0x8028000E: "TPM_E_INAPPROPRIATE_ENC",
0x8028000F: "TPM_E_MIGRATEFAIL",
0x80280010: "TPM_E_INVALID_PCR_INFO",
0x80280011: "TPM_E_NOSPACE",
0x80280012: "TPM_E_NOSRK",
0x80280013: "TPM_E_NOTSEALED_BLOB",
0x80280014: "TPM_E_OWNER_SET",
0x80280015: "TPM_E_RESOURCES",
0x80280016: "TPM_E_SHORTRANDOM",
0x80280017: "TPM_E_SIZE",
0x80280018: "TPM_E_WRONGPCRVAL",
0x80280019: "TPM_E_BAD_PARAM_SIZE",
0x8028001A: "TPM_E_SHA_THREAD",
0x8028001B: "TPM_E_SHA_ERROR",
0x8028001C: "TPM_E_FAILEDSELFTEST",
0x8028001D: "TPM_E_AUTH2FAIL",
0x8028001E: "TPM_E_BADTAG",
0x8028001F: "TPM_E_IOERROR",
0x80280020: "TPM_E_ENCRYPT_ERROR",
0x80280021: "TPM_E_DECRYPT_ERROR",
0x80280022: "TPM_E_INVALID_AUTHHANDLE",
0x80280023: "TPM_E_NO_ENDORSEMENT",
0x80280024: "TPM_E_INVALID_KEYUSAGE",
0x80280025: "TPM_E_WRONG_ENTITYTYPE",
0x80280026: "TPM_E_INVALID_POSTINIT",
0x80280027: "TPM_E_INAPPROPRIATE_SIG",
0x80280028: "TPM_E_BAD_KEY_PROPERTY",
0x80280029: "TPM_E_BAD_MIGRATION",
0x8028002A: "TPM_E_BAD_SCHEME",
0x8028002B: "TPM_E_BAD_DATASIZE",
0x8028002C: "TPM_E_BAD_MODE",
0x8028002D: "TPM_E_BAD_PRESENCE",
0x8028002E: "TPM_E_BAD_VERSION",
0x8028002F: "TPM_E_NO_WRAP_TRANSPORT",
0x80280030: "TPM_E_AUDITFAIL_UNSUCCESSFUL",
0x80280031: "TPM_E_AUDITFAIL_SUCCESSFUL",
0x80280032: "TPM_E_NOTRESETABLE",
0x80280033: "TPM_E_NOTLOCAL",
0x80280034: "TPM_E_BAD_TYPE",
0x80280035: "TPM_E_INVALID_RESOURCE",
0x80280036: "TPM_E_NOTFIPS",
0x80280037: "TPM_E_INVALID_FAMILY",
0x80280038: "TPM_E_NO_NV_PERMISSION",
0x80280039: "TPM_E_REQUIRES_SIGN",
0x8028003A: "TPM_E_KEY_NOTSUPPORTED",
0x8028003B: "TPM_E_AUTH_CONFLICT",
0x8028003C: "TPM_E_AREA_LOCKED",
// TODO: Finish NVRAM error codes.
0x80280049: "TPM_E_NOOPERATOR",
0x8028004A: "TPM_E_RESOURCEMISSING",
0x8028004B: "TPM_E_DELEGATE_LOCK",
0x8028004C: "TPM_E_DELEGATE_FAMILY",
0x8028004D: "TPM_E_DELEGATE_ADMIN",
0x8028004E: "TPM_E_TRANSPORT_NOTEXCLUSIVE",
0x8028004F: "TPM_E_OWNER_CONTROL",
0x80280050: "TPM_E_DAA_RESOURCES",
// TODO: Finish DAA error codes.
0x80280058: "TPM_E_BAD_HANDLE",
0x80280059: "TPM_E_BAD_DELEGATE",
0x8028005A: "TPM_E_BADCONTEXT",
0x8028005B: "TPM_E_TOOMANYCONTEXTS",
0x8028005C: "TPM_E_MA_TICKET_SIGNATURE",
0x8028005D: "TPM_E_MA_DESTINATION",
0x8028005E: "TPM_E_MA_SOURCE",
0x8028005F: "TPM_E_MA_AUTHORITY",
0x80280061: "TPM_E_PERMANENTEK",
0x80280062: "TPM_E_BAD_SIGNATURE",
0x80280063: "TPM_E_NOCONTEXTSPACE",
0x80280400: "TPM_E_COMMAND_BLOCKED",
0x80280401: "TPM_E_INVALID_HANDLE",
0x80280402: "TPM_E_DUPLICATE_VHANDLE",
0x80280403: "TPM_E_EMBEDDED_COMMAND_BLOCKED",
0x80280404: "TPM_E_EMBEDDED_COMMAND_UNSUPPORTED",
0x80280800: "TPM_E_RETRY",
0x80280801: "TPM_E_NEEDS_SELFTEST",
0x80280802: "TPM_E_DOING_SELFTEST",
0x80280803: "TPM_E_DEFEND_LOCK_RUNNING",
0x80284001: "TBS_E_INTERNAL_ERROR",
0x80284002: "TBS_E_BAD_PARAMETER",
0x80284003: "TBS_E_INVALID_OUTPUT_POINTER",
0x80284004: "TBS_E_INVALID_CONTEXT",
0x80284005: "TBS_E_INSUFFICIENT_BUFFER",
0x80284006: "TBS_E_IOERROR",
0x80284007: "TBS_E_INVALID_CONTEXT_PARAM",
0x80284008: "TBS_E_SERVICE_NOT_RUNNING",
0x80284009: "TBS_E_TOO_MANY_TBS_CONTEXTS",
0x8028400A: "TBS_E_TOO_MANY_RESOURCES",
0x8028400B: "TBS_E_SERVICE_START_PENDING",
0x8028400C: "TBS_E_PPI_NOT_SUPPORTED",
0x8028400D: "TBS_E_COMMAND_CANCELED",
0x8028400E: "TBS_E_BUFFER_TOO_LARGE",
0x8028400F: "TBS_E_TPM_NOT_FOUND",
0x80284010: "TBS_E_SERVICE_DISABLED",
0x80284011: "TBS_E_NO_EVENT_LOG",
0x80284012: "TBS_E_ACCESS_DENIED",
0x80284013: "TBS_E_PROVISIONING_NOT_ALLOWED",
0x80284014: "TBS_E_PPI_FUNCTION_UNSUPPORTED",
0x80284015: "TBS_E_OWNERAUTH_NOT_FOUND",
0x80284016: "TBS_E_PROVISIONING_INCOMPLETE",
// TODO: TPMAPI & TPMSIMP error codes.
0x80290401: "TPM_E_PCP_DEVICE_NOT_READY",
0x80290402: "TPM_E_PCP_INVALID_HANDLE",
0x80290403: "TPM_E_PCP_INVALID_PARAMETER",
0x80290404: "TPM_E_PCP_FLAG_NOT_SUPPORTED",
0x80290405: "TPM_E_PCP_NOT_SUPPORTED",
0x80290406: "TPM_E_PCP_BUFFER_TOO_SMALL",
0x80290407: "TPM_E_PCP_INTERNAL_ERROR",
0x80290408: "TPM_E_PCP_AUTHENTICATION_FAILED",
0x80290409: "TPM_E_PCP_AUTHENTICATION_IGNORED",
0x8029040A: "TPM_E_PCP_POLICY_NOT_FOUND",
0x8029040B: "TPM_E_PCP_PROFILE_NOT_FOUND",
0x8029040C: "TPM_E_PCP_VALIDATION_FAILED",
0x80090009: "NTE_BAD_FLAGS",
0x80090026: "NTE_INVALID_HANDLE",
0x80090027: "NTE_INVALID_PARAMETER",
0x80090029: "NTE_NOT_SUPPORTED",
}
)
func maybeWinErr(errNo uintptr) error {
if code, known := tpmErrNums[uint32(errNo)]; known {
return fmt.Errorf("tpm or subsystem failure: %s", code)
}
return nil
}
func utf16ToString(buf []byte) (string, error) {
b := make([]uint16, len(buf)/2)
// LPCSTR (Windows' representation of utf16) is always little endian.
if err := binary.Read(bytes.NewReader(buf), binary.LittleEndian, &b); err != nil {
return "", err
}
return windows.UTF16ToString(b), nil
}
// closeNCryptoObject is a helper to call NCryptFreeObject on a given handle.
func closeNCryptObject(hnd uintptr) error {
r, _, msg := nCryptFreeObject.Call(hnd)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
return tpmErr
}
return fmt.Errorf("NCryptFreeObject returned %X: %v", r, msg)
}
return nil
}
// getNCryptBufferProperty is a helper to read a byte slice from a NCrypt handle property
// using NCryptGetProperty.
func getNCryptBufferProperty(hnd uintptr, field string) ([]byte, error) {
var size uint32
wideField, err := windows.UTF16FromString(field)
if err != nil {
return nil, err
}
r, _, msg := nCryptGetProperty.Call(hnd, uintptr(unsafe.Pointer(&wideField[0])), 0, 0, uintptr(unsafe.Pointer(&size)), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return nil, fmt.Errorf("NCryptGetProperty returned %d,%X (%v) for key %q on size read", size, r, msg, field)
}
buff := make([]byte, size)
r, _, msg = nCryptGetProperty.Call(hnd, uintptr(unsafe.Pointer(&wideField[0])), uintptr(unsafe.Pointer(&buff[0])), uintptr(size), uintptr(unsafe.Pointer(&size)), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return nil, fmt.Errorf("NCryptGetProperty returned %X (%v) for key %q on data read", r, msg, field)
}
return buff, nil
}
// winPCP represents a reference to the Platform Crypto Provider.
type winPCP struct {
hProv uintptr
}
// tbsDeviceInfo represents TPM device information from the TBS
// API. This structure is identical to _TBS_DEVICE_INFO in tbs.h.
type tbsDeviceInfo struct {
TBSVersion uint32
TPMVersion uint32
TPMInterfaceType uint32
TPMImplementationRevision uint32
}
// windowsTPMInfo describes the versions of the TPM and OS interface code.
type windowsTPMInfo struct {
Manufacturer string
PCPVersion string
TBSInfo tbsDeviceInfo
}
// TPMInfo returns version information about the TPM & OS interface code.
func (h *winPCP) TPMInfo() (*windowsTPMInfo, error) {
var err error
out := &windowsTPMInfo{}
buf, err := getNCryptBufferProperty(h.hProv, "PCP_PLATFORM_TYPE")
if err != nil {
return nil, fmt.Errorf("Failed to read PCP_PLATFORM_TYPE: %v", err)
}
out.Manufacturer, err = utf16ToString(buf)
if err != nil {
return nil, err
}
buf, err = getNCryptBufferProperty(h.hProv, "PCP_PROVIDER_VERSION")
if err != nil {
return nil, fmt.Errorf("Failed to read PCP_PROVIDER_VERSION: %v", err)
}
out.PCPVersion, err = utf16ToString(buf)
if err != nil {
return nil, err
}
r, _, msg := tbsGetDeviceInfo.Call(unsafe.Sizeof(out.TBSInfo), uintptr(unsafe.Pointer(&out.TBSInfo)))
if r != 0 {
return nil, fmt.Errorf("Failed to call Tbsi_GetDeviceInfo: %v", msg)
}
return out, nil
}
// TPMCommandInterface returns an interface where TPM commands can issued directly.
func (h *winPCP) TPMCommandInterface() (io.ReadWriteCloser, error) {
var provTBS tpmtbs.Context
var sz uint32
platformHndField, err := windows.UTF16FromString("PCP_PLATFORMHANDLE")
if err != nil {
return nil, err
}
r, _, err := nCryptGetProperty.Call(h.hProv, uintptr(unsafe.Pointer(&platformHndField[0])), uintptr(unsafe.Pointer(&provTBS)), unsafe.Sizeof(provTBS), uintptr(unsafe.Pointer(&sz)), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
err = tpmErr
}
return nil, fmt.Errorf("NCryptGetProperty for platform handle returned %X (%v)", r, err)
}
return tpmutil.FromContext(provTBS), nil
}
// TPMKeyHandle returns a transient handle to the given key on the TPM.
func (h *winPCP) TPMKeyHandle(hnd uintptr) (tpmutil.Handle, error) {
var keyHndTBS tpmutil.Handle
var sz uint32
platformHndField, err := windows.UTF16FromString("PCP_PLATFORMHANDLE")
if err != nil {
return 0, err
}
if r, _, err := nCryptGetProperty.Call(hnd, uintptr(unsafe.Pointer(&platformHndField[0])), uintptr(unsafe.Pointer(&keyHndTBS)), unsafe.Sizeof(keyHndTBS), uintptr(unsafe.Pointer(&sz)), 0); r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
err = tpmErr
}
return 0, fmt.Errorf("NCryptGetProperty for hKey platform handle returned %X (%v)", r, err)
}
return keyHndTBS, nil
}
// Close releases all resources managed by the Handle.
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.
func (h *winPCP) DeleteKey(kh uintptr) error {
r, _, msg := nCryptDeleteKey.Call(kh, 0)
if r != 0 {
return fmt.Errorf("nCryptDeleteKey returned %X: %v", r, msg)
}
return nil
}
// EKCerts returns the Endorsement Certificates.
// Failure to fetch an ECC certificate is not considered
// an error as they do not exist on all platforms.
func (h *winPCP) EKCerts() ([]*x509.Certificate, error) {
c, err := getPCPCerts(h.hProv, "PCP_RSA_EKNVCERT")
if err != nil {
return nil, err
}
eccCerts, err := getPCPCerts(h.hProv, "PCP_ECC_EKNVCERT")
if err == nil { // ECC certs are not present on all platforms
c = append(c, eccCerts...)
}
// Reading the certificate from the system store has failed.
// Lets try reading the raw bytes directly from NVRAM instead.
if len(c) == 0 {
certs, err := getPCPCerts(h.hProv, "PCP_EKNVCERT")
if err != nil {
return nil, fmt.Errorf("Failed to read PCP_EKNVCERT: %v", err)
}
c = append(c, certs...)
}
var out []*x509.Certificate
for _, der := range c {
cert, err := ParseEKCertificate(der)
if err != nil {
return nil, err
}
out = append(out, cert)
}
return out, nil
}
// getPCPCerts is a helper to iterate over a certificates in a cert store,
// whose handle was obtained by reading a specific property on a PCP handle.
func getPCPCerts(hProv uintptr, propertyName string) ([][]byte, error) {
var size, cryptCertHnd uintptr
utf16PropName, err := windows.UTF16FromString(propertyName)
if err != nil {
return nil, err
}
r, _, msg := nCryptGetProperty.Call(hProv, uintptr(unsafe.Pointer(&utf16PropName[0])), uintptr(unsafe.Pointer(&cryptCertHnd)), 8, uintptr(unsafe.Pointer(&size)), 0)
if r != 0 {
return nil, fmt.Errorf("NCryptGetProperty returned %X, %v", r, msg)
}
defer crypt32CertCloseStore.Call(uintptr(unsafe.Pointer(cryptCertHnd)), 0)
var out [][]byte
var certContext uintptr
for {
certContext, _, msg = crypt32CertEnumCertificatesInStore.Call(uintptr(unsafe.Pointer(cryptCertHnd)), certContext)
if certContext == 0 && msg != nil {
if errno, ok := msg.(syscall.Errno); ok {
// cryptENotFound is returned when there are no more certificates to iterate through.
if errno == cryptENotFound {
break
}
}
return nil, msg
}
cert := (*syscall.CertContext)(unsafe.Pointer(certContext))
// Copy the buffer. This was taken straight from the Go source: src/crypto/x509/root_windows.go#L70
buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
buf2 := make([]byte, cert.Length)
copy(buf2, buf)
out = append(out, buf2)
}
return out, nil
}
// NewAK creates a persistent attestation key of the specified name.
func (h *winPCP) NewAK(name string) (uintptr, error) {
var kh uintptr
utf16Name, err := windows.UTF16FromString(name)
if err != nil {
return 0, err
}
utf16RSA, err := windows.UTF16FromString("RSA")
if err != nil {
return 0, err
}
// Create a persistent RSA key of the specified name.
r, _, msg := nCryptCreatePersistedKey.Call(h.hProv, uintptr(unsafe.Pointer(&kh)), uintptr(unsafe.Pointer(&utf16RSA[0])), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return 0, fmt.Errorf("NCryptCreatePersistedKey returned %X: %v", r, msg)
}
// Specify generated key length to be 2048 bits.
utf16Length, err := windows.UTF16FromString("Length")
if err != nil {
return 0, err
}
var length uint32 = 2048
r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16Length[0])), uintptr(unsafe.Pointer(&length)), unsafe.Sizeof(length), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return 0, fmt.Errorf("NCryptSetProperty (Length) returned %X: %v", r, msg)
}
// Specify the generated key can only be used for identity attestation.
utf16KeyPolicy, err := windows.UTF16FromString("PCP_KEY_USAGE_POLICY")
if err != nil {
return 0, err
}
var policy uint32 = nCryptPropertyPCPKeyUsagePolicyIdentity
r, _, msg = nCryptSetProperty.Call(kh, uintptr(unsafe.Pointer(&utf16KeyPolicy[0])), uintptr(unsafe.Pointer(&policy)), unsafe.Sizeof(policy), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return 0, fmt.Errorf("NCryptSetProperty (PCP KeyUsage Policy) returned %X: %v", r, msg)
}
// Finalize (create) the key.
r, _, msg = nCryptFinalizeKey.Call(kh, 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return 0, fmt.Errorf("NCryptFinalizeKey returned %X: %v", r, msg)
}
return kh, nil
}
// EKPub returns a BCRYPT_RSA_BLOB structure representing the EK.
func (h *winPCP) EKPub() ([]byte, error) {
return getNCryptBufferProperty(h.hProv, "PCP_EKPUB")
}
type akProps struct {
RawPublic []byte
RawCreationData []byte
RawAttest []byte
RawSignature []byte
}
// AKProperties returns the binding properties of the given attestation
// key. Note that it is only valid to call this function with the same
// winPCP handle within which the AK was created.
func (h *winPCP) AKProperties(kh uintptr) (*akProps, error) {
idBlob, err := getNCryptBufferProperty(kh, "PCP_TPM12_IDBINDING")
if err != nil {
return nil, err
}
r := bytes.NewReader(idBlob)
// Because the TPM 1.2 blob leads with a version tag,
// we can switch decoding logic based on it.
if bytes.Equal(idBlob[0:4], []byte{1, 1, 0, 0}) {
return decodeAKProps12(r)
}
return decodeAKProps20(r)
}
// decodeAKProps12 separates the single TPM 1.2 blob from the PCP property
// into its constituents, returning information about the public key
// of the AK.
func decodeAKProps12(r *bytes.Reader) (*akProps, error) {
var out akProps
// Skip over fixed-size fields in TPM_IDENTITY_CONTENTS which
// we don't need to read.
// Specifically: ver, ordinal, & labelPrivCADigest.
r.Seek(4+4+20, io.SeekCurrent)
pubKeyStartIdx := int(r.Size()) - r.Len()
// Skip over fixed-size key parameters in TPM_PUBKEY, so
// we can read the length of the exponent &
// determine where the pubkey structure ends.
// Specifically: algID, encScheme, sigScheme, paramSize, keyLength,
// and numPrimes.
r.Seek(4+2+2+4+4+4, io.SeekCurrent)
// Read the size of the exponent section.
var exponentSize uint32
if err := binary.Read(r, binary.BigEndian, &exponentSize); err != nil {
return nil, fmt.Errorf("failed to decode exponentSize: %v", err)
}
// Consume the bytes representing the exponent.
exp := make([]byte, int(exponentSize))
if err := binary.Read(r, binary.BigEndian, &exp); err != nil {
return nil, fmt.Errorf("failed to decode exp: %v", err)
}
// Read the size of the key data.
var keyDataSize uint32
if err := binary.Read(r, binary.BigEndian, &keyDataSize); err != nil {
return nil, fmt.Errorf("failed to decode keyDataSize: %v", err)
}
// Seek to the end of the key data.
r.Seek(int64(keyDataSize), io.SeekCurrent)
// Read the trailing signature.
out.RawSignature = make([]byte, r.Len())
if err := binary.Read(r, binary.BigEndian, &out.RawSignature); err != nil {
return nil, fmt.Errorf("failed to decode signature: %v", err)
}
// Seek back to the location of the public key, and consume it.
r.Seek(int64(pubKeyStartIdx), io.SeekStart)
out.RawPublic = make([]byte, 24+int(exponentSize)+4+int(keyDataSize))
if err := binary.Read(r, binary.BigEndian, &out.RawPublic); err != nil {
return nil, fmt.Errorf("failed to decode public: %v", err)
}
return &out, nil
}
// decodeAKProps20 separates the single TPM 2.0 blob from the PCP property
// into its constituents. For TPM 2.0 devices, these are bytes representing
// the following structures: TPM2B_PUBLIC, TPM2B_CREATION_DATA, TPM2B_ATTEST,
// and TPMT_SIGNATURE.
func decodeAKProps20(r *bytes.Reader) (*akProps, error) {
var out akProps
var publicSize uint16
if err := binary.Read(r, binary.BigEndian, &publicSize); err != nil {
return nil, fmt.Errorf("failed to decode TPM2B_PUBLIC.size: %v", err)
}
out.RawPublic = make([]byte, publicSize)
if err := binary.Read(r, binary.BigEndian, &out.RawPublic); err != nil {
return nil, fmt.Errorf("failed to decode TPM2B_PUBLIC.data: %v", err)
}
var creationDataSize uint16
if err := binary.Read(r, binary.BigEndian, &creationDataSize); err != nil {
return nil, fmt.Errorf("failed to decode TPM2B_CREATION_DATA.size: %v", err)
}
out.RawCreationData = make([]byte, creationDataSize)
if err := binary.Read(r, binary.BigEndian, &out.RawCreationData); err != nil {
return nil, fmt.Errorf("failed to decode TPM2B_CREATION_DATA.data: %v", err)
}
var attestSize uint16
if err := binary.Read(r, binary.BigEndian, &attestSize); err != nil {
return nil, fmt.Errorf("failed to decode TPM2B_ATTEST.size: %v", err)
}
out.RawAttest = make([]byte, attestSize)
if err := binary.Read(r, binary.BigEndian, &out.RawAttest); err != nil {
return nil, fmt.Errorf("failed to decode TPM2B_ATTEST.data: %v", err)
}
// The encoded TPMT_SIGNATURE structure represents the remaining bytes in
// the ID binding blob.
out.RawSignature = make([]byte, r.Len())
if err := binary.Read(r, binary.BigEndian, &out.RawSignature); err != nil {
return nil, fmt.Errorf("failed to decode TPMT_SIGNATURE.data: %v", err)
}
return &out, nil
}
// LoadKeyByName returns a handle to the persistent PCP key with the specified
// name.
func (h *winPCP) LoadKeyByName(name string) (uintptr, error) {
utf16Name, err := windows.UTF16FromString(name)
if err != nil {
return 0, err
}
var hKey uintptr
r, _, msg := nCryptOpenKey.Call(h.hProv, uintptr(unsafe.Pointer(&hKey)), uintptr(unsafe.Pointer(&utf16Name[0])), 0, 0)
if r != 0 {
return 0, msg
}
return hKey, nil
}
// ActivateCredential performs TPM2_ActivateCredential or TPM_ActivateIdentity.
func (h *winPCP) ActivateCredential(hKey uintptr, activationBlob []byte) ([]byte, error) {
utf16ActivationStr, err := windows.UTF16FromString("PCP_TPM12_IDACTIVATION")
if err != nil {
return nil, err
}
r, _, msg := nCryptSetProperty.Call(hKey, uintptr(unsafe.Pointer(&utf16ActivationStr[0])), uintptr(unsafe.Pointer(&activationBlob[0])), uintptr(len(activationBlob)), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return nil, fmt.Errorf("NCryptSetProperty returned %X (%v) for key activation", r, msg)
}
secretBuff := make([]byte, 256)
var size uint32
r, _, msg = nCryptGetProperty.Call(hKey, uintptr(unsafe.Pointer(&utf16ActivationStr[0])), uintptr(unsafe.Pointer(&secretBuff[0])), uintptr(len(secretBuff)), uintptr(unsafe.Pointer(&size)), 0)
if r != 0 {
if tpmErr := maybeWinErr(r); tpmErr != nil {
msg = tpmErr
}
return nil, fmt.Errorf("NCryptGetProperty returned %X (%v) for key activation", r, msg)
}
return secretBuff[:size], nil
}
// openPCP initializes a reference to the Microsoft PCP provider.
// The Caller is expected to call Close() when they are done.
func openPCP() (*winPCP, error) {
var err error
var h winPCP
pname, err := windows.UTF16FromString(pcpProviderName)
if err != nil {
return nil, err
}
r, _, err := nCryptOpenStorageProvider.Call(uintptr(unsafe.Pointer(&h.hProv)), uintptr(unsafe.Pointer(&pname[0])), 0)
if r != 0 { // r is non-zero on error, err is always populated in this case.
if tpmErr := maybeWinErr(r); tpmErr != nil {
return nil, tpmErr
}
return nil, err
}
return &h, nil
}
go-attestation-0.5.1/attest/secureboot.go 0000664 0000000 0000000 00000025572 14523205536 0020504 0 ustar 00root root 0000000 0000000 // Copyright 2020 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/x509"
"errors"
"fmt"
"github.com/google/go-attestation/attest/internal"
)
// SecurebootState describes the secure boot status of a machine, as determined
// by processing its event log.
type SecurebootState struct {
Enabled bool
// PlatformKeys enumerates keys which can sign a key exchange key.
PlatformKeys []x509.Certificate
// PlatformKeys enumerates key hashes which can sign a key exchange key.
PlatformKeyHashes [][]byte
// ExchangeKeys enumerates keys which can sign a database of permitted or
// forbidden keys.
ExchangeKeys []x509.Certificate
// ExchangeKeyHashes enumerates key hashes which can sign a database or
// permitted or forbidden keys.
ExchangeKeyHashes [][]byte
// PermittedKeys enumerates keys which may sign binaries to run.
PermittedKeys []x509.Certificate
// PermittedHashes enumerates hashes which permit binaries to run.
PermittedHashes [][]byte
// ForbiddenKeys enumerates keys which must not permit a binary to run.
ForbiddenKeys []x509.Certificate
// ForbiddenKeys enumerates hashes which must not permit a binary to run.
ForbiddenHashes [][]byte
// PreSeparatorAuthority describes the use of a secure-boot key to authorize
// the execution of a binary before the separator.
PreSeparatorAuthority []x509.Certificate
// 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
// in such a way that it may have been tampered post-execution of
// platform firmware.
func ParseSecurebootState(events []Event) (*SecurebootState, error) {
// This algorithm verifies the following:
// - All events in PCR 7 have event types which are expected in PCR 7.
// - All events are parsable according to their event type.
// - All events have digests values corresponding to their data/event type.
// - No unverifiable events were present.
// - All variables are specified before the separator and never duplicated.
// - The SecureBoot variable has a value of 0 or 1.
// - If SecureBoot was 1 (enabled), authority events were present indicating
// keys were used to perform verification.
// - If SecureBoot was 1 (enabled), platform + exchange + database keys
// were specified.
// - No UEFI debugger was attached.
var (
out SecurebootState
seenSeparator7 bool
seenSeparator2 bool
seenAuthority bool
seenVars = map[string]bool{}
driverSources [][]internal.EFIDevicePathElement
)
for _, e := range events {
if e.Index != 7 && e.Index != 2 {
continue
}
et, err := internal.UntrustedParseEventType(uint32(e.Type))
if err != nil {
return nil, fmt.Errorf("unrecognised event type: %v", err)
}
digestVerify := e.digestEquals(e.Data)
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.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 {
return nil, fmt.Errorf("invalid digest for EFI Action 'DMA Protection Disabled' on event %d: %v", e.sequence, digestVerify)
}
out.DMAProtectionDisabled = true
default:
return nil, fmt.Errorf("event %d: unexpected EFI action event", e.sequence)
}
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)
}
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
}
if !seenAuthority {
return nil, errors.New("secure boot was enabled but no key was used")
}
if len(out.PlatformKeys) == 0 && len(out.PlatformKeyHashes) == 0 {
return nil, errors.New("secure boot was enabled but no platform keys were known")
}
if len(out.ExchangeKeys) == 0 && len(out.ExchangeKeyHashes) == 0 {
return nil, errors.New("secure boot was enabled but no key exchange keys were known")
}
if len(out.PermittedKeys) == 0 && len(out.PermittedHashes) == 0 {
return nil, errors.New("secure boot was enabled but no keys or hashes were permitted")
}
return &out, nil
}
go-attestation-0.5.1/attest/secureboot_test.go 0000664 0000000 0000000 00000025706 14523205536 0021542 0 ustar 00root root 0000000 0000000 // Copyright 2020 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 (
"encoding/base64"
"encoding/json"
"os"
"testing"
)
func TestSecureBoot(t *testing.T) {
data, err := os.ReadFile("testdata/windows_gcp_shielded_vm.json")
if err != nil {
t.Fatalf("reading test data: %v", err)
}
var dump Dump
if err := json.Unmarshal(data, &dump); err != nil {
t.Fatalf("parsing test data: %v", err)
}
el, err := ParseEventLog(dump.Log.Raw)
if err != nil {
t.Fatalf("parsing event log: %v", err)
}
events, err := el.Verify(dump.Log.PCRs)
if err != nil {
t.Fatalf("validating event log: %v", err)
}
sbState, err := ParseSecurebootState(events)
if err != nil {
t.Fatalf("ExtractSecurebootState() failed: %v", err)
}
if got, want := sbState.Enabled, true; got != want {
t.Errorf("secureboot.Enabled = %v, want %v", got, want)
}
}
// See: https://github.com/google/go-attestation/issues/157
func TestSecureBootBug157(t *testing.T) {
raw, err := os.ReadFile("testdata/sb_cert_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', []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)
if err != nil {
t.Errorf("failed to verify log: %v", err)
}
sbs, err := ParseSecurebootState(events)
if err != nil {
t.Errorf("failed parsing secureboot state: %v", err)
}
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)
}
}
go-attestation-0.5.1/attest/storage.go 0000664 0000000 0000000 00000004522 14523205536 0017766 0 ustar 00root root 0000000 0000000 // Copyright 2019 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 (
"encoding/json"
"fmt"
)
// serializedKey represents a loadable, TPM-backed key.
type serializedKey struct {
// Encoding describes the strategy by which the key should be
// loaded/unloaded.
Encoding keyEncoding `json:"KeyEncoding"`
// TPMVersion describes the version of the TPM which the key was generated
// on. deserializeKey() returns an error if it attempts to deserialize a key
// which is from a different TPM version to the currently opened TPM.
TPMVersion TPMVersion
// Public represents the public key, in a TPM-specific format. This
// field is populated on all platforms and TPM versions.
Public []byte
// The following fields are only valid for TPM 2.0 hardware, holding
// information returned as the result to a TPM2_CertifyCreation command.
// These are stored alongside the key for later use, as the certification
// can only be obtained immediately after the key is generated.
CreateData []byte
CreateAttestation []byte
CreateSignature []byte
// Name is only valid for KeyEncodingOSManaged, which is only used
// on Windows.
Name string
// Blob represents the key material for KeyEncodingEncrypted keys. This
// is only used on Linux.
Blob []byte `json:"KeyBlob"`
}
// Serialize represents the key in a persistent format which may be
// loaded at a later time using deserializeKey().
func (k *serializedKey) Serialize() ([]byte, error) {
return json.Marshal(k)
}
func deserializeKey(b []byte, version TPMVersion) (*serializedKey, error) {
var k serializedKey
var err error
if err = json.Unmarshal(b, &k); err != nil {
return nil, fmt.Errorf("json.Unmarshal() failed: %v", err)
}
if k.TPMVersion != version {
return nil, fmt.Errorf("key for different TPM version: %v", k.TPMVersion)
}
return &k, nil
}
go-attestation-0.5.1/attest/testdata/ 0000775 0000000 0000000 00000000000 14523205536 0017601 5 ustar 00root root 0000000 0000000 go-attestation-0.5.1/attest/testdata/coreos_36_shielded_vm_no_secure_boot_eventlog 0000664 0000000 0000000 00000074527 14523205536 0030700 0 ustar 00root root 0000000 0000000 ) Spec ID Event03 0 ?p‹Û¯ò fUµ@6GL Ðüñ2¨ûõ¤á¥Œ×MÒ5}çP;[jýZy‰©Ž¾ m±‚.BÏ’4ö§ŠÅËIô›ÁÄ9?71a!‹¶ßŠ÷¦ŒÎ¦‚ae‰¿ c0 G C E V i r t u a l F i r m w a r e v 1 +líÑca—»Á¯ª€ÌnÍ> jÉ$H¨WU¦;Íe¹öÕrn’]Èi»F”(Å gã,8%ž¤€’4ÌϽ'…Ã+Þˆ(3»mö½˜šIôVcæ<á™üÐP, GCE NonHostInfo € WÍMÁ”BGZ¨'CHO;ªˆáB¸ Z¨'ÛÌûDÒžÏÚV½êb†
”¾Õ·¢{ºMØ Ï¤âÆõrb{ðmViÌ*±ƒXÒ{E¼cnÁ ϯ·@øG¦§K^®Ö·32ì5 aßä‹Ê“Òª
à˜+Œ
S e c u r e B o o t € Z½”«ó>4§›=“ÓPçBØìØ Û»ã—fX…eÅ̘¢®¶äJ‘xÉñ“[ÒAóƒrD„» §cU<–wÓ¥æøàÁïÍòUZ÷Sú:jþCë{*
ô¦øØäÝE–hó°àJ aßä‹Ê“Òª
à˜+Œ & P K ¡YÀ¥ä”§J‡µ«\+ðr&
ÒúÒˆ¤G—’[ªG»‰0‚ö0‚Þ ÕJègƒ]ê0
*†H†÷
010Unewpk0
180821215115Z
180920215115Z010Unewpk0‚"0
*†H†÷
‚ 0‚
‚ ̹Õ|ømkcêÉbô“Éþã|,EÎ…RRH|ûC&¿
¥‰°òÝÇ6èi•ªŒoТ#o4ÂO±žkÆú¸”ÁœŽp‡‘BÎi!“}ÚuŸ¿1 PÁï€#û¾;Vã
×Gê0¯Ô]©c‰¢ü9á–¡‡Ã=&²Ó¢lìÈ—×ÎÚ겕<³ÎŠÏÚ:Qˆ?%°K>ÌÝùÚÛQÕ‘š¡½ˆâñ´ñ³ ’·ãgæŒjÜ'?ÜÔNlÁ½ïÞV*ÚY` yÓ©|WýQa7}ä·n•ðR›CžÜH<ͼ9Ÿ£èG
F¸6Ü’³•¨ÎJãF#XK £S0Q0U˜P„Bó/Û—‚'(—t1F^`ËÅ0U#0€˜P„Bó/Û—‚'(—t1F^`ËÅ0Uÿ0ÿ0
*†H†÷
‚ Š¡R?ªç½°$«‹2°Iý{4Ë;€v`=ŽÜÊkiÂ[”踜Œ,×¼¬}‚YõÄ.¢l¥ì˜ð1#6øË0ƒg::+b!`,ºõ.ù(nÊfpX(†gi„l 'X%œPép¸`nT…J¯mE¦[4ü&M<á»$.yàÑȬ|Ù8GCk3PWãsž¦‡ÝÃâJüs{K ‡Çƒ 6Zêi‡@„,”€/=®=ËpÄ5¹>q²£ÆØ$?}`«o îzšÏuŸèL«ÍQ‡hX3²Ù¼8%*˜KEûg3›%œ?¬¤CDÌÍÓÈná € ðPy¶ÌBé.èZtÙÂviÀâ b&GØ[Šd}-.f‚Áb {l¦·"ZfWÂVµ‚ À §¦@“íy΋V—=ÞöÚ‘¿
ëW’³ÈBB7Bµ)C¥‹ß#(¤4“~2xˆ> aßä‹Ê“Òª
à˜+Œ K E K ¡YÀ¥ä”§J‡µ«\+ðr ü ÒúÒˆ¤G—’[ªG»‰0‚è0‚Ð
a
ш 0
*†H†÷
0‘10 UUS10U
Washington10URedmond10U
Microsoft Corporation1;09U2Microsoft Corporation Third Party Marketplace Root0
110624204129Z
260624205129Z0€10 UUS10U
Washington10URedmond10U
Microsoft Corporation1*0(U!Microsoft Corporation KEK CA 20110‚"0
*†H†÷
‚ 0‚
‚ Ä赊¿W&°&ÃêçûWzD]
ÚJåt*æ°ìmëì¹ãZc2|Oã§8“ŽÆõà„±š›,çõ·‘Ö áâÀ¨¬0ßHóPšd§QÈ…O †Îþ/áŸÿ‚ÀíéÍÎôSjb:C¹â%ýþùÔÄ«â#‰p·¤Mì®åœúÂ×ÁËÔèÄ/å™î$‹ìò‹êÃJûC~µG’lÜæ‰ëõ3ë*qåùƒ<ÿ% /hvFÿºO¾Üq*XªûÒy=ä›e;Ì)*ŸürY¢ë®’ïö5€Æìä_ÌvÍïc’Á¯y@„y‡ãR¨è{i £‚O0‚K0 +‚7 0UbüCÍ >¤ËgÒ[ÙU¬{̶Š_0 +‚7
S u b C A0U†0Uÿ0ÿ0U#0€EfRCá~X¿ÖNž#U;:"j¨0\UU0S0Q O M†Khttp://crl.microsoft.com/pki/crl/products/MicCorThiParMarRoo_2010-10-05.crl0`+T0R0P+0†Dhttp://www.microsoft.com/pki/certs/MicCorThiParMarRoo_2010-10-05.crt0
*†H†÷
‚ Ô„ˆõ”Ê*<û*’× ÑñèRf¨î¢µuz ª-¤vZêy·¹7jQ{döádòg¾÷¨x½ºÎˆXdÖWÈ£_ÖÛÆÐiÎHK2·ë]Ò0õÀõ¸ºx£+þ›Û4V„ì‚Ê®A%pœkéþ×–å甲*Kÿ(){÷×|¥Ñv¹Èyí’œÂþßo~l{ÔÁEÝ4Q–9å^VØ–ô¦B³ wýòqVÌŸ†#¤‡Ë¦ýX~Ôig‘~òå
‹Š<‡„ëãνCå-„“Žj+Z|DúRªÈ-»àRß øš=Á`°á3µ£ˆÑe
ç¬|¤Á‚‡N8±/
Ňoý.¼9¶çæÃàäÍ'„ï”Bï)‹FA;gØùCYeË
¼ý ’Oôu;§©$üPA@yà-O
j'vnRí–i{¯÷‡ÐEÂSû0ª76aÚJi4ØhíÖÏl”ÓÏl"y±ð¼¢F`©ÄÂ!‚ñýòèy2`¿Ø¬¥"KÊÁØKë}?W5²æOu´°`"S®‘yÖ›A†Tp²Þ
5|°4rº—`;ðy뢲]¢¸‡Åéöµ—%o8Ÿã‘úŠy˜Ãi·£ —øÊ® ×ÄóÀuk4 µ™`ó\°ÅWN6Ò2„¿ž € ¢œ'û¢a€`³"ÇÉr bº8Ã„Š”bù‡tņéÙTç)!³¥%A$¶62̯Z 5 Íbº¾öúà[î|<å(ó(yÓwŒ6ù»ñêóbB:؛Ȧ’ƒ(!Åü7k ˲×:=–E£¼ÚÐgeo G d b ¡YÀ¥ä”§J‡µ«\+ðr@ $ ÒúÒˆ¤G—’[ªG»‰0‚0‚ø
aÓÄ 0
*†H†÷
0‘10 UUS10U
Washington10URedmond10U
Microsoft Corporation1;09U2Microsoft Corporation Third Party Marketplace Root0
110627212245Z
260627213245Z010 UUS10U
Washington10URedmond10U
Microsoft Corporation1+0)U"Microsoft Corporation UEFI CA 20110‚"0
*†H†÷
‚ 0‚
‚ ¥lLÇE jK¤À‡uCTdàí’}²s¿
ÆJEa Å-–Óõ+ ûMI›A€<¹Týæ¼ÑĤŠAŠ\Yƒh2»ŒGÉîq¼!OšŠ|ÿD?2²&H®uµîÉLJ~䂚xwM°½öÓÓ¼ú+¥Q8]õûºÛxÛÿì
–Õƒ¸é¶À{@{á('ÉúïV^æ~”~ÀðD²y9åÚ²b‹M¿8pâh$É3¤7ÕXi^Ó|íÁSçN°*‡caocYê²+y×agŠ[ý^‡º†gOqX"""΋ïTq ÎP5Xv•îj±¢Õ £‚v0‚r0 +‚7 0# +‚7øÁk·wSJó%7N¡&{ p€0U¿C ½‚pœŒÕO1nÕ"˜ŠÔ0 +‚7
S u b C A0U†0Uÿ0ÿ0U#0€EfRCá~X¿ÖNž#U;:"j¨0\UU0S0Q O M†Khttp://crl.microsoft.com/pki/crl/products/MicCorThiParMarRoo_2010-10-05.crl0`+T0R0P+0†Dhttp://www.microsoft.com/pki/certs/MicCorThiParMarRoo_2010-10-05.crt0
*†H†÷
‚ 5Bÿ0ÌÎ÷vhX5)F2v'|ïA'BJªm8HYUóéX4¦‚ª]‚Ú€ƒA´ò¹ó]ñPù³U„B( ½²®QÅÀ¬—•!Ûüwž•s‘ˆÊ½½R¹P
ßWž aí
åm%Ù@@ÈΣJÂM¯šT½Ç¼¹+=I+2üj!iO›È~B4ü6‹ @À³š%u'ÍÉ£ö]Ñç6Tz¹PµÓÑ¿»tßÜ€Õíô/k/ÞfŒ°#åÇ„ØíêÁ3‚VK-ñh•ÍÏðrð®»Ý†…˜,!L3+ðJðh‡µ’U2u¡j‚j<£%¤í×®ËØ@Y „Ñ•Lb‘"tŒ=GD¦ä°›45±ú¶S¨,ì¤qȸºèDfäGTŽV³Ÿ˜²†Ðh>#µ/^P…Æ‚_A¡ô.
à™Òluä¶iµ!†úÑöâMÑÚ,wS%27ÇlRr•†°ñ5ajõ²;PV¦2-þ¢‰ùB†'U¡‚ÊZ›ø0˜T¦G–%/È&äA”\?å–ã…[<>?»GrUâ%"±Ù{ç*£÷FÃ
Ö‰ã5'bq¦ïÐ' Y7`ø8”¸àxpøºL†‡”öà®Eîe¶£~iu’›õ¦¼YƒX¡YÀ¥ä”§J‡µ«\+ðr ë ÒúÒˆ¤G—’[ªG»‰0‚×0‚¿
avV 0
*†H†÷
0ˆ10 UUS10U
Washington10URedmond10U
Microsoft Corporation1200U)Microsoft Root Certificate Authority 20100
111019184142Z
261019185142Z0„10 UUS10U
Washington10URedmond10U
Microsoft Corporation1.0,U%Microsoft Windows Production PCA 20110‚"0
*†H†÷
‚ 0‚
‚ Ý»¢ä. ãçÅ÷–i¼ !½i33ïËT€îƒ»Å „Ù÷Ò‹ó8°«¤-|byÿãJ?5 pãÄçkàœÀ6uéŠ1ÝpåÜ7µtF–([‡`#,¿ÜG¥g÷Q'žrë¦É¹;S5|åÓì'¹‡þ¹É# o¨F‘Án–