Smack 4.4.0: Regenerate OMEMO Identity Implementation

aTalk is trying to implement user option to “Regenerate OMEMO Identity”, after few days of trial and error, finally it settles down on the following code implementation as attached below.
Appreciate if someone would review and advice if the code needs improvement.

The test observations using the code are as follow:
1, The new omemo identity has successfully published to the ejabberd server; and the user/buddy are able to perform omemo message chat using the newly created omemoDevice.
2, However checking on the ejabberd server database, both the publish_item & publish_node tables still contain all the old omemo devices info.

In going through the source, I see that OmemoManager#purgeDeviceList() publishes only the newly create omemoDevice, but no attempt is made to purge the old omemDevices info on server that were previously published.

In this the correct/intended implementation for omemo? Should not the old omemo device info be purged from the server, leaving only the newly created omemDevice info.

  1. Who is responsible to clean up the obsoleted omemo info on server.

  2. Is there a public method in omemoService that can be called to remove the old omemo devices info on the server.

    /**
     * Regenerate new omemo identity for the device
     * 1. Wipeout account Omemo info in database
     * 2. generate fresh user identityKeyPairs, bundle
     * 3. publish it to the server.
     */
    public void regenerate(AccountID accountId)
    {
        ProtocolProviderService pps = accountId.getProtocolProvider();
        if (pps != null) {
            XMPPTCPConnection connection = pps.getConnection();
            if ((connection != null) && connection.isAuthenticated()) {
                // Purge all omemo devices info in database for the specific account
                mDB.purgeOmemoDb(accountId.getAccountJid());
                trustCache.evictAll();

                // Generate new omemoDevice
                BareJid userJid = accountId.getBareJid();
                int defaultDeviceId = OmemoManager.randomDeviceId();
                setDefaultDeviceId(userJid, defaultDeviceId);

                mOmemoManager = OmemoManager.getInstanceFor(connection, defaultDeviceId);
                try {
                    mOmemoManager.setTrustCallback(aTalkTrustCallback);
                    // Publish a new device list with just our own deviceId in it.
                    mOmemoManager.purgeDeviceList();
                } catch (Exception e) {
                    Timber.w("Ignoring setTrustCallBack Exception: %s", e.getMessage());
                }
                // Init and publish to server
                mOmemoManager.initializeAsync(this);
            }
        }
    }

    @Override
    public void initializationFinished(OmemoManager manager)
    {
        Timber.i("Initialize OmemoManager successful for %s", manager.getOwnDevice());
    }

    @Override
    public void initializationFailed(Exception cause)
    {
        String title = aTalkApp.getResString(R.string.omemo_init_failed_title);
        String errMsg = cause.getMessage();
        if (errMsg != null) {
            if (errMsg.contains("CorruptedOmemoKeyException")) {
                String msg = aTalkApp.getResString(R.string.omemo_init_failed_CorruptedOmemoKeyException,
                        mOmemoManager.getOwnDevice(), cause.getMessage());
                DialogActivity.showDialog(aTalkApp.getGlobalContext(), title, msg);
            }
            else {
                aTalkApp.showToastMessage(R.string.omemo_init_failed_noresponse, mOmemoManager.getOwnDevice());
            }
        }
    }

    public void purgeOmemoDb(String account)
    {
        Timber.d(">>> Wiping OMEMO database for account : %s", account);
        SQLiteDatabase db = this.getWritableDatabase();
        String[] deleteArgs = {account};

        db.delete(SQLiteOmemoStore.OMEMO_DEVICES_TABLE_NAME, SQLiteOmemoStore.OMEMO_JID + "=?", deleteArgs);
        db.delete(SQLiteOmemoStore.PREKEY_TABLE_NAME, SQLiteOmemoStore.BARE_JID + "=?", deleteArgs);
        db.delete(SQLiteOmemoStore.SIGNED_PREKEY_TABLE_NAME, SQLiteOmemoStore.BARE_JID + "=?", deleteArgs);
        db.delete(SQLiteOmemoStore.IDENTITIES_TABLE_NAME, SQLiteOmemoStore.BARE_JID + "=?", deleteArgs);
        db.delete(SQLiteOmemoStore.SESSION_TABLE_NAME, SQLiteOmemoStore.BARE_JID + "=?", deleteArgs);
    }

It is true, smack-omemo does not delete the old bundle nodes. This is because the specification does not say anything about this.

You can however manually delete the bundle nodes of devices by doing

pubsubManager.deleteNode(omemoDevice.getBundleNodeName());

Unfortunately the OMEMO XEP does not specify, who is responsible to clean up obsoleted omemo info.

Thanks. I think it should be the responsible of the omemo device owner to upkeep the server memo data whenever the device no longer requires it. What I observed is that the OMEMO service spends time to process the old data whenever it is sending/receiving OMEMO messages etc. Overtime with the accumulation of obsoleted OMEMO data, it not only slows down the server process, it too takes up resources on the device.

Under the following conditions i.e.
a. Removal of user account
b. Regenerate OMEMO identities
c. Purge unused identities

aTalk proceeds to clean up OMEMO local database, as well as data on server using your proposed
pubsubManager.deleteNode() method.

I would suppose the deleteNode() method will also takes care the removal of the prekeys data. I see ejabberd stores the prekeys on a different table pubsub_nodeitem; linked by the nodeid in table pubsub_node where the bundle is stored.

However the server returns error “Node not found” even the node bundle exists on the server DB; and the bundle data is left untouched on server. I see there is similar problem being reported but has been fixed.

Look like I may need to go back to ejabberd for assistance.
PubSub: Delete an Item from a Node published returns error - Node not found even though it exists #2974

2019-08-05 11:13:22.176 24105-28413/org.atalk.android D/SMACK: SENT (2): 
    <iq to='pubsub.atalk.org' id='MWWUB-471' type='set'>
      <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
        <delete node='eu.siacs.conversations.axolotl.bundles:1632652505'/>
      </pubsub>
    </iq>
2019-08-05 11:13:22.257 24105-28414/org.atalk.android D/SMACK: RECV (2): 
    <iq xml:lang='en' to='swan@atalk.org/atalk' from='pubsub.atalk.org' type='error' id='MWWUB-471'>
      <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
        <delete node='eu.siacs.conversations.axolotl.bundles:1632652505'/>
      </pubsub>
      <error code='404' type='cancel'>
        <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
        <text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>
          Node not found
        </text>
      </error>
    </iq>

The general consensus within the OMEMO developer community is, that bundle nodes are not cleaned up in the current OMEMO version at all. Yes, you could delete the bundle node of your own device (note: not other devices of the user, only THE currently used device), but removing other devices bundles may break implementations that only check, if their device id is on the currently published devicelist, but not if the node still exists.

Proper handling of abandoned nodes will maybe go into the next version of the OMEMO XEP though.

Great to know OMEMO developer community is taking action to address the issue.

Yes, aTalk currently only purge bundles for the omemo devices on the user device (e.g. active/inactive on regenerate, account deletion etc).

But no sure how to take care of the following situation. Most of today user has multiple pc/mobile devices. When user upgrades his mobile devices / pc, what happen to those OMEMO bundles that have been inactive after that. Will these inactive devices be included in the newly upgraded device, and get auto purge after some elapsed time?

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.