1
1
Fork 0
mirror of https://github.com/goreleaser/nfpm synced 2024-05-22 08:06:17 +02:00

feat: add support for remote signing keys (#695)

* feat: Add support for remote signing keys

When used as a library, `nfpm.PackageSignature.SignFn` can be set as an
alternative to `KeyFile`. This allows arbitrary signing key
implementations, like a remote signing server.

Updates https://github.com/tailscale/tailscale/issues/1882

* Update rpm/rpm_test.go

Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Andrew Lytvynov 2023-08-03 05:36:30 -07:00 committed by GitHub
parent 577ae45531
commit 24a43c5ad7
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 129 additions and 6 deletions

View File

@ -132,7 +132,7 @@ func (*Apk) Package(info *nfpm.Info, apk io.Writer) (err error) {
return err
}
if info.APK.Signature.KeyFile == "" {
if info.APK.Signature.KeyFile == "" && info.APK.Signature.SignFn == nil {
return combineToApk(apk, &bufControl, &bufData)
}
@ -267,8 +267,14 @@ var errNoKeyAddress = errors.New("key name not set and maintainer mail address e
func createSignatureBuilder(digest []byte, info *nfpm.Info) func(*tar.Writer) error {
return func(tw *tar.Writer) error {
signature, err := sign.RSASignSHA1Digest(digest,
info.APK.Signature.KeyFile, info.APK.Signature.KeyPassphrase)
var signature []byte
var err error
if signFn := info.APK.Signature.SignFn; signFn != nil {
signature, err = signFn(bytes.NewReader(digest))
} else {
signature, err = sign.RSASignSHA1Digest(digest,
info.APK.Signature.KeyFile, info.APK.Signature.KeyPassphrase)
}
if err != nil {
return err
}

View File

@ -363,6 +363,33 @@ func TestSignatureError(t *testing.T) {
require.True(t, errors.As(err, &expectedError))
}
func TestSignatureCallback(t *testing.T) {
info := exampleInfo()
info.APK.Signature.SignFn = func(r io.Reader) ([]byte, error) {
digest, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return sign.RSASignSHA1Digest(digest, "../internal/sign/testdata/rsa.priv", "hunter2")
}
info.APK.Signature.KeyName = "testkey.rsa.pub"
err := nfpm.PrepareForPackager(info, "apk")
require.NoError(t, err)
digest := sha1.New().Sum(nil) // nolint:gosec
var signatureTarGz bytes.Buffer
tw := tar.NewWriter(&signatureTarGz)
require.NoError(t, createSignatureBuilder(digest, info)(tw))
signature := extractFromTar(t, signatureTarGz.Bytes(), ".SIGN.RSA.testkey.rsa.pub")
err = sign.RSAVerifySHA1Digest(digest, signature, "../internal/sign/testdata/rsa.pub")
require.NoError(t, err)
err = Default.Package(info, io.Discard)
require.NoError(t, err)
}
func TestDisableGlobbing(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true

View File

@ -136,7 +136,7 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
return fmt.Errorf("cannot add data.tar.gz to deb: %w", err)
}
if info.Deb.Signature.KeyFile != "" {
if info.Deb.Signature.KeyFile != "" || info.Deb.Signature.SignFn != nil {
sig, sigType, err := doSign(info, debianBinary, controlTarGz, dataTarball)
if err != nil {
return err
@ -172,7 +172,12 @@ func dpkgSign(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) (
return nil, sigType, &nfpm.ErrSigningFailure{Err: err}
}
sig, err := sign.PGPClearSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
var sig []byte
if signFn := info.Deb.Signature.SignFn; signFn != nil {
sig, err = signFn(data)
} else {
sig, err = sign.PGPClearSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
}
if err != nil {
return nil, sigType, &nfpm.ErrSigningFailure{Err: err}
}
@ -193,7 +198,13 @@ func debSign(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) ([
}
}
sig, err := sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
var sig []byte
var err error
if signFn := info.Deb.Signature.SignFn; signFn != nil {
sig, err = signFn(data)
} else {
sig, err = sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
}
if err != nil {
return nil, sigType, &nfpm.ErrSigningFailure{Err: err}
}

View File

@ -960,6 +960,28 @@ func TestDebsigsSignatureError(t *testing.T) {
require.True(t, errors.As(err, &expectedError))
}
func TestDebsigsSignatureCallback(t *testing.T) {
info := exampleInfo()
info.Deb.Signature.SignFn = func(r io.Reader) ([]byte, error) {
return sign.PGPArmoredDetachSignWithKeyID(r, "../internal/sign/testdata/privkey.asc", "hunter2", nil)
}
var deb bytes.Buffer
err := Default.Package(info, &deb)
require.NoError(t, err)
debBinary := extractFileFromAr(t, deb.Bytes(), "debian-binary")
controlTarGz := extractFileFromAr(t, deb.Bytes(), "control.tar.gz")
dataTarball := extractFileFromAr(t, deb.Bytes(), findDataTarball(t, deb.Bytes()))
signature := extractFileFromAr(t, deb.Bytes(), "_gpgorigin")
message := io.MultiReader(bytes.NewReader(debBinary),
bytes.NewReader(controlTarGz), bytes.NewReader(dataTarball))
err = sign.PGPVerify(message, signature, "../internal/sign/testdata/pubkey.asc")
require.NoError(t, err)
}
func TestDpkgSigSignature(t *testing.T) {
info := exampleInfo()
info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
@ -990,6 +1012,24 @@ func TestDpkgSigSignatureError(t *testing.T) {
require.True(t, errors.As(err, &expectedError))
}
func TestDpkgSigSignatureCallback(t *testing.T) {
info := exampleInfo()
info.Deb.Signature.SignFn = func(r io.Reader) ([]byte, error) {
return sign.PGPClearSignWithKeyID(r, "../internal/sign/testdata/privkey.asc", "hunter2", nil)
}
info.Deb.Signature.Method = "dpkg-sig"
info.Deb.Signature.Signer = "bob McRobert"
var deb bytes.Buffer
err := Default.Package(info, &deb)
require.NoError(t, err)
signature := extractFileFromAr(t, deb.Bytes(), "_gpgbuilder")
err = sign.PGPReadMessage(signature, "../internal/sign/testdata/pubkey.asc")
require.NoError(t, err)
}
func TestDisableGlobbing(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true

View File

@ -351,6 +351,13 @@ type PackageSignature struct {
KeyFile string `yaml:"key_file,omitempty" json:"key_file,omitempty" jsonschema:"title=key file,example=key.gpg"`
KeyID *string `yaml:"key_id,omitempty" json:"key_id,omitempty" jsonschema:"title=key id,example=bc8acdd415bd80b3"`
KeyPassphrase string `yaml:"-" json:"-"` // populated from environment variable
// SignFn, if set, will be called with the package-specific data to sign.
// For deb and rpm packages, data is the full package content.
// For apk packages, data is the SHA1 digest of control tgz.
//
// This allows for signing implementations other than using a local file
// (for example using a remote signer like KMS).
SignFn func(data io.Reader) ([]byte, error) `yaml:"-" json:"-"` // populated when used as a library
}
type RPMSignature struct {

View File

@ -126,6 +126,11 @@ func (*RPM) Package(info *nfpm.Info, w io.Writer) (err error) {
info.RPM.Signature.KeyID,
))
}
if signFn := info.RPM.Signature.SignFn; signFn != nil {
rpm.SetPGPSigner(func(data []byte) ([]byte, error) {
return signFn(bytes.NewReader(data))
})
}
if err = createFilesInsideRPM(info, rpm); err != nil {
return err

View File

@ -17,6 +17,7 @@ import (
"github.com/goreleaser/chglog"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"github.com/goreleaser/nfpm/v2/internal/sign"
"github.com/stretchr/testify/require"
)
@ -743,6 +744,32 @@ func TestRPMSignatureError(t *testing.T) {
require.True(t, errors.As(err, &expectedError))
}
func TestRPMSignatureCallback(t *testing.T) {
info := exampleInfo()
info.RPM.Signature.SignFn = func(r io.Reader) ([]byte, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return sign.PGPSignerWithKeyID("../internal/sign/testdata/privkey.asc", "hunter2", nil)(data)
}
pubkeyFileContent, err := os.ReadFile("../internal/sign/testdata/pubkey.gpg")
require.NoError(t, err)
keyring, err := openpgp.ReadKeyRing(bytes.NewReader(pubkeyFileContent))
require.NoError(t, err)
require.NotNil(t, keyring, "cannot verify sigs with an empty keyring")
var rpmBuffer bytes.Buffer
err = Default.Package(info, &rpmBuffer)
require.NoError(t, err)
_, sigs, err := rpmutils.Verify(bytes.NewReader(rpmBuffer.Bytes()), keyring)
require.NoError(t, err)
require.Len(t, sigs, 2)
}
func TestRPMGhostFiles(t *testing.T) {
filename := "/usr/lib/casper.a"