diff --git a/go/lib/scrypto/BUILD.bazel b/go/lib/scrypto/BUILD.bazel index c76861abba..00ff2728a8 100644 --- a/go/lib/scrypto/BUILD.bazel +++ b/go/lib/scrypto/BUILD.bazel @@ -5,9 +5,11 @@ go_library( srcs = [ "asym.go", "defs.go", + "keymeta.go", "mac.go", "rand.go", "validity.go", + "version.go", ], importpath = "github.com/scionproto/scion/go/lib/scrypto", visibility = ["//visibility:public"], @@ -24,8 +26,10 @@ go_test( name = "go_default_test", srcs = [ "asym_test.go", + "keymeta_test.go", "rand_test.go", "validity_test.go", + "version_test.go", ], embed = [":go_default_library"], deps = [ diff --git a/go/lib/scrypto/keymeta.go b/go/lib/scrypto/keymeta.go new file mode 100644 index 0000000000..11aa2d94c4 --- /dev/null +++ b/go/lib/scrypto/keymeta.go @@ -0,0 +1,83 @@ +// Copyright 2019 Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scrypto + +import ( + "bytes" + "encoding/json" + "errors" + + "github.com/scionproto/scion/go/lib/common" +) + +var ( + // ErrKeyVersionNotSet indicates KeyVersion is not set. + ErrKeyVersionNotSet = errors.New("key version not set") + // ErrAlgorithmNotSet indicates the key algorithm is not set. + ErrAlgorithmNotSet = errors.New("algorithm not set") + // ErrKeyNotSet indicates the key is not set. + ErrKeyNotSet = errors.New("key not set") +) + +// KeyMeta holds the raw key with metadata. +type KeyMeta struct { + // KeyVersion identifies the key. It must change if the key changes, and + // stay the same if the key does not change. + KeyVersion KeyVersion `json:"KeyVersion"` + // Algorithm indicates the algorithm associated with the key. + Algorithm string `json:"Algorithm"` + // Key is the raw public key. + Key common.RawBytes `json:"Key"` +} + +// UnmarshalJSON checks that all fields are set. +func (m *KeyMeta) UnmarshalJSON(b []byte) error { + var alias keyMetaAlias + dec := json.NewDecoder(bytes.NewReader(b)) + dec.DisallowUnknownFields() + if err := dec.Decode(&alias); err != nil { + return err + } + if err := alias.checkAllSet(); err != nil { + return err + } + *m = KeyMeta{ + KeyVersion: *alias.KeyVersion, + Algorithm: *alias.Algorithm, + Key: *alias.Key, + } + return nil +} + +type keyMetaAlias struct { + KeyVersion *KeyVersion `json:"KeyVersion"` + Algorithm *string `json:"Algorithm"` + Key *common.RawBytes `json:"Key"` +} + +func (m *keyMetaAlias) checkAllSet() error { + switch { + case m.KeyVersion == nil: + return ErrKeyVersionNotSet + case m.Algorithm == nil: + return ErrAlgorithmNotSet + case m.Key == nil: + return ErrKeyNotSet + } + return nil +} + +// KeyVersion identifies a key version. +type KeyVersion uint64 diff --git a/go/lib/scrypto/keymeta_test.go b/go/lib/scrypto/keymeta_test.go new file mode 100644 index 0000000000..3d447981fb --- /dev/null +++ b/go/lib/scrypto/keymeta_test.go @@ -0,0 +1,100 @@ +// Copyright 2019 Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scrypto_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/scionproto/scion/go/lib/scrypto" + "github.com/scionproto/scion/go/lib/xtest" +) + +func TestKeyMetaUnmarshalJSON(t *testing.T) { + tests := map[string]struct { + Input string + Meta scrypto.KeyMeta + ExpectedErrMsg string + }{ + "Valid": { + Input: ` + { + "KeyVersion": 1, + "Algorithm": "ed25519", + "Key": "YW5hcGF5YSDinaQgIHNjaW9u" + }`, + Meta: scrypto.KeyMeta{ + KeyVersion: 1, + Algorithm: scrypto.Ed25519, + Key: xtest.MustParseHexString("616e617061796120e29da420207363696f6e"), + }, + }, + "KeyVersion not set": { + Input: ` + { + "Algorithm": "ed25519", + "Key": "YW5hcGF5YSDinaQgIHNjaW9u" + }`, + ExpectedErrMsg: scrypto.ErrKeyVersionNotSet.Error(), + }, + "Algorithm not set": { + Input: ` + { + "KeyVersion": 1, + "Key": "YW5hcGF5YSDinaQgIHNjaW9u" + }`, + ExpectedErrMsg: scrypto.ErrAlgorithmNotSet.Error(), + }, + "Key not set": { + Input: ` + { + "KeyVersion": 1, + "Algorithm": "ed25519" + }`, + ExpectedErrMsg: scrypto.ErrKeyNotSet.Error(), + }, + "Unknown field": { + Input: ` + { + "UnknownField": "UNKNOWN" + }`, + ExpectedErrMsg: `json: unknown field "UnknownField"`, + }, + "invalid json": { + Input: ` + { + "KeyVersion": 1, + "Algorithm": "ed25519" + `, + ExpectedErrMsg: "unexpected end of JSON input", + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var meta scrypto.KeyMeta + err := json.Unmarshal([]byte(test.Input), &meta) + if test.ExpectedErrMsg == "" { + require.NoError(t, err) + assert.Equal(t, test.Meta, meta) + } else { + require.Error(t, err) + assert.Contains(t, err.Error(), test.ExpectedErrMsg) + } + }) + } +} diff --git a/go/lib/scrypto/trc/v2/keychanges.go b/go/lib/scrypto/trc/v2/keychanges.go index 3849724974..948e9a08d5 100644 --- a/go/lib/scrypto/trc/v2/keychanges.go +++ b/go/lib/scrypto/trc/v2/keychanges.go @@ -19,6 +19,7 @@ import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/scrypto" ) const ( @@ -29,7 +30,7 @@ const ( ) // ASToKeyMeta maps an AS to its key metadata for a single key type. -type ASToKeyMeta map[addr.AS]KeyMeta +type ASToKeyMeta map[addr.AS]scrypto.KeyMeta // KeyChanges contains all new keys in a TRC update. type KeyChanges struct { @@ -81,7 +82,7 @@ func (c *KeyChanges) insertModifications(as addr.AS, prev, next PrimaryAS) error // If the algorithm and key are not modified by the update, the version must not // change. If they are modified, the version must be increased by one. The // return value indicates, whether the update is a modification. -func ValidateKeyUpdate(prev, next KeyMeta) (bool, error) { +func ValidateKeyUpdate(prev, next scrypto.KeyMeta) (bool, error) { modified := next.Algorithm != prev.Algorithm || !bytes.Equal(next.Key, prev.Key) switch { case modified && next.KeyVersion != prev.KeyVersion+1: diff --git a/go/lib/scrypto/trc/v2/pop.go b/go/lib/scrypto/trc/v2/pop.go index 00f4671cae..9d8696c665 100644 --- a/go/lib/scrypto/trc/v2/pop.go +++ b/go/lib/scrypto/trc/v2/pop.go @@ -17,6 +17,7 @@ package trc import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/scrypto" ) const ( @@ -69,7 +70,7 @@ func (v *popValidator) popForModType(changes map[KeyType]ASToKeyMeta) error { return nil } -func (v *popValidator) popForKeyType(keyType KeyType, m map[addr.AS]KeyMeta) error { +func (v *popValidator) popForKeyType(keyType KeyType, m map[addr.AS]scrypto.KeyMeta) error { for as := range m { if !v.hasPop(v.TRC.ProofOfPossession[as], keyType) { return common.NewBasicError(MissingProofOfPossession, nil, "AS", as, "keyType", keyType) diff --git a/go/lib/scrypto/trc/v2/primary.go b/go/lib/scrypto/trc/v2/primary.go index 830feb4b06..94be4a8c41 100644 --- a/go/lib/scrypto/trc/v2/primary.go +++ b/go/lib/scrypto/trc/v2/primary.go @@ -22,6 +22,7 @@ import ( "github.com/scionproto/scion/go/lib/addr" "github.com/scionproto/scion/go/lib/common" + "github.com/scionproto/scion/go/lib/scrypto" ) // Parsing errors with context. @@ -57,11 +58,6 @@ var ( ErrAttributesNotSet = errors.New("attributes not set") // ErrKeysNotSet indicates the keys in a primary AS are not set. ErrKeysNotSet = errors.New("keys not set") - - // ErrAlgorithmNotSet indicates the key algorithm is not set. - ErrAlgorithmNotSet = errors.New("algorithm not set") - // ErrKeyNotSet indicates the key is not set. - ErrKeyNotSet = errors.New("key not set") ) // PrimaryASes holds all primary ASes and maps them to their attributes and keys. @@ -104,8 +100,8 @@ type primaryASAlias PrimaryAS // PrimaryAS holds the attributes and keys of a primary AS. type PrimaryAS struct { - Attributes Attributes `json:"Attributes"` - Keys map[KeyType]KeyMeta `json:"Keys"` + Attributes Attributes `json:"Attributes"` + Keys map[KeyType]scrypto.KeyMeta `json:"Keys"` } // Is returns true if the primary AS holds this property. @@ -167,58 +163,6 @@ func (p *PrimaryAS) checkAllSet() error { return nil } -// KeyMeta holds the key with metadata. TODO(roosd): rename to key. Inspect all -// occurrences of trc.Key in the codebase, as they are different things. -type KeyMeta struct { - // KeyVersion identifies the key. It must change if the key changes, and - // stay the same if the key does not change. - KeyVersion KeyVersion `json:"KeyVersion"` - // Algorithm indicates the algorithm associated with the key. - Algorithm string `json:"Algorithm"` - // Key is the raw public key. - Key common.RawBytes `json:"Key"` -} - -// UnmarshalJSON checks that all fields are set. -func (m *KeyMeta) UnmarshalJSON(b []byte) error { - var alias keyMetaAlias - dec := json.NewDecoder(bytes.NewReader(b)) - dec.DisallowUnknownFields() - if err := dec.Decode(&alias); err != nil { - return err - } - if err := alias.checkAllSet(); err != nil { - return err - } - *m = KeyMeta{ - KeyVersion: *alias.KeyVersion, - Algorithm: *alias.Algorithm, - Key: *alias.Key, - } - return nil -} - -type keyMetaAlias struct { - KeyVersion *KeyVersion `json:"KeyVersion"` - Algorithm *string `json:"Algorithm"` - Key *common.RawBytes `json:"Key"` -} - -func (m *keyMetaAlias) checkAllSet() error { - switch { - case m.KeyVersion == nil: - return ErrKeyVersionNotSet - case m.Algorithm == nil: - return ErrAlgorithmNotSet - case m.Key == nil: - return ErrKeyNotSet - } - return nil -} - -// KeyVersion identifies a key for a given KeyType and ISD-AS. -type KeyVersion uint64 - var _ json.Marshaler = (*Attributes)(nil) var _ json.Unmarshaler = (*Attributes)(nil) diff --git a/go/lib/scrypto/trc/v2/primary_json_test.go b/go/lib/scrypto/trc/v2/primary_json_test.go index aff2884e5a..81e4e1535a 100644 --- a/go/lib/scrypto/trc/v2/primary_json_test.go +++ b/go/lib/scrypto/trc/v2/primary_json_test.go @@ -46,7 +46,7 @@ func TestPrimaryASUnmarshalJSON(t *testing.T) { }`, Primary: trc.PrimaryAS{ Attributes: trc.Attributes{"Issuing", "Core"}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.IssuingKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -87,7 +87,7 @@ func TestPrimaryASUnmarshalJSON(t *testing.T) { } } }`, - ExpectedErrMsg: trc.ErrKeyVersionNotSet.Error(), + ExpectedErrMsg: scrypto.ErrKeyVersionNotSet.Error(), }, } for name, test := range tests { @@ -105,80 +105,6 @@ func TestPrimaryASUnmarshalJSON(t *testing.T) { } } -func TestKeyMetaUnmarshalJSON(t *testing.T) { - tests := map[string]struct { - Input string - Meta trc.KeyMeta - ExpectedErrMsg string - }{ - "Valid": { - Input: ` - { - "KeyVersion": 1, - "Algorithm": "ed25519", - "Key": "YW5hcGF5YSDinaQgIHNjaW9u" - }`, - Meta: trc.KeyMeta{ - KeyVersion: 1, - Algorithm: scrypto.Ed25519, - Key: xtest.MustParseHexString("616e617061796120e29da420207363696f6e"), - }, - }, - "KeyVersion not set": { - Input: ` - { - "Algorithm": "ed25519", - "Key": "YW5hcGF5YSDinaQgIHNjaW9u" - }`, - ExpectedErrMsg: trc.ErrKeyVersionNotSet.Error(), - }, - "Algorithm not set": { - Input: ` - { - "KeyVersion": 1, - "Key": "YW5hcGF5YSDinaQgIHNjaW9u" - }`, - ExpectedErrMsg: trc.ErrAlgorithmNotSet.Error(), - }, - "Key not set": { - Input: ` - { - "KeyVersion": 1, - "Algorithm": "ed25519" - }`, - ExpectedErrMsg: trc.ErrKeyNotSet.Error(), - }, - "Unknown field": { - Input: ` - { - "UnknownField": "UNKNOWN" - }`, - ExpectedErrMsg: `json: unknown field "UnknownField"`, - }, - "invalid json": { - Input: ` - { - "KeyVersion": 1, - "Algorithm": "ed25519" - `, - ExpectedErrMsg: "unexpected end of JSON input", - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var meta trc.KeyMeta - err := json.Unmarshal([]byte(test.Input), &meta) - if test.ExpectedErrMsg == "" { - require.NoError(t, err) - assert.Equal(t, test.Meta, meta) - } else { - require.Error(t, err) - assert.Contains(t, err.Error(), test.ExpectedErrMsg) - } - }) - } -} - func TestAttributesUnmarshalJSON(t *testing.T) { tests := map[string]struct { Input string diff --git a/go/lib/scrypto/trc/v2/primary_test.go b/go/lib/scrypto/trc/v2/primary_test.go index bffa401ac6..c8bb74af49 100644 --- a/go/lib/scrypto/trc/v2/primary_test.go +++ b/go/lib/scrypto/trc/v2/primary_test.go @@ -33,7 +33,7 @@ func TestPrimaryASesValidateInvariant(t *testing.T) { Primaries: trc.PrimaryASes{ a110: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OfflineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -48,7 +48,7 @@ func TestPrimaryASesValidateInvariant(t *testing.T) { Primaries: trc.PrimaryASes{ a110: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Core}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OfflineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -164,7 +164,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Non-Core and Authoritative": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Authoritative}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: {}, trc.OfflineKey: {}, }, @@ -174,7 +174,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Voting AS without online key": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OfflineKey: {}, }, }, @@ -183,7 +183,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Voting AS without offline key": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: {}, }, }, @@ -192,7 +192,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Voting AS with issuing key": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: {}, trc.OfflineKey: {}, trc.IssuingKey: {}, @@ -203,14 +203,14 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Issuer AS without issuing key": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Issuing}, - Keys: make(map[trc.KeyType]trc.KeyMeta), + Keys: make(map[trc.KeyType]scrypto.KeyMeta), }, ExpectedErrMsg: trc.MissingKey, }, "Issuer AS with online key": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Issuing}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: {}, trc.IssuingKey: {}, }, @@ -225,7 +225,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Valid Voting": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: {}, trc.OfflineKey: {}, }, @@ -234,7 +234,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Valid Issuing": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Issuing}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.IssuingKey: {}, }, }, @@ -247,7 +247,7 @@ func TestPrimaryASValidateInvariant(t *testing.T) { "Valid multi": { Primary: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Authoritative, trc.Issuing, trc.Core, trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: {}, trc.OfflineKey: {}, trc.IssuingKey: {}, diff --git a/go/lib/scrypto/trc/v2/signed.go b/go/lib/scrypto/trc/v2/signed.go index c9217c1077..f960e0fccd 100644 --- a/go/lib/scrypto/trc/v2/signed.go +++ b/go/lib/scrypto/trc/v2/signed.go @@ -34,6 +34,8 @@ const ( ) var ( + // ErrAlgorithmNotSet indicates the key algorithm is not set. + ErrAlgorithmNotSet = errors.New("algorithm not set") // ErrASNotSet indicates the AS is not set. ErrASNotSet = errors.New("AS not set") // ErrCritNotSet indicates that crit is not set. @@ -112,12 +114,12 @@ func (h *EncodedProtected) Decode() (Protected, error) { // Protected is the signature metadata. type Protected struct { - Algorithm string `json:"alg"` - Type SignatureType `json:"Type"` - KeyType KeyType `json:"KeyType"` - KeyVersion KeyVersion `json:"KeyVersion"` - AS addr.AS `json:"AS"` - Crit Crit `json:"crit"` + Algorithm string `json:"alg"` + Type SignatureType `json:"Type"` + KeyType KeyType `json:"KeyType"` + KeyVersion scrypto.KeyVersion `json:"KeyVersion"` + AS addr.AS `json:"AS"` + Crit Crit `json:"crit"` } // UnmarshalJSON checks that all fields are set. @@ -143,12 +145,12 @@ func (p *Protected) UnmarshalJSON(b []byte) error { } type protectedAlias struct { - Algorithm *string `json:"alg"` - Type *SignatureType `json:"Type"` - KeyType *KeyType `json:"KeyType"` - KeyVersion *KeyVersion `json:"KeyVersion"` - AS *addr.AS `json:"AS"` - Crit *Crit `json:"crit"` + Algorithm *string `json:"alg"` + Type *SignatureType `json:"Type"` + KeyType *KeyType `json:"KeyType"` + KeyVersion *scrypto.KeyVersion `json:"KeyVersion"` + AS *addr.AS `json:"AS"` + Crit *Crit `json:"crit"` } func (p *protectedAlias) checkAllSet() error { diff --git a/go/lib/scrypto/trc/v2/signed_test.go b/go/lib/scrypto/trc/v2/signed_test.go index 9d25aa99bc..f1a99d9421 100644 --- a/go/lib/scrypto/trc/v2/signed_test.go +++ b/go/lib/scrypto/trc/v2/signed_test.go @@ -37,7 +37,7 @@ func TestEncode(t *testing.T) { }, "Invalid Version": { Modify: func(base *trc.TRC) { - base.Version = trc.Version(scrypto.LatestVer) + base.Version = scrypto.Version(scrypto.LatestVer) }, Assertion: assert.Error, }, diff --git a/go/lib/scrypto/trc/v2/trc.go b/go/lib/scrypto/trc/v2/trc.go index 92fcd3f599..eacd48886c 100644 --- a/go/lib/scrypto/trc/v2/trc.go +++ b/go/lib/scrypto/trc/v2/trc.go @@ -37,13 +37,8 @@ const ( VotingQuorumTooLarge = "voting quorum too large" ) -// Parse errors with context -const ( - // UnsupportedFormat indicates an invalid TRC format. - UnsupportedFormat = "Unsupported TRC format" - // InvalidVersion indicates an invalid TRC version. - InvalidVersion = "Invalid TRC version" -) +// UnsupportedFormat indicates an invalid TRC format. +const UnsupportedFormat = "unsupported TRC format" // Invariant errors var ( @@ -115,10 +110,10 @@ type TRC struct { ISD addr.ISD `json:"ISD"` // Version is the version number of the TRC. // The value scrypto.LatestVer is reserved and shall not be used. - Version Version `json:"TRCVersion"` + Version scrypto.Version `json:"TRCVersion"` // BaseVersion indicates the initial TRC version for this TRC chain. // If BaseVersion equals TRCVersion this TRC is a base TRC. - BaseVersion Version `json:"BaseVersion"` + BaseVersion scrypto.Version `json:"BaseVersion"` // Description is an human-readable description of the ISD. Description string `json:"Description"` // VotingQuorum is the number of signatures the next TRC needs from voting @@ -257,7 +252,7 @@ type Vote struct { // Type is the type of key that is used to issue the signature. Type KeyType `json:"Type"` // KeyVersion is the key version of the key that is used to issue the signautre. - KeyVersion KeyVersion `json:"KeyVersion"` + KeyVersion scrypto.KeyVersion `json:"KeyVersion"` } // UnmarshalJSON checks that all fields are set. @@ -279,8 +274,8 @@ func (v *Vote) UnmarshalJSON(b []byte) error { } type voteAlias struct { - Type *KeyType `json:"Type"` - KeyVersion *KeyVersion `json:"KeyVersion"` + Type *KeyType `json:"Type"` + KeyVersion *scrypto.KeyVersion `json:"KeyVersion"` } func (v *voteAlias) checkAllSet() error { @@ -293,8 +288,6 @@ func (v *voteAlias) checkAllSet() error { return nil } -var _ json.Unmarshaler = (*Version)(nil) - // FormatVersion indicates the TRC format version. Currently, only format // version 1 is supported. type FormatVersion uint8 @@ -312,34 +305,6 @@ func (v *FormatVersion) UnmarshalJSON(b []byte) error { return nil } -var _ json.Unmarshaler = (*Version)(nil) -var _ json.Marshaler = (*Version)(nil) - -// Version identifies the version of a TRC. It cannot be -// marshalled/unmarshalled to/from scrypto.LatestVer. -type Version uint64 - -// UnmarshalJSON checks that the value is not scrypto.LatestVer. -func (v *Version) UnmarshalJSON(b []byte) error { - parsed, err := strconv.ParseUint(string(b), 10, 64) - if err != nil { - return err - } - if parsed == scrypto.LatestVer { - return common.NewBasicError(InvalidVersion, nil, "ver", parsed) - } - *v = Version(parsed) - return nil -} - -// MarshalJSON checks that the value is not scrypto.LatestVer. -func (v Version) MarshalJSON() ([]byte, error) { - if uint64(v) == scrypto.LatestVer { - return nil, common.NewBasicError(InvalidVersion, nil, "ver", v) - } - return json.Marshal(uint64(v)) -} - // Period indicates a time duration. type Period struct { time.Duration diff --git a/go/lib/scrypto/trc/v2/trc_json_test.go b/go/lib/scrypto/trc/v2/trc_json_test.go index 4c46555903..2bb5a72715 100644 --- a/go/lib/scrypto/trc/v2/trc_json_test.go +++ b/go/lib/scrypto/trc/v2/trc_json_test.go @@ -30,8 +30,8 @@ import ( type genTRC struct { ISD *addr.ISD `json:"ISD,omitempty"` - Version *trc.Version `json:"TRCVersion,omitempty"` - BaseVersion *trc.Version `json:"BaseVersion,omitempty"` + Version *scrypto.Version `json:"TRCVersion,omitempty"` + BaseVersion *scrypto.Version `json:"BaseVersion,omitempty"` Description *string `json:"Description,omitempty"` VotingQuorum *uint8 `json:"VotingQuorum,omitempty"` FormatVersion *trc.FormatVersion `json:"FormatVersion,omitempty"` @@ -247,68 +247,6 @@ func TestFormatVersionUnmarshalJSON(t *testing.T) { } } -func TestVersionUnmarshalJSON(t *testing.T) { - tests := map[string]struct { - Input []byte - Expected trc.Version - Assertion assert.ErrorAssertionFunc - }{ - "Valid": { - Input: []byte("1"), - Expected: 1, - Assertion: assert.NoError, - }, - "Reserved": { - Input: []byte(strconv.FormatUint(scrypto.LatestVer, 10)), - Assertion: assert.Error, - }, - "String": { - Input: []byte(`"1"`), - Assertion: assert.Error, - }, - "Garbage": { - Input: []byte(`"Garbage"`), - Assertion: assert.Error, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var v trc.Version - test.Assertion(t, json.Unmarshal(test.Input, &v)) - assert.Equal(t, test.Expected, v) - }) - } -} - -func TestVersionMarshalJSON(t *testing.T) { - type mockTRC struct { - Version trc.Version - } - tests := map[string]struct { - // Use a struct to simulate TRC marshaling. Pointer vs value receiver. - Input mockTRC - Expected []byte - Assertion assert.ErrorAssertionFunc - }{ - "Valid": { - Input: mockTRC{Version: 1}, - Expected: []byte(`{"Version":1}`), - Assertion: assert.NoError, - }, - "Reserved": { - Input: mockTRC{Version: trc.Version(scrypto.LatestVer)}, - Assertion: assert.Error, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - b, err := json.Marshal(test.Input) - test.Assertion(t, err) - assert.Equal(t, test.Expected, b) - }) - } -} - func TestPeriodUnmarshalJSON(t *testing.T) { tests := map[string]struct { Input []byte diff --git a/go/lib/scrypto/trc/v2/trc_test.go b/go/lib/scrypto/trc/v2/trc_test.go index 4fc866bbf8..5afa0b9afb 100644 --- a/go/lib/scrypto/trc/v2/trc_test.go +++ b/go/lib/scrypto/trc/v2/trc_test.go @@ -164,7 +164,7 @@ func newBaseTRC() *trc.TRC { PrimaryASes: trc.PrimaryASes{ a110: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Authoritative, trc.Core, trc.Voting, trc.Issuing}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -184,7 +184,7 @@ func newBaseTRC() *trc.TRC { }, a120: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -199,7 +199,7 @@ func newBaseTRC() *trc.TRC { }, a130: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Issuing}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.IssuingKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -209,7 +209,7 @@ func newBaseTRC() *trc.TRC { }, a140: trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, diff --git a/go/lib/scrypto/trc/v2/update_test.go b/go/lib/scrypto/trc/v2/update_test.go index 865b8c81d8..339b2033af 100644 --- a/go/lib/scrypto/trc/v2/update_test.go +++ b/go/lib/scrypto/trc/v2/update_test.go @@ -143,7 +143,7 @@ func TestSensitiveUpdate(t *testing.T) { *updated.VotingQuorumPtr += 1 updated.PrimaryASes[a190] = trc.PrimaryAS{ Attributes: trc.Attributes{trc.Issuing, trc.Voting}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -217,7 +217,7 @@ func TestSensitiveUpdate(t *testing.T) { Modify: func(updated, _ *trc.TRC) { primary := updated.PrimaryASes[a150] primary.Attributes = append(primary.Attributes, trc.Issuing) - primary.Keys = map[trc.KeyType]trc.KeyMeta{ + primary.Keys = map[trc.KeyType]scrypto.KeyMeta{ trc.IssuingKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -246,12 +246,12 @@ func TestSensitiveUpdate(t *testing.T) { Modify: func(updated, _ *trc.TRC) { primary := updated.PrimaryASes[a130] primary.Attributes = trc.Attributes{trc.Issuing, trc.Core, trc.Voting} - primary.Keys[trc.OnlineKey] = trc.KeyMeta{ + primary.Keys[trc.OnlineKey] = scrypto.KeyMeta{ KeyVersion: 1, Algorithm: scrypto.Ed25519, Key: []byte{0, 130, 1}, } - primary.Keys[trc.OfflineKey] = trc.KeyMeta{ + primary.Keys[trc.OfflineKey] = scrypto.KeyMeta{ KeyVersion: 1, Algorithm: scrypto.Ed25519, Key: []byte{1, 130, 1}, @@ -322,7 +322,7 @@ func TestSensitiveUpdate(t *testing.T) { }, "Update offline key": { Modify: func(updated, _ *trc.TRC) { - updated.PrimaryASes[a110].Keys[trc.OfflineKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.OfflineKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{1, 110, 2}, @@ -378,7 +378,7 @@ func TestSensitiveUpdate(t *testing.T) { Modify: func(updated, _ *trc.TRC) { updated.PrimaryASes[a190] = trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting, trc.Core}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -399,7 +399,7 @@ func TestSensitiveUpdate(t *testing.T) { Modify: func(updated, _ *trc.TRC) { updated.PrimaryASes[a190] = trc.PrimaryAS{ Attributes: trc.Attributes{trc.Voting, trc.Core}, - Keys: map[trc.KeyType]trc.KeyMeta{ + Keys: map[trc.KeyType]scrypto.KeyMeta{ trc.OnlineKey: { KeyVersion: 1, Algorithm: scrypto.Ed25519, @@ -442,7 +442,7 @@ func TestSensitiveUpdate(t *testing.T) { }, "Update offline key without proof of possession": { Modify: func(updated, _ *trc.TRC) { - updated.PrimaryASes[a110].Keys[trc.OfflineKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.OfflineKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{1, 110, 2}, @@ -571,7 +571,7 @@ func TestRegularUpdate(t *testing.T) { }, "Update issuing key": { Modify: func(updated, _ *trc.TRC) { - updated.PrimaryASes[a110].Keys[trc.IssuingKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.IssuingKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{2, 110, 2}, @@ -595,7 +595,7 @@ func TestRegularUpdate(t *testing.T) { }, "Update online key": { Modify: func(updated, _ *trc.TRC) { - updated.PrimaryASes[a110].Keys[trc.OnlineKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.OnlineKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{0, 110, 2}, @@ -668,7 +668,7 @@ func TestRegularUpdate(t *testing.T) { }, "Missing proof of possession": { Modify: func(updated, _ *trc.TRC) { - updated.PrimaryASes[a110].Keys[trc.OnlineKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.OnlineKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{0, 110, 2}, @@ -688,7 +688,7 @@ func TestRegularUpdate(t *testing.T) { }, "Update online key with online vote": { Modify: func(updated, _ *trc.TRC) { - updated.PrimaryASes[a110].Keys[trc.OnlineKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.OnlineKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{0, 110, 2}, @@ -701,7 +701,7 @@ func TestRegularUpdate(t *testing.T) { Modify: func(updated, prev *trc.TRC) { *prev.VotingQuorumPtr = 2 *updated.VotingQuorumPtr = 2 - updated.PrimaryASes[a110].Keys[trc.OnlineKey] = trc.KeyMeta{ + updated.PrimaryASes[a110].Keys[trc.OnlineKey] = scrypto.KeyMeta{ KeyVersion: 2, Algorithm: scrypto.Ed25519, Key: []byte{0, 110, 2}, diff --git a/go/lib/scrypto/validity.go b/go/lib/scrypto/validity.go index 67a061f7ec..e1044a5048 100644 --- a/go/lib/scrypto/validity.go +++ b/go/lib/scrypto/validity.go @@ -15,6 +15,7 @@ package scrypto import ( + "bytes" "encoding/json" "fmt" "time" @@ -29,22 +30,21 @@ type Validity struct { NotAfter util.UnixTime `json:"NotAfter"` } -type validity struct { - NotBefore *util.UnixTime `json:"NotBefore"` - NotAfter *util.UnixTime `json:"NotAfter"` +// Contains indicates whether the provided time is inside the validity period. +func (v *Validity) Contains(t time.Time) bool { + return !t.Before(v.NotBefore.Time) && !t.After(v.NotAfter.Time) } // UnmarshalJSON checks that both NotBefore and NotAfter are set. func (v *Validity) UnmarshalJSON(b []byte) error { var p validity - if err := json.Unmarshal(b, &p); err != nil { + dec := json.NewDecoder(bytes.NewReader(b)) + dec.DisallowUnknownFields() + if err := dec.Decode(&p); err != nil { return err } - if p.NotBefore == nil { - return common.NewBasicError("NotBefore unset", nil) - } - if p.NotAfter == nil { - return common.NewBasicError("NotBefore unset", nil) + if err := p.checkAllSet(); err != nil { + return err } *v = Validity{ NotBefore: *p.NotBefore, @@ -53,11 +53,21 @@ func (v *Validity) UnmarshalJSON(b []byte) error { return nil } -// Contains indicates whether the provided time is inside the validity period. -func (v *Validity) Contains(t time.Time) bool { - return !t.Before(v.NotBefore.Time) && !t.After(v.NotAfter.Time) -} - func (v *Validity) String() string { return fmt.Sprintf("[%s, %s]", v.NotBefore, v.NotAfter) } + +type validity struct { + NotBefore *util.UnixTime `json:"NotBefore"` + NotAfter *util.UnixTime `json:"NotAfter"` +} + +func (v *validity) checkAllSet() error { + if v.NotBefore == nil { + return common.NewBasicError("NotBefore unset", nil) + } + if v.NotAfter == nil { + return common.NewBasicError("NotBefore unset", nil) + } + return nil +} diff --git a/go/lib/scrypto/validity_test.go b/go/lib/scrypto/validity_test.go index b75b5c8fe5..fa5091a41b 100644 --- a/go/lib/scrypto/validity_test.go +++ b/go/lib/scrypto/validity_test.go @@ -77,7 +77,17 @@ func TestValidityUnmarshal(t *testing.T) { Input: `{"NotBefore": 1356048000}`, Assert: assert.Error, }, - "correct": { + "Unknown field": { + Input: ` + { + "UnknownField": "UNKNOWN" + "NotBefore": 1356048000, + "NotAfter": 1356134400 + } + `, + Assert: assert.Error, + }, + "Valid": { Input: ` { "NotBefore": 1356048000, diff --git a/go/lib/scrypto/version.go b/go/lib/scrypto/version.go new file mode 100644 index 0000000000..6d3d5ee558 --- /dev/null +++ b/go/lib/scrypto/version.go @@ -0,0 +1,52 @@ +// Copyright 2019 Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scrypto + +import ( + "encoding/json" + "errors" + "strconv" +) + +// ErrInvalidVersion indicates an invalid trust file version. +var ErrInvalidVersion = errors.New("version must not be zero") + +var _ json.Unmarshaler = (*Version)(nil) +var _ json.Marshaler = (*Version)(nil) + +// Version identifies the version of a trust file. It cannot be +// marshalled/unmarshalled to/from LatestVer. +type Version uint64 + +// UnmarshalJSON checks that the value is not LatestVer. +func (v *Version) UnmarshalJSON(b []byte) error { + parsed, err := strconv.ParseUint(string(b), 10, 64) + if err != nil { + return err + } + if parsed == LatestVer { + return ErrInvalidVersion + } + *v = Version(parsed) + return nil +} + +// MarshalJSON checks that the value is not LatestVer. +func (v Version) MarshalJSON() ([]byte, error) { + if uint64(v) == LatestVer { + return nil, ErrInvalidVersion + } + return json.Marshal(uint64(v)) +} diff --git a/go/lib/scrypto/version_test.go b/go/lib/scrypto/version_test.go new file mode 100644 index 0000000000..075f274a37 --- /dev/null +++ b/go/lib/scrypto/version_test.go @@ -0,0 +1,87 @@ +// Copyright 2019 Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package scrypto_test + +import ( + "encoding/json" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/scionproto/scion/go/lib/scrypto" +) + +func TestVersionUnmarshalJSON(t *testing.T) { + tests := map[string]struct { + Input []byte + Expected scrypto.Version + Assertion assert.ErrorAssertionFunc + }{ + "Valid": { + Input: []byte("1"), + Expected: 1, + Assertion: assert.NoError, + }, + "Reserved": { + Input: []byte(strconv.FormatUint(scrypto.LatestVer, 10)), + Assertion: assert.Error, + }, + "String": { + Input: []byte(`"1"`), + Assertion: assert.Error, + }, + "Garbage": { + Input: []byte(`"Garbage"`), + Assertion: assert.Error, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var v scrypto.Version + test.Assertion(t, json.Unmarshal(test.Input, &v)) + assert.Equal(t, test.Expected, v) + }) + } +} + +func TestVersionMarshalJSON(t *testing.T) { + type mockObj struct { + Version scrypto.Version + } + tests := map[string]struct { + // Use a struct to simulate value type marshaling. Pointer vs value receiver. + Input mockObj + Expected []byte + Assertion assert.ErrorAssertionFunc + }{ + "Valid": { + Input: mockObj{Version: 1}, + Expected: []byte(`{"Version":1}`), + Assertion: assert.NoError, + }, + "Reserved": { + Input: mockObj{Version: scrypto.Version(scrypto.LatestVer)}, + Assertion: assert.Error, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + b, err := json.Marshal(test.Input) + test.Assertion(t, err) + assert.Equal(t, test.Expected, b) + }) + } +}