package secrets import ( "crypto/ecdh" "crypto/rand" "encoding/base64" "encoding/json" "strings" "testing" "golang.org/x/crypto/nacl/box" ) // A node's key, as the node would generate it and report the public half. func nodeKey(t *testing.T) (public string, open func(string) ([]byte, error)) { t.Helper() private, err := ecdh.X25519().GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } var pub, priv [32]byte copy(pub[:], private.PublicKey().Bytes()) copy(priv[:], private.Bytes()) return base64.StdEncoding.EncodeToString(private.PublicKey().Bytes()), func(sealed string) ([]byte, error) { blob, err := base64.StdEncoding.DecodeString(sealed) if err != nil { return nil, err } out, ok := box.OpenAnonymous(nil, blob, &pub, &priv) if !ok { return nil, errNotForYou } return out, nil } } var errNotForYou = ¬ForYou{} type notForYou struct{} func (*notForYou) Error() string { return "not sealed to this node" } func TestBothEndsGetTheSameSecretAndTheMeshGetsNeither(t *testing.T) { // The whole arrangement in one test. The provider must create the credential the consumer was // given, or the mesh reports success and nothing can connect — and neither blob is readable // by whoever is holding them, which is the part encrypting a column does not achieve. consumerPub, openConsumer := nodeKey(t) providerPub, openProvider := nodeKey(t) sealed, err := Make(consumerPub, providerPub) if err != nil { t.Fatal(err) } forConsumer, err := openConsumer(sealed.ForConsumer) if err != nil { t.Fatal(err) } forProvider, err := openProvider(sealed.ForProvider) if err != nil { t.Fatal(err) } if string(forConsumer) != string(forProvider) { t.Fatalf("the two ends were given different passwords: %q and %q", forConsumer, forProvider) } if len(forConsumer) < 32 { t.Fatalf("the password is %d characters, which is not a password", len(forConsumer)) } // Neither can open the other's, which is what makes two blobs different from one shared key. if _, err := openConsumer(sealed.ForProvider); err == nil { t.Fatal("the consumer opened the provider's copy") } } func TestTheSealedFormLooksNothingLikeTheSecret(t *testing.T) { consumerPub, openConsumer := nodeKey(t) providerPub, _ := nodeKey(t) sealed, err := Make(consumerPub, providerPub) if err != nil { t.Fatal(err) } password, err := openConsumer(sealed.ForConsumer) if err != nil { t.Fatal(err) } if strings.Contains(sealed.ForConsumer, string(password)) { t.Fatal("the secret is visible inside what is stored") } if sealed.ForConsumer == sealed.ForProvider { // Sealed boxes are randomised, so an observer cannot tell the two ends hold the same // value — nor that a rotation changed nothing. t.Fatal("the two blobs are identical, so the storage says they hold the same value") } } func TestAPasswordIsSafeToPutInAFile(t *testing.T) { // It lands in a file something else reads. A newline or a quote in it is a support call. consumerPub, openConsumer := nodeKey(t) providerPub, _ := nodeKey(t) for i := 0; i < 50; i++ { sealed, err := Make(consumerPub, providerPub) if err != nil { t.Fatal(err) } password, err := openConsumer(sealed.ForConsumer) if err != nil { t.Fatal(err) } if strings.ContainsAny(string(password), "\n\r\t \"'\\$`") { t.Fatalf("a password needs quoting: %q", password) } } } func TestEverySecretIsDifferent(t *testing.T) { consumerPub, openConsumer := nodeKey(t) providerPub, _ := nodeKey(t) seen := map[string]bool{} for i := 0; i < 50; i++ { sealed, _ := Make(consumerPub, providerPub) password, _ := openConsumer(sealed.ForConsumer) if seen[string(password)] { t.Fatalf("the same password came out twice: %q", password) } seen[string(password)] = true } } func TestAnEndWithNoSealingKeyIsRefused(t *testing.T) { // Sealing to nothing would produce a blob nobody can open, stored as though it were a working // credential — which is the failure this whole design exists to make impossible. // // Asserted on the message, not merely on failing. Seal refuses an empty key anyway, so a test // that only checked for an error passed with this check removed and proved nothing about it. // What the check adds is a reason a person can act on: the remedy is on the node, not here. public, _ := nodeKey(t) for _, pair := range [][2]string{{public, ""}, {"", public}} { _, err := Make(pair[0], pair[1]) if err == nil { t.Fatal("a secret was made for a node with no sealing key") } if !strings.Contains(err.Error(), "both ends need a sealing key") { t.Fatalf("the refusal does not say what is missing: %v", err) } } } func TestSomethingThatIsNotAKeyIsRefused(t *testing.T) { if _, err := Seal("not-a-key", []byte("x")); err == nil { t.Fatal("a secret was sealed to nonsense") } short := base64.StdEncoding.EncodeToString([]byte("too short")) if _, err := Seal(short, []byte("x")); err == nil { t.Fatal("a secret was sealed to a key of the wrong length") } } func TestASecretSomebodySuppliedIsKeptTheSameWay(t *testing.T) { // An API key comes from a person; the mesh's job is to carry it without being able to read it // afterwards. Same storage, same property, different origin. consumerPub, openConsumer := nodeKey(t) providerPub, openProvider := nodeKey(t) sealed, err := Accept("sk-a-real-looking-key", consumerPub, providerPub) if err != nil { t.Fatal(err) } got, err := openConsumer(sealed.ForConsumer) if err != nil { t.Fatal(err) } if string(got) != "sk-a-real-looking-key" { t.Fatalf("the value did not survive: %q", got) } if _, err := openProvider(sealed.ForProvider); err != nil { t.Fatal("the other end cannot open its copy") } // And what is stored is not the value, which is the whole point. for what, blob := range map[string]string{ "the consumer's copy": sealed.ForConsumer, "the provider's copy": sealed.ForProvider, } { if strings.Contains(blob, "sk-a-real-looking-key") { t.Fatalf("%s holds the key in the clear", what) } } } func TestSealingNothingIsRefused(t *testing.T) { // An empty key sealed and stored would be a credential that exists, authenticates nowhere, // and looks exactly like a working one. public, _ := nodeKey(t) for _, value := range []string{"", " ", "\n"} { if _, err := Accept(value, public, public); err == nil { t.Fatalf("%q was accepted as a secret", value) } } } func TestAnAcceptedSecretCannotBeReadBack(t *testing.T) { // Stated as a test because it is a property somebody will ask to break. There is no field // holding the value and no function returning it — a mesh that can reveal a secret is a mesh // that holds it. public, _ := nodeKey(t) sealed, err := Accept("sk-something", public, public) if err != nil { t.Fatal(err) } said, err := json.Marshal(sealed) if err != nil { t.Fatal(err) } if strings.Contains(string(said), "sk-something") { t.Fatalf("what is kept carries the secret: %s", said) } }