mirror of
https://github.com/goreleaser/nfpm.git
synced 2026-06-19 08:05:04 +00:00
feat: implement dpkg-sig Package signing (#508)
* implement dpkg-sig Package signing * Fix dpkgsig template syntax * Fix dpkgsig template syntax * Correctly handle template errors when reading dpkg-sig templates * Fix dkpgsig signature templateing
This commit is contained in:
+100
-13
@@ -5,6 +5,7 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"crypto/md5" // nolint:gas
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -128,23 +129,49 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
|
||||
}
|
||||
|
||||
// TODO: refactor this
|
||||
method := "debsign"
|
||||
if info.Deb.Signature.Method != "" {
|
||||
method = info.Deb.Signature.Method
|
||||
}
|
||||
|
||||
if info.Deb.Signature.KeyFile != "" {
|
||||
data := io.MultiReader(bytes.NewReader(debianBinary), bytes.NewReader(controlTarGz),
|
||||
bytes.NewReader(dataTarball))
|
||||
var data io.Reader
|
||||
var sigType string
|
||||
var sig []byte
|
||||
|
||||
sig, err := sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
if err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
if method == "debsign" {
|
||||
data = readDebsignData(debianBinary, controlTarGz, dataTarball)
|
||||
|
||||
sigType := "origin"
|
||||
if info.Deb.Signature.Type != "" {
|
||||
sigType = info.Deb.Signature.Type
|
||||
}
|
||||
sigType = "origin"
|
||||
if info.Deb.Signature.Type != "" {
|
||||
sigType = info.Deb.Signature.Type
|
||||
}
|
||||
|
||||
if sigType != "origin" && sigType != "maint" && sigType != "archive" {
|
||||
return &nfpm.ErrSigningFailure{
|
||||
Err: ErrInvalidSignatureType,
|
||||
if sigType != "origin" && sigType != "maint" && sigType != "archive" {
|
||||
return &nfpm.ErrSigningFailure{
|
||||
Err: ErrInvalidSignatureType,
|
||||
}
|
||||
}
|
||||
|
||||
sig, err = sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
if err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
} else if method == "dpkg-sig" {
|
||||
data, err := readDpkgSigData(info, debianBinary, controlTarGz, dataTarball)
|
||||
if err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
sigType = "builder"
|
||||
if info.Deb.Signature.Type != "" {
|
||||
sigType = info.Deb.Signature.Type
|
||||
}
|
||||
|
||||
sig, err = sign.PGPClearSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
if err != nil {
|
||||
return &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +185,66 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
|
||||
return nil
|
||||
}
|
||||
|
||||
func readDebsignData(debianBinary, controlTarGz, dataTarball []byte) io.Reader {
|
||||
return io.MultiReader(bytes.NewReader(debianBinary), bytes.NewReader(controlTarGz),
|
||||
bytes.NewReader(dataTarball))
|
||||
}
|
||||
|
||||
// reference: https://manpages.debian.org/jessie/dpkg-sig/dpkg-sig.1.en.html
|
||||
const dpkgSigTemplate = `
|
||||
Hash: SHA1
|
||||
|
||||
Version: 4
|
||||
Signer: {{ .Signer }}
|
||||
Date: {{ .Date }}
|
||||
Role: {{ .Role }}
|
||||
Files:
|
||||
{{range .Files}}{{ .Md5Sum }} {{ .Sha1Sum }} {{ .Size }} {{ .Name }}{{end}}
|
||||
`
|
||||
|
||||
type dpkgSigData struct {
|
||||
Signer string
|
||||
Date time.Time
|
||||
Role string
|
||||
Files []dpkgSigFileLine
|
||||
Info *nfpm.Info
|
||||
}
|
||||
type dpkgSigFileLine struct {
|
||||
Md5Sum [16]byte
|
||||
Sha1Sum [20]byte
|
||||
Size int
|
||||
Name string
|
||||
}
|
||||
|
||||
func newDpkgSigFileLine(name string, fileContent []byte) dpkgSigFileLine {
|
||||
return dpkgSigFileLine{
|
||||
Name: name,
|
||||
Md5Sum: md5.Sum(fileContent),
|
||||
Sha1Sum: sha1.Sum(fileContent),
|
||||
Size: len(fileContent),
|
||||
}
|
||||
}
|
||||
|
||||
func readDpkgSigData(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) (io.Reader, error) {
|
||||
data := dpkgSigData{
|
||||
Signer: info.Deb.Signature.Signer,
|
||||
Date: time.Now(),
|
||||
Role: info.Deb.Signature.Type,
|
||||
Files: []dpkgSigFileLine{
|
||||
newDpkgSigFileLine("debian-binary", debianBinary),
|
||||
newDpkgSigFileLine("control.tar.gz", controlTarGz),
|
||||
newDpkgSigFileLine("data.tar.gz", dataTarball),
|
||||
},
|
||||
}
|
||||
temp, _ := template.New("dpkg-sig").Parse(dpkgSigTemplate)
|
||||
buf := &bytes.Buffer{}
|
||||
err := temp.Execute(buf, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dpkg-sig template error: %w", err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (*Deb) SetPackagerDefaults(info *nfpm.Info) {
|
||||
// Priority should be set on all packages per:
|
||||
// https://www.debian.org/doc/debian-policy/ch-archive.html#priorities
|
||||
|
||||
@@ -946,6 +946,36 @@ func TestDebsigsSignatureError(t *testing.T) {
|
||||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestDpkgSigSignature(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
|
||||
info.Deb.Signature.KeyPassphrase = "hunter2"
|
||||
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 TestDpkgSigSignatureError(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.KeyFile = "/does/not/exist"
|
||||
info.Deb.Signature.Method = "dpkg-sig"
|
||||
|
||||
var deb bytes.Buffer
|
||||
err := Default.Package(info, &deb)
|
||||
require.Error(t, err)
|
||||
|
||||
var expectedError *nfpm.ErrSigningFailure
|
||||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestDisableGlobbing(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.DisableGlobbing = true
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/clearsign"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
)
|
||||
@@ -79,6 +80,48 @@ func PGPArmoredDetachSignWithKeyID(message io.Reader, keyFile, passphrase string
|
||||
return signature.Bytes(), nil
|
||||
}
|
||||
|
||||
func PGPClearSignWithKeyID(message io.Reader, keyFile, passphrase string, hexKeyId *string) ([]byte, error) {
|
||||
keyId, err := parseKeyID(hexKeyId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v is not a valid key id: %w", hexKeyId, err)
|
||||
}
|
||||
|
||||
key, err := readSigningKey(keyFile, passphrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clear sign: %w", err)
|
||||
}
|
||||
|
||||
var signature bytes.Buffer
|
||||
|
||||
writeCloser, err := clearsign.Encode(&signature, key.PrivateKey, &packet.Config{
|
||||
SigningKeyId: keyId,
|
||||
DefaultHash: crypto.SHA256,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clear sign: %w", err)
|
||||
}
|
||||
|
||||
messageBytes, err := io.ReadAll(message)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clear sign: %w", err)
|
||||
}
|
||||
|
||||
writtenLength, err := writeCloser.Write(messageBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clear sign: %w", err)
|
||||
}
|
||||
if writtenLength != len(messageBytes) {
|
||||
return nil, fmt.Errorf("partial signature written")
|
||||
}
|
||||
writeCloser.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("armored detach sign: %w", err)
|
||||
}
|
||||
|
||||
return signature.Bytes(), nil
|
||||
}
|
||||
|
||||
// PGPVerify is exported for use in tests and verifies an 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
|
||||
@@ -112,6 +155,32 @@ func PGPVerify(message io.Reader, signature []byte, armoredPubKeyFile string) er
|
||||
return err
|
||||
}
|
||||
|
||||
func PGPReadMessage(message []byte, armoredPubKeyFile string) error {
|
||||
keyFileContent, err := ioutil.ReadFile(armoredPubKeyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading armored public key file: %w", err)
|
||||
}
|
||||
|
||||
var keyring openpgp.EntityList
|
||||
|
||||
if isASCII(keyFileContent) {
|
||||
keyring, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(keyFileContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding armored public key file: %w", err)
|
||||
}
|
||||
} else {
|
||||
keyring, err = openpgp.ReadKeyRing(bytes.NewReader(keyFileContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding public key file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
block, _ := clearsign.Decode(message)
|
||||
_, err = block.VerifySignature(keyring, nil)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func parseKeyID(hexKeyId *string) (uint64, error) {
|
||||
if hexKeyId == nil || *hexKeyId == "" {
|
||||
return 0, nil
|
||||
|
||||
@@ -332,8 +332,11 @@ type Deb struct {
|
||||
|
||||
type DebSignature struct {
|
||||
PackageSignature `yaml:",inline"`
|
||||
// debsign, or dpkg-sig (defaults to debsign)
|
||||
Method string `yaml:"method,omitempty" jsonschema:"title=method role,enum=debsign,enum=dpkg-sig,default=debsign"`
|
||||
// origin, maint or archive (defaults to origin)
|
||||
Type string `yaml:"type,omitempty" jsonschema:"title=signer role,enum=origin,enum=maint,enum=archive,default=origin"`
|
||||
Type string `yaml:"type,omitempty" jsonschema:"title=signer role,enum=origin,enum=maint,enum=archive,default=origin"`
|
||||
Signer string `yaml:"signer,omitempty" jsonschema:"title=signer"`
|
||||
}
|
||||
|
||||
// DebTriggers contains triggers only available for deb packages.
|
||||
|
||||
Reference in New Issue
Block a user