That fuck shit the fascists are using
1package org.tm.archive.groups;
2
3import androidx.annotation.NonNull;
4import androidx.annotation.Nullable;
5
6import org.signal.core.util.DatabaseId;
7import org.signal.core.util.Hex;
8import org.signal.libsignal.protocol.kdf.HKDFv3;
9import org.signal.libsignal.zkgroup.InvalidInputException;
10import org.signal.libsignal.zkgroup.groups.GroupIdentifier;
11import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
12import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
13import org.tm.archive.util.LRUCache;
14import org.tm.archive.util.Util;
15
16import java.io.IOException;
17import java.security.SecureRandom;
18
19import okio.ByteString;
20
21public abstract class GroupId implements DatabaseId {
22
23 private static final String ENCODED_SIGNAL_GROUP_V1_PREFIX = "__textsecure_group__!";
24 private static final String ENCODED_SIGNAL_GROUP_V2_PREFIX = "__signal_group__v2__!";
25 private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!";
26 private static final int MMS_BYTE_LENGTH = 16;
27 private static final int V1_MMS_BYTE_LENGTH = 16;
28 private static final int V1_BYTE_LENGTH = 16;
29 private static final int V2_BYTE_LENGTH = GroupIdentifier.SIZE;
30
31 private final String encodedId;
32
33 private static final LRUCache<GroupMasterKey, GroupIdentifier> groupIdentifierCache = new LRUCache<>(1000);
34
35 private GroupId(@NonNull String prefix, @NonNull byte[] bytes) {
36 this.encodedId = prefix + Hex.toStringCondensed(bytes);
37 }
38
39 public static @NonNull GroupId.Mms mms(byte[] mmsGroupIdBytes) {
40 return new GroupId.Mms(mmsGroupIdBytes);
41 }
42
43 public static @NonNull GroupId.V1 v1orThrow(byte[] gv1GroupIdBytes) {
44 try {
45 return v1(gv1GroupIdBytes);
46 } catch (BadGroupIdException e) {
47 throw new AssertionError(e);
48 }
49 }
50
51 public static @NonNull GroupId.V1 v1(byte[] gv1GroupIdBytes) throws BadGroupIdException {
52 if (gv1GroupIdBytes.length != V1_BYTE_LENGTH) {
53 throw new BadGroupIdException();
54 }
55 return new GroupId.V1(gv1GroupIdBytes);
56 }
57
58 public static GroupId.V1 createV1(@NonNull SecureRandom secureRandom) {
59 return v1orThrow(Util.getSecretBytes(secureRandom, V1_MMS_BYTE_LENGTH));
60 }
61
62 public static GroupId.Mms createMms(@NonNull SecureRandom secureRandom) {
63 return mms(Util.getSecretBytes(secureRandom, MMS_BYTE_LENGTH));
64 }
65
66 /**
67 * Private because it's too easy to pass the {@link GroupMasterKey} bytes directly to this as they
68 * are the same length as the {@link GroupIdentifier}.
69 */
70 private static GroupId.V2 v2(@NonNull byte[] bytes) throws BadGroupIdException {
71 if (bytes.length != V2_BYTE_LENGTH) {
72 throw new BadGroupIdException();
73 }
74 return new GroupId.V2(bytes);
75 }
76
77 public static GroupId.V2 v2(@NonNull GroupIdentifier groupIdentifier) {
78 try {
79 return v2(groupIdentifier.serialize());
80 } catch (BadGroupIdException e) {
81 throw new AssertionError(e);
82 }
83 }
84
85 public static GroupId.V2 v2(@NonNull GroupMasterKey masterKey) {
86 return v2(getIdentifierForMasterKey(masterKey));
87 }
88
89 public static GroupIdentifier getIdentifierForMasterKey(@NonNull GroupMasterKey masterKey) {
90 GroupIdentifier cachedIdentifier;
91 synchronized (groupIdentifierCache) {
92 cachedIdentifier = groupIdentifierCache.get(masterKey);
93 }
94 if (cachedIdentifier == null) {
95 cachedIdentifier = GroupSecretParams.deriveFromMasterKey(masterKey)
96 .getPublicParams()
97 .getGroupIdentifier();
98 synchronized (groupIdentifierCache) {
99 groupIdentifierCache.put(masterKey, cachedIdentifier);
100 }
101 }
102 return cachedIdentifier;
103 }
104
105 public static GroupId.Push push(ByteString bytes) throws BadGroupIdException {
106 return push(bytes.toByteArray());
107 }
108
109 public static GroupId.Push push(byte[] bytes) throws BadGroupIdException {
110 return bytes.length == V2_BYTE_LENGTH ? v2(bytes) : v1(bytes);
111 }
112
113 public static GroupId.Push pushOrThrow(byte[] bytes) {
114 try {
115 return push(bytes);
116 } catch (BadGroupIdException e) {
117 throw new AssertionError(e);
118 }
119 }
120
121 public static GroupId.Push pushOrNull(byte[] bytes) {
122 try {
123 return GroupId.push(bytes);
124 } catch (BadGroupIdException e) {
125 return null;
126 }
127 }
128
129 public static @NonNull GroupId parseOrThrow(@NonNull String encodedGroupId) {
130 try {
131 return parse(encodedGroupId);
132 } catch (BadGroupIdException e) {
133 throw new AssertionError(e);
134 }
135 }
136
137 public static @NonNull GroupId parse(@NonNull String encodedGroupId) throws BadGroupIdException {
138 try {
139 if (!isEncodedGroup(encodedGroupId)) {
140 throw new BadGroupIdException("Invalid encoding");
141 }
142
143 byte[] bytes = extractDecodedId(encodedGroupId);
144
145 if (encodedGroupId.startsWith(ENCODED_SIGNAL_GROUP_V2_PREFIX)) return v2(bytes);
146 else if (encodedGroupId.startsWith(ENCODED_SIGNAL_GROUP_V1_PREFIX)) return v1(bytes);
147 else if (encodedGroupId.startsWith(ENCODED_MMS_GROUP_PREFIX)) return mms(bytes);
148
149 throw new BadGroupIdException();
150 } catch (IOException e) {
151 throw new BadGroupIdException(e);
152 }
153 }
154
155 public static @Nullable GroupId parseNullable(@Nullable String encodedGroupId) throws BadGroupIdException {
156 if (encodedGroupId == null) {
157 return null;
158 }
159
160 return parse(encodedGroupId);
161 }
162
163 public static @Nullable GroupId parseNullableOrThrow(@Nullable String encodedGroupId) {
164 if (encodedGroupId == null) {
165 return null;
166 }
167
168 return parseOrThrow(encodedGroupId);
169 }
170
171 public static boolean isEncodedGroup(@NonNull String groupId) {
172 return groupId.startsWith(ENCODED_SIGNAL_GROUP_V2_PREFIX) ||
173 groupId.startsWith(ENCODED_SIGNAL_GROUP_V1_PREFIX) ||
174 groupId.startsWith(ENCODED_MMS_GROUP_PREFIX);
175 }
176
177 private static byte[] extractDecodedId(@NonNull String encodedGroupId) throws IOException {
178 return Hex.fromStringCondensed(encodedGroupId.split("!", 2)[1]);
179 }
180
181 public byte[] getDecodedId() {
182 try {
183 return extractDecodedId(encodedId);
184 } catch (IOException e) {
185 throw new AssertionError(e);
186 }
187 }
188
189 @Override
190 public boolean equals(@Nullable Object obj) {
191 if (obj instanceof GroupId) {
192 return ((GroupId) obj).encodedId.equals(encodedId);
193 }
194
195 return false;
196 }
197
198 @Override
199 public int hashCode() {
200 return encodedId.hashCode();
201 }
202
203 @Override
204 public @NonNull String toString() {
205 return encodedId;
206 }
207
208 @Override
209 public @NonNull String serialize() {
210 return encodedId;
211 }
212
213 public abstract boolean isMms();
214
215 public abstract boolean isV1();
216
217 public abstract boolean isV2();
218
219 public abstract boolean isPush();
220
221 public GroupId.Mms requireMms() {
222 if (this instanceof GroupId.Mms) return (GroupId.Mms) this;
223 throw new AssertionError();
224 }
225
226 public GroupId.V1 requireV1() {
227 if (this instanceof GroupId.V1) return (GroupId.V1) this;
228 throw new AssertionError();
229 }
230
231 public GroupId.V2 requireV2() {
232 if (this instanceof GroupId.V2) return (GroupId.V2) this;
233 throw new AssertionError();
234 }
235
236 public GroupId.Push requirePush() {
237 if (this instanceof GroupId.Push) return (GroupId.Push) this;
238 throw new AssertionError();
239 }
240
241 public static final class Mms extends GroupId {
242
243 private Mms(@NonNull byte[] bytes) {
244 super(ENCODED_MMS_GROUP_PREFIX, bytes);
245 }
246
247 @Override
248 public boolean isMms() {
249 return true;
250 }
251
252 @Override
253 public boolean isV1() {
254 return false;
255 }
256
257 @Override
258 public boolean isV2() {
259 return false;
260 }
261
262 @Override
263 public boolean isPush() {
264 return false;
265 }
266 }
267
268 public static abstract class Push extends GroupId {
269 private Push(@NonNull String prefix, @NonNull byte[] bytes) {
270 super(prefix, bytes);
271 }
272
273 @Override
274 public boolean isMms() {
275 return false;
276 }
277
278 @Override
279 public boolean isPush() {
280 return true;
281 }
282 }
283
284 public static final class V1 extends GroupId.Push {
285
286 private V1(@NonNull byte[] bytes) {
287 super(ENCODED_SIGNAL_GROUP_V1_PREFIX, bytes);
288 }
289
290 @Override
291 public boolean isV1() {
292 return true;
293 }
294
295 @Override
296 public boolean isV2() {
297 return false;
298 }
299
300 public GroupMasterKey deriveV2MigrationMasterKey() {
301 try {
302 return new GroupMasterKey(new HKDFv3().deriveSecrets(getDecodedId(), "GV2 Migration".getBytes(), GroupMasterKey.SIZE));
303 } catch (InvalidInputException e) {
304 throw new AssertionError(e);
305 }
306 }
307
308 public GroupId.V2 deriveV2MigrationGroupId() {
309 return v2(deriveV2MigrationMasterKey());
310 }
311 }
312
313 public static final class V2 extends GroupId.Push {
314
315 private V2(@NonNull byte[] bytes) {
316 super(ENCODED_SIGNAL_GROUP_V2_PREFIX, bytes);
317 }
318
319 @Override
320 public boolean isV1() {
321 return false;
322 }
323
324 @Override
325 public boolean isV2() {
326 return true;
327 }
328 }
329}