Skip to content

Commit de175e3

Browse files
Add signature support for the RPM module (#27069)
close #27031 If the rpm package does not contain a matching gpg signature, the installation will fail. See (#27031) , now auto-signing rpm uploads. This option is turned off by default for compatibility.
1 parent 94cca88 commit de175e3

File tree

5 files changed

+82
-5
lines changed

5 files changed

+82
-5
lines changed

custom/conf/app.example.ini

+2-1
Original file line numberDiff line numberDiff line change
@@ -2555,7 +2555,8 @@ LEVEL = Info
25552555
;LIMIT_SIZE_SWIFT = -1
25562556
;; Maximum size of a Vagrant upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
25572557
;LIMIT_SIZE_VAGRANT = -1
2558-
2558+
;; Enable RPM re-signing by default. (It will overwrite the old signature ,using v4 format, not compatible with CentOS 6 or older)
2559+
;DEFAULT_RPM_SIGN_ENABLED = false
25592560
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25602561
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25612562
;; default storage for attachments, lfs and avatars

modules/setting/packages.go

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ var (
4242
LimitSizeRubyGems int64
4343
LimitSizeSwift int64
4444
LimitSizeVagrant int64
45+
46+
DefaultRPMSignEnabled bool
4547
}{
4648
Enabled: true,
4749
LimitTotalOwnerCount: -1,
@@ -97,6 +99,7 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
9799
Packages.LimitSizeRubyGems = mustBytes(sec, "LIMIT_SIZE_RUBYGEMS")
98100
Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT")
99101
Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT")
102+
Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false)
100103
return nil
101104
}
102105

routers/api/packages/rpm/rpm.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ func UploadPackageFile(ctx *context.Context) {
133133
}
134134
defer buf.Close()
135135

136+
// if rpm sign enabled
137+
if setting.Packages.DefaultRPMSignEnabled || ctx.FormBool("sign") {
138+
pri, _, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
139+
if err != nil {
140+
apiError(ctx, http.StatusInternalServerError, err)
141+
return
142+
}
143+
buf, err = rpm_service.SignPackage(buf, pri)
144+
if err != nil {
145+
// Not in rpm format, parsing failed.
146+
apiError(ctx, http.StatusBadRequest, err)
147+
return
148+
}
149+
}
150+
136151
pck, err := rpm_module.ParsePackage(buf)
137152
if err != nil {
138153
if errors.Is(err, util.ErrInvalidArgument) {
@@ -142,7 +157,6 @@ func UploadPackageFile(ctx *context.Context) {
142157
}
143158
return
144159
}
145-
146160
if _, err := buf.Seek(0, io.SeekStart); err != nil {
147161
apiError(ctx, http.StatusInternalServerError, err)
148162
return

services/packages/rpm/repository.go

+35-3
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@ import (
2121
rpm_model "code.gitea.io/gitea/models/packages/rpm"
2222
user_model "code.gitea.io/gitea/models/user"
2323
"code.gitea.io/gitea/modules/json"
24+
"code.gitea.io/gitea/modules/log"
2425
packages_module "code.gitea.io/gitea/modules/packages"
2526
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
2627
"code.gitea.io/gitea/modules/util"
2728
packages_service "code.gitea.io/gitea/services/packages"
2829

29-
"github.com/keybase/go-crypto/openpgp"
30-
"github.com/keybase/go-crypto/openpgp/armor"
31-
"github.com/keybase/go-crypto/openpgp/packet"
30+
"github.com/ProtonMail/go-crypto/openpgp"
31+
"github.com/ProtonMail/go-crypto/openpgp/armor"
32+
"github.com/ProtonMail/go-crypto/openpgp/packet"
33+
"github.com/sassoftware/go-rpmutils"
3234
)
3335

3436
// GetOrCreateRepositoryVersion gets or creates the internal repository package
@@ -641,3 +643,33 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion,
641643
OpenSize: wc.Written(),
642644
}, nil
643645
}
646+
647+
func SignPackage(rpm *packages_module.HashedBuffer, privateKey string) (*packages_module.HashedBuffer, error) {
648+
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(privateKey)))
649+
if err != nil {
650+
// failed to parse key
651+
return nil, err
652+
}
653+
entity := keyring[0]
654+
h, err := rpmutils.SignRpmStream(rpm, entity.PrivateKey, nil)
655+
if err != nil {
656+
// error signing rpm
657+
return nil, err
658+
}
659+
signBlob, err := h.DumpSignatureHeader(false)
660+
if err != nil {
661+
// error writing sig header
662+
return nil, err
663+
}
664+
if len(signBlob)%8 != 0 {
665+
log.Info("incorrect padding: got %d bytes, expected a multiple of 8", len(signBlob))
666+
return nil, err
667+
}
668+
669+
// move fp to sign end
670+
if _, err := rpm.Seek(int64(h.OriginalSignatureHeaderSize()), io.SeekStart); err != nil {
671+
return nil, err
672+
}
673+
// create signed rpm buf
674+
return packages_module.CreateHashedBufferFromReader(io.MultiReader(bytes.NewReader(signBlob), rpm))
675+
}

tests/integration/api_packages_rpm_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import (
2424
"code.gitea.io/gitea/modules/util"
2525
"code.gitea.io/gitea/tests"
2626

27+
"github.com/ProtonMail/go-crypto/openpgp"
28+
"github.com/sassoftware/go-rpmutils"
2729
"github.com/stretchr/testify/assert"
30+
"github.com/stretchr/testify/require"
2831
)
2932

3033
func TestPackageRpm(t *testing.T) {
@@ -431,6 +434,30 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`,
431434
AddBasicAuth(user.Name)
432435
MakeRequest(t, req, http.StatusNotFound)
433436
})
437+
438+
t.Run("UploadSign", func(t *testing.T) {
439+
defer tests.PrintCurrentTest(t)()
440+
url := groupURL + "/upload?sign=true"
441+
req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)).
442+
AddBasicAuth(user.Name)
443+
MakeRequest(t, req, http.StatusCreated)
444+
445+
gpgReq := NewRequest(t, "GET", rootURL+"/repository.key")
446+
gpgResp := MakeRequest(t, gpgReq, http.StatusOK)
447+
pub, err := openpgp.ReadArmoredKeyRing(gpgResp.Body)
448+
require.NoError(t, err)
449+
450+
req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture))
451+
resp := MakeRequest(t, req, http.StatusOK)
452+
453+
_, sigs, err := rpmutils.Verify(resp.Body, pub)
454+
require.NoError(t, err)
455+
require.NotEmpty(t, sigs)
456+
457+
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/package/%s/%s/%s", groupURL, packageName, packageVersion, packageArchitecture)).
458+
AddBasicAuth(user.Name)
459+
MakeRequest(t, req, http.StatusNoContent)
460+
})
434461
})
435462
}
436463
}

0 commit comments

Comments
 (0)