From ea043f4ca3f125f73a7bfa3f9d5378923f2cf9d5 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 5 Apr 2022 14:13:34 +0300 Subject: [PATCH] [#190] Refactor cryptographic functionality Remove `signature` and `util/signature` packages. Re-implement their functionality in new `crypto` package. Generalize the approach of digital signature computation and verification by adding `Signer` and `PublicKey` primitives similar to standard `crypto` package. Support already exising in protocol signature schemes. Signed-off-by: Leonard Lyubich --- bearer/bearer.go | 52 +++++++++++---- client/container.go | 123 +++++++++++++++++++++++++----------- container/container.go | 8 +-- container/container_test.go | 10 --- crypto/crypto_test.go | 51 +++++++++++++++ crypto/doc.go | 48 ++++++++++++++ crypto/ecdsa/doc.go | 11 ++++ crypto/ecdsa/init.go | 16 +++++ crypto/ecdsa/public.go | 78 +++++++++++++++++++++++ crypto/ecdsa/signer.go | 86 +++++++++++++++++++++++++ crypto/signature.go | 85 +++++++++++++++++++++++++ crypto/signer.go | 83 ++++++++++++++++++++++++ eacl/table.go | 8 +-- eacl/table_test.go | 12 ---- object/fmt.go | 32 +++++----- object/fmt_test.go | 16 ----- object/id/id.go | 24 +++---- object/object.go | 27 ++++++-- object/raw_test.go | 14 ---- object/test/generate.go | 2 - pool/cache_test.go | 2 +- pool/pool_test.go | 3 +- reputation/peer.go | 3 +- reputation/test/generate.go | 3 +- reputation/trust.go | 62 +++++++++++++----- session/session.go | 55 ++++++++++------ session/session_test.go | 2 +- signature/signature.go | 94 --------------------------- signature/signature_test.go | 66 ------------------- signature/test/generate.go | 15 ----- util/signature/data.go | 119 ---------------------------------- util/signature/options.go | 114 --------------------------------- util/signature/util.go | 31 --------- 33 files changed, 728 insertions(+), 627 deletions(-) create mode 100644 crypto/crypto_test.go create mode 100644 crypto/doc.go create mode 100644 crypto/ecdsa/doc.go create mode 100644 crypto/ecdsa/init.go create mode 100644 crypto/ecdsa/public.go create mode 100644 crypto/ecdsa/signer.go create mode 100644 crypto/signature.go create mode 100644 crypto/signer.go delete mode 100644 signature/signature.go delete mode 100644 signature/signature_test.go delete mode 100644 signature/test/generate.go delete mode 100644 util/signature/data.go delete mode 100644 util/signature/options.go delete mode 100644 util/signature/util.go diff --git a/bearer/bearer.go b/bearer/bearer.go index a28ea01b..568dad14 100644 --- a/bearer/bearer.go +++ b/bearer/bearer.go @@ -4,14 +4,15 @@ import ( "crypto/ecdsa" "crypto/elliptic" "errors" + "fmt" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature" + "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/nspcc-dev/neofs-sdk-go/signature" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" ) var ( @@ -210,15 +211,27 @@ func (b *Token) Sign(key ecdsa.PrivateKey) error { return err } - v2 := (*acl.BearerToken)(b) - signWrapper := v2signature.StableMarshalerWrapper{SM: v2.GetBody()} + m := (*acl.BearerToken)(b) - sig, err := sigutil.SignData(&key, signWrapper) + data, err := m.GetBody().StableMarshal(nil) if err != nil { - return err + return fmt.Errorf("marshal body: %w", err) } - v2.SetSignature(sig.ToV2()) + var sig neofscrypto.Signature + var signer neofsecdsa.Signer + + signer.SetKey(key) + + err = sig.Calculate(signer, data) + if err != nil { + return fmt.Errorf("calculate signature: %w", err) + } + + var sigV2 refs.Signature + sig.WriteToV2(&sigV2) + + m.SetSignature(&sigV2) return nil } @@ -229,11 +242,26 @@ func (b Token) VerifySignature() error { return nil } - v2 := (acl.BearerToken)(b) + m := (acl.BearerToken)(b) - return sigutil.VerifyData( - v2signature.StableMarshalerWrapper{SM: v2.GetBody()}, - signature.NewFromV2(v2.GetSignature())) + sigV2 := m.GetSignature() + if sigV2 == nil { + return errors.New("missing signature") + } + + data, err := m.GetBody().StableMarshal(nil) + if err != nil { + return fmt.Errorf("marshal body: %w", err) + } + + var sig neofscrypto.Signature + sig.ReadFromV2(*sigV2) + + if !sig.Verify(data) { + return errors.New("wrong signature") + } + + return nil } // Issuer returns owner.ID associated with the key that signed bearer token. diff --git a/client/container.go b/client/container.go index 58428d15..889e8554 100644 --- a/client/container.go +++ b/client/container.go @@ -2,20 +2,20 @@ package client import ( "context" + "fmt" v2container "github.com/nspcc-dev/neofs-api-go/v2/container" "github.com/nspcc-dev/neofs-api-go/v2/refs" rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" v2session "github.com/nspcc-dev/neofs-api-go/v2/session" - v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature" "github.com/nspcc-dev/neofs-sdk-go/container" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/owner" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/signature" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" ) // PrmContainerPut groups parameters of ContainerPut operation. @@ -81,20 +81,33 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon } // TODO: check private key is set before forming the request + // sign container + cnr := prm.cnr.ToV2() + + data, err := cnr.StableMarshal(nil) + if err != nil { + return nil, fmt.Errorf("marshal container: %w", err) + } + + var sig neofscrypto.Signature + var signer neofsecdsa.Signer + + signer.SetKey(c.prm.key) + signer.MakeDeterministic() + + err = sig.Calculate(signer, data) + if err != nil { + return nil, fmt.Errorf("calculate signature: %w", err) + } + + var sigv2 refs.Signature + + sig.WriteToV2(&sigv2) // form request body reqBody := new(v2container.PutRequestBody) reqBody.SetContainer(prm.cnr.ToV2()) - - // sign container - signWrapper := v2signature.StableMarshalerWrapper{SM: reqBody.GetContainer()} - - sig, err := sigutil.SignData(&c.prm.key, signWrapper, sigutil.SignWithRFC6979()) - if err != nil { - return nil, err - } - - reqBody.SetSignature(sig.ToV2()) + reqBody.SetSignature(&sigv2) // form meta header var meta v2session.RequestMetaHeader @@ -235,9 +248,14 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon session.NewTokenFromV2(body.GetSessionToken()), ) - cnr.SetSignature( - signature.NewFromV2(body.GetSignature()), - ) + var sig *neofscrypto.Signature + + if sigv2 := body.GetSignature(); sigv2 != nil { + sig = new(neofscrypto.Signature) + sig.ReadFromV2(*sigv2) + } + + cnr.SetSignature(sig) res.setContainer(cnr) } @@ -414,22 +432,34 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (* panic(panicMsgMissingContainer) } + // sign container ID var cidV2 refs.ContainerID prm.id.WriteToV2(&cidV2) + data, err := cidV2.StableMarshal(nil) + if err != nil { + return nil, fmt.Errorf("marshal container ID: %w", err) + } + + var sig neofscrypto.Signature + var signer neofsecdsa.Signer + + signer.SetKey(c.prm.key) + signer.MakeDeterministic() + + err = sig.Calculate(signer, data) + if err != nil { + return nil, fmt.Errorf("calculate signature: %w", err) + } + + var sigv2 refs.Signature + + sig.WriteToV2(&sigv2) + // form request body reqBody := new(v2container.DeleteRequestBody) reqBody.SetContainerID(&cidV2) - - signWrapper := delContainerSignWrapper{body: reqBody} - - // sign container - sig, err := sigutil.SignData(&c.prm.key, signWrapper, sigutil.SignWithRFC6979()) - if err != nil { - return nil, err - } - - reqBody.SetSignature(sig.ToV2()) + reqBody.SetSignature(&sigv2) // form meta header var meta v2session.RequestMetaHeader @@ -558,9 +588,14 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC session.NewTokenFromV2(body.GetSessionToken()), ) - table.SetSignature( - signature.NewFromV2(body.GetSignature()), - ) + var sig *neofscrypto.Signature + + if sigv2 := body.GetSignature(); sigv2 != nil { + sig = new(neofscrypto.Signature) + sig.ReadFromV2(*sigv2) + } + + table.SetSignature(sig) res.setTable(table) } @@ -620,19 +655,33 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) panic("eACL table not set") } - // form request body - reqBody := new(v2container.SetExtendedACLRequestBody) - reqBody.SetEACL(prm.table.ToV2()) - // sign the eACL table - signWrapper := v2signature.StableMarshalerWrapper{SM: reqBody.GetEACL()} + eaclV2 := prm.table.ToV2() - sig, err := sigutil.SignData(&c.prm.key, signWrapper, sigutil.SignWithRFC6979()) + data, err := eaclV2.StableMarshal(nil) if err != nil { - return nil, err + return nil, fmt.Errorf("marshal eACL: %w", err) } - reqBody.SetSignature(sig.ToV2()) + var sig neofscrypto.Signature + var signer neofsecdsa.Signer + + signer.SetKey(c.prm.key) + signer.MakeDeterministic() + + err = sig.Calculate(signer, data) + if err != nil { + return nil, fmt.Errorf("calculate signature: %w", err) + } + + var sigv2 refs.Signature + + sig.WriteToV2(&sigv2) + + // form request body + reqBody := new(v2container.SetExtendedACLRequestBody) + reqBody.SetEACL(eaclV2) + reqBody.SetSignature(&sigv2) // form meta header var meta v2session.RequestMetaHeader diff --git a/container/container.go b/container/container.go index 47bed79f..6512effa 100644 --- a/container/container.go +++ b/container/container.go @@ -8,10 +8,10 @@ import ( "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/owner" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/signature" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -20,7 +20,7 @@ type Container struct { token *session.Token - sig *signature.Signature + sig *neofscrypto.Signature } // New creates, initializes and returns blank Container instance. @@ -171,12 +171,12 @@ func (c *Container) SetSessionToken(t *session.Token) { } // Signature returns signature of the marshaled container. -func (c Container) Signature() *signature.Signature { +func (c Container) Signature() *neofscrypto.Signature { return c.sig } // SetSignature sets signature of the marshaled container. -func (c *Container) SetSignature(sig *signature.Signature) { +func (c *Container) SetSignature(sig *neofscrypto.Signature) { c.sig = sig } diff --git a/container/container_test.go b/container/container_test.go index 2d00c674..ee4e938f 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -11,7 +11,6 @@ import ( netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test" ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" - sigtest "github.com/nspcc-dev/neofs-sdk-go/signature/test" "github.com/nspcc-dev/neofs-sdk-go/version" versiontest "github.com/nspcc-dev/neofs-sdk-go/version/test" "github.com/stretchr/testify/require" @@ -86,15 +85,6 @@ func TestContainer_SessionToken(t *testing.T) { require.Equal(t, tok, cnr.SessionToken()) } -func TestContainer_Signature(t *testing.T) { - sig := sigtest.Signature() - - cnr := container.New() - cnr.SetSignature(sig) - - require.Equal(t, sig, cnr.Signature()) -} - func TestContainer_ToV2(t *testing.T) { t.Run("nil", func(t *testing.T) { var x *container.Container diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go new file mode 100644 index 00000000..f5cb31bf --- /dev/null +++ b/crypto/crypto_test.go @@ -0,0 +1,51 @@ +package neofscrypto_test + +import ( + "math/rand" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" + "github.com/stretchr/testify/require" +) + +func TestSignature(t *testing.T) { + data := make([]byte, 512) + rand.Read(data) + + k, err := keys.NewPrivateKey() + require.NoError(t, err) + + var s neofscrypto.Signature + var m refs.Signature + + for _, f := range []func() neofscrypto.Signer{ + func() neofscrypto.Signer { + var key neofsecdsa.Signer + key.SetKey(k.PrivateKey) + + return &key + }, + func() neofscrypto.Signer { + var key neofsecdsa.Signer + key.SetKey(k.PrivateKey) + key.MakeDeterministic() + + return &key + }, + } { + signer := f() + + err := s.Calculate(signer, data) + require.NoError(t, err) + + s.WriteToV2(&m) + + s.ReadFromV2(m) + + valid := s.Verify(data) + require.True(t, valid) + } +} diff --git a/crypto/doc.go b/crypto/doc.go new file mode 100644 index 00000000..b3566f00 --- /dev/null +++ b/crypto/doc.go @@ -0,0 +1,48 @@ +/* +Package neofscrypto collects NeoFS cryptographic primitives. + +Signer type unifies entities for signing NeoFS data. + // instantiate Signer + // select data to be signed + + var sig Signature + + err := sig.Calculate(signer, data) + // ... + + // attach signature to the request + +SDK natively supports several signature schemes that are implemented +in nested packages. + +PublicKey allows to verify signatures. + // get signature to be verified + // compose signed data + + isValid := sig.Verify(data) + // ... + +Signature can be also used to process NeoFS API V2 protocol messages +(see neo.fs.v2.refs package in https://github.com/nspcc-dev/neofs-api). + +On client side: + import "github.com/nspcc-dev/neofs-api-go/v2/refs" + + var msg refs.Signature + sig.WriteToV2(&msg) + + // send msg + +On server side: + // recv msg + + var sig neofscrypto.Signature + sig.ReadFromV2(msg) + + // process sig + +Using package types in an application is recommended to potentially work with +different protocol versions with which these types are compatible. + +*/ +package neofscrypto diff --git a/crypto/ecdsa/doc.go b/crypto/ecdsa/doc.go new file mode 100644 index 00000000..1cdc9da1 --- /dev/null +++ b/crypto/ecdsa/doc.go @@ -0,0 +1,11 @@ +/* +Package neofsecdsa collects ECDSA primitives for NeoFS cryptography. + +Signer and PublicKey provide corresponding interfaces from neofscrypto package. + +Package import causes registration of next signature schemes via neofscrypto.RegisterScheme: + - neofscrypto.ECDSA_SHA512 + - neofscrypto.ECDSA_DETERMINISTIC_SHA256 + +*/ +package neofsecdsa diff --git a/crypto/ecdsa/init.go b/crypto/ecdsa/init.go new file mode 100644 index 00000000..36e256d7 --- /dev/null +++ b/crypto/ecdsa/init.go @@ -0,0 +1,16 @@ +package neofsecdsa + +import neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + +func init() { + neofscrypto.RegisterScheme(neofscrypto.ECDSA_SHA512, func() neofscrypto.PublicKey { + return new(PublicKey) + }) + + neofscrypto.RegisterScheme(neofscrypto.ECDSA_DETERMINISTIC_SHA256, func() neofscrypto.PublicKey { + var key PublicKey + key.MakeDeterministic() + + return &key + }) +} diff --git a/crypto/ecdsa/public.go b/crypto/ecdsa/public.go new file mode 100644 index 00000000..ddaf4139 --- /dev/null +++ b/crypto/ecdsa/public.go @@ -0,0 +1,78 @@ +package neofsecdsa + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/sha256" + "crypto/sha512" + "math/big" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" +) + +// PublicKey implements neofscrypto.PublicKey based on ECDSA. +// +// Supported schemes: +// - neofscrypto.ECDSA_SHA512 (default) +// - neofscrypto.ECDSA_DETERMINISTIC_SHA256 +type PublicKey struct { + deterministic bool + + key ecdsa.PublicKey +} + +// MakeDeterministic makes PublicKey to use Deterministic ECDSA scheme +// (see neofscrypto.ECDSA_DETERMINISTIC_SHA256). By default, +// neofscrypto.ECDSA_SHA512 is used. +func (x *PublicKey) MakeDeterministic() { + x.deterministic = true +} + +// Decode decodes compressed binary representation of the PublicKey. +// +// See also Signer.EncodePublicKey. +func (x *PublicKey) Decode(data []byte) error { + pub, err := keys.NewPublicKeyFromBytes(data, elliptic.P256()) + if err != nil { + return err + } + + x.key = (ecdsa.PublicKey)(*pub) + + return nil +} + +// similar to elliptic.Unmarshal but without IsOnCurve check. +func unmarshalXY(data []byte) (x *big.Int, y *big.Int) { + if len(data) != 65 { + return + } else if data[0] != 4 { // uncompressed form + return + } + + p := elliptic.P256().Params().P + x = new(big.Int).SetBytes(data[1:33]) + y = new(big.Int).SetBytes(data[33:]) + + if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 { + x, y = nil, nil + } + + return +} + +// Verify verifies data signature calculated by algorithm depending on +// PublicKey state: +// - Deterministic ECDSA with SHA-256 hashing if MakeDeterministic called +// - ECDSA with SHA-512 hashing, otherwise +func (x PublicKey) Verify(data, signature []byte) bool { + if x.deterministic { + h := sha256.Sum256(data) + return (*keys.PublicKey)(&x.key).Verify(signature, h[:]) + } + + h := sha512.Sum512(data) + r, s := unmarshalXY(signature) + + return r != nil && s != nil && ecdsa.Verify(&x.key, h[:], r, s) +} diff --git a/crypto/ecdsa/signer.go b/crypto/ecdsa/signer.go new file mode 100644 index 00000000..4f4247af --- /dev/null +++ b/crypto/ecdsa/signer.go @@ -0,0 +1,86 @@ +package neofsecdsa + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha512" + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" +) + +// Signer implements neofscrypto.Signer based on ECDSA. +// +// Supported schemes: +// - neofscrypto.ECDSA_SHA512 (default) +// - neofscrypto.ECDSA_DETERMINISTIC_SHA256 +// +// Instances MUST be initialized with ecdsa.PrivateKey using SetKey. +type Signer struct { + deterministic bool + + key ecdsa.PrivateKey +} + +// SetKey specifies ecdsa.PrivateKey to be used for ECDSA signature calculation. +func (x *Signer) SetKey(key ecdsa.PrivateKey) { + x.key = key +} + +// MakeDeterministic makes Signer to use Deterministic ECDSA scheme +// (see neofscrypto.ECDSA_DETERMINISTIC_SHA256). By default, +// neofscrypto.ECDSA_SHA512 is used. +func (x *Signer) MakeDeterministic() { + x.deterministic = true +} + +// Scheme returns signature scheme depending on Signer state: +// - neofscrypto.ECDSA_DETERMINISTIC_SHA256 if MakeDeterministic called +// - neofscrypto.ECDSA_SHA512 otherwise. +func (x Signer) Scheme() neofscrypto.Scheme { + if x.deterministic { + return neofscrypto.ECDSA_DETERMINISTIC_SHA256 + } + + return neofscrypto.ECDSA_SHA512 +} + +// Sign signs data with algorithm depending on Signer state: +// - Deterministic ECDSA with SHA-256 hashing if MakeDeterministic called +// - ECDSA with SHA-512 hashing, otherwise +func (x Signer) Sign(data []byte) ([]byte, error) { + if x.deterministic { + p := keys.PrivateKey{PrivateKey: x.key} + return p.Sign(data), nil + } + + h := sha512.Sum512(data) + r, s, err := ecdsa.Sign(rand.Reader, &x.key, h[:]) + if err != nil { + return nil, err + } + + return elliptic.Marshal(elliptic.P256(), r, s), nil +} + +// MaxPublicKeyEncodedSize returns size of the compressed ECDSA public key +// of the Signer. +func (x Signer) MaxPublicKeyEncodedSize() int { + return 33 +} + +// EncodePublicKey encodes ECDSA public key of the Signer in compressed form +// into buf. Uses exactly MaxPublicKeyEncodedSize bytes of the buf. +// +// EncodePublicKey panics if buf length is less than MaxPublicKeyEncodedSize. +// +// See also PublicKey.Decode. +func (x Signer) EncodePublicKey(buf []byte) int { + if len(buf) < 33 { + panic(fmt.Sprintf("too short buffer %d", len(buf))) + } + + return copy(buf, (*keys.PublicKey)(&x.key.PublicKey).Bytes()) +} diff --git a/crypto/signature.go b/crypto/signature.go new file mode 100644 index 00000000..fa050f00 --- /dev/null +++ b/crypto/signature.go @@ -0,0 +1,85 @@ +package neofscrypto + +import ( + "fmt" + + "github.com/nspcc-dev/neofs-api-go/v2/refs" +) + +// Signature represents a confirmation of data integrity received by the +// digital signature mechanism. +// +// Signature is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Signature +// message. See ReadFromV2 / WriteToV2 methods. +// +// Note that direct typecast is not safe and may result in loss of compatibility: +// _ = Signature(refs.Signature{}) // not recommended +type Signature refs.Signature + +// ReadFromV2 reads Signature from the refs.Signature message. +// +// See also WriteToV2. +func (x *Signature) ReadFromV2(m refs.Signature) { + *x = Signature(m) +} + +// WriteToV2 writes Signature to the refs.Signature message. +// The message must not be nil. +// +// See also ReadFromV2. +func (x Signature) WriteToV2(m *refs.Signature) { + *m = (refs.Signature)(x) +} + +// Calculate signs data using Signer and encodes public key for subsequent +// verification. +// +// Signer MUST NOT be nil. +// +// See also Verify. +func (x *Signature) Calculate(signer Signer, data []byte) error { + signature, err := signer.Sign(data) + if err != nil { + return fmt.Errorf("signer %T failure: %w", signer, err) + } + + key := make([]byte, signer.MaxPublicKeyEncodedSize()) + key = key[:signer.EncodePublicKey(key)] + + m := (*refs.Signature)(x) + + scheme := refs.SignatureScheme(signer.Scheme()) + if scheme > 0 { + scheme-- // to sync numeric values + } + + m.SetScheme(scheme) + m.SetSign(signature) + m.SetKey(key) + + return nil +} + +// Verify verifies data signature using encoded public key. True means valid +// signature. +// +// Verify fails if signature scheme is not supported (see RegisterScheme). +// +// See also Calculate. +func (x Signature) Verify(data []byte) bool { + m := (*refs.Signature)(&x) + + f, ok := publicKeys[Scheme(m.GetScheme()+1)] // increment to sync numeric values + if !ok { + return false + } + + key := f() + + err := key.Decode(m.GetKey()) + if err != nil { + return false + } + + return key.Verify(data, m.GetSign()) +} diff --git a/crypto/signer.go b/crypto/signer.go new file mode 100644 index 00000000..ec55fbc7 --- /dev/null +++ b/crypto/signer.go @@ -0,0 +1,83 @@ +package neofscrypto + +import ( + "fmt" +) + +// Scheme represents digital signature algorithm with fixed cryptographic hash function. +// +// Non-positive values are reserved and depend on context (e.g. unsupported scheme). +type Scheme uint32 + +//nolint:revive +const ( + _ Scheme = iota + + ECDSA_SHA512 // ECDSA with SHA-512 hashing (FIPS 186-3) + ECDSA_DETERMINISTIC_SHA256 // Deterministic ECDSA with SHA-256 hashing (RFC 6979) +) + +// maps Scheme to blank PublicKey constructor. +var publicKeys = make(map[Scheme]func() PublicKey) + +// RegisterScheme registers a function that returns a new blank PublicKey +// instance for the given Scheme. This is intended to be called from the init +// function in packages that implement signature schemes. +// +// RegisterScheme panics if function for the given Scheme is already registered. +func RegisterScheme(scheme Scheme, f func() PublicKey) { + _, ok := publicKeys[scheme] + if ok { + panic(fmt.Sprintf("scheme %v is already registered", scheme)) + } + + publicKeys[scheme] = f +} + +// Signer is an interface of entities that can be used for signing operations +// in NeoFS. Unites secret and public parts. For example, an ECDSA private key +// or external auth service. +// +// See also PublicKey. +type Signer interface { + // Scheme returns corresponding signature scheme. + Scheme() Scheme + + // Sign signs digest of the given data. Implementations encapsulate data + // hashing that depends on Scheme. For example, if scheme uses SHA-256, then + // Sign signs SHA-256 hash of the data. + Sign(data []byte) ([]byte, error) + + // MaxPublicKeyEncodedSize returns maximum size required for binary-encoded + // public key. + // + // MaxPublicKeyEncodedSize MUST NOT return value greater than any return of + // EncodePublicKey. + MaxPublicKeyEncodedSize() int + + // EncodePublicKey encodes public key into buf. Returns number of bytes + // written. + // + // EncodePublicKey MUST panic if buffer size is insufficient and less than + // MaxPublicKeyEncodedSize (*). EncodePublicKey MUST return negative value + // on any failure except (*). + // + // EncodePublicKey is expected to be compatible with PublicKey.Decode for + // similar signature schemes. + EncodePublicKey(buf []byte) int +} + +// PublicKey represents a public key using fixed signature scheme supported by +// NeoFS. +// +// See also Signer. +type PublicKey interface { + // Decode decodes binary public key. + // + // Decode is expected to be compatible with Signer.EncodePublicKey for + // similar signature schemes. + Decode([]byte) error + + // Verify checks signature of the given data. True means correct signature. + Verify(data, signature []byte) bool +} diff --git a/eacl/table.go b/eacl/table.go index 4cdfe93b..ff1e2cbb 100644 --- a/eacl/table.go +++ b/eacl/table.go @@ -8,8 +8,8 @@ import ( v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/signature" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -20,7 +20,7 @@ type Table struct { version version.Version cid *cid.ID token *session.Token - sig *signature.Signature + sig *neofscrypto.Signature records []Record } @@ -74,12 +74,12 @@ func (t *Table) SetSessionToken(tok *session.Token) { } // Signature returns Table signature. -func (t Table) Signature() *signature.Signature { +func (t Table) Signature() *neofscrypto.Signature { return t.sig } // SetSignature sets Table signature. -func (t *Table) SetSignature(sig *signature.Signature) { +func (t *Table) SetSignature(sig *neofscrypto.Signature) { t.sig = sig } diff --git a/eacl/table_test.go b/eacl/table_test.go index 800d3b05..a38acdb7 100644 --- a/eacl/table_test.go +++ b/eacl/table_test.go @@ -9,7 +9,6 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/eacl" eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" - "github.com/nspcc-dev/neofs-sdk-go/signature" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/stretchr/testify/require" ) @@ -102,17 +101,6 @@ func TestTable_SessionToken(t *testing.T) { require.Equal(t, tok, table.SessionToken()) } -func TestTable_Signature(t *testing.T) { - sig := signature.New() - sig.SetKey([]byte{1, 2, 3}) - sig.SetSign([]byte{4, 5, 6}) - - table := eacl.NewTable() - table.SetSignature(sig) - - require.Equal(t, sig, table.Signature()) -} - func TestTable_ToV2(t *testing.T) { t.Run("nil", func(t *testing.T) { var x *eacl.Table diff --git a/object/fmt.go b/object/fmt.go index 168d7228..b893e8b5 100644 --- a/object/fmt.go +++ b/object/fmt.go @@ -7,11 +7,10 @@ import ( "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - signatureV2 "github.com/nspcc-dev/neofs-api-go/v2/signature" + "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-sdk-go/checksum" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" ) var ( @@ -120,24 +119,27 @@ func CalculateAndSetSignature(key ecdsa.PrivateKey, obj *Object) error { // VerifyIDSignature verifies object ID signature. func (o *Object) VerifyIDSignature() bool { - oID, set := o.ID() - if !set { + m := (*object.Object)(o) + + sigV2 := m.GetSignature() + if sigV2 == nil { return false } - var idV2 refs.ObjectID - oID.WriteToV2(&idV2) + idV2 := m.GetObjectID() + if idV2 == nil { + return false + } - sig := o.Signature() + data, err := idV2.StableMarshal(nil) + if err != nil { + return false + } - err := sigutil.VerifyData( - signatureV2.StableMarshalerWrapper{ - SM: &idV2, - }, - sig, - ) + var sig neofscrypto.Signature + sig.ReadFromV2(*sigV2) - return err == nil + return sig.Verify(data) } // SetIDWithSignature sets object identifier and signature. diff --git a/object/fmt_test.go b/object/fmt_test.go index aa22e755..b7470807 100644 --- a/object/fmt_test.go +++ b/object/fmt_test.go @@ -51,22 +51,6 @@ func TestVerificationFields(t *testing.T) { obj.ToV2().GetObjectID().GetValue()[0]-- }, }, - { - corrupt: func() { - obj.Signature().Key()[0]++ - }, - restore: func() { - obj.Signature().Key()[0]-- - }, - }, - { - corrupt: func() { - obj.Signature().Sign()[0]++ - }, - restore: func() { - obj.Signature().Sign()[0]-- - }, - }, } for _, item := range items { diff --git a/object/id/id.go b/object/id/id.go index dc5990de..05ad3c3a 100644 --- a/object/id/id.go +++ b/object/id/id.go @@ -7,9 +7,8 @@ import ( "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-api-go/v2/refs" - signatureV2 "github.com/nspcc-dev/neofs-api-go/v2/signature" - "github.com/nspcc-dev/neofs-sdk-go/signature" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" ) // ID represents NeoFS object identifier in a container. @@ -117,17 +116,18 @@ func (id ID) String() string { } // CalculateIDSignature signs object id with provided key. -func (id ID) CalculateIDSignature(key ecdsa.PrivateKey) (signature.Signature, error) { - var idV2 refs.ObjectID - id.WriteToV2(&idV2) +func (id ID) CalculateIDSignature(key ecdsa.PrivateKey) (neofscrypto.Signature, error) { + data, err := id.Marshal() + if err != nil { + return neofscrypto.Signature{}, fmt.Errorf("marshal ID: %w", err) + } - sign, err := sigutil.SignData(&key, - signatureV2.StableMarshalerWrapper{ - SM: &idV2, - }, - ) + var sig neofscrypto.Signature + var signer neofsecdsa.Signer - return *sign, err + signer.SetKey(key) + + return sig, sig.Calculate(signer, data) } // Marshal marshals ID into a protobuf binary form. diff --git a/object/object.go b/object/object.go index 05eb9011..191b2170 100644 --- a/object/object.go +++ b/object/object.go @@ -8,10 +8,10 @@ import ( "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/checksum" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/owner" "github.com/nspcc-dev/neofs-sdk-go/session" - "github.com/nspcc-dev/neofs-sdk-go/signature" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -109,14 +109,29 @@ func (o *Object) SetID(v oid.ID) { } // Signature returns signature of the object identifier. -func (o *Object) Signature() *signature.Signature { - return signature.NewFromV2( - (*object.Object)(o).GetSignature()) +func (o *Object) Signature() *neofscrypto.Signature { + sigv2 := (*object.Object)(o).GetSignature() + if sigv2 == nil { + return nil + } + + var sig neofscrypto.Signature + sig.ReadFromV2(*sigv2) + + return &sig } // SetSignature sets signature of the object identifier. -func (o *Object) SetSignature(v *signature.Signature) { - (*object.Object)(o).SetSignature(v.ToV2()) +func (o *Object) SetSignature(v *neofscrypto.Signature) { + var sigv2 *refs.Signature + + if v != nil { + sigv2 = new(refs.Signature) + + v.WriteToV2(sigv2) + } + + (*object.Object)(o).SetSignature(sigv2) } // Payload returns payload bytes. diff --git a/object/raw_test.go b/object/raw_test.go index 0db51b37..ea27a07b 100644 --- a/object/raw_test.go +++ b/object/raw_test.go @@ -11,7 +11,6 @@ import ( oid "github.com/nspcc-dev/neofs-sdk-go/object/id" ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" - "github.com/nspcc-dev/neofs-sdk-go/signature" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/stretchr/testify/require" ) @@ -49,18 +48,6 @@ func TestObject_SetID(t *testing.T) { require.Equal(t, id, oID) } -func TestObject_SetSignature(t *testing.T) { - obj := New() - - sig := signature.New() - sig.SetKey([]byte{1, 2, 3}) - sig.SetSign([]byte{4, 5, 6}) - - obj.SetSignature(sig) - - require.Equal(t, sig, obj.Signature()) -} - func TestObject_SetPayload(t *testing.T) { obj := New() @@ -208,7 +195,6 @@ func TestObject_SetParent(t *testing.T) { par := New() par.SetID(randID(t)) par.SetContainerID(cidtest.ID()) - par.SetSignature(signature.New()) obj.SetParent(par) diff --git a/object/test/generate.go b/object/test/generate.go index 3ec9e007..0f5df889 100644 --- a/object/test/generate.go +++ b/object/test/generate.go @@ -9,7 +9,6 @@ import ( oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" - sigtest "github.com/nspcc-dev/neofs-sdk-go/signature/test" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -62,7 +61,6 @@ func generate(withParent bool) *object.Object { x.SetSplitID(SplitID()) x.SetPayloadChecksum(checksumtest.Checksum()) x.SetPayloadHomomorphicHash(checksumtest.Checksum()) - x.SetSignature(sigtest.Signature()) if withParent { x.SetParent(generate(false)) diff --git a/pool/cache_test.go b/pool/cache_test.go index 5a5e2a28..c1702c38 100644 --- a/pool/cache_test.go +++ b/pool/cache_test.go @@ -17,7 +17,7 @@ func TestSessionCache_GetUnmodifiedToken(t *testing.T) { require.NoError(t, err) check := func(t *testing.T, tok *session.Token, extra string) { - require.Empty(t, tok.Signature().Sign(), extra) + require.False(t, tok.VerifySignature(), extra) require.Nil(t, tok.Context(), extra) } diff --git a/pool/pool_test.go b/pool/pool_test.go index 3466674d..41c00808 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -669,8 +669,7 @@ func TestCopySessionTokenWithoutSignatureAndContext(t *testing.T) { require.Equal(t, from.OwnerID().String(), to.OwnerID().String()) require.Equal(t, from.SessionKey(), to.SessionKey()) - require.Empty(t, to.Signature().Sign()) - require.Empty(t, to.Signature().Key()) + require.False(t, to.VerifySignature()) t.Run("empty object context", func(t *testing.T) { octx := sessiontest.ObjectContext() diff --git a/reputation/peer.go b/reputation/peer.go index 5d50455d..1edd2a64 100644 --- a/reputation/peer.go +++ b/reputation/peer.go @@ -5,7 +5,6 @@ import ( "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-api-go/v2/reputation" - "github.com/nspcc-dev/neofs-sdk-go/util/signature" ) // PeerID represents peer ID compatible with NeoFS API v2. @@ -27,7 +26,7 @@ func PeerIDFromV2(id *reputation.PeerID) *PeerID { } // SetPublicKey sets peer ID as a compressed public key. -func (x *PeerID) SetPublicKey(v [signature.PublicKeyCompressedSize]byte) { +func (x *PeerID) SetPublicKey(v [33]byte) { (*reputation.PeerID)(x).SetPublicKey(v[:]) } diff --git a/reputation/test/generate.go b/reputation/test/generate.go index 01776431..622254f0 100644 --- a/reputation/test/generate.go +++ b/reputation/test/generate.go @@ -5,7 +5,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-sdk-go/reputation" - "github.com/nspcc-dev/neofs-sdk-go/util/signature" "github.com/stretchr/testify/require" ) @@ -17,7 +16,7 @@ func PeerID() *reputation.PeerID { panic(err) } - key := [signature.PublicKeyCompressedSize]byte{} + key := [33]byte{} copy(key[:], p.Bytes()) v.SetPublicKey(key) diff --git a/reputation/trust.go b/reputation/trust.go index b55b7b04..9485a289 100644 --- a/reputation/trust.go +++ b/reputation/trust.go @@ -2,12 +2,13 @@ package reputation import ( "crypto/ecdsa" + "errors" + "fmt" "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/reputation" - signatureV2 "github.com/nspcc-dev/neofs-api-go/v2/signature" - "github.com/nspcc-dev/neofs-sdk-go/signature" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -264,31 +265,58 @@ func (x *GlobalTrust) Trust() *Trust { // Sign signs global trust value with key. func (x *GlobalTrust) Sign(key *ecdsa.PrivateKey) error { - v2 := (*reputation.GlobalTrust)(x) - - sig, err := sigutil.SignData(key, - signatureV2.StableMarshalerWrapper{SM: v2.GetBody()}) - if err != nil { - return err + if key == nil { + return errors.New("nil private key") } - v2.SetSignature(sig.ToV2()) + m := (*reputation.GlobalTrust)(x) + + data, err := m.GetBody().StableMarshal(nil) + if err != nil { + return fmt.Errorf("marshal body: %w", err) + } + + var sig neofscrypto.Signature + var signer neofsecdsa.Signer + + signer.SetKey(*key) + + err = sig.Calculate(signer, data) + if err != nil { + return fmt.Errorf("calculate signature: %w", err) + } + + var sigv2 refs.Signature + + sig.WriteToV2(&sigv2) + + m.SetSignature(&sigv2) + return nil } // VerifySignature verifies global trust signature. func (x *GlobalTrust) VerifySignature() error { - v2 := (*reputation.GlobalTrust)(x) + m := (*reputation.GlobalTrust)(x) - sigV2 := v2.GetSignature() + sigV2 := m.GetSignature() if sigV2 == nil { - sigV2 = new(refs.Signature) + return errors.New("missing signature") } - return sigutil.VerifyData( - signatureV2.StableMarshalerWrapper{SM: v2.GetBody()}, - signature.NewFromV2(sigV2), - ) + data, err := m.GetBody().StableMarshal(nil) + if err != nil { + return fmt.Errorf("marshal body: %w", err) + } + + var sig neofscrypto.Signature + sig.ReadFromV2(*sigV2) + + if !sig.Verify(data) { + return errors.New("wrong signature") + } + + return nil } // Marshal marshals GlobalTrust into a protobuf binary form. diff --git a/session/session.go b/session/session.go index 0c905a93..60066e2f 100644 --- a/session/session.go +++ b/session/session.go @@ -2,12 +2,14 @@ package session import ( "crypto/ecdsa" + "errors" + "fmt" + "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/session" - v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/owner" - "github.com/nspcc-dev/neofs-sdk-go/signature" - sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature" ) // Token represents NeoFS API v2-compatible @@ -162,18 +164,32 @@ func (t *Token) SetIat(iat uint64) { // // Returns signature calculation errors. func (t *Token) Sign(key *ecdsa.PrivateKey) error { - tV2 := (*session.Token)(t) - - signedData := v2signature.StableMarshalerWrapper{ - SM: tV2.GetBody(), + if key == nil { + return errors.New("nil private key") } - sig, err := sigutil.SignData(key, signedData) + tV2 := (*session.Token)(t) + + digest, err := tV2.GetBody().StableMarshal(nil) + if err != nil { + panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err)) + } + + var sig neofscrypto.Signature + var signer neofsecdsa.Signer + + signer.SetKey(*key) + + err = sig.Calculate(signer, digest) if err != nil { return err } - tV2.SetSignature(sig.ToV2()) + var sigV2 refs.Signature + sig.WriteToV2(&sigV2) + + tV2.SetSignature(&sigV2) + return nil } @@ -182,19 +198,20 @@ func (t *Token) Sign(key *ecdsa.PrivateKey) error { func (t *Token) VerifySignature() bool { tV2 := (*session.Token)(t) - signedData := v2signature.StableMarshalerWrapper{ - SM: tV2.GetBody(), + digest, err := tV2.GetBody().StableMarshal(nil) + if err != nil { + panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err)) } - return sigutil.VerifyData(signedData, t.Signature()) == nil -} + sigV2 := tV2.GetSignature() + if sigV2 == nil { + return false + } -// Signature returns Token signature. -func (t *Token) Signature() *signature.Signature { - return signature.NewFromV2( - (*session.Token)(t). - GetSignature(), - ) + var sig neofscrypto.Signature + sig.ReadFromV2(*sigV2) + + return sig.Verify(digest) } // SetContext sets context of the Token. diff --git a/session/session_test.go b/session/session_test.go index b729a2f1..b0271144 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -185,7 +185,7 @@ func TestNewToken(t *testing.T) { token := session.NewToken() // check initial values - require.Nil(t, token.Signature()) + require.False(t, token.VerifySignature()) require.Nil(t, token.OwnerID()) require.Nil(t, token.SessionKey()) require.Nil(t, token.ID()) diff --git a/signature/signature.go b/signature/signature.go deleted file mode 100644 index 75b78439..00000000 --- a/signature/signature.go +++ /dev/null @@ -1,94 +0,0 @@ -package signature - -import ( - "github.com/nspcc-dev/neofs-api-go/v2/refs" -) - -// Signature represents v2-compatible signature. -type Signature refs.Signature - -// Scheme represents signature scheme. -type Scheme uint32 - -// Supported signature schemes. -const ( - ECDSAWithSHA512 Scheme = iota - RFC6979WithSHA256 -) - -func (x Scheme) String() string { - return refs.SignatureScheme(x).String() -} - -// NewFromV2 wraps v2 Signature message to Signature. -// -// Nil refs.Signature converts to nil. -func NewFromV2(sV2 *refs.Signature) *Signature { - return (*Signature)(sV2) -} - -// New creates and initializes blank Signature. -// -// Defaults: -// - key: nil; -// - signature: nil. -func New() *Signature { - return NewFromV2(new(refs.Signature)) -} - -// Key sets binary public key. -func (s *Signature) Key() []byte { - return (*refs.Signature)(s).GetKey() -} - -// SetKey returns binary public key. -func (s *Signature) SetKey(v []byte) { - (*refs.Signature)(s).SetKey(v) -} - -// Sign return signature value. -func (s *Signature) Sign() []byte { - return (*refs.Signature)(s).GetSign() -} - -// SetSign sets signature value. -func (s *Signature) SetSign(v []byte) { - (*refs.Signature)(s).SetSign(v) -} - -// Scheme returns signature scheme. -func (s *Signature) Scheme() Scheme { - return Scheme((*refs.Signature)(s).GetScheme()) -} - -// SetScheme sets signature scheme. -func (s *Signature) SetScheme(v Scheme) { - (*refs.Signature)(s).SetScheme(refs.SignatureScheme(v)) -} - -// ToV2 converts Signature to v2 Signature message. -// -// Nil Signature converts to nil. -func (s *Signature) ToV2() *refs.Signature { - return (*refs.Signature)(s) -} - -// Marshal marshals Signature into a protobuf binary form. -func (s *Signature) Marshal() ([]byte, error) { - return (*refs.Signature)(s).StableMarshal(nil) -} - -// Unmarshal unmarshals protobuf binary representation of Signature. -func (s *Signature) Unmarshal(data []byte) error { - return (*refs.Signature)(s).Unmarshal(data) -} - -// MarshalJSON encodes Signature to protobuf JSON format. -func (s *Signature) MarshalJSON() ([]byte, error) { - return (*refs.Signature)(s).MarshalJSON() -} - -// UnmarshalJSON decodes Signature from protobuf JSON format. -func (s *Signature) UnmarshalJSON(data []byte) error { - return (*refs.Signature)(s).UnmarshalJSON(data) -} diff --git a/signature/signature_test.go b/signature/signature_test.go deleted file mode 100644 index 479546c6..00000000 --- a/signature/signature_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package signature - -import ( - "testing" - - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/stretchr/testify/require" -) - -func TestSignatureEncoding(t *testing.T) { - s := New() - s.SetKey([]byte("key")) - s.SetSign([]byte("sign")) - - t.Run("binary", func(t *testing.T) { - data, err := s.Marshal() - require.NoError(t, err) - - s2 := New() - require.NoError(t, s2.Unmarshal(data)) - - require.Equal(t, s, s2) - }) - - t.Run("json", func(t *testing.T) { - data, err := s.MarshalJSON() - require.NoError(t, err) - - s2 := New() - require.NoError(t, s2.UnmarshalJSON(data)) - - require.Equal(t, s, s2) - }) -} - -func TestNewSignatureFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *refs.Signature - - require.Nil(t, NewFromV2(x)) - }) -} - -func TestSignature_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *Signature - - require.Nil(t, x.ToV2()) - }) -} - -func TestNewSignature(t *testing.T) { - t.Run("default values", func(t *testing.T) { - sg := New() - - // check initial values - require.Nil(t, sg.Key()) - require.Nil(t, sg.Sign()) - - // convert to v2 message - sgV2 := sg.ToV2() - - require.Nil(t, sgV2.GetKey()) - require.Nil(t, sgV2.GetSign()) - }) -} diff --git a/signature/test/generate.go b/signature/test/generate.go deleted file mode 100644 index 80edc589..00000000 --- a/signature/test/generate.go +++ /dev/null @@ -1,15 +0,0 @@ -package sigtest - -import ( - "github.com/nspcc-dev/neofs-sdk-go/signature" -) - -// Signature returns random pkg.Signature. -func Signature() *signature.Signature { - x := signature.New() - - x.SetKey([]byte("key")) - x.SetSign([]byte("sign")) - - return x -} diff --git a/util/signature/data.go b/util/signature/data.go deleted file mode 100644 index e18960ff..00000000 --- a/util/signature/data.go +++ /dev/null @@ -1,119 +0,0 @@ -package signature - -import ( - "crypto/ecdsa" - "errors" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-sdk-go/signature" -) - -type DataSource interface { - ReadSignedData([]byte) ([]byte, error) - SignedDataSize() int -} - -type DataWithSignature interface { - DataSource - GetSignature() *signature.Signature - SetSignature(*signature.Signature) -} - -type SignOption func(*cfg) - -const ( - // PrivateKeyCompressedSize is constant with compressed size of private key (SK). - // D coordinate stored, recover PK by formula x, y = curve.ScalarBaseMul(d,bytes). - PrivateKeyCompressedSize = 32 - - // PublicKeyCompressedSize is constant with compressed size of public key (PK). - PublicKeyCompressedSize = 33 - - // PublicKeyUncompressedSize is constant with uncompressed size of public key (PK). - // First byte always should be 0x4 other 64 bytes is X and Y (32 bytes per coordinate). - // 2 * 32 + 1. - PublicKeyUncompressedSize = 65 -) - -var ( - // ErrEmptyPrivateKey is returned when used private key is empty. - ErrEmptyPrivateKey = errors.New("empty private key") - // ErrInvalidPublicKey is returned when public key cannot be unmarshalled. - ErrInvalidPublicKey = errors.New("invalid public key") - // ErrInvalidSignature is returned if signature cannot be verified. - ErrInvalidSignature = errors.New("invalid signature") -) - -func SignData(key *ecdsa.PrivateKey, src DataSource, opts ...SignOption) (res *signature.Signature, err error) { - err = signDataWithHandler(key, src, func(key, sig []byte, scheme signature.Scheme) { - res = new(signature.Signature) - res.SetKey(key) - res.SetSign(sig) - res.SetScheme(scheme) - }, opts...) - - return -} - -func VerifyData(dataSrc DataSource, sig *signature.Signature, opts ...SignOption) error { - return verifyDataWithSource(dataSrc, func() ([]byte, []byte, signature.Scheme) { - return sig.Key(), sig.Sign(), sig.Scheme() - }, opts...) -} - -func SignDataWithHandler(key *ecdsa.PrivateKey, src DataSource, handler func(key, sig []byte)) error { - return signDataWithHandler(key, src, func(key, sig []byte, scheme signature.Scheme) { - handler(key, sig) - }) -} - -func signDataWithHandler( - key *ecdsa.PrivateKey, - src DataSource, - handler func(key, sig []byte, scheme signature.Scheme), - opts ...SignOption, -) error { - if key == nil { - return ErrEmptyPrivateKey - } - - data, err := dataForSignature(src) - if err != nil { - return err - } - defer bytesPool.Put(&data) - - cfg := getConfig(opts...) - - sigData, err := sign(cfg.scheme, key, data) - if err != nil { - return err - } - - handler((*keys.PublicKey)(&key.PublicKey).Bytes(), sigData, cfg.scheme) - - return nil -} - -func VerifyDataWithSource(dataSrc DataSource, sigSrc func() (key, sig []byte)) error { - return verifyDataWithSource(dataSrc, func() ([]byte, []byte, signature.Scheme) { - key, sign := sigSrc() - return key, sign, signature.ECDSAWithSHA512 - }) -} - -func verifyDataWithSource( - dataSrc DataSource, - sigSrc func() (key, sig []byte, scheme signature.Scheme), - opts ...SignOption, -) error { - data, err := dataForSignature(dataSrc) - if err != nil { - return err - } - defer bytesPool.Put(&data) - - cfg := getConfig(opts...) - - return verify(cfg, data, sigSrc) -} diff --git a/util/signature/options.go b/util/signature/options.go deleted file mode 100644 index a825b9ae..00000000 --- a/util/signature/options.go +++ /dev/null @@ -1,114 +0,0 @@ -package signature - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/sha256" - "crypto/sha512" - "fmt" - "math/big" - - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-sdk-go/signature" -) - -var curve = elliptic.P256() - -type cfg struct { - schemeFixed bool - scheme signature.Scheme -} - -func getConfig(opts ...SignOption) *cfg { - cfg := &cfg{ - scheme: signature.ECDSAWithSHA512, - } - - for i := range opts { - opts[i](cfg) - } - - return cfg -} - -func sign(scheme signature.Scheme, key *ecdsa.PrivateKey, msg []byte) ([]byte, error) { - switch scheme { - case signature.ECDSAWithSHA512: - h := sha512.Sum512(msg) - x, y, err := ecdsa.Sign(rand.Reader, key, h[:]) - if err != nil { - return nil, err - } - return elliptic.Marshal(elliptic.P256(), x, y), nil - case signature.RFC6979WithSHA256: - p := &keys.PrivateKey{PrivateKey: *key} - return p.Sign(msg), nil - default: - panic(fmt.Sprintf("unsupported scheme %s", scheme)) - } -} - -func verify(cfg *cfg, msg []byte, f func() (key, sign []byte, scheme signature.Scheme)) error { - key, sign, scheme := f() - - pub, err := keys.NewPublicKeyFromBytes(key, elliptic.P256()) - if err != nil { - return fmt.Errorf("%w: %v", ErrInvalidPublicKey, err) - } - - if !cfg.schemeFixed { - cfg.scheme = scheme - } - - switch cfg.scheme { - case signature.ECDSAWithSHA512: - h := sha512.Sum512(msg) - r, s := unmarshalXY(sign) - if r != nil && s != nil && ecdsa.Verify((*ecdsa.PublicKey)(pub), h[:], r, s) { - return nil - } - return ErrInvalidSignature - case signature.RFC6979WithSHA256: - h := sha256.Sum256(msg) - if pub.Verify(sign, h[:]) { - return nil - } - return ErrInvalidSignature - default: - return fmt.Errorf("unsupported signature scheme %s", cfg.scheme) - } -} - -// unmarshalXY converts a point, serialized by Marshal, into an x, y pair. -// It is an error if the point is not in uncompressed form. -// On error, x,y = nil. -// Unlike the original version of the code, we ignore that x or y not on the curve -// -------------- -// It's copy-paste elliptic.Unmarshal(curve, data) stdlib function, without last line -// of code. -// Link - https://golang.org/pkg/crypto/elliptic/#Unmarshal -func unmarshalXY(data []byte) (x *big.Int, y *big.Int) { - if len(data) != PublicKeyUncompressedSize { - return - } else if data[0] != 4 { // uncompressed form - return - } - - p := curve.Params().P - x = new(big.Int).SetBytes(data[1:PublicKeyCompressedSize]) - y = new(big.Int).SetBytes(data[PublicKeyCompressedSize:]) - - if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 { - x, y = nil, nil - } - - return -} - -func SignWithRFC6979() SignOption { - return func(c *cfg) { - c.schemeFixed = true - c.scheme = signature.RFC6979WithSHA256 - } -} diff --git a/util/signature/util.go b/util/signature/util.go deleted file mode 100644 index f9aef100..00000000 --- a/util/signature/util.go +++ /dev/null @@ -1,31 +0,0 @@ -package signature - -import ( - "errors" - "sync" -) - -var bytesPool = sync.Pool{ - New: func() interface{} { - b := make([]byte, 5<<20) - return &b - }, -} - -func dataForSignature(src DataSource) ([]byte, error) { - if src == nil { - return nil, errors.New("nil source") - } - - buf := *bytesPool.Get().(*[]byte) - - if size := src.SignedDataSize(); size < 0 { - return nil, errors.New("negative length") - } else if size <= cap(buf) { - buf = buf[:size] - } else { - buf = make([]byte, size) - } - - return src.ReadSignedData(buf) -}