diff --git a/cmd/neofs-rest-gw/integration_test.go b/cmd/neofs-rest-gw/integration_test.go index b36fb1b..b9dd50e 100644 --- a/cmd/neofs-rest-gw/integration_test.go +++ b/cmd/neofs-rest-gw/integration_test.go @@ -89,8 +89,7 @@ func runLocalTests(ctx context.Context, t *testing.T, key *keys.PrivateKey) { func runTestInContainer(rootCtx context.Context, t *testing.T, key *keys.PrivateKey) { versions := []dockerImage{ - {image: "nspccdev/neofs-aio", version: "0.37.0"}, - {image: "nspccdev/neofs-aio", version: "0.38.1"}, + {image: "nspccdev/neofs-aio", version: "0.41.0"}, } for _, version := range versions { @@ -153,7 +152,7 @@ func runTests(ctx context.Context, t *testing.T, key *keys.PrivateKey, node stri func createDockerContainer(ctx context.Context, t *testing.T, image, version string) testcontainers.Container { req := testcontainers.ContainerRequest{ Image: image, - WaitingFor: wait.NewLogStrategy("aio container started").WithStartupTimeout(30 * time.Second), + WaitingFor: wait.NewLogStrategy("aio container started").WithStartupTimeout(2 * time.Minute), Name: "restgw-aio-test-" + version, Hostname: "aio", HostConfigModifier: func(hostConfig *dockerContainer.HostConfig) { diff --git a/go.mod b/go.mod index 5d3e198..a4e33dc 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,7 @@ require ( github.com/google/uuid v1.6.0 github.com/labstack/echo/v4 v4.11.4 github.com/nspcc-dev/neo-go v0.105.1 - github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4 - github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11.0.20240326133951-7f940dcb37d8 + github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11.0.20240423143337-3cdb540f5511 github.com/oapi-codegen/echo-middleware v1.0.1 github.com/oapi-codegen/runtime v1.1.1 github.com/spf13/pflag v1.0.5 @@ -40,6 +39,7 @@ require ( github.com/moby/sys/sequential v0.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/nspcc-dev/hrw/v2 v2.0.1 // indirect + github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/perimeterx/marshmallow v1.1.4 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect diff --git a/go.sum b/go.sum index 2bb6bc0..6029326 100644 --- a/go.sum +++ b/go.sum @@ -135,8 +135,8 @@ github.com/nspcc-dev/neo-go v0.105.1 h1:r0b2yIwLBi+ARBKU94gHL9oTFEB/XMJ0YlS2HN9Q github.com/nspcc-dev/neo-go v0.105.1/go.mod h1:GNh0cRALV/cuj+/xg2ZHDsrFbqcInqG7jjhqsLEnlNc= github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4 h1:arN0Ypn+jawZpu1BND7TGRn44InAVIqKygndsx0y2no= github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240305074711-35bc78d84dc4/go.mod h1:7Tm1NKEoUVVIUlkVwFrPh7GG5+Lmta2m7EGr4oVpBd8= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11.0.20240326133951-7f940dcb37d8 h1:0qr5CEPXp94CRnYyikKu54lJgFLBVJ7Per+zXIBr6tc= -github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11.0.20240326133951-7f940dcb37d8/go.mod h1:2XHytVt+AFQkwr6vpcYvdm13mA2rZxB+STrxtwSrtx8= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11.0.20240423143337-3cdb540f5511 h1:g+UEnrsCBMrqZ/6+UIE3o6ObZzinK+4oQt91vOYmMV0= +github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.11.0.20240423143337-3cdb540f5511/go.mod h1:AApSmHoQ6o/4bz6Am2RmfX0mdgCTEPDDXpV/g4OFOlE= github.com/nspcc-dev/rfc6979 v0.2.1 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM= github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc= github.com/nspcc-dev/tzhash v1.8.0 h1:pJvzME2mZzP/h5rcy/Wb6amT9FJBFeKbJ3HEnWEeUpY= diff --git a/handlers/api.go b/handlers/api.go index cb0bd81..ab2f5a4 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -8,11 +8,11 @@ import ( "github.com/labstack/echo/v4" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-rest-gw/internal/util" "github.com/nspcc-dev/neofs-rest-gw/metrics" "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" "go.uber.org/zap" ) @@ -39,7 +39,7 @@ type BearerToken struct { type SessionToken struct { BearerToken - Verb sessionv2.ContainerSessionVerb + Verb session.ContainerVerb } const ( diff --git a/handlers/auth.go b/handlers/auth.go index 211f910..7e31c79 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -9,9 +9,6 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v4" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-rest-gw/internal/util" "github.com/nspcc-dev/neofs-sdk-go/client" @@ -145,6 +142,12 @@ func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *poo return nil, fmt.Errorf("couldn't transform token to native: %w", err) } + var issuer user.ID + if err = issuer.DecodeString(params.XBearerOwnerID); err != nil { + return nil, fmt.Errorf("invalid bearer owner: %w", err) + } + btoken.SetIssuer(issuer) + if !params.XBearerForAllUsers { btoken.ForUser(owner) } @@ -156,9 +159,7 @@ func prepareObjectToken(ctx context.Context, params objectTokenParams, pool *poo btoken.SetIat(iat) btoken.SetExp(exp) - var v2token acl.BearerToken - btoken.WriteToV2(&v2token) - binaryBearer := v2token.GetBody().StableMarshal(nil) + binaryBearer := btoken.SignedData() return &apiserver.TokenResponse{ Name: ¶ms.Name, @@ -192,15 +193,9 @@ func prepareContainerTokens(ctx context.Context, params containerTokenParams, po stoken.SetExp(exp) stoken.SetAuthKey(pubKey) + stoken.SetIssuer(ownerID) - var v2token sessionv2.Token - stoken.WriteToV2(&v2token) - - var issuer refs.OwnerID - ownerID.WriteToV2(&issuer) - v2token.GetBody().SetOwnerID(&issuer) - - binaryToken := v2token.GetBody().StableMarshal(nil) + binaryToken := stoken.SignedData() return &apiserver.TokenResponse{ Name: ¶ms.Name, diff --git a/handlers/auth_test.go b/handlers/auth_test.go index 9f871ab..f84c06b 100644 --- a/handlers/auth_test.go +++ b/handlers/auth_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-rest-gw/internal/util" "github.com/nspcc-dev/neofs-sdk-go/user" @@ -40,11 +39,9 @@ func TestSign(t *testing.T) { signer := user.NewAutoIDSigner(key.PrivateKey) owner := signer.UserID() btoken.ForUser(owner) + btoken.SetIssuer(signer.UserID()) - var v2token acl.BearerToken - btoken.WriteToV2(&v2token) - - binaryBearer := v2token.GetBody().StableMarshal(nil) + binaryBearer := btoken.SignedData() bearerBase64 := base64.StdEncoding.EncodeToString(binaryBearer) signatureData, err := signer.Sign(binaryBearer) diff --git a/handlers/container_test.go b/handlers/container_test.go index 79e908b..d02f287 100644 --- a/handlers/container_test.go +++ b/handlers/container_test.go @@ -1,9 +1,19 @@ package handlers import ( + "bytes" + "encoding/base64" + "encoding/hex" "testing" - sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + 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/crypto/test" + "github.com/nspcc-dev/neofs-sdk-go/session" + sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" + "github.com/nspcc-dev/neofs-sdk-go/user" + usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" ) @@ -44,9 +54,153 @@ func TestPrepareSessionToken(t *testing.T) { Signature: "2ebdc1f2fea2bba397d1be6f982a6fe1b2bc9f46a348b700108fe2eba4e6531a1bb585febf9a40a3fa2e085fca5e2a75ca57f61166117c6d3e04a95ef9a2d2196f52648546784853e17c0b7ba762eae1", Key: "03bd9108c0b49f657e9eee50d1399022bd1e436118e5b7529a1b7cd606652f578f", }, - Verb: sessionv2.ContainerVerbSetEACL, + Verb: session.VerbContainerSetEACL, } _, err := prepareSessionToken(st, true) require.NoError(t, err) + + issuer := usertest.ID(t) + signer := user.NewSigner(test.RandomSigner(t), issuer) + token := sessiontest.Container() + token.SetIssuer(issuer) + const verb = session.VerbContainerPut + token.ForVerb(verb) + + sig, err := signer.Sign(token.SignedData()) + require.NoError(t, err) + + err = token.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), issuer)) + require.NoError(t, err) + require.True(t, token.VerifySignature()) + + unsignedTokenB64 := base64.StdEncoding.EncodeToString(token.SignedData()) + sigHex := hex.EncodeToString(sig) + keyHex := hex.EncodeToString(neofscrypto.PublicKeyBytes(signer.Public())) + + t.Run("invalid base64", func(t *testing.T) { + _, err := prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: "not a base64 string", + }, + }, false) + require.ErrorContains(t, err, "can't base64-decode session token") + }) + + res, err := prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: unsignedTokenB64, + Signature: sigHex, + Key: keyHex, + }, + Verb: verb, + }, false) + require.NoError(t, err) + require.Equal(t, token, res) + + t.Run("invalid signature hex", func(t *testing.T) { + _, err := prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: unsignedTokenB64, + Signature: "not a hex string", + }, + Verb: 0, + }, false) + require.ErrorContains(t, err, "couldn't decode signature") + }) + + t.Run("invalid public key", func(t *testing.T) { + _, err := prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: unsignedTokenB64, + Signature: sigHex, + Key: "not a public key", + }, + Verb: 0, + }, false) + require.ErrorContains(t, err, "couldn't fetch session token owner key") + }) + + t.Run("invalid body binary", func(t *testing.T) { + _, err := prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: base64.StdEncoding.EncodeToString([]byte("not a bearer token")), + Signature: sigHex, + Key: keyHex, + }, + Verb: 0, + }, false) + require.ErrorContains(t, err, "can't unmarshal session token") + }) + + t.Run("invalid signature", func(t *testing.T) { + tokenCp := token + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), issuer)) + require.NoError(t, err) + require.True(t, tokenCp.VerifySignature()) + + // corrupt signature + sig := bytes.Clone(sig) + sig[0]++ + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), issuer)) + require.NoError(t, err) + + _, err = prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: unsignedTokenB64, + Signature: hex.EncodeToString(sig), + Key: keyHex, + }, + Verb: verb, + }, false) + require.ErrorContains(t, err, "invalid signature") + }) + + t.Run("WalletConnect", func(t *testing.T) { + key, err := keys.NewPrivateKey() + require.NoError(t, err) + signer := neofsecdsa.SignerWalletConnect(key.PrivateKey) + keyHex := hex.EncodeToString(key.PublicKey().Bytes()) + var tokenCp session.Container + token.CopyTo(&tokenCp) + unsignedTokenB64 := base64.StdEncoding.EncodeToString(tokenCp.SignedData()) + + sig, err := signer.Sign(tokenCp.SignedData()) + require.NoError(t, err) + + sigHex := hex.EncodeToString(sig) + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), issuer)) + require.NoError(t, err) + require.True(t, tokenCp.VerifySignature()) + + res, err := prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: unsignedTokenB64, + Signature: sigHex, + Key: keyHex, + }, + Verb: verb, + }, true) + require.NoError(t, err) + require.Equal(t, tokenCp, res) + + // corrupt signature + sig[0]++ + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), issuer)) + require.NoError(t, err) + + _, err = prepareSessionToken(&SessionToken{ + BearerToken: BearerToken{ + Token: unsignedTokenB64, + Signature: hex.EncodeToString(sig), + Key: keyHex, + }, + Verb: verb, + }, true) + require.ErrorContains(t, err, "invalid signature") + }) } diff --git a/handlers/containers.go b/handlers/containers.go index 5bba612..53c552d 100644 --- a/handlers/containers.go +++ b/handlers/containers.go @@ -12,20 +12,18 @@ import ( "github.com/labstack/echo/v4" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - containerv2 "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-rest-gw/internal/util" "github.com/nspcc-dev/neofs-sdk-go/client" "github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container/acl" 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/netmap" "github.com/nspcc-dev/neofs-sdk-go/pool" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" - "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/neofs-sdk-go/waiter" "go.uber.org/zap" ) @@ -49,7 +47,7 @@ func (a *RestAPI) PutContainer(ctx echo.Context, params apiserver.PutContainerPa return ctx.JSON(http.StatusBadRequest, util.NewErrorResponse(err)) } - st, err := formSessionTokenFromHeaders(principal, params.XBearerSignature, params.XBearerSignatureKey, sessionv2.ContainerVerbPut) + st, err := formSessionTokenFromHeaders(principal, params.XBearerSignature, params.XBearerSignatureKey, session.VerbContainerPut) if err != nil { resp := a.logAndGetErrorResponse("invalid session token headers", err) return ctx.JSON(http.StatusBadRequest, resp) @@ -121,7 +119,7 @@ func (a *RestAPI) PutContainerEACL(ctx echo.Context, containerID apiserver.Conta return ctx.JSON(http.StatusBadRequest, util.NewErrorResponse(err)) } - st, err := formSessionTokenFromHeaders(principal, params.XBearerSignature, params.XBearerSignatureKey, sessionv2.ContainerVerbSetEACL) + st, err := formSessionTokenFromHeaders(principal, params.XBearerSignature, params.XBearerSignatureKey, session.VerbContainerSetEACL) if err != nil { resp := a.logAndGetErrorResponse("invalid session token headers", err) return ctx.JSON(http.StatusBadRequest, resp) @@ -230,7 +228,7 @@ func (a *RestAPI) DeleteContainer(ctx echo.Context, containerID apiserver.Contai return ctx.JSON(http.StatusBadRequest, util.NewErrorResponse(err)) } - st, err := formSessionTokenFromHeaders(principal, params.XBearerSignature, params.XBearerSignatureKey, sessionv2.ContainerVerbDelete) + st, err := formSessionTokenFromHeaders(principal, params.XBearerSignature, params.XBearerSignatureKey, session.VerbContainerDelete) if err != nil { resp := a.logAndGetErrorResponse("invalid session token headers", err) return ctx.JSON(http.StatusBadRequest, resp) @@ -310,7 +308,7 @@ func getContainerInfo(ctx context.Context, p *pool.Pool, cnrID cid.ID) (*apiserv CannedAcl: util.NewString(friendlyBasicACL(cnr.BasicACL())), PlacementPolicy: sb.String(), Attributes: attrs, - Version: getContainerVersion(cnr).String(), + Version: cnr.Version().String(), }, nil } @@ -337,19 +335,6 @@ func friendlyBasicACL(basicACL acl.Basic) string { } } -func getContainerVersion(cnr container.Container) version.Version { - var v2cnr containerv2.Container - cnr.WriteToV2(&v2cnr) - - var cnrVersion version.Version - v2version := v2cnr.GetVersion() - if v2version != nil { - cnrVersion = version.Version(*v2version) - } - - return cnrVersion -} - func parseContainerID(containerID string) (cid.ID, error) { var cnrID cid.ID if err := cnrID.DecodeString(containerID); err != nil { @@ -430,7 +415,7 @@ func createContainer(ctx context.Context, p *pool.Pool, stoken session.Container for _, attr := range request.Attributes { switch attr.Key { case attributeName, attributeTimestamp, - containerv2.SysAttributeName, containerv2.SysAttributeZone: + containerDomainNameAttribute, containerDomainZoneAttribute: default: cnr.SetAttribute(attr.Key, attr.Value) } @@ -506,32 +491,29 @@ func prepareSessionToken(st *SessionToken, isWalletConnect bool) (session.Contai return session.Container{}, fmt.Errorf("couldn't fetch session token owner key: %w", err) } - body := new(sessionv2.TokenBody) - if err = body.Unmarshal(data); err != nil { + var stoken session.Container + if err = stoken.UnmarshalSignedData(data); err != nil { return session.Container{}, fmt.Errorf("can't unmarshal session token: %w", err) } - if sessionContext, ok := body.GetContext().(*sessionv2.ContainerSessionContext); !ok { - return session.Container{}, errors.New("expected container session context but got something different") - } else if sessionContext.Verb() != st.Verb { - return session.Container{}, fmt.Errorf("invalid container session verb '%s', expected: '%s'", sessionContext.Verb().String(), st.Verb.String()) + if !stoken.AssertVerb(st.Verb) { + return session.Container{}, errors.New("wrong container session verb") } - v2signature := new(refs.Signature) - v2signature.SetScheme(refs.ECDSA_SHA512) + var scheme neofscrypto.Scheme + var pubKey neofscrypto.PublicKey if isWalletConnect { - v2signature.SetScheme(refs.ECDSA_RFC6979_SHA256_WALLET_CONNECT) + scheme = neofscrypto.ECDSA_WALLETCONNECT + pubKey = (*neofsecdsa.PublicKeyWalletConnect)(ownerKey) + } else { + scheme = neofscrypto.ECDSA_SHA512 + pubKey = (*neofsecdsa.PublicKey)(ownerKey) } - v2signature.SetSign(signature) - v2signature.SetKey(ownerKey.Bytes()) - - var v2token sessionv2.Token - v2token.SetBody(body) - v2token.SetSignature(v2signature) - var stoken session.Container - if err = stoken.ReadFromV2(v2token); err != nil { - return session.Container{}, fmt.Errorf("read from v2 token: %w", err) + err = stoken.Sign(user.NewSigner(neofscrypto.NewStaticSigner(scheme, signature, pubKey), stoken.Issuer())) + if err != nil { + // should never happen + return session.Container{}, fmt.Errorf("set pre-calculated signature of the token: %w", err) } if !stoken.VerifySignature() { diff --git a/handlers/objects.go b/handlers/objects.go index 65800a2..7c42b3d 100644 --- a/handlers/objects.go +++ b/handlers/objects.go @@ -3,6 +3,7 @@ package handlers import ( "bytes" "context" + "crypto/ecdsa" "encoding/base64" "encoding/hex" "errors" @@ -19,15 +20,14 @@ import ( "github.com/labstack/echo/v4" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-rest-gw/internal/util" "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/client" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" 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/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/pool" @@ -592,7 +592,7 @@ func (a *RestAPI) setAttributes(ctx echo.Context, payloadSize uint64, cid string case object.AttributeContentType: contentType = val default: - if strings.HasPrefix(key, container.SysAttributePrefix) { + if strings.HasPrefix(key, SystemAttributePrefix) { key = systemBackwardTranslator(key) } ctx.Response().Header().Set(userAttributeHeaderPrefix+key, attr.Value()) @@ -652,10 +652,10 @@ func isValidValue(s string) bool { // systemBackwardTranslator is used to convert headers looking like '__NEOFS__ATTR_NAME' to 'Neofs-Attr-Name'. func systemBackwardTranslator(key string) string { // trim specified prefix '__NEOFS__' - key = strings.TrimPrefix(key, container.SysAttributePrefix) + key = strings.TrimPrefix(key, SystemAttributePrefix) var res strings.Builder - res.WriteString("Neofs-") + res.WriteString(neofsAttributeHeaderPrefix) strs := strings.Split(key, "_") for i, s := range strs { @@ -751,8 +751,8 @@ func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (*be return nil, fmt.Errorf("can't base64-decode bearer token: %w", err) } + var btoken bearer.Token if isFullToken { - var btoken bearer.Token if err = btoken.Unmarshal(data); err != nil { return nil, fmt.Errorf("couldn't unmarshall bearer token: %w", err) } @@ -773,26 +773,25 @@ func prepareBearerToken(bt *BearerToken, isWalletConnect, isFullToken bool) (*be return nil, fmt.Errorf("couldn't fetch bearer token owner key: %w", err) } - body := new(acl.BearerTokenBody) - if err = body.Unmarshal(data); err != nil { + if err = btoken.UnmarshalSignedData(data); err != nil { return nil, fmt.Errorf("can't unmarshal bearer token body: %w", err) } - v2signature := new(refs.Signature) - v2signature.SetScheme(refs.ECDSA_SHA512) + var scheme neofscrypto.Scheme + var pubKey neofscrypto.PublicKey if isWalletConnect { - v2signature.SetScheme(refs.ECDSA_RFC6979_SHA256_WALLET_CONNECT) + scheme = neofscrypto.ECDSA_WALLETCONNECT + pubKey = (*neofsecdsa.PublicKeyWalletConnect)(ownerKey) + } else { + scheme = neofscrypto.ECDSA_SHA512 + pubKey = (*neofsecdsa.PublicKey)(ownerKey) } - v2signature.SetSign(signature) - v2signature.SetKey(ownerKey.Bytes()) - - var v2btoken acl.BearerToken - v2btoken.SetBody(body) - v2btoken.SetSignature(v2signature) - var btoken bearer.Token - if err = btoken.ReadFromV2(v2btoken); err != nil { - return nil, fmt.Errorf("read from v2 token: %w", err) + err = btoken.Sign(user.NewSigner(neofscrypto.NewStaticSigner(scheme, signature, pubKey), + user.ResolveFromECDSAPublicKey(ecdsa.PublicKey(*ownerKey)))) + if err != nil { + // should never happen + return nil, fmt.Errorf("set pre-calculated signature of the token: %w", err) } if !btoken.VerifySignature() { diff --git a/handlers/objects_test.go b/handlers/objects_test.go index 4e4c9c8..171f629 100644 --- a/handlers/objects_test.go +++ b/handlers/objects_test.go @@ -1,10 +1,20 @@ package handlers import ( + "bytes" + "crypto/ecdsa" + "encoding/base64" + "encoding/hex" "testing" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-rest-gw/internal/util" + bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" + 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/crypto/test" + "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/stretchr/testify/require" ) @@ -75,3 +85,169 @@ func TestPrepareOffset(t *testing.T) { }) } } + +func TestPrepareBearerToken(t *testing.T) { + signer := test.RandomSigner(t) + token := bearertest.Token(t) + + keyHex := hex.EncodeToString(neofscrypto.PublicKeyBytes(signer.Public())) + pKey, err := keys.NewPublicKeyFromString(keyHex) + require.NoError(t, err) + usrID := user.ResolveFromECDSAPublicKey(ecdsa.PublicKey(*pKey)) + + token.SetIssuer(usrID) + + sig, err := signer.Sign(token.SignedData()) + require.NoError(t, err) + + err = token.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), usrID)) + require.NoError(t, err) + require.True(t, token.VerifySignature()) + + tokenB64 := base64.StdEncoding.EncodeToString(token.Marshal()) + unsignedTokenB64 := base64.StdEncoding.EncodeToString(token.SignedData()) + sigHex := hex.EncodeToString(sig) + + t.Run("invalid base64", func(t *testing.T) { + _, err := prepareBearerToken(&BearerToken{ + Token: "not a base64 string", + }, false, false) + require.ErrorContains(t, err, "can't base64-decode bearer token") + }) + + res, err := prepareBearerToken(&BearerToken{ + Token: unsignedTokenB64, + Signature: sigHex, + Key: keyHex, + }, false, false) + require.NoError(t, err) + require.Equal(t, token.Marshal(), res.Marshal()) + + t.Run("full", func(t *testing.T) { + res, err := prepareBearerToken(&BearerToken{ + Token: tokenB64, + Signature: sigHex, + Key: keyHex, + }, false, true) + require.NoError(t, err) + require.Equal(t, token.Marshal(), res.Marshal()) + + t.Run("invalid binary", func(t *testing.T) { + _, err := prepareBearerToken(&BearerToken{ + Token: base64.StdEncoding.EncodeToString([]byte("not a bearer token")), + }, false, true) + require.ErrorContains(t, err, "couldn't unmarshall bearer token") + }) + + t.Run("invalid signature", func(t *testing.T) { + tokenCp := token + + // corrupt signature + sig := bytes.Clone(sig) + sig[0]++ + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), usrID)) + require.NoError(t, err) + + _, err = prepareBearerToken(&BearerToken{ + Token: base64.StdEncoding.EncodeToString(tokenCp.Marshal()), + }, false, true) + require.ErrorContains(t, err, "invalid signature") + }) + }) + + t.Run("invalid signature hex", func(t *testing.T) { + _, err := prepareBearerToken(&BearerToken{ + Token: tokenB64, + Signature: "not a hex string", + }, false, false) + require.ErrorContains(t, err, "couldn't decode bearer signature") + }) + + t.Run("invalid signature hex", func(t *testing.T) { + _, err := prepareBearerToken(&BearerToken{ + Token: tokenB64, + Signature: "not a hex string", + }, false, false) + require.ErrorContains(t, err, "couldn't decode bearer signature") + }) + + t.Run("invalid public key", func(t *testing.T) { + _, err := prepareBearerToken(&BearerToken{ + Token: tokenB64, + Signature: sigHex, + Key: "not a public key", + }, false, false) + require.ErrorContains(t, err, "couldn't fetch bearer token owner key") + }) + + t.Run("invalid body binary", func(t *testing.T) { + _, err := prepareBearerToken(&BearerToken{ + Token: base64.StdEncoding.EncodeToString([]byte("not a bearer token")), + Signature: sigHex, + Key: keyHex, + }, false, false) + require.ErrorContains(t, err, "can't unmarshal bearer token body") + }) + + t.Run("invalid signature", func(t *testing.T) { + tokenCp := token + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), usrID)) + require.NoError(t, err) + require.True(t, tokenCp.VerifySignature()) + + // corrupt signature + sig := bytes.Clone(sig) + sig[0]++ + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), usrID)) + require.NoError(t, err) + + _, err = prepareBearerToken(&BearerToken{ + Token: unsignedTokenB64, + Signature: hex.EncodeToString(sig), + Key: keyHex, + }, false, false) + require.ErrorContains(t, err, "invalid signature") + }) + + t.Run("WalletConnect", func(t *testing.T) { + key, err := keys.NewPrivateKey() + require.NoError(t, err) + signer := neofsecdsa.SignerWalletConnect(key.PrivateKey) + keyHex := hex.EncodeToString(key.PublicKey().Bytes()) + tokenCp := token + unsignedTokenB64 := base64.StdEncoding.EncodeToString(tokenCp.SignedData()) + usrID := user.ResolveFromECDSAPublicKey(ecdsa.PublicKey(*key.PublicKey())) + tokenCp.SetIssuer(usrID) + + sig, err := signer.Sign(tokenCp.SignedData()) + require.NoError(t, err) + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), usrID)) + require.NoError(t, err) + require.True(t, tokenCp.VerifySignature()) + + res, err := prepareBearerToken(&BearerToken{ + Token: unsignedTokenB64, + Signature: hex.EncodeToString(sig), + Key: keyHex, + }, true, false) + require.NoError(t, err) + require.Equal(t, tokenCp.Marshal(), res.Marshal()) + + // corrupt signature + sig[0]++ + + err = tokenCp.Sign(user.NewSigner(neofscrypto.NewStaticSigner(signer.Scheme(), sig, signer.Public()), usrID)) + require.NoError(t, err) + + _, err = prepareBearerToken(&BearerToken{ + Token: unsignedTokenB64, + Signature: hex.EncodeToString(sig), + Key: keyHex, + }, true, false) + require.ErrorContains(t, err, "invalid signature") + }) +} diff --git a/handlers/util.go b/handlers/util.go index e4beaad..bf58542 100644 --- a/handlers/util.go +++ b/handlers/util.go @@ -10,14 +10,12 @@ import ( "strings" "time" - "github.com/nspcc-dev/neofs-api-go/v2/container" - objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object" - sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-rest-gw/handlers/apiserver" "github.com/nspcc-dev/neofs-sdk-go/client" "github.com/nspcc-dev/neofs-sdk-go/container/acl" "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/pool" + "github.com/nspcc-dev/neofs-sdk-go/session" "go.uber.org/zap" ) @@ -36,9 +34,11 @@ type epochDurations struct { const ( SystemAttributePrefix = "__NEOFS__" - ExpirationDurationAttr = SystemAttributePrefix + "EXPIRATION_DURATION" - ExpirationTimestampAttr = SystemAttributePrefix + "EXPIRATION_TIMESTAMP" - ExpirationRFC3339Attr = SystemAttributePrefix + "EXPIRATION_RFC3339" + ExpirationDurationAttr = SystemAttributePrefix + "EXPIRATION_DURATION" + ExpirationTimestampAttr = SystemAttributePrefix + "EXPIRATION_TIMESTAMP" + ExpirationRFC3339Attr = SystemAttributePrefix + "EXPIRATION_RFC3339" + containerDomainNameAttribute = SystemAttributePrefix + "NAME" + containerDomainZoneAttribute = SystemAttributePrefix + "ZONE" neofsAttributeHeaderPrefix = "Neofs-" @@ -112,7 +112,7 @@ func needParseExpiration(headers map[string]string) bool { } func prepareExpirationHeader(headers map[string]string, epochDurations *epochDurations, now time.Time) error { - expirationInEpoch := headers[objectv2.SysAttributeExpEpoch] + expirationInEpoch := headers[object.AttributeExpirationEpoch] if timeRFC3339, ok := headers[ExpirationRFC3339Attr]; ok { expTime, err := time.Parse(time.RFC3339, timeRFC3339) @@ -155,7 +155,7 @@ func prepareExpirationHeader(headers map[string]string, epochDurations *epochDur } if expirationInEpoch != "" { - headers[objectv2.SysAttributeExpEpoch] = expirationInEpoch + headers[object.AttributeExpirationEpoch] = expirationInEpoch } return nil @@ -175,7 +175,7 @@ func updateExpirationHeader(headers map[string]string, durations *epochDurations expirationEpoch = currentEpoch + numEpoch } - headers[objectv2.SysAttributeExpEpoch] = strconv.FormatUint(expirationEpoch, 10) + headers[object.AttributeExpirationEpoch] = strconv.FormatUint(expirationEpoch, 10) } // IsObjectToken check that provided token is for object. @@ -194,7 +194,7 @@ func IsObjectToken(token apiserver.Bearer) (bool, error) { return isObject, nil } -func formSessionTokenFromHeaders(principal string, signature, key *string, verb sessionv2.ContainerSessionVerb) (*SessionToken, error) { +func formSessionTokenFromHeaders(principal string, signature, key *string, verb session.ContainerVerb) (*SessionToken, error) { if signature == nil || key == nil { return nil, errors.New("missed signature or key header") } @@ -248,7 +248,7 @@ func decodeBasicACL(input string) (acl.Basic, error) { func systemTranslator(key, prefix string) string { // replace the specified prefix with `__NEOFS__` - key = strings.Replace(key, prefix, container.SysAttributePrefix, 1) + key = strings.Replace(key, prefix, SystemAttributePrefix, 1) // replace `-` with `_` key = strings.ReplaceAll(key, "-", "_") diff --git a/handlers/util_test.go b/handlers/util_test.go index 58004db..96436ab 100644 --- a/handlers/util_test.go +++ b/handlers/util_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - objectv2 "github.com/nspcc-dev/neofs-api-go/v2/object" + "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -47,71 +47,71 @@ func TestPrepareExpirationHeader(t *testing.T) { }{ { name: "valid epoch", - headers: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, - expected: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, + headers: map[string]string{object.AttributeExpirationEpoch: epoch}, + expected: map[string]string{object.AttributeExpirationEpoch: epoch}, }, { name: "valid epoch, valid duration", headers: map[string]string{ - objectv2.SysAttributeExpEpoch: epoch, - ExpirationDurationAttr: duration, + object.AttributeExpirationEpoch: epoch, + ExpirationDurationAttr: duration, }, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, + expected: map[string]string{object.AttributeExpirationEpoch: epoch}, }, { name: "valid epoch, valid rfc3339", headers: map[string]string{ - objectv2.SysAttributeExpEpoch: epoch, - ExpirationRFC3339Attr: tomorrow.Format(time.RFC3339), + object.AttributeExpirationEpoch: epoch, + ExpirationRFC3339Attr: tomorrow.Format(time.RFC3339), }, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, + expected: map[string]string{object.AttributeExpirationEpoch: epoch}, }, { name: "valid epoch, valid timestamp sec", headers: map[string]string{ - objectv2.SysAttributeExpEpoch: epoch, - ExpirationTimestampAttr: timestampSec, + object.AttributeExpirationEpoch: epoch, + ExpirationTimestampAttr: timestampSec, }, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, + expected: map[string]string{object.AttributeExpirationEpoch: epoch}, }, { name: "valid epoch, valid timestamp milli", headers: map[string]string{ - objectv2.SysAttributeExpEpoch: epoch, - ExpirationTimestampAttr: timestampMilli, + object.AttributeExpirationEpoch: epoch, + ExpirationTimestampAttr: timestampMilli, }, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, + expected: map[string]string{object.AttributeExpirationEpoch: epoch}, }, { name: "valid epoch, valid timestamp nano", headers: map[string]string{ - objectv2.SysAttributeExpEpoch: epoch, - ExpirationTimestampAttr: timestampNano, + object.AttributeExpirationEpoch: epoch, + ExpirationTimestampAttr: timestampNano, }, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: epoch}, + expected: map[string]string{object.AttributeExpirationEpoch: epoch}, }, { name: "valid timestamp sec", headers: map[string]string{ExpirationTimestampAttr: timestampSec}, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: defaultExpEpoch}, + expected: map[string]string{object.AttributeExpirationEpoch: defaultExpEpoch}, }, { name: "valid duration", headers: map[string]string{ExpirationDurationAttr: duration}, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: defaultExpEpoch}, + expected: map[string]string{object.AttributeExpirationEpoch: defaultExpEpoch}, }, { name: "valid rfc3339", headers: map[string]string{ExpirationRFC3339Attr: tomorrow.Format(time.RFC3339)}, durations: defaultDurations, - expected: map[string]string{objectv2.SysAttributeExpEpoch: defaultExpEpoch}, + expected: map[string]string{object.AttributeExpirationEpoch: defaultExpEpoch}, }, { name: "valid max uint 64", @@ -121,7 +121,7 @@ func TestPrepareExpirationHeader(t *testing.T) { msPerBlock: defaultDurations.msPerBlock, blockPerEpoch: defaultDurations.blockPerEpoch, }, - expected: map[string]string{objectv2.SysAttributeExpEpoch: strconv.FormatUint(uint64(math.MaxUint64), 10)}, + expected: map[string]string{object.AttributeExpirationEpoch: strconv.FormatUint(uint64(math.MaxUint64), 10)}, }, { name: "invalid timestamp sec",