200 lines
8.1 KiB
Go
200 lines
8.1 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The shared vector. client-go/release_sign_test.go holds the same three
|
|
// values; if the statement format changes on one side only, one of the two
|
|
// tests fails.
|
|
var (
|
|
vectorSeed = strings.Repeat("\x01", ed25519.SeedSize)
|
|
vectorManifest = []byte(`{"version":"1.2.3","binaries":{}}`)
|
|
)
|
|
|
|
func vectorKey() ed25519.PrivateKey { return ed25519.NewKeyFromSeed([]byte(vectorSeed)) }
|
|
|
|
func TestSharedVector(t *testing.T) {
|
|
priv := vectorKey()
|
|
sum := sha256.Sum256(vectorManifest)
|
|
sig := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, Statement("bastion", "1.2.3", hex.EncodeToString(sum[:]))))
|
|
if sig != sharedVectorSig {
|
|
t.Fatalf("the statement format changed: signature is now %s", sig)
|
|
}
|
|
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
|
|
if got, err := VerifyManifest("bastion", vectorManifest, []byte(sig), []string{pub}); err != nil || got != pub {
|
|
t.Fatalf("verify: %v", err)
|
|
}
|
|
}
|
|
|
|
const sharedVectorSig = "tr5h3oXjBDmwvwQiZSriW4T6Y6oBFQDymfofLEGXMdTJSXfoQAKN+ajhCs89FO9vdCEZH3Y8mYzYXPZ7RzZVDw=="
|
|
|
|
// The C-Series Cloud vector. The box's updater (vm/cloud in the C-Series
|
|
// tree) checks the same seed, manifest and signature, so a change to how a
|
|
// cseries-cloud statement is built fails on both sides.
|
|
var cloudVectorManifest = []byte(`{"version":"1.2.3","images":{}}`)
|
|
|
|
const cloudVectorSig = "69+ug9q0ndTtLijHaOlpD8aFiQcO9/TRvmB+4jLNNrDSbMzx1mvDlOXzOgxDvQLTWa8LZmqxHABLF75kxYl4Bw=="
|
|
|
|
func TestCloudKindSignsAndVerifiesOnlyAsItself(t *testing.T) {
|
|
priv := vectorKey()
|
|
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
|
|
sum := sha256.Sum256(cloudVectorManifest)
|
|
if err := checkStatement("cseries-cloud", "1.2.3", hex.EncodeToString(sum[:])); err != nil {
|
|
t.Fatalf("a cseries-cloud statement must be signable: %v", err)
|
|
}
|
|
sig := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, Statement("cseries-cloud", "1.2.3", hex.EncodeToString(sum[:]))))
|
|
if sig != cloudVectorSig {
|
|
t.Fatalf("the cseries-cloud statement changed: signature is now %s", sig)
|
|
}
|
|
if got, err := VerifyManifest("cseries-cloud", cloudVectorManifest, []byte(sig), []string{pub}); err != nil || got != pub {
|
|
t.Fatalf("verify: %v", err)
|
|
}
|
|
for _, other := range []string{"bastion", "bastion-agent"} {
|
|
if _, err := VerifyManifest(other, cloudVectorManifest, []byte(sig), []string{pub}); err == nil {
|
|
t.Fatalf("a cseries-cloud signature must not pass as %s", other)
|
|
}
|
|
}
|
|
bastionSig := []byte(base64.StdEncoding.EncodeToString(ed25519.Sign(priv, Statement("bastion", "1.2.3", hex.EncodeToString(sum[:])))))
|
|
if _, err := VerifyManifest("cseries-cloud", cloudVectorManifest, bastionSig, []string{pub}); err == nil {
|
|
t.Fatal("a bastion signature must not pass as a cseries-cloud signature")
|
|
}
|
|
}
|
|
|
|
// The C-Series App Store catalog. The C-Series Host's public store reader
|
|
// builds the same statement and asserts these exact bytes for it, so a change
|
|
// to the text on either side fails a test.
|
|
var storeVectorCatalog = []byte(`{"version":"1.2.3","apps":[]}`)
|
|
|
|
const storeVectorSig = "+wAepICIvr4vLfhhbbONLJjP2KnoEysh7hMZjZtgRbqrxVcUiHKSWbzP3AoOKemvH7oHjRMkpqQAX/GVlmjWDg=="
|
|
|
|
func TestStoreKindSignsAndVerifiesOnlyAsItself(t *testing.T) {
|
|
want := "privatae-release-signature-v1\ncseries-store\n1.2.3\n" + strings.Repeat("ab", 32) + "\n"
|
|
if got := string(Statement("cseries-store", "1.2.3", strings.Repeat("ab", 32))); got != want {
|
|
t.Fatalf("the cseries-store statement is %q, the C-Series reader builds %q", got, want)
|
|
}
|
|
priv := vectorKey()
|
|
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
|
|
sum := sha256.Sum256(storeVectorCatalog)
|
|
if err := checkStatement("cseries-store", "1.2.3", hex.EncodeToString(sum[:])); err != nil {
|
|
t.Fatalf("a cseries-store statement must be signable: %v", err)
|
|
}
|
|
sig := base64.StdEncoding.EncodeToString(ed25519.Sign(priv, Statement("cseries-store", "1.2.3", hex.EncodeToString(sum[:]))))
|
|
if sig != storeVectorSig {
|
|
t.Fatalf("the cseries-store statement changed: signature is now %s", sig)
|
|
}
|
|
if got, err := VerifyManifest("cseries-store", storeVectorCatalog, []byte(sig), []string{pub}); err != nil || got != pub {
|
|
t.Fatalf("verify: %v", err)
|
|
}
|
|
for _, other := range []string{"bastion", "bastion-agent", "cseries-cloud"} {
|
|
if _, err := VerifyManifest(other, storeVectorCatalog, []byte(sig), []string{pub}); err == nil {
|
|
t.Fatalf("a cseries-store signature must not pass as %s", other)
|
|
}
|
|
otherSig := []byte(base64.StdEncoding.EncodeToString(ed25519.Sign(priv, Statement(other, "1.2.3", hex.EncodeToString(sum[:])))))
|
|
if _, err := VerifyManifest("cseries-store", storeVectorCatalog, otherSig, []string{pub}); err == nil {
|
|
t.Fatalf("a %s signature must not pass as a cseries-store signature", other)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSealAndOpen(t *testing.T) {
|
|
priv := vectorKey()
|
|
kf, err := seal(priv.Seed(), priv.Public().(ed25519.PublicKey), []byte("correct horse battery"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := open(kf, []byte("correct horse battery"))
|
|
if err != nil || !got.Equal(priv) {
|
|
t.Fatalf("the right passphrase must open the key: %v", err)
|
|
}
|
|
if _, err := open(kf, []byte("wrong horse battery")); err == nil {
|
|
t.Fatal("a wrong passphrase must not open the key")
|
|
}
|
|
kf.Pub = base64.StdEncoding.EncodeToString(make([]byte, 32))
|
|
if _, err := open(kf, []byte("correct horse battery")); err == nil {
|
|
t.Fatal("a key file whose public key was swapped must not open")
|
|
}
|
|
}
|
|
|
|
func TestVerifyRefusesTheWrongKindOrAChangedManifest(t *testing.T) {
|
|
priv := vectorKey()
|
|
pub := base64.StdEncoding.EncodeToString(priv.Public().(ed25519.PublicKey))
|
|
sum := sha256.Sum256(vectorManifest)
|
|
sig := []byte(base64.StdEncoding.EncodeToString(ed25519.Sign(priv, Statement("bastion", "1.2.3", hex.EncodeToString(sum[:])))))
|
|
if _, err := VerifyManifest("bastion-agent", vectorManifest, sig, []string{pub}); err == nil {
|
|
t.Fatal("a bastion signature must not pass as an agent signature")
|
|
}
|
|
if _, err := VerifyManifest("bastion", []byte(`{"version":"1.2.4","binaries":{}}`), sig, []string{pub}); err == nil {
|
|
t.Fatal("a changed manifest must not verify")
|
|
}
|
|
}
|
|
|
|
func TestSignRefusesAnythingThatIsNotAStatement(t *testing.T) {
|
|
good := strings.Repeat("a", 64)
|
|
for _, c := range [][3]string{
|
|
{"other", "1.2.3", good},
|
|
{"bastion", "1.2.3\nextra", good},
|
|
{"bastion", "1.2.3", "abc"},
|
|
{"bastion", "1.2.3", strings.Repeat("A", 64)},
|
|
} {
|
|
if err := checkStatement(c[0], c[1], c[2]); err == nil {
|
|
t.Fatalf("%q must be refused", c)
|
|
}
|
|
}
|
|
if err := checkStatement("bastion-agent", "0.1.12", good); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestPinnedKeysReadsWhatTheCompilerSees(t *testing.T) {
|
|
dir := t.TempDir()
|
|
write := func(src string) string {
|
|
p := dir + "/release_sign.go"
|
|
if err := os.WriteFile(p, []byte(src), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p
|
|
}
|
|
k1 := strings.Repeat("A", 43) + "="
|
|
k2 := strings.Repeat("B", 43) + "="
|
|
for name, c := range map[string]struct {
|
|
src string
|
|
want int
|
|
}{
|
|
"empty": {"package main\nvar productionReleaseKeys = []string{}\nfunc x() {\n\t_ = \"" + k1 + "\"\n}\n", 0},
|
|
"one line": {"package main\nvar productionReleaseKeys = []string{\"" + k1 + "\", \"" + k2 + "\"}\n", 2},
|
|
"commented": {"package main\nvar productionReleaseKeys = []string{\n\t// \"" + k2 + "\",\n\t\"" + k1 + "\",\n}\n", 1},
|
|
"var block": {"package main\nvar (\n\tother = 1\n\tproductionReleaseKeys = []string{\n\t\t\"" + k1 + "\",\n\t\t\"" + k2 + "\",\n\t}\n)\n", 2},
|
|
} {
|
|
keys, err := PinnedKeys(write(c.src))
|
|
if err != nil || len(keys) != c.want {
|
|
t.Fatalf("%s: got %v %v, want %d keys", name, keys, err, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOpenRefusesOutOfRangeSettings(t *testing.T) {
|
|
priv := vectorKey()
|
|
kf, err := seal(priv.Seed(), priv.Public().(ed25519.PublicKey), []byte("correct horse battery"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, mutate := range []func(*keyFile){
|
|
func(k *keyFile) { k.Time = 0 },
|
|
func(k *keyFile) { k.Thread = 0 },
|
|
func(k *keyFile) { k.Memory = 1 << 31 },
|
|
} {
|
|
bad := *kf
|
|
mutate(&bad)
|
|
if _, err := open(&bad, []byte("correct horse battery")); err == nil {
|
|
t.Fatal("out-of-range settings must be refused, not run")
|
|
}
|
|
}
|
|
}
|