Skip to content

Commit

Permalink
fix: switch to use gopenpgp
Browse files Browse the repository at this point in the history
With Googles anouncement that x/crypto/openpgp is depreciated golang/go#44226 we have decided to switch to github.com/ProtonMail/gopenpgp & github.com/ProtonMail/go-crypto/openpgp
  • Loading branch information
Dj Gilcrease committed Apr 7, 2021
1 parent fe57ea8 commit 7b3bbe3
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 158 deletions.
3 changes: 1 addition & 2 deletions deb/deb.go
Expand Up @@ -124,8 +124,7 @@ func (*Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: fun
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)
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}
}
Expand Down
25 changes: 20 additions & 5 deletions go.mod
Expand Up @@ -3,25 +3,40 @@ module github.com/goreleaser/nfpm/v2
go 1.16

require (
github.com/AlekSi/pointer v1.1.0
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/gopenpgp/v2 v2.1.7
github.com/alecthomas/kingpin v2.2.6+incompatible
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/go-git/go-git/v5 v5.3.0 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/google/rpmpack v0.0.0-20201225075926-0a97c2c4b688
github.com/google/uuid v1.2.0 // indirect
github.com/goreleaser/chglog v0.1.2
github.com/goreleaser/fileglob v1.2.0
github.com/imdario/mergo v0.3.12
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b
github.com/sergi/go-diff v1.2.0 // indirect
github.com/stretchr/testify v1.7.0
github.com/ulikunitz/xz v0.5.9 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 // indirect
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

replace github.com/ProtonMail/go-crypto => github.com/digitalxero/go-crypto v0.0.0-20210406211906-712a9153d2a0

replace github.com/ProtonMail/gopenpgp/v2 => github.com/digitalxero/gopenpgp/v2 v2.0.0-20210406235549-7f834bb39c75

//replace github.com/ProtonMail/go-crypto => ../go-crypto
//
//replace github.com/ProtonMail/gopenpgp/v2 => ../gopenpgp
71 changes: 53 additions & 18 deletions go.sum

Large diffs are not rendered by default.

145 changes: 87 additions & 58 deletions internal/sign/pgp.go
Expand Up @@ -2,52 +2,76 @@ package sign

import (
"bytes"
"errors"
goCrypto "crypto"
"fmt"
"io"
"io/ioutil"
"strconv"
"unicode"

"golang.org/x/crypto/openpgp"
"github.com/ProtonMail/gopenpgp/v2/crypto"

"github.com/goreleaser/nfpm/v2"
)

// 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 PGPSignerWithKeyID(keyFile, passphrase, nil)
}

// PGPSignerWithKeyID returns a PGP signer that creates a detached non-ASCII-armored
// signature and is compatible with rpmpack's signature API.
func PGPSignerWithKeyID(keyFile, passphrase string, hexKeyId *string) func([]byte) ([]byte, error) {
return func(data []byte) ([]byte, error) {
key, err := readSigningKey(keyFile, passphrase)
keyId, err := parseKeyID(hexKeyId)
if err != nil {
return nil, fmt.Errorf("%v is not a valid key id: %w", hexKeyId, err)
}
signingKeyRing, 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)
pgpSignature, err := signingKeyRing.WithSigningHash(goCrypto.SHA256).SignDetachedWithKeyID(crypto.NewPlainMessage(data), keyId)
if err != nil {
return nil, &nfpm.ErrSigningFailure{Err: err}
}

return signature.Bytes(), nil
return pgpSignature.GetBinary(), nil
}
}

// PGPArmoredDetachSign creates an ASCII-armored detached signature.
func PGPArmoredDetachSign(message io.Reader, keyFile, passphrase string) ([]byte, error) {
key, err := readSigningKey(keyFile, passphrase)
return PGPArmoredDetachSignWithKeyID(message, keyFile, passphrase, nil)
}

// PGPArmoredDetachSignWithKeyID creates an ASCII-armored detached signature.
func PGPArmoredDetachSignWithKeyID(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)
}

signingKeyRing, err := readSigningKey(keyFile, passphrase)
if err != nil {
return nil, fmt.Errorf("armored detach sign: %w", err)
}

var signature bytes.Buffer
data, err := ioutil.ReadAll(message)
if err != nil {
return nil, fmt.Errorf("reading data: %w", err)
}

err = openpgp.ArmoredDetachSign(&signature, key, message, nil)
pgpSignature, err := signingKeyRing.SignDetachedWithKeyID(crypto.NewPlainMessage(data), keyId)
if err != nil {
return nil, fmt.Errorf("armored detach sign: %w", err)
return nil, &nfpm.ErrSigningFailure{Err: err}
}

return signature.Bytes(), nil
sig, err := pgpSignature.GetArmored()

