mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-11-13 14:37:18 +00:00
Debian co-signs each suite's InRelease with more than one archive key, so gpgv reports a good signature from the key you trust and NO_PUBKEY for the others, exiting 2. Both verifiers treat that as failure, which means mirroring Debian requires importing every key Debian happens to sign with. apt does not require this: it is fatal only when there is no good signature or a BADSIG. Adopt that rule for unknown signers. When gpgv exits non-zero the status stream is parsed per NEWSIG block and the result is a success only when the process exited, at least one block is a good signature from a held key, at least one block is an unknown issuer (ERRSIG reason 9 with NO_PUBKEY), and nothing else appears. BADSIG, EXPSIG, EXPKEYSIG and REVKEYSIG from a held key still fail, as does any unexplained failure. The exit-zero path is unchanged. KEYEXPIRED and KEYREVOKED are deliberately not disqualifiers: doc/DETAILS notes KEYEXPIRED is emitted for an expired subkey even when that subkey was not used, so treating it as a verdict rejects valid signatures. checkDetachedSignature gets the equivalent change for the internal verifier.
171 lines
5.5 KiB
Go
171 lines
5.5 KiB
Go
package pgp
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
|
|
. "gopkg.in/check.v1"
|
|
)
|
|
|
|
// Common set of tests shared by internal & external GnuPG implementations
|
|
type VerifierSuite struct {
|
|
verifier Verifier
|
|
multisigOneVerifier Verifier
|
|
multisigBothVerifier Verifier
|
|
}
|
|
|
|
// The partial-keyring fixtures were generated with GnuPG 2.4:
|
|
//
|
|
// gpg --batch --pinentry-mode loopback --passphrase '' --quick-generate-key 'Aptly multisign known fixture <known@fixture.invalid>' rsa2048 sign 0
|
|
// gpg --batch --pinentry-mode loopback --passphrase '' --quick-generate-key 'Aptly multisign unknown fixture <unknown@fixture.invalid>' rsa2048 sign 0
|
|
// gpg --export 2B5E1D91576341AF44C600E130AD2886ED58775C 32F003A216681D4CC810FA2198B5301EB1FA1317 > multisig-both.gpg
|
|
// gpg --export 2B5E1D91576341AF44C600E130AD2886ED58775C > multisig-one.gpg
|
|
// gpg --local-user 2B5E1D91576341AF44C600E130AD2886ED58775C --local-user 32F003A216681D4CC810FA2198B5301EB1FA1317 --detach-sign --output multisig.signature multisig.text
|
|
// gpg --local-user 2B5E1D91576341AF44C600E130AD2886ED58775C --local-user 32F003A216681D4CC810FA2198B5301EB1FA1317 --clearsign --output multisig.clearsigned multisig.text
|
|
// gpg --local-user 2B5E1D91576341AF44C600E130AD2886ED58775C --detach-sign --output multisig-known-good.signature multisig.text
|
|
// gpg --local-user 32F003A216681D4CC810FA2198B5301EB1FA1317 --detach-sign --output multisig-known-bad.signature multisig-bad.text
|
|
//
|
|
// multisig-bad.text was made by changing multisig.text after signing it.
|
|
|
|
func (s *VerifierSuite) TestVerifyDetached(c *C) {
|
|
for _, test := range []struct {
|
|
textName, signatureName string
|
|
}{
|
|
{"1.text", "1.signature"},
|
|
{"2.text", "2.signature"},
|
|
{"3.text", "3.signature"},
|
|
{"4.text", "4.signature"},
|
|
} {
|
|
cleartext, err := os.Open(test.textName)
|
|
c.Assert(err, IsNil)
|
|
|
|
signature, err := os.Open(test.signatureName)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = s.verifier.VerifyDetachedSignature(signature, cleartext, false)
|
|
c.Assert(err, IsNil)
|
|
|
|
_ = signature.Close()
|
|
_ = cleartext.Close()
|
|
}
|
|
}
|
|
|
|
func (s *VerifierSuite) TestVerifyClearsigned(c *C) {
|
|
clearsigned, err := os.Open("multisig.clearsigned")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = clearsigned.Close() }()
|
|
|
|
keyInfo, err := s.multisigBothVerifier.VerifyClearsigned(clearsigned, false)
|
|
c.Assert(err, IsNil)
|
|
c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"30AD2886ED58775C", "98B5301EB1FA1317"})
|
|
c.Check(keyInfo.MissingKeys, DeepEquals, []Key(nil))
|
|
}
|
|
|
|
func (s *VerifierSuite) TestVerifyClearsignedPartialKeyring(c *C) {
|
|
clearsigned, err := os.Open("multisig.clearsigned")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = clearsigned.Close() }()
|
|
|
|
keyInfo, err := s.multisigOneVerifier.VerifyClearsigned(clearsigned, false)
|
|
c.Assert(err, IsNil)
|
|
c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"30AD2886ED58775C"})
|
|
c.Check(keyInfo.MissingKeys, DeepEquals, []Key{"98B5301EB1FA1317"})
|
|
}
|
|
|
|
func (s *VerifierSuite) TestVerifyDetachedPartialKeyring(c *C) {
|
|
cleartext, err := os.Open("multisig.text")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = cleartext.Close() }()
|
|
|
|
signature, err := os.Open("multisig.signature")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = signature.Close() }()
|
|
|
|
err = s.multisigOneVerifier.VerifyDetachedSignature(signature, cleartext, false)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *VerifierSuite) TestVerifyPartialKeyringRejectsBadSignature(c *C) {
|
|
cleartext, err := os.Open("multisig-bad.text")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = cleartext.Close() }()
|
|
|
|
signature, err := os.Open("multisig.signature")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = signature.Close() }()
|
|
|
|
err = s.multisigOneVerifier.VerifyDetachedSignature(signature, cleartext, false)
|
|
c.Assert(err, NotNil)
|
|
}
|
|
|
|
func (s *VerifierSuite) TestVerifyRejectsBadSignatureAlongsideGood(c *C) {
|
|
cleartext, err := os.Open("multisig.text")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = cleartext.Close() }()
|
|
|
|
goodSignature, err := os.Open("multisig-known-good.signature")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = goodSignature.Close() }()
|
|
|
|
badSignature, err := os.Open("multisig-known-bad.signature")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = badSignature.Close() }()
|
|
|
|
signature := io.MultiReader(goodSignature, badSignature)
|
|
err = s.multisigBothVerifier.VerifyDetachedSignature(signature, cleartext, false)
|
|
c.Assert(err, NotNil)
|
|
}
|
|
|
|
func (s *VerifierSuite) TestVerifyNoKnownSignerStillFails(c *C) {
|
|
cleartext, err := os.Open("multisig.text")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = cleartext.Close() }()
|
|
|
|
signature, err := os.Open("multisig.signature")
|
|
c.Assert(err, IsNil)
|
|
defer func() { _ = signature.Close() }()
|
|
|
|
err = s.verifier.VerifyDetachedSignature(signature, cleartext, false)
|
|
c.Assert(err, NotNil)
|
|
}
|
|
|
|
func (s *VerifierSuite) TestExtractClearsigned(c *C) {
|
|
for _, test := range []struct {
|
|
clearSignedName, clearTextName string
|
|
}{
|
|
{"1.clearsigned", "1.cleartext"},
|
|
} {
|
|
clearsigned, err := os.Open(test.clearSignedName)
|
|
c.Assert(err, IsNil)
|
|
|
|
cleartext, err := os.Open(test.clearTextName)
|
|
c.Assert(err, IsNil)
|
|
|
|
is, err := s.verifier.IsClearSigned(clearsigned)
|
|
c.Assert(err, IsNil)
|
|
c.Check(is, Equals, true)
|
|
|
|
_, _ = clearsigned.Seek(0, 0)
|
|
|
|
extractedF, err := s.verifier.ExtractClearsigned(clearsigned)
|
|
c.Assert(err, IsNil)
|
|
|
|
expected, err := io.ReadAll(cleartext)
|
|
c.Assert(err, IsNil)
|
|
|
|
extracted, err := io.ReadAll(extractedF)
|
|
c.Assert(err, IsNil)
|
|
|
|
// normalize newlines
|
|
extracted = bytes.TrimRight(bytes.Replace(extracted, []byte("\r\n"), []byte("\n"), -1), "\n")
|
|
expected = bytes.Replace(expected, []byte("\r\n"), []byte("\n"), -1)
|
|
|
|
c.Check(extracted, DeepEquals, expected)
|
|
|
|
_ = extractedF.Close()
|
|
_ = clearsigned.Close()
|
|
_ = cleartext.Close()
|
|
}
|
|
}
|