diff --git a/end-to-end/end-to-end b/end-to-end/end-to-end index 2a791b02..f290b6ff 100755 --- a/end-to-end/end-to-end +++ b/end-to-end/end-to-end @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright 2022-2023 Contributors to the Veraison project. +# Copyright 2022-2024 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 SCHEME=${SCHEME:-psa} @@ -20,6 +20,10 @@ function provision() { local corim_file=$THIS_DIR/input/cca-endorsements.cbor local media_type="application/corim-unsigned+cbor; profile=http://arm.com/cca/ssd/1" ;; + sev) + local corim_file=$THIS_DIR/input/sev-endorsements.cbor + local media_type="application/corim-unsigned+cbor; profile=https://amd.com/sev" + ;; *) echo "ERROR: bad SCHEME: $SCHEME" exit 1 @@ -67,6 +71,9 @@ function verify_as_relying_party() { cca) local token=$THIS_DIR/input/cca-evidence.cbor ;; + sev) + local token=$THIS_DIR/input/sev-evidence.cbor + ;; *) echo "ERROR: bad SCHEME: $SCHEME" exit 1 @@ -108,10 +115,10 @@ EOF function _check_scheme() { case $SCHEME in - psa | cca) + psa | cca | sev) ;; *) - echo "ERROR: unknown SCHEME: '$SCHEME'; must be 'cca' or 'psa'"; exit 1 + echo "ERROR: unknown SCHEME: '$SCHEME'; must be 'cca', 'psa' or 'sev'"; exit 1 ;; esac } diff --git a/end-to-end/input/sev-endorsements.cbor b/end-to-end/input/sev-endorsements.cbor new file mode 100644 index 00000000..a765c27b Binary files /dev/null and b/end-to-end/input/sev-endorsements.cbor differ diff --git a/end-to-end/input/sev-evidence.cbor b/end-to-end/input/sev-evidence.cbor new file mode 100644 index 00000000..1c0e5f8a Binary files /dev/null and b/end-to-end/input/sev-evidence.cbor differ diff --git a/scheme/sev/cbor.go b/scheme/sev/cbor.go new file mode 100644 index 00000000..d20f6d7e --- /dev/null +++ b/scheme/sev/cbor.go @@ -0,0 +1,23 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package sev + +import cbor "github.com/fxamacker/cbor/v2" + +var ( + dm, dmError = initCBORDecMode() +) + +func initCBORDecMode() (dm cbor.DecMode, err error) { + decOpt := cbor.DecOptions{ + IndefLength: cbor.IndefLengthForbidden, + } + return decOpt.DecMode() +} + +func init() { + if dmError != nil { + panic(dmError) + } +} diff --git a/scheme/sev/evidence_handler.go b/scheme/sev/evidence_handler.go index 512ca2d9..9431626a 100644 --- a/scheme/sev/evidence_handler.go +++ b/scheme/sev/evidence_handler.go @@ -7,6 +7,13 @@ import ( "encoding/json" "net/url" "strings" + "os" + "os/exec" + "fmt" + "path/filepath" + "bytes" + + "github.com/google/uuid" "github.com/veraison/services/handler" "github.com/veraison/services/proto" @@ -50,7 +57,20 @@ func (o EvidenceHandler) SynthKeysFromRefValue( } func (o EvidenceHandler) GetTrustAnchorID(token *proto.AttestationToken) (string, error) { - return "ID-Milan", nil + var sevToken SevToken + + err := dm.Unmarshal(token.Data, &sevToken) + if err != nil { + return "", err + } + + u := url.URL{ + Scheme: SchemeName, + Host: "AMD", + Path: strings.Join([]string{"TA", sevToken.Evidence.Core.HwModel}, "/"), + } + + return u.String(), nil } func (o EvidenceHandler) SynthKeysFromTrustAnchor(tenantID string, ta *handler.Endorsement) ([]string, error) { @@ -75,15 +95,192 @@ func (o EvidenceHandler) ExtractClaims( trustAnchor string, ) (*handler.ExtractedClaims, error) { var extracted handler.ExtractedClaims + var claimsSet map[string]interface{} + var sevToken SevToken + + err := dm.Unmarshal(token.Data, &sevToken) + if err != nil { + return nil, err + } + + sevClaims, err := json.Marshal(sevToken) + if err != nil { + return nil, err + } + + err = json.Unmarshal(sevClaims, &claimsSet) + if err != nil { + return nil, err + } + + extracted.ClaimsSet = claimsSet + extracted.ReferenceID = sevToken.ReferenceId return &extracted, nil } +func createCertDirectory() (string, error) { + var err error + var dirName string + var retry int + + for retry = 5; retry > 0; retry-- { + dirName = uuid.New().String() + + _, err = os.Stat("certs" + dirName) + if err != nil { + break + } + } + + if retry == 0 { + return "", fmt.Errorf("failed to get unique name for certificate directory") + } + + dirName = "certs" + dirName + + err = os.Mkdir(dirName, 0755) + if err != nil { + return "", err + } + + return dirName, nil +} + +func populateCertDirectory(dirName string, sevTa SevTrustAnchors, sevToken SevToken) error { + err := os.WriteFile(filepath.Join(".", dirName, "ark.cert"), + sevTa.ARK, 0644) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(".", dirName, "ask.cert"), + sevTa.ASK, 0644) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(".", dirName, "cek.cert"), + sevToken.Evidence.Keys.Cek, 0644) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(".", dirName, "oca.cert"), + sevToken.Evidence.Keys.Oca, 0644) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(".", dirName, "pek.cert"), + sevToken.Evidence.Keys.Pek, 0644) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(".", dirName, "pdh.cert"), + sevToken.Evidence.Keys.Pdh, 0644) + if err != nil { + return err + } + + err = os.WriteFile(filepath.Join(".", dirName, "attestation_report.bin"), + sevToken.Evidence.Core.AttestationReport, 0644) + if err != nil { + return err + } + + return nil +} + +func validateCertChain(dirName string) error { + cmd := exec.Command("sevtool", "--ofolder", dirName, "--validate_cert_chain") + + out, err := cmd.CombinedOutput() + if err != nil { + return err + } + + output := string(out) + + if strings.Contains(output, "Command Unsuccessful:") { + return fmt.Errorf("Certificate chain validation failed") + } + + return nil +} + +func validateAttestationReport(dirName string) error { + cmd := exec.Command("sevtool", "--ofolder", dirName, "--validate_attestation") + + out, err := cmd.CombinedOutput() + if err != nil { + return err + } + + output := string(out) + + if strings.Contains(output, "Command Unsuccessful:") { + return fmt.Errorf("Attestation Report validation failed") + } + return nil +} + func (o EvidenceHandler) ValidateEvidenceIntegrity( token *proto.AttestationToken, trustAnchor string, endorsementsStrings []string, ) error { + var taEndorsement handler.Endorsement + var sevTa SevTrustAnchors + var sevToken SevToken + var err error + var dirName string = "" + + err = dm.Unmarshal(token.Data, &sevToken) + if err != nil { + return err + } + + err = json.Unmarshal([]byte(trustAnchor), &taEndorsement) + if err != nil { + goto exit + } + + err = json.Unmarshal([]byte(taEndorsement.Attributes), &sevTa) + if err != nil { + goto exit + } + + dirName, err = createCertDirectory() + if err != nil { + goto exit + } + + err = populateCertDirectory(dirName, sevTa, sevToken) + if err != nil { + goto exit + } + + err = validateCertChain(dirName) + if err != nil { + goto exit + } + + err = validateAttestationReport(dirName) + if err != nil { + goto exit + } + +exit: + if dirName != "" { + os.RemoveAll(dirName) + } + + if err != nil { + return handler.BadEvidence(err) + } + return nil } @@ -91,7 +288,68 @@ func (o EvidenceHandler) AppraiseEvidence( ec *proto.EvidenceContext, endorsementsStrings []string, ) (*ear.AttestationResult, error) { + var refValEndorsement handler.Endorsement + var refVal SevReferenceValues + var sevToken SevToken + result := handler.CreateAttestationResult(SchemeName) + for i, e := range endorsementsStrings { + var endorsement handler.Endorsement + + if err := json.Unmarshal([]byte(e), &endorsement); err != nil { + return nil, fmt.Errorf("could not decode endorsement at index %d: %w", i, err) + } + + if endorsement.Type == handler.EndorsementType_REFERENCE_VALUE { + refValEndorsement = endorsement + break + } + } + + if refValEndorsement.Type != handler.EndorsementType_REFERENCE_VALUE { + return nil, fmt.Errorf("reference values unavailable") + } + + err := json.Unmarshal([]byte(refValEndorsement.Attributes), &refVal) + if err != nil { + return nil, err + } + + tokenJson, err := json.Marshal(ec.Evidence.AsMap()) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(tokenJson), &sevToken) + if err != nil { + return nil, err + } + + expectedLD := refVal.LaunchDigest + claimedLD := sevToken.Evidence.Core.AttestationReport[0x10:0x30] + + evidence := ec.Evidence.AsMap() + + appraisal := result.Submods[SchemeName] + + if bytes.Equal(expectedLD, claimedLD) { + appraisal.TrustVector.Hardware = ear.GenuineHardwareClaim + appraisal.TrustVector.InstanceIdentity = ear.TrustworthyInstanceClaim + appraisal.TrustVector.RuntimeOpaque = ear.ApprovedRuntimeClaim + appraisal.TrustVector.StorageOpaque = ear.HwKeysEncryptedSecretsClaim + appraisal.TrustVector.Executables = ear.ApprovedRuntimeClaim + } else { + appraisal.TrustVector.Hardware = ear.UnsafeHardwareClaim + appraisal.TrustVector.InstanceIdentity = ear.UntrustworthyInstanceClaim + appraisal.TrustVector.RuntimeOpaque = ear.VisibleMemoryRuntimeClaim + appraisal.TrustVector.StorageOpaque = ear.UnencryptedSecretsClaim + appraisal.TrustVector.Executables = ear.UnrecognizedRuntimeClaim + } + + appraisal.UpdateStatusFromTrustVector() + + appraisal.VeraisonAnnotatedEvidence = &evidence + return result, nil } diff --git a/scheme/sev/sevtoken.go b/scheme/sev/sevtoken.go new file mode 100644 index 00000000..145dfea4 --- /dev/null +++ b/scheme/sev/sevtoken.go @@ -0,0 +1,99 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package sev + +/** + * The following contains a description of the EAT for SEV + */ + +type SevToken struct { + Evidence SevEvidence `cbor:"99989,keyasint" json:"evidence"` + ReferenceId string `cbor:"99988,keyasint" json:"reference-id"` + TrustAnchorId string `cbor:"99987,keyasint" json:"trust-anchor-id"` +} + +type SevEvidence struct { + Nonce []byte `cbor:"99979,keyasint" json:"sev-nonce"` + UniqueId string `cbor:"99978,keyasint" json:"sev-uniqueid"` + Keys SevKeys `cbor:"99977,keyasint" json:"sev-keys"` + Core SevCoreComponents `cbor:"99976,keyasint" json:"sev-core-components"` + SwInfo SevSwInfo `cbor:"99975,keyasint" json:"sev-sw-info"` + InstanceInfo SevInstanceInfo `cbor:"99973,keyasint" json:"sev-instance-info"` +} + +type SevKeys struct { + Cek []byte `cbor:"99499,keyasint" json:"cek"` + Pek []byte `cbor:"99498,keyasint" json:"pek"` + Oca []byte `cbor:"99497,keyasint" json:"oca"` + Pdh []byte `cbor:"99496,keyasint" json:"pdh"` +} + +type SevCoreComponents struct { + HwModel string `cbor:"99399,keyasint" json:"hw-model"` + AttestationReport []byte `cbor:"99398,keyasint" json:"attestation-report"` +} + +type SevSwInfo struct { + Kernel string `cbor:"99349,keyasint" json:"hw-model"` +} + +type SevInstanceInfo struct { + AvailabilityDomain string `cbor:"99199,keyasint" json:"availabilityDomain"` + CanonicalRegionName string `cbor:"99198,keyasint" json:"canonicalRegionName"` + CompartmentId string `cbor:"99197,keyasint" json:"compartmentId"` + DefinedTags SevDefinedTags `cbor:"99196,keyasint" json:"definedTags"` + DisplayName string `cbor:"99195,keyasint" json:"displayName"` + FaultDomain string `cbor:"99194,keyasint" json:"faultDomain"` + Hostname string `cbor:"99193,keyasint" json:"hostname"` + Id string `cbor:"99192,keyasint" json:"id"` + Image string `cbor:"99191,keyasint" json:"image"` + Metadata SevMetadata `cbor:"99190,keyasint" json:"metadata"` + OciAdName string `cbor:"99189,keyasint" json:"ociAdName"` + Region string `cbor:"99188,keyasint" json:"region"` + RegionInfo SevRegionInfo `cbor:"99187,keyasint" json:"regionInfo"` + Shape string `cbor:"99186,keyasint" json:"shape"` + ShapeConfig SevShapeConfig `cbor:"99185,keyasint" json:"shapeConfig"` + State string `cbor:"99184,keyasint" json:"state"` + TimeCreated float64 `cbor:"99183,keyasint" json:"timeCreated"` +} + +type SevDefinedTags struct { + Operations SevOperations `cbor:"99129,keyasint" json:"Operations"` + OracleRecommendedTags SevOracleRecommendedTags `cbor:"99128,keyasint" json:"Oracle-Recommended-Tags"` + OracleTags SevOracleTags `cbor:"99127,keyasint" json:"Oracle-Tags"` +} + +type SevOperations struct { + CreateBy string `cbor:"99099,keyasint" json:"CreateBy"` + CreatedDateTime string `cbor:"99098,keyasint" json:"CreatedDateTime"` +} + +type SevOracleRecommendedTags struct { + ResourceOwner string `cbor:"99089,keyasint" json:"ResourceOwner"` + ResourceType string `cbor:"99088,keyasint" json:"ResourceType"` +} + +type SevOracleTags struct { + CreatedBy string `cbor:"99079,keyasint" json:"CreatedBy"` + CreatedOn string `cbor:"99078,keyasint" json:"CreatedOn"` +} + +type SevMetadata struct { + SshAuthorizedKeys string `cbor:"99069,keyasint" json:"ssh_authorized_keys"` +} + +type SevRegionInfo struct { + RealmDomainComponent string `cbor:"99059,keyasint" json:"realmDomainComponent"` + RealmKey string `cbor:"99058,keyasint" json:"realmKey"` + RegionIdentifier string `cbor:"99057,keyasint" json:"regionIdentifier"` + RegionKey string `cbor:"99056,keyasint" json:"regionKey"` +} + +type SevShapeConfig struct { + BaselineOcpuUtilization string `cbor:"99049,keyasint" json:"baselineOcpuUtilization"` + MaxVnicAttachments float64 `cbor:"99048,keyasint" json:"maxVnicAttachments"` + MemoryInGBs float64 `cbor:"99047,keyasint" json:"memoryInGBs"` + NetworkingBandwidthInGbps float64 `cbor:"99046,keyasint" json:"networkingBandwidthInGbps"` + Ocpus float64 `cbor:"99045,keyasint" json:"ocpus"` +}