diff --git a/scheme/Makefile b/scheme/Makefile index 7771f757..0e807e75 100644 --- a/scheme/Makefile +++ b/scheme/Makefile @@ -8,6 +8,7 @@ SUBDIR += psa-iot SUBDIR += tpm-enacttrust SUBDIR += parsec-tpm SUBDIR += parsec-cca +SUBDIR += cca-realm clean: ; $(RM) -rf ./bin diff --git a/scheme/cca-realm/Makefile b/scheme/cca-realm/Makefile new file mode 100644 index 00000000..724dd1b1 --- /dev/null +++ b/scheme/cca-realm/Makefile @@ -0,0 +1,15 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +.DEFAULT_GOAL := test + +GOPKG := github.com/veraison/services/scheme/cca-realm +SRCS := $(wildcard *.go) + +SUBDIR += plugin + +include ../../mk/common.mk +include ../../mk/lint.mk +include ../../mk/pkg.mk +include ../../mk/subdir.mk +include ../../mk/test.mk diff --git a/scheme/cca-realm/README.md b/scheme/cca-realm/README.md index 1e794fef..3b5eb9a5 100644 --- a/scheme/cca-realm/README.md +++ b/scheme/cca-realm/README.md @@ -4,29 +4,29 @@ ```json { - "scheme": "CCA_REALM", - "type": "REFERENCE_VALUE", - "attributes": { - "cca_realm.vendor": "Worload Client Ltd", - "cca_realm.id": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C", - "cca_realm.alg-id": "sha-256", - "cca_realm.measurement-array": [ - { - "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - }, - { - "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - }, - { - "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - }, - { - "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - }, - { - "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" - } - ] - } + "scheme": "CCA_REALM", + "type": "REFERENCE_VALUE", + "attributes": { + "cca_realm.vendor": "Worload Client Ltd", + "cca_realm.id": "CD1F0E55-26F9-460D-B9D8-F7FDE171787C", + "cca_realm.alg-id": "sha-256", + "cca_realm.measurement-array": [ + { + "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + }, + { + "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + }, + { + "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + }, + { + "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + }, + { + "measurement-value": "h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + } + ] + } } ``` diff --git a/scheme/cca-realm/endorsement_handler.go b/scheme/cca-realm/endorsement_handler.go new file mode 100644 index 00000000..a32ea38d --- /dev/null +++ b/scheme/cca-realm/endorsement_handler.go @@ -0,0 +1,33 @@ +// Copyright 2022-2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package cca_realm + +import ( + "github.com/veraison/services/handler" +) + +type EndorsementHandler struct{} + +func (o EndorsementHandler) Init(params handler.EndorsementHandlerParams) error { + return nil // no-op +} + +func (o EndorsementHandler) Close() error { + return nil // no-op +} + +func (o EndorsementHandler) GetName() string { + return "unsigned-corim (CCA realm profile)" +} + +func (o EndorsementHandler) GetAttestationScheme() string { + return SchemeName +} + +func (o EndorsementHandler) GetSupportedMediaTypes() []string { + return EndorsementMediaTypes +} + +func (o EndorsementHandler) Decode(data []byte) (*handler.EndorsementHandlerResponse, error) { + return nil, nil +} diff --git a/scheme/cca-realm/endorsement_handler_test.go b/scheme/cca-realm/endorsement_handler_test.go new file mode 100644 index 00000000..f3547edd --- /dev/null +++ b/scheme/cca-realm/endorsement_handler_test.go @@ -0,0 +1,65 @@ +// Copyright 2022-2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package cca_realm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecoder_GetAttestationScheme(t *testing.T) { + d := &EndorsementHandler{} + + expected := SchemeName + + actual := d.GetAttestationScheme() + + assert.Equal(t, expected, actual) +} + +func TestDecoder_GetSupportedMediaTypes(t *testing.T) { + d := &EndorsementHandler{} + + expected := EndorsementMediaTypes + + actual := d.GetSupportedMediaTypes() + + assert.Equal(t, expected, actual) +} + +func TestDecoder_Init(t *testing.T) { + d := &EndorsementHandler{} + + assert.Nil(t, d.Init(nil)) +} + +func TestDecoder_Close(t *testing.T) { + d := &EndorsementHandler{} + + assert.Nil(t, d.Close()) +} + +func TestDecoder_Decode_empty_data(t *testing.T) { + d := &EndorsementHandler{} + + emptyData := []byte{} + + expectedErr := `empty data` + + _, err := d.Decode(emptyData) + + assert.EqualError(t, err, expectedErr) +} + +func TestDecoder_Decode_invalid_data(t *testing.T) { + d := &EndorsementHandler{} + + invalidCbor := []byte("invalid CBOR") + + expectedErr := `CBOR decoding failed: cbor: cannot unmarshal UTF-8 text string into Go value of type corim.UnsignedCorim` + + _, err := d.Decode(invalidCbor) + + assert.EqualError(t, err, expectedErr) +} diff --git a/scheme/cca-realm/evidence_handler.go b/scheme/cca-realm/evidence_handler.go new file mode 100644 index 00000000..e795c7be --- /dev/null +++ b/scheme/cca-realm/evidence_handler.go @@ -0,0 +1,224 @@ +// Copyright 2021-2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cca_realm + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/veraison/ccatoken" + "github.com/veraison/ear" + "github.com/veraison/psatoken" + "github.com/veraison/services/handler" + "github.com/veraison/services/log" + "github.com/veraison/services/proto" + "github.com/veraison/services/scheme/common" + "github.com/veraison/services/scheme/common/arm" +) + +type EvidenceHandler struct{} + +func (s EvidenceHandler) GetName() string { + return "cca-realm-evidence-handler" +} + +func (s EvidenceHandler) GetAttestationScheme() string { + return SchemeName +} + +func (s EvidenceHandler) GetSupportedMediaTypes() []string { + return EvidenceMediaTypes +} + +func (s EvidenceHandler) SynthKeysFromRefValue( + tenantID string, + refVal *handler.Endorsement, +) ([]string, error) { + return arm.SynthKeysFromRefValue(SchemeName, tenantID, refVal) + +} + +func (s EvidenceHandler) SynthKeysFromTrustAnchor(tenantID string, ta *handler.Endorsement) ([]string, error) { + + return arm.SynthKeysFromTrustAnchors(SchemeName, tenantID, ta) +} + +func (s EvidenceHandler) GetTrustAnchorID(token *proto.AttestationToken) (string, error) { + return arm.GetTrustAnchorID(SchemeName, token) +} + +func (s EvidenceHandler) ExtractClaims( + token *proto.AttestationToken, + trustAnchor string, +) (*handler.ExtractedClaims, error) { + + var ccaToken ccatoken.Evidence + + if err := ccaToken.FromCBOR(token.Data); err != nil { + return nil, handler.BadEvidence(err) + } + + var extracted handler.ExtractedClaims + + platformClaimsSet, err := common.ClaimsToMap(ccaToken.PlatformClaims) + if err != nil { + return nil, handler.BadEvidence(fmt.Errorf( + "could not convert platform claims: %w", err)) + } + + realmClaimsSet, err := common.ClaimsToMap(ccaToken.RealmClaims) + if err != nil { + return nil, handler.BadEvidence(fmt.Errorf( + "could not convert realm claims: %w", err)) + } + + extracted.ClaimsSet = map[string]interface{}{ + "platform": platformClaimsSet, + "realm": realmClaimsSet, + } + + extracted.ReferenceID = arm.RefValLookupKey( + SchemeName, + token.TenantId, + arm.MustImplIDString(ccaToken.PlatformClaims), + ) + log.Debugf("extracted Reference ID Key = %s", extracted.ReferenceID) + return &extracted, nil +} + +// ValidateEvidenceIntegrity, decodes CCA collection and then invokes Verify API of ccatoken library +// which verifies the signature on the platform part of CCA collection, using supplied trust anchor +// and internally verifies the realm part of CCA token using realm public key extracted from +// realm token. +func (s EvidenceHandler) ValidateEvidenceIntegrity( + token *proto.AttestationToken, + trustAnchor string, + endorsementsStrings []string, +) error { + var ( + ccaToken ccatoken.Evidence + ) + + if err := ccaToken.FromCBOR(token.Data); err != nil { + return handler.BadEvidence(err) + } + + realmChallenge, err := ccaToken.RealmClaims.GetChallenge() + if err != nil { + return handler.BadEvidence(err) + } + + // If the provided challenge was less than 64 bytes long, the RMM will + // zero-pad pad it when generating the attestation token, so do the + // same to the session nonce. + sessionNonce := make([]byte, 64) + copy(sessionNonce, token.Nonce) + + if !bytes.Equal(realmChallenge, sessionNonce) { + return handler.BadEvidence( + "freshness: realm challenge (%s) does not match session nonce (%s)", + hex.EncodeToString(realmChallenge), + hex.EncodeToString(token.Nonce), + ) + } + + pk, err := arm.GetPublicKeyFromTA(SchemeName, trustAnchor) + if err != nil { + return fmt.Errorf("could not get public key from trust anchor: %w", err) + } + + if err = ccaToken.Verify(pk); err != nil { + return handler.BadEvidence(err) + } + log.Debug("CCA platform token signature, realm token signature and cryptographic binding verified") + return nil +} + +func (s EvidenceHandler) AppraiseEvidence( + ec *proto.EvidenceContext, endorsementsStrings []string, +) (*ear.AttestationResult, error) { + var endorsements []handler.Endorsement // nolint:prealloc + + 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) + } + + endorsements = append(endorsements, endorsement) + } + + err := populateAttestationResult(result, ec.Evidence.AsMap(), endorsements) + + // TO DO: Handle Unprocessed evidence when new Attestation Result interface + // is ready. Please see issue #105 + return result, err +} + +func populateAttestationResult( + result *ear.AttestationResult, + evidence map[string]interface{}, + endorsements []handler.Endorsement, +) error { + claims, err := common.MapToClaims(evidence["platform"].(map[string]interface{})) + if err != nil { + return err + } + + appraisal := result.Submods[SchemeName] + + // once the signature on the token is verified, we can claim the HW is + // authentic + appraisal.TrustVector.Hardware = ear.GenuineHardwareClaim + + rawLifeCycle, err := claims.GetSecurityLifeCycle() + if err != nil { + return handler.BadEvidence(err) + } + + lifeCycle := psatoken.CcaLifeCycleToState(rawLifeCycle) + if lifeCycle == psatoken.CcaStateSecured || + lifeCycle == psatoken.CcaStateNonCcaPlatformDebug { + appraisal.TrustVector.InstanceIdentity = ear.TrustworthyInstanceClaim + appraisal.TrustVector.RuntimeOpaque = ear.ApprovedRuntimeClaim + appraisal.TrustVector.StorageOpaque = ear.HwKeysEncryptedSecretsClaim + } else { + appraisal.TrustVector.InstanceIdentity = ear.UntrustworthyInstanceClaim + appraisal.TrustVector.RuntimeOpaque = ear.VisibleMemoryRuntimeClaim + appraisal.TrustVector.StorageOpaque = ear.UnencryptedSecretsClaim + } + + swComps := arm.FilterRefVal(endorsements, "CCA_SSD_PLATFORM.sw-component") + match := arm.MatchSoftware(SchemeName, claims, swComps) + if match { + appraisal.TrustVector.Executables = ear.ApprovedRuntimeClaim + log.Debug("matchSoftware Success") + + } else { + appraisal.TrustVector.Executables = ear.UnrecognizedRuntimeClaim + log.Debug("matchSoftware Failed") + } + + platformConfig := arm.FilterRefVal(endorsements, "CCA_SSD_PLATFORM.platform-config") + match = arm.MatchPlatformConfig(SchemeName, claims, platformConfig) + + if match { + appraisal.TrustVector.Configuration = ear.ApprovedConfigClaim + log.Debug("matchPlatformConfig Success") + + } else { + appraisal.TrustVector.Configuration = ear.UnsafeConfigClaim + log.Debug("matchPlatformConfig Failed") + } + appraisal.UpdateStatusFromTrustVector() + + appraisal.VeraisonAnnotatedEvidence = &evidence + + return nil +} diff --git a/scheme/cca-realm/plugin/Makefile b/scheme/cca-realm/plugin/Makefile new file mode 100644 index 00000000..3f2ad321 --- /dev/null +++ b/scheme/cca-realm/plugin/Makefile @@ -0,0 +1,12 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +ifndef COMBINED_PLUGINS + SUBDIR += endorsement-handler + SUBDIR += evidence-handler +else + SUBDIR += combined +endif + +include ../../../mk/common.mk +include ../../../mk/subdir.mk \ No newline at end of file diff --git a/scheme/cca-realm/plugin/combined/Makefile b/scheme/cca-realm/plugin/combined/Makefile new file mode 100644 index 00000000..358ab82d --- /dev/null +++ b/scheme/cca-realm/plugin/combined/Makefile @@ -0,0 +1,11 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +PLUGIN := ../../../bin/cca-realm.plugin +GOPKG := github.com/veraison/services/scheme/cca-realm +SRCS := main.go + +include ../../../../mk/common.mk +include ../../../../mk/plugin.mk +include ../../../../mk/lint.mk +include ../../../../mk/test.mk diff --git a/scheme/cca-realm/plugin/combined/main.go b/scheme/cca-realm/plugin/combined/main.go new file mode 100644 index 00000000..4c72df35 --- /dev/null +++ b/scheme/cca-realm/plugin/combined/main.go @@ -0,0 +1,15 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "github.com/veraison/services/handler" + "github.com/veraison/services/plugin" + scheme "github.com/veraison/services/scheme/cca-realm" +) + +func main() { + handler.RegisterEndorsementHandler(&scheme.EndorsementHandler{}) + handler.RegisterEvidenceHandler(&scheme.EvidenceHandler{}) + plugin.Serve() +} diff --git a/scheme/cca-realm/plugin/endorsement-handler/Makefile b/scheme/cca-realm/plugin/endorsement-handler/Makefile new file mode 100644 index 00000000..665cf496 --- /dev/null +++ b/scheme/cca-realm/plugin/endorsement-handler/Makefile @@ -0,0 +1,11 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +PLUGIN := ../../../bin/cca-realm-endorsement-handler.plugin +GOPKG := github.com/veraison/services/scheme/cca-realm +SRCS := main.go + +include ../../../../mk/common.mk +include ../../../../mk/plugin.mk +include ../../../../mk/lint.mk +include ../../../../mk/test.mk diff --git a/scheme/cca-realm/plugin/endorsement-handler/main.go b/scheme/cca-realm/plugin/endorsement-handler/main.go new file mode 100644 index 00000000..61dcbe9d --- /dev/null +++ b/scheme/cca-realm/plugin/endorsement-handler/main.go @@ -0,0 +1,14 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "github.com/veraison/services/handler" + "github.com/veraison/services/plugin" + scheme "github.com/veraison/services/scheme/cca-realm" +) + +func main() { + handler.RegisterEndorsementHandler(&scheme.EndorsementHandler{}) + plugin.Serve() +} diff --git a/scheme/cca-realm/plugin/evidence-handler/Makefile b/scheme/cca-realm/plugin/evidence-handler/Makefile new file mode 100644 index 00000000..d8c5bb07 --- /dev/null +++ b/scheme/cca-realm/plugin/evidence-handler/Makefile @@ -0,0 +1,11 @@ +# Copyright 2023 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 + +PLUGIN := ../../../bin/cca-realm-evidence-handler.plugin +GOPKG := github.com/veraison/services/scheme/cca-realm +SRCS := main.go + +include ../../../../mk/common.mk +include ../../../../mk/plugin.mk +include ../../../../mk/lint.mk +include ../../../../mk/test.mk diff --git a/scheme/cca-realm/plugin/evidence-handler/main.go b/scheme/cca-realm/plugin/evidence-handler/main.go new file mode 100644 index 00000000..df9d32ee --- /dev/null +++ b/scheme/cca-realm/plugin/evidence-handler/main.go @@ -0,0 +1,14 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package main + +import ( + "github.com/veraison/services/handler" + "github.com/veraison/services/plugin" + scheme "github.com/veraison/services/scheme/cca-realm" +) + +func main() { + handler.RegisterEvidenceHandler(&scheme.EvidenceHandler{}) + plugin.Serve() +} diff --git a/scheme/cca-realm/scheme.go b/scheme/cca-realm/scheme.go new file mode 100644 index 00000000..7c6b0c09 --- /dev/null +++ b/scheme/cca-realm/scheme.go @@ -0,0 +1,15 @@ +// Copyright 2023 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package cca_realm + +const SchemeName = "CCA_REALM" + +var ( + EndorsementMediaTypes = []string{ + "application/corim-unsigned+cbor; profile=http://arm.com/cca/realm/1", + } + + EvidenceMediaTypes = []string{ + "application/eat-collection; profile=http://arm.com/CCA-SSD/1.0.0", + } +)