func (m *accountManager) GrantStaff( ctx scope.Context, accountID snowflake.Snowflake, kmsCred security.KMSCredential) error { m.b.Lock() defer m.b.Unlock() account, ok := m.b.accounts[accountID] if !ok { return proto.ErrAccountNotFound } memAcc := account.(*memAccount) kms := kmsCred.KMS() key := memAcc.sec.SystemKey.Clone() if err := kms.DecryptKey(&key); err != nil { return err } nonce, err := kms.GenerateNonce(key.KeyType.BlockSize()) if err != nil { return err } capability, err := security.GrantSharedSecretCapability(&key, nonce, kmsCred.KMSType(), kmsCred) if err != nil { return err } memAcc.staffCapability = capability return nil }
func (gs *GrantManager) GrantToPasscode( ctx scope.Context, manager Account, managerKey *security.ManagedKey, passcode string) error { _, public, private, err := gs.Authority(ctx, manager, managerKey) if err != nil { return err } c, err := security.GrantSharedSecretCapability( security.KeyFromPasscode([]byte(passcode), gs.SubjectNonce, security.AES128), gs.SubjectNonce, public, private) if err != nil { return err } return gs.Capabilities.Save(ctx, nil, c) }
func (b *AccountManagerBinding) GrantStaff( ctx scope.Context, accountID snowflake.Snowflake, kmsCred security.KMSCredential) error { // Look up the target account's (system) encrypted client key. This is // not part of the transaction, because we want to interact with KMS // before we proceed. That should be fine, since this is an infrequently // used action. var row struct { EncryptedClientKey []byte `db:"encrypted_system_key"` Nonce []byte `db:"nonce"` } err := b.DbMap.SelectOne( &row, "SELECT encrypted_system_key, nonce FROM account WHERE id = $1", accountID.String()) if err != nil { if err == sql.ErrNoRows { return proto.ErrAccountNotFound } return err } // Use kmsCred to obtain kms and decrypt the client's key. kms := kmsCred.KMS() clientKey := &security.ManagedKey{ KeyType: proto.ClientKeyType, Ciphertext: row.EncryptedClientKey, ContextKey: "nonce", ContextValue: base64.URLEncoding.EncodeToString(row.Nonce), } if err := kms.DecryptKey(clientKey); err != nil { return err } // Grant staff capability. This involves marshalling kmsCred to JSON and // encrypting it with the client key. nonce, err := kms.GenerateNonce(clientKey.KeyType.BlockSize()) if err != nil { return err } capability, err := security.GrantSharedSecretCapability(clientKey, nonce, kmsCred.KMSType(), kmsCred) if err != nil { return err } // Store capability and update account table. t, err := b.DbMap.Begin() if err != nil { return err } rollback := func() { if err := t.Rollback(); err != nil { backend.Logger(ctx).Printf("rollback error: %s", err) } } dbCap := &Capability{ ID: capability.CapabilityID(), NonceBytes: capability.Nonce(), EncryptedPrivateData: capability.EncryptedPayload(), PublicData: capability.PublicPayload(), } if err := t.Insert(dbCap); err != nil { rollback() return err } result, err := t.Exec( "UPDATE account SET staff_capability_id = $2 WHERE id = $1", accountID.String(), capability.CapabilityID()) if err != nil { rollback() return err } n, err := result.RowsAffected() if err != nil { rollback() return err } if n != 1 { rollback() return proto.ErrAccountNotFound } if err := t.Commit(); err != nil { return err } return nil }
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") }) }