Initial commit.

This commit is contained in:
Tom 2019-03-28 13:21:16 -07:00
commit 21c2bfd1dc
12 changed files with 2577 additions and 0 deletions

28
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,28 @@
# 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 <https://cla.developers.google.com/> 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/).

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
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.

12
README.md Normal file
View File

@ -0,0 +1,12 @@
Go-Attestation
==============
Go-Attestation abstracts remote attestation operations across a variety of platforms
and TPMs.
## Status
Go-Attestation is under active development and **is not** ready for production use. Expect
API changes at any time.
Please note that this is not an official Google product.

198
attest/attest.go Normal file
View File

@ -0,0 +1,198 @@
// 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"
"errors"
"github.com/google/certificate-transparency-go/x509"
)
// 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
)
// 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
}
// KeyEncoding indicates how an exported TPM key is represented.
type KeyEncoding uint8
// 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
)
// KeyPurpose indicates the intended use of the key. It is implied that
// the key was created with usage restrictions to constrain its use
// to the given purpose.
type KeyPurpose uint8
// Key purposes.
const (
AttestationKey KeyPurpose = iota
StorageKey
)
// MintOptions encapsulates parameters for minting keys. This type is defined
// now (despite being empty) for future interface compatibility.
type MintOptions struct {
}
// 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
}
// PlatformEK represents a burned-in Endorsement Key, and its
// corrresponding EKCert (where present).
type PlatformEK struct {
Cert *x509.Certificate
Public crypto.PublicKey
}
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
}
// 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
}
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
}

View File

@ -0,0 +1,231 @@
// 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"
"testing"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm/tpm2/credactivation"
"github.com/google/go-tpm/tpm2"
"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)
}
return tpm, &TPM{
version: TPMVersion20,
interf: TPMInterfaceKernelManaged,
sysPath: "/dev/tpmrm0",
rwc: tpm,
}
}
func TestEK(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].Cert == nil && eks[0].Public == nil) {
t.Errorf("EKs() = %v, want at least 1 EK with populated fields", eks)
}
}
func TestInfo(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
info, err := tpm.Info()
if err != nil {
t.Errorf("tpm.Info() failed: %v", err)
}
// We dont expect anything to be meaningfully populated as this is a simulator.
t.Logf("TPM Info = %+v", info)
}
func TestAIKCreateAndLoad(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK() failed: %v", err)
}
enc, err := aik.Marshal()
if err != nil {
aik.Close(tpm)
t.Fatalf("aik.Marshal() failed: %v", err)
}
if err := aik.Close(tpm); err != nil {
t.Fatalf("aik.Close() failed: %v", err)
}
loaded, err := tpm.LoadKey(enc)
if err != nil {
t.Fatalf("LoadKey() failed: %v", err)
}
defer loaded.Close(tpm)
if !bytes.Equal(loaded.Public, aik.Public) {
t.Error("Original & loaded AIK public blobs did not match.")
t.Logf("Original = %v", aik.Public)
t.Logf("Loaded = %v", loaded.Public)
}
}
// chooseEK selects the EK public which will be activated against.
func chooseEK(t *testing.T, eks []PlatformEK) crypto.PublicKey {
t.Helper()
for _, ek := range eks {
if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA {
return ek.Cert.PublicKey.(*rsa.PublicKey)
} else if ek.Public != nil {
return ek.Public
}
}
t.Skip("No suitable RSA EK found")
return nil
}
func TestActivateCredentialTPM20(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK() failed: %v", err)
}
defer aik.Close(tpm)
EKs, err := tpm.EKs()
if err != nil {
t.Fatalf("EKs() failed: %v", err)
}
ek := chooseEK(t, EKs)
att, err := tpm2.DecodeAttestationData(aik.CreateAttestation)
if err != nil {
t.Fatalf("tpm2.DecodeAttestationData() failed: %v", err)
}
secret := []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}
id, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, ek, 16, secret)
if err != nil {
t.Fatalf("credactivation.Generate() failed: %v", err)
}
decryptedSecret, err := aik.ActivateCredential(tpm, EncryptedCredential{
Credential: id,
Secret: encSecret,
})
if err != nil {
t.Errorf("aik.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 TestQuoteTPM20(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK() failed: %v", err)
}
defer aik.Close(tpm)
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8}
quote, err := aik.Quote(tpm, nonce, tpm2.AlgSHA256)
if err != nil {
t.Fatalf("aik.Quote() failed: %v", err)
}
// TODO(jsonp): Parse quote structure once gotpm/tpm2 supports it.
if quote == nil {
t.Error("quote was nil, want *Quote")
}
}
func TestPCRsTPM20(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
PCRs, alg, err := tpm.PCRs()
if err != nil {
t.Fatalf("PCRs() failed: %v", err)
}
if len(PCRs) != 24 {
t.Errorf("len(PCRs) = %d, want %d", len(PCRs), 24)
}
if got, want := tpm2.AlgSHA256, alg; got != want {
t.Errorf("alg = %v, want %v", got, want)
}
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 TestPersistence(t *testing.T) {
sim, tpm := setupSimulatedTPM(t)
defer sim.Close()
ekHnd, p, err := tpm.getPrimaryKeyHandle(commonEkEquivalentHandle)
if err != nil {
t.Fatalf("getPrimaryKeyHandle() failed: %v", err)
}
if ekHnd != commonEkEquivalentHandle {
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, commonEkEquivalentHandle)
}
if p {
t.Logf("generated a new key")
} else {
t.Logf("used existing key")
}
ekHnd, p, err = tpm.getPrimaryKeyHandle(commonEkEquivalentHandle)
if err != nil {
t.Fatalf("second getPrimaryKeyHandle() failed: %v", err)
}
if ekHnd != commonEkEquivalentHandle {
t.Fatalf("bad EK-equivalent handle: got 0x%x, wanted 0x%x", ekHnd, commonEkEquivalentHandle)
}
if p {
t.Fatalf("generated a new key the second time; that shouldn't happen")
}
}

