mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-28 13:48:12 +00:00
Handle GV2 addresses.
This commit is contained in:
committed by
Greyson Parrelli
parent
06eadd0c15
commit
e4456bb236
@@ -0,0 +1,101 @@
|
||||
package org.thoughtcrime.securesms.groups.v2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class GroupInviteLinkUrlTest {
|
||||
|
||||
private final GroupMasterKey groupMasterKey;
|
||||
private final GroupLinkPassword password;
|
||||
private final String expectedUrl;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(
|
||||
|
||||
givenGroup().withMasterKey("a501335111fa04e3756c24b6eb87264e2dfb622e8e1d339179765410776c0488")
|
||||
.andPassword("f08b7e22fb938c025e6c158b7d544956")
|
||||
.expectUrl("https://group.signal.org/#CjQKIKUBM1ER-gTjdWwktuuHJk4t-2Iujh0zkXl2VBB3bASIEhDwi34i-5OMAl5sFYt9VElW"),
|
||||
|
||||
givenGroup().withMasterKey("2ca23c04d7cf60fe04039ae76d1912202c2a463d345d9cd48cf27f260dd37f6f")
|
||||
.andPassword("2734457c02ce51da71ad0b62f3c222f7")
|
||||
.expectUrl("https://group.signal.org/#CjQKICyiPATXz2D-BAOa520ZEiAsKkY9NF2c1IzyfyYN039vEhAnNEV8As5R2nGtC2LzwiL3"),
|
||||
|
||||
givenGroup().withMasterKey("00f7e0c2a71ab064cc3ced4c04f08d7b7ef4b84b2c2206f69833be6cfe34df80")
|
||||
.andPassword("9bc324eec437cfda6ae5b8aefbf47ee8")
|
||||
.expectUrl("https://group.signal.org/#CjQKIAD34MKnGrBkzDztTATwjXt-9LhLLCIG9pgzvmz-NN-AEhCbwyTuxDfP2mrluK779H7o")
|
||||
);
|
||||
}
|
||||
|
||||
public GroupInviteLinkUrlTest(GroupMasterKey groupMasterKey, GroupLinkPassword password, String expectedUrl) {
|
||||
this.groupMasterKey = groupMasterKey;
|
||||
this.password = password;
|
||||
this.expectedUrl = expectedUrl;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_extract_group_master_key_from_url() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertEquals(groupMasterKey, GroupInviteLinkUrl.fromUrl(expectedUrl).getGroupMasterKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_extract_group_master_key_from_url_padded() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertEquals(groupMasterKey, GroupInviteLinkUrl.fromUrl(expectedUrl + "=").getGroupMasterKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_extract_password_from_url() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertEquals(password, GroupInviteLinkUrl.fromUrl(expectedUrl).getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_extract_password_from_url_padded() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertEquals(password, GroupInviteLinkUrl.fromUrl(expectedUrl + "=").getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void can_reconstruct_url() {
|
||||
assertEquals(expectedUrl, GroupInviteLinkUrl.createUrl(groupMasterKey, password));
|
||||
}
|
||||
|
||||
private static TestBuilder givenGroup() {
|
||||
return new TestBuilder();
|
||||
}
|
||||
|
||||
private static class TestBuilder {
|
||||
private GroupMasterKey groupMasterKey;
|
||||
private byte[] passwordBytes;
|
||||
|
||||
public TestBuilder withMasterKey(String groupMasterKeyAsHex) {
|
||||
try {
|
||||
groupMasterKey = new GroupMasterKey(Hex.fromStringOrThrow(groupMasterKeyAsHex));
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestBuilder andPassword(String passwordAsHex) {
|
||||
passwordBytes = Hex.fromStringOrThrow(passwordAsHex);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object[] expectUrl(String url) {
|
||||
try {
|
||||
return new Object[]{ groupMasterKey, GroupLinkPassword.fromBytes(passwordBytes), url };
|
||||
} catch (GroupLinkPassword.InvalidLengthException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.thoughtcrime.securesms.groups.v2;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.storageservice.protos.groups.GroupInviteLink;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.thoughtcrime.securesms.util.Base64UrlSafe;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public final class GroupInviteLinkUrl_InvalidGroupLinkException_Test {
|
||||
|
||||
@Test
|
||||
public void empty_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertNull(GroupInviteLinkUrl.fromUrl(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void not_a_url_string() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertNull(GroupInviteLinkUrl.fromUrl("abc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong_host() throws GroupInviteLinkUrl.InvalidGroupLinkException, GroupInviteLinkUrl.UnknownGroupLinkVersionException {
|
||||
assertNull(GroupInviteLinkUrl.fromUrl("https://x.signal.org/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6OHTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void has_path() {
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://group.signal.org/not_expected/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6OHTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY"))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasMessage("No path was expected in url");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missing_ref() {
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://group.signal.org/"))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasMessage("No reference was in the url");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void empty_ref() {
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://group.signal.org/#"))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasMessage("No reference was in the url");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bad_base64() {
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://group.signal.org/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCX;mwVi31USVY"))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasCauseExactlyInstanceOf(IOException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bad_protobuf() {
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl("https://group.signal.org/#CAESNAogpQEzURH6BON1bCS264cmTi37Yi6HTOReXZUEHdsBIgSEPCLfiL7k4wCXmwVi31USVY"))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasCauseExactlyInstanceOf(InvalidProtocolBufferException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void version_999_url() {
|
||||
String url = "https://group.signal.org/#uj4zCiDMSxlNUvF4bQ3z3fYzGyZTFbJ1xEqWbPE3uZSD8bjOrxIP8NxV-0GUz3jpxMLR1rN3";
|
||||
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url))
|
||||
.isInstanceOf(GroupInviteLinkUrl.UnknownGroupLinkVersionException.class)
|
||||
.hasMessage("Url contains no known group link content");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bad_master_key_length() {
|
||||
byte[] masterKeyBytes = Util.getSecretBytes(33);
|
||||
GroupLinkPassword password = GroupLinkPassword.createNew();
|
||||
|
||||
String encoding = createEncodedProtobuf(masterKeyBytes, password.serialize());
|
||||
|
||||
String url = "https://group.signal.org/#" + encoding;
|
||||
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasCauseExactlyInstanceOf(InvalidInputException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bad_password_length() throws InvalidInputException {
|
||||
GroupMasterKey groupMasterKey = new GroupMasterKey(Util.getSecretBytes(32));
|
||||
byte[] passwordBytes = Util.getSecretBytes(15);
|
||||
|
||||
String encoding = createEncodedProtobuf(groupMasterKey.serialize(), passwordBytes);
|
||||
|
||||
String url = "https://group.signal.org/#" + encoding;
|
||||
|
||||
assertThatThrownBy(() -> GroupInviteLinkUrl.fromUrl(url))
|
||||
.isInstanceOf(GroupInviteLinkUrl.InvalidGroupLinkException.class)
|
||||
.hasCauseExactlyInstanceOf(GroupLinkPassword.InvalidLengthException.class);
|
||||
}
|
||||
|
||||
private static String createEncodedProtobuf(@NonNull byte[] groupMasterKey,
|
||||
@NonNull byte[] passwordBytes)
|
||||
{
|
||||
return Base64UrlSafe.encodeBytesWithoutPadding(GroupInviteLink.newBuilder()
|
||||
.setV1Contents(GroupInviteLink.GroupInviteLinkContentsV1.newBuilder()
|
||||
.setGroupMasterKey(ByteString.copyFrom(groupMasterKey))
|
||||
.setInviteLinkPassword(ByteString.copyFrom(passwordBytes)))
|
||||
.build()
|
||||
.toByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class Base64UrlSafeTest {
|
||||
|
||||
private final byte[] data;
|
||||
private final String encoded;
|
||||
private final String encodedWithoutPadding;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
{ "", "", "" },
|
||||
{ "01", "AQ==", "AQ" },
|
||||
{ "0102", "AQI=", "AQI" },
|
||||
{ "010203", "AQID", "AQID" },
|
||||
{ "030405", "AwQF", "AwQF" },
|
||||
{ "03040506", "AwQFBg==", "AwQFBg" },
|
||||
{ "0304050708", "AwQFBwg=", "AwQFBwg" },
|
||||
{ "af4d6cff", "r01s_w==", "r01s_w" },
|
||||
{ "ffefde", "_-_e", "_-_e" },
|
||||
});
|
||||
}
|
||||
|
||||
public Base64UrlSafeTest(String hexData, String encoded, String encodedWithoutPadding) throws IOException {
|
||||
this.data = Hex.fromStringCondensed(hexData);
|
||||
this.encoded = encoded;
|
||||
this.encodedWithoutPadding = encodedWithoutPadding;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodes_as_expected() {
|
||||
assertEquals(encoded, Base64UrlSafe.encodeBytes(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodes_as_expected_without_padding() {
|
||||
assertEquals(encodedWithoutPadding, Base64UrlSafe.encodeBytesWithoutPadding(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodes_as_expected() throws IOException {
|
||||
assertArrayEquals(data, Base64UrlSafe.decode(encoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodes_padding_agnostic_as_expected() throws IOException {
|
||||
assertArrayEquals(data, Base64UrlSafe.decodePaddingAgnostic(encoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodes_as_expected_without_padding() throws IOException {
|
||||
assertArrayEquals(data, Base64UrlSafe.decodePaddingAgnostic(encodedWithoutPadding));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user