forked from TrueCloudLab/frostfs-api-go
Merge pull request #74 from nspcc-dev/service-package-refactoring
Service package refactoring
This commit is contained in:
commit
2abcfb7219
22 changed files with 1777 additions and 405 deletions
|
@ -31,7 +31,7 @@ type (
|
||||||
// All object operations must have TTL, Epoch, Type, Container ID and
|
// All object operations must have TTL, Epoch, Type, Container ID and
|
||||||
// permission of usage previous network map.
|
// permission of usage previous network map.
|
||||||
Request interface {
|
Request interface {
|
||||||
service.MetaHeader
|
service.SeizedRequestMetaContainer
|
||||||
|
|
||||||
CID() CID
|
CID() CID
|
||||||
Type() RequestType
|
Type() RequestType
|
||||||
|
|
272
object/sign.go
Normal file
272
object/sign.go
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
//
|
||||||
|
// If payload is nil, ErrHeaderNotFound returns.
|
||||||
|
func (m PutRequest) SignedData() ([]byte, error) {
|
||||||
|
sz := m.SignedDataSize()
|
||||||
|
if sz < 0 {
|
||||||
|
return nil, ErrHeaderNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, sz)
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m PutRequest) ReadSignedData(p []byte) error {
|
||||||
|
r := m.GetR()
|
||||||
|
if r == nil {
|
||||||
|
return ErrHeaderNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := r.MarshalTo(p)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns the size of payload of the Put request.
|
||||||
|
//
|
||||||
|
// If payload is nil, -1 returns.
|
||||||
|
func (m PutRequest) SignedDataSize() int {
|
||||||
|
r := m.GetR()
|
||||||
|
if r == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
func (m GetRequest) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m GetRequest) ReadSignedData(p []byte) error {
|
||||||
|
addr := m.GetAddress()
|
||||||
|
|
||||||
|
if len(p) < m.SignedDataSize() {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
off := copy(p, addr.CID.Bytes())
|
||||||
|
|
||||||
|
copy(p[off:], addr.ObjectID.Bytes())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns payload size of the request.
|
||||||
|
func (m GetRequest) SignedDataSize() int {
|
||||||
|
return addressSize(m.GetAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
func (m HeadRequest) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m HeadRequest) ReadSignedData(p []byte) error {
|
||||||
|
if len(p) < m.SignedDataSize() {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.GetFullHeaders() {
|
||||||
|
p[0] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
off := 1 + copy(p[1:], m.Address.CID.Bytes())
|
||||||
|
|
||||||
|
copy(p[off:], m.Address.ObjectID.Bytes())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns payload size of the request.
|
||||||
|
func (m HeadRequest) SignedDataSize() int {
|
||||||
|
return addressSize(m.Address) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
func (m DeleteRequest) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m DeleteRequest) ReadSignedData(p []byte) error {
|
||||||
|
if len(p) < m.SignedDataSize() {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
off := copy(p, m.OwnerID.Bytes())
|
||||||
|
|
||||||
|
copy(p[off:], addressBytes(m.Address))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns payload size of the request.
|
||||||
|
func (m DeleteRequest) SignedDataSize() int {
|
||||||
|
return m.OwnerID.Size() + addressSize(m.Address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
func (m GetRangeRequest) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m GetRangeRequest) ReadSignedData(p []byte) error {
|
||||||
|
if len(p) < m.SignedDataSize() {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := (&m.Range).MarshalTo(p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(p[n:], addressBytes(m.GetAddress()))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns payload size of the request.
|
||||||
|
func (m GetRangeRequest) SignedDataSize() int {
|
||||||
|
return (&m.Range).Size() + addressSize(m.GetAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
func (m GetRangeHashRequest) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m GetRangeHashRequest) ReadSignedData(p []byte) error {
|
||||||
|
if len(p) < m.SignedDataSize() {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
var off int
|
||||||
|
|
||||||
|
off += copy(p[off:], addressBytes(m.GetAddress()))
|
||||||
|
|
||||||
|
off += copy(p[off:], rangeSetBytes(m.GetRanges()))
|
||||||
|
|
||||||
|
off += copy(p[off:], m.GetSalt())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns payload size of the request.
|
||||||
|
func (m GetRangeHashRequest) SignedDataSize() int {
|
||||||
|
var sz int
|
||||||
|
|
||||||
|
sz += addressSize(m.GetAddress())
|
||||||
|
|
||||||
|
sz += rangeSetSize(m.GetRanges())
|
||||||
|
|
||||||
|
sz += len(m.GetSalt())
|
||||||
|
|
||||||
|
return sz
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData returns payload bytes of the request.
|
||||||
|
func (m SearchRequest) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
return data, m.ReadSignedData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies payload bytes to passed buffer.
|
||||||
|
//
|
||||||
|
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m SearchRequest) ReadSignedData(p []byte) error {
|
||||||
|
if len(p) < m.SignedDataSize() {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
var off int
|
||||||
|
|
||||||
|
off += copy(p[off:], m.CID().Bytes())
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(p[off:], m.GetQueryVersion())
|
||||||
|
off += 4
|
||||||
|
|
||||||
|
copy(p[off:], m.GetQuery())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns payload size of the request.
|
||||||
|
func (m SearchRequest) SignedDataSize() int {
|
||||||
|
var sz int
|
||||||
|
|
||||||
|
sz += m.CID().Size()
|
||||||
|
|
||||||
|
sz += 4 // uint32 Version
|
||||||
|
|
||||||
|
sz += len(m.GetQuery())
|
||||||
|
|
||||||
|
return sz
|
||||||
|
}
|
||||||
|
|
||||||
|
func rangeSetSize(rs []Range) int {
|
||||||
|
return 4 + len(rs)*16 // two uint64 fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func rangeSetBytes(rs []Range) []byte {
|
||||||
|
data := make([]byte, rangeSetSize(rs))
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(data, uint32(len(rs)))
|
||||||
|
|
||||||
|
off := 4
|
||||||
|
|
||||||
|
for i := range rs {
|
||||||
|
binary.BigEndian.PutUint64(data[off:], rs[i].Offset)
|
||||||
|
off += 8
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint64(data[off:], rs[i].Length)
|
||||||
|
off += 8
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressSize(addr Address) int {
|
||||||
|
return addr.CID.Size() + addr.ObjectID.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressBytes(addr Address) []byte {
|
||||||
|
return append(addr.CID.Bytes(), addr.ObjectID.Bytes()...)
|
||||||
|
}
|
189
object/sign_test.go
Normal file
189
object/sign_test.go
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/service"
|
||||||
|
"github.com/nspcc-dev/neofs-crypto/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSignVerifyRequests(t *testing.T) {
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
type sigType interface {
|
||||||
|
service.SignedDataWithToken
|
||||||
|
service.SignKeyPairAccumulator
|
||||||
|
service.SignKeyPairSource
|
||||||
|
SetToken(*Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []struct {
|
||||||
|
constructor func() sigType
|
||||||
|
payloadCorrupt []func(sigType)
|
||||||
|
}{
|
||||||
|
{ // PutRequest.PutHeader
|
||||||
|
constructor: func() sigType {
|
||||||
|
return MakePutRequestHeader(new(Object))
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
obj := s.(*PutRequest).GetR().(*PutRequest_Header).Header.GetObject()
|
||||||
|
obj.SystemHeader.PayloadLength++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // PutRequest.Chunk
|
||||||
|
constructor: func() sigType {
|
||||||
|
return MakePutRequestChunk(make([]byte, 10))
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
h := s.(*PutRequest).GetR().(*PutRequest_Chunk)
|
||||||
|
h.Chunk[0]++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // GetRequest
|
||||||
|
constructor: func() sigType {
|
||||||
|
return new(GetRequest)
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRequest).Address.CID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRequest).Address.ObjectID[0]++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // HeadRequest
|
||||||
|
constructor: func() sigType {
|
||||||
|
return new(HeadRequest)
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*HeadRequest).Address.CID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*HeadRequest).Address.ObjectID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*HeadRequest).FullHeaders = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // DeleteRequest
|
||||||
|
constructor: func() sigType {
|
||||||
|
return new(DeleteRequest)
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*DeleteRequest).OwnerID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*DeleteRequest).Address.CID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*DeleteRequest).Address.ObjectID[0]++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // GetRangeRequest
|
||||||
|
constructor: func() sigType {
|
||||||
|
return new(GetRangeRequest)
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeRequest).Range.Length++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeRequest).Range.Offset++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeRequest).Address.CID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeRequest).Address.ObjectID[0]++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // GetRangeHashRequest
|
||||||
|
constructor: func() sigType {
|
||||||
|
return &GetRangeHashRequest{
|
||||||
|
Ranges: []Range{{}},
|
||||||
|
Salt: []byte{1, 2, 3},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeHashRequest).Address.CID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeHashRequest).Address.ObjectID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeHashRequest).Salt[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeHashRequest).Ranges[0].Length++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeHashRequest).Ranges[0].Offset++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*GetRangeHashRequest).Ranges = nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ // GetRangeHashRequest
|
||||||
|
constructor: func() sigType {
|
||||||
|
return &SearchRequest{
|
||||||
|
Query: []byte{1, 2, 3},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
payloadCorrupt: []func(sigType){
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*SearchRequest).ContainerID[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*SearchRequest).Query[0]++
|
||||||
|
},
|
||||||
|
func(s sigType) {
|
||||||
|
s.(*SearchRequest).QueryVersion++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
{ // token corruptions
|
||||||
|
v := item.constructor()
|
||||||
|
|
||||||
|
token := new(Token)
|
||||||
|
v.SetToken(token)
|
||||||
|
|
||||||
|
require.NoError(t, service.SignDataWithSessionToken(sk, v))
|
||||||
|
|
||||||
|
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
|
||||||
|
|
||||||
|
token.SetSessionKey(append(token.GetSessionKey(), 1))
|
||||||
|
|
||||||
|
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // payload corruptions
|
||||||
|
for _, corruption := range item.payloadCorrupt {
|
||||||
|
v := item.constructor()
|
||||||
|
|
||||||
|
require.NoError(t, service.SignDataWithSessionToken(sk, v))
|
||||||
|
|
||||||
|
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
|
||||||
|
|
||||||
|
corruption(v)
|
||||||
|
|
||||||
|
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,17 @@ import (
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
"github.com/nspcc-dev/neofs-api-go/refs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenID is type alias of UUID ref.
|
// TokenID is a type alias of UUID ref.
|
||||||
type TokenID = refs.UUID
|
type TokenID = refs.UUID
|
||||||
|
|
||||||
// OwnerID is type alias of OwnerID ref.
|
// OwnerID is a type alias of OwnerID ref.
|
||||||
type OwnerID = refs.OwnerID
|
type OwnerID = refs.OwnerID
|
||||||
|
|
||||||
// Address is type alias of Address ref.
|
// Address is a type alias of Address ref.
|
||||||
type Address = refs.Address
|
type Address = refs.Address
|
||||||
|
|
||||||
|
// AddressContainer is a type alias of refs.AddressContainer.
|
||||||
|
type AddressContainer = refs.AddressContainer
|
||||||
|
|
||||||
|
// OwnerIDContainer is a type alias of refs.OwnerIDContainer.
|
||||||
|
type OwnerIDContainer = refs.OwnerIDContainer
|
||||||
|
|
11
service/epoch.go
Normal file
11
service/epoch.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
// SetEpoch is an Epoch field setter.
|
||||||
|
func (m *ResponseMetaHeader) SetEpoch(v uint64) {
|
||||||
|
m.Epoch = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEpoch is an Epoch field setter.
|
||||||
|
func (m *RequestMetaHeader) SetEpoch(v uint64) {
|
||||||
|
m.Epoch = v
|
||||||
|
}
|
21
service/epoch_test.go
Normal file
21
service/epoch_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSetEpoch(t *testing.T) {
|
||||||
|
v := uint64(5)
|
||||||
|
|
||||||
|
items := []EpochContainer{
|
||||||
|
new(ResponseMetaHeader),
|
||||||
|
new(RequestMetaHeader),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
item.SetEpoch(v)
|
||||||
|
require.Equal(t, v, item.GetEpoch())
|
||||||
|
}
|
||||||
|
}
|
45
service/errors.go
Normal file
45
service/errors.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neofs-api-go/internal"
|
||||||
|
|
||||||
|
// ErrNilToken is returned by functions that expect
|
||||||
|
// a non-nil token argument, but received nil.
|
||||||
|
const ErrNilToken = internal.Error("token is nil")
|
||||||
|
|
||||||
|
// ErrInvalidTTL means that the TTL value does not
|
||||||
|
// satisfy a specific criterion.
|
||||||
|
const ErrInvalidTTL = internal.Error("invalid TTL value")
|
||||||
|
|
||||||
|
// ErrInvalidPublicKeyBytes means that the public key could not be unmarshaled.
|
||||||
|
const ErrInvalidPublicKeyBytes = internal.Error("cannot load public key")
|
||||||
|
|
||||||
|
// ErrCannotFindOwner is raised when signatures empty in GetOwner.
|
||||||
|
const ErrCannotFindOwner = internal.Error("cannot find owner public key")
|
||||||
|
|
||||||
|
// ErrWrongOwner is raised when passed OwnerID
|
||||||
|
// not equal to present PublicKey
|
||||||
|
const ErrWrongOwner = internal.Error("wrong owner")
|
||||||
|
|
||||||
|
// ErrNilSignedDataSource returned by functions that expect a non-nil
|
||||||
|
// SignedDataSource, but received nil.
|
||||||
|
const ErrNilSignedDataSource = internal.Error("signed data source is nil")
|
||||||
|
|
||||||
|
// ErrNilSignatureKeySource is returned by functions that expect a non-nil
|
||||||
|
// SignatureKeySource, but received nil.
|
||||||
|
const ErrNilSignatureKeySource = internal.Error("empty key-signature source")
|
||||||
|
|
||||||
|
// ErrEmptyDataWithSignature is returned by functions that expect
|
||||||
|
// a non-nil DataWithSignature, but received nil.
|
||||||
|
const ErrEmptyDataWithSignature = internal.Error("empty data with signature")
|
||||||
|
|
||||||
|
// ErrNegativeLength is returned by functions that received
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// 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")
|
136
service/meta.go
136
service/meta.go
|
@ -1,141 +1,13 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
// CutMeta returns current value and sets RequestMetaHeader to empty value.
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
func (m *RequestMetaHeader) CutMeta() RequestMetaHeader {
|
||||||
"github.com/pkg/errors"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// MetaHeader contains meta information of request.
|
|
||||||
// It provides methods to get or set meta information meta header.
|
|
||||||
// Also contains methods to reset and restore meta header.
|
|
||||||
// Also contains methods to get or set request protocol version
|
|
||||||
MetaHeader interface {
|
|
||||||
ResetMeta() RequestMetaHeader
|
|
||||||
RestoreMeta(RequestMetaHeader)
|
|
||||||
|
|
||||||
// TTLRequest to verify and update ttl requests.
|
|
||||||
GetTTL() uint32
|
|
||||||
SetTTL(uint32)
|
|
||||||
|
|
||||||
// EpochHeader gives possibility to get or set epoch in RPC Requests.
|
|
||||||
EpochHeader
|
|
||||||
|
|
||||||
// VersionHeader allows get or set version of protocol request
|
|
||||||
VersionHeader
|
|
||||||
|
|
||||||
// RawHeader allows to get and set raw option of request
|
|
||||||
RawHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
// EpochHeader interface gives possibility to get or set epoch in RPC Requests.
|
|
||||||
EpochHeader interface {
|
|
||||||
GetEpoch() uint64
|
|
||||||
SetEpoch(v uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionHeader allows get or set version of protocol request
|
|
||||||
VersionHeader interface {
|
|
||||||
GetVersion() uint32
|
|
||||||
SetVersion(uint32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawHeader is an interface of the container of a boolean Raw value
|
|
||||||
RawHeader interface {
|
|
||||||
GetRaw() bool
|
|
||||||
SetRaw(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TTLCondition is closure, that allows to validate request with ttl.
|
|
||||||
TTLCondition func(ttl uint32) error
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ZeroTTL is empty ttl, should produce ErrZeroTTL.
|
|
||||||
ZeroTTL = iota
|
|
||||||
|
|
||||||
// NonForwardingTTL is a ttl that allows direct connections only.
|
|
||||||
NonForwardingTTL
|
|
||||||
|
|
||||||
// SingleForwardingTTL is a ttl that allows connections through another node.
|
|
||||||
SingleForwardingTTL
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrZeroTTL is raised when zero ttl is passed.
|
|
||||||
ErrZeroTTL = internal.Error("zero ttl")
|
|
||||||
|
|
||||||
// ErrIncorrectTTL is raised when NonForwardingTTL is passed and NodeRole != InnerRingNode.
|
|
||||||
ErrIncorrectTTL = internal.Error("incorrect ttl")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetVersion sets protocol version to ResponseMetaHeader.
|
|
||||||
func (m *ResponseMetaHeader) SetVersion(v uint32) { m.Version = v }
|
|
||||||
|
|
||||||
// SetEpoch sets Epoch to ResponseMetaHeader.
|
|
||||||
func (m *ResponseMetaHeader) SetEpoch(v uint64) { m.Epoch = v }
|
|
||||||
|
|
||||||
// SetVersion sets protocol version to RequestMetaHeader.
|
|
||||||
func (m *RequestMetaHeader) SetVersion(v uint32) { m.Version = v }
|
|
||||||
|
|
||||||
// SetTTL sets TTL to RequestMetaHeader.
|
|
||||||
func (m *RequestMetaHeader) SetTTL(v uint32) { m.TTL = v }
|
|
||||||
|
|
||||||
// SetEpoch sets Epoch to RequestMetaHeader.
|
|
||||||
func (m *RequestMetaHeader) SetEpoch(v uint64) { m.Epoch = v }
|
|
||||||
|
|
||||||
// SetRaw is a Raw field setter.
|
|
||||||
func (m *RequestMetaHeader) SetRaw(raw bool) {
|
|
||||||
m.Raw = raw
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetMeta returns current value and sets RequestMetaHeader to empty value.
|
|
||||||
func (m *RequestMetaHeader) ResetMeta() RequestMetaHeader {
|
|
||||||
cp := *m
|
cp := *m
|
||||||
m.Reset()
|
m.Reset()
|
||||||
return cp
|
return cp
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreMeta sets current RequestMetaHeader to passed value.
|
// RestoreMeta sets current RequestMetaHeader to passed value.
|
||||||
func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v }
|
func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) {
|
||||||
|
*m = v
|
||||||
// IRNonForwarding condition that allows NonForwardingTTL only for IR
|
|
||||||
func IRNonForwarding(role NodeRole) TTLCondition {
|
|
||||||
return func(ttl uint32) error {
|
|
||||||
if ttl == NonForwardingTTL && role != InnerRingNode {
|
|
||||||
return ErrIncorrectTTL
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessRequestTTL validates and update ttl requests.
|
|
||||||
func ProcessRequestTTL(req MetaHeader, cond ...TTLCondition) error {
|
|
||||||
ttl := req.GetTTL()
|
|
||||||
|
|
||||||
if ttl == ZeroTTL {
|
|
||||||
return status.New(codes.InvalidArgument, ErrZeroTTL.Error()).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range cond {
|
|
||||||
if cond[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check specific condition:
|
|
||||||
if err := cond[i](ttl); err != nil {
|
|
||||||
if st, ok := status.FromError(errors.Cause(err)); ok {
|
|
||||||
return st.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return status.New(codes.InvalidArgument, err.Error()).Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req.SetTTL(ttl - 1)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,112 +3,23 @@ package service
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockedRequest struct {
|
func TestCutRestoreMeta(t *testing.T) {
|
||||||
msg string
|
items := []func() SeizedMetaHeaderContainer{
|
||||||
name string
|
func() SeizedMetaHeaderContainer {
|
||||||
code codes.Code
|
|
||||||
handler TTLCondition
|
|
||||||
RequestMetaHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetaRequest(t *testing.T) {
|
|
||||||
tests := []mockedRequest{
|
|
||||||
{
|
|
||||||
name: "direct to ir node",
|
|
||||||
handler: IRNonForwarding(InnerRingNode),
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: codes.InvalidArgument,
|
|
||||||
msg: ErrIncorrectTTL.Error(),
|
|
||||||
name: "direct to storage node",
|
|
||||||
handler: IRNonForwarding(StorageNode),
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: ErrZeroTTL.Error(),
|
|
||||||
code: codes.InvalidArgument,
|
|
||||||
name: "zero ttl",
|
|
||||||
handler: IRNonForwarding(StorageNode),
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: ZeroTTL},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default to ir node",
|
|
||||||
handler: IRNonForwarding(InnerRingNode),
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default to storage node",
|
|
||||||
handler: IRNonForwarding(StorageNode),
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: "not found",
|
|
||||||
code: codes.NotFound,
|
|
||||||
name: "custom status error",
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
|
||||||
handler: func(_ uint32) error { return status.Error(codes.NotFound, "not found") },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: "not found",
|
|
||||||
code: codes.NotFound,
|
|
||||||
name: "custom wrapped status error",
|
|
||||||
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
|
||||||
handler: func(_ uint32) error {
|
|
||||||
err := status.Error(codes.NotFound, "not found")
|
|
||||||
err = errors.Wrap(err, "some error context")
|
|
||||||
err = errors.Wrap(err, "another error context")
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
before := tt.GetTTL()
|
|
||||||
err := ProcessRequestTTL(&tt, tt.handler)
|
|
||||||
if tt.msg != "" {
|
|
||||||
require.Errorf(t, err, tt.msg)
|
|
||||||
|
|
||||||
state, ok := status.FromError(err)
|
|
||||||
require.True(t, ok)
|
|
||||||
require.Equal(t, tt.code, state.Code())
|
|
||||||
require.Equal(t, tt.msg, state.Message())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEqualf(t, before, tt.GetTTL(), "ttl should be changed: %d vs %d", before, tt.GetTTL())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestMetaHeader_SetEpoch(t *testing.T) {
|
|
||||||
m := new(ResponseMetaHeader)
|
|
||||||
epoch := uint64(3)
|
|
||||||
m.SetEpoch(epoch)
|
|
||||||
require.Equal(t, epoch, m.GetEpoch())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestMetaHeader_SetVersion(t *testing.T) {
|
|
||||||
m := new(ResponseMetaHeader)
|
|
||||||
version := uint32(3)
|
|
||||||
m.SetVersion(version)
|
|
||||||
require.Equal(t, version, m.GetVersion())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestMetaHeader_SetRaw(t *testing.T) {
|
|
||||||
m := new(RequestMetaHeader)
|
m := new(RequestMetaHeader)
|
||||||
|
m.SetEpoch(1)
|
||||||
m.SetRaw(true)
|
return m
|
||||||
require.True(t, m.GetRaw())
|
},
|
||||||
|
}
|
||||||
m.SetRaw(false)
|
|
||||||
require.False(t, m.GetRaw())
|
for _, item := range items {
|
||||||
|
v1 := item()
|
||||||
|
m1 := v1.CutMeta()
|
||||||
|
v1.RestoreMeta(m1)
|
||||||
|
|
||||||
|
require.Equal(t, item(), v1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
6
service/raw.go
Normal file
6
service/raw.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
// SetRaw is a Raw field setter.
|
||||||
|
func (m *RequestMetaHeader) SetRaw(raw bool) {
|
||||||
|
m.Raw = raw
|
||||||
|
}
|
24
service/raw_test.go
Normal file
24
service/raw_test.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSetRaw(t *testing.T) {
|
||||||
|
items := []RawContainer{
|
||||||
|
new(RequestMetaHeader),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
// init with false
|
||||||
|
item.SetRaw(false)
|
||||||
|
|
||||||
|
item.SetRaw(true)
|
||||||
|
require.True(t, item.GetRaw())
|
||||||
|
|
||||||
|
item.SetRaw(false)
|
||||||
|
require.False(t, item.GetRaw())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
// NodeRole to identify in Bootstrap service.
|
|
||||||
type NodeRole int32
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ NodeRole = iota
|
_ NodeRole = iota
|
||||||
// InnerRingNode that work like IR node.
|
// InnerRingNode that work like IR node.
|
||||||
|
|
222
service/sign.go
Normal file
222
service/sign.go
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keySign struct {
|
||||||
|
key *ecdsa.PublicKey
|
||||||
|
sign []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignature is a sign field getter.
|
||||||
|
func (s keySign) GetSignature() []byte {
|
||||||
|
return s.sign
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey is a key field getter,
|
||||||
|
func (s keySign) GetPublicKey() *ecdsa.PublicKey {
|
||||||
|
return s.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unites passed key with signature and returns SignKeyPair interface.
|
||||||
|
func newSignatureKeyPair(key *ecdsa.PublicKey, sign []byte) SignKeyPair {
|
||||||
|
return &keySign{
|
||||||
|
key: key,
|
||||||
|
sign: sign,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns data from DataSignatureAccumulator for signature creation/verification.
|
||||||
|
//
|
||||||
|
// If passed DataSignatureAccumulator provides a SignedDataReader interface, data for signature is obtained
|
||||||
|
// using this interface for optimization. In this case, it is understood that reading into the slice D
|
||||||
|
// that the method DataForSignature returns does not change D.
|
||||||
|
//
|
||||||
|
// If returned length of data is negative, ErrNegativeLength returns.
|
||||||
|
func dataForSignature(src SignedDataSource) ([]byte, error) {
|
||||||
|
if src == nil {
|
||||||
|
return nil, ErrNilSignedDataSource
|
||||||
|
}
|
||||||
|
|
||||||
|
r, ok := src.(SignedDataReader)
|
||||||
|
if !ok {
|
||||||
|
return src.SignedData()
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytesPool.Get().([]byte)
|
||||||
|
|
||||||
|
if size := r.SignedDataSize(); size < 0 {
|
||||||
|
return nil, ErrNegativeLength
|
||||||
|
} else if size <= cap(buf) {
|
||||||
|
buf = buf[:size]
|
||||||
|
} else {
|
||||||
|
buf = make([]byte, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := r.ReadSignedData(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf[:n], nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSignature returns the signature of data obtained using the private key.
|
||||||
|
//
|
||||||
|
// If passed data container is nil, ErrNilSignedDataSource returns.
|
||||||
|
// If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
|
||||||
|
// If the data container or the signature function returns an error, it is returned directly.
|
||||||
|
func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) {
|
||||||
|
if key == nil {
|
||||||
|
return nil, crypto.ErrEmptyPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := dataForSignature(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer bytesPool.Put(data)
|
||||||
|
|
||||||
|
return crypto.Sign(key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSignatureWithKey calculates the data signature and adds it to accumulator with public key.
|
||||||
|
//
|
||||||
|
// Any change of data provoke signature breakdown.
|
||||||
|
//
|
||||||
|
// Returns signing errors only.
|
||||||
|
func AddSignatureWithKey(key *ecdsa.PrivateKey, v DataWithSignKeyAccumulator) error {
|
||||||
|
sign, err := DataSignature(key, v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.AddSignKey(sign, &key.PublicKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks passed key-signature pairs for data from the passed container.
|
||||||
|
//
|
||||||
|
// If passed key-signatures pair set is empty, nil returns immediately.
|
||||||
|
func verifySignatures(src SignedDataSource, items ...SignKeyPair) error {
|
||||||
|
if len(items) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := dataForSignature(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer bytesPool.Put(data)
|
||||||
|
|
||||||
|
for _, signKey := range items {
|
||||||
|
if err := crypto.Verify(
|
||||||
|
signKey.GetPublicKey(),
|
||||||
|
data,
|
||||||
|
signKey.GetSignature(),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySignatures checks passed key-signature pairs for data from the passed container.
|
||||||
|
//
|
||||||
|
// If passed data source is nil, ErrNilSignedDataSource returns.
|
||||||
|
// If check data is not ready, corresponding error returns.
|
||||||
|
// If at least one of the pairs is invalid, an error returns.
|
||||||
|
func VerifySignatures(src SignedDataSource, items ...SignKeyPair) error {
|
||||||
|
return verifySignatures(src, items...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAccumulatedSignatures checks if accumulated key-signature pairs are valid.
|
||||||
|
//
|
||||||
|
// Behaves like VerifySignatures.
|
||||||
|
// If passed key-signature source is empty, ErrNilSignatureKeySource returns.
|
||||||
|
func VerifyAccumulatedSignatures(src DataWithSignKeySource) error {
|
||||||
|
if src == nil {
|
||||||
|
return ErrNilSignatureKeySource
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifySignatures(src, src.GetSignKeyPairs()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifySignatureWithKey checks data signature from the passed container with passed key.
|
||||||
|
//
|
||||||
|
// If passed data with signature is nil, ErrEmptyDataWithSignature returns.
|
||||||
|
// If passed key is nil, crypto.ErrEmptyPublicKey returns.
|
||||||
|
// A non-nil error returns if and only if the signature does not pass verification.
|
||||||
|
func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error {
|
||||||
|
if src == nil {
|
||||||
|
return ErrEmptyDataWithSignature
|
||||||
|
} else if key == nil {
|
||||||
|
return crypto.ErrEmptyPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifySignatures(
|
||||||
|
src,
|
||||||
|
newSignatureKeyPair(
|
||||||
|
key,
|
||||||
|
src.GetSignature(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDataWithSessionToken calculates data with token signature and adds it to accumulator.
|
||||||
|
//
|
||||||
|
// Any change of data or session token info 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 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 AddSignatureWithKey(key, &signAccumWithToken{
|
||||||
|
SignedDataSource: src,
|
||||||
|
SignKeyPairAccumulator: src,
|
||||||
|
|
||||||
|
token: src.GetSessionToken(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAccumulatedSignaturesWithToken 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 src == nil {
|
||||||
|
return ErrNilSignatureKeySourceWithToken
|
||||||
|
} else if r, ok := src.(SignedDataReader); ok {
|
||||||
|
return VerifyAccumulatedSignatures(&signDataReaderWithToken{
|
||||||
|
SignedDataSource: src,
|
||||||
|
SignKeyPairSource: src,
|
||||||
|
|
||||||
|
rdr: r,
|
||||||
|
token: src.GetSessionToken(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return VerifyAccumulatedSignatures(&signAccumWithToken{
|
||||||
|
SignedDataSource: src,
|
||||||
|
SignKeyPairSource: src,
|
||||||
|
|
||||||
|
token: src.GetSessionToken(),
|
||||||
|
})
|
||||||
|
}
|
326
service/sign_test.go
Normal file
326
service/sign_test.go
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||||
|
"github.com/nspcc-dev/neofs-crypto/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testSignedDataSrc struct {
|
||||||
|
err error
|
||||||
|
data []byte
|
||||||
|
sig []byte
|
||||||
|
key *ecdsa.PublicKey
|
||||||
|
token SessionToken
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSignedDataReader struct {
|
||||||
|
*testSignedDataSrc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testSignedDataSrc) GetSignature() []byte {
|
||||||
|
return s.sig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair {
|
||||||
|
return []SignKeyPair{
|
||||||
|
newSignatureKeyPair(s.key, s.sig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testSignedDataSrc) SignedData() ([]byte, error) {
|
||||||
|
return s.data, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
||||||
|
s.key = key
|
||||||
|
s.sig = sig
|
||||||
|
}
|
||||||
|
|
||||||
|
func testData(t *testing.T, sz int) []byte {
|
||||||
|
d := make([]byte, sz)
|
||||||
|
_, err := rand.Read(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testSignedDataSrc) GetSessionToken() SessionToken {
|
||||||
|
return s.token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testSignedDataReader) SignedDataSize() int {
|
||||||
|
return len(s.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s testSignedDataReader) ReadSignedData(buf []byte) (int, error) {
|
||||||
|
if s.err != nil {
|
||||||
|
return 0, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if len(buf) < len(s.data) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return copy(buf, s.data), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDataSignature(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// nil private key
|
||||||
|
_, err = DataSignature(nil, nil)
|
||||||
|
require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error())
|
||||||
|
|
||||||
|
// create test private key
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
// nil private key
|
||||||
|
_, err = DataSignature(sk, nil)
|
||||||
|
require.EqualError(t, err, ErrNilSignedDataSource.Error())
|
||||||
|
|
||||||
|
t.Run("common signed data source", func(t *testing.T) {
|
||||||
|
// create test data source
|
||||||
|
src := &testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
// create custom error for data source
|
||||||
|
src.err = errors.New("test error for data source")
|
||||||
|
|
||||||
|
_, err = DataSignature(sk, src)
|
||||||
|
require.EqualError(t, err, src.err.Error())
|
||||||
|
|
||||||
|
// reset error to nil
|
||||||
|
src.err = nil
|
||||||
|
|
||||||
|
// calculate data signature
|
||||||
|
sig, err := DataSignature(sk, src)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ascertain that the signature passes verification
|
||||||
|
require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("signed data reader", func(t *testing.T) {
|
||||||
|
// create test signed data reader
|
||||||
|
src := &testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
// create custom error for signed data reader
|
||||||
|
src.err = errors.New("test error for signed data reader")
|
||||||
|
|
||||||
|
sig, err := DataSignature(sk, src)
|
||||||
|
require.EqualError(t, err, src.err.Error())
|
||||||
|
|
||||||
|
// reset error to nil
|
||||||
|
src.err = nil
|
||||||
|
|
||||||
|
// calculate data signature
|
||||||
|
sig, err = DataSignature(sk, src)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ascertain that the signature passes verification
|
||||||
|
require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddSignatureWithKey(t *testing.T) {
|
||||||
|
require.NoError(t,
|
||||||
|
AddSignatureWithKey(
|
||||||
|
test.DecodeKey(0),
|
||||||
|
&testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySignatures(t *testing.T) {
|
||||||
|
// empty signatures
|
||||||
|
require.NoError(t, VerifySignatures(nil))
|
||||||
|
|
||||||
|
// create test signature source
|
||||||
|
src := &testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
// create private key for test
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
// calculate a signature of the data
|
||||||
|
sig, err := crypto.Sign(sk, src.data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ascertain that verification is passed
|
||||||
|
require.NoError(t,
|
||||||
|
VerifySignatures(
|
||||||
|
src,
|
||||||
|
newSignatureKeyPair(&sk.PublicKey, sig),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// break the signature
|
||||||
|
sig[0]++
|
||||||
|
|
||||||
|
require.Error(t,
|
||||||
|
VerifySignatures(
|
||||||
|
src,
|
||||||
|
newSignatureKeyPair(&sk.PublicKey, sig),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// restore the signature
|
||||||
|
sig[0]--
|
||||||
|
|
||||||
|
// empty data source
|
||||||
|
require.EqualError(t,
|
||||||
|
VerifySignatures(nil, nil),
|
||||||
|
ErrNilSignedDataSource.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyAccumulatedSignatures(t *testing.T) {
|
||||||
|
// nil signature source
|
||||||
|
require.EqualError(t,
|
||||||
|
VerifyAccumulatedSignatures(nil),
|
||||||
|
ErrNilSignatureKeySource.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// create test private key
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
// create signature source
|
||||||
|
src := &testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
key: &sk.PublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// calculate a signature
|
||||||
|
src.sig, err = crypto.Sign(sk, src.data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ascertain that verification is passed
|
||||||
|
require.NoError(t, VerifyAccumulatedSignatures(src))
|
||||||
|
|
||||||
|
// break the signature
|
||||||
|
src.sig[0]++
|
||||||
|
|
||||||
|
// ascertain that verification is failed
|
||||||
|
require.Error(t, VerifyAccumulatedSignatures(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySignatureWithKey(t *testing.T) {
|
||||||
|
// nil signature source
|
||||||
|
require.EqualError(t,
|
||||||
|
VerifySignatureWithKey(nil, nil),
|
||||||
|
ErrEmptyDataWithSignature.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// create test signature source
|
||||||
|
src := &testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
// nil public key
|
||||||
|
require.EqualError(t,
|
||||||
|
VerifySignatureWithKey(nil, src),
|
||||||
|
crypto.ErrEmptyPublicKey.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// create test private key
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// calculate a signature
|
||||||
|
src.sig, err = crypto.Sign(sk, src.data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// ascertain that verification is passed
|
||||||
|
require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
||||||
|
|
||||||
|
// break the signature
|
||||||
|
src.sig[0]++
|
||||||
|
|
||||||
|
// ascertain that verification is failed
|
||||||
|
require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignVerifyDataWithSessionToken(t *testing.T) {
|
||||||
|
// sign with empty DataWithTokenSignAccumulator
|
||||||
|
require.EqualError(t,
|
||||||
|
SignDataWithSessionToken(nil, nil),
|
||||||
|
ErrNilDataWithTokenSignAccumulator.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// verify with empty DataWithTokenSignSource
|
||||||
|
require.EqualError(t,
|
||||||
|
VerifyAccumulatedSignaturesWithToken(nil),
|
||||||
|
ErrNilSignatureKeySourceWithToken.Error(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// create test session token
|
||||||
|
var (
|
||||||
|
token = new(Token)
|
||||||
|
initVerb = Token_Info_Verb(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
token.SetVerb(initVerb)
|
||||||
|
|
||||||
|
// create test data with token
|
||||||
|
src := &testSignedDataSrc{
|
||||||
|
data: testData(t, 10),
|
||||||
|
token: token,
|
||||||
|
}
|
||||||
|
|
||||||
|
// create test private key
|
||||||
|
sk := test.DecodeKey(0)
|
||||||
|
|
||||||
|
// sign with private key
|
||||||
|
require.NoError(t, SignDataWithSessionToken(sk, src))
|
||||||
|
|
||||||
|
// ascertain that verification is passed
|
||||||
|
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src))
|
||||||
|
|
||||||
|
// break the data
|
||||||
|
src.data[0]++
|
||||||
|
|
||||||
|
// ascertain that verification is failed
|
||||||
|
require.Error(t, VerifyAccumulatedSignaturesWithToken(src))
|
||||||
|
|
||||||
|
// restore the data
|
||||||
|
src.data[0]--
|
||||||
|
|
||||||
|
// break the token
|
||||||
|
token.SetVerb(initVerb + 1)
|
||||||
|
|
||||||
|
// ascertain that verification is failed
|
||||||
|
require.Error(t, VerifyAccumulatedSignaturesWithToken(src))
|
||||||
|
|
||||||
|
// restore the token
|
||||||
|
token.SetVerb(initVerb)
|
||||||
|
|
||||||
|
// ascertain that verification is passed
|
||||||
|
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src))
|
||||||
|
|
||||||
|
// wrap to data reader
|
||||||
|
rdr := &testSignedDataReader{
|
||||||
|
testSignedDataSrc: src,
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign with private key
|
||||||
|
require.NoError(t, SignDataWithSessionToken(sk, rdr))
|
||||||
|
|
||||||
|
// ascertain that verification is passed
|
||||||
|
require.NoError(t, VerifyAccumulatedSignaturesWithToken(rdr))
|
||||||
|
}
|
262
service/token.go
262
service/token.go
|
@ -3,69 +3,39 @@ package service
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
"github.com/nspcc-dev/neofs-api-go/refs"
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// VerbContainer is an interface of the container of a token verb value.
|
type signAccumWithToken struct {
|
||||||
type VerbContainer interface {
|
SignedDataSource
|
||||||
GetVerb() Token_Info_Verb
|
SignKeyPairAccumulator
|
||||||
SetVerb(Token_Info_Verb)
|
SignKeyPairSource
|
||||||
|
|
||||||
|
token SessionToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenIDContainer is an interface of the container of a token ID value.
|
type signDataReaderWithToken struct {
|
||||||
type TokenIDContainer interface {
|
SignedDataSource
|
||||||
GetID() TokenID
|
SignKeyPairAccumulator
|
||||||
SetID(TokenID)
|
SignKeyPairSource
|
||||||
|
|
||||||
|
rdr SignedDataReader
|
||||||
|
|
||||||
|
token SessionToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreationEpochContainer is an interface of the container of a creation epoch number.
|
const verbSize = 4
|
||||||
type CreationEpochContainer interface {
|
|
||||||
CreationEpoch() uint64
|
|
||||||
SetCreationEpoch(uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpirationEpochContainer is an interface of the container of an expiration epoch number.
|
const fixedTokenDataSize = 0 +
|
||||||
type ExpirationEpochContainer interface {
|
refs.UUIDSize +
|
||||||
ExpirationEpoch() uint64
|
refs.OwnerIDSize +
|
||||||
SetExpirationEpoch(uint64)
|
verbSize +
|
||||||
}
|
refs.UUIDSize +
|
||||||
|
refs.CIDSize +
|
||||||
// SessionKeyContainer is an interface of the container of session key bytes.
|
8 +
|
||||||
type SessionKeyContainer interface {
|
8
|
||||||
GetSessionKey() []byte
|
|
||||||
SetSessionKey([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignatureContainer is an interface of the container of signature bytes.
|
|
||||||
type SignatureContainer interface {
|
|
||||||
GetSignature() []byte
|
|
||||||
SetSignature([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionTokenInfo is an interface that determines the information scope of session token.
|
|
||||||
type SessionTokenInfo interface {
|
|
||||||
TokenIDContainer
|
|
||||||
refs.OwnerIDContainer
|
|
||||||
VerbContainer
|
|
||||||
refs.AddressContainer
|
|
||||||
CreationEpochContainer
|
|
||||||
ExpirationEpochContainer
|
|
||||||
SessionKeyContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionToken is an interface of token information and signature pair.
|
|
||||||
type SessionToken interface {
|
|
||||||
SessionTokenInfo
|
|
||||||
SignatureContainer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrEmptyToken is raised when passed Token is nil.
|
|
||||||
const ErrEmptyToken = internal.Error("token is empty")
|
|
||||||
|
|
||||||
var _ SessionToken = (*Token)(nil)
|
|
||||||
|
|
||||||
var tokenEndianness = binary.BigEndian
|
var tokenEndianness = binary.BigEndian
|
||||||
|
|
||||||
|
@ -134,88 +104,132 @@ func (m *Token) SetSignature(sig []byte) {
|
||||||
m.Signature = sig
|
m.Signature = sig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns byte slice that is used for creation/verification of the token signature.
|
// Size returns the size of a binary representation of the verb.
|
||||||
func verificationTokenData(token SessionToken) []byte {
|
func (x Token_Info_Verb) Size() int {
|
||||||
var sz int
|
return verbSize
|
||||||
|
}
|
||||||
id := token.GetID()
|
|
||||||
sz += id.Size()
|
|
||||||
|
|
||||||
ownerID := token.GetOwnerID()
|
|
||||||
sz += ownerID.Size()
|
|
||||||
|
|
||||||
verb := uint32(token.GetVerb())
|
|
||||||
sz += 4
|
|
||||||
|
|
||||||
addr := token.GetAddress()
|
|
||||||
sz += addr.CID.Size() + addr.ObjectID.Size()
|
|
||||||
|
|
||||||
cEpoch := token.CreationEpoch()
|
|
||||||
sz += 8
|
|
||||||
|
|
||||||
fEpoch := token.ExpirationEpoch()
|
|
||||||
sz += 8
|
|
||||||
|
|
||||||
key := token.GetSessionKey()
|
|
||||||
sz += len(key)
|
|
||||||
|
|
||||||
data := make([]byte, sz)
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
tokenEndianness.PutUint32(data, verb)
|
|
||||||
off += 4
|
|
||||||
|
|
||||||
tokenEndianness.PutUint64(data[off:], cEpoch)
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
tokenEndianness.PutUint64(data[off:], fEpoch)
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
off += copy(data[off:], id.Bytes())
|
|
||||||
off += copy(data[off:], ownerID.Bytes())
|
|
||||||
off += copy(data[off:], addr.CID.Bytes())
|
|
||||||
off += copy(data[off:], addr.ObjectID.Bytes())
|
|
||||||
off += copy(data[off:], key)
|
|
||||||
|
|
||||||
|
// Bytes returns a binary representation of the verb.
|
||||||
|
func (x Token_Info_Verb) Bytes() []byte {
|
||||||
|
data := make([]byte, verbSize)
|
||||||
|
tokenEndianness.PutUint32(data, uint32(x))
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignToken calculates and stores the signature of token information.
|
// AddSignKey calls a Signature field setter with passed signature.
|
||||||
//
|
func (m *Token) AddSignKey(sig []byte, _ *ecdsa.PublicKey) {
|
||||||
// If passed token is nil, ErrEmptyToken returns.
|
m.SetSignature(sig)
|
||||||
// If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
|
|
||||||
func SignToken(token SessionToken, key *ecdsa.PrivateKey) error {
|
|
||||||
if token == nil {
|
|
||||||
return ErrEmptyToken
|
|
||||||
} else if key == nil {
|
|
||||||
return crypto.ErrEmptyPrivateKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := crypto.Sign(key, verificationTokenData(token))
|
// SignedData returns token information in a binary representation.
|
||||||
|
func (m *Token) SignedData() ([]byte, error) {
|
||||||
|
data := make([]byte, m.SignedDataSize())
|
||||||
|
|
||||||
|
copyTokenSignedData(data, m)
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSignedData copies a binary representation of the token information to passed buffer.
|
||||||
|
//
|
||||||
|
// If buffer length is less than required, io.ErrUnexpectedEOF returns.
|
||||||
|
func (m *Token_Info) ReadSignedData(p []byte) (int, error) {
|
||||||
|
sz := m.SignedDataSize()
|
||||||
|
if len(p) < sz {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTokenSignedData(p, m)
|
||||||
|
|
||||||
|
return sz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSize returns the length of signed token information slice.
|
||||||
|
func (m *Token_Info) SignedDataSize() int {
|
||||||
|
return tokenInfoSize(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenInfoSize(v SessionKeySource) int {
|
||||||
|
if v == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return fixedTokenDataSize + len(v.GetSessionKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 SessionTokenInfo, buffer remains unchanged.
|
||||||
|
func copyTokenSignedData(buf []byte, token SessionTokenInfo) {
|
||||||
|
if token == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var off int
|
||||||
|
|
||||||
|
off += copy(buf[off:], token.GetID().Bytes())
|
||||||
|
|
||||||
|
off += copy(buf[off:], token.GetOwnerID().Bytes())
|
||||||
|
|
||||||
|
off += copy(buf[off:], token.GetVerb().Bytes())
|
||||||
|
|
||||||
|
addr := token.GetAddress()
|
||||||
|
off += copy(buf[off:], addr.CID.Bytes())
|
||||||
|
off += copy(buf[off:], addr.ObjectID.Bytes())
|
||||||
|
|
||||||
|
tokenEndianness.PutUint64(buf[off:], token.CreationEpoch())
|
||||||
|
off += 8
|
||||||
|
|
||||||
|
tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch())
|
||||||
|
off += 8
|
||||||
|
|
||||||
|
copy(buf[off:], token.GetSessionKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedData concatenates signed data with session token information. Returns concatenation result.
|
||||||
|
//
|
||||||
|
// Token bytes are added if and only if token is not nil.
|
||||||
|
func (s signAccumWithToken) SignedData() ([]byte, error) {
|
||||||
|
data, err := s.SignedDataSource.SignedData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token.SetSignature(sig)
|
tokenData := make([]byte, tokenInfoSize(s.token))
|
||||||
|
|
||||||
return nil
|
copyTokenSignedData(tokenData, s.token)
|
||||||
|
|
||||||
|
return append(data, tokenData...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyTokenSignature checks if token was signed correctly.
|
func (s signDataReaderWithToken) SignedDataSize() int {
|
||||||
//
|
sz := s.rdr.SignedDataSize()
|
||||||
// If passed token is nil, ErrEmptyToken returns.
|
if sz < 0 {
|
||||||
// If passed public key is nil, crypto.ErrEmptyPublicKey returns.
|
return -1
|
||||||
func VerifyTokenSignature(token SessionToken, key *ecdsa.PublicKey) error {
|
|
||||||
if token == nil {
|
|
||||||
return ErrEmptyToken
|
|
||||||
} else if key == nil {
|
|
||||||
return crypto.ErrEmptyPublicKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return crypto.Verify(
|
sz += tokenInfoSize(s.token)
|
||||||
key,
|
|
||||||
verificationTokenData(token),
|
return sz
|
||||||
token.GetSignature(),
|
}
|
||||||
)
|
|
||||||
|
func (s signDataReaderWithToken) ReadSignedData(p []byte) (int, error) {
|
||||||
|
dataSize := s.rdr.SignedDataSize()
|
||||||
|
if dataSize < 0 {
|
||||||
|
return 0, ErrNegativeLength
|
||||||
|
}
|
||||||
|
|
||||||
|
sumSize := dataSize + tokenInfoSize(s.token)
|
||||||
|
|
||||||
|
if len(p) < sumSize {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := s.rdr.ReadSignedData(p); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copyTokenSignedData(p[dataSize:], s.token)
|
||||||
|
|
||||||
|
return sumSize, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
"github.com/nspcc-dev/neofs-api-go/refs"
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
"github.com/nspcc-dev/neofs-crypto/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -90,29 +89,7 @@ func TestTokenGettersSetters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignToken(t *testing.T) {
|
func TestSignToken(t *testing.T) {
|
||||||
// nil token
|
token := new(Token)
|
||||||
require.EqualError(t,
|
|
||||||
SignToken(nil, nil),
|
|
||||||
ErrEmptyToken.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifyTokenSignature(nil, nil),
|
|
||||||
ErrEmptyToken.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
var token SessionToken = new(Token)
|
|
||||||
|
|
||||||
// nil key
|
|
||||||
require.EqualError(t,
|
|
||||||
SignToken(token, nil),
|
|
||||||
crypto.ErrEmptyPrivateKey.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifyTokenSignature(token, nil),
|
|
||||||
crypto.ErrEmptyPublicKey.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// create private key for signing
|
// create private key for signing
|
||||||
sk := test.DecodeKey(0)
|
sk := test.DecodeKey(0)
|
||||||
|
@ -150,8 +127,8 @@ func TestSignToken(t *testing.T) {
|
||||||
token.SetSessionKey(sessionKey)
|
token.SetSessionKey(sessionKey)
|
||||||
|
|
||||||
// sign and verify token
|
// sign and verify token
|
||||||
require.NoError(t, SignToken(token, sk))
|
require.NoError(t, AddSignatureWithKey(sk, token))
|
||||||
require.NoError(t, VerifyTokenSignature(token, pk))
|
require.NoError(t, VerifySignatureWithKey(pk, token))
|
||||||
|
|
||||||
items := []struct {
|
items := []struct {
|
||||||
corrupt func()
|
corrupt func()
|
||||||
|
@ -235,8 +212,8 @@ func TestSignToken(t *testing.T) {
|
||||||
|
|
||||||
for _, v := range items {
|
for _, v := range items {
|
||||||
v.corrupt()
|
v.corrupt()
|
||||||
require.Error(t, VerifyTokenSignature(token, pk))
|
require.Error(t, VerifySignatureWithKey(pk, token))
|
||||||
v.restore()
|
v.restore()
|
||||||
require.NoError(t, VerifyTokenSignature(token, pk))
|
require.NoError(t, VerifySignatureWithKey(pk, token))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
63
service/ttl.go
Normal file
63
service/ttl.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TTL constants.
|
||||||
|
const (
|
||||||
|
// ZeroTTL is an upper bound of invalid TTL values.
|
||||||
|
ZeroTTL = iota
|
||||||
|
|
||||||
|
// NonForwardingTTL is a TTL value that does not imply a request forwarding.
|
||||||
|
NonForwardingTTL
|
||||||
|
|
||||||
|
// SingleForwardingTTL is a TTL value that imply potential forwarding with NonForwardingTTL.
|
||||||
|
SingleForwardingTTL
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetTTL is a TTL field setter.
|
||||||
|
func (m *RequestMetaHeader) SetTTL(v uint32) {
|
||||||
|
m.TTL = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRNonForwarding condition that allows NonForwardingTTL only for IR.
|
||||||
|
func IRNonForwarding(role NodeRole) TTLCondition {
|
||||||
|
return func(ttl uint32) error {
|
||||||
|
if ttl == NonForwardingTTL && role != InnerRingNode {
|
||||||
|
return ErrInvalidTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessRequestTTL validates and updates requests with TTL.
|
||||||
|
func ProcessRequestTTL(req TTLContainer, cond ...TTLCondition) error {
|
||||||
|
ttl := req.GetTTL()
|
||||||
|
|
||||||
|
if ttl == ZeroTTL {
|
||||||
|
return status.New(codes.InvalidArgument, ErrInvalidTTL.Error()).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range cond {
|
||||||
|
if cond[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check specific condition:
|
||||||
|
if err := cond[i](ttl); err != nil {
|
||||||
|
if st, ok := status.FromError(errors.Cause(err)); ok {
|
||||||
|
return st.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
return status.New(codes.InvalidArgument, err.Error()).Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetTTL(ttl - 1)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
99
service/ttl_test.go
Normal file
99
service/ttl_test.go
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockedRequest struct {
|
||||||
|
msg string
|
||||||
|
name string
|
||||||
|
code codes.Code
|
||||||
|
handler TTLCondition
|
||||||
|
RequestMetaHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetaRequest(t *testing.T) {
|
||||||
|
tests := []mockedRequest{
|
||||||
|
{
|
||||||
|
name: "direct to ir node",
|
||||||
|
handler: IRNonForwarding(InnerRingNode),
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: codes.InvalidArgument,
|
||||||
|
msg: ErrInvalidTTL.Error(),
|
||||||
|
name: "direct to storage node",
|
||||||
|
handler: IRNonForwarding(StorageNode),
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: ErrInvalidTTL.Error(),
|
||||||
|
code: codes.InvalidArgument,
|
||||||
|
name: "zero ttl",
|
||||||
|
handler: IRNonForwarding(StorageNode),
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: ZeroTTL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default to ir node",
|
||||||
|
handler: IRNonForwarding(InnerRingNode),
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default to storage node",
|
||||||
|
handler: IRNonForwarding(StorageNode),
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "not found",
|
||||||
|
code: codes.NotFound,
|
||||||
|
name: "custom status error",
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
||||||
|
handler: func(_ uint32) error { return status.Error(codes.NotFound, "not found") },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "not found",
|
||||||
|
code: codes.NotFound,
|
||||||
|
name: "custom wrapped status error",
|
||||||
|
RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL},
|
||||||
|
handler: func(_ uint32) error {
|
||||||
|
err := status.Error(codes.NotFound, "not found")
|
||||||
|
err = errors.Wrap(err, "some error context")
|
||||||
|
err = errors.Wrap(err, "another error context")
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range tests {
|
||||||
|
tt := tests[i]
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
before := tt.GetTTL()
|
||||||
|
err := ProcessRequestTTL(&tt, tt.handler)
|
||||||
|
if tt.msg != "" {
|
||||||
|
require.Errorf(t, err, tt.msg)
|
||||||
|
|
||||||
|
state, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, tt.code, state.Code())
|
||||||
|
require.Equal(t, tt.msg, state.Message())
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqualf(t, before, tt.GetTTL(), "ttl should be changed: %d vs %d", before, tt.GetTTL())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestMetaHeader_SetTTL(t *testing.T) {
|
||||||
|
m := new(RequestMetaHeader)
|
||||||
|
ttl := uint32(3)
|
||||||
|
|
||||||
|
m.SetTTL(ttl)
|
||||||
|
|
||||||
|
require.Equal(t, ttl, m.GetTTL())
|
||||||
|
}
|
246
service/types.go
Normal file
246
service/types.go
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeRole to identify in Bootstrap service.
|
||||||
|
type NodeRole int32
|
||||||
|
|
||||||
|
// TTLCondition is a function type that used to verify that TTL values match a specific criterion.
|
||||||
|
// Nil error indicates compliance with the criterion.
|
||||||
|
type TTLCondition func(uint32) error
|
||||||
|
|
||||||
|
// RawSource is an interface of the container of a boolean Raw value with read access.
|
||||||
|
type RawSource interface {
|
||||||
|
GetRaw() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawContainer is an interface of the container of a boolean Raw value.
|
||||||
|
type RawContainer interface {
|
||||||
|
RawSource
|
||||||
|
SetRaw(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionSource is an interface of the container of a numerical Version value with read access.
|
||||||
|
type VersionSource interface {
|
||||||
|
GetVersion() uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionContainer is an interface of the container of a numerical Version value.
|
||||||
|
type VersionContainer interface {
|
||||||
|
VersionSource
|
||||||
|
SetVersion(uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EpochSource is an interface of the container of a NeoFS epoch number with read access.
|
||||||
|
type EpochSource interface {
|
||||||
|
GetEpoch() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// EpochContainer is an interface of the container of a NeoFS epoch number.
|
||||||
|
type EpochContainer interface {
|
||||||
|
EpochSource
|
||||||
|
SetEpoch(uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTLSource is an interface of the container of a numerical TTL value with read access.
|
||||||
|
type TTLSource interface {
|
||||||
|
GetTTL() uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// TTLContainer is an interface of the container of a numerical TTL value.
|
||||||
|
type TTLContainer interface {
|
||||||
|
TTLSource
|
||||||
|
SetTTL(uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeizedMetaHeaderContainer is an interface of container of RequestMetaHeader that can be cut and restored.
|
||||||
|
type SeizedMetaHeaderContainer interface {
|
||||||
|
CutMeta() RequestMetaHeader
|
||||||
|
RestoreMeta(RequestMetaHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestMetaContainer is an interface of a fixed set of request meta value containers.
|
||||||
|
// Contains:
|
||||||
|
// - TTL value;
|
||||||
|
// - NeoFS epoch number;
|
||||||
|
// - Protocol version;
|
||||||
|
// - Raw toggle option.
|
||||||
|
type RequestMetaContainer interface {
|
||||||
|
TTLContainer
|
||||||
|
EpochContainer
|
||||||
|
VersionContainer
|
||||||
|
RawContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeizedRequestMetaContainer is a RequestMetaContainer with seized meta.
|
||||||
|
type SeizedRequestMetaContainer interface {
|
||||||
|
RequestMetaContainer
|
||||||
|
SeizedMetaHeaderContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerbSource is an interface of the container of a token verb value with read access.
|
||||||
|
type VerbSource interface {
|
||||||
|
GetVerb() Token_Info_Verb
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerbContainer is an interface of the container of a token verb value.
|
||||||
|
type VerbContainer interface {
|
||||||
|
VerbSource
|
||||||
|
SetVerb(Token_Info_Verb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenIDSource is an interface of the container of a token ID value with read access.
|
||||||
|
type TokenIDSource interface {
|
||||||
|
GetID() TokenID
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenIDContainer is an interface of the container of a token ID value.
|
||||||
|
type TokenIDContainer interface {
|
||||||
|
TokenIDSource
|
||||||
|
SetID(TokenID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreationEpochSource is an interface of the container of a creation epoch number with read access.
|
||||||
|
type CreationEpochSource interface {
|
||||||
|
CreationEpoch() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreationEpochContainer is an interface of the container of a creation epoch number.
|
||||||
|
type CreationEpochContainer interface {
|
||||||
|
CreationEpochSource
|
||||||
|
SetCreationEpoch(uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpirationEpochSource is an interface of the container of an expiration epoch number with read access.
|
||||||
|
type ExpirationEpochSource interface {
|
||||||
|
ExpirationEpoch() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpirationEpochContainer is an interface of the container of an expiration epoch number.
|
||||||
|
type ExpirationEpochContainer interface {
|
||||||
|
ExpirationEpochSource
|
||||||
|
SetExpirationEpoch(uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionKeySource is an interface of the container of session key bytes with read access.
|
||||||
|
type SessionKeySource interface {
|
||||||
|
GetSessionKey() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionKeyContainer is an interface of the container of public session key bytes.
|
||||||
|
type SessionKeyContainer interface {
|
||||||
|
SessionKeySource
|
||||||
|
SetSessionKey([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureSource is an interface of the container of signature bytes with read access.
|
||||||
|
type SignatureSource interface {
|
||||||
|
GetSignature() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureContainer is an interface of the container of signature bytes.
|
||||||
|
type SignatureContainer interface {
|
||||||
|
SignatureSource
|
||||||
|
SetSignature([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionTokenSource is an interface of the container of a SessionToken with read access.
|
||||||
|
type SessionTokenSource interface {
|
||||||
|
GetSessionToken() SessionToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionTokenInfo is an interface of a fixed set of token information value containers.
|
||||||
|
// Contains:
|
||||||
|
// - ID of the token;
|
||||||
|
// - ID of the token's owner;
|
||||||
|
// - verb of the session;
|
||||||
|
// - address of the session object;
|
||||||
|
// - creation epoch number of the token;
|
||||||
|
// - expiration epoch number of the token;
|
||||||
|
// - public session key bytes.
|
||||||
|
type SessionTokenInfo interface {
|
||||||
|
TokenIDContainer
|
||||||
|
OwnerIDContainer
|
||||||
|
VerbContainer
|
||||||
|
AddressContainer
|
||||||
|
CreationEpochContainer
|
||||||
|
ExpirationEpochContainer
|
||||||
|
SessionKeyContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionToken is an interface of token information and signature pair.
|
||||||
|
type SessionToken interface {
|
||||||
|
SessionTokenInfo
|
||||||
|
SignatureContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataSource is an interface of the container of a data for signing.
|
||||||
|
type SignedDataSource interface {
|
||||||
|
// Must return the required for signature byte slice.
|
||||||
|
// A non-nil error indicates that the data is not ready for signature.
|
||||||
|
SignedData() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataReader is an interface of signed data reader.
|
||||||
|
type SignedDataReader interface {
|
||||||
|
// Must return the minimum length of the slice for full reading.
|
||||||
|
// Must return a negative value if the length cannot be calculated.
|
||||||
|
SignedDataSize() int
|
||||||
|
|
||||||
|
// Must behave like Read method of io.Reader and differ only in the reading of the signed data.
|
||||||
|
ReadSignedData([]byte) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignKeyPairAccumulator is an interface of a set of key-signature pairs with append access.
|
||||||
|
type SignKeyPairAccumulator interface {
|
||||||
|
AddSignKey([]byte, *ecdsa.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignKeyPairSource is an interface of a set of key-signature pairs with read access.
|
||||||
|
type SignKeyPairSource interface {
|
||||||
|
GetSignKeyPairs() []SignKeyPair
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignKeyPair is an interface of key-signature pair with read access.
|
||||||
|
type SignKeyPair interface {
|
||||||
|
SignatureSource
|
||||||
|
GetPublicKey() *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataWithSignature is an interface of data-signature pair with read access.
|
||||||
|
type DataWithSignature interface {
|
||||||
|
SignedDataSource
|
||||||
|
SignatureSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataWithSignKeyAccumulator is an interface of data and key-signature accumulator pair.
|
||||||
|
type DataWithSignKeyAccumulator interface {
|
||||||
|
SignedDataSource
|
||||||
|
SignKeyPairAccumulator
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataWithSignKeySource is an interface of data and key-signature source pair.
|
||||||
|
type DataWithSignKeySource interface {
|
||||||
|
SignedDataSource
|
||||||
|
SignKeyPairSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedDataWithToken is an interface of data-token pair with read access.
|
||||||
|
type SignedDataWithToken interface {
|
||||||
|
SignedDataSource
|
||||||
|
SessionTokenSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataWithTokenSignAccumulator is an interface of data-token pair with signature write access.
|
||||||
|
type DataWithTokenSignAccumulator interface {
|
||||||
|
SignedDataWithToken
|
||||||
|
SignKeyPairAccumulator
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataWithTokenSignSource is an interface of data-token pair with signature read access.
|
||||||
|
type DataWithTokenSignSource interface {
|
||||||
|
SignedDataWithToken
|
||||||
|
SignKeyPairSource
|
||||||
|
}
|
|
@ -35,16 +35,55 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// GetSessionToken returns SessionToken interface of Token field.
|
||||||
// ErrCannotLoadPublicKey is raised when cannot unmarshal public key from RequestVerificationHeader_Sign.
|
//
|
||||||
ErrCannotLoadPublicKey = internal.Error("cannot load public key")
|
// If token field value is nil, nil returns.
|
||||||
|
func (m RequestVerificationHeader) GetSessionToken() SessionToken {
|
||||||
|
if t := m.GetToken(); t != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
// ErrCannotFindOwner is raised when signatures empty in GetOwner.
|
return nil
|
||||||
ErrCannotFindOwner = internal.Error("cannot find owner public key")
|
}
|
||||||
|
|
||||||
// ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey
|
// AddSignKey adds new element to Signatures field.
|
||||||
ErrWrongOwner = internal.Error("wrong owner")
|
//
|
||||||
|
// Sets Sign field to passed sign. Set Peer field to marshaled passed key.
|
||||||
|
func (m *RequestVerificationHeader) AddSignKey(sign []byte, key *ecdsa.PublicKey) {
|
||||||
|
m.SetSignatures(
|
||||||
|
append(
|
||||||
|
m.GetSignatures(),
|
||||||
|
&RequestVerificationHeader_Signature{
|
||||||
|
Sign: sign,
|
||||||
|
Peer: crypto.MarshalPublicKey(key),
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignKeyPairs returns the elements of Signatures field as SignKeyPair slice.
|
||||||
|
func (m RequestVerificationHeader) GetSignKeyPairs() []SignKeyPair {
|
||||||
|
var (
|
||||||
|
signs = m.GetSignatures()
|
||||||
|
res = make([]SignKeyPair, len(signs))
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := range signs {
|
||||||
|
res[i] = signs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignature returns the result of a Sign field getter.
|
||||||
|
func (m RequestVerificationHeader_Signature) GetSignature() []byte {
|
||||||
|
return m.GetSign()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey unmarshals and returns the result of a Peer field getter.
|
||||||
|
func (m RequestVerificationHeader_Signature) GetPublicKey() *ecdsa.PublicKey {
|
||||||
|
return crypto.UnmarshalPublicKey(m.GetPeer())
|
||||||
|
}
|
||||||
|
|
||||||
// SetSignatures replaces signatures stored in RequestVerificationHeader.
|
// SetSignatures replaces signatures stored in RequestVerificationHeader.
|
||||||
func (m *RequestVerificationHeader) SetSignatures(signatures []*RequestVerificationHeader_Signature) {
|
func (m *RequestVerificationHeader) SetSignatures(signatures []*RequestVerificationHeader_Signature) {
|
||||||
|
@ -81,7 +120,7 @@ func (m *RequestVerificationHeader) GetOwner() (*ecdsa.PublicKey, error) {
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrCannotLoadPublicKey
|
return nil, ErrInvalidPublicKeyBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLastPeer tries to get last peer public key from signatures.
|
// GetLastPeer tries to get last peer public key from signatures.
|
||||||
|
@ -99,7 +138,7 @@ func (m *RequestVerificationHeader) GetLastPeer() (*ecdsa.PublicKey, error) {
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrCannotLoadPublicKey
|
return nil, ErrInvalidPublicKeyBytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,8 +168,8 @@ var bytesPool = sync.Pool{New: func() interface{} {
|
||||||
// new signature to headers. If something went wrong, returns error.
|
// new signature to headers. If something went wrong, returns error.
|
||||||
func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error {
|
func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error {
|
||||||
// ignore meta header
|
// ignore meta header
|
||||||
if meta, ok := msg.(MetaHeader); ok {
|
if meta, ok := msg.(SeizedRequestMetaContainer); ok {
|
||||||
h := meta.ResetMeta()
|
h := meta.CutMeta()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
meta.RestoreMeta(h)
|
meta.RestoreMeta(h)
|
||||||
|
@ -168,8 +207,8 @@ func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error {
|
||||||
// If something went wrong, returns error.
|
// If something went wrong, returns error.
|
||||||
func VerifyRequestHeader(msg VerifiableRequest) error {
|
func VerifyRequestHeader(msg VerifiableRequest) error {
|
||||||
// ignore meta header
|
// ignore meta header
|
||||||
if meta, ok := msg.(MetaHeader); ok {
|
if meta, ok := msg.(SeizedRequestMetaContainer); ok {
|
||||||
h := meta.ResetMeta()
|
h := meta.CutMeta()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
meta.RestoreMeta(h)
|
meta.RestoreMeta(h)
|
||||||
|
@ -190,7 +229,7 @@ func VerifyRequestHeader(msg VerifiableRequest) error {
|
||||||
|
|
||||||
key := crypto.UnmarshalPublicKey(peer)
|
key := crypto.UnmarshalPublicKey(peer)
|
||||||
if key == nil {
|
if key == nil {
|
||||||
return errors.Wrapf(ErrCannotLoadPublicKey, "%d: %02x", i, peer)
|
return errors.Wrapf(ErrInvalidPublicKeyBytes, "%d: %02x", i, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if size := msg.Size(); size <= cap(data) {
|
if size := msg.Size(); size <= cap(data) {
|
||||||
|
|
11
service/version.go
Normal file
11
service/version.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
// SetVersion is a Version field setter.
|
||||||
|
func (m *ResponseMetaHeader) SetVersion(v uint32) {
|
||||||
|
m.Version = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVersion is a Version field setter.
|
||||||
|
func (m *RequestMetaHeader) SetVersion(v uint32) {
|
||||||
|
m.Version = v
|
||||||
|
}
|
21
service/version_test.go
Normal file
21
service/version_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSetVersion(t *testing.T) {
|
||||||
|
v := uint32(7)
|
||||||
|
|
||||||
|
items := []VersionContainer{
|
||||||
|
new(ResponseMetaHeader),
|
||||||
|
new(RequestMetaHeader),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
item.SetVersion(v)
|
||||||
|
require.Equal(t, v, item.GetVersion())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue