Skip to content

Commit

Permalink
Compatibility with GnuPG 1.x and 2.x, auto-detect GnuPG version
Browse files Browse the repository at this point in the history
* aptly can sign and verify without issues with GnuPG 1.x and 2.x
* aptly auto-detects GnuPG version and adapts accordingly
* aptly automatically finds suitable GnuPG version

Majority of the work was to get unit-tests which can work with GnuPG 1.x & 2.x.
Locally I've verified that aptly supports GnuPG 1.4.x & 2.2.x. Travis CI
environment is based on trusty, so it runs gpg2 tests with GnuPG 2.0.x.

Configuration parameter gpgProvider now supports three values for GnuPG:

* gpg (same as before, default): use GnuPG 1.x if available (checks gpg, gpg1),
otherwise uses GnuPG 2.x; for aptly users who already have GnuPG 1.x
environment (as it was the only supported version) nothing should change; new
users might start with GnuPG 2.x if that's their installed version

* gpg1 looks for GnuPG 1.x only, fails otherwise

* gpg2 looks for GnuPG 2.x only, fails otherwise
  • Loading branch information
smira committed Sep 26, 2018
1 parent ec57d17 commit 73c1c2b
Show file tree
Hide file tree
Showing 25 changed files with 559 additions and 93 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ system/env/

# created by make build for release artifacts
build/

pgp/keyrings/aptly2*.gpg
pgp/keyrings/aptly2*.gpg~
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ addons:
packages:
- python-virtualenv
- graphviz
- gnupg2
- gpgv2

