constellation/internal/attestation/azure/issuer.go
Otto Bittner 7c5556864b AB#2333: Add AMD SNP-based attestation
Currently only available on Azure CVMs.

* Get the public attestation key from the TPM.
* Get the snp report from the TPM.
* Get the VCEK and ASK certificate from the metadata api.
* Verify VCEK using hardcoded root key (ARK)
* Verify SNP report using VCEK
* Verify HCLAkPub using SNP report by comparing
AK with runtimeData
* Extend unittest

Co-authored-by: Thomas Tendyck <51411342+thomasten@users.noreply.github.com>
Co-authored-by: Daniel Weiße <dw@edgeless.systems>
2022-08-29 16:29:33 +02:00

126 lines
3.9 KiB
Go

package azure
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/oid"
tpmclient "github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm/tpm2"
)
const (
lenHclHeader = 0x20
lenSnpReport = 0x4a0
lenSnpReportRuntimeDataPadding = 0x14
)
// Issuer for Azure TPM attestation.
type Issuer struct {
oid.Azure
*vtpm.Issuer
}
// NewIssuer initializes a new Azure Issuer.
func NewIssuer() *Issuer {
imdsAPI := imdsClient{
client: &http.Client{Transport: &http.Transport{Proxy: nil}},
}
return &Issuer{
Issuer: vtpm.NewIssuer(
vtpm.OpenVTPM,
getHCLAttestationKey,
getSNPAttestation(&tpmReport{}, imdsAPI),
),
}
}
func hclAkTemplate() tpm2.Public {
akFlags := tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagNoDA | tpm2.FlagRestricted | tpm2.FlagSign
return tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: akFlags,
RSAParameters: &tpm2.RSAParams{
Sign: &tpm2.SigScheme{
Alg: tpm2.AlgRSASSA,
Hash: tpm2.AlgSHA256,
},
KeyBits: 2048,
},
}
}
// getHCLAttestationKey reads the attesation key put into the TPM during early boot.
func getHCLAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
// A minor drawback of `NewCachedKey` is that it will transparently create/overwrite a key if it does not find one matching the template at the given index.
// We actually wouldn't want to continue at this point if we realize that the key at the index is not present, due to
// easier debuggability. If `NewCachedKey` creates a new key, attestation will fail at the validator.
// The function in tpmclient that doesn't create a new key, ReadPublic, can't be used as we would have to create
// a tpmclient.Key object manually, which we can't since there is no constructor exported.
ak, err := tpmclient.NewCachedKey(tpm, tpm2.HandleOwner, hclAkTemplate(), 0x81000003)
if err != nil {
return nil, fmt.Errorf("reading HCL attestation key from TPM: %w", err)
}
return ak, nil
}
// getSNPAttestation loads and returns the SEV-SNP attestation report [1] and the
// AMD VCEK certificate chain.
// The attestation report is loaded from the TPM, the certificate chain is queried
// from the cloud metadata API.
// [1] https://github.com/AMDESE/sev-guest/blob/main/include/attestation.h
func getSNPAttestation(reportGetter tpmReportGetter, imdsAPI imdsApi) func(tpm io.ReadWriteCloser) ([]byte, error) {
return func(tpm io.ReadWriteCloser) ([]byte, error) {
hclReport, err := reportGetter.get(tpm)
if err != nil {
return nil, fmt.Errorf("reading report from TPM: %w", err)
}
if len(hclReport) < lenHclHeader+lenSnpReport+lenSnpReportRuntimeDataPadding {
return nil, fmt.Errorf("report read from TPM is shorter then expected: %x", hclReport)
}
hclReport = hclReport[lenHclHeader:]
runtimeData, _, _ := bytes.Cut(hclReport[lenSnpReport+lenSnpReportRuntimeDataPadding:], []byte{0})
vcekResponse, err := imdsAPI.getVcek(context.TODO())
if err != nil {
return nil, fmt.Errorf("getVcekFromIMDS: %w", err)
}
instanceInfo := azureInstanceInfo{
Vcek: []byte(vcekResponse.VcekCert),
CertChain: []byte(vcekResponse.CertificateChain),
AttestationReport: hclReport[:0x4a0],
RuntimeData: runtimeData,
}
statement, err := json.Marshal(instanceInfo)
if err != nil {
return nil, fmt.Errorf("marshalling AzureInstanceInfo: %w", err)
}
return statement, nil
}
}
type tpmReport struct{}
func (s *tpmReport) get(tpm io.ReadWriteCloser) ([]byte, error) {
return tpm2.NVReadEx(tpm, 0x01400001, tpm2.HandleOwner, "", 0)
}
type tpmReportGetter interface {
get(tpm io.ReadWriteCloser) ([]byte, error)
}
type imdsApi interface {
getVcek(ctx context.Context) (vcekResponse, error)
}