return []byte(sig), err
}

// PGPVerify is exported for use in tests and verifies a ASCII-armored or non-ASCII-armored
Expand All @@ -60,89 +84,94 @@ func PGPVerify(message io.Reader, signature []byte, armoredPubKeyFile string) er
return fmt.Errorf("reading armored public key file: %w", err)
}

var keyring openpgp.EntityList
data, err := ioutil.ReadAll(message)
if err != nil {
return fmt.Errorf("reading data: %w", err)
}
msg := crypto.NewPlainMessage(data)

var publicKeyObj *crypto.Key
if isASCII(keyFileContent) {
keyring, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(keyFileContent))
publicKeyObj, err = crypto.NewKeyFromArmoredReader(bytes.NewReader(keyFileContent))
if err != nil {
return fmt.Errorf("decoding armored public key file: %w", err)
return fmt.Errorf("decoding public key: %w", err)
}
} else {
keyring, err = openpgp.ReadKeyRing(bytes.NewReader(keyFileContent))
publicKeyObj, err = crypto.NewKeyFromReader(bytes.NewReader(keyFileContent))
if err != nil {
return fmt.Errorf("decoding public key file: %w", err)
return fmt.Errorf("decoding public key: %w", err)
}
}

signingKeyRing, err := crypto.NewKeyRing(publicKeyObj)
if err != nil {
return fmt.Errorf("decoding public key keyring: %w", err)
}

var pgpSignature *crypto.PGPSignature
if isASCII(signature) {
_, err = openpgp.CheckArmoredDetachedSignature(keyring, message, bytes.NewReader(signature))
return err
pgpSignature, err = crypto.NewPGPSignatureFromArmored(string(signature))
if err != nil {
return fmt.Errorf("decoding signature: %w", err)
}
} else {
pgpSignature = crypto.NewPGPSignature(signature)
}

_, err = openpgp.CheckDetachedSignature(keyring, message, bytes.NewReader(signature))
return err
return signingKeyRing.VerifyDetached(msg, pgpSignature, crypto.GetUnixTime())
}

var (
errMoreThanOneKey = errors.New("more than one signing key in keyring")
errNoKeys = errors.New("no signing key in keyring")
errNoPassword = errors.New("key is encrypted but no passphrase was provided")
)
func parseKeyID(hexKeyId *string) (uint64, error) {
if hexKeyId == nil {
return 0, nil
}

func readSigningKey(keyFile, passphrase string) (*openpgp.Entity, error) {
result, err := strconv.ParseUint(*hexKeyId, 16, 64)
if err != nil {
return 0, err
}
return uint64(result), nil
}

func readSigningKey(keyFile, passphrase string) (signingKeyRing *crypto.KeyRing, err error) {
fileContent, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, fmt.Errorf("reading PGP key file: %w", err)
}

var entityList openpgp.EntityList
var (
locked bool
privateKeyObj *crypto.Key
)

if isASCII(fileContent) {
entityList, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(fileContent))
privateKeyObj, err = crypto.NewKeyFromArmoredReader(bytes.NewReader(fileContent))
if err != nil {
return nil, fmt.Errorf("decoding armored PGP keyring: %w", err)
}
} else {
entityList, err = openpgp.ReadKeyRing(bytes.NewReader(fileContent))
privateKeyObj, err = crypto.NewKeyFromReader(bytes.NewReader(fileContent))
if err != nil {
return nil, fmt.Errorf("decoding PGP keyring: %w", err)
}
}

var key *openpgp.Entity

for _, candidate := range entityList {
if candidate.PrivateKey == nil {
continue
}

if !candidate.PrivateKey.CanSign() {
continue
}

if key != nil {
return nil, errMoreThanOneKey
}

key = candidate
}

if key == nil {
return nil, errNoKeys
if locked, err = privateKeyObj.IsLocked(); err != nil {
return nil, fmt.Errorf("checking if key is locked: %w", err)
}

if key.PrivateKey.Encrypted {
if locked {
if passphrase == "" {
return nil, errNoPassword
return nil, fmt.Errorf("key is encrypted but no passphrase was provided")
}

err = key.PrivateKey.Decrypt([]byte(passphrase))
if err != nil {
return nil, fmt.Errorf("decrypt secret signing key: %w", err)
if privateKeyObj, err = privateKeyObj.Unlock([]byte(passphrase)); err != nil {
return nil, fmt.Errorf("unlocking private key: %w", err)
}
}
if signingKeyRing, err = crypto.NewKeyRing(privateKeyObj); err != nil {
return nil, fmt.Errorf("creating signing key ring: %w", err)
}

return key, nil
return signingKeyRing, nil
}

func isASCII(s []byte) bool {
Expand Down

0 comments on commit 7b3bbe3

Please sign in to comment.