go-attestation/attest/tpm_windows.go
Eric Chiang e688ff6d7f attest: rename MintAIK and MintOptions to NewAIK and AIKConfig
This helps the godoc read better and is more inline with Go's naming
scheme. No functional changes made, just naming.
2019-08-28 09:25:14 -07:00

408 lines
10 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.
// +build windows
package attest
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
tpm1 "github.com/google/go-tpm/tpm"
tpmtbs "github.com/google/go-tpm/tpmutil/tbs"
)
var wellKnownAuth [20]byte
// 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, fmt.Errorf("TPMInfo() failed: %v", err)
}
var out probedTPM
out.Version, err = tbsConvertVersion(info.TBSInfo)
if err != nil {
return nil, fmt.Errorf("tbsConvertVersion(%v) failed: %v", info.TBSInfo.TPMVersion, 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, fmt.Errorf("openPCP() failed: %v", err)
}
info, err := pcp.TPMInfo()
if err != nil {
return nil, fmt.Errorf("TPMInfo() failed: %v", err)
}
vers, err := tbsConvertVersion(info.TBSInfo)
if err != nil {
return nil, fmt.Errorf("tbsConvertVersion(%v) failed: %v", info.TBSInfo.TPMVersion, err)
}
return &TPM{
pcp: pcp,
version: vers,
}, nil
}
// Version returns the version of the TPM.
func (t *TPM) Version() TPMVersion {
return t.version
}
// Close shuts down the connection to the TPM.
func (t *TPM) Close() error {
return t.pcp.Close()
}
func readTPM12VendorAttributes(tpm io.ReadWriter) (TCGVendorID, string, error) {
vendor, err := tpm1.GetManufacturer(tpm)
if err != nil {
return TCGVendorID(0), "", fmt.Errorf("tpm1.GetCapability failed: %v", err)
}
vendorID := TCGVendorID(binary.BigEndian.Uint32(vendor))
return vendorID, vendorID.String(), nil
}
// Info returns information about the TPM.
func (t *TPM) Info() (*TPMInfo, error) {
tInfo := TPMInfo{
Version: t.version,
Interface: TPMInterfaceKernelManaged,
}
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, err
}
switch t.version {
case TPMVersion12:
tInfo.Manufacturer, tInfo.VendorInfo, err = readTPM12VendorAttributes(tpm)
case TPMVersion20:
var t2Info tpm20Info
t2Info, err = readTPM2VendorAttributes(tpm)
tInfo.Manufacturer = t2Info.manufacturer
tInfo.VendorInfo = t2Info.vendor
tInfo.FirmwareVersionMajor = t2Info.fwMajor
tInfo.FirmwareVersionMinor = t2Info.fwMinor
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
if err != nil {
return nil, err
}
return &tInfo, nil
}
// EKs returns the Endorsement Keys burned-in to the platform.
func (t *TPM) EKs() ([]EK, error) {
ekCerts, err := t.pcp.EKCerts()
if err != nil {
return nil, fmt.Errorf("could not read EKCerts: %v", err)
}
if len(ekCerts) > 0 {
var eks []EK
for _, cert := range ekCerts {
eks = append(eks, EK{Certificate: cert, Public: cert.PublicKey})
}
return eks, nil
}
pub, err := t.ekPub()
if err != nil {
return nil, fmt.Errorf("could not read ek public key from tpm: %v", err)
}
ek := EK{Public: pub}
i, err := t.Info()
if err != nil {
return nil, err
}
if i.Manufacturer.String() == manufacturerIntel {
ek.CertificateURL = intelEKURL(pub)
}
return []EK{ek}, nil
}
func (t *TPM) ekPub() (*rsa.PublicKey, error) {
p, err := t.pcp.EKPub()
if err != nil {
return nil, fmt.Errorf("could not read ekpub: %v", err)
}
ekPub, err := decodeWindowsBcryptRSABlob(p)
if err != nil {
return nil, fmt.Errorf("could not decode ekpub: %v", err)
}
return ekPub, nil
}
type bcryptRSABlobHeader struct {
Magic uint32
BitLength uint32
ExponentLen uint32
ModulusLen uint32
Prime1Len uint32
Prime2Len uint32
}
func decodeWindowsBcryptRSABlob(b []byte) (*rsa.PublicKey, error) {
var (
r = bytes.NewReader(b)
header = &bcryptRSABlobHeader{}
exp = make([]byte, 8)
mod = []byte("")
)
if err := binary.Read(r, binary.LittleEndian, header); err != nil {
return nil, err
}
if header.Magic != 0x31415352 { // "RSA1"
return nil, fmt.Errorf("invalid header magic %x", header.Magic)
}
if header.ExponentLen > 8 {
return nil, errors.New("exponent too large")
}
if _, err := r.Read(exp[8-header.ExponentLen:]); err != nil {
return nil, fmt.Errorf("failed to read public exponent: %v", err)
}
mod = make([]byte, header.ModulusLen)
if n, err := r.Read(mod); n != int(header.ModulusLen) || err != nil {
return nil, fmt.Errorf("failed to read modulus (%d, %v)", n, err)
}
return &rsa.PublicKey{
N: new(big.Int).SetBytes(mod),
E: int(binary.BigEndian.Uint64(exp)),
}, nil
}
func decryptCredential(secretKey, blob []byte) ([]byte, error) {
var scheme uint32
symbuf := bytes.NewReader(blob)
if err := binary.Read(symbuf, binary.BigEndian, &scheme); err != nil {
return nil, fmt.Errorf("reading scheme: %v", err)
}
if scheme != 0x00000002 {
return nil, fmt.Errorf("can only handle CBC schemes")
}
iv := make([]byte, 16)
if err := binary.Read(symbuf, binary.BigEndian, &iv); err != nil {
return nil, err
}
cipherText := make([]byte, len(blob)-20)
if err := binary.Read(symbuf, binary.BigEndian, &cipherText); err != nil {
return nil, fmt.Errorf("reading ciphertext: %v", err)
}
// Decrypt the credential.
var (
block cipher.Block
secret []byte
err error
)
block, err = aes.NewCipher(secretKey)
if err != nil {
return nil, fmt.Errorf("aes.NewCipher failed: %v", err)
}
secret = cipherText
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(secret, cipherText)
// Remove PKCS5 padding.
padlen := int(secret[len(secret)-1])
secret = secret[:len(secret)-padlen]
return secret, nil
}
// NewAIK 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) NewAIK(opts *AIKConfig) (*AIK, error) {
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.NewAIK(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)
}
switch t.version {
case TPMVersion12:
return &AIK{aik: newKey12(kh, name, props.RawPublic)}, nil
case TPMVersion20:
return &AIK{aik: newKey20(kh, name, props.RawPublic, props.RawCreationData, props.RawAttest, props.RawSignature)}, nil
default:
return nil, fmt.Errorf("cannot handle TPM version: %v", t.version)
}
}
func (t *TPM) loadAIK(opaqueBlob []byte) (*AIK, error) {
sKey, err := deserializeKey(opaqueBlob, t.version)
if err != nil {
return nil, fmt.Errorf("deserializeKey() failed: %v", err)
}
if sKey.Encoding != keyEncodingOSManaged {
return nil, fmt.Errorf("unsupported key encoding: %x", sKey.Encoding)
}
hnd, err := t.pcp.LoadKeyByName(sKey.Name)
if err != nil {
return nil, fmt.Errorf("pcp failed to load key: %v", err)
}
switch t.version {
case TPMVersion12:
return &AIK{aik: newKey12(hnd, sKey.Name, sKey.Public)}, nil
case TPMVersion20:
return &AIK{aik: newKey20(hnd, sKey.Name, sKey.Public, sKey.CreateData, sKey.CreateAttestation, sKey.CreateSignature)}, nil
default:
return nil, fmt.Errorf("cannot handle TPM version: %v", t.version)
}
}
func allPCRs12(tpm io.ReadWriter) (map[uint32][]byte, error) {
numPCRs := 24
out := map[uint32][]byte{}
for pcr := 0; pcr < numPCRs; pcr++ {
pcrval, err := tpm1.ReadPCR(tpm, uint32(pcr))
if err != nil {
return nil, fmt.Errorf("tpm.ReadPCR() failed with err: %v", err)
}
out[uint32(pcr)] = pcrval
}
if len(out) != numPCRs {
return nil, fmt.Errorf("failed to read all PCRs, only read %d", len(out))
}
return out, nil
}
// PCRs returns the present value of Platform Configuration Registers with the
// given digest algorithm.
func (t *TPM) PCRs(alg HashAlg) ([]PCR, error) {
var PCRs map[uint32][]byte
switch t.version {
case TPMVersion12:
if alg != HashSHA1 {
return nil, fmt.Errorf("non-SHA1 algorithm %v is not supported on TPM 1.2", alg)
}
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
PCRs, err = allPCRs12(tpm)
if err != nil {
return nil, fmt.Errorf("failed to read PCRs: %v", err)
}
case TPMVersion20:
tpm, err := t.pcp.TPMCommandInterface()
if err != nil {
return nil, fmt.Errorf("TPMCommandInterface() failed: %v", err)
}
PCRs, err = readAllPCRs20(tpm, alg.goTPMAlg())
if err != nil {
return nil, fmt.Errorf("failed to read PCRs: %v", err)
}
default:
return nil, fmt.Errorf("unsupported TPM version: %x", t.version)
}
out := make([]PCR, len(PCRs))
for index, digest := range PCRs {
out[int(index)] = PCR{
Index: int(index),
Digest: digest,
DigestAlg: alg.cryptoHash(),
}
}
return out, nil
}
// MeasurementLog returns the present value of the System Measurement Log.
func (t *TPM) MeasurementLog() ([]byte, error) {
context, err := tpmtbs.CreateContext(tpmtbs.TPMVersion20, tpmtbs.IncludeTPM20|tpmtbs.IncludeTPM12)
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
}