From 52d3c827763d28b3d8324d1e408b3b5f314c6ec0 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 11:44:55 +0300 Subject: [PATCH] service: implement sign/verify function for data with session token --- service/errors.go | 30 ++++++-- service/sign.go | 71 ++++++++++++++++-- service/sign_test.go | 171 ++++++++++++++++++++++++++++-------------- service/token.go | 83 +++++++++++++++++++- service/token_test.go | 8 +- service/types.go | 47 +++++++++--- 6 files changed, 326 insertions(+), 84 deletions(-) diff --git a/service/errors.go b/service/errors.go index 6b0ed24..6241ad2 100644 --- a/service/errors.go +++ b/service/errors.go @@ -2,10 +2,12 @@ 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. +// 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. +// 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. @@ -14,14 +16,30 @@ 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 +// 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. +// 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. +// 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. +// 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") diff --git a/service/sign.go b/service/sign.go index 9a6eba9..f5cdc0b 100644 --- a/service/sign.go +++ b/service/sign.go @@ -34,6 +34,8 @@ func newSignatureKeyPair(key *ecdsa.PublicKey, sign []byte) SignKeyPair { // 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 @@ -45,11 +47,10 @@ func dataForSignature(src SignedDataSource) ([]byte, error) { } buf := bytesPool.Get().([]byte) - defer func() { - bytesPool.Put(buf) - }() - if size := r.SignedDataSize(); size <= cap(buf) { + if size := r.SignedDataSize(); size < 0 { + return nil, ErrNegativeLength + } else if size <= cap(buf) { buf = buf[:size] } else { buf = make([]byte, size) @@ -78,14 +79,17 @@ func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) 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(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error { +func AddSignatureWithKey(key *ecdsa.PrivateKey, v DataWithSignKeyAccumulator) error { sign, err := DataSignature(key, v) if err != nil { return err @@ -108,6 +112,7 @@ func verifySignatures(src SignedDataSource, items ...SignKeyPair) error { if err != nil { return err } + defer bytesPool.Put(data) for _, signKey := range items { if err := crypto.Verify( @@ -135,7 +140,7 @@ func VerifySignatures(src SignedDataSource, items ...SignKeyPair) error { // // Behaves like VerifySignatures. // If passed key-signature source is empty, ErrNilSignatureKeySource returns. -func VerifyAccumulatedSignatures(src SignatureKeySource) error { +func VerifyAccumulatedSignatures(src DataWithSignKeySource) error { if src == nil { return ErrNilSignatureKeySource } @@ -148,7 +153,7 @@ func VerifyAccumulatedSignatures(src SignatureKeySource) error { // 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(src DataWithSignature, key *ecdsa.PublicKey) error { +func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error { if src == nil { return ErrEmptyDataWithSignature } else if key == nil { @@ -163,3 +168,55 @@ func VerifySignatureWithKey(src DataWithSignature, key *ecdsa.PublicKey) error { ), ) } + +// 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(), + }) +} diff --git a/service/sign_test.go b/service/sign_test.go index 8fba968..5cb7c40 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -13,38 +13,32 @@ import ( ) type testSignedDataSrc struct { - e error - d []byte + err error + data []byte + sig []byte + key *ecdsa.PublicKey + token SessionToken } type testSignedDataReader struct { - SignedDataSource - - e error - d []byte + *testSignedDataSrc } -type testKeySigAccum struct { - data []byte - sig []byte - key *ecdsa.PublicKey -} - -func (s testKeySigAccum) GetSignature() []byte { +func (s testSignedDataSrc) GetSignature() []byte { return s.sig } -func (s testKeySigAccum) GetSignKeyPairs() []SignKeyPair { +func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair { return []SignKeyPair{ newSignatureKeyPair(s.key, s.sig), } } -func (s testKeySigAccum) SignedData() ([]byte, error) { - return s.data, nil +func (s testSignedDataSrc) SignedData() ([]byte, error) { + return s.data, s.err } -func (s testKeySigAccum) AddSignKey(sig []byte, key *ecdsa.PublicKey) { +func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) { s.key = key s.sig = sig } @@ -56,24 +50,24 @@ func testData(t *testing.T, sz int) []byte { return d } +func (s testSignedDataSrc) GetSessionToken() SessionToken { + return s.token +} + func (s testSignedDataReader) SignedDataSize() int { - return len(s.d) + return len(s.data) } func (s testSignedDataReader) ReadSignedData(buf []byte) (int, error) { - if s.e != nil { - return 0, s.e + if s.err != nil { + return 0, s.err } var err error - if len(buf) < len(s.d) { + if len(buf) < len(s.data) { err = io.ErrUnexpectedEOF } - return copy(buf, s.d), err -} - -func (s testSignedDataSrc) SignedData() ([]byte, error) { - return s.d, s.e + return copy(buf, s.data), err } func TestDataSignature(t *testing.T) { @@ -93,63 +87,59 @@ func TestDataSignature(t *testing.T) { t.Run("common signed data source", func(t *testing.T) { // create test data source src := &testSignedDataSrc{ - d: testData(t, 10), + data: testData(t, 10), } // create custom error for data source - src.e = errors.New("test error for data source") + src.err = errors.New("test error for data source") _, err = DataSignature(sk, src) - require.EqualError(t, err, src.e.Error()) + require.EqualError(t, err, src.err.Error()) // reset error to nil - src.e = 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.d, sig)) + 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 := &testSignedDataReader{ - d: testData(t, 10), + src := &testSignedDataSrc{ + data: testData(t, 10), } // create custom error for signed data reader - src.e = errors.New("test 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.e.Error()) + require.EqualError(t, err, src.err.Error()) // reset error to nil - src.e = 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.d, sig)) + require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig)) }) } func TestAddSignatureWithKey(t *testing.T) { - // create test data - data := testData(t, 10) - - // create test private key - sk := test.DecodeKey(0) - - // create test signature accumulator - var s SignatureKeyAccumulator = &testKeySigAccum{ - data: data, - } - - require.NoError(t, AddSignatureWithKey(s, sk)) + require.NoError(t, + AddSignatureWithKey( + test.DecodeKey(0), + &testSignedDataSrc{ + data: testData(t, 10), + }, + ), + ) } func TestVerifySignatures(t *testing.T) { @@ -158,14 +148,14 @@ func TestVerifySignatures(t *testing.T) { // create test signature source src := &testSignedDataSrc{ - d: testData(t, 10), + 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.d) + sig, err := crypto.Sign(sk, src.data) require.NoError(t, err) // ascertain that verification is passed @@ -208,7 +198,7 @@ func TestVerifyAccumulatedSignatures(t *testing.T) { sk := test.DecodeKey(0) // create signature source - src := &testKeySigAccum{ + src := &testSignedDataSrc{ data: testData(t, 10), key: &sk.PublicKey, } @@ -237,13 +227,13 @@ func TestVerifySignatureWithKey(t *testing.T) { ) // create test signature source - src := &testKeySigAccum{ + src := &testSignedDataSrc{ data: testData(t, 10), } // nil public key require.EqualError(t, - VerifySignatureWithKey(src, nil), + VerifySignatureWithKey(nil, src), crypto.ErrEmptyPublicKey.Error(), ) @@ -257,11 +247,80 @@ func TestVerifySignatureWithKey(t *testing.T) { require.NoError(t, err) // ascertain that verification is passed - require.NoError(t, VerifySignatureWithKey(src, &sk.PublicKey)) + require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src)) // break the signature src.sig[0]++ // ascertain that verification is failed - require.Error(t, VerifySignatureWithKey(src, &sk.PublicKey)) + 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)) } diff --git a/service/token.go b/service/token.go index 5786a40..f431427 100644 --- a/service/token.go +++ b/service/token.go @@ -8,6 +8,24 @@ import ( "github.com/nspcc-dev/neofs-api-go/refs" ) +type signAccumWithToken struct { + SignedDataSource + SignKeyPairAccumulator + SignKeyPairSource + + token SessionToken +} + +type signDataReaderWithToken struct { + SignedDataSource + SignKeyPairAccumulator + SignKeyPairSource + + rdr SignedDataReader + + token SessionToken +} + const verbSize = 4 const fixedTokenDataSize = 0 + @@ -127,13 +145,26 @@ func (m *Token_Info) ReadSignedData(p []byte) (int, error) { } // SignedDataSize returns the length of signed token information slice. -func (m Token_Info) SignedDataSize() int { - return fixedTokenDataSize + len(m.GetSessionKey()) +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()) @@ -154,3 +185,51 @@ func copyTokenSignedData(buf []byte, token SessionTokenInfo) { 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 { + return nil, err + } + + tokenData := make([]byte, tokenInfoSize(s.token)) + + copyTokenSignedData(tokenData, s.token) + + return append(data, tokenData...), nil +} + +func (s signDataReaderWithToken) SignedDataSize() int { + sz := s.rdr.SignedDataSize() + if sz < 0 { + return -1 + } + + sz += tokenInfoSize(s.token) + + return sz +} + +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 +} diff --git a/service/token_test.go b/service/token_test.go index 968364c..ce3d2c8 100644 --- a/service/token_test.go +++ b/service/token_test.go @@ -127,8 +127,8 @@ func TestSignToken(t *testing.T) { token.SetSessionKey(sessionKey) // sign and verify token - require.NoError(t, AddSignatureWithKey(token, sk)) - require.NoError(t, VerifySignatureWithKey(token, pk)) + require.NoError(t, AddSignatureWithKey(sk, token)) + require.NoError(t, VerifySignatureWithKey(pk, token)) items := []struct { corrupt func() @@ -212,8 +212,8 @@ func TestSignToken(t *testing.T) { for _, v := range items { v.corrupt() - require.Error(t, VerifySignatureWithKey(token, pk)) + require.Error(t, VerifySignatureWithKey(pk, token)) v.restore() - require.NoError(t, VerifySignatureWithKey(token, pk)) + require.NoError(t, VerifySignatureWithKey(pk, token)) } } diff --git a/service/types.go b/service/types.go index 3bf8c3a..020cba0 100644 --- a/service/types.go +++ b/service/types.go @@ -186,32 +186,61 @@ type SignedDataSource interface { // 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) } -// SignatureKeyAccumulator is an interface of the container of a data and signatures. -type SignatureKeyAccumulator interface { - SignedDataSource +// 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 } -// SignatureKeyAccumulator is an interface of the container of a data and signatures with read access. -type SignatureKeySource interface { - SignedDataSource - GetSignKeyPairs() []SignKeyPair -} - // 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 +}