2022-09-05 03:06:08 -04:00
/ *
Copyright ( c ) Edgeless Systems GmbH
SPDX - License - Identifier : AGPL - 3.0 - only
* /
2022-03-22 11:03:15 -04:00
package vtpm
import (
2023-03-21 07:46:49 -04:00
"context"
2022-03-22 11:03:15 -04:00
"crypto"
2024-04-16 12:13:47 -04:00
"crypto/sha256"
2022-03-22 11:03:15 -04:00
"encoding/json"
2023-08-16 04:45:54 -04:00
"errors"
2022-03-22 11:03:15 -04:00
"fmt"
"io"
2024-04-16 12:13:47 -04:00
"slices"
2022-03-22 11:03:15 -04:00
2023-10-09 08:37:40 -04:00
"github.com/google/go-sev-guest/proto/sevsnp"
2022-03-22 11:03:15 -04:00
tpmClient "github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm-tools/proto/attest"
tpmProto "github.com/google/go-tpm-tools/proto/tpm"
tpmServer "github.com/google/go-tpm-tools/server"
2023-07-07 07:17:58 -04:00
"github.com/google/go-tpm/legacy/tpm2"
2023-03-09 09:23:42 -05:00
2023-03-08 08:13:57 -05:00
"github.com/edgelesssys/constellation/v2/internal/attestation"
2023-03-09 09:23:42 -05:00
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
2022-03-22 11:03:15 -04:00
)
var (
// AzurePCRSelection are the PCR values verified for Azure Constellations.
// PCR[0] is excluded due to changing rarely, but unpredictably.
// PCR[6] is excluded due to being different for any 2 VMs. See: https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf#%5B%7B%22num%22%3A157%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C33%2C400%2C0%5D
2022-04-21 10:28:37 -04:00
// PCR[10] is excluded since its value is derived from a digest of PCR[0-7]. See: https://sourceforge.net/p/linux-ima/wiki/Home/#ima-measurement-list
2022-03-22 11:03:15 -04:00
AzurePCRSelection = tpm2 . PCRSelection {
Hash : tpm2 . AlgSHA256 ,
PCRs : [ ] int { 1 , 2 , 3 , 4 , 5 , 7 , 8 , 9 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 } ,
}
2022-10-27 05:04:23 -04:00
2022-03-22 11:03:15 -04:00
// GCPPCRSelection are the PCR values verified for GCP Constellations.
// On GCP firmware and other host controlled systems are static. This results in the same PCRs for any 2 VMs using the same image.
GCPPCRSelection = tpmClient . FullPcrSel ( tpm2 . AlgSHA256 )
2022-10-27 05:04:23 -04:00
// AWSPCRSelection are the PCR values verified for AWS based Constellations.
// PCR[1] is excluded. See: https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf#%5B%7B%22num%22:157,%22gen%22:0%7D,%7B%22name%22:%22XYZ%22%7D,33,400,0%5D
// PCR[10] is excluded since its value is derived from a digest of PCR[0-7]. See: https://sourceforge.net/p/linux-ima/wiki/Home/#ima-measurement-list
AWSPCRSelection = tpm2 . PCRSelection {
Hash : tpm2 . AlgSHA256 ,
2022-12-12 08:05:31 -05:00
PCRs : [ ] int { 0 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 } ,
2022-10-27 05:04:23 -04:00
}
// QEMUPCRSelection are the PCR values verified for QEMU based Constellations.
2022-04-21 10:28:37 -04:00
// PCR[1] is excluded. See: https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf#%5B%7B%22num%22:157,%22gen%22:0%7D,%7B%22name%22:%22XYZ%22%7D,33,400,0%5D
// PCR[10] is excluded since its value is derived from a digest of PCR[0-7]. See: https://sourceforge.net/p/linux-ima/wiki/Home/#ima-measurement-list
QEMUPCRSelection = tpm2 . PCRSelection {
Hash : tpm2 . AlgSHA256 ,
PCRs : [ ] int { 0 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 } ,
}
2022-03-22 11:03:15 -04:00
)
type (
// GetTPMAttestationKey loads a TPM key to perform attestation.
GetTPMAttestationKey func ( tpm io . ReadWriter ) ( * tpmClient . Key , error )
// GetTPMTrustedAttestationPublicKey verifies and returns the attestation public key.
2023-03-21 07:46:49 -04:00
GetTPMTrustedAttestationPublicKey func ( context . Context , AttestationDocument , [ ] byte ) ( crypto . PublicKey , error )
2022-03-22 11:03:15 -04:00
// GetInstanceInfo returns VM metdata.
2023-03-21 07:46:49 -04:00
GetInstanceInfo func ( ctx context . Context , tpm io . ReadWriteCloser , extraData [ ] byte ) ( [ ] byte , error )
2022-03-22 11:03:15 -04:00
// ValidateCVM validates confidential computing capabilities of the instance issuing the attestation.
2023-03-06 03:15:52 -05:00
ValidateCVM func ( attestation AttestationDocument , state * attest . MachineState ) error
2022-03-22 11:03:15 -04:00
)
// AttestationDocument contains the TPM attestation with signed user data.
type AttestationDocument struct {
// Attestation contains the TPM event log, PCR values and quotes, and public key of the key used to sign the attestation.
Attestation * attest . Attestation
// InstanceInfo is used to verify the provided public key.
InstanceInfo [ ] byte
2023-02-12 11:26:54 -05:00
// arbitrary data, quoted by the TPM.
UserData [ ] byte
2022-03-22 11:03:15 -04:00
}
// Issuer handles issuing of TPM based attestation documents.
type Issuer struct {
openTPM TPMOpenFunc
getAttestationKey GetTPMAttestationKey
getInstanceInfo GetInstanceInfo
2023-03-08 08:13:57 -05:00
log attestation . Logger
2022-03-22 11:03:15 -04:00
}
// NewIssuer returns a new Issuer.
2023-02-28 10:34:18 -05:00
func NewIssuer (
openTPM TPMOpenFunc , getAttestationKey GetTPMAttestationKey ,
2023-03-08 08:13:57 -05:00
getInstanceInfo GetInstanceInfo , log attestation . Logger ,
2023-02-28 10:34:18 -05:00
) * Issuer {
if log == nil {
2023-03-08 08:13:57 -05:00
log = & attestation . NOPLogger { }
2023-02-28 10:34:18 -05:00
}
2022-03-22 11:03:15 -04:00
return & Issuer {
openTPM : openTPM ,
getAttestationKey : getAttestationKey ,
getInstanceInfo : getInstanceInfo ,
2023-02-28 10:34:18 -05:00
log : log ,
2022-03-22 11:03:15 -04:00
}
}
// Issue generates an attestation document using a TPM.
2023-03-29 03:06:10 -04:00
func ( i * Issuer ) Issue ( ctx context . Context , userData [ ] byte , nonce [ ] byte ) ( res [ ] byte , err error ) {
2024-02-08 09:20:01 -05:00
i . log . Info ( "Issuing attestation statement" )
2023-02-28 10:34:18 -05:00
defer func ( ) {
if err != nil {
2024-02-08 09:20:01 -05:00
i . log . Warn ( fmt . Sprintf ( "Failed to issue attestation statement: %s" , err ) )
2023-02-28 10:34:18 -05:00
}
} ( )
2022-03-22 11:03:15 -04:00
tpm , err := i . openTPM ( )
if err != nil {
return nil , fmt . Errorf ( "opening TPM: %w" , err )
}
defer tpm . Close ( )
// Load the TPM's attestation key
aK , err := i . getAttestationKey ( tpm )
if err != nil {
return nil , fmt . Errorf ( "loading attestation key: %w" , err )
}
defer aK . Close ( )
2023-03-08 08:13:57 -05:00
extraData := attestation . MakeExtraData ( userData , nonce )
2022-03-22 11:03:15 -04:00
// Fetch instance info of the VM
2023-03-29 03:06:10 -04:00
instanceInfo , err := i . getInstanceInfo ( ctx , tpm , extraData )
2022-03-22 11:03:15 -04:00
if err != nil {
return nil , fmt . Errorf ( "fetching instance info: %w" , err )
}
2024-04-16 12:13:47 -04:00
tpmNonce := makeTpmNonce ( instanceInfo , extraData )
// Create an attestation using the loaded key
tpmAttestation , err := aK . Attest ( tpmClient . AttestOpts { Nonce : tpmNonce [ : ] } )
if err != nil {
return nil , fmt . Errorf ( "creating attestation: %w" , err )
}
2022-03-22 11:03:15 -04:00
attDoc := AttestationDocument {
2023-02-28 10:34:18 -05:00
Attestation : tpmAttestation ,
2023-02-12 11:26:54 -05:00
InstanceInfo : instanceInfo ,
UserData : userData ,
2022-03-22 11:03:15 -04:00
}
2023-02-28 10:34:18 -05:00
rawAttDoc , err := json . Marshal ( attDoc )
if err != nil {
return nil , fmt . Errorf ( "marshaling attestation document: %w" , err )
}
2024-02-08 09:20:01 -05:00
i . log . Info ( "Successfully issued attestation statement" )
2023-02-28 10:34:18 -05:00
return rawAttDoc , nil
2022-03-22 11:03:15 -04:00
}
// Validator handles validation of TPM based attestation.
type Validator struct {
2023-02-12 11:33:33 -05:00
expected measurements . M
getTrustedKey GetTPMTrustedAttestationPublicKey
validateCVM ValidateCVM
2022-08-12 09:59:45 -04:00
2023-03-08 08:13:57 -05:00
log attestation . Logger
2022-03-22 11:03:15 -04:00
}
// NewValidator returns a new Validator.
2022-11-24 04:57:58 -05:00
func NewValidator ( expected measurements . M , getTrustedKey GetTPMTrustedAttestationPublicKey ,
2023-03-08 08:13:57 -05:00
validateCVM ValidateCVM , log attestation . Logger ,
2022-08-12 09:59:45 -04:00
) * Validator {
2023-01-26 05:24:29 -05:00
if log == nil {
2023-03-08 08:13:57 -05:00
log = & attestation . NOPLogger { }
2023-01-26 05:24:29 -05:00
}
2022-03-22 11:03:15 -04:00
return & Validator {
2023-02-12 11:33:33 -05:00
expected : expected ,
getTrustedKey : getTrustedKey ,
validateCVM : validateCVM ,
log : log ,
2022-03-22 11:03:15 -04:00
}
}
// Validate a TPM based attestation.
2023-03-29 03:06:10 -04:00
func ( v * Validator ) Validate ( ctx context . Context , attDocRaw [ ] byte , nonce [ ] byte ) ( userData [ ] byte , err error ) {
2024-02-08 09:20:01 -05:00
v . log . Info ( "Validating attestation document" )
2023-01-26 05:24:29 -05:00
defer func ( ) {
if err != nil {
2024-02-08 09:20:01 -05:00
v . log . Warn ( fmt . Sprintf ( "Failed to validate attestation document: %s" , err ) )
2023-01-26 05:24:29 -05:00
}
} ( )
2022-10-14 10:29:21 -04:00
2023-10-09 08:37:40 -04:00
// Explicitly initialize this struct, as TeeAttestation
// is a "oneof" protobuf field, which needs an explicit
// type to be set to be unmarshaled correctly.
// Note: this value is incompatible with TDX attestation!
// TODO(msanft): select the correct attestation type (SEV-SNP, TDX, ...) here.
attDoc := AttestationDocument {
Attestation : & attest . Attestation {
TeeAttestation : & attest . Attestation_SevSnpAttestation {
SevSnpAttestation : & sevsnp . Attestation { } ,
} ,
} ,
}
2022-03-22 11:03:15 -04:00
if err := json . Unmarshal ( attDocRaw , & attDoc ) ; err != nil {
return nil , fmt . Errorf ( "unmarshaling TPM attestation document: %w" , err )
}
2023-03-08 08:13:57 -05:00
extraData := attestation . MakeExtraData ( attDoc . UserData , nonce )
2023-03-21 07:46:49 -04:00
2022-03-22 11:03:15 -04:00
// Verify and retrieve the trusted attestation public key using the provided instance info
2023-03-29 03:06:10 -04:00
aKP , err := v . getTrustedKey ( ctx , attDoc , extraData )
2022-03-22 11:03:15 -04:00
if err != nil {
return nil , fmt . Errorf ( "validating attestation public key: %w" , err )
}
2024-04-16 12:13:47 -04:00
tpmNonce := makeTpmNonce ( attDoc . InstanceInfo , extraData )
2022-03-22 11:03:15 -04:00
// Verify the TPM attestation
2023-03-06 03:17:08 -05:00
state , err := tpmServer . VerifyAttestation (
2022-03-22 11:03:15 -04:00
attDoc . Attestation ,
tpmServer . VerifyOpts {
2024-04-16 12:13:47 -04:00
Nonce : tpmNonce [ : ] ,
2022-03-22 11:03:15 -04:00
TrustedAKs : [ ] crypto . PublicKey { aKP } ,
AllowSHA1 : false ,
} ,
2023-03-06 03:17:08 -05:00
)
if err != nil {
2022-03-22 11:03:15 -04:00
return nil , fmt . Errorf ( "verifying attestation document: %w" , err )
}
2023-03-06 03:17:08 -05:00
// Validate confidential computing capabilities of the VM
if err := v . validateCVM ( attDoc , state ) ; err != nil {
return nil , fmt . Errorf ( "verifying VM confidential computing capabilities: %w" , err )
}
2022-03-22 11:03:15 -04:00
// Verify PCRs
quoteIdx , err := GetSHA256QuoteIndex ( attDoc . Attestation . Quotes )
if err != nil {
return nil , err
}
2023-08-16 04:45:54 -04:00
warnings , errs := v . expected . Compare ( attDoc . Attestation . Quotes [ quoteIdx ] . Pcrs . Pcrs )
for _ , warning := range warnings {
2024-02-08 09:20:01 -05:00
v . log . Warn ( warning )
2023-08-16 04:45:54 -04:00
}
if len ( errs ) > 0 {
return nil , fmt . Errorf ( "measurement validation failed:\n%w" , errors . Join ( errs ... ) )
2022-03-22 11:03:15 -04:00
}
2024-02-08 09:20:01 -05:00
v . log . Info ( "Successfully validated attestation document" )
2022-03-22 11:03:15 -04:00
return attDoc . UserData , nil
}
// GetSHA256QuoteIndex performs safety checks and returns the index for SHA256 PCR quotes.
func GetSHA256QuoteIndex ( quotes [ ] * tpmProto . Quote ) ( int , error ) {
if len ( quotes ) == 0 {
return 0 , fmt . Errorf ( "attestation is missing quotes" )
}
for idx , quote := range quotes {
if quote == nil {
return 0 , fmt . Errorf ( "quote is nil" )
}
if quote . Pcrs == nil {
return 0 , fmt . Errorf ( "no PCR data in attestation" )
}
if quote . Pcrs . Hash == tpmProto . HashAlgo_SHA256 {
return idx , nil
}
}
return 0 , fmt . Errorf ( "attestation did not include SHA256 hashed PCRs" )
}
2022-11-24 04:57:58 -05:00
// GetSelectedMeasurements returns a map of Measurments for the PCRs in selection.
func GetSelectedMeasurements ( open TPMOpenFunc , selection tpm2 . PCRSelection ) ( measurements . M , error ) {
2022-03-22 11:03:15 -04:00
tpm , err := open ( )
if err != nil {
return nil , err
}
defer tpm . Close ( )
pcrList , err := tpmClient . ReadPCRs ( tpm , selection )
if err != nil {
return nil , err
}
2022-11-24 04:57:58 -05:00
m := make ( measurements . M )
for i , pcr := range pcrList . Pcrs {
if len ( pcr ) != 32 {
return nil , fmt . Errorf ( "invalid measurement: invalid length: %d" , len ( pcr ) )
}
m [ i ] = measurements . Measurement {
2023-03-08 08:13:57 -05:00
Expected : pcr ,
2022-11-24 04:57:58 -05:00
}
}
return m , nil
2022-03-22 11:03:15 -04:00
}
2024-04-16 12:13:47 -04:00
// makeTpmNonce creates a nonce for the TPM attestation and returns it in its marshaled form.
func makeTpmNonce ( instanceInfo [ ] byte , extraData [ ] byte ) [ 32 ] byte {
// Finding: GCP nonces cannot be larger than 32 bytes.
return sha256 . Sum256 ( slices . Concat ( instanceInfo , extraData ) )
}