diff --git a/src/main/java/im/conversations/android/Conversations.java b/src/main/java/im/conversations/android/Conversations.java index 488134ba6..c284a4302 100644 --- a/src/main/java/im/conversations/android/Conversations.java +++ b/src/main/java/im/conversations/android/Conversations.java @@ -2,9 +2,12 @@ package im.conversations.android; import android.app.Application; import im.conversations.android.xmpp.ConnectionPool; +import java.security.SecureRandom; public class Conversations extends Application { + public static final SecureRandom SECURE_RANDOM = new SecureRandom(); + @Override public void onCreate() { super.onCreate(); diff --git a/src/main/java/im/conversations/android/IDs.java b/src/main/java/im/conversations/android/IDs.java new file mode 100644 index 000000000..5e555dac6 --- /dev/null +++ b/src/main/java/im/conversations/android/IDs.java @@ -0,0 +1,68 @@ +package im.conversations.android; + +import com.google.common.base.Preconditions; +import com.google.common.io.BaseEncoding; +import com.google.common.io.ByteSource; +import java.io.IOException; +import java.util.UUID; + +public class IDs { + + private static final long UUID_VERSION_MASK = 4 << 12; + + public static String medium() { + final var random = new byte[9]; + Conversations.SECURE_RANDOM.nextBytes(random); + return BaseEncoding.base64Url().encode(random); + } + + public static String tiny() { + final var random = new byte[3]; + Conversations.SECURE_RANDOM.nextBytes(random); + return BaseEncoding.base64Url().encode(random); + } + + public static String tiny(final byte[] seed) { + return BaseEncoding.base64Url().encode(slice(seed)); + } + + private static byte[] slice(final byte[] input) { + if (input == null || input.length < 3) { + return new byte[3]; + } + try { + return ByteSource.wrap(input).slice(0, 3).read(); + } catch (final IOException e) { + return new byte[3]; + } + } + + public static UUID uuid(final byte[] bytes) { + Preconditions.checkArgument(bytes != null && bytes.length == 32); + + long msb = 0; + long lsb = 0; + + msb |= (bytes[0x0] & 0xffL) << 56; + msb |= (bytes[0x1] & 0xffL) << 48; + msb |= (bytes[0x2] & 0xffL) << 40; + msb |= (bytes[0x3] & 0xffL) << 32; + msb |= (bytes[0x4] & 0xffL) << 24; + msb |= (bytes[0x5] & 0xffL) << 16; + msb |= (bytes[0x6] & 0xffL) << 8; + msb |= (bytes[0x7] & 0xffL); + + lsb |= (bytes[0x8] & 0xffL) << 56; + lsb |= (bytes[0x9] & 0xffL) << 48; + lsb |= (bytes[0xa] & 0xffL) << 40; + lsb |= (bytes[0xb] & 0xffL) << 32; + lsb |= (bytes[0xc] & 0xffL) << 24; + lsb |= (bytes[0xd] & 0xffL) << 16; + lsb |= (bytes[0xe] & 0xffL) << 8; + lsb |= (bytes[0xf] & 0xffL); + + msb = (msb & 0xffffffffffff0fffL) | UUID_VERSION_MASK; // set version + lsb = (lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // set variant + return new UUID(msb, lsb); + } +} diff --git a/src/main/java/im/conversations/android/Uuids.java b/src/main/java/im/conversations/android/Uuids.java deleted file mode 100644 index 4cf3ff29c..000000000 --- a/src/main/java/im/conversations/android/Uuids.java +++ /dev/null @@ -1,38 +0,0 @@ -package im.conversations.android; - -import com.google.common.base.Preconditions; -import java.util.UUID; - -public class Uuids { - - private static final long VERSION_MASK = 4 << 12; - - public static UUID getUuid(final byte[] bytes) { - Preconditions.checkArgument(bytes != null && bytes.length == 32); - - long msb = 0; - long lsb = 0; - - msb |= (bytes[0x0] & 0xffL) << 56; - msb |= (bytes[0x1] & 0xffL) << 48; - msb |= (bytes[0x2] & 0xffL) << 40; - msb |= (bytes[0x3] & 0xffL) << 32; - msb |= (bytes[0x4] & 0xffL) << 24; - msb |= (bytes[0x5] & 0xffL) << 16; - msb |= (bytes[0x6] & 0xffL) << 8; - msb |= (bytes[0x7] & 0xffL); - - lsb |= (bytes[0x8] & 0xffL) << 56; - lsb |= (bytes[0x9] & 0xffL) << 48; - lsb |= (bytes[0xa] & 0xffL) << 40; - lsb |= (bytes[0xb] & 0xffL) << 32; - lsb |= (bytes[0xc] & 0xffL) << 24; - lsb |= (bytes[0xd] & 0xffL) << 16; - lsb |= (bytes[0xe] & 0xffL) << 8; - lsb |= (bytes[0xf] & 0xffL); - - msb = (msb & 0xffffffffffff0fffL) | VERSION_MASK; // set version - lsb = (lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // set variant - return new UUID(msb, lsb); - } -} diff --git a/src/main/java/im/conversations/android/database/model/Account.java b/src/main/java/im/conversations/android/database/model/Account.java index 95e048abf..d36bd11c4 100644 --- a/src/main/java/im/conversations/android/database/model/Account.java +++ b/src/main/java/im/conversations/android/database/model/Account.java @@ -6,7 +6,7 @@ import com.google.common.base.Preconditions; import com.google.common.hash.Hashing; import com.google.common.io.ByteSource; import eu.siacs.conversations.xmpp.Jid; -import im.conversations.android.Uuids; +import im.conversations.android.IDs; import java.io.IOException; import java.util.UUID; @@ -48,7 +48,7 @@ public class Account { public UUID getPublicDeviceId() { try { - return Uuids.getUuid( + return IDs.uuid( ByteSource.wrap(randomSeed).slice(0, 16).hash(Hashing.sha256()).asBytes()); } catch (final IOException e) { return UUID.randomUUID(); diff --git a/src/main/java/im/conversations/android/xmpp/XmppConnection.java b/src/main/java/im/conversations/android/xmpp/XmppConnection.java index ab0e1ee7a..cf48e2210 100644 --- a/src/main/java/im/conversations/android/xmpp/XmppConnection.java +++ b/src/main/java/im/conversations/android/xmpp/XmppConnection.java @@ -16,7 +16,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.common.base.Optional; import com.google.common.base.Strings; -import com.google.common.io.ByteSource; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.XmppDomainVerifier; @@ -27,7 +26,6 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.MemorizingTrustManager; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.NotificationService; -import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.Patterns; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.utils.Resolver; @@ -56,6 +54,7 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.AckPacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.EnablePacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket; import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket; +import im.conversations.android.IDs; import im.conversations.android.database.ConversationsDatabase; import im.conversations.android.database.CredentialStore; import im.conversations.android.database.model.Account; @@ -1713,6 +1712,7 @@ public class XmppConnection implements Runnable { private void sendBindRequest() { clearIqCallbacks(); + // TODO if we never store a 'broken' resource we don’t need to fix it final String recentResource = fixResource( ConversationsDatabase.getInstance(context) @@ -1722,7 +1722,7 @@ public class XmppConnection implements Runnable { if (recentResource != null) { resource = recentResource; } else { - resource = this.createNewResource(account.randomSeed); + resource = this.createNewResource(IDs.tiny(account.randomSeed)); } final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource); @@ -1783,8 +1783,7 @@ public class XmppConnection implements Runnable { if (packet.getType() == IqPacket.TYPE.ERROR && error != null && error.hasChild("conflict")) { - final String alternativeResource = - createNewResource(SECURE_RANDOM.generateSeed(3)); + final String alternativeResource = createNewResource(IDs.tiny()); ConversationsDatabase.getInstance(context) .accountDao() .setResource(account.id, alternativeResource); @@ -2132,7 +2131,7 @@ public class XmppConnection implements Runnable { return; } if (streamError.hasChild("conflict")) { - final String alternativeResource = createNewResource(SECURE_RANDOM.generateSeed(3)); + final String alternativeResource = createNewResource(IDs.tiny()); ConversationsDatabase.getInstance(context) .accountDao() .setResource(account.id, alternativeResource); @@ -2224,31 +2223,8 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(stream, flush); } - private String createNewResource(final byte[] random) { - return String.format( - "%s.%s", - context.getString(R.string.app_name), - Base64.encodeToString( - slice(random), Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE)); - } - - private static byte[] slice(final byte[] input) { - if (input == null || input.length < 3) { - return new byte[3]; - } - try { - return ByteSource.wrap(input).slice(0, 3).read(); - } catch (final IOException e) { - return new byte[3]; - } - } - - private String nextRandomId() { - return nextRandomId(false); - } - - private String nextRandomId(final boolean s) { - return CryptoHelper.random(s ? 3 : 9); + private String createNewResource(final String postfixId) { + return String.format("%s.%s", context.getString(R.string.app_name), postfixId); } public String sendIqPacket(final IqPacket packet, final Consumer callback) { @@ -2258,8 +2234,8 @@ public class XmppConnection implements Runnable { public synchronized String sendUnmodifiedIqPacket( final IqPacket packet, final Consumer callback, boolean force) { - if (packet.getId() == null) { - packet.setAttribute("id", nextRandomId()); + if (Strings.isNullOrEmpty(packet.getId())) { + packet.setAttribute("id", IDs.medium()); } if (callback != null) { synchronized (this.packetCallbacks) {