diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd6afb8..e771c197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog This is the changelog for NeoFS-API-Go +## [1.1.0] - 2020-06-18 + +### Added + +- `container.SetExtendedACL` rpc. +- `container.GetExtendedACL` rpc. +- Bearer token to all request messages. +- X-headers to all request messages. + +### Changed + +- Implementation and signatures of Sign/Verify request functions. + +### Updated + +- NeoFS API v1.0.0 => 1.1.0 + ## [1.0.0] - 2020-05-26 - Bump major release @@ -339,3 +356,4 @@ Initial public release [0.7.5]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.4...v0.7.5 [0.7.6]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.5...v0.7.6 [1.0.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.6...v1.0.0 +[1.1.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v1.0.0...v1.1.0 diff --git a/Makefile b/Makefile index 3dcd690c..159c7e39 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PROTO_VERSION=v1.0.0 +PROTO_VERSION=v1.1.0 PROTO_URL=https://github.com/nspcc-dev/neofs-api/archive/$(PROTO_VERSION).tar.gz B=\033[0;1m diff --git a/README.md b/README.md index fac04d07..d44e0570 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ NeoFS API repository contains implementation of core NeoFS structures that can be used for integration with NeoFS. +## Сompatibility + +[neofs-api v1.1.0]: https://github.com/nspcc-dev/neofs-api/releases/tag/v1.1.0 +[neofs-api-go v1.1.0]: https://github.com/nspcc-dev/neofs-api-go/releases/tag/v1.1.0 +[neofs-api-go v1.1.0] supports [neofs-api v1.1.0] + ## Description Repository contains 13 packages that implement NeoFS core structures. These diff --git a/accounting/sign_test.go b/accounting/sign_test.go index dd7a819f..ebc683bf 100644 --- a/accounting/sign_test.go +++ b/accounting/sign_test.go @@ -13,7 +13,7 @@ func TestSignBalanceRequest(t *testing.T) { sk := test.DecodeKey(0) type sigType interface { - service.SignedDataWithToken + service.RequestData service.SignKeyPairAccumulator service.SignKeyPairSource SetToken(*service.Token) @@ -159,26 +159,26 @@ func TestSignBalanceRequest(t *testing.T) { token := new(service.Token) v.SetToken(token) - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) token.SetSessionKey(append(token.GetSessionKey(), 1)) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } { // payload corruptions for _, corruption := range item.payloadCorrupt { v := item.constructor() - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) corruption(v) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } } } diff --git a/bootstrap/sign_test.go b/bootstrap/sign_test.go index 2c76117a..38121305 100644 --- a/bootstrap/sign_test.go +++ b/bootstrap/sign_test.go @@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) { sk := test.DecodeKey(0) type sigType interface { - service.SignedDataWithToken + service.RequestData service.SignKeyPairAccumulator service.SignKeyPairSource SetToken(*service.Token) @@ -56,26 +56,26 @@ func TestRequestSign(t *testing.T) { token := new(service.Token) v.SetToken(token) - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) token.SetSessionKey(append(token.GetSessionKey(), 1)) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } { // payload corruptions for _, corruption := range item.payloadCorrupt { v := item.constructor() - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) corruption(v) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } } } diff --git a/container/service.pb.go b/container/service.pb.go index 61312544..f49f79fe 100644 Binary files a/container/service.pb.go and b/container/service.pb.go differ diff --git a/container/service.proto b/container/service.proto index 7df2c66d..b174052a 100644 --- a/container/service.proto +++ b/container/service.proto @@ -27,6 +27,12 @@ service Service { // List returns all user's containers rpc List(ListRequest) returns (ListResponse); + + // SetExtendedACL changes extended ACL rules of the container + rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse); + + // GetExtendedACL returns extended ACL rules of the container + rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse); } message PutRequest { @@ -99,3 +105,42 @@ message ListResponse { // CID (container id) is list of SHA256 hashes of the container structures repeated bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; } + +message ExtendedACLKey { + // ID (container id) is a SHA256 hash of the container structure + bytes ID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; +} + +message ExtendedACLValue { + // EACL carries binary representation of the table of extended ACL rules + bytes EACL = 1; + // Signature carries EACL field signature + bytes Signature = 2; +} + +message SetExtendedACLRequest { + // Key carries key to extended ACL information + ExtendedACLKey Key = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // Value carries extended ACL information + ExtendedACLValue Value = 2 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestMetaHeader contains information about request meta headers (should be embedded into message) + service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) + service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; +} + +message SetExtendedACLResponse {} + +message GetExtendedACLRequest { + // Key carries key to extended ACL information + ExtendedACLKey Key = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestMetaHeader contains information about request meta headers (should be embedded into message) + service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) + service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; +} + +message GetExtendedACLResponse { + // ACL carries extended ACL information + ExtendedACLValue ACL = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; +} diff --git a/container/sign.go b/container/sign.go index eafd93c3..f538f2d7 100644 --- a/container/sign.go +++ b/container/sign.go @@ -135,3 +135,60 @@ func (m ListRequest) ReadSignedData(p []byte) (int, error) { return off, nil } + +// SignedData returns payload bytes of the request. +func (m GetExtendedACLRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m GetExtendedACLRequest) SignedDataSize() int { + return m.GetID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetExtendedACLRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetID().Bytes()) + + return off, nil +} + +// SignedData returns payload bytes of the request. +func (m SetExtendedACLRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m SetExtendedACLRequest) SignedDataSize() int { + return 0 + + m.GetID().Size() + + len(m.GetEACL()) + + len(m.GetSignature()) +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m SetExtendedACLRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetID().Bytes()) + + off += copy(p[off:], m.GetEACL()) + + off += copy(p[off:], m.GetSignature()) + + return off, nil +} diff --git a/container/sign_test.go b/container/sign_test.go index e469399c..d04a6989 100644 --- a/container/sign_test.go +++ b/container/sign_test.go @@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) { sk := test.DecodeKey(0) type sigType interface { - service.SignedDataWithToken + service.RequestData service.SignKeyPairAccumulator service.SignKeyPairSource SetToken(*service.Token) @@ -108,6 +108,50 @@ func TestRequestSign(t *testing.T) { }, }, }, + { // GetExtendedACLRequest + constructor: func() sigType { + return new(GetExtendedACLRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*GetExtendedACLRequest) + + id := req.GetID() + id[0]++ + + req.SetID(id) + }, + }, + }, + { // SetExtendedACLRequest + constructor: func() sigType { + return new(SetExtendedACLRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*SetExtendedACLRequest) + + id := req.GetID() + id[0]++ + + req.SetID(id) + }, + func(s sigType) { + req := s.(*SetExtendedACLRequest) + + req.SetEACL( + append(req.GetEACL(), 1), + ) + }, + func(s sigType) { + req := s.(*SetExtendedACLRequest) + + req.SetSignature( + append(req.GetSignature(), 1), + ) + }, + }, + }, } for _, item := range items { @@ -117,26 +161,26 @@ func TestRequestSign(t *testing.T) { token := new(service.Token) v.SetToken(token) - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) token.SetSessionKey(append(token.GetSessionKey(), 1)) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } { // payload corruptions for _, corruption := range item.payloadCorrupt { v := item.constructor() - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) corruption(v) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } } } diff --git a/container/types.go b/container/types.go index f340aa59..38bdcffb 100644 --- a/container/types.go +++ b/container/types.go @@ -158,3 +158,23 @@ func (m ListRequest) GetOwnerID() OwnerID { func (m *ListRequest) SetOwnerID(owner OwnerID) { m.OwnerID = owner } + +// GetID is an ID field getter. +func (m ExtendedACLKey) GetID() CID { + return m.ID +} + +// SetID is an ID field setter. +func (m *ExtendedACLKey) SetID(v CID) { + m.ID = v +} + +// SetEACL is an EACL field setter. +func (m *ExtendedACLValue) SetEACL(v []byte) { + m.EACL = v +} + +// SetSignature is a Signature field setter. +func (m *ExtendedACLValue) SetSignature(sig []byte) { + m.Signature = sig +} diff --git a/container/types_test.go b/container/types_test.go index cc171cbe..76bbe1ce 100644 --- a/container/types_test.go +++ b/container/types_test.go @@ -140,3 +140,23 @@ func TestListRequestGettersSetters(t *testing.T) { require.Equal(t, owner, m.GetOwnerID()) }) } + +func TestExtendedACLKey(t *testing.T) { + s := new(ExtendedACLKey) + + id := CID{1, 2, 3} + s.SetID(id) + require.Equal(t, id, s.GetID()) +} + +func TestExtendedACLValue(t *testing.T) { + s := new(ExtendedACLValue) + + acl := []byte{1, 2, 3} + s.SetEACL(acl) + require.Equal(t, acl, s.GetEACL()) + + sig := []byte{4, 5, 6} + s.SetSignature(sig) + require.Equal(t, sig, s.GetSignature()) +} diff --git a/docs/container.md b/docs/container.md index f0188ca7..fd89acd8 100644 --- a/docs/container.md +++ b/docs/container.md @@ -10,12 +10,18 @@ - Messages - [DeleteRequest](#container.DeleteRequest) - [DeleteResponse](#container.DeleteResponse) + - [ExtendedACLKey](#container.ExtendedACLKey) + - [ExtendedACLValue](#container.ExtendedACLValue) + - [GetExtendedACLRequest](#container.GetExtendedACLRequest) + - [GetExtendedACLResponse](#container.GetExtendedACLResponse) - [GetRequest](#container.GetRequest) - [GetResponse](#container.GetResponse) - [ListRequest](#container.ListRequest) - [ListResponse](#container.ListResponse) - [PutRequest](#container.PutRequest) - [PutResponse](#container.PutResponse) + - [SetExtendedACLRequest](#container.SetExtendedACLRequest) + - [SetExtendedACLResponse](#container.SetExtendedACLResponse) - [container/types.proto](#container/types.proto) @@ -46,6 +52,8 @@ rpc Put(PutRequest) returns (PutResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Get(GetRequest) returns (GetResponse); rpc List(ListRequest) returns (ListResponse); +rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse); +rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse); ``` @@ -80,6 +88,20 @@ List returns all user's containers | Name | Input | Output | | ---- | ----- | ------ | | List | [ListRequest](#container.ListRequest) | [ListResponse](#container.ListResponse) | +#### Method SetExtendedACL + +SetExtendedACL changes extended ACL rules of the container + +| Name | Input | Output | +| ---- | ----- | ------ | +| SetExtendedACL | [SetExtendedACLRequest](#container.SetExtendedACLRequest) | [SetExtendedACLResponse](#container.SetExtendedACLResponse) | +#### Method GetExtendedACL + +GetExtendedACL returns extended ACL rules of the container + +| Name | Input | Output | +| ---- | ----- | ------ | +| GetExtendedACL | [GetExtendedACLRequest](#container.GetExtendedACLRequest) | [GetExtendedACLResponse](#container.GetExtendedACLResponse) | @@ -104,6 +126,53 @@ via consensus in inner ring nodes + + +### Message ExtendedACLKey + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ID | [bytes](#bytes) | | ID (container id) is a SHA256 hash of the container structure | + + + + +### Message ExtendedACLValue + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| EACL | [bytes](#bytes) | | EACL carries binary representation of the table of extended ACL rules | +| Signature | [bytes](#bytes) | | Signature carries EACL field signature | + + + + +### Message GetExtendedACLRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Key | [ExtendedACLKey](#container.ExtendedACLKey) | | Key carries key to extended ACL information | +| Meta | [service.RequestMetaHeader](#service.RequestMetaHeader) | | RequestMetaHeader contains information about request meta headers (should be embedded into message) | +| Verify | [service.RequestVerificationHeader](#service.RequestVerificationHeader) | | RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) | + + + + +### Message GetExtendedACLResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ACL | [ExtendedACLValue](#container.ExtendedACLValue) | | ACL carries extended ACL information | + + ### Message GetRequest @@ -179,6 +248,27 @@ via consensus in inner ring nodes | ----- | ---- | ----- | ----------- | | CID | [bytes](#bytes) | | CID (container id) is a SHA256 hash of the container structure | + + + +### Message SetExtendedACLRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Key | [ExtendedACLKey](#container.ExtendedACLKey) | | Key carries key to extended ACL information | +| Value | [ExtendedACLValue](#container.ExtendedACLValue) | | Value carries extended ACL information | +| Meta | [service.RequestMetaHeader](#service.RequestMetaHeader) | | RequestMetaHeader contains information about request meta headers (should be embedded into message) | +| Verify | [service.RequestVerificationHeader](#service.RequestVerificationHeader) | | RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) | + + + + +### Message SetExtendedACLResponse + + + diff --git a/docs/service.md b/docs/service.md index 0765f044..223ddd18 100644 --- a/docs/service.md +++ b/docs/service.md @@ -6,6 +6,8 @@ - [service/meta.proto](#service/meta.proto) - Messages + - [RequestExtendedHeader](#service.RequestExtendedHeader) + - [RequestExtendedHeader.KV](#service.RequestExtendedHeader.KV) - [RequestMetaHeader](#service.RequestMetaHeader) - [ResponseMetaHeader](#service.ResponseMetaHeader) @@ -13,6 +15,8 @@ - [service/verify.proto](#service/verify.proto) - Messages + - [BearerTokenMsg](#service.BearerTokenMsg) + - [BearerTokenMsg.Info](#service.BearerTokenMsg.Info) - [RequestVerificationHeader](#service.RequestVerificationHeader) - [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature) - [Token](#service.Token) @@ -39,6 +43,29 @@ + + +### Message RequestExtendedHeader +RequestExtendedHeader contains extended headers of request + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Headers | [RequestExtendedHeader.KV](#service.RequestExtendedHeader.KV) | repeated | Headers carries list of key-value headers | + + + + +### Message RequestExtendedHeader.KV +KV contains string key-value pair + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| K | [string](#string) | | K carries extended header key | +| V | [string](#string) | | V carries extended header value | + + ### Message RequestMetaHeader @@ -52,6 +79,7 @@ RequestMetaHeader contains information about request meta headers | Epoch | [uint64](#uint64) | | Epoch for user can be empty, because node sets epoch to the actual value | | Version | [uint32](#uint32) | | Version defines protocol version TODO: not used for now, should be implemented in future | | Raw | [bool](#bool) | | Raw determines whether the request is raw or not | +| ExtendedHeader | [RequestExtendedHeader](#service.RequestExtendedHeader) | | ExtendedHeader carries extended headers of the request | @@ -81,6 +109,32 @@ ResponseMetaHeader contains meta information based on request processing by serv + + +### Message BearerTokenMsg +BearerTokenMsg carries information about request ACL rules with limited lifetime + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| TokenInfo | [BearerTokenMsg.Info](#service.BearerTokenMsg.Info) | | TokenInfo is a grouped information about token | +| OwnerKey | [bytes](#bytes) | | OwnerKey is a public key of the token owner | +| Signature | [bytes](#bytes) | | Signature is a signature of token information | + + + + +### Message BearerTokenMsg.Info + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ACLRules | [bytes](#bytes) | | ACLRules carries a binary representation of the table of extended ACL rules | +| OwnerID | [bytes](#bytes) | | OwnerID is an owner of token | +| ValidUntil | [uint64](#uint64) | | ValidUntil carries a last epoch of token lifetime | + + ### Message RequestVerificationHeader @@ -92,6 +146,7 @@ RequestVerificationHeader is a set of signatures of every NeoFS Node that proces | ----- | ---- | ----- | ----------- | | Signatures | [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature) | repeated | Signatures is a set of signatures of every passed NeoFS Node | | Token | [Token](#service.Token) | | Token is a token of the session within which the request is sent | +| Bearer | [BearerTokenMsg](#service.BearerTokenMsg) | | Bearer is a Bearer token of the request | diff --git a/object/sign_test.go b/object/sign_test.go index 9480fda7..dcfbd7e3 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -13,7 +13,7 @@ func TestSignVerifyRequests(t *testing.T) { sk := test.DecodeKey(0) type sigType interface { - service.SignedDataWithToken + service.RequestData service.SignKeyPairAccumulator service.SignKeyPairSource SetToken(*Token) @@ -164,26 +164,26 @@ func TestSignVerifyRequests(t *testing.T) { token := new(Token) v.SetToken(token) - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) token.SetSessionKey(append(token.GetSessionKey(), 1)) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } { // payload corruptions for _, corruption := range item.payloadCorrupt { v := item.constructor() - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) corruption(v) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } } } diff --git a/service/bearer.go b/service/bearer.go new file mode 100644 index 00000000..327f74f6 --- /dev/null +++ b/service/bearer.go @@ -0,0 +1,140 @@ +package service + +import ( + "crypto/ecdsa" + "io" + + "github.com/nspcc-dev/neofs-api-go/refs" + crypto "github.com/nspcc-dev/neofs-crypto" +) + +type signedBearerToken struct { + BearerToken +} + +type bearerMsgWrapper struct { + *BearerTokenMsg +} + +const fixedBearerTokenDataSize = 0 + + refs.OwnerIDSize + + 8 + +// NewSignedBearerToken wraps passed BearerToken in a component suitable for signing. +// +// Result can be used in AddSignatureWithKey function. +func NewSignedBearerToken(token BearerToken) DataWithSignKeyAccumulator { + return &signedBearerToken{ + BearerToken: token, + } +} + +// NewVerifiedBearerToken wraps passed SessionToken in a component suitable for signature verification. +// +// Result can be used in VerifySignatureWithKey function. +func NewVerifiedBearerToken(token BearerToken) DataWithSignature { + return &signedBearerToken{ + BearerToken: token, + } +} + +// AddSignKey calls a Signature field setter and an OwnerKey field setter with corresponding arguments. +func (s signedBearerToken) AddSignKey(sig []byte, key *ecdsa.PublicKey) { + if s.BearerToken != nil { + s.SetSignature(sig) + + s.SetOwnerKey( + crypto.MarshalPublicKey(key), + ) + } +} + +// SignedData returns token information in a binary representation. +func (s signedBearerToken) SignedData() ([]byte, error) { + return SignedDataFromReader(s) +} + +// SignedDataSize returns the length of signed token information slice. +func (s signedBearerToken) SignedDataSize() int { + return bearerTokenInfoSize(s.BearerToken) +} + +// ReadSignedData copies a binary representation of the token information to passed buffer. +// +// If buffer length is less than required, io.ErrUnexpectedEOF returns. +func (s signedBearerToken) ReadSignedData(p []byte) (int, error) { + sz := s.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + copyBearerTokenSignedData(p, s.BearerToken) + + return sz, nil +} + +func bearerTokenInfoSize(v ACLRulesSource) int { + if v == nil { + return 0 + } + return fixedBearerTokenDataSize + len(v.GetACLRules()) +} + +// Fills passed buffer with signing token information bytes. +// Does not check buffer length, it is understood that enough space is allocated in it. +// +// If passed BearerTokenInfo, buffer remains unchanged. +func copyBearerTokenSignedData(buf []byte, token BearerTokenInfo) { + if token == nil { + return + } + + var off int + + off += copy(buf[off:], token.GetACLRules()) + + off += copy(buf[off:], token.GetOwnerID().Bytes()) + + tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch()) + off += 8 +} + +// SetACLRules is an ACLRules field setter. +func (m *BearerTokenMsg_Info) SetACLRules(v []byte) { + m.ACLRules = v +} + +// SetValidUntil is a ValidUntil field setter. +func (m *BearerTokenMsg_Info) SetValidUntil(v uint64) { + m.ValidUntil = v +} + +// GetOwnerID if an OwnerID field getter. +func (m BearerTokenMsg_Info) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *BearerTokenMsg_Info) SetOwnerID(v OwnerID) { + m.OwnerID = v +} + +// ExpirationEpoch returns the result of ValidUntil field getter. +func (m BearerTokenMsg_Info) ExpirationEpoch() uint64 { + return m.GetValidUntil() +} + +// SetExpirationEpoch passes argument to ValidUntil field setter. +func (m *BearerTokenMsg_Info) SetExpirationEpoch(v uint64) { + m.SetValidUntil(v) +} + +// SetOwnerKey is an OwnerKey field setter. +func (m *BearerTokenMsg) SetOwnerKey(v []byte) { + m.OwnerKey = v +} + +// SetSignature is a Signature field setter. +func (m *BearerTokenMsg) SetSignature(v []byte) { + m.Signature = v +} diff --git a/service/bearer_test.go b/service/bearer_test.go new file mode 100644 index 00000000..d6985cbd --- /dev/null +++ b/service/bearer_test.go @@ -0,0 +1,199 @@ +package service + +import ( + "crypto/rand" + "testing" + + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +type testBearerToken struct { + aclRules []byte + expEpoch uint64 + owner OwnerID + key []byte + sig []byte +} + +func (s testBearerToken) GetACLRules() []byte { + return s.aclRules +} + +func (s *testBearerToken) SetACLRules(v []byte) { + s.aclRules = v +} + +func (s testBearerToken) ExpirationEpoch() uint64 { + return s.expEpoch +} + +func (s *testBearerToken) SetExpirationEpoch(v uint64) { + s.expEpoch = v +} + +func (s testBearerToken) GetOwnerID() OwnerID { + return s.owner +} + +func (s *testBearerToken) SetOwnerID(v OwnerID) { + s.owner = v +} + +func (s testBearerToken) GetOwnerKey() []byte { + return s.key +} + +func (s *testBearerToken) SetOwnerKey(v []byte) { + s.key = v +} + +func (s testBearerToken) GetSignature() []byte { + return s.sig +} + +func (s *testBearerToken) SetSignature(v []byte) { + s.sig = v +} + +func TestBearerTokenMsgGettersSetters(t *testing.T) { + var tok BearerToken = new(testBearerToken) + + { // ACLRules + rules := []byte{1, 2, 3} + + tok.SetACLRules(rules) + + require.Equal(t, rules, tok.GetACLRules()) + } + + { // OwnerID + ownerID := OwnerID{} + _, err := rand.Read(ownerID[:]) + require.NoError(t, err) + + tok.SetOwnerID(ownerID) + + require.Equal(t, ownerID, tok.GetOwnerID()) + } + + { // ValidUntil + e := uint64(5) + + tok.SetExpirationEpoch(e) + + require.Equal(t, e, tok.ExpirationEpoch()) + } + + { // OwnerKey + key := make([]byte, 10) + _, err := rand.Read(key) + require.NoError(t, err) + + tok.SetOwnerKey(key) + + require.Equal(t, key, tok.GetOwnerKey()) + } + + { // Signature + sig := make([]byte, 10) + _, err := rand.Read(sig) + require.NoError(t, err) + + tok.SetSignature(sig) + + require.Equal(t, sig, tok.GetSignature()) + } +} + +func TestSignVerifyBearerToken(t *testing.T) { + var token BearerToken = new(testBearerToken) + + // create private key for signing + sk := test.DecodeKey(0) + pk := &sk.PublicKey + + rules := []byte{1, 2, 3} + token.SetACLRules(rules) + + ownerID := OwnerID{} + _, err := rand.Read(ownerID[:]) + require.NoError(t, err) + token.SetOwnerID(ownerID) + + fEpoch := uint64(2) + token.SetExpirationEpoch(fEpoch) + + signedToken := NewSignedBearerToken(token) + verifiedToken := NewVerifiedBearerToken(token) + + // sign and verify token + require.NoError(t, AddSignatureWithKey(sk, signedToken)) + require.NoError(t, VerifySignatureWithKey(pk, verifiedToken)) + + items := []struct { + corrupt func() + restore func() + }{ + { // ACLRules + corrupt: func() { + token.SetACLRules(append(rules, 1)) + }, + restore: func() { + token.SetACLRules(rules) + }, + }, + { // Owner ID + corrupt: func() { + ownerID[0]++ + token.SetOwnerID(ownerID) + }, + restore: func() { + ownerID[0]-- + token.SetOwnerID(ownerID) + }, + }, + { // Expiration epoch + corrupt: func() { + token.SetExpirationEpoch(fEpoch + 1) + }, + restore: func() { + token.SetExpirationEpoch(fEpoch) + }, + }, + } + + for _, v := range items { + v.corrupt() + require.Error(t, VerifySignatureWithKey(pk, verifiedToken)) + v.restore() + require.NoError(t, VerifySignatureWithKey(pk, verifiedToken)) + } +} + +func TestBearerTokenMsg_Setters(t *testing.T) { + s := new(BearerTokenMsg) + + aclRules := []byte{1, 2, 3} + s.SetACLRules(aclRules) + require.Equal(t, aclRules, s.GetACLRules()) + + validUntil := uint64(6) + s.SetValidUntil(validUntil) + require.Equal(t, validUntil, s.GetValidUntil()) + + s.SetExpirationEpoch(validUntil + 1) + require.Equal(t, validUntil+1, s.ExpirationEpoch()) + + ownerID := OwnerID{1, 2, 3} + s.SetOwnerID(ownerID) + require.Equal(t, ownerID, s.GetOwnerID()) + + ownerKey := []byte{4, 5, 6} + s.SetOwnerKey(ownerKey) + require.Equal(t, ownerKey, s.GetOwnerKey()) + + sig := []byte{7, 8, 9} + s.SetSignature(sig) + require.Equal(t, sig, s.GetSignature()) +} diff --git a/service/errors.go b/service/errors.go index f3a0dfc0..e1c49002 100644 --- a/service/errors.go +++ b/service/errors.go @@ -36,14 +36,18 @@ const ErrEmptyDataWithSignature = internal.Error("empty data with signature") // negative length for slice allocation. const ErrNegativeLength = internal.Error("negative slice length") -// ErrNilDataWithTokenSignAccumulator is returned by functions that expect -// a non-nil DataWithTokenSignAccumulator, but received nil. -const ErrNilDataWithTokenSignAccumulator = internal.Error("signed data with token is nil") +// ErrNilRequestSignedData is returned by functions that expect +// a non-nil RequestSignedData, but received nil. +const ErrNilRequestSignedData = internal.Error("request signed data is nil") -// ErrNilSignatureKeySourceWithToken is returned by functions that expect -// a non-nil SignatureKeySourceWithToken, but received nil. -const ErrNilSignatureKeySourceWithToken = internal.Error("key-signature source with token is nil") +// ErrNilRequestVerifyData is returned by functions that expect +// a non-nil RequestVerifyData, but received nil. +const ErrNilRequestVerifyData = internal.Error("request verification data is nil") // ErrNilSignedDataReader is returned by functions that expect // a non-nil SignedDataReader, but received nil. const ErrNilSignedDataReader = internal.Error("signed data reader is nil") + +// ErrNilSignKeyPairAccumulator is returned by functions that expect +// a non-nil SignKeyPairAccumulator, but received nil. +const ErrNilSignKeyPairAccumulator = internal.Error("signature-key pair accumulator is nil") diff --git a/service/meta.go b/service/meta.go index 3f017584..557d0762 100644 --- a/service/meta.go +++ b/service/meta.go @@ -1,5 +1,17 @@ package service +import ( + "io" +) + +type extHdrWrapper struct { + msg *RequestExtendedHeader_KV +} + +type extHdrSrcWrapper struct { + extHdrSrc ExtendedHeadersSource +} + // CutMeta returns current value and sets RequestMetaHeader to empty value. func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { cp := *m @@ -11,3 +23,109 @@ func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v } + +// ExtendedHeadersSignedData wraps passed ExtendedHeadersSource and returns SignedDataSource. +func ExtendedHeadersSignedData(headers ExtendedHeadersSource) SignedDataSource { + return &extHdrSrcWrapper{ + extHdrSrc: headers, + } +} + +// SignedData returns extended headers in a binary representation. +func (s extHdrSrcWrapper) SignedData() ([]byte, error) { + return SignedDataFromReader(s) +} + +// SignedDataSize returns the length of extended headers slice. +func (s extHdrSrcWrapper) SignedDataSize() (res int) { + if s.extHdrSrc != nil { + for _, h := range s.extHdrSrc.ExtendedHeaders() { + if h != nil { + res += len(h.Key()) + len(h.Value()) + } + } + } + + return +} + +// ReadSignedData copies a binary representation of the extended headers to passed buffer. +// +// If buffer length is less than required, io.ErrUnexpectedEOF returns. +func (s extHdrSrcWrapper) ReadSignedData(p []byte) (int, error) { + sz := s.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + if s.extHdrSrc != nil { + off := 0 + for _, h := range s.extHdrSrc.ExtendedHeaders() { + if h == nil { + continue + } + + off += copy(p[off:], []byte(h.Key())) + + off += copy(p[off:], []byte(h.Value())) + } + } + + return sz, nil +} + +// SetK is a K field setter. +func (m *RequestExtendedHeader_KV) SetK(v string) { + m.K = v +} + +// SetV is a V field setter. +func (m *RequestExtendedHeader_KV) SetV(v string) { + m.V = v +} + +// SetHeaders is a Headers field setter. +func (m *RequestExtendedHeader) SetHeaders(v []RequestExtendedHeader_KV) { + m.Headers = v +} + +func wrapExtendedHeaderKV(msg *RequestExtendedHeader_KV) extHdrWrapper { + return extHdrWrapper{ + msg: msg, + } +} + +// Key returns the result of K field getter. +// +// If message is nil, empty string returns. +func (m extHdrWrapper) Key() string { + if m.msg != nil { + return m.msg.GetK() + } + + return "" +} + +// Value returns the result of V field getter. +// +// If message is nil, empty string returns. +func (m extHdrWrapper) Value() string { + if m.msg != nil { + return m.msg.GetV() + } + + return "" +} + +// ExtendedHeaders composes ExtendedHeader list from the Headers field getter result. +func (m RequestExtendedHeader) ExtendedHeaders() []ExtendedHeader { + hs := m.GetHeaders() + + res := make([]ExtendedHeader, 0, len(hs)) + + for i := range hs { + res = append(res, wrapExtendedHeaderKV(&hs[i])) + } + + return res +} diff --git a/service/meta.pb.go b/service/meta.pb.go index 25a54690..0d630774 100644 Binary files a/service/meta.pb.go and b/service/meta.pb.go differ diff --git a/service/meta.proto b/service/meta.proto index 093f1185..8171980f 100644 --- a/service/meta.proto +++ b/service/meta.proto @@ -19,6 +19,8 @@ message RequestMetaHeader { uint32 Version = 3; // Raw determines whether the request is raw or not bool Raw = 4; + // ExtendedHeader carries extended headers of the request + RequestExtendedHeader ExtendedHeader = 5 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; } // ResponseMetaHeader contains meta information based on request processing by server @@ -30,3 +32,18 @@ message ResponseMetaHeader { // TODO: not used for now, should be implemented in future uint32 Version = 2; } + +// RequestExtendedHeader contains extended headers of request +message RequestExtendedHeader { + // KV contains string key-value pair + message KV { + // K carries extended header key + string K = 1; + + // V carries extended header value + string V = 2; + } + + // Headers carries list of key-value headers + repeated KV Headers = 1 [(gogoproto.nullable) = false]; +} diff --git a/service/meta_test.go b/service/meta_test.go index a0b85ef9..9e9c0129 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -23,3 +23,77 @@ func TestCutRestoreMeta(t *testing.T) { require.Equal(t, item(), v1) } } + +func TestRequestExtendedHeader_KV_Setters(t *testing.T) { + s := new(RequestExtendedHeader_KV) + + key := "key" + s.SetK(key) + require.Equal(t, key, s.GetK()) + + val := "val" + s.SetV(val) + require.Equal(t, val, s.GetV()) +} + +func TestRequestExtendedHeader_SetHeaders(t *testing.T) { + s := new(RequestExtendedHeader) + + hdr := RequestExtendedHeader_KV{} + hdr.SetK("key") + hdr.SetV("val") + + hdrs := []RequestExtendedHeader_KV{ + hdr, + } + + s.SetHeaders(hdrs) + + require.Equal(t, hdrs, s.GetHeaders()) +} + +func TestExtHdrWrapper(t *testing.T) { + s := wrapExtendedHeaderKV(nil) + require.Empty(t, s.Key()) + require.Empty(t, s.Value()) + + msg := new(RequestExtendedHeader_KV) + s = wrapExtendedHeaderKV(msg) + + key := "key" + msg.SetK(key) + require.Equal(t, key, s.Key()) + + val := "val" + msg.SetV(val) + require.Equal(t, val, s.Value()) +} + +func TestRequestExtendedHeader_ExtendedHeaders(t *testing.T) { + var ( + k1, v1 = "key1", "value1" + k2, v2 = "key2", "value2" + h1 = new(RequestExtendedHeader_KV) + h2 = new(RequestExtendedHeader_KV) + ) + + h1.SetK(k1) + h1.SetV(v1) + + h2.SetK(k2) + h2.SetV(v2) + + s := new(RequestExtendedHeader) + s.SetHeaders([]RequestExtendedHeader_KV{ + *h1, *h2, + }) + + xHdrs := s.ExtendedHeaders() + require.Len(t, xHdrs, 2) + + require.Equal(t, k1, xHdrs[0].Key()) + require.Equal(t, v1, xHdrs[0].Value()) + + require.Equal(t, k2, xHdrs[1].Key()) + require.Equal(t, v2, xHdrs[1].Value()) +} diff --git a/service/sign.go b/service/sign.go index 5b1548f0..50453b9f 100644 --- a/service/sign.go +++ b/service/sign.go @@ -2,9 +2,11 @@ package service import ( "crypto/ecdsa" + "io" "sync" crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/pkg/errors" ) type keySign struct { @@ -12,6 +14,20 @@ type keySign struct { sign []byte } +type signSourceGroup struct { + SignKeyPairSource + SignKeyPairAccumulator + + sources []SignedDataSource +} + +type signReadersGroup struct { + SignKeyPairSource + SignKeyPairAccumulator + + readers []SignedDataReader +} + var bytesPool = sync.Pool{ New: func() interface{} { return make([]byte, 5<<20) @@ -176,54 +192,199 @@ func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error { ) } -// SignDataWithSessionToken calculates data with token signature and adds it to accumulator. +// SignRequestData calculates request data signature and adds it to accumulator. // -// Any change of data or session token info provoke signature breakdown. +// Any change of request data provoke signature breakdown. // // If passed private key is nil, crypto.ErrEmptyPrivateKey returns. -// If passed DataWithTokenSignAccumulator is nil, ErrNilDataWithTokenSignAccumulator returns. -func SignDataWithSessionToken(key *ecdsa.PrivateKey, src DataWithTokenSignAccumulator) error { +// If passed RequestSignedData is nil, ErrNilRequestSignedData returns. +func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error { if src == nil { - return ErrNilDataWithTokenSignAccumulator - } else if r, ok := src.(SignedDataReader); ok { - return AddSignatureWithKey(key, &signDataReaderWithToken{ - SignedDataSource: src, - SignKeyPairAccumulator: src, - - rdr: r, - token: src.GetSessionToken(), - }, - ) + return ErrNilRequestSignedData } - return AddSignatureWithKey(key, &signAccumWithToken{ - SignedDataSource: src, - SignKeyPairAccumulator: src, + sigSrc, err := GroupSignedPayloads( + src, + src, + NewSignedSessionToken( + src.GetSessionToken(), + ), + NewSignedBearerToken( + src.GetBearerToken(), + ), + ExtendedHeadersSignedData(src), + ) + if err != nil { + return err + } - token: src.GetSessionToken(), - }) + return AddSignatureWithKey(key, sigSrc) } -// VerifyAccumulatedSignaturesWithToken checks if accumulated key-signature pairs of data with token are valid. +// VerifyRequestData checks if accumulated key-signature pairs of data with token are valid. // -// If passed DataWithTokenSignSource is nil, ErrNilSignatureKeySourceWithToken returns. -func VerifyAccumulatedSignaturesWithToken(src DataWithTokenSignSource) error { +// If passed RequestVerifyData is nil, ErrNilRequestVerifyData returns. +func VerifyRequestData(src RequestVerifyData) error { if src == nil { - return ErrNilSignatureKeySourceWithToken - } else if r, ok := src.(SignedDataReader); ok { - return VerifyAccumulatedSignatures(&signDataReaderWithToken{ - SignedDataSource: src, - SignKeyPairSource: src, - - rdr: r, - token: src.GetSessionToken(), - }) + return ErrNilRequestVerifyData } - return VerifyAccumulatedSignatures(&signAccumWithToken{ - SignedDataSource: src, - SignKeyPairSource: src, + verSrc, err := GroupVerifyPayloads( + src, + src, + NewVerifiedSessionToken( + src.GetSessionToken(), + ), + NewVerifiedBearerToken( + src.GetBearerToken(), + ), + ExtendedHeadersSignedData(src), + ) + if err != nil { + return err + } - token: src.GetSessionToken(), - }) + return VerifyAccumulatedSignatures(verSrc) +} + +// SignedData returns payload bytes concatenation from all sources keeping order. +func (s signSourceGroup) SignedData() ([]byte, error) { + chunks := make([][]byte, 0, len(s.sources)) + sz := 0 + + for i := range s.sources { + data, err := s.sources[i].SignedData() + if err != nil { + return nil, errors.Wrapf(err, "could not get signed payload of element #%d", i) + } + + chunks = append(chunks, data) + + sz += len(data) + } + + res := make([]byte, sz) + off := 0 + + for i := range chunks { + off += copy(res[off:], chunks[i]) + } + + return res, nil +} + +// SignedData returns payload bytes concatenation from all readers. +func (s signReadersGroup) SignedData() ([]byte, error) { + return SignedDataFromReader(s) +} + +// SignedDataSize returns the sum of sizes of all readers. +func (s signReadersGroup) SignedDataSize() (sz int) { + for i := range s.readers { + sz += s.readers[i].SignedDataSize() + } + + return +} + +// ReadSignedData reads data from all readers to passed buffer keeping order. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (s signReadersGroup) ReadSignedData(p []byte) (int, error) { + sz := s.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + off := 0 + + for i := range s.readers { + n, err := s.readers[i].ReadSignedData(p[off:]) + off += n + if err != nil { + return off, errors.Wrapf(err, "could not read signed payload of element #%d", i) + } + } + + return off, nil +} + +// GroupSignedPayloads groups SignKeyPairAccumulator and SignedDataSource list to DataWithSignKeyAccumulator. +// +// If passed SignKeyPairAccumulator is nil, ErrNilSignKeyPairAccumulator returns. +// +// Signed payload of the result is a concatenation of payloads of list elements keeping order. +// Nil elements in list are ignored. +// +// If all elements implement SignedDataReader, result implements it too. +func GroupSignedPayloads(acc SignKeyPairAccumulator, sources ...SignedDataSource) (DataWithSignKeyAccumulator, error) { + if acc == nil { + return nil, ErrNilSignKeyPairAccumulator + } + + return groupPayloads(acc, nil, sources...), nil +} + +// GroupVerifyPayloads groups SignKeyPairSource and SignedDataSource list to DataWithSignKeySource. +// +// If passed SignKeyPairSource is nil, ErrNilSignatureKeySource returns. +// +// Signed payload of the result is a concatenation of payloads of list elements keeping order. +// Nil elements in list are ignored. +// +// If all elements implement SignedDataReader, result implements it too. +func GroupVerifyPayloads(src SignKeyPairSource, sources ...SignedDataSource) (DataWithSignKeySource, error) { + if src == nil { + return nil, ErrNilSignatureKeySource + } + + return groupPayloads(nil, src, sources...), nil +} + +func groupPayloads(acc SignKeyPairAccumulator, src SignKeyPairSource, sources ...SignedDataSource) interface { + SignedDataSource + SignKeyPairSource + SignKeyPairAccumulator +} { + var allReaders bool + + for i := range sources { + if sources[i] == nil { + continue + } else if _, allReaders = sources[i].(SignedDataReader); !allReaders { + break + } + } + + if !allReaders { + res := &signSourceGroup{ + SignKeyPairSource: src, + SignKeyPairAccumulator: acc, + + sources: make([]SignedDataSource, 0, len(sources)), + } + + for i := range sources { + if sources[i] != nil { + res.sources = append(res.sources, sources[i]) + } + } + + return res + } + + res := &signReadersGroup{ + SignKeyPairSource: src, + SignKeyPairAccumulator: acc, + + readers: make([]SignedDataReader, 0, len(sources)), + } + + for i := range sources { + if sources[i] != nil { + res.readers = append(res.readers, sources[i].(SignedDataReader)) + } + } + + return res } diff --git a/service/sign_test.go b/service/sign_test.go index 5cb7c409..6f1e9136 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -18,6 +18,10 @@ type testSignedDataSrc struct { sig []byte key *ecdsa.PublicKey token SessionToken + + bearer BearerToken + + extHdrs []ExtendedHeader } type testSignedDataReader struct { @@ -54,6 +58,14 @@ func (s testSignedDataSrc) GetSessionToken() SessionToken { return s.token } +func (s testSignedDataSrc) GetBearerToken() BearerToken { + return s.bearer +} + +func (s testSignedDataSrc) ExtendedHeaders() []ExtendedHeader { + return s.extHdrs +} + func (s testSignedDataReader) SignedDataSize() int { return len(s.data) } @@ -256,47 +268,63 @@ func TestVerifySignatureWithKey(t *testing.T) { require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src)) } -func TestSignVerifyDataWithSessionToken(t *testing.T) { - // sign with empty DataWithTokenSignAccumulator +func TestSignVerifyRequestData(t *testing.T) { + // sign with empty RequestSignedData require.EqualError(t, - SignDataWithSessionToken(nil, nil), - ErrNilDataWithTokenSignAccumulator.Error(), + SignRequestData(nil, nil), + ErrNilRequestSignedData.Error(), ) - // verify with empty DataWithTokenSignSource + // verify with empty RequestVerifyData require.EqualError(t, - VerifyAccumulatedSignaturesWithToken(nil), - ErrNilSignatureKeySourceWithToken.Error(), + VerifyRequestData(nil), + ErrNilRequestVerifyData.Error(), ) // create test session token var ( token = new(Token) initVerb = Token_Info_Verb(1) + + bearer = new(BearerTokenMsg) + bearerEpoch = uint64(8) + + extHdrKey = "key" + extHdr = new(RequestExtendedHeader_KV) ) token.SetVerb(initVerb) + bearer.SetExpirationEpoch(bearerEpoch) + + extHdr.SetK(extHdrKey) + // create test data with token src := &testSignedDataSrc{ data: testData(t, 10), token: token, + + bearer: bearer, + + extHdrs: []ExtendedHeader{ + wrapExtendedHeaderKV(extHdr), + }, } // create test private key sk := test.DecodeKey(0) // sign with private key - require.NoError(t, SignDataWithSessionToken(sk, src)) + require.NoError(t, SignRequestData(sk, src)) // ascertain that verification is passed - require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) + require.NoError(t, VerifyRequestData(src)) // break the data src.data[0]++ // ascertain that verification is failed - require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) + require.Error(t, VerifyRequestData(src)) // restore the data src.data[0]-- @@ -305,13 +333,37 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) { token.SetVerb(initVerb + 1) // ascertain that verification is failed - require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) + require.Error(t, VerifyRequestData(src)) // restore the token token.SetVerb(initVerb) // ascertain that verification is passed - require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) + require.NoError(t, VerifyRequestData(src)) + + // break the Bearer token + bearer.SetExpirationEpoch(bearerEpoch + 1) + + // ascertain that verification is failed + require.Error(t, VerifyRequestData(src)) + + // restore the Bearer token + bearer.SetExpirationEpoch(bearerEpoch) + + // ascertain that verification is passed + require.NoError(t, VerifyRequestData(src)) + + // break the extended header + extHdr.SetK(extHdrKey + "1") + + // ascertain that verification is failed + require.Error(t, VerifyRequestData(src)) + + // restore the extended header + extHdr.SetK(extHdrKey) + + // ascertain that verification is passed + require.NoError(t, VerifyRequestData(src)) // wrap to data reader rdr := &testSignedDataReader{ @@ -319,8 +371,8 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) { } // sign with private key - require.NoError(t, SignDataWithSessionToken(sk, rdr)) + require.NoError(t, SignRequestData(sk, rdr)) // ascertain that verification is passed - require.NoError(t, VerifyAccumulatedSignaturesWithToken(rdr)) + require.NoError(t, VerifyRequestData(rdr)) } diff --git a/service/types.go b/service/types.go index 66582f56..75a5a0a8 100644 --- a/service/types.go +++ b/service/types.go @@ -250,20 +250,67 @@ type DataWithSignKeySource interface { SignKeyPairSource } -// SignedDataWithToken is an interface of data-token pair with read access. -type SignedDataWithToken interface { +// RequestData is an interface of the request information with read access. +type RequestData interface { SignedDataSource SessionTokenSource + BearerTokenSource + ExtendedHeadersSource } -// DataWithTokenSignAccumulator is an interface of data-token pair with signature write access. -type DataWithTokenSignAccumulator interface { - SignedDataWithToken +// RequestSignedData is an interface of request information with signature write access. +type RequestSignedData interface { + RequestData SignKeyPairAccumulator } -// DataWithTokenSignSource is an interface of data-token pair with signature read access. -type DataWithTokenSignSource interface { - SignedDataWithToken +// RequestVerifyData is an interface of request information with signature read access. +type RequestVerifyData interface { + RequestData SignKeyPairSource } + +// ACLRulesSource is an interface of the container of binary extended ACL rules with read access. +type ACLRulesSource interface { + GetACLRules() []byte +} + +// ACLRulesContainer is an interface of the container of binary extended ACL rules. +type ACLRulesContainer interface { + ACLRulesSource + SetACLRules([]byte) +} + +// BearerTokenInfo is an interface of a fixed set of Bearer token information value containers. +// Contains: +// - binary extended ACL rules; +// - expiration epoch number; +// - ID of the token's owner. +type BearerTokenInfo interface { + ACLRulesContainer + ExpirationEpochContainer + OwnerIDContainer +} + +// BearerToken is an interface of Bearer token information and key-signature pair. +type BearerToken interface { + BearerTokenInfo + OwnerKeyContainer + SignatureContainer +} + +// BearerTokenSource is an interface of the container of a BearerToken with read access. +type BearerTokenSource interface { + GetBearerToken() BearerToken +} + +// ExtendedHeader is an interface of string key-value pair with read access. +type ExtendedHeader interface { + Key() string + Value() string +} + +// ExtendedHeadersSource is an interface of ExtendedHeader list with read access. +type ExtendedHeadersSource interface { + ExtendedHeaders() []ExtendedHeader +} diff --git a/service/verify.go b/service/verify.go index 62db2f53..e1caa068 100644 --- a/service/verify.go +++ b/service/verify.go @@ -67,6 +67,11 @@ func (m *RequestVerificationHeader) SetToken(token *Token) { m.Token = token } +// SetBearer is a Bearer field setter. +func (m *RequestVerificationHeader) SetBearer(v *BearerTokenMsg) { + m.Bearer = v +} + // testCustomField for test usage only. type testCustomField [8]uint32 @@ -98,3 +103,14 @@ func (t testCustomField) MarshalTo(data []byte) (int, error) { return 0, nil } // Marshal skip, it's for test usage only. func (t testCustomField) Marshal() ([]byte, error) { return nil, nil } + +// GetBearerToken wraps Bearer field and return BearerToken interface. +// +// If Bearer field value is nil, nil returns. +func (m RequestVerificationHeader) GetBearerToken() BearerToken { + if t := m.GetBearer(); t != nil { + return t + } + + return nil +} diff --git a/service/verify.pb.go b/service/verify.pb.go index d198302b..02f3f39c 100644 Binary files a/service/verify.pb.go and b/service/verify.pb.go differ diff --git a/service/verify.proto b/service/verify.proto index a6619a6c..a7e694f6 100644 --- a/service/verify.proto +++ b/service/verify.proto @@ -23,6 +23,9 @@ message RequestVerificationHeader { // Token is a token of the session within which the request is sent Token Token = 2; + + // Bearer is a Bearer token of the request + BearerTokenMsg Bearer = 3; } // User token granting rights for object manipulation @@ -91,3 +94,26 @@ message TokenLifetime { // uint32 Version = 2; // bytes Data = 3; // } + +// BearerTokenMsg carries information about request ACL rules with limited lifetime +message BearerTokenMsg { + message Info { + // ACLRules carries a binary representation of the table of extended ACL rules + bytes ACLRules = 1; + + // OwnerID is an owner of token + bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + + // ValidUntil carries a last epoch of token lifetime + uint64 ValidUntil = 3; + } + + // TokenInfo is a grouped information about token + Info TokenInfo = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + + // OwnerKey is a public key of the token owner + bytes OwnerKey = 2; + + // Signature is a signature of token information + bytes Signature = 3; +} diff --git a/service/verify_test.go b/service/verify_test.go index c6e4d619..5ab87534 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -69,7 +69,7 @@ func BenchmarkSignDataWithSessionToken(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - require.NoError(b, SignDataWithSessionToken(key, req)) + require.NoError(b, SignRequestData(key, req)) } } @@ -91,14 +91,14 @@ func BenchmarkVerifyAccumulatedSignaturesWithToken(b *testing.B) { for i := 0; i < 10; i++ { key := test.DecodeKey(i) - require.NoError(b, SignDataWithSessionToken(key, req)) + require.NoError(b, SignRequestData(key, req)) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - require.NoError(b, VerifyAccumulatedSignaturesWithToken(req)) + require.NoError(b, VerifyRequestData(req)) } } @@ -115,3 +115,26 @@ func TestRequestVerificationHeader_SetToken(t *testing.T) { require.Equal(t, token, h.GetToken()) } + +func TestRequestVerificationHeader_SetBearer(t *testing.T) { + aclRules := []byte{1, 2, 3} + + token := new(BearerTokenMsg) + token.SetACLRules(aclRules) + + h := new(RequestVerificationHeader) + + h.SetBearer(token) + + require.Equal(t, token, h.GetBearer()) +} + +func TestRequestVerificationHeader_GetBearerToken(t *testing.T) { + s := new(RequestVerificationHeader) + + require.Nil(t, s.GetBearerToken()) + + bearer := new(BearerTokenMsg) + s.SetBearer(bearer) + require.Equal(t, bearer, s.GetBearerToken()) +} diff --git a/session/create.go b/session/create.go index 35d05400..412d1fd3 100644 --- a/session/create.go +++ b/session/create.go @@ -53,7 +53,7 @@ func (s gRPCCreator) Create(ctx context.Context, p CreateParamsSource) (CreateRe req.SetExpirationEpoch(p.ExpirationEpoch()) // sign with private key - if err := service.SignDataWithSessionToken(s.key, req); err != nil { + if err := service.SignRequestData(s.key, req); err != nil { return nil, err } diff --git a/session/create_test.go b/session/create_test.go index 732d4fd6..943c5daa 100644 --- a/session/create_test.go +++ b/session/create_test.go @@ -84,7 +84,7 @@ func TestGRPCCreator_Create(t *testing.T) { require.Equal(t, ownerID, req.GetOwnerID()) require.Equal(t, created, req.CreationEpoch()) require.Equal(t, expired, req.ExpirationEpoch()) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(req)) + require.NoError(t, service.VerifyRequestData(req)) }, resp: &CreateResponse{ ID: TokenID{1, 2, 3}, diff --git a/state/sign_test.go b/state/sign_test.go index 9b2bca90..05af6548 100644 --- a/state/sign_test.go +++ b/state/sign_test.go @@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) { sk := test.DecodeKey(0) type sigType interface { - service.SignedDataWithToken + service.RequestData service.SignKeyPairAccumulator service.SignKeyPairSource SetToken(*service.Token) @@ -68,26 +68,26 @@ func TestRequestSign(t *testing.T) { token := new(service.Token) v.SetToken(token) - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) token.SetSessionKey(append(token.GetSessionKey(), 1)) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } { // payload corruptions for _, corruption := range item.payloadCorrupt { v := item.constructor() - require.NoError(t, service.SignDataWithSessionToken(sk, v)) + require.NoError(t, service.SignRequestData(sk, v)) - require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.NoError(t, service.VerifyRequestData(v)) corruption(v) - require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + require.Error(t, service.VerifyRequestData(v)) } } }