mirror of
https://github.com/google/go-attestation.git
synced 2024-12-23 14:52:29 +00:00
b128fd7448
Moving AIK to storage hierarchy so that the key blob can be saved and loaded instead of recreating the aik each time.
223 lines
6.7 KiB
Go
223 lines
6.7 KiB
Go
// 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),
|
|
},
|
|
}
|
|
defaultSRKTemplate = tpm2.Public{
|
|
Type: tpm2.AlgRSA,
|
|
NameAlg: tpm2.AlgSHA256,
|
|
Attributes: tpm2.FlagStorageDefault,
|
|
RSAParameters: &tpm2.RSAParams{
|
|
Symmetric: &tpm2.SymScheme{
|
|
Alg: tpm2.AlgAES,
|
|
KeyBits: 128,
|
|
Mode: tpm2.AlgCFB,
|
|
},
|
|
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
|
|
}
|