mirror of
https://github.com/goreleaser/nfpm
synced 2024-09-27 23:09:52 +02:00
feat: Package Signing (#224)
* feat: Add initial openpgp signing capability. * refactor: Rename signatures to sigs and expose verify method. * feat: Add debsigs support. * test: Add debsigs acceptance test. * feat: Add RSA signing capability. * feat: Add RPM signature support. * test: Add RPM signature acceptance test. * test: Move acceptance test keys in dedicated folder. * feat: Add APK signature support. * test: Add APK signature acceptance test. * feat: Expose deb signature type in config. * fix: Fix typo and superfluous explicit error check * fix: Fix password env extraction and add tests. * fix: Redirect rpmpack to temporary goreleaser vendoring. * fix: Catch missing maintainer email if no apk key name is set. * refactor: Put signature info in a dedicated struct. * doc: Add signing documentation. * fix: Add trailing newlines to some files and remove unnecessary comment. * fix: Change wrong references from SHA256 to SHA1 in the sign package. * fix: Improve error wording when no passphrase was provided. * fix: Remove another SHA256 reference. * fix: Fix signature errors. * test: Split up environment variable test. * fix: Don't parse passphrase from YAML. * deps: Update redirected rpmpack. * fix: Export and use signature related error type. * doc: Document ErrSigningFailure. * test: Fix apk signature error test. * test: Add rpm signature error test.
This commit is contained in:
parent
e3d6f8344d
commit
285a6bcaea
@ -251,6 +251,20 @@ func TestDebBreaks(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignatures(t *testing.T) {
|
||||
for _, format := range formats {
|
||||
format := format
|
||||
t.Run("signed", func(t *testing.T) {
|
||||
accept(t, acceptParms{
|
||||
Name: fmt.Sprintf("signed_%s", format),
|
||||
Conf: "signed.yaml",
|
||||
Format: format,
|
||||
Dockerfile: fmt.Sprintf("%s.signed.dockerfile", format),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type acceptParms struct {
|
||||
Name string
|
||||
Conf string
|
||||
|
7
acceptance/testdata/apk.signed.dockerfile
vendored
Normal file
7
acceptance/testdata/apk.signed.dockerfile
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
FROM alpine
|
||||
ARG package
|
||||
COPY keys/rsa_unprotected.pub /etc/apk/keys/john@example.com.rsa.pub
|
||||
COPY ${package} /tmp/foo.apk
|
||||
|
||||
RUN apk verify /tmp/foo.apk | grep "/tmp/foo.apk: 0 - OK"
|
||||
RUN apk add /tmp/foo.apk
|
31
acceptance/testdata/deb.signed.dockerfile
vendored
Normal file
31
acceptance/testdata/deb.signed.dockerfile
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
FROM ubuntu
|
||||
ARG package
|
||||
COPY keys/pubkey.gpg /usr/share/debsig/keyrings/BC8ACDD415BD80B3/debsig.gpg
|
||||
COPY ${package} /tmp/foo.deb
|
||||
|
||||
RUN apt update -y
|
||||
RUN apt install -y debsig-verify
|
||||
RUN mkdir -p /etc/debsig/policies/BC8ACDD415BD80B3
|
||||
RUN echo '<?xml version="1.0"?>\n\
|
||||
<!DOCTYPE Policy SYSTEM "https://www.debian.org/debsig/1.0/policy.dtd">\n\
|
||||
<Policy xmlns="https://www.debian.org/debsig/1.0/">\n\
|
||||
\n\
|
||||
<Origin Name="test" id="BC8ACDD415BD80B3" Description="Test package"/>\n\
|
||||
\n\
|
||||
<Selection>\n\
|
||||
<Required Type="origin" File="debsig.gpg" id="BC8ACDD415BD80B3"/>\n\
|
||||
</Selection>\n\
|
||||
\n\
|
||||
<Verification MinOptional="0">\n\
|
||||
<Required Type="origin" File="debsig.gpg" id="BC8ACDD415BD80B3"/>\n\
|
||||
</Verification>\n\
|
||||
</Policy>\n\
|
||||
\n' >> /etc/debsig/policies/BC8ACDD415BD80B3/policy.pol
|
||||
|
||||
# manually check signature
|
||||
RUN debsig-verify /tmp/foo.deb | grep "debsig: Verified package from 'Test package' (test)"
|
||||
|
||||
# clear dpkg config as it contains 'no-debsig', now every
|
||||
# package that will be installed must be signed
|
||||
RUN echo "" > /etc/dpkg/dpkg.cfg
|
||||
RUN dpkg -i /tmp/foo.deb
|
37
acceptance/testdata/keys/pubkey.asc
vendored
Normal file
37
acceptance/testdata/keys/pubkey.asc
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
CTI=
|
||||
=arXz
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
BIN
acceptance/testdata/keys/pubkey.gpg
vendored
Normal file
BIN
acceptance/testdata/keys/pubkey.gpg
vendored
Normal file
Binary file not shown.
9
acceptance/testdata/keys/rsa_unprotected.pub
vendored
Normal file
9
acceptance/testdata/keys/rsa_unprotected.pub
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxWFb75R71bdD12OL9Ba/
|
||||
Z9/61byXEMatIpliLZZKf2Ul937SIGLECGKvw3DIlQfjdv/u6RIXLQL7ybKKT7R0
|
||||
V8yZK6yhGXnjarPzeoOjaQBqbzR8tXdJJdpy9vbfTVbFonzb/e8uSgGtoKPwDosL
|
||||
6JjcRz9hLl2B/UaHPhIEoqO4n6tOBeqcTJwAYV5YrPA+J1Tb8BBpuXPttUFFvXh4
|
||||
bqeB/uLfCorMDP/OnQ51waVb6zxjHgeY+R35X885rb6XHCTbKPveEeIFkC13TiO9
|
||||
Td9d34lvNX+2z+hlS997Gb8n1uGTGek4iEX/k0baI/FUYWzQtIUK7okWJlgSZg7Q
|
||||
4wIDAQAB
|
||||
-----END PUBLIC KEY-----
|
10
acceptance/testdata/rpm.signed.dockerfile
vendored
Normal file
10
acceptance/testdata/rpm.signed.dockerfile
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
FROM fedora
|
||||
ARG package
|
||||
COPY keys/pubkey.asc /tmp/pubkey.asc
|
||||
COPY ${package} /tmp/foo.rpm
|
||||
|
||||
RUN rpm --import /tmp/pubkey.asc
|
||||
RUN rpm -K /tmp/foo.rpm | grep ": digests signatures OK"
|
||||
RUN rpm -K /tmp/foo.rpm -v | grep "RSA/SHA256 Signature, key ID 15bd80b3: OK"
|
||||
|
||||
RUN rpm -i /tmp/foo.rpm
|
19
acceptance/testdata/signed.yaml
vendored
Normal file
19
acceptance/testdata/signed.yaml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: "foo"
|
||||
arch: "amd64"
|
||||
platform: "linux"
|
||||
version: "v1.0.0"
|
||||
maintainer: "John Doe <john@example.com>"
|
||||
description: This package is sigend
|
||||
vendor: "FooBarCorp"
|
||||
homepage: "http://example.com"
|
||||
files:
|
||||
../testdata/fake: "/usr/local/bin/fake"
|
||||
deb:
|
||||
signature:
|
||||
key_file: ../internal/sign/testdata/privkey_unprotected.asc
|
||||
rpm:
|
||||
signature:
|
||||
key_file: ../internal/sign/testdata/privkey_unprotected.asc
|
||||
apk:
|
||||
signature:
|
||||
key_file: ../internal/sign/testdata/rsa_unprotected.priv
|
87
apk/apk.go
87
apk/apk.go
@ -20,7 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package apk (someday) implements nfpm.Packager providing .apk bindings.
|
||||
// Package apk implements nfpm.Packager providing .apk bindings.
|
||||
package apk
|
||||
|
||||
// Initial implementation from https://gist.github.com/tcurdt/512beaac7e9c12dcf5b6b7603b09d0d8
|
||||
@ -30,12 +30,15 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha1" // nolint:gosec
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/mail"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -45,6 +48,8 @@ import (
|
||||
|
||||
"github.com/goreleaser/nfpm/internal/files"
|
||||
|
||||
"github.com/goreleaser/nfpm/internal/sign"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/goreleaser/nfpm"
|
||||
@ -109,19 +114,29 @@ func (*Apk) Package(info *nfpm.Info, apk io.Writer) (err error) {
|
||||
|
||||
size := int64(0)
|
||||
// create the data tgz
|
||||
_, err = createData(&bufData, info, &size)
|
||||
dataDigest, err := createData(&bufData, info, &size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the control tgz
|
||||
var bufControl bytes.Buffer
|
||||
if _, err = createControl(&bufControl, info, size); err != nil {
|
||||
controlDigest, err := createControl(&bufControl, info, size, dataDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// combine
|
||||
return combineToApk(apk, &bufData, &bufControl)
|
||||
if info.APK.Signature.KeyFile == "" {
|
||||
return combineToApk(apk, &bufControl, &bufData)
|
||||
}
|
||||
|
||||
// create the signature tgz
|
||||
var bufSignature bytes.Buffer
|
||||
if err = createSignature(&bufSignature, info, controlDigest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return combineToApk(apk, &bufSignature, &bufControl, &bufData)
|
||||
}
|
||||
|
||||
type writerCounter struct {
|
||||
@ -224,17 +239,64 @@ func createData(dataTgz io.Writer, info *nfpm.Info, sizep *int64) ([]byte, error
|
||||
return dataDigest, nil
|
||||
}
|
||||
|
||||
func createControl(controlTgz io.Writer, info *nfpm.Info, size int64) ([]byte, error) {
|
||||
builderControl := createBuilderControl(info, size)
|
||||
controlDigest, err := writeTgz(controlTgz, tarCut, builderControl, sha256.New())
|
||||
func createControl(controlTgz io.Writer, info *nfpm.Info, size int64, dataDigest []byte) ([]byte, error) {
|
||||
builderControl := createBuilderControl(info, size, dataDigest)
|
||||
controlDigest, err := writeTgz(controlTgz, tarCut, builderControl, sha1.New()) // nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return controlDigest, nil
|
||||
}
|
||||
|
||||
func combineToApk(target io.Writer, dataTgz, controlTgz io.Reader) error {
|
||||
for _, tgz := range []io.Reader{controlTgz, dataTgz} {
|
||||
func createSignature(signatureTgz io.Writer, info *nfpm.Info, controlSHA1Digest []byte) error {
|
||||
signatureBuilder := createSignatureBuilder(controlSHA1Digest, info)
|
||||
// we don't actually need to produce a digest here, but writeTgz
|
||||
// requires it so we just use SHA1 since it is already imported
|
||||
_, err := writeTgz(signatureTgz, tarCut, signatureBuilder, sha1.New()) // nolint:gosec
|
||||
if err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// needs to exist on the machine during installation: /etc/apk/keys/<keyname>.rsa.pub
|
||||
keyname := info.APK.Signature.KeyName
|
||||
if keyname == "" {
|
||||
addr, err := mail.ParseAddress(info.Maintainer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "key name not set and unable to parse maintainer mail address")
|
||||
} else if addr.Address == "" {
|
||||
return errors.New("key name not set and maintainer mail address empty")
|
||||
}
|
||||
|
||||
keyname = addr.Address + ".rsa.pub"
|
||||
}
|
||||
|
||||
// In principle apk supports RSA signatures over SHA256/512 keys, but in
|
||||
// practice verification works but installation segfaults. If this is
|
||||
// fixed at some point we should also upgrade the hash. In this case,
|
||||
// the file name will have to start with .SIGN.RSA256 or .SIGN.RSA512.
|
||||
signHeader := &tar.Header{
|
||||
Name: fmt.Sprintf(".SIGN.RSA.%s", keyname),
|
||||
Mode: 0600,
|
||||
Size: int64(len(signature)),
|
||||
}
|
||||
|
||||
return writeFile(tw, signHeader, bytes.NewReader(signature))
|
||||
}
|
||||
}
|
||||
|
||||
func combineToApk(target io.Writer, readers ...io.Reader) error {
|
||||
for _, tgz := range readers {
|
||||
if _, err := io.Copy(target, tgz); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -242,12 +304,13 @@ func combineToApk(target io.Writer, dataTgz, controlTgz io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBuilderControl(info *nfpm.Info, size int64) func(tw *tar.Writer) error {
|
||||
func createBuilderControl(info *nfpm.Info, size int64, dataDigest []byte) func(tw *tar.Writer) error {
|
||||
return func(tw *tar.Writer) error {
|
||||
var infoBuf bytes.Buffer
|
||||
if err := writeControl(&infoBuf, controlData{
|
||||
Info: info,
|
||||
InstalledSize: size,
|
||||
Datahash: hex.EncodeToString(dataDigest),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -493,11 +556,13 @@ depend = {{ $dep }}
|
||||
{{- if .Info.License}}
|
||||
license = {{.Info.License}}
|
||||
{{- end }}
|
||||
datahash = {{.Datahash}}
|
||||
`
|
||||
|
||||
type controlData struct {
|
||||
Info *nfpm.Info
|
||||
InstalledSize int64
|
||||
Datahash string
|
||||
}
|
||||
|
||||
func writeControl(w io.Writer, data controlData) error {
|
||||
|
@ -3,6 +3,9 @@ package apk
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"crypto/sha1" // nolint:gosec
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -12,6 +15,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goreleaser/nfpm"
|
||||
"github.com/goreleaser/nfpm/internal/sign"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -202,13 +206,13 @@ func TestNoFiles(t *testing.T) {
|
||||
func TestCreateBuilderControl(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
size := int64(12345)
|
||||
builderControl := createBuilderControl(info, size)
|
||||
builderControl := createBuilderControl(info, size, sha256.New().Sum(nil))
|
||||
|
||||
var w bytes.Buffer
|
||||
tw := tar.NewWriter(&w)
|
||||
assert.NoError(t, builderControl(tw))
|
||||
|
||||
var control = extractControl(t, &w)
|
||||
var control = string(extractFromTar(t, w.Bytes(), ".PKGINFO"))
|
||||
var golden = "testdata/TestCreateBuilderControl.golden"
|
||||
if *update {
|
||||
require.NoError(t, ioutil.WriteFile(golden, []byte(control), 0655)) // nolint: gosec
|
||||
@ -228,13 +232,13 @@ func TestCreateBuilderControlScripts(t *testing.T) {
|
||||
}
|
||||
|
||||
size := int64(12345)
|
||||
builderControl := createBuilderControl(info, size)
|
||||
builderControl := createBuilderControl(info, size, sha256.New().Sum(nil))
|
||||
|
||||
var w bytes.Buffer
|
||||
tw := tar.NewWriter(&w)
|
||||
assert.NoError(t, builderControl(tw))
|
||||
|
||||
var control = extractControl(t, &w)
|
||||
var control = string(extractFromTar(t, w.Bytes(), ".PKGINFO"))
|
||||
var golden = "testdata/TestCreateBuilderControlScripts.golden"
|
||||
if *update {
|
||||
require.NoError(t, ioutil.WriteFile(golden, []byte(control), 0655)) // nolint: gosec
|
||||
@ -259,8 +263,51 @@ func TestControl(t *testing.T) {
|
||||
assert.Equal(t, string(bts), w.String())
|
||||
}
|
||||
|
||||
func extractControl(t *testing.T, r io.Reader) string {
|
||||
var tr = tar.NewReader(r)
|
||||
func TestSignature(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.APK.Signature.KeyFile = "../internal/sign/testdata/rsa.priv"
|
||||
info.APK.Signature.KeyName = "testkey.rsa.pub"
|
||||
info.APK.Signature.KeyPassphrase = "hunter2"
|
||||
|
||||
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, ioutil.Discard)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSignatureError(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.APK.Signature.KeyFile = "../internal/sign/testdata/rsa.priv"
|
||||
info.APK.Signature.KeyName = "testkey.rsa.pub"
|
||||
info.APK.Signature.KeyPassphrase = "hunter2"
|
||||
|
||||
// wrong hash format
|
||||
digest := sha256.New().Sum(nil)
|
||||
|
||||
var signatureTarGz bytes.Buffer
|
||||
|
||||
err := createSignature(&signatureTarGz, info, digest)
|
||||
require.Error(t, err)
|
||||
|
||||
var expectedError *nfpm.ErrSigningFailure
|
||||
require.True(t, errors.As(err, &expectedError))
|
||||
|
||||
_, ok := err.(*nfpm.ErrSigningFailure)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func extractFromTar(t *testing.T, tarFile []byte, fileName string) []byte {
|
||||
t.Helper()
|
||||
|
||||
var tr = tar.NewReader(bytes.NewReader(tarFile))
|
||||
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
@ -268,14 +315,16 @@ func extractControl(t *testing.T, r io.Reader) string {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if hdr.Name == ".PKGINFO" {
|
||||
var w bytes.Buffer
|
||||
_, err := io.Copy(&w, tr)
|
||||
require.NoError(t, err)
|
||||
return w.String()
|
||||
|
||||
if hdr.Name != fileName {
|
||||
continue
|
||||
}
|
||||
t.Log("ignored", hdr.Name)
|
||||
|
||||
data, err := ioutil.ReadAll(tr)
|
||||
require.NoError(t, err)
|
||||
return data
|
||||
}
|
||||
|
||||
return ""
|
||||
t.Fatalf("file %q not found in tar file", fileName)
|
||||
return nil
|
||||
}
|
||||
|
1
apk/testdata/TestControl.golden
vendored
1
apk/testdata/TestControl.golden
vendored
@ -11,3 +11,4 @@ provides = bzr
|
||||
provides = zzz
|
||||
depend = bash
|
||||
depend = foo
|
||||
datahash =
|
||||
|
1
apk/testdata/TestCreateBuilderControl.golden
vendored
1
apk/testdata/TestCreateBuilderControl.golden
vendored
@ -11,3 +11,4 @@ provides = bzr
|
||||
provides = zzz
|
||||
depend = bash
|
||||
depend = foo
|
||||
datahash = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
|
@ -11,3 +11,4 @@ provides = bzr
|
||||
provides = zzz
|
||||
depend = bash
|
||||
depend = foo
|
||||
datahash = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
|
36
deb/deb.go
36
deb/deb.go
@ -19,6 +19,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/goreleaser/nfpm/internal/files"
|
||||
"github.com/goreleaser/nfpm/internal/sign"
|
||||
|
||||
"github.com/goreleaser/chglog"
|
||||
|
||||
@ -80,27 +81,60 @@ func (*Deb) Package(info *nfpm.Info, deb io.Writer) (err error) {
|
||||
if ok {
|
||||
info.Arch = arch
|
||||
}
|
||||
|
||||
dataTarGz, md5sums, instSize, err := createDataTarGz(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
controlTarGz, err := createControl(instSize, md5sums, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
debianBinary := []byte("2.0\n")
|
||||
|
||||
var w = ar.NewWriter(deb)
|
||||
if err := w.WriteGlobalHeader(); err != nil {
|
||||
return errors.Wrap(err, "cannot write ar header to deb file")
|
||||
}
|
||||
if err := addArFile(w, "debian-binary", []byte("2.0\n")); err != nil {
|
||||
|
||||
if err := addArFile(w, "debian-binary", debianBinary); err != nil {
|
||||
return errors.Wrap(err, "cannot pack debian-binary")
|
||||
}
|
||||
|
||||
if err := addArFile(w, "control.tar.gz", controlTarGz); err != nil {
|
||||
return errors.Wrap(err, "cannot add control.tar.gz to deb")
|
||||
}
|
||||
|
||||
if err := addArFile(w, "data.tar.gz", dataTarGz); err != nil {
|
||||
return errors.Wrap(err, "cannot add data.tar.gz to deb")
|
||||
}
|
||||
|
||||
if info.Deb.Signature.KeyFile != "" {
|
||||
data := io.MultiReader(bytes.NewReader(debianBinary), bytes.NewReader(controlTarGz),
|
||||
bytes.NewReader(dataTarGz))
|
||||
|
||||
sig, err := sign.PGPArmoredDetachSign(data, info.Deb.Signature.KeyFile,
|
||||
info.Deb.Signature.KeyPassphrase)
|
||||
if err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
sigType := "origin"
|
||||
if info.Deb.Signature.Type != "" {
|
||||
sigType = info.Deb.Signature.Type
|
||||
}
|
||||
|
||||
if sigType != "origin" && sigType != "maint" && sigType != "archive" {
|
||||
return &nfpm.ErrSigningFailure{Err: errors.New("invalid signature type")}
|
||||
}
|
||||
|
||||
if err := addArFile(w, "_gpg"+sigType, sig); err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: errors.Wrap(err, "add signature to ar file")}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"compress/gzip"
|
||||
"crypto/md5" // nolint: gosec
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -17,11 +18,14 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goreleaser/chglog"
|
||||
|
||||
"github.com/goreleaser/nfpm/internal/sign"
|
||||
|
||||
"github.com/goreleaser/nfpm"
|
||||
)
|
||||
|
||||
@ -729,6 +733,49 @@ func testRelativePathPrefixInTarGzFiles(t *testing.T, tarGzFile []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebsigsSignature(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
|
||||
info.Deb.Signature.KeyPassphrase = "hunter2"
|
||||
|
||||
var deb bytes.Buffer
|
||||
err := Default.Package(info, &deb)
|
||||
require.NoError(t, err)
|
||||
|
||||
debBinary, err := extractFileFromAr(deb.Bytes(), "debian-binary")
|
||||
require.NoError(t, err)
|
||||
|
||||
controlTarGz, err := extractFileFromAr(deb.Bytes(), "control.tar.gz")
|
||||
require.NoError(t, err)
|
||||
|
||||
dataTarGz, err := extractFileFromAr(deb.Bytes(), "data.tar.gz")
|
||||
require.NoError(t, err)
|
||||
|
||||
signature, err := extractFileFromAr(deb.Bytes(), "_gpgorigin")
|
||||
require.NoError(t, err)
|
||||
|
||||
message := io.MultiReader(bytes.NewReader(debBinary),
|
||||
bytes.NewReader(controlTarGz), bytes.NewReader(dataTarGz))
|
||||
|
||||
err = sign.PGPVerify(message, signature, "../internal/sign/testdata/pubkey.asc")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDebsigsSignatureError(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.KeyFile = "/does/not/exist"
|
||||
|
||||
var deb bytes.Buffer
|
||||
err := Default.Package(info, &deb)
|
||||
require.Error(t, err)
|
||||
|
||||
var expectedError *nfpm.ErrSigningFailure
|
||||
require.True(t, errors.As(err, &expectedError))
|
||||
|
||||
_, ok := err.(*nfpm.ErrSigningFailure)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func extractFileFromTarGz(tarGzFile []byte, filename string) ([]byte, error) {
|
||||
tarFile, err := gzipInflate(tarGzFile)
|
||||
if err != nil {
|
||||
@ -844,3 +891,29 @@ func symlinkTo(tb testing.TB, fileName string) string {
|
||||
|
||||
return symlinkName
|
||||
}
|
||||
|
||||
func extractFileFromAr(arFile []byte, filename string) ([]byte, error) {
|
||||
tr := ar.NewReader(bytes.NewReader(arFile))
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break // End of archive
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if path.Join("/", hdr.Name) != path.Join("/", filename) {
|
||||
continue
|
||||
}
|
||||
|
||||
fileContents, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileContents, nil
|
||||
}
|
||||
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@ -2,6 +2,8 @@ module github.com/goreleaser/nfpm
|
||||
|
||||
go 1.14
|
||||
|
||||
replace github.com/google/rpmpack => github.com/goreleaser/rpmpack v0.0.0-20200915084912-ac8a7d0c1fdc
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/alecthomas/kingpin v2.2.6+incompatible
|
||||
@ -19,6 +21,7 @@ require (
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
|
||||
)
|
||||
|
6
go.sum
6
go.sum
@ -203,6 +203,12 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/goreleaser/chglog v0.1.1 h1:UaY3enMEVeavOaZyCraLn+2iM7/2T0yji8Mh7ZFsDp4=
|
||||
github.com/goreleaser/chglog v0.1.1/go.mod h1:xSDa/73C0TxBcLvoT2JHh47QyXpCx5rrNVzJKyeFGPw=
|
||||
github.com/goreleaser/rpmpack v0.0.0-20200731134257-3685799e8fdf h1:+QCryOyAhxMBMtB90ALMxTb2C822fon+hsY6av1sdIk=
|
||||
github.com/goreleaser/rpmpack v0.0.0-20200731134257-3685799e8fdf/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
|
||||
github.com/goreleaser/rpmpack v0.0.0-20200912130434-2762bbe52570 h1:xR3+O5ERbBVojh+NPOFNV7gvtrU9rLkblJtzaaFB3yA=
|
||||
github.com/goreleaser/rpmpack v0.0.0-20200912130434-2762bbe52570/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
|
||||
github.com/goreleaser/rpmpack v0.0.0-20200915084912-ac8a7d0c1fdc h1:bYP7CnnBF4FGZUWg6VcEMmGLPGNkwTmEPgoQlgSiaRM=
|
||||
github.com/goreleaser/rpmpack v0.0.0-20200915084912-ac8a7d0c1fdc/go.mod h1:+y9lKiqDhR4zkLl+V9h4q0rdyrYVsWWm6LLCQP33DIk=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
|
148
internal/sign/pgp.go
Normal file
148
internal/sign/pgp.go
Normal file
@ -0,0 +1,148 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"unicode"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
|
||||
"github.com/goreleaser/nfpm"
|
||||
)
|
||||
|
||||
// PGPSigner returns a PGP signer that creates a detached non-ASCII-armored
|
||||
// signature and is compatible with rpmpack's signature API.
|
||||
func PGPSigner(keyFile, passphrase string) func([]byte) ([]byte, error) {
|
||||
return func(data []byte) ([]byte, error) {
|
||||
key, err := readSigningKey(keyFile, passphrase)
|
||||
if err != nil {
|
||||
return nil, &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
var signature bytes.Buffer
|
||||
|
||||
err = openpgp.DetachSign(&signature, key, bytes.NewReader(data), nil)
|
||||
if err != nil {
|
||||
return nil, &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
return signature.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// PGPArmoredDetachSign creates an ASCII-armored detached signature.
|
||||
func PGPArmoredDetachSign(message io.Reader, keyFile, passphrase string) ([]byte, error) {
|
||||
key, err := readSigningKey(keyFile, passphrase)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "armored detach sign")
|
||||
}
|
||||
|
||||
var signature bytes.Buffer
|
||||
|
||||
err = openpgp.ArmoredDetachSign(&signature, key, message, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "armored detach sign")
|
||||
}
|
||||
|
||||
return signature.Bytes(), nil
|
||||
}
|
||||
|
||||
// PGPVerify is exported for use in tests and verifies a ASCII-armored or non-ASCII-armored
|
||||
// signature using an ASCII-armored or non-ASCII-armored public key file. The signer
|
||||
// identity is not explicitly checked, other that the obvious fact that the signer's key must
|
||||
// be in the armoredPubKeyFile.
|
||||
func PGPVerify(message io.Reader, signature []byte, armoredPubKeyFile string) error {
|
||||
keyFileContent, err := ioutil.ReadFile(armoredPubKeyFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading armored public key file")
|
||||
}
|
||||
|
||||
var keyring openpgp.EntityList
|
||||
|
||||
if isASCII(keyFileContent) {
|
||||
keyring, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(keyFileContent))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decoding armored public key file")
|
||||
}
|
||||
} else {
|
||||
keyring, err = openpgp.ReadKeyRing(bytes.NewReader(keyFileContent))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decoding public key file")
|
||||
}
|
||||
}
|
||||
|
||||
if isASCII(signature) {
|
||||
_, err = openpgp.CheckArmoredDetachedSignature(keyring, message, bytes.NewReader(signature))
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = openpgp.CheckDetachedSignature(keyring, message, bytes.NewReader(signature))
|
||||
return err
|
||||
}
|
||||
|
||||
func readSigningKey(keyFile, passphrase string) (*openpgp.Entity, error) {
|
||||
fileContent, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "reading PGP key file")
|
||||
}
|
||||
|
||||
var entityList openpgp.EntityList
|
||||
|
||||
if isASCII(fileContent) {
|
||||
entityList, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(fileContent))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decoding armored PGP keyring")
|
||||
}
|
||||
} else {
|
||||
entityList, err = openpgp.ReadKeyRing(bytes.NewReader(fileContent))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decoding PGP keyring")
|
||||
}
|
||||
}
|
||||
|
||||
var key *openpgp.Entity
|
||||
|
||||
for _, candidate := range entityList {
|
||||
if candidate.PrivateKey == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !candidate.PrivateKey.CanSign() {
|
||||
continue
|
||||
}
|
||||
|
||||
if key != nil {
|
||||
return nil, errors.New("more than one signing key in keyring")
|
||||
}
|
||||
|
||||
key = candidate
|
||||
}
|
||||
|
||||
if key == nil {
|
||||
return nil, errors.New("no signing key in keyring")
|
||||
}
|
||||
|
||||
if key.PrivateKey.Encrypted {
|
||||
if passphrase == "" {
|
||||
return nil, errors.New("key is encrypted but no passphrase was provided")
|
||||
}
|
||||
|
||||
err = key.PrivateKey.Decrypt([]byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decrypt secret signing key")
|
||||
}
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func isASCII(s []byte) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
145
internal/sign/pgp_test.go
Normal file
145
internal/sign/pgp_test.go
Normal file
@ -0,0 +1,145 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
|
||||
"github.com/goreleaser/nfpm"
|
||||
)
|
||||
|
||||
const pass = "hunter2"
|
||||
|
||||
func TestPGPSignerAndVerify(t *testing.T) {
|
||||
data := []byte("testdata")
|
||||
verifierKeyring := readArmoredKeyring(t, "testdata/pubkey.asc")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keyFile string
|
||||
pass string
|
||||
}{
|
||||
{"protected", "testdata/privkey.gpg", pass},
|
||||
{"unprotected", "testdata/privkey_unprotected.gpg", ""},
|
||||
{"armored protected", "testdata/privkey.asc", pass},
|
||||
{"armored unprotected", "testdata/privkey_unprotected.asc", ""},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
sig, err := PGPSigner(testCase.keyFile, testCase.pass)(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = openpgp.CheckDetachedSignature(verifierKeyring,
|
||||
bytes.NewReader(data), bytes.NewReader(sig))
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = PGPVerify(bytes.NewReader(data), sig, "testdata/pubkey.asc")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = PGPVerify(bytes.NewReader(data), sig, "testdata/pubkey.gpg")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestArmoredDetachSignAndVerify(t *testing.T) {
|
||||
data := []byte("testdata")
|
||||
verifierKeyring := readArmoredKeyring(t, "testdata/pubkey.asc")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keyFile string
|
||||
pass string
|
||||
}{
|
||||
{"protected", "testdata/privkey.gpg", pass},
|
||||
{"unprotected", "testdata/privkey_unprotected.gpg", ""},
|
||||
{"armored protected", "testdata/privkey.asc", pass},
|
||||
{"armored unprotected", "testdata/privkey_unprotected.asc", ""},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
sig, err := PGPArmoredDetachSign(bytes.NewReader(data),
|
||||
testCase.keyFile, testCase.pass)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = openpgp.CheckArmoredDetachedSignature(verifierKeyring,
|
||||
bytes.NewReader(data), bytes.NewReader(sig))
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = PGPVerify(bytes.NewReader(data), sig, "testdata/pubkey.asc")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = PGPVerify(bytes.NewReader(data), sig, "testdata/pubkey.gpg")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readArmoredKeyring(t *testing.T, fileName string) openpgp.EntityList {
|
||||
t.Helper()
|
||||
content, err := ioutil.ReadFile(fileName)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(content))
|
||||
require.NoError(t, err)
|
||||
|
||||
return keyring
|
||||
}
|
||||
|
||||
func TestPGPSignerError(t *testing.T) {
|
||||
_, err := PGPSigner("/does/not/exist", "")([]byte("data"))
|
||||
require.Error(t, err)
|
||||
|
||||
var expectedError *nfpm.ErrSigningFailure
|
||||
assert.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestNoSigningKey(t *testing.T) {
|
||||
_, err := readSigningKey("testdata/pubkey.asc", pass)
|
||||
require.EqualError(t, err, "no signing key in keyring")
|
||||
}
|
||||
|
||||
func TestMultipleKeys(t *testing.T) {
|
||||
_, err := readSigningKey("testdata/multiple_privkeys.asc", pass)
|
||||
require.EqualError(t, err, "more than one signing key in keyring")
|
||||
}
|
||||
|
||||
func TestWrongPass(t *testing.T) {
|
||||
_, err := readSigningKey("testdata/privkey.asc", "password123")
|
||||
require.EqualError(t, err,
|
||||
"decrypt secret signing key: openpgp: invalid data: private key checksum failure")
|
||||
}
|
||||
|
||||
func TestEmptyPass(t *testing.T) {
|
||||
_, err := readSigningKey("testdata/privkey.asc", "")
|
||||
require.EqualError(t, err, "key is encrypted but no passphrase was provided")
|
||||
}
|
||||
|
||||
func TestReadArmoredKey(t *testing.T) {
|
||||
_, err := readSigningKey("testdata/privkey.asc", pass)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestReadKey(t *testing.T) {
|
||||
_, err := readSigningKey("testdata/privkey.gpg", pass)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestIsASCII(t *testing.T) {
|
||||
data, err := ioutil.ReadFile("testdata/privkey.asc")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isASCII(data))
|
||||
|
||||
data, err = ioutil.ReadFile("testdata/privkey.gpg")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isASCII(data))
|
||||
}
|
115
internal/sign/rsa.go
Normal file
115
internal/sign/rsa.go
Normal file
@ -0,0 +1,115 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1" // nolint:gosec
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RSASignSHA1Digest signs the provided SHA1 message digest. The key file
|
||||
// must be in the PEM format and can either be encrypted or not.
|
||||
func RSASignSHA1Digest(sha1Digest []byte, keyFile, passphrase string) ([]byte, error) {
|
||||
if len(sha1Digest) != sha1.Size {
|
||||
return nil, errors.New("digest is not a SHA1 hash")
|
||||
}
|
||||
|
||||
keyFileContent, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "reading key file")
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(keyFileContent)
|
||||
if block == nil {
|
||||
return nil, errors.New("parse PEM block with private key")
|
||||
}
|
||||
|
||||
blockData := block.Bytes
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
if passphrase == "" {
|
||||
return nil, errors.New("key is encrypted but no passphrase was provided")
|
||||
}
|
||||
|
||||
var decryptedBlockData []byte
|
||||
|
||||
decryptedBlockData, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decrypt private key PEM block")
|
||||
}
|
||||
|
||||
blockData = decryptedBlockData
|
||||
}
|
||||
|
||||
priv, err := x509.ParsePKCS1PrivateKey(blockData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse PKCS1 private key")
|
||||
}
|
||||
|
||||
signature, err := priv.Sign(rand.Reader, sha1Digest, crypto.SHA1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "signing")
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func rsaSign(message io.Reader, keyFile, passphrase string) ([]byte, error) {
|
||||
sha256Hash := sha1.New() // nolint:gosec
|
||||
_, err := io.Copy(sha256Hash, message)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create SHA256 message digest")
|
||||
}
|
||||
|
||||
return RSASignSHA1Digest(sha256Hash.Sum(nil), keyFile, passphrase)
|
||||
}
|
||||
|
||||
// RSAVerifySHA1Digest is exported for use in tests and verifies a signature over the
|
||||
// provided SHA1 hash of a message. The key file must be in the PEM format.
|
||||
func RSAVerifySHA1Digest(sha1Digest, signature []byte, publicKeyFile string) error {
|
||||
if len(sha1Digest) != sha1.Size {
|
||||
return errors.New("digest is not a SHA1 hash")
|
||||
}
|
||||
|
||||
keyFileContent, err := ioutil.ReadFile(publicKeyFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "reading key file")
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(keyFileContent)
|
||||
if block == nil {
|
||||
return errors.New("parse PEM block with public key")
|
||||
}
|
||||
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "parse PKIX public key")
|
||||
}
|
||||
|
||||
rsaPub, ok := pub.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return errors.Wrap(err, "public key is no RSA key")
|
||||
}
|
||||
|
||||
err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA1, sha1Digest, signature)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "verify PKCS1v15 signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rsaVerify(message io.Reader, signature []byte, publicKeyFile string) error {
|
||||
sha256Hash := sha1.New() // nolint:gosec
|
||||
_, err := io.Copy(sha256Hash, message)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create SHA1 message digest")
|
||||
}
|
||||
|
||||
return RSAVerifySHA1Digest(sha256Hash.Sum(nil), signature, publicKeyFile)
|
||||
}
|
51
internal/sign/rsa_test.go
Normal file
51
internal/sign/rsa_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRSASignAndVerify(t *testing.T) {
|
||||
testData := []byte("test")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
privKey string
|
||||
pubKey string
|
||||
passphrase string
|
||||
}{
|
||||
{"unprotected", "testdata/rsa_unprotected.priv", "testdata/rsa_unprotected.pub", ""},
|
||||
{"protected", "testdata/rsa.priv", "testdata/rsa.pub", pass},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
sig, err := rsaSign(bytes.NewReader(testData), testCase.privKey, testCase.passphrase)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = rsaVerify(bytes.NewReader(testData), sig, testCase.pubKey)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrongPassphrase(t *testing.T) {
|
||||
testData := []byte("test")
|
||||
_, err := rsaSign(bytes.NewReader(testData), "testdata/rsa.priv", "password123")
|
||||
require.EqualError(t, err, "decrypt private key PEM block: x509: decryption password incorrect")
|
||||
}
|
||||
|
||||
func TestNoPassphrase(t *testing.T) {
|
||||
testData := []byte("test")
|
||||
_, err := rsaSign(bytes.NewReader(testData), "testdata/rsa.priv", "")
|
||||
require.EqualError(t, err, "key is encrypted but no passphrase was provided")
|
||||
}
|
||||
|
||||
func TestInvalidHash(t *testing.T) {
|
||||
invalidDigest := []byte("test")
|
||||
_, err := RSASignSHA1Digest(invalidDigest, "testdata/rsa.priv", "hunter2")
|
||||
require.EqualError(t, err, "digest is not a SHA1 hash")
|
||||
}
|
127
internal/sign/testdata/multiple_privkeys.asc
vendored
Normal file
127
internal/sign/testdata/multiple_privkeys.asc
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
CeA6I6SIMQKWAsosxcLD
|
||||
=Uei0
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
66
internal/sign/testdata/privkey.asc
vendored
Normal file
66
internal/sign/testdata/privkey.asc
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
R9YJMg==
|
||||
=/NbH
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
BIN
internal/sign/testdata/privkey.gpg
vendored
Normal file
BIN
internal/sign/testdata/privkey.gpg
vendored
Normal file
Binary file not shown.
64
internal/sign/testdata/privkey_unprotected.asc
vendored
Normal file
64
internal/sign/testdata/privkey_unprotected.asc
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||
|
||||
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
|
||||
WCY7Z0fWCTI=
|
||||
=dvS6
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
BIN
internal/sign/testdata/privkey_unprotected.gpg
vendored
Normal file
BIN
internal/sign/testdata/privkey_unprotected.gpg
vendored
Normal file
Binary file not shown.
37
internal/sign/testdata/pubkey.asc
vendored
Normal file
37
internal/sign/testdata/pubkey.asc
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
CTI=
|
||||
=arXz
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
BIN
internal/sign/testdata/pubkey.gpg
vendored
Normal file
BIN
internal/sign/testdata/pubkey.gpg
vendored
Normal file
Binary file not shown.
30
internal/sign/testdata/rsa.priv
vendored
Normal file
30
internal/sign/testdata/rsa.priv
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,F681796F2F5F6592720D154E441631AF
|
||||
|
||||
U4aCJ0bGTN+/l162THI8CQdVmn62QX7+CxGOrxz8vbPOcMic8ppBD/h4lcUmca1L
|
||||
B3q9gBV+nM4KWOUjKDsmXJ7PeHrQJxR30RbGKGOP8WZSlf7oqICHX9WfjzsaLmMC
|
||||
zr7z7osO4DhW0nkr+CJATplKxjF5kry2jsBohmG7AJKVB6JRenpyz/icLrwfeywo
|
||||
dM1aZEw2Tm6Aso+un+rdmRxvYG5SEVLVh5M334MvOwhKHNGGfNOUJG38uym/sYXM
|
||||
WbBRyOUhQXabbuHlJdr5V7mnxm+KLi+hZQAIoRsFTS7tkIUpFjtvCS0RI0L6MtkL
|
||||
3N76xiW82n3SxrPdZdJCGy9AWor66QTmr25yTZpUJMYqpHaXc2pKqZOWZ/U0bAbA
|
||||
Oyf/CCTwI9lLT1a+CtGTHYTXHHKhQ9n1TI2FI4DRhnDcdDix2S/shF8aeu7mcKF1
|
||||
SqS8r+DTKzmbohUVa0/Ad9Z6R8Q1Ed+fl2/l0/eb3tI7c7hH+AxV+AqSAgLsZ1/T
|
||||
SQoIjUX4Kna0U2wLi7wEWlwrQQE2lugoJ2tOH6F33tEd4FFh4CjTw4LKKFEIgjPj
|
||||
yNKJ5CC+5jQPdyN/utR6Q+PNlxnT5jU02dAupdGfS/On2ecONmBKFCHHT+Sh3F21
|
||||
Q8zKZPKTUgIqG8JrjIaO1akjJqGgr6c0kefVTYpHEH3HNNkqUvzywIMR7IooPJrD
|
||||
XQYLMgTuE4ccJk7JN5OCHlSkTHPcrkuD6rRmi7v9zIOMEmwfkN0Ea3HgNJ/7W8gL
|
||||
mnv47HtwEuuXpyhyQ5xpaJcNWfSaUvSHBr33Ni8rDL2iJu22zEeZXlOU1t4h7wCA
|
||||
VGd3GBk4n5PILABwILDWKP16bvB3I491QK6m7LehawJNMGDC4Yk379Havcl6BzSX
|
||||
Rs6NOFcAGyM0VS97HYj3ju7fecdvFwmd86CA92CoK6JbafsQ6o/sOZOhxnrg6nLu
|
||||
bS26TPwLoqNHrAzg2vTLosbiG3ezge7VuL2GBbQcZm4SsotNLbfpZIhn3JV9/Ivg
|
||||
rkiUNOEExd52+VImeAJ15Pl6lOb2uAdXwZNsPXfBbanfc5qoggJJlU/xCnQyrPNW
|
||||
xCi2y3+SQjjlEqdoQbmEEHrU4zzOSj505qDz/Md18W/aih5n1VdzI7Z1YeMAzlpe
|
||||
X7cGHrQD83AsAtWWv5af5qkHCRYn3fQDGf4svJprsbYxz7e8oAoyjXpKPVbfZcfk
|
||||
hqlR0T7yN91upFwHhq8q3ZY/CLsZbLecT28M3G7NgE+LtrF0bKcPrDfOCeWyyVBN
|
||||
wtxPB0P62Nr4jHs5GVO5D1qyCiDMLsS3mwSz3IZn2xf0pit7+O2uu5yIUeGhUN8k
|
||||
1Y4RwZyGf1e5y7Ulhb5OAbeDYtUTnvgWOqwj5grmACQhhWmKiXX6qUHHn54xyC9P
|
||||
Yn5f0jdGGVRKINgA+KsipDs3OjMY0D9Dyx3ao646wX+1ypNkUq+hZhykuKlTgqi6
|
||||
29LdVwXTbcYjc25hbIj9TrXZS7swmwtUhpLwtFyjqrCSMjHW6f6n27Vq8cvL788L
|
||||
7SbWmpZlxaZA2lj3yjjYnY2bSQBfmA45fF7tBZfHq/ObWFbpvCOU5LMwKjffoPpC
|
||||
-----END RSA PRIVATE KEY-----
|
9
internal/sign/testdata/rsa.pub
vendored
Normal file
9
internal/sign/testdata/rsa.pub
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA53JMl3py0/sBU1y3LBgn
|
||||
Ffb/GYHATaCiS/Dvoxp8cDTvm2NFGtqCAeyclD3qQAIrZD0igXfi6qeONw3jwea6
|
||||
4sHE5mHv0ZvkOKqc1G8SE8WPGUiGuk8xsSY/nILFzdqH1yKz81Sv0Y6ti65Gi3Jo
|
||||
AQPn3DfoDuvyye2hwzXsMTIkfgTj+Mnczsv4Ji1CYKrR8GqlBkePR1hbbeJcWnxb
|
||||
gqsI2jml9rqx28Fc3fxlkNwG5I619MSN0o1cegKq2gSgodd/EDjqhGF/Fw5nfCGI
|
||||
Cel3qPPNY/T6mk2P3oqY95YIKXtjeaSloZAkqPw+bl6B0wowigus6wuQ5rx1gN11
|
||||
fQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
27
internal/sign/testdata/rsa_unprotected.priv
vendored
Normal file
27
internal/sign/testdata/rsa_unprotected.priv
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAxWFb75R71bdD12OL9Ba/Z9/61byXEMatIpliLZZKf2Ul937S
|
||||
IGLECGKvw3DIlQfjdv/u6RIXLQL7ybKKT7R0V8yZK6yhGXnjarPzeoOjaQBqbzR8
|
||||
tXdJJdpy9vbfTVbFonzb/e8uSgGtoKPwDosL6JjcRz9hLl2B/UaHPhIEoqO4n6tO
|
||||
BeqcTJwAYV5YrPA+J1Tb8BBpuXPttUFFvXh4bqeB/uLfCorMDP/OnQ51waVb6zxj
|
||||
HgeY+R35X885rb6XHCTbKPveEeIFkC13TiO9Td9d34lvNX+2z+hlS997Gb8n1uGT
|
||||
Gek4iEX/k0baI/FUYWzQtIUK7okWJlgSZg7Q4wIDAQABAoIBACPfcP0XCHasKylP
|
||||
T1VaeQkg/Q6xiucipL7gD6bnZLzInmDf7MK0EJrRjM5BhkMHvwCWvU4g2pN0slxT
|
||||
Fa4sxuRaLBBy/IiLzZhMAllm6WEhETcK3AuDFYlKrZBTB/yfY6YHeVgqi0gqC+th
|
||||
QDT0coKgnZBZZYkut3xXwCa40ZzZQHQkGduu7DzNSVTc3P1PoC6FovgJ0G/6d5sa
|
||||
p+b+c6/2jp15P8OMAUYCibZUDO1EqXAnfTeseW5Lk6cjfwjb+EO9x45AU44t0lk9
|
||||
GNUPQbsymURAal5tXUDyRMZ0JONtYj70n8rW/Wkxm7VN32KdGJTP3F2qx3VBL8Dd
|
||||
puFOOvkCgYEA+xV2sY3shIWysZadpsM5I5kFkAsKUV5hl0zF8QJ5VYztVGGMK59N
|
||||
lXQ09PWEzCzi9H8um9prVPGucR+vdPEbgJ0zufZ6h5StO9+i01A7qpBiHezyXwls
|
||||
KJ3fdEybT/7cNohreoJp3D92TBKkYuE7B6Mw0aLwdkgUU3Z0FPj9oN0CgYEAyT62
|
||||
Cl2gK9ts+CxN10j6tZRwZ/4cSXMaQXvBT6prCWHnmfV2OcC+AGnQNTdkzAFvfcdK
|
||||
xEtrPRICiy37JCKvOoYemhfg1ZCVpO+Md0iXhd31K1bcTKf3XSRwtt2Av8INGM2H
|
||||
d7Vrie5ZFbnmeb5I9kegwCBoQowXlty8ummgPL8CgYEAuH6BBN6gWJBVSaC+nvb8
|
||||
WWfranhyUG/ljeNf30ROG8q96S70bGlV/OeysfxmZBDng3igquzHyVb9MypxJqCz
|
||||
8MGSbKbsSgu5bzFY16Uw8pe9QPgG8EZgL0gyFabkyNcgwQFk2FRTrP8E0ckw+Wc+
|
||||
lNIY4TG1N3SnWtzDV1XOKBECgYEAs2AZRI4FSXIw49ghneXnydfAhYDjV7iny3xy
|
||||
Zv8AnwFMPVekBIA/GwE122k/h/MznEn545NRx8J4z/OxKZlc1O5c8n6cId/Y8oWu
|
||||
j1f97w1TDUlD3XPNsZDcgrJWoxVlNRllSRdE/lGbyBjGTPeF8zcPGpEIPWC/WfHw
|
||||
qruhXuMCgYBR5LpIe0jXMo2RgSkLT2YVomy/3ZjaU736DLUwjv4quXjrkW/JTrv6
|
||||
APCQXVwcHrn0sJmjyXTlBAXuzpBx/IUiQ7qGxSNg6ZpDmZq0imqnsXXRZBUShnnT
|
||||
JLH0Qy1f8hcTFvLUfVyLiQaj+2XpKaCnb2Bc3YRZ8TRrwx6HeRT0Ug==
|
||||
-----END RSA PRIVATE KEY-----
|
9
internal/sign/testdata/rsa_unprotected.pub
vendored
Normal file
9
internal/sign/testdata/rsa_unprotected.pub
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxWFb75R71bdD12OL9Ba/
|
||||
Z9/61byXEMatIpliLZZKf2Ul937SIGLECGKvw3DIlQfjdv/u6RIXLQL7ybKKT7R0
|
||||
V8yZK6yhGXnjarPzeoOjaQBqbzR8tXdJJdpy9vbfTVbFonzb/e8uSgGtoKPwDosL
|
||||
6JjcRz9hLl2B/UaHPhIEoqO4n6tOBeqcTJwAYV5YrPA+J1Tb8BBpuXPttUFFvXh4
|
||||
bqeB/uLfCorMDP/OnQ51waVb6zxjHgeY+R35X885rb6XHCTbKPveEeIFkC13TiO9
|
||||
Td9d34lvNX+2z+hlS997Gb8n1uGTGek4iEX/k0baI/FUYWzQtIUK7okWJlgSZg7Q
|
||||
4wIDAQAB
|
||||
-----END PUBLIC KEY-----
|
73
nfpm.go
73
nfpm.go
@ -59,6 +59,26 @@ func Parse(in io.Reader) (config Config, err error) {
|
||||
config.Info.Release = os.ExpandEnv(config.Info.Release)
|
||||
config.Info.Version = os.ExpandEnv(config.Info.Version)
|
||||
|
||||
generalPassphrase := os.ExpandEnv("$NFPM_PASSPHRASE")
|
||||
config.Deb.Signature.KeyPassphrase = generalPassphrase
|
||||
config.RPM.Signature.KeyPassphrase = generalPassphrase
|
||||
config.APK.Signature.KeyPassphrase = generalPassphrase
|
||||
|
||||
debPassphrase := os.ExpandEnv("$NFPM_DEB_PASSPHRASE")
|
||||
if debPassphrase != "" {
|
||||
config.Deb.Signature.KeyPassphrase = debPassphrase
|
||||
}
|
||||
|
||||
rpmPassphrase := os.ExpandEnv("$NFPM_RPM_PASSPHRASE")
|
||||
if rpmPassphrase != "" {
|
||||
config.RPM.Signature.KeyPassphrase = rpmPassphrase
|
||||
}
|
||||
|
||||
apkPassphrase := os.ExpandEnv("$NFPM_APK_PASSPHRASE")
|
||||
if apkPassphrase != "" {
|
||||
config.APK.Signature.KeyPassphrase = apkPassphrase
|
||||
}
|
||||
|
||||
return config, config.Validate()
|
||||
}
|
||||
|
||||
@ -152,6 +172,7 @@ type Overridables struct {
|
||||
Scripts Scripts `yaml:"scripts,omitempty"`
|
||||
RPM RPM `yaml:"rpm,omitempty"`
|
||||
Deb Deb `yaml:"deb,omitempty"`
|
||||
APK APK `yaml:"apk,omitempty"`
|
||||
}
|
||||
|
||||
// RPM is custom configs that are only available on RPM packages.
|
||||
@ -160,14 +181,42 @@ type RPM struct {
|
||||
Compression string `yaml:"compression,omitempty"`
|
||||
// https://www.cl.cam.ac.uk/~jw35/docs/rpm_config.html
|
||||
ConfigNoReplaceFiles map[string]string `yaml:"config_noreplace_files,omitempty"`
|
||||
Signature RPMSignature `yaml:"signature,omitempty"`
|
||||
}
|
||||
|
||||
type RPMSignature struct {
|
||||
// PGP secret key, can be ASCII-armored
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
KeyPassphrase string `yaml:"-"` // populated from environment variable
|
||||
}
|
||||
|
||||
type APK struct {
|
||||
Signature APKSignature `yaml:"signature,omitempty"`
|
||||
}
|
||||
|
||||
type APKSignature struct {
|
||||
// RSA private key in PEM format
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
KeyPassphrase string `yaml:"-"` // populated from environment variable
|
||||
// defaults to <maintainer email>.rsa.pub
|
||||
KeyName string `yaml:"key_name,omitempty"`
|
||||
}
|
||||
|
||||
// Deb is custom configs that are only available on deb packages.
|
||||
type Deb struct {
|
||||
Scripts DebScripts `yaml:"scripts,omitempty"`
|
||||
Triggers DebTriggers `yaml:"triggers,omitempty"`
|
||||
Breaks []string `yaml:"breaks,omitempty"`
|
||||
VersionMetadata string `yaml:"metadata,omitempty"` // Deprecated: Moved to Info
|
||||
Scripts DebScripts `yaml:"scripts,omitempty"`
|
||||
Triggers DebTriggers `yaml:"triggers,omitempty"`
|
||||
Breaks []string `yaml:"breaks,omitempty"`
|
||||
VersionMetadata string `yaml:"metadata,omitempty"` // Deprecated: Moved to Info
|
||||
Signature DebSignature `yaml:"signature,omitempty"`
|
||||
}
|
||||
|
||||
type DebSignature struct {
|
||||
// PGP secret key, can be ASCII-armored
|
||||
KeyFile string `yaml:"key_file,omitempty"`
|
||||
KeyPassphrase string `yaml:"-"` // populated from environment variable
|
||||
// origin, maint or archive (defaults to origin)
|
||||
Type string `yaml:"type,omitempty"`
|
||||
}
|
||||
|
||||
// DebTriggers contains triggers only available for deb packages.
|
||||
@ -273,3 +322,19 @@ func (info *Info) GetChangeLog() (log *chglog.PackageChangeLog, err error) {
|
||||
Entries: entries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ErrSigningFailure is returned whenever something went wrong during
|
||||
// the package signing process. The underlying error can be unwrapped
|
||||
// and could be crypto-related or something that occurred while adding
|
||||
// the signature to the package.
|
||||
type ErrSigningFailure struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (s *ErrSigningFailure) Error() string {
|
||||
return fmt.Sprintf("signing error: %v", s.Err)
|
||||
}
|
||||
|
||||
func (s *ErrSigningFailure) Unwarp() error {
|
||||
return s.Err
|
||||
}
|
||||
|
51
nfpm_test.go
51
nfpm_test.go
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -149,6 +150,56 @@ func TestParseFile(t *testing.T) {
|
||||
assert.Equal(t, fmt.Sprintf("v%s", os.Getenv("GOROOT")), config.Version)
|
||||
}
|
||||
|
||||
func TestOptionsFromEnvironment(t *testing.T) {
|
||||
const (
|
||||
globalPass = "hunter2"
|
||||
debPass = "password123"
|
||||
rpmPass = "secret"
|
||||
apkPass = "foobar"
|
||||
release = "3"
|
||||
version = "1.0.0"
|
||||
)
|
||||
|
||||
t.Run("version", func(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("VERSION", version)
|
||||
info, err := Parse(strings.NewReader("name: foo\nversion: $VERSION"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, version, info.Version)
|
||||
})
|
||||
|
||||
t.Run("release", func(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("RELEASE", release)
|
||||
info, err := Parse(strings.NewReader("name: foo\nrelease: $RELEASE"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, release, info.Release)
|
||||
})
|
||||
|
||||
t.Run("global passphrase", func(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("NFPM_PASSPHRASE", globalPass)
|
||||
info, err := Parse(strings.NewReader("name: foo"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, globalPass, info.Deb.Signature.KeyPassphrase)
|
||||
assert.Equal(t, globalPass, info.RPM.Signature.KeyPassphrase)
|
||||
assert.Equal(t, globalPass, info.APK.Signature.KeyPassphrase)
|
||||
})
|
||||
|
||||
t.Run("specific passphrases", func(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("NFPM_PASSPHRASE", globalPass)
|
||||
os.Setenv("NFPM_DEB_PASSPHRASE", debPass)
|
||||
os.Setenv("NFPM_RPM_PASSPHRASE", rpmPass)
|
||||
os.Setenv("NFPM_APK_PASSPHRASE", apkPass)
|
||||
info, err := Parse(strings.NewReader("name: foo"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, debPass, info.Deb.Signature.KeyPassphrase)
|
||||
assert.Equal(t, rpmPass, info.RPM.Signature.KeyPassphrase)
|
||||
assert.Equal(t, apkPass, info.APK.Signature.KeyPassphrase)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOverrides(t *testing.T) {
|
||||
file := "./testdata/overrides.yaml"
|
||||
config, err := ParseFile(file)
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/sassoftware/go-rpmutils/cpio"
|
||||
|
||||
"github.com/goreleaser/nfpm/internal/files"
|
||||
"github.com/goreleaser/nfpm/internal/sign"
|
||||
|
||||
"github.com/goreleaser/chglog"
|
||||
|
||||
@ -98,6 +99,10 @@ func (*RPM) Package(info *nfpm.Info, w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.RPM.Signature.KeyFile != "" {
|
||||
rpm.SetPGPSigner(sign.PGPSigner(info.RPM.Signature.KeyFile, info.RPM.Signature.KeyPassphrase))
|
||||
}
|
||||
|
||||
addEmptyDirsRPM(info, rpm)
|
||||
if err = createFilesInsideRPM(info, rpm); err != nil {
|
||||
return err
|
||||
|
@ -2,6 +2,7 @@ package rpm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
"github.com/sassoftware/go-rpmutils/cpio"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
|
||||
"github.com/goreleaser/chglog"
|
||||
|
||||
@ -555,6 +557,39 @@ func TestSymlink(t *testing.T) {
|
||||
assert.Equal(t, symlinkTarget, string(packagedSymlink))
|
||||
}
|
||||
|
||||
func TestRPMSignature(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.RPM.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
|
||||
info.RPM.Signature.KeyPassphrase = "hunter2"
|
||||
|
||||
pubkeyFileContent, err := ioutil.ReadFile("../internal/sign/testdata/pubkey.gpg")
|
||||
require.NoError(t, err)
|
||||
|
||||
keyring, err := openpgp.ReadKeyRing(bytes.NewReader(pubkeyFileContent))
|
||||
require.NoError(t, err)
|
||||
|
||||
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, 1)
|
||||
}
|
||||
|
||||
func TestRPMSignatureError(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.RPM.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
|
||||
info.RPM.Signature.KeyPassphrase = "wrongpass"
|
||||
|
||||
var rpmBuffer bytes.Buffer
|
||||
err := Default.Package(info, &rpmBuffer)
|
||||
require.Error(t, err)
|
||||
|
||||
var expectedError *nfpm.ErrSigningFailure
|
||||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func extractFileFromRpm(rpm []byte, filename string) ([]byte, error) {
|
||||
rpmFile, err := rpmutils.ReadRpm(bytes.NewReader(rpm))
|
||||
if err != nil {
|
||||
|
@ -143,6 +143,13 @@ rpm:
|
||||
config_noreplace_files:
|
||||
path/to/local/bar.con: /etc/bar.conf
|
||||
|
||||
# The package is signed if a key_file is set
|
||||
signature:
|
||||
# PGP secret key (can also be ASCII-armored), the passphrase is taken
|
||||
# from the environment variable $NFPM_RPM_PASSPHRASE with a fallback
|
||||
# to #NFPM_PASSPHRASE.
|
||||
key_file: key.gpg
|
||||
|
||||
# Custon configuration applied only to the Deb packager.
|
||||
deb:
|
||||
# Custom deb rules script.
|
||||
@ -165,6 +172,28 @@ deb:
|
||||
# is already installed.
|
||||
breaks:
|
||||
- some-package
|
||||
|
||||
# The package is signed if a key_file is set
|
||||
signature:
|
||||
# PGP secret key (can also be ASCII-armored). The passphrase is taken
|
||||
# from the environment variable $NFPM_DEB_PASSPHRASE with a fallback
|
||||
# to #NFPM_PASSPHRASE.
|
||||
key_file: key.gpg
|
||||
# The type describes the signers role, possible values are "origin",
|
||||
# "maint" and "archive". If unset, the type defaults to "origin".
|
||||
type: origin
|
||||
|
||||
apk:
|
||||
# The package is signed if a key_file is set
|
||||
signature:
|
||||
# RSA private key in the PEM format. The passphrase is taken from
|
||||
# the environment variable $NFPM_APK_PASSPHRASE with a fallback
|
||||
# to #NFPM_PASSPHRASE.
|
||||
key_file: key.gpg
|
||||
# The name of the signing key. When verifying a package, the signature
|
||||
# is matched to the public key store in /etc/apk/keys/<key_name>.rsa.pub.
|
||||
# If unset, it defaults to the maintainer email address.
|
||||
key_name: origin
|
||||
```
|
||||
|
||||
## Templating
|
||||
|
Loading…
Reference in New Issue
Block a user