diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 89d04d381c..62edef597c 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2555,7 +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 - +;; 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 00fba67b39..bc093e7ea6 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -42,6 +42,8 @@ var ( LimitSizeRubyGems int64 LimitSizeSwift int64 LimitSizeVagrant int64 + + DefaultRPMSignEnabled bool }{ Enabled: true, LimitTotalOwnerCount: -1, @@ -97,6 +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.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 11d7729eec..4c822e0999 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -133,6 +133,21 @@ func UploadPackageFile(ctx *context.Context) { } defer buf.Close() + // if rpm sign enabled + 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) + return + } + buf, err = rpm_service.SignPackage(buf, pri) + if err != nil { + // Not in rpm format, parsing failed. + apiError(ctx, http.StatusBadRequest, err) + return + } + } + pck, err := rpm_module.ParsePackage(buf) if err != nil { if errors.Is(err, util.ErrInvalidArgument) { @@ -142,7 +157,6 @@ func UploadPackageFile(ctx *context.Context) { } return } - if _, err := buf.Seek(0, io.SeekStart); err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go index c52c8a5dd9..19968f9b30 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/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 1dcec6099e..6feceaeb78 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) + }) }) } }