func (kc *KMSConfig) local() (security.KMS, error) { f, err := os.Open(kc.AES256.KeyFile) if err != nil { return nil, err } defer f.Close() keySize := security.AES256.KeySize() fi, err := f.Stat() if err != nil { return nil, err } if fi.Size() != int64(keySize) { return nil, fmt.Errorf("key must be exactly %d bytes in size", keySize) } masterKey, err := ioutil.ReadAll(f) if err != nil { return nil, err } kms := security.LocalKMS() kms.SetMasterKey(masterKey) return kms, nil }
func TestRoomPresence(t *testing.T) { userA := newSession("A", "A1", "ip1") userA2 := newSession("A", "A2", "ip2") userB := newSession("B", "B1", "ip3") ctx := scope.New() kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) roomp, err := NewRoom(ctx, kms, false, "test", "testver") if err != nil { t.Fatal(err) } room := roomp.(*memRoom) client := &proto.Client{Agent: &proto.Agent{}} client.FromRequest(ctx, &http.Request{}) Convey("First join", t, func() { _, err := room.Join(ctx, userA) So(err, ShouldBeNil) So(room.identities, ShouldResemble, map[proto.UserID]proto.Identity{"A": userA.Identity()}) So(room.live, ShouldResemble, map[proto.UserID][]proto.Session{"A": []proto.Session{userA}}) }) Convey("Second join", t, func() { _, err := room.Join(ctx, userB) So(err, ShouldBeNil) So(room.identities["B"], ShouldResemble, userB.Identity()) So(room.live["B"], ShouldResemble, []proto.Session{userB}) }) Convey("Duplicate join", t, func() { _, err := room.Join(ctx, userA2) So(err, ShouldBeNil) So(room.live["A"], ShouldResemble, []proto.Session{userA, userA2}) }) Convey("Deduplicate part", t, func() { So(room.Part(ctx, userA), ShouldBeNil) So(room.identities["A"], ShouldResemble, userA.Identity()) So(room.live["A"], ShouldResemble, []proto.Session{userA2}) }) Convey("More parts", t, func() { So(room.Part(ctx, userA2), ShouldBeNil) So(room.identities["A"], ShouldBeNil) So(room.live["A"], ShouldBeNil) So(room.Part(ctx, userB), ShouldBeNil) So(room.identities["B"], ShouldBeNil) So(room.live["B"], ShouldBeNil) }) }
func TestNewAccountSecurity(t *testing.T) { kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) unlock := func(sec *AccountSecurity, password string) (*security.ManagedKeyPair, error) { return sec.Unlock(security.KeyFromPasscode([]byte(password), sec.Nonce, sec.UserKey.KeyType)) } Convey("Encryption and decryption of generated keys", t, func() { sec, clientKey, err := NewAccountSecurity(kms, "hunter2") So(err, ShouldBeNil) So(sec.SystemKey.Encrypted(), ShouldBeTrue) So(sec.UserKey.Encrypted(), ShouldBeTrue) So(sec.KeyPair.Encrypted(), ShouldBeTrue) So(len(sec.Nonce), ShouldEqual, sec.KeyPair.NonceSize()) So(clientKey.Encrypted(), ShouldBeFalse) kek := sec.SystemKey.Clone() So(kms.DecryptKey(&kek), ShouldBeNil) skp := sec.KeyPair.Clone() So(skp.Decrypt(&kek), ShouldBeNil) kp, err := unlock(sec, "") So(err, ShouldEqual, ErrAccessDenied) So(kp, ShouldBeNil) kp, err = unlock(sec, "hunter2") So(err, ShouldBeNil) So(kp.PrivateKey, ShouldResemble, skp.PrivateKey) }) Convey("Password resets", t, func() { sec, _, err := NewAccountSecurity(kms, "hunter2") So(err, ShouldBeNil) nsec, err := sec.ResetPassword(kms, "hunter3") So(err, ShouldBeNil) skp, err := unlock(sec, "hunter2") So(err, ShouldBeNil) _, err = unlock(nsec, "hunter2") So(err, ShouldEqual, ErrAccessDenied) kp, err := unlock(nsec, "hunter3") So(err, ShouldBeNil) So(kp.PrivateKey, ShouldResemble, skp.PrivateKey) }) }
func TestRoomBroadcast(t *testing.T) { userA := newSession("A", "A1", "ip1") userB := newSession("B", "B1", "ip2") userC := newSession("C", "C1", "ip3") ctx := scope.New() kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) roomp, err := NewRoom(ctx, kms, false, "test", "testver") if err != nil { t.Fatal(err) } room := roomp.(*memRoom) client := &proto.Client{Agent: &proto.Agent{}} client.FromRequest(ctx, &http.Request{}) Convey("Setup", t, func() { _, err := room.Join(ctx, userA) So(err, ShouldBeNil) _, err = room.Join(ctx, userB) So(err, ShouldBeNil) _, err = room.Join(ctx, userC) So(err, ShouldBeNil) }) Convey("Multiple exclude", t, func() { So(room.broadcast(ctx, proto.SendType, proto.Message{Content: "1"}, userA, userB), ShouldBeNil) So(userA.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "B", IdentityView: proto.IdentityView{ID: "B"}, }, }, { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, }) So(userB.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, }) So(userC.history, ShouldResemble, []message{{cmdType: proto.SendEventType, payload: proto.Message{Content: "1"}}}) }) Convey("No exclude", t, func() { So(room.broadcast(ctx, proto.SendType, proto.Message{Content: "2"}), ShouldBeNil) So(userA.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "B", IdentityView: proto.IdentityView{ID: "B"}, }, }, { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, { cmdType: proto.SendEventType, payload: proto.Message{Content: "2"}, }, }) So(userB.history, ShouldResemble, []message{ { cmdType: proto.JoinEventType, payload: &proto.PresenceEvent{ SessionID: "C", IdentityView: proto.IdentityView{ID: "C"}, }, }, {cmdType: proto.SendEventType, payload: proto.Message{Content: "2"}}, }) So(userC.history, ShouldResemble, []message{ {cmdType: proto.SendEventType, payload: proto.Message{Content: "1"}}, {cmdType: proto.SendEventType, payload: proto.Message{Content: "2"}}, }) }) }
func TestDeleteMessage(t *testing.T) { ctx := scope.New() kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) session := mock.TestSession("test", "T1", "ip1") sendMessage := func(room proto.Room) (proto.Message, error) { msg := proto.Message{ Sender: proto.SessionView{ SessionID: "test", IdentityView: proto.IdentityView{ID: "test"}, }, Content: "test", } if managedRoom, ok := room.(proto.ManagedRoom); ok { key, err := managedRoom.MessageKey(ctx) if err != nil { return proto.Message{}, err } if key != nil { mkey := key.ManagedKey() if err := kms.DecryptKey(&mkey); err != nil { return proto.Message{}, err } if err := proto.EncryptMessage(&msg, key.KeyID(), &mkey); err != nil { return proto.Message{}, err } } } return room.Send(ctx, session, msg) } Convey("Delete message in public room", t, func() { ctrl := &Controller{ backend: &mock.TestBackend{}, kms: kms, } term := &testTerm{} public, err := ctrl.backend.CreateRoom(ctx, kms, false, "public") So(err, ShouldBeNil) sent, err := sendMessage(public) So(err, ShouldBeNil) runCommand(ctx, ctrl, "delete-message", term, []string{"public:" + sent.ID.String()}) deleted, err := public.GetMessage(ctx, sent.ID) So(err, ShouldBeNil) So(time.Time(deleted.Deleted).IsZero(), ShouldBeFalse) }) Convey("Delete message in private room", t, func() { ctrl := &Controller{ backend: &mock.TestBackend{}, kms: kms, } term := &testTerm{} private, err := ctrl.backend.CreateRoom(ctx, kms, true, "private") So(err, ShouldBeNil) runCommand(ctx, ctrl, "lock-room", term, []string{"private"}) sent, err := sendMessage(private) So(err, ShouldBeNil) runCommand(ctx, ctrl, "delete-message", term, []string{"private:" + sent.ID.String()}) deleted, err := private.GetMessage(ctx, sent.ID) So(err, ShouldBeNil) So(time.Time(deleted.Deleted).IsZero(), ShouldBeFalse) }) }
func TestEtcdCluster(t *testing.T) { s, err := clustertest.StartEtcd() if err != nil { t.Fatal(err) } if s == nil { t.Skipf("etcd not in PATH, skipping tests") } defer s.Shutdown() Convey("Observe peer departure", t, func() { a := s.Join("/departure", "a", "0") // no defer a.Part() because we'll do that explicitly b := s.Join("/departure", "b", "0") defer b.Part() So(<-a.Watch(), ShouldResemble, &cluster.PeerJoinedEvent{cluster.PeerDesc{ID: "b", Era: "0"}}) a.Part() So(<-b.Watch(), ShouldResemble, &cluster.PeerLostEvent{cluster.PeerDesc{ID: "a"}}) }) Convey("Observe initial peers upon joining", t, func() { a := s.Join("/initial", "a", "0") defer a.Part() So(a.Peers(), ShouldResemble, []cluster.PeerDesc{ {ID: "a", Era: "0"}, }) b := s.Join("/initial", "b", "0") defer b.Part() So(b.Peers(), ShouldResemble, []cluster.PeerDesc{ {ID: "a", Era: "0"}, {ID: "b", Era: "0"}, }) }) Convey("Updates are seen", t, func() { a := s.Join("/updates", "a", "0") defer a.Part() b := s.Join("/updates", "b", "0") defer b.Part() b.Update(&cluster.PeerDesc{ID: "b", Era: "1"}) b.Update(&cluster.PeerDesc{ID: "b", Era: "2"}) So(<-a.Watch(), ShouldResemble, &cluster.PeerJoinedEvent{cluster.PeerDesc{ID: "b", Era: "0"}}) So(<-a.Watch(), ShouldResemble, &cluster.PeerAliveEvent{cluster.PeerDesc{ID: "b", Era: "1"}}) So(<-a.Watch(), ShouldResemble, &cluster.PeerAliveEvent{cluster.PeerDesc{ID: "b", Era: "2"}}) }) Convey("Secrets are created if necessary", t, func() { kms := security.LocalKMS() a := s.Join("/secrets1", "a", "0") defer a.Part() secret, err := a.GetSecret(kms, "test1", 16) So(err, ShouldBeNil) So(len(secret), ShouldEqual, 16) secretCopy, err := a.GetSecret(kms, "test1", 16) So(err, ShouldBeNil) So(string(secretCopy), ShouldEqual, string(secret)) }) Convey("Race to create secret is conceded gracefully", t, func() { kms := &syncKMS{ KMS: security.LocalKMS(), c: make(chan struct{}), } a := s.Join("/secrets1", "a", "0") defer a.Part() sc := make(chan []byte) errc := make(chan error) go func() { s, err := a.GetSecret(kms, "test2", 16) errc <- err sc <- s }() // Synchronize with secret generation. <-kms.c // Set the secret before releasing the goroutine. secret, err := a.GetSecret(kms.KMS, "test2", 16) So(err, ShouldBeNil) // Release the goroutine and verify it gets the secret that was set. kms.c <- struct{}{} So(<-errc, ShouldBeNil) So(string(<-sc), ShouldEqual, string(secret)) }) }
func TestGrants(t *testing.T) { Convey("Grant a capability on a room", t, func() { kms := security.LocalKMS() kms.SetMasterKey(make([]byte, security.AES256.KeySize())) ctx := scope.New() client := &proto.Client{Agent: &proto.Agent{}} client.FromRequest(ctx, &http.Request{}) backend := &mock.TestBackend{} room, err := backend.CreateRoom(ctx, kms, true, "test") So(err, ShouldBeNil) rkey, err := room.MessageKey(ctx) So(err, ShouldBeNil) mkey := rkey.ManagedKey() So(kms.DecryptKey(&mkey), ShouldBeNil) // Sign in as alice and send an encrypted message with aliceSendTime // as the nonce. aliceSendTime := time.Now() msgNonce := []byte(snowflake.NewFromTime(aliceSendTime).String()) aliceKey := &security.ManagedKey{ KeyType: security.AES256, Plaintext: make([]byte, security.AES256.KeySize()), } grant, err := security.GrantSharedSecretCapability(aliceKey, rkey.Nonce(), nil, mkey.Plaintext) So(err, ShouldBeNil) alice := mock.TestSession("Alice", "A1", "ip1") _, err = room.Join(ctx, alice) So(err, ShouldBeNil) msg := proto.Message{ ID: snowflake.NewFromTime(aliceSendTime), UnixTime: proto.Time(aliceSendTime), Content: "hello", } iv, err := base64.URLEncoding.DecodeString(grant.CapabilityID()) So(err, ShouldBeNil) payload := grant.EncryptedPayload() So(aliceKey.BlockCrypt(iv, aliceKey.Plaintext, payload, false), ShouldBeNil) key := &security.ManagedKey{ KeyType: security.AES128, } So(json.Unmarshal(aliceKey.Unpad(payload), &key.Plaintext), ShouldBeNil) digest, ciphertext, err := security.EncryptGCM( key, msgNonce, []byte(msg.Content), []byte("Alice")) So(err, ShouldBeNil) digestStr := base64.URLEncoding.EncodeToString(digest) cipherStr := base64.URLEncoding.EncodeToString(ciphertext) msg.Content = digestStr + "/" + cipherStr _, err = room.Send(ctx, alice, msg) So(err, ShouldBeNil) // Now sign in as bob and decrypt the message. bobKey := &security.ManagedKey{ KeyType: security.AES256, Plaintext: make([]byte, security.AES256.KeySize()), } //bobKey.Plaintext[0] = 1 grant, err = security.GrantSharedSecretCapability(bobKey, rkey.Nonce(), nil, mkey.Plaintext) So(err, ShouldBeNil) iv, err = base64.URLEncoding.DecodeString(grant.CapabilityID()) So(err, ShouldBeNil) payload = grant.EncryptedPayload() So(bobKey.BlockCrypt(iv, bobKey.Plaintext, payload, false), ShouldBeNil) key = &security.ManagedKey{ KeyType: security.AES128, } So(json.Unmarshal(bobKey.Unpad(payload), &key.Plaintext), ShouldBeNil) bob := mock.TestSession("Bob", "B1", "ip2") _, err = room.Join(ctx, bob) So(err, ShouldBeNil) log, err := room.Latest(ctx, 1, 0) So(err, ShouldBeNil) So(len(log), ShouldEqual, 1) msg = log[0] parts := strings.Split(msg.Content, "/") So(len(parts), ShouldEqual, 2) digest, err = base64.URLEncoding.DecodeString(parts[0]) So(err, ShouldBeNil) ciphertext, err = base64.URLEncoding.DecodeString(parts[1]) So(err, ShouldBeNil) plaintext, err := security.DecryptGCM(key, msgNonce, digest, ciphertext, []byte("Alice")) So(err, ShouldBeNil) So(string(plaintext), ShouldEqual, "hello") }) }