Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This is a compliant implementation of ~~three~~two specifications:

* [draft-tschofenig-rats-psa-token-07](https://datatracker.ietf.org/doc/html/draft-tschofenig-rats-psa-token-07) (`PSA_IOT_PROFILE_1`), and
* [draft-tschofenig-rats-psa-token-09](https://datatracker.ietf.org/doc/html/draft-tschofenig-rats-psa-token-09) (`http://arm.com/psa/2.0.0`)
* [RFC9783](https://datatracker.ietf.org/doc/html/RFC9783) (`tag:psacertified.org,2023:psa#tfm`)
* ~~Realm Management Monitor Specificiation [RMM Spec](https://developer.arm.com/documentation/den0137/latest)~~

> [!Note]
Expand All @@ -22,7 +23,7 @@ The package exposes the following functionalities:

# Implementing new profiles

It is possible to support PSA-derived profiles other than profiles 1 and 2
It is possible to support PSA-derived profiles other than profiles 1, 2 and tfm
implemented here. To do this you need to provide an implementation of `IClaims`
and an implementation of `IProfile` that associates your `IClaims`
implementation with an `eat.Profile` value, and then register the `IProfile`
Expand Down
93 changes: 93 additions & 0 deletions claims_9783.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2025 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package psatoken

import (
"github.com/veraison/eat"
"github.com/veraison/psatoken/encoding"
)

type RFC9783Claims struct {
// embed P2Claims to inherit existing implementation
P2Claims
}

func (o *RFC9783Claims) Validate() error {
// "baseline" validation of P2 claims
if err := ValidateClaims(o); err != nil {
return err
}

return nil
}

// To ensure embedding is handled correctly during marshaling, we need to use
// custom encoding functions, which means implementing the eight marshaling
// methods defined by IClaims.

func (o RFC9783Claims) MarshalCBOR() ([]byte, error) { //nolint:gocritic
return encoding.SerializeStructToCBOR(em, &o)
}

func (o *RFC9783Claims) UnmarshalCBOR(data []byte) error {
return encoding.PopulateStructFromCBOR(dm, data, o)
}

func (o RFC9783Claims) MarshalJSON() ([]byte, error) { //nolint:gocritic
return encoding.SerializeStructToJSON(&o)

}
func (o *RFC9783Claims) UnmarshalJSON(data []byte) error {
return encoding.PopulateStructFromJSON(data, o)
}

// Name of the profile associated with RFC9783Claims
const RFC9783ProfileName = "tag:psacertified.org,2023:psa#tfm"

// factory function for RFC9783Claims
func NewRFC9783Claims() IClaims {
p := eat.Profile{}
if err := p.Set(RFC9783ProfileName); err != nil {
// should never get here as using known good constant as input
panic(err)
}

return &RFC9783Claims{
P2Claims: P2Claims{
Profile: &p,

// We need to provide an implementation of
// ISwComponent; as we're not extending software
// components, we're using the default implmentation
SwComponents: &SwComponents[*SwComponent]{},

// setting CanonicalProfile to our profile name, as we will be
// relying on the P2Claims's implementation to validate
// Profile claim,
CanonicalProfile: RFC9783ProfileName,
},
}
}

// Implementation of IProfile. This is used to register the new IClaims
// implementation and associated it with the profile name.
type RFC9783Profile struct{}

func (o RFC9783Profile) GetName() string {
return RFC9783ProfileName
}

func (o RFC9783Profile) GetClaims() IClaims {
return NewRFC9783Claims()
}

// Registering the profile inside init() to ensure that it it is available to
// the general NewClaims() and DecodeClaims() functions, and the IClaims
// implementation associated with the profile will automatically be used when
// the profile in the data matches the registered name.
func init() {
if err := RegisterProfile(RFC9783Profile{}); err != nil {
panic(err)
}
}
161 changes: 161 additions & 0 deletions claims_9783_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright 2025 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package psatoken

import (
"fmt"
"log"
)

func ExampleRFC9783Claims_unmarshalCBOR() {
input := mustHexDecode(nil, testEncodedRFC9783ClaimsAll)

claims, err := DecodeAndValidateClaimsFromCBOR(input)
if err != nil {
log.Fatalf("could not decode claims: %v", err)
}

profileName, err := claims.GetProfile()
if err != nil {
log.Fatalf("could not get profile: %v", err)
}
fmt.Printf("Profile: %s\n", profileName)

if _, ok := claims.(*RFC9783Claims); !ok {
log.Fatalf("not a *RFC9783Claims: %T", claims)
}

// output:
// Profile: tag:psacertified.org,2023:psa#tfm
}

func ExampleRFC9783Claims_marshalCBOR() {
rfc9783Claims := claims9783ExampleSetup()

out, err := rfc9783Claims.MarshalCBOR()
if err != nil {
log.Fatalf("could not marshal claims: %v", err)
}

fmt.Printf("marshaled claims: %x", out)

// output:
// marshaled claims: a719010978217461673a7073616365727469666965642e6f72672c323032333a7073612374666d19095a0119095b19300019095c58200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2019095f81a20258200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200558200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200a58300102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f1019010058210102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021
}

func ExampleRFC9783Claims_unmarshalJSON() {
input := []byte(`
{
"eat-profile": "tag:psacertified.org,2023:psa#tfm",
"psa-client-id": 1,
"psa-security-lifecycle": 12288,
"psa-implementation-id": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=",
"psa-boot-seed": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=",
"psa-hwver": "1234567890123",
"psa-software-components": [
{
"measurement-type": "BL",
"measurement-value": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=",
"signer-id": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA="
}
],
"psa-nonce": "AQIDBAUGBwgJCgsMDQ4PEAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8g",
"psa-instance-id": "AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAh",
"psa-verification-service-indicator": "https://psa-verifier.org",
"psa-certification-reference": "1234567890123-12345",
"timestamp": 1721138454
}
`)

claims, err := DecodeAndValidateClaimsFromJSON(input)
if err != nil {
log.Fatalf("could not decode claims: %v", err)
}

profileName, err := claims.GetProfile()
if err != nil {
log.Fatalf("could not get profile: %v", err)
}
fmt.Printf("Profile: %s\n", profileName)

if _, ok := claims.(*RFC9783Claims); !ok {
log.Fatalf("not a *RFC9783Claims: %T", claims)
}

// output:
// Profile: tag:psacertified.org,2023:psa#tfm
}

func ExampleRFC9783Claims_marshalJSON() {
rfc9783Claims := claims9783ExampleSetup()

out, err := rfc9783Claims.MarshalJSON()
if err != nil {
log.Fatalf("could not marshal claims: %v", err)
}

fmt.Printf("marshaled claims: %s", string(out))

// output:
// marshaled claims: {"eat-profile":"tag:psacertified.org,2023:psa#tfm","psa-client-id":1,"psa-security-lifecycle":12288,"psa-implementation-id":"AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=","psa-software-components":[{"measurement-value":"AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=","signer-id":"AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA="}],"psa-nonce":"AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyABAgMEBQYHCAkKCwwNDg8Q","psa-instance-id":"AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAh"}
}

func claims9783ExampleSetup() *RFC9783Claims {
exampleBytes := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14,
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
0x1f, 0x20,
}

// instance id must be 33 bytes
instIDBytes := append(exampleBytes, 0x21) // nolint:gocritic

// as per our profile, nonce must be 48 bytes
nonceBytes := append(exampleBytes, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // nolint:gocritic
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10)

// note: alternatively, can call NewExampleClaims() directly
claims, err := NewClaims(RFC9783ProfileName)
if err != nil {
log.Fatalf("could not create new claims: %v", err)
}

if err = claims.SetClientID(1); err != nil {
log.Fatalf("could not set client ID: %v", err)
}

if err = claims.SetSecurityLifeCycle(12288); err != nil {
log.Fatalf("could not set security life cycle: %v", err)
}

if err = claims.SetImplID(exampleBytes); err != nil {
log.Fatalf("could not set implementation ID: %v", err)
}

if err = claims.SetInstID(instIDBytes); err != nil {
log.Fatalf("could not set instance ID: %v", err)
}

if err = claims.SetNonce(nonceBytes); err != nil {
log.Fatalf("could not set nonce: %v", err)
}

swComponents := []ISwComponent{
&SwComponent{
MeasurementValue: &exampleBytes,
SignerID: &exampleBytes,
},
}
if err = claims.SetSoftwareComponents(swComponents); err != nil {
log.Fatalf("could not set implementation ID: %v", err)
}

rfc9783Claims, ok := claims.(*RFC9783Claims)
if !ok {
log.Fatalf("not a *RFC9783Claims: %T", claims)
}

return rfc9783Claims
}
23 changes: 20 additions & 3 deletions pretty_test_vectors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// Copyright 2021-2024 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package psatoken

// automatically generated from P1ClaimsAll.diag
Expand Down Expand Up @@ -165,3 +162,23 @@ aaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd
6d2e636f6d2f7073612f322e302e3019095e733036303435363532373238
32392d3130303130
`

// automatically generated from RFC9783ClaimsAll.diag
var testEncodedRFC9783ClaimsAll = `
a919010978217461673a7073616365727469666965642e6f72672c323032
333a7073612374666d19095a0119095b19300019095c582061636d652d69
6d706c656d656e746174696f6e2d69642d30303030303030303119010c58
20deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefde
adbeef19095f83a40162424c02582087428fc522803d31065e7bce3cf03f
e475096631e5e07bbd7a0fde60c4cf25c70465322e312e30055820acbb11
c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86ba4
016450526f540258200263829989b6fd954f72baaf2fc64bc2e2f01d692d
4de72986ea808f6e99813f0465312e332e35055820acbb11c7e4da217205
523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86ba4016441526f54
025820a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909
da779a14780465302e312e34055820acbb11c7e4da217205523ce4ce1a24
5ae1a239ae3c6bfd9e7871f7e5d8bae86b0a5820414a7c174141b3d0e9a1
d28af31520f0d42299feac4007ded89d68ae6cd92f19190100582101ceeb
ae7b8927a3227e5303cf5e0f1f7b34bb542ad7250ac03fbcde36ec2f1508
190960781868747470733a2f2f7073612d76657269666965722e6f7267
`
26 changes: 26 additions & 0 deletions testvectors/cbor/RFC9783ClaimsAll.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
/ 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"
}
3 changes: 2 additions & 1 deletion testvectors/cbor/build-test-vectors.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# Copyright 2022 Contributors to the Veraison project.
# Copyright 2022-2025 Contributors to the Veraison project.
# SPDX-License-Identifier: Apache-2.0

set -eu
Expand All @@ -17,6 +17,7 @@ DIAG_FILES="${DIAG_FILES} P2ClaimsMissingMandatoryNonce"
DIAG_FILES="${DIAG_FILES} P2ClaimsInvalidMultiNonce"
DIAG_FILES="${DIAG_FILES} P1ClaimsTFM"
DIAG_FILES="${DIAG_FILES} P2ClaimsTFM"
DIAG_FILES="${DIAG_FILES} RFC9783ClaimsAll"

TV_DOT_GO=${TV_DOT_GO?must be set in the environment.}

Expand Down