diff --git a/service/errors.go b/service/errors.go index 1154a28..6b0ed24 100644 --- a/service/errors.go +++ b/service/errors.go @@ -17,5 +17,11 @@ 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. +// 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") diff --git a/service/sign.go b/service/sign.go index 654c4a9..9a6eba9 100644 --- a/service/sign.go +++ b/service/sign.go @@ -6,12 +6,39 @@ import ( 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. func dataForSignature(src SignedDataSource) ([]byte, error) { + if src == nil { + return nil, ErrNilSignedDataSource + } + r, ok := src.(SignedDataReader) if !ok { return src.SignedData() @@ -42,10 +69,8 @@ func dataForSignature(src SignedDataSource) ([]byte, error) { // 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 { +func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) { + if key == nil { return nil, crypto.ErrEmptyPrivateKey } @@ -61,7 +86,7 @@ func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) // // Returns signing errors only. func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error { - sign, err := DataSignature(v, key) + sign, err := DataSignature(key, v) if err != nil { return err } @@ -70,3 +95,71 @@ func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error 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 + } + + 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 SignatureKeySource) 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(src DataWithSignature, key *ecdsa.PublicKey) error { + if src == nil { + return ErrEmptyDataWithSignature + } else if key == nil { + return crypto.ErrEmptyPublicKey + } + + return verifySignatures( + src, + newSignatureKeyPair( + key, + src.GetSignature(), + ), + ) +} diff --git a/service/sign_test.go b/service/sign_test.go index 5ac7c6c..8fba968 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -25,18 +25,28 @@ type testSignedDataReader struct { } type testKeySigAccum struct { - d []byte - f func([]byte, *ecdsa.PublicKey) + data []byte + sig []byte + key *ecdsa.PublicKey +} + +func (s testKeySigAccum) GetSignature() []byte { + return s.sig +} + +func (s testKeySigAccum) GetSignKeyPairs() []SignKeyPair { + return []SignKeyPair{ + newSignatureKeyPair(s.key, s.sig), + } } func (s testKeySigAccum) SignedData() ([]byte, error) { - return s.d, nil + return s.data, nil } func (s testKeySigAccum) AddSignKey(sig []byte, key *ecdsa.PublicKey) { - if s.f != nil { - s.f(sig, key) - } + s.key = key + s.sig = sig } func testData(t *testing.T, sz int) []byte { @@ -69,17 +79,17 @@ func (s testSignedDataSrc) SignedData() ([]byte, error) { 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) + _, 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{ @@ -89,14 +99,14 @@ func TestDataSignature(t *testing.T) { // create custom error for data source src.e = errors.New("test error for data source") - _, err = DataSignature(src, sk) + _, err = DataSignature(sk, src) require.EqualError(t, err, src.e.Error()) // reset error to nil src.e = nil // calculate data signature - sig, err := DataSignature(src, sk) + sig, err := DataSignature(sk, src) require.NoError(t, err) // ascertain that the signature passes verification @@ -112,14 +122,14 @@ func TestDataSignature(t *testing.T) { // create custom error for signed data reader src.e = errors.New("test error for signed data reader") - sig, err := DataSignature(src, sk) + sig, err := DataSignature(sk, src) require.EqualError(t, err, src.e.Error()) // reset error to nil src.e = nil // calculate data signature - sig, err = DataSignature(src, sk) + sig, err = DataSignature(sk, src) require.NoError(t, err) // ascertain that the signature passes verification @@ -136,12 +146,122 @@ func TestAddSignatureWithKey(t *testing.T) { // create test signature accumulator var s SignatureKeyAccumulator = &testKeySigAccum{ - d: data, - f: func(sig []byte, key *ecdsa.PublicKey) { - require.Equal(t, &sk.PublicKey, key) - require.NoError(t, crypto.Verify(key, data, sig)) - }, + data: data, } require.NoError(t, AddSignatureWithKey(s, sk)) } + +func TestVerifySignatures(t *testing.T) { + // empty signatures + require.NoError(t, VerifySignatures(nil)) + + // create test signature source + src := &testSignedDataSrc{ + d: 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) + 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 := &testKeySigAccum{ + 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 := &testKeySigAccum{ + data: testData(t, 10), + } + + // nil public key + require.EqualError(t, + VerifySignatureWithKey(src, nil), + 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(src, &sk.PublicKey)) + + // break the signature + src.sig[0]++ + + // ascertain that verification is failed + require.Error(t, VerifySignatureWithKey(src, &sk.PublicKey)) +} diff --git a/service/types.go b/service/types.go index 80a4a49..3bf8c3a 100644 --- a/service/types.go +++ b/service/types.go @@ -192,8 +192,26 @@ type SignedDataReader interface { ReadSignedData([]byte) (int, error) } -// SignatureKeyAccumulator is an interface of the accumulator of data signatures with keys. +// SignatureKeyAccumulator is an interface of the container of a data and signatures. type SignatureKeyAccumulator interface { SignedDataSource AddSignKey([]byte, *ecdsa.PublicKey) } + +// 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 +}