Skip to content

feature: support the latest profile tag:psacertified.org,2023:psa#tfm #53

@kentakayama

Description

@kentakayama

Is your feature request related to a problem? Please describe

RFC 9783 defines a profile tag:psacertified.org,2023:psa#tfm, which is not supported by this package.
Thus, the following PSA Attestation Token is rejected by Veraison.

NOTE: the psa-boot-seed label was also updated from 2397 to 268 in the v21 draft.

{
    / eat_profile / 265 : "tag:psacertified.org,2023:psa#tfm",
    / psa-client-id / 2394 : 1,
    / psa-lifecycle / 2395 : 12288,
    / psa-implementation-id / 2396 : 'acme-implementation-id-000000001',
    / psa-boot-seed / 268 : h'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
    / psa-software-components / 2399 : [ {
        / measurement-type / 1 : "BL",
        / measurement-value / 2 : h'87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7',
        / version / 4 : "2.1.0",
        / signer-id / 5 : h'acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b'
      }, {
        / measurement-type / 1 : "PRoT",
        / measurement-value / 2 : h'0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f',
        / version / 4 : "1.3.5",
        / signer-id / 5 : h'acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b'
      }, {
        / measurement-type / 1 : "ARoT",
        / measurement-value / 2 : h'a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478',
        / version / 4 : "0.1.4",
        / signer-id / 5 : h'acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b'
      } ],
    / psa-nonce / 10 : h'414a7c174141b3d0e9a1d28af31520f0d42299feac4007ded89d68ae6cd92f19',
    / psa-instance-id / 256 : h'01ceebae7b8927a3227e5303cf5e0f1f7b34bb542ad7250ac03fbcde36ec2f1508',
    / psa-verification-service-indicator / 2400 : "https://psa-verifier.org"
}

If possible, describe a solution

Adding claims_psacertified2023.go ?

// Copyright 2021-2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package psatoken

import (
	_ "crypto/sha256" // used hash algorithms need to be imported explicitly
	"encoding/json"
	"fmt"

	"github.com/veraison/eat"
)

const ProfilePSAC2023Name = "tag:psacertified.org,2023:psa#tfm"

// ProfilePSAC2023 provides the IClaims implementation associated with tag:psacertified.org,2023:psa#tfm
type ProfilePSAC2023 struct{}

func (o ProfilePSAC2023) GetName() string {
	return ProfilePSAC2023Name
}

func (o ProfilePSAC2023) GetClaims() IClaims {
	return newPSAC2023()
}

// PSAC2023 are associated with profile "tag:psacertified.org,2023:psa#tfm"
// See https://datatracker.ietf.org/doc/html/rfc9783
type PSAC2023 struct {
	Profile                *eat.Profile  `cbor:"265,keyasint" json:"eat-profile"`
	ClientID               *int32        `cbor:"2394,keyasint" json:"psa-client-id"`
	SecurityLifeCycle      *uint16       `cbor:"2395,keyasint" json:"psa-security-lifecycle"`
	ImplID                 *[]byte       `cbor:"2396,keyasint" json:"psa-implementation-id"`
	BootSeed               *[]byte       `cbor:"268,keyasint,omitempty" json:"psa-boot-seed,omitempty"`
	CertificationReference *string       `cbor:"2398,keyasint,omitempty" json:"psa-certification-reference,omitempty"`
	SwComponents           ISwComponents `cbor:"2399,keyasint" json:"psa-software-components"`
	Nonce                  *eat.Nonce    `cbor:"10,keyasint" json:"psa-nonce"`
	InstID                 *eat.UEID     `cbor:"256,keyasint" json:"psa-instance-id"`
	VSI                    *string       `cbor:"2400,keyasint,omitempty" json:"psa-verification-service-indicator,omitempty"`

	// CanonicalProfile contains the "correct" profile name associated with
	// this IClaims implementation (e.g. "tag:psacertified.org,2023:psa#tfm" for
	// PSAC2023). The reason this is a field rather than a global constant
	// is so that derived profiles can embed this struct and rely on its
	// existing validation methods.
	CanonicalProfile string `cbor:"-" json:"-"`
}

func newPSAC2023() IClaims {
	p := eat.Profile{}
	if err := p.Set(ProfilePSAC2023Name); err != nil {
		// should never get here as using known good constant as input
		panic(err)
	}

	return &PSAC2023{
		Profile:          &p,
		SwComponents:     &SwComponents[*SwComponent]{},
		CanonicalProfile: ProfilePSAC2023Name,
	}
}

// Semantic validation
func (c PSAC2023) Validate() error { //nolint:gocritic
	return ValidateClaims(&c)
}

func (c *PSAC2023) SetClientID(v int32) error {
	// any int32 value is acceptable
	c.ClientID = &v
	return nil
}

func (c *PSAC2023) SetSecurityLifeCycle(v uint16) error {
	if err := ValidateSecurityLifeCycle(v); err != nil {
		return err
	}

	c.SecurityLifeCycle = &v

	return nil
}

func (c *PSAC2023) SetImplID(v []byte) error {
	if err := ValidateImplID(v); err != nil {
		return err
	}

	c.ImplID = &v

	return nil
}

func (c *PSAC2023) SetBootSeed(v []byte) error {
	l := len(v)
	if l < 8 || l > 32 {
		return fmt.Errorf(
			"%w: invalid length %d (MUST be between 8 and 32 bytes)",
			ErrWrongSyntax, l,
		)
	}

	c.BootSeed = &v

	return nil
}

func (c *PSAC2023) SetCertificationReference(v string) error {
	if !CertificationReferenceP1RE.MatchString(v) &&
		!CertificationReferenceP2RE.MatchString(v) {
		return fmt.Errorf(
			"%w: MUST be in EAN-13 or EAN-13+5 format",
			ErrWrongSyntax,
		)
	}

	c.CertificationReference = &v

	return nil
}

func (c *PSAC2023) SetSoftwareComponents(scs []ISwComponent) error {
	if c.SwComponents == nil {
		c.SwComponents = &SwComponents[*SwComponent]{}
	}

	return c.SwComponents.Replace(scs)
}

func (c *PSAC2023) SetNonce(v []byte) error {
	if err := ValidatePSAHashType(v); err != nil {
		return err
	}

	n := eat.Nonce{}

	if err := n.Add(v); err != nil {
		return err
	}

	c.Nonce = &n

	return nil
}

func (c *PSAC2023) SetInstID(v []byte) error {
	if err := ValidateInstID(v); err != nil {
		return err
	}

	ueid := eat.UEID(v)

	c.InstID = &ueid

	return nil
}

func (c *PSAC2023) SetVSI(v string) error {
	if err := ValidateVSI(v); err != nil {
		return err
	}

	c.VSI = &v

	return nil
}

// Codecs

// this type alias is used to prevent infinite recursion during marshaling.
type psaC2023 PSAC2023

func (c *PSAC2023) UnmarshalCBOR(buf []byte) error {
	c.Profile = nil // clear profile to make sure we take it from buf

	return dm.Unmarshal(buf, (*psaC2023)(c))
}

func (c *PSAC2023) UnmarshalJSON(buf []byte) error {
	c.Profile = nil // clear profile to make sure we take it from buf

	return json.Unmarshal(buf, (*psaC2023)(c))
}

// Getters return a validated value or an error
// After successful call to Validate(), getters of mandatory claims are assured
// to never fail.  Getters of optional claim may still fail with
// ErrOptionalClaimMissing in case the claim is not present.

func (c PSAC2023) GetProfile() (string, error) { //nolint:gocritic
	if c.Profile == nil {
		return "", ErrMandatoryClaimMissing
	}

	profileString, err := c.Profile.Get()
	if err != nil {
		return "", err
	}

	if profileString != c.CanonicalProfile {
		return "", fmt.Errorf("%w: expecting %q, got %q",
			ErrWrongProfile, c.CanonicalProfile, profileString)
	}

	return profileString, nil
}

func (c PSAC2023) GetClientID() (int32, error) { //nolint:gocritic
	if c.ClientID == nil {
		return 0, ErrMandatoryClaimMissing
	}

	return *c.ClientID, nil
}

func (c PSAC2023) GetSecurityLifeCycle() (uint16, error) { //nolint:gocritic
	if c.SecurityLifeCycle == nil {
		return 0, ErrMandatoryClaimMissing
	}

	if err := ValidateSecurityLifeCycle(*c.SecurityLifeCycle); err != nil {
		return 0, err
	}

	return *c.SecurityLifeCycle, nil
}

func (c PSAC2023) GetImplID() ([]byte, error) { //nolint:gocritic
	if c.ImplID == nil {
		return nil, ErrMandatoryClaimMissing
	}

	if err := ValidateImplID(*c.ImplID); err != nil {
		return nil, err
	}

	return *c.ImplID, nil
}

func (c PSAC2023) GetBootSeed() ([]byte, error) { //nolint:gocritic
	if c.BootSeed == nil {
		return nil, ErrOptionalClaimMissing
	}

	l := len(*c.BootSeed)
	if l < 8 || l > 32 {
		return nil, fmt.Errorf(
			"%w: invalid length %d (MUST be between 8 and 32 bytes)",
			ErrWrongSyntax, l,
		)
	}

	return *c.BootSeed, nil
}

func (c PSAC2023) GetCertificationReference() (string, error) { //nolint:gocritic
	if c.CertificationReference == nil {
		return "", ErrOptionalClaimMissing
	}

	if !CertificationReferenceP2RE.MatchString(*c.CertificationReference) {
		return "", fmt.Errorf(
			"%w: MUST be in EAN-13+5 format",
			ErrWrongSyntax,
		)
	}

	return *c.CertificationReference, nil
}

func (c PSAC2023) GetSoftwareComponents() ([]ISwComponent, error) { //nolint:gocritic
	if c.SwComponents == nil || c.SwComponents.IsEmpty() {
		return nil, fmt.Errorf("%w (MUST have at least one sw component)",
			ErrMandatoryClaimMissing)
	}

	return c.SwComponents.Values()
}

func (c PSAC2023) GetNonce() ([]byte, error) { //nolint:gocritic
	v := c.Nonce

	if v == nil {
		return nil, ErrMandatoryClaimMissing
	}

	l := v.Len()

	if l != 1 {
		return nil, fmt.Errorf("%w: got %d nonces, want 1", ErrWrongSyntax, l)
	}

	n := v.GetI(0)
	if err := ValidateNonce(n); err != nil {
		return nil, err
	}

	return n, nil
}

func (c PSAC2023) GetInstID() ([]byte, error) { //nolint:gocritic
	v := c.InstID

	if v == nil {
		return nil, ErrMandatoryClaimMissing
	}

	if err := ValidateInstID(*v); err != nil {
		return nil, err
	}

	return *v, nil
}

func (c PSAC2023) GetVSI() (string, error) { //nolint:gocritic
	if c.VSI == nil {
		return "", ErrOptionalClaimMissing
	}

	if err := ValidateVSI(*c.VSI); err != nil {
		return "", err
	}

	return *c.VSI, nil
}

func init() {
	if err := RegisterProfile(ProfilePSAC2023{}); err != nil {
		panic(err)
	}
}

(Not tested yet, just replaced some words.)

Describe alternatives you have considered

Fix the label in claims_p2.go
https://github.com/veraison/psatoken/compare/main...kentakayama:psatoken:fix-boot-seed?expand=1

Additional context

Summary of changes across the drafts and RFC 9783:

Version Profile BootSeed label
v07 PSA_IOT_PROFILE_1 -75004
v13 http://arm.com/psa/2.0.0 2397
v14 tag:psacertified.org,2023:psa#tfm 2397
v21 tag:psacertified.org,2023:psa#tfm 268
RFC 9783 tag:psacertified.org,2023:psa#tfm 268

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions