Skip to content

Commit

Permalink
setter/getter interfaces for key attestation (#33)
Browse files Browse the repository at this point in the history
* setter/getter interfaces for key attestation

Fix #30

Signed-off-by: Thomas Fossati <[email protected]>

* better document the types returned by GetKeyAttestation

Signed-off-by: Thomas Fossati <[email protected]>

---------

Signed-off-by: Thomas Fossati <[email protected]>
  • Loading branch information
thomas-fossati authored May 18, 2023
1 parent 263a891 commit e82a194
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 1 deletion.
67 changes: 66 additions & 1 deletion ear_appraisal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@

package ear

import "errors"
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
)

// Appraisal represents the result of an evidence appraisal
// by the verifier. It wraps the AR4SI trustworthiness vector together with
Expand All @@ -26,6 +34,63 @@ type AppraisalExtensions struct {
VeraisonKeyAttestation *map[string]interface{} `json:"ear.veraison.key-attestation,omitempty"`
}

// SetKeyAttestation sets the value of `akpub` in the
// "ear.veraison.key-attestation" claim.
// The following key types are currently supported: *rsa.PublicKey,
// *ecdsa.PublicKey, ed25519.PublicKey (not a pointer).
// Unsupported key types result in an error.
func (o *AppraisalExtensions) SetKeyAttestation(pub any) error {
switch v := pub.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
default:
return fmt.Errorf("unsupported type for public key: %T", v)
}

k, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return fmt.Errorf("unable to marshal public key: %w", err)
}

akpub := base64.RawURLEncoding.EncodeToString(k)

o.VeraisonKeyAttestation = &map[string]interface{}{
"akpub": akpub,
}

return nil
}

// GetKeyAttestation returns the decoded public key carried in the
// "ear.veraison.key-attestation" claim.
// The returned key type is one supported by x509.ParsePKIXPublicKey.
func (o AppraisalExtensions) GetKeyAttestation() (any, error) {
if o.VeraisonKeyAttestation == nil {
return nil, errors.New(`"ear.veraison.key-attestation" claim not found`)
}

v, ok := (*o.VeraisonKeyAttestation)["akpub"]
if !ok {
return nil, errors.New(`"akpub" claim not found in "ear.veraison.key-attestation"`)
}

akpub, ok := v.(string)
if !ok {
return nil, errors.New(`"ear.veraison.key-attestation" malformed: "akpub" must be string`)
}

k, err := base64.RawURLEncoding.DecodeString(akpub)
if err != nil {
return nil, fmt.Errorf(`"ear.veraison.key-attestation" malformed: decoding "akpub": %w`, err)
}

pub, err := x509.ParsePKIXPublicKey(k)
if err != nil {
return nil, fmt.Errorf(`parsing "akpub" failed: %w`, err)
}

return pub, nil
}

// UpdateStatusFromTrustVector ensure that Status trustworthiness is not
// higher than is warranted by trust vector claims. For every claim that has
// been made (i.e. is not in TrustTierNone), if the claim's trust tier is lower
Expand Down
91 changes: 91 additions & 0 deletions ear_appraisal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2023 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package ear

import (
"crypto/ecdsa"
"crypto/elliptic"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAppraisalExtensions_SetGetKeyAttestation_ok(t *testing.T) {
expected := AppraisalExtensions{
VeraisonKeyAttestation: &map[string]interface{}{
"akpub": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li_hp_m47n60p8D54WK84zV2sxXs7LtkBoN79R9Q",
},
}

kp, err := ecdsa.GenerateKey(elliptic.P256(), new(zeroSource))
require.NoError(t, err)
tv := kp.Public()

actual := AppraisalExtensions{}

err = actual.SetKeyAttestation(tv)
assert.NoError(t, err)
assert.Equal(t, expected, actual)

pub, err := actual.GetKeyAttestation()
assert.NoError(t, err)
assert.Equal(t, tv, pub)
}

func TestAppraisalExtensions_SetKeyAttestation_fail_unsupported_key_type(t *testing.T) {
tv := "MFkwWwYHKo"

actual := AppraisalExtensions{}
err := actual.SetKeyAttestation(tv)
assert.EqualError(t, err, "unsupported type for public key: string")
}

func TestAppraisalExtensions_GetKeyAttestation_fail_no_claim(t *testing.T) {
tv := AppraisalExtensions{}

_, err := tv.GetKeyAttestation()
assert.EqualError(t, err, `"ear.veraison.key-attestation" claim not found`)
}

func TestAppraisalExtensions_GetKeyAttestation_fail_akpub_missing(t *testing.T) {
tv := AppraisalExtensions{
VeraisonKeyAttestation: &map[string]interface{}{},
}

_, err := tv.GetKeyAttestation()
assert.EqualError(t, err, `"akpub" claim not found in "ear.veraison.key-attestation"`)
}

func TestAppraisalExtensions_GetKeyAttestation_fail_akpub_truncated(t *testing.T) {
tv := AppraisalExtensions{
VeraisonKeyAttestation: &map[string]interface{}{
"akpub": "MFkwEwYHKo",
},
}

_, err := tv.GetKeyAttestation()
assert.EqualError(t, err, `parsing "akpub" failed: asn1: syntax error: data truncated`)
}

func TestAppraisalExtensions_GetKeyAttestation_fail_akpub_not_a_string(t *testing.T) {
tv := AppraisalExtensions{
VeraisonKeyAttestation: &map[string]interface{}{
"akpub": 141245,
},
}

_, err := tv.GetKeyAttestation()
assert.EqualError(t, err, `"ear.veraison.key-attestation" malformed: "akpub" must be string`)
}

func TestAppraisalExtensions_GetKeyAttestation_fail_akpub_no_b64url(t *testing.T) {
tv := AppraisalExtensions{
VeraisonKeyAttestation: &map[string]interface{}{
"akpub": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9Q==",
},
}
_, err := tv.GetKeyAttestation()
assert.EqualError(t, err, `"ear.veraison.key-attestation" malformed: decoding "akpub": illegal base64 data at input byte 84`)
}
15 changes: 15 additions & 0 deletions test_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2023 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package ear

// zeroSource is an io.Reader that returns an unlimited number of zero bytes.
type zeroSource struct{}

func (zeroSource) Read(b []byte) (n int, err error) {
for i := range b {
b[i] = 0
}

return len(b), nil
}

0 comments on commit e82a194

Please sign in to comment.