constellation/internal/attestation/azure/azure.go

117 lines
3.5 KiB
Go
Raw Normal View History

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
# Azure attestation
Constellation supports multiple attestation technologies on Azure.
- SEV - Secure Nested Paging (SEV-SNP)
TPM attestation verified using an SEV-SNP attestation statement.
- TDX - Trust Domain Extensions
TPM attestation verified using a TDX attestation statement.
- Trusted Launch
Basic TPM attestation.
*/
package azure
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
tpmclient "github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm/legacy/tpm2"
)
const (
// tpmAkIdx is the NV index of the attestation key used by Azure VMs.
tpmAkIdx = 0x81000003
)
// GetAttestationKey reads the attestation key put into the TPM during early boot.
func GetAttestationKey(tpm io.ReadWriter) (*tpmclient.Key, error) {
ak, err := tpmclient.LoadCachedKey(tpm, tpmAkIdx, tpmclient.NullSession{})
if err != nil {
return nil, fmt.Errorf("reading HCL attestation key from TPM: %w", err)
}
return ak, nil
}
// HCLAkValidator validates an attestation key issued by the Host Compatibility Layer (HCL).
// The HCL is written by Azure, and sits between the Hypervisor and CVM OS.
// The HCL runs in the protected context of the CVM.
type HCLAkValidator struct{}
// Validate validates that the attestation key from the TPM is trustworthy. The steps are:
// 1. runtime data read from the TPM has the same sha256 digest as reported in `report_data` of the SNP report or `TdQuoteBody.ReportData` of the TDX report.
// 2. modulus reported in runtime data matches modulus from key at idx 0x81000003.
// 3. exponent reported in runtime data matches exponent from key at idx 0x81000003.
// The function is currently tested manually on a Azure Ubuntu CVM.
func (a *HCLAkValidator) Validate(runtimeDataRaw []byte, reportData []byte, rsaParameters *tpm2.RSAParams) error {
var rtData runtimeData
if err := json.Unmarshal(runtimeDataRaw, &rtData); err != nil {
return fmt.Errorf("unmarshalling json: %w", err)
}
sum := sha256.Sum256(runtimeDataRaw)
if len(reportData) < len(sum) {
return fmt.Errorf("reportData has unexpected size: %d", len(reportData))
}
if !bytes.Equal(sum[:], reportData[:len(sum)]) {
return errors.New("unexpected runtimeData digest in TPM")
}
if len(rtData.PublicPart) < 1 {
return errors.New("did not receive any keys in runtime data")
}
rawN, err := base64.RawURLEncoding.DecodeString(rtData.PublicPart[0].N)
if err != nil {
return fmt.Errorf("decoding modulus string: %w", err)
}
if !bytes.Equal(rawN, rsaParameters.ModulusRaw) {
return fmt.Errorf("unexpected modulus value in TPM")
}
rawE, err := base64.RawURLEncoding.DecodeString(rtData.PublicPart[0].E)
if err != nil {
return fmt.Errorf("decoding exponent string: %w", err)
}
paddedRawE := make([]byte, 4)
copy(paddedRawE, rawE)
exponent := binary.LittleEndian.Uint32(paddedRawE)
// According to this comment [1] the TPM uses "0" to represent the default exponent "65537".
// The go tpm library also reports the exponent as 0. Thus we have to handle it specially.
// [1] https://github.com/tpm2-software/tpm2-tools/pull/1973#issue-596685005
if !((exponent == 65537 && rsaParameters.ExponentRaw == 0) || exponent == rsaParameters.ExponentRaw) {
return fmt.Errorf("unexpected N value in TPM")
}
return nil
}
type runtimeData struct {
PublicPart []akPub `json:"keys"`
}
// akPub are the public parameters of an RSA attestation key.
type akPub struct {
E string `json:"e"`
N string `json:"n"`
}