Fix issue with Sign/VerifyRequestHeader proto.Clone

proto.Clone couldn't makes copy for custom fields.
We should reset and restore MetaHeader before/after Sign/Verify.
Add test coverage to check that all works like expected.
This commit is contained in:
Evgeniy Kulikov 2019-11-19 19:03:42 +03:00
parent 24e5497b1d
commit 5c344bfceb
No known key found for this signature in database
GPG key ID: BF6AEE0A2A699BF2
5 changed files with 185 additions and 45 deletions

View file

@ -8,10 +8,11 @@ import (
type (
// MetaHeader contains meta information of request.
// It provides methods to get or set meta information
// and reset meta header.
// It provides methods to get or set meta information meta header.
// Also contains methods to reset and restore meta header.
MetaHeader interface {
ResetMeta()
ResetMeta() RequestMetaHeader
RestoreMeta(RequestMetaHeader)
// TTLRequest to verify and update ttl requests.
GetTTL() uint32
@ -22,7 +23,7 @@ type (
SetEpoch(v uint64)
}
// TTLCondition is closure, that allows to validate request with ttl
// TTLCondition is closure, that allows to validate request with ttl.
TTLCondition func(ttl uint32) error
)
@ -45,14 +46,21 @@ const (
ErrIncorrectTTL = internal.Error("incorrect ttl")
)
// SetTTL sets TTL to RequestMetaHeader
// SetTTL sets TTL to RequestMetaHeader.
func (m *RequestMetaHeader) SetTTL(v uint32) { m.TTL = v }
// SetEpoch sets Epoch to RequestMetaHeader
// SetEpoch sets Epoch to RequestMetaHeader.
func (m *RequestMetaHeader) SetEpoch(v uint64) { m.Epoch = v }
// ResetMeta sets RequestMetaHeader to empty value
func (m *RequestMetaHeader) ResetMeta() { m.Reset() }
// ResetMeta returns current value and sets RequestMetaHeader to empty value.
func (m *RequestMetaHeader) ResetMeta() RequestMetaHeader {
cp := *m
m.Reset()
return cp
}
// RestoreMeta sets current RequestMetaHeader to passed value.
func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v }
// IRNonForwarding condition that allows NonForwardingTTL only for IR
func IRNonForwarding(role NodeRole) TTLCondition {

View file

@ -10,7 +10,7 @@ import (
)
type (
// VerifiableRequest adds possibility to sign and verify request header
// VerifiableRequest adds possibility to sign and verify request header.
VerifiableRequest interface {
proto.Message
Marshal() ([]byte, error)
@ -30,19 +30,19 @@ type (
)
const (
// ErrCannotLoadPublicKey is raised when cannot unmarshal public key from RequestVerificationHeader_Sign
// ErrCannotLoadPublicKey is raised when cannot unmarshal public key from RequestVerificationHeader_Sign.
ErrCannotLoadPublicKey = internal.Error("cannot load public key")
// ErrCannotFindOwner is raised when signatures empty in GetOwner
// ErrCannotFindOwner is raised when signatures empty in GetOwner.
ErrCannotFindOwner = internal.Error("cannot find owner public key")
)
// SetSignatures replaces signatures stored in RequestVerificationHeader
// SetSignatures replaces signatures stored in RequestVerificationHeader.
func (m *RequestVerificationHeader) SetSignatures(signatures []*RequestVerificationHeader_Signature) {
m.Signatures = signatures
}
// AddSignature adds new Signature into RequestVerificationHeader
// AddSignature adds new Signature into RequestVerificationHeader.
func (m *RequestVerificationHeader) AddSignature(sig *RequestVerificationHeader_Signature) {
if sig == nil {
return
@ -123,12 +123,14 @@ func newSignature(key *ecdsa.PrivateKey, data []byte) (*RequestVerificationHeade
// SignRequestHeader receives private key and request with RequestVerificationHeader,
// tries to marshal and sign request with passed PrivateKey, after that adds
// new signature to headers. If something went wrong, returns error.
func SignRequestHeader(key *ecdsa.PrivateKey, req VerifiableRequest) error {
msg := proto.Clone(req).(VerifiableRequest)
func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error {
// ignore meta header
if meta, ok := msg.(MetaHeader); ok {
meta.ResetMeta()
h := meta.ResetMeta()
defer func() {
meta.RestoreMeta(h)
}()
}
data, err := msg.Marshal()
@ -141,22 +143,28 @@ func SignRequestHeader(key *ecdsa.PrivateKey, req VerifiableRequest) error {
return err
}
req.AddSignature(signature)
msg.AddSignature(signature)
return nil
}
// VerifyRequestHeader receives request with RequestVerificationHeader,
// tries to marshal and verify each signature from request
// tries to marshal and verify each signature from request.
// If something went wrong, returns error.
func VerifyRequestHeader(req VerifiableRequest) error {
msg := proto.Clone(req).(VerifiableRequest)
func VerifyRequestHeader(msg VerifiableRequest) error {
// ignore meta header
if meta, ok := msg.(MetaHeader); ok {
meta.ResetMeta()
h := meta.ResetMeta()
defer func() {
meta.RestoreMeta(h)
}()
}
signatures := msg.GetSignatures()
defer func() {
msg.SetSignatures(signatures)
}()
for i := range signatures {
msg.SetSignatures(signatures[:i])
@ -177,3 +185,35 @@ func VerifyRequestHeader(req VerifiableRequest) error {
return nil
}
// testCustomField for test usage only.
type testCustomField [8]uint32
var _ internal.Custom = (*testCustomField)(nil)
// Reset skip, it's for test usage only.
func (t testCustomField) Reset() {}
// ProtoMessage skip, it's for test usage only.
func (t testCustomField) ProtoMessage() {}
// Size skip, it's for test usage only.
func (t testCustomField) Size() int { return 32 }
// String skip, it's for test usage only.
func (t testCustomField) String() string { return "" }
// Bytes skip, it's for test usage only.
func (t testCustomField) Bytes() []byte { return nil }
// Unmarshal skip, it's for test usage only.
func (t testCustomField) Unmarshal(data []byte) error { return nil }
// Empty skip, it's for test usage only.
func (t testCustomField) Empty() bool { return false }
// UnmarshalTo skip, it's for test usage only.
func (t testCustomField) MarshalTo(data []byte) (int, error) { return 0, nil }
// Marshal skip, it's for test usage only.
func (t testCustomField) Marshal() ([]byte, error) { return nil, nil }

View file

@ -1,9 +1,12 @@
package service
import (
"bytes"
"log"
"math"
"testing"
"github.com/gogo/protobuf/proto"
crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/nspcc-dev/neofs-crypto/test"
"github.com/pkg/errors"
@ -104,3 +107,37 @@ func TestMaintainableRequest(t *testing.T) {
require.EqualError(t, errors.Cause(err), crypto.ErrInvalidSignature.Error())
}
}
func TestVerifyAndSignRequestHeaderWithoutCloning(t *testing.T) {
key := test.DecodeKey(0)
custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8}
b := &TestRequest{
IntField: math.MaxInt32,
StringField: "TestRequestStringField",
BytesField: []byte("TestRequestBytesField"),
CustomField: &custom,
RequestMetaHeader: RequestMetaHeader{
TTL: math.MaxInt32 - 8,
Epoch: math.MaxInt64 - 12,
},
}
require.NoError(t, SignRequestHeader(key, b))
require.NoError(t, VerifyRequestHeader(b))
require.Len(t, b.Signatures, 1)
require.Equal(t, custom, *b.CustomField)
require.Equal(t, uint32(math.MaxInt32-8), b.GetTTL())
require.Equal(t, uint64(math.MaxInt64-12), b.GetEpoch())
buf := bytes.NewBuffer(nil)
log.SetOutput(buf)
cp, ok := proto.Clone(b).(*TestRequest)
require.True(t, ok)
require.NotEqual(t, b, cp)
require.Contains(t, buf.String(), "proto: don't know how to copy")
}

View file

@ -24,9 +24,10 @@ var _ = math.Inf
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type TestRequest struct {
IntField int32 `protobuf:"varint,1,opt,name=IntField,proto3" json:"IntField,omitempty"`
StringField string `protobuf:"bytes,2,opt,name=StringField,proto3" json:"StringField,omitempty"`
BytesField []byte `protobuf:"bytes,3,opt,name=BytesField,proto3" json:"BytesField,omitempty"`
IntField int32 `protobuf:"varint,1,opt,name=IntField,proto3" json:"IntField,omitempty"`
StringField string `protobuf:"bytes,2,opt,name=StringField,proto3" json:"StringField,omitempty"`
BytesField []byte `protobuf:"bytes,3,opt,name=BytesField,proto3" json:"BytesField,omitempty"`
CustomField *testCustomField `protobuf:"bytes,4,opt,name=CustomField,proto3,customtype=testCustomField" json:"CustomField,omitempty"`
RequestMetaHeader `protobuf:"bytes,98,opt,name=Meta,proto3,embedded=Meta" json:"Meta"`
RequestVerificationHeader `protobuf:"bytes,99,opt,name=Header,proto3,embedded=Header" json:"Header"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@ -91,26 +92,28 @@ func init() {
func init() { proto.RegisterFile("service/verify_test.proto", fileDescriptor_1effa83201a30d75) }
var fileDescriptor_1effa83201a30d75 = []byte{
// 296 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xeb, 0x30,
0x14, 0xc6, 0xeb, 0x7b, 0xa1, 0x14, 0x97, 0xc9, 0x62, 0x28, 0x19, 0xdc, 0xa8, 0x53, 0x96, 0x26,
0x12, 0x2c, 0x4c, 0x0c, 0x15, 0x42, 0x30, 0xb0, 0x04, 0xc4, 0xc0, 0x82, 0x12, 0xe7, 0x24, 0x58,
0xa2, 0x71, 0x89, 0x4f, 0x22, 0xf5, 0x4d, 0x78, 0xa4, 0x8e, 0x1d, 0x99, 0x2a, 0x14, 0x46, 0x5e,
0x02, 0xd5, 0x36, 0x28, 0xc0, 0x96, 0xef, 0xcf, 0x2f, 0x47, 0xfe, 0xe8, 0x91, 0x86, 0xaa, 0x91,
0x02, 0xa2, 0x06, 0x2a, 0x99, 0x2f, 0x1f, 0x10, 0x34, 0x86, 0x8b, 0x4a, 0xa1, 0x62, 0x7b, 0x2e,
0xf2, 0xd8, 0x57, 0x67, 0x0e, 0x98, 0xd8, 0xd0, 0x3b, 0xfc, 0xc9, 0x39, 0x77, 0x5a, 0x48, 0x7c,
0xac, 0xd3, 0x50, 0xa8, 0x79, 0x54, 0xa8, 0x42, 0x45, 0xc6, 0x4e, 0xeb, 0xdc, 0x28, 0x23, 0xcc,
0x97, 0xad, 0x4f, 0x3e, 0x08, 0x1d, 0xde, 0x82, 0xc6, 0x18, 0x9e, 0x6b, 0xd0, 0xc8, 0x3c, 0x3a,
0xb8, 0x2a, 0xf1, 0x42, 0xc2, 0x53, 0x36, 0x22, 0x3e, 0x09, 0x76, 0xe3, 0x6f, 0xcd, 0x7c, 0x3a,
0xbc, 0xc1, 0x4a, 0x96, 0x85, 0x8d, 0xff, 0xf9, 0x24, 0xd8, 0x8f, 0xbb, 0x16, 0xe3, 0x94, 0xce,
0x96, 0x08, 0xda, 0x16, 0xfe, 0xfb, 0x24, 0x38, 0x88, 0x3b, 0x0e, 0x3b, 0xa5, 0x3b, 0xd7, 0x80,
0xc9, 0x28, 0xf5, 0x49, 0x30, 0x3c, 0xf6, 0x42, 0xf7, 0x82, 0xd0, 0x5d, 0xdf, 0x66, 0x97, 0x90,
0x64, 0x50, 0xcd, 0x06, 0xab, 0xcd, 0xb8, 0xb7, 0xde, 0x8c, 0x49, 0x6c, 0x08, 0x76, 0x4e, 0xfb,
0x36, 0x19, 0x09, 0xc3, 0x4e, 0x7e, 0xb3, 0x77, 0xdb, 0x11, 0xa4, 0x48, 0x50, 0xaa, 0xf2, 0xcf,
0x3f, 0x1c, 0x3b, 0x3b, 0x5b, 0xb5, 0x9c, 0xac, 0x5b, 0x4e, 0x5e, 0x5b, 0x4e, 0xde, 0x5a, 0x4e,
0x5e, 0xde, 0x79, 0xef, 0x3e, 0xe8, 0x4c, 0x56, 0xea, 0x85, 0x10, 0xd3, 0x0c, 0x9a, 0xa8, 0x04,
0x95, 0xeb, 0xa9, 0x1d, 0xcc, 0xdd, 0x4a, 0xfb, 0x46, 0x9e, 0x7c, 0x06, 0x00, 0x00, 0xff, 0xff,
0xa6, 0xdb, 0x5f, 0x7a, 0xb3, 0x01, 0x00, 0x00,
// 322 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x31, 0x4f, 0xc3, 0x30,
0x10, 0x85, 0xeb, 0x52, 0x4a, 0x71, 0x90, 0x90, 0x0c, 0x43, 0xc9, 0x90, 0x46, 0x9d, 0xb2, 0x34,
0x91, 0x40, 0x48, 0x4c, 0x0c, 0x01, 0x21, 0x18, 0x58, 0x02, 0x62, 0x60, 0x41, 0x49, 0x7a, 0x0d,
0x96, 0x68, 0x5c, 0xe2, 0x4b, 0xa5, 0xfe, 0x13, 0x66, 0x7e, 0x4d, 0xc7, 0x8e, 0x88, 0xa1, 0x42,
0xe1, 0x8f, 0xa0, 0xd8, 0x06, 0x05, 0xd8, 0xfc, 0xde, 0xfb, 0x9e, 0xed, 0x3b, 0x7a, 0x20, 0xa1,
0x98, 0xf3, 0x14, 0x82, 0x39, 0x14, 0x7c, 0xb2, 0x78, 0x40, 0x90, 0xe8, 0xcf, 0x0a, 0x81, 0x82,
0x6d, 0x99, 0xc8, 0x66, 0xdf, 0xcc, 0x14, 0x30, 0xd6, 0xa1, 0xbd, 0xff, 0xbb, 0x67, 0xdc, 0x51,
0xc6, 0xf1, 0xb1, 0x4c, 0xfc, 0x54, 0x4c, 0x83, 0x4c, 0x64, 0x22, 0x50, 0x76, 0x52, 0x4e, 0x94,
0x52, 0x42, 0x9d, 0x34, 0x3e, 0x7c, 0x6d, 0x53, 0xeb, 0x16, 0x24, 0x46, 0xf0, 0x5c, 0x82, 0x44,
0x66, 0xd3, 0xde, 0x55, 0x8e, 0x17, 0x1c, 0x9e, 0xc6, 0x7d, 0xe2, 0x12, 0x6f, 0x33, 0xfa, 0xd1,
0xcc, 0xa5, 0xd6, 0x0d, 0x16, 0x3c, 0xcf, 0x74, 0xdc, 0x76, 0x89, 0xb7, 0x1d, 0x35, 0x2d, 0xe6,
0x50, 0x1a, 0x2e, 0x10, 0xa4, 0x06, 0x36, 0x5c, 0xe2, 0xed, 0x44, 0x0d, 0x87, 0x1d, 0x53, 0xeb,
0xac, 0x94, 0x28, 0xa6, 0x1a, 0xe8, 0xd4, 0x40, 0xb8, 0xf7, 0xbe, 0x1e, 0xec, 0xd6, 0x43, 0x37,
0xa2, 0xa8, 0xc9, 0xb1, 0x13, 0xda, 0xb9, 0x06, 0x8c, 0xfb, 0x89, 0x4b, 0x3c, 0xeb, 0xd0, 0xf6,
0xcd, 0xe0, 0xbe, 0xf9, 0x74, 0x9d, 0x5d, 0x42, 0x3c, 0x86, 0x22, 0xec, 0x2d, 0xd7, 0x83, 0xd6,
0x6a, 0x3d, 0x20, 0x91, 0x6a, 0xb0, 0x73, 0xda, 0xd5, 0x49, 0x3f, 0x55, 0xdd, 0xe1, 0xdf, 0xee,
0x5d, 0xbd, 0x3b, 0x9e, 0xc6, 0xc8, 0x45, 0xfe, 0xef, 0x0e, 0xd3, 0x0d, 0x4f, 0x97, 0x95, 0x43,
0x56, 0x95, 0x43, 0xde, 0x2a, 0x87, 0x7c, 0x54, 0x0e, 0x79, 0xf9, 0x74, 0x5a, 0xf7, 0x5e, 0x63,
0xd3, 0xb9, 0x9c, 0xa5, 0xe9, 0x68, 0x0c, 0xf3, 0x20, 0x07, 0x31, 0x91, 0x23, 0xbd, 0x67, 0xf3,
0x56, 0xd2, 0x55, 0xf2, 0xe8, 0x2b, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x37, 0x92, 0xd2, 0xea, 0x01,
0x00, 0x00,
}
func (m *TestRequest) Marshal() (dAtA []byte, err error) {
@ -161,6 +164,18 @@ func (m *TestRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
dAtA[i] = 0x6
i--
dAtA[i] = 0x92
if m.CustomField != nil {
{
size := m.CustomField.Size()
i -= size
if _, err := m.CustomField.MarshalTo(dAtA[i:]); err != nil {
return 0, err
}
i = encodeVarintVerifyTest(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x22
}
if len(m.BytesField) > 0 {
i -= len(m.BytesField)
copy(dAtA[i:], m.BytesField)
@ -211,6 +226,10 @@ func (m *TestRequest) Size() (n int) {
if l > 0 {
n += 1 + l + sovVerifyTest(uint64(l))
}
if m.CustomField != nil {
l = m.CustomField.Size()
n += 1 + l + sovVerifyTest(uint64(l))
}
l = m.RequestMetaHeader.Size()
n += 2 + l + sovVerifyTest(uint64(l))
l = m.RequestVerificationHeader.Size()
@ -341,6 +360,41 @@ func (m *TestRequest) Unmarshal(dAtA []byte) error {
m.BytesField = []byte{}
}
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CustomField", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowVerifyTest
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthVerifyTest
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthVerifyTest
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
var v testCustomField
m.CustomField = &v
if err := m.CustomField.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 98:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field RequestMetaHeader", wireType)

View file

@ -12,6 +12,7 @@ message TestRequest {
int32 IntField = 1;
string StringField = 2;
bytes BytesField = 3;
bytes CustomField = 4 [(gogoproto.customtype) = "testCustomField"];
RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
RequestVerificationHeader Header = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
}