service: implement a function for signing data

This commit is contained in:
Leonard Lyubich 2020-05-04 18:52:56 +03:00
parent eb94cf7549
commit 0ffb1bd61d
4 changed files with 189 additions and 0 deletions

View file

@ -16,3 +16,6 @@ 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 argument, but received nil.
const ErrNilSignedDataSource = internal.Error("signed data source is nil")

58
service/sign.go Normal file
View file

@ -0,0 +1,58 @@
package service
import (
"crypto/ecdsa"
crypto "github.com/nspcc-dev/neofs-crypto"
)
// 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.
func dataForSignature(src SignedDataSource) ([]byte, error) {
r, ok := src.(SignedDataReader)
if !ok {
return src.SignedData()
}
buf := bytesPool.Get().([]byte)
defer func() {
bytesPool.Put(buf)
}()
if size := r.SignedDataSize(); 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(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) {
if src == nil {
return nil, ErrNilSignedDataSource
} else if key == nil {
return nil, crypto.ErrEmptyPrivateKey
}
data, err := dataForSignature(src)
if err != nil {
return nil, err
}
return crypto.Sign(key, data)
}

112
service/sign_test.go Normal file
View file

@ -0,0 +1,112 @@
package service
import (
"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 {
e error
d []byte
}
type testSignedDataReader struct {
SignedDataSource
e error
d []byte
}
func testData(t *testing.T, sz int) []byte {
d := make([]byte, sz)
_, err := rand.Read(d)
require.NoError(t, err)
return d
}
func (s testSignedDataReader) SignedDataSize() int {
return len(s.d)
}
func (s testSignedDataReader) ReadSignedData(buf []byte) (int, error) {
if s.e != nil {
return 0, s.e
}
var err error
if len(buf) < len(s.d) {
err = io.ErrUnexpectedEOF
}
return copy(buf, s.d), err
}
func (s testSignedDataSrc) SignedData() ([]byte, error) {
return s.d, s.e
}
func TestDataSignature(t *testing.T) {
var err error
// nil data source
_, err = DataSignature(nil, nil)
require.EqualError(t, err, ErrNilSignedDataSource.Error())
// nil private key
_, err = DataSignature(new(testSignedDataSrc), nil)
require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error())
// create test private key
sk := test.DecodeKey(0)
t.Run("common signed data source", func(t *testing.T) {
// create test data source
src := &testSignedDataSrc{
d: testData(t, 10),
}
// create custom error for data source
src.e = errors.New("test error for data source")
_, err = DataSignature(src, sk)
require.EqualError(t, err, src.e.Error())
// reset error to nil
src.e = nil
// calculate data signature
sig, err := DataSignature(src, sk)
require.NoError(t, err)
// ascertain that the signature passes verification
require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig))
})
t.Run("signed data reader", func(t *testing.T) {
// create test signed data reader
src := &testSignedDataReader{
d: testData(t, 10),
}
// create custom error for signed data reader
src.e = errors.New("test error for signed data reader")
sig, err := DataSignature(src, sk)
require.EqualError(t, err, src.e.Error())
// reset error to nil
src.e = nil
// calculate data signature
sig, err = DataSignature(src, sk)
require.NoError(t, err)
// ascertain that the signature passes verification
require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig))
})
}

View file

@ -171,3 +171,19 @@ 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.
SignedDataSize() int
// Must behave like Read method of io.Reader and differ only in the reading of the signed data.
ReadSignedData([]byte) (int, error)
}