Return error for public-only GPG signing keyring
`SelectOpenPGPSigningEntity` selects `keyRing[0]` when no key id is supplied and then calls `entity.PrivateKey.Decrypt` directly. For a keyring that contains only public keys — e.g. an armor-exported public key file — `PrivateKey` is `nil` and the call panics with a nil pointer dereference rather than surfacing an actionable error. The keyed branch already guards against this; the default branch did not. Guard the default branch with the same nil check and return an error pointing at `gpg --export-secret-keys` or `--gpg-key-id` so the user knows how to recover. Cover the public-only-keyring case in `TestSelectOpenPGPSigningEntity` so a future regression cannot re-introduce the panic. Assisted-by: claude/opus-4.7 Signed-off-by: Hidde Beydals <hidde@hhh.computer>
This commit is contained in:
@@ -574,6 +574,10 @@ func SelectOpenPGPSigningEntity(keyRing openpgp.EntityList, passphrase, keyID st
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
entity = keyRing[0]
|
entity = keyRing[0]
|
||||||
|
if entity.PrivateKey == nil {
|
||||||
|
return nil, fmt.Errorf("keyring does not contain a private key; " +
|
||||||
|
"export the secret key with 'gpg --export-secret-keys' or specify --gpg-key-id")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := entity.PrivateKey.Decrypt([]byte(passphrase))
|
err := entity.PrivateKey.Decrypt([]byte(passphrase))
|
||||||
|
|||||||
@@ -542,6 +542,35 @@ func TestPlainGitBootstrapper_resolveSigner(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelectOpenPGPSigningEntity(t *testing.T) {
|
||||||
|
t.Run("empty key ring errors", func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
_, err := SelectOpenPGPSigningEntity(openpgp.EntityList{}, "", "")
|
||||||
|
g.Expect(err).To(MatchError(ContainSubstring("empty GPG key ring")))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("public-only key ring without key id errors instead of panicking", func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
entity, err := openpgp.NewEntity("Alice", "test", "alice@example.com", nil)
|
||||||
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
entity.PrivateKey = nil
|
||||||
|
|
||||||
|
_, err = SelectOpenPGPSigningEntity(openpgp.EntityList{entity}, "", "")
|
||||||
|
g.Expect(err).To(MatchError(ContainSubstring("keyring does not contain a private key")))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("public-only key ring with matching key id errors with key id context", func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
entity, err := openpgp.NewEntity("Alice", "test", "alice@example.com", nil)
|
||||||
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
keyID := entity.PrimaryKey.KeyIdString()
|
||||||
|
entity.PrivateKey = nil
|
||||||
|
|
||||||
|
_, err = SelectOpenPGPSigningEntity(openpgp.EntityList{entity}, "", keyID)
|
||||||
|
g.Expect(err).To(MatchError(ContainSubstring("keyring does not contain private key for key id")))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TestPlainGitBootstrapper_sshSignerProducesVerifiableCommit is an
|
// TestPlainGitBootstrapper_sshSignerProducesVerifiableCommit is an
|
||||||
// end-to-end wiring test. resolveSigner already has unit tests for
|
// end-to-end wiring test. resolveSigner already has unit tests for
|
||||||
// dispatch behaviour, but nothing in pkg/bootstrap exercises the full
|
// dispatch behaviour, but nothing in pkg/bootstrap exercises the full
|
||||||
|
|||||||
Reference in New Issue
Block a user