env:
global:
Expand Down
2 changes: 1 addition & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ package environment to new version.`,
cmd.Flag.Bool("dep-verbose-resolve", false, "when processing dependencies, print detailed logs")
cmd.Flag.String("architectures", "", "list of architectures to consider during (comma-separated), default to all available")
cmd.Flag.String("config", "", "location of configuration file (default locations are /etc/aptly.conf, ~/.aptly.conf)")
cmd.Flag.String("gpg-provider", "", "PGP implementation (\"gpg\" for external gpg or \"internal\" for Go internal implementation)")
cmd.Flag.String("gpg-provider", "", "PGP implementation (\"gpg\", \"gpg1\", \"gpg2\" for external gpg or \"internal\" for Go internal implementation)")

if aptly.EnableDebug {
cmd.Flag.String("cpuprofile", "", "write cpu profile to file")
Expand Down
34 changes: 27 additions & 7 deletions context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,35 +387,55 @@ func (context *AptlyContext) pgpProvider() string {
provider = context.config().GpgProvider
}

if !(provider == "gpg" || provider == "internal") { // nolint: goconst
switch provider {
case "gpg": // nolint: goconst
case "gpg1": // nolint: goconst
case "gpg2": // nolint: goconst
case "internal": // nolint: goconst
default:
Fatal(fmt.Errorf("unknown gpg provider: %v", provider))
}

return provider
}

func (context *AptlyContext) getGPGFinder(provider string) pgp.GPGFinder {
switch context.pgpProvider() {
case "gpg1":
return pgp.GPG1Finder()
case "gpg2":
return pgp.GPG2Finder()
case "gpg":
return pgp.GPGDefaultFinder()
}

panic("uknown GPG provider type")
}

// GetSigner returns Signer with respect to provider
func (context *AptlyContext) GetSigner() pgp.Signer {
context.Lock()
defer context.Unlock()

if context.pgpProvider() == "gpg" { // nolint: goconst
return pgp.NewGpgSigner()
provider := context.pgpProvider()
if provider == "internal" { // nolint: goconst
return &pgp.GoSigner{}
}

return &pgp.GoSigner{}
return pgp.NewGpgSigner(context.getGPGFinder(provider))
}

// GetVerifier returns Verifier with respect to provider
func (context *AptlyContext) GetVerifier() pgp.Verifier {
context.Lock()
defer context.Unlock()

if context.pgpProvider() == "gpg" { // nolint: goconst
return pgp.NewGpgVerifier()
provider := context.pgpProvider()
if provider == "internal" { // nolint: goconst
return &pgp.GoVerifier{}
}

return &pgp.GoVerifier{}
return pgp.NewGpgVerifier(context.getGPGFinder(provider))
}

// UpdateFlags sets internal copy of flags in the context
Expand Down
18 changes: 15 additions & 3 deletions man/aptly.1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "APTLY" "1" "November 2017" "" ""
.TH "APTLY" "1" "September 2018" "" ""
.
.SH "NAME"
\fBaptly\fR \- Debian repository management tool
Expand Down Expand Up @@ -150,7 +150,7 @@ don\(cqt verify remote mirrors with gpg(1), also can be disabled on per\-mirror
.
.TP
\fBgpgProvider\fR
implementation of PGP signing/validation \- \fBgpg\fR for external \fBgpg\fR utility or \fBinternal\fR to use Go internal implementation
implementation of PGP signing/validation \- \fBgpg\fR for external \fBgpg\fR utility or \fBinternal\fR to use Go internal implementation; \fBgpg1\fR might be used to force use of GnuPG 1\.x, \fBgpg2\fR enables GnuPG 2\.x only; default is to use GnuPG 1\.x if available and GnuPG 2\.x otherwise
.
.TP
\fBdownloadSourcePackages\fR
Expand Down Expand Up @@ -434,7 +434,7 @@ when processing dependencies, print detailed logs
.
.TP
\-\fBgpg\-provider\fR=
PGP implementation ("gpg" for external gpg or "internal" for Go internal implementation)
PGP implementation ("gpg", "gpg1", "gpg2" for external gpg or "internal" for Go internal implementation)
.
.SH "CREATE NEW MIRROR"
\fBaptly\fR \fBmirror\fR \fBcreate\fR \fIname\fR \fIarchive url\fR \fIdistribution\fR [\fIcomponent1\fR \|\.\|\.\|\.]
Expand Down Expand Up @@ -2030,5 +2030,17 @@ Matt Martyn (https://github\.com/MMartyn)
.IP "\[ci]" 4
Ludovico Cavedon (https://github\.com/cavedon)
.
.IP "\[ci]" 4
Petr Jediny (https://github\.com/pjediny)
.
.IP "\[ci]" 4
Maximilian Stein (https://github\.com/steinymity)
.
.IP "\[ci]" 4
Strajan Sebastian (https://github\.com/strajansebastian)
.
.IP "\[ci]" 4
Artem Smirnov (https://github\.com/urpylka)
.
.IP "" 0

4 changes: 3 additions & 1 deletion man/aptly.1.ronn.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ Options:

* `gpgProvider`:
implementation of PGP signing/validation - `gpg` for external `gpg` utility or
`internal` to use Go internal implementation
`internal` to use Go internal implementation; `gpg1` might be used to force use
of GnuPG 1.x, `gpg2` enables GnuPG 2.x only; default is to use GnuPG 1.x if
available and GnuPG 2.x otherwise

* `downloadSourcePackages`:
if enabled, all mirrors created would have flag set to download source packages;
Expand Down
76 changes: 21 additions & 55 deletions pgp/gnupg.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pgp
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
Expand All @@ -18,12 +19,10 @@ var (
_ Verifier = &GpgVerifier{}
)

// Skip GPG version check for GPG 1.x
var skipGPGVersionCheck bool

// GpgSigner is implementation of Signer interface using gpg as external program
type GpgSigner struct {
gpg string
version GPGVersion
keyRef string
keyring, secretKeyring string
passphrase, passphraseFile string
Expand Down Expand Up @@ -55,7 +54,7 @@ func (g *GpgSigner) gpgArgs() []string {
if g.keyring != "" {
args = append(args, "--no-auto-check-trustdb", "--no-default-keyring", "--keyring", g.keyring)
}
if g.secretKeyring != "" {
if g.secretKeyring != "" && g.version == GPG1x {
args = append(args, "--secret-keyring", g.secretKeyring)
}

Expand All @@ -64,7 +63,9 @@ func (g *GpgSigner) gpgArgs() []string {
}

if g.passphrase != "" || g.passphraseFile != "" {
args = append(args, "--no-use-agent")
if g.version == GPG1x {
args = append(args, "--no-use-agent")
}
}

if g.passphrase != "" {
Expand All @@ -77,53 +78,21 @@ func (g *GpgSigner) gpgArgs() []string {

if g.batch {
args = append(args, "--no-tty", "--batch")
}

return args
}

func cliVersionCheck(cmd string, marker string) bool {
output, err := exec.Command(cmd, "--version").CombinedOutput()
if err != nil {
return false
}
return skipGPGVersionCheck || strings.Contains(string(output), marker)
}

func findSuitableCLI(cmds []string, versionMarker string) string {
for _, cmd := range cmds {
if cliVersionCheck(cmd, versionMarker) {
return cmd
if g.version == GPG21xPlus {
args = append(args, "--pinentry-mode", "loopback")
}
}
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")
return args
}

// NewGpgSigner creates a new gpg signer
func NewGpgSigner() *GpgSigner {
gpg, err := findGPG1()
func NewGpgSigner(finder GPGFinder) *GpgSigner {
gpg, version, err := finder.FindGPG()
if err != nil {
panic(err)
}
return &GpgSigner{gpg: gpg}
return &GpgSigner{gpg: gpg, version: version}
}

// Init verifies availability of gpg & presence of keys
Expand Down Expand Up @@ -171,22 +140,27 @@ func (g *GpgSigner) ClearSign(source string, destination string) error {
type GpgVerifier struct {
gpg string
gpgv string
version GPGVersion
keyRings []string
}

// NewGpgVerifier creates a new gpg verifier
func NewGpgVerifier() *GpgVerifier {
gpg, err := findGPG1()
func NewGpgVerifier(finder GPGFinder) *GpgVerifier {
gpg, versionGPG, err := finder.FindGPG()
if err != nil {
panic(err)
}

gpgv, err := findGPGV1()
gpgv, versionGPGV, err := finder.FindGPGV()
if err != nil {
panic(err)
}

return &GpgVerifier{gpg: gpg, gpgv: gpgv}
if versionGPG != versionGPGV {
panic(errors.New("gpg and gpgv versions don't match"))
}

return &GpgVerifier{gpg: gpg, gpgv: gpgv, version: versionGPG}
}

// InitKeyring verifies that gpg is installed and some keys are trusted
Expand Down Expand Up @@ -417,11 +391,3 @@ func (g *GpgVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File,

return
}

func init() {
skipCheck := os.Getenv("APTLY_SKIP_GPG_VERSION_CHECK")
switch strings.ToLower(skipCheck) {
case "1", "y", "yes", "true":
skipGPGVersionCheck = true
}
}
Loading

0 comments on commit 73c1c2b

Please sign in to comment.