Merge branch 'release/1.1.0'

This commit is contained in:
Leonard Lyubich 2020-06-18 17:26:15 +03:00
commit 2456521240
31 changed files with 1337 additions and 105 deletions

View file

@ -1,6 +1,23 @@
# Changelog # Changelog
This is the changelog for NeoFS-API-Go 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 ## [1.0.0] - 2020-05-26
- Bump major release - 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.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 [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.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

View file

@ -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 PROTO_URL=https://github.com/nspcc-dev/neofs-api/archive/$(PROTO_VERSION).tar.gz
B=\033[0;1m B=\033[0;1m

View file

@ -11,6 +11,12 @@
NeoFS API repository contains implementation of core NeoFS structures that NeoFS API repository contains implementation of core NeoFS structures that
can be used for integration with NeoFS. 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 ## Description
Repository contains 13 packages that implement NeoFS core structures. These Repository contains 13 packages that implement NeoFS core structures. These

View file

@ -13,7 +13,7 @@ func TestSignBalanceRequest(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -159,26 +159,26 @@ func TestSignBalanceRequest(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(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)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() 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) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -56,26 +56,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(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)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() 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) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

Binary file not shown.

View file

@ -27,6 +27,12 @@ service Service {
// List returns all user's containers // List returns all user's containers
rpc List(ListRequest) returns (ListResponse); 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 { message PutRequest {
@ -99,3 +105,42 @@ message ListResponse {
// CID (container id) is list of SHA256 hashes of the container structures // CID (container id) is list of SHA256 hashes of the container structures
repeated bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; 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];
}

View file

@ -135,3 +135,60 @@ func (m ListRequest) ReadSignedData(p []byte) (int, error) {
return off, nil 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
}

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) 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 { for _, item := range items {
@ -117,26 +161,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(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)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() 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) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

View file

@ -158,3 +158,23 @@ func (m ListRequest) GetOwnerID() OwnerID {
func (m *ListRequest) SetOwnerID(owner OwnerID) { func (m *ListRequest) SetOwnerID(owner OwnerID) {
m.OwnerID = owner 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
}

View file

@ -140,3 +140,23 @@ func TestListRequestGettersSetters(t *testing.T) {
require.Equal(t, owner, m.GetOwnerID()) 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())
}

View file

@ -10,12 +10,18 @@
- Messages - Messages
- [DeleteRequest](#container.DeleteRequest) - [DeleteRequest](#container.DeleteRequest)
- [DeleteResponse](#container.DeleteResponse) - [DeleteResponse](#container.DeleteResponse)
- [ExtendedACLKey](#container.ExtendedACLKey)
- [ExtendedACLValue](#container.ExtendedACLValue)
- [GetExtendedACLRequest](#container.GetExtendedACLRequest)
- [GetExtendedACLResponse](#container.GetExtendedACLResponse)
- [GetRequest](#container.GetRequest) - [GetRequest](#container.GetRequest)
- [GetResponse](#container.GetResponse) - [GetResponse](#container.GetResponse)
- [ListRequest](#container.ListRequest) - [ListRequest](#container.ListRequest)
- [ListResponse](#container.ListResponse) - [ListResponse](#container.ListResponse)
- [PutRequest](#container.PutRequest) - [PutRequest](#container.PutRequest)
- [PutResponse](#container.PutResponse) - [PutResponse](#container.PutResponse)
- [SetExtendedACLRequest](#container.SetExtendedACLRequest)
- [SetExtendedACLResponse](#container.SetExtendedACLResponse)
- [container/types.proto](#container/types.proto) - [container/types.proto](#container/types.proto)
@ -46,6 +52,8 @@ rpc Put(PutRequest) returns (PutResponse);
rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Delete(DeleteRequest) returns (DeleteResponse);
rpc Get(GetRequest) returns (GetResponse); rpc Get(GetRequest) returns (GetResponse);
rpc List(ListRequest) returns (ListResponse); 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 | | Name | Input | Output |
| ---- | ----- | ------ | | ---- | ----- | ------ |
| List | [ListRequest](#container.ListRequest) | [ListResponse](#container.ListResponse) | | 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) |
<!-- end services --> <!-- end services -->
@ -104,6 +126,53 @@ via consensus in inner ring nodes
<a name="container.ExtendedACLKey"></a>
### Message ExtendedACLKey
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| ID | [bytes](#bytes) | | ID (container id) is a SHA256 hash of the container structure |
<a name="container.ExtendedACLValue"></a>
### 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 |
<a name="container.GetExtendedACLRequest"></a>
### 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) |
<a name="container.GetExtendedACLResponse"></a>
### Message GetExtendedACLResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| ACL | [ExtendedACLValue](#container.ExtendedACLValue) | | ACL carries extended ACL information |
<a name="container.GetRequest"></a> <a name="container.GetRequest"></a>
### Message GetRequest ### 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 | | CID | [bytes](#bytes) | | CID (container id) is a SHA256 hash of the container structure |
<a name="container.SetExtendedACLRequest"></a>
### 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) |
<a name="container.SetExtendedACLResponse"></a>
### Message SetExtendedACLResponse
<!-- end messages --> <!-- end messages -->
<!-- end enums --> <!-- end enums -->

View file

@ -6,6 +6,8 @@
- [service/meta.proto](#service/meta.proto) - [service/meta.proto](#service/meta.proto)
- Messages - Messages
- [RequestExtendedHeader](#service.RequestExtendedHeader)
- [RequestExtendedHeader.KV](#service.RequestExtendedHeader.KV)
- [RequestMetaHeader](#service.RequestMetaHeader) - [RequestMetaHeader](#service.RequestMetaHeader)
- [ResponseMetaHeader](#service.ResponseMetaHeader) - [ResponseMetaHeader](#service.ResponseMetaHeader)
@ -13,6 +15,8 @@
- [service/verify.proto](#service/verify.proto) - [service/verify.proto](#service/verify.proto)
- Messages - Messages
- [BearerTokenMsg](#service.BearerTokenMsg)
- [BearerTokenMsg.Info](#service.BearerTokenMsg.Info)
- [RequestVerificationHeader](#service.RequestVerificationHeader) - [RequestVerificationHeader](#service.RequestVerificationHeader)
- [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature) - [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature)
- [Token](#service.Token) - [Token](#service.Token)
@ -39,6 +43,29 @@
<!-- end services --> <!-- end services -->
<a name="service.RequestExtendedHeader"></a>
### 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 |
<a name="service.RequestExtendedHeader.KV"></a>
### 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 |
<a name="service.RequestMetaHeader"></a> <a name="service.RequestMetaHeader"></a>
### Message RequestMetaHeader ### 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 | | 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 | | 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 | | Raw | [bool](#bool) | | Raw determines whether the request is raw or not |
| ExtendedHeader | [RequestExtendedHeader](#service.RequestExtendedHeader) | | ExtendedHeader carries extended headers of the request |
<a name="service.ResponseMetaHeader"></a> <a name="service.ResponseMetaHeader"></a>
@ -81,6 +109,32 @@ ResponseMetaHeader contains meta information based on request processing by serv
<!-- end services --> <!-- end services -->
<a name="service.BearerTokenMsg"></a>
### 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 |
<a name="service.BearerTokenMsg.Info"></a>
### 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 |
<a name="service.RequestVerificationHeader"></a> <a name="service.RequestVerificationHeader"></a>
### Message RequestVerificationHeader ### 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 | | 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 | | 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 |
<a name="service.RequestVerificationHeader.Signature"></a> <a name="service.RequestVerificationHeader.Signature"></a>

View file

@ -13,7 +13,7 @@ func TestSignVerifyRequests(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*Token) SetToken(*Token)
@ -164,26 +164,26 @@ func TestSignVerifyRequests(t *testing.T) {
token := new(Token) token := new(Token)
v.SetToken(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)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() 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) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }

140
service/bearer.go Normal file
View file

@ -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
}

199
service/bearer_test.go Normal file
View file

@ -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())
}

View file

@ -36,14 +36,18 @@ const ErrEmptyDataWithSignature = internal.Error("empty data with signature")
// negative length for slice allocation. // negative length for slice allocation.
const ErrNegativeLength = internal.Error("negative slice length") const ErrNegativeLength = internal.Error("negative slice length")
// ErrNilDataWithTokenSignAccumulator is returned by functions that expect // ErrNilRequestSignedData is returned by functions that expect
// a non-nil DataWithTokenSignAccumulator, but received nil. // a non-nil RequestSignedData, but received nil.
const ErrNilDataWithTokenSignAccumulator = internal.Error("signed data with token is nil") const ErrNilRequestSignedData = internal.Error("request signed data is nil")
// ErrNilSignatureKeySourceWithToken is returned by functions that expect // ErrNilRequestVerifyData is returned by functions that expect
// a non-nil SignatureKeySourceWithToken, but received nil. // a non-nil RequestVerifyData, but received nil.
const ErrNilSignatureKeySourceWithToken = internal.Error("key-signature source with token is nil") const ErrNilRequestVerifyData = internal.Error("request verification data is nil")
// ErrNilSignedDataReader is returned by functions that expect // ErrNilSignedDataReader is returned by functions that expect
// a non-nil SignedDataReader, but received nil. // a non-nil SignedDataReader, but received nil.
const ErrNilSignedDataReader = internal.Error("signed data reader is 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")

View file

@ -1,5 +1,17 @@
package service 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. // CutMeta returns current value and sets RequestMetaHeader to empty value.
func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { func (m *RequestMetaHeader) CutMeta() RequestMetaHeader {
cp := *m cp := *m
@ -11,3 +23,109 @@ func (m *RequestMetaHeader) CutMeta() RequestMetaHeader {
func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) {
*m = v *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
}

Binary file not shown.

View file

@ -19,6 +19,8 @@ message RequestMetaHeader {
uint32 Version = 3; uint32 Version = 3;
// Raw determines whether the request is raw or not // Raw determines whether the request is raw or not
bool Raw = 4; 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 // 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 // TODO: not used for now, should be implemented in future
uint32 Version = 2; 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];
}

View file

@ -23,3 +23,77 @@ func TestCutRestoreMeta(t *testing.T) {
require.Equal(t, item(), v1) 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())
}

View file

@ -2,9 +2,11 @@ package service
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"io"
"sync" "sync"
crypto "github.com/nspcc-dev/neofs-crypto" crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/pkg/errors"
) )
type keySign struct { type keySign struct {
@ -12,6 +14,20 @@ type keySign struct {
sign []byte sign []byte
} }
type signSourceGroup struct {
SignKeyPairSource
SignKeyPairAccumulator
sources []SignedDataSource
}
type signReadersGroup struct {
SignKeyPairSource
SignKeyPairAccumulator
readers []SignedDataReader
}
var bytesPool = sync.Pool{ var bytesPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return make([]byte, 5<<20) 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 private key is nil, crypto.ErrEmptyPrivateKey returns.
// If passed DataWithTokenSignAccumulator is nil, ErrNilDataWithTokenSignAccumulator returns. // If passed RequestSignedData is nil, ErrNilRequestSignedData returns.
func SignDataWithSessionToken(key *ecdsa.PrivateKey, src DataWithTokenSignAccumulator) error { func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error {
if src == nil { if src == nil {
return ErrNilDataWithTokenSignAccumulator return ErrNilRequestSignedData
} else if r, ok := src.(SignedDataReader); ok { }
return AddSignatureWithKey(key, &signDataReaderWithToken{
SignedDataSource: src,
SignKeyPairAccumulator: src,
rdr: r, sigSrc, err := GroupSignedPayloads(
token: src.GetSessionToken(), src,
}, src,
NewSignedSessionToken(
src.GetSessionToken(),
),
NewSignedBearerToken(
src.GetBearerToken(),
),
ExtendedHeadersSignedData(src),
) )
if err != nil {
return err
} }
return AddSignatureWithKey(key, &signAccumWithToken{ return AddSignatureWithKey(key, sigSrc)
SignedDataSource: src,
SignKeyPairAccumulator: src,
token: src.GetSessionToken(),
})
} }
// 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. // If passed RequestVerifyData is nil, ErrNilRequestVerifyData returns.
func VerifyAccumulatedSignaturesWithToken(src DataWithTokenSignSource) error { func VerifyRequestData(src RequestVerifyData) error {
if src == nil { if src == nil {
return ErrNilSignatureKeySourceWithToken return ErrNilRequestVerifyData
} else if r, ok := src.(SignedDataReader); ok {
return VerifyAccumulatedSignatures(&signDataReaderWithToken{
SignedDataSource: src,
SignKeyPairSource: src,
rdr: r,
token: src.GetSessionToken(),
})
} }
return VerifyAccumulatedSignatures(&signAccumWithToken{ verSrc, err := GroupVerifyPayloads(
SignedDataSource: src, src,
SignKeyPairSource: 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
} }

View file

@ -18,6 +18,10 @@ type testSignedDataSrc struct {
sig []byte sig []byte
key *ecdsa.PublicKey key *ecdsa.PublicKey
token SessionToken token SessionToken
bearer BearerToken
extHdrs []ExtendedHeader
} }
type testSignedDataReader struct { type testSignedDataReader struct {
@ -54,6 +58,14 @@ func (s testSignedDataSrc) GetSessionToken() SessionToken {
return s.token return s.token
} }
func (s testSignedDataSrc) GetBearerToken() BearerToken {
return s.bearer
}
func (s testSignedDataSrc) ExtendedHeaders() []ExtendedHeader {
return s.extHdrs
}
func (s testSignedDataReader) SignedDataSize() int { func (s testSignedDataReader) SignedDataSize() int {
return len(s.data) return len(s.data)
} }
@ -256,47 +268,63 @@ func TestVerifySignatureWithKey(t *testing.T) {
require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src)) require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src))
} }
func TestSignVerifyDataWithSessionToken(t *testing.T) { func TestSignVerifyRequestData(t *testing.T) {
// sign with empty DataWithTokenSignAccumulator // sign with empty RequestSignedData
require.EqualError(t, require.EqualError(t,
SignDataWithSessionToken(nil, nil), SignRequestData(nil, nil),
ErrNilDataWithTokenSignAccumulator.Error(), ErrNilRequestSignedData.Error(),
) )
// verify with empty DataWithTokenSignSource // verify with empty RequestVerifyData
require.EqualError(t, require.EqualError(t,
VerifyAccumulatedSignaturesWithToken(nil), VerifyRequestData(nil),
ErrNilSignatureKeySourceWithToken.Error(), ErrNilRequestVerifyData.Error(),
) )
// create test session token // create test session token
var ( var (
token = new(Token) token = new(Token)
initVerb = Token_Info_Verb(1) initVerb = Token_Info_Verb(1)
bearer = new(BearerTokenMsg)
bearerEpoch = uint64(8)
extHdrKey = "key"
extHdr = new(RequestExtendedHeader_KV)
) )
token.SetVerb(initVerb) token.SetVerb(initVerb)
bearer.SetExpirationEpoch(bearerEpoch)
extHdr.SetK(extHdrKey)
// create test data with token // create test data with token
src := &testSignedDataSrc{ src := &testSignedDataSrc{
data: testData(t, 10), data: testData(t, 10),
token: token, token: token,
bearer: bearer,
extHdrs: []ExtendedHeader{
wrapExtendedHeaderKV(extHdr),
},
} }
// create test private key // create test private key
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
// sign with private key // sign with private key
require.NoError(t, SignDataWithSessionToken(sk, src)) require.NoError(t, SignRequestData(sk, src))
// ascertain that verification is passed // ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) require.NoError(t, VerifyRequestData(src))
// break the data // break the data
src.data[0]++ src.data[0]++
// ascertain that verification is failed // ascertain that verification is failed
require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) require.Error(t, VerifyRequestData(src))
// restore the data // restore the data
src.data[0]-- src.data[0]--
@ -305,13 +333,37 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
token.SetVerb(initVerb + 1) token.SetVerb(initVerb + 1)
// ascertain that verification is failed // ascertain that verification is failed
require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) require.Error(t, VerifyRequestData(src))
// restore the token // restore the token
token.SetVerb(initVerb) token.SetVerb(initVerb)
// ascertain that verification is passed // 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 // wrap to data reader
rdr := &testSignedDataReader{ rdr := &testSignedDataReader{
@ -319,8 +371,8 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
} }
// sign with private key // sign with private key
require.NoError(t, SignDataWithSessionToken(sk, rdr)) require.NoError(t, SignRequestData(sk, rdr))
// ascertain that verification is passed // ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(rdr)) require.NoError(t, VerifyRequestData(rdr))
} }

View file

@ -250,20 +250,67 @@ type DataWithSignKeySource interface {
SignKeyPairSource SignKeyPairSource
} }
// SignedDataWithToken is an interface of data-token pair with read access. // RequestData is an interface of the request information with read access.
type SignedDataWithToken interface { type RequestData interface {
SignedDataSource SignedDataSource
SessionTokenSource SessionTokenSource
BearerTokenSource
ExtendedHeadersSource
} }
// DataWithTokenSignAccumulator is an interface of data-token pair with signature write access. // RequestSignedData is an interface of request information with signature write access.
type DataWithTokenSignAccumulator interface { type RequestSignedData interface {
SignedDataWithToken RequestData
SignKeyPairAccumulator SignKeyPairAccumulator
} }
// DataWithTokenSignSource is an interface of data-token pair with signature read access. // RequestVerifyData is an interface of request information with signature read access.
type DataWithTokenSignSource interface { type RequestVerifyData interface {
SignedDataWithToken RequestData
SignKeyPairSource 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
}

View file

@ -67,6 +67,11 @@ func (m *RequestVerificationHeader) SetToken(token *Token) {
m.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. // testCustomField for test usage only.
type testCustomField [8]uint32 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. // Marshal skip, it's for test usage only.
func (t testCustomField) Marshal() ([]byte, error) { return nil, nil } 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
}

Binary file not shown.

View file

@ -23,6 +23,9 @@ message RequestVerificationHeader {
// Token is a token of the session within which the request is sent // Token is a token of the session within which the request is sent
Token Token = 2; Token Token = 2;
// Bearer is a Bearer token of the request
BearerTokenMsg Bearer = 3;
} }
// User token granting rights for object manipulation // User token granting rights for object manipulation
@ -91,3 +94,26 @@ message TokenLifetime {
// uint32 Version = 2; // uint32 Version = 2;
// bytes Data = 3; // 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;
}

View file

@ -69,7 +69,7 @@ func BenchmarkSignDataWithSessionToken(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { 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++ { for i := 0; i < 10; i++ {
key := test.DecodeKey(i) key := test.DecodeKey(i)
require.NoError(b, SignDataWithSessionToken(key, req)) require.NoError(b, SignRequestData(key, req))
} }
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { 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()) 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())
}

View file

@ -53,7 +53,7 @@ func (s gRPCCreator) Create(ctx context.Context, p CreateParamsSource) (CreateRe
req.SetExpirationEpoch(p.ExpirationEpoch()) req.SetExpirationEpoch(p.ExpirationEpoch())
// sign with private key // 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 return nil, err
} }

View file

@ -84,7 +84,7 @@ func TestGRPCCreator_Create(t *testing.T) {
require.Equal(t, ownerID, req.GetOwnerID()) require.Equal(t, ownerID, req.GetOwnerID())
require.Equal(t, created, req.CreationEpoch()) require.Equal(t, created, req.CreationEpoch())
require.Equal(t, expired, req.ExpirationEpoch()) require.Equal(t, expired, req.ExpirationEpoch())
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(req)) require.NoError(t, service.VerifyRequestData(req))
}, },
resp: &CreateResponse{ resp: &CreateResponse{
ID: TokenID{1, 2, 3}, ID: TokenID{1, 2, 3},

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0) sk := test.DecodeKey(0)
type sigType interface { type sigType interface {
service.SignedDataWithToken service.RequestData
service.SignKeyPairAccumulator service.SignKeyPairAccumulator
service.SignKeyPairSource service.SignKeyPairSource
SetToken(*service.Token) SetToken(*service.Token)
@ -68,26 +68,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token) token := new(service.Token)
v.SetToken(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)) token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
{ // payload corruptions { // payload corruptions
for _, corruption := range item.payloadCorrupt { for _, corruption := range item.payloadCorrupt {
v := item.constructor() 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) corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) require.Error(t, service.VerifyRequestData(v))
} }
} }
} }