From 8f040b52f5bba0a27f6373b86731bc7463053059 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 22 Jul 2024 11:51:51 +0100 Subject: [PATCH] refactor!: more conventional marshaling This commit refactors marshaling API. It has two objectives: - Align how marshaling works with conventional Go practices, ensuring new users familiar with other Go libraries have minimal surprises. - Minimize the impact of marshaling on IClaims interface, relieving the burden on current and future implementations. To that end, the following changes are made: - From/To* marshaling API are replaced with implementations of standard cbor and json marshaling interfaces (i.e. Unmasrshal/Marshal*). As per convention, these implementations do not validate. - There are no validating versions of marshaling methods (thus no requirement for every IClaims implementation to re-implement them). Instead, there are convenience functions that instantiate, marshal, and validate in a single call. - The functions have been renamed such that Decode* now only performs unmarshaling, and DecodeAndValidate* unmarshals and validates. - Decoding/Encoding functions for IClaims have been moved from profile.go to iclaims.go. note: this is implemented on top of a similar refactor inside psatoken BREAKING CHANGE: the From/To marshaling API have been removed; Decode* functions have been renamed to be consistent with marshaling (rather then DecodeXXX--that validates--and DecodeUnvalidatedXXX, there is now DecodeAndValidateXXX and DecodeXXX--that does not validate). Signed-off-by: Sergei Trofimov --- .github/workflows/ci-go-cover.yml | 2 +- evidence.go | 441 ++++++++++++++---------------- evidence_test.go | 135 +++++---- go.mod | 2 +- go.sum | 4 +- platform/claims.go | 93 +------ platform/claims_test.go | 98 +++---- platform/iclaims.go | 102 ++++++- platform/iclaims_test.go | 6 +- realm/claims.go | 81 ------ realm/claims_test.go | 77 +++--- realm/iclaims.go | 86 +++++- 12 files changed, 552 insertions(+), 575 deletions(-) diff --git a/.github/workflows/ci-go-cover.yml b/.github/workflows/ci-go-cover.yml index c0c9b4e..deea136 100644 --- a/.github/workflows/ci-go-cover.yml +++ b/.github/workflows/ci-go-cover.yml @@ -14,7 +14,7 @@ # 1. Change workflow name from "cover 100%" to "cover ≥92.5%". Script will automatically use 92.5%. # 2. Update README.md to use the new path to badge.svg because the path includes the workflow name. -name: cover ≥85.0% +name: cover ≥83.1% on: [push, pull_request] jobs: cover: diff --git a/evidence.go b/evidence.go index cea203b..3201c9b 100644 --- a/evidence.go +++ b/evidence.go @@ -34,126 +34,155 @@ type JSONCollection struct { RealmToken json.RawMessage `json:"cca-realm-delegated-token,omitempty"` } -// Evidence is a wrapper around CcaToken -type Evidence struct { - PlatformClaims platform.IClaims - RealmClaims realm.IClaims - collection *CBORCollection -} +// DecodeAndValidateEvidenceFromCBOR unmarshals CCA claims collection from +// provided CBOR and validates both sets of claims. +func DecodeAndValidateEvidenceFromCBOR(buf []byte) (*Evidence, error) { + ev, err := DecodeEvidenceFromCBOR(buf) + if err != nil { + return nil, err + } -func (e *Evidence) MarshalJSON() ([]byte, error) { - if e.PlatformClaims == nil || e.RealmClaims == nil { - return nil, errors.New("invalid evidence") + if err := ev.Validate(); err != nil { + return nil, err } - pj, err := e.PlatformClaims.ToJSON() - if err != nil { - return nil, fmt.Errorf("error serializing platform claims: %w", err) + return ev, nil +} + +// DecodeEvidenceFromCBOR unmarshals CCA claims collection from provided CBOR. +func DecodeEvidenceFromCBOR(buf []byte) (*Evidence, error) { + ev := &Evidence{} + + if err := ev.UnmarshalCBOR(buf); err != nil { + return nil, err } - rj, err := e.RealmClaims.ToJSON() + return ev, nil +} + +// DecodeAndValidateEvidenceFromJSON unmarshals CCA claims collection from +// provided JSON and validates both sets of claims. +func DecodeAndValidateEvidenceFromJSON(buf []byte) (*Evidence, error) { + ev, err := DecodeEvidenceFromJSON(buf) if err != nil { - return nil, fmt.Errorf("error serializing realm claims: %w", err) + return nil, err } - c := JSONCollection{ - PlatformToken: pj, - RealmToken: rj, + if err := ev.Validate(); err != nil { + return nil, err } - return json.Marshal(c) + return ev, nil } -func (e *Evidence) MarshalUnvalidatedJSON() ([]byte, error) { - var pj, rj []byte - var err error +// DecodeEvidenceFromJSON unmarshals CCA claims collection from provided JSON. +func DecodeEvidenceFromJSON(buf []byte) (*Evidence, error) { + ev := &Evidence{} - if e.PlatformClaims != nil { - pj, err = e.PlatformClaims.ToUnvalidatedJSON() - if err != nil { - return nil, fmt.Errorf("error serializing platform claims: %w", err) - } + if err := ev.UnmarshalJSON(buf); err != nil { + return nil, err } - if e.RealmClaims != nil { - rj, err = e.RealmClaims.ToUnvalidatedJSON() - if err != nil { - return nil, fmt.Errorf("error serializing realm claims: %w", err) - } - } + return ev, nil +} - c := JSONCollection{ - PlatformToken: pj, - RealmToken: rj, +// ValidateAndEncodeEvidenceToJSON validates and then marshals CCA platform claims +// to JSON. +func ValidateAndEncodeEvidenceToJSON(e *Evidence) ([]byte, error) { + if err := e.Validate(); err != nil { + return nil, err } - return json.Marshal(c) + return EncodeEvidenceToJSON(e) } -func (e *Evidence) UnmarshalJSON(data []byte) error { - p, r, err := e.doUnmarshalJSON(data) - if err != nil { - return err - } +// EncodeEvidenceToJSON marshals CCA platform claims to JSON. +func EncodeEvidenceToJSON(e *Evidence) ([]byte, error) { + return json.Marshal(e) +} - if p == nil { - return errors.New("unmarshaling CCA claims: missing platform claims") +// Evidence is a wrapper around CcaToken +type Evidence struct { + PlatformClaims platform.IClaims + RealmClaims realm.IClaims + collection *CBORCollection +} +func (e *Evidence) Validate() error { + if e.PlatformClaims == nil && e.RealmClaims == nil { + return errors.New("claims not set in evidence") + } else if e.PlatformClaims == nil { + return errors.New("missing platform claims") + } else if e.RealmClaims == nil { + return errors.New("missing realm claims") } - if r == nil { - return errors.New("unmarshaling CCA claims: missing realm claims") + + if err := e.PlatformClaims.Validate(); err != nil { + return fmt.Errorf("validation of cca-platform-claims failed: %w", err) } - if err := e.SetClaims(p, r); err != nil { - return fmt.Errorf("setting claims: %w", err) + if err := e.RealmClaims.Validate(); err != nil { + return fmt.Errorf("validation of cca-realm-claims failed: %w", err) } return nil } -func (e *Evidence) UnmarshalUnvalidatedJSON(data []byte) error { - p, r, err := e.doUnmarshalJSON(data) +// UnmarshalCBOR extracts and validates the realm and platform tokens from the +// serialized collection. +func (e *Evidence) UnmarshalCBOR(buf []byte) error { + e.collection = &CBORCollection{} + + err := dm.Unmarshal(buf, e.collection) if err != nil { - return err + return fmt.Errorf("CBOR decoding of CCA evidence failed: %w", err) } - if err := e.SetUnvalidatedClaims(p, r); err != nil { - return fmt.Errorf("setting claims: %w", err) + if e.collection.PlatformToken == nil { + return fmt.Errorf("CCA platform token not set") + } + + if e.collection.RealmToken == nil { + return fmt.Errorf("CCA realm token not set") + } + + // This will decode both platform and realm claims + err = e.decodeClaimsFromCBOR() + if err != nil { + return fmt.Errorf("decoding of CCA evidence failed: %w", err) } return nil } -func (e *Evidence) doUnmarshalJSON(data []byte) (platform.IClaims, realm.IClaims, error) { - var c map[string]json.RawMessage - - if err := json.Unmarshal(data, &c); err != nil { - return nil, nil, fmt.Errorf("unmarshaling CCA claims: %w", err) +func (e *Evidence) MarshalJSON() ([]byte, error) { + pj, err := platform.EncodeClaimsToJSON(e.PlatformClaims) + if err != nil { + return nil, fmt.Errorf("error serializing platform claims: %w", err) } - // platform - var p platform.IClaims - platToken, ok := c["cca-platform-token"] - if ok { - p = platform.NewClaims() + rj, err := realm.EncodeClaimsToJSON(e.RealmClaims) + if err != nil { + return nil, fmt.Errorf("error serializing realm claims: %w", err) + } - if err := json.Unmarshal(platToken, &p); err != nil { - return nil, nil, fmt.Errorf("unmarshaling platform claims: %w", err) - } + c := JSONCollection{ + PlatformToken: pj, + RealmToken: rj, } - // realm - var r realm.IClaims - realmToken, ok := c["cca-realm-delegated-token"] - if ok { - r = realm.NewClaims() + return json.Marshal(c) +} - if err := json.Unmarshal(realmToken, &r); err != nil { - return nil, nil, fmt.Errorf("unmarshaling realm claims: %w", err) - } +func (e *Evidence) UnmarshalJSON(data []byte) error { + p, r, err := e.doUnmarshalJSON(data) + if err != nil { + return err } - return p, r, nil + e.SetUnvalidatedClaims(p, r) + + return nil } func (e *Evidence) SetClaims(p platform.IClaims, r realm.IClaims) error { @@ -161,12 +190,7 @@ func (e *Evidence) SetClaims(p platform.IClaims, r realm.IClaims) error { return errors.New("nil claims supplied") } - if err := e.SetUnvalidatedClaims(p, r); err != nil { - return err - } - - e.RealmClaims = r - e.PlatformClaims = p + e.SetUnvalidatedClaims(p, r) // This call will set the nonce in the platform claims based on the RAK and // hash algorithm in the realm claims. @@ -185,7 +209,7 @@ func (e *Evidence) SetClaims(p platform.IClaims, r realm.IClaims) error { return nil } -func (e *Evidence) SetUnvalidatedClaims(p platform.IClaims, r realm.IClaims) error { +func (e *Evidence) SetUnvalidatedClaims(p platform.IClaims, r realm.IClaims) { e.RealmClaims = r e.PlatformClaims = p @@ -194,17 +218,22 @@ func (e *Evidence) SetUnvalidatedClaims(p platform.IClaims, r realm.IClaims) err if p != nil && r != nil { _ = e.bind() } +} + +// ValidateAndSign validates and then signs the given evidence using the +// supplied Platform and Realm Signer and returns the complete CCA token as +// CBOR bytes +func (e *Evidence) ValidateAndSign(pSigner cose.Signer, rSigner cose.Signer) ([]byte, error) { + if err := e.Validate(); err != nil { + return nil, err + } - return nil + return e.Sign(pSigner, rSigner) } // Sign signs the given evidence using the supplied Platform and Realm Signer // and returns the complete CCA token as CBOR bytes func (e *Evidence) Sign(pSigner cose.Signer, rSigner cose.Signer) ([]byte, error) { - if e.PlatformClaims == nil || e.RealmClaims == nil { - return nil, fmt.Errorf("claims not set in evidence") - } - if pSigner == nil || rSigner == nil { return nil, fmt.Errorf("nil signer(s) supplied") } @@ -233,124 +262,116 @@ func (e *Evidence) Sign(pSigner cose.Signer, rSigner cose.Signer) ([]byte, error return buf, nil } -// Sign signs the given evidence using the supplied Platform and Realm Signer -// and returns the complete CCA token as CBOR bytes -func (e *Evidence) SignUnvalidated(pSigner cose.Signer, rSigner cose.Signer) ([]byte, error) { - if pSigner == nil || rSigner == nil { - return nil, fmt.Errorf("nil signer(s) supplied") +// Verify verifies the CCA evidence using the supplied platform public key. +// The integrity of the realm token is checked by extracting the inlined realm +// public key. This also checks the correctness of the chaining between +// platform and realm tokens. +func (e *Evidence) Verify(iak crypto.PublicKey) error { + if e.collection == nil { + return fmt.Errorf("no message found") } - var err error - var platformToken []byte - - if e.PlatformClaims != nil { - platformToken, err = signUnvalidatedClaims(e.PlatformClaims, pSigner) - if err != nil { - return nil, fmt.Errorf("signing platform claims: %w", err) - - } - } else { - platformToken = []byte("") + // Check CCA Platform Token + if e.collection.PlatformToken == nil { + return fmt.Errorf("missing CCA platform Token") } - var realmToken []byte - if e.RealmClaims != nil { - realmToken, err = signUnvalidatedClaims(e.RealmClaims, rSigner) - if err != nil { - return nil, fmt.Errorf("signing realm claims: %w", err) - } - } else { - realmToken = []byte("") + // First verify the platform token + if err := e.verifyCOSEToken(*e.collection.PlatformToken, iak); err != nil { + return fmt.Errorf("unable to verify platform token: %w", err) } - e.collection = &CBORCollection{ - PlatformToken: &platformToken, - RealmToken: &realmToken, + // Check CCA Realm Token + if e.collection.RealmToken == nil { + return fmt.Errorf("missing CCA realm Token") } - // We do now have CcaPlatform and Realm Token setup correctly. - buf, err := em.Marshal(e.collection) + // extract RAK from the realm token + rawRAK, err := e.RealmClaims.GetPubKey() if err != nil { - return nil, fmt.Errorf("CBOR encoding of CCA token failed: %w", err) + return fmt.Errorf("extracting RAK from the realm token: %w", err) } - return buf, nil -} - -type CBORClaimer interface { - ToCBOR() ([]byte, error) - ToUnvalidatedCBOR() ([]byte, error) -} - -func signClaims(claimer CBORClaimer, signer cose.Signer) ([]byte, error) { - claimSet, err := claimer.ToCBOR() + rak, err := ecdsaPublicKeyFromRaw(rawRAK) if err != nil { - return nil, fmt.Errorf("CBOR encoding the payload: %w", err) + return fmt.Errorf("decoding RAK: %w", err) } - return signPayload(claimSet, signer) -} - -func signUnvalidatedClaims(claimer CBORClaimer, signer cose.Signer) ([]byte, error) { - claimSet, err := claimer.ToUnvalidatedCBOR() - if err != nil { - return nil, fmt.Errorf("CBOR encoding the payload: %w", err) + // Next verify the realm token + if err := e.verifyCOSEToken(*e.collection.RealmToken, rak); err != nil { + return fmt.Errorf("unable to verify realm token: %w", err) } - return signPayload(claimSet, signer) -} - -func signPayload(payload []byte, signer cose.Signer) ([]byte, error) { - alg := signer.Algorithm() - if strings.Contains(alg.String(), "unknown algorithm value") { - return nil, errors.New("signer has no algorithm") + // check the collection binding + if err := e.checkBinding(); err != nil { + return fmt.Errorf("binding verification failed: %w", err) } - message := cose.NewSign1Message() - message.Payload = payload - message.Headers.Protected.SetAlgorithm(alg) + return nil +} - err := message.Sign(rand.Reader, []byte(""), signer) +// GetInstanceID returns the InstanceID from CCA platform token +// or a nil pointer if no suitable InstanceID could be located. +func (e *Evidence) GetInstanceID() *[]byte { + instID, err := e.PlatformClaims.GetInstID() if err != nil { - return nil, fmt.Errorf("COSE Sign1 failed: %w", err) + return nil } + return &instID +} - sign1, err := message.MarshalCBOR() +// GetImplementationID returns the ImplementationID from CCA platform token +// or a nil pointer if no suitable ImplementationID could be located. +func (e *Evidence) GetImplementationID() *[]byte { + implID, err := e.PlatformClaims.GetImplID() if err != nil { - return nil, fmt.Errorf("CBOR encoding the COSE Sign1: %w", err) + return nil } - - return sign1, nil + return &implID } -// FromCBOR extracts and validates the realm and platform tokens from the -// serialized collection. -func (e *Evidence) FromCBOR(buf []byte) error { - e.collection = &CBORCollection{} - - err := dm.Unmarshal(buf, e.collection) +// GetRealmPublicKey returns the RMM Public Key +// RMM Public Key is used to verify the signature on the Realm Token +func (e *Evidence) GetRealmPublicKey() *[]byte { + pubKey, err := e.RealmClaims.GetPubKey() if err != nil { - return fmt.Errorf("CBOR decoding of CCA evidence failed: %w", err) + return nil } + return &pubKey +} - if e.collection.PlatformToken == nil { - return fmt.Errorf("CCA platform token not set") +func (e *Evidence) doUnmarshalJSON(data []byte) (platform.IClaims, realm.IClaims, error) { + var c map[string]json.RawMessage + var err error + + if err = json.Unmarshal(data, &c); err != nil { + return nil, nil, fmt.Errorf("unmarshaling CCA claims: %w", err) } - if e.collection.RealmToken == nil { - return fmt.Errorf("CCA realm token not set") + // platform + var p platform.IClaims + platToken, ok := c["cca-platform-token"] + if ok && platToken != nil { + p, err = platform.DecodeClaimsFromJSON(platToken) + if err != nil { + return nil, nil, fmt.Errorf("unmarshaling platform claims: %w", err) + } } - // This will decode both platform and realm claims - err = e.decodeClaims() - if err != nil { - return fmt.Errorf("decoding of CCA evidence failed: %w", err) + // realm + var r realm.IClaims + realmToken, ok := c["cca-realm-delegated-token"] + if ok && realmToken != nil { + r, err = realm.DecodeClaimsFromJSON(realmToken) + if err != nil { + return nil, nil, fmt.Errorf("unmarshaling realm claims: %w", err) + } } - return nil + return p, r, nil } -func (e *Evidence) decodeClaims() error { +func (e *Evidence) decodeClaimsFromCBOR() error { if e.collection.RealmToken == nil || e.collection.PlatformToken == nil { panic("broken invariant: nil tokens") } @@ -362,7 +383,7 @@ func (e *Evidence) decodeClaims() error { return fmt.Errorf("failed CBOR decoding for CWT: %w", err) } - PlatformClaims, err := platform.DecodeClaims(pSign1.Payload) + PlatformClaims, err := platform.DecodeClaimsFromCBOR(pSign1.Payload) if err != nil { return fmt.Errorf("failed CBOR decoding of CCA platform claims: %w", err) } @@ -375,7 +396,7 @@ func (e *Evidence) decodeClaims() error { return fmt.Errorf("failed CBOR decoding for CWT: %w", err) } - RealmClaims, err := realm.DecodeClaims(rSign1.Payload) + RealmClaims, err := realm.DecodeClaimsFromCBOR(rSign1.Payload) if err != nil { return fmt.Errorf("failed CBOR decoding of CCA realm claims: %w", err) } @@ -384,52 +405,36 @@ func (e *Evidence) decodeClaims() error { return nil } -// Verify verifies the CCA evidence using the supplied platform public key. -// The integrity of the realm token is checked by extracting the inlined realm -// public key. This also checks the correctness of the chaining between -// platform and realm tokens. -func (e *Evidence) Verify(iak crypto.PublicKey) error { - if e.collection == nil { - return fmt.Errorf("no message found") +func signClaims(claims any, signer cose.Signer) ([]byte, error) { + claimSet, err := em.Marshal(claims) + if err != nil { + return nil, fmt.Errorf("CBOR encoding the payload: %w", err) } - // Check CCA Platform Token - if e.collection.PlatformToken == nil { - return fmt.Errorf("missing CCA platform Token") - } + return signPayload(claimSet, signer) +} - // First verify the platform token - if err := e.verifyCOSEToken(*e.collection.PlatformToken, iak); err != nil { - return fmt.Errorf("unable to verify platform token: %w", err) +func signPayload(payload []byte, signer cose.Signer) ([]byte, error) { + alg := signer.Algorithm() + if strings.Contains(alg.String(), "unknown algorithm value") { + return nil, errors.New("signer has no algorithm") } - // Check CCA Realm Token - if e.collection.RealmToken == nil { - return fmt.Errorf("missing CCA realm Token") - } + message := cose.NewSign1Message() + message.Payload = payload + message.Headers.Protected.SetAlgorithm(alg) - // extract RAK from the realm token - rawRAK, err := e.RealmClaims.GetPubKey() + err := message.Sign(rand.Reader, []byte(""), signer) if err != nil { - return fmt.Errorf("extracting RAK from the realm token: %w", err) + return nil, fmt.Errorf("COSE Sign1 failed: %w", err) } - rak, err := ecdsaPublicKeyFromRaw(rawRAK) + sign1, err := message.MarshalCBOR() if err != nil { - return fmt.Errorf("decoding RAK: %w", err) - } - - // Next verify the realm token - if err := e.verifyCOSEToken(*e.collection.RealmToken, rak); err != nil { - return fmt.Errorf("unable to verify realm token: %w", err) - } - - // check the collection binding - if err := e.checkBinding(); err != nil { - return fmt.Errorf("binding verification failed: %w", err) + return nil, fmt.Errorf("CBOR encoding the COSE Sign1: %w", err) } - return nil + return sign1, nil } func (e *Evidence) bind() error { @@ -528,33 +533,3 @@ func (e *Evidence) verifyCOSEToken(token []byte, pk crypto.PublicKey) error { return nil } - -// GetInstanceID returns the InstanceID from CCA platform token -// or a nil pointer if no suitable InstanceID could be located. -func (e *Evidence) GetInstanceID() *[]byte { - instID, err := e.PlatformClaims.GetInstID() - if err != nil { - return nil - } - return &instID -} - -// GetImplementationID returns the ImplementationID from CCA platform token -// or a nil pointer if no suitable ImplementationID could be located. -func (e *Evidence) GetImplementationID() *[]byte { - implID, err := e.PlatformClaims.GetImplID() - if err != nil { - return nil - } - return &implID -} - -// GetRealmPublicKey returns the RMM Public Key -// RMM Public Key is used to verify the signature on the Realm Token -func (e *Evidence) GetRealmPublicKey() *[]byte { - pubKey, err := e.RealmClaims.GetPubKey() - if err != nil { - return nil - } - return &pubKey -} diff --git a/evidence_test.go b/evidence_test.go index 6b27391..80decad 100644 --- a/evidence_test.go +++ b/evidence_test.go @@ -80,14 +80,12 @@ func TestEvidence_sign_and_verify_ok(t *testing.T) { ) assert.NoError(t, err) - ccaToken, err := EvidenceIn.Sign(pSigner, rSigner) + ccaToken, err := EvidenceIn.ValidateAndSign(pSigner, rSigner) assert.NoError(t, err, "signing failed") fmt.Printf("CCA evidence : %x\n", ccaToken) - var EvidenceOut Evidence - - err = EvidenceOut.FromCBOR(ccaToken) + EvidenceOut, err := DecodeAndValidateEvidenceFromCBOR(ccaToken) assert.NoError(t, err, "CCA token decoding failed") verifier := pubKeyFromJWK(t, testIAK) @@ -112,14 +110,12 @@ func TestEvidence_sign_and_verify_bad_binder(t *testing.T) { err = EvidenceIn.PlatformClaims.SetNonce([]byte("tampered binder!tampered binder!")) require.NoError(t, err, "overriding binder") - ccaToken, err := EvidenceIn.Sign(pSigner, rSigner) + ccaToken, err := EvidenceIn.ValidateAndSign(pSigner, rSigner) assert.NoError(t, err, "signing failed") fmt.Printf("CCA evidence : %x\n", ccaToken) - var EvidenceOut Evidence - - err = EvidenceOut.FromCBOR(ccaToken) + EvidenceOut, err := DecodeAndValidateEvidenceFromCBOR(ccaToken) assert.NoError(t, err, "CCA token decoding failed") verifier := pubKeyFromJWK(t, testIAK) @@ -140,14 +136,12 @@ func TestEvidence_sign_and_verify_platform_key_mismatch(t *testing.T) { ) assert.NoError(t, err) - ccaToken, err := EvidenceIn.Sign(pSigner, rSigner) + ccaToken, err := EvidenceIn.ValidateAndSign(pSigner, rSigner) assert.NoError(t, err, "signing failed") fmt.Printf("CCA evidence : %x\n", ccaToken) - var EvidenceOut Evidence - - err = EvidenceOut.FromCBOR(ccaToken) + EvidenceOut, err := DecodeAndValidateEvidenceFromCBOR(ccaToken) assert.NoError(t, err, "CCA token decoding failed") mismatchedVerifier := pubKeyFromJWK(t, testAltIAK) @@ -173,14 +167,12 @@ func TestEvidence_sign_and_verify_realm_key_mismatch(t *testing.T) { err = EvidenceIn.RealmClaims.SetPubKey(testAltRAKPubRaw) assert.NoError(t, err) - ccaToken, err := EvidenceIn.Sign(pSigner, rSigner) + ccaToken, err := EvidenceIn.ValidateAndSign(pSigner, rSigner) assert.NoError(t, err, "signing failed") fmt.Printf("CCA evidence : %x\n", ccaToken) - var EvidenceOut Evidence - - err = EvidenceOut.FromCBOR(ccaToken) + EvidenceOut, err := DecodeAndValidateEvidenceFromCBOR(ccaToken) assert.NoError(t, err, "CCA token decoding failed") mismatchedVerifier := pubKeyFromJWK(t, testIAK) @@ -218,10 +210,9 @@ func TestEvidence_sign_unvalidated(t *testing.T) { for _, tv := range testVectors { var EvidenceIn Evidence - err := EvidenceIn.SetUnvalidatedClaims(tv.Platform, tv.Realm) - assert.NoError(t, err) + EvidenceIn.SetUnvalidatedClaims(tv.Platform, tv.Realm) - _, err = EvidenceIn.SignUnvalidated(pSigner, rSigner) + _, err := EvidenceIn.Sign(pSigner, rSigner) if tv.Error == "" { assert.NoError(t, err, "signing failed") } else { @@ -277,8 +268,8 @@ func TestEvidence_GetRealmPubKey_ok(t *testing.T) { func TestEvidence_MarshalJSON_fail(t *testing.T) { var e Evidence - _, err := e.MarshalJSON() - assert.EqualError(t, err, "invalid evidence") + _, err := ValidateAndEncodeEvidenceToJSON(&e) + assert.EqualError(t, err, "claims not set in evidence") } func TestEvidence_MarshalJSON_ok(t *testing.T) { @@ -308,47 +299,43 @@ func TestEvidence_MarshalUnvalidatedJSON(t *testing.T) { expected := testCombinedClaimsJSON - actual, err := e.MarshalUnvalidatedJSON() + actual, err := e.MarshalJSON() assert.NoError(t, err) assert.JSONEq(t, expected, string(actual)) var empty Evidence - actual, err = empty.MarshalUnvalidatedJSON() + actual, err = empty.MarshalJSON() assert.NoError(t, err) assert.JSONEq(t, "{}", string(actual)) } -func TestEvidence_UnmarshalJSON_ok(t *testing.T) { - var e Evidence +func TestEvidence_JSON_round_trip(t *testing.T) { + e, err := DecodeAndValidateEvidenceFromJSON([]byte(testCombinedClaimsJSON)) + assert.NoError(t, err) - err := e.UnmarshalJSON([]byte(testCombinedClaimsJSON)) + buf, err := ValidateAndEncodeEvidenceToJSON(e) assert.NoError(t, err) + assert.JSONEq(t, testCombinedClaimsJSON, string(buf)) } func TestEvidence_UnmarshalJSON_missing_platform(t *testing.T) { - var e Evidence - - expectedErr := "unmarshaling CCA claims: missing platform claims" + expectedErr := "missing platform claims" - err := e.UnmarshalJSON([]byte(testCombinedClaimsJSONMissingPlatform)) + _, err := DecodeAndValidateEvidenceFromJSON([]byte(testCombinedClaimsJSONMissingPlatform)) assert.EqualError(t, err, expectedErr) } func TestEvidence_UnmarshalJSON_missing_realm(t *testing.T) { - var e Evidence - - expectedErr := "unmarshaling CCA claims: missing realm claims" + expectedErr := "missing realm claims" - err := e.UnmarshalJSON([]byte(testCombinedClaimsJSONMissingRealm)) + _, err := DecodeAndValidateEvidenceFromJSON([]byte(testCombinedClaimsJSONMissingRealm)) assert.EqualError(t, err, expectedErr) } func TestEvidence_UnmarshalJSON_syntax_error(t *testing.T) { - var e Evidence - expectedErr := "unmarshaling CCA claims: unexpected end of JSON input" - err := e.UnmarshalJSON(testNotJSON) + _, err := DecodeAndValidateEvidenceFromJSON(testNotJSON) assert.EqualError(t, err, expectedErr) } @@ -363,8 +350,7 @@ func TestEvidence_UnmarshalUnvalidatedJSON(t *testing.T) { } for _, tv := range testVectors { - var e Evidence - err := e.UnmarshalUnvalidatedJSON(tv.Bytes) + _, err := DecodeEvidenceFromJSON(tv.Bytes) if tv.Error == "" { assert.NoError(t, err) @@ -467,7 +453,7 @@ func TestEvidence_Sign_no_platform_claims(t *testing.T) { expectedErr := "claims not set in evidence" - _, err := e.Sign(unused, unused) + _, err := e.ValidateAndSign(unused, unused) assert.EqualError(t, err, expectedErr) } @@ -485,7 +471,7 @@ func TestEvidence_Sign_invalid_signers(t *testing.T) { expectedErr := "nil signer(s) supplied" - _, err = e.Sign(invalid, invalid) + _, err = e.ValidateAndSign(invalid, invalid) assert.EqualError(t, err, expectedErr) } @@ -502,8 +488,7 @@ func TestEvidence_Verify_no_message(t *testing.T) { func TestEvidence_Verify_RMM(t *testing.T) { b := mustHexDecode(t, testRMMEvidence) - var e Evidence - err := e.FromCBOR(b) + e, err := DecodeAndValidateEvidenceFromCBOR(b) require.NoError(t, err) verifier := pubKeyFromJWK(t, testRMMCPAK) @@ -512,7 +497,7 @@ func TestEvidence_Verify_RMM(t *testing.T) { assert.NoError(t, err) } -func TestEvidence_FromCBOR_wrong_top_level_tag(t *testing.T) { +func TestEvidence_UnmarshalCBOR_wrong_top_level_tag(t *testing.T) { wrongCBORTag := []byte{ 0xd2, 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x58, 0x1e, 0xa1, 0x19, 0x01, 0x09, 0x78, 0x18, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x72, @@ -522,26 +507,68 @@ func TestEvidence_FromCBOR_wrong_top_level_tag(t *testing.T) { expectedErr := `CBOR decoding of CCA evidence failed: cbor: wrong tag number for ccatoken.CBORCollection, got [18], expected [399]` - var e Evidence - - err := e.FromCBOR(wrongCBORTag) + _, err := DecodeAndValidateEvidenceFromCBOR(wrongCBORTag) assert.EqualError(t, err, expectedErr) } -func TestEvidence_FromCBOR_wrong_unwrapped_tokens(t *testing.T) { +func TestEvidence_UnmarshalCBOR_wrong_unwrapped_tokens(t *testing.T) { b := mustHexDecode(t, testBadUnwrappedTokens) - expectedErr := `CBOR decoding of CCA evidence failed: cbor: cannot unmarshal byte string into Go struct field ccatoken.CBORCollection.44234 of type uint8` - var e Evidence - err := e.FromCBOR(b) + _, err := DecodeAndValidateEvidenceFromCBOR(b) assert.EqualError(t, err, expectedErr) } -func TestEvidence_FromCBOR_good_CCA_token(t *testing.T) { +func TestEvidence_UnmarshalCBOR_good_CCA_token(t *testing.T) { b := mustHexDecode(t, testGoodCCAToken) - var e Evidence - err := e.FromCBOR(b) + _, err := DecodeAndValidateEvidenceFromCBOR(b) assert.NoError(t, err) } + +func TestEvidence_UnmarshalCBOR_token_not_set(t *testing.T) { + buf := []byte{ + 0xd9, 0x01, 0x8f, // tag(399) + 0xa0, // empty map + } + + var ev Evidence + + err := ev.UnmarshalCBOR(buf) + assert.EqualError(t, err, "CCA platform token not set") + + buf = []byte{ + 0xd9, 0x01, 0x8f, // tag(399) + 0xa1, // map(1) + 0x19, 0xac, 0xca, // key: 44234 + 0x40, // value: empty bstr + } + + err = ev.UnmarshalCBOR(buf) + assert.EqualError(t, err, "CCA realm token not set") +} + +func TestEvidence_Validate_nagative(t *testing.T) { + ev := &Evidence{ + PlatformClaims: mustBuildValidPlatformClaims(t, false), + RealmClaims: mustBuildValidCcaRealmClaims(t), + } + + err := ev.Validate() + assert.Contains(t, err.Error(), "validating nonce: missing mandatory claim") + + err = ev.PlatformClaims.SetNonce([]byte{ + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }) + require.NoError(t, err) + + rc := (ev.RealmClaims).(*realm.Claims) + *rc.Challenge = nil + + err = ev.Validate() + assert.Contains(t, err.Error(), "realm challenge claim: wrong syntax") + +} diff --git a/go.mod b/go.mod index 6757335..a8a60ad 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/stretchr/testify v1.8.1 github.com/veraison/eat v0.0.0-20220117140849-ddaf59d69f53 github.com/veraison/go-cose v1.1.0 - github.com/veraison/psatoken v1.2.1-0.20240718103721-89fde617284b + github.com/veraison/psatoken v1.2.1-0.20240719122628-26fe500fd5d4 ) require ( diff --git a/go.sum b/go.sum index aceb1b1..b4fd58e 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/veraison/eat v0.0.0-20220117140849-ddaf59d69f53 h1:5gnX2TrGd/Xz8DOp2O github.com/veraison/eat v0.0.0-20220117140849-ddaf59d69f53/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v1.1.0 h1:AalPS4VGiKavpAzIlBjrn7bhqXiXi4jbMYY/2+UC+4o= github.com/veraison/go-cose v1.1.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= -github.com/veraison/psatoken v1.2.1-0.20240718103721-89fde617284b h1:dFgjvrZWCalGq/Di9d8sIGXQPJ+CbmDbrl2eK6HbXJI= -github.com/veraison/psatoken v1.2.1-0.20240718103721-89fde617284b/go.mod h1:6+WZzXr0ACXYiUAJJqTaCxW43gY2+gEaCoVNdDv3+Bw= +github.com/veraison/psatoken v1.2.1-0.20240719122628-26fe500fd5d4 h1:N7qg7vDF2mUg7I+8AoU+ieJ20cgcShwFHXHkV5b2YAA= +github.com/veraison/psatoken v1.2.1-0.20240719122628-26fe500fd5d4/go.mod h1:6+WZzXr0ACXYiUAJJqTaCxW43gY2+gEaCoVNdDv3+Bw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/platform/claims.go b/platform/claims.go index 4ac97bb..8faf743 100644 --- a/platform/claims.go +++ b/platform/claims.go @@ -65,108 +65,35 @@ func (c *Claims) Validate() error { // Codecs -func (c *Claims) FromCBOR(buf []byte) error { - err := c.FromUnvalidatedCBOR(buf) - if err != nil { - return err - } - - err = c.Validate() - if err != nil { - return fmt.Errorf("validation of CCA platform claims failed: %w", err) - } +// this type alias is used to prevent infinite recursion during marshaling. +type claims Claims - return nil -} - -func (c *Claims) FromUnvalidatedCBOR(buf []byte) error { +func (c *Claims) UnmarshalCBOR(buf []byte) error { c.Profile = nil // clear profile to make sure we taked it from buf - err := dm.Unmarshal(buf, c) - if err != nil { - return fmt.Errorf("CBOR decoding of CCA platform claims failed: %w", err) - } - - return nil -} - -func (c *Claims) ToCBOR() ([]byte, error) { - err := c.Validate() - if err != nil { - return nil, fmt.Errorf("validation of CCA platform claims failed: %w", err) - } - - return c.ToUnvalidatedCBOR() + return dm.Unmarshal(buf, (*claims)(c)) } -func (c *Claims) ToUnvalidatedCBOR() ([]byte, error) { - var scs psatoken.ISwComponents +func (c Claims) MarshalCBOR() ([]byte, error) { if c.SwComponents != nil && c.SwComponents.IsEmpty() { - scs = c.SwComponents c.SwComponents = nil } - buf, err := em.Marshal(&c) - if scs != nil { - c.SwComponents = scs - } - if err != nil { - return nil, fmt.Errorf("CBOR encoding of CCA platform claims failed: %w", err) - } - - return buf, nil + return em.Marshal((*claims)(&c)) } -func (c *Claims) FromJSON(buf []byte) error { - err := c.FromUnvalidatedJSON(buf) - if err != nil { - return err - } - - err = c.Validate() - if err != nil { - return fmt.Errorf("validation of CCA platform claims failed: %w", err) - } - - return nil -} - -func (c *Claims) FromUnvalidatedJSON(buf []byte) error { +func (c *Claims) UnmarshalJSON(buf []byte) error { c.Profile = nil // clear profile to make sure we taked it from buf - err := json.Unmarshal(buf, c) - if err != nil { - return fmt.Errorf("JSON decoding of CCA platform claims failed: %w", err) - } - - return nil + return json.Unmarshal(buf, (*claims)(c)) } -func (c *Claims) ToJSON() ([]byte, error) { - err := c.Validate() - if err != nil { - return nil, fmt.Errorf("validation of CCA platform claims failed: %w", err) - } - - return c.ToUnvalidatedJSON() -} - -func (c *Claims) ToUnvalidatedJSON() ([]byte, error) { - var scs psatoken.ISwComponents +func (c Claims) MarsahlJSON() ([]byte, error) { if c.SwComponents != nil && c.SwComponents.IsEmpty() { - scs = c.SwComponents c.SwComponents = nil } - buf, err := json.Marshal(&c) - if scs != nil { - c.SwComponents = scs - } - if err != nil { - return nil, fmt.Errorf("JSON encoding of CCA platform claims failed: %w", err) - } - - return buf, nil + return json.Marshal((*claims)(&c)) } func (c *Claims) SetImplID(v []byte) error { diff --git a/platform/claims_test.go b/platform/claims_test.go index 2223b0a..44b32e2 100644 --- a/platform/claims_test.go +++ b/platform/claims_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/psatoken" ) var ( @@ -117,44 +116,39 @@ func Test_Claims_Get_NonValid_Claims(t *testing.T) { } -func Test_CCAPlatform_Claims_ToCBOR_invalid(t *testing.T) { +func Test_CCAPlatform_Claims_MarshalCBOR_invalid(t *testing.T) { c := NewClaims() + expectedErr := `validating security lifecycle: missing mandatory claim` - expectedErr := `validation of CCA platform claims failed: validating security lifecycle: missing mandatory claim` - - _, err := c.ToCBOR() + _, err := ValidateAndEncodeClaimsToCBOR(c) assert.EqualError(t, err, expectedErr) } -func Test_CCAPlatform_Claims_ToCBOR_all_claims(t *testing.T) { +func Test_CCAPlatform_Claims_MarshalCBOR_all_claims(t *testing.T) { c := mustBuildValidClaims(t, true) - expected := mustHexDecode(t, testEncodedCcaPlatformClaimsAll) - actual, err := c.ToCBOR() + actual, err := ValidateAndEncodeClaimsToCBOR(c) assert.NoError(t, err) assert.Equal(t, expected, actual) } -func Test_CCAPlatform_Claims_ToCBOR_mandatory_only_claims(t *testing.T) { +func Test_CCAPlatform_Claims_MarshalCBOR_mandatory_only_claims(t *testing.T) { c := mustBuildValidClaims(t, false) - expected := mustHexDecode(t, testEncodedCcaPlatformClaimsMandatoryOnly) - actual, err := c.ToCBOR() + actual, err := ValidateAndEncodeClaimsToCBOR(c) assert.NoError(t, err) assert.Equal(t, expected, actual) } -func Test_CCAPlatform_FromCBOR_ok_mandatory_only(t *testing.T) { +func Test_CCAPlatform_UnmarshalCBOR_ok_mandatory_only(t *testing.T) { buf := mustHexDecode(t, testEncodedCcaPlatformClaimsMandatoryOnly) - c := NewClaims() - - err := c.FromCBOR(buf) + c, err := DecodeAndValidateClaimsFromCBOR(buf) assert.NoError(t, err) // mandatory @@ -191,43 +185,34 @@ func Test_CCAPlatform_FromCBOR_ok_mandatory_only(t *testing.T) { } -func Test_CCAPlatform_Claims_FromCBOR_bad_input(t *testing.T) { +func Test_CCAPlatform_Claims_UnmarshalCBOR_bad_input(t *testing.T) { buf := mustHexDecode(t, testNotCBOR) + expectedErr := "unexpected EOF" - expectedErr := "CBOR decoding of CCA platform claims failed: unexpected EOF" - - c := NewClaims() - - err := c.FromCBOR(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) } -func Test_CCAPlatform_Claims_FromCBOR_missing_mandatory_claim(t *testing.T) { +func Test_CCAPlatform_Claims_UnmarshalCBOR_missing_mandatory_claim(t *testing.T) { buf := mustHexDecode(t, testEncodedCcaPlatformClaimsMissingMandatoryNonce) + expectedErr := "validating nonce: missing mandatory claim" - expectedErr := "validation of CCA platform claims failed: validating nonce: missing mandatory claim" - - c := NewClaims() - - err := c.FromCBOR(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) } -func Test_CCAPlatform_Claims_FromCBOR_invalid_multi_nonce(t *testing.T) { +func Test_CCAPlatform_Claims_UnmarshalCBOR_invalid_multi_nonce(t *testing.T) { buf := mustHexDecode(t, testEncodedCcaPlatformClaimsInvalidMultiNonce) + expectedErr := "validating nonce: wrong syntax: got 2 nonces, want 1" - expectedErr := "validation of CCA platform claims failed: validating nonce: wrong syntax: got 2 nonces, want 1" - - c := NewClaims() - - err := c.FromCBOR(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) } -func Test_CCAPlatform_ToJSON_ok(t *testing.T) { +func Test_CCAPlatform_MarshalJSON_ok(t *testing.T) { c := mustBuildValidClaims(t, true) expected := `{ @@ -246,19 +231,21 @@ func Test_CCAPlatform_ToJSON_ok(t *testing.T) { "cca-platform-service-indicator" : "https://veraison.example/v1/challenge-response", "cca-platform-hash-algo-id": "sha-256" }` - actual, err := c.ToJSON() + actual, err := ValidateAndEncodeClaimsToJSON(c) assert.NoError(t, err) assert.JSONEq(t, expected, string(actual)) } -func Test_CCAPlatform_ToJSON_not_ok(t *testing.T) { - c := Claims{} - expectedErr := `validation of CCA platform claims failed: validating profile: missing mandatory claim` - _, err := c.ToJSON() +func Test_CCAPlatform_MarshalJSON_not_ok(t *testing.T) { + c := &Claims{} + expectedErr := `validating profile: missing mandatory claim` + + _, err := ValidateAndEncodeClaimsToCBOR(c) + assert.EqualError(t, err, expectedErr) } -func Test_CCAPlatform_FromJSON_ok(t *testing.T) { +func Test_CCAPlatform_UnmarshalJSON_ok(t *testing.T) { tv := `{ "cca-platform-profile": "http://arm.com/CCA-SSD/1.0.0", "cca-platform-challenge": "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=", @@ -276,24 +263,21 @@ func Test_CCAPlatform_FromJSON_ok(t *testing.T) { "cca-platform-hash-algo-id": "sha-256" }` - c := NewClaims() + _, err := DecodeAndValidateClaimsFromJSON([]byte(tv)) - err := c.FromJSON([]byte(tv)) assert.NoError(t, err) } -func Test_CCAPlatform_FromJSON_invalid_json(t *testing.T) { +func Test_CCAPlatform_UnmarshalJSON_invalid_json(t *testing.T) { tv := testNotJSON + expectedErr := `unexpected end of JSON input` - expectedErr := `JSON decoding of CCA platform claims failed: unexpected end of JSON input` - - c := NewClaims() + _, err := DecodeAndValidateClaimsFromJSON(tv) - err := c.FromJSON(tv) assert.EqualError(t, err, expectedErr) } -func Test_CCAPlatform_FromJSON_negatives(t *testing.T) { +func Test_CCAPlatform_UnmarshalJSON_negatives(t *testing.T) { tvs := []string{ /* 0 */ "testvectors/json/test-invalid-profile.json", /* 1 */ "testvectors/json/test-vsi-invalid-empty.json", @@ -316,9 +300,8 @@ func Test_CCAPlatform_FromJSON_negatives(t *testing.T) { buf, err := os.ReadFile(fn) require.NoError(t, err) - var claimsSet Claims + _, err = DecodeAndValidateClaimsFromJSON(buf) - err = claimsSet.FromJSON(buf) assert.Error(t, err, "test vector %d failed", i) } } @@ -331,7 +314,7 @@ func Test_DecodeClaims_CCAPlatform_ok(t *testing.T) { for _, tv := range tvs { buf := mustHexDecode(t, tv) - c, err := psatoken.DecodeClaimsFromCBOR(buf) + c, err := DecodeAndValidateClaimsFromCBOR(buf) assert.NoError(t, err) @@ -348,7 +331,7 @@ func Test_DecodeClaims_CCAPlatform_failure(t *testing.T) { for _, tv := range tvs { buf := mustHexDecode(t, tv) - _, err := psatoken.DecodeClaimsFromCBOR(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) expectedError := `validating nonce: missing mandatory claim` @@ -368,7 +351,7 @@ func Test_DecodeUnvalidatedCCAClaims(t *testing.T) { for _, tv := range tvs { buf := mustHexDecode(t, tv.Input) - v, err := psatoken.DecodeUnvalidatedClaimsFromCBOR(buf) + v, err := DecodeClaimsFromCBOR(buf) assert.NoError(t, err) assert.IsType(t, tv.Expected, v) @@ -387,7 +370,7 @@ func Test_DecodeJSONClaims_CcaPlatform(t *testing.T) { buf, err := os.ReadFile("testvectors/json/test-token-valid-full.json") require.NoError(t, err) - c, err := psatoken.DecodeClaimsFromJSON(buf) + c, err := DecodeAndValidateClaimsFromJSON(buf) assert.NoError(t, err) actualProfile, err := c.GetProfile() assert.NoError(t, err) @@ -413,8 +396,7 @@ func Test_DecodeUnvalidatedJSONCCAClaims(t *testing.T) { buf, err := os.ReadFile(tv.Path) require.NoError(t, err) - v := NewClaims() - err = v.FromUnvalidatedJSON(buf) + v, err := DecodeClaimsFromJSON(buf) assert.NoError(t, err) assert.IsType(t, tv.Expected, v) @@ -469,9 +451,9 @@ func Test_CcaLifeCycleState(t *testing.T) { func Test_ToUnvalidated(t *testing.T) { c := NewClaims() - _, err := c.ToUnvalidatedCBOR() + _, err := EncodeClaimsToCBOR(c) assert.NoError(t, err) - _, err = c.ToUnvalidatedJSON() + _, err = EncodeClaimsToJSON(c) assert.NoError(t, err) } diff --git a/platform/iclaims.go b/platform/iclaims.go index 1dd0a55..46fe19c 100644 --- a/platform/iclaims.go +++ b/platform/iclaims.go @@ -4,6 +4,7 @@ package platform import ( + "encoding/json" "fmt" "github.com/veraison/psatoken" @@ -20,17 +21,6 @@ type IClaims interface { SetHashAlgID(string) error } -// DecodeClaims unmarshals CCA platform claims from provided CBOR data. -func DecodeClaims(buf []byte) (IClaims, error) { - cl := NewClaims() - - if err := cl.FromCBOR(buf); err != nil { - return nil, err - } - - return cl, nil -} - // ValidateClaims returns an error if the provided IClaims instance does not // contain a valid set of CCA platform claims. func ValidateClaims(c IClaims) error { @@ -48,3 +38,93 @@ func ValidateClaims(c IClaims) error { return nil } + +// DecodeClaims unmarshals and validates CCA platform claims from provided CBOR +// data. +func DecodeAndValidateClaimsFromCBOR(buf []byte) (IClaims, error) { + cl, err := DecodeClaimsFromCBOR(buf) + if err != nil { + return nil, err + } + + if err = cl.Validate(); err != nil { + return nil, err + } + + return cl, nil +} + +// DecodeClaims unmarshals CCA platform claims from provided CBOR data. +func DecodeClaimsFromCBOR(buf []byte) (IClaims, error) { + cl := NewClaims() + + if err := dm.Unmarshal(buf, cl); err != nil { + return nil, err + } + + return cl, nil +} + +// DecodeClaims unmarshals and validates CCA platform claims from provided JSON +// data. +func DecodeAndValidateClaimsFromJSON(buf []byte) (IClaims, error) { + cl, err := DecodeClaimsFromJSON(buf) + if err != nil { + return nil, err + } + + if err = cl.Validate(); err != nil { + return nil, err + } + + return cl, nil +} + +// DecodeClaims unmarshals CCA platform claims from provided JSON data. +func DecodeClaimsFromJSON(buf []byte) (IClaims, error) { + cl := NewClaims() + + if err := json.Unmarshal(buf, cl); err != nil { + return nil, err + } + + return cl, nil +} + +// ValidateAndEncodeClaimsToCBOR validates and then marshals CCA platform claims +// to CBOR. +func ValidateAndEncodeClaimsToCBOR(c IClaims) ([]byte, error) { + if err := c.Validate(); err != nil { + return nil, err + } + + return EncodeClaimsToCBOR(c) +} + +// EncodeClaimsToCBOR marshals CCA platform claims to CBOR. +func EncodeClaimsToCBOR(c IClaims) ([]byte, error) { + if c == nil { + return nil, nil + } + + return em.Marshal(c) +} + +// ValidateAndEncodeClaimsToJSON validates and then marshals CCA platform claims +// to JSON. +func ValidateAndEncodeClaimsToJSON(c IClaims) ([]byte, error) { + if err := c.Validate(); err != nil { + return nil, err + } + + return EncodeClaimsToJSON(c) +} + +// EncodeClaimsToJSON marshals CCA platform claims to JSON. +func EncodeClaimsToJSON(c IClaims) ([]byte, error) { + if c == nil { + return nil, nil + } + + return json.Marshal(c) +} diff --git a/platform/iclaims_test.go b/platform/iclaims_test.go index 47287a3..eceeada 100644 --- a/platform/iclaims_test.go +++ b/platform/iclaims_test.go @@ -11,10 +11,10 @@ import ( func Test_DecodeClaims(t *testing.T) { buf := mustHexDecode(t, testEncodedCcaPlatformClaimsAll) - _, err := DecodeClaims(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) assert.NoError(t, err) buf = mustHexDecode(t, testEncodedCcaPlatformClaimsInvalidMultiNonce) - _, err = DecodeClaims(buf) - assert.EqualError(t, err, "validation of CCA platform claims failed: validating nonce: wrong syntax: got 2 nonces, want 1") + _, err = DecodeAndValidateClaimsFromCBOR(buf) + assert.EqualError(t, err, "validating nonce: wrong syntax: got 2 nonces, want 1") } diff --git a/realm/claims.go b/realm/claims.go index 4fd1a7f..d001701 100644 --- a/realm/claims.go +++ b/realm/claims.go @@ -2,7 +2,6 @@ package realm import ( - "encoding/json" "fmt" "github.com/veraison/eat" @@ -190,83 +189,3 @@ func (c Claims) GetPubKeyHashAlgID() (string, error) { func (c Claims) Validate() error { return ValidateClaims(&c) } - -// Codecs - -func (c *Claims) FromCBOR(buf []byte) error { - err := c.FromUnvalidatedCBOR(buf) - if err != nil { - return err - } - - err = c.Validate() - if err != nil { - return fmt.Errorf("validation of CCA realm claims failed: %w", err) - } - - return nil -} - -func (c *Claims) FromUnvalidatedCBOR(buf []byte) error { - err := dm.Unmarshal(buf, c) - if err != nil { - return fmt.Errorf("CBOR decoding of CCA realm claims failed: %w", err) - } - - return nil -} - -func (c Claims) ToCBOR() ([]byte, error) { - err := c.Validate() - if err != nil { - return nil, fmt.Errorf("validation of CCA realm claims failed: %w", err) - } - - return c.ToUnvalidatedCBOR() -} - -func (c Claims) ToUnvalidatedCBOR() ([]byte, error) { - buf, err := em.Marshal(&c) - if err != nil { - return nil, fmt.Errorf("CBOR encoding of CCA realm claims failed: %w", err) - } - - return buf, nil -} - -func (c *Claims) FromJSON(buf []byte) error { - if err := c.FromUnvalidatedJSON(buf); err != nil { - return err - } - - if err := c.Validate(); err != nil { - return fmt.Errorf("validation of CCA realm claims failed: %w", err) - } - - return nil -} - -func (c *Claims) FromUnvalidatedJSON(buf []byte) error { - if err := json.Unmarshal(buf, c); err != nil { - return fmt.Errorf("JSON decoding of CCA realm claims failed: %w", err) - } - - return nil -} - -func (c Claims) ToJSON() ([]byte, error) { - if err := c.Validate(); err != nil { - return nil, fmt.Errorf("validation of CCA realm claims failed: %w", err) - } - - return c.ToUnvalidatedJSON() -} - -func (c Claims) ToUnvalidatedJSON() ([]byte, error) { - buf, err := json.Marshal(&c) - if err != nil { - return nil, fmt.Errorf("JSON encoding of CCA realm claims failed: %w", err) - } - - return buf, nil -} diff --git a/realm/claims_test.go b/realm/claims_test.go index 7f0e87e..7830a33 100644 --- a/realm/claims_test.go +++ b/realm/claims_test.go @@ -88,30 +88,30 @@ func Test_CcaRealmClaims_Set_nok(t *testing.T) { assert.EqualError(t, err, expectedErr) } -func Test_CcaRealmClaims_ToCBOR_invalid(t *testing.T) { +func Test_CcaRealmClaims_MarshalCBOR_invalid(t *testing.T) { c := NewClaims() + expectedErr := `validating realm challenge claim: missing mandatory claim` + + _, err := ValidateAndEncodeClaimsToCBOR(c) - _, err := c.ToCBOR() - expectedErr := `validation of CCA realm claims failed: validating realm challenge claim: missing mandatory claim` assert.EqualError(t, err, expectedErr) } -func Test_CcaRealmClaims_ToCBOR_all_claims(t *testing.T) { +func Test_CcaRealmClaims_MarshalCBOR_all_claims(t *testing.T) { c := mustBuildValidCcaRealmClaims(t) - expected := mustHexDecode(t, testEncodedCcaRealmClaimsAll) - actual, err := c.ToCBOR() + actual, err := ValidateAndEncodeClaimsToCBOR(c) assert.NoError(t, err) assert.Equal(t, expected, actual) } -func Test_CcaRealmClaims_FromCBOR_ok(t *testing.T) { +func Test_CcaRealmClaims_UnmarshalCBOR_ok(t *testing.T) { buf := mustHexDecode(t, testEncodedCcaRealmClaimsAll) - var c Claims - err := c.FromCBOR(buf) + c, err := DecodeAndValidateClaimsFromCBOR(buf) + assert.NoError(t, err) // mandatory @@ -146,57 +146,48 @@ func Test_CcaRealmClaims_FromCBOR_ok(t *testing.T) { assert.Equal(t, expectedPubKey, actualPubKey) } -func Test_CcaRealmClaims_FromCBOR_bad_input(t *testing.T) { +func Test_CcaRealmClaims_UnmarshalCBOR_bad_input(t *testing.T) { buf := mustHexDecode(t, testNotCBOR) + expectedErr := "unexpected EOF" - expectedErr := "CBOR decoding of CCA realm claims failed: unexpected EOF" - - var c Claims - err := c.FromCBOR(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) } -func Test_CcaRealmClaims_FromCBOR_missing_mandatory_claims(t *testing.T) { +func Test_CcaRealmClaims_UnmarshalCBOR_missing_mandatory_claims(t *testing.T) { buf := mustHexDecode(t, testEncodedCcaRealmClaimsMissingMandNonce) + expectedErr := "validating realm challenge claim: missing mandatory claim" - expectedErr := "validation of CCA realm claims failed: validating realm challenge claim: missing mandatory claim" - - var c Claims - err := c.FromCBOR(buf) + _, err := DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) buf = mustHexDecode(t, testEncodedCcaClaimsMissingMandInitialMeas) + expectedErr = "validating realm initial measurements claim: missing mandatory claim" - expectedErr = "validation of CCA realm claims failed: validating realm initial measurements claim: missing mandatory claim" - c = Claims{} - err = c.FromCBOR(buf) + _, err = DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) buf = mustHexDecode(t, testEncodedCcaClaimsMissingMandHashAlgID) + expectedErr = "validating realm hash alg ID claim: missing mandatory claim" - expectedErr = "validation of CCA realm claims failed: validating realm hash alg ID claim: missing mandatory claim" - c = Claims{} - err = c.FromCBOR(buf) + _, err = DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) buf = mustHexDecode(t, testEncodedCcaClaimsMissingMandPubKey) + expectedErr = "validating realm public key claim: missing mandatory claim" - expectedErr = "validation of CCA realm claims failed: validating realm public key claim: missing mandatory claim" - c = Claims{} - err = c.FromCBOR(buf) + _, err = DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) buf = mustHexDecode(t, testEncodedCcaClaimsMissingMandExtendedMeas) + expectedErr = "validating realm extended measurements claim: missing mandatory claim" - expectedErr = "validation of CCA realm claims failed: validating realm extended measurements claim: missing mandatory claim" - c = Claims{} - err = c.FromCBOR(buf) + _, err = DecodeAndValidateClaimsFromCBOR(buf) assert.EqualError(t, err, expectedErr) - } -func Test_CcaRealm_Claims_ToJSON_ok(t *testing.T) { +func Test_CcaRealm_Claims_MarshalJSON_ok(t *testing.T) { c := mustBuildValidCcaRealmClaims(t) expected := `{ @@ -214,12 +205,12 @@ func Test_CcaRealm_Claims_ToJSON_ok(t *testing.T) { "cca-realm-public-key": "BIEZWICiIH+5VgMqPLl/XaWvcm/8txXuFkeEp/sWwGCWvdlGKjJlCykSqFUVcNbqHzstH32oonX6ADMPAHhhi8PhSVScgXDTLsVYkKf57HifHxiukusV0iKvlx2XHJZa8Q==", "cca-realm-public-key-hash-algo-id": "sha-512" }` - actual, err := c.ToJSON() + actual, err := ValidateAndEncodeClaimsToJSON(c) assert.NoError(t, err) assert.JSONEq(t, expected, string(actual)) } -func Test_CcaRealmClaims_FromJSON_ok(t *testing.T) { +func Test_CcaRealmClaims_UnmarshalJSON_ok(t *testing.T) { tv := `{ "cca-realm-challenge": "QUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQkFCQUJBQg==", "cca-realm-personalization-value": "QURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBRA==", @@ -235,24 +226,21 @@ func Test_CcaRealmClaims_FromJSON_ok(t *testing.T) { "cca-realm-public-key": "BIEZWICiIH+5VgMqPLl/XaWvcm/8txXuFkeEp/sWwGCWvdlGKjJlCykSqFUVcNbqHzstH32oonX6ADMPAHhhi8PhSVScgXDTLsVYkKf57HifHxiukusV0iKvlx2XHJZa8Q==", "cca-realm-public-key-hash-algo-id": "sha-512" }` - var c Claims - err := c.FromJSON([]byte(tv)) + _, err := DecodeAndValidateClaimsFromJSON([]byte(tv)) assert.NoError(t, err) } -func Test_CcaRealmClaims_FromJSON_invalid_json(t *testing.T) { +func Test_CcaRealmClaims_UnmarshalJSON_invalid_json(t *testing.T) { tv := testNotJSON + expectedErr := `unexpected end of JSON input` - expectedErr := `JSON decoding of CCA realm claims failed: unexpected end of JSON input` - - var c Claims - err := c.FromJSON(tv) + _, err := DecodeAndValidateClaimsFromJSON(tv) assert.EqualError(t, err, expectedErr) } -func Test_CcaRealmClaims_FromJSON_negatives(t *testing.T) { +func Test_CcaRealmClaims_UnmarshalJSON_negatives(t *testing.T) { tvs := []string{ /* 0 */ "testvectors/json/test-invalid-nonce.json", /* 1 */ "testvectors/json/test-invalid-extended-meas.json", @@ -271,9 +259,8 @@ func Test_CcaRealmClaims_FromJSON_negatives(t *testing.T) { buf, err := os.ReadFile(fn) require.NoError(t, err) - var claimsSet Claims + _, err = DecodeAndValidateClaimsFromJSON(buf) - err = claimsSet.FromJSON(buf) assert.Error(t, err, "test vector %d failed", i) } } diff --git a/realm/iclaims.go b/realm/iclaims.go index 2a4faaf..475f85a 100644 --- a/realm/iclaims.go +++ b/realm/iclaims.go @@ -4,6 +4,7 @@ package realm import ( + "encoding/json" "fmt" "github.com/veraison/psatoken" @@ -37,17 +38,96 @@ func NewClaims() IClaims { return &Claims{} } +// DecodeClaims unmarshals and validates CCA realm claims from provided CBOR +// data. +func DecodeAndValidateClaimsFromCBOR(buf []byte) (IClaims, error) { + cl, err := DecodeClaimsFromCBOR(buf) + if err != nil { + return nil, err + } + + if err = cl.Validate(); err != nil { + return nil, err + } + + return cl, nil +} + // DecodeClaims unmarshals CCA realm claims from provided CBOR data. -func DecodeClaims(buf []byte) (IClaims, error) { - cl := &Claims{} +func DecodeClaimsFromCBOR(buf []byte) (IClaims, error) { + cl := NewClaims() + + if err := dm.Unmarshal(buf, cl); err != nil { + return nil, err + } + + return cl, nil +} - if err := cl.FromCBOR(buf); err != nil { +// DecodeClaims unmarshals and validates CCA realm claims from provided JSON +// data. +func DecodeAndValidateClaimsFromJSON(buf []byte) (IClaims, error) { + cl, err := DecodeClaimsFromJSON(buf) + if err != nil { + return nil, err + } + + if err = cl.Validate(); err != nil { return nil, err } return cl, nil } +// DecodeClaims unmarshals CCA realm claims from provided JSON data. +func DecodeClaimsFromJSON(buf []byte) (IClaims, error) { + cl := NewClaims() + + if err := json.Unmarshal(buf, cl); err != nil { + return nil, err + } + + return cl, nil +} + +// ValidateAndEncodeClaimsToCBOR validates and then marshals CCA platform claims +// to CBOR. +func ValidateAndEncodeClaimsToCBOR(c IClaims) ([]byte, error) { + if err := c.Validate(); err != nil { + return nil, err + } + + return EncodeClaimsToCBOR(c) +} + +// EncodeClaimsToCBOR marshals CCA platform claims to CBOR. +func EncodeClaimsToCBOR(c IClaims) ([]byte, error) { + if c == nil { + return nil, nil + } + + return em.Marshal(c) +} + +// ValidateAndEncodeClaimsToJSON validates and then marshals CCA platform claims +// to JSON. +func ValidateAndEncodeClaimsToJSON(c IClaims) ([]byte, error) { + if err := c.Validate(); err != nil { + return nil, err + } + + return EncodeClaimsToJSON(c) +} + +// EncodeClaimsToJSON marshals CCA platform claims to JSON. +func EncodeClaimsToJSON(c IClaims) ([]byte, error) { + if c == nil { + return nil, nil + } + + return json.Marshal(c) +} + // ValidateClaims returns an error if the provided IClaims instance does not // contain a valid set of CCA realm claims. func ValidateClaims(c IClaims) error {