That fuck shit the fascists are using
1package org.tm.archive.crypto;
2
3
4import android.content.Context;
5
6import androidx.annotation.NonNull;
7import androidx.annotation.Nullable;
8import androidx.annotation.WorkerThread;
9
10import com.annimon.stream.Stream;
11
12import org.signal.core.util.logging.Log;
13import org.signal.libsignal.metadata.certificate.CertificateValidator;
14import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
15import org.signal.libsignal.protocol.InvalidKeyException;
16import org.signal.libsignal.protocol.ecc.Curve;
17import org.signal.libsignal.protocol.ecc.ECPublicKey;
18import org.signal.libsignal.zkgroup.profiles.ProfileKey;
19import org.tm.archive.BuildConfig;
20import org.tm.archive.keyvalue.CertificateType;
21import org.tm.archive.keyvalue.SignalStore;
22import org.tm.archive.recipients.Recipient;
23import org.tm.archive.recipients.RecipientId;
24import org.signal.core.util.Base64;
25import org.tm.archive.util.TextSecurePreferences;
26import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
27import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
28
29import java.io.IOException;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35import java.util.Optional;
36import java.util.stream.Collectors;
37
38public class UnidentifiedAccessUtil {
39
40 private static final String TAG = Log.tag(UnidentifiedAccessUtil.class);
41
42 private static final byte[] UNRESTRICTED_KEY = new byte[16];
43
44 public static CertificateValidator getCertificateValidator() {
45 return CertificateValidatorHolder.INSTANCE.certificateValidator;
46 }
47
48 @WorkerThread
49 public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient) {
50 return getAccessFor(context, recipient, true);
51 }
52
53 @WorkerThread
54 public static Optional<UnidentifiedAccessPair> getAccessFor(@NonNull Context context, @NonNull Recipient recipient, boolean log) {
55 return getAccessFor(context, Collections.singletonList(recipient), log).get(0);
56 }
57
58 @WorkerThread
59 public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients) {
60 return getAccessFor(context, recipients, true);
61 }
62
63 @WorkerThread
64 public static Map<RecipientId, Optional<UnidentifiedAccessPair>> getAccessMapFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory) {
65 List<Optional<UnidentifiedAccessPair>> accessList = getAccessFor(context, recipients, isForStory, true);
66
67 Iterator<Recipient> recipientIterator = recipients.iterator();
68 Iterator<Optional<UnidentifiedAccessPair>> accessIterator = accessList.iterator();
69
70 Map<RecipientId, Optional<UnidentifiedAccessPair>> accessMap = new HashMap<>(recipients.size());
71
72 while (recipientIterator.hasNext()) {
73 accessMap.put(recipientIterator.next().getId(), accessIterator.next());
74 }
75
76 return accessMap;
77 }
78
79 @WorkerThread
80 public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean log) {
81 return getAccessFor(context, recipients, false, log);
82 }
83
84 @WorkerThread
85 public static List<Optional<UnidentifiedAccessPair>> getAccessFor(@NonNull Context context, @NonNull List<Recipient> recipients, boolean isForStory, boolean log) {
86 final byte[] ourUnidentifiedAccessKey;
87
88 if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
89 ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
90 } else {
91 ourUnidentifiedAccessKey = ProfileKeyUtil.getSelfProfileKey().deriveAccessKey();
92 }
93
94 CertificateType certificateType = getUnidentifiedAccessCertificateType();
95 byte[] ourUnidentifiedAccessCertificate = SignalStore.certificateValues().getUnidentifiedAccessCertificate(certificateType);
96
97 List<Optional<UnidentifiedAccessPair>> access = recipients.parallelStream().map(recipient -> {
98 UnidentifiedAccessPair unidentifiedAccessPair = null;
99 if (ourUnidentifiedAccessCertificate != null) {
100 try {
101 UnidentifiedAccess theirAccess = getTargetUnidentifiedAccess(recipient, ourUnidentifiedAccessCertificate, isForStory);
102 UnidentifiedAccess ourAccess = new UnidentifiedAccess(ourUnidentifiedAccessKey, ourUnidentifiedAccessCertificate, false);
103
104 if (theirAccess != null) {
105 unidentifiedAccessPair = new UnidentifiedAccessPair(theirAccess, ourAccess);
106 }
107 } catch (InvalidCertificateException e) {
108 Log.w(TAG, "Invalid unidentified access certificate!", e);
109 }
110 } else {
111 Log.w(TAG, "Missing unidentified access certificate!");
112 }
113 return Optional.ofNullable(unidentifiedAccessPair);
114 }).collect(Collectors.toList());
115
116 int unidentifiedCount = Stream.of(access).filter(Optional::isPresent).toList().size();
117 int otherCount = access.size() - unidentifiedCount;
118
119 if (log) {
120 Log.i(TAG, "Unidentified: " + unidentifiedCount + ", Other: " + otherCount);
121 }
122
123 return access;
124 }
125
126 public static Optional<UnidentifiedAccessPair> getAccessForSync(@NonNull Context context) {
127 try {
128 byte[] ourUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(ProfileKeyUtil.getSelfProfileKey());
129 byte[] ourUnidentifiedAccessCertificate = getUnidentifiedAccessCertificate();
130
131 if (TextSecurePreferences.isUniversalUnidentifiedAccess(context)) {
132 ourUnidentifiedAccessKey = UNRESTRICTED_KEY;
133 }
134
135 if (ourUnidentifiedAccessCertificate != null) {
136 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(ourUnidentifiedAccessKey,
137 ourUnidentifiedAccessCertificate,
138 false),
139 new UnidentifiedAccess(ourUnidentifiedAccessKey,
140 ourUnidentifiedAccessCertificate,
141 false)));
142 }
143
144 return Optional.empty();
145 } catch (InvalidCertificateException e) {
146 Log.w(TAG, e);
147 return Optional.empty();
148 }
149 }
150
151 private static @NonNull CertificateType getUnidentifiedAccessCertificateType() {
152 if (SignalStore.phoneNumberPrivacy().isPhoneNumberSharingEnabled()) {
153 return CertificateType.ACI_AND_E164;
154 } else {
155 return CertificateType.ACI_ONLY;
156 }
157 }
158
159 private static byte[] getUnidentifiedAccessCertificate() {
160 return SignalStore.certificateValues()
161 .getUnidentifiedAccessCertificate(getUnidentifiedAccessCertificateType());
162 }
163
164 private static @Nullable UnidentifiedAccess getTargetUnidentifiedAccess(@NonNull Recipient recipient, @NonNull byte[] certificate, boolean isForStory) throws InvalidCertificateException {
165 ProfileKey theirProfileKey = ProfileKeyUtil.profileKeyOrNull(recipient.resolve().getProfileKey());
166
167 byte[] accessKey;
168
169 switch (recipient.resolve().getUnidentifiedAccessMode()) {
170 case UNKNOWN:
171 if (theirProfileKey == null) {
172 if (isForStory) {
173 accessKey = null;
174 } else {
175 accessKey = UNRESTRICTED_KEY;
176 }
177 } else {
178 accessKey = theirProfileKey.deriveAccessKey();
179 }
180 break;
181 case DISABLED:
182 accessKey = null;
183 break;
184 case ENABLED:
185 if (theirProfileKey == null) {
186 accessKey = null;
187 } else {
188 accessKey = theirProfileKey.deriveAccessKey();
189 }
190 break;
191 case UNRESTRICTED:
192 accessKey = UNRESTRICTED_KEY;
193 break;
194 default:
195 throw new AssertionError("Unknown mode: " + recipient.getUnidentifiedAccessMode().getMode());
196 }
197
198 if (accessKey == null && isForStory) {
199 return new UnidentifiedAccess(UNRESTRICTED_KEY, certificate, true);
200 } else if (accessKey != null) {
201 return new UnidentifiedAccess(accessKey, certificate, false);
202 } else {
203 return null;
204 }
205 }
206
207 private enum CertificateValidatorHolder {
208 INSTANCE;
209
210 final CertificateValidator certificateValidator = buildCertificateValidator();
211
212 private static CertificateValidator buildCertificateValidator() {
213 try {
214 ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BuildConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
215 return new CertificateValidator(unidentifiedSenderTrustRoot);
216 } catch (InvalidKeyException | IOException e) {
217 throw new AssertionError(e);
218 }
219 }
220 }
221}