Merge pull request #734 from aptly-dev/gpg1

introduce a gpg and gpgv version compatibility check and fall back to v1
This commit is contained in:
Oliver Sauder
2018-04-26 10:34:03 +02:00
committed by GitHub
11 changed files with 264 additions and 10 deletions

View File

@@ -400,7 +400,7 @@ func (context *AptlyContext) GetSigner() pgp.Signer {
defer context.Unlock()
if context.pgpProvider() == "gpg" { // nolint: goconst
return &pgp.GpgSigner{}
return pgp.NewGpgSigner()
}
return &pgp.GoSigner{}
@@ -412,7 +412,7 @@ func (context *AptlyContext) GetVerifier() pgp.Verifier {
defer context.Unlock()
if context.pgpProvider() == "gpg" { // nolint: goconst
return &pgp.GpgVerifier{}
return pgp.NewGpgVerifier()
}
return &pgp.GoVerifier{}

View File

@@ -20,6 +20,7 @@ var (
// GpgSigner is implementation of Signer interface using gpg as external program
type GpgSigner struct {
gpg string
keyRef string
keyring, secretKeyring string
passphrase, passphraseFile string
@@ -78,9 +79,53 @@ func (g *GpgSigner) gpgArgs() []string {
return args
}
func cliVersionCheck(cmd string, marker string) bool {
output, err := exec.Command(cmd, "--version").CombinedOutput()
if err != nil {
return false
}
return strings.Contains(string(output), marker)
}
func findSuitableCLI(cmds []string, versionMarker string) string {
for _, cmd := range cmds {
if cliVersionCheck(cmd, versionMarker) {
return cmd
}
}
return ""
}
// We only support gpg1 at this time. Make sure we find a suitable binary.
func findGPG1() (string, error) {
cmd := findSuitableCLI([]string{"gpg", "gpg1"}, "gpg (GnuPG) 1.")
if cmd != "" {
return cmd, nil
}
return "", fmt.Errorf("Couldn't find a suitable gpg executable. Make sure gnupg1 is available as either gpg or gpg1 in $PATH")
}
// We only support gpgv1 at this time. Make sure we find a suitable binary.
func findGPGV1() (string, error) {
cmd := findSuitableCLI([]string{"gpgv", "gpgv1"}, "gpgv (GnuPG) 1.")
if cmd != "" {
return cmd, nil
}
return "", fmt.Errorf("Couldn't find a suitable gpgv executable. Make sure gpgv1 is available as either gpgv or gpgv1 in $PATH")
}
// NewGpgSigner creates a new gpg signer
func NewGpgSigner() *GpgSigner {
gpg, err := findGPG1()
if err != nil {
panic(err)
}
return &GpgSigner{gpg: gpg}
}
// Init verifies availability of gpg & presence of keys
func (g *GpgSigner) Init() error {
output, err := exec.Command("gpg", "--list-keys", "--dry-run", "--no-auto-check-trustdb").CombinedOutput()
output, err := exec.Command(g.gpg, "--list-keys", "--dry-run", "--no-auto-check-trustdb").CombinedOutput()
if err != nil {
return fmt.Errorf("unable to execute gpg: %s (is gpg installed?): %s", err, string(output))
}
@@ -99,7 +144,7 @@ func (g *GpgSigner) DetachedSign(source string, destination string) error {
args := []string{"-o", destination, "--digest-algo", "SHA256", "--armor", "--yes"}
args = append(args, g.gpgArgs()...)
args = append(args, "--detach-sign", source)
cmd := exec.Command("gpg", args...)
cmd := exec.Command(g.gpg, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -112,7 +157,7 @@ func (g *GpgSigner) ClearSign(source string, destination string) error {
args := []string{"-o", destination, "--digest-algo", "SHA256", "--yes"}
args = append(args, g.gpgArgs()...)
args = append(args, "--clearsign", source)
cmd := exec.Command("gpg", args...)
cmd := exec.Command(g.gpg, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -121,19 +166,43 @@ func (g *GpgSigner) ClearSign(source string, destination string) error {
// GpgVerifier is implementation of Verifier interface using gpgv as external program
type GpgVerifier struct {
gpg string
gpgv string
keyRings []string
}
// NewGpgVerifier creates a new gpg signer
func NewGpgVerifier() *GpgVerifier {
gpg, err := findGPG1()
if err != nil {
panic(err)
}
gpgv, err := findGPGV1()
if err != nil {
panic(err)
}
return &GpgVerifier{gpg: gpg, gpgv: gpgv}
}
// InitKeyring verifies that gpg is installed and some keys are trusted
func (g *GpgVerifier) InitKeyring() error {
err := exec.Command("gpgv", "--version").Run()
cmd, err := findGPG1()
if err != nil {
return fmt.Errorf("unable to execute gpgv: %s (is gpg installed?)", err)
return err
}
g.gpg = cmd
cmd, err = findGPGV1()
if err != nil {
return err
}
g.gpgv = cmd
if len(g.keyRings) == 0 {
// using default keyring
output, err := exec.Command("gpg", "--no-default-keyring", "--no-auto-check-trustdb", "--keyring", "trustedkeys.gpg", "--list-keys").Output()
output, err := exec.Command(g.gpg, "--no-default-keyring", "--no-auto-check-trustdb", "--keyring", "trustedkeys.gpg", "--list-keys").Output()
if err == nil && len(output) == 0 {
fmt.Printf("\nLooks like your keyring with trusted keys is empty. You might consider importing some keys.\n")
fmt.Printf("If you're running Debian or Ubuntu, it's a good idea to import current archive keys by running:\n\n")
@@ -164,7 +233,7 @@ func (g *GpgVerifier) argsKeyrings() (args []string) {
func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (*KeyInfo, error) {
args = append([]string{"--status-fd", "3"}, args...)
cmd := exec.Command("gpgv", args...)
cmd := exec.Command(g.gpgv, args...)
tempf, err := ioutil.TempFile("", "aptly-gpg-status")
if err != nil {
@@ -327,7 +396,7 @@ func (g *GpgVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File,
args := []string{"--no-auto-check-trustdb", "--decrypt", "--batch", "--skip-verify", "--output", "-", clearf.Name()}
cmd := exec.Command("gpg", args...)
cmd := exec.Command(g.gpg, args...)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err

79
pgp/gnupg_test.go Normal file
View File

@@ -0,0 +1,79 @@
package pgp
import (
"os"
"path/filepath"
"runtime"
. "gopkg.in/check.v1"
)
type GnupgSuite struct {
verifier Verifier
bins string
}
var _ = Suite(&GnupgSuite{})
func (s *GnupgSuite) SetUpSuite(c *C) {
_, _File, _, _ := runtime.Caller(0)
s.bins = filepath.Join(filepath.Dir(_File), "test-bins")
}
// If gpg == gpg1 = pick gpg
func (s *GnupgSuite) TestGPG1(c *C) {
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Join(s.bins, "gpg1"))
defer func() { os.Setenv("PATH", origPath) }()
signer := NewGpgSigner()
c.Assert(signer.gpg, Equals, "gpg")
}
// gpg(2) + gpg1 installed = pick gpg1
func (s *GnupgSuite) TestGPG1Not2(c *C) {
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Join(s.bins, "gpg2-and-1"))
defer func() { os.Setenv("PATH", origPath) }()
signer := NewGpgSigner()
c.Assert(signer.gpg, Equals, "gpg1")
}
// If gpg == gpg2 and no gpg1 is available = error
func (s *GnupgSuite) TestGPGNothing(c *C) {
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Join(s.bins, "gpg2-only"))
defer func() { os.Setenv("PATH", origPath) }()
c.Assert(func() { NewGpgSigner() }, PanicMatches, `Couldn't find a suitable gpg executable.+`)
}
// If gpgv == gpgv1 = pick gpgv
func (s *GnupgSuite) TestGPGV1(c *C) {
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Join(s.bins, "gpgv1")+":"+filepath.Join(s.bins, "gpg1"))
defer func() { os.Setenv("PATH", origPath) }()
verifier := NewGpgVerifier()
c.Assert(verifier.gpgv, Equals, "gpgv")
}
// gpgv(2) + gpgv1 installed = pick gpgv1
func (s *GnupgSuite) TestGPGV1Not2(c *C) {
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Join(s.bins, "gpgv2-and-1")+":"+filepath.Join(s.bins, "gpg1"))
defer func() { os.Setenv("PATH", origPath) }()
verifier := NewGpgVerifier()
c.Assert(verifier.gpgv, Equals, "gpgv1")
}
// If gpgv == gpgv2 and no gpgv1 is available = error
func (s *GnupgSuite) TestGPGVNothing(c *C) {
origPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Join(s.bins, "gpgv2-only")+":"+filepath.Join(s.bins, "gpg1"))
defer func() { os.Setenv("PATH", origPath) }()
c.Assert(func() { NewGpgVerifier() }, PanicMatches, `Couldn't find a suitable gpgv executable.+`)
}

1
pgp/test-bins/gpg1/gpg Symbolic link
View File

@@ -0,0 +1 @@
../gpg2-and-1/gpg1

30
pgp/test-bins/gpg2-and-1/gpg Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/sh
while [ "$1" != "" ]; do
case $1 in
--version)
/bin/cat <<'OUTPUT'
gpg (GnuPG) 2.2.4
libgcrypt 1.8.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Home: /root/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
OUTPUT
;;
-?*)
echo "Unknown option: $1"
;;
*)
break
esac
shift
done

29
pgp/test-bins/gpg2-and-1/gpg1 Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/sh
while [ "$1" != "" ]; do
case $1 in
--version)
/bin/cat <<'OUTPUT'
gpg (GnuPG) 1.4.22
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Home: ~/.gnupg
Supported algorithms:
Pubkey: RSA, RSA-E, RSA-S, ELG-E, DSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
OUTPUT
;;
-?*)
echo "Unknown option: $1"
;;
*)
break
esac
shift
done

1
pgp/test-bins/gpg2-only/gpg Symbolic link
View File

@@ -0,0 +1 @@
../gpg2-and-1/gpg

1
pgp/test-bins/gpgv1/gpgv Symbolic link
View File

@@ -0,0 +1 @@
../gpgv2-and-1/gpgv1

22
pgp/test-bins/gpgv2-and-1/gpgv Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
while [ "$1" != "" ]; do
case $1 in
--version)
/bin/cat <<'OUTPUT'
gpgv (GnuPG) 2.2.4
libgcrypt 1.8.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
OUTPUT
;;
-?*)
echo "Unknown option: $1"
;;
*)
break
esac
shift
done

21
pgp/test-bins/gpgv2-and-1/gpgv1 Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
while [ "$1" != "" ]; do
case $1 in
--version)
/bin/cat <<'OUTPUT'
gpgv (GnuPG) 1.4.22
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
OUTPUT
;;
-?*)
echo "Unknown option: $1"
;;
*)
break
esac
shift
done

View File

@@ -0,0 +1 @@
../gpgv2-and-1/gpgv