mirror of
https://github.com/google/go-attestation.git
synced 2024-12-21 05:53:25 +00:00
3a523cf51f
attest: move public key parsing server side
343 lines
10 KiB
Go
343 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.
|
|
|
|
// Package attest abstracts TPM attestation operations.
|
|
package attest
|
|
|
|
import (
|
|
"crypto"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/google/certificate-transparency-go/x509"
|
|
"github.com/google/go-tpm/tpm"
|
|
"github.com/google/go-tpm/tpm2"
|
|
)
|
|
|
|
// 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
|
|
|
|
func (e keyEncoding) String() string {
|
|
switch e {
|
|
case keyEncodingInvalid:
|
|
return "invalid"
|
|
case keyEncodingOSManaged:
|
|
return "os-managed"
|
|
case keyEncodingEncrypted:
|
|
return "encrypted"
|
|
case keyEncodingParameterized:
|
|
return "parameterized"
|
|
default:
|
|
return fmt.Sprintf("keyEncoding<%d>", int(e))
|
|
}
|
|
}
|
|
|
|
// Key encodings
|
|
const (
|
|
keyEncodingInvalid keyEncoding = iota
|
|
// Managed by the OS but loadable by name.
|
|
keyEncodingOSManaged
|
|
// Key fully represented but in encrypted form.
|
|
keyEncodingEncrypted
|
|
// Parameters stored, but key must be regenerated before use.
|
|
keyEncodingParameterized
|
|
)
|
|
|
|
type aik interface {
|
|
Close(*TPM) error
|
|
Marshal() ([]byte, error)
|
|
ActivateCredential(tpm *TPM, in EncryptedCredential) ([]byte, error)
|
|
Quote(t *TPM, nonce []byte, alg HashAlg) (*Quote, error)
|
|
AttestationParameters() AttestationParameters
|
|
}
|
|
|
|
// AIK represents a key which can be used for attestation.
|
|
type AIK struct {
|
|
aik aik
|
|
}
|
|
|
|
// Close unloads the AIK from the system.
|
|
func (k *AIK) Close(t *TPM) error {
|
|
return k.aik.Close(t)
|
|
}
|
|
|
|
// Marshal encodes the AIK in a format that can be reloaded with tpm.LoadAIK().
|
|
// This method exists to allow consumers to store the key persistently and load
|
|
// it as a later time. Users SHOULD NOT attempt to interpret or extract values
|
|
// from this blob.
|
|
func (k *AIK) Marshal() ([]byte, error) {
|
|
return k.aik.Marshal()
|
|
}
|
|
|
|
// ActivateCredential decrypts the secret using the key to prove that the AIK
|
|
// was generated on the same TPM as the EK.
|
|
//
|
|
// This operation is synonymous with TPM2_ActivateCredential.
|
|
func (k *AIK) ActivateCredential(tpm *TPM, in EncryptedCredential) (secret []byte, err error) {
|
|
return k.aik.ActivateCredential(tpm, in)
|
|
}
|
|
|
|
// Quote returns a quote over the platform state, signed by the AIK.
|
|
func (k *AIK) Quote(tpm *TPM, nonce []byte, alg HashAlg) (*Quote, error) {
|
|
return k.aik.Quote(tpm, nonce, alg)
|
|
}
|
|
|
|
// Parameters returns information about the AIK, typically used to generate
|
|
// a credential activation challenge.
|
|
func (k *AIK) AttestationParameters() AttestationParameters {
|
|
return k.aik.AttestationParameters()
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// AttestationParameters describes information about a key which is necessary
|
|
// for verifying its properties remotely.
|
|
type AttestationParameters struct {
|
|
// Public represents the AIK's canonical encoding. This blob includes the
|
|
// public key, as well as signing parameters such as the hash algorithm
|
|
// used to generate quotes.
|
|
//
|
|
// Use ParseAIKPublic to access the key's data.
|
|
Public []byte
|
|
// For TPM 2.0 devices, Public is encoded as a TPMT_PUBLIC structure.
|
|
// For TPM 1.2 devices, Public is a TPM_PUBKEY structure, as defined in
|
|
// the TPM Part 2 Structures specification, available at
|
|
// https://trustedcomputinggroup.org/wp-content/uploads/TPM-Main-Part-2-TPM-Structures_v1.2_rev116_01032011.pdf
|
|
|
|
// UseTCSDActivationFormat is set when tcsd (trousers daemon) is operating
|
|
// as an intermediary between this library and the TPM. A value of true
|
|
// indicates that activation challenges should use the TCSD-specific format.
|
|
UseTCSDActivationFormat bool
|
|
|
|
// Subsequent fields are only populated for AIKs generated on a TPM
|
|
// implementing version 2.0 of the specification. The specific structures
|
|
// referenced for each field are defined in the TPM Revision 2, Part 2 -
|
|
// Structures specification, available here:
|
|
// https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
|
|
|
|
// CreateData represents the properties of a TPM 2.0 key. It is encoded
|
|
// as a TPMS_CREATION_DATA structure.
|
|
CreateData []byte
|
|
// CreateAttestation represents an assertion as to the details of the key.
|
|
// It is encoded as a TPMS_ATTEST structure.
|
|
CreateAttestation []byte
|
|
// CreateSignature represents a signature of the CreateAttestation structure.
|
|
// It is encoded as a TPMT_SIGNATURE structure.
|
|
CreateSignature []byte
|
|
}
|
|
|
|
// AIKPublic holds structured information about an AIK's public key.
|
|
type AIKPublic struct {
|
|
// Public is the public part of the AIK. This can either be an *rsa.PublicKey or
|
|
// and *ecdsa.PublicKey.
|
|
Public crypto.PublicKey
|
|
// Hash is the hashing algorithm the AIK will use when signing quotes.
|
|
Hash crypto.Hash
|
|
}
|
|
|
|
// ParseAIKPublic parses the Public blob from the AttestationParameters,
|
|
// returning the public key and signing parameters for the key.
|
|
func ParseAIKPublic(version TPMVersion, public []byte) (*AIKPublic, error) {
|
|
switch version {
|
|
case TPMVersion12:
|
|
rsaPub, err := tpm.UnmarshalPubRSAPublicKey(public)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing public key: %v", err)
|
|
}
|
|
return &AIKPublic{Public: rsaPub, Hash: crypto.SHA1}, nil
|
|
case TPMVersion20:
|
|
pub, err := tpm2.DecodePublic(public)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing TPM public key structure: %v", err)
|
|
}
|
|
pubKey, err := pub.Key()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing public key: %v", err)
|
|
}
|
|
var h crypto.Hash
|
|
switch pub.Type {
|
|
case tpm2.AlgRSA:
|
|
h, err = cryptoHash(pub.RSAParameters.Sign.Hash)
|
|
case tpm2.AlgECC:
|
|
h, err = cryptoHash(pub.ECCParameters.Sign.Hash)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported public key type 0x%x", pub.Type)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid public key hash: %v", err)
|
|
}
|
|
return &AIKPublic{Public: pubKey, Hash: h}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown tpm version 0x%x", version)
|
|
}
|
|
}
|
|
|
|
// HashAlg identifies a hashing Algorithm.
|
|
type HashAlg uint8
|
|
|
|
// Valid hash algorithms.
|
|
var (
|
|
HashSHA1 = HashAlg(tpm2.AlgSHA1)
|
|
HashSHA256 = HashAlg(tpm2.AlgSHA256)
|
|
)
|
|
|
|
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
|
|
}
|