package service import ( "context" "sync" "time" "github.com/keys-pub/keys" "github.com/keys-pub/keys-ext/http/client" "github.com/keys-pub/keys-ext/sdb" "github.com/keys-pub/keys-ext/vault" khttp "github.com/keys-pub/keys/http" "github.com/keys-pub/keys/tsutil" "github.com/keys-pub/keys/users" ) type service struct { UnimplementedRPCServer env *Env build Build auth *auth db *sdb.DB client *client.Client scs *keys.Sigchains users *users.Users clock tsutil.Clock vault *vault.Vault relay *relay unlocked bool unlockMtx sync.Mutex checkMtx sync.Mutex checking bool checkCancelFn func() } const cdbPath = "cache.sdb" const vdbPath = "vault.vdb" func newService(env *Env, build Build, auth *auth, hclient khttp.Client, clock tsutil.Clock) (*service, error) { client, err := client.New(env.Server()) if err != nil { return nil, err } client.SetClock(clock) path, err := env.AppPath(vdbPath, true) if err != nil { return nil, err } vlt := vault.New(vault.NewDB(path), vault.WithClock(clock)) vlt.SetClient(client) db := sdb.New() db.SetClock(clock) scs := keys.NewSigchains(db) usrs := users.New(db, scs, users.Client(hclient), users.Clock(clock)) return &service{ auth: auth, build: build, env: env, scs: scs, db: db, users: usrs, client: client, vault: vlt, relay: newRelay(), clock: clock, checkCancelFn: func() {}, }, nil } func (s *service) Open() error { logger.Infof("Opening vault...") if err := s.vault.Open(); err != nil { return err } // TODO: We can remove this soon (for old keyring conversions)... if err := checkKeyringConvert(s.env, s.vault); err != nil { return err } return nil } func (s *service) Close() { logger.Infof("Closing...") s.lock() if err := s.vault.Close(); err != nil { logger.Errorf("Error closing vault: %v", err) } } // TODO: unlock can be called multiple times, while already unlocked (by // different clients to get an auth token), we could be more explicit // about this. func (s *service) unlock(ctx context.Context, req *AuthUnlockRequest) (string, error) { s.unlockMtx.Lock() defer s.unlockMtx.Unlock() // Unlock auth/vault (get token) token, err := s.auth.unlock(ctx, s.vault, req) if err != nil { return "", err } isNew := false // DB if !s.db.IsOpen() { logger.Infof("Opening %s...", cdbPath) path, err := s.env.AppPath(cdbPath, true) if err != nil { return "", err } exists, err := pathExists(path) if err != nil { return "", err } if !exists { isNew = true } // Derive sdb key // TODO: Check if key is wrong mk := s.vault.MasterKey() dbk := keys.Bytes32(keys.HKDFSHA256(mk[:], 32, nil, []byte("keys.pub/cache"))) if err := s.db.OpenAtPath(ctx, path, dbk); err != nil { return "", err } } // If database is new, we are either in a new state or from a uninstalled // (or migrated) state. In the uninstalled state, we should try to update // local db for any keys we have in our vault. if isNew { if err := s.updateAllKeys(ctx); err != nil { logger.Errorf("Failed to update keys on new database: %+v", err) } } s.startCheck() s.unlocked = true return token, nil } func (s *service) lock() { s.unlockMtx.Lock() defer s.unlockMtx.Unlock() if !s.unlocked { logger.Infof("Service already locked") return } s.stopCheck() s.auth.lock(s.vault) logger.Infof("Closing sdb...") s.db.Close() s.unlocked = false } func (s *service) tryCheck(ctx context.Context) { s.checkMtx.Lock() defer s.checkMtx.Unlock() logger.Debugf("Checking...") if _, err := s.vault.CheckSync(ctx, time.Duration(time.Minute)); err != nil { logger.Warningf("Failed to check sync: %v", err) } if ctx.Err() != nil { return } if err := s.checkKeys(ctx); err != nil { logger.Warningf("Failed to check keys: %v", err) } }