From 9fdaf1c3f3669457ea67e085c9451196aeb8f8ba Mon Sep 17 00:00:00 2001 From: Exploding Dragon Date: Thu, 14 Sep 2023 04:47:50 +0000 Subject: [PATCH 1/8] support gpg sign for rpm --- modules/packages/rpm/sign.go | 39 ++++ routers/api/packages/rpm/rpm.go | 30 +++- routers/api/packages/rpm/rpm.go.orig | 259 +++++++++++++++++++++++++++ 3 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 modules/packages/rpm/sign.go create mode 100644 routers/api/packages/rpm/rpm.go.orig diff --git a/modules/packages/rpm/sign.go b/modules/packages/rpm/sign.go new file mode 100644 index 0000000000000..f49a45f804de8 --- /dev/null +++ b/modules/packages/rpm/sign.go @@ -0,0 +1,39 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rpm + +import ( + "bytes" + "io" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/packages" + + "github.com/sassoftware/go-rpmutils" + "golang.org/x/crypto/openpgp" +) + +func SignPackage(rpm *packages.HashedBuffer, privateKey string) (io.Reader, int64, error) { + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey))) + if err != nil { + // failed to parse key + return nil, 0, err + } + entity := keyring[0] + h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil) + if err != nil { + // error signing rpm + return nil, 0, err + } + signBlob, err := h.DumpSignatureHeader(false) + if err != nil { + // error writing sig header + return nil, 0, err + } + if len(signBlob)%8 != 0 { + log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob)) + return nil, 0, err + } + return bytes.NewReader(signBlob), int64(h.OriginalSignatureHeaderSize()), nil +} diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 1e462bb9080f4..c651dc71dbaa6 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -101,7 +101,30 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() - pck, err := rpm_module.ParsePackage(buf) + pri, _, err := rpm_service.GetOrCreateKeyPair(ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + hBuf, seek, err := rpm_module.SignPackage(buf, pri) + if _, err := buf.Seek(seek, io.SeekStart); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + signBuf, err := packages_module.CreateHashedBufferFromReader(io.MultiReader(hBuf, buf)) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + defer signBuf.Close() + + if _, err := signBuf.Seek(0, io.SeekStart); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + pck, err := rpm_module.ParsePackage(signBuf) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { apiError(ctx, http.StatusBadRequest, err) @@ -110,8 +133,7 @@ func UploadPackageFile(ctx *context.Context) { } return } - - if _, err := buf.Seek(0, io.SeekStart); err != nil { + if _, err := signBuf.Seek(0, io.SeekStart); err != nil { apiError(ctx, http.StatusInternalServerError, err) return } @@ -138,7 +160,7 @@ func UploadPackageFile(ctx *context.Context) { Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), }, Creator: ctx.Doer, - Data: buf, + Data: signBuf, IsLead: true, Properties: map[string]string{ rpm_module.PropertyMetadata: string(fileMetadataRaw), diff --git a/routers/api/packages/rpm/rpm.go.orig b/routers/api/packages/rpm/rpm.go.orig new file mode 100644 index 0000000000000..1e462bb9080f4 --- /dev/null +++ b/routers/api/packages/rpm/rpm.go.orig @@ -0,0 +1,259 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rpm + +import ( + stdctx "context" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/json" + packages_module "code.gitea.io/gitea/modules/packages" + rpm_module "code.gitea.io/gitea/modules/packages/rpm" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/api/packages/helper" + notify_service "code.gitea.io/gitea/services/notify" + packages_service "code.gitea.io/gitea/services/packages" + rpm_service "code.gitea.io/gitea/services/packages/rpm" +) + +func apiError(ctx *context.Context, status int, obj any) { + helper.LogAndProcessError(ctx, status, obj, func(message string) { + ctx.PlainText(status, message) + }) +} + +// https://dnf.readthedocs.io/en/latest/conf_ref.html +func GetRepositoryConfig(ctx *context.Context) { + url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name) + + ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`] +name=`+ctx.Package.Owner.Name+` - `+setting.AppName+` +baseurl=`+url+` +enabled=1 +gpgcheck=1 +gpgkey=`+url+`/repository.key`) +} + +// Gets or creates the PGP public key used to sign repository metadata files +func GetRepositoryKey(ctx *context.Context) { + _, pub, err := rpm_service.GetOrCreateKeyPair(ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ + ContentType: "application/pgp-keys", + Filename: "repository.key", + }) +} + +// Gets a pre-generated repository metadata file +func GetRepositoryFile(ctx *context.Context) { + pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( + ctx, + pv, + &packages_service.PackageFileInfo{ + Filename: ctx.Params("filename"), + }, + ) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + helper.ServePackageFile(ctx, s, u, pf) +} + +func UploadPackageFile(ctx *context.Context) { + upload, close, err := ctx.UploadStream() + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if close { + defer upload.Close() + } + + buf, err := packages_module.CreateHashedBufferFromReader(upload) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer buf.Close() + + pck, err := rpm_module.ParsePackage(buf) + if err != nil { + if errors.Is(err, util.ErrInvalidArgument) { + apiError(ctx, http.StatusBadRequest, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if _, err := buf.Seek(0, io.SeekStart); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + fileMetadataRaw, err := json.Marshal(pck.FileMetadata) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + _, _, err = packages_service.CreatePackageOrAddFileToExisting( + &packages_service.PackageCreationInfo{ + PackageInfo: packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeRpm, + Name: pck.Name, + Version: pck.Version, + }, + Creator: ctx.Doer, + Metadata: pck.VersionMetadata, + }, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), + }, + Creator: ctx.Doer, + Data: buf, + IsLead: true, + Properties: map[string]string{ + rpm_module.PropertyMetadata: string(fileMetadataRaw), + }, + }, + ) + if err != nil { + switch err { + case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile: + apiError(ctx, http.StatusConflict, err) + case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + apiError(ctx, http.StatusForbidden, err) + default: + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + ctx.Status(http.StatusCreated) +} + +func DownloadPackageFile(ctx *context.Context) { + name := ctx.Params("name") + version := ctx.Params("version") + + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + ctx, + &packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeRpm, + Name: name, + Version: version, + }, + &packages_service.PackageFileInfo{ + Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), + }, + ) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(ctx, http.StatusNotFound, err) + } else { + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + helper.ServePackageFile(ctx, s, u, pf) +} + +func DeletePackageFile(webctx *context.Context) { + name := webctx.Params("name") + version := webctx.Params("version") + architecture := webctx.Params("architecture") + + var pd *packages_model.PackageDescriptor + + err := db.WithTx(webctx, func(ctx stdctx.Context) error { + pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version) + if err != nil { + return err + } + + pf, err := packages_model.GetFileForVersionByName( + ctx, + pv.ID, + fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture), + packages_model.EmptyFileKey, + ) + if err != nil { + return err + } + + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { + return err + } + + has, err := packages_model.HasVersionFileReferences(ctx, pv.ID) + if err != nil { + return err + } + if !has { + pd, err = packages_model.GetPackageDescriptor(ctx, pv) + if err != nil { + return err + } + + if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { + return err + } + } + + return nil + }) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + apiError(webctx, http.StatusNotFound, err) + } else { + apiError(webctx, http.StatusInternalServerError, err) + } + return + } + + if pd != nil { + notify_service.PackageDelete(webctx, webctx.Doer, pd) + } + + if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil { + apiError(webctx, http.StatusInternalServerError, err) + return + } + + webctx.Status(http.StatusNoContent) +} From d2cf61a7a2acd4cd7c232ffaf71ac1a0e2cb6145 Mon Sep 17 00:00:00 2001 From: Exploding Dragon Date: Thu, 14 Sep 2023 12:58:12 +0800 Subject: [PATCH 2/8] Delete routers/api/packages/rpm/rpm.go.orig --- routers/api/packages/rpm/rpm.go.orig | 259 --------------------------- 1 file changed, 259 deletions(-) delete mode 100644 routers/api/packages/rpm/rpm.go.orig diff --git a/routers/api/packages/rpm/rpm.go.orig b/routers/api/packages/rpm/rpm.go.orig deleted file mode 100644 index 1e462bb9080f4..0000000000000 --- a/routers/api/packages/rpm/rpm.go.orig +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package rpm - -import ( - stdctx "context" - "errors" - "fmt" - "io" - "net/http" - "strings" - - "code.gitea.io/gitea/models/db" - packages_model "code.gitea.io/gitea/models/packages" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/json" - packages_module "code.gitea.io/gitea/modules/packages" - rpm_module "code.gitea.io/gitea/modules/packages/rpm" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" - "code.gitea.io/gitea/routers/api/packages/helper" - notify_service "code.gitea.io/gitea/services/notify" - packages_service "code.gitea.io/gitea/services/packages" - rpm_service "code.gitea.io/gitea/services/packages/rpm" -) - -func apiError(ctx *context.Context, status int, obj any) { - helper.LogAndProcessError(ctx, status, obj, func(message string) { - ctx.PlainText(status, message) - }) -} - -// https://dnf.readthedocs.io/en/latest/conf_ref.html -func GetRepositoryConfig(ctx *context.Context) { - url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name) - - ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`] -name=`+ctx.Package.Owner.Name+` - `+setting.AppName+` -baseurl=`+url+` -enabled=1 -gpgcheck=1 -gpgkey=`+url+`/repository.key`) -} - -// Gets or creates the PGP public key used to sign repository metadata files -func GetRepositoryKey(ctx *context.Context) { - _, pub, err := rpm_service.GetOrCreateKeyPair(ctx.Package.Owner.ID) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ - ContentType: "application/pgp-keys", - Filename: "repository.key", - }) -} - -// Gets a pre-generated repository metadata file -func GetRepositoryFile(ctx *context.Context) { - pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - s, u, pf, err := packages_service.GetFileStreamByPackageVersion( - ctx, - pv, - &packages_service.PackageFileInfo{ - Filename: ctx.Params("filename"), - }, - ) - if err != nil { - if errors.Is(err, util.ErrNotExist) { - apiError(ctx, http.StatusNotFound, err) - } else { - apiError(ctx, http.StatusInternalServerError, err) - } - return - } - - helper.ServePackageFile(ctx, s, u, pf) -} - -func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - if close { - defer upload.Close() - } - - buf, err := packages_module.CreateHashedBufferFromReader(upload) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - defer buf.Close() - - pck, err := rpm_module.ParsePackage(buf) - if err != nil { - if errors.Is(err, util.ErrInvalidArgument) { - apiError(ctx, http.StatusBadRequest, err) - } else { - apiError(ctx, http.StatusInternalServerError, err) - } - return - } - - if _, err := buf.Seek(0, io.SeekStart); err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - fileMetadataRaw, err := json.Marshal(pck.FileMetadata) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - _, _, err = packages_service.CreatePackageOrAddFileToExisting( - &packages_service.PackageCreationInfo{ - PackageInfo: packages_service.PackageInfo{ - Owner: ctx.Package.Owner, - PackageType: packages_model.TypeRpm, - Name: pck.Name, - Version: pck.Version, - }, - Creator: ctx.Doer, - Metadata: pck.VersionMetadata, - }, - &packages_service.PackageFileCreationInfo{ - PackageFileInfo: packages_service.PackageFileInfo{ - Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), - }, - Creator: ctx.Doer, - Data: buf, - IsLead: true, - Properties: map[string]string{ - rpm_module.PropertyMetadata: string(fileMetadataRaw), - }, - }, - ) - if err != nil { - switch err { - case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile: - apiError(ctx, http.StatusConflict, err) - case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: - apiError(ctx, http.StatusForbidden, err) - default: - apiError(ctx, http.StatusInternalServerError, err) - } - return - } - - if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - ctx.Status(http.StatusCreated) -} - -func DownloadPackageFile(ctx *context.Context) { - name := ctx.Params("name") - version := ctx.Params("version") - - s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( - ctx, - &packages_service.PackageInfo{ - Owner: ctx.Package.Owner, - PackageType: packages_model.TypeRpm, - Name: name, - Version: version, - }, - &packages_service.PackageFileInfo{ - Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), - }, - ) - if err != nil { - if errors.Is(err, util.ErrNotExist) { - apiError(ctx, http.StatusNotFound, err) - } else { - apiError(ctx, http.StatusInternalServerError, err) - } - return - } - - helper.ServePackageFile(ctx, s, u, pf) -} - -func DeletePackageFile(webctx *context.Context) { - name := webctx.Params("name") - version := webctx.Params("version") - architecture := webctx.Params("architecture") - - var pd *packages_model.PackageDescriptor - - err := db.WithTx(webctx, func(ctx stdctx.Context) error { - pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version) - if err != nil { - return err - } - - pf, err := packages_model.GetFileForVersionByName( - ctx, - pv.ID, - fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture), - packages_model.EmptyFileKey, - ) - if err != nil { - return err - } - - if err := packages_service.DeletePackageFile(ctx, pf); err != nil { - return err - } - - has, err := packages_model.HasVersionFileReferences(ctx, pv.ID) - if err != nil { - return err - } - if !has { - pd, err = packages_model.GetPackageDescriptor(ctx, pv) - if err != nil { - return err - } - - if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { - return err - } - } - - return nil - }) - if err != nil { - if errors.Is(err, util.ErrNotExist) { - apiError(webctx, http.StatusNotFound, err) - } else { - apiError(webctx, http.StatusInternalServerError, err) - } - return - } - - if pd != nil { - notify_service.PackageDelete(webctx, webctx.Doer, pd) - } - - if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil { - apiError(webctx, http.StatusInternalServerError, err) - return - } - - webctx.Status(http.StatusNoContent) -} From 4b7ac8177b30d91a82916e20f2fa41a3880b1fa6 Mon Sep 17 00:00:00 2001 From: dragon Date: Wed, 15 May 2024 15:42:24 +0800 Subject: [PATCH 3/8] upgrade go-rpmutils to v0.4.0 --- go.mod | 12 ++++++------ go.sum | 27 ++++++++++++++------------- modules/packages/rpm/sign.go | 4 ++-- routers/api/packages/rpm/rpm.go | 6 +++++- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 8afefc6367ccf..397e116f0b396 100644 --- a/go.mod +++ b/go.mod @@ -92,13 +92,13 @@ require ( github.com/redis/go-redis/v9 v9.5.1 github.com/robfig/cron/v3 v3.0.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 - github.com/sassoftware/go-rpmutils v0.3.0 + github.com/sassoftware/go-rpmutils v0.4.0 github.com/sergi/go-diff v1.3.1 github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.0 github.com/tstranex/u2f v1.0.0 - github.com/ulikunitz/xz v0.5.11 + github.com/ulikunitz/xz v0.5.12 github.com/urfave/cli/v2 v2.27.1 github.com/xanzy/go-gitlab v0.100.0 github.com/xeipuuv/gojsonschema v1.2.0 @@ -106,12 +106,12 @@ require ( github.com/yuin/goldmark v1.7.0 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 - golang.org/x/crypto v0.22.0 + golang.org/x/crypto v0.23.0 golang.org/x/image v0.15.0 golang.org/x/net v0.24.0 golang.org/x/oauth2 v0.18.0 - golang.org/x/sys v0.19.0 - golang.org/x/text v0.14.0 + golang.org/x/sys v0.20.0 + golang.org/x/text v0.15.0 golang.org/x/tools v0.19.0 google.golang.org/grpc v1.62.1 google.golang.org/protobuf v1.33.0 @@ -164,7 +164,7 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect + github.com/cloudflare/circl v1.3.8 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.3.1 // indirect github.com/couchbase/goutils v0.1.2 // indirect diff --git a/go.sum b/go.sum index 1d493f4ca44e2..34043cc06f60d 100644 --- a/go.sum +++ b/go.sum @@ -185,8 +185,8 @@ github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwys github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= +github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -690,8 +690,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/sassoftware/go-rpmutils v0.3.0 h1:tE4TZ8KcOXay5iIP64P291s6Qxd9MQCYhI7DU+f3gFA= -github.com/sassoftware/go-rpmutils v0.3.0/go.mod h1:hM9wdxFsjUFR/tJ6SMsLrJuChcucCa0DsCzE9RMfwMo= +github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= +github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= @@ -768,8 +768,8 @@ github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGB github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= @@ -860,8 +860,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw= golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= @@ -945,8 +945,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -956,8 +956,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -969,8 +969,9 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/modules/packages/rpm/sign.go b/modules/packages/rpm/sign.go index f49a45f804de8..4068e07ac0154 100644 --- a/modules/packages/rpm/sign.go +++ b/modules/packages/rpm/sign.go @@ -10,14 +10,14 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/packages" + "github.com/ProtonMail/go-crypto/openpgp" "github.com/sassoftware/go-rpmutils" - "golang.org/x/crypto/openpgp" ) func SignPackage(rpm *packages.HashedBuffer, privateKey string) (io.Reader, int64, error) { keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey))) if err != nil { - // failed to parse key + // failed to parse key return nil, 0, err } entity := keyring[0] diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 3171726c0784b..caf328a84394d 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -133,12 +133,16 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() - pri, _, err := rpm_service.GetOrCreateKeyPair(ctx.Package.Owner.ID) + pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } hBuf, seek, err := rpm_module.SignPackage(buf, pri) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } if _, err := buf.Seek(seek, io.SeekStart); err != nil { apiError(ctx, http.StatusInternalServerError, err) return From ca21f30563b638b9bf522fceab76356c18a0ba65 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 21 May 2024 17:36:36 +0800 Subject: [PATCH 4/8] add test --- custom/conf/app.example.ini | 3 +- modules/setting/packages.go | 3 + routers/api/packages/rpm/rpm.go | 38 ++++-------- {modules => services}/packages/rpm/sign.go | 18 ++++-- services/packages/rpm/sign_test.go | 71 ++++++++++++++++++++++ 5 files changed, 101 insertions(+), 32 deletions(-) rename {modules => services}/packages/rpm/sign.go (69%) create mode 100644 services/packages/rpm/sign_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 4df843b8cef2a..bdf952996e12f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2496,7 +2496,8 @@ LEVEL = Info ;LIMIT_SIZE_SWIFT = -1 ;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_VAGRANT = -1 - +;; Resign RPM packages (using v4 format, not compatible with CentOS 7 or older) +;RPM_SIGN_ENABLED = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; default storage for attachments, lfs and avatars diff --git a/modules/setting/packages.go b/modules/setting/packages.go index b225615a24012..576c76c6a83e6 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -44,9 +44,11 @@ var ( LimitSizeRubyGems int64 LimitSizeSwift int64 LimitSizeVagrant int64 + RPMSginEnabled bool }{ Enabled: true, LimitTotalOwnerCount: -1, + RPMSginEnabled: false, } ) @@ -102,6 +104,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") + Packages.RPMSginEnabled = sec.Key("RPM_SIGN_ENABLED").MustBool(false) return nil } diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index caf328a84394d..45021720fba89 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -133,31 +133,19 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() - pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - hBuf, seek, err := rpm_module.SignPackage(buf, pri) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - if _, err := buf.Seek(seek, io.SeekStart); err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - signBuf, err := packages_module.CreateHashedBufferFromReader(io.MultiReader(hBuf, buf)) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - defer signBuf.Close() - - if _, err := signBuf.Seek(0, io.SeekStart); err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return + var signBuf = buf + // if rpm sign enabled + if setting.Packages.RPMSginEnabled { + pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + signBuf, err = rpm_service.SignPackage(buf, pri) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } } pck, err := rpm_module.ParsePackage(signBuf) diff --git a/modules/packages/rpm/sign.go b/services/packages/rpm/sign.go similarity index 69% rename from modules/packages/rpm/sign.go rename to services/packages/rpm/sign.go index 4068e07ac0154..a16ab1745a763 100644 --- a/modules/packages/rpm/sign.go +++ b/services/packages/rpm/sign.go @@ -14,26 +14,32 @@ import ( "github.com/sassoftware/go-rpmutils" ) -func SignPackage(rpm *packages.HashedBuffer, privateKey string) (io.Reader, int64, error) { +func SignPackage(rpm *packages.HashedBuffer, privateKey string) (*packages.HashedBuffer, error) { keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey))) if err != nil { // failed to parse key - return nil, 0, err + return nil, err } entity := keyring[0] h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil) if err != nil { // error signing rpm - return nil, 0, err + return nil, err } signBlob, err := h.DumpSignatureHeader(false) if err != nil { // error writing sig header - return nil, 0, err + return nil, err } if len(signBlob)%8 != 0 { log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob)) - return nil, 0, err + return nil, err } - return bytes.NewReader(signBlob), int64(h.OriginalSignatureHeaderSize()), nil + + // move fp to sign end + if _, err := rpm.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil { + return nil, err + } + // create signed rpm buf + return packages.CreateHashedBufferFromReader(io.MultiReader(bytes.NewReader(signBlob), rpm)) } diff --git a/services/packages/rpm/sign_test.go b/services/packages/rpm/sign_test.go new file mode 100644 index 0000000000000..36c8b33332adc --- /dev/null +++ b/services/packages/rpm/sign_test.go @@ -0,0 +1,71 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rpm + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io" + "strings" + "testing" + + "code.gitea.io/gitea/modules/packages" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/sassoftware/go-rpmutils" + "github.com/stretchr/testify/assert" +) + +func TestSignPackage(t *testing.T) { + base64RpmPackageContent := `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` + rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent) + assert.NoError(t, err) + zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent)) + assert.NoError(t, err) + content, err := io.ReadAll(zr) + assert.NoError(t, err) + privateKey, publicKey, err := generateKeypair() + assert.NoError(t, err) + buf, err := packages.CreateHashedBufferFromReader(bytes.NewReader(content)) + assert.NoError(t, err) + body, err := SignPackage(buf, privateKey) + assert.NoError(t, err) + signedBody, err := io.ReadAll(body) + assert.NoError(t, err) + pub, err := openpgp.ReadArmoredKeyRing(strings.NewReader(publicKey)) + assert.NoError(t, err) + _, sigs, err := rpmutils.Verify(bytes.NewBuffer(signedBody), pub) + assert.NoError(t, err) + assert.NotEqual(t, len(sigs), 0) + _, sigs, err = rpmutils.Verify(bytes.NewBuffer(content), pub) + assert.NoError(t, err) + assert.Equal(t, len(sigs), 0) +} From 181cac8bd74c274bc35b4c419c0a47e487ff3afd Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 21 May 2024 17:36:40 +0800 Subject: [PATCH 5/8] add test --- routers/api/packages/rpm/rpm.go | 2 +- services/packages/rpm/sign_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 45021720fba89..0bca343774c82 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -133,7 +133,7 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() - var signBuf = buf + signBuf := buf // if rpm sign enabled if setting.Packages.RPMSginEnabled { pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) diff --git a/services/packages/rpm/sign_test.go b/services/packages/rpm/sign_test.go index 36c8b33332adc..6ca601c0ea850 100644 --- a/services/packages/rpm/sign_test.go +++ b/services/packages/rpm/sign_test.go @@ -12,6 +12,7 @@ import ( "testing" "code.gitea.io/gitea/modules/packages" + "github.com/ProtonMail/go-crypto/openpgp" "github.com/sassoftware/go-rpmutils" "github.com/stretchr/testify/assert" From 613045b7569fc2aaa253d21ec43143db4ec02bc2 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 21 May 2024 18:10:07 +0800 Subject: [PATCH 6/8] clean style --- routers/api/packages/rpm/rpm.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 0bca343774c82..66855e92c4e71 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -133,7 +133,6 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() - signBuf := buf // if rpm sign enabled if setting.Packages.RPMSginEnabled { pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) @@ -141,14 +140,15 @@ func UploadPackageFile(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - signBuf, err = rpm_service.SignPackage(buf, pri) + buf, err = rpm_service.SignPackage(buf, pri) if err != nil { - apiError(ctx, http.StatusInternalServerError, err) + // Not in rpm format, parsing failed. + apiError(ctx, http.StatusBadRequest, err) return } } - pck, err := rpm_module.ParsePackage(signBuf) + pck, err := rpm_module.ParsePackage(buf) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { apiError(ctx, http.StatusBadRequest, err) @@ -157,7 +157,7 @@ func UploadPackageFile(ctx *context.Context) { } return } - if _, err := signBuf.Seek(0, io.SeekStart); err != nil { + if _, err := buf.Seek(0, io.SeekStart); err != nil { apiError(ctx, http.StatusInternalServerError, err) return } @@ -186,7 +186,7 @@ func UploadPackageFile(ctx *context.Context) { CompositeKey: group, }, Creator: ctx.Doer, - Data: signBuf, + Data: buf, IsLead: true, Properties: map[string]string{ rpm_module.PropertyGroup: group, From 7ebc829d64f3712e83a0202702e0738f9786bfd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B9=B4=E5=B9=BC=E6=B6=A9=E6=89=B9?= Date: Fri, 2 Aug 2024 00:29:04 +0800 Subject: [PATCH 7/8] fix typo --- modules/setting/packages.go | 5 ++--- routers/api/packages/rpm/rpm.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 12209c7518bd5..ff346827b4101 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -42,11 +42,10 @@ var ( LimitSizeRubyGems int64 LimitSizeSwift int64 LimitSizeVagrant int64 - RPMSginEnabled bool + RPMSignEnabled bool }{ Enabled: true, LimitTotalOwnerCount: -1, - RPMSginEnabled: false, } ) @@ -99,7 +98,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") - Packages.RPMSginEnabled = sec.Key("RPM_SIGN_ENABLED").MustBool(false) + Packages.RPMSignEnabled = sec.Key("RPM_SIGN_ENABLED").MustBool(false) return nil } diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 87fdc0a251d44..9e78dd391feb6 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -134,7 +134,7 @@ func UploadPackageFile(ctx *context.Context) { defer buf.Close() // if rpm sign enabled - if setting.Packages.RPMSginEnabled { + if setting.Packages.RPMSignEnabled { pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) From 4ff93968800629a075366a8ebf6310daa1dd1126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B9=B4=E5=B9=BC=E6=B6=A9=E6=89=B9?= Date: Sat, 3 Aug 2024 11:33:05 +0800 Subject: [PATCH 8/8] clean style --- custom/conf/app.example.ini | 4 +- modules/setting/packages.go | 5 +- routers/api/packages/rpm/rpm.go | 2 +- services/packages/rpm/repository.go | 38 +++++++++++- services/packages/rpm/sign.go | 45 -------------- services/packages/rpm/sign_test.go | 72 ---------------------- tests/integration/api_packages_rpm_test.go | 27 ++++++++ 7 files changed, 68 insertions(+), 125 deletions(-) delete mode 100644 services/packages/rpm/sign.go delete mode 100644 services/packages/rpm/sign_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index cad9253389381..3a767b71e363f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2555,8 +2555,8 @@ LEVEL = Info ;LIMIT_SIZE_SWIFT = -1 ;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_VAGRANT = -1 -;; Resign RPM packages (using v4 format, not compatible with CentOS 7 or older) -;RPM_SIGN_ENABLED = false +;; Enable RPM re-signing by default. (It will overwrite the old signature ,using v4 format, not compatible with CentOS 6 or older) +;DEFAULT_RPM_SIGN_ENABLED = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; default storage for attachments, lfs and avatars diff --git a/modules/setting/packages.go b/modules/setting/packages.go index ff346827b4101..bc093e7ea6c1d 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -42,7 +42,8 @@ var ( LimitSizeRubyGems int64 LimitSizeSwift int64 LimitSizeVagrant int64 - RPMSignEnabled bool + + DefaultRPMSignEnabled bool }{ Enabled: true, LimitTotalOwnerCount: -1, @@ -98,7 +99,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS") Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") - Packages.RPMSignEnabled = sec.Key("RPM_SIGN_ENABLED").MustBool(false) + Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false) return nil } diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 9e78dd391feb6..4c822e0999d2a 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -134,7 +134,7 @@ func UploadPackageFile(ctx *context.Context) { defer buf.Close() // if rpm sign enabled - if setting.Packages.RPMSignEnabled { + if setting.Packages.DefaultRPMSignEnabled || ctx.FormBool("sign") { pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go index c52c8a5dd98c5..19968f9b30a24 100644 --- a/services/packages/rpm/repository.go +++ b/services/packages/rpm/repository.go @@ -21,14 +21,16 @@ import ( rpm_model "code.gitea.io/gitea/models/packages/rpm" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" packages_module "code.gitea.io/gitea/modules/packages" rpm_module "code.gitea.io/gitea/modules/packages/rpm" "code.gitea.io/gitea/modules/util" packages_service "code.gitea.io/gitea/services/packages" - "github.com/keybase/go-crypto/openpgp" - "github.com/keybase/go-crypto/openpgp/armor" - "github.com/keybase/go-crypto/openpgp/packet" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" + "github.com/ProtonMail/go-crypto/openpgp/packet" + "github.com/sassoftware/go-rpmutils" ) // GetOrCreateRepositoryVersion gets or creates the internal repository package @@ -641,3 +643,33 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, OpenSize: wc.Written(), }, nil } + +func SignPackage(rpm *packages_module.HashedBuffer, privateKey string) (*packages_module.HashedBuffer, error) { + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey))) + if err != nil { + // failed to parse key + return nil, err + } + entity := keyring[0] + h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil) + if err != nil { + // error signing rpm + return nil, err + } + signBlob, err := h.DumpSignatureHeader(false) + if err != nil { + // error writing sig header + return nil, err + } + if len(signBlob)%8 != 0 { + log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob)) + return nil, err + } + + // move fp to sign end + if _, err := rpm.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil { + return nil, err + } + // create signed rpm buf + return packages_module.CreateHashedBufferFromReader(io.MultiReader(bytes.NewReader(signBlob), rpm)) +} diff --git a/services/packages/rpm/sign.go b/services/packages/rpm/sign.go deleted file mode 100644 index a16ab1745a763..0000000000000 --- a/services/packages/rpm/sign.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package rpm - -import ( - "bytes" - "io" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/packages" - - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/sassoftware/go-rpmutils" -) - -func SignPackage(rpm *packages.HashedBuffer, privateKey string) (*packages.HashedBuffer, error) { - keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey))) - if err != nil { - // failed to parse key - return nil, err - } - entity := keyring[0] - h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil) - if err != nil { - // error signing rpm - return nil, err - } - signBlob, err := h.DumpSignatureHeader(false) - if err != nil { - // error writing sig header - return nil, err - } - if len(signBlob)%8 != 0 { - log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob)) - return nil, err - } - - // move fp to sign end - if _, err := rpm.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil { - return nil, err - } - // create signed rpm buf - return packages.CreateHashedBufferFromReader(io.MultiReader(bytes.NewReader(signBlob), rpm)) -} diff --git a/services/packages/rpm/sign_test.go b/services/packages/rpm/sign_test.go deleted file mode 100644 index 6ca601c0ea850..0000000000000 --- a/services/packages/rpm/sign_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package rpm - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "io" - "strings" - "testing" - - "code.gitea.io/gitea/modules/packages" - - "github.com/ProtonMail/go-crypto/openpgp" - "github.com/sassoftware/go-rpmutils" - "github.com/stretchr/testify/assert" -) - -func TestSignPackage(t *testing.T) { - base64RpmPackageContent := `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` - rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent) - assert.NoError(t, err) - zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent)) - assert.NoError(t, err) - content, err := io.ReadAll(zr) - assert.NoError(t, err) - privateKey, publicKey, err := generateKeypair() - assert.NoError(t, err) - buf, err := packages.CreateHashedBufferFromReader(bytes.NewReader(content)) - assert.NoError(t, err) - body, err := SignPackage(buf, privateKey) - assert.NoError(t, err) - signedBody, err := io.ReadAll(body) - assert.NoError(t, err) - pub, err := openpgp.ReadArmoredKeyRing(strings.NewReader(publicKey)) - assert.NoError(t, err) - _, sigs, err := rpmutils.Verify(bytes.NewBuffer(signedBody), pub) - assert.NoError(t, err) - assert.NotEqual(t, len(sigs), 0) - _, sigs, err = rpmutils.Verify(bytes.NewBuffer(content), pub) - assert.NoError(t, err) - assert.Equal(t, len(sigs), 0) -} diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 1dcec6099e32b..6feceaeb78d7f 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -24,7 +24,10 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/tests" + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/sassoftware/go-rpmutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPackageRpm(t *testing.T) { @@ -431,6 +434,30 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) }) + + t.Run("UploadSign", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + url := groupURL + "/upload?sign=true" + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + + gpgReq := NewRequest(t, "GET", rootURL+"/repository.key") + gpgResp := MakeRequest(t, gpgReq, http.StatusOK) + pub, err := openpgp.ReadArmoredKeyRing(gpgResp.Body) + require.NoError(t, err) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)) + resp := MakeRequest(t, req, http.StatusOK) + + _, sigs, err := rpmutils.Verify(resp.Body, pub) + require.NoError(t, err) + require.NotEmpty(t, sigs) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + }) }) } }