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>
127 lines
3.9 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|