From a57902197a852fdc6ce23067f3a4c5d607e11d5d Mon Sep 17 00:00:00 2001 From: setrofim Date: Thu, 29 Jun 2023 13:08:12 +0100 Subject: [PATCH 01/25] Add `COSE_Key` support (#146) Signed-off-by: Sergei Trofimov --- .github/.codecov.yml | 3 +- algorithm.go | 39 ++- algorithm_test.go | 52 +-- common.go | 96 ++++++ common_test.go | 140 ++++++++ errors.go | 4 + headers.go | 40 ++- headers_test.go | 10 +- key.go | 802 +++++++++++++++++++++++++++++++++++++++++++ key_test.go | 641 ++++++++++++++++++++++++++++++++++ 10 files changed, 1774 insertions(+), 53 deletions(-) create mode 100644 common.go create mode 100644 common_test.go create mode 100644 key.go create mode 100644 key_test.go diff --git a/.github/.codecov.yml b/.github/.codecov.yml index 7c6d863..4b598be 100644 --- a/.github/.codecov.yml +++ b/.github/.codecov.yml @@ -1,5 +1,6 @@ coverage: status: + patch: off project: default: - target: 89% \ No newline at end of file + target: 89% diff --git a/algorithm.go b/algorithm.go index 7b95ed7..7e68535 100644 --- a/algorithm.go +++ b/algorithm.go @@ -2,6 +2,7 @@ package cose import ( "crypto" + "fmt" "strconv" ) @@ -36,10 +37,12 @@ const ( // PureEdDSA by RFC 8152. AlgorithmEd25519 Algorithm = -8 + + // An invalid/unrecognised algorithm. + AlgorithmInvalid Algorithm = 0 ) // Algorithm represents an IANA algorithm entry in the COSE Algorithms registry. -// Algorithms with string values are not supported. // // # See Also // @@ -72,6 +75,35 @@ func (a Algorithm) String() string { } } +// MarshalCBOR marshals the Algorithm as a CBOR int. +func (a Algorithm) MarshalCBOR() ([]byte, error) { + return encMode.Marshal(int64(a)) +} + +// UnmarshalCBOR populates the Algorithm from the provided CBOR value (must be +// int or tstr). +func (a *Algorithm) UnmarshalCBOR(data []byte) error { + var raw intOrStr + + if err := raw.UnmarshalCBOR(data); err != nil { + return fmt.Errorf("invalid algorithm value: %w", err) + } + + if raw.IsString() { + v := algorithmFromString(raw.String()) + if v == AlgorithmInvalid { + return fmt.Errorf("unknown algorithm value %q", raw.String()) + } + + *a = v + } else { + v := raw.Int() + *a = Algorithm(v) + } + + return nil +} + // hashFunc returns the hash associated with the algorithm supported by this // library. func (a Algorithm) hashFunc() crypto.Hash { @@ -103,3 +135,8 @@ func computeHash(h crypto.Hash, data []byte) ([]byte, error) { } return hh.Sum(nil), nil } + +// NOTE: there are currently no registered string values for an algorithm. +func algorithmFromString(v string) Algorithm { + return AlgorithmInvalid +} diff --git a/algorithm_test.go b/algorithm_test.go index 7ccafeb..bd92a8d 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -16,41 +16,6 @@ func TestAlgorithm_String(t *testing.T) { alg Algorithm want string }{ - { - name: "PS256", - alg: AlgorithmPS256, - want: "PS256", - }, - { - name: "PS384", - alg: AlgorithmPS384, - want: "PS384", - }, - { - name: "PS512", - alg: AlgorithmPS512, - want: "PS512", - }, - { - name: "ES256", - alg: AlgorithmES256, - want: "ES256", - }, - { - name: "ES384", - alg: AlgorithmES384, - want: "ES384", - }, - { - name: "ES512", - alg: AlgorithmES512, - want: "ES512", - }, - { - name: "Ed25519", - alg: AlgorithmEd25519, - want: "EdDSA", - }, { name: "unknown algorithm", alg: 0, @@ -66,6 +31,23 @@ func TestAlgorithm_String(t *testing.T) { } } +func TestAlgorithm_CBOR(t *testing.T) { + tvs2 := []struct { + Data []byte + ExpectedError string + }{ + {[]byte{0x63, 0x66, 0x6f, 0x6f}, "unknown algorithm value \"foo\""}, + {[]byte{0x40}, "invalid algorithm value: must be int or string, found []uint8"}, + } + + for _, tv := range tvs2 { + var a Algorithm + + err := a.UnmarshalCBOR(tv.Data) + assertEqualError(t, err, tv.ExpectedError) + } +} + func TestAlgorithm_computeHash(t *testing.T) { // run tests data := []byte("hello world") diff --git a/common.go b/common.go new file mode 100644 index 0000000..32294a5 --- /dev/null +++ b/common.go @@ -0,0 +1,96 @@ +package cose + +import ( + "errors" + "fmt" +) + +// intOrStr is a value that can be either an int or a tstr when serialized to +// CBOR. +type intOrStr struct { + intVal int64 + strVal string + isString bool +} + +func newIntOrStr(v interface{}) *intOrStr { + var ios intOrStr + if err := ios.Set(v); err != nil { + return nil + } + return &ios +} + +func (ios intOrStr) Int() int64 { + return ios.intVal +} + +func (ios intOrStr) String() string { + if ios.IsString() { + return ios.strVal + } + return fmt.Sprint(ios.intVal) +} + +func (ios intOrStr) IsInt() bool { + return !ios.isString +} + +func (ios intOrStr) IsString() bool { + return ios.isString +} + +func (ios intOrStr) Value() interface{} { + if ios.IsInt() { + return ios.intVal + } + + return ios.strVal +} + +func (ios *intOrStr) Set(v interface{}) error { + switch t := v.(type) { + case int64: + ios.intVal = t + ios.strVal = "" + ios.isString = false + case int: + ios.intVal = int64(t) + ios.strVal = "" + ios.isString = false + case string: + ios.strVal = t + ios.intVal = 0 + ios.isString = true + default: + return fmt.Errorf("must be int or string, found %T", t) + } + + return nil +} + +// MarshalCBOR returns the encoded CBOR representation of the intOrString, as +// either int or tstr, depending on the value. If no value has been set, +// intOrStr is encoded as a zero-length tstr. +func (ios intOrStr) MarshalCBOR() ([]byte, error) { + if ios.IsInt() { + return encMode.Marshal(ios.intVal) + } + + return encMode.Marshal(ios.strVal) +} + +// UnmarshalCBOR unmarshals the provided CBOR encoded data (must be an int, +// uint, or tstr). +func (ios *intOrStr) UnmarshalCBOR(data []byte) error { + if len(data) == 0 { + return errors.New("zero length buffer") + } + + var val interface{} + if err := decMode.Unmarshal(data, &val); err != nil { + return err + } + + return ios.Set(val) +} diff --git a/common_test.go b/common_test.go new file mode 100644 index 0000000..5f2c8d5 --- /dev/null +++ b/common_test.go @@ -0,0 +1,140 @@ +package cose + +import ( + "bytes" + "reflect" + "testing" + + "github.com/fxamacker/cbor/v2" +) + +func Test_intOrStr(t *testing.T) { + ios := newIntOrStr(3) + assertEqual(t, true, ios.IsInt()) + assertEqual(t, false, ios.IsString()) + assertEqual(t, 3, ios.Int()) + assertEqual(t, "3", ios.String()) + + ios = newIntOrStr("foo") + assertEqual(t, false, ios.IsInt()) + assertEqual(t, true, ios.IsString()) + assertEqual(t, 0, ios.Int()) + assertEqual(t, "foo", ios.String()) + + ios = newIntOrStr(3.5) + if ios != nil { + t.Errorf("Expected nil, got %v", ios) + } +} + +func Test_intOrStr_CBOR(t *testing.T) { + ios := newIntOrStr(3) + data, err := ios.MarshalCBOR() + requireNoError(t, err) + assertEqual(t, []byte{0x03}, data) + + ios = &intOrStr{} + err = ios.UnmarshalCBOR(data) + requireNoError(t, err) + assertEqual(t, true, ios.IsInt()) + assertEqual(t, 3, ios.Int()) + + ios = newIntOrStr("foo") + data, err = ios.MarshalCBOR() + requireNoError(t, err) + assertEqual(t, []byte{0x63, 0x66, 0x6f, 0x6f}, data) + + ios = &intOrStr{} + err = ios.UnmarshalCBOR(data) + requireNoError(t, err) + assertEqual(t, true, ios.IsString()) + assertEqual(t, "foo", ios.String()) + + // empty value as field + s := struct { + Field1 intOrStr `cbor:"1,keyasint"` + Field2 int `cbor:"2,keyasint"` + }{Field1: intOrStr{}, Field2: 7} + + data, err = cbor.Marshal(s) + requireNoError(t, err) + assertEqual(t, []byte{0xa2, 0x1, 0x00, 0x2, 0x7}, data) + + ios = &intOrStr{} + data = []byte{0x22} + err = ios.UnmarshalCBOR(data) + requireNoError(t, err) + assertEqual(t, true, ios.IsInt()) + assertEqual(t, -3, ios.Int()) + + data = []byte{} + err = ios.UnmarshalCBOR(data) + assertEqualError(t, err, "zero length buffer") + + data = []byte{0x40} + err = ios.UnmarshalCBOR(data) + assertEqualError(t, err, "must be int or string, found []uint8") + + data = []byte{0xff, 0xff} + err = ios.UnmarshalCBOR(data) + assertEqualError(t, err, "cbor: unexpected \"break\" code") +} + +func requireNoError(t *testing.T, err error) { + if err != nil { + t.Errorf("Unexpected error: %q", err) + t.Fail() + } +} + +func assertEqualError(t *testing.T, err error, expected string) { + if err == nil || err.Error() != expected { + t.Errorf("Unexpected error: want %q, got %q", expected, err) + } +} + +func assertEqual(t *testing.T, expected, actual interface{}) { + if !objectsAreEqualValues(expected, actual) { + t.Errorf("Unexpected value: want %v, got %v", expected, actual) + } +} + +// taken from github.com/stretchr/testify +func objectsAreEqualValues(expected, actual interface{}) bool { + if objectsAreEqual(expected, actual) { + return true + } + + actualType := reflect.TypeOf(actual) + if actualType == nil { + return false + } + expectedValue := reflect.ValueOf(expected) + if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { + // Attempt comparison after type conversion + return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) + } + + return false +} + +// taken from github.com/stretchr/testify +func objectsAreEqual(expected, actual interface{}) bool { + if expected == nil || actual == nil { + return expected == actual + } + + exp, ok := expected.([]byte) + if !ok { + return reflect.DeepEqual(expected, actual) + } + + act, ok := actual.([]byte) + if !ok { + return false + } + if exp == nil || act == nil { + return exp == nil && act == nil + } + return bytes.Equal(exp, act) +} diff --git a/errors.go b/errors.go index 8c240e2..7d16741 100644 --- a/errors.go +++ b/errors.go @@ -14,4 +14,8 @@ var ( ErrUnavailableHashFunc = errors.New("hash function is not available") ErrVerification = errors.New("verification error") ErrInvalidPubKey = errors.New("invalid public key") + ErrInvalidPrivKey = errors.New("invalid private key") + ErrNotPrivKey = errors.New("not a private key") + ErrSignOpNotSupported = errors.New("sign key_op not supported by key") + ErrVerifyOpNotSupported = errors.New("verify key_op not supported by key") ) diff --git a/headers.go b/headers.go index 7074936..4218eee 100644 --- a/headers.go +++ b/headers.go @@ -53,7 +53,8 @@ func (h ProtectedHeader) MarshalCBOR() ([]byte, error) { // UnmarshalCBOR decodes a CBOR bstr object into ProtectedHeader. // // ProtectedHeader is an empty_or_serialized_map where -// empty_or_serialized_map = bstr .cbor header_map / bstr .size 0 +// +// empty_or_serialized_map = bstr .cbor header_map / bstr .size 0 func (h *ProtectedHeader) UnmarshalCBOR(data []byte) error { if h == nil { return errors.New("cbor: UnmarshalCBOR on nil ProtectedHeader pointer") @@ -117,8 +118,17 @@ func (h ProtectedHeader) Algorithm() (Algorithm, error) { return Algorithm(alg), nil case int64: return Algorithm(alg), nil + case string: + v := algorithmFromString(alg) + + var err error + if v == AlgorithmInvalid { + err = fmt.Errorf("unknown algorithm value %q", alg) + } + + return v, err default: - return 0, ErrInvalidAlgorithm + return AlgorithmInvalid, ErrInvalidAlgorithm } } @@ -212,22 +222,22 @@ func (h *UnprotectedHeader) UnmarshalCBOR(data []byte) error { // // It is represented by CDDL fragments: // -// Headers = ( -// protected : empty_or_serialized_map, -// unprotected : header_map -// ) +// Headers = ( +// protected : empty_or_serialized_map, +// unprotected : header_map +// ) // -// header_map = { -// Generic_Headers, -// * label => values -// } +// header_map = { +// Generic_Headers, +// * label => values +// } // -// label = int / tstr -// values = any +// label = int / tstr +// values = any // -// empty_or_serialized_map = bstr .cbor header_map / bstr .size 0 +// empty_or_serialized_map = bstr .cbor header_map / bstr .size 0 // -// See Also +// # See Also // // https://tools.ietf.org/html/rfc8152#section-3 type Headers struct { @@ -553,7 +563,7 @@ func (discardedCBORMessage) UnmarshalCBOR(data []byte) error { // validateHeaderLabelCBOR validates if all header labels are integers or // strings of a CBOR map object. // -// label = int / tstr +// label = int / tstr // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-1.4 func validateHeaderLabelCBOR(data []byte) error { diff --git a/headers_test.go b/headers_test.go index 1bc505d..11d4e72 100644 --- a/headers_test.go +++ b/headers_test.go @@ -1,6 +1,7 @@ package cose import ( + "errors" "reflect" "testing" ) @@ -422,13 +423,20 @@ func TestProtectedHeader_Algorithm(t *testing.T) { h: ProtectedHeader{ HeaderLabelAlgorithm: "foo", }, + wantErr: errors.New("unknown algorithm value \"foo\""), + }, + { + name: "invalid algorithm", + h: ProtectedHeader{ + HeaderLabelAlgorithm: 2.5, + }, wantErr: ErrInvalidAlgorithm, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.h.Algorithm() - if err != tt.wantErr { + if tt.wantErr != nil && err.Error() != tt.wantErr.Error() { t.Errorf("ProtectedHeader.Algorithm() error = %v, wantErr %v", err, tt.wantErr) return } diff --git a/key.go b/key.go new file mode 100644 index 0000000..741eaf3 --- /dev/null +++ b/key.go @@ -0,0 +1,802 @@ +package cose + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "errors" + "fmt" + "math/big" + "strconv" + + cbor "github.com/fxamacker/cbor/v2" +) + +const ( + // An inviald key_op value + KeyOpInvalid KeyOp = 0 + + // The key is used to create signatures. Requires private key fields. + KeyOpSign KeyOp = 1 + + // The key is used for verification of signatures. + KeyOpVerify KeyOp = 2 + + // The key is used for key transport encryption. + KeyOpEncrypt KeyOp = 3 + + // The key is used for key transport decryption. Requires private key fields. + KeyOpDecrypt KeyOp = 4 + + // The key is used for key wrap encryption. + KeyOpWrapKey KeyOp = 5 + + // The key is used for key wrap decryption. + KeyOpUnwrapKey KeyOp = 6 + + // The key is used for deriving keys. Requires private key fields. + KeyOpDeriveKey KeyOp = 7 + + // The key is used for deriving bits not to be used as a key. Requires + // private key fields. + KeyOpDeriveBits KeyOp = 8 + + // The key is used for creating MACs. + KeyOpMACCreate KeyOp = 9 + + // The key is used for validating MACs. + KeyOpMACVerify KeyOp = 10 +) + +// KeyOp represents a key_ops value used to restrict purposes for which a Key +// may be used. +type KeyOp int64 + +// KeyOpFromString returns the KeyOp corresponding to the specified name. +// The values are taken from https://www.rfc-editor.org/rfc/rfc7517#section-4.3 +func KeyOpFromString(val string) (KeyOp, error) { + switch val { + case "sign": + return KeyOpSign, nil + case "verify": + return KeyOpVerify, nil + case "encrypt": + return KeyOpEncrypt, nil + case "decrypt": + return KeyOpDecrypt, nil + case "wrapKey": + return KeyOpWrapKey, nil + case "unwrapKey": + return KeyOpUnwrapKey, nil + case "deriveKey": + return KeyOpDeriveKey, nil + case "deriveBits": + return KeyOpDeriveBits, nil + default: + return KeyOpInvalid, fmt.Errorf("unknown key_ops value %q", val) + } +} + +// String returns a string representation of the KeyType. Note does not +// represent a valid value of the corresponding serialized entry, and must not +// be used as such. (The values returned _mostly_ correspond to those accepted +// by KeyOpFromString, except for MAC create/verify, which are not defined by +// RFC7517). +func (ko KeyOp) String() string { + switch ko { + case KeyOpSign: + return "sign" + case KeyOpVerify: + return "verify" + case KeyOpEncrypt: + return "encrypt" + case KeyOpDecrypt: + return "decrypt" + case KeyOpWrapKey: + return "wrapKey" + case KeyOpUnwrapKey: + return "unwrapKey" + case KeyOpDeriveKey: + return "deriveKey" + case KeyOpDeriveBits: + return "deriveBits" + case KeyOpMACCreate: + return "MAC create" + case KeyOpMACVerify: + return "MAC verify" + default: + return "unknown key_op value " + strconv.Itoa(int(ko)) + } +} + +// IsSupported returnns true if the specified value is represents one of the +// key_ops defined in +// https://www.rfc-editor.org/rfc/rfc9052.html#name-cose-key-common-parameters +func (ko KeyOp) IsSupported() bool { + return ko >= 1 && ko <= 10 +} + +// MarshalCBOR marshals the KeyOp as a CBOR int. +func (ko KeyOp) MarshalCBOR() ([]byte, error) { + return encMode.Marshal(int64(ko)) +} + +// UnmarshalCBOR populates the KeyOp from the provided CBOR value (must be int +// or tstr). +func (ko *KeyOp) UnmarshalCBOR(data []byte) error { + var raw intOrStr + + if err := raw.UnmarshalCBOR(data); err != nil { + return fmt.Errorf("invalid key_ops value %w", err) + } + + if raw.IsString() { + v, err := KeyOpFromString(raw.String()) + if err != nil { + return err + } + + *ko = v + } else { + v := raw.Int() + *ko = KeyOp(v) + + if !ko.IsSupported() { + return fmt.Errorf("unknown key_ops value %d", v) + } + } + + return nil +} + +// KeyType identifies the family of keys represented by the associated Key. +// This determines which files within the Key must be set in order for it to be +// valid. +type KeyType int64 + +const ( + // Invlaid key type + KeyTypeInvalid KeyType = 0 + // Octet Key Pair + KeyTypeOKP KeyType = 1 + // Elliptic Curve Keys w/ x- and y-coordinate pair + KeyTypeEC2 KeyType = 2 + // Symmetric Keys + KeyTypeSymmetric KeyType = 4 +) + +// String returns a string representation of the KeyType. Note does not +// represent a valid value of the corresponding serialized entry, and must +// not be used as such. +func (kt KeyType) String() string { + switch kt { + case KeyTypeOKP: + return "OKP" + case KeyTypeEC2: + return "EC2" + case KeyTypeSymmetric: + return "Symmetric" + default: + return "unknown key type value " + strconv.Itoa(int(kt)) + } +} + +// MarshalCBOR marshals the KeyType as a CBOR int. +func (kt KeyType) MarshalCBOR() ([]byte, error) { + return encMode.Marshal(int(kt)) +} + +// UnmarshalCBOR populates the KeyType from the provided CBOR value (must be +// int or tstr). +func (kt *KeyType) UnmarshalCBOR(data []byte) error { + var raw intOrStr + + if err := raw.UnmarshalCBOR(data); err != nil { + return fmt.Errorf("invalid key type value: %w", err) + } + + if raw.IsString() { + v, err := keyTypeFromString(raw.String()) + + if err != nil { + return err + } + + *kt = v + } else { + v := raw.Int() + + if v == 0 { + // 0 is reserved, and so can never be valid + return fmt.Errorf("invalid key type value 0") + } + + if v > 4 || v < 0 || v == 3 { + return fmt.Errorf("unknown key type value %d", v) + } + + *kt = KeyType(v) + } + + return nil +} + +// NOTE: there are currently no registered string key type values. +func keyTypeFromString(v string) (KeyType, error) { + return KeyTypeInvalid, fmt.Errorf("unknown key type value %q", v) +} + +const ( + + // Invalid/unrecognised curve + CurveInvalid Curve = 0 + + // NIST P-256 also known as secp256r1 + CurveP256 Curve = 1 + + // NIST P-384 also known as secp384r1 + CurveP384 Curve = 2 + + // NIST P-521 also known as secp521r1 + CurveP521 Curve = 3 + + // X25519 for use w/ ECDH only + CurveX25519 Curve = 4 + + // X448 for use w/ ECDH only + CurveX448 Curve = 5 + + // Ed25519 for use /w EdDSA only + CurveEd25519 Curve = 6 + + // Ed448 for use /w EdDSA only + CurveEd448 Curve = 7 +) + +// Curve represents the EC2/OKP key's curve. See: +// https://datatracker.ietf.org/doc/html/rfc8152#section-13.1 +type Curve int64 + +// String returns a string representation of the Curve. Note does not +// represent a valid value of the corresponding serialized entry, and must +// not be used as such. +func (c Curve) String() string { + switch c { + case CurveP256: + return "P-256" + case CurveP384: + return "P-384" + case CurveP521: + return "P-521" + case CurveX25519: + return "X25519" + case CurveX448: + return "X448" + case CurveEd25519: + return "Ed25519" + case CurveEd448: + return "Ed448" + default: + return "unknown curve value " + strconv.Itoa(int(c)) + } +} + +// MarshalCBOR marshals the KeyType as a CBOR int. +func (c Curve) MarshalCBOR() ([]byte, error) { + return encMode.Marshal(int(c)) +} + +// UnmarshalCBOR populates the KeyType from the provided CBOR value (must be +// int or tstr). +func (c *Curve) UnmarshalCBOR(data []byte) error { + var raw intOrStr + + if err := raw.UnmarshalCBOR(data); err != nil { + return fmt.Errorf("invalid curve value: %w", err) + } + + if raw.IsString() { + v, err := curveFromString(raw.String()) + + if err != nil { + return err + } + + *c = v + } else { + v := raw.Int() + + if v < 1 || v > 7 { + return fmt.Errorf("unknown curve value %d", v) + } + + *c = Curve(v) + } + + return nil +} + +// NOTE: there are currently no registered string values for curves. +func curveFromString(v string) (Curve, error) { + return CurveInvalid, fmt.Errorf("unknown curve value %q", v) +} + +// Key represents a COSE_Key structure, as defined by RFC8152. +// Note: currently, this does NOT support RFC8230 (RSA algorithms). +type Key struct { + // Common parameters. These are independent of the key type. Only + // KeyType common parameter MUST be set. + + // KeyType identifies the family of keys for this structure, and thus, + // which of the key-type-specific parameters need to be set. + KeyType KeyType `cbor:"1,keyasint"` + // KeyID is the identification value matched to the kid in the message. + KeyID []byte `cbor:"2,keyasint,omitempty"` + // KeyOps can be set to restrict the set of operations that the Key is used for. + KeyOps []KeyOp `cbor:"4,keyasint,omitempty"` + // BaseIV is the Base IV to be xor-ed with Partial IVs. + BaseIV []byte `cbor:"5,keyasint,omitempty"` + + // Algorithm is used to restrict the algorithm that is used with the + // key. If it is set, the application MUST verify that it matches the + // algorithm for which the Key is being used. + Algorithm Algorithm `cbor:"-"` + // Curve is EC identifier -- taken form "COSE Elliptic Curves" IANA registry. + // Populated from keyStruct.RawKeyParam when key type is EC2 or OKP. + Curve Curve `cbor:"-"` + // K is the key value. Populated from keyStruct.RawKeyParam when key + // type is Symmetric. + K []byte `cbor:"-"` + + // EC2/OKP params + + // X is the x-coordinate + X []byte `cbor:"-2,keyasint,omitempty"` + // Y is the y-coordinate (sign bits are not supported) + Y []byte `cbor:"-3,keyasint,omitempty"` + // D is the private key + D []byte `cbor:"-4,keyasint,omitempty"` +} + +// NewOKPKey returns a Key created using the provided Octet Key Pair data. +func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { + if alg != AlgorithmEd25519 { + return nil, fmt.Errorf("unsupported algorithm %q", alg) + } + + key := &Key{ + KeyType: KeyTypeOKP, + Algorithm: alg, + Curve: CurveEd25519, + X: x, + D: d, + } + return key, key.Validate() +} + +// NewEC2Key returns a Key created using the provided elliptic curve key +// data. +func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { + var curve Curve + + switch alg { + case AlgorithmES256: + curve = CurveP256 + case AlgorithmES384: + curve = CurveP384 + case AlgorithmES512: + curve = CurveP521 + default: + return nil, fmt.Errorf("unsupported algorithm %q", alg) + } + + key := &Key{ + KeyType: KeyTypeEC2, + Algorithm: alg, + Curve: curve, + X: x, + Y: y, + D: d, + } + return key, key.Validate() +} + +// NewSymmetricKey returns a Key created using the provided Symmetric key +// bytes. +func NewSymmetricKey(k []byte) (*Key, error) { + key := &Key{ + KeyType: KeyTypeSymmetric, + K: k, + } + return key, key.Validate() +} + +// NewKeyFromPublic returns a Key created using the provided crypto.PublicKey +// and Algorithm. +func NewKeyFromPublic(alg Algorithm, pub crypto.PublicKey) (*Key, error) { + switch alg { + case AlgorithmES256, AlgorithmES384, AlgorithmES512: + vk, ok := pub.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) + } + + return NewEC2Key(alg, vk.X.Bytes(), vk.Y.Bytes(), nil) + case AlgorithmEd25519: + vk, ok := pub.(ed25519.PublicKey) + if !ok { + return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) + } + + return NewOKPKey(alg, []byte(vk), nil) + default: + return nil, ErrAlgorithmNotSupported + } +} + +// NewKeyFromPrivate returns a Key created using provided crypto.PrivateKey +// and Algorithm. +func NewKeyFromPrivate(alg Algorithm, priv crypto.PrivateKey) (*Key, error) { + switch alg { + case AlgorithmES256, AlgorithmES384, AlgorithmES512: + sk, ok := priv.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPrivKey) + } + + return NewEC2Key(alg, sk.X.Bytes(), sk.Y.Bytes(), sk.D.Bytes()) + case AlgorithmEd25519: + sk, ok := priv.(ed25519.PrivateKey) + if !ok { + return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPrivKey) + } + return NewOKPKey(alg, []byte(sk[32:]), []byte(sk[:32])) + default: + return nil, ErrAlgorithmNotSupported + } +} + +// Validate ensures that the parameters set inside the Key are internally +// consistent (e.g., that the key type is appropriate to the curve.) +func (k Key) Validate() error { + switch k.KeyType { + case KeyTypeEC2: + switch k.Curve { + case CurveP256, CurveP384, CurveP521: + // ok + default: + return fmt.Errorf( + "EC2 curve must be P-256, P-384, or P-521; found %q", + k.Curve.String(), + ) + } + case KeyTypeOKP: + switch k.Curve { + case CurveX25519, CurveX448, CurveEd25519, CurveEd448: + // ok + default: + return fmt.Errorf( + "OKP curve must be X25519, X448, Ed25519, or Ed448; found %q", + k.Curve.String(), + ) + } + case KeyTypeSymmetric: + default: + return errors.New(k.KeyType.String()) + } + + // If Algorithm is set, it must match the specified key parameters. + if k.Algorithm != AlgorithmInvalid { + expectedAlg, err := k.deriveAlgorithm() + if err != nil { + return err + } + + if k.Algorithm != expectedAlg { + return fmt.Errorf( + "found algorithm %q (expected %q)", + k.Algorithm.String(), + expectedAlg.String(), + ) + } + } + + return nil +} + +type keyalias Key + +type marshaledKey struct { + keyalias + + // RawAlgorithm contains the raw Algorithm value, this is necessary + // because cbor library ignores omitempty on types that implement the + // cbor.Marshaler interface. + RawAlgorithm cbor.RawMessage `cbor:"3,keyasint,omitempty"` + + // RawKeyParam contains the raw CBOR encoded data for the label -1. + // Depending on the KeyType this is used to populate either Curve or K + // below. + RawKeyParam cbor.RawMessage `cbor:"-1,keyasint,omitempty"` +} + +// MarshalCBOR encodes Key into a COSE_Key object. +func (k *Key) MarshalCBOR() ([]byte, error) { + tmp := marshaledKey{ + keyalias: keyalias(*k), + } + var err error + + switch k.KeyType { + case KeyTypeSymmetric: + if tmp.RawKeyParam, err = encMode.Marshal(k.K); err != nil { + return nil, err + } + case KeyTypeEC2, KeyTypeOKP: + if tmp.RawKeyParam, err = encMode.Marshal(k.Curve); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid key type: %q", k.KeyType.String()) + } + + if k.Algorithm != AlgorithmInvalid { + if tmp.RawAlgorithm, err = encMode.Marshal(k.Algorithm); err != nil { + return nil, err + } + } + + return encMode.Marshal(tmp) +} + +// UnmarshalCBOR decodes a COSE_Key object into Key. +func (k *Key) UnmarshalCBOR(data []byte) error { + var tmp marshaledKey + + if err := decMode.Unmarshal(data, &tmp); err != nil { + return err + } + *k = Key(tmp.keyalias) + + if tmp.RawAlgorithm != nil { + if err := decMode.Unmarshal(tmp.RawAlgorithm, &k.Algorithm); err != nil { + return err + } + } + + switch k.KeyType { + case KeyTypeEC2: + if tmp.RawKeyParam == nil { + return errors.New("missing Curve parameter (required for EC2 key type)") + } + + if err := decMode.Unmarshal(tmp.RawKeyParam, &k.Curve); err != nil { + return err + } + case KeyTypeOKP: + if tmp.RawKeyParam == nil { + return errors.New("missing Curve parameter (required for OKP key type)") + } + + if err := decMode.Unmarshal(tmp.RawKeyParam, &k.Curve); err != nil { + return err + } + case KeyTypeSymmetric: + if tmp.RawKeyParam == nil { + return errors.New("missing K parameter (required for Symmetric key type)") + } + + if err := decMode.Unmarshal(tmp.RawKeyParam, &k.K); err != nil { + return err + } + default: + // this should not be reachable as KeyType.UnmarshalCBOR would + // result in an error during decMode.Unmarshal() above, if the + // value in the data doesn't correspond to one of the above + // types. + return fmt.Errorf("unexpected key type %q", k.KeyType.String()) + } + + return k.Validate() +} + +// PublicKey returns a crypto.PublicKey generated using Key's parameters. +func (k *Key) PublicKey() (crypto.PublicKey, error) { + alg, err := k.deriveAlgorithm() + if err != nil { + return nil, err + } + + switch alg { + case AlgorithmES256, AlgorithmES384, AlgorithmES512: + var curve elliptic.Curve + + switch alg { + case AlgorithmES256: + curve = elliptic.P256() + case AlgorithmES384: + curve = elliptic.P384() + case AlgorithmES512: + curve = elliptic.P521() + } + + pub := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} + pub.X.SetBytes(k.X) + pub.Y.SetBytes(k.Y) + + return pub, nil + case AlgorithmEd25519: + return ed25519.PublicKey(k.X), nil + default: + return nil, ErrAlgorithmNotSupported + } +} + +// PrivateKey returns a crypto.PrivateKey generated using Key's parameters. +func (k *Key) PrivateKey() (crypto.PrivateKey, error) { + alg, err := k.deriveAlgorithm() + if err != nil { + return nil, err + } + + if len(k.D) == 0 { + return nil, ErrNotPrivKey + } + + switch alg { + case AlgorithmES256, AlgorithmES384, AlgorithmES512: + var curve elliptic.Curve + + switch alg { + case AlgorithmES256: + curve = elliptic.P256() + case AlgorithmES384: + curve = elliptic.P384() + case AlgorithmES512: + curve = elliptic.P521() + } + + priv := &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}, + D: new(big.Int), + } + priv.X.SetBytes(k.X) + priv.Y.SetBytes(k.Y) + priv.D.SetBytes(k.D) + + return priv, nil + case AlgorithmEd25519: + buf := make([]byte, ed25519.PrivateKeySize) + + copy(buf, k.D) + copy(buf[32:], k.X) + + return ed25519.PrivateKey(buf), nil + default: + return nil, ErrAlgorithmNotSupported + } +} + +// AlgorithmOrDefault returns the Algorithm associated with Key. If Key.Algorithm is +// set, that is what is returned. Otherwise, the algorithm is inferred using +// Key.Curve. This method does NOT validate that Key.Algorithm, if set, aligns +// with Key.Curve. +func (k *Key) AlgorithmOrDefault() (Algorithm, error) { + if k.Algorithm != AlgorithmInvalid { + return k.Algorithm, nil + } + + return k.deriveAlgorithm() +} + +// Signer returns a Signer created using Key. +func (k *Key) Signer() (Signer, error) { + if err := k.Validate(); err != nil { + return nil, err + } + + if k.KeyOps != nil { + signFound := false + + for _, kop := range k.KeyOps { + if kop == KeyOpSign { + signFound = true + break + } + } + + if !signFound { + return nil, ErrSignOpNotSupported + } + } + + priv, err := k.PrivateKey() + if err != nil { + return nil, err + } + + alg, err := k.AlgorithmOrDefault() + if err != nil { + return nil, err + } + + var signer crypto.Signer + var ok bool + + switch alg { + case AlgorithmES256, AlgorithmES384, AlgorithmES512: + signer, ok = priv.(*ecdsa.PrivateKey) + if !ok { + return nil, ErrInvalidPrivKey + } + case AlgorithmEd25519: + signer, ok = priv.(ed25519.PrivateKey) + if !ok { + return nil, ErrInvalidPrivKey + } + default: + return nil, ErrAlgorithmNotSupported + } + + return NewSigner(alg, signer) +} + +// Verifier returns a Verifier created using Key. +func (k *Key) Verifier() (Verifier, error) { + if err := k.Validate(); err != nil { + return nil, err + } + + if k.KeyOps != nil { + verifyFound := false + + for _, kop := range k.KeyOps { + if kop == KeyOpVerify { + verifyFound = true + break + } + } + + if !verifyFound { + return nil, ErrVerifyOpNotSupported + } + } + + pub, err := k.PublicKey() + if err != nil { + return nil, err + } + + alg, err := k.AlgorithmOrDefault() + if err != nil { + return nil, err + } + + return NewVerifier(alg, pub) +} + +// deriveAlgorithm derives the intended algorithm for the key from its curve. +// The deriviation is based on the recommendation in RFC8152 that SHA-256 is +// only used with P-256, etc. For other combinations, the Algorithm in the Key +// must be explicitly set,so that this derivation is not used. +func (k *Key) deriveAlgorithm() (Algorithm, error) { + switch k.KeyType { + case KeyTypeEC2, KeyTypeOKP: + switch k.Curve { + case CurveP256: + return AlgorithmES256, nil + case CurveP384: + return AlgorithmES384, nil + case CurveP521: + return AlgorithmES512, nil + case CurveEd25519: + return AlgorithmEd25519, nil + default: + return AlgorithmInvalid, fmt.Errorf("unsupported curve %q", k.Curve.String()) + } + default: + // Symmetric algorithms are not supported in the current inmplementation. + return AlgorithmInvalid, fmt.Errorf("unexpected key type %q", k.KeyType.String()) + } +} diff --git a/key_test.go b/key_test.go new file mode 100644 index 0000000..e57db62 --- /dev/null +++ b/key_test.go @@ -0,0 +1,641 @@ +package cose + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/fxamacker/cbor/v2" +) + +func Test_KeyOp(t *testing.T) { + + tvs := []struct { + Name string + Value KeyOp + }{ + {"sign", KeyOpSign}, + {"verify", KeyOpVerify}, + {"encrypt", KeyOpEncrypt}, + {"decrypt", KeyOpDecrypt}, + {"wrapKey", KeyOpWrapKey}, + {"unwrapKey", KeyOpUnwrapKey}, + {"deriveKey", KeyOpDeriveKey}, + {"deriveBits", KeyOpDeriveBits}, + } + + for _, tv := range tvs { + if tv.Name != tv.Value.String() { + t.Errorf( + "String value mismatch: expected %q, got %q", + tv.Name, + tv.Value.String(), + ) + } + + data, err := cbor.Marshal(tv.Name) + if err != nil { + t.Errorf("Unexpected error: %s", err) + return + } + + var ko KeyOp + err = cbor.Unmarshal(data, &ko) + if err != nil { + t.Errorf("Unexpected error: %s", err) + return + } + if tv.Value != ko { + t.Errorf( + "Value mismatch: want %v, got %v", + tv.Value, + ko, + ) + } + + data, err = cbor.Marshal(int(tv.Value)) + if err != nil { + t.Errorf("Unexpected error: %q", err) + return + } + + err = cbor.Unmarshal(data, &ko) + if err != nil { + t.Errorf("Unexpected error: %q", err) + return + } + if tv.Value != ko { + t.Errorf( + "Value mismatch: want %v, got %v", + tv.Value, + ko, + ) + } + } + + var ko KeyOp + + data := []byte{0x20} + err := ko.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown key_ops value -1") + + data = []byte{0x18, 0xff} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown key_ops value 255") + + data = []byte{0x63, 0x66, 0x6f, 0x6f} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown key_ops value \"foo\"") + + data = []byte{0x40} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "invalid key_ops value must be int or string, found []uint8") + + if "MAC create" != KeyOpMACCreate.String() { + t.Errorf("Unexpected value: %q", KeyOpMACCreate.String()) + } + + if "MAC verify" != KeyOpMACVerify.String() { + t.Errorf("Unexpected value: %q", KeyOpMACVerify.String()) + } + + if "unknown key_op value 42" != KeyOp(42).String() { + t.Errorf("Unexpected value: %q", KeyOp(42).String()) + } +} + +func Test_KeyType(t *testing.T) { + var ko KeyType + + data := []byte{0x20} + err := ko.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown key type value -1") + + data = []byte{0x00} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "invalid key type value 0") + + data = []byte{0x03} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown key type value 3") + + data = []byte{0x63, 0x66, 0x6f, 0x6f} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown key type value \"foo\"") + + data = []byte{0x40} + err = ko.UnmarshalCBOR(data) + assertEqualError(t, err, "invalid key type value: must be int or string, found []uint8") +} + +func Test_Curve(t *testing.T) { + var c Curve + + data := []byte{0x20} + err := c.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown curve value -1") + + data = []byte{0x00} + err = c.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown curve value 0") + + data = []byte{0x63, 0x66, 0x6f, 0x6f} + err = c.UnmarshalCBOR(data) + assertEqualError(t, err, "unknown curve value \"foo\"") + + data = []byte{0x40} + err = c.UnmarshalCBOR(data) + assertEqualError(t, err, "invalid curve value: must be int or string, found []uint8") + + if "unknown curve value 42" != Curve(42).String() { + t.Errorf("Unexpected string value %q", Curve(42).String()) + } +} + +func Test_Key_UnmarshalCBOR(t *testing.T) { + tvs := []struct { + Name string + Value []byte + WantErr string + Validate func(k *Key) + }{ + { + Name: "ok OKP", + Value: []byte{ + 0xa5, // map (5) + 0x01, 0x01, // kty: OKP + 0x03, 0x27, // alg: EdDSA w/ Ed25519 + 0x04, // key ops + 0x81, // array (1) + 0x02, // verify + 0x20, 0x06, // curve: Ed25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + WantErr: "", + Validate: func(k *Key) { + assertEqual(t, KeyTypeOKP, k.KeyType) + assertEqual(t, AlgorithmEd25519, k.Algorithm) + assertEqual(t, CurveEd25519, k.Curve) + assertEqual(t, []KeyOp{KeyOpVerify}, k.KeyOps) + assertEqual(t, []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + k.X, + ) + assertEqual(t, []byte(nil), k.K) + }, + }, + { + Name: "invalid key type", + Value: []byte{ + 0xa1, // map (2) + 0x01, 0x00, // kty: invalid + }, + WantErr: "invalid key type value 0", + Validate: nil, + }, + { + Name: "missing curve OKP", + Value: []byte{ + 0xa1, // map (2) + 0x01, 0x01, // kty: OKP + }, + WantErr: "missing Curve parameter (required for OKP key type)", + Validate: nil, + }, + { + Name: "missing curve EC2", + Value: []byte{ + 0xa1, // map (2) + 0x01, 0x02, // kty: EC2 + }, + WantErr: "missing Curve parameter (required for EC2 key type)", + Validate: nil, + }, + { + Name: "invalid curve OKP", + Value: []byte{ + 0xa2, // map (2) + 0x01, 0x01, // kty: OKP + 0x20, 0x01, // curve: CurveP256 + }, + WantErr: "OKP curve must be X25519, X448, Ed25519, or Ed448; found \"P-256\"", + Validate: nil, + }, + { + Name: "invalid curve EC2", + Value: []byte{ + 0xa2, // map (2) + 0x01, 0x02, // kty: EC2 + 0x20, 0x06, // curve: CurveEd25519 + }, + WantErr: "EC2 curve must be P-256, P-384, or P-521; found \"Ed25519\"", + Validate: nil, + }, + { + Name: "ok Symmetric", + Value: []byte{ + 0xa4, // map (4) + 0x01, 0x04, // kty: Symmetric + 0x03, 0x38, 0x24, // alg: PS256 + 0x04, // key ops + 0x81, // array (1) + 0x02, // verify + 0x20, 0x58, 0x20, // k: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + WantErr: "", + Validate: func(k *Key) { + assertEqual(t, KeyTypeSymmetric, k.KeyType) + assertEqual(t, AlgorithmPS256, k.Algorithm) + assertEqual(t, int64(0), int64(k.Curve)) + assertEqual(t, []KeyOp{KeyOpVerify}, k.KeyOps) + assertEqual(t, []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + k.K, + ) + }, + }, + { + Name: "missing K", + Value: []byte{ + 0xa1, // map (1) + 0x01, 0x04, // kty: Symmetric + }, + WantErr: "missing K parameter (required for Symmetric key type)", + Validate: nil, + }, + { + Name: "wrong algorithm", + Value: []byte{ + 0xa4, // map (3) + 0x01, 0x01, // kty: OKP + 0x03, 0x26, // alg: ECDSA w/ SHA-256 + 0x20, 0x06, // curve: Ed25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + WantErr: "found algorithm \"ES256\" (expected \"EdDSA\")", + Validate: nil, + }, + } + + for _, tv := range tvs { + t.Run(tv.Name, func(t *testing.T) { + var k Key + + err := k.UnmarshalCBOR(tv.Value) + if tv.WantErr != "" { + if err == nil || err.Error() != tv.WantErr { + t.Errorf("Unexpected error: want %q, got %q", tv.WantErr, err) + } + } else { + tv.Validate(&k) + } + }) + } +} + +func Test_Key_MarshalCBOR(t *testing.T) { + k := Key{ + KeyType: KeyTypeOKP, + KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, + X: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + Algorithm: AlgorithmEd25519, + Curve: CurveEd25519, + } + + data, err := k.MarshalCBOR() + if err != nil { + t.Errorf("Unexpected error: %s", err) + return + } + expected := []byte{ + 0xa5, // map (5) + 0x01, 0x01, // kty: OKP + 0x03, 0x27, // alg: EdDSA w/ Ed25519 + 0x04, // key ops + 0x82, // array (2) + 0x02, 0x03, // verify, encrypt + 0x20, 0x06, // curve: Ed25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + } + if !bytes.Equal(expected, data) { + t.Errorf("Bad marshal: %v", data) + } + + k = Key{ + KeyType: KeyTypeSymmetric, + K: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + } + + data, err = k.MarshalCBOR() + if err != nil { + t.Errorf("Unexpected error: %s", err) + return + } + expected = []byte{ + 0xa2, // map (2) + 0x01, 0x04, // kty: Symmetric + 0x20, 0x58, 0x20, // K: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + } + if !bytes.Equal(expected, data) { + t.Errorf("Bad marshal: %v", data) + } + + k.KeyType = KeyType(42) + _, err = k.MarshalCBOR() + wantErr := "invalid key type: \"unknown key type value 42\"" + if err == nil || err.Error() != wantErr { + t.Errorf("Unexpected error: want %q, got %q", wantErr, err) + } +} + +func Test_Key_Create_and_Validate(t *testing.T) { + x := []byte{ + 0x30, 0xa0, 0x42, 0x4c, 0xd2, 0x1c, 0x29, 0x44, + 0x83, 0x8a, 0x2d, 0x75, 0xc9, 0x2b, 0x37, 0xe7, + 0x6e, 0xa2, 0x0d, 0x9f, 0x00, 0x89, 0x3a, 0x3b, + 0x4e, 0xee, 0x8a, 0x3c, 0x0a, 0xaf, 0xec, 0x3e, + } + + y := []byte{ + 0xe0, 0x4b, 0x65, 0xe9, 0x24, 0x56, 0xd9, 0x88, + 0x8b, 0x52, 0xb3, 0x79, 0xbd, 0xfb, 0xd5, 0x1e, + 0xe8, 0x69, 0xef, 0x1f, 0x0f, 0xc6, 0x5b, 0x66, + 0x59, 0x69, 0x5b, 0x6c, 0xce, 0x08, 0x17, 0x23, + } + + key, err := NewOKPKey(AlgorithmEd25519, x, nil) + requireNoError(t, err) + assertEqual(t, KeyTypeOKP, key.KeyType) + assertEqual(t, x, key.X) + + _, err = NewOKPKey(AlgorithmES256, x, nil) + assertEqualError(t, err, "unsupported algorithm \"ES256\"") + + _, err = NewEC2Key(AlgorithmEd25519, x, y, nil) + assertEqualError(t, err, "unsupported algorithm \"EdDSA\"") + + key, err = NewEC2Key(AlgorithmES256, x, y, nil) + requireNoError(t, err) + assertEqual(t, KeyTypeEC2, key.KeyType) + assertEqual(t, x, key.X) + assertEqual(t, y, key.Y) + + key, err = NewSymmetricKey(x) + requireNoError(t, err) + assertEqual(t, x, key.K) + + key.KeyType = KeyType(7) + err = key.Validate() + assertEqualError(t, err, "unknown key type value 7") + + _, err = NewKeyFromPublic(AlgorithmES256, + crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "ES256: invalid public key") + + _, err = NewKeyFromPublic(AlgorithmEd25519, + crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "EdDSA: invalid public key") + + _, err = NewKeyFromPublic(AlgorithmInvalid, + crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "algorithm not supported") + + _, err = NewKeyFromPrivate(AlgorithmES256, + crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "ES256: invalid private key") + + _, err = NewKeyFromPrivate(AlgorithmEd25519, + crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "EdDSA: invalid private key") + + _, err = NewKeyFromPrivate(AlgorithmInvalid, + crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "algorithm not supported") +} + +func Test_Key_ed25519_signature_round_trip(t *testing.T) { + pub, priv, err := ed25519.GenerateKey(rand.Reader) + requireNoError(t, err) + + key, err := NewKeyFromPrivate(AlgorithmEd25519, priv) + requireNoError(t, err) + assertEqual(t, AlgorithmEd25519, key.Algorithm) + assertEqual(t, CurveEd25519, key.Curve) + assertEqual(t, pub, key.X) + assertEqual(t, priv[:32], key.D) + + signer, err := key.Signer() + requireNoError(t, err) + + message := []byte("foo bar") + sig, err := signer.Sign(rand.Reader, message) + requireNoError(t, err) + + key, err = NewKeyFromPublic(AlgorithmEd25519, pub) + requireNoError(t, err) + + assertEqual(t, AlgorithmEd25519, key.Algorithm) + assertEqual(t, CurveEd25519, key.Curve) + assertEqual(t, pub, key.X) + + verifier, err := key.Verifier() + requireNoError(t, err) + + err = verifier.Verify(message, sig) + requireNoError(t, err) +} + +func Test_Key_ecdsa_signature_round_trip(t *testing.T) { + for _, tv := range []struct { + EC elliptic.Curve + Curve Curve + Algorithm Algorithm + }{ + {elliptic.P256(), CurveP256, AlgorithmES256}, + {elliptic.P384(), CurveP384, AlgorithmES384}, + {elliptic.P521(), CurveP521, AlgorithmES512}, + } { + t.Run(tv.Curve.String(), func(t *testing.T) { + priv, err := ecdsa.GenerateKey(tv.EC, rand.Reader) + requireNoError(t, err) + + key, err := NewKeyFromPrivate(tv.Algorithm, priv) + requireNoError(t, err) + assertEqual(t, tv.Algorithm, key.Algorithm) + assertEqual(t, tv.Curve, key.Curve) + assertEqual(t, priv.X.Bytes(), key.X) + assertEqual(t, priv.Y.Bytes(), key.Y) + assertEqual(t, priv.D.Bytes(), key.D) + + signer, err := key.Signer() + requireNoError(t, err) + + message := []byte("foo bar") + sig, err := signer.Sign(rand.Reader, message) + requireNoError(t, err) + + pub := priv.Public() + + key, err = NewKeyFromPublic(tv.Algorithm, pub) + requireNoError(t, err) + + assertEqual(t, tv.Algorithm, key.Algorithm) + assertEqual(t, tv.Curve, key.Curve) + assertEqual(t, priv.X.Bytes(), key.X) + assertEqual(t, priv.Y.Bytes(), key.Y) + + verifier, err := key.Verifier() + requireNoError(t, err) + + err = verifier.Verify(message, sig) + requireNoError(t, err) + }) + } +} + +func Test_Key_derive_algorithm(t *testing.T) { + k := Key{ + KeyType: KeyTypeOKP, + Curve: CurveX448, + } + + _, err := k.AlgorithmOrDefault() + assertEqualError(t, err, "unsupported curve \"X448\"") + + k = Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + } + + alg, err := k.AlgorithmOrDefault() + requireNoError(t, err) + assertEqual(t, AlgorithmEd25519, alg) +} + +func Test_Key_signer_validation(t *testing.T) { + pub, priv, err := ed25519.GenerateKey(rand.Reader) + requireNoError(t, err) + + key, err := NewKeyFromPublic(AlgorithmEd25519, pub) + requireNoError(t, err) + + _, err = key.Signer() + assertEqualError(t, err, ErrNotPrivKey.Error()) + + key, err = NewKeyFromPrivate(AlgorithmEd25519, priv) + requireNoError(t, err) + + key.KeyType = KeyTypeEC2 + _, err = key.Signer() + assertEqualError(t, err, "EC2 curve must be P-256, P-384, or P-521; found \"Ed25519\"") + + key.Curve = CurveP256 + _, err = key.Signer() + assertEqualError(t, err, "found algorithm \"EdDSA\" (expected \"ES256\")") + + key.KeyType = KeyTypeOKP + key.Algorithm = AlgorithmEd25519 + key.Curve = CurveEd25519 + key.KeyOps = []KeyOp{} + _, err = key.Signer() + assertEqualError(t, err, ErrSignOpNotSupported.Error()) + + key.KeyOps = []KeyOp{KeyOpSign} + _, err = key.Signer() + requireNoError(t, err) + + key.Algorithm = AlgorithmES256 + _, err = key.Signer() + assertEqualError(t, err, "found algorithm \"ES256\" (expected \"EdDSA\")") + + key.Curve = CurveX448 + _, err = key.Signer() + assertEqualError(t, err, "unsupported curve \"X448\"") +} + +func Test_Key_verifier_validation(t *testing.T) { + pub, _, err := ed25519.GenerateKey(rand.Reader) + requireNoError(t, err) + + key, err := NewKeyFromPublic(AlgorithmEd25519, pub) + requireNoError(t, err) + + _, err = key.Verifier() + requireNoError(t, err) + + key.KeyType = KeyTypeEC2 + _, err = key.Verifier() + assertEqualError(t, err, "EC2 curve must be P-256, P-384, or P-521; found \"Ed25519\"") + + key.KeyType = KeyTypeOKP + key.KeyOps = []KeyOp{} + _, err = key.Verifier() + assertEqualError(t, err, ErrVerifyOpNotSupported.Error()) + + key.KeyOps = []KeyOp{KeyOpVerify} + _, err = key.Verifier() + requireNoError(t, err) +} + +func Test_Key_crypto_keys(t *testing.T) { + k := Key{ + KeyType: KeyType(7), + } + + _, err := k.PublicKey() + assertEqualError(t, err, "unexpected key type \"unknown key type value 7\"") + _, err = k.PrivateKey() + assertEqualError(t, err, "unexpected key type \"unknown key type value 7\"") + + k = Key{ + KeyType: KeyTypeOKP, + Curve: CurveX448, + } + + _, err = k.PublicKey() + assertEqualError(t, err, "unsupported curve \"X448\"") + _, err = k.PrivateKey() + assertEqualError(t, err, "unsupported curve \"X448\"") +} From b67457d00c55749e9e03b832d9f497c92f838061 Mon Sep 17 00:00:00 2001 From: setrofim Date: Tue, 4 Jul 2023 09:11:22 +0100 Subject: [PATCH 02/25] Retract v1.2.0 release (#153) Signed-off-by: setrofim --- go.mod | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/go.mod b/go.mod index 609875d..4a202d9 100644 --- a/go.mod +++ b/go.mod @@ -5,3 +5,8 @@ go 1.18 require github.com/fxamacker/cbor/v2 v2.4.0 require github.com/x448/float16 v0.8.4 // indirect + +retract ( + v1.2.1 // contains retractions only + v1.2.0 // published in error +) From 8ba8d6979f47f6ab843886178a46d056c07176cb Mon Sep 17 00:00:00 2001 From: Spencer McCreary Date: Tue, 4 Jul 2023 16:16:10 -0500 Subject: [PATCH 03/25] fix: typo UnataggedSign1Message -> UntaggedSign1Message (#148) Signed-off-by: Spencer McCreary --- sign1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sign1.go b/sign1.go index fa229e9..48640ab 100644 --- a/sign1.go +++ b/sign1.go @@ -262,7 +262,7 @@ func (m *UntaggedSign1Message) MarshalCBOR() ([]byte, error) { return encMode.Marshal(content) } -// UnmarshalCBOR decodes a COSE_Sign1 object into an UnataggedSign1Message. +// UnmarshalCBOR decodes a COSE_Sign1 object into an UntaggedSign1Message. func (m *UntaggedSign1Message) UnmarshalCBOR(data []byte) error { if m == nil { return errors.New("cbor: UnmarshalCBOR on nil UntaggedSign1Message pointer") From cebef1481e227ce239453b9dd91f2032b058cd66 Mon Sep 17 00:00:00 2001 From: setrofim Date: Wed, 5 Jul 2023 12:50:37 +0100 Subject: [PATCH 04/25] Key fixes (#154) Signed-off-by: setrofim --- errors.go | 2 + key.go | 111 ++++++++++++++++++++++++++---------------- key_test.go | 137 +++++++++++++++++++++++++++++++++------------------- 3 files changed, 158 insertions(+), 92 deletions(-) diff --git a/errors.go b/errors.go index 7d16741..de601e5 100644 --- a/errors.go +++ b/errors.go @@ -18,4 +18,6 @@ var ( ErrNotPrivKey = errors.New("not a private key") ErrSignOpNotSupported = errors.New("sign key_op not supported by key") ErrVerifyOpNotSupported = errors.New("verify key_op not supported by key") + ErrEC2NoPub = errors.New("cannot create PrivateKey from EC2 key: missing X or Y") + ErrOKPNoPub = errors.New("cannot create PrivateKey from OKP key: missing X") ) diff --git a/key.go b/key.go index 741eaf3..953736f 100644 --- a/key.go +++ b/key.go @@ -412,48 +412,41 @@ func NewSymmetricKey(k []byte) (*Key, error) { return key, key.Validate() } -// NewKeyFromPublic returns a Key created using the provided crypto.PublicKey -// and Algorithm. -func NewKeyFromPublic(alg Algorithm, pub crypto.PublicKey) (*Key, error) { - switch alg { - case AlgorithmES256, AlgorithmES384, AlgorithmES512: - vk, ok := pub.(*ecdsa.PublicKey) - if !ok { - return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) - } +// NewKeyFromPublic returns a Key created using the provided crypto.PublicKey. +// Supported key formats are: *ecdsa.PublicKey and ed25519.PublicKey +func NewKeyFromPublic(pub crypto.PublicKey) (*Key, error) { + switch vk := pub.(type) { + case *ecdsa.PublicKey: + alg := algorithmFromEllipticCurve(vk.Curve) - return NewEC2Key(alg, vk.X.Bytes(), vk.Y.Bytes(), nil) - case AlgorithmEd25519: - vk, ok := pub.(ed25519.PublicKey) - if !ok { - return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) + if alg == AlgorithmInvalid { + return nil, fmt.Errorf("unsupported curve: %v", vk.Curve) } - return NewOKPKey(alg, []byte(vk), nil) + return NewEC2Key(alg, vk.X.Bytes(), vk.Y.Bytes(), nil) + case ed25519.PublicKey: + return NewOKPKey(AlgorithmEd25519, []byte(vk), nil) default: - return nil, ErrAlgorithmNotSupported + return nil, ErrInvalidPubKey } } -// NewKeyFromPrivate returns a Key created using provided crypto.PrivateKey -// and Algorithm. -func NewKeyFromPrivate(alg Algorithm, priv crypto.PrivateKey) (*Key, error) { - switch alg { - case AlgorithmES256, AlgorithmES384, AlgorithmES512: - sk, ok := priv.(*ecdsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPrivKey) +// NewKeyFromPrivate returns a Key created using provided crypto.PrivateKey. +// Supported key formats are: *ecdsa.PrivateKey and ed25519.PrivateKey +func NewKeyFromPrivate(priv crypto.PrivateKey) (*Key, error) { + switch sk := priv.(type) { + case *ecdsa.PrivateKey: + alg := algorithmFromEllipticCurve(sk.Curve) + + if alg == AlgorithmInvalid { + return nil, fmt.Errorf("unsupported curve: %v", sk.Curve) } return NewEC2Key(alg, sk.X.Bytes(), sk.Y.Bytes(), sk.D.Bytes()) - case AlgorithmEd25519: - sk, ok := priv.(ed25519.PrivateKey) - if !ok { - return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPrivKey) - } - return NewOKPKey(alg, []byte(sk[32:]), []byte(sk[:32])) + case ed25519.PrivateKey: + return NewOKPKey(AlgorithmEd25519, []byte(sk[32:]), []byte(sk[:32])) default: - return nil, ErrAlgorithmNotSupported + return nil, ErrInvalidPrivKey } } @@ -463,23 +456,25 @@ func (k Key) Validate() error { switch k.KeyType { case KeyTypeEC2: switch k.Curve { - case CurveP256, CurveP384, CurveP521: - // ok - default: + case CurveX25519, CurveX448, CurveEd25519, CurveEd448: return fmt.Errorf( - "EC2 curve must be P-256, P-384, or P-521; found %q", + "Key type mismatch for curve %q (must be OKP, found EC2)", k.Curve.String(), ) + default: + // ok -- a key may contain a currently unsupported curve + // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 } case KeyTypeOKP: switch k.Curve { - case CurveX25519, CurveX448, CurveEd25519, CurveEd448: - // ok - default: + case CurveP256, CurveP384, CurveP521: return fmt.Errorf( - "OKP curve must be X25519, X448, Ed25519, or Ed448; found %q", + "Key type mismatch for curve %q (must be EC2, found OKP)", k.Curve.String(), ) + default: + // ok -- a key may contain a currently unsupported curve + // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 } case KeyTypeSymmetric: default: @@ -646,6 +641,13 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { switch alg { case AlgorithmES256, AlgorithmES384, AlgorithmES512: + // RFC8152 allows omitting X and Y from private keys; + // crypto.PrivateKey assumes they are available. + // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 + if len(k.X) == 0 || len(k.Y) == 0 { + return nil, ErrEC2NoPub + } + var curve elliptic.Curve switch alg { @@ -667,6 +669,13 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { return priv, nil case AlgorithmEd25519: + // RFC8152 allows omitting X from private keys; + // crypto.PrivateKey assumes it is available. + // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 + if len(k.X) == 0 { + return nil, ErrOKPNoPub + } + buf := make([]byte, ed25519.PrivateKeySize) copy(buf, k.D) @@ -782,7 +791,7 @@ func (k *Key) Verifier() (Verifier, error) { // must be explicitly set,so that this derivation is not used. func (k *Key) deriveAlgorithm() (Algorithm, error) { switch k.KeyType { - case KeyTypeEC2, KeyTypeOKP: + case KeyTypeEC2: switch k.Curve { case CurveP256: return AlgorithmES256, nil @@ -790,13 +799,33 @@ func (k *Key) deriveAlgorithm() (Algorithm, error) { return AlgorithmES384, nil case CurveP521: return AlgorithmES512, nil + default: + return AlgorithmInvalid, fmt.Errorf( + "unsupported curve %q for key type EC2", k.Curve.String()) + } + case KeyTypeOKP: + switch k.Curve { case CurveEd25519: return AlgorithmEd25519, nil default: - return AlgorithmInvalid, fmt.Errorf("unsupported curve %q", k.Curve.String()) + return AlgorithmInvalid, fmt.Errorf( + "unsupported curve %q for key type OKP", k.Curve.String()) } default: // Symmetric algorithms are not supported in the current inmplementation. return AlgorithmInvalid, fmt.Errorf("unexpected key type %q", k.KeyType.String()) } } + +func algorithmFromEllipticCurve(c elliptic.Curve) Algorithm { + switch c { + case elliptic.P256(): + return AlgorithmES256 + case elliptic.P384(): + return AlgorithmES384 + case elliptic.P521(): + return AlgorithmES512 + default: + return AlgorithmInvalid + } +} diff --git a/key_test.go b/key_test.go index e57db62..5cb1e9f 100644 --- a/key_test.go +++ b/key_test.go @@ -7,6 +7,7 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "math/big" "testing" "github.com/fxamacker/cbor/v2" @@ -89,7 +90,7 @@ func Test_KeyOp(t *testing.T) { data = []byte{0x63, 0x66, 0x6f, 0x6f} err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown key_ops value \"foo\"") + assertEqualError(t, err, `unknown key_ops value "foo"`) data = []byte{0x40} err = ko.UnmarshalCBOR(data) @@ -125,7 +126,7 @@ func Test_KeyType(t *testing.T) { data = []byte{0x63, 0x66, 0x6f, 0x6f} err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown key type value \"foo\"") + assertEqualError(t, err, `unknown key type value "foo"`) data = []byte{0x40} err = ko.UnmarshalCBOR(data) @@ -145,7 +146,7 @@ func Test_Curve(t *testing.T) { data = []byte{0x63, 0x66, 0x6f, 0x6f} err = c.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown curve value \"foo\"") + assertEqualError(t, err, `unknown curve value "foo"`) data = []byte{0x40} err = c.UnmarshalCBOR(data) @@ -230,7 +231,7 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x01, 0x01, // kty: OKP 0x20, 0x01, // curve: CurveP256 }, - WantErr: "OKP curve must be X25519, X448, Ed25519, or Ed448; found \"P-256\"", + WantErr: `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, Validate: nil, }, { @@ -240,7 +241,7 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x01, 0x02, // kty: EC2 0x20, 0x06, // curve: CurveEd25519 }, - WantErr: "EC2 curve must be P-256, P-384, or P-521; found \"Ed25519\"", + WantErr: `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`, Validate: nil, }, { @@ -296,7 +297,7 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - WantErr: "found algorithm \"ES256\" (expected \"EdDSA\")", + WantErr: `found algorithm "ES256" (expected "EdDSA")`, Validate: nil, }, } @@ -384,7 +385,7 @@ func Test_Key_MarshalCBOR(t *testing.T) { k.KeyType = KeyType(42) _, err = k.MarshalCBOR() - wantErr := "invalid key type: \"unknown key type value 42\"" + wantErr := `invalid key type: "unknown key type value 42"` if err == nil || err.Error() != wantErr { t.Errorf("Unexpected error: want %q, got %q", wantErr, err) } @@ -411,10 +412,10 @@ func Test_Key_Create_and_Validate(t *testing.T) { assertEqual(t, x, key.X) _, err = NewOKPKey(AlgorithmES256, x, nil) - assertEqualError(t, err, "unsupported algorithm \"ES256\"") + assertEqualError(t, err, `unsupported algorithm "ES256"`) _, err = NewEC2Key(AlgorithmEd25519, x, y, nil) - assertEqualError(t, err, "unsupported algorithm \"EdDSA\"") + assertEqualError(t, err, `unsupported algorithm "EdDSA"`) key, err = NewEC2Key(AlgorithmES256, x, y, nil) requireNoError(t, err) @@ -430,36 +431,18 @@ func Test_Key_Create_and_Validate(t *testing.T) { err = key.Validate() assertEqualError(t, err, "unknown key type value 7") - _, err = NewKeyFromPublic(AlgorithmES256, - crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "ES256: invalid public key") + _, err = NewKeyFromPublic(crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "invalid public key") - _, err = NewKeyFromPublic(AlgorithmEd25519, - crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "EdDSA: invalid public key") - - _, err = NewKeyFromPublic(AlgorithmInvalid, - crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "algorithm not supported") - - _, err = NewKeyFromPrivate(AlgorithmES256, - crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "ES256: invalid private key") - - _, err = NewKeyFromPrivate(AlgorithmEd25519, - crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "EdDSA: invalid private key") - - _, err = NewKeyFromPrivate(AlgorithmInvalid, - crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "algorithm not supported") + _, err = NewKeyFromPrivate(crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) + assertEqualError(t, err, "invalid private key") } func Test_Key_ed25519_signature_round_trip(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) requireNoError(t, err) - key, err := NewKeyFromPrivate(AlgorithmEd25519, priv) + key, err := NewKeyFromPrivate(priv) requireNoError(t, err) assertEqual(t, AlgorithmEd25519, key.Algorithm) assertEqual(t, CurveEd25519, key.Curve) @@ -473,7 +456,7 @@ func Test_Key_ed25519_signature_round_trip(t *testing.T) { sig, err := signer.Sign(rand.Reader, message) requireNoError(t, err) - key, err = NewKeyFromPublic(AlgorithmEd25519, pub) + key, err = NewKeyFromPublic(pub) requireNoError(t, err) assertEqual(t, AlgorithmEd25519, key.Algorithm) @@ -501,9 +484,8 @@ func Test_Key_ecdsa_signature_round_trip(t *testing.T) { priv, err := ecdsa.GenerateKey(tv.EC, rand.Reader) requireNoError(t, err) - key, err := NewKeyFromPrivate(tv.Algorithm, priv) + key, err := NewKeyFromPrivate(priv) requireNoError(t, err) - assertEqual(t, tv.Algorithm, key.Algorithm) assertEqual(t, tv.Curve, key.Curve) assertEqual(t, priv.X.Bytes(), key.X) assertEqual(t, priv.Y.Bytes(), key.Y) @@ -518,10 +500,9 @@ func Test_Key_ecdsa_signature_round_trip(t *testing.T) { pub := priv.Public() - key, err = NewKeyFromPublic(tv.Algorithm, pub) + key, err = NewKeyFromPublic(pub) requireNoError(t, err) - assertEqual(t, tv.Algorithm, key.Algorithm) assertEqual(t, tv.Curve, key.Curve) assertEqual(t, priv.X.Bytes(), key.X) assertEqual(t, priv.Y.Bytes(), key.Y) @@ -537,12 +518,28 @@ func Test_Key_ecdsa_signature_round_trip(t *testing.T) { func Test_Key_derive_algorithm(t *testing.T) { k := Key{ + KeyType: KeyTypeEC2, + Curve: CurveEd25519, + } + + _, err := k.AlgorithmOrDefault() + assertEqualError(t, err, `unsupported curve "Ed25519" for key type EC2`) + + k = Key{ + KeyType: KeyTypeOKP, + Curve: CurveP256, + } + + _, err = k.AlgorithmOrDefault() + assertEqualError(t, err, `unsupported curve "P-256" for key type OKP`) + + k = Key{ KeyType: KeyTypeOKP, Curve: CurveX448, } - _, err := k.AlgorithmOrDefault() - assertEqualError(t, err, "unsupported curve \"X448\"") + _, err = k.AlgorithmOrDefault() + assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) k = Key{ KeyType: KeyTypeOKP, @@ -554,26 +551,41 @@ func Test_Key_derive_algorithm(t *testing.T) { assertEqual(t, AlgorithmEd25519, alg) } +func Test_NewKeyFrom(t *testing.T) { + pub := ecdsa.PublicKey{Curve: *new(elliptic.Curve), X: new(big.Int), Y: new(big.Int)} + _, err := NewKeyFromPublic(&pub) + assertEqualError(t, err, "unsupported curve: ") + + priv := ecdsa.PrivateKey{PublicKey: pub, D: new(big.Int)} + _, err = NewKeyFromPrivate(&priv) + assertEqualError(t, err, "unsupported curve: ") +} + +func Test_algorithmFromEllipticCurve(t *testing.T) { + alg := algorithmFromEllipticCurve(*new(elliptic.Curve)) + assertEqual(t, alg, AlgorithmInvalid) +} + func Test_Key_signer_validation(t *testing.T) { pub, priv, err := ed25519.GenerateKey(rand.Reader) requireNoError(t, err) - key, err := NewKeyFromPublic(AlgorithmEd25519, pub) + key, err := NewKeyFromPublic(pub) requireNoError(t, err) _, err = key.Signer() assertEqualError(t, err, ErrNotPrivKey.Error()) - key, err = NewKeyFromPrivate(AlgorithmEd25519, priv) + key, err = NewKeyFromPrivate(priv) requireNoError(t, err) key.KeyType = KeyTypeEC2 _, err = key.Signer() - assertEqualError(t, err, "EC2 curve must be P-256, P-384, or P-521; found \"Ed25519\"") + assertEqualError(t, err, `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`) key.Curve = CurveP256 _, err = key.Signer() - assertEqualError(t, err, "found algorithm \"EdDSA\" (expected \"ES256\")") + assertEqualError(t, err, `found algorithm "EdDSA" (expected "ES256")`) key.KeyType = KeyTypeOKP key.Algorithm = AlgorithmEd25519 @@ -588,18 +600,18 @@ func Test_Key_signer_validation(t *testing.T) { key.Algorithm = AlgorithmES256 _, err = key.Signer() - assertEqualError(t, err, "found algorithm \"ES256\" (expected \"EdDSA\")") + assertEqualError(t, err, `found algorithm "ES256" (expected "EdDSA")`) key.Curve = CurveX448 _, err = key.Signer() - assertEqualError(t, err, "unsupported curve \"X448\"") + assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) } func Test_Key_verifier_validation(t *testing.T) { pub, _, err := ed25519.GenerateKey(rand.Reader) requireNoError(t, err) - key, err := NewKeyFromPublic(AlgorithmEd25519, pub) + key, err := NewKeyFromPublic(pub) requireNoError(t, err) _, err = key.Verifier() @@ -607,7 +619,7 @@ func Test_Key_verifier_validation(t *testing.T) { key.KeyType = KeyTypeEC2 _, err = key.Verifier() - assertEqualError(t, err, "EC2 curve must be P-256, P-384, or P-521; found \"Ed25519\"") + assertEqualError(t, err, `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`) key.KeyType = KeyTypeOKP key.KeyOps = []KeyOp{} @@ -625,9 +637,9 @@ func Test_Key_crypto_keys(t *testing.T) { } _, err := k.PublicKey() - assertEqualError(t, err, "unexpected key type \"unknown key type value 7\"") + assertEqualError(t, err, `unexpected key type "unknown key type value 7"`) _, err = k.PrivateKey() - assertEqualError(t, err, "unexpected key type \"unknown key type value 7\"") + assertEqualError(t, err, `unexpected key type "unknown key type value 7"`) k = Key{ KeyType: KeyTypeOKP, @@ -635,7 +647,30 @@ func Test_Key_crypto_keys(t *testing.T) { } _, err = k.PublicKey() - assertEqualError(t, err, "unsupported curve \"X448\"") + assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) + _, err = k.PrivateKey() + assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) + + k = Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + D: []byte{0xde, 0xad, 0xbe, 0xef}, + } + + _, err = k.PrivateKey() + assertEqualError(t, err, ErrOKPNoPub.Error()) + + k.KeyType = KeyTypeEC2 + k.Curve = CurveP256 + _, err = k.PrivateKey() - assertEqualError(t, err, "unsupported curve \"X448\"") + assertEqualError(t, err, ErrEC2NoPub.Error()) +} + +func Test_String(t *testing.T) { + // test string conversions not exercised by other test cases + assertEqual(t, "OKP", KeyTypeOKP.String()) + assertEqual(t, "EC2", KeyTypeEC2.String()) + assertEqual(t, "X25519", CurveX25519.String()) + assertEqual(t, "Ed448", CurveEd448.String()) } From 2f778dada361a2e8355adb10f052cc9d8b716f92 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Wed, 5 Jul 2023 13:59:38 +0200 Subject: [PATCH 05/25] Remove unnecessary cbor marshal methods (#155) Signed-off-by: setrofim Signed-off-by: qmuntal Co-authored-by: setrofim --- algorithm.go | 35 ------------- algorithm_test.go | 17 ------- common_test.go | 3 ++ headers.go | 9 +--- key.go | 126 +++------------------------------------------- key_test.go | 58 +-------------------- 6 files changed, 13 insertions(+), 235 deletions(-) diff --git a/algorithm.go b/algorithm.go index 7e68535..9e2f7d9 100644 --- a/algorithm.go +++ b/algorithm.go @@ -2,7 +2,6 @@ package cose import ( "crypto" - "fmt" "strconv" ) @@ -75,35 +74,6 @@ func (a Algorithm) String() string { } } -// MarshalCBOR marshals the Algorithm as a CBOR int. -func (a Algorithm) MarshalCBOR() ([]byte, error) { - return encMode.Marshal(int64(a)) -} - -// UnmarshalCBOR populates the Algorithm from the provided CBOR value (must be -// int or tstr). -func (a *Algorithm) UnmarshalCBOR(data []byte) error { - var raw intOrStr - - if err := raw.UnmarshalCBOR(data); err != nil { - return fmt.Errorf("invalid algorithm value: %w", err) - } - - if raw.IsString() { - v := algorithmFromString(raw.String()) - if v == AlgorithmInvalid { - return fmt.Errorf("unknown algorithm value %q", raw.String()) - } - - *a = v - } else { - v := raw.Int() - *a = Algorithm(v) - } - - return nil -} - // hashFunc returns the hash associated with the algorithm supported by this // library. func (a Algorithm) hashFunc() crypto.Hash { @@ -135,8 +105,3 @@ func computeHash(h crypto.Hash, data []byte) ([]byte, error) { } return hh.Sum(nil), nil } - -// NOTE: there are currently no registered string values for an algorithm. -func algorithmFromString(v string) Algorithm { - return AlgorithmInvalid -} diff --git a/algorithm_test.go b/algorithm_test.go index bd92a8d..b839f2e 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -31,23 +31,6 @@ func TestAlgorithm_String(t *testing.T) { } } -func TestAlgorithm_CBOR(t *testing.T) { - tvs2 := []struct { - Data []byte - ExpectedError string - }{ - {[]byte{0x63, 0x66, 0x6f, 0x6f}, "unknown algorithm value \"foo\""}, - {[]byte{0x40}, "invalid algorithm value: must be int or string, found []uint8"}, - } - - for _, tv := range tvs2 { - var a Algorithm - - err := a.UnmarshalCBOR(tv.Data) - assertEqualError(t, err, tv.ExpectedError) - } -} - func TestAlgorithm_computeHash(t *testing.T) { // run tests data := []byte("hello world") diff --git a/common_test.go b/common_test.go index 5f2c8d5..ed981b7 100644 --- a/common_test.go +++ b/common_test.go @@ -81,6 +81,7 @@ func Test_intOrStr_CBOR(t *testing.T) { } func requireNoError(t *testing.T, err error) { + t.Helper() if err != nil { t.Errorf("Unexpected error: %q", err) t.Fail() @@ -88,12 +89,14 @@ func requireNoError(t *testing.T, err error) { } func assertEqualError(t *testing.T, err error, expected string) { + t.Helper() if err == nil || err.Error() != expected { t.Errorf("Unexpected error: want %q, got %q", expected, err) } } func assertEqual(t *testing.T, expected, actual interface{}) { + t.Helper() if !objectsAreEqualValues(expected, actual) { t.Errorf("Unexpected value: want %v, got %v", expected, actual) } diff --git a/headers.go b/headers.go index 4218eee..391d217 100644 --- a/headers.go +++ b/headers.go @@ -119,14 +119,7 @@ func (h ProtectedHeader) Algorithm() (Algorithm, error) { case int64: return Algorithm(alg), nil case string: - v := algorithmFromString(alg) - - var err error - if v == AlgorithmInvalid { - err = fmt.Errorf("unknown algorithm value %q", alg) - } - - return v, err + return AlgorithmInvalid, fmt.Errorf("unknown algorithm value %q", alg) default: return AlgorithmInvalid, ErrInvalidAlgorithm } diff --git a/key.go b/key.go index 953736f..fd1a074 100644 --- a/key.go +++ b/key.go @@ -110,13 +110,6 @@ func (ko KeyOp) String() string { } } -// IsSupported returnns true if the specified value is represents one of the -// key_ops defined in -// https://www.rfc-editor.org/rfc/rfc9052.html#name-cose-key-common-parameters -func (ko KeyOp) IsSupported() bool { - return ko >= 1 && ko <= 10 -} - // MarshalCBOR marshals the KeyOp as a CBOR int. func (ko KeyOp) MarshalCBOR() ([]byte, error) { return encMode.Marshal(int64(ko)) @@ -141,10 +134,6 @@ func (ko *KeyOp) UnmarshalCBOR(data []byte) error { } else { v := raw.Int() *ko = KeyOp(v) - - if !ko.IsSupported() { - return fmt.Errorf("unknown key_ops value %d", v) - } } return nil @@ -182,51 +171,6 @@ func (kt KeyType) String() string { } } -// MarshalCBOR marshals the KeyType as a CBOR int. -func (kt KeyType) MarshalCBOR() ([]byte, error) { - return encMode.Marshal(int(kt)) -} - -// UnmarshalCBOR populates the KeyType from the provided CBOR value (must be -// int or tstr). -func (kt *KeyType) UnmarshalCBOR(data []byte) error { - var raw intOrStr - - if err := raw.UnmarshalCBOR(data); err != nil { - return fmt.Errorf("invalid key type value: %w", err) - } - - if raw.IsString() { - v, err := keyTypeFromString(raw.String()) - - if err != nil { - return err - } - - *kt = v - } else { - v := raw.Int() - - if v == 0 { - // 0 is reserved, and so can never be valid - return fmt.Errorf("invalid key type value 0") - } - - if v > 4 || v < 0 || v == 3 { - return fmt.Errorf("unknown key type value %d", v) - } - - *kt = KeyType(v) - } - - return nil -} - -// NOTE: there are currently no registered string key type values. -func keyTypeFromString(v string) (KeyType, error) { - return KeyTypeInvalid, fmt.Errorf("unknown key type value %q", v) -} - const ( // Invalid/unrecognised curve @@ -282,46 +226,6 @@ func (c Curve) String() string { } } -// MarshalCBOR marshals the KeyType as a CBOR int. -func (c Curve) MarshalCBOR() ([]byte, error) { - return encMode.Marshal(int(c)) -} - -// UnmarshalCBOR populates the KeyType from the provided CBOR value (must be -// int or tstr). -func (c *Curve) UnmarshalCBOR(data []byte) error { - var raw intOrStr - - if err := raw.UnmarshalCBOR(data); err != nil { - return fmt.Errorf("invalid curve value: %w", err) - } - - if raw.IsString() { - v, err := curveFromString(raw.String()) - - if err != nil { - return err - } - - *c = v - } else { - v := raw.Int() - - if v < 1 || v > 7 { - return fmt.Errorf("unknown curve value %d", v) - } - - *c = Curve(v) - } - - return nil -} - -// NOTE: there are currently no registered string values for curves. -func curveFromString(v string) (Curve, error) { - return CurveInvalid, fmt.Errorf("unknown curve value %q", v) -} - // Key represents a COSE_Key structure, as defined by RFC8152. // Note: currently, this does NOT support RFC8230 (RSA algorithms). type Key struct { @@ -333,15 +237,15 @@ type Key struct { KeyType KeyType `cbor:"1,keyasint"` // KeyID is the identification value matched to the kid in the message. KeyID []byte `cbor:"2,keyasint,omitempty"` + // Algorithm is used to restrict the algorithm that is used with the + // key. If it is set, the application MUST verify that it matches the + // algorithm for which the Key is being used. + Algorithm Algorithm `cbor:"3,keyasint,omitempty"` // KeyOps can be set to restrict the set of operations that the Key is used for. KeyOps []KeyOp `cbor:"4,keyasint,omitempty"` // BaseIV is the Base IV to be xor-ed with Partial IVs. BaseIV []byte `cbor:"5,keyasint,omitempty"` - // Algorithm is used to restrict the algorithm that is used with the - // key. If it is set, the application MUST verify that it matches the - // algorithm for which the Key is being used. - Algorithm Algorithm `cbor:"-"` // Curve is EC identifier -- taken form "COSE Elliptic Curves" IANA registry. // Populated from keyStruct.RawKeyParam when key type is EC2 or OKP. Curve Curve `cbor:"-"` @@ -505,11 +409,6 @@ type keyalias Key type marshaledKey struct { keyalias - // RawAlgorithm contains the raw Algorithm value, this is necessary - // because cbor library ignores omitempty on types that implement the - // cbor.Marshaler interface. - RawAlgorithm cbor.RawMessage `cbor:"3,keyasint,omitempty"` - // RawKeyParam contains the raw CBOR encoded data for the label -1. // Depending on the KeyType this is used to populate either Curve or K // below. @@ -535,13 +434,6 @@ func (k *Key) MarshalCBOR() ([]byte, error) { default: return nil, fmt.Errorf("invalid key type: %q", k.KeyType.String()) } - - if k.Algorithm != AlgorithmInvalid { - if tmp.RawAlgorithm, err = encMode.Marshal(k.Algorithm); err != nil { - return nil, err - } - } - return encMode.Marshal(tmp) } @@ -552,14 +444,12 @@ func (k *Key) UnmarshalCBOR(data []byte) error { if err := decMode.Unmarshal(data, &tmp); err != nil { return err } - *k = Key(tmp.keyalias) - - if tmp.RawAlgorithm != nil { - if err := decMode.Unmarshal(tmp.RawAlgorithm, &k.Algorithm); err != nil { - return err - } + if tmp.KeyType == KeyTypeInvalid { + return errors.New("invalid key type value 0") } + *k = Key(tmp.keyalias) + switch k.KeyType { case KeyTypeEC2: if tmp.RawKeyParam == nil { diff --git a/key_test.go b/key_test.go index 5cb1e9f..0c7b236 100644 --- a/key_test.go +++ b/key_test.go @@ -80,16 +80,8 @@ func Test_KeyOp(t *testing.T) { var ko KeyOp - data := []byte{0x20} + data := []byte{0x63, 0x66, 0x6f, 0x6f} err := ko.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown key_ops value -1") - - data = []byte{0x18, 0xff} - err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown key_ops value 255") - - data = []byte{0x63, 0x66, 0x6f, 0x6f} - err = ko.UnmarshalCBOR(data) assertEqualError(t, err, `unknown key_ops value "foo"`) data = []byte{0x40} @@ -109,54 +101,6 @@ func Test_KeyOp(t *testing.T) { } } -func Test_KeyType(t *testing.T) { - var ko KeyType - - data := []byte{0x20} - err := ko.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown key type value -1") - - data = []byte{0x00} - err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "invalid key type value 0") - - data = []byte{0x03} - err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown key type value 3") - - data = []byte{0x63, 0x66, 0x6f, 0x6f} - err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, `unknown key type value "foo"`) - - data = []byte{0x40} - err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "invalid key type value: must be int or string, found []uint8") -} - -func Test_Curve(t *testing.T) { - var c Curve - - data := []byte{0x20} - err := c.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown curve value -1") - - data = []byte{0x00} - err = c.UnmarshalCBOR(data) - assertEqualError(t, err, "unknown curve value 0") - - data = []byte{0x63, 0x66, 0x6f, 0x6f} - err = c.UnmarshalCBOR(data) - assertEqualError(t, err, `unknown curve value "foo"`) - - data = []byte{0x40} - err = c.UnmarshalCBOR(data) - assertEqualError(t, err, "invalid curve value: must be int or string, found []uint8") - - if "unknown curve value 42" != Curve(42).String() { - t.Errorf("Unexpected string value %q", Curve(42).String()) - } -} - func Test_Key_UnmarshalCBOR(t *testing.T) { tvs := []struct { Name string From d16fe134744d0801ded952847bc911be679e70fd Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Mon, 10 Jul 2023 12:56:56 +0200 Subject: [PATCH 06/25] Centralize key validations and check more invalid cases (#156) Signed-off-by: qmuntal --- errors.go | 8 +- key.go | 125 ++++++++++--------- key_test.go | 342 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 362 insertions(+), 113 deletions(-) diff --git a/errors.go b/errors.go index de601e5..770dc9e 100644 --- a/errors.go +++ b/errors.go @@ -13,11 +13,11 @@ var ( ErrNoSignatures = errors.New("no signatures attached") ErrUnavailableHashFunc = errors.New("hash function is not available") ErrVerification = errors.New("verification error") + ErrInvalidKey = errors.New("invalid key") ErrInvalidPubKey = errors.New("invalid public key") ErrInvalidPrivKey = errors.New("invalid private key") ErrNotPrivKey = errors.New("not a private key") - ErrSignOpNotSupported = errors.New("sign key_op not supported by key") - ErrVerifyOpNotSupported = errors.New("verify key_op not supported by key") - ErrEC2NoPub = errors.New("cannot create PrivateKey from EC2 key: missing X or Y") - ErrOKPNoPub = errors.New("cannot create PrivateKey from OKP key: missing X") + ErrOpNotSupported = errors.New("key_op not supported by key") + ErrEC2NoPub = errors.New("cannot create PrivateKey from EC2 key: missing x or y") + ErrOKPNoPub = errors.New("cannot create PrivateKey from OKP key: missing x") ) diff --git a/key.go b/key.go index fd1a074..504b14f 100644 --- a/key.go +++ b/key.go @@ -276,7 +276,7 @@ func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { X: x, D: d, } - return key, key.Validate() + return key, key.validate(KeyOpInvalid) } // NewEC2Key returns a Key created using the provided elliptic curve key @@ -303,17 +303,17 @@ func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { Y: y, D: d, } - return key, key.Validate() + return key, key.validate(KeyOpInvalid) } // NewSymmetricKey returns a Key created using the provided Symmetric key // bytes. -func NewSymmetricKey(k []byte) (*Key, error) { +func NewSymmetricKey(k []byte) *Key { key := &Key{ KeyType: KeyTypeSymmetric, K: k, } - return key, key.Validate() + return key } // NewKeyFromPublic returns a Key created using the provided crypto.PublicKey. @@ -355,10 +355,24 @@ func NewKeyFromPrivate(priv crypto.PrivateKey) (*Key, error) { } // Validate ensures that the parameters set inside the Key are internally -// consistent (e.g., that the key type is appropriate to the curve.) -func (k Key) Validate() error { +// consistent (e.g., that the key type is appropriate to the curve). +// It also checks that the key is valid for the requested operation. +func (k Key) validate(op KeyOp) error { switch k.KeyType { case KeyTypeEC2: + switch op { + case KeyOpVerify: + if len(k.X) == 0 || len(k.Y) == 0 { + return ErrEC2NoPub + } + case KeyOpSign: + if len(k.D) == 0 { + return ErrNotPrivKey + } + } + if k.Curve == CurveInvalid || (len(k.X) == 0 && len(k.Y) == 0 && len(k.D) == 0) { + return ErrInvalidKey + } switch k.Curve { case CurveX25519, CurveX448, CurveEd25519, CurveEd448: return fmt.Errorf( @@ -370,6 +384,19 @@ func (k Key) Validate() error { // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 } case KeyTypeOKP: + switch op { + case KeyOpVerify: + if len(k.X) == 0 { + return ErrOKPNoPub + } + case KeyOpSign: + if len(k.D) == 0 { + return ErrNotPrivKey + } + } + if k.Curve == CurveInvalid || (len(k.X) == 0 && len(k.D) == 0) { + return ErrInvalidKey + } switch k.Curve { case CurveP256, CurveP384, CurveP521: return fmt.Errorf( @@ -381,8 +408,9 @@ func (k Key) Validate() error { // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 } case KeyTypeSymmetric: + // Nothing to validate default: - return errors.New(k.KeyType.String()) + // Unknown key type, we can't validate custom parameters. } // If Algorithm is set, it must match the specified key parameters. @@ -404,6 +432,18 @@ func (k Key) Validate() error { return nil } +func (k Key) canOp(op KeyOp) bool { + if k.KeyOps == nil { + return true + } + for _, kop := range k.KeyOps { + if kop == op { + return true + } + } + return false +} + type keyalias Key type marshaledKey struct { @@ -483,11 +523,14 @@ func (k *Key) UnmarshalCBOR(data []byte) error { return fmt.Errorf("unexpected key type %q", k.KeyType.String()) } - return k.Validate() + return k.validate(KeyOpInvalid) } // PublicKey returns a crypto.PublicKey generated using Key's parameters. func (k *Key) PublicKey() (crypto.PublicKey, error) { + if err := k.validate(KeyOpVerify); err != nil { + return nil, err + } alg, err := k.deriveAlgorithm() if err != nil { return nil, err @@ -520,15 +563,14 @@ func (k *Key) PublicKey() (crypto.PublicKey, error) { // PrivateKey returns a crypto.PrivateKey generated using Key's parameters. func (k *Key) PrivateKey() (crypto.PrivateKey, error) { + if err := k.validate(KeyOpSign); err != nil { + return nil, err + } alg, err := k.deriveAlgorithm() if err != nil { return nil, err } - if len(k.D) == 0 { - return nil, ErrNotPrivKey - } - switch alg { case AlgorithmES256, AlgorithmES384, AlgorithmES512: // RFC8152 allows omitting X and Y from private keys; @@ -591,25 +633,9 @@ func (k *Key) AlgorithmOrDefault() (Algorithm, error) { // Signer returns a Signer created using Key. func (k *Key) Signer() (Signer, error) { - if err := k.Validate(); err != nil { - return nil, err - } - - if k.KeyOps != nil { - signFound := false - - for _, kop := range k.KeyOps { - if kop == KeyOpSign { - signFound = true - break - } - } - - if !signFound { - return nil, ErrSignOpNotSupported - } + if !k.canOp(KeyOpSign) { + return nil, ErrOpNotSupported } - priv, err := k.PrivateKey() if err != nil { return nil, err @@ -620,22 +646,9 @@ func (k *Key) Signer() (Signer, error) { return nil, err } - var signer crypto.Signer - var ok bool - - switch alg { - case AlgorithmES256, AlgorithmES384, AlgorithmES512: - signer, ok = priv.(*ecdsa.PrivateKey) - if !ok { - return nil, ErrInvalidPrivKey - } - case AlgorithmEd25519: - signer, ok = priv.(ed25519.PrivateKey) - if !ok { - return nil, ErrInvalidPrivKey - } - default: - return nil, ErrAlgorithmNotSupported + signer, ok := priv.(crypto.Signer) + if !ok { + return nil, ErrInvalidPrivKey } return NewSigner(alg, signer) @@ -643,25 +656,9 @@ func (k *Key) Signer() (Signer, error) { // Verifier returns a Verifier created using Key. func (k *Key) Verifier() (Verifier, error) { - if err := k.Validate(); err != nil { - return nil, err + if !k.canOp(KeyOpVerify) { + return nil, ErrOpNotSupported } - - if k.KeyOps != nil { - verifyFound := false - - for _, kop := range k.KeyOps { - if kop == KeyOpVerify { - verifyFound = true - break - } - } - - if !verifyFound { - return nil, ErrVerifyOpNotSupported - } - } - pub, err := k.PublicKey() if err != nil { return nil, err diff --git a/key_test.go b/key_test.go index 0c7b236..bc549f6 100644 --- a/key_test.go +++ b/key_test.go @@ -7,7 +7,9 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "errors" "math/big" + "reflect" "testing" "github.com/fxamacker/cbor/v2" @@ -88,15 +90,15 @@ func Test_KeyOp(t *testing.T) { err = ko.UnmarshalCBOR(data) assertEqualError(t, err, "invalid key_ops value must be int or string, found []uint8") - if "MAC create" != KeyOpMACCreate.String() { + if KeyOpMACCreate.String() != "MAC create" { t.Errorf("Unexpected value: %q", KeyOpMACCreate.String()) } - if "MAC verify" != KeyOpMACVerify.String() { + if KeyOpMACVerify.String() != "MAC verify" { t.Errorf("Unexpected value: %q", KeyOpMACVerify.String()) } - if "unknown key_op value 42" != KeyOp(42).String() { + if KeyOp(42).String() != "unknown key_op value 42" { t.Errorf("Unexpected value: %q", KeyOp(42).String()) } } @@ -171,9 +173,14 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { { Name: "invalid curve OKP", Value: []byte{ - 0xa2, // map (2) + 0xa3, // map (3) 0x01, 0x01, // kty: OKP 0x20, 0x01, // curve: CurveP256 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, WantErr: `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, Validate: nil, @@ -181,9 +188,19 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { { Name: "invalid curve EC2", Value: []byte{ - 0xa2, // map (2) + 0xa4, // map (4) 0x01, 0x02, // kty: EC2 0x20, 0x06, // curve: CurveEd25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + 0x22, 0x58, 0x20, // y-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, WantErr: `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`, Validate: nil, @@ -367,14 +384,9 @@ func Test_Key_Create_and_Validate(t *testing.T) { assertEqual(t, x, key.X) assertEqual(t, y, key.Y) - key, err = NewSymmetricKey(x) - requireNoError(t, err) + key = NewSymmetricKey(x) assertEqual(t, x, key.K) - key.KeyType = KeyType(7) - err = key.Validate() - assertEqualError(t, err, "unknown key type value 7") - _, err = NewKeyFromPublic(crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) assertEqualError(t, err, "invalid public key") @@ -536,7 +548,7 @@ func Test_Key_signer_validation(t *testing.T) { key.Curve = CurveEd25519 key.KeyOps = []KeyOp{} _, err = key.Signer() - assertEqualError(t, err, ErrSignOpNotSupported.Error()) + assertEqualError(t, err, ErrOpNotSupported.Error()) key.KeyOps = []KeyOp{KeyOpSign} _, err = key.Signer() @@ -551,7 +563,7 @@ func Test_Key_signer_validation(t *testing.T) { assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) } -func Test_Key_verifier_validation(t *testing.T) { +func TestKey_Verifier(t *testing.T) { pub, _, err := ed25519.GenerateKey(rand.Reader) requireNoError(t, err) @@ -563,52 +575,292 @@ func Test_Key_verifier_validation(t *testing.T) { key.KeyType = KeyTypeEC2 _, err = key.Verifier() - assertEqualError(t, err, `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`) + assertEqualError(t, err, ErrEC2NoPub.Error()) key.KeyType = KeyTypeOKP key.KeyOps = []KeyOp{} _, err = key.Verifier() - assertEqualError(t, err, ErrVerifyOpNotSupported.Error()) + assertEqualError(t, err, ErrOpNotSupported.Error()) key.KeyOps = []KeyOp{KeyOpVerify} _, err = key.Verifier() requireNoError(t, err) } -func Test_Key_crypto_keys(t *testing.T) { - k := Key{ - KeyType: KeyType(7), +func TestKey_PrivateKey(t *testing.T) { + x := []byte{0xde, 0xad, 0xbe, 0xef} + y := []byte{0xef, 0xbe, 0xad, 0xde} + d := []byte{0xad, 0xde, 0xef, 0xbe} + tests := []struct { + name string + k *Key + want crypto.PrivateKey + wantErr error + }{ + { + "CurveEd25519", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + X: x, + D: d, + }, + ed25519.PrivateKey{ + d[0], d[1], d[2], d[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + x[0], x[1], x[2], x[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + nil, + }, { + "CurveP256", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: x, + Y: y, + D: d, + }, + &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }, + D: new(big.Int).SetBytes(d), + }, + nil, + }, { + "CurveP384", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP384, + X: x, + Y: y, + D: d, + }, + &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P384(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }, + D: new(big.Int).SetBytes(d), + }, + nil, + }, { + "CurveP521", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP521, + X: x, + Y: y, + D: d, + }, + &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P521(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }, + D: new(big.Int).SetBytes(d), + }, + nil, + }, { + "unknown key type", &Key{ + KeyType: KeyType(7), + }, + nil, + errors.New(`unexpected key type "unknown key type value 7"`), + }, { + "OKP missing X", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + D: d, + }, + nil, + ErrOKPNoPub, + }, { + "OKP missing D", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + X: x, + }, + nil, + ErrNotPrivKey, + }, { + "OKP unknown curve", &Key{ + KeyType: KeyTypeOKP, + Curve: 70, + X: x, + D: d, + }, + nil, + errors.New(`unsupported curve "unknown curve value 70" for key type OKP`), + }, { + "EC2 missing X", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + Y: y, + D: d, + }, + nil, + ErrEC2NoPub, + }, { + "EC2 missing Y", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: x, + D: d, + }, + nil, + ErrEC2NoPub, + }, { + "EC2 missing D", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: x, + Y: y, + }, + nil, + ErrNotPrivKey, + }, { + "EC2 unknown curve", &Key{ + KeyType: KeyTypeEC2, + Curve: 70, + X: x, + Y: y, + D: d, + }, + nil, + errors.New(`unsupported curve "unknown curve value 70" for key type EC2`), + }, } - - _, err := k.PublicKey() - assertEqualError(t, err, `unexpected key type "unknown key type value 7"`) - _, err = k.PrivateKey() - assertEqualError(t, err, `unexpected key type "unknown key type value 7"`) - - k = Key{ - KeyType: KeyTypeOKP, - Curve: CurveX448, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.k.PrivateKey() + if err != nil && err.Error() != tt.wantErr.Error() { + t.Errorf("Key.PrivateKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Key.PrivateKey() = %v, want %v", got, tt.want) + } + }) } +} - _, err = k.PublicKey() - assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) - _, err = k.PrivateKey() - assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) - - k = Key{ - KeyType: KeyTypeOKP, - Curve: CurveEd25519, - D: []byte{0xde, 0xad, 0xbe, 0xef}, +func TestKey_PublicKey(t *testing.T) { + x := []byte{0xde, 0xad, 0xbe, 0xef} + y := []byte{0xef, 0xbe, 0xad, 0xde} + tests := []struct { + name string + k *Key + want crypto.PublicKey + wantErr error + }{ + { + "CurveEd25519", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + X: x, + }, + ed25519.PublicKey(x), + nil, + }, { + "CurveP256", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: x, + Y: y, + }, + &ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }, + nil, + }, { + "CurveP384", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP384, + X: x, + Y: y, + }, + &ecdsa.PublicKey{ + Curve: elliptic.P384(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }, + nil, + }, { + "CurveP521", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP521, + X: x, + Y: y, + }, + &ecdsa.PublicKey{ + Curve: elliptic.P521(), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + }, + nil, + }, { + "unknown key type", &Key{ + KeyType: KeyType(7), + }, + nil, + errors.New(`unexpected key type "unknown key type value 7"`), + }, { + "OKP missing X", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + }, + nil, + ErrOKPNoPub, + }, { + "OKP unknown curve", &Key{ + KeyType: KeyTypeOKP, + Curve: 70, + X: x, + Y: y, + }, + nil, + errors.New(`unsupported curve "unknown curve value 70" for key type OKP`), + }, { + "EC2 missing X", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + Y: y, + }, + nil, + ErrEC2NoPub, + }, { + "EC2 missing Y", &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: x, + }, + nil, + ErrEC2NoPub, + }, { + "EC2 unknown curve", &Key{ + KeyType: KeyTypeEC2, + Curve: 70, + X: x, + Y: y, + }, + nil, + errors.New(`unsupported curve "unknown curve value 70" for key type EC2`), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.k.PublicKey() + if err != nil && err.Error() != tt.wantErr.Error() { + t.Errorf("Key.PublicKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Key.PublicKey() = %v, want %v", got, tt.want) + } + }) } - - _, err = k.PrivateKey() - assertEqualError(t, err, ErrOKPNoPub.Error()) - - k.KeyType = KeyTypeEC2 - k.Curve = CurveP256 - - _, err = k.PrivateKey() - assertEqualError(t, err, ErrEC2NoPub.Error()) } func Test_String(t *testing.T) { From c862f87ff06b85f22fc4866ecc13febc44c403c2 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Mon, 10 Jul 2023 17:50:51 +0200 Subject: [PATCH 07/25] Convert key tests to tabular tests (#157) Signed-off-by: qmuntal --- key.go | 13 +- key_test.go | 1070 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 711 insertions(+), 372 deletions(-) diff --git a/key.go b/key.go index 504b14f..80a22d8 100644 --- a/key.go +++ b/key.go @@ -276,7 +276,10 @@ func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { X: x, D: d, } - return key, key.validate(KeyOpInvalid) + if err := key.validate(KeyOpInvalid); err != nil { + return nil, err + } + return key, nil } // NewEC2Key returns a Key created using the provided elliptic curve key @@ -303,17 +306,19 @@ func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { Y: y, D: d, } - return key, key.validate(KeyOpInvalid) + if err := key.validate(KeyOpInvalid); err != nil { + return nil, err + } + return key, nil } // NewSymmetricKey returns a Key created using the provided Symmetric key // bytes. func NewSymmetricKey(k []byte) *Key { - key := &Key{ + return &Key{ KeyType: KeyTypeSymmetric, K: k, } - return key } // NewKeyFromPublic returns a Key created using the provided crypto.PublicKey. diff --git a/key_test.go b/key_test.go index bc549f6..aa60f64 100644 --- a/key_test.go +++ b/key_test.go @@ -1,13 +1,11 @@ package cose import ( - "bytes" "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" - "errors" "math/big" "reflect" "testing" @@ -103,16 +101,16 @@ func Test_KeyOp(t *testing.T) { } } -func Test_Key_UnmarshalCBOR(t *testing.T) { - tvs := []struct { - Name string - Value []byte - WantErr string - Validate func(k *Key) +func TestKey_UnmarshalCBOR(t *testing.T) { + tests := []struct { + name string + data []byte + want *Key + wantErr string }{ { - Name: "ok OKP", - Value: []byte{ + name: "ok OKP", + data: []byte{ 0xa5, // map (5) 0x01, 0x01, // kty: OKP 0x03, 0x27, // alg: EdDSA w/ Ed25519 @@ -126,53 +124,50 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - WantErr: "", - Validate: func(k *Key) { - assertEqual(t, KeyTypeOKP, k.KeyType) - assertEqual(t, AlgorithmEd25519, k.Algorithm) - assertEqual(t, CurveEd25519, k.Curve) - assertEqual(t, []KeyOp{KeyOpVerify}, k.KeyOps) - assertEqual(t, []byte{ + want: &Key{ + KeyType: KeyTypeOKP, + Algorithm: AlgorithmEd25519, + KeyOps: []KeyOp{KeyOpVerify}, + Curve: CurveEd25519, + X: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - k.X, - ) - assertEqual(t, []byte(nil), k.K) }, + wantErr: "", }, { - Name: "invalid key type", - Value: []byte{ + name: "invalid key type", + data: []byte{ 0xa1, // map (2) 0x01, 0x00, // kty: invalid }, - WantErr: "invalid key type value 0", - Validate: nil, + want: nil, + wantErr: "invalid key type value 0", }, { - Name: "missing curve OKP", - Value: []byte{ + name: "missing curve OKP", + data: []byte{ 0xa1, // map (2) 0x01, 0x01, // kty: OKP }, - WantErr: "missing Curve parameter (required for OKP key type)", - Validate: nil, + want: nil, + wantErr: "missing Curve parameter (required for OKP key type)", }, { - Name: "missing curve EC2", - Value: []byte{ + name: "missing curve EC2", + data: []byte{ 0xa1, // map (2) 0x01, 0x02, // kty: EC2 }, - WantErr: "missing Curve parameter (required for EC2 key type)", - Validate: nil, + want: nil, + wantErr: "missing Curve parameter (required for EC2 key type)", }, { - Name: "invalid curve OKP", - Value: []byte{ + name: "invalid curve OKP", + data: []byte{ 0xa3, // map (3) 0x01, 0x01, // kty: OKP 0x20, 0x01, // curve: CurveP256 @@ -182,12 +177,12 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - WantErr: `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, - Validate: nil, + want: nil, + wantErr: `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, }, { - Name: "invalid curve EC2", - Value: []byte{ + name: "invalid curve EC2", + data: []byte{ 0xa4, // map (4) 0x01, 0x02, // kty: EC2 0x20, 0x06, // curve: CurveEd25519 @@ -202,52 +197,43 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - WantErr: `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`, - Validate: nil, + want: nil, + wantErr: `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`, }, { - Name: "ok Symmetric", - Value: []byte{ - 0xa4, // map (4) + name: "ok Symmetric", + data: []byte{ + 0xa2, // map (2) 0x01, 0x04, // kty: Symmetric - 0x03, 0x38, 0x24, // alg: PS256 - 0x04, // key ops - 0x81, // array (1) - 0x02, // verify 0x20, 0x58, 0x20, // k: bytes(32) 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - WantErr: "", - Validate: func(k *Key) { - assertEqual(t, KeyTypeSymmetric, k.KeyType) - assertEqual(t, AlgorithmPS256, k.Algorithm) - assertEqual(t, int64(0), int64(k.Curve)) - assertEqual(t, []KeyOp{KeyOpVerify}, k.KeyOps) - assertEqual(t, []byte{ + want: &Key{ + KeyType: KeyTypeSymmetric, + K: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - k.K, - ) }, + wantErr: "", }, { - Name: "missing K", - Value: []byte{ + name: "missing K", + data: []byte{ 0xa1, // map (1) 0x01, 0x04, // kty: Symmetric }, - WantErr: "missing K parameter (required for Symmetric key type)", - Validate: nil, + want: nil, + wantErr: "missing K parameter (required for Symmetric key type)", }, { - Name: "wrong algorithm", - Value: []byte{ + name: "wrong algorithm", + data: []byte{ 0xa4, // map (3) 0x01, 0x01, // kty: OKP 0x03, 0x26, // alg: ECDSA w/ SHA-256 @@ -258,333 +244,653 @@ func Test_Key_UnmarshalCBOR(t *testing.T) { 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - WantErr: `found algorithm "ES256" (expected "EdDSA")`, - Validate: nil, + want: nil, + wantErr: `found algorithm "ES256" (expected "EdDSA")`, }, } - for _, tv := range tvs { - t.Run(tv.Name, func(t *testing.T) { - var k Key - - err := k.UnmarshalCBOR(tv.Value) - if tv.WantErr != "" { - if err == nil || err.Error() != tv.WantErr { - t.Errorf("Unexpected error: want %q, got %q", tv.WantErr, err) - } - } else { - tv.Validate(&k) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := new(Key) + err := got.UnmarshalCBOR(tt.data) + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("Key.UnmarshalCBOR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil && !reflect.DeepEqual(got, tt.want) { + t.Errorf("Key.UnmarshalCBOR() = %v, want %v", got, tt.want) } }) } } -func Test_Key_MarshalCBOR(t *testing.T) { - k := Key{ - KeyType: KeyTypeOKP, - KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, - X: []byte{ - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, - }, - Algorithm: AlgorithmEd25519, - Curve: CurveEd25519, - } - - data, err := k.MarshalCBOR() - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - expected := []byte{ - 0xa5, // map (5) - 0x01, 0x01, // kty: OKP - 0x03, 0x27, // alg: EdDSA w/ Ed25519 - 0x04, // key ops - 0x82, // array (2) - 0x02, 0x03, // verify, encrypt - 0x20, 0x06, // curve: Ed25519 - 0x21, 0x58, 0x20, // x-coordinate: bytes(32) - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, - } - if !bytes.Equal(expected, data) { - t.Errorf("Bad marshal: %v", data) - } - - k = Key{ - KeyType: KeyTypeSymmetric, - K: []byte{ - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, +func TestKey_MarshalCBOR(t *testing.T) { + tests := []struct { + name string + key *Key + want []byte + wantErr string + }{ + { + name: "OKP", + key: &Key{ + KeyType: KeyTypeOKP, + KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, + X: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + Algorithm: AlgorithmEd25519, + Curve: CurveEd25519, + }, + want: []byte{ + 0xa5, // map (5) + 0x01, 0x01, // kty: OKP + 0x03, 0x27, // alg: EdDSA w/ Ed25519 + 0x04, // key ops + 0x82, // array (2) + 0x02, 0x03, // verify, encrypt + 0x20, 0x06, // curve: Ed25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + wantErr: "", + }, { + name: "Symmetric", + key: &Key{ + KeyType: KeyTypeSymmetric, + K: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + }, + want: []byte{ + 0xa2, // map (2) + 0x01, 0x04, // kty: Symmetric + 0x20, 0x58, 0x20, // K: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + wantErr: "", + }, { + name: "unknown key type", + key: &Key{KeyType: 42}, + want: nil, + wantErr: `invalid key type: "unknown key type value 42"`, }, } - - data, err = k.MarshalCBOR() - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - expected = []byte{ - 0xa2, // map (2) - 0x01, 0x04, // kty: Symmetric - 0x20, 0x58, 0x20, // K: bytes(32) - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, - } - if !bytes.Equal(expected, data) { - t.Errorf("Bad marshal: %v", data) - } - - k.KeyType = KeyType(42) - _, err = k.MarshalCBOR() - wantErr := `invalid key type: "unknown key type value 42"` - if err == nil || err.Error() != wantErr { - t.Errorf("Unexpected error: want %q, got %q", wantErr, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.key.MarshalCBOR() + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("Key.MarshalCBOR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil && !reflect.DeepEqual(got, tt.want) { + t.Errorf("Key.MarshalCBOR() = %v, want %v", got, tt.want) + } + }) } } -func Test_Key_Create_and_Validate(t *testing.T) { +func TestNewOKPKey(t *testing.T) { x := []byte{ 0x30, 0xa0, 0x42, 0x4c, 0xd2, 0x1c, 0x29, 0x44, 0x83, 0x8a, 0x2d, 0x75, 0xc9, 0x2b, 0x37, 0xe7, 0x6e, 0xa2, 0x0d, 0x9f, 0x00, 0x89, 0x3a, 0x3b, 0x4e, 0xee, 0x8a, 0x3c, 0x0a, 0xaf, 0xec, 0x3e, } - - y := []byte{ + d := []byte{ 0xe0, 0x4b, 0x65, 0xe9, 0x24, 0x56, 0xd9, 0x88, 0x8b, 0x52, 0xb3, 0x79, 0xbd, 0xfb, 0xd5, 0x1e, 0xe8, 0x69, 0xef, 0x1f, 0x0f, 0xc6, 0x5b, 0x66, 0x59, 0x69, 0x5b, 0x6c, 0xce, 0x08, 0x17, 0x23, } - - key, err := NewOKPKey(AlgorithmEd25519, x, nil) - requireNoError(t, err) - assertEqual(t, KeyTypeOKP, key.KeyType) - assertEqual(t, x, key.X) - - _, err = NewOKPKey(AlgorithmES256, x, nil) - assertEqualError(t, err, `unsupported algorithm "ES256"`) - - _, err = NewEC2Key(AlgorithmEd25519, x, y, nil) - assertEqualError(t, err, `unsupported algorithm "EdDSA"`) - - key, err = NewEC2Key(AlgorithmES256, x, y, nil) - requireNoError(t, err) - assertEqual(t, KeyTypeEC2, key.KeyType) - assertEqual(t, x, key.X) - assertEqual(t, y, key.Y) - - key = NewSymmetricKey(x) - assertEqual(t, x, key.K) - - _, err = NewKeyFromPublic(crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "invalid public key") - - _, err = NewKeyFromPrivate(crypto.PublicKey([]byte{0xde, 0xad, 0xbe, 0xef})) - assertEqualError(t, err, "invalid private key") + type args struct { + alg Algorithm + x []byte + d []byte + } + tests := []struct { + name string + args args + want *Key + wantErr string + }{ + { + name: "valid", args: args{AlgorithmEd25519, x, d}, + want: &Key{ + KeyType: KeyTypeOKP, + Algorithm: AlgorithmEd25519, + Curve: CurveEd25519, + X: x, + D: d, + }, + wantErr: "", + }, { + name: "invalid alg", args: args{Algorithm(-100), x, d}, + want: nil, + wantErr: `unsupported algorithm "unknown algorithm value -100"`, + }, { + name: "x and d missing", args: args{AlgorithmEd25519, nil, nil}, + want: nil, + wantErr: ErrInvalidKey.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewOKPKey(tt.args.alg, tt.args.x, tt.args.d) + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("NewOKPKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewOKPKey() = %v, want %v", got, tt.want) + } + }) + } } -func Test_Key_ed25519_signature_round_trip(t *testing.T) { - pub, priv, err := ed25519.GenerateKey(rand.Reader) - requireNoError(t, err) - - key, err := NewKeyFromPrivate(priv) - requireNoError(t, err) - assertEqual(t, AlgorithmEd25519, key.Algorithm) - assertEqual(t, CurveEd25519, key.Curve) - assertEqual(t, pub, key.X) - assertEqual(t, priv[:32], key.D) - - signer, err := key.Signer() - requireNoError(t, err) - - message := []byte("foo bar") - sig, err := signer.Sign(rand.Reader, message) - requireNoError(t, err) - - key, err = NewKeyFromPublic(pub) - requireNoError(t, err) - - assertEqual(t, AlgorithmEd25519, key.Algorithm) - assertEqual(t, CurveEd25519, key.Curve) - assertEqual(t, pub, key.X) - - verifier, err := key.Verifier() - requireNoError(t, err) - - err = verifier.Verify(message, sig) - requireNoError(t, err) +func TestNewEC2Key(t *testing.T) { + x := []byte{1, 2, 3} + y := []byte{4, 5, 6} + d := []byte{7, 8, 9} + type args struct { + alg Algorithm + x []byte + y []byte + d []byte + } + tests := []struct { + name string + args args + want *Key + wantErr string + }{ + { + name: "valid ES256", args: args{AlgorithmES256, x, y, d}, + want: &Key{ + KeyType: KeyTypeEC2, + Algorithm: AlgorithmES256, + Curve: CurveP256, + X: x, + Y: y, + D: d, + }, + wantErr: "", + }, { + name: "valid ES384", args: args{AlgorithmES384, x, y, d}, + want: &Key{ + KeyType: KeyTypeEC2, + Algorithm: AlgorithmES384, + Curve: CurveP384, + X: x, + Y: y, + D: d, + }, + wantErr: "", + }, { + name: "valid ES521", args: args{AlgorithmES512, x, y, d}, + want: &Key{ + KeyType: KeyTypeEC2, + Algorithm: AlgorithmES512, + Curve: CurveP521, + X: x, + Y: y, + D: d, + }, + wantErr: "", + }, { + name: "invalid alg", args: args{Algorithm(-100), x, y, d}, + want: nil, + wantErr: `unsupported algorithm "unknown algorithm value -100"`, + }, { + name: "x, y and d missing", args: args{AlgorithmES512, nil, nil, nil}, + want: nil, + wantErr: ErrInvalidKey.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewEC2Key(tt.args.alg, tt.args.x, tt.args.y, tt.args.d) + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("NewEC2Key() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewEC2Key() = %v, want %v", got, tt.want) + } + }) + } } -func Test_Key_ecdsa_signature_round_trip(t *testing.T) { - for _, tv := range []struct { - EC elliptic.Curve - Curve Curve - Algorithm Algorithm +func TestNewSymmetricKey(t *testing.T) { + type args struct { + k []byte + } + tests := []struct { + name string + args args + want *Key }{ - {elliptic.P256(), CurveP256, AlgorithmES256}, - {elliptic.P384(), CurveP384, AlgorithmES384}, - {elliptic.P521(), CurveP521, AlgorithmES512}, - } { - t.Run(tv.Curve.String(), func(t *testing.T) { - priv, err := ecdsa.GenerateKey(tv.EC, rand.Reader) - requireNoError(t, err) + {"valid", args{[]byte{1, 2, 3}}, &Key{ + KeyType: KeyTypeSymmetric, + K: []byte{1, 2, 3}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewSymmetricKey(tt.args.k); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewSymmetricKey() = %v, want %v", got, tt.want) + } + }) + } +} +func TestKey_SignRoundtrip(t *testing.T) { + tests := []struct { + name string + newKey func() (crypto.PrivateKey, error) + }{ + { + "P-256", func() (crypto.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + }, + }, { + "P-384", func() (crypto.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + }, + }, { + "P-521", func() (crypto.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + }, + }, { + "Ed25519", func() (crypto.PrivateKey, error) { + _, priv, err := ed25519.GenerateKey(rand.Reader) + return priv, err + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + priv, err := tt.newKey() + if err != nil { + t.Fatal(err) + } key, err := NewKeyFromPrivate(priv) - requireNoError(t, err) - assertEqual(t, tv.Curve, key.Curve) - assertEqual(t, priv.X.Bytes(), key.X) - assertEqual(t, priv.Y.Bytes(), key.Y) - assertEqual(t, priv.D.Bytes(), key.D) - + if err != nil { + t.Fatal(err) + } signer, err := key.Signer() - requireNoError(t, err) - + if err != nil { + t.Fatal(err) + } message := []byte("foo bar") sig, err := signer.Sign(rand.Reader, message) - requireNoError(t, err) - - pub := priv.Public() - - key, err = NewKeyFromPublic(pub) - requireNoError(t, err) - - assertEqual(t, tv.Curve, key.Curve) - assertEqual(t, priv.X.Bytes(), key.X) - assertEqual(t, priv.Y.Bytes(), key.Y) - + if err != nil { + t.Fatal(err) + } verifier, err := key.Verifier() - requireNoError(t, err) - + if err != nil { + t.Fatal(err) + } err = verifier.Verify(message, sig) - requireNoError(t, err) + if err != nil { + t.Fatal(err) + } }) } } -func Test_Key_derive_algorithm(t *testing.T) { - k := Key{ - KeyType: KeyTypeEC2, - Curve: CurveEd25519, +func TestKey_AlgorithmOrDefault(t *testing.T) { + tests := []struct { + name string + k *Key + want Algorithm + wantErr string + }{ + { + "custom", + &Key{Algorithm: -1000}, + -1000, + "", + }, + { + "OKP-Ed25519", + &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + }, + AlgorithmEd25519, + "", + }, + { + "OKP-P256", + &Key{ + KeyType: KeyTypeOKP, + Curve: CurveP256, + }, + AlgorithmInvalid, + `unsupported curve "P-256" for key type OKP`, + }, + { + "EC2-P256", + &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP256, + }, + AlgorithmES256, + "", + }, + { + "EC2-P384", + &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP384, + }, + AlgorithmES384, + "", + }, + { + "EC2-P521", + &Key{ + KeyType: KeyTypeEC2, + Curve: CurveP521, + }, + AlgorithmES512, + "", + }, + { + "EC2-Ed25519", + &Key{ + KeyType: KeyTypeEC2, + Curve: CurveEd25519, + }, + AlgorithmInvalid, + `unsupported curve "Ed25519" for key type EC2`, + }, } - - _, err := k.AlgorithmOrDefault() - assertEqualError(t, err, `unsupported curve "Ed25519" for key type EC2`) - - k = Key{ - KeyType: KeyTypeOKP, - Curve: CurveP256, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.k.AlgorithmOrDefault() + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("Key.AlgorithmOrDefault() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Key.AlgorithmOrDefault() = %v, want %v", got, tt.want) + } + }) } +} - _, err = k.AlgorithmOrDefault() - assertEqualError(t, err, `unsupported curve "P-256" for key type OKP`) - - k = Key{ - KeyType: KeyTypeOKP, - Curve: CurveX448, +func TestNewKeyFromPrivate(t *testing.T) { + tests := []struct { + name string + k crypto.PrivateKey + want *Key + wantErr string + }{ + { + "ecdsa", &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(1), Y: big.NewInt(2)}, + D: big.NewInt(3), + }, &Key{ + Algorithm: AlgorithmES256, + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: big.NewInt(1).Bytes(), + Y: big.NewInt(2).Bytes(), + D: big.NewInt(3).Bytes(), + }, + "", + }, + { + "ecdsa invalid", &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{Curve: *new(elliptic.Curve), X: big.NewInt(1), Y: big.NewInt(2)}, + D: big.NewInt(3), + }, + nil, + "unsupported curve: ", + }, + { + "ed25519", ed25519.PrivateKey{ + 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }, + &Key{ + Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, Curve: CurveEd25519, + X: []byte{4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + D: []byte{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + "", + }, + { + "invalid key", ed25519.PublicKey{1, 2, 3}, + nil, + ErrInvalidPrivKey.Error(), + }, } - - _, err = k.AlgorithmOrDefault() - assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) - - k = Key{ - KeyType: KeyTypeOKP, - Curve: CurveEd25519, + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewKeyFromPrivate(tt.k) + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("NewKeyFromPrivate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewKeyFromPrivate() = %v, want %v", got, tt.want) + } + }) } - - alg, err := k.AlgorithmOrDefault() - requireNoError(t, err) - assertEqual(t, AlgorithmEd25519, alg) } -func Test_NewKeyFrom(t *testing.T) { - pub := ecdsa.PublicKey{Curve: *new(elliptic.Curve), X: new(big.Int), Y: new(big.Int)} - _, err := NewKeyFromPublic(&pub) - assertEqualError(t, err, "unsupported curve: ") - - priv := ecdsa.PrivateKey{PublicKey: pub, D: new(big.Int)} - _, err = NewKeyFromPrivate(&priv) - assertEqualError(t, err, "unsupported curve: ") -} - -func Test_algorithmFromEllipticCurve(t *testing.T) { - alg := algorithmFromEllipticCurve(*new(elliptic.Curve)) - assertEqual(t, alg, AlgorithmInvalid) +func TestNewKeyFromPublic(t *testing.T) { + tests := []struct { + name string + k crypto.PublicKey + want *Key + wantErr string + }{ + { + "ecdsa", &ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(1), Y: big.NewInt(2)}, + &Key{ + Algorithm: AlgorithmES256, + KeyType: KeyTypeEC2, + Curve: CurveP256, + X: big.NewInt(1).Bytes(), + Y: big.NewInt(2).Bytes(), + }, + "", + }, + { + "ecdsa invalid", &ecdsa.PublicKey{Curve: *new(elliptic.Curve), X: big.NewInt(1), Y: big.NewInt(2)}, + nil, + "unsupported curve: ", + }, + { + "ed25519", ed25519.PublicKey{1, 2, 3}, + &Key{Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, Curve: CurveEd25519, X: []byte{1, 2, 3}}, + "", + }, + { + "invalid key", ed25519.PrivateKey{1, 2, 3, 1, 2, 3}, + nil, + ErrInvalidPubKey.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewKeyFromPublic(tt.k) + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("NewKeyFromPublic() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewKeyFromPublic() = %v, want %v", got, tt.want) + } + }) + } } -func Test_Key_signer_validation(t *testing.T) { - pub, priv, err := ed25519.GenerateKey(rand.Reader) - requireNoError(t, err) - - key, err := NewKeyFromPublic(pub) - requireNoError(t, err) - - _, err = key.Signer() - assertEqualError(t, err, ErrNotPrivKey.Error()) - - key, err = NewKeyFromPrivate(priv) - requireNoError(t, err) - - key.KeyType = KeyTypeEC2 - _, err = key.Signer() - assertEqualError(t, err, `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`) - - key.Curve = CurveP256 - _, err = key.Signer() - assertEqualError(t, err, `found algorithm "EdDSA" (expected "ES256")`) - - key.KeyType = KeyTypeOKP - key.Algorithm = AlgorithmEd25519 - key.Curve = CurveEd25519 - key.KeyOps = []KeyOp{} - _, err = key.Signer() - assertEqualError(t, err, ErrOpNotSupported.Error()) - - key.KeyOps = []KeyOp{KeyOpSign} - _, err = key.Signer() - requireNoError(t, err) - - key.Algorithm = AlgorithmES256 - _, err = key.Signer() - assertEqualError(t, err, `found algorithm "ES256" (expected "EdDSA")`) - - key.Curve = CurveX448 - _, err = key.Signer() - assertEqualError(t, err, `unsupported curve "X448" for key type OKP`) +func TestKey_Signer(t *testing.T) { + x := []byte{0xde, 0xad, 0xbe, 0xef} + d := []byte{0xde, 0xad, 0xbe, 0xef} + tests := []struct { + name string + k *Key + wantAlg Algorithm + wantErr string + }{ + { + "without algorithm", &Key{ + KeyType: KeyTypeOKP, + KeyOps: []KeyOp{KeyOpSign}, + Curve: CurveEd25519, + X: x, + D: d, + }, + AlgorithmEd25519, + "", + }, + { + "without key_ops", &Key{ + KeyType: KeyTypeOKP, + Algorithm: AlgorithmEd25519, + Curve: CurveEd25519, + X: x, + D: d, + }, + AlgorithmEd25519, + "", + }, + { + "invalid algorithm", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveP256, + X: x, + D: d, + }, + AlgorithmInvalid, + `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, + }, + { + "can't sign", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + KeyOps: []KeyOp{KeyOpVerify}, + X: x, + D: d, + }, + AlgorithmInvalid, + ErrOpNotSupported.Error(), + }, + { + "unsupported key", &Key{ + KeyType: KeyTypeSymmetric, + KeyOps: []KeyOp{KeyOpSign}, + K: x, + D: d, + }, + AlgorithmInvalid, + `unexpected key type "Symmetric"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v, err := tt.k.Signer() + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("Key.Signer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil { + if got := v.Algorithm(); got != tt.wantAlg { + t.Errorf("Key.Signer().Algorithm() = %v, want %v", got, tt.wantAlg) + } + } + }) + } } func TestKey_Verifier(t *testing.T) { - pub, _, err := ed25519.GenerateKey(rand.Reader) - requireNoError(t, err) - - key, err := NewKeyFromPublic(pub) - requireNoError(t, err) - - _, err = key.Verifier() - requireNoError(t, err) - - key.KeyType = KeyTypeEC2 - _, err = key.Verifier() - assertEqualError(t, err, ErrEC2NoPub.Error()) - - key.KeyType = KeyTypeOKP - key.KeyOps = []KeyOp{} - _, err = key.Verifier() - assertEqualError(t, err, ErrOpNotSupported.Error()) - - key.KeyOps = []KeyOp{KeyOpVerify} - _, err = key.Verifier() - requireNoError(t, err) + x := []byte{0xde, 0xad, 0xbe, 0xef} + tests := []struct { + name string + k *Key + wantAlg Algorithm + wantErr string + }{ + { + "without algorithm", &Key{ + KeyType: KeyTypeOKP, + KeyOps: []KeyOp{KeyOpVerify}, + Curve: CurveEd25519, + X: x, + }, + AlgorithmEd25519, + "", + }, + { + "without key_ops", &Key{ + KeyType: KeyTypeOKP, + Algorithm: AlgorithmEd25519, + Curve: CurveEd25519, + X: x, + }, + AlgorithmEd25519, + "", + }, + { + "invalid algorithm", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveP256, + X: x, + }, + AlgorithmInvalid, + `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, + }, + { + "can't verify", &Key{ + KeyType: KeyTypeOKP, + Curve: CurveEd25519, + KeyOps: []KeyOp{KeyOpSign}, + X: x, + }, + AlgorithmInvalid, + ErrOpNotSupported.Error(), + }, + { + "unsupported key", &Key{ + KeyType: KeyTypeSymmetric, + KeyOps: []KeyOp{KeyOpVerify}, + K: x, + }, + AlgorithmInvalid, + `unexpected key type "Symmetric"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v, err := tt.k.Verifier() + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { + t.Errorf("Key.Verifier() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil { + if got := v.Algorithm(); got != tt.wantAlg { + t.Errorf("Key.Verifier().Algorithm() = %v, want %v", got, tt.wantAlg) + } + } + }) + } } func TestKey_PrivateKey(t *testing.T) { @@ -595,7 +901,7 @@ func TestKey_PrivateKey(t *testing.T) { name string k *Key want crypto.PrivateKey - wantErr error + wantErr string }{ { "CurveEd25519", &Key{ @@ -608,7 +914,7 @@ func TestKey_PrivateKey(t *testing.T) { d[0], d[1], d[2], d[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, x[0], x[1], x[2], x[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, - nil, + "", }, { "CurveP256", &Key{ KeyType: KeyTypeEC2, @@ -625,7 +931,7 @@ func TestKey_PrivateKey(t *testing.T) { }, D: new(big.Int).SetBytes(d), }, - nil, + "", }, { "CurveP384", &Key{ KeyType: KeyTypeEC2, @@ -642,7 +948,7 @@ func TestKey_PrivateKey(t *testing.T) { }, D: new(big.Int).SetBytes(d), }, - nil, + "", }, { "CurveP521", &Key{ KeyType: KeyTypeEC2, @@ -659,13 +965,13 @@ func TestKey_PrivateKey(t *testing.T) { }, D: new(big.Int).SetBytes(d), }, - nil, + "", }, { "unknown key type", &Key{ KeyType: KeyType(7), }, nil, - errors.New(`unexpected key type "unknown key type value 7"`), + `unexpected key type "unknown key type value 7"`, }, { "OKP missing X", &Key{ KeyType: KeyTypeOKP, @@ -673,7 +979,7 @@ func TestKey_PrivateKey(t *testing.T) { D: d, }, nil, - ErrOKPNoPub, + ErrOKPNoPub.Error(), }, { "OKP missing D", &Key{ KeyType: KeyTypeOKP, @@ -681,7 +987,7 @@ func TestKey_PrivateKey(t *testing.T) { X: x, }, nil, - ErrNotPrivKey, + ErrNotPrivKey.Error(), }, { "OKP unknown curve", &Key{ KeyType: KeyTypeOKP, @@ -690,7 +996,7 @@ func TestKey_PrivateKey(t *testing.T) { D: d, }, nil, - errors.New(`unsupported curve "unknown curve value 70" for key type OKP`), + `unsupported curve "unknown curve value 70" for key type OKP`, }, { "EC2 missing X", &Key{ KeyType: KeyTypeEC2, @@ -699,7 +1005,7 @@ func TestKey_PrivateKey(t *testing.T) { D: d, }, nil, - ErrEC2NoPub, + ErrEC2NoPub.Error(), }, { "EC2 missing Y", &Key{ KeyType: KeyTypeEC2, @@ -708,7 +1014,7 @@ func TestKey_PrivateKey(t *testing.T) { D: d, }, nil, - ErrEC2NoPub, + ErrEC2NoPub.Error(), }, { "EC2 missing D", &Key{ KeyType: KeyTypeEC2, @@ -717,7 +1023,7 @@ func TestKey_PrivateKey(t *testing.T) { Y: y, }, nil, - ErrNotPrivKey, + ErrNotPrivKey.Error(), }, { "EC2 unknown curve", &Key{ KeyType: KeyTypeEC2, @@ -727,13 +1033,13 @@ func TestKey_PrivateKey(t *testing.T) { D: d, }, nil, - errors.New(`unsupported curve "unknown curve value 70" for key type EC2`), + `unsupported curve "unknown curve value 70" for key type EC2`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.k.PrivateKey() - if err != nil && err.Error() != tt.wantErr.Error() { + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { t.Errorf("Key.PrivateKey() error = %v, wantErr %v", err, tt.wantErr) return } @@ -751,7 +1057,7 @@ func TestKey_PublicKey(t *testing.T) { name string k *Key want crypto.PublicKey - wantErr error + wantErr string }{ { "CurveEd25519", &Key{ @@ -760,7 +1066,7 @@ func TestKey_PublicKey(t *testing.T) { X: x, }, ed25519.PublicKey(x), - nil, + "", }, { "CurveP256", &Key{ KeyType: KeyTypeEC2, @@ -773,7 +1079,7 @@ func TestKey_PublicKey(t *testing.T) { X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y), }, - nil, + "", }, { "CurveP384", &Key{ KeyType: KeyTypeEC2, @@ -786,7 +1092,7 @@ func TestKey_PublicKey(t *testing.T) { X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y), }, - nil, + "", }, { "CurveP521", &Key{ KeyType: KeyTypeEC2, @@ -799,20 +1105,20 @@ func TestKey_PublicKey(t *testing.T) { X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y), }, - nil, + "", }, { "unknown key type", &Key{ KeyType: KeyType(7), }, nil, - errors.New(`unexpected key type "unknown key type value 7"`), + `unexpected key type "unknown key type value 7"`, }, { "OKP missing X", &Key{ KeyType: KeyTypeOKP, Curve: CurveEd25519, }, nil, - ErrOKPNoPub, + ErrOKPNoPub.Error(), }, { "OKP unknown curve", &Key{ KeyType: KeyTypeOKP, @@ -821,7 +1127,7 @@ func TestKey_PublicKey(t *testing.T) { Y: y, }, nil, - errors.New(`unsupported curve "unknown curve value 70" for key type OKP`), + `unsupported curve "unknown curve value 70" for key type OKP`, }, { "EC2 missing X", &Key{ KeyType: KeyTypeEC2, @@ -829,7 +1135,7 @@ func TestKey_PublicKey(t *testing.T) { Y: y, }, nil, - ErrEC2NoPub, + ErrEC2NoPub.Error(), }, { "EC2 missing Y", &Key{ KeyType: KeyTypeEC2, @@ -837,7 +1143,7 @@ func TestKey_PublicKey(t *testing.T) { X: x, }, nil, - ErrEC2NoPub, + ErrEC2NoPub.Error(), }, { "EC2 unknown curve", &Key{ KeyType: KeyTypeEC2, @@ -846,13 +1152,13 @@ func TestKey_PublicKey(t *testing.T) { Y: y, }, nil, - errors.New(`unsupported curve "unknown curve value 70" for key type EC2`), + `unsupported curve "unknown curve value 70" for key type EC2`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.k.PublicKey() - if err != nil && err.Error() != tt.wantErr.Error() { + if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { t.Errorf("Key.PublicKey() error = %v, wantErr %v", err, tt.wantErr) return } @@ -863,10 +1169,38 @@ func TestKey_PublicKey(t *testing.T) { } } -func Test_String(t *testing.T) { +func TestKeyType_String(t *testing.T) { // test string conversions not exercised by other test cases - assertEqual(t, "OKP", KeyTypeOKP.String()) - assertEqual(t, "EC2", KeyTypeEC2.String()) - assertEqual(t, "X25519", CurveX25519.String()) - assertEqual(t, "Ed448", CurveEd448.String()) + tests := []struct { + kt KeyType + want string + }{ + {KeyTypeOKP, "OKP"}, + {KeyTypeEC2, "EC2"}, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := tt.kt.String(); got != tt.want { + t.Errorf("KeyType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCurve_String(t *testing.T) { + // test string conversions not exercised by other test cases + tests := []struct { + kt Curve + want string + }{ + {CurveX25519, "X25519"}, + {CurveEd448, "Ed448"}, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + if got := tt.kt.String(); got != tt.want { + t.Errorf("Curve.String() = %v, want %v", got, tt.want) + } + }) + } } From bfe4717a94593a17d9be13ab9f49ae8e5b346d83 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Thu, 13 Jul 2023 08:07:17 +0200 Subject: [PATCH 08/25] Refactor Key to support custom parameters (#158) Signed-off-by: qmuntal --- common.go | 96 ---- common_test.go | 143 ------ key.go | 502 ++++++++++++++------- key_test.go | 1154 ++++++++++++++++++++++++++++++++++++------------ 4 files changed, 1224 insertions(+), 671 deletions(-) delete mode 100644 common.go delete mode 100644 common_test.go diff --git a/common.go b/common.go deleted file mode 100644 index 32294a5..0000000 --- a/common.go +++ /dev/null @@ -1,96 +0,0 @@ -package cose - -import ( - "errors" - "fmt" -) - -// intOrStr is a value that can be either an int or a tstr when serialized to -// CBOR. -type intOrStr struct { - intVal int64 - strVal string - isString bool -} - -func newIntOrStr(v interface{}) *intOrStr { - var ios intOrStr - if err := ios.Set(v); err != nil { - return nil - } - return &ios -} - -func (ios intOrStr) Int() int64 { - return ios.intVal -} - -func (ios intOrStr) String() string { - if ios.IsString() { - return ios.strVal - } - return fmt.Sprint(ios.intVal) -} - -func (ios intOrStr) IsInt() bool { - return !ios.isString -} - -func (ios intOrStr) IsString() bool { - return ios.isString -} - -func (ios intOrStr) Value() interface{} { - if ios.IsInt() { - return ios.intVal - } - - return ios.strVal -} - -func (ios *intOrStr) Set(v interface{}) error { - switch t := v.(type) { - case int64: - ios.intVal = t - ios.strVal = "" - ios.isString = false - case int: - ios.intVal = int64(t) - ios.strVal = "" - ios.isString = false - case string: - ios.strVal = t - ios.intVal = 0 - ios.isString = true - default: - return fmt.Errorf("must be int or string, found %T", t) - } - - return nil -} - -// MarshalCBOR returns the encoded CBOR representation of the intOrString, as -// either int or tstr, depending on the value. If no value has been set, -// intOrStr is encoded as a zero-length tstr. -func (ios intOrStr) MarshalCBOR() ([]byte, error) { - if ios.IsInt() { - return encMode.Marshal(ios.intVal) - } - - return encMode.Marshal(ios.strVal) -} - -// UnmarshalCBOR unmarshals the provided CBOR encoded data (must be an int, -// uint, or tstr). -func (ios *intOrStr) UnmarshalCBOR(data []byte) error { - if len(data) == 0 { - return errors.New("zero length buffer") - } - - var val interface{} - if err := decMode.Unmarshal(data, &val); err != nil { - return err - } - - return ios.Set(val) -} diff --git a/common_test.go b/common_test.go deleted file mode 100644 index ed981b7..0000000 --- a/common_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package cose - -import ( - "bytes" - "reflect" - "testing" - - "github.com/fxamacker/cbor/v2" -) - -func Test_intOrStr(t *testing.T) { - ios := newIntOrStr(3) - assertEqual(t, true, ios.IsInt()) - assertEqual(t, false, ios.IsString()) - assertEqual(t, 3, ios.Int()) - assertEqual(t, "3", ios.String()) - - ios = newIntOrStr("foo") - assertEqual(t, false, ios.IsInt()) - assertEqual(t, true, ios.IsString()) - assertEqual(t, 0, ios.Int()) - assertEqual(t, "foo", ios.String()) - - ios = newIntOrStr(3.5) - if ios != nil { - t.Errorf("Expected nil, got %v", ios) - } -} - -func Test_intOrStr_CBOR(t *testing.T) { - ios := newIntOrStr(3) - data, err := ios.MarshalCBOR() - requireNoError(t, err) - assertEqual(t, []byte{0x03}, data) - - ios = &intOrStr{} - err = ios.UnmarshalCBOR(data) - requireNoError(t, err) - assertEqual(t, true, ios.IsInt()) - assertEqual(t, 3, ios.Int()) - - ios = newIntOrStr("foo") - data, err = ios.MarshalCBOR() - requireNoError(t, err) - assertEqual(t, []byte{0x63, 0x66, 0x6f, 0x6f}, data) - - ios = &intOrStr{} - err = ios.UnmarshalCBOR(data) - requireNoError(t, err) - assertEqual(t, true, ios.IsString()) - assertEqual(t, "foo", ios.String()) - - // empty value as field - s := struct { - Field1 intOrStr `cbor:"1,keyasint"` - Field2 int `cbor:"2,keyasint"` - }{Field1: intOrStr{}, Field2: 7} - - data, err = cbor.Marshal(s) - requireNoError(t, err) - assertEqual(t, []byte{0xa2, 0x1, 0x00, 0x2, 0x7}, data) - - ios = &intOrStr{} - data = []byte{0x22} - err = ios.UnmarshalCBOR(data) - requireNoError(t, err) - assertEqual(t, true, ios.IsInt()) - assertEqual(t, -3, ios.Int()) - - data = []byte{} - err = ios.UnmarshalCBOR(data) - assertEqualError(t, err, "zero length buffer") - - data = []byte{0x40} - err = ios.UnmarshalCBOR(data) - assertEqualError(t, err, "must be int or string, found []uint8") - - data = []byte{0xff, 0xff} - err = ios.UnmarshalCBOR(data) - assertEqualError(t, err, "cbor: unexpected \"break\" code") -} - -func requireNoError(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Errorf("Unexpected error: %q", err) - t.Fail() - } -} - -func assertEqualError(t *testing.T, err error, expected string) { - t.Helper() - if err == nil || err.Error() != expected { - t.Errorf("Unexpected error: want %q, got %q", expected, err) - } -} - -func assertEqual(t *testing.T, expected, actual interface{}) { - t.Helper() - if !objectsAreEqualValues(expected, actual) { - t.Errorf("Unexpected value: want %v, got %v", expected, actual) - } -} - -// taken from github.com/stretchr/testify -func objectsAreEqualValues(expected, actual interface{}) bool { - if objectsAreEqual(expected, actual) { - return true - } - - actualType := reflect.TypeOf(actual) - if actualType == nil { - return false - } - expectedValue := reflect.ValueOf(expected) - if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) { - // Attempt comparison after type conversion - return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual) - } - - return false -} - -// taken from github.com/stretchr/testify -func objectsAreEqual(expected, actual interface{}) bool { - if expected == nil || actual == nil { - return expected == actual - } - - exp, ok := expected.([]byte) - if !ok { - return reflect.DeepEqual(expected, actual) - } - - act, ok := actual.([]byte) - if !ok { - return false - } - if exp == nil || act == nil { - return exp == nil && act == nil - } - return bytes.Equal(exp, act) -} diff --git a/key.go b/key.go index 80a22d8..038ee27 100644 --- a/key.go +++ b/key.go @@ -8,9 +8,29 @@ import ( "errors" "fmt" "math/big" + "reflect" "strconv" +) + +const ( + KeyLabelOKPCurve int64 = -1 + KeyLabelOKPX int64 = -2 + KeyLabelOKPD int64 = -4 - cbor "github.com/fxamacker/cbor/v2" + KeyLabelEC2Curve int64 = -1 + KeyLabelEC2X int64 = -2 + KeyLabelEC2Y int64 = -3 + KeyLabelEC2D int64 = -4 + + KeyLabelSymmetricK int64 = -1 +) + +const ( + keyLabelKeyType int64 = 1 + keyLabelKeyID int64 = 2 + keyLabelAlgorithm int64 = 3 + keyLabelKeyOps int64 = 4 + keyLabelBaseIV int64 = 5 ) const ( @@ -55,26 +75,26 @@ type KeyOp int64 // KeyOpFromString returns the KeyOp corresponding to the specified name. // The values are taken from https://www.rfc-editor.org/rfc/rfc7517#section-4.3 -func KeyOpFromString(val string) (KeyOp, error) { +func KeyOpFromString(val string) (KeyOp, bool) { switch val { case "sign": - return KeyOpSign, nil + return KeyOpSign, true case "verify": - return KeyOpVerify, nil + return KeyOpVerify, true case "encrypt": - return KeyOpEncrypt, nil + return KeyOpEncrypt, true case "decrypt": - return KeyOpDecrypt, nil + return KeyOpDecrypt, true case "wrapKey": - return KeyOpWrapKey, nil + return KeyOpWrapKey, true case "unwrapKey": - return KeyOpUnwrapKey, nil + return KeyOpUnwrapKey, true case "deriveKey": - return KeyOpDeriveKey, nil + return KeyOpDeriveKey, true case "deriveBits": - return KeyOpDeriveBits, nil + return KeyOpDeriveBits, true default: - return KeyOpInvalid, fmt.Errorf("unknown key_ops value %q", val) + return KeyOpInvalid, false } } @@ -110,35 +130,6 @@ func (ko KeyOp) String() string { } } -// MarshalCBOR marshals the KeyOp as a CBOR int. -func (ko KeyOp) MarshalCBOR() ([]byte, error) { - return encMode.Marshal(int64(ko)) -} - -// UnmarshalCBOR populates the KeyOp from the provided CBOR value (must be int -// or tstr). -func (ko *KeyOp) UnmarshalCBOR(data []byte) error { - var raw intOrStr - - if err := raw.UnmarshalCBOR(data); err != nil { - return fmt.Errorf("invalid key_ops value %w", err) - } - - if raw.IsString() { - v, err := KeyOpFromString(raw.String()) - if err != nil { - return err - } - - *ko = v - } else { - v := raw.Int() - *ko = KeyOp(v) - } - - return nil -} - // KeyType identifies the family of keys represented by the associated Key. // This determines which files within the Key must be set in order for it to be // valid. @@ -229,38 +220,22 @@ func (c Curve) String() string { // Key represents a COSE_Key structure, as defined by RFC8152. // Note: currently, this does NOT support RFC8230 (RSA algorithms). type Key struct { - // Common parameters. These are independent of the key type. Only - // KeyType common parameter MUST be set. - // KeyType identifies the family of keys for this structure, and thus, // which of the key-type-specific parameters need to be set. - KeyType KeyType `cbor:"1,keyasint"` + KeyType KeyType // KeyID is the identification value matched to the kid in the message. - KeyID []byte `cbor:"2,keyasint,omitempty"` + KeyID []byte // Algorithm is used to restrict the algorithm that is used with the // key. If it is set, the application MUST verify that it matches the // algorithm for which the Key is being used. - Algorithm Algorithm `cbor:"3,keyasint,omitempty"` + Algorithm Algorithm // KeyOps can be set to restrict the set of operations that the Key is used for. - KeyOps []KeyOp `cbor:"4,keyasint,omitempty"` + KeyOps []KeyOp // BaseIV is the Base IV to be xor-ed with Partial IVs. - BaseIV []byte `cbor:"5,keyasint,omitempty"` + BaseIV []byte - // Curve is EC identifier -- taken form "COSE Elliptic Curves" IANA registry. - // Populated from keyStruct.RawKeyParam when key type is EC2 or OKP. - Curve Curve `cbor:"-"` - // K is the key value. Populated from keyStruct.RawKeyParam when key - // type is Symmetric. - K []byte `cbor:"-"` - - // EC2/OKP params - - // X is the x-coordinate - X []byte `cbor:"-2,keyasint,omitempty"` - // Y is the y-coordinate (sign bits are not supported) - Y []byte `cbor:"-3,keyasint,omitempty"` - // D is the private key - D []byte `cbor:"-4,keyasint,omitempty"` + // Any additional parameter (label,value) pairs. + Params map[interface{}]interface{} } // NewOKPKey returns a Key created using the provided Octet Key Pair data. @@ -272,9 +247,15 @@ func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { key := &Key{ KeyType: KeyTypeOKP, Algorithm: alg, - Curve: CurveEd25519, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + }, + } + if x != nil { + key.Params[KeyLabelOKPX] = x + } + if d != nil { + key.Params[KeyLabelOKPD] = d } if err := key.validate(KeyOpInvalid); err != nil { return nil, err @@ -282,6 +263,52 @@ func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { return key, nil } +// ParamBytes returns the value of the parameter with the given label, if it +// exists and is of type []byte or can be converted to []byte. +func (k *Key) ParamBytes(label interface{}) ([]byte, bool) { + v, ok, err := decodeBytes(k.Params, label) + return v, ok && err == nil +} + +// ParamInt returns the value of the parameter with the given label, if it +// exists and is of type int64 or can be converted to int64. +func (k *Key) ParamInt(label interface{}) (int64, bool) { + v, ok, err := decodeInt(k.Params, label) + return v, ok && err == nil +} + +// ParamUint returns the value of the parameter with the given label, if it +// exists and is of type uint64 or can be converted to uint64. +func (k *Key) ParamUint(label interface{}) (uint64, bool) { + v, ok, err := decodeUint(k.Params, label) + return v, ok && err == nil +} + +// ParamString returns the value of the parameter with the given label, if it +// exists and is of type string or can be converted to string. +func (k *Key) ParamString(label interface{}) (string, bool) { + v, ok, err := decodeString(k.Params, label) + return v, ok && err == nil +} + +// ParamBool returns the value of the parameter with the given label, if it +// exists and is of type bool or can be converted to bool. +func (k *Key) ParamBool(label interface{}) (bool, bool) { + v, ok, err := decodeBool(k.Params, label) + return v, ok && err == nil +} + +// OKP returns the Octet Key Pair parameters for the key. +func (k *Key) OKP() (crv Curve, x []byte, d []byte) { + v, ok := k.ParamInt(KeyLabelOKPCurve) + if ok { + crv = Curve(v) + } + x, _ = k.ParamBytes(KeyLabelOKPX) + d, _ = k.ParamBytes(KeyLabelOKPD) + return +} + // NewEC2Key returns a Key created using the provided elliptic curve key // data. func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { @@ -301,10 +328,18 @@ func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { key := &Key{ KeyType: KeyTypeEC2, Algorithm: alg, - Curve: curve, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: curve, + }, + } + if x != nil { + key.Params[KeyLabelEC2X] = x + } + if y != nil { + key.Params[KeyLabelEC2Y] = y + } + if d != nil { + key.Params[KeyLabelEC2D] = d } if err := key.validate(KeyOpInvalid); err != nil { return nil, err @@ -312,15 +347,35 @@ func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { return key, nil } +// EC2 returns the Elliptic Curve parameters for the key. +func (k *Key) EC2() (crv Curve, x []byte, y, d []byte) { + v, ok := k.ParamInt(KeyLabelEC2Curve) + if ok { + crv = Curve(v) + } + x, _ = k.ParamBytes(KeyLabelEC2X) + y, _ = k.ParamBytes(KeyLabelEC2Y) + d, _ = k.ParamBytes(KeyLabelEC2D) + return +} + // NewSymmetricKey returns a Key created using the provided Symmetric key // bytes. func NewSymmetricKey(k []byte) *Key { return &Key{ KeyType: KeyTypeSymmetric, - K: k, + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: k, + }, } } +// Symmetric returns the Symmetric parameters for the key. +func (key *Key) Symmetric() (k []byte) { + k, _ = key.ParamBytes(KeyLabelSymmetricK) + return +} + // NewKeyFromPublic returns a Key created using the provided crypto.PublicKey. // Supported key formats are: *ecdsa.PublicKey and ed25519.PublicKey func NewKeyFromPublic(pub crypto.PublicKey) (*Key, error) { @@ -365,55 +420,62 @@ func NewKeyFromPrivate(priv crypto.PrivateKey) (*Key, error) { func (k Key) validate(op KeyOp) error { switch k.KeyType { case KeyTypeEC2: + crv, x, y, d := k.EC2() switch op { case KeyOpVerify: - if len(k.X) == 0 || len(k.Y) == 0 { + if len(x) == 0 || len(y) == 0 { return ErrEC2NoPub } case KeyOpSign: - if len(k.D) == 0 { + if len(d) == 0 { return ErrNotPrivKey } } - if k.Curve == CurveInvalid || (len(k.X) == 0 && len(k.Y) == 0 && len(k.D) == 0) { + if crv == CurveInvalid || (len(x) == 0 && len(y) == 0 && len(d) == 0) { return ErrInvalidKey } - switch k.Curve { + switch crv { case CurveX25519, CurveX448, CurveEd25519, CurveEd448: return fmt.Errorf( "Key type mismatch for curve %q (must be OKP, found EC2)", - k.Curve.String(), + crv.String(), ) default: // ok -- a key may contain a currently unsupported curve // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 } case KeyTypeOKP: + crv, x, d := k.OKP() switch op { case KeyOpVerify: - if len(k.X) == 0 { + if len(x) == 0 { return ErrOKPNoPub } case KeyOpSign: - if len(k.D) == 0 { + if len(d) == 0 { return ErrNotPrivKey } } - if k.Curve == CurveInvalid || (len(k.X) == 0 && len(k.D) == 0) { + if crv == CurveInvalid || (len(x) == 0 && len(d) == 0) { return ErrInvalidKey } - switch k.Curve { + switch crv { case CurveP256, CurveP384, CurveP521: return fmt.Errorf( "Key type mismatch for curve %q (must be EC2, found OKP)", - k.Curve.String(), + crv.String(), ) default: // ok -- a key may contain a currently unsupported curve // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 } case KeyTypeSymmetric: - // Nothing to validate + k := k.Symmetric() + if len(k) == 0 { + return ErrInvalidKey + } + case KeyTypeInvalid: + return errors.New("invalid kty value 0") default: // Unknown key type, we can't validate custom parameters. } @@ -449,85 +511,114 @@ func (k Key) canOp(op KeyOp) bool { return false } -type keyalias Key - -type marshaledKey struct { - keyalias - - // RawKeyParam contains the raw CBOR encoded data for the label -1. - // Depending on the KeyType this is used to populate either Curve or K - // below. - RawKeyParam cbor.RawMessage `cbor:"-1,keyasint,omitempty"` -} - // MarshalCBOR encodes Key into a COSE_Key object. func (k *Key) MarshalCBOR() ([]byte, error) { - tmp := marshaledKey{ - keyalias: keyalias(*k), + tmp := map[interface{}]interface{}{ + keyLabelKeyType: k.KeyType, } - var err error - - switch k.KeyType { - case KeyTypeSymmetric: - if tmp.RawKeyParam, err = encMode.Marshal(k.K); err != nil { - return nil, err + if k.KeyID != nil { + tmp[keyLabelKeyID] = k.KeyID + } + if k.Algorithm != AlgorithmInvalid { + tmp[keyLabelAlgorithm] = k.Algorithm + } + if k.KeyOps != nil { + tmp[keyLabelKeyOps] = k.KeyOps + } + if k.BaseIV != nil { + tmp[keyLabelBaseIV] = k.BaseIV + } + existing := make(map[interface{}]struct{}, len(k.Params)) + for label, v := range k.Params { + lbl, ok := normalizeLabel(label) + if !ok { + return nil, fmt.Errorf("invalid label type %T", label) } - case KeyTypeEC2, KeyTypeOKP: - if tmp.RawKeyParam, err = encMode.Marshal(k.Curve); err != nil { - return nil, err + if _, ok := existing[lbl]; ok { + return nil, fmt.Errorf("duplicate label %v", lbl) } - default: - return nil, fmt.Errorf("invalid key type: %q", k.KeyType.String()) + existing[lbl] = struct{}{} + tmp[lbl] = v } return encMode.Marshal(tmp) } // UnmarshalCBOR decodes a COSE_Key object into Key. func (k *Key) UnmarshalCBOR(data []byte) error { - var tmp marshaledKey - + var tmp map[interface{}]interface{} if err := decMode.Unmarshal(data, &tmp); err != nil { return err } - if tmp.KeyType == KeyTypeInvalid { - return errors.New("invalid key type value 0") - } - - *k = Key(tmp.keyalias) - switch k.KeyType { - case KeyTypeEC2: - if tmp.RawKeyParam == nil { - return errors.New("missing Curve parameter (required for EC2 key type)") - } - - if err := decMode.Unmarshal(tmp.RawKeyParam, &k.Curve); err != nil { - return err - } - case KeyTypeOKP: - if tmp.RawKeyParam == nil { - return errors.New("missing Curve parameter (required for OKP key type)") - } - - if err := decMode.Unmarshal(tmp.RawKeyParam, &k.Curve); err != nil { - return err - } - case KeyTypeSymmetric: - if tmp.RawKeyParam == nil { - return errors.New("missing K parameter (required for Symmetric key type)") + *k = Key{} + kty, exist, err := decodeInt(tmp, keyLabelKeyType) + if !exist { + return errors.New("kty: missing") + } + if err != nil { + return fmt.Errorf("kty: %w", err) + } + k.KeyType = KeyType(kty) + if k.KeyType == KeyTypeInvalid { + return errors.New("kty: invalid value 0") + } + k.KeyID, _, err = decodeBytes(tmp, keyLabelKeyID) + if err != nil { + return fmt.Errorf("kid: %w", err) + } + alg, _, err := decodeInt(tmp, keyLabelAlgorithm) + if err != nil { + return fmt.Errorf("alg: %w", err) + } + k.Algorithm = Algorithm(alg) + key_ops, err := decodeSlice(tmp, keyLabelKeyOps) + if err != nil { + return fmt.Errorf("key_ops: %w", err) + } + if len(key_ops) > 0 { + k.KeyOps = make([]KeyOp, len(key_ops)) + for i, op := range key_ops { + switch op := op.(type) { + case int64: + k.KeyOps[i] = KeyOp(op) + case string: + var ok bool + if k.KeyOps[i], ok = KeyOpFromString(op); !ok { + return fmt.Errorf("key_ops: unknown entry value %q", op) + } + default: + return fmt.Errorf("key_ops: invalid entry type %T", op) + } } - - if err := decMode.Unmarshal(tmp.RawKeyParam, &k.K); err != nil { - return err + } + k.BaseIV, _, err = decodeBytes(tmp, keyLabelBaseIV) + if err != nil { + return fmt.Errorf("base_iv: %w", err) + } + + delete(tmp, keyLabelKeyType) + delete(tmp, keyLabelKeyID) + delete(tmp, keyLabelAlgorithm) + delete(tmp, keyLabelKeyOps) + delete(tmp, keyLabelBaseIV) + + if len(tmp) > 0 { + k.Params = make(map[interface{}]interface{}, len(tmp)) + for lbl, v := range tmp { + switch lbl := lbl.(type) { + case int64: + if (k.KeyType == KeyTypeEC2 || k.KeyType == KeyTypeOKP) && + (lbl == KeyLabelEC2Curve || lbl == KeyLabelOKPCurve) { + v = Curve(v.(int64)) + } + k.Params[lbl] = v + case string: + k.Params[lbl] = v + default: + return fmt.Errorf("invalid label type %T", lbl) + } } - default: - // this should not be reachable as KeyType.UnmarshalCBOR would - // result in an error during decMode.Unmarshal() above, if the - // value in the data doesn't correspond to one of the above - // types. - return fmt.Errorf("unexpected key type %q", k.KeyType.String()) } - return k.validate(KeyOpInvalid) } @@ -554,13 +645,16 @@ func (k *Key) PublicKey() (crypto.PublicKey, error) { curve = elliptic.P521() } + _, x, y, _ := k.EC2() + pub := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} - pub.X.SetBytes(k.X) - pub.Y.SetBytes(k.Y) + pub.X.SetBytes(x) + pub.Y.SetBytes(y) return pub, nil case AlgorithmEd25519: - return ed25519.PublicKey(k.X), nil + _, x, _ := k.OKP() + return ed25519.PublicKey(x), nil default: return nil, ErrAlgorithmNotSupported } @@ -578,10 +672,11 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { switch alg { case AlgorithmES256, AlgorithmES384, AlgorithmES512: + _, x, y, d := k.EC2() // RFC8152 allows omitting X and Y from private keys; // crypto.PrivateKey assumes they are available. // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 - if len(k.X) == 0 || len(k.Y) == 0 { + if len(x) == 0 || len(y) == 0 { return nil, ErrEC2NoPub } @@ -600,23 +695,24 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { PublicKey: ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}, D: new(big.Int), } - priv.X.SetBytes(k.X) - priv.Y.SetBytes(k.Y) - priv.D.SetBytes(k.D) + priv.X.SetBytes(x) + priv.Y.SetBytes(y) + priv.D.SetBytes(d) return priv, nil case AlgorithmEd25519: + _, x, d := k.OKP() // RFC8152 allows omitting X from private keys; // crypto.PrivateKey assumes it is available. // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 - if len(k.X) == 0 { + if len(x) == 0 { return nil, ErrOKPNoPub } buf := make([]byte, ed25519.PrivateKeySize) - copy(buf, k.D) - copy(buf[32:], k.X) + copy(buf, d) + copy(buf[32:], x) return ed25519.PrivateKey(buf), nil default: @@ -684,7 +780,8 @@ func (k *Key) Verifier() (Verifier, error) { func (k *Key) deriveAlgorithm() (Algorithm, error) { switch k.KeyType { case KeyTypeEC2: - switch k.Curve { + crv, _, _, _ := k.EC2() + switch crv { case CurveP256: return AlgorithmES256, nil case CurveP384: @@ -693,15 +790,16 @@ func (k *Key) deriveAlgorithm() (Algorithm, error) { return AlgorithmES512, nil default: return AlgorithmInvalid, fmt.Errorf( - "unsupported curve %q for key type EC2", k.Curve.String()) + "unsupported curve %q for key type EC2", crv.String()) } case KeyTypeOKP: - switch k.Curve { + crv, _, _ := k.OKP() + switch crv { case CurveEd25519: return AlgorithmEd25519, nil default: return AlgorithmInvalid, fmt.Errorf( - "unsupported curve %q for key type OKP", k.Curve.String()) + "unsupported curve %q for key type OKP", crv.String()) } default: // Symmetric algorithms are not supported in the current inmplementation. @@ -721,3 +819,93 @@ func algorithmFromEllipticCurve(c elliptic.Curve) Algorithm { return AlgorithmInvalid } } + +func decodeBytes(dic map[interface{}]interface{}, lbl interface{}) (b []byte, ok bool, err error) { + val, ok := dic[lbl] + if !ok { + return nil, false, nil + } + if b, ok = val.([]byte); ok { + return b, true, nil + } + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("invalid type: expected []uint8, got %T", val) + } + }() + return reflect.ValueOf(val).Bytes(), true, nil +} + +func decodeInt(dic map[interface{}]interface{}, lbl interface{}) (int64, bool, error) { + val, ok := dic[lbl] + if !ok { + return 0, false, nil + } + if b, ok := val.(int64); ok { + return b, true, nil + } + if v := reflect.ValueOf(val); v.CanInt() { + return v.Int(), true, nil + } + return 0, true, fmt.Errorf("invalid type: expected int64, got %T", val) +} + +func decodeUint(dic map[interface{}]interface{}, lbl interface{}) (uint64, bool, error) { + val, ok := dic[lbl] + if !ok { + return 0, false, nil + } + if b, ok := val.(uint64); ok { + return b, true, nil + } + v := reflect.ValueOf(val) + if v.CanUint() { + return v.Uint(), true, nil + } + if v.CanInt() { + if b := v.Int(); b >= 0 { + return uint64(b), true, nil + } + } + return 0, true, fmt.Errorf("invalid type: expected uint64, got %T", val) +} + +func decodeString(dic map[interface{}]interface{}, lbl interface{}) (string, bool, error) { + val, ok := dic[lbl] + if !ok { + return "", false, nil + } + if b, ok := val.(string); ok { + return b, true, nil + } + if v := reflect.ValueOf(val); v.Kind() == reflect.String { + return v.String(), true, nil + } + return "", true, fmt.Errorf("invalid type: expected uint64, got %T", val) +} + +func decodeBool(dic map[interface{}]interface{}, lbl interface{}) (bool, bool, error) { + val, ok := dic[lbl] + if !ok { + return false, false, nil + } + if b, ok := val.(bool); ok { + return b, true, nil + } + if v := reflect.ValueOf(val); v.Kind() == reflect.Bool { + return v.Bool(), true, nil + } + return false, true, fmt.Errorf("invalid type: expected uint64, got %T", val) +} + +func decodeSlice(dic map[interface{}]interface{}, lbl interface{}) ([]interface{}, error) { + v, ok := dic[lbl] + if !ok { + return nil, nil + } + arr, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("invalid type: expected []interface{}, got %T", v) + } + return arr, nil +} diff --git a/key_test.go b/key_test.go index aa60f64..02a6e22 100644 --- a/key_test.go +++ b/key_test.go @@ -6,98 +6,219 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "encoding/hex" "math/big" "reflect" + "strconv" "testing" - - "github.com/fxamacker/cbor/v2" ) -func Test_KeyOp(t *testing.T) { - - tvs := []struct { - Name string - Value KeyOp +func TestKey_ParamBytes(t *testing.T) { + key := &Key{ + Params: map[interface{}]interface{}{ + int64(-1): []byte{1}, + 2: []byte{2}, + uint16(3): []byte{3}, + "foo": ed25519.PublicKey([]byte{4}), + 5: 5, + }, + } + tests := []struct { + label interface{} + want []byte + want1 bool }{ - {"sign", KeyOpSign}, - {"verify", KeyOpVerify}, - {"encrypt", KeyOpEncrypt}, - {"decrypt", KeyOpDecrypt}, - {"wrapKey", KeyOpWrapKey}, - {"unwrapKey", KeyOpUnwrapKey}, - {"deriveKey", KeyOpDeriveKey}, - {"deriveBits", KeyOpDeriveBits}, + {int64(-1), []byte{1}, true}, + {2, []byte{2}, true}, + {uint16(3), []byte{3}, true}, + {3, nil, false}, + {5, nil, false}, + {"foo", []byte{4}, true}, + {"bar", nil, false}, } - - for _, tv := range tvs { - if tv.Name != tv.Value.String() { - t.Errorf( - "String value mismatch: expected %q, got %q", - tv.Name, - tv.Value.String(), - ) - } - - data, err := cbor.Marshal(tv.Name) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - - var ko KeyOp - err = cbor.Unmarshal(data, &ko) - if err != nil { - t.Errorf("Unexpected error: %s", err) - return - } - if tv.Value != ko { - t.Errorf( - "Value mismatch: want %v, got %v", - tv.Value, - ko, - ) - } - - data, err = cbor.Marshal(int(tv.Value)) - if err != nil { - t.Errorf("Unexpected error: %q", err) - return - } - - err = cbor.Unmarshal(data, &ko) - if err != nil { - t.Errorf("Unexpected error: %q", err) - return - } - if tv.Value != ko { - t.Errorf( - "Value mismatch: want %v, got %v", - tv.Value, - ko, - ) - } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got, got1 := key.ParamBytes(tt.label) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Key.ParamBytes() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Key.ParamBytes() got1 = %v, want %v", got1, tt.want1) + } + }) } +} - var ko KeyOp +func TestKey_ParamInt(t *testing.T) { + type i16 int16 + type u16 uint16 + key := &Key{ + Params: map[interface{}]interface{}{ + int64(-1): 1, + 2: int8(2), + uint16(3): i16(3), + uint16(6): u16(3), + "foo": -4, + 5: []byte{5}, + }, + } + tests := []struct { + label interface{} + want int64 + want1 bool + }{ + {int64(-1), 1, true}, + {2, 2, true}, + {uint16(3), 3, true}, + {3, 0, false}, + {5, 0, false}, + {uint16(6), 0, false}, + {"foo", -4, true}, + {"bar", 0, false}, + } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got, got1 := key.ParamInt(tt.label) + if got != tt.want { + t.Errorf("Key.ParamInt() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Key.ParamInt() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} - data := []byte{0x63, 0x66, 0x6f, 0x6f} - err := ko.UnmarshalCBOR(data) - assertEqualError(t, err, `unknown key_ops value "foo"`) +func TestKey_ParamUint(t *testing.T) { + type i16 int16 + type u16 uint16 + key := &Key{ + Params: map[interface{}]interface{}{ + int64(-1): 1, + 2: int8(2), + uint16(3): i16(3), + 4: i16(-3), + uint16(6): u16(3), + "foo": -4, + 5: []byte{5}, + }, + } + tests := []struct { + label interface{} + want uint64 + want1 bool + }{ + {int64(-1), 1, true}, + {2, 2, true}, + {uint16(3), 3, true}, + {uint16(6), 3, true}, + {4, 0, false}, + {3, 0, false}, + {5, 0, false}, + {"foo", 0, false}, + {"bar", 0, false}, + } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got, got1 := key.ParamUint(tt.label) + if got != tt.want { + t.Errorf("Key.ParamUint() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Key.ParamUint() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} - data = []byte{0x40} - err = ko.UnmarshalCBOR(data) - assertEqualError(t, err, "invalid key_ops value must be int or string, found []uint8") +func TestKey_ParamString(t *testing.T) { + type str string + key := &Key{ + Params: map[interface{}]interface{}{ + 1: "foo", + "2": str("bar"), + 3: []byte("baz"), + 4: 5, + }, + } + tests := []struct { + label interface{} + want string + want1 bool + }{ + {1, "foo", true}, + {"2", "bar", true}, + {3, "", false}, + {4, "", false}, + } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got, got1 := key.ParamString(tt.label) + if got != tt.want { + t.Errorf("Key.ParamString() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Key.ParamString() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} - if KeyOpMACCreate.String() != "MAC create" { - t.Errorf("Unexpected value: %q", KeyOpMACCreate.String()) +func TestKey_ParamBool(t *testing.T) { + type boo bool + key := &Key{ + Params: map[interface{}]interface{}{ + 1: true, + "2": boo(false), + 3: []byte("baz"), + 4: 5, + }, + } + tests := []struct { + label interface{} + want bool + want1 bool + }{ + {1, true, true}, + {"2", false, true}, + {3, false, false}, + {4, false, false}, + } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + got, got1 := key.ParamBool(tt.label) + if got != tt.want { + t.Errorf("Key.ParamBool() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("Key.ParamBool() got1 = %v, want %v", got1, tt.want1) + } + }) } +} - if KeyOpMACVerify.String() != "MAC verify" { - t.Errorf("Unexpected value: %q", KeyOpMACVerify.String()) +func TestKeyOp_String(t *testing.T) { + tests := []struct { + op KeyOp + want string + }{ + {KeyOpSign, "sign"}, + {KeyOpVerify, "verify"}, + {KeyOpEncrypt, "encrypt"}, + {KeyOpDecrypt, "decrypt"}, + {KeyOpWrapKey, "wrapKey"}, + {KeyOpUnwrapKey, "unwrapKey"}, + {KeyOpDeriveKey, "deriveKey"}, + {KeyOpDeriveBits, "deriveBits"}, + {KeyOpMACCreate, "MAC create"}, + {KeyOpMACVerify, "MAC verify"}, + {42, "unknown key_op value 42"}, } - if KeyOp(42).String() != "unknown key_op value 42" { - t.Errorf("Unexpected value: %q", KeyOp(42).String()) + for _, tt := range tests { + if got := tt.op.String(); got != tt.want { + t.Errorf("KeyOp.String() = %v, want %v", got, tt.want) + } } } @@ -109,64 +230,144 @@ func TestKey_UnmarshalCBOR(t *testing.T) { wantErr string }{ { - name: "ok OKP", + name: "invalid COSE_Key CBOR type", data: []byte{ - 0xa5, // map (5) + 0x82, // array(2) 0x01, 0x01, // kty: OKP - 0x03, 0x27, // alg: EdDSA w/ Ed25519 - 0x04, // key ops - 0x81, // array (1) - 0x02, // verify - 0x20, 0x06, // curve: Ed25519 - 0x21, 0x58, 0x20, // x-coordinate: bytes(32) - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, - want: &Key{ - KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, - KeyOps: []KeyOp{KeyOpVerify}, - Curve: CurveEd25519, - X: []byte{ - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, - }, + want: nil, + wantErr: "cbor: cannot unmarshal array into Go value of type map[interface {}]interface {}", + }, { + name: "invalid kty value", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x41, 0x01, // kty: bytes(1) + 0x02, 0x43, 0x01, 0x02, 0x03, // kdi: bytes(3) }, - wantErr: "", - }, - { + want: nil, + wantErr: "kty: invalid type: expected int64, got []uint8", + }, { + name: "missing kty", + data: []byte{ + 0xa1, // map(1) + 0x02, 0x41, 0x01, // kdi: bytes(1) + }, + want: nil, + wantErr: "kty: missing", + }, { name: "invalid key type", data: []byte{ 0xa1, // map (2) 0x01, 0x00, // kty: invalid }, want: nil, - wantErr: "invalid key type value 0", - }, - { - name: "missing curve OKP", + wantErr: "kty: invalid value 0", + }, { + name: "invalid kdi type", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x02, 0x01, // kdi: int(1) + }, + want: nil, + wantErr: "kid: invalid type: expected []uint8, got int64", + }, { + name: "invalid alg type", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x03, 0x41, 0x01, // alg: bstr(1) + }, + want: nil, + wantErr: "alg: invalid type: expected int64, got []uint8", + }, { + name: "invalid key_ops type", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x04, 0x41, 0x01, // key_ops: bstr(1) + }, + want: nil, + wantErr: "key_ops: invalid type: expected []interface{}, got []uint8", + }, { + name: "unknown key_ops entry value", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x04, 0x82, // key_ops: array (2) + 0x02, // verify + 0x63, 0x66, 0x6f, 0x6f, // tstr: foo + }, + want: nil, + wantErr: `key_ops: unknown entry value "foo"`, + }, { + name: "invalid key_ops entry type", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x04, 0x82, // key_ops: array (2) + 0x02, // verify + 0xf6, // nil + }, + want: nil, + wantErr: `key_ops: invalid entry type `, + }, { + name: "invalid base_iv type", + data: []byte{ + 0xa2, // map(2) + 0x01, 0x01, // kty: OKP + 0x05, 0x01, // base_iv: int(1) + }, + want: nil, + wantErr: "base_iv: invalid type: expected []uint8, got int64", + }, { + name: "custom key invalid param type", + data: []byte{ + 0xa3, // map (3) + 0x01, 0x3a, 0x00, 0x01, 0x11, 0x6f, // kty: -70000 + 0x20, 0x06, // 0x20: 0x06 + 0xf6, 0xf6, // nil: nil + }, + want: nil, + wantErr: "invalid label type ", + }, { + name: "duplicated param", + data: []byte{ + 0xa3, // map(3) + 0x01, 0x01, // kty: OKP + 0x18, 0x66, 0x18, 0x67, // 66: 67 + 0x18, 0x66, 0x18, 0x47, // 66: 47 + }, + want: nil, + wantErr: `cbor: found duplicate map key "102" at map element index 2`, + }, { + name: "duplicated kty", + data: []byte{ + 0xa3, // map(3) + 0x01, 0x01, // kty: OKP + 0x02, 0x41, 0x01, // kdi: bytes(1) + 0x01, 0x01, // kty: OKP (duplicated) + }, + want: nil, + wantErr: `cbor: found duplicate map key "1" at map element index 2`, + }, { + name: "OKP missing curve", data: []byte{ 0xa1, // map (2) 0x01, 0x01, // kty: OKP }, want: nil, - wantErr: "missing Curve parameter (required for OKP key type)", - }, - { - name: "missing curve EC2", + wantErr: ErrInvalidKey.Error(), + }, { + name: "EC2 missing curve", data: []byte{ 0xa1, // map (2) 0x01, 0x02, // kty: EC2 }, want: nil, - wantErr: "missing Curve parameter (required for EC2 key type)", - }, - { - name: "invalid curve OKP", + wantErr: ErrInvalidKey.Error(), + }, { + name: "OKP invalid curve", data: []byte{ 0xa3, // map (3) 0x01, 0x01, // kty: OKP @@ -179,9 +380,8 @@ func TestKey_UnmarshalCBOR(t *testing.T) { }, want: nil, wantErr: `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, - }, - { - name: "invalid curve EC2", + }, { + name: "EC2 invalid curve", data: []byte{ 0xa4, // map (4) 0x01, 0x02, // kty: EC2 @@ -199,9 +399,80 @@ func TestKey_UnmarshalCBOR(t *testing.T) { }, want: nil, wantErr: `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`, - }, - { - name: "ok Symmetric", + }, { + name: "Symmetric missing K", + data: []byte{ + 0xa1, // map (1) + 0x01, 0x04, // kty: Symmetric + }, + want: nil, + wantErr: ErrInvalidKey.Error(), + }, { + name: "EC2 invalid algorithm", + data: []byte{ + 0xa4, // map (3) + 0x01, 0x01, // kty: OKP + 0x03, 0x26, // alg: ECDSA w/ SHA-256 + 0x20, 0x06, // curve: Ed25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + want: nil, + wantErr: `found algorithm "ES256" (expected "EdDSA")`, + }, { + name: "custom key", + data: []byte{ + 0xa3, // map (3) + 0x01, 0x3a, 0x00, 0x01, 0x11, 0x6f, // kty: -70000 + 0x20, 0x06, // 0x20: 0x06 + 0x61, 0x66, 0x63, 0x66, 0x6f, 0x6f, // 0x21: foo + }, + want: &Key{ + KeyType: -70000, + Params: map[interface{}]interface{}{ + int64(-1): int64(6), + "f": "foo", + }, + }, + }, { + name: "OKP", + data: []byte{ + 0xa6, // map (6) + 0x01, 0x01, // kty: OKP + 0x03, 0x27, // alg: EdDSA w/ Ed25519 + 0x04, // key ops + 0x82, // array (2) + 0x02, // verify + 0x64, 0x73, 0x69, 0x67, 0x6e, // tstr: sign + 0x05, 0x43, 0x03, 0x02, 0x01, // base_iv: bytes(5) + 0x20, 0x06, // curve: Ed25519 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + want: &Key{ + KeyType: KeyTypeOKP, + Algorithm: AlgorithmEd25519, + KeyOps: []KeyOp{KeyOpVerify, KeyOpSign}, + BaseIV: []byte{0x03, 0x02, 0x01}, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + }, + }, + wantErr: "", + }, { + name: "Symmetric", data: []byte{ 0xa2, // map (2) 0x01, 0x04, // kty: Symmetric @@ -213,39 +484,106 @@ func TestKey_UnmarshalCBOR(t *testing.T) { }, want: &Key{ KeyType: KeyTypeSymmetric, - K: []byte{ - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, }, }, wantErr: "", }, + // The following samples are taken from RFC8152 C.7.1. { - name: "missing K", - data: []byte{ - 0xa1, // map (1) - 0x01, 0x04, // kty: Symmetric + name: "EC2 P-256 public", + data: mustHexToBytes("a5" + + "0102" + + "0258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65" + + "2001" + + "21582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d" + + "2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c"), + want: &Key{ + KeyType: KeyTypeEC2, + KeyID: []byte("meriadoc.brandybuck@buckland.example"), + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: mustHexToBytes("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d"), + KeyLabelEC2Y: mustHexToBytes("1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c"), + }, }, - want: nil, - wantErr: "missing K parameter (required for Symmetric key type)", }, { - name: "wrong algorithm", - data: []byte{ - 0xa4, // map (3) - 0x01, 0x01, // kty: OKP - 0x03, 0x26, // alg: ECDSA w/ SHA-256 - 0x20, 0x06, // curve: Ed25519 - 0x21, 0x58, 0x20, // x-coordinate: bytes(32) - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, // 32-byte value - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + name: "EC2 P-521 public", + data: mustHexToBytes("a5" + + "0102" + + "02581e62696c626f2e62616767696e7340686f626269746f6e2e6578616d706c65" + + "2003" + + "2158420072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad" + + "22584201dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475"), + want: &Key{ + KeyType: KeyTypeEC2, + KeyID: []byte("bilbo.baggins@hobbiton.example"), + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP521, + KeyLabelEC2X: mustHexToBytes("0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad"), + KeyLabelEC2Y: mustHexToBytes("01dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475"), + }, + }, + }, + // The following samples are taken from RFC8152 C.7.2. + { + name: "EC2 P-256 private", + data: mustHexToBytes("a6" + + "0102" + + "0258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65" + + "2001" + + "21582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d" + + "2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c" + + "235820aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa208cf"), + want: &Key{ + KeyType: KeyTypeEC2, + KeyID: []byte("meriadoc.brandybuck@buckland.example"), + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: mustHexToBytes("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d"), + KeyLabelEC2Y: mustHexToBytes("1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c"), + KeyLabelEC2D: mustHexToBytes("aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa208cf"), + }, + }, + }, { + name: "EC2 P-521 private", + data: mustHexToBytes("a6" + + "0102" + + "02581e62696c626f2e62616767696e7340686f626269746f6e2e6578616d706c65" + + "2003" + + "2158420072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad" + + "22584201dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475" + + "23584200085138ddabf5ca975f5860f91a08e91d6d5f9a76ad4018766a476680b55cd339e8ab6c72b5facdb2a2a50ac25bd086647dd3e2e6e99e84ca2c3609fdf177feb26d"), + want: &Key{ + KeyType: KeyTypeEC2, + KeyID: []byte("bilbo.baggins@hobbiton.example"), + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP521, + KeyLabelEC2X: mustHexToBytes("0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad"), + KeyLabelEC2Y: mustHexToBytes("01dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475"), + KeyLabelEC2D: mustHexToBytes("00085138ddabf5ca975f5860f91a08e91d6d5f9a76ad4018766a476680b55cd339e8ab6c72b5facdb2a2a50ac25bd086647dd3e2e6e99e84ca2c3609fdf177feb26d"), + }, + }, + }, { + name: "Symmetric", + data: mustHexToBytes("a3" + + "0104" + + "024a6f75722d736563726574" + + "205820849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188"), + want: &Key{ + KeyType: KeyTypeSymmetric, + KeyID: []byte("our-secret"), + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: mustHexToBytes("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188"), + }, }, - want: nil, - wantErr: `found algorithm "ES256" (expected "EdDSA")`, }, } @@ -272,18 +610,137 @@ func TestKey_MarshalCBOR(t *testing.T) { wantErr string }{ { - name: "OKP", + name: "OKP with kty and kid", key: &Key{ KeyType: KeyTypeOKP, - KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, - X: []byte{ - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + KeyID: []byte{1, 2, 3}, + }, + want: []byte{ + 0xa2, // map (2) + 0x01, 0x01, // kty: OKP + 0x02, 0x43, 0x01, 0x02, 0x03, // kid: bytes(3) + }, + }, { + name: "OKP with only kty", + key: &Key{ + KeyType: KeyTypeOKP, + }, + want: []byte{ + 0xa1, // map (1) + 0x01, 0x01, // kty: OKP + }, + }, { + name: "OKP with kty and base_iv", + key: &Key{ + KeyType: KeyTypeOKP, + BaseIV: []byte{3, 2, 1}, + }, + want: []byte{ + 0xa2, // map (2) + 0x01, 0x01, // kty: OKP + 0x05, 0x43, 0x03, 0x02, 0x01, // base_iv: bytes(3) + }, + }, { + name: "OKP with kty and alg", + key: &Key{ + KeyType: KeyTypeOKP, + Algorithm: AlgorithmEd25519, + }, + want: []byte{ + 0xa2, // map (2) + 0x01, 0x01, // kty: OKP + 0x03, 0x27, // alg: EdDSA + }, + }, { + name: "OKP with kty and private alg", + key: &Key{ + KeyType: KeyTypeOKP, + Algorithm: -70_000, + }, + want: []byte{ + 0xa2, // map (2) + 0x01, 0x01, // kty: OKP + 0x03, 0x3a, 0x00, 0x01, 0x11, 0x6f, // alg: -70000 + }, + }, { + name: "OKP with kty and key_ops", + key: &Key{ + KeyType: KeyTypeOKP, + KeyID: []byte{1, 2, 3}, + KeyOps: []KeyOp{KeyOpEncrypt, KeyOpDecrypt, -70_000}, + }, + want: []byte{ + 0xa3, // map (3) + 0x01, 0x01, // kty: OKP + 0x02, 0x43, 0x01, 0x02, 0x03, // kid: bytes(3) + 0x04, 0x83, // key_ops: array(3) + 0x03, 0x04, 0x3a, 0x00, 0x01, 0x11, 0x6f, // -70000 + }, + }, { + name: "OKP with kty and private int params", + key: &Key{ + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + 0x46: 0x47, + 0x66: 0x67, }, + }, + want: []byte{ + 0xa3, // map (3) + 0x01, 0x01, // kty: OKP + 0x18, 0x46, 0x18, 0x47, // 0x46: 0x47 (note canonical ordering) + 0x18, 0x66, 0x18, 0x67, // 0x66: 0x67 + }, + }, { + name: "OKP with kty and private mixed params", + key: &Key{ + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + 0x1234: 0x47, + "a": 0x67, + }, + }, + want: []byte{ + 0xa3, // map (3) + 0x01, 0x01, // kty: OKP + 0x19, 0x12, 0x34, 0x18, 0x47, // 0x1234: 0x47 (note canonical lexicographic ordering) + 0x61, 0x61, 0x18, 0x67, // "a": 0x67 + }, + }, { + name: "OKP duplicated params", + key: &Key{ + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + int8(10): 0, + int32(10): 1, + }, + }, + wantErr: "duplicate label 10", + }, { + name: "OKP with invalid param label", + key: &Key{ + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + int8(10): 0, + -3.5: 1, + }, + }, + wantErr: "invalid label type float64", + }, { + name: "OKP", + key: &Key{ + KeyType: KeyTypeOKP, Algorithm: AlgorithmEd25519, - Curve: CurveEd25519, + KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, + }, }, want: []byte{ 0xa5, // map (5) @@ -304,11 +761,13 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "Symmetric", key: &Key{ KeyType: KeyTypeSymmetric, - K: []byte{ - 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, - 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, - 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, - 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: []byte{ + 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, + 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, + 0x49, 0xe3, 0x88, 0x07, 0xa5, 0xc2, 0x6e, 0xf9, + 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, + }, }, }, want: []byte{ @@ -322,10 +781,13 @@ func TestKey_MarshalCBOR(t *testing.T) { }, wantErr: "", }, { - name: "unknown key type", - key: &Key{KeyType: 42}, - want: nil, - wantErr: `invalid key type: "unknown key type value 42"`, + name: "unknown key type", + key: &Key{KeyType: 42}, + want: []byte{ + 0xa1, // map (1) + 0x01, 0x18, 0x2a, // kty: 42 + }, + wantErr: "", }, } for _, tt := range tests { @@ -371,9 +833,11 @@ func TestNewOKPKey(t *testing.T) { want: &Key{ KeyType: KeyTypeOKP, Algorithm: AlgorithmEd25519, - Curve: CurveEd25519, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, wantErr: "", }, { @@ -421,10 +885,12 @@ func TestNewEC2Key(t *testing.T) { want: &Key{ KeyType: KeyTypeEC2, Algorithm: AlgorithmES256, - Curve: CurveP256, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, wantErr: "", }, { @@ -432,10 +898,12 @@ func TestNewEC2Key(t *testing.T) { want: &Key{ KeyType: KeyTypeEC2, Algorithm: AlgorithmES384, - Curve: CurveP384, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP384, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, wantErr: "", }, { @@ -443,10 +911,12 @@ func TestNewEC2Key(t *testing.T) { want: &Key{ KeyType: KeyTypeEC2, Algorithm: AlgorithmES512, - Curve: CurveP521, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP521, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, wantErr: "", }, { @@ -484,7 +954,9 @@ func TestNewSymmetricKey(t *testing.T) { }{ {"valid", args{[]byte{1, 2, 3}}, &Key{ KeyType: KeyTypeSymmetric, - K: []byte{1, 2, 3}, + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: []byte{1, 2, 3}, + }, }}, } for _, tt := range tests { @@ -568,7 +1040,9 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "OKP-Ed25519", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + }, }, AlgorithmEd25519, "", @@ -577,7 +1051,9 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "OKP-P256", &Key{ KeyType: KeyTypeOKP, - Curve: CurveP256, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveP256, + }, }, AlgorithmInvalid, `unsupported curve "P-256" for key type OKP`, @@ -586,7 +1062,9 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-P256", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + }, }, AlgorithmES256, "", @@ -595,7 +1073,9 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-P384", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP384, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP384, + }, }, AlgorithmES384, "", @@ -604,7 +1084,9 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-P521", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP521, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP521, + }, }, AlgorithmES512, "", @@ -613,7 +1095,9 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-Ed25519", &Key{ KeyType: KeyTypeEC2, - Curve: CurveEd25519, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveEd25519, + }, }, AlgorithmInvalid, `unsupported curve "Ed25519" for key type EC2`, @@ -647,10 +1131,12 @@ func TestNewKeyFromPrivate(t *testing.T) { }, &Key{ Algorithm: AlgorithmES256, KeyType: KeyTypeEC2, - Curve: CurveP256, - X: big.NewInt(1).Bytes(), - Y: big.NewInt(2).Bytes(), - D: big.NewInt(3).Bytes(), + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: big.NewInt(1).Bytes(), + KeyLabelEC2Y: big.NewInt(2).Bytes(), + KeyLabelEC2D: big.NewInt(3).Bytes(), + }, }, "", }, @@ -668,9 +1154,12 @@ func TestNewKeyFromPrivate(t *testing.T) { 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, &Key{ - Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, Curve: CurveEd25519, - X: []byte{4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - D: []byte{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: []byte{4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + KeyLabelOKPD: []byte{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }}, "", }, { @@ -705,9 +1194,11 @@ func TestNewKeyFromPublic(t *testing.T) { &Key{ Algorithm: AlgorithmES256, KeyType: KeyTypeEC2, - Curve: CurveP256, - X: big.NewInt(1).Bytes(), - Y: big.NewInt(2).Bytes(), + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: big.NewInt(1).Bytes(), + KeyLabelEC2Y: big.NewInt(2).Bytes(), + }, }, "", }, @@ -718,7 +1209,14 @@ func TestNewKeyFromPublic(t *testing.T) { }, { "ed25519", ed25519.PublicKey{1, 2, 3}, - &Key{Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, Curve: CurveEd25519, X: []byte{1, 2, 3}}, + &Key{ + Algorithm: AlgorithmEd25519, + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: []byte{1, 2, 3}, + }, + }, "", }, { @@ -754,9 +1252,11 @@ func TestKey_Signer(t *testing.T) { "without algorithm", &Key{ KeyType: KeyTypeOKP, KeyOps: []KeyOp{KeyOpSign}, - Curve: CurveEd25519, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, AlgorithmEd25519, "", @@ -765,9 +1265,11 @@ func TestKey_Signer(t *testing.T) { "without key_ops", &Key{ KeyType: KeyTypeOKP, Algorithm: AlgorithmEd25519, - Curve: CurveEd25519, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, AlgorithmEd25519, "", @@ -775,9 +1277,11 @@ func TestKey_Signer(t *testing.T) { { "invalid algorithm", &Key{ KeyType: KeyTypeOKP, - Curve: CurveP256, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveP256, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, AlgorithmInvalid, `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, @@ -785,10 +1289,12 @@ func TestKey_Signer(t *testing.T) { { "can't sign", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, KeyOps: []KeyOp{KeyOpVerify}, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, AlgorithmInvalid, ErrOpNotSupported.Error(), @@ -797,8 +1303,9 @@ func TestKey_Signer(t *testing.T) { "unsupported key", &Key{ KeyType: KeyTypeSymmetric, KeyOps: []KeyOp{KeyOpSign}, - K: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: d, + }, }, AlgorithmInvalid, `unexpected key type "Symmetric"`, @@ -832,8 +1339,10 @@ func TestKey_Verifier(t *testing.T) { "without algorithm", &Key{ KeyType: KeyTypeOKP, KeyOps: []KeyOp{KeyOpVerify}, - Curve: CurveEd25519, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + }, }, AlgorithmEd25519, "", @@ -842,8 +1351,10 @@ func TestKey_Verifier(t *testing.T) { "without key_ops", &Key{ KeyType: KeyTypeOKP, Algorithm: AlgorithmEd25519, - Curve: CurveEd25519, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + }, }, AlgorithmEd25519, "", @@ -851,8 +1362,10 @@ func TestKey_Verifier(t *testing.T) { { "invalid algorithm", &Key{ KeyType: KeyTypeOKP, - Curve: CurveP256, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveP256, + KeyLabelOKPX: x, + }, }, AlgorithmInvalid, `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, @@ -860,9 +1373,11 @@ func TestKey_Verifier(t *testing.T) { { "can't verify", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, KeyOps: []KeyOp{KeyOpSign}, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + }, }, AlgorithmInvalid, ErrOpNotSupported.Error(), @@ -871,7 +1386,9 @@ func TestKey_Verifier(t *testing.T) { "unsupported key", &Key{ KeyType: KeyTypeSymmetric, KeyOps: []KeyOp{KeyOpVerify}, - K: x, + Params: map[interface{}]interface{}{ + KeyLabelSymmetricK: x, + }, }, AlgorithmInvalid, `unexpected key type "Symmetric"`, @@ -906,9 +1423,11 @@ func TestKey_PrivateKey(t *testing.T) { { "CurveEd25519", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, ed25519.PrivateKey{ d[0], d[1], d[2], d[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -918,10 +1437,12 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP256", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ @@ -935,10 +1456,12 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP384", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP384, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP384, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ @@ -952,10 +1475,12 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP521", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP521, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP521, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ @@ -975,62 +1500,76 @@ func TestKey_PrivateKey(t *testing.T) { }, { "OKP missing X", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPD: d, + }, }, nil, ErrOKPNoPub.Error(), }, { "OKP missing D", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + }, }, nil, ErrNotPrivKey.Error(), }, { "OKP unknown curve", &Key{ KeyType: KeyTypeOKP, - Curve: 70, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: 70, + KeyLabelOKPX: x, + KeyLabelOKPD: d, + }, }, nil, `unsupported curve "unknown curve value 70" for key type OKP`, }, { "EC2 missing X", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, nil, ErrEC2NoPub.Error(), }, { "EC2 missing Y", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - X: x, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: x, + KeyLabelEC2D: d, + }, }, nil, ErrEC2NoPub.Error(), }, { "EC2 missing D", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - X: x, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + }, }, nil, ErrNotPrivKey.Error(), }, { "EC2 unknown curve", &Key{ KeyType: KeyTypeEC2, - Curve: 70, - X: x, - Y: y, - D: d, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: 70, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, + }, }, nil, `unsupported curve "unknown curve value 70" for key type EC2`, @@ -1062,17 +1601,21 @@ func TestKey_PublicKey(t *testing.T) { { "CurveEd25519", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: x, + }, }, ed25519.PublicKey(x), "", }, { "CurveP256", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - X: x, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + }, }, &ecdsa.PublicKey{ Curve: elliptic.P256(), @@ -1083,9 +1626,11 @@ func TestKey_PublicKey(t *testing.T) { }, { "CurveP384", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP384, - X: x, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP384, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + }, }, &ecdsa.PublicKey{ Curve: elliptic.P384(), @@ -1096,9 +1641,11 @@ func TestKey_PublicKey(t *testing.T) { }, { "CurveP521", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP521, - X: x, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP521, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + }, }, &ecdsa.PublicKey{ Curve: elliptic.P521(), @@ -1112,44 +1659,59 @@ func TestKey_PublicKey(t *testing.T) { }, nil, `unexpected key type "unknown key type value 7"`, + }, { + "invalid key type", &Key{ + KeyType: KeyTypeInvalid, + }, + nil, + `invalid kty value 0`, }, { "OKP missing X", &Key{ KeyType: KeyTypeOKP, - Curve: CurveEd25519, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + }, }, nil, ErrOKPNoPub.Error(), }, { "OKP unknown curve", &Key{ KeyType: KeyTypeOKP, - Curve: 70, - X: x, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: 70, + KeyLabelOKPX: x, + }, }, nil, `unsupported curve "unknown curve value 70" for key type OKP`, }, { "EC2 missing X", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2Y: y, + }, }, nil, ErrEC2NoPub.Error(), }, { "EC2 missing Y", &Key{ KeyType: KeyTypeEC2, - Curve: CurveP256, - X: x, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: x, + }, }, nil, ErrEC2NoPub.Error(), }, { "EC2 unknown curve", &Key{ KeyType: KeyTypeEC2, - Curve: 70, - X: x, - Y: y, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: 70, + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + }, }, nil, `unsupported curve "unknown curve value 70" for key type EC2`, @@ -1188,12 +1750,16 @@ func TestKeyType_String(t *testing.T) { } func TestCurve_String(t *testing.T) { - // test string conversions not exercised by other test cases tests := []struct { kt Curve want string }{ + {CurveP256, "P-256"}, + {CurveP384, "P-384"}, + {CurveP521, "P-521"}, {CurveX25519, "X25519"}, + {CurveX448, "X448"}, + {CurveEd25519, "Ed25519"}, {CurveEd448, "Ed448"}, } for _, tt := range tests { @@ -1204,3 +1770,41 @@ func TestCurve_String(t *testing.T) { }) } } + +func TestKeyOpFromString(t *testing.T) { + tests := []struct { + val string + want KeyOp + want1 bool + }{ + {"sign", KeyOpSign, true}, + {"verify", KeyOpVerify, true}, + {"encrypt", KeyOpEncrypt, true}, + {"decrypt", KeyOpDecrypt, true}, + {"wrapKey", KeyOpWrapKey, true}, + {"unwrapKey", KeyOpUnwrapKey, true}, + {"deriveKey", KeyOpDeriveKey, true}, + {"deriveBits", KeyOpDeriveBits, true}, + {"", KeyOp(0), false}, + {"foo", KeyOp(0), false}, + } + for _, tt := range tests { + t.Run(tt.val, func(t *testing.T) { + got, got1 := KeyOpFromString(tt.val) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("KeyOpFromString() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("KeyOpFromString() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func mustHexToBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} From ec64bcbc8ebb2cf1176507c727cb4a31d886e605 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Wed, 19 Jul 2023 16:03:14 +0200 Subject: [PATCH 09/25] Validate key sizes and allow signing with empty public keys (#159) Signed-off-by: qmuntal --- key.go | 96 +++++++++----- key_test.go | 354 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 286 insertions(+), 164 deletions(-) diff --git a/key.go b/key.go index 038ee27..f7376d7 100644 --- a/key.go +++ b/key.go @@ -414,6 +414,15 @@ func NewKeyFromPrivate(priv crypto.PrivateKey) (*Key, error) { } } +var ( + // The following errors are used multiple times + // in Key.validate. We declare them here to avoid + // duplication. They are not considered public errors. + errCoordOverflow = fmt.Errorf("%w: overflowing coordinate", ErrInvalidKey) + errReqParamsMissing = fmt.Errorf("%w: required parameters missing", ErrInvalidKey) + errInvalidCurve = fmt.Errorf("%w: curve not supported for the given key type", ErrInvalidKey) +) + // Validate ensures that the parameters set inside the Key are internally // consistent (e.g., that the key type is appropriate to the curve). // It also checks that the key is valid for the requested operation. @@ -432,14 +441,20 @@ func (k Key) validate(op KeyOp) error { } } if crv == CurveInvalid || (len(x) == 0 && len(y) == 0 && len(d) == 0) { - return ErrInvalidKey + return errReqParamsMissing + } + if size := curveSize(crv); size > 0 { + // RFC 8152 Section 13.1.1 says that x and y leading zero octets MUST be preserved, + // but the Go crypto/elliptic package trims them. So we relax the check + // here to allow for omitted leading zero octets, we will add them back + // when marshaling. + if len(x) > size || len(y) > size || len(d) > size { + return errCoordOverflow + } } switch crv { case CurveX25519, CurveX448, CurveEd25519, CurveEd448: - return fmt.Errorf( - "Key type mismatch for curve %q (must be OKP, found EC2)", - crv.String(), - ) + return errInvalidCurve default: // ok -- a key may contain a currently unsupported curve // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 @@ -457,14 +472,14 @@ func (k Key) validate(op KeyOp) error { } } if crv == CurveInvalid || (len(x) == 0 && len(d) == 0) { - return ErrInvalidKey + return errReqParamsMissing + } + if (len(x) > 0 && len(x) != ed25519.PublicKeySize) || (len(d) > 0 && len(d) != ed25519.SeedSize) { + return errCoordOverflow } switch crv { case CurveP256, CurveP384, CurveP521: - return fmt.Errorf( - "Key type mismatch for curve %q (must be EC2, found OKP)", - crv.String(), - ) + return errInvalidCurve default: // ok -- a key may contain a currently unsupported curve // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 @@ -472,10 +487,10 @@ func (k Key) validate(op KeyOp) error { case KeyTypeSymmetric: k := k.Symmetric() if len(k) == 0 { - return ErrInvalidKey + return errReqParamsMissing } case KeyTypeInvalid: - return errors.New("invalid kty value 0") + return fmt.Errorf("%w: kty value 0", ErrInvalidKey) default: // Unknown key type, we can't validate custom parameters. } @@ -540,6 +555,18 @@ func (k *Key) MarshalCBOR() ([]byte, error) { existing[lbl] = struct{}{} tmp[lbl] = v } + if k.KeyType == KeyTypeEC2 { + // If EC2 key, ensure that x and y are padded to the correct size. + crv, x, y, _ := k.EC2() + if size := curveSize(crv); size > 0 { + if 0 < len(x) && len(x) < size { + tmp[KeyLabelEC2X] = append(make([]byte, size-len(x), size), x...) + } + if 0 < len(y) && len(y) < size { + tmp[KeyLabelEC2Y] = append(make([]byte, size-len(y), size), y...) + } + } + } return encMode.Marshal(tmp) } @@ -672,14 +699,6 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { switch alg { case AlgorithmES256, AlgorithmES384, AlgorithmES512: - _, x, y, d := k.EC2() - // RFC8152 allows omitting X and Y from private keys; - // crypto.PrivateKey assumes they are available. - // see https://www.rfc-editor.org/rfc/rfc8152#section-13.1.1 - if len(x) == 0 || len(y) == 0 { - return nil, ErrEC2NoPub - } - var curve elliptic.Curve switch alg { @@ -691,22 +710,24 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { curve = elliptic.P521() } - priv := &ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}, - D: new(big.Int), + _, x, y, d := k.EC2() + var bx, by *big.Int + if len(x) == 0 || len(y) == 0 { + bx, by = curve.ScalarBaseMult(d) + } else { + bx = new(big.Int).SetBytes(x) + by = new(big.Int).SetBytes(y) } - priv.X.SetBytes(x) - priv.Y.SetBytes(y) - priv.D.SetBytes(d) + bd := new(big.Int).SetBytes(d) - return priv, nil + return &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{Curve: curve, X: bx, Y: by}, + D: bd, + }, nil case AlgorithmEd25519: _, x, d := k.OKP() - // RFC8152 allows omitting X from private keys; - // crypto.PrivateKey assumes it is available. - // see https://www.rfc-editor.org/rfc/rfc8152#section-13.2 if len(x) == 0 { - return nil, ErrOKPNoPub + return ed25519.NewKeyFromSeed(d), nil } buf := make([]byte, ed25519.PrivateKeySize) @@ -820,6 +841,19 @@ func algorithmFromEllipticCurve(c elliptic.Curve) Algorithm { } } +func curveSize(crv Curve) int { + var bitSize int + switch crv { + case CurveP256: + bitSize = elliptic.P256().Params().BitSize + case CurveP384: + bitSize = elliptic.P384().Params().BitSize + case CurveP521: + bitSize = elliptic.P521().Params().BitSize + } + return (bitSize + 7) / 8 +} + func decodeBytes(dic map[interface{}]interface{}, lbl interface{}) (b []byte, ok bool, err error) { val, ok := dic[lbl] if !ok { diff --git a/key_test.go b/key_test.go index 02a6e22..f234a3a 100644 --- a/key_test.go +++ b/key_test.go @@ -357,7 +357,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x01, 0x01, // kty: OKP }, want: nil, - wantErr: ErrInvalidKey.Error(), + wantErr: "invalid key: required parameters missing", }, { name: "EC2 missing curve", data: []byte{ @@ -365,7 +365,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x01, 0x02, // kty: EC2 }, want: nil, - wantErr: ErrInvalidKey.Error(), + wantErr: "invalid key: required parameters missing", }, { name: "OKP invalid curve", data: []byte{ @@ -379,7 +379,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, want: nil, - wantErr: `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, + wantErr: "invalid key: curve not supported for the given key type", }, { name: "EC2 invalid curve", data: []byte{ @@ -398,7 +398,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, want: nil, - wantErr: `Key type mismatch for curve "Ed25519" (must be OKP, found EC2)`, + wantErr: "invalid key: curve not supported for the given key type", }, { name: "Symmetric missing K", data: []byte{ @@ -406,7 +406,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x01, 0x04, // kty: Symmetric }, want: nil, - wantErr: ErrInvalidKey.Error(), + wantErr: "invalid key: required parameters missing", }, { name: "EC2 invalid algorithm", data: []byte{ @@ -757,6 +757,34 @@ func TestKey_MarshalCBOR(t *testing.T) { 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, wantErr: "", + }, { + name: "EC2 with short x and y", + key: &Key{ + KeyType: KeyTypeEC2, + Algorithm: AlgorithmES256, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: []byte{0x01}, + KeyLabelEC2Y: []byte{0x02, 0x03}, + }, + }, + want: []byte{ + 0xa5, // map (4) + 0x01, 0x02, // kty: EC2 + 0x03, 0x26, // alg: ES256 + 0x20, 0x01, // curve: P256 + 0x21, 0x58, 0x20, // x-coordinate: bytes(32) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32-byte value + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x22, 0x58, 0x20, // y-coordinate: bytes(32) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 32-byte value + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, + }, + wantErr: "", }, { name: "Symmetric", key: &Key{ @@ -805,18 +833,7 @@ func TestKey_MarshalCBOR(t *testing.T) { } func TestNewOKPKey(t *testing.T) { - x := []byte{ - 0x30, 0xa0, 0x42, 0x4c, 0xd2, 0x1c, 0x29, 0x44, - 0x83, 0x8a, 0x2d, 0x75, 0xc9, 0x2b, 0x37, 0xe7, - 0x6e, 0xa2, 0x0d, 0x9f, 0x00, 0x89, 0x3a, 0x3b, - 0x4e, 0xee, 0x8a, 0x3c, 0x0a, 0xaf, 0xec, 0x3e, - } - d := []byte{ - 0xe0, 0x4b, 0x65, 0xe9, 0x24, 0x56, 0xd9, 0x88, - 0x8b, 0x52, 0xb3, 0x79, 0xbd, 0xfb, 0xd5, 0x1e, - 0xe8, 0x69, 0xef, 0x1f, 0x0f, 0xc6, 0x5b, 0x66, - 0x59, 0x69, 0x5b, 0x6c, 0xce, 0x08, 0x17, 0x23, - } + x, d := newEd25519(t) type args struct { alg Algorithm x []byte @@ -847,7 +864,7 @@ func TestNewOKPKey(t *testing.T) { }, { name: "x and d missing", args: args{AlgorithmEd25519, nil, nil}, want: nil, - wantErr: ErrInvalidKey.Error(), + wantErr: "invalid key: required parameters missing", }, } for _, tt := range tests { @@ -865,9 +882,9 @@ func TestNewOKPKey(t *testing.T) { } func TestNewEC2Key(t *testing.T) { - x := []byte{1, 2, 3} - y := []byte{4, 5, 6} - d := []byte{7, 8, 9} + ec256x, ec256y, ec256d := newEC2(t, elliptic.P256()) + ec384x, ec384y, ec384d := newEC2(t, elliptic.P384()) + ec521x, ec521y, ec521d := newEC2(t, elliptic.P521()) type args struct { alg Algorithm x []byte @@ -881,52 +898,52 @@ func TestNewEC2Key(t *testing.T) { wantErr string }{ { - name: "valid ES256", args: args{AlgorithmES256, x, y, d}, + name: "valid ES256", args: args{AlgorithmES256, ec256x, ec256y, ec256d}, want: &Key{ KeyType: KeyTypeEC2, Algorithm: AlgorithmES256, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, + KeyLabelEC2D: ec256d, }, }, wantErr: "", }, { - name: "valid ES384", args: args{AlgorithmES384, x, y, d}, + name: "valid ES384", args: args{AlgorithmES384, ec384x, ec384y, ec384d}, want: &Key{ KeyType: KeyTypeEC2, Algorithm: AlgorithmES384, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec384x, + KeyLabelEC2Y: ec384y, + KeyLabelEC2D: ec384d, }, }, wantErr: "", }, { - name: "valid ES521", args: args{AlgorithmES512, x, y, d}, + name: "valid ES521", args: args{AlgorithmES512, ec521x, ec521y, ec521d}, want: &Key{ KeyType: KeyTypeEC2, Algorithm: AlgorithmES512, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec521x, + KeyLabelEC2Y: ec521y, + KeyLabelEC2D: ec521d, }, }, wantErr: "", }, { - name: "invalid alg", args: args{Algorithm(-100), x, y, d}, + name: "invalid alg", args: args{Algorithm(-100), ec256x, ec256y, ec256d}, want: nil, wantErr: `unsupported algorithm "unknown algorithm value -100"`, }, { name: "x, y and d missing", args: args{AlgorithmES512, nil, nil, nil}, want: nil, - wantErr: ErrInvalidKey.Error(), + wantErr: "invalid key: required parameters missing", }, } for _, tt := range tests { @@ -1118,6 +1135,8 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { } func TestNewKeyFromPrivate(t *testing.T) { + x, y, d := newEC2(t, elliptic.P256()) + okpx, okpd := newEd25519(t) tests := []struct { name string k crypto.PrivateKey @@ -1126,16 +1145,16 @@ func TestNewKeyFromPrivate(t *testing.T) { }{ { "ecdsa", &ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(1), Y: big.NewInt(2)}, - D: big.NewInt(3), + PublicKey: ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(x), Y: new(big.Int).SetBytes(y)}, + D: new(big.Int).SetBytes(d), }, &Key{ Algorithm: AlgorithmES256, KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: big.NewInt(1).Bytes(), - KeyLabelEC2Y: big.NewInt(2).Bytes(), - KeyLabelEC2D: big.NewInt(3).Bytes(), + KeyLabelEC2X: x, + KeyLabelEC2Y: y, + KeyLabelEC2D: d, }, }, "", @@ -1149,16 +1168,13 @@ func TestNewKeyFromPrivate(t *testing.T) { "unsupported curve: ", }, { - "ed25519", ed25519.PrivateKey{ - 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }, + "ed25519", ed25519.PrivateKey(append(okpd, okpx...)), &Key{ Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, - KeyLabelOKPX: []byte{4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - KeyLabelOKPD: []byte{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + KeyLabelOKPX: okpx, + KeyLabelOKPD: okpd, }}, "", }, @@ -1183,6 +1199,8 @@ func TestNewKeyFromPrivate(t *testing.T) { } func TestNewKeyFromPublic(t *testing.T) { + ecx, ecy, _ := newEC2(t, elliptic.P256()) + okpx, _ := newEd25519(t) tests := []struct { name string k crypto.PublicKey @@ -1190,14 +1208,14 @@ func TestNewKeyFromPublic(t *testing.T) { wantErr string }{ { - "ecdsa", &ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(1), Y: big.NewInt(2)}, + "ecdsa", &ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(ecx), Y: new(big.Int).SetBytes(ecy)}, &Key{ Algorithm: AlgorithmES256, KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: big.NewInt(1).Bytes(), - KeyLabelEC2Y: big.NewInt(2).Bytes(), + KeyLabelEC2X: ecx, + KeyLabelEC2Y: ecy, }, }, "", @@ -1208,13 +1226,13 @@ func TestNewKeyFromPublic(t *testing.T) { "unsupported curve: ", }, { - "ed25519", ed25519.PublicKey{1, 2, 3}, + "ed25519", ed25519.PublicKey(okpx), &Key{ Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, - KeyLabelOKPX: []byte{1, 2, 3}, + KeyLabelOKPX: okpx, }, }, "", @@ -1240,8 +1258,7 @@ func TestNewKeyFromPublic(t *testing.T) { } func TestKey_Signer(t *testing.T) { - x := []byte{0xde, 0xad, 0xbe, 0xef} - d := []byte{0xde, 0xad, 0xbe, 0xef} + x, d := newEd25519(t) tests := []struct { name string k *Key @@ -1284,7 +1301,7 @@ func TestKey_Signer(t *testing.T) { }, }, AlgorithmInvalid, - `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, + "invalid key: curve not supported for the given key type", }, { "can't sign", &Key{ @@ -1328,7 +1345,7 @@ func TestKey_Signer(t *testing.T) { } func TestKey_Verifier(t *testing.T) { - x := []byte{0xde, 0xad, 0xbe, 0xef} + x, _ := newEd25519(t) tests := []struct { name string k *Key @@ -1368,7 +1385,7 @@ func TestKey_Verifier(t *testing.T) { }, }, AlgorithmInvalid, - `Key type mismatch for curve "P-256" (must be EC2, found OKP)`, + "invalid key: curve not supported for the given key type", }, { "can't verify", &Key{ @@ -1411,9 +1428,10 @@ func TestKey_Verifier(t *testing.T) { } func TestKey_PrivateKey(t *testing.T) { - x := []byte{0xde, 0xad, 0xbe, 0xef} - y := []byte{0xef, 0xbe, 0xad, 0xde} - d := []byte{0xad, 0xde, 0xef, 0xbe} + ec256x, ec256y, ec256d := newEC2(t, elliptic.P256()) + ec384x, ec384y, ec384d := newEC2(t, elliptic.P384()) + ec521x, ec521y, ec521d := newEC2(t, elliptic.P521()) + okpx, okpd := newEd25519(t) tests := []struct { name string k *Key @@ -1425,32 +1443,56 @@ func TestKey_PrivateKey(t *testing.T) { KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, - KeyLabelOKPX: x, - KeyLabelOKPD: d, + KeyLabelOKPX: okpx, + KeyLabelOKPD: okpd, }, }, - ed25519.PrivateKey{ - d[0], d[1], d[2], d[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - x[0], x[1], x[2], x[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ed25519.PrivateKey(append(okpd, okpx...)), + "", + }, { + "CurveEd25519 missing x", &Key{ + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPD: okpd, + }, }, + ed25519.PrivateKey(append(okpd, okpx...)), "", }, { "CurveP256", &Key{ KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, + KeyLabelEC2D: ec256d, + }, + }, + &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P256(), + X: new(big.Int).SetBytes(ec256x), + Y: new(big.Int).SetBytes(ec256y), + }, + D: new(big.Int).SetBytes(ec256d), + }, + "", + }, { + "CurveP256 missing x and y", &Key{ + KeyType: KeyTypeEC2, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2D: ec256d, }, }, &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ Curve: elliptic.P256(), - X: new(big.Int).SetBytes(x), - Y: new(big.Int).SetBytes(y), + X: new(big.Int).SetBytes(ec256x), + Y: new(big.Int).SetBytes(ec256y), }, - D: new(big.Int).SetBytes(d), + D: new(big.Int).SetBytes(ec256d), }, "", }, { @@ -1458,18 +1500,18 @@ func TestKey_PrivateKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec384x, + KeyLabelEC2Y: ec384y, + KeyLabelEC2D: ec384d, }, }, &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ Curve: elliptic.P384(), - X: new(big.Int).SetBytes(x), - Y: new(big.Int).SetBytes(y), + X: new(big.Int).SetBytes(ec384x), + Y: new(big.Int).SetBytes(ec384y), }, - D: new(big.Int).SetBytes(d), + D: new(big.Int).SetBytes(ec384d), }, "", }, { @@ -1477,18 +1519,18 @@ func TestKey_PrivateKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec521x, + KeyLabelEC2Y: ec521y, + KeyLabelEC2D: ec521d, }, }, &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{ Curve: elliptic.P521(), - X: new(big.Int).SetBytes(x), - Y: new(big.Int).SetBytes(y), + X: new(big.Int).SetBytes(ec521x), + Y: new(big.Int).SetBytes(ec521y), }, - D: new(big.Int).SetBytes(d), + D: new(big.Int).SetBytes(ec521d), }, "", }, { @@ -1498,81 +1540,107 @@ func TestKey_PrivateKey(t *testing.T) { nil, `unexpected key type "unknown key type value 7"`, }, { - "OKP missing X", &Key{ + "OKP unknown curve", &Key{ KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ - KeyLabelOKPCurve: CurveEd25519, - KeyLabelOKPD: d, + KeyLabelOKPCurve: 70, + KeyLabelOKPX: okpx, + KeyLabelOKPD: okpd, }, }, nil, - ErrOKPNoPub.Error(), + `unsupported curve "unknown curve value 70" for key type OKP`, }, { - "OKP missing D", &Key{ + "OKP missing d", &Key{ KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, - KeyLabelOKPX: x, + KeyLabelOKPX: okpx, }, }, nil, ErrNotPrivKey.Error(), }, { - "OKP unknown curve", &Key{ + "OKP incorrect x size", &Key{ KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ - KeyLabelOKPCurve: 70, - KeyLabelOKPX: x, - KeyLabelOKPD: d, + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: make([]byte, 10), + KeyLabelOKPD: okpd, }, }, nil, - `unsupported curve "unknown curve value 70" for key type OKP`, + "invalid key: overflowing coordinate", }, { - "EC2 missing X", &Key{ + "OKP incorrect d size", &Key{ + KeyType: KeyTypeOKP, + Params: map[interface{}]interface{}{ + KeyLabelOKPCurve: CurveEd25519, + KeyLabelOKPX: okpx, + KeyLabelOKPD: make([]byte, 5), + }, + }, + nil, + "invalid key: overflowing coordinate", + }, { + "EC2 missing D", &Key{ KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, }, }, nil, - ErrEC2NoPub.Error(), + ErrNotPrivKey.Error(), }, { - "EC2 missing Y", &Key{ + "EC2 unknown curve", &Key{ + KeyType: KeyTypeEC2, + Params: map[interface{}]interface{}{ + KeyLabelEC2Curve: 70, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, + KeyLabelEC2D: ec256d, + }, + }, + nil, + `unsupported curve "unknown curve value 70" for key type EC2`, + }, { + "EC2 incorrect x size", &Key{ KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: x, - KeyLabelEC2D: d, + KeyLabelEC2X: ec384x, + KeyLabelEC2Y: ec256y, + KeyLabelEC2D: ec256d, }, }, nil, - ErrEC2NoPub.Error(), + "invalid key: overflowing coordinate", }, { - "EC2 missing D", &Key{ + "EC2 incorrect y size", &Key{ KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec384y, + KeyLabelEC2D: ec256d, }, }, nil, - ErrNotPrivKey.Error(), + "invalid key: overflowing coordinate", }, { - "EC2 unknown curve", &Key{ + "EC2 incorrect d size", &Key{ KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ - KeyLabelEC2Curve: 70, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, - KeyLabelEC2D: d, + KeyLabelEC2Curve: CurveP256, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, + KeyLabelEC2D: ec384d, }, }, nil, - `unsupported curve "unknown curve value 70" for key type EC2`, + "invalid key: overflowing coordinate", }, } for _, tt := range tests { @@ -1590,8 +1658,10 @@ func TestKey_PrivateKey(t *testing.T) { } func TestKey_PublicKey(t *testing.T) { - x := []byte{0xde, 0xad, 0xbe, 0xef} - y := []byte{0xef, 0xbe, 0xad, 0xde} + ec256x, ec256y, _ := newEC2(t, elliptic.P256()) + ec384x, ec384y, _ := newEC2(t, elliptic.P384()) + ec521x, ec521y, _ := newEC2(t, elliptic.P521()) + okpx, _ := newEd25519(t) tests := []struct { name string k *Key @@ -1603,24 +1673,24 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, - KeyLabelOKPX: x, + KeyLabelOKPX: okpx, }, }, - ed25519.PublicKey(x), + ed25519.PublicKey(okpx), "", }, { "CurveP256", &Key{ KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, }, }, &ecdsa.PublicKey{ Curve: elliptic.P256(), - X: new(big.Int).SetBytes(x), - Y: new(big.Int).SetBytes(y), + X: new(big.Int).SetBytes(ec256x), + Y: new(big.Int).SetBytes(ec256y), }, "", }, { @@ -1628,14 +1698,14 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, + KeyLabelEC2X: ec384x, + KeyLabelEC2Y: ec384y, }, }, &ecdsa.PublicKey{ Curve: elliptic.P384(), - X: new(big.Int).SetBytes(x), - Y: new(big.Int).SetBytes(y), + X: new(big.Int).SetBytes(ec384x), + Y: new(big.Int).SetBytes(ec384y), }, "", }, { @@ -1643,14 +1713,14 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, + KeyLabelEC2X: ec521x, + KeyLabelEC2Y: ec521y, }, }, &ecdsa.PublicKey{ Curve: elliptic.P521(), - X: new(big.Int).SetBytes(x), - Y: new(big.Int).SetBytes(y), + X: new(big.Int).SetBytes(ec521x), + Y: new(big.Int).SetBytes(ec521y), }, "", }, { @@ -1664,7 +1734,7 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeInvalid, }, nil, - `invalid kty value 0`, + `invalid key: kty value 0`, }, { "OKP missing X", &Key{ KeyType: KeyTypeOKP, @@ -1679,7 +1749,7 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: 70, - KeyLabelOKPX: x, + KeyLabelOKPX: okpx, }, }, nil, @@ -1689,7 +1759,7 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2Y: y, + KeyLabelEC2Y: ec256y, }, }, nil, @@ -1699,7 +1769,7 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, - KeyLabelEC2X: x, + KeyLabelEC2X: ec256x, }, }, nil, @@ -1709,8 +1779,8 @@ func TestKey_PublicKey(t *testing.T) { KeyType: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: 70, - KeyLabelEC2X: x, - KeyLabelEC2Y: y, + KeyLabelEC2X: ec256x, + KeyLabelEC2Y: ec256y, }, }, nil, @@ -1808,3 +1878,21 @@ func mustHexToBytes(s string) []byte { } return b } + +func newEd25519(t *testing.T) (x, d []byte) { + t.Helper() + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + return pub, priv[:32] +} + +func newEC2(t *testing.T, crv elliptic.Curve) (x, y, d []byte) { + t.Helper() + priv, err := ecdsa.GenerateKey(crv, rand.Reader) + if err != nil { + t.Fatal(err) + } + return priv.X.Bytes(), priv.Y.Bytes(), priv.D.Bytes() +} From 88e7f1e53936b5e669d1b8b4b893e1be32d293f7 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Mon, 24 Jul 2023 12:44:25 +0200 Subject: [PATCH 10/25] Deprecate AlgorithmEd25519 and provide AlgorithmEdDSA instead (#160) Signed-off-by: qmuntal --- README.md | 2 +- algorithm.go | 8 +++++++- algorithm_test.go | 2 +- ed25519.go | 4 ++-- ed25519_test.go | 8 ++++---- fuzz_test.go | 4 ++-- key.go | 12 ++++++------ key_test.go | 30 +++++++++++++++--------------- signer.go | 2 +- signer_test.go | 4 ++-- verifier.go | 2 +- verifier_test.go | 4 ++-- 12 files changed, 44 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 74a8f77..e93536e 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ These are the required packages for each built-in cose.Algorithm: - cose.AlgorithmPS256, cose.AlgorithmES256: `crypto/sha256` - cose.AlgorithmPS384, cose.AlgorithmPS512, cose.AlgorithmES384, cose.AlgorithmES512: `crypto/sha512` -- cose.AlgorithmEd25519: none +- cose.AlgorithmEdDSA: none ## Features diff --git a/algorithm.go b/algorithm.go index 9e2f7d9..0a22090 100644 --- a/algorithm.go +++ b/algorithm.go @@ -35,8 +35,14 @@ const ( AlgorithmES512 Algorithm = -36 // PureEdDSA by RFC 8152. + // + // Deprecated: use AlgorithmEdDSA instead, which has + // the same value but with a more accurate name. AlgorithmEd25519 Algorithm = -8 + // PureEdDSA by RFC 8152. + AlgorithmEdDSA Algorithm = -8 + // An invalid/unrecognised algorithm. AlgorithmInvalid Algorithm = 0 ) @@ -65,7 +71,7 @@ func (a Algorithm) String() string { return "ES384" case AlgorithmES512: return "ES512" - case AlgorithmEd25519: + case AlgorithmEdDSA: // As stated in RFC 8152 8.2, only the pure EdDSA version is used for // COSE. return "EdDSA" diff --git a/algorithm_test.go b/algorithm_test.go index b839f2e..1bf1aa0 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -96,7 +96,7 @@ func TestAlgorithm_computeHash(t *testing.T) { }, { name: "Ed25519", - alg: AlgorithmEd25519, + alg: AlgorithmEdDSA, wantErr: ErrUnavailableHashFunc, }, { diff --git a/ed25519.go b/ed25519.go index ae88740..ebbf638 100644 --- a/ed25519.go +++ b/ed25519.go @@ -13,7 +13,7 @@ type ed25519Signer struct { // Algorithm returns the signing algorithm associated with the private key. func (es *ed25519Signer) Algorithm() Algorithm { - return AlgorithmEd25519 + return AlgorithmEdDSA } // Sign signs message content with the private key, possibly using entropy from @@ -34,7 +34,7 @@ type ed25519Verifier struct { // Algorithm returns the signing algorithm associated with the public key. func (ev *ed25519Verifier) Algorithm() Algorithm { - return AlgorithmEd25519 + return AlgorithmEdDSA } // Verify verifies message content with the public key, returning nil for diff --git a/ed25519_test.go b/ed25519_test.go index d23e65b..b0d9f29 100644 --- a/ed25519_test.go +++ b/ed25519_test.go @@ -17,7 +17,7 @@ func generateTestEd25519Key(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey func Test_ed25519Signer(t *testing.T) { // generate key - alg := AlgorithmEd25519 + alg := AlgorithmEdDSA _, key := generateTestEd25519Key(t) // set up signer @@ -51,7 +51,7 @@ func Test_ed25519Signer(t *testing.T) { func Test_ed25519Verifier_Verify_Success(t *testing.T) { // generate key - alg := AlgorithmEd25519 + alg := AlgorithmEdDSA _, key := generateTestEd25519Key(t) // generate a valid signature @@ -77,7 +77,7 @@ func Test_ed25519Verifier_Verify_Success(t *testing.T) { func Test_ed25519Verifier_Verify_KeyMismatch(t *testing.T) { // generate key - alg := AlgorithmEd25519 + alg := AlgorithmEdDSA _, key := generateTestEd25519Key(t) // generate a valid signature @@ -97,7 +97,7 @@ func Test_ed25519Verifier_Verify_KeyMismatch(t *testing.T) { func Test_ed25519Verifier_Verify_InvalidSignature(t *testing.T) { // generate key - alg := AlgorithmEd25519 + alg := AlgorithmEdDSA vk, sk := generateTestEd25519Key(t) // generate a valid signature with a tampered one diff --git a/fuzz_test.go b/fuzz_test.go index 90db8aa..4e697a1 100644 --- a/fuzz_test.go +++ b/fuzz_test.go @@ -24,7 +24,7 @@ import ( var supportedAlgorithms = [...]cose.Algorithm{ cose.AlgorithmPS256, cose.AlgorithmPS384, cose.AlgorithmPS512, cose.AlgorithmES256, cose.AlgorithmES384, cose.AlgorithmES512, - cose.AlgorithmEd25519, + cose.AlgorithmEdDSA, } func FuzzSign1Message_UnmarshalCBOR(f *testing.F) { @@ -181,7 +181,7 @@ func newSignerWithEphemeralKey(alg cose.Algorithm) (sv signVerifier, err error) key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) case cose.AlgorithmES512: key, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) - case cose.AlgorithmEd25519: + case cose.AlgorithmEdDSA: _, key, err = ed25519.GenerateKey(rand.Reader) default: err = cose.ErrAlgorithmNotSupported diff --git a/key.go b/key.go index f7376d7..2496271 100644 --- a/key.go +++ b/key.go @@ -240,7 +240,7 @@ type Key struct { // NewOKPKey returns a Key created using the provided Octet Key Pair data. func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { - if alg != AlgorithmEd25519 { + if alg != AlgorithmEdDSA { return nil, fmt.Errorf("unsupported algorithm %q", alg) } @@ -389,7 +389,7 @@ func NewKeyFromPublic(pub crypto.PublicKey) (*Key, error) { return NewEC2Key(alg, vk.X.Bytes(), vk.Y.Bytes(), nil) case ed25519.PublicKey: - return NewOKPKey(AlgorithmEd25519, []byte(vk), nil) + return NewOKPKey(AlgorithmEdDSA, []byte(vk), nil) default: return nil, ErrInvalidPubKey } @@ -408,7 +408,7 @@ func NewKeyFromPrivate(priv crypto.PrivateKey) (*Key, error) { return NewEC2Key(alg, sk.X.Bytes(), sk.Y.Bytes(), sk.D.Bytes()) case ed25519.PrivateKey: - return NewOKPKey(AlgorithmEd25519, []byte(sk[32:]), []byte(sk[:32])) + return NewOKPKey(AlgorithmEdDSA, []byte(sk[32:]), []byte(sk[:32])) default: return nil, ErrInvalidPrivKey } @@ -679,7 +679,7 @@ func (k *Key) PublicKey() (crypto.PublicKey, error) { pub.Y.SetBytes(y) return pub, nil - case AlgorithmEd25519: + case AlgorithmEdDSA: _, x, _ := k.OKP() return ed25519.PublicKey(x), nil default: @@ -724,7 +724,7 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { PublicKey: ecdsa.PublicKey{Curve: curve, X: bx, Y: by}, D: bd, }, nil - case AlgorithmEd25519: + case AlgorithmEdDSA: _, x, d := k.OKP() if len(x) == 0 { return ed25519.NewKeyFromSeed(d), nil @@ -817,7 +817,7 @@ func (k *Key) deriveAlgorithm() (Algorithm, error) { crv, _, _ := k.OKP() switch crv { case CurveEd25519: - return AlgorithmEd25519, nil + return AlgorithmEdDSA, nil default: return AlgorithmInvalid, fmt.Errorf( "unsupported curve %q for key type OKP", crv.String()) diff --git a/key_test.go b/key_test.go index f234a3a..ec1e064 100644 --- a/key_test.go +++ b/key_test.go @@ -457,7 +457,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { }, want: &Key{ KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, KeyOps: []KeyOp{KeyOpVerify, KeyOpSign}, BaseIV: []byte{0x03, 0x02, 0x01}, Params: map[interface{}]interface{}{ @@ -644,7 +644,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "OKP with kty and alg", key: &Key{ KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, }, want: []byte{ 0xa2, // map (2) @@ -730,7 +730,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "OKP", key: &Key{ KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -846,10 +846,10 @@ func TestNewOKPKey(t *testing.T) { wantErr string }{ { - name: "valid", args: args{AlgorithmEd25519, x, d}, + name: "valid", args: args{AlgorithmEdDSA, x, d}, want: &Key{ KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, @@ -862,7 +862,7 @@ func TestNewOKPKey(t *testing.T) { want: nil, wantErr: `unsupported algorithm "unknown algorithm value -100"`, }, { - name: "x and d missing", args: args{AlgorithmEd25519, nil, nil}, + name: "x and d missing", args: args{AlgorithmEdDSA, nil, nil}, want: nil, wantErr: "invalid key: required parameters missing", }, @@ -1061,7 +1061,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { KeyLabelOKPCurve: CurveEd25519, }, }, - AlgorithmEd25519, + AlgorithmEdDSA, "", }, { @@ -1170,7 +1170,7 @@ func TestNewKeyFromPrivate(t *testing.T) { { "ed25519", ed25519.PrivateKey(append(okpd, okpx...)), &Key{ - Algorithm: AlgorithmEd25519, KeyType: KeyTypeOKP, + Algorithm: AlgorithmEdDSA, KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1228,7 +1228,7 @@ func TestNewKeyFromPublic(t *testing.T) { { "ed25519", ed25519.PublicKey(okpx), &Key{ - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, KeyType: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -1275,20 +1275,20 @@ func TestKey_Signer(t *testing.T) { KeyLabelOKPD: d, }, }, - AlgorithmEd25519, + AlgorithmEdDSA, "", }, { "without key_ops", &Key{ KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, KeyLabelOKPD: d, }, }, - AlgorithmEd25519, + AlgorithmEdDSA, "", }, { @@ -1361,19 +1361,19 @@ func TestKey_Verifier(t *testing.T) { KeyLabelOKPX: x, }, }, - AlgorithmEd25519, + AlgorithmEdDSA, "", }, { "without key_ops", &Key{ KeyType: KeyTypeOKP, - Algorithm: AlgorithmEd25519, + Algorithm: AlgorithmEdDSA, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, }, }, - AlgorithmEd25519, + AlgorithmEdDSA, "", }, { diff --git a/signer.go b/signer.go index 6747546..18985dd 100644 --- a/signer.go +++ b/signer.go @@ -68,7 +68,7 @@ func NewSigner(alg Algorithm, key crypto.Signer) (Signer, error) { key: vk, signer: key, }, nil - case AlgorithmEd25519: + case AlgorithmEdDSA: if _, ok := key.Public().(ed25519.PublicKey); !ok { return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) } diff --git a/signer_test.go b/signer_test.go index e9475b9..62a17a7 100644 --- a/signer_test.go +++ b/signer_test.go @@ -77,7 +77,7 @@ func TestNewSigner(t *testing.T) { }, { name: "ed25519 signer", - alg: AlgorithmEd25519, + alg: AlgorithmEdDSA, key: ed25519Key, want: &ed25519Signer{ key: ed25519Key, @@ -85,7 +85,7 @@ func TestNewSigner(t *testing.T) { }, { name: "ed25519 key mismatch", - alg: AlgorithmEd25519, + alg: AlgorithmEdDSA, key: rsaKey, wantErr: "EdDSA: invalid public key", }, diff --git a/verifier.go b/verifier.go index 1c6e83b..b9db1f0 100644 --- a/verifier.go +++ b/verifier.go @@ -53,7 +53,7 @@ func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { alg: alg, key: vk, }, nil - case AlgorithmEd25519: + case AlgorithmEdDSA: vk, ok := key.(ed25519.PublicKey) if !ok { return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) diff --git a/verifier_test.go b/verifier_test.go index b662cd0..03e1e37 100644 --- a/verifier_test.go +++ b/verifier_test.go @@ -73,7 +73,7 @@ func TestNewVerifier(t *testing.T) { }, { name: "ed25519 verifier", - alg: AlgorithmEd25519, + alg: AlgorithmEdDSA, key: ed25519Key, want: &ed25519Verifier{ key: ed25519Key, @@ -81,7 +81,7 @@ func TestNewVerifier(t *testing.T) { }, { name: "ed25519 invalid public key", - alg: AlgorithmEd25519, + alg: AlgorithmEdDSA, key: rsaKey, wantErr: "EdDSA: invalid public key", }, From a5dc571754a60381220792a2e0c71bfc6f8102f6 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Mon, 24 Jul 2023 12:49:34 +0200 Subject: [PATCH 11/25] Make built-in types implement the new DigestSigner and DigestVerify interface (#144) Signed-off-by: qmuntal --- README.md | 12 ++++++++++++ ecdsa.go | 22 ++++++++++++++++++++++ ecdsa_test.go | 20 +++++++++++++++++++- ed25519_test.go | 9 +++++++++ example_test.go | 27 +++++++++++++++++++++++++++ rsa.go | 26 ++++++++++++++++++++------ rsa_test.go | 20 +++++++++++++++++++- signer.go | 13 +++++++++++++ verifier.go | 13 +++++++++++++ 9 files changed, 154 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e93536e..82b56ec 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,18 @@ See [example_test.go](./example_test.go) for more examples. Untagged COSE_Sign1 messages can be signed and verified as above, using `cose.UntaggedSign1Message` instead of `cose.Sign1Message`. +#### Signing and Verification of payload digest + +When `cose.NewSigner` is used with PS{256,384,512} or ES{256,384,512}, the returned signer +can be casted to the `cose.DigestSigner` interface, whose `SignDigest` method signs an +already digested message. + +When `cose.NewVerifier` is used with PS{256,384,512} or ES{256,384,512}, the returned verifier +can be casted to the `cose.DigestVerifier` interface, whose `VerifyDigest` method verifies an +already digested message. + +Please refer to [example_test.go](./example_test.go) for the API usage. + ### About hashing `go-cose` does not import any hash package by its own to avoid linking unnecessary algorithms to the final binary. diff --git a/ecdsa.go b/ecdsa.go index 7e426be..d1ae0e9 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -57,6 +57,13 @@ func (es *ecdsaKeySigner) Sign(rand io.Reader, content []byte) ([]byte, error) { if err != nil { return nil, err } + return es.SignDigest(rand, digest) +} + +// SignDigest signs message digest with the private key, possibly using +// entropy from rand. +// The resulting signature should follow RFC 8152 section 8. +func (es *ecdsaKeySigner) SignDigest(rand io.Reader, digest []byte) ([]byte, error) { r, s, err := ecdsa.Sign(rand, es.key, digest) if err != nil { return nil, err @@ -86,6 +93,13 @@ func (es *ecdsaCryptoSigner) Sign(rand io.Reader, content []byte) ([]byte, error if err != nil { return nil, err } + return es.SignDigest(rand, digest) +} + +// SignDigest signs message digest with the private key, possibly using +// entropy from rand. +// The resulting signature should follow RFC 8152 section 8. +func (es *ecdsaCryptoSigner) SignDigest(rand io.Reader, digest []byte) ([]byte, error) { sigASN1, err := es.signer.Sign(rand, digest, nil) if err != nil { return nil, err @@ -153,7 +167,15 @@ func (ev *ecdsaVerifier) Verify(content []byte, signature []byte) error { if err != nil { return err } + return ev.VerifyDigest(digest, signature) +} +// VerifyDigest verifies message digest with the public key, returning nil +// for success. +// Otherwise, it returns ErrVerification. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 +func (ev *ecdsaVerifier) VerifyDigest(digest []byte, signature []byte) error { // verify signature r, s, err := decodeECDSASignature(ev.key.Curve, signature) if err != nil { diff --git a/ecdsa_test.go b/ecdsa_test.go index 308ca6a..1cb2c12 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -219,7 +219,7 @@ func testSignVerify(t *testing.T, alg Algorithm, key crypto.Signer, isCryptoSign // sign / verify round trip // see also conformance_test.go for strict tests. - content := []byte("hello world") + content := []byte("hello world, مرحبا بالعالم") sig, err := signer.Sign(rand.Reader, content) if err != nil { t.Fatalf("Sign() error = %v", err) @@ -232,6 +232,24 @@ func testSignVerify(t *testing.T, alg Algorithm, key crypto.Signer, isCryptoSign if err := verifier.Verify(content, sig); err != nil { t.Fatalf("Verifier.Verify() error = %v", err) } + + // digested sign/verify round trip + dsigner, ok := signer.(DigestSigner) + if !ok { + t.Fatalf("signer is not a DigestSigner") + } + digest := sha256.Sum256(content) + dsig, err := dsigner.SignDigest(rand.Reader, digest[:]) + if err != nil { + t.Fatalf("SignDigest() error = %v", err) + } + dverifier, ok := verifier.(DigestVerifier) + if !ok { + t.Fatalf("verifier is not a DigestVerifier") + } + if err := dverifier.VerifyDigest(digest[:], dsig); err != nil { + t.Fatalf("VerifyDigest() error = %v", err) + } } type ecdsaBadCryptoSigner struct { diff --git a/ed25519_test.go b/ed25519_test.go index b0d9f29..4ecddaf 100644 --- a/ed25519_test.go +++ b/ed25519_test.go @@ -47,6 +47,15 @@ func Test_ed25519Signer(t *testing.T) { if err := verifier.Verify(content, sig); err != nil { t.Fatalf("Verifier.Verify() error = %v", err) } + + _, ok := signer.(DigestSigner) + if ok { + t.Fatalf("signer shouldn't be a DigestSigner") + } + _, ok = verifier.(DigestVerifier) + if ok { + t.Fatalf("verifier shouldn't be a DigestVerifier") + } } func Test_ed25519Verifier_Verify_Success(t *testing.T) { diff --git a/example_test.go b/example_test.go index 07ea624..afdf128 100644 --- a/example_test.go +++ b/example_test.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/sha512" _ "crypto/sha512" "fmt" @@ -202,3 +203,29 @@ func ExampleSign1Untagged() { // Output: // message signed } + +func ExampleDigestSigner() { + // create a signer + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + signer, err := cose.NewSigner(cose.AlgorithmES256, privateKey) + if err != nil { + panic(err) + } + digestSigner, ok := signer.(cose.DigestSigner) + if !ok { + panic("signer does not support digest signing") + } + + // hash payload outside go-cose. + payload := []byte("hello world") + digested := sha512.Sum512(payload) + sig, err := digestSigner.SignDigest(rand.Reader, digested[:]) + + fmt.Println("digest signed") + _ = sig // further process on sig + // Output: + // digest signed +} diff --git a/rsa.go b/rsa.go index b63098c..bb920d6 100644 --- a/rsa.go +++ b/rsa.go @@ -24,14 +24,20 @@ func (rs *rsaSigner) Algorithm() Algorithm { // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8 func (rs *rsaSigner) Sign(rand io.Reader, content []byte) ([]byte, error) { - hash := rs.alg.hashFunc() - digest, err := computeHash(hash, content) + digest, err := rs.alg.computeHash(content) if err != nil { return nil, err } + return rs.SignDigest(rand, digest) +} + +// SignDigest signs message digest with the private key, possibly using +// entropy from rand. +// The resulting signature should follow RFC 8152 section 8. +func (rs *rsaSigner) SignDigest(rand io.Reader, digest []byte) ([]byte, error) { return rs.key.Sign(rand, digest, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, // defined in RFC 8230 sec 2 - Hash: hash, + Hash: rs.alg.hashFunc(), }) } @@ -54,12 +60,20 @@ func (rv *rsaVerifier) Algorithm() Algorithm { // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8 func (rv *rsaVerifier) Verify(content []byte, signature []byte) error { - hash := rv.alg.hashFunc() - digest, err := computeHash(hash, content) + digest, err := rv.alg.computeHash(content) if err != nil { return err } - if err := rsa.VerifyPSS(rv.key, hash, digest, signature, &rsa.PSSOptions{ + return rv.VerifyDigest(digest, signature) +} + +// VerifyDigest verifies message digest with the public key, returning nil +// for success. +// Otherwise, it returns ErrVerification. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-8.1 +func (rv *rsaVerifier) VerifyDigest(digest []byte, signature []byte) error { + if err := rsa.VerifyPSS(rv.key, rv.alg.hashFunc(), digest, signature, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, // defined in RFC 8230 sec 2 }); err != nil { return ErrVerification diff --git a/rsa_test.go b/rsa_test.go index e859b78..803406b 100644 --- a/rsa_test.go +++ b/rsa_test.go @@ -36,7 +36,7 @@ func Test_rsaSigner(t *testing.T) { // sign / verify round trip // see also conformance_test.go for strict tests. - content := []byte("hello world") + content := []byte("hello world, مرحبا بالعالم") sig, err := signer.Sign(rand.Reader, content) if err != nil { t.Fatalf("Sign() error = %v", err) @@ -49,6 +49,24 @@ func Test_rsaSigner(t *testing.T) { if err := verifier.Verify(content, sig); err != nil { t.Fatalf("Verifier.Verify() error = %v", err) } + + // digested sign/verify round trip + dsigner, ok := signer.(DigestSigner) + if !ok { + t.Fatalf("signer is not a DigestSigner") + } + digest := sha256.Sum256(content) + dsig, err := dsigner.SignDigest(rand.Reader, digest[:]) + if err != nil { + t.Fatalf("SignDigest() error = %v", err) + } + dverifier, ok := verifier.(DigestVerifier) + if !ok { + t.Fatalf("verifier is not a DigestVerifier") + } + if err := dverifier.VerifyDigest(digest[:], dsig); err != nil { + t.Fatalf("VerifyDigest() error = %v", err) + } } func Test_rsaSigner_SignHashFailure(t *testing.T) { diff --git a/signer.go b/signer.go index 18985dd..33d42ef 100644 --- a/signer.go +++ b/signer.go @@ -23,6 +23,17 @@ type Signer interface { Sign(rand io.Reader, content []byte) ([]byte, error) } +// DigestSigner is an interface for private keys to sign digested COSE signatures. +type DigestSigner interface { + // Algorithm returns the signing algorithm associated with the private key. + Algorithm() Algorithm + + // SignDigest signs message digest with the private key, possibly using + // entropy from rand. + // The resulting signature should follow RFC 8152 section 8. + SignDigest(rand io.Reader, digest []byte) ([]byte, error) +} + // NewSigner returns a signer with a given signing key. // The signing key can be a golang built-in crypto private key, a key in HSM, or // a remote KMS. @@ -34,6 +45,8 @@ type Signer interface { // public key of type `*rsa.PublicKey`, `*ecdsa.PublicKey`, or // `ed25519.PublicKey` are accepted. // +// The returned signer for rsa and ecdsa keys also implements `cose.DigestSigner`. +// // Note: `*rsa.PrivateKey`, `*ecdsa.PrivateKey`, and `ed25519.PrivateKey` // implement `crypto.Signer`. func NewSigner(alg Algorithm, key crypto.Signer) (Signer, error) { diff --git a/verifier.go b/verifier.go index b9db1f0..5e5c667 100644 --- a/verifier.go +++ b/verifier.go @@ -22,9 +22,22 @@ type Verifier interface { Verify(content, signature []byte) error } +// DigestVerifier is an interface for public keys to verify digested COSE signatures. +type DigestVerifier interface { + // Algorithm returns the signing algorithm associated with the public key. + Algorithm() Algorithm + + // VerifyDigest verifies message digest with the public key, returning nil + // for success. + // Otherwise, it returns ErrVerification. + VerifyDigest(digest, signature []byte) error +} + // NewVerifier returns a verifier with a given public key. // Only golang built-in crypto public keys of type `*rsa.PublicKey`, // `*ecdsa.PublicKey`, and `ed25519.PublicKey` are accepted. +// +// The returned signer for rsa and ecdsa keys also implements `cose.DigestSigner`. func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { switch alg { case AlgorithmPS256, AlgorithmPS384, AlgorithmPS512: From 160b7e039b32df9a778421ebbbbe907847fa9763 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 1 Aug 2023 11:24:49 +0200 Subject: [PATCH 12/25] Consolidate API names related to Key (#161) Signed-off-by: qmuntal --- algorithm.go | 6 +- algorithm_test.go | 17 ++-- headers.go | 4 +- key.go | 152 ++++++++++++++++--------------- key_test.go | 225 +++++++++++++++++++++++----------------------- 5 files changed, 208 insertions(+), 196 deletions(-) diff --git a/algorithm.go b/algorithm.go index 0a22090..6fac36e 100644 --- a/algorithm.go +++ b/algorithm.go @@ -43,8 +43,8 @@ const ( // PureEdDSA by RFC 8152. AlgorithmEdDSA Algorithm = -8 - // An invalid/unrecognised algorithm. - AlgorithmInvalid Algorithm = 0 + // Reserved value. + AlgorithmReserved Algorithm = 0 ) // Algorithm represents an IANA algorithm entry in the COSE Algorithms registry. @@ -75,6 +75,8 @@ func (a Algorithm) String() string { // As stated in RFC 8152 8.2, only the pure EdDSA version is used for // COSE. return "EdDSA" + case AlgorithmReserved: + return "Reserved" default: return "unknown algorithm value " + strconv.Itoa(int(a)) } diff --git a/algorithm_test.go b/algorithm_test.go index 1bf1aa0..fea3e0a 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -12,18 +12,21 @@ import ( func TestAlgorithm_String(t *testing.T) { // run tests tests := []struct { - name string alg Algorithm want string }{ - { - name: "unknown algorithm", - alg: 0, - want: "unknown algorithm value 0", - }, + {AlgorithmPS256, "PS256"}, + {AlgorithmPS384, "PS384"}, + {AlgorithmPS512, "PS512"}, + {AlgorithmES256, "ES256"}, + {AlgorithmES384, "ES384"}, + {AlgorithmES512, "ES512"}, + {AlgorithmEdDSA, "EdDSA"}, + {AlgorithmReserved, "Reserved"}, + {7, "unknown algorithm value 7"}, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.want, func(t *testing.T) { if got := tt.alg.String(); got != tt.want { t.Errorf("Algorithm.String() = %v, want %v", got, tt.want) } diff --git a/headers.go b/headers.go index 391d217..88ab2d6 100644 --- a/headers.go +++ b/headers.go @@ -119,9 +119,9 @@ func (h ProtectedHeader) Algorithm() (Algorithm, error) { case int64: return Algorithm(alg), nil case string: - return AlgorithmInvalid, fmt.Errorf("unknown algorithm value %q", alg) + return AlgorithmReserved, fmt.Errorf("unknown algorithm value %q", alg) default: - return AlgorithmInvalid, ErrInvalidAlgorithm + return AlgorithmReserved, ErrInvalidAlgorithm } } diff --git a/key.go b/key.go index 2496271..7c042b4 100644 --- a/key.go +++ b/key.go @@ -33,9 +33,15 @@ const ( keyLabelBaseIV int64 = 5 ) +// KeyOp represents a key_ops value used to restrict purposes for which a Key +// may be used. +// +// https://datatracker.ietf.org/doc/html/rfc8152#section-7.1 +type KeyOp int64 + const ( - // An inviald key_op value - KeyOpInvalid KeyOp = 0 + // Reserved value. + KeyOpReserved KeyOp = 0 // The key is used to create signatures. Requires private key fields. KeyOpSign KeyOp = 1 @@ -69,10 +75,6 @@ const ( KeyOpMACVerify KeyOp = 10 ) -// KeyOp represents a key_ops value used to restrict purposes for which a Key -// may be used. -type KeyOp int64 - // KeyOpFromString returns the KeyOp corresponding to the specified name. // The values are taken from https://www.rfc-editor.org/rfc/rfc7517#section-4.3 func KeyOpFromString(val string) (KeyOp, bool) { @@ -94,7 +96,7 @@ func KeyOpFromString(val string) (KeyOp, bool) { case "deriveBits": return KeyOpDeriveBits, true default: - return KeyOpInvalid, false + return KeyOpReserved, false } } @@ -125,24 +127,22 @@ func (ko KeyOp) String() string { return "MAC create" case KeyOpMACVerify: return "MAC verify" + case KeyOpReserved: + return "Reserved" default: return "unknown key_op value " + strconv.Itoa(int(ko)) } } // KeyType identifies the family of keys represented by the associated Key. -// This determines which files within the Key must be set in order for it to be -// valid. +// +// https://datatracker.ietf.org/doc/html/rfc8152#section-13 type KeyType int64 const ( - // Invlaid key type - KeyTypeInvalid KeyType = 0 - // Octet Key Pair - KeyTypeOKP KeyType = 1 - // Elliptic Curve Keys w/ x- and y-coordinate pair - KeyTypeEC2 KeyType = 2 - // Symmetric Keys + KeyTypeReserved KeyType = 0 + KeyTypeOKP KeyType = 1 + KeyTypeEC2 KeyType = 2 KeyTypeSymmetric KeyType = 4 ) @@ -157,15 +157,21 @@ func (kt KeyType) String() string { return "EC2" case KeyTypeSymmetric: return "Symmetric" + case KeyTypeReserved: + return "Reserved" default: return "unknown key type value " + strconv.Itoa(int(kt)) } } -const ( +// Curve represents the EC2/OKP key's curve. +// +// https://datatracker.ietf.org/doc/html/rfc8152#section-13.1 +type Curve int64 - // Invalid/unrecognised curve - CurveInvalid Curve = 0 +const ( + // Reserved value + CurveReserved Curve = 0 // NIST P-256 also known as secp256r1 CurveP256 Curve = 1 @@ -189,10 +195,6 @@ const ( CurveEd448 Curve = 7 ) -// Curve represents the EC2/OKP key's curve. See: -// https://datatracker.ietf.org/doc/html/rfc8152#section-13.1 -type Curve int64 - // String returns a string representation of the Curve. Note does not // represent a valid value of the corresponding serialized entry, and must // not be used as such. @@ -212,6 +214,8 @@ func (c Curve) String() string { return "Ed25519" case CurveEd448: return "Ed448" + case CurveReserved: + return "Reserved" default: return "unknown curve value " + strconv.Itoa(int(c)) } @@ -220,17 +224,17 @@ func (c Curve) String() string { // Key represents a COSE_Key structure, as defined by RFC8152. // Note: currently, this does NOT support RFC8230 (RSA algorithms). type Key struct { - // KeyType identifies the family of keys for this structure, and thus, + // Type identifies the family of keys for this structure, and thus, // which of the key-type-specific parameters need to be set. - KeyType KeyType - // KeyID is the identification value matched to the kid in the message. - KeyID []byte + Type KeyType + // ID is the identification value matched to the kid in the message. + ID []byte // Algorithm is used to restrict the algorithm that is used with the // key. If it is set, the application MUST verify that it matches the // algorithm for which the Key is being used. Algorithm Algorithm - // KeyOps can be set to restrict the set of operations that the Key is used for. - KeyOps []KeyOp + // Ops can be set to restrict the set of operations that the Key is used for. + Ops []KeyOp // BaseIV is the Base IV to be xor-ed with Partial IVs. BaseIV []byte @@ -238,14 +242,14 @@ type Key struct { Params map[interface{}]interface{} } -// NewOKPKey returns a Key created using the provided Octet Key Pair data. -func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { +// NewKeyOKP returns a Key created using the provided Octet Key Pair data. +func NewKeyOKP(alg Algorithm, x, d []byte) (*Key, error) { if alg != AlgorithmEdDSA { return nil, fmt.Errorf("unsupported algorithm %q", alg) } key := &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: alg, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -257,7 +261,7 @@ func NewOKPKey(alg Algorithm, x, d []byte) (*Key, error) { if d != nil { key.Params[KeyLabelOKPD] = d } - if err := key.validate(KeyOpInvalid); err != nil { + if err := key.validate(KeyOpReserved); err != nil { return nil, err } return key, nil @@ -309,9 +313,9 @@ func (k *Key) OKP() (crv Curve, x []byte, d []byte) { return } -// NewEC2Key returns a Key created using the provided elliptic curve key +// NewKeyEC2 returns a Key created using the provided elliptic curve key // data. -func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { +func NewKeyEC2(alg Algorithm, x, y, d []byte) (*Key, error) { var curve Curve switch alg { @@ -326,7 +330,7 @@ func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { } key := &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Algorithm: alg, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: curve, @@ -341,7 +345,7 @@ func NewEC2Key(alg Algorithm, x, y, d []byte) (*Key, error) { if d != nil { key.Params[KeyLabelEC2D] = d } - if err := key.validate(KeyOpInvalid); err != nil { + if err := key.validate(KeyOpReserved); err != nil { return nil, err } return key, nil @@ -359,11 +363,11 @@ func (k *Key) EC2() (crv Curve, x []byte, y, d []byte) { return } -// NewSymmetricKey returns a Key created using the provided Symmetric key +// NewKeySymmetric returns a Key created using the provided Symmetric key // bytes. -func NewSymmetricKey(k []byte) *Key { +func NewKeySymmetric(k []byte) *Key { return &Key{ - KeyType: KeyTypeSymmetric, + Type: KeyTypeSymmetric, Params: map[interface{}]interface{}{ KeyLabelSymmetricK: k, }, @@ -383,13 +387,13 @@ func NewKeyFromPublic(pub crypto.PublicKey) (*Key, error) { case *ecdsa.PublicKey: alg := algorithmFromEllipticCurve(vk.Curve) - if alg == AlgorithmInvalid { + if alg == AlgorithmReserved { return nil, fmt.Errorf("unsupported curve: %v", vk.Curve) } - return NewEC2Key(alg, vk.X.Bytes(), vk.Y.Bytes(), nil) + return NewKeyEC2(alg, vk.X.Bytes(), vk.Y.Bytes(), nil) case ed25519.PublicKey: - return NewOKPKey(AlgorithmEdDSA, []byte(vk), nil) + return NewKeyOKP(AlgorithmEdDSA, []byte(vk), nil) default: return nil, ErrInvalidPubKey } @@ -402,13 +406,13 @@ func NewKeyFromPrivate(priv crypto.PrivateKey) (*Key, error) { case *ecdsa.PrivateKey: alg := algorithmFromEllipticCurve(sk.Curve) - if alg == AlgorithmInvalid { + if alg == AlgorithmReserved { return nil, fmt.Errorf("unsupported curve: %v", sk.Curve) } - return NewEC2Key(alg, sk.X.Bytes(), sk.Y.Bytes(), sk.D.Bytes()) + return NewKeyEC2(alg, sk.X.Bytes(), sk.Y.Bytes(), sk.D.Bytes()) case ed25519.PrivateKey: - return NewOKPKey(AlgorithmEdDSA, []byte(sk[32:]), []byte(sk[:32])) + return NewKeyOKP(AlgorithmEdDSA, []byte(sk[32:]), []byte(sk[:32])) default: return nil, ErrInvalidPrivKey } @@ -427,7 +431,7 @@ var ( // consistent (e.g., that the key type is appropriate to the curve). // It also checks that the key is valid for the requested operation. func (k Key) validate(op KeyOp) error { - switch k.KeyType { + switch k.Type { case KeyTypeEC2: crv, x, y, d := k.EC2() switch op { @@ -440,7 +444,7 @@ func (k Key) validate(op KeyOp) error { return ErrNotPrivKey } } - if crv == CurveInvalid || (len(x) == 0 && len(y) == 0 && len(d) == 0) { + if crv == CurveReserved || (len(x) == 0 && len(y) == 0 && len(d) == 0) { return errReqParamsMissing } if size := curveSize(crv); size > 0 { @@ -471,7 +475,7 @@ func (k Key) validate(op KeyOp) error { return ErrNotPrivKey } } - if crv == CurveInvalid || (len(x) == 0 && len(d) == 0) { + if crv == CurveReserved || (len(x) == 0 && len(d) == 0) { return errReqParamsMissing } if (len(x) > 0 && len(x) != ed25519.PublicKeySize) || (len(d) > 0 && len(d) != ed25519.SeedSize) { @@ -489,14 +493,14 @@ func (k Key) validate(op KeyOp) error { if len(k) == 0 { return errReqParamsMissing } - case KeyTypeInvalid: + case KeyTypeReserved: return fmt.Errorf("%w: kty value 0", ErrInvalidKey) default: // Unknown key type, we can't validate custom parameters. } // If Algorithm is set, it must match the specified key parameters. - if k.Algorithm != AlgorithmInvalid { + if k.Algorithm != AlgorithmReserved { expectedAlg, err := k.deriveAlgorithm() if err != nil { return err @@ -515,10 +519,10 @@ func (k Key) validate(op KeyOp) error { } func (k Key) canOp(op KeyOp) bool { - if k.KeyOps == nil { + if k.Ops == nil { return true } - for _, kop := range k.KeyOps { + for _, kop := range k.Ops { if kop == op { return true } @@ -529,16 +533,16 @@ func (k Key) canOp(op KeyOp) bool { // MarshalCBOR encodes Key into a COSE_Key object. func (k *Key) MarshalCBOR() ([]byte, error) { tmp := map[interface{}]interface{}{ - keyLabelKeyType: k.KeyType, + keyLabelKeyType: k.Type, } - if k.KeyID != nil { - tmp[keyLabelKeyID] = k.KeyID + if k.ID != nil { + tmp[keyLabelKeyID] = k.ID } - if k.Algorithm != AlgorithmInvalid { + if k.Algorithm != AlgorithmReserved { tmp[keyLabelAlgorithm] = k.Algorithm } - if k.KeyOps != nil { - tmp[keyLabelKeyOps] = k.KeyOps + if k.Ops != nil { + tmp[keyLabelKeyOps] = k.Ops } if k.BaseIV != nil { tmp[keyLabelBaseIV] = k.BaseIV @@ -555,7 +559,7 @@ func (k *Key) MarshalCBOR() ([]byte, error) { existing[lbl] = struct{}{} tmp[lbl] = v } - if k.KeyType == KeyTypeEC2 { + if k.Type == KeyTypeEC2 { // If EC2 key, ensure that x and y are padded to the correct size. crv, x, y, _ := k.EC2() if size := curveSize(crv); size > 0 { @@ -585,11 +589,11 @@ func (k *Key) UnmarshalCBOR(data []byte) error { if err != nil { return fmt.Errorf("kty: %w", err) } - k.KeyType = KeyType(kty) - if k.KeyType == KeyTypeInvalid { + k.Type = KeyType(kty) + if k.Type == KeyTypeReserved { return errors.New("kty: invalid value 0") } - k.KeyID, _, err = decodeBytes(tmp, keyLabelKeyID) + k.ID, _, err = decodeBytes(tmp, keyLabelKeyID) if err != nil { return fmt.Errorf("kid: %w", err) } @@ -603,14 +607,14 @@ func (k *Key) UnmarshalCBOR(data []byte) error { return fmt.Errorf("key_ops: %w", err) } if len(key_ops) > 0 { - k.KeyOps = make([]KeyOp, len(key_ops)) + k.Ops = make([]KeyOp, len(key_ops)) for i, op := range key_ops { switch op := op.(type) { case int64: - k.KeyOps[i] = KeyOp(op) + k.Ops[i] = KeyOp(op) case string: var ok bool - if k.KeyOps[i], ok = KeyOpFromString(op); !ok { + if k.Ops[i], ok = KeyOpFromString(op); !ok { return fmt.Errorf("key_ops: unknown entry value %q", op) } default: @@ -634,7 +638,7 @@ func (k *Key) UnmarshalCBOR(data []byte) error { for lbl, v := range tmp { switch lbl := lbl.(type) { case int64: - if (k.KeyType == KeyTypeEC2 || k.KeyType == KeyTypeOKP) && + if (k.Type == KeyTypeEC2 || k.Type == KeyTypeOKP) && (lbl == KeyLabelEC2Curve || lbl == KeyLabelOKPCurve) { v = Curve(v.(int64)) } @@ -646,7 +650,7 @@ func (k *Key) UnmarshalCBOR(data []byte) error { } } } - return k.validate(KeyOpInvalid) + return k.validate(KeyOpReserved) } // PublicKey returns a crypto.PublicKey generated using Key's parameters. @@ -746,7 +750,7 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { // Key.Curve. This method does NOT validate that Key.Algorithm, if set, aligns // with Key.Curve. func (k *Key) AlgorithmOrDefault() (Algorithm, error) { - if k.Algorithm != AlgorithmInvalid { + if k.Algorithm != AlgorithmReserved { return k.Algorithm, nil } @@ -799,7 +803,7 @@ func (k *Key) Verifier() (Verifier, error) { // only used with P-256, etc. For other combinations, the Algorithm in the Key // must be explicitly set,so that this derivation is not used. func (k *Key) deriveAlgorithm() (Algorithm, error) { - switch k.KeyType { + switch k.Type { case KeyTypeEC2: crv, _, _, _ := k.EC2() switch crv { @@ -810,7 +814,7 @@ func (k *Key) deriveAlgorithm() (Algorithm, error) { case CurveP521: return AlgorithmES512, nil default: - return AlgorithmInvalid, fmt.Errorf( + return AlgorithmReserved, fmt.Errorf( "unsupported curve %q for key type EC2", crv.String()) } case KeyTypeOKP: @@ -819,12 +823,12 @@ func (k *Key) deriveAlgorithm() (Algorithm, error) { case CurveEd25519: return AlgorithmEdDSA, nil default: - return AlgorithmInvalid, fmt.Errorf( + return AlgorithmReserved, fmt.Errorf( "unsupported curve %q for key type OKP", crv.String()) } default: // Symmetric algorithms are not supported in the current inmplementation. - return AlgorithmInvalid, fmt.Errorf("unexpected key type %q", k.KeyType.String()) + return AlgorithmReserved, fmt.Errorf("unexpected key type %q", k.Type.String()) } } @@ -837,7 +841,7 @@ func algorithmFromEllipticCurve(c elliptic.Curve) Algorithm { case elliptic.P521(): return AlgorithmES512 default: - return AlgorithmInvalid + return AlgorithmReserved } } diff --git a/key_test.go b/key_test.go index ec1e064..0c1ab73 100644 --- a/key_test.go +++ b/key_test.go @@ -202,6 +202,7 @@ func TestKeyOp_String(t *testing.T) { op KeyOp want string }{ + {KeyOpReserved, "Reserved"}, {KeyOpSign, "sign"}, {KeyOpVerify, "verify"}, {KeyOpEncrypt, "encrypt"}, @@ -431,7 +432,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x61, 0x66, 0x63, 0x66, 0x6f, 0x6f, // 0x21: foo }, want: &Key{ - KeyType: -70000, + Type: -70000, Params: map[interface{}]interface{}{ int64(-1): int64(6), "f": "foo", @@ -456,9 +457,9 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, want: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, - KeyOps: []KeyOp{KeyOpVerify, KeyOpSign}, + Ops: []KeyOp{KeyOpVerify, KeyOpSign}, BaseIV: []byte{0x03, 0x02, 0x01}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -483,7 +484,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x28, 0x14, 0x87, 0xef, 0x4a, 0xe6, 0x7b, 0x46, }, want: &Key{ - KeyType: KeyTypeSymmetric, + Type: KeyTypeSymmetric, Params: map[interface{}]interface{}{ KeyLabelSymmetricK: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, @@ -505,8 +506,8 @@ func TestKey_UnmarshalCBOR(t *testing.T) { "21582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d" + "2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c"), want: &Key{ - KeyType: KeyTypeEC2, - KeyID: []byte("meriadoc.brandybuck@buckland.example"), + Type: KeyTypeEC2, + ID: []byte("meriadoc.brandybuck@buckland.example"), Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: mustHexToBytes("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d"), @@ -523,8 +524,8 @@ func TestKey_UnmarshalCBOR(t *testing.T) { "2158420072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad" + "22584201dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475"), want: &Key{ - KeyType: KeyTypeEC2, - KeyID: []byte("bilbo.baggins@hobbiton.example"), + Type: KeyTypeEC2, + ID: []byte("bilbo.baggins@hobbiton.example"), Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: mustHexToBytes("0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad"), @@ -543,8 +544,8 @@ func TestKey_UnmarshalCBOR(t *testing.T) { "2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c" + "235820aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa208cf"), want: &Key{ - KeyType: KeyTypeEC2, - KeyID: []byte("meriadoc.brandybuck@buckland.example"), + Type: KeyTypeEC2, + ID: []byte("meriadoc.brandybuck@buckland.example"), Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: mustHexToBytes("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d"), @@ -562,8 +563,8 @@ func TestKey_UnmarshalCBOR(t *testing.T) { "22584201dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475" + "23584200085138ddabf5ca975f5860f91a08e91d6d5f9a76ad4018766a476680b55cd339e8ab6c72b5facdb2a2a50ac25bd086647dd3e2e6e99e84ca2c3609fdf177feb26d"), want: &Key{ - KeyType: KeyTypeEC2, - KeyID: []byte("bilbo.baggins@hobbiton.example"), + Type: KeyTypeEC2, + ID: []byte("bilbo.baggins@hobbiton.example"), Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: mustHexToBytes("0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad"), @@ -578,8 +579,8 @@ func TestKey_UnmarshalCBOR(t *testing.T) { "024a6f75722d736563726574" + "205820849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188"), want: &Key{ - KeyType: KeyTypeSymmetric, - KeyID: []byte("our-secret"), + Type: KeyTypeSymmetric, + ID: []byte("our-secret"), Params: map[interface{}]interface{}{ KeyLabelSymmetricK: mustHexToBytes("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188"), }, @@ -612,8 +613,8 @@ func TestKey_MarshalCBOR(t *testing.T) { { name: "OKP with kty and kid", key: &Key{ - KeyType: KeyTypeOKP, - KeyID: []byte{1, 2, 3}, + Type: KeyTypeOKP, + ID: []byte{1, 2, 3}, }, want: []byte{ 0xa2, // map (2) @@ -623,7 +624,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with only kty", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, }, want: []byte{ 0xa1, // map (1) @@ -632,8 +633,8 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with kty and base_iv", key: &Key{ - KeyType: KeyTypeOKP, - BaseIV: []byte{3, 2, 1}, + Type: KeyTypeOKP, + BaseIV: []byte{3, 2, 1}, }, want: []byte{ 0xa2, // map (2) @@ -643,7 +644,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with kty and alg", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, }, want: []byte{ @@ -654,7 +655,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with kty and private alg", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: -70_000, }, want: []byte{ @@ -665,9 +666,9 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with kty and key_ops", key: &Key{ - KeyType: KeyTypeOKP, - KeyID: []byte{1, 2, 3}, - KeyOps: []KeyOp{KeyOpEncrypt, KeyOpDecrypt, -70_000}, + Type: KeyTypeOKP, + ID: []byte{1, 2, 3}, + Ops: []KeyOp{KeyOpEncrypt, KeyOpDecrypt, -70_000}, }, want: []byte{ 0xa3, // map (3) @@ -679,7 +680,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with kty and private int params", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ 0x46: 0x47, 0x66: 0x67, @@ -694,7 +695,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with kty and private mixed params", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ 0x1234: 0x47, "a": 0x67, @@ -709,7 +710,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP duplicated params", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ int8(10): 0, int32(10): 1, @@ -719,7 +720,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP with invalid param label", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ int8(10): 0, -3.5: 1, @@ -729,9 +730,9 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "OKP", key: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, - KeyOps: []KeyOp{KeyOpVerify, KeyOpEncrypt}, + Ops: []KeyOp{KeyOpVerify, KeyOpEncrypt}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: []byte{ @@ -760,7 +761,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "EC2 with short x and y", key: &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Algorithm: AlgorithmES256, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, @@ -788,7 +789,7 @@ func TestKey_MarshalCBOR(t *testing.T) { }, { name: "Symmetric", key: &Key{ - KeyType: KeyTypeSymmetric, + Type: KeyTypeSymmetric, Params: map[interface{}]interface{}{ KeyLabelSymmetricK: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, @@ -810,7 +811,7 @@ func TestKey_MarshalCBOR(t *testing.T) { wantErr: "", }, { name: "unknown key type", - key: &Key{KeyType: 42}, + key: &Key{Type: 42}, want: []byte{ 0xa1, // map (1) 0x01, 0x18, 0x2a, // kty: 42 @@ -832,7 +833,7 @@ func TestKey_MarshalCBOR(t *testing.T) { } } -func TestNewOKPKey(t *testing.T) { +func TestNewKeyOKP(t *testing.T) { x, d := newEd25519(t) type args struct { alg Algorithm @@ -848,7 +849,7 @@ func TestNewOKPKey(t *testing.T) { { name: "valid", args: args{AlgorithmEdDSA, x, d}, want: &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -869,19 +870,19 @@ func TestNewOKPKey(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewOKPKey(tt.args.alg, tt.args.x, tt.args.d) + got, err := NewKeyOKP(tt.args.alg, tt.args.x, tt.args.d) if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { - t.Errorf("NewOKPKey() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("NewKeyOKP() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewOKPKey() = %v, want %v", got, tt.want) + t.Errorf("NewKeyOKP() = %v, want %v", got, tt.want) } }) } } -func TestNewEC2Key(t *testing.T) { +func TestNewNewKeyEC2(t *testing.T) { ec256x, ec256y, ec256d := newEC2(t, elliptic.P256()) ec384x, ec384y, ec384d := newEC2(t, elliptic.P384()) ec521x, ec521y, ec521d := newEC2(t, elliptic.P521()) @@ -900,7 +901,7 @@ func TestNewEC2Key(t *testing.T) { { name: "valid ES256", args: args{AlgorithmES256, ec256x, ec256y, ec256d}, want: &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Algorithm: AlgorithmES256, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, @@ -913,7 +914,7 @@ func TestNewEC2Key(t *testing.T) { }, { name: "valid ES384", args: args{AlgorithmES384, ec384x, ec384y, ec384d}, want: &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Algorithm: AlgorithmES384, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, @@ -926,7 +927,7 @@ func TestNewEC2Key(t *testing.T) { }, { name: "valid ES521", args: args{AlgorithmES512, ec521x, ec521y, ec521d}, want: &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Algorithm: AlgorithmES512, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, @@ -948,19 +949,19 @@ func TestNewEC2Key(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewEC2Key(tt.args.alg, tt.args.x, tt.args.y, tt.args.d) + got, err := NewKeyEC2(tt.args.alg, tt.args.x, tt.args.y, tt.args.d) if (err != nil && err.Error() != tt.wantErr) || (err == nil && tt.wantErr != "") { - t.Errorf("NewEC2Key() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("NewKeyEC2() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewEC2Key() = %v, want %v", got, tt.want) + t.Errorf("NewKeyEC2() = %v, want %v", got, tt.want) } }) } } -func TestNewSymmetricKey(t *testing.T) { +func TestNewKeySymmetric(t *testing.T) { type args struct { k []byte } @@ -970,7 +971,7 @@ func TestNewSymmetricKey(t *testing.T) { want *Key }{ {"valid", args{[]byte{1, 2, 3}}, &Key{ - KeyType: KeyTypeSymmetric, + Type: KeyTypeSymmetric, Params: map[interface{}]interface{}{ KeyLabelSymmetricK: []byte{1, 2, 3}, }, @@ -978,8 +979,8 @@ func TestNewSymmetricKey(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := NewSymmetricKey(tt.args.k); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewSymmetricKey() = %v, want %v", got, tt.want) + if got := NewKeySymmetric(tt.args.k); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewKeySymmetric() = %v, want %v", got, tt.want) } }) } @@ -1056,7 +1057,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { { "OKP-Ed25519", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, }, @@ -1067,18 +1068,18 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { { "OKP-P256", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveP256, }, }, - AlgorithmInvalid, + AlgorithmReserved, `unsupported curve "P-256" for key type OKP`, }, { "EC2-P256", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, }, @@ -1089,7 +1090,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { { "EC2-P384", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, }, @@ -1100,7 +1101,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { { "EC2-P521", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, }, @@ -1111,12 +1112,12 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { { "EC2-Ed25519", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveEd25519, }, }, - AlgorithmInvalid, + AlgorithmReserved, `unsupported curve "Ed25519" for key type EC2`, }, } @@ -1149,7 +1150,7 @@ func TestNewKeyFromPrivate(t *testing.T) { D: new(big.Int).SetBytes(d), }, &Key{ Algorithm: AlgorithmES256, - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: x, @@ -1170,7 +1171,7 @@ func TestNewKeyFromPrivate(t *testing.T) { { "ed25519", ed25519.PrivateKey(append(okpd, okpx...)), &Key{ - Algorithm: AlgorithmEdDSA, KeyType: KeyTypeOKP, + Algorithm: AlgorithmEdDSA, Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1211,7 +1212,7 @@ func TestNewKeyFromPublic(t *testing.T) { "ecdsa", &ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(ecx), Y: new(big.Int).SetBytes(ecy)}, &Key{ Algorithm: AlgorithmES256, - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ecx, @@ -1229,7 +1230,7 @@ func TestNewKeyFromPublic(t *testing.T) { "ed25519", ed25519.PublicKey(okpx), &Key{ Algorithm: AlgorithmEdDSA, - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1267,8 +1268,8 @@ func TestKey_Signer(t *testing.T) { }{ { "without algorithm", &Key{ - KeyType: KeyTypeOKP, - KeyOps: []KeyOp{KeyOpSign}, + Type: KeyTypeOKP, + Ops: []KeyOp{KeyOpSign}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, @@ -1280,7 +1281,7 @@ func TestKey_Signer(t *testing.T) { }, { "without key_ops", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -1293,38 +1294,38 @@ func TestKey_Signer(t *testing.T) { }, { "invalid algorithm", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveP256, KeyLabelOKPX: x, KeyLabelOKPD: d, }, }, - AlgorithmInvalid, + AlgorithmReserved, "invalid key: curve not supported for the given key type", }, { "can't sign", &Key{ - KeyType: KeyTypeOKP, - KeyOps: []KeyOp{KeyOpVerify}, + Type: KeyTypeOKP, + Ops: []KeyOp{KeyOpVerify}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, KeyLabelOKPD: d, }, }, - AlgorithmInvalid, + AlgorithmReserved, ErrOpNotSupported.Error(), }, { "unsupported key", &Key{ - KeyType: KeyTypeSymmetric, - KeyOps: []KeyOp{KeyOpSign}, + Type: KeyTypeSymmetric, + Ops: []KeyOp{KeyOpSign}, Params: map[interface{}]interface{}{ KeyLabelSymmetricK: d, }, }, - AlgorithmInvalid, + AlgorithmReserved, `unexpected key type "Symmetric"`, }, } @@ -1354,8 +1355,8 @@ func TestKey_Verifier(t *testing.T) { }{ { "without algorithm", &Key{ - KeyType: KeyTypeOKP, - KeyOps: []KeyOp{KeyOpVerify}, + Type: KeyTypeOKP, + Ops: []KeyOp{KeyOpVerify}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, @@ -1366,7 +1367,7 @@ func TestKey_Verifier(t *testing.T) { }, { "without key_ops", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, @@ -1378,36 +1379,36 @@ func TestKey_Verifier(t *testing.T) { }, { "invalid algorithm", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveP256, KeyLabelOKPX: x, }, }, - AlgorithmInvalid, + AlgorithmReserved, "invalid key: curve not supported for the given key type", }, { "can't verify", &Key{ - KeyType: KeyTypeOKP, - KeyOps: []KeyOp{KeyOpSign}, + Type: KeyTypeOKP, + Ops: []KeyOp{KeyOpSign}, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, }, }, - AlgorithmInvalid, + AlgorithmReserved, ErrOpNotSupported.Error(), }, { "unsupported key", &Key{ - KeyType: KeyTypeSymmetric, - KeyOps: []KeyOp{KeyOpVerify}, + Type: KeyTypeSymmetric, + Ops: []KeyOp{KeyOpVerify}, Params: map[interface{}]interface{}{ KeyLabelSymmetricK: x, }, }, - AlgorithmInvalid, + AlgorithmReserved, `unexpected key type "Symmetric"`, }, } @@ -1440,7 +1441,7 @@ func TestKey_PrivateKey(t *testing.T) { }{ { "CurveEd25519", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1451,7 +1452,7 @@ func TestKey_PrivateKey(t *testing.T) { "", }, { "CurveEd25519 missing x", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPD: okpd, @@ -1461,7 +1462,7 @@ func TestKey_PrivateKey(t *testing.T) { "", }, { "CurveP256", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, @@ -1480,7 +1481,7 @@ func TestKey_PrivateKey(t *testing.T) { "", }, { "CurveP256 missing x and y", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2D: ec256d, @@ -1497,7 +1498,7 @@ func TestKey_PrivateKey(t *testing.T) { "", }, { "CurveP384", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, KeyLabelEC2X: ec384x, @@ -1516,7 +1517,7 @@ func TestKey_PrivateKey(t *testing.T) { "", }, { "CurveP521", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: ec521x, @@ -1535,13 +1536,13 @@ func TestKey_PrivateKey(t *testing.T) { "", }, { "unknown key type", &Key{ - KeyType: KeyType(7), + Type: KeyType(7), }, nil, `unexpected key type "unknown key type value 7"`, }, { "OKP unknown curve", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: 70, KeyLabelOKPX: okpx, @@ -1552,7 +1553,7 @@ func TestKey_PrivateKey(t *testing.T) { `unsupported curve "unknown curve value 70" for key type OKP`, }, { "OKP missing d", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1562,7 +1563,7 @@ func TestKey_PrivateKey(t *testing.T) { ErrNotPrivKey.Error(), }, { "OKP incorrect x size", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: make([]byte, 10), @@ -1573,7 +1574,7 @@ func TestKey_PrivateKey(t *testing.T) { "invalid key: overflowing coordinate", }, { "OKP incorrect d size", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1584,7 +1585,7 @@ func TestKey_PrivateKey(t *testing.T) { "invalid key: overflowing coordinate", }, { "EC2 missing D", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, @@ -1595,7 +1596,7 @@ func TestKey_PrivateKey(t *testing.T) { ErrNotPrivKey.Error(), }, { "EC2 unknown curve", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: 70, KeyLabelEC2X: ec256x, @@ -1607,7 +1608,7 @@ func TestKey_PrivateKey(t *testing.T) { `unsupported curve "unknown curve value 70" for key type EC2`, }, { "EC2 incorrect x size", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec384x, @@ -1619,7 +1620,7 @@ func TestKey_PrivateKey(t *testing.T) { "invalid key: overflowing coordinate", }, { "EC2 incorrect y size", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, @@ -1631,7 +1632,7 @@ func TestKey_PrivateKey(t *testing.T) { "invalid key: overflowing coordinate", }, { "EC2 incorrect d size", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, @@ -1670,7 +1671,7 @@ func TestKey_PublicKey(t *testing.T) { }{ { "CurveEd25519", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, @@ -1680,7 +1681,7 @@ func TestKey_PublicKey(t *testing.T) { "", }, { "CurveP256", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, @@ -1695,7 +1696,7 @@ func TestKey_PublicKey(t *testing.T) { "", }, { "CurveP384", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP384, KeyLabelEC2X: ec384x, @@ -1710,7 +1711,7 @@ func TestKey_PublicKey(t *testing.T) { "", }, { "CurveP521", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: ec521x, @@ -1725,19 +1726,19 @@ func TestKey_PublicKey(t *testing.T) { "", }, { "unknown key type", &Key{ - KeyType: KeyType(7), + Type: KeyType(7), }, nil, `unexpected key type "unknown key type value 7"`, }, { "invalid key type", &Key{ - KeyType: KeyTypeInvalid, + Type: KeyTypeReserved, }, nil, `invalid key: kty value 0`, }, { "OKP missing X", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: CurveEd25519, }, @@ -1746,7 +1747,7 @@ func TestKey_PublicKey(t *testing.T) { ErrOKPNoPub.Error(), }, { "OKP unknown curve", &Key{ - KeyType: KeyTypeOKP, + Type: KeyTypeOKP, Params: map[interface{}]interface{}{ KeyLabelOKPCurve: 70, KeyLabelOKPX: okpx, @@ -1756,7 +1757,7 @@ func TestKey_PublicKey(t *testing.T) { `unsupported curve "unknown curve value 70" for key type OKP`, }, { "EC2 missing X", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2Y: ec256y, @@ -1766,7 +1767,7 @@ func TestKey_PublicKey(t *testing.T) { ErrEC2NoPub.Error(), }, { "EC2 missing Y", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, @@ -1776,7 +1777,7 @@ func TestKey_PublicKey(t *testing.T) { ErrEC2NoPub.Error(), }, { "EC2 unknown curve", &Key{ - KeyType: KeyTypeEC2, + Type: KeyTypeEC2, Params: map[interface{}]interface{}{ KeyLabelEC2Curve: 70, KeyLabelEC2X: ec256x, @@ -1802,13 +1803,14 @@ func TestKey_PublicKey(t *testing.T) { } func TestKeyType_String(t *testing.T) { - // test string conversions not exercised by other test cases tests := []struct { kt KeyType want string }{ + {KeyTypeReserved, "Reserved"}, {KeyTypeOKP, "OKP"}, {KeyTypeEC2, "EC2"}, + {KeyTypeSymmetric, "Symmetric"}, } for _, tt := range tests { t.Run(tt.want, func(t *testing.T) { @@ -1831,6 +1833,7 @@ func TestCurve_String(t *testing.T) { {CurveX448, "X448"}, {CurveEd25519, "Ed25519"}, {CurveEd448, "Ed448"}, + {CurveReserved, "Reserved"}, } for _, tt := range tests { t.Run(tt.want, func(t *testing.T) { From ed78bf9ee97cd30fd53fdb1900cce4096b71fc18 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 8 Aug 2023 15:45:53 +0200 Subject: [PATCH 13/25] Rewrite interface{} to any (#162) Signed-off-by: qmuntal --- fuzz_test.go | 2 +- headers.go | 38 ++++++------ headers_test.go | 18 +++--- key.go | 42 +++++++------- key_test.go | 150 ++++++++++++++++++++++++------------------------ sign.go | 2 +- sign1.go | 2 +- 7 files changed, 127 insertions(+), 127 deletions(-) diff --git a/fuzz_test.go b/fuzz_test.go index 4e697a1..aa5db19 100644 --- a/fuzz_test.go +++ b/fuzz_test.go @@ -58,7 +58,7 @@ func FuzzSign1Message_UnmarshalCBOR(f *testing.F) { enc, _ := cbor.CanonicalEncOptions().EncMode() dec, _ := cbor.DecOptions{IntDec: cbor.IntDecConvertSigned}.DecMode() isCanonical := func(b []byte) bool { - var tmp interface{} + var tmp any err := dec.Unmarshal(b, &tmp) if err != nil { return false diff --git a/headers.go b/headers.go index 88ab2d6..ceacefa 100644 --- a/headers.go +++ b/headers.go @@ -28,7 +28,7 @@ const ( // ProtectedHeader contains parameters that are to be cryptographically // protected. -type ProtectedHeader map[interface{}]interface{} +type ProtectedHeader map[any]any // MarshalCBOR encodes the protected header into a CBOR bstr object. // A zero-length header is encoded as a zero-length string rather than as a @@ -42,7 +42,7 @@ func (h ProtectedHeader) MarshalCBOR() ([]byte, error) { if err != nil { return nil, fmt.Errorf("protected header: %w", err) } - encoded, err = encMode.Marshal(map[interface{}]interface{}(h)) + encoded, err = encMode.Marshal(map[any]any(h)) if err != nil { return nil, err } @@ -75,7 +75,7 @@ func (h *ProtectedHeader) UnmarshalCBOR(data []byte) error { if err := validateHeaderLabelCBOR(encoded); err != nil { return err } - var header map[interface{}]interface{} + var header map[any]any if err := decMode.Unmarshal(encoded, &header); err != nil { return err } @@ -129,7 +129,7 @@ func (h ProtectedHeader) Algorithm() (Algorithm, error) { // processing a message is required to understand. // // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-3.1 -func (h ProtectedHeader) Critical() ([]interface{}, error) { +func (h ProtectedHeader) Critical() ([]any, error) { value, ok := h[HeaderLabelCritical] if !ok { return nil, nil @@ -138,12 +138,12 @@ func (h ProtectedHeader) Critical() ([]interface{}, error) { if err != nil { return nil, err } - return value.([]interface{}), nil + return value.([]any), nil } // ensureCritical ensures all critical headers are present in the protected bucket. -func ensureCritical(value interface{}, headers map[interface{}]interface{}) error { - labels, ok := value.([]interface{}) +func ensureCritical(value any, headers map[any]any) error { + labels, ok := value.([]any) if !ok { return errors.New("invalid crit header") } @@ -164,7 +164,7 @@ func ensureCritical(value interface{}, headers map[interface{}]interface{}) erro // UnprotectedHeader contains parameters that are not cryptographically // protected. -type UnprotectedHeader map[interface{}]interface{} +type UnprotectedHeader map[any]any // MarshalCBOR encodes the unprotected header into a CBOR map object. // A zero-length header is encoded as a zero-length map (encoded as h'a0'). @@ -175,7 +175,7 @@ func (h UnprotectedHeader) MarshalCBOR() ([]byte, error) { if err := validateHeaderParameters(h, false); err != nil { return nil, fmt.Errorf("unprotected header: %w", err) } - return encMode.Marshal(map[interface{}]interface{}(h)) + return encMode.Marshal(map[any]any(h)) } // UnmarshalCBOR decodes a CBOR map object into UnprotectedHeader. @@ -197,7 +197,7 @@ func (h *UnprotectedHeader) UnmarshalCBOR(data []byte) error { if err := validateHeaderLabelCBOR(data); err != nil { return err } - var header map[interface{}]interface{} + var header map[any]any if err := decMode.Unmarshal(data, &header); err != nil { return err } @@ -376,14 +376,14 @@ func (h *Headers) ensureIV() error { } // hasLabel returns true if h contains label. -func hasLabel(h map[interface{}]interface{}, label interface{}) bool { +func hasLabel(h map[any]any, label any) bool { _, ok := h[label] return ok } // validateHeaderParameters validates all headers conform to the spec. -func validateHeaderParameters(h map[interface{}]interface{}, protected bool) error { - existing := make(map[interface{}]struct{}, len(h)) +func validateHeaderParameters(h map[any]any, protected bool) error { + existing := make(map[any]struct{}, len(h)) for label, value := range h { // Validate that all header labels are integers or strings. // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-1.4 @@ -443,7 +443,7 @@ func validateHeaderParameters(h map[interface{}]interface{}, protected bool) err } // canUint reports whether v can be used as a CBOR uint type. -func canUint(v interface{}) bool { +func canUint(v any) bool { switch v := v.(type) { case uint, uint8, uint16, uint32, uint64: return true @@ -462,7 +462,7 @@ func canUint(v interface{}) bool { } // canInt reports whether v can be used as a CBOR int type. -func canInt(v interface{}) bool { +func canInt(v any) bool { switch v.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: @@ -472,20 +472,20 @@ func canInt(v interface{}) bool { } // canTstr reports whether v can be used as a CBOR tstr type. -func canTstr(v interface{}) bool { +func canTstr(v any) bool { _, ok := v.(string) return ok } // canBstr reports whether v can be used as a CBOR bstr type. -func canBstr(v interface{}) bool { +func canBstr(v any) bool { _, ok := v.([]byte) return ok } // normalizeLabel tries to cast label into a int64 or a string. // Returns (nil, false) if the label type is not valid. -func normalizeLabel(label interface{}) (interface{}, bool) { +func normalizeLabel(label any) (any, bool) { switch v := label.(type) { case int: label = int64(v) @@ -517,7 +517,7 @@ func normalizeLabel(label interface{}) (interface{}, bool) { // headerLabelValidator is used to validate the header label of a COSE header. type headerLabelValidator struct { - value interface{} + value any } // String prints the value without brackets `{}`. Useful in error printing. diff --git a/headers_test.go b/headers_test.go index 11d4e72..004390b 100644 --- a/headers_test.go +++ b/headers_test.go @@ -17,7 +17,7 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { name: "valid header", h: ProtectedHeader{ HeaderLabelAlgorithm: AlgorithmES256, - HeaderLabelCritical: []interface{}{ + HeaderLabelCritical: []any{ HeaderLabelContentType, "foo", }, @@ -84,7 +84,7 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { { name: "empty critical", h: ProtectedHeader{ - HeaderLabelCritical: []interface{}{}, + HeaderLabelCritical: []any{}, }, wantErr: "protected header: header parameter: crit: empty crit header", }, @@ -98,7 +98,7 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { { name: "missing header marked as critical", h: ProtectedHeader{ - HeaderLabelCritical: []interface{}{ + HeaderLabelCritical: []any{ HeaderLabelContentType, }, }, @@ -107,7 +107,7 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { { name: "critical header contains non-label element", h: ProtectedHeader{ - HeaderLabelCritical: []interface{}{[]uint8{}}, + HeaderLabelCritical: []any{[]uint8{}}, }, wantErr: "protected header: header parameter: crit: require int / tstr type, got '[]uint8': []", }, @@ -199,7 +199,7 @@ func TestProtectedHeader_UnmarshalCBOR(t *testing.T) { }, want: ProtectedHeader{ HeaderLabelAlgorithm: AlgorithmES256, - HeaderLabelCritical: []interface{}{ + HeaderLabelCritical: []any{ HeaderLabelContentType, "foo", }, @@ -451,21 +451,21 @@ func TestProtectedHeader_Critical(t *testing.T) { tests := []struct { name string h ProtectedHeader - want []interface{} + want []any wantErr string }{ { name: "valid header", h: ProtectedHeader{ HeaderLabelAlgorithm: AlgorithmES256, - HeaderLabelCritical: []interface{}{ + HeaderLabelCritical: []any{ HeaderLabelContentType, "foo", }, HeaderLabelContentType: "text/plain", "foo": "bar", }, - want: []interface{}{ + want: []any{ HeaderLabelContentType, "foo", }, @@ -490,7 +490,7 @@ func TestProtectedHeader_Critical(t *testing.T) { { name: "empty critical", h: ProtectedHeader{ - HeaderLabelCritical: []interface{}{}, + HeaderLabelCritical: []any{}, }, wantErr: "empty crit header", }, diff --git a/key.go b/key.go index 7c042b4..71ad331 100644 --- a/key.go +++ b/key.go @@ -239,7 +239,7 @@ type Key struct { BaseIV []byte // Any additional parameter (label,value) pairs. - Params map[interface{}]interface{} + Params map[any]any } // NewKeyOKP returns a Key created using the provided Octet Key Pair data. @@ -251,7 +251,7 @@ func NewKeyOKP(alg Algorithm, x, d []byte) (*Key, error) { key := &Key{ Type: KeyTypeOKP, Algorithm: alg, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, }, } @@ -269,35 +269,35 @@ func NewKeyOKP(alg Algorithm, x, d []byte) (*Key, error) { // ParamBytes returns the value of the parameter with the given label, if it // exists and is of type []byte or can be converted to []byte. -func (k *Key) ParamBytes(label interface{}) ([]byte, bool) { +func (k *Key) ParamBytes(label any) ([]byte, bool) { v, ok, err := decodeBytes(k.Params, label) return v, ok && err == nil } // ParamInt returns the value of the parameter with the given label, if it // exists and is of type int64 or can be converted to int64. -func (k *Key) ParamInt(label interface{}) (int64, bool) { +func (k *Key) ParamInt(label any) (int64, bool) { v, ok, err := decodeInt(k.Params, label) return v, ok && err == nil } // ParamUint returns the value of the parameter with the given label, if it // exists and is of type uint64 or can be converted to uint64. -func (k *Key) ParamUint(label interface{}) (uint64, bool) { +func (k *Key) ParamUint(label any) (uint64, bool) { v, ok, err := decodeUint(k.Params, label) return v, ok && err == nil } // ParamString returns the value of the parameter with the given label, if it // exists and is of type string or can be converted to string. -func (k *Key) ParamString(label interface{}) (string, bool) { +func (k *Key) ParamString(label any) (string, bool) { v, ok, err := decodeString(k.Params, label) return v, ok && err == nil } // ParamBool returns the value of the parameter with the given label, if it // exists and is of type bool or can be converted to bool. -func (k *Key) ParamBool(label interface{}) (bool, bool) { +func (k *Key) ParamBool(label any) (bool, bool) { v, ok, err := decodeBool(k.Params, label) return v, ok && err == nil } @@ -332,7 +332,7 @@ func NewKeyEC2(alg Algorithm, x, y, d []byte) (*Key, error) { key := &Key{ Type: KeyTypeEC2, Algorithm: alg, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: curve, }, } @@ -368,7 +368,7 @@ func (k *Key) EC2() (crv Curve, x []byte, y, d []byte) { func NewKeySymmetric(k []byte) *Key { return &Key{ Type: KeyTypeSymmetric, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: k, }, } @@ -532,7 +532,7 @@ func (k Key) canOp(op KeyOp) bool { // MarshalCBOR encodes Key into a COSE_Key object. func (k *Key) MarshalCBOR() ([]byte, error) { - tmp := map[interface{}]interface{}{ + tmp := map[any]any{ keyLabelKeyType: k.Type, } if k.ID != nil { @@ -547,7 +547,7 @@ func (k *Key) MarshalCBOR() ([]byte, error) { if k.BaseIV != nil { tmp[keyLabelBaseIV] = k.BaseIV } - existing := make(map[interface{}]struct{}, len(k.Params)) + existing := make(map[any]struct{}, len(k.Params)) for label, v := range k.Params { lbl, ok := normalizeLabel(label) if !ok { @@ -576,7 +576,7 @@ func (k *Key) MarshalCBOR() ([]byte, error) { // UnmarshalCBOR decodes a COSE_Key object into Key. func (k *Key) UnmarshalCBOR(data []byte) error { - var tmp map[interface{}]interface{} + var tmp map[any]any if err := decMode.Unmarshal(data, &tmp); err != nil { return err } @@ -634,7 +634,7 @@ func (k *Key) UnmarshalCBOR(data []byte) error { delete(tmp, keyLabelBaseIV) if len(tmp) > 0 { - k.Params = make(map[interface{}]interface{}, len(tmp)) + k.Params = make(map[any]any, len(tmp)) for lbl, v := range tmp { switch lbl := lbl.(type) { case int64: @@ -858,7 +858,7 @@ func curveSize(crv Curve) int { return (bitSize + 7) / 8 } -func decodeBytes(dic map[interface{}]interface{}, lbl interface{}) (b []byte, ok bool, err error) { +func decodeBytes(dic map[any]any, lbl any) (b []byte, ok bool, err error) { val, ok := dic[lbl] if !ok { return nil, false, nil @@ -874,7 +874,7 @@ func decodeBytes(dic map[interface{}]interface{}, lbl interface{}) (b []byte, ok return reflect.ValueOf(val).Bytes(), true, nil } -func decodeInt(dic map[interface{}]interface{}, lbl interface{}) (int64, bool, error) { +func decodeInt(dic map[any]any, lbl any) (int64, bool, error) { val, ok := dic[lbl] if !ok { return 0, false, nil @@ -888,7 +888,7 @@ func decodeInt(dic map[interface{}]interface{}, lbl interface{}) (int64, bool, e return 0, true, fmt.Errorf("invalid type: expected int64, got %T", val) } -func decodeUint(dic map[interface{}]interface{}, lbl interface{}) (uint64, bool, error) { +func decodeUint(dic map[any]any, lbl any) (uint64, bool, error) { val, ok := dic[lbl] if !ok { return 0, false, nil @@ -908,7 +908,7 @@ func decodeUint(dic map[interface{}]interface{}, lbl interface{}) (uint64, bool, return 0, true, fmt.Errorf("invalid type: expected uint64, got %T", val) } -func decodeString(dic map[interface{}]interface{}, lbl interface{}) (string, bool, error) { +func decodeString(dic map[any]any, lbl any) (string, bool, error) { val, ok := dic[lbl] if !ok { return "", false, nil @@ -922,7 +922,7 @@ func decodeString(dic map[interface{}]interface{}, lbl interface{}) (string, boo return "", true, fmt.Errorf("invalid type: expected uint64, got %T", val) } -func decodeBool(dic map[interface{}]interface{}, lbl interface{}) (bool, bool, error) { +func decodeBool(dic map[any]any, lbl any) (bool, bool, error) { val, ok := dic[lbl] if !ok { return false, false, nil @@ -936,14 +936,14 @@ func decodeBool(dic map[interface{}]interface{}, lbl interface{}) (bool, bool, e return false, true, fmt.Errorf("invalid type: expected uint64, got %T", val) } -func decodeSlice(dic map[interface{}]interface{}, lbl interface{}) ([]interface{}, error) { +func decodeSlice(dic map[any]any, lbl any) ([]any, error) { v, ok := dic[lbl] if !ok { return nil, nil } - arr, ok := v.([]interface{}) + arr, ok := v.([]any) if !ok { - return nil, fmt.Errorf("invalid type: expected []interface{}, got %T", v) + return nil, fmt.Errorf("invalid type: expected []any, got %T", v) } return arr, nil } diff --git a/key_test.go b/key_test.go index 0c1ab73..92691e5 100644 --- a/key_test.go +++ b/key_test.go @@ -15,7 +15,7 @@ import ( func TestKey_ParamBytes(t *testing.T) { key := &Key{ - Params: map[interface{}]interface{}{ + Params: map[any]any{ int64(-1): []byte{1}, 2: []byte{2}, uint16(3): []byte{3}, @@ -24,7 +24,7 @@ func TestKey_ParamBytes(t *testing.T) { }, } tests := []struct { - label interface{} + label any want []byte want1 bool }{ @@ -53,7 +53,7 @@ func TestKey_ParamInt(t *testing.T) { type i16 int16 type u16 uint16 key := &Key{ - Params: map[interface{}]interface{}{ + Params: map[any]any{ int64(-1): 1, 2: int8(2), uint16(3): i16(3), @@ -63,7 +63,7 @@ func TestKey_ParamInt(t *testing.T) { }, } tests := []struct { - label interface{} + label any want int64 want1 bool }{ @@ -93,7 +93,7 @@ func TestKey_ParamUint(t *testing.T) { type i16 int16 type u16 uint16 key := &Key{ - Params: map[interface{}]interface{}{ + Params: map[any]any{ int64(-1): 1, 2: int8(2), uint16(3): i16(3), @@ -104,7 +104,7 @@ func TestKey_ParamUint(t *testing.T) { }, } tests := []struct { - label interface{} + label any want uint64 want1 bool }{ @@ -134,7 +134,7 @@ func TestKey_ParamUint(t *testing.T) { func TestKey_ParamString(t *testing.T) { type str string key := &Key{ - Params: map[interface{}]interface{}{ + Params: map[any]any{ 1: "foo", "2": str("bar"), 3: []byte("baz"), @@ -142,7 +142,7 @@ func TestKey_ParamString(t *testing.T) { }, } tests := []struct { - label interface{} + label any want string want1 bool }{ @@ -167,7 +167,7 @@ func TestKey_ParamString(t *testing.T) { func TestKey_ParamBool(t *testing.T) { type boo bool key := &Key{ - Params: map[interface{}]interface{}{ + Params: map[any]any{ 1: true, "2": boo(false), 3: []byte("baz"), @@ -175,7 +175,7 @@ func TestKey_ParamBool(t *testing.T) { }, } tests := []struct { - label interface{} + label any want bool want1 bool }{ @@ -289,7 +289,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { 0x04, 0x41, 0x01, // key_ops: bstr(1) }, want: nil, - wantErr: "key_ops: invalid type: expected []interface{}, got []uint8", + wantErr: "key_ops: invalid type: expected []any, got []uint8", }, { name: "unknown key_ops entry value", data: []byte{ @@ -433,7 +433,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { }, want: &Key{ Type: -70000, - Params: map[interface{}]interface{}{ + Params: map[any]any{ int64(-1): int64(6), "f": "foo", }, @@ -461,7 +461,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { Algorithm: AlgorithmEdDSA, Ops: []KeyOp{KeyOpVerify, KeyOpSign}, BaseIV: []byte{0x03, 0x02, 0x01}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, @@ -485,7 +485,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { }, want: &Key{ Type: KeyTypeSymmetric, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, @@ -508,7 +508,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { want: &Key{ Type: KeyTypeEC2, ID: []byte("meriadoc.brandybuck@buckland.example"), - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: mustHexToBytes("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d"), KeyLabelEC2Y: mustHexToBytes("1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c"), @@ -526,7 +526,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { want: &Key{ Type: KeyTypeEC2, ID: []byte("bilbo.baggins@hobbiton.example"), - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: mustHexToBytes("0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad"), KeyLabelEC2Y: mustHexToBytes("01dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475"), @@ -546,7 +546,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { want: &Key{ Type: KeyTypeEC2, ID: []byte("meriadoc.brandybuck@buckland.example"), - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: mustHexToBytes("65eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d"), KeyLabelEC2Y: mustHexToBytes("1e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c"), @@ -565,7 +565,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { want: &Key{ Type: KeyTypeEC2, ID: []byte("bilbo.baggins@hobbiton.example"), - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: mustHexToBytes("0072992cb3ac08ecf3e5c63dedec0d51a8c1f79ef2f82f94f3c737bf5de7986671eac625fe8257bbd0394644caaa3aaf8f27a4585fbbcad0f2457620085e5c8f42ad"), KeyLabelEC2Y: mustHexToBytes("01dca6947bce88bc5790485ac97427342bc35f887d86d65a089377e247e60baa55e4e8501e2ada5724ac51d6909008033ebc10ac999b9d7f5cc2519f3fe1ea1d9475"), @@ -581,7 +581,7 @@ func TestKey_UnmarshalCBOR(t *testing.T) { want: &Key{ Type: KeyTypeSymmetric, ID: []byte("our-secret"), - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: mustHexToBytes("849b57219dae48de646d07dbb533566e976686457c1491be3a76dcea6c427188"), }, }, @@ -681,7 +681,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "OKP with kty and private int params", key: &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ 0x46: 0x47, 0x66: 0x67, }, @@ -696,7 +696,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "OKP with kty and private mixed params", key: &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ 0x1234: 0x47, "a": 0x67, }, @@ -711,7 +711,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "OKP duplicated params", key: &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ int8(10): 0, int32(10): 1, }, @@ -721,7 +721,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "OKP with invalid param label", key: &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ int8(10): 0, -3.5: 1, }, @@ -733,7 +733,7 @@ func TestKey_MarshalCBOR(t *testing.T) { Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, Ops: []KeyOp{KeyOpVerify, KeyOpEncrypt}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, @@ -763,7 +763,7 @@ func TestKey_MarshalCBOR(t *testing.T) { key: &Key{ Type: KeyTypeEC2, Algorithm: AlgorithmES256, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: []byte{0x01}, KeyLabelEC2Y: []byte{0x02, 0x03}, @@ -790,7 +790,7 @@ func TestKey_MarshalCBOR(t *testing.T) { name: "Symmetric", key: &Key{ Type: KeyTypeSymmetric, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: []byte{ 0x15, 0x52, 0x2e, 0xf1, 0x57, 0x29, 0xcc, 0xf3, 0x95, 0x09, 0xea, 0x5c, 0x15, 0xa2, 0x6b, 0xe9, @@ -851,7 +851,7 @@ func TestNewKeyOKP(t *testing.T) { want: &Key{ Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, KeyLabelOKPD: d, @@ -903,7 +903,7 @@ func TestNewNewKeyEC2(t *testing.T) { want: &Key{ Type: KeyTypeEC2, Algorithm: AlgorithmES256, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, @@ -916,7 +916,7 @@ func TestNewNewKeyEC2(t *testing.T) { want: &Key{ Type: KeyTypeEC2, Algorithm: AlgorithmES384, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP384, KeyLabelEC2X: ec384x, KeyLabelEC2Y: ec384y, @@ -929,7 +929,7 @@ func TestNewNewKeyEC2(t *testing.T) { want: &Key{ Type: KeyTypeEC2, Algorithm: AlgorithmES512, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: ec521x, KeyLabelEC2Y: ec521y, @@ -972,7 +972,7 @@ func TestNewKeySymmetric(t *testing.T) { }{ {"valid", args{[]byte{1, 2, 3}}, &Key{ Type: KeyTypeSymmetric, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: []byte{1, 2, 3}, }, }}, @@ -1058,7 +1058,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "OKP-Ed25519", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, }, }, @@ -1069,7 +1069,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "OKP-P256", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveP256, }, }, @@ -1080,7 +1080,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-P256", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, }, }, @@ -1091,7 +1091,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-P384", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP384, }, }, @@ -1102,7 +1102,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-P521", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP521, }, }, @@ -1113,7 +1113,7 @@ func TestKey_AlgorithmOrDefault(t *testing.T) { "EC2-Ed25519", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveEd25519, }, }, @@ -1151,7 +1151,7 @@ func TestNewKeyFromPrivate(t *testing.T) { }, &Key{ Algorithm: AlgorithmES256, Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: x, KeyLabelEC2Y: y, @@ -1172,7 +1172,7 @@ func TestNewKeyFromPrivate(t *testing.T) { "ed25519", ed25519.PrivateKey(append(okpd, okpx...)), &Key{ Algorithm: AlgorithmEdDSA, Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, KeyLabelOKPD: okpd, @@ -1213,7 +1213,7 @@ func TestNewKeyFromPublic(t *testing.T) { &Key{ Algorithm: AlgorithmES256, Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ecx, KeyLabelEC2Y: ecy, @@ -1231,7 +1231,7 @@ func TestNewKeyFromPublic(t *testing.T) { &Key{ Algorithm: AlgorithmEdDSA, Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, }, @@ -1270,7 +1270,7 @@ func TestKey_Signer(t *testing.T) { "without algorithm", &Key{ Type: KeyTypeOKP, Ops: []KeyOp{KeyOpSign}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, KeyLabelOKPD: d, @@ -1283,7 +1283,7 @@ func TestKey_Signer(t *testing.T) { "without key_ops", &Key{ Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, KeyLabelOKPD: d, @@ -1295,7 +1295,7 @@ func TestKey_Signer(t *testing.T) { { "invalid algorithm", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveP256, KeyLabelOKPX: x, KeyLabelOKPD: d, @@ -1308,7 +1308,7 @@ func TestKey_Signer(t *testing.T) { "can't sign", &Key{ Type: KeyTypeOKP, Ops: []KeyOp{KeyOpVerify}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, KeyLabelOKPD: d, @@ -1321,7 +1321,7 @@ func TestKey_Signer(t *testing.T) { "unsupported key", &Key{ Type: KeyTypeSymmetric, Ops: []KeyOp{KeyOpSign}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: d, }, }, @@ -1357,7 +1357,7 @@ func TestKey_Verifier(t *testing.T) { "without algorithm", &Key{ Type: KeyTypeOKP, Ops: []KeyOp{KeyOpVerify}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, }, @@ -1369,7 +1369,7 @@ func TestKey_Verifier(t *testing.T) { "without key_ops", &Key{ Type: KeyTypeOKP, Algorithm: AlgorithmEdDSA, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, }, @@ -1380,7 +1380,7 @@ func TestKey_Verifier(t *testing.T) { { "invalid algorithm", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveP256, KeyLabelOKPX: x, }, @@ -1392,7 +1392,7 @@ func TestKey_Verifier(t *testing.T) { "can't verify", &Key{ Type: KeyTypeOKP, Ops: []KeyOp{KeyOpSign}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: x, }, @@ -1404,7 +1404,7 @@ func TestKey_Verifier(t *testing.T) { "unsupported key", &Key{ Type: KeyTypeSymmetric, Ops: []KeyOp{KeyOpVerify}, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelSymmetricK: x, }, }, @@ -1442,7 +1442,7 @@ func TestKey_PrivateKey(t *testing.T) { { "CurveEd25519", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, KeyLabelOKPD: okpd, @@ -1453,7 +1453,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveEd25519 missing x", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPD: okpd, }, @@ -1463,7 +1463,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP256", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, @@ -1482,7 +1482,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP256 missing x and y", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2D: ec256d, }, @@ -1499,7 +1499,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP384", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP384, KeyLabelEC2X: ec384x, KeyLabelEC2Y: ec384y, @@ -1518,7 +1518,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "CurveP521", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: ec521x, KeyLabelEC2Y: ec521y, @@ -1543,7 +1543,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "OKP unknown curve", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: 70, KeyLabelOKPX: okpx, KeyLabelOKPD: okpd, @@ -1554,7 +1554,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "OKP missing d", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, }, @@ -1564,7 +1564,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "OKP incorrect x size", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: make([]byte, 10), KeyLabelOKPD: okpd, @@ -1575,7 +1575,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "OKP incorrect d size", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, KeyLabelOKPD: make([]byte, 5), @@ -1586,7 +1586,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "EC2 missing D", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, @@ -1597,7 +1597,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "EC2 unknown curve", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: 70, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, @@ -1609,7 +1609,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "EC2 incorrect x size", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec384x, KeyLabelEC2Y: ec256y, @@ -1621,7 +1621,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "EC2 incorrect y size", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec384y, @@ -1633,7 +1633,7 @@ func TestKey_PrivateKey(t *testing.T) { }, { "EC2 incorrect d size", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, @@ -1672,7 +1672,7 @@ func TestKey_PublicKey(t *testing.T) { { "CurveEd25519", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, KeyLabelOKPX: okpx, }, @@ -1682,7 +1682,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "CurveP256", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, @@ -1697,7 +1697,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "CurveP384", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP384, KeyLabelEC2X: ec384x, KeyLabelEC2Y: ec384y, @@ -1712,7 +1712,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "CurveP521", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP521, KeyLabelEC2X: ec521x, KeyLabelEC2Y: ec521y, @@ -1739,7 +1739,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "OKP missing X", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: CurveEd25519, }, }, @@ -1748,7 +1748,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "OKP unknown curve", &Key{ Type: KeyTypeOKP, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelOKPCurve: 70, KeyLabelOKPX: okpx, }, @@ -1758,7 +1758,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "EC2 missing X", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2Y: ec256y, }, @@ -1768,7 +1768,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "EC2 missing Y", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: CurveP256, KeyLabelEC2X: ec256x, }, @@ -1778,7 +1778,7 @@ func TestKey_PublicKey(t *testing.T) { }, { "EC2 unknown curve", &Key{ Type: KeyTypeEC2, - Params: map[interface{}]interface{}{ + Params: map[any]any{ KeyLabelEC2Curve: 70, KeyLabelEC2X: ec256x, KeyLabelEC2Y: ec256y, diff --git a/sign.go b/sign.go index cd86d11..a2bb6c0 100644 --- a/sign.go +++ b/sign.go @@ -236,7 +236,7 @@ func (s *Signature) toBeSigned(bodyProtected cbor.RawMessage, payload, external if external == nil { external = []byte{} } - sigStructure := []interface{}{ + sigStructure := []any{ "Signature", // context bodyProtected, // body_protected signProtected, // sign_protected diff --git a/sign1.go b/sign1.go index 48640ab..e1bd4d0 100644 --- a/sign1.go +++ b/sign1.go @@ -174,7 +174,7 @@ func (m *Sign1Message) toBeSigned(external []byte) ([]byte, error) { if external == nil { external = []byte{} } - sigStructure := []interface{}{ + sigStructure := []any{ "Signature1", // context protected, // body_protected external, // external_aad From 4451940c4c0ef1d968262ce511002f75d15215b8 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Fri, 11 Aug 2023 17:49:01 +0200 Subject: [PATCH 14/25] Add support for RS256, RS384, RS512 constants (#163) Signed-off-by: hslatman --- algorithm.go | 25 +++++++++++++++++++++++++ algorithm_test.go | 3 +++ conformance_test.go | 6 ++++++ signer.go | 2 +- signer_test.go | 12 +++++++++++- verifier.go | 2 +- verifier_test.go | 12 +++++++++++- 7 files changed, 58 insertions(+), 4 deletions(-) diff --git a/algorithm.go b/algorithm.go index 6fac36e..7aacef1 100644 --- a/algorithm.go +++ b/algorithm.go @@ -47,6 +47,25 @@ const ( AlgorithmReserved Algorithm = 0 ) +// Algorithms known, but not supported by this library. +// +// Signers and Verifiers requiring the algorithms below are not +// directly supported by this library. They need to be provided +// as an external [cose.Signer] or [cose.Verifier] implementation. +// +// An example use case where RS256 is allowed and used is in +// WebAuthn: https://www.w3.org/TR/webauthn-2/#sctn-sample-registration. +const ( + // RSASSA-PKCS1-v1_5 using SHA-256 by RFC 8812. + AlgorithmRS256 Algorithm = -257 + + // RSASSA-PKCS1-v1_5 using SHA-384 by RFC 8812. + AlgorithmRS384 Algorithm = -258 + + // RSASSA-PKCS1-v1_5 using SHA-512 by RFC 8812. + AlgorithmRS512 Algorithm = -259 +) + // Algorithm represents an IANA algorithm entry in the COSE Algorithms registry. // // # See Also @@ -65,6 +84,12 @@ func (a Algorithm) String() string { return "PS384" case AlgorithmPS512: return "PS512" + case AlgorithmRS256: + return "RS256" + case AlgorithmRS384: + return "RS384" + case AlgorithmRS512: + return "RS512" case AlgorithmES256: return "ES256" case AlgorithmES384: diff --git a/algorithm_test.go b/algorithm_test.go index fea3e0a..dbb1bd4 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -18,6 +18,9 @@ func TestAlgorithm_String(t *testing.T) { {AlgorithmPS256, "PS256"}, {AlgorithmPS384, "PS384"}, {AlgorithmPS512, "PS512"}, + {AlgorithmRS256, "RS256"}, + {AlgorithmRS384, "RS384"}, + {AlgorithmRS512, "RS512"}, {AlgorithmES256, "ES256"}, {AlgorithmES384, "ES384"}, {AlgorithmES512, "ES512"}, diff --git a/conformance_test.go b/conformance_test.go index cb76135..ae4e719 100644 --- a/conformance_test.go +++ b/conformance_test.go @@ -308,6 +308,12 @@ func mustNameToAlg(name string) cose.Algorithm { return cose.AlgorithmPS384 case "PS512": return cose.AlgorithmPS512 + case "RS256": + return cose.AlgorithmRS256 + case "RS384": + return cose.AlgorithmRS384 + case "RS512": + return cose.AlgorithmRS512 case "ES256": return cose.AlgorithmES256 case "ES384": diff --git a/signer.go b/signer.go index 33d42ef..b2d5f8c 100644 --- a/signer.go +++ b/signer.go @@ -89,6 +89,6 @@ func NewSigner(alg Algorithm, key crypto.Signer) (Signer, error) { key: key, }, nil default: - return nil, ErrAlgorithmNotSupported + return nil, fmt.Errorf("can't create new Signer for %s: %w", alg, ErrAlgorithmNotSupported) } } diff --git a/signer_test.go b/signer_test.go index 62a17a7..aba323b 100644 --- a/signer_test.go +++ b/signer_test.go @@ -110,10 +110,20 @@ func TestNewSigner(t *testing.T) { key: rsaKeyLowEntropy, wantErr: "RSA key must be at least 2048 bits long", }, + { + name: "unsupported rsa signing algorithm", + alg: AlgorithmRS256, + wantErr: "can't create new Signer for RS256: algorithm not supported", + }, { name: "unknown algorithm", alg: 0, - wantErr: "algorithm not supported", + wantErr: "can't create new Signer for Reserved: algorithm not supported", + }, + { + name: "unassigned algorithm", + alg: -1, + wantErr: "can't create new Signer for unknown algorithm value -1: algorithm not supported", }, } for _, tt := range tests { diff --git a/verifier.go b/verifier.go index 5e5c667..49fad00 100644 --- a/verifier.go +++ b/verifier.go @@ -75,6 +75,6 @@ func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { key: vk, }, nil default: - return nil, ErrAlgorithmNotSupported + return nil, fmt.Errorf("can't create new Verifier for %s: %w", alg, ErrAlgorithmNotSupported) } } diff --git a/verifier_test.go b/verifier_test.go index 03e1e37..2703b16 100644 --- a/verifier_test.go +++ b/verifier_test.go @@ -106,10 +106,20 @@ func TestNewVerifier(t *testing.T) { key: rsaKeyLowEntropy, wantErr: "RSA key must be at least 2048 bits long", }, + { + name: "unsupported rsa signing algorithm", + alg: AlgorithmRS256, + wantErr: "can't create new Verifier for RS256: algorithm not supported", + }, { name: "unknown algorithm", alg: 0, - wantErr: "algorithm not supported", + wantErr: "can't create new Verifier for Reserved: algorithm not supported", + }, + { + name: "unassigned algorithm", + alg: -1, + wantErr: "can't create new Verifier for unknown algorithm value -1: algorithm not supported", }, { name: "bogus ecdsa public key (point not on curve)", From da0f9a62ade748e21e0096bd54e07f1c007b5e58 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Fri, 25 Aug 2023 17:35:10 +0200 Subject: [PATCH 15/25] Improve Algorithm.String() message for unknown values (#167) Signed-off-by: qmuntal --- algorithm.go | 2 +- algorithm_test.go | 2 +- headers.go | 2 +- headers_test.go | 2 +- key_test.go | 4 ++-- signer.go | 8 +++++++- signer_test.go | 10 +++++----- verifier.go | 8 +++++++- verifier_test.go | 10 +++++----- 9 files changed, 30 insertions(+), 18 deletions(-) diff --git a/algorithm.go b/algorithm.go index 7aacef1..40d5256 100644 --- a/algorithm.go +++ b/algorithm.go @@ -103,7 +103,7 @@ func (a Algorithm) String() string { case AlgorithmReserved: return "Reserved" default: - return "unknown algorithm value " + strconv.Itoa(int(a)) + return "Algorithm(" + strconv.Itoa(int(a)) + ")" } } diff --git a/algorithm_test.go b/algorithm_test.go index dbb1bd4..e2985cf 100644 --- a/algorithm_test.go +++ b/algorithm_test.go @@ -26,7 +26,7 @@ func TestAlgorithm_String(t *testing.T) { {AlgorithmES512, "ES512"}, {AlgorithmEdDSA, "EdDSA"}, {AlgorithmReserved, "Reserved"}, - {7, "unknown algorithm value 7"}, + {7, "Algorithm(7)"}, } for _, tt := range tests { t.Run(tt.want, func(t *testing.T) { diff --git a/headers.go b/headers.go index ceacefa..2da2356 100644 --- a/headers.go +++ b/headers.go @@ -119,7 +119,7 @@ func (h ProtectedHeader) Algorithm() (Algorithm, error) { case int64: return Algorithm(alg), nil case string: - return AlgorithmReserved, fmt.Errorf("unknown algorithm value %q", alg) + return AlgorithmReserved, fmt.Errorf("Algorithm(%q)", alg) default: return AlgorithmReserved, ErrInvalidAlgorithm } diff --git a/headers_test.go b/headers_test.go index 004390b..2dedd44 100644 --- a/headers_test.go +++ b/headers_test.go @@ -423,7 +423,7 @@ func TestProtectedHeader_Algorithm(t *testing.T) { h: ProtectedHeader{ HeaderLabelAlgorithm: "foo", }, - wantErr: errors.New("unknown algorithm value \"foo\""), + wantErr: errors.New("Algorithm(\"foo\")"), }, { name: "invalid algorithm", diff --git a/key_test.go b/key_test.go index 92691e5..04f499f 100644 --- a/key_test.go +++ b/key_test.go @@ -861,7 +861,7 @@ func TestNewKeyOKP(t *testing.T) { }, { name: "invalid alg", args: args{Algorithm(-100), x, d}, want: nil, - wantErr: `unsupported algorithm "unknown algorithm value -100"`, + wantErr: `unsupported algorithm "Algorithm(-100)"`, }, { name: "x and d missing", args: args{AlgorithmEdDSA, nil, nil}, want: nil, @@ -940,7 +940,7 @@ func TestNewNewKeyEC2(t *testing.T) { }, { name: "invalid alg", args: args{Algorithm(-100), ec256x, ec256y, ec256d}, want: nil, - wantErr: `unsupported algorithm "unknown algorithm value -100"`, + wantErr: `unsupported algorithm "Algorithm(-100)"`, }, { name: "x, y and d missing", args: args{AlgorithmES512, nil, nil, nil}, want: nil, diff --git a/signer.go b/signer.go index b2d5f8c..9992b93 100644 --- a/signer.go +++ b/signer.go @@ -50,6 +50,7 @@ type DigestSigner interface { // Note: `*rsa.PrivateKey`, `*ecdsa.PrivateKey`, and `ed25519.PrivateKey` // implement `crypto.Signer`. func NewSigner(alg Algorithm, key crypto.Signer) (Signer, error) { + var errReason string switch alg { case AlgorithmPS256, AlgorithmPS384, AlgorithmPS512: vk, ok := key.Public().(*rsa.PublicKey) @@ -88,7 +89,12 @@ func NewSigner(alg Algorithm, key crypto.Signer) (Signer, error) { return &ed25519Signer{ key: key, }, nil + case AlgorithmReserved: + errReason = "can't be implemented" + case AlgorithmRS256, AlgorithmRS384, AlgorithmRS512: + errReason = "no built-in implementation available" default: - return nil, fmt.Errorf("can't create new Signer for %s: %w", alg, ErrAlgorithmNotSupported) + errReason = "unknown algorithm" } + return nil, fmt.Errorf("can't create new Signer for %s: %s: %w", alg, errReason, ErrAlgorithmNotSupported) } diff --git a/signer_test.go b/signer_test.go index aba323b..eb04f8a 100644 --- a/signer_test.go +++ b/signer_test.go @@ -113,17 +113,17 @@ func TestNewSigner(t *testing.T) { { name: "unsupported rsa signing algorithm", alg: AlgorithmRS256, - wantErr: "can't create new Signer for RS256: algorithm not supported", + wantErr: "can't create new Signer for RS256: no built-in implementation available: algorithm not supported", }, { - name: "unknown algorithm", - alg: 0, - wantErr: "can't create new Signer for Reserved: algorithm not supported", + name: "reserved algorithm", + alg: AlgorithmReserved, + wantErr: "can't create new Signer for Reserved: can't be implemented: algorithm not supported", }, { name: "unassigned algorithm", alg: -1, - wantErr: "can't create new Signer for unknown algorithm value -1: algorithm not supported", + wantErr: "can't create new Signer for Algorithm(-1): unknown algorithm: algorithm not supported", }, } for _, tt := range tests { diff --git a/verifier.go b/verifier.go index 49fad00..69e576f 100644 --- a/verifier.go +++ b/verifier.go @@ -39,6 +39,7 @@ type DigestVerifier interface { // // The returned signer for rsa and ecdsa keys also implements `cose.DigestSigner`. func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { + var errReason string switch alg { case AlgorithmPS256, AlgorithmPS384, AlgorithmPS512: vk, ok := key.(*rsa.PublicKey) @@ -74,7 +75,12 @@ func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { return &ed25519Verifier{ key: vk, }, nil + case AlgorithmReserved: + errReason = "can't be implemented" + case AlgorithmRS256, AlgorithmRS384, AlgorithmRS512: + errReason = "no built-in implementation available" default: - return nil, fmt.Errorf("can't create new Verifier for %s: %w", alg, ErrAlgorithmNotSupported) + errReason = "unknown algorithm" } + return nil, fmt.Errorf("can't create new Verifier for %s: %s: %w", alg, errReason, ErrAlgorithmNotSupported) } diff --git a/verifier_test.go b/verifier_test.go index 2703b16..5295133 100644 --- a/verifier_test.go +++ b/verifier_test.go @@ -109,17 +109,17 @@ func TestNewVerifier(t *testing.T) { { name: "unsupported rsa signing algorithm", alg: AlgorithmRS256, - wantErr: "can't create new Verifier for RS256: algorithm not supported", + wantErr: "can't create new Verifier for RS256: no built-in implementation available: algorithm not supported", }, { - name: "unknown algorithm", - alg: 0, - wantErr: "can't create new Verifier for Reserved: algorithm not supported", + name: "reserved algorithm", + alg: AlgorithmReserved, + wantErr: "can't create new Verifier for Reserved: can't be implemented: algorithm not supported", }, { name: "unassigned algorithm", alg: -1, - wantErr: "can't create new Verifier for unknown algorithm value -1: algorithm not supported", + wantErr: "can't create new Verifier for Algorithm(-1): unknown algorithm: algorithm not supported", }, { name: "bogus ecdsa public key (point not on curve)", From 6e436d202b2fac013836816b0cec5764fe774ea8 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 26 Sep 2023 16:30:36 +0200 Subject: [PATCH 16/25] Upgrade fxamacker/cbor to v2.5.0 (#166) Signed-off-by: qmuntal --- cbor.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- headers_test.go | 34 ++++++++++++++++++++++++++++++++++ sign_test.go | 37 ++++++++++++++++++++++++++----------- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/cbor.go b/cbor.go index 15bdc54..883f52b 100644 --- a/cbor.go +++ b/cbor.go @@ -95,7 +95,7 @@ func deterministicBinaryString(data cbor.RawMessage) (cbor.RawMessage, error) { } // fast path: return immediately if bstr is already deterministic - if err := decModeWithTagsForbidden.Valid(data); err != nil { + if err := decModeWithTagsForbidden.Wellformed(data); err != nil { return nil, err } ai := data[0] & 0x1f diff --git a/go.mod b/go.mod index 4a202d9..7e60365 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/veraison/go-cose go 1.18 -require github.com/fxamacker/cbor/v2 v2.4.0 +require github.com/fxamacker/cbor/v2 v2.5.0 require github.com/x448/float16 v0.8.4 // indirect diff --git a/go.sum b/go.sum index f424051..0f986a9 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= -github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= +github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= diff --git a/headers_test.go b/headers_test.go index 2dedd44..a28d6ae 100644 --- a/headers_test.go +++ b/headers_test.go @@ -2,6 +2,7 @@ package cose import ( "errors" + "math" "reflect" "testing" ) @@ -34,6 +35,39 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { }, }, { + name: "header with MinInt64 alg", + h: ProtectedHeader{ + HeaderLabelAlgorithm: math.MinInt64, + }, + want: []byte{ + 0x4b, // bstr + 0xa1, // map + 0x01, 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // alg + }, + }, + { + name: "canonical ordering", + h: ProtectedHeader{ + HeaderLabelAlgorithm: 1, + HeaderLabelCritical: []any{HeaderLabelAlgorithm}, + HeaderLabelContentType: 16, + HeaderLabelKeyID: []byte{1, 2, 3}, + HeaderLabelIV: []byte{1, 2, 3}, + 0x46: 0x47, + 0x66: 0x67, + }, + want: []byte{ + 0x58, 0x1a, // bstr + 0xa7, // map + 0x01, 0x01, // alg + 0x02, 0x81, 0x01, // crit + 0x03, 0x10, // cty + 0x04, 0x43, 0x01, 0x02, 0x03, // kid + 0x05, 0x43, 0x01, 0x02, 0x03, // iv + 0x18, 0x46, 0x18, 0x47, // 0x46: 0x47 + 0x18, 0x66, 0x18, 0x67, // 0x66: 0x67 + }, + }, { name: "nil header", h: nil, want: []byte{0x40}, diff --git a/sign_test.go b/sign_test.go index 793d967..4b1f701 100644 --- a/sign_test.go +++ b/sign_test.go @@ -654,13 +654,13 @@ func TestSignature_Sign_Internal(t *testing.T) { }, }, }, - protected: []byte{0x40, 0xa1, 0x00, 0x00}, + protected: []byte{0x43, 0xa1, 0x00, 0x00}, payload: []byte("hello world"), external: []byte{}, toBeSigned: []byte{ 0x85, // array type 0x69, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, // context - 0x40, 0xa1, 0x00, 0x00, // body_protected + 0x43, 0xa1, 0x00, 0x00, // body_protected 0x47, 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, // sign_protected 0x40, // external 0x4b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, // payload @@ -2222,7 +2222,7 @@ func TestSignature_toBeSigned(t *testing.T) { payload []byte external []byte want []byte - wantErr bool + wantErr string }{ { name: "valid signature", @@ -2233,12 +2233,12 @@ func TestSignature_toBeSigned(t *testing.T) { }, }, }, - protected: []byte{0x40, 0xa1, 0x00, 0x00}, + protected: []byte{0x43, 0xa1, 0x00, 0x00}, payload: []byte("hello world"), want: []byte{ 0x85, // array type 0x69, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, // context - 0x40, 0xa1, 0x00, 0x00, // body_protected + 0x43, 0xa1, 0x00, 0x00, // body_protected 0x47, 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, // sign_protected 0x40, // external 0x4b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, // payload @@ -2255,7 +2255,20 @@ func TestSignature_toBeSigned(t *testing.T) { }, protected: []byte{0x00}, payload: []byte{}, - wantErr: true, + wantErr: "cbor: require bstr type", + }, + { + name: "extraneous protected data", + s: &Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: algorithmMock, + }, + }, + }, + protected: []byte{0x40, 0xa1, 0x00, 0x00}, + payload: []byte("hello world"), + wantErr: "cbor: 3 bytes of extraneous data starting at index 1", }, { name: "invalid sign protected header", @@ -2268,7 +2281,7 @@ func TestSignature_toBeSigned(t *testing.T) { }, protected: []byte{0x40}, payload: []byte{}, - wantErr: true, + wantErr: "protected header: header label: require int / tstr type", }, { name: "invalid raw sign protected header", @@ -2279,15 +2292,17 @@ func TestSignature_toBeSigned(t *testing.T) { }, protected: []byte{0x40}, payload: []byte{}, - wantErr: true, + wantErr: "cbor: require bstr type", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.s.toBeSigned(tt.protected, tt.payload, tt.external) - if (err != nil) != tt.wantErr { - t.Errorf("Signature.toBeSigned() error = %v, wantErr %v", err, tt.wantErr) - return + if err != nil && (err.Error() != tt.wantErr) { + t.Fatalf("Signature.toBeSigned() error = %v, wantErr %v", err, tt.wantErr) + } + if err == nil && (tt.wantErr != "") { + t.Fatalf("Signature.toBeSigned() error = %v, wantErr %v", err, tt.wantErr) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Signature.toBeSigned() = %v, want %v", got, tt.want) From e7ac36de50034cef7d1909382bbbc2bff4d42917 Mon Sep 17 00:00:00 2001 From: Guilherme Balena Versiani Date: Sat, 30 Sep 2023 14:31:59 -0300 Subject: [PATCH 17/25] Add support to `COSE_Countersignature` (RFC 9338) (#172) Signed-off-by: Guilherme Balena Versiani --- README.md | 19 + countersign.go | 305 +++++++ countersign_test.go | 2044 +++++++++++++++++++++++++++++++++++++++++++ example_test.go | 117 +++ headers.go | 115 ++- headers_test.go | 347 +++++++- 6 files changed, 2921 insertions(+), 26 deletions(-) create mode 100644 countersign.go create mode 100644 countersign_test.go diff --git a/README.md b/README.md index 82b56ec..f7fccf3 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,20 @@ These are the required packages for each built-in cose.Algorithm: - cose.AlgorithmPS384, cose.AlgorithmPS512, cose.AlgorithmES384, cose.AlgorithmES512: `crypto/sha512` - cose.AlgorithmEdDSA: none +### Countersigning + +It is possible to countersign `cose.Sign1Message`, `cose.SignMessage`, `cose.Signature` and +`cose.Countersignature` objects and add them as unprotected headers. In order to do so, first create +a countersignature holder with `cose.NewCountersignature()` and call its `Sign` function passing +the parent object which is going to be countersigned. Then assign the countersignature as an +unprotected header `cose.HeaderLabelCounterSignatureV2` or, if preferred, maintain it as a +detached countersignature. + +When verifying countersignatures, it is necessary to pass the parent object in the `Verify` function +of the countersignature holder. + +See [example_test.go](./example_test.go) for examples. + ## Features ### Signing and Verifying Objects @@ -177,6 +191,11 @@ go-cose supports two different signature structures: - [cose.SignMessage](https://pkg.go.dev/github.com/veraison/go-cose#SignMessage) implements [COSE_Sign](https://datatracker.ietf.org/doc/html/rfc8152#section-4.1). > :warning: The COSE_Sign API is currently **EXPERIMENTAL** and may be changed or removed in a later release. In addition, the amount of functional and security testing it has received so far is significantly lower than the COSE_Sign1 API. +### Countersignatures + +go-cose supports [COSE_Countersignature](https://tools.ietf.org/html/rfc9338#section-3.1), check [cose.Countersignature](https://pkg.go.dev/github.com/veraison/go-cose#Countersignature). +> :warning: The COSE_Countersignature API is currently **EXPERIMENTAL** and may be changed or removed in a later release. + ### Built-in Algorithms go-cose has built-in supports the following algorithms: diff --git a/countersign.go b/countersign.go new file mode 100644 index 0000000..4b385c7 --- /dev/null +++ b/countersign.go @@ -0,0 +1,305 @@ +package cose + +import ( + "errors" + "fmt" + "io" + + "github.com/fxamacker/cbor/v2" +) + +// Countersignature represents a decoded COSE_Countersignature. +// +// Reference: https://tools.ietf.org/html/rfc9338#section-3.1 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +type Countersignature Signature + +// NewCountersignature returns a Countersignature with header initialized. +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func NewCountersignature() *Countersignature { + return (*Countersignature)(NewSignature()) +} + +// MarshalCBOR encodes Countersignature into a COSE_Countersignature object. +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func (s *Countersignature) MarshalCBOR() ([]byte, error) { + if s == nil { + return nil, errors.New("cbor: MarshalCBOR on nil Countersignature pointer") + } + // COSE_Countersignature share the exact same format as COSE_Signature + return (*Signature)(s).MarshalCBOR() +} + +// UnmarshalCBOR decodes a COSE_Countersignature object into Countersignature. +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func (s *Countersignature) UnmarshalCBOR(data []byte) error { + if s == nil { + return errors.New("cbor: UnmarshalCBOR on nil Countersignature pointer") + } + // COSE_Countersignature share the exact same format as COSE_Signature + return (*Signature)(s).UnmarshalCBOR(data) +} + +// Sign signs a Countersignature using the provided Signer. +// Signing a COSE_Countersignature requires the parent message to be completely +// fulfilled. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.3 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func (s *Countersignature) Sign(rand io.Reader, signer Signer, parent any, external []byte) error { + if s == nil { + return errors.New("signing nil Countersignature") + } + if len(s.Signature) > 0 { + return errors.New("Countersignature already has signature bytes") + } + + // check algorithm if present. + // `alg` header MUST present if there is no externally supplied data. + alg := signer.Algorithm() + if err := s.Headers.ensureSigningAlgorithm(alg, external); err != nil { + return err + } + + // sign the message + toBeSigned, err := s.toBeSigned(parent, external) + if err != nil { + return err + } + sig, err := signer.Sign(rand, toBeSigned) + if err != nil { + return err + } + + s.Signature = sig + return nil +} + +// Verify verifies the countersignature, returning nil on success or a suitable error +// if verification fails. +// Verifying a COSE_Countersignature requires the parent message. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-4.4 +// +// # Experimental +// +// Notice: The COSE Sign API is EXPERIMENTAL and may be changed or removed in a +// later release. +func (s *Countersignature) Verify(verifier Verifier, parent any, external []byte) error { + if s == nil { + return errors.New("verifying nil Countersignature") + } + if len(s.Signature) == 0 { + return ErrEmptySignature + } + + // check algorithm if present. + // `alg` header MUST present if there is no externally supplied data. + alg := verifier.Algorithm() + err := s.Headers.ensureVerificationAlgorithm(alg, external) + if err != nil { + return err + } + + // verify the message + toBeSigned, err := s.toBeSigned(parent, external) + if err != nil { + return err + } + return verifier.Verify(toBeSigned, s.Signature) +} + +// toBeSigned returns ToBeSigned from COSE_Countersignature object. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.3 +func (s *Countersignature) toBeSigned(target any, external []byte) ([]byte, error) { + var signProtected cbor.RawMessage + signProtected, err := s.Headers.MarshalProtected() + if err != nil { + return nil, err + } + return countersignToBeSigned(false, target, signProtected, external) +} + +// countersignToBeSigned constructs Countersign_structure, computes and returns ToBeSigned. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.3 +func countersignToBeSigned(abbreviated bool, target any, signProtected cbor.RawMessage, external []byte) ([]byte, error) { + // create a Countersign_structure and populate it with the appropriate fields. + // + // Countersign_structure = [ + // context : "CounterSignature" / "CounterSignature0" / + // "CounterSignatureV2" / "CounterSignature0V2" /, + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr, + // ? other_fields : [+ bstr ] + // ] + + var err error + var bodyProtected cbor.RawMessage + var otherFields []cbor.RawMessage + var payload []byte + + switch t := target.(type) { + case *SignMessage: + return countersignToBeSigned(abbreviated, *t, signProtected, external) + case SignMessage: + if len(t.Signatures) == 0 { + return nil, errors.New("SignMessage has no signatures yet") + } + bodyProtected, err = t.Headers.MarshalProtected() + if err != nil { + return nil, err + } + if t.Payload == nil { + return nil, ErrMissingPayload + } + payload = t.Payload + case *Sign1Message: + return countersignToBeSigned(abbreviated, *t, signProtected, external) + case Sign1Message: + if len(t.Signature) == 0 { + return nil, errors.New("Sign1Message was not signed yet") + } + bodyProtected, err = t.Headers.MarshalProtected() + if err != nil { + return nil, err + } + if t.Payload == nil { + return nil, ErrMissingPayload + } + payload = t.Payload + signature, err := encMode.Marshal(t.Signature) + if err != nil { + return nil, err + } + signature, err = deterministicBinaryString(signature) + if err != nil { + return nil, err + } + otherFields = []cbor.RawMessage{signature} + case *Signature: + return countersignToBeSigned(abbreviated, *t, signProtected, external) + case Signature: + bodyProtected, err = t.Headers.MarshalProtected() + if err != nil { + return nil, err + } + if len(t.Signature) == 0 { + return nil, errors.New("Signature was not signed yet") + } + payload = t.Signature + case *Countersignature: + return countersignToBeSigned(abbreviated, *t, signProtected, external) + case Countersignature: + bodyProtected, err = t.Headers.MarshalProtected() + if err != nil { + return nil, err + } + if len(t.Signature) == 0 { + return nil, errors.New("Countersignature was not signed yet") + } + payload = t.Signature + default: + return nil, fmt.Errorf("unsupported target %T", target) + } + + var context string + if len(otherFields) == 0 { + if abbreviated { + context = "CounterSignature0" + } else { + context = "CounterSignature" + } + } else { + if abbreviated { + context = "CounterSignature0V2" + } else { + context = "CounterSignatureV2" + } + } + + bodyProtected, err = deterministicBinaryString(bodyProtected) + if err != nil { + return nil, err + } + signProtected, err = deterministicBinaryString(signProtected) + if err != nil { + return nil, err + } + if external == nil { + external = []byte{} + } + countersigStructure := []any{ + context, // context + bodyProtected, // body_protected + signProtected, // sign_protected + external, // external_aad + payload, // payload + } + if len(otherFields) > 0 { + countersigStructure = append(countersigStructure, otherFields) + } + + // create the value ToBeSigned by encoding the Countersign_structure to a byte + // string. + return encMode.Marshal(countersigStructure) +} + +// Countersign0 performs an abbreviated signature over a parent message using +// the provided Signer. +// +// The parent message must be completely fulfilled prior signing. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.2 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func Countersign0(rand io.Reader, signer Signer, parent any, external []byte) ([]byte, error) { + toBeSigned, err := countersignToBeSigned(true, parent, []byte{0x40}, external) + if err != nil { + return nil, err + } + return signer.Sign(rand, toBeSigned) +} + +// VerifyCountersign0 verifies an abbreviated signature over a parent message +// using the provided Verifier. +// +// Reference: https://datatracker.ietf.org/doc/html/rfc9338#section-3.2 +// +// # Experimental +// +// Notice: The COSE Countersignature API is EXPERIMENTAL and may be changed or +// removed in a later release. +func VerifyCountersign0(verifier Verifier, parent any, external, signature []byte) error { + toBeSigned, err := countersignToBeSigned(true, parent, []byte{0x40}, external) + if err != nil { + return err + } + return verifier.Verify(toBeSigned, signature) +} diff --git a/countersign_test.go b/countersign_test.go new file mode 100644 index 0000000..2bb4903 --- /dev/null +++ b/countersign_test.go @@ -0,0 +1,2044 @@ +package cose + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "reflect" + "testing" +) + +func TestCountersignature_MarshalCBOR(t *testing.T) { + tests := []struct { + name string + s *Countersignature + want []byte + wantErr string + }{ + { + name: "valid message", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelContentType: 42, + }, + }, + Signature: []byte("bar"), + }, + want: []byte{ + 0x83, // array of size 3 + 0x43, 0xa1, 0x01, 0x26, // protected + 0xa1, 0x03, 0x18, 0x2a, // unprotected + 0x43, 0x62, 0x61, 0x72, // signature + }, + }, + { + name: "nil signature", + s: nil, + wantErr: "cbor: MarshalCBOR on nil Countersignature pointer", + }, + { + name: "nil signature", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelContentType: 42, + }, + }, + Signature: nil, + }, + wantErr: "empty signature", + }, + { + name: "empty signature", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelContentType: 42, + }, + }, + Signature: []byte{}, + }, + wantErr: "empty signature", + }, + { + name: "invalid protected header", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: make(chan bool), + }, + Unprotected: UnprotectedHeader{ + HeaderLabelContentType: 42, + }, + }, + Signature: []byte("bar"), + }, + wantErr: "protected header: header parameter: alg: require int / tstr type", + }, + { + name: "invalid unprotected header", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + "foo": make(chan bool), + }, + }, + Signature: []byte("bar"), + }, + wantErr: "cbor: unsupported type: chan bool", + }, + { + name: "protected has IV and unprotected has PartialIV error", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + HeaderLabelIV: []byte(""), + }, + Unprotected: UnprotectedHeader{ + HeaderLabelPartialIV: []byte(""), + }, + }, + Signature: []byte("bar"), + }, + wantErr: "IV (protected) and PartialIV (unprotected) parameters must not both be present", + }, + { + name: "protected has PartialIV and unprotected has IV error", + s: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + HeaderLabelPartialIV: []byte(""), + }, + Unprotected: UnprotectedHeader{ + HeaderLabelIV: []byte(""), + }, + }, + Signature: []byte("bar"), + }, + wantErr: "IV (unprotected) and PartialIV (protected) parameters must not both be present", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.MarshalCBOR() + if err != nil && (err.Error() != tt.wantErr) { + t.Errorf("Countersignature.MarshalCBOR() error = %v, wantErr %v", err, tt.wantErr) + return + } else if err == nil && (tt.wantErr != "") { + t.Errorf("Countersignature.MarshalCBOR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Countersignature.MarshalCBOR() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCountersignature_UnmarshalCBOR(t *testing.T) { + // test nil pointer + t.Run("nil Countersignature pointer", func(t *testing.T) { + var sig *Countersignature + data := []byte{0x83, 0x40, 0xa0, 0x41, 0x00} + if err := sig.UnmarshalCBOR(data); err == nil { + t.Errorf("want error on nil *Countersignature") + } + }) + + // test others + tests := []struct { + name string + data []byte + want Countersignature + wantErr string + }{ + { + name: "valid signature struct", + data: []byte{ + 0x83, + 0x43, 0xa1, 0x01, 0x26, // protected + 0xa1, 0x03, 0x18, 0x2a, // unprotected + 0x43, 0x62, 0x61, 0x72, // signature + }, + want: Countersignature{ + Headers: Headers{ + RawProtected: []byte{0x43, 0xa1, 0x01, 0x26}, + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + RawUnprotected: []byte{0xa1, 0x03, 0x18, 0x2a}, + Unprotected: UnprotectedHeader{ + HeaderLabelContentType: int64(42), + }, + }, + Signature: []byte("bar"), + }, + }, + { + name: "nil CBOR data", + data: nil, + wantErr: "cbor: invalid Signature object", + }, + { + name: "empty CBOR data", + data: []byte{}, + wantErr: "cbor: invalid Signature object", + }, + { + name: "tagged signature", // issue #30 + data: []byte{ + 0x83, + 0x40, 0xa0, // empty headers + 0xcb, 0xa1, 0x00, // tagged signature + }, + wantErr: "cbor: CBOR tag isn't allowed", + }, + { + name: "nil signature", + data: []byte{ + 0x83, + 0x40, 0xa0, // empty headers + 0xf6, // nil signature + }, + wantErr: "empty signature", + }, + { + name: "empty signature", + data: []byte{ + 0x83, + 0x40, 0xa0, // empty headers + 0x40, // empty signature + }, + wantErr: "empty signature", + }, + { + name: "mismatch type", + data: []byte{ + 0x40, + }, + wantErr: "cbor: invalid Signature object", + }, + { + name: "smaller array size", + data: []byte{ + 0x82, + 0x40, 0xa0, // empty headers + }, + wantErr: "cbor: invalid Signature object", + }, + { + name: "larger array size", + data: []byte{ + 0x84, + 0x40, 0xa0, // empty headers + 0x41, 0x00, // signature + 0x40, + }, + wantErr: "cbor: invalid Signature object", + }, + { + name: "signature as a byte array", + data: []byte{ + 0x83, + 0x40, 0xa0, // empty headers + 0x81, 0x00, // signature + }, + wantErr: "cbor: require bstr type", + }, + { + name: "protected has IV and unprotected has PartialIV", + data: []byte{ + 0x83, + 0x46, 0xa1, 0x5, 0x63, 0x66, 0x6f, 0x6f, // protected + 0xa1, 0x6, 0x63, 0x62, 0x61, 0x72, // unprotected + 0x43, 0x62, 0x61, 0x72, // signature + }, + wantErr: "cbor: invalid protected header: protected header: header parameter: IV: require bstr type", + }, + { + name: "protected has PartialIV and unprotected has IV", + data: []byte{ + 0x83, + 0x46, 0xa1, 0x6, 0x63, 0x66, 0x6f, 0x6f, // protected + 0xa1, 0x5, 0x63, 0x62, 0x61, 0x72, // unprotected + 0x43, 0x62, 0x61, 0x72, // signature + }, + wantErr: "cbor: invalid protected header: protected header: header parameter: Partial IV: require bstr type", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got Countersignature + err := got.UnmarshalCBOR(tt.data) + if err != nil && (err.Error() != tt.wantErr) { + t.Errorf("Countersignature.UnmarshalCBOR() error = %v, wantErr %v", err, tt.wantErr) + return + } else if err == nil && (tt.wantErr != "") { + t.Errorf("Countersignature.UnmarshalCBOR() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Countersignature.MarshalCBOR() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCountersignature_Sign(t *testing.T) { + // generate key and set up signer / verifier + alg := AlgorithmES256 + key := generateTestECDSAKey(t) + signer, err := NewSigner(alg, key) + if err != nil { + t.Fatalf("NewSigner() error = %v", err) + } + verifier, err := NewVerifier(alg, key.Public()) + if err != nil { + t.Fatalf("NewVerifier() error = %v", err) + } + + // sign / verify round trip + type args struct { + parent any + external []byte + } + tests := []struct { + name string + sig *Countersignature + onSign args + onVerify args + wantErr string + check func(t *testing.T, s *Countersignature) + }{ + { + name: "valid message", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + }, + { + name: "valid message with external", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte("foo"), + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte("foo"), + }, + }, + { + name: "nil external", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: nil, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: nil, + }, + }, + { + name: "mixed nil / empty external", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: nil, + }, + }, + { + name: "nil payload", // payload is detached + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + }, + }, + onSign: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: nil, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: nil, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "missing payload", + }, + { + name: "mismatch algorithm", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES512, + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "algorithm mismatch: signer ES256: header ES512", + }, + { + name: "missing algorithm", + sig: &Countersignature{}, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + check: func(t *testing.T, s *Countersignature) { + got, err := s.Headers.Protected.Algorithm() + if err != nil { + t.Errorf("Countersignature.Headers.Protected.Algorithm() error = %v", err) + } + if got != alg { + t.Errorf("Countersignature.Headers.Protected.Algorithm() = %v, want %v", got, alg) + } + }, + }, + { + name: "missing algorithm with raw protected", + sig: &Countersignature{ + Headers: Headers{ + RawProtected: []byte{0x40}, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "algorithm not found", + }, + { + name: "missing algorithm with externally supplied data", + sig: &Countersignature{}, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte("foo"), + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte("foo"), + }, + check: func(t *testing.T, s *Countersignature) { + _, err := s.Headers.Protected.Algorithm() + if want := ErrAlgorithmNotFound; err != want { + t.Errorf("Countersignature.Headers.Protected.Algorithm() error = %v, wantErr %v", err, want) + } + }, + }, + { + name: "double signing", + sig: &Countersignature{ + Signature: []byte("foobar"), + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "Countersignature already has signature bytes", + }, + { + name: "nil countersignature", + sig: nil, + onSign: args{}, + onVerify: args{}, + wantErr: "signing nil Countersignature", + }, + { + name: "empty body protected header, zero-length byte string is used", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + }, + { + name: "invalid protected header", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + HeaderLabelCritical: []any{}, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + HeaderLabelCritical: []any{}, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "protected header: header parameter: crit: empty crit header", + }, + { + name: "countersign a Signature that was not signed is not allowed", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + }, + external: []byte{}, + }, + wantErr: "Signature was not signed yet", + }, + { + name: "countersign a valid SignMessage", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + Signatures: []*Signature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + Signatures: []*Signature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + }, + external: []byte{}, + }, + }, + { + name: "countersign a SignMessage without signatures", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + onVerify: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + wantErr: "SignMessage has no signatures yet", + }, + { + name: "countersign a valid Sign1Message", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: []byte("hello world"), + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: []byte("hello world"), + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + }, + { + name: "countersign a Sign1Message without signature", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + onVerify: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + wantErr: "Sign1Message was not signed yet", + }, + { + name: "countersign a valid Countersignature", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + }, + { + name: "countersign a Countersignature without signature is not allowed", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + }, + external: []byte{}, + }, + wantErr: "Countersignature was not signed yet", + }, + { + name: "countersign an unsupported parent", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + onSign: args{ + parent: struct{}{}, + external: []byte{}, + }, + onVerify: args{ + parent: struct{}{}, + external: []byte{}, + }, + wantErr: "unsupported target struct {}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.sig.Sign(rand.Reader, signer, tt.onSign.parent, tt.onSign.external) + if err != nil { + if err.Error() != tt.wantErr { + t.Errorf("Countersignature.Sign() error = %v, wantErr %v", err, tt.wantErr) + } + return + } else if tt.wantErr != "" { + t.Errorf("Countersignature.Sign() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.check != nil { + tt.check(t, tt.sig) + } + if err := tt.sig.Verify(verifier, tt.onVerify.parent, tt.onVerify.external); err != nil { + t.Errorf("Countersignature.Verify() error = %v", err) + } + }) + } +} + +func TestCountersign0(t *testing.T) { + // generate key and set up signer / verifier + alg := AlgorithmES256 + key := generateTestECDSAKey(t) + signer, err := NewSigner(alg, key) + if err != nil { + t.Fatalf("NewSigner() error = %v", err) + } + verifier, err := NewVerifier(alg, key.Public()) + if err != nil { + t.Fatalf("NewVerifier() error = %v", err) + } + + // sign / verify round trip + type args struct { + parent any + external []byte + } + tests := []struct { + name string + onSign args + onVerify args + wantErr string + }{ + { + name: "valid message", + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + }, + { + name: "valid message with external", + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte("foo"), + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte("foo"), + }, + }, + { + name: "nil external", + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: nil, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: nil, + }, + }, + { + name: "mixed nil / empty external", + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: nil, + }, + }, + { + name: "nil payload", // payload is detached + onSign: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: nil, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: nil, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "missing payload", + }, + { + name: "empty body protected header, zero-length byte string is used", + onSign: args{ + parent: Signature{ + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + }, + { + name: "invalid protected header", + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + HeaderLabelCritical: []any{}, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + HeaderLabelCritical: []any{}, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + wantErr: "protected header: header parameter: crit: empty crit header", + }, + { + name: "countersign a Signature that was not signed is not allowed", + onSign: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + }, + external: []byte{}, + }, + wantErr: "Signature was not signed yet", + }, + { + name: "countersign a valid SignMessage", + onSign: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + Signatures: []*Signature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + Signatures: []*Signature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + }, + }, + external: []byte{}, + }, + }, + { + name: "countersign a SignMessage without signatures", + onSign: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + onVerify: args{ + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + wantErr: "SignMessage has no signatures yet", + }, + { + name: "countersign a valid Sign1Message", + onSign: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: []byte("hello world"), + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Payload: []byte("hello world"), + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + }, + { + name: "countersign a Sign1Message without signature", + onSign: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + onVerify: args{ + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("hello world"), + }, + external: []byte{}, + }, + wantErr: "Sign1Message was not signed yet", + }, + { + name: "countersign a valid Countersignature", + onSign: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmES256, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("42"), + }, + }, + Signature: []byte{ + 0x74, 0xc6, 0xac, 0xa6, 0x7d, 0x7a, 0x00, 0xea, + 0x0f, 0x9b, 0x86, 0xb3, 0x85, 0x7a, 0x7d, 0x36, + 0xd2, 0x77, 0x91, 0x73, 0x40, 0x09, 0x35, 0x4e, + 0x8c, 0x9f, 0xd6, 0x03, 0x37, 0xab, 0x43, 0xf5, + }, + }, + external: []byte{}, + }, + }, + { + name: "countersign a Countersignature without signature is not allowed", + onSign: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + }, + external: []byte{}, + }, + onVerify: args{ + parent: Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{}, + Unprotected: UnprotectedHeader{}, + }, + }, + external: []byte{}, + }, + wantErr: "Countersignature was not signed yet", + }, + { + name: "countersign an unsupported parent", + onSign: args{ + parent: struct{}{}, + external: []byte{}, + }, + onVerify: args{ + parent: struct{}{}, + external: []byte{}, + }, + wantErr: "unsupported target struct {}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sig, err := Countersign0(rand.Reader, signer, tt.onSign.parent, tt.onSign.external) + if err != nil { + if err.Error() != tt.wantErr { + t.Errorf("Countersign0() error = %v, wantErr %v", err, tt.wantErr) + } + return + } else if tt.wantErr != "" { + t.Errorf("Countersign0() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err := VerifyCountersign0(verifier, tt.onVerify.parent, tt.onVerify.external, sig); err != nil { + t.Errorf("VerifyCountersign0() error = %v", err) + } + }) + } +} + +func TestCountersignature_Sign_Internal(t *testing.T) { + tests := []struct { + name string + sig *Countersignature + parent any + external []byte + toBeSigned []byte + }{ + { + // adapted from https://github.com/cose-wg/Examples/blob/master/countersign/signed1-01.json + // by modifying the context to "CounterSignatureV2" (to adjust to RFC 9338), including the + // signature as other_fields and altering the countersignature algorithm. + name: "COSE_Sign1 countersignature conformance test", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: algorithmMock, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + parent: Sign1Message{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEdDSA, + HeaderLabelContentType: 0, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Payload: []byte("This is the content."), + Signature: []byte{ + 0x71, 0x42, 0xfd, 0x2f, 0xf9, 0x6d, 0x56, 0xdb, + 0x85, 0xbe, 0xe9, 0x05, 0xa7, 0x6b, 0xa1, 0xd0, + 0xb7, 0x32, 0x1a, 0x95, 0xc8, 0xc4, 0xd3, 0x60, + 0x7c, 0x57, 0x81, 0x93, 0x2b, 0x7a, 0xfb, 0x87, + 0x11, 0x49, 0x7d, 0xfa, 0x75, 0x1b, 0xf4, 0x0b, + 0x58, 0xb3, 0xbc, 0xc3, 0x23, 0x00, 0xb1, 0x48, + 0x7f, 0x3d, 0xb3, 0x40, 0x85, 0xee, 0xf0, 0x13, + 0xbf, 0x08, 0xf4, 0xa4, 0x4d, 0x6f, 0xef, 0x0d, + }, + }, + toBeSigned: []byte{ + 0x86, // array(6) + 0x72, // text(18) "CounterSignatureV2" + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x56, 0x32, + 0x45, // bytes(5) + 0xa2, 0x01, 0x27, 0x03, 0x00, + 0x47, // bytes(7) + 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, + 0x40, // bytes(0) + 0x54, // bytes(20) "This is the content." + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2e, + 0x81, // array(1) + 0x58, 0x40, // bytes(64) signature: + 0x71, 0x42, 0xfd, 0x2f, 0xf9, 0x6d, 0x56, 0xdb, + 0x85, 0xbe, 0xe9, 0x05, 0xa7, 0x6b, 0xa1, 0xd0, + 0xb7, 0x32, 0x1a, 0x95, 0xc8, 0xc4, 0xd3, 0x60, + 0x7c, 0x57, 0x81, 0x93, 0x2b, 0x7a, 0xfb, 0x87, + 0x11, 0x49, 0x7d, 0xfa, 0x75, 0x1b, 0xf4, 0x0b, + 0x58, 0xb3, 0xbc, 0xc3, 0x23, 0x00, 0xb1, 0x48, + 0x7f, 0x3d, 0xb3, 0x40, 0x85, 0xee, 0xf0, 0x13, + 0xbf, 0x08, 0xf4, 0xa4, 0x4d, 0x6f, 0xef, 0x0d, + }, + }, + { + // adapted from https://github.com/cose-wg/Examples/blob/master/countersign/signed-01.json + name: "COSE_Signature countersignature conformance test", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: algorithmMock, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + parent: Signature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEdDSA, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0x8e, 0x1b, 0xe2, 0xf9, 0x45, 0x3d, 0x26, 0x48, + 0x12, 0xe5, 0x90, 0x49, 0x91, 0x32, 0xbe, 0xf3, + 0xfb, 0xf9, 0xee, 0x9d, 0xb2, 0x7c, 0x2c, 0x16, + 0x87, 0x88, 0xe3, 0xb7, 0xeb, 0xe5, 0x06, 0xc0, + 0x4f, 0xd3, 0xd1, 0x9f, 0xaa, 0x9f, 0x51, 0x23, + 0x2a, 0xf5, 0xc9, 0x59, 0xe4, 0xef, 0x47, 0x92, + 0x88, 0x34, 0x64, 0x7f, 0x56, 0xdf, 0xbe, 0x93, + 0x91, 0x12, 0x88, 0x4d, 0x08, 0xef, 0x25, 0x05, + }, + }, + toBeSigned: []byte{ + 0x85, // array(5) + 0x70, // text(16) "CounterSignature" + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x43, // bytes(3) + 0xa1, 0x01, 0x27, + 0x47, // bytes(7) + 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, + 0x40, // bytes(0) + 0x58, 0x40, // bytes(64) signature: + 0x8e, 0x1b, 0xe2, 0xf9, 0x45, 0x3d, 0x26, 0x48, + 0x12, 0xe5, 0x90, 0x49, 0x91, 0x32, 0xbe, 0xf3, + 0xfb, 0xf9, 0xee, 0x9d, 0xb2, 0x7c, 0x2c, 0x16, + 0x87, 0x88, 0xe3, 0xb7, 0xeb, 0xe5, 0x06, 0xc0, + 0x4f, 0xd3, 0xd1, 0x9f, 0xaa, 0x9f, 0x51, 0x23, + 0x2a, 0xf5, 0xc9, 0x59, 0xe4, 0xef, 0x47, 0x92, + 0x88, 0x34, 0x64, 0x7f, 0x56, 0xdf, 0xbe, 0x93, + 0x91, 0x12, 0x88, 0x4d, 0x08, 0xef, 0x25, 0x05, + }, + }, + { + // adapted from https://github.com/cose-wg/Examples/blob/master/countersign/signed-03.json + name: "COSE_Sign countersignature conformance test", + sig: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: algorithmMock, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + parent: SignMessage{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelContentType: 0, + }, + Unprotected: UnprotectedHeader{}, + }, + Payload: []byte("This is the content."), + Signatures: []*Signature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEdDSA, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0x77, 0xf3, 0xea, 0xcd, 0x11, 0x85, 0x2c, 0x4b, + 0xf9, 0xcb, 0x1d, 0x72, 0xfa, 0xbe, 0x6b, 0x26, + 0xfb, 0xa1, 0xd7, 0x60, 0x92, 0xb2, 0xb5, 0xb7, + 0xec, 0x83, 0xb8, 0x35, 0x57, 0x65, 0x22, 0x64, + 0xe6, 0x96, 0x90, 0xdb, 0xc1, 0x17, 0x2d, 0xdc, + 0x0b, 0xf8, 0x84, 0x11, 0xc0, 0xd2, 0x5a, 0x50, + 0x7f, 0xdb, 0x24, 0x7a, 0x20, 0xc4, 0x0d, 0x5e, + 0x24, 0x5f, 0xab, 0xd3, 0xfc, 0x9e, 0xc1, 0x06, + }, + }, + }, + }, + toBeSigned: []byte{ + 0x85, // array(5) + 0x70, // text(16) "CounterSignature" + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x43, // bytes(3) + 0xa1, 0x03, 0x00, + 0x47, // bytes(7) + 0xa1, 0x01, 0x3a, 0x6d, 0x6f, 0x63, 0x6a, + 0x40, // bytes(0) + 0x54, // bytes(20) "This is the content." + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2e, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + want := make([]byte, 64) + _, err := rand.Read(want) + if err != nil { + t.Fatalf("rand.Read() error = %v", err) + } + signer := newMockSigner(t) + signer.setup(tt.toBeSigned, want) + + sig := tt.sig + if err := sig.Sign(rand.Reader, signer, tt.parent, tt.external); err != nil { + t.Errorf("Countersignature.Sign() error = %v", err) + return + } + if got := sig.Signature; !bytes.Equal(got, want) { + t.Errorf("Countersignature.Sign() signature = %s, want %s", + hex.EncodeToString(got), + hex.EncodeToString(want)) + } + }) + } +} diff --git a/example_test.go b/example_test.go index afdf128..80f909d 100644 --- a/example_test.go +++ b/example_test.go @@ -229,3 +229,120 @@ func ExampleDigestSigner() { // Output: // digest signed } + +// This example demonstrates signing and verifying countersignatures. +// +// The COSE Countersignature API is EXPERIMENTAL and may be changed or removed in a later +// release. +func ExampleCountersignature() { + // create a signature holder + sigHolder := cose.NewSignature() + sigHolder.Headers.Protected.SetAlgorithm(cose.AlgorithmES512) + sigHolder.Headers.Unprotected[cose.HeaderLabelKeyID] = []byte("1") + + // create message to be signed + msgToSign := cose.NewSignMessage() + msgToSign.Payload = []byte("hello world") + msgToSign.Signatures = append(msgToSign.Signatures, sigHolder) + + // create a signer + privateKey, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + signer, _ := cose.NewSigner(cose.AlgorithmES512, privateKey) + + // sign message + msgToSign.Sign(rand.Reader, nil, signer) + + // create a countersignature holder for the message + msgCountersig := cose.NewCountersignature() + msgCountersig.Headers.Protected.SetAlgorithm(cose.AlgorithmES512) + msgCountersig.Headers.Unprotected[cose.HeaderLabelKeyID] = []byte("11") + + // create a countersigner + counterPrivateKey, _ := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + countersigner, _ := cose.NewSigner(cose.AlgorithmES512, counterPrivateKey) + + // countersign message + err := msgCountersig.Sign(rand.Reader, countersigner, msgToSign, nil) + if err != nil { + panic(err) + } + + // add countersignature as message unprotected header; notice the + // countersignature should be assigned as reference + msgToSign.Headers.Unprotected[cose.HeaderLabelCounterSignatureV2] = msgCountersig + + // create a countersignature holder for the signature + sigCountersig := cose.NewCountersignature() + sigCountersig.Headers.Protected.SetAlgorithm(cose.AlgorithmES512) + sigCountersig.Headers.Unprotected[cose.HeaderLabelKeyID] = []byte("11") + + // countersign signature + err = sigCountersig.Sign(rand.Reader, countersigner, sigHolder, nil) + if err != nil { + panic(err) + } + + // add countersignature as signature unprotected header; notice the + // countersignature should be assigned as reference + sigHolder.Headers.Unprotected[cose.HeaderLabelCounterSignatureV2] = sigCountersig + + sig, err := msgToSign.MarshalCBOR() + if err != nil { + panic(err) + } + fmt.Println("message signed and countersigned") + + // create a verifier from a trusted public key + publicKey := counterPrivateKey.Public() + verifier, err := cose.NewVerifier(cose.AlgorithmES512, publicKey) + if err != nil { + panic(err) + } + + // decode COSE_Sign message containing countersignatures + var msgToVerify cose.SignMessage + err = msgToVerify.UnmarshalCBOR(sig) + if err != nil { + panic(err) + } + + // unwrap the message countersignature; the example assumes the header is a + // single countersignature, but real code would consider checking if it + // consists in a slice of countersignatures too. + msgCountersigHdr := msgToVerify.Headers.Unprotected[cose.HeaderLabelCounterSignatureV2] + msgCountersigToVerify := msgCountersigHdr.(*cose.Countersignature) + + // verify message countersignature + err = msgCountersigToVerify.Verify(verifier, msgToVerify, nil) + if err != nil { + panic(err) + } + fmt.Println("message countersignature verified") + + // unwrap the signature countersignature; the example assumes the header is a + // single countersignature, but real code would consider checking if it + // consists in a slice of countersignatures too. + sig0 := msgToVerify.Signatures[0] + sigCountersigHdr := sig0.Headers.Unprotected[cose.HeaderLabelCounterSignatureV2] + sigCountersigToVerify := sigCountersigHdr.(*cose.Countersignature) + + // verify signature countersignature + err = sigCountersigToVerify.Verify(verifier, sig0, nil) + if err != nil { + panic(err) + } + fmt.Println("signature countersignature verified") + + // tamper the message and verification should fail + msgToVerify.Payload = []byte("foobar") + err = msgCountersigToVerify.Verify(verifier, msgToVerify, nil) + if err != cose.ErrVerification { + panic(err) + } + fmt.Println("verification error as expected") + // Output: + // message signed and countersigned + // message countersignature verified + // signature countersignature verified + // verification error as expected +} diff --git a/headers.go b/headers.go index 2da2356..1daaaaa 100644 --- a/headers.go +++ b/headers.go @@ -12,18 +12,20 @@ import ( // // Reference: https://www.iana.org/assignments/cose/cose.xhtml#header-parameters const ( - HeaderLabelAlgorithm int64 = 1 - HeaderLabelCritical int64 = 2 - HeaderLabelContentType int64 = 3 - HeaderLabelKeyID int64 = 4 - HeaderLabelIV int64 = 5 - HeaderLabelPartialIV int64 = 6 - HeaderLabelCounterSignature int64 = 7 - HeaderLabelCounterSignature0 int64 = 9 - HeaderLabelX5Bag int64 = 32 - HeaderLabelX5Chain int64 = 33 - HeaderLabelX5T int64 = 34 - HeaderLabelX5U int64 = 35 + HeaderLabelAlgorithm int64 = 1 + HeaderLabelCritical int64 = 2 + HeaderLabelContentType int64 = 3 + HeaderLabelKeyID int64 = 4 + HeaderLabelIV int64 = 5 + HeaderLabelPartialIV int64 = 6 + HeaderLabelCounterSignature int64 = 7 + HeaderLabelCounterSignature0 int64 = 9 + HeaderLabelCounterSignatureV2 int64 = 11 + HeaderLabelCounterSignature0V2 int64 = 12 + HeaderLabelX5Bag int64 = 32 + HeaderLabelX5Chain int64 = 33 + HeaderLabelX5T int64 = 34 + HeaderLabelX5U int64 = 35 ) // ProtectedHeader contains parameters that are to be cryptographically @@ -197,10 +199,22 @@ func (h *UnprotectedHeader) UnmarshalCBOR(data []byte) error { if err := validateHeaderLabelCBOR(data); err != nil { return err } - var header map[any]any - if err := decMode.Unmarshal(data, &header); err != nil { + + // In order to unmarshal Countersignature structs, it is required to make it + // in two steps instead of one. + var partialHeader map[any]cbor.RawMessage + if err := decMode.Unmarshal(data, &partialHeader); err != nil { return err } + header := make(map[any]any, len(partialHeader)) + for k, v := range partialHeader { + v, err := unmarshalUnprotected(k, v) + if err != nil { + return err + } + header[k] = v + } + if err := validateHeaderParameters(header, false); err != nil { return fmt.Errorf("unprotected header: %w", err) } @@ -208,6 +222,47 @@ func (h *UnprotectedHeader) UnmarshalCBOR(data []byte) error { return nil } +// unmarshalUnprotected produces known structs such as counter signature +// headers, otherwise it defaults to regular unmarshaling to simple types. +func unmarshalUnprotected(key any, value cbor.RawMessage) (any, error) { + label, ok := normalizeLabel(key) + if ok { + switch label { + case HeaderLabelCounterSignature, HeaderLabelCounterSignatureV2: + return unmarshalAsCountersignature(value) + default: + } + } + + return unmarshalAsAny(value) +} + +// unmarshalAsCountersignature produces a Countersignature struct or a list of +// Countersignatures. +func unmarshalAsCountersignature(value cbor.RawMessage) (any, error) { + var result1 Countersignature + err := decMode.Unmarshal(value, &result1) + if err == nil { + return &result1, nil + } + var result2 []*Countersignature + err = decMode.Unmarshal(value, &result2) + if err == nil { + return result2, nil + } + return nil, errors.New("invalid Countersignature object / list of objects") +} + +// unmarshalAsAny produces simple types. +func unmarshalAsAny(value cbor.RawMessage) (any, error) { + var result any + err := decMode.Unmarshal(value, &result) + if err != nil { + return nil, err + } + return result, nil +} + // Headers represents "two buckets of information that are not // considered to be part of the payload itself, but are used for // holding information about content, algorithms, keys, or evaluation @@ -437,6 +492,38 @@ func validateHeaderParameters(h map[any]any, protected bool) error { if hasLabel(h, HeaderLabelIV) { return errors.New("header parameter: IV and PartialIV: parameters must not both be present") } + case HeaderLabelCounterSignature: + if protected { + return errors.New("header parameter: counter signature: not allowed") + } + if _, ok := value.(*Countersignature); !ok { + if _, ok := value.([]*Countersignature); !ok { + return errors.New("header parameter: counter signature is not a Countersignature or a list") + } + } + case HeaderLabelCounterSignature0: + if protected { + return errors.New("header parameter: countersignature0: not allowed") + } + if !canBstr(value) { + return errors.New("header parameter: countersignature0: require bstr type") + } + case HeaderLabelCounterSignatureV2: + if protected { + return errors.New("header parameter: Countersignature version 2: not allowed") + } + if _, ok := value.(*Countersignature); !ok { + if _, ok := value.([]*Countersignature); !ok { + return errors.New("header parameter: Countersignature version 2 is not a Countersignature or a list") + } + } + case HeaderLabelCounterSignature0V2: + if protected { + return errors.New("header parameter: Countersignature0 version 2: not allowed") + } + if !canBstr(value) { + return errors.New("header parameter: Countersignature0 version 2: require bstr type") + } } } return nil diff --git a/headers_test.go b/headers_test.go index a28d6ae..fcac4a4 100644 --- a/headers_test.go +++ b/headers_test.go @@ -81,10 +81,10 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { name: "various types of integer label", h: ProtectedHeader{ uint(10): 0, - uint8(11): 0, - uint16(12): 0, - uint32(13): 0, - uint64(14): 0, + uint8(13): 0, + uint16(14): 0, + uint32(15): 0, + uint64(16): 0, int(-1): 0, int8(-2): 0, int16(-3): 0, @@ -95,10 +95,10 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { 0x55, // bstr 0xaa, // map 0x0a, 0x00, - 0x0b, 0x00, - 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, + 0x0f, 0x00, + 0x10, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, @@ -196,6 +196,20 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { }, wantErr: "protected header: header parameter: content type: require tstr / uint type", }, + { + name: "invalid counter signature", + h: ProtectedHeader{ + HeaderLabelCounterSignature: &Countersignature{}, + }, + wantErr: "protected header: header parameter: counter signature: not allowed", + }, + { + name: "invalid counter signature version 2", + h: ProtectedHeader{ + HeaderLabelCounterSignatureV2: &Countersignature{}, + }, + wantErr: "protected header: header parameter: Countersignature version 2: not allowed", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -367,6 +381,24 @@ func TestProtectedHeader_UnmarshalCBOR(t *testing.T) { }, wantErr: "protected header: header parameter: Partial IV: require bstr type", }, + { + name: "countersignature0 is not allowed", + data: []byte{ + 0x54, 0xa1, 0x09, 0x58, 0x10, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + }, + wantErr: "protected header: header parameter: countersignature0: not allowed", + }, + { + name: "Countersignature0V2 is not allowed", + data: []byte{ + 0x54, 0xa1, 0x0c, 0x58, 0x10, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + }, + wantErr: "protected header: header parameter: Countersignature0 version 2: not allowed", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -585,10 +617,10 @@ func TestUnprotectedHeader_MarshalCBOR(t *testing.T) { name: "various types of integer label", h: UnprotectedHeader{ uint(10): 0, - uint8(11): 0, - uint16(12): 0, - uint32(13): 0, - uint64(14): 0, + uint8(13): 0, + uint16(14): 0, + uint32(15): 0, + uint64(16): 0, int(-1): 0, int8(-2): 0, int16(-3): 0, @@ -598,10 +630,10 @@ func TestUnprotectedHeader_MarshalCBOR(t *testing.T) { want: []byte{ 0xaa, // map 0x0a, 0x00, - 0x0b, 0x00, - 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, + 0x0f, 0x00, + 0x10, 0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, @@ -648,6 +680,194 @@ func TestUnprotectedHeader_MarshalCBOR(t *testing.T) { }, wantErr: "unprotected header: header parameter: crit: not allowed", }, + { + name: "malformed counter signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignature: "", + }, + wantErr: "unprotected header: header parameter: counter signature is not a Countersignature or a list", + }, + { + name: "counter signature without signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignature: []*Countersignature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + }, + }, + wantErr: "empty signature", + }, + { + name: "complete counter signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignature: []*Countersignature{ + { + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + }, + want: []byte{ + 0xa1, 0x07, 0x81, 0x83, 0x43, 0xa1, 0x01, 0x27, 0xa1, + 0x04, 0x42, 0x31, 0x31, 0x58, 0x40, 0xb7, 0xca, 0xcb, + 0xa2, 0x85, 0xc4, 0xcd, 0x3e, 0xd2, 0xf0, 0x14, 0x6f, + 0x41, 0x98, 0x86, 0x14, 0x4c, 0xa6, 0x38, 0xd0, 0x87, + 0xde, 0x12, 0x3d, 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, + 0xab, 0xc4, 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, + 0xb7, 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, 0xf2, + 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "malformed Countersignature version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: "", + }, + wantErr: "unprotected header: header parameter: Countersignature version 2 is not a Countersignature or a list", + }, + { + name: "Countersignature version 2 without signature", + h: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + }, + }, + wantErr: "empty signature", + }, + { + name: "complete Countersignature version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: &Countersignature{ + Headers: Headers{ + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + want: []byte{ + 0xa1, 0x0b, 0x83, 0x43, 0xa1, 0x01, 0x27, 0xa1, 0x04, + 0x42, 0x31, 0x31, 0x58, 0x40, 0xb7, 0xca, 0xcb, 0xa2, + 0x85, 0xc4, 0xcd, 0x3e, 0xd2, 0xf0, 0x14, 0x6f, 0x41, + 0x98, 0x86, 0x14, 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, + 0x12, 0x3d, 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, + 0xc4, 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, 0xfe, + 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, 0xf2, 0x43, + 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "complete countersignature0", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + want: []byte{ + 0xa1, 0x09, 0x58, 0x40, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "invalid countersignature0", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0: "11", + }, + wantErr: "unprotected header: header parameter: countersignature0: require bstr type", + }, + { + name: "complete Countersignature0 version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0V2: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + want: []byte{ + 0xa1, 0x0c, 0x58, 0x40, + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + { + name: "invalid Countersignature0 version 2", + h: UnprotectedHeader{ + HeaderLabelCounterSignature0V2: "11", + }, + wantErr: "unprotected header: header parameter: Countersignature0 version 2: require bstr type", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -773,7 +993,110 @@ func TestUnprotectedHeader_UnmarshalCBOR(t *testing.T) { }, wantErr: "unprotected header: header parameter: crit: not allowed", }, + { + name: "single counter signature present", + data: []byte{ + 0xa1, // { + 0x07, 0x83, // / counter signature / 7: [ + 0x43, 0xa1, 0x01, 0x27, // / protected h'a10127' / << { / alg / 1:-8 / EdDSA / } >>, + 0xa1, 0x04, 0x42, 0x31, 0x31, // / unprotected / { / kid / 4: '11' }, + 0x58, 0x40, // bytes(64) + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + want: UnprotectedHeader{ + HeaderLabelCounterSignature: &Countersignature{ + Headers: Headers{ + RawProtected: []byte{0x43, 0xa1, 0x01, 0x27}, + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + RawUnprotected: []byte{0xa1, 0x04, 0x42, 0x31, 0x31}, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + }, + { + name: "CountersignatureV2 in a list", + data: []byte{ + 0xa1, // { + 0x0b, 0x81, 0x83, // / counter signature / 7: [ [ + 0x43, 0xa1, 0x01, 0x27, // / protected h'a10127' / << { / alg / 1:-8 / EdDSA / } >>, + 0xa1, 0x04, 0x42, 0x31, 0x31, // / unprotected / { / kid / 4: '11' }, + 0x58, 0x40, // bytes(64) + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + want: UnprotectedHeader{ + HeaderLabelCounterSignatureV2: []*Countersignature{ + { + Headers: Headers{ + RawProtected: []byte{0x43, 0xa1, 0x01, 0x27}, + Protected: ProtectedHeader{ + HeaderLabelAlgorithm: AlgorithmEd25519, + }, + RawUnprotected: []byte{0xa1, 0x04, 0x42, 0x31, 0x31}, + Unprotected: UnprotectedHeader{ + HeaderLabelKeyID: []byte("11"), + }, + }, + Signature: []byte{ + 0xb7, 0xca, 0xcb, 0xa2, 0x85, 0xc4, 0xcd, 0x3e, + 0xd2, 0xf0, 0x14, 0x6f, 0x41, 0x98, 0x86, 0x14, + 0x4c, 0xa6, 0x38, 0xd0, 0x87, 0xde, 0x12, 0x3d, + 0x40, 0x01, 0x67, 0x30, 0x8a, 0xce, 0xab, 0xc4, + 0xb5, 0xe5, 0xc6, 0xa4, 0x0c, 0x0d, 0xe0, 0xb7, + 0x11, 0x67, 0xa3, 0x91, 0x75, 0xea, 0x56, 0xc1, + 0xfe, 0x96, 0xc8, 0x9e, 0x5e, 0x7d, 0x30, 0xda, + 0xf2, 0x43, 0x8a, 0x45, 0x61, 0x59, 0xa2, 0x0a, + }, + }, + }, + }, + }, + { + name: "counter signature should be object or list", + data: []byte{ + 0xa1, // { + 0x07, 0x42, 0xf0, 0x0d, // / counter signature / 7: h'f00d' + }, + wantErr: "invalid Countersignature object / list of objects", + }, + { + name: "CountersignatureV2 should be object or list", + data: []byte{ + 0xa1, // { + 0x0b, 0x42, 0xf0, 0x0d, // / counter signature / 11: h'f00d' + }, + wantErr: "invalid Countersignature object / list of objects", + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var got UnprotectedHeader From 2c01a2e4bc522fd2ea3273e59e86509ce2b96300 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Fri, 6 Oct 2023 17:58:30 +0200 Subject: [PATCH 18/25] Validate content type header parameter (#176) Signed-off-by: qmuntal --- headers.go | 18 +++++++++++++++++- headers_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/headers.go b/headers.go index 1daaaaa..2999207 100644 --- a/headers.go +++ b/headers.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math/big" + "strings" "github.com/fxamacker/cbor/v2" ) @@ -471,9 +472,24 @@ func validateHeaderParameters(h map[any]any, protected bool) error { return fmt.Errorf("header parameter: crit: %w", err) } case HeaderLabelContentType: - if !canTstr(value) && !canUint(value) { + is_tstr := canTstr(value) + if !is_tstr && !canUint(value) { return errors.New("header parameter: content type: require tstr / uint type") } + if is_tstr { + v := value.(string) + if len(v) == 0 { + return errors.New("header parameter: content type: require non-empty string") + } + if v[0] == ' ' || v[len(v)-1] == ' ' { + return errors.New("header parameter: content type: require no leading/trailing whitespace") + } + // Basic check that the content type is of form type/subtype. + // We don't check the precise definition though (RFC 6838 Section 4.2). + if strings.Count(v, "/") != 1 { + return errors.New("header parameter: content type: require text of form type/subtype") + } + } case HeaderLabelKeyID: if !canBstr(value) { return errors.New("header parameter: kid: require bstr type") diff --git a/headers_test.go b/headers_test.go index fcac4a4..efb11f9 100644 --- a/headers_test.go +++ b/headers_test.go @@ -210,6 +210,41 @@ func TestProtectedHeader_MarshalCBOR(t *testing.T) { }, wantErr: "protected header: header parameter: Countersignature version 2: not allowed", }, + { + name: "content type empty", + h: ProtectedHeader{ + HeaderLabelContentType: "", + }, + wantErr: "protected header: header parameter: content type: require non-empty string", + }, + { + name: "content type leading space", + h: ProtectedHeader{ + HeaderLabelContentType: " a/b", + }, + wantErr: "protected header: header parameter: content type: require no leading/trailing whitespace", + }, + { + name: "content type trailing space", + h: ProtectedHeader{ + HeaderLabelContentType: "a/b ", + }, + wantErr: "protected header: header parameter: content type: require no leading/trailing whitespace", + }, + { + name: "content type no slash", + h: ProtectedHeader{ + HeaderLabelContentType: "ab", + }, + wantErr: "protected header: header parameter: content type: require text of form type/subtype", + }, + { + name: "content type too many slashes", + h: ProtectedHeader{ + HeaderLabelContentType: "a/b/c", + }, + wantErr: "protected header: header parameter: content type: require text of form type/subtype", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 6d7578a4e172119ff00493130b38eefc7e58d43d Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Thu, 14 Dec 2023 04:57:20 +0800 Subject: [PATCH 19/25] chore: fix duplicated import in the example (#179) Signed-off-by: Shiwei Zhang --- example_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/example_test.go b/example_test.go index 80f909d..ffc2482 100644 --- a/example_test.go +++ b/example_test.go @@ -5,7 +5,6 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/sha512" - _ "crypto/sha512" "fmt" "github.com/veraison/go-cose" From 2b6f94f33749f8dbf96d199cfe7c498df431c6cf Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Thu, 14 Dec 2023 04:58:20 +0800 Subject: [PATCH 20/25] docs: fix the syntax of CODEOWNERS (#180) Signed-off-by: Shiwei Zhang --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 767969e..c3ebac9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,4 +1,4 @@ # To be kept in sync with: [community/OWNERS](https://github.com/veraison/community/blob/main/OWNERS) # and the GitHub Team: [go-cose-maintainers](https://github.com/orgs/veraison/teams/go-cose-maintainers) -* henkbirkholz qmuntal roywill setrofim shizhMSFT simonfrost-arm SteveLasker thomas-fossati yogeshbdeshpande +* @henkbirkholz @qmuntal @roywill @setrofim @shizhMSFT @simonfrost-arm @SteveLasker @thomas-fossati @yogeshbdeshpande From 2300d5c96dbdca1061541459471a78add54e5b57 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Fri, 26 Jan 2024 08:53:38 -0800 Subject: [PATCH 21/25] Update Readme for latest release (#182) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7fccf3..0de72d3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A golang library for the [COSE specification][cose-spec] ## Project Status -**Current Release**: [go-cose v1.0.0][current-release] +**Current Release**: [go-cose v1.1.0][current-release] The project was *initially* forked from the upstream [mozilla-services/go-cose][mozilla-go-cose] project, however the Veraison and Mozilla maintainers have agreed to retire the mozilla-services/go-cose project and focus on [veraison/go-cose][veraison-go-cose] as the active project. @@ -243,4 +243,4 @@ go test -fuzz=FuzzSign1 [mozilla-contributors]: https://github.com/mozilla-services/go-cose/graphs/contributors [mozilla-go-cose]: http://github.com/mozilla-services/go-cose [veraison-go-cose]: https://github.com/veraison/go-cose -[current-release]: https://github.com/veraison/go-cose/releases/tag/v1.0.0 +[current-release]: https://github.com/veraison/go-cose/releases/tag/v1.1.0 From 09d6cfa18c96b6f53c870ca56658fabc0a80983c Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Fri, 12 Jul 2024 01:13:07 +0800 Subject: [PATCH 22/25] refactor!: deprecate crypto elliptic (#187) Signed-off-by: Shiwei Zhang --- .github/workflows/ci.yml | 2 +- ecdsa_test.go | 12 ------------ go.mod | 2 +- key.go | 17 ++++++++--------- key_test.go | 11 ++--------- verifier.go | 12 +++++++++--- verifier_test.go | 15 ++++++++++++++- 7 files changed, 35 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9693a83..8e8e9e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - go-version: [1.18, 1.19] + go-version: [1.21, 1.22] runs-on: ubuntu-latest steps: - name: Install Go diff --git a/ecdsa_test.go b/ecdsa_test.go index 1cb2c12..e549c02 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -172,18 +172,6 @@ func generateTestECDSAKey(t *testing.T) *ecdsa.PrivateKey { return key } -func Test_customCurveKeySigner(t *testing.T) { - // https://github.com/veraison/go-cose/issues/59 - pCustom := *elliptic.P256().Params() - pCustom.Name = "P-custom" - pCustom.BitSize /= 2 - key, err := ecdsa.GenerateKey(&pCustom, rand.Reader) - if err != nil { - t.Fatalf("ecdsa.GenerateKey() error = %v", err) - } - testSignVerify(t, AlgorithmES256, key, false) -} - func Test_ecdsaKeySigner(t *testing.T) { key := generateTestECDSAKey(t) testSignVerify(t, AlgorithmES256, key, false) diff --git a/go.mod b/go.mod index 7e60365..fad879c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/veraison/go-cose -go 1.18 +go 1.21 require github.com/fxamacker/cbor/v2 v2.5.0 diff --git a/key.go b/key.go index 71ad331..f3dbbb9 100644 --- a/key.go +++ b/key.go @@ -692,6 +692,7 @@ func (k *Key) PublicKey() (crypto.PublicKey, error) { } // PrivateKey returns a crypto.PrivateKey generated using Key's parameters. +// Compressed point is not supported for EC2 keys. func (k *Key) PrivateKey() (crypto.PrivateKey, error) { if err := k.validate(KeyOpSign); err != nil { return nil, err @@ -703,8 +704,12 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { switch alg { case AlgorithmES256, AlgorithmES384, AlgorithmES512: - var curve elliptic.Curve + _, x, y, d := k.EC2() + if len(x) == 0 || len(y) == 0 { + return nil, fmt.Errorf("%w: compressed point not supported", ErrInvalidPrivKey) + } + var curve elliptic.Curve switch alg { case AlgorithmES256: curve = elliptic.P256() @@ -714,14 +719,8 @@ func (k *Key) PrivateKey() (crypto.PrivateKey, error) { curve = elliptic.P521() } - _, x, y, d := k.EC2() - var bx, by *big.Int - if len(x) == 0 || len(y) == 0 { - bx, by = curve.ScalarBaseMult(d) - } else { - bx = new(big.Int).SetBytes(x) - by = new(big.Int).SetBytes(y) - } + bx := new(big.Int).SetBytes(x) + by := new(big.Int).SetBytes(y) bd := new(big.Int).SetBytes(d) return &ecdsa.PrivateKey{ diff --git a/key_test.go b/key_test.go index 04f499f..6e5ee0c 100644 --- a/key_test.go +++ b/key_test.go @@ -1487,15 +1487,8 @@ func TestKey_PrivateKey(t *testing.T) { KeyLabelEC2D: ec256d, }, }, - &ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{ - Curve: elliptic.P256(), - X: new(big.Int).SetBytes(ec256x), - Y: new(big.Int).SetBytes(ec256y), - }, - D: new(big.Int).SetBytes(ec256d), - }, - "", + nil, + "invalid private key: compressed point not supported", }, { "CurveP384", &Key{ Type: KeyTypeEC2, diff --git a/verifier.go b/verifier.go index 69e576f..72d2039 100644 --- a/verifier.go +++ b/verifier.go @@ -36,8 +36,11 @@ type DigestVerifier interface { // NewVerifier returns a verifier with a given public key. // Only golang built-in crypto public keys of type `*rsa.PublicKey`, // `*ecdsa.PublicKey`, and `ed25519.PublicKey` are accepted. +// When `*ecdsa.PublicKey` is specified, its curve must be supported by +// crypto/ecdh. // -// The returned signer for rsa and ecdsa keys also implements `cose.DigestSigner`. +// The returned signer for rsa and ecdsa keys also implements +// `cose.DigestSigner`. func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { var errReason string switch alg { @@ -60,8 +63,11 @@ func NewVerifier(alg Algorithm, key crypto.PublicKey) (Verifier, error) { if !ok { return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) } - if !vk.Curve.IsOnCurve(vk.X, vk.Y) { - return nil, errors.New("public key point is not on curve") + if _, err := vk.ECDH(); err != nil { + if err.Error() == "ecdsa: invalid public key" { + return nil, fmt.Errorf("%v: %w", alg, ErrInvalidPubKey) + } + return nil, fmt.Errorf("%v: %w: %v", alg, ErrInvalidPubKey, err) } return &ecdsaVerifier{ alg: alg, diff --git a/verifier_test.go b/verifier_test.go index 5295133..82d84ca 100644 --- a/verifier_test.go +++ b/verifier_test.go @@ -48,6 +48,13 @@ func TestNewVerifier(t *testing.T) { // craft an EC public key with the x-coord not on curve ecdsaKeyPointNotOnCurve := generateBogusECKey() + // craft an EC public key with a curve not supported by crypto/ecdh + ecdsaKeyUnsupportedCurve := &ecdsa.PublicKey{ + Curve: ecdsaKey.Curve.Params(), + X: ecdsaKey.X, + Y: ecdsaKey.Y, + } + // run tests tests := []struct { name string @@ -125,7 +132,13 @@ func TestNewVerifier(t *testing.T) { name: "bogus ecdsa public key (point not on curve)", alg: AlgorithmES256, key: ecdsaKeyPointNotOnCurve, - wantErr: "public key point is not on curve", + wantErr: "ES256: invalid public key", + }, + { + name: "ecdsa public key with unsupported curve", + alg: AlgorithmES256, + key: ecdsaKeyUnsupportedCurve, + wantErr: "ES256: invalid public key: ecdsa: unsupported curve by crypto/ecdh", }, } for _, tt := range tests { From 96ea810f5a3a1086c9e833be245072087a5e02b0 Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Fri, 12 Jul 2024 10:52:23 -0700 Subject: [PATCH 23/25] Document the Release Process (#188) Signed-off-by: steve lasker Co-authored-by: Orie Steele Co-authored-by: Yogesh Deshpande --- .vscode/settings.json | 5 ++ release-checklist.md | 25 +++++++ release-management.md | 152 ++++++++++++++++++++++++++++++++++++++++++ reports/README.md | 6 +- 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 release-checklist.md create mode 100644 release-management.md diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ad24d8c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "supermajority" + ] +} \ No newline at end of file diff --git a/release-checklist.md b/release-checklist.md new file mode 100644 index 0000000..abbbf48 --- /dev/null +++ b/release-checklist.md @@ -0,0 +1,25 @@ +# Release Checklist + +## Overview + +This document describes the checklist to publish a release via GitHub workflow. + +The maintainers may periodically update this checklist based on feedback. + +NOTE: Make sure the dependencies in `go.mod` file are expected by the release. +For example, if there are dependencies on certain version of notation library (notation-go or notation-core-go) or ORAS library (oras-go), make sure that version of library is released first, and the version number is updated accordingly in `go.mod` file. +After updating go.mod file, run `go mod tidy` to ensure the go.sum file is also updated with any potential changes. + +## Release Process + +1. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. +For example, `version="v1.0.0-alpha.1"`. +1. Bump up the `Version` in [internal/version/version.go](internal/version/version.go#L5) and open a PR for the changes. +1. Wait for the PR merge. +1. Be on the main branch connected to the actual repository (not a fork) and `git pull`. +Ensure `git log -1` shows the latest commit on the main branch. +1. Create a tag `git tag -am $version $version` +1. `git tag` and ensure the name in the list added looks correct, then push the tag directly to the repository by `git push --follow-tags`. +1. Wait for the completion of the GitHub action [release-github](https://github.com/veraison/go-cose/blob/main/.github/workflows/ci.yml). +1. Check the new draft release, revise the release description, and publish the release. +1. Announce the release in the community. diff --git a/release-management.md b/release-management.md new file mode 100644 index 0000000..4d66ce0 --- /dev/null +++ b/release-management.md @@ -0,0 +1,152 @@ +# go-cose Release Management + +## Overview + +This document describes [go-cose][go-cose] project release management, which includes release criteria, versioning, supported releases, and supported upgrades. + +The go-cose project maintainers strive to provide a stable go-lang implementation for interacting with [COSE][ietf-cose] constructs. +Stable implies appropriate and measured changes to the library assuring consumers have the necessary functionality to interact with COSE objects. +If you or your project require added functionality, or bug fixes, please open an issue or create a pull request. +The project welcomes all contributions from adding functionality, implementing testing, security reviews to the release management. +Please see [here](https://github.com/veraison#contributing) for how to contribute. + +The maintainers may periodically update this policy based on feedback. + +## Release Versioning + +Consumers of the go-cose project may build directly from main, or pull from released builds. +Builds from main must reference the git-commit as the version: `v1.0.0-2300d5c` + +All go-cose [releases][releases] follow a go-lang flavored derivation (`v*`) of the [semver][sem-ver] format, with optional pre-release labels. + +Logical Progression of a release: `v1.0.0-2300d5c` --> `v1.0.0-alpha.1` --> `v1.0.0-alpha.2` --> `v1.0.0-rc.1` --> `v1.0.0` + +A new major or minor release will not have an automated build/release posted until the branch reaches alpha quality. + +- all versions use a preface of `v` +- `X` is the [Major](#major-releases) version +- `Y` is the [Minor](#minor-releases) version +- `Z` is the [Patch](#patch-releases) version +- _Optional_ `-alpha.n` | `-rc.n` [pre-release](#pre-release) version + - Each incremental alpha or rc build will bump the suffix (`n`) number. + - It's not expected to have more than 9 alphas or rcs. +The suffix will be a single digit. + - If > 9 builds do occur, the format will simply use two digit indicators (`v1.0.0-alpha.10`) + +_**Note**: Pre-releases will NOT use the github pre-release flag._ + +## Branch Management + +To meet the projects stability goals, go-cose does not currently operate with multiple feature branches. +All active development happens in main. +For each release, a branch is created for servicing, following the versioning name. +`v1.0.0-alpha-1` would have an associated [v1.0.0-alpha.1](https://github.com/veraison/go-cose/tree/v1.0.0-alpha.1) branch. +All version and branch names are lower case. + +### Major Releases + +As a best practice, consumers should opt-into new capabilities through major releases. +The go-cose project will not add new functionality to patches or minor releases as this could create a new surface area that may be exploited. +Consumers should make explicit opt-in decisions to upgrade, or possibly downgrade if necessary due to unexpected breaking changes. + +The go-cose project will issue major releases when: + +- Functionality has changed +- Breaking changes are required + +Each major release will go through one or more `-alpha.n` and `-rc.n` pre-release phases. + +### Minor Releases + +The go-cose project will issue minor releases when incremental improvements, or bug fixes are added to existing functionality. +Minor releases will increment the minor field within the [semver][sem-ver] format. + +Each minor release will go through one or more `-alpha.n` and `-rc.n` pre-release phases. + +### Patch Releases + +Patch Releases include bug and security fixes. +Patches will branch from the released branch being patched. +Fixes completed in main may be ported to a patch release if the maintainers believe the effort is justified by requests from the go-cose community. +If a bug fix requires new incremental, non-breaking change functionality, a new minor release may be issued. + +Principals of a patch release: + +- Should be a "safe bet" to upgrade to. +- No breaking changes. +- No feature or surface area changes. +- A "bug fix" that may be a breaking change may require a major release. +- Applicable fixes, including security fixes, may be cherry-picked from main into the latest supported minor release-X.Y branches. +- Patch releases are cut from a release-X.Y.Z branch. + +Each patch release will go through one or more `-alpha.n` and `-rc.n` pre-release phases. + +### Pre-Release + +As builds of main become stable, and a pending release is planned, a pre-release build will be made. +Pre-releases go through one or more `-alpha.n` releases, followed by one or more incremental `-rc.n` releases. + +- **alpha.n:** `X.Y.Z-alpha.n` + - alpha release, cut from the branch where development occurs. +To minimize branch management, no additional branches are maintained for each incremental release. + - Considered an unstable release which should only be used for early development purposes. + - Released incrementally until no additional issues and prs are made against the release. + - Once no triaged issues or pull requests (prs) are scoped to the release, a release candidate (rc) is cut. + - To minimize confusion, and the risk of an alpha being widely deployed, alpha branches and released binaries may be removed at the discretion, and a [two-thirds supermajority][super-majority] vote, of the maintainers. +Maintainers will create an Issue, and vote upon it for transparency to the decision to remove a release and/or branch. + - Not [supported](#supported-releases) +- **rc.n:** `X.Y.Z-rc.n` + - Released as needed before a final version is released + - Bugfixes on new features only as reported through usage + - An rc is not expected to revert to an alpha release. + - Once no triaged issues or prs are scoped to the release, an final version is cut. + - A release candidate will typically have at least two weeks of bake time, providing the community time to provide feedback. + - Release candidates are cut from the branch where the work is done. + - To minimize confusion, and the risk of an rc being widely deployed, rc branches and released binaries may be removed at the discretion, and a [two-thirds supermajority][super-majority] vote, of the maintainers. +Maintainers will create an Issue, and vote upon it for transparency to the decision to remove a release and/or branch. + - Not [supported](#supported-releases) + +## Supported Releases + +The go-cose maintainers expect to "support" n (current) and n-1 major.minor releases. +"Support" means we expect users to be running that version in production. +For example, when v1.3.0 comes out, v1.1.x will no longer be supported for patches, and the maintainers encourage users to upgrade to a supported version as soon as possible. +Support will be provided best effort by the maintainers via GitHub issues and pull requests from the community. + +The go-cose maintainers expect users to stay up-to-date with the versions of go-cose release they use in production, but understand that it may take time to upgrade. +We expect users to be running approximately the latest patch release of a given minor release and encourage users to upgrade as soon as possible. + +While pre-releases may be deleted at the discretion of the maintainers, all Major, Minor and Patch releases should be maintained. +Only in extreme circumstances, as agreed upon by a [two-thirds supermajority][super-majority] of the maintainers, shall a release be removed. + +Applicable fixes, including security fixes, may be cherry-picked into the release branch, depending on severity and feasibility. +Patch releases are cut from that branch as needed. + +## Security Reviews + +The go-cose library is an sdk around underlying crypto libraries, tailored to COSE scenarios. +The go-cose library does not implement cryptographic functionality, reducing the potential risk. +To assure go-cose had the proper baseline, two [security reviews](./reports) were conducted prior to the [v1.0.0](https://github.com/veraison/go-cose/releases/tag/v1.0.0) release + +For each release, new security reviews are evaluated by the maintainers as required or optional. +The go-cose project welcomes additional security reviews. +See [SECURITY.md](./SECURITY.md) for more information. + +## Glossary of Terms + +- **X.Y.Z** refers to the version (based on git tag) of go-cose that is released. +This is the version of the go-cose binary. +- **Breaking changes** refer to schema changes, flag changes, and behavior changes of go-cose that may require existing content to be upgraded and may also introduce changes that could break backward compatibility. +- **Milestone** GitHub milestones are used by maintainers to manage each release. +PRs and Issues for each release should be created as part of a corresponding milestone. +- **Patch releases** refer to applicable fixes, including security fixes, may be backported to support releases, depending on severity and feasibility. + +## Attribution + +This document builds on the ideas and implementations of release processes from the [notation](https://github.com/notaryproject/notation) project. + +[go-cose]: https://github.com/veraison/go-cose +[ietf-cose]: https://datatracker.ietf.org/group/cose/about/ +[sem-ver]: https://semver.org +[releases]: https://github.com/veraison/go-cose/releases +[super-majority]: https://en.wikipedia.org/wiki/Supermajority#Two-thirds_vote diff --git a/reports/README.md b/reports/README.md index d624698..7dc67f2 100644 --- a/reports/README.md +++ b/reports/README.md @@ -1,10 +1,10 @@ # Security Reports -This folder contains all the security review reports for the go-cose library. +This folder contains all the security review reports for the go-cose library. ## List of Security Reports | Review Date | Name of Security Review | Report Location | -|:------------|:--------------------------------------| ------------------------------- +|:------------|:--------------------------------------| -------------------------------| | May 16, 2022 | NCC Group go-cose Security Assessment | [NCC Report](./NCC_Microsoft-go-cose-Report_2022-05-26_v1.0.pdf) | -| July 26, 2022 | Trail of Bits go-cose Security Assessment | [Trail of Bits Report](./Trail-of-Bits_Microsoft-go-cose-Report_2022-07-26_v1.0.pdf) | \ No newline at end of file +| July 26, 2022 | Trail of Bits go-cose Security Assessment | [Trail of Bits Report](./Trail-of-Bits_Microsoft-go-cose-Report_2022-07-26_v1.0.pdf) | From 8c458e278da806b56444d1cbfd6ec24fc28aad9c Mon Sep 17 00:00:00 2001 From: Steve Lasker Date: Mon, 15 Jul 2024 09:46:54 -0700 Subject: [PATCH 24/25] Add support for CWT Claims & Type in Protected Headers (#189) Signed-off-by: steve lasker Co-authored-by: Orie Steele --- cwt.go | 20 +++++++++++++ cwt_test.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++ headers.go | 57 +++++++++++++++++++++++++++++++++---- 3 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 cwt.go create mode 100644 cwt_test.go diff --git a/cwt.go b/cwt.go new file mode 100644 index 0000000..e2aa9a2 --- /dev/null +++ b/cwt.go @@ -0,0 +1,20 @@ +package cose + +// https://www.iana.org/assignments/cwt/cwt.xhtml#claims-registry +const ( + CWTClaimIssuer int64 = 1 + CWTClaimSubject int64 = 2 + CWTClaimAudience int64 = 3 + CWTClaimExpirationTime int64 = 4 + CWTClaimNotBefore int64 = 5 + CWTClaimIssuedAt int64 = 6 + CWTClaimCWTID int64 = 7 + CWTClaimConfirmation int64 = 8 + CWTClaimScope int64 = 9 + + // TODO: the rest upon request +) + +// CWTClaims contains parameters that are to be cryptographically +// protected. +type CWTClaims map[any]any diff --git a/cwt_test.go b/cwt_test.go new file mode 100644 index 0000000..93dd0ae --- /dev/null +++ b/cwt_test.go @@ -0,0 +1,82 @@ +package cose_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + + "github.com/veraison/go-cose" +) + +// This example demonstrates signing and verifying COSE_Sign1 signatures. +func ExampleCWTMessage() { + // create message to be signed + msgToSign := cose.NewSign1Message() + msgToSign.Payload = []byte("hello world") + msgToSign.Headers.Protected.SetAlgorithm(cose.AlgorithmES512) + + msgToSign.Headers.Protected.SetType("application/cwt") + claims := cose.CWTClaims{ + cose.CWTClaimIssuer: "issuer.example", + cose.CWTClaimSubject: "subject.example", + } + msgToSign.Headers.Protected.SetCWTClaims(claims) + + msgToSign.Headers.Unprotected[cose.HeaderLabelKeyID] = []byte("1") + + // create a signer + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + panic(err) + } + signer, err := cose.NewSigner(cose.AlgorithmES512, privateKey) + if err != nil { + panic(err) + } + + // sign message + err = msgToSign.Sign(rand.Reader, nil, signer) + if err != nil { + panic(err) + } + sig, err := msgToSign.MarshalCBOR() + // uncomment to review EDN + // coseSign1Diagnostic, err := cbor.Diagnose(sig) + // fmt.Println(coseSign1Diagnostic) + if err != nil { + panic(err) + } + fmt.Println("message signed") + + // create a verifier from a trusted public key + publicKey := privateKey.Public() + verifier, err := cose.NewVerifier(cose.AlgorithmES512, publicKey) + if err != nil { + panic(err) + } + + // verify message + var msgToVerify cose.Sign1Message + err = msgToVerify.UnmarshalCBOR(sig) + if err != nil { + panic(err) + } + err = msgToVerify.Verify(nil, verifier) + if err != nil { + panic(err) + } + fmt.Println("message verified") + + // tamper the message and verification should fail + msgToVerify.Payload = []byte("foobar") + err = msgToVerify.Verify(nil, verifier) + if err != cose.ErrVerification { + panic(err) + } + fmt.Println("verification error as expected") + // Output: + // message signed + // message verified + // verification error as expected +} diff --git a/headers.go b/headers.go index 2999207..19c2b3f 100644 --- a/headers.go +++ b/headers.go @@ -23,6 +23,8 @@ const ( HeaderLabelCounterSignature0 int64 = 9 HeaderLabelCounterSignatureV2 int64 = 11 HeaderLabelCounterSignature0V2 int64 = 12 + HeaderLabelCWTClaims int64 = 15 + HeaderLabelType int64 = 16 HeaderLabelX5Bag int64 = 32 HeaderLabelX5Chain int64 = 33 HeaderLabelX5T int64 = 34 @@ -97,11 +99,35 @@ func (h *ProtectedHeader) UnmarshalCBOR(data []byte) error { return nil } -// SetAlgorithm sets the algorithm value to the algorithm header. +// SetAlgorithm sets the algorithm value of the protected header. func (h ProtectedHeader) SetAlgorithm(alg Algorithm) { h[HeaderLabelAlgorithm] = alg } +// SetType sets the type of the cose object in the protected header. +func (h ProtectedHeader) SetType(typ any) (any, error) { + if !canTstr(typ) && !canUint(typ) { + return typ, errors.New("header parameter: type: require tstr / uint type") + } + h[HeaderLabelType] = typ + return typ, nil +} + +// SetCWTClaims sets the CWT Claims value of the protected header. +func (h ProtectedHeader) SetCWTClaims(claims CWTClaims) (CWTClaims, error) { + iss, hasIss := claims[1] + if hasIss && !canTstr(iss) { + return claims, errors.New("cwt claim: iss: require tstr") + } + sub, hasSub := claims[2] + if hasSub && !canTstr(sub) { + return claims, errors.New("cwt claim: sub: require tstr") + } + // TODO: validate claims, other claims + h[HeaderLabelCWTClaims] = claims + return claims, nil +} + // Algorithm gets the algorithm value from the algorithm header. func (h ProtectedHeader) Algorithm() (Algorithm, error) { value, ok := h[HeaderLabelAlgorithm] @@ -460,8 +486,8 @@ func validateHeaderParameters(h map[any]any, protected bool) error { // Reference: https://datatracker.ietf.org/doc/html/rfc8152#section-3.1 switch label { case HeaderLabelAlgorithm: - _, is_alg := value.(Algorithm) - if !is_alg && !canInt(value) && !canTstr(value) { + _, isAlg := value.(Algorithm) + if !isAlg && !canInt(value) && !canTstr(value) { return errors.New("header parameter: alg: require int / tstr type") } case HeaderLabelCritical: @@ -471,12 +497,31 @@ func validateHeaderParameters(h map[any]any, protected bool) error { if err := ensureCritical(value, h); err != nil { return fmt.Errorf("header parameter: crit: %w", err) } + case HeaderLabelType: + isTstr := canTstr(value) + if !isTstr && !canUint(value) { + return errors.New("header parameter: type: require tstr / uint type") + } + if isTstr { + v := value.(string) + if len(v) == 0 { + return errors.New("header parameter: type: require non-empty string") + } + if v[0] == ' ' || v[len(v)-1] == ' ' { + return errors.New("header parameter: type: require no leading/trailing whitespace") + } + // Basic check that the content type is of form type/subtype. + // We don't check the precise definition though (RFC 6838 Section 4.2). + if strings.Count(v, "/") != 1 { + return errors.New("header parameter: type: require text of form type/subtype") + } + } case HeaderLabelContentType: - is_tstr := canTstr(value) - if !is_tstr && !canUint(value) { + isTstr := canTstr(value) + if !isTstr && !canUint(value) { return errors.New("header parameter: content type: require tstr / uint type") } - if is_tstr { + if isTstr { v := value.(string) if len(v) == 0 { return errors.New("header parameter: content type: require non-empty string") From e5c68f97dfbb7dc2677e891ceea9a8eafe42ddb4 Mon Sep 17 00:00:00 2001 From: Shiwei Zhang Date: Fri, 19 Jul 2024 23:21:40 +0800 Subject: [PATCH 25/25] docs: update docs for release process (#193) Signed-off-by: Shiwei Zhang --- .gitignore | 3 +++ .vscode/settings.json | 5 ----- release-checklist.md | 24 +++++++++++------------- release-management.md | 37 +++++++++++++++++++------------------ 4 files changed, 33 insertions(+), 36 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 4d0f184..b04f6b0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ vendor/ .DS_Store /cose-fuzz.zip /workdir/ + +# Editor files +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ad24d8c..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "cSpell.words": [ - "supermajority" - ] -} \ No newline at end of file diff --git a/release-checklist.md b/release-checklist.md index abbbf48..fb09d26 100644 --- a/release-checklist.md +++ b/release-checklist.md @@ -6,20 +6,18 @@ This document describes the checklist to publish a release via GitHub workflow. The maintainers may periodically update this checklist based on feedback. -NOTE: Make sure the dependencies in `go.mod` file are expected by the release. -For example, if there are dependencies on certain version of notation library (notation-go or notation-core-go) or ORAS library (oras-go), make sure that version of library is released first, and the version number is updated accordingly in `go.mod` file. -After updating go.mod file, run `go mod tidy` to ensure the go.sum file is also updated with any potential changes. +> [!NOTE] +> Make sure the dependencies in `go.mod` file are expected by the release. +> After updating `go.mod` file, run `go mod tidy` to ensure the `go.sum` file is also updated with any potential changes. ## Release Process -1. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. -For example, `version="v1.0.0-alpha.1"`. -1. Bump up the `Version` in [internal/version/version.go](internal/version/version.go#L5) and open a PR for the changes. -1. Wait for the PR merge. -1. Be on the main branch connected to the actual repository (not a fork) and `git pull`. -Ensure `git log -1` shows the latest commit on the main branch. -1. Create a tag `git tag -am $version $version` -1. `git tag` and ensure the name in the list added looks correct, then push the tag directly to the repository by `git push --follow-tags`. -1. Wait for the completion of the GitHub action [release-github](https://github.com/veraison/go-cose/blob/main/.github/workflows/ci.yml). -1. Check the new draft release, revise the release description, and publish the release. +1. Determine a [SemVer2](https://semver.org/)-valid version prefixed with the letter `v` for release. For example, `v1.0.0-alpha.1`. +1. Determine the commit to be tagged and released. +1. Create an issue for voting with title similar to `vote: tag v1.0.0-alpha.1` with the proposed commit. +1. Wait for the vote pass. +1. Cut a release branch `release-X.Y` (e.g. `release-1.0`) if it does not exist. The voted commit MUST be the head of the release branch. + - To cut a release branch directly on GitHub, navigate to `https://github.com/veraison/go-cose/tree/{commit}` and then follow the [creating a branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository#creating-a-branch-using-the-branch-dropdown) doc. +1. Draft a new release from the release branch by following the [creating a release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release) doc. Set release title to the voted version and create a tag in the **Choose a tag** dropdown menu with the voted version as the tag name. +1. Proofread the draft release, and publish the release. 1. Announce the release in the community. diff --git a/release-management.md b/release-management.md index 4d66ce0..6c123bf 100644 --- a/release-management.md +++ b/release-management.md @@ -14,31 +14,32 @@ The maintainers may periodically update this policy based on feedback. ## Release Versioning -Consumers of the go-cose project may build directly from main, or pull from released builds. -Builds from main must reference the git-commit as the version: `v1.0.0-2300d5c` +Consumers of the go-cose module may reference `main` directly, or reference released tags. All go-cose [releases][releases] follow a go-lang flavored derivation (`v*`) of the [semver][sem-ver] format, with optional pre-release labels. -Logical Progression of a release: `v1.0.0-2300d5c` --> `v1.0.0-alpha.1` --> `v1.0.0-alpha.2` --> `v1.0.0-rc.1` --> `v1.0.0` +Logical Progression of a release: `v1.0.0-alpha.1` --> `v1.0.0-alpha.2` --> `v1.0.0-rc.1` --> `v1.0.0` -A new major or minor release will not have an automated build/release posted until the branch reaches alpha quality. +A new major or minor release will not have an automated release posted until the branch reaches alpha quality. -- all versions use a preface of `v` -- `X` is the [Major](#major-releases) version -- `Y` is the [Minor](#minor-releases) version -- `Z` is the [Patch](#patch-releases) version +- All versions use a preface of `v` +- Given a version `vX.Y.Z`, + - `X` is the [Major](#major-releases) version + - `Y` is the [Minor](#minor-releases) version + - `Z` is the [Patch](#patch-releases) version - _Optional_ `-alpha.n` | `-rc.n` [pre-release](#pre-release) version - Each incremental alpha or rc build will bump the suffix (`n`) number. - It's not expected to have more than 9 alphas or rcs. The suffix will be a single digit. - If > 9 builds do occur, the format will simply use two digit indicators (`v1.0.0-alpha.10`) -_**Note**: Pre-releases will NOT use the github pre-release flag._ +> [!IMPORTANT] +> Pre-releases will NOT use the github pre-release flag. ## Branch Management To meet the projects stability goals, go-cose does not currently operate with multiple feature branches. -All active development happens in main. +All active development happens in `main`. For each release, a branch is created for servicing, following the versioning name. `v1.0.0-alpha-1` would have an associated [v1.0.0-alpha.1](https://github.com/veraison/go-cose/tree/v1.0.0-alpha.1) branch. All version and branch names are lower case. @@ -76,14 +77,14 @@ Principals of a patch release: - No breaking changes. - No feature or surface area changes. - A "bug fix" that may be a breaking change may require a major release. -- Applicable fixes, including security fixes, may be cherry-picked from main into the latest supported minor release-X.Y branches. -- Patch releases are cut from a release-X.Y.Z branch. +- Applicable fixes, including security fixes, may be cherry-picked from main into the latest supported minor `release-X.Y` branches. +- Patch releases are cut from a `release-X.Y` branch. Each patch release will go through one or more `-alpha.n` and `-rc.n` pre-release phases. ### Pre-Release -As builds of main become stable, and a pending release is planned, a pre-release build will be made. +As builds of `main` become stable, and a pending release is planned, a pre-release build will be made. Pre-releases go through one or more `-alpha.n` releases, followed by one or more incremental `-rc.n` releases. - **alpha.n:** `X.Y.Z-alpha.n` @@ -91,15 +92,15 @@ Pre-releases go through one or more `-alpha.n` releases, followed by one or more To minimize branch management, no additional branches are maintained for each incremental release. - Considered an unstable release which should only be used for early development purposes. - Released incrementally until no additional issues and prs are made against the release. - - Once no triaged issues or pull requests (prs) are scoped to the release, a release candidate (rc) is cut. + - Once no triaged issues or pull requests (prs) are scoped to the release, a release candidate (`rc`) is cut. - To minimize confusion, and the risk of an alpha being widely deployed, alpha branches and released binaries may be removed at the discretion, and a [two-thirds supermajority][super-majority] vote, of the maintainers. Maintainers will create an Issue, and vote upon it for transparency to the decision to remove a release and/or branch. - Not [supported](#supported-releases) - **rc.n:** `X.Y.Z-rc.n` - Released as needed before a final version is released - Bugfixes on new features only as reported through usage - - An rc is not expected to revert to an alpha release. - - Once no triaged issues or prs are scoped to the release, an final version is cut. + - An `rc` is not expected to revert to an alpha release. + - Once no triaged issues or PRs are scoped to the release, an final version is cut. - A release candidate will typically have at least two weeks of bake time, providing the community time to provide feedback. - Release candidates are cut from the branch where the work is done. - To minimize confusion, and the risk of an rc being widely deployed, rc branches and released binaries may be removed at the discretion, and a [two-thirds supermajority][super-majority] vote, of the maintainers. @@ -108,9 +109,9 @@ Maintainers will create an Issue, and vote upon it for transparency to the decis ## Supported Releases -The go-cose maintainers expect to "support" n (current) and n-1 major.minor releases. +The go-cose maintainers expect to "support" n (current) and `n-1` major.minor releases. "Support" means we expect users to be running that version in production. -For example, when v1.3.0 comes out, v1.1.x will no longer be supported for patches, and the maintainers encourage users to upgrade to a supported version as soon as possible. +For example, when `v1.3.0` comes out, `v1.1.x` will no longer be supported for patches, and the maintainers encourage users to upgrade to a supported version as soon as possible. Support will be provided best effort by the maintainers via GitHub issues and pull requests from the community. The go-cose maintainers expect users to stay up-to-date with the versions of go-cose release they use in production, but understand that it may take time to upgrade.