pgp: accept multi-signed releases when the keyring holds only some signers

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.
This commit is contained in:
Daniel Pinder authored and André Roth committed 2026-11-01 13:26:36 +01:00
1 parent a65c2cd850
commit 59ff63de62
19 files changed
+410 -19

No files matched your search

+98 -4
View File
@@ -209,6 +209,40 @@ func (g *GpgVerifier) argsKeyrings() (args []string) {
return
}
type gpgvSignatureDisposition int
const (
gpgvSignatureOther gpgvSignatureDisposition = iota
gpgvSignatureGood
gpgvSignatureUnknown
gpgvSignatureDisqualified
)
type gpgvSignatureStatus struct {
goodSig bool
validSig bool
missingKeyErrSig bool
noPubKey bool
disqualified bool
otherFailure bool
}
func (status gpgvSignatureStatus) disposition() gpgvSignatureDisposition {
if status.disqualified {
return gpgvSignatureDisqualified
}
if status.otherFailure {
return gpgvSignatureOther
}
if status.goodSig && status.validSig {
return gpgvSignatureGood
}
if status.missingKeyErrSig && status.noPubKey && !status.goodSig && !status.validSig {
return gpgvSignatureUnknown
}
return gpgvSignatureOther
}
func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (*KeyInfo, error) {
args = append([]string{"--status-fd", "3"}, args...)
cmd := exec.Command(g.gpgv, args...)
@@ -255,22 +289,82 @@ func (g *GpgVerifier) runGpgv(args []string, context string, showKeyTip bool) (*
statusr := bufio.NewScanner(tempf)
result := &KeyInfo{}
statuses := []gpgvSignatureStatus{}
var status *gpgvSignatureStatus
flushStatus := func() {
if status != nil {
statuses = append(statuses, *status)
}
}
for statusr.Scan() {
line := strings.TrimSpace(statusr.Text())
fields := strings.Fields(line)
if len(fields) >= 2 && fields[0] == "[GNUPG:]" && fields[1] == "NEWSIG" {
flushStatus()
status = &gpgvSignatureStatus{}
continue
}
if status == nil {
status = &gpgvSignatureStatus{otherFailure: true}
}
if len(fields) < 2 || fields[0] != "[GNUPG:]" {
status.otherFailure = true
continue
}
if strings.HasPrefix(line, "[GNUPG:] GOODSIG ") {
result.GoodKeys = append(result.GoodKeys, Key(strings.Fields(line)[2]))
} else if strings.HasPrefix(line, "[GNUPG:] NO_PUBKEY ") {
result.MissingKeys = append(result.MissingKeys, Key(strings.Fields(line)[2]))
switch fields[1] {
case "GOODSIG":
if len(fields) < 3 {
status.otherFailure = true
continue
}
status.goodSig = true
result.GoodKeys = append(result.GoodKeys, Key(fields[2]))
case "VALIDSIG":
status.validSig = true
case "NO_PUBKEY":
if len(fields) < 3 {
status.otherFailure = true
continue
}
status.noPubKey = true
result.MissingKeys = append(result.MissingKeys, Key(fields[2]))
case "ERRSIG":
// Field 7 is the gpg-error reason; 9 is GPG_ERR_NO_PUBKEY.
if len(fields) > 7 && fields[7] == "9" {
status.missingKeyErrSig = true
} else {
status.otherFailure = true
}
case "BADSIG", "EXPSIG", "EXPKEYSIG", "REVKEYSIG":
status.disqualified = true
}
}
flushStatus()
if err = statusr.Err(); err != nil {
return nil, err
}
if cmderr != nil {
goodSignatures := 0
unknownSignatures := 0
failureExplained := cmd.ProcessState != nil && cmd.ProcessState.Exited()
for _, signatureStatus := range statuses {
switch signatureStatus.disposition() {
case gpgvSignatureGood:
goodSignatures++
case gpgvSignatureUnknown:
unknownSignatures++
default:
failureExplained = false
}
}
if failureExplained && goodSignatures > 0 && unknownSignatures > 0 {
return result, nil
}
if showKeyTip && len(g.keyRings) == 0 && len(result.MissingKeys) > 0 {
fmt.Printf("\nLooks like some keys are missing in your trusted keyring, you may consider importing them from keyserver:\n\n")
+189
View File
@@ -93,8 +93,14 @@ func (s *Gnupg1VerifierSuite) SetUpTest(c *C) {
s.verifier = NewGpgVerifier(finder)
s.verifier.AddKeyring("./trusted.gpg")
s.multisigOneVerifier = NewGpgVerifier(finder)
s.multisigOneVerifier.AddKeyring("./multisig-one.gpg")
s.multisigBothVerifier = NewGpgVerifier(finder)
s.multisigBothVerifier.AddKeyring("./multisig-both.gpg")
c.Assert(s.verifier.InitKeyring(false), IsNil)
c.Assert(s.multisigOneVerifier.InitKeyring(false), IsNil)
c.Assert(s.multisigBothVerifier.InitKeyring(false), IsNil)
}
type Gnupg1SignerSuite struct {
@@ -142,8 +148,191 @@ func (s *Gnupg2VerifierSuite) SetUpTest(c *C) {
s.verifier = NewGpgVerifier(finder)
s.verifier.AddKeyring("./trusted.gpg")
s.multisigOneVerifier = NewGpgVerifier(finder)
s.multisigOneVerifier.AddKeyring("./multisig-one.gpg")
s.multisigBothVerifier = NewGpgVerifier(finder)
s.multisigBothVerifier.AddKeyring("./multisig-both.gpg")
c.Assert(s.verifier.InitKeyring(false), IsNil)
c.Assert(s.multisigOneVerifier.InitKeyring(false), IsNil)
c.Assert(s.multisigBothVerifier.InitKeyring(false), IsNil)
}
// The status fixtures were generated with GnuPG 2.4 at fixed times so the
// expired key and subkey remain expired without making the other keys age out:
//
// export GNUPGHOME=$(mktemp -d); chmod 700 "$GNUPGHOME"
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status good fixture <good@fixture.invalid>' rsa2048 sign 0
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status expired fixture <expired@fixture.invalid>' rsa2048 sign seconds=5
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status unknown fixture <unknown-status@fixture.invalid>' rsa2048 sign 0
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly status revoked fixture <revoked@fixture.invalid>' rsa2048 sign 0
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-generate-key 'Aptly unused expired subkey fixture <subkey@fixture.invalid>' rsa2048 cert 0
// good=A35DB05543372EE999F6EF74EBC0B25127B9BF5A expired=DA1728275CB25D14DC8B6DC97D0DD4A295844522 unknown=A068D4E79AD454D37C0888F5B316843CAB67504E
// revoked=CD11F407D4CEB092E66A83EC402BF908BCB202A9 primary=E1745C372D24AC29BD57FAB094ABAB0251F5E2FC active=724D68416C95AA282D8280DF22EE1E88523FD531
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-add-key "$primary" rsa2048 sign 0
// gpg --batch --pinentry-mode loopback --passphrase '' --faked-system-time 20250101T000000 --quick-add-key "$primary" rsa2048 sign seconds=5
// gpg --batch --yes --faked-system-time 20250101T000002 --local-user "$good!" --local-user "$expired!" --local-user "$unknown!" --detach-sign --output status-expired-held.signature multisig.text
// gpg --batch --yes --export "$good" "$expired" > status-expired-held.gpg
// gpg --batch --yes --faked-system-time 20250101T000002 --local-user "$good!" --local-user "$revoked!" --local-user "$unknown!" --detach-sign --output status-revoked-held.signature multisig.text
// sed 's/^://' "$GNUPGHOME/openpgp-revocs.d/$revoked.rev" | gpg --batch --yes --import
// gpg --batch --yes --export "$good" "$revoked" > status-revoked-held.gpg
// gpg --batch --yes --faked-system-time 20260902T143800 --local-user "$active!" --local-user "$unknown!" --detach-sign --output status-unused-expired-subkey.signature multisig.text
// gpg --batch --yes --export "$primary" > status-unused-expired-subkey.gpg
func verifyGpgStatusFixture(c *C, finder GPGFinder, keyring, signature string) error {
verifier := NewGpgVerifier(finder)
verifier.AddKeyring(keyring)
c.Assert(verifier.InitKeyring(false), IsNil)
cleartext, err := os.Open("multisig.text")
c.Assert(err, IsNil)
defer func() { _ = cleartext.Close() }()
signatureFile, err := os.Open(signature)
c.Assert(err, IsNil)
defer func() { _ = signatureFile.Close() }()
return verifier.VerifyDetachedSignature(signatureFile, cleartext, false)
}
func (s *Gnupg2VerifierSuite) TestGpgVerifierRejectsExpiredHeldSignatureAlongsideGood(c *C) {
err := verifyGpgStatusFixture(c, GPG2Finder(), "./status-expired-held.gpg", "status-expired-held.signature")
c.Assert(err, NotNil)
}
func (s *Gnupg2VerifierSuite) TestGpgVerifierRejectsRevokedHeldSignatureAlongsideGood(c *C) {
err := verifyGpgStatusFixture(c, GPG2Finder(), "./status-revoked-held.gpg", "status-revoked-held.signature")
c.Assert(err, NotNil)
}
func (s *Gnupg2VerifierSuite) TestGpgVerifierIgnoresUnusedExpiredSubkey(c *C) {
err := verifyGpgStatusFixture(c, GPG2Finder(), "./status-unused-expired-subkey.gpg", "status-unused-expired-subkey.signature")
c.Assert(err, IsNil)
}
type fixedGPGFinder struct {
gpgv string
}
func (f fixedGPGFinder) FindGPG() (string, GPGVersion, error) {
return f.gpgv, GPG22xPlus, nil
}
func (f fixedGPGFinder) FindGPGV() (string, GPGVersion, error) {
return f.gpgv, GPG22xPlus, nil
}
func (s *GnupgSuite) TestGpgVerifierRejectsUnexplainedFailure(c *C) {
tests := []struct {
name string
body string
wantError bool
goodKeys []Key
missingKeys []Key
}{
{
name: "unexplained exit",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' >&3
exit 70
`,
wantError: true,
goodKeys: []Key{"0123456789ABCDEF"},
},
{
name: "unknown co-signer",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] ERRSIG FEDCBA9876543210 1 10 00 0 9 0000000000000000FEDCBA9876543210' \
'[GNUPG:] NO_PUBKEY FEDCBA9876543210' >&3
exit 2
`,
goodKeys: []Key{"0123456789ABCDEF"},
missingKeys: []Key{"FEDCBA9876543210"},
},
{
name: "non-missing-key ERRSIG",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] ERRSIG FEDCBA9876543210 1 10 00 0 1 0000000000000000FEDCBA9876543210' >&3
exit 2
`,
wantError: true,
goodKeys: []Key{"0123456789ABCDEF"},
},
{
name: "empty signature block",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' \
'[GNUPG:] NEWSIG' >&3
exit 2
`,
wantError: true,
goodKeys: []Key{"0123456789ABCDEF"},
},
{
name: "GOODSIG without VALIDSIG",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] GOODSIG 0123456789ABCDEF held' >&3
exit 2
`,
wantError: true,
goodKeys: []Key{"0123456789ABCDEF"},
},
{
name: "signal death",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] GOODSIG 0123456789ABCDEF held' \
'[GNUPG:] VALIDSIG 00000000000000000123456789ABCDEF 0 0 0 0 0 0 0 00 00000000000000000123456789ABCDEF' >&3
kill -TERM "$$"
`,
wantError: true,
goodKeys: []Key{"0123456789ABCDEF"},
},
{
name: "exit zero preserves upstream behavior",
body: `#!/bin/sh
printf '%s\n' \
'[GNUPG:] NEWSIG' \
'[GNUPG:] BADSIG 0123456789ABCDEF held' >&3
exit 0
`,
},
}
for _, test := range tests {
directory := c.MkDir()
gpgv := filepath.Join(directory, "gpgv")
c.Assert(os.WriteFile(gpgv, []byte(test.body), 0o700), IsNil)
verifier := NewGpgVerifier(fixedGPGFinder{gpgv: gpgv})
keyInfo, err := verifier.runGpgv(nil, "detached signature", false)
if test.wantError {
c.Check(err, NotNil, Commentf(test.name))
} else {
c.Check(err, IsNil, Commentf(test.name))
}
c.Check(keyInfo.GoodKeys, DeepEquals, test.goodKeys, Commentf(test.name))
c.Check(keyInfo.MissingKeys, DeepEquals, test.missingKeys, Commentf(test.name))
}
}
type Gnupg2SignerSuite struct {
+6
View File
@@ -13,8 +13,14 @@ var _ = Suite(&GoVerifierSuite{})
func (s *GoVerifierSuite) SetUpTest(c *C) {
s.verifier = &GoVerifier{}
s.verifier.AddKeyring("./trusted.gpg")
s.multisigOneVerifier = &GoVerifier{}
s.multisigOneVerifier.AddKeyring("./multisig-one.gpg")
s.multisigBothVerifier = &GoVerifier{}
s.multisigBothVerifier.AddKeyring("./multisig-both.gpg")
c.Assert(s.verifier.InitKeyring(false), IsNil)
c.Assert(s.multisigOneVerifier.InitKeyring(false), IsNil)
c.Assert(s.multisigBothVerifier.InitKeyring(false), IsNil)
}
type GoSignerSuite struct {
+1
View File
@@ -0,0 +1 @@
tampered aptly multi-signature verification fixture
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
aptly multi-signature verification fixture
-----BEGIN PGP SIGNATURE-----
iQEzBAEBCAAdFiEEK14dkVdjQa9ExgDhMK0ohu1Yd1wFAmqYKJEACgkQMK0ohu1Y
d1wi/Qf/du6Cfs5Og7jqNG17jUEMNO+ZTYfI60ytrcQ47BgX21zGorRWE6M9L6EJ
YbgYptGRrSHrVIZJXWVVWsdWbTHG4pW5ph2cSeVjOQWGF7JqlqjJOmrbzrQo2FSs
EwQiCloJ7sBRj7bVtf1ovkfFWp00wNO77ZHJK0vBCSf8v43V0BEykL25lvEG5KTG
TO8k+Yh9hDo/0DkPQpp7b5k9IVOgJrnQn9NuW7iD62FeM8WKrhk6EAHDzhDD5jyT
hf3ADA9nEc4i0bPVMwVXMHgW+Wmm6DtJxQYNlZrPzPLG7+1mpZN1OQBG0w/QnLsW
p6o2Nh+xVwO5ko7uSUtTCrt2IGogk4kBMwQBAQgAHRYhBDLwA6IWaB1MyBD6IZi1
MB6x+hMXBQJqmCiRAAoJEJi1MB6x+hMXZzgH/jHmAjbymQcvZ9HsQCIv7aN07sCr
HtjgUPzaOsibn8NRQiXtVQqqDChaQi2brSqeTG2oNN2+buRNQAXwyEmtC7+6SdH3
/lnH/7IpdqMRBBtmiAH5ny9DfgenWNWb8F2hAcoc00RugiDFFyVmlBfBXK5/l+KI
DanIKW97TXXMA8J8KAe/asOXaht8oiW85dmAlSlrHRWZxOBTLR3540nMTD0/Cp5n
i+ngQpqIzSykFQ/4scYVT4rc/Iu64yCV75O5fumQDrqMPqXfD+Lms4bFhQYxja8K
uofxwhwHvz5xEGSDve/krZrD8JG7mMINZjjxIr5t83eKXcxhHd7BVUBj4yI=
=ulCF
-----END PGP SIGNATURE-----
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
aptly multi-signature verification fixture
+3 -1
View File
@@ -63,7 +63,9 @@ func checkDetachedSignature(keyring openpgp.KeyRing, signed, signature io.Reader
for {
p, err = packets.Next()
if err == io.EOF {
if len(signers) == 0 || missingKeys > 0 {
// Revocation and expiry of held keys deliberately remain unchanged
// from upstream in this provider; only unknown co-signers are ignored.
if len(signers) == missingKeys {
err = errors.ErrUnknownIssuer
} else {
err = nil
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+90 -14
View File
@@ -10,9 +10,24 @@ import (
// Common set of tests shared by internal & external GnuPG implementations
type VerifierSuite struct {
verifier Verifier
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
@@ -37,21 +52,82 @@ func (s *VerifierSuite) TestVerifyDetached(c *C) {
}
func (s *VerifierSuite) TestVerifyClearsigned(c *C) {
for _, test := range []struct {
clearSignedName string
}{
{"1.clearsigned"},
} {
clearsigned, err := os.Open(test.clearSignedName)
c.Assert(err, IsNil)
clearsigned, err := os.Open("multisig.clearsigned")
c.Assert(err, IsNil)
defer func() { _ = clearsigned.Close() }()
keyInfo, err := s.verifier.VerifyClearsigned(clearsigned, false)
c.Assert(err, IsNil)
c.Check(keyInfo.GoodKeys, DeepEquals, []Key{"04EE7237B7D453EC", "648ACFD622F3D138", "DCC9EFBF77E11517"})
c.Check(keyInfo.MissingKeys, DeepEquals, []Key(nil))
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))
}
_ = clearsigned.Close()
}
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) {