diff --git a/app/app.go b/app/app.go index af87955..a4adc46 100644 --- a/app/app.go +++ b/app/app.go @@ -145,11 +145,14 @@ func (app *application) DeletePeer(onion string, password string) { defer app.appmutex.Unlock() if app.peers[onion].CheckPassword(password) { - app.shutdownPeer(onion) + // soft-shutdown + app.peers[onion].Shutdown() + // delete the underlying storage app.peers[onion].Delete() + // hard shutdown / remove from app + app.shutdownPeer(onion) // Shutdown and Remove the Engine - log.Debugf("Delete peer for %v Done\n", onion) app.appBus.Publish(event.NewEventList(event.PeerDeleted, event.Identity, onion)) return diff --git a/go.mod b/go.mod index 8c40e1b..22f10aa 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module cwtch.im/cwtch go 1.17 require ( - git.openprivacy.ca/cwtch.im/tapir v0.5.5 + git.openprivacy.ca/cwtch.im/tapir v0.6.0 git.openprivacy.ca/openprivacy/connectivity v1.8.6 git.openprivacy.ca/openprivacy/log v1.0.3 github.com/gtank/ristretto255 v0.1.3-0.20210930101514-6bb39798585c diff --git a/go.sum b/go.sum index b4f5627..d5839b5 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -git.openprivacy.ca/cwtch.im/tapir v0.5.5 h1:km6UDrLYH/GCEn2s+S299/TiRHhxKCIAipYr9GbG3Hk= -git.openprivacy.ca/cwtch.im/tapir v0.5.5/go.mod h1:bWWHrDYBtHvxMri59RwIB/w7Eg1aC0BrQ/ycKlnbB5k= +git.openprivacy.ca/cwtch.im/tapir v0.6.0 h1:TtnKjxitkIDMM7Qn0n/u+mOHRLJzuQUYjYRu5n0/QFY= +git.openprivacy.ca/cwtch.im/tapir v0.6.0/go.mod h1:iQIq4y7N+DuP3CxyG66WNEC/d6vzh+wXvvOmelB+KoY= git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c= git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU= git.openprivacy.ca/openprivacy/connectivity v1.8.6 h1:g74PyDGvpMZ3+K0dXy3mlTJh+e0rcwNk0XF8owzkmOA= diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index 14c85c0..8109bd4 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -99,42 +99,54 @@ func (cp *cwtchPeer) Delete() { cp.storage.Delete() } +// CheckPassword returns true if the given password can be used to derive the key that encrypts the underlying +// cwtch storage database. Returns false otherwise. func (cp *cwtchPeer) CheckPassword(password string) bool { + + // this lock is not really needed, but because we directly access cp.storage.ProfileDirectory + // we keep it here. cp.mutex.Lock() defer cp.mutex.Unlock() + + // open *our* database with the given password (set createIfNotExists to false) db, err := openEncryptedDatabase(cp.storage.ProfileDirectory, password, false) if db == nil || err != nil { + // this will only fail in the rare cases that ProfileDirectory has been moved or deleted + // it is actually a critical error, but far beyond the scope of Cwtch to deal with. return false } - db.Close() + // check that the storage object is valid (this will fail if the DB key is incorrect) + cps, err := NewCwtchProfileStorage(db, cp.storage.ProfileDirectory) + if err != nil { + // this will error if any SQL queries fail, which will be the case if the profile is invalid. + return false + } + // we have a valid database, close the storage (but don't purge as we may be using those conversations...) + cps.Close(false) + + // success! return true } func (cp *cwtchPeer) ChangePassword(password string, newpassword string, newpasswordAgain string) error { - cp.mutex.Lock() - defer cp.mutex.Unlock() - db, err := openEncryptedDatabase(cp.storage.ProfileDirectory, password, false) - if db == nil || err != nil { - return errors.New(constants.InvalidPasswordError) - } - cps, err := NewCwtchProfileStorage(db, cp.storage.ProfileDirectory) - if err != nil { - return errors.New(constants.InvalidPasswordError) - } - cps.Close() + if cp.CheckPassword(password) { + cp.mutex.Lock() + defer cp.mutex.Unlock() - salt, err := os.ReadFile(path.Join(cp.storage.ProfileDirectory, saltFile)) - if err != nil { - return err - } + salt, err := os.ReadFile(path.Join(cp.storage.ProfileDirectory, saltFile)) + if err != nil { + return err + } - // probably redundant but we like api safety - if newpassword == newpasswordAgain { - rekey := createKey(newpassword, salt) - log.Infof("rekeying database...") - return cp.storage.Rekey(rekey) + // probably redundant but we like api safety + if newpassword == newpasswordAgain { + rekey := createKey(newpassword, salt) + log.Infof("rekeying database...") + return cp.storage.Rekey(rekey) + } + return errors.New(constants.PasswordsDoNotMatchError) } - return errors.New(constants.PasswordsDoNotMatchError) + return errors.New(constants.InvalidPasswordError) } // GenerateProtocolEngine @@ -1027,7 +1039,7 @@ func (cp *cwtchPeer) Shutdown() { cp.shutdown = true cp.queue.Shutdown() if cp.storage != nil { - cp.storage.Close() + cp.storage.Close(true) } } diff --git a/peer/cwtchprofilestorage.go b/peer/cwtchprofilestorage.go index 3ba6b81..8769b42 100644 --- a/peer/cwtchprofilestorage.go +++ b/peer/cwtchprofilestorage.go @@ -771,12 +771,13 @@ func (cps *CwtchProfileStorage) PurgeNonSavedMessages() { } // Close closes the underlying database and prepared statements -func (cps *CwtchProfileStorage) Close() { +func (cps *CwtchProfileStorage) Close(purgeAllNonSavedMessages bool) { cps.mutex.Lock() defer cps.mutex.Unlock() if cps.db != nil { - - cps.PurgeNonSavedMessages() + if purgeAllNonSavedMessages { + cps.PurgeNonSavedMessages() + } cps.insertProfileKeyValueStmt.Close() cps.selectProfileKeyValueStmt.Close() diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index 1335a73..744750d 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -490,14 +490,14 @@ func (e *engine) peerAuthed(onion string) { func (e *engine) peerConnecting(onion string) { e.eventManager.Publish(event.NewEvent(event.PeerStateChange, map[event.Field]string{ - event.RemotePeer: string(onion), + event.RemotePeer: onion, event.ConnectionState: ConnectionStateName[CONNECTING], })) } func (e *engine) serverConnecting(onion string) { e.eventManager.Publish(event.NewEvent(event.ServerStateChange, map[event.Field]string{ - event.GroupServer: string(onion), + event.GroupServer: onion, event.ConnectionState: ConnectionStateName[CONNECTING], })) } diff --git a/protocol/connections/token_manager.go b/protocol/connections/token_manager.go index 4e0ed7f..12ab0a1 100644 --- a/protocol/connections/token_manager.go +++ b/protocol/connections/token_manager.go @@ -4,18 +4,19 @@ import ( "encoding/json" "errors" "git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass" + "git.openprivacy.ca/openprivacy/log" "sync" ) // TokenManager maintains a list of tokens associated with a single TokenServer type TokenManager struct { lock sync.Mutex - tokens map[string]bool + tokens map[string]*privacypass.Token } func NewTokenManager() *TokenManager { tm := new(TokenManager) - tm.tokens = make(map[string]bool) + tm.tokens = make(map[string]*privacypass.Token) return tm } @@ -23,9 +24,10 @@ func NewTokenManager() *TokenManager { func (tm *TokenManager) NewTokens(tokens []*privacypass.Token) { tm.lock.Lock() defer tm.lock.Unlock() + log.Debugf("acquired %v new tokens", tokens) for _, token := range tokens { serialized, _ := json.Marshal(token) - tm.tokens[string(serialized)] = true + tm.tokens[string(serialized)] = token } } @@ -44,10 +46,8 @@ func (tm *TokenManager) FetchToken() (*privacypass.Token, int, error) { if len(tm.tokens) == 0 { return nil, 0, errors.New("no more tokens") } - for serializedToken := range tm.tokens { + for serializedToken, token := range tm.tokens { delete(tm.tokens, serializedToken) - token := new(privacypass.Token) - json.Unmarshal([]byte(serializedToken), token) return token, len(tm.tokens), nil } return nil, 0, errors.New("no more tokens") diff --git a/protocol/connections/tokenboardclientapp.go b/protocol/connections/tokenboardclientapp.go index 0b300ba..bf37cdf 100644 --- a/protocol/connections/tokenboardclientapp.go +++ b/protocol/connections/tokenboardclientapp.go @@ -66,17 +66,22 @@ func (ta *TokenBoardClient) NewInstance() tapir.Application { // Init initializes the cryptographic TokenBoardApp func (ta *TokenBoardClient) Init(connection tapir.Connection) { + // connection.Hostname is always valid because we are ALWAYS the initiating party + log.Debugf("connecting to server: %v", connection.Hostname()) ta.AuthApp.Init(connection) + log.Debugf("server protocol complete: %v", connection.Hostname()) if connection.HasCapability(applications.AuthCapability) { - ta.connection = connection - ta.tokenBoardHandler.ServerAuthedHandler(ta.connection.Hostname()) log.Debugf("Successfully Initialized Connection to %v", connection.Hostname()) + ta.connection = connection + ta.tokenBoardHandler.ServerAuthedHandler(connection.Hostname()) go ta.Listen() // Optimistically acquire many tokens for this server... go ta.PurchaseTokens() go ta.PurchaseTokens() ta.Replay() } else { + log.Debugf("Error Connecting to %v", connection.Hostname()) + ta.tokenBoardHandler.ServerClosedHandler(connection.Hostname()) connection.Close() } } @@ -117,10 +122,12 @@ func (ta *TokenBoardClient) Listen() { ta.postQueue = ta.postQueue[1:] ta.postLock.Unlock() if !message.PostResult.Success { + log.Debugf("post result message: %v", message.PostResult) // Retry using another token posted, _ := ta.Post(egm.Group, egm.Ciphertext, egm.Signature) // if posting failed... if !posted { + log.Errorf("error posting message") ta.tokenBoardHandler.PostingFailed(egm.Group, egm.Signature) } }