224
attest/attest_test.go Normal file
View File

@ -0,0 +1,224 @@
// 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"
"testing"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm/tpm2/credactivation"
"github.com/google/go-tpm/tpm2"
)
func TestOpen(t *testing.T) {
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) {
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) {
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 TestAIKCreateAndLoad(t *testing.T) {
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK() failed: %v", err)
}
enc, err := aik.Marshal()
if err != nil {
aik.Close(tpm)
t.Fatalf("aik.Marshal() failed: %v", err)
}
if err := aik.Close(tpm); err != nil {
t.Fatalf("aik.Close() failed: %v", err)
}
loaded, err := tpm.LoadKey(enc)
if err != nil {
t.Fatalf("LoadKey() failed: %v", err)
}
defer loaded.Close(tpm)
if !bytes.Equal(loaded.Public, aik.Public) {
t.Error("Original & loaded AIK public blobs did not match.")
t.Logf("Original = %v", aik.Public)
t.Logf("Loaded = %v", loaded.Public)
}
}
// chooseEK selects the EK public which will be activated against.
func chooseEK(t *testing.T, eks []PlatformEK) crypto.PublicKey {
t.Helper()
for _, ek := range eks {
if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA {
return ek.Cert.PublicKey.(*rsa.PublicKey)
} else if ek.Public != nil {
return ek.Public
}
}
t.Skip("No suitable RSA EK found")
return nil
}
func TestActivateCredentialTPM20(t *testing.T) {
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
if tpm.version != TPMVersion20 {
t.Skip("N/A for non-TPM2.0 TPMs")
}
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK() failed: %v", err)
}
defer aik.Close(tpm)
EKs, err := tpm.EKs()
if err != nil {
t.Fatalf("EKs() failed: %v", err)
}
ek := chooseEK(t, EKs)
att, err := tpm2.DecodeAttestationData(aik.CreateAttestation)
if err != nil {
t.Fatalf("tpm2.DecodeAttestationData() failed: %v", err)
}
secret := []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}
id, encSecret, err := credactivation.Generate(att.AttestedCreationInfo.Name.Digest, ek, 16, secret)
if err != nil {
t.Fatalf("credactivation.Generate() failed: %v", err)
}
decryptedSecret, err := aik.ActivateCredential(tpm, EncryptedCredential{
Credential: id,
Secret: encSecret,
})
if err != nil {
t.Errorf("aik.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 TestQuoteTPM20(t *testing.T) {
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
if tpm.version != TPMVersion20 {
t.Skip("N/A for non-TPM2.0 TPMs")
}
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK() failed: %v", err)
}
defer aik.Close(tpm)
nonce := []byte{1, 2, 3, 4, 5, 6, 7, 8}
quote, err := aik.Quote(tpm, nonce, tpm2.AlgSHA1)
if err != nil {
t.Fatalf("aik.Quote() failed: %v", err)
}
// TODO(jsonp): Parse quote structure once gotpm/tpm2 supports it.
if quote == nil {
t.Error("quote was nil, want *Quote")
}
}
func TestPCRsTPM20(t *testing.T) {
tpm, err := OpenTPM(nil)
if err != nil {
t.Fatalf("OpenTPM() failed: %v", err)
}
defer tpm.Close()
if tpm.version != TPMVersion20 {
t.Skip("N/A for non-TPM2.0 TPMs")
}
PCRs, _, err := tpm.PCRs()
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.SHA1 {
t.Errorf("pcr.DigestAlg = %v, expected crypto.SHA1", pcr.DigestAlg)
}
}
}

199
attest/attest_tpm12_test.go Normal file
View File

@ -0,0 +1,199 @@
// 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"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tspi/verification"
)
var (
testTPM12 = flag.Bool("testTPM12", false, "run tests for TPM1.2")
tpm12config = &OpenConfig{TPMVersion12}
)
func TestTPM12Info(t *testing.T) {
if !*testTPM12 {
t.SkipNow()
}
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("Failed to open tpm 1.2: %v", err)
}
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) {
if !*testTPM12 {
t.SkipNow()
}
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("Failed to open tpm 1.2: %v", err)
}
defer tpm.Close()
PCRs, _, err := tpm.PCRs()
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) {
if !*testTPM12 {
t.SkipNow()
}
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("Failed to open tpm 1.2: %v", err)
}
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")
}
t.Logf("EKCert Raw: %x\n", EKs[0].Cert.Raw)
}
func TestMintAIK(t *testing.T) {
if !*testTPM12 {
t.SkipNow()
}
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("failed to open tpm 1.2: %v", err)
}
defer tpm.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK failed: %v", err)
}
if (aik.TPMVersion != TPMVersion12) ||
(aik.Purpose != AttestationKey) {
t.Error("aik does not match expected format")
}
t.Logf("aik blob: %x\naik pubkey: %x\n", aik.KeyBlob, aik.Public)
}
func TestTPMQuote(t *testing.T) {
if !*testTPM12 {
t.SkipNow()
}
nonce := make([]byte, 20)
rand.Read(nonce)
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("Failed to open tpm 1.2: %v", err)
}
defer tpm.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK failed: %v", err)
}
quote, err := aik.Quote(tpm, nonce, tpm2.AlgSHA1)
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 chooseEK(t *testing.T, eks []PlatformEK) []byte {
t.Helper()
for _, ek := range eks {
if ek.Cert != nil && ek.Cert.PublicKeyAlgorithm == x509.RSA || ek.Cert.PublicKeyAlgorithm == x509.RSAESOAEP {
return ek.Cert.Raw
}
}
t.Skip("No suitable RSA EK found")
return nil
}
func TestTPMActivateCredential(t *testing.T) {
if !*testTPM12 {
t.SkipNow()
}
var challenge EncryptedCredential
nonce := make([]byte, 20)
rand.Read(nonce)
tpm, err := OpenTPM(tpm12config)
if err != nil {
t.Fatalf("failed to open tpm 1.2: %v", err)
}
defer tpm.Close()
aik, err := tpm.MintAIK(nil)
if err != nil {
t.Fatalf("MintAIK failed: %v", err)
}
EKs, err := tpm.EKs()
if err != nil {
t.Fatalf("failed to read EKs: %v", err)
}
ekcert := chooseEK(t, EKs)
challenge.Credential, challenge.Secret, err = verification.GenerateChallenge(ekcert, aik.Public, nonce)
if err != nil {
t.Fatalf("GenerateChallenge failed: %v", err)
}
validation, err := aik.ActivateCredential(tpm, challenge)
if err != nil {
t.Fatalf("ActivateCredential failed: %v", err)
}
t.Logf("validation: %x", validation)
}

