2022-03-22 11:03:15 -04:00
package azure
import (
2022-08-19 06:26:29 -04:00
"bytes"
"context"
"encoding/json"
"fmt"
2022-03-22 11:03:15 -04:00
"io"
2022-08-19 06:26:29 -04:00
"net/http"
2022-03-22 11:03:15 -04:00
2022-06-01 09:08:42 -04:00
"github.com/edgelesssys/constellation/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/internal/oid"
2022-03-22 11:03:15 -04:00
tpmclient "github.com/google/go-tpm-tools/client"
2022-08-19 06:26:29 -04:00
"github.com/google/go-tpm/tpm2"
)
const (
lenHclHeader = 0x20
lenSnpReport = 0x4a0
lenSnpReportRuntimeDataPadding = 0x14
2022-08-29 10:41:09 -04:00
tpmReportIdx = 0x01400001
2022-03-22 11:03:15 -04:00
)
// Issuer for Azure TPM attestation.
type Issuer struct {
oid . Azure
* vtpm . Issuer
}
// NewIssuer initializes a new Azure Issuer.
func NewIssuer ( ) * Issuer {
2022-08-19 06:26:29 -04:00
imdsAPI := imdsClient {
client : & http . Client { Transport : & http . Transport { Proxy : nil } } ,
}
2022-03-22 11:03:15 -04:00
return & Issuer {
Issuer : vtpm . NewIssuer (
vtpm . OpenVTPM ,
2022-08-19 06:26:29 -04:00
getHCLAttestationKey ,
getSNPAttestation ( & tpmReport { } , imdsAPI ) ,
2022-03-22 11:03:15 -04:00
) ,
}
}
2022-08-29 10:41:09 -04:00
// GetIdKeyDigest reads the idkeydigest from the snp report saved in the TPM's non-volatile memory.
func GetIdKeyDigest ( open vtpm . TPMOpenFunc ) ( [ ] byte , error ) {
tpm , err := open ( )
if err != nil {
return nil , err
}
defer tpm . Close ( )
reportRaw , err := tpm2 . NVReadEx ( tpm , tpmReportIdx , tpm2 . HandleOwner , "" , 0 )
if err != nil {
return nil , fmt . Errorf ( "reading idx %x from TMP: %w" , tpmReportIdx , err )
}
report , err := newSNPReportFromBytes ( reportRaw )
if err != nil {
return nil , fmt . Errorf ( "creating snp report: %w" , err )
}
return report . IdKeyDigest [ : ] , nil
}
2022-08-19 06:26:29 -04:00
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 ) {
2022-08-29 10:41:09 -04:00
return tpm2 . NVReadEx ( tpm , tpmReportIdx , tpm2 . HandleOwner , "" , 0 )
2022-08-19 06:26:29 -04:00
}
type tpmReportGetter interface {
get ( tpm io . ReadWriteCloser ) ( [ ] byte , error )
}
type imdsApi interface {
getVcek ( ctx context . Context ) ( vcekResponse , error )
2022-03-22 11:03:15 -04:00
}