bastion 0.53.2
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user