409
attest/pcp_windows.go Normal file
View File

@ -0,0 +1,409 @@
// 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.
// +build windows
package attest
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"syscall"
"unsafe"
"github.com/google/certificate-transparency-go/x509"
"golang.org/x/sys/windows"
tpmtbs "github.com/google/go-tpm/tpmutil/tbs"
"github.com/google/go-tpm/tpmutil"
)
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 AIKs.
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")
crypt32 = windows.MustLoadDLL("crypt32.dll")
crypt32CertEnumCertificatesInStore = crypt32.MustFindProc("CertEnumCertificatesInStore")
crypt32CertCloseStore = crypt32.MustFindProc("CertCloseStore")
tbs = windows.MustLoadDLL("Tbs.dll")
tbsGetDeviceInfo = tbs.MustFindProc("Tbsi_GetDeviceInfo")
)
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 {
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 {
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 {
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 {
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 {
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)
}
// 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...)
}
var out []*x509.Certificate
for _, der := range c {
cert, err := x509.ParseCertificate(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
}
// MintAIK creates a persistent attestation key of the specified name.
func (h *winPCP) MintAIK(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 {
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 {
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 {
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 {
return 0, fmt.Errorf("NCryptFinalizeKey returned %X: %v", r, msg)
}
return kh, nil
}
type aikProps struct {
RawPublic []byte
RawCreationData []byte
RawAttest []byte
RawSignature []byte
}
// AIKProperties 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 AIK was created.
func (h *winPCP) AIKProperties(kh uintptr) (*aikProps, error) {
idBlob, err := getNCryptBufferProperty(kh, "PCP_TPM12_IDBINDING")
if err != nil {
return nil, err
}
r := bytes.NewReader(idBlob)
var out aikProps
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 {
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 {
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.
return nil, err
}
return &h, nil
}

207
attest/tpm.go Normal file
View File

@ -0,0 +1,207 @@
// 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 (
"crypto"
"encoding/asn1"
"fmt"
"io"
"math/big"
"strings"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil"
)
const (
tpmPtManufacturer = 0x00000100 + 5 // PT_FIXED + offset of 5
tpmPtVendorString = 0x00000100 + 6 // PT_FIXED + offset of 6
// Defined in "Registry of reserved TPM 2.0 handles and localities".
nvramCertIndex = 0x1c00002
// Defined in "Registry of reserved TPM 2.0 handles and localities", and checked on a glinux machine.
commonSrkEquivalentHandle = 0x81000001
commonEkEquivalentHandle = 0x81010001
)
var (
aikTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagSignerDefault | tpm2.FlagNoDA,
RSAParameters: &tpm2.RSAParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: tpm2.AlgSHA256,
},
KeyBits: 2048,
Modulus: big.NewInt(0),
},
}
// Default EK template defined in:
// https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf
defaultEKTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
tpm2.FlagAdminWithPolicy | tpm2.FlagRestricted | tpm2.FlagDecrypt,
AuthPolicy: []byte{
0x83, 0x71, 0x97, 0x67, 0x44, 0x84,
0xB3, 0xF8, 0x1A, 0x90, 0xCC, 0x8D,
0x46, 0xA5, 0xD7, 0x24, 0xFD, 0x52,
0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64,
0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14,
0x69, 0xAA,
},
RSAParameters: &tpm2.RSAParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB,
},
KeyBits: 2048,
Exponent: 0,
ModulusRaw: make([]byte, 256),
},
}
)
func readTPM2VendorAttributes(tpm io.ReadWriter) (TCGVendorID, string, error) {
var vendorInfo string
// The Vendor String is split up into 4 sections of 4 bytes,
// for a maximum length of 16 octets of ASCII text. We iterate
// through the 4 indexes to get all 16 bytes & construct vendorInfo.
// See: TPM_PT_VENDOR_STRING_1 in TPM 2.0 Structures reference.
for i := 0; i < 4; i++ {
caps, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityTPMProperties, 1, tpmPtVendorString+uint32(i))
if err != nil {
return TCGVendorID(0), "", fmt.Errorf("tpm2.GetCapability(PT_VENDOR_STRING_%d) failed: %v", i+1, err)
}
subset, ok := caps[0].(tpm2.TaggedProperty)
if !ok {
return TCGVendorID(0), "", fmt.Errorf("got capability of type %T, want tpm2.TaggedProperty", caps[0])
}
// Reconstruct the 4 ASCII octets from the uint32 value.
vendorInfo += string(subset.Value&0xFF000000) + string(subset.Value&0xFF0000) + string(subset.Value&0xFF00) + string(subset.Value&0xFF)
}
caps, _, err := tpm2.GetCapability(tpm, tpm2.CapabilityTPMProperties, 1, tpmPtManufacturer)
if err != nil {
return TCGVendorID(0), "", fmt.Errorf("tpm2.GetCapability(PT_MANUFACTURER) failed: %v", err)
}
manu, ok := caps[0].(tpm2.TaggedProperty)
if !ok {
return TCGVendorID(0), "", fmt.Errorf("got capability of type %T, want tpm2.TaggedProperty", caps[0])
}
return TCGVendorID(manu.Value), strings.Trim(vendorInfo, "\x00"), nil
}
func parseCert(ekCert []byte) (*x509.Certificate, error) {
// If the cert parses fine without any changes, we are G2G.
if c, err := x509.ParseCertificate(ekCert); err == nil {
return c, nil
}
// There might be trailing nonsense in the cert, which Go
// does not parse correctly. As ASN1 data is TLV encoded, we should
// be able to just get the certificate, and then send that to Go's
// certificate parser.
var cert struct {
Raw asn1.RawContent
}
if _, err := asn1.Unmarshal(ekCert, &cert); err != nil {
return nil, err
}
return x509.ParseCertificate(cert.Raw)
}
func readEKCertFromNVRAM20(tpm io.ReadWriter) (*x509.Certificate, error) {
ekCert, err := tpm2.NVReadEx(tpm, nvramCertIndex, tpm2.HandleOwner, "", 0)
if err != nil {
return nil, fmt.Errorf("reading EK cert: %v", err)
}
return parseCert(ekCert)
}
func quote20(tpm io.ReadWriter, aikHandle tpmutil.Handle, hashAlg tpm2.Algorithm, nonce []byte) (*Quote, error) {
sel := tpm2.PCRSelection{Hash: hashAlg}
numPCRs := 24
for pcr := 0; pcr < numPCRs; pcr++ {
sel.PCRs = append(sel.PCRs, pcr)
}
quote, sig, err := tpm2.Quote(tpm, aikHandle, "", "", nonce, sel, tpm2.AlgNull)
if err != nil {
return nil, err
}
rawSig, err := tpmutil.Pack(sig.Alg, sig.RSA.HashAlg, sig.RSA.Signature)
return &Quote{
Version: TPMVersion20,
Quote: quote,
Signature: rawSig,
}, err
}
func readAllPCRs20(tpm io.ReadWriter, alg tpm2.Algorithm) (map[uint32][]byte, error) {
numPCRs := 24
out := map[uint32][]byte{}
// The TPM 2.0 spec says that the TPM can partially fulfill the
// request. As such, we repeat the command up to 8 times to get all
// 24 PCRs.
for i := 0; i < numPCRs; i++ {
// Build a selection structure, specifying all PCRs we do
// not have the value for.
sel := tpm2.PCRSelection{Hash: alg}
for pcr := 0; pcr < numPCRs; pcr++ {
if _, present := out[uint32(pcr)]; !present {
sel.PCRs = append(sel.PCRs, pcr)
}
}
// Ask the TPM for those PCR values.
ret, err := tpm2.ReadPCRs(tpm, sel)
if err != nil {
return nil, fmt.Errorf("tpm2.ReadPCRs(%+v) failed with err: %v", sel, err)
}
// Keep track of the PCRs we were actually given.
for pcr, digest := range ret {
out[uint32(pcr)] = digest
}
if len(out) == numPCRs {
break
}
}
if len(out) != numPCRs {
return nil, fmt.Errorf("failed to read all PCRs, only read %d", len(out))
}
return out, nil
}
func allPCRs20(tpm io.ReadWriter) (map[uint32][]byte, crypto.Hash, error) {
out256, err256 := readAllPCRs20(tpm, tpm2.AlgSHA256)
if err256 != nil {
// TPM may not implement active banks with SHA256 - try SHA1.
out1, err1 := readAllPCRs20(tpm, tpm2.AlgSHA1)
return out1, crypto.SHA1, err1
}
return out256, crypto.SHA256, nil
}

520
attest/tpm_linux.go Normal file
View File

@ -0,0 +1,520 @@
// 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.
// +build linux
package attest
import (
"crypto"
"crypto/rsa"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
"github.com/google/certificate-transparency-go/x509"
"github.com/google/go-tspi/tspi" //for tpm12 support
"github.com/google/go-tspi/tspiconst"
"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil"
"github.com/google/go-tspi/attestation"
)
const (
tpmRoot = "/sys/class/tpm"
)
// TPM interfaces with a TPM device on the system.
type TPM struct {
version TPMVersion
interf TPMInterface
sysPath string
rwc io.ReadWriteCloser
ctx *tspi.Context
}
func probeSystemTPMs() ([]probedTPM, error) {
var tpms []probedTPM
tpmDevs, err := ioutil.ReadDir(tpmRoot)
if err != nil && !os.IsNotExist(err) {
return nil, err
}
if err == nil {
for _, tpmDev := range tpmDevs {
if strings.HasPrefix(tpmDev.Name(), "tpm") {
tpm := probedTPM{
Path: path.Join(tpmRoot, tpmDev.Name()),
}
if _, err := os.Stat(path.Join(tpm.Path, "caps")); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
tpm.Version = TPMVersion20
} else {
tpm.Version = TPMVersion12
}
tpms = append(tpms, tpm)
}
}
}
return tpms, nil
}
func openTPM(tpm probedTPM) (*TPM, error) {
interf := TPMInterfaceDirect
var rwc io.ReadWriteCloser
var ctx *tspi.Context
var err error
switch tpm.Version {
case TPMVersion12:
// TPM1.2 must be using Daemon (Connect will fail if not the case)
interf = TPMInterfaceDaemonManaged
ctx, err = tspi.NewContext()
if err != nil {
return nil, err
}
err = ctx.Connect()
if err != nil {
return nil, err
}
case TPMVersion20:
// If the TPM has a kernel-provided resource manager, we should
// use that instead of communicating directly.
devPath := path.Join("/dev", path.Base(tpm.Path))
f, err := ioutil.ReadDir(path.Join(tpm.Path, "device", "tpmrm"))
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else if len(f) > 0 {
devPath = path.Join("/dev", f[0].Name())
interf = TPMInterfaceKernelManaged
}
rwc, err = tpm2.OpenTPM(devPath)
if err != nil {
return nil, err
}
}
return &TPM{
version: tpm.Version,
interf: interf,
sysPath: tpm.Path,
rwc: rwc,
ctx: ctx,
}, nil
}
// Close shuts down the connection to the TPM.
func (t *TPM) Close() error {
switch t.version {
case TPMVersion12:
return t.ctx.Close()
case TPMVersion20:
return t.rwc.Close()
default:
return fmt.Errorf("unsupported TPM version: %x", t.version)
}
}
func readTPM12VendorAttributes(context *tspi.Context) (TCGVendorID, string, error) {
// TPM 1.2 doesn't seem to store vendor data (other than unique ID)
vendor, err := context.GetCapability(tspiconst.TSS_TPMCAP_PROPERTY, 4, tspiconst.TSS_TPMCAP_PROP_MANUFACTURER)
if err != nil {
return TCGVendorID(0), "", fmt.Errorf("tspi::Context::GetCapability failed: %v", err)
}
if len(vendor) > 4 {
return TCGVendorID(0), "", fmt.Errorf("expecting at most 32-bit VendorID, got %d-bit ID instead", len(vendor)*8)
}
vendorID := TCGVendorID(binary.BigEndian.Uint32(vendor))
return vendorID, vendorID.String(), nil
}
// Info returns information about the TPM.
func (t *TPM) Info() (*TPMInfo, error) {
var manufacturer TCGVendorID
var vendorInfo string
var err error
switch t.version {
case TPMVersion12:
manufacturer, vendorInfo, err = readTPM12VendorAttributes(t.ctx)
case TPMVersion20:
manufacturer, vendorInfo, err = readTPM2VendorAttributes(t.rwc)
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
if err != nil {
return nil, err
}
return &TPMInfo{
Version: t.version,
Interface: t.interf,
VendorInfo: vendorInfo,
Manufacturer: manufacturer,
}, nil
}
// Return value: handle, whether we generated a new one, error
func (t *TPM) getPrimaryKeyHandle(pHnd tpmutil.Handle) (tpmutil.Handle, bool, error) {
_, _, _, err := tpm2.ReadPublic(t.rwc, pHnd)
if err == nil {
// Found the persistent handle, assume it's the key we want.
return pHnd, false, nil
}
ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
if err != nil {
return 0, false, fmt.Errorf("EK CreatePrimary failed: %v", err)
}
defer tpm2.FlushContext(t.rwc, ekHnd)
err = tpm2.EvictControl(t.rwc, "", tpm2.HandleOwner, ekHnd, pHnd)
if err != nil {
return 0, false, fmt.Errorf("EK EvictControl failed: %v", err)
}
return pHnd, true, nil
}
func readEKCertFromNVRAM12(ctx *tspi.Context) (*x509.Certificate, error) {
ekCert, err := attestation.GetEKCert(ctx)
if err != nil {
return nil, fmt.Errorf("reading EK cert: %v", err)
}
return parseCert(ekCert)
}
// EKs returns the endorsement keys burned-in to the platform.
func (t *TPM) EKs() ([]PlatformEK, error) {
var cert *x509.Certificate
var err error
switch t.version {
case TPMVersion12:
cert, err = readEKCertFromNVRAM12(t.ctx)
if err != nil {
return nil, fmt.Errorf("readEKCertFromNVRAM failed: %v", err)
}
case TPMVersion20:
cert, err = readEKCertFromNVRAM20(t.rwc)
if err != nil {
ekHnd, _, err := tpm2.CreatePrimary(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", defaultEKTemplate)
if err != nil {
return nil, fmt.Errorf("EK CreatePrimary failed: %v", err)
}
defer tpm2.FlushContext(t.rwc, ekHnd)
pub, _, _, err := tpm2.ReadPublic(t.rwc, ekHnd)
if err != nil {
return nil, fmt.Errorf("EK ReadPublic failed: %v", err)
}
if pub.RSAParameters == nil {
return nil, errors.New("ECC EK not yet supported")
}
return []PlatformEK{
{nil, &rsa.PublicKey{E: int(pub.RSAParameters.Exponent), N: pub.RSAParameters.Modulus}},
}, nil
}
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
return []PlatformEK{
{cert, cert.PublicKey},
}, nil
}
// Key represents a key bound to the TPM.
type Key struct {
hnd tpmutil.Handle
KeyEncoding KeyEncoding
TPMVersion TPMVersion
Purpose KeyPurpose
KeyBlob []byte // exclusive to TPM1.2
Public []byte // used by both TPM1.2 and 2.0
CreateData []byte
CreateAttestation []byte
CreateSignature []byte
}
// Marshal represents the key in a persistent format which may be
// loaded at a later time using tpm.LoadKey().
func (k *Key) Marshal() ([]byte, error) {
return json.Marshal(k)
}
// Close frees any resources associated with the key.
func (k *Key) Close(tpm *TPM) error {
switch tpm.version {
case TPMVersion12:
return nil
case TPMVersion20:
return tpm2.FlushContext(tpm.rwc, k.hnd)
default:
return fmt.Errorf("unsupported TPM version: %x", tpm.version)
}
}
// ActivateCredential decrypts the specified credential using key.
// This operation is synonymous with TPM2_ActivateCredential.
func (k *Key) ActivateCredential(t *TPM, in EncryptedCredential) ([]byte, error) {
switch t.version {
case TPMVersion12:
cred, err := attestation.AIKChallengeResponse(t.ctx, k.KeyBlob, in.Credential, in.Secret)
if err != nil {
return nil, fmt.Errorf("failed to refresh aik: %v", err)
}
return cred, nil
case TPMVersion20:
ekHnd, _, err := t.getPrimaryKeyHandle(commonEkEquivalentHandle)
if err != nil {
return nil, err
}
sessHandle, _, err := tpm2.StartAuthSession(
t.rwc,
tpm2.HandleNull, /*tpmKey*/
tpm2.HandleNull, /*bindKey*/
make([]byte, 16), /*nonceCaller*/
nil, /*secret*/
tpm2.SessionPolicy,
tpm2.AlgNull,
tpm2.AlgSHA256)
if err != nil {
return nil, fmt.Errorf("creating session: %v", err)
}
defer tpm2.FlushContext(t.rwc, sessHandle)
if _, err := tpm2.PolicySecret(t.rwc, tpm2.HandleEndorsement, tpm2.AuthCommand{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession}, sessHandle, nil, nil, nil, 0); err != nil {
return nil, fmt.Errorf("tpm2.PolicySecret() failed: %v", err)
}
return tpm2.ActivateCredentialUsingAuth(t.rwc, []tpm2.AuthCommand{
{Session: tpm2.HandlePasswordSession, Attributes: tpm2.AttrContinueSession},
{Session: sessHandle, Attributes: tpm2.AttrContinueSession},
}, k.hnd, ekHnd, in.Credential[2:], in.Secret[2:])
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
}
func (k *Key) quote12(ctx *tspi.Context, nonce []byte) (*Quote, error) {
quote, rawSig, err := attestation.GetQuote(ctx, k.KeyBlob, nonce)
if err != nil {
return nil, fmt.Errorf("GetQuote() failed: %v", err)
}
return &Quote{
Version: TPMVersion12,
Quote: quote,
Signature: rawSig,
}, nil
}
// Quote returns a quote over the platform state, signed by the key.
func (k *Key) Quote(t *TPM, nonce []byte, alg tpm2.Algorithm) (*Quote, error) {
switch t.version {
case TPMVersion12:
return k.quote12(t.ctx, nonce)
case TPMVersion20:
return quote20(t.rwc, k.hnd, alg, nonce)
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
}
// MintAIK creates an attestation key.
func (t *TPM) MintAIK(opts *MintOptions) (*Key, error) {
switch t.version {
case TPMVersion12:
pub, blob, err := attestation.CreateAIK(t.ctx)
if err != nil {
return nil, fmt.Errorf("CreateAIK failed: %v", err)
}
return &Key{
KeyEncoding: KeyEncodingEncrypted,
TPMVersion: t.version,
Purpose: AttestationKey,
KeyBlob: blob,
Public: pub,
}, nil
case TPMVersion20:
// TODO(jsonp): Abstract choice of hierarchy & parent.
keyHandle, pub, creationData, creationHash, tix, _, err := tpm2.CreatePrimaryEx(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", aikTemplate)
if err != nil {
return nil, fmt.Errorf("CreatePrimaryEx failed: %v", err)
}
// If any errors occur, free the AIK's handle.
defer func() {
if err != nil {
tpm2.FlushContext(t.rwc, keyHandle)
}
}()
// We can only certify the creation immediately afterwards, so we cache the result.
attestation, sig, err := tpm2.CertifyCreation(t.rwc, "", keyHandle, keyHandle, nil, creationHash, tpm2.SigScheme{tpm2.AlgRSASSA, tpm2.AlgSHA256, 0}, tix)
if err != nil {
return nil, fmt.Errorf("CertifyCreation failed: %v", err)
}
// Pack the raw structure into a TPMU_SIGNATURE.
signature, err := tpmutil.Pack(tpm2.AlgRSASSA, tpm2.AlgSHA256, sig)
if err != nil {
return nil, fmt.Errorf("failed to pack TPMT_SIGNATURE: %v", err)
}
return &Key{
hnd: keyHandle,
KeyEncoding: KeyEncodingParameterized,
TPMVersion: t.version,
Purpose: AttestationKey,
Public: pub,
CreateData: creationData,
CreateAttestation: attestation,
CreateSignature: signature,
}, nil
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
}
// LoadKey loads a previously-created key into the TPM for use.
// A key loaded via this function needs to be closed with .Close().
func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) {
// TODO(b/124266168): Load under the key handle loaded by t.getPrimaryKeyHandle()
var k Key
var err error
if err = json.Unmarshal(opaqueBlob, &k); err != nil {
return nil, fmt.Errorf("Unmarshal failed: %v", err)
}
if k.TPMVersion != t.version {
return nil, errors.New("key TPM version does not match opened TPM")
}
if k.Purpose != AttestationKey {
return nil, fmt.Errorf("unsupported key kind: %x", k.Purpose)
}
switch t.version {
case TPMVersion12:
if k.KeyEncoding != KeyEncodingEncrypted {
return nil, fmt.Errorf("unsupported key encoding: %x", k.KeyEncoding)
}
case TPMVersion20:
if k.KeyEncoding != KeyEncodingParameterized {
return nil, fmt.Errorf("unsupported key encoding: %x", k.KeyEncoding)
}
if k.hnd, _, _, _, _, _, err = tpm2.CreatePrimaryEx(t.rwc, tpm2.HandleEndorsement, tpm2.PCRSelection{}, "", "", aikTemplate); err != nil {
return nil, fmt.Errorf("CreatePrimaryEx failed: %v", err)
}
}
return &k, nil
}
// allPCRs12 returns a map of all the PCR values on the TPM
func allPCRs12(ctx *tspi.Context) (map[uint32][]byte, error) {
tpm := ctx.GetTPM()
PCRlist, err := tpm.GetPCRValues()
if err != nil {
return nil, fmt.Errorf("failed to read PCRs: %v", err)
}
PCRs := make(map[uint32][]byte)
for i := 0; i < len(PCRlist); i++ {
PCRs[(uint32)(i)] = PCRlist[i]
}
return PCRs, nil
}
// PCRs returns the present value of all Platform Configuration Registers.
func (t *TPM) PCRs() (map[int]PCR, tpm2.Algorithm, error) {
var PCRs map[uint32][]byte
var alg crypto.Hash
var err error
switch t.version {
case TPMVersion12:
PCRs, err = allPCRs12(t.ctx)
if err != nil {
return nil, 0, fmt.Errorf("failed to read PCRs: %v", err)
}
alg = crypto.SHA1
case TPMVersion20:
PCRs, alg, err = allPCRs20(t.rwc)
if err != nil {
return nil, 0, fmt.Errorf("failed to read PCRs: %v", err)
}
default:
return nil, 0, fmt.Errorf("unsupported TPM version: %x", t.version)
}
out := map[int]PCR{}
var lastAlg crypto.Hash
for index, digest := range PCRs {
out[int(index)] = PCR{
Index: int(index),
Digest: digest,
DigestAlg: alg,
}
lastAlg = alg
}
switch lastAlg {
case crypto.SHA1:
return out, tpm2.AlgSHA1, nil
case crypto.SHA256:
return out, tpm2.AlgSHA256, nil
default:
return nil, 0, fmt.Errorf("unexpected algorithm: %v", lastAlg)
}
}
// MeasurementLog returns the present value of the System Measurement Log.
func (t *TPM) MeasurementLog() ([]byte, error) {
return ioutil.ReadFile("/sys/kernel/security/tpm0/binary_bios_measurements")
}

303
attest/tpm_windows.go Normal file
View File

@ -0,0 +1,303 @@
// 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.
// +build windows
package attest
import (
"crypto"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"github.com/google/go-tpm/tpm2"
tpmtbs "github.com/google/go-tpm/tpmutil/tbs"
)
// TPM interfaces with a TPM device on the system.
type TPM struct {
version TPMVersion
pcp *winPCP
}
func probeSystemTPMs() ([]probedTPM, error) {
// Windows systems appear to only support a single abstracted TPM.
// If we fail to initialize the Platform Crypto Provider, we assume
// a TPM is not present.
pcp, err := openPCP()
if err != nil {
return nil, nil
}
defer pcp.Close()
info, err := pcp.TPMInfo()
if err != nil {
return nil, err
}
var out probedTPM
out.Version, err = tbsConvertVersion(info.TBSInfo)
if err != nil {
return nil, err
}
return []probedTPM{out}, nil
}
func tbsConvertVersion(info tbsDeviceInfo) (TPMVersion, error) {
switch info.TPMVersion {
case 1:
return TPMVersion12, nil
case 2:
return TPMVersion20, nil
default:
return TPMVersionAgnostic, fmt.Errorf("TBSInfo.TPMVersion %d unsupported", info.TPMVersion)
}
}
func openTPM(tpm probedTPM) (*TPM, error) {
pcp, err := openPCP()
if err != nil {
return nil, err
}
info, err := pcp.TPMInfo()
if err != nil {
return nil, err
}
vers, err := tbsConvertVersion(info.TBSInfo)
if err != nil {
return nil, err
}
return &TPM{
pcp: pcp,
version: vers,
}, nil
}
// Close shuts down the connection to the TPM.
func (t *TPM) Close() error {
return t.pcp.Close()
}
// Info returns information about the TPM.
func (t *TPM) Info() (*TPMInfo, error) {
if t.version != TPMVersion20 {
return nil, ErrTPM12NotImplemented
}
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, err
}
manufacturer, vendorInfo, err := readTPM2VendorAttributes(tpm)
if err != nil {
return nil, err
}
return &TPMInfo{
Version: t.version,
Interface: TPMInterfaceKernelManaged,
VendorInfo: vendorInfo,
Manufacturer: manufacturer,
}, nil
}
// EKs returns the Endorsement Keys burned-in to the platform.
func (t *TPM) EKs() ([]PlatformEK, error) {
ekCerts, err := t.pcp.EKCerts()
if err != nil {
return nil, fmt.Errorf("could not read EKCerts from PCP: %v", err)
}
var out []PlatformEK
for _, cert := range ekCerts {
out = append(out, PlatformEK{cert, cert.PublicKey})
}
// TODO(jsonp): Fallback to reading PCP_RSA_EKPUB/PCP_ECC_EKPUB, and maybe direct.
// if len(out) == 0 {
// ...
// }
return out, nil
}
// Key represents a key bound to the TPM.
type Key struct {
hnd uintptr
KeyEncoding KeyEncoding
TPMVersion TPMVersion
Purpose KeyPurpose
PCPKeyName string
Public []byte
CreateData []byte
CreateAttestation []byte
CreateSignature []byte
}
// Marshal represents the key in a persistent format which may be
// loaded at a later time using tpm.LoadKey().
func (k *Key) Marshal() ([]byte, error) {
return json.Marshal(k)
}
// ActivateCredential decrypts the specified credential using key.
// This operation is synonymous with TPM2_ActivateCredential.
func (k *Key) ActivateCredential(tpm *TPM, in EncryptedCredential) ([]byte, error) {
if tpm.version != TPMVersion20 {
return nil, ErrTPM12NotImplemented
}
return tpm.pcp.ActivateCredential(k.hnd, append(in.Credential, in.Secret...))
}
// Quote returns a quote over the platform state, signed by the key.
func (k *Key) Quote(t *TPM, nonce []byte, alg tpm2.Algorithm) (*Quote, error) {
if t.version != TPMVersion20 {
return nil, ErrTPM12NotImplemented
}
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
tpmKeyHnd, err := t.pcp.TPMKeyHandle(k.hnd)
if err != nil {
return nil, fmt.Errorf("TPMKeyHandle() failed: %v", err)
}
return quote20(tpm, tpmKeyHnd, alg, nonce)
}
// Close frees any resources associated with the key.
func (k *Key) Close(tpm *TPM) error {
return closeNCryptObject(k.hnd)
}
// MintAIK creates a persistent attestation key. The returned key must be
// closed with a call to key.Close() when the caller has finished using it.
func (t *TPM) MintAIK(opts *MintOptions) (*Key, error) {
if t.version != TPMVersion20 {
return nil, ErrTPM12NotImplemented
}
nameHex := make([]byte, 5)
if n, err := rand.Read(nameHex); err != nil || n != len(nameHex) {
return nil, fmt.Errorf("rand.Read() failed with %d/%d bytes read and error: %v", n, len(nameHex), err)
}
name := fmt.Sprintf("aik-%x", nameHex)
kh, err := t.pcp.MintAIK(name)
if err != nil {
return nil, fmt.Errorf("pcp failed to mint attestation key: %v", err)
}
props, err := t.pcp.AIKProperties(kh)
if err != nil {
closeNCryptObject(kh)
return nil, fmt.Errorf("pcp failed to read attestation key properties: %v", err)
}
return &Key{
hnd: kh,
KeyEncoding: KeyEncodingOSManaged,
TPMVersion: t.version,
Purpose: AttestationKey,
PCPKeyName: name,
Public: props.RawPublic,
CreateData: props.RawCreationData,
CreateAttestation: props.RawAttest,
CreateSignature: props.RawSignature,
}, nil
}
// LoadKey loads a previously-created key into the TPM for use.
// A key loaded via this function needs to be closed with .Close().
func (t *TPM) LoadKey(opaqueBlob []byte) (*Key, error) {
var k Key
var err error
if err = json.Unmarshal(opaqueBlob, &k); err != nil {
return nil, fmt.Errorf("Unmarshal failed: %v", err)
}
if k.TPMVersion != t.version {
return nil, errors.New("key TPM version does not match opened TPM")
}
if k.KeyEncoding != KeyEncodingOSManaged {
return nil, fmt.Errorf("unsupported key encoding: %x", k.KeyEncoding)
}
if k.Purpose != AttestationKey {
return nil, fmt.Errorf("unsupported key kind: %x", k.Purpose)
}
if k.hnd, err = t.pcp.LoadKeyByName(k.PCPKeyName); err != nil {
return nil, fmt.Errorf("pcp failed to load key: %v", err)
}
return &k, nil
}
// PCRs returns the present value of all Platform Configuration Registers.
func (t *TPM) PCRs() (map[int]PCR, tpm2.Algorithm, error) {
if t.version != TPMVersion20 {
return nil, 0, ErrTPM12NotImplemented
}
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, 0, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
PCRs, alg, err := allPCRs20(tpm)
if err != nil {
return nil, 0, fmt.Errorf("failed to read PCRs: %v", err)
}
out := map[int]PCR{}
var lastAlg crypto.Hash
for index, digest := range PCRs {
out[int(index)] = PCR{
Index: int(index),
Digest: digest,
DigestAlg: alg,
}
lastAlg = alg
}
switch lastAlg {
case crypto.SHA1:
return out, tpm2.AlgSHA1, nil
case crypto.SHA256:
return out, tpm2.AlgSHA256, nil
default:
return nil, 0, fmt.Errorf("unexpected algorithm: %v", lastAlg)
}
}
// MeasurementLog returns the present value of the System Measurement Log.
func (t *TPM) MeasurementLog() ([]byte, error) {
context, err := tpmtbs.CreateContext(tpmtbs.TPMVersion20, tpmtbs.IncludeTPM20)
if err != nil {
return nil, err
}
defer context.Close()
// Run command first with nil buffer to get required buffer size.
logLen, err := context.GetTCGLog(nil)
if err != nil {
return nil, err
}
logBuffer := make([]byte, logLen)
if _, err = context.GetTCGLog(logBuffer); err != nil {
return nil, err
}
return logBuffer, nil
}

44
attest/vendors.go Normal file
View File

@ -0,0 +1,44 @@
// 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
// TCGVendorID represents a unique TCG manufacturer code.
// The canonical reference used is located at:
// https://trustedcomputinggroup.org/wp-content/uploads/Vendor_ID_Registry_0-8_clean.pdf
type TCGVendorID uint32
var vendors = map[TCGVendorID]string{
1095582720: "AMD",
1096043852: "Atmel",
1112687437: "Broadcom",
1229081856: "IBM",
1229346816: "Infineon",
1229870147: "Intel",
1279610368: "Lenovo",
1314082080: "National Semiconductor",
1314150912: "Nationz",
1314145024: "Nuvoton Technology",
1363365709: "Qualcomm",
1397576515: "SMSC",
1398033696: "ST Microelectronics",
1397576526: "Samsung",
1397641984: "Sinosun",
1415073280: "Texas Instruments",
1464156928: "Winbond",
1380926275: "Fuzhou Rockchip",
}
func (id TCGVendorID) String() string {
return vendors[id]
}