banger/internal/updater/verify_signature_test.go
Thales Maciel b7c9661c99
updater: embed real cosign public key for v0.1.0 release signing
The placeholder in BangerReleasePublicKey is replaced with the
production cosign public key (P-256 ECDSA). The matching private
key is stored offline by the maintainer; this is the public half
that every banger CLI baked from this commit forward will use to
verify SHA256SUMS signatures.

cosign.pub is also committed at the repo root so external auditors
can re-verify a release without parsing the Go source.

The placeholder-refuses test now swaps the embedded key for a
synthetic placeholder for the duration of the test, since the
default value is no longer a placeholder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:50:52 -03:00

127 lines
3.9 KiB
Go

package updater
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"strings"
"testing"
)
// generateTestKey produces an ECDSA P-256 keypair in PEM form,
// matching the shape `cosign generate-key-pair` emits for the public
// half. The private half stays in-test for signing.
func generateTestKey(t *testing.T) (privKey *ecdsa.PrivateKey, pubPEM string) {
t.Helper()
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("generate key: %v", err)
}
der, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
if err != nil {
t.Fatalf("marshal public key: %v", err)
}
pubPEM = string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))
return priv, pubPEM
}
// signBlob mimics `cosign sign-blob`'s output: base64-encoded ASN.1-DER
// ECDSA signature over SHA256(body).
func signBlob(t *testing.T, priv *ecdsa.PrivateKey, body []byte) string {
t.Helper()
digest := sha256.Sum256(body)
sig, err := ecdsa.SignASN1(rand.Reader, priv, digest[:])
if err != nil {
t.Fatalf("sign: %v", err)
}
return base64.StdEncoding.EncodeToString(sig)
}
func TestVerifyBlobSignaturePlaceholderRefuses(t *testing.T) {
// A build that hasn't replaced the placeholder key must refuse
// every verify call with ErrSignatureRequired so an un-rotated
// build can't silently accept anything. Swap the embedded key
// out for the placeholder shape and assert that.
prev := BangerReleasePublicKey
BangerReleasePublicKey = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLACEHOLDER0000000000000000000
000000000000000000000000000000000000000000000000000000000000PLACE
-----END PUBLIC KEY-----`
defer func() { BangerReleasePublicKey = prev }()
err := VerifyBlobSignature([]byte("body"), []byte("sig"))
if !errors.Is(err, ErrSignatureRequired) {
t.Fatalf("err = %v, want ErrSignatureRequired", err)
}
}
func TestVerifyBlobSignatureHappyPath(t *testing.T) {
priv, pubPEM := generateTestKey(t)
prev := BangerReleasePublicKey
BangerReleasePublicKey = pubPEM
defer func() { BangerReleasePublicKey = prev }()
body := []byte("SHA256SUMS body bytes")
sig := signBlob(t, priv, body)
if err := VerifyBlobSignature(body, []byte(sig)); err != nil {
t.Fatalf("VerifyBlobSignature: %v", err)
}
}
func TestVerifyBlobSignatureRejectsTamperedBody(t *testing.T) {
priv, pubPEM := generateTestKey(t)
prev := BangerReleasePublicKey
BangerReleasePublicKey = pubPEM
defer func() { BangerReleasePublicKey = prev }()
body := []byte("original body")
sig := signBlob(t, priv, body)
tampered := []byte("tampered body")
err := VerifyBlobSignature(tampered, []byte(sig))
if err == nil || !strings.Contains(err.Error(), "does not verify") {
t.Fatalf("err = %v, want signature-mismatch", err)
}
}
func TestVerifyBlobSignatureRejectsWrongKey(t *testing.T) {
// Sign with one key, verify with a different one.
signingPriv, _ := generateTestKey(t)
_, otherPubPEM := generateTestKey(t)
prev := BangerReleasePublicKey
BangerReleasePublicKey = otherPubPEM
defer func() { BangerReleasePublicKey = prev }()
body := []byte("body")
sig := signBlob(t, signingPriv, body)
err := VerifyBlobSignature(body, []byte(sig))
if err == nil || !strings.Contains(err.Error(), "does not verify") {
t.Fatalf("err = %v, want wrong-key rejection", err)
}
}
func TestVerifyBlobSignatureRejectsMalformed(t *testing.T) {
_, pubPEM := generateTestKey(t)
prev := BangerReleasePublicKey
BangerReleasePublicKey = pubPEM
defer func() { BangerReleasePublicKey = prev }()
for _, tc := range []struct {
name string
sig string
}{
{name: "not_base64", sig: "!!!not_b64!!!"},
{name: "empty", sig: ""},
{name: "garbage_bytes", sig: base64.StdEncoding.EncodeToString([]byte{0x01, 0x02, 0x03})},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := VerifyBlobSignature([]byte("body"), []byte(tc.sig))
if err == nil {
t.Fatalf("expected error for %s; got success", tc.name)
}
})
}
}