introduce a gpg and gpgv version compatibility check and fall back to v1

Newer versions of debian and ubuntu come with gpg pointing to gpg2.
We can currently only handle gpg1 CLIs though. Luckily the old gpg is still
available in the package gnupg1 (providing bin/gpg1).

As a bit of a stop-gap, until #657 can be resolved properly, we'll detect
the version of bin/gpg. If it is unsuitable we'll fall back and try
bin/gpg1. If neither is found to be suitable the signer/verifier will
not work.

Same applies to gpgv/gpgv1.
This commit is contained in:
Harald Sitter
2018-04-25 15:05:53 +02:00
parent 77033df27b
commit cd6075ba94
10 changed files with 253 additions and 8 deletions

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,50 @@ 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")
}
// 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()
cmd, err := findGPG1()
if err != nil {
return err
}
g.gpg = cmd
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 +141,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 +154,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 +163,28 @@ 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
}
// 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 +215,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 +378,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

88
pgp/gnupg_test.go Normal file
View File

@@ -0,0 +1,88 @@
package pgp
import (
"os"
"path/filepath"
"runtime"
"github.com/stretchr/testify/assert"
. "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 := GpgSigner{}
assert.NoError(c, signer.Init())
assert.Equal(c, "gpg", signer.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 := GpgSigner{}
assert.NoError(c, signer.Init())
assert.Equal(c, "gpg1", signer.gpg)
}
// 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) }()
signer := GpgSigner{}
assert.Error(c, signer.Init())
assert.Equal(c, "", signer.gpg)
}
// 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 := GpgVerifier{}
assert.NoError(c, verifier.InitKeyring())
assert.Equal(c, "gpgv", verifier.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 := GpgVerifier{}
assert.NoError(c, verifier.InitKeyring())
assert.Equal(c, "gpgv1", verifier.gpgv)
}
// 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) }()
verifier := GpgVerifier{}
assert.Error(c, verifier.InitKeyring())
assert.Equal(c, "", verifier.gpgv)
}

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