mirror of
https://github.com/google/go-attestation.git
synced 2024-12-18 20:47:57 +00:00
Initial commit.
This commit is contained in:
commit
21c2bfd1dc
28
CONTRIBUTING.md
Normal file
28
CONTRIBUTING.md
Normal 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
202
LICENSE
Normal 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
12
README.md
Normal 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
198
attest/attest.go
Normal 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
|
||||||
|
}
|
231
attest/attest_simulated_tpm20_test.go
Normal file
231
attest/attest_simulated_tpm20_test.go
Normal 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
224
attest/attest_test.go
Normal 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
199
attest/attest_tpm12_test.go
Normal 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
409
attest/pcp_windows.go
Normal 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
207
attest/tpm.go
Normal 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
520
attest/tpm_linux.go
Normal 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
303
attest/tpm_windows.go
Normal 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
44
attest/vendors.go
Normal 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]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user