-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
[CoRIM | ||
specification](https://www.ietf.org/archive/id/draft-ietf-rats-corim-02.htm) | ||
may be extended by other specifications at several points identified in the | ||
spec using CDDL extension sockets. This implementation, likewise, allows | ||
dependent code to register extension types. This is done via three distinct | ||
extension mechanisms: | ||
|
||
1. Some structures embed an Extensions object that allows registering a | ||
user-provided struct. The fields of that struct will be treated as extension | ||
fields to embedding structure. This corresponds to the map-extension sockets | ||
in the spec. | ||
2. Some elements can contain values of one of pre-determined types. New types | ||
can be provided by registering a factory function for those types using an | ||
appropriate function. This (mostly) corresponds to the type-choice extension | ||
sockets in the spec. | ||
3. A couple of type-choice sockets (`$tag-rel-type-choice`, | ||
`$corim-role-type-choice` and `$comid-role-type-choice`) define what, in | ||
effect, are extensible enums. They allow providing additional values, rather | ||
than types. This implementation provides registration functions for new | ||
values for those types. | ||
|
||
> [!NOTE] | ||
> The CoRIM spec includes some CDDL from the CoSWID spec that also features | ||
> extension sockets. This code base does include th CoSWID implementation, and | ||
> so does not cover those extension points. | ||
|
||
## Map Extensions | ||
|
||
Map extensions allow extending CoRIM maps with additional keys, effectively | ||
defining new fields for the corresponding structures. In the code base, these | ||
can be identified by the embedded `Extensions` struct. These are | ||
|
||
- `comid.Comid` | ||
- `comid.Entity` | ||
- `comid.FlagsMap` | ||
- `comid.Mval` | ||
- `comid.Triples` | ||
- `corim.Entity` | ||
- `corim.Signer` | ||
- `corim.UnsignedCorim` | ||
|
||
To extend the above types, you need to define a struct containing your | ||
extensions and pass a pointer to an instance of that struct to the | ||
`RegisterExtensions()` method of the corresponding instance of the type that is | ||
being extended. This should be done as early as possible, before any marshaling | ||
is performed. The | ||
|
||
These types can be extended in two ways: by adding additional fields, and by | ||
introducing additional constraints over existing fields. | ||
|
||
### Adding new fields | ||
|
||
To add a new fields, simply add them to your extensions struct, ensuring that | ||
the `cbor` and `json` tags on those fields are set corrects. As CoRIM spec | ||
mandates integer keys, you must use `keyasint` option for the `cbor` tag. | ||
|
||
To access the values of those fields, you can call the extended type instance's | ||
`Extensions.Get()` passing in the name of the field you want to access. The | ||
name can be either the Go struct's field name, the name specified in the `json` | ||
tag, or (a string containing) the integer specified in the `cbor` tag. | ||
|
||
`Get()` returns an `interface{}`. There are equivalent `GetInt()`, | ||
`GetString()`, etc. methods that perform the required conversions, and return | ||
the value of the indicated type, along with possible errors. ("Must" versions | ||
of these also exists, e.g. `MustGetString()`, that do not return an error). | ||
|
||
You can also get the pointer your extensions instance itself by calling | ||
extended type instance's `GetExtensions()`. This returns an `interface{}`, so | ||
you will need to type assert in order to be able to access the fields directly. | ||
|
||
### Introducing additional constraints | ||
|
||
To introduce new constraints, add a method called `Validate<type>(v *<type>)` | ||
to your extensions struct, where `<type>` is the name of the type being | ||
extended (one of the ones listed above) -- e.g. | ||
`ValidateComid(v *comid.Comid)` when extending `comid.Comid`. This method, if | ||
it exists, will be invoked inside the extended type instance's `Valid()` | ||
method, passing itself as the parameter. | ||
|
||
You do not need to define this method, unless you actually want to enforce some | ||
constraints (i.e. if you just want to define additional fields). | ||
|
||
### Example | ||
|
||
The following example illustrates how to implement a map extension by extending `comid.Entity` with the following features: | ||
|
||
1. an optional "email" field | ||
2. additional validation to ensure that the existing name field contains a | ||
valid UUID (note: since `NameEntry` is a type choice extensible, this can | ||
also be done by defining a new value type for `NameEntry` -- see the | ||
following section). | ||
|
||
``` | ||
package main | ||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"github.com/google/uuid" | ||
"github.com/veraison/corim/comid" | ||
) | ||
type EntityExtensions struct { | ||
Email string `cbor:"-1,keyasint,omitempty" json:"email,omitempty"` | ||
} | ||
func (o EntityExtensions) ValidEntity(val *comid.Entity) error { | ||
_, err := uuid.Parse(val.EntityName.String()) | ||
if err != nil { | ||
return fmt.Errorf("invalid UUID: %w", err) | ||
} | ||
return nil | ||
} | ||
var sampleText = ` | ||
{ | ||
"name": "31fb5abf-023e-4992-aa4e-95f9c1503bfa", | ||
"regid": "https://acme.example", | ||
"email": "[email protected]", | ||
"roles": [ | ||
"tagCreator", | ||
"creator", | ||
"maintainer" | ||
] | ||
} | ||
` | ||
func main() { | ||
var entity comid.Entity | ||
entity.RegisterExtensions(&EntityExtensions{}) | ||
if err := json.Unmarshal([]byte(sampleText), &entity); err != nil { | ||
log.Fatalf("ERROR: %s", err.Error()) | ||
} | ||
if err := entity.Valid(); err != nil { | ||
log.Fatalf("failed to validate: %s", err.Error()) | ||
} else { | ||
fmt.Println("validation succeeded") | ||
} | ||
// obtain the extension field value via a generic getter | ||
email := entity.Extensions.MustGetString("email") | ||
fmt.Printf("entity email: %s\n", email) | ||
// retrive the extensions struct and get value via its field. | ||
exts := entity.GetExtensions().(*EntityExtensions) | ||
fmt.Printf("also entity email: %s\n", exts.Email) | ||
} | ||
``` | ||
|
||
|
||
## Type Choice Extensions | ||
|
||
Type Choice extensions allow specifying alternative types for existing CoRIM | ||
fields by defining a struct that implements an appropriate interface and | ||
registering it with CBOR tag. | ||
|
||
|
||
### Example | ||
|
||
The following example illustrates how to add a new type choice value | ||
implementation by extending `CryptoKey` type to support DER values. | ||
|
||
``` | ||
package main | ||
import ( | ||
"crypto" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"log" | ||
"github.com/veraison/corim/comid" | ||
) | ||
var DerKeyTag = uint64(9999) | ||
type TaggedDerKey []byte | ||
func NewTaggedDerKey(k any) (*comid.CryptoKey, error) { | ||
var b []byte | ||
var err error | ||
if k == nil { | ||
k = *new([]byte) | ||
} | ||
switch t := k.(type) { | ||
case []byte: | ||
b = t | ||
case string: | ||
b, err = base64.StdEncoding.DecodeString(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
default: | ||
return nil, fmt.Errorf("value must be a []byte; found %T", k) | ||
} | ||
key := TaggedDerKey(b) | ||
return &comid.CryptoKey{Value: key}, nil | ||
} | ||
func (o TaggedDerKey) String() string { | ||
return base64.StdEncoding.EncodeToString(o) | ||
} | ||
func (o TaggedDerKey) Valid() error { | ||
_, err := o.PublicKey() | ||
return err | ||
} | ||
func (o TaggedDerKey) Type() string { | ||
return "pkix-der-key" | ||
} | ||
func (o TaggedDerKey) PublicKey() (crypto.PublicKey, error) { | ||
if len(o) == 0 { | ||
return nil, errors.New("key value not set") | ||
} | ||
key, err := x509.ParsePKIXPublicKey(o) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to parse public key: %w", err) | ||
} | ||
return key, nil | ||
} | ||
var testKeyJSON = ` | ||
{ | ||
"type": "pkix-der-key", | ||
"value": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8BlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==" | ||
} | ||
` | ||
func main() { | ||
if err := comid.RegisterCryptoKeyType(DerKeyTag, NewTaggedDerKey); err != nil { | ||
log.Fatal(err) | ||
} | ||
var key comid.CryptoKey | ||
if err := json.Unmarshal([]byte(testKeyJSON), &key); err != nil { | ||
log.Fatal(err) | ||
} | ||
fmt.Printf("Decoded DER key: %x\n", key) | ||
} | ||
``` | ||
|
||
|
||
## Enum extensions |