]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java
Paralellize profile fetching
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / UnidentifiedAccessHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.SignalDependencies;
4 import org.asamk.signal.manager.api.PhoneNumberSharingMode;
5 import org.asamk.signal.manager.storage.SignalAccount;
6 import org.asamk.signal.manager.storage.recipients.Profile;
7 import org.asamk.signal.manager.storage.recipients.RecipientId;
8 import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
9 import org.signal.libsignal.metadata.certificate.SenderCertificate;
10 import org.signal.zkgroup.profiles.ProfileKey;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 import org.whispersystems.libsignal.util.guava.Optional;
14 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
15 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
16
17 import java.io.IOException;
18 import java.util.List;
19 import java.util.concurrent.TimeUnit;
20
21 public class UnidentifiedAccessHelper {
22
23 private final static Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class);
24 private final static long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit.DAYS.toMillis(1);
25 private static final byte[] UNRESTRICTED_KEY = new byte[16];
26
27 private final SignalAccount account;
28 private final SignalDependencies dependencies;
29 private final SelfProfileKeyProvider selfProfileKeyProvider;
30 private final ProfileProvider profileProvider;
31
32 private SenderCertificate privacySenderCertificate;
33 private SenderCertificate senderCertificate;
34
35 public UnidentifiedAccessHelper(
36 final SignalAccount account,
37 final SignalDependencies dependencies,
38 final SelfProfileKeyProvider selfProfileKeyProvider,
39 final ProfileProvider profileProvider
40 ) {
41 this.account = account;
42 this.dependencies = dependencies;
43 this.selfProfileKeyProvider = selfProfileKeyProvider;
44 this.profileProvider = profileProvider;
45 }
46
47 private byte[] getSenderCertificateFor(final RecipientId recipientId) {
48 final var sharingMode = account.getConfigurationStore().getPhoneNumberSharingMode();
49 if (sharingMode == PhoneNumberSharingMode.EVERYBODY || (
50 sharingMode == PhoneNumberSharingMode.CONTACTS
51 && account.getContactStore().getContact(recipientId) != null
52 )) {
53 logger.debug("Using normal sender certificate for message to {}", recipientId);
54 return getSenderCertificate();
55 } else {
56 logger.debug("Using phone number privacy sender certificate for message to {}", recipientId);
57 return getSenderCertificateForPhoneNumberPrivacy();
58 }
59 }
60
61 private byte[] getSenderCertificateForPhoneNumberPrivacy() {
62 if (privacySenderCertificate != null && System.currentTimeMillis() < (
63 privacySenderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
64 )) {
65 return privacySenderCertificate.getSerialized();
66 }
67 try {
68 final var certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
69 privacySenderCertificate = new SenderCertificate(certificate);
70 return certificate;
71 } catch (IOException | InvalidCertificateException e) {
72 logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
73 return null;
74 }
75 }
76
77 private byte[] getSenderCertificate() {
78 if (senderCertificate != null && System.currentTimeMillis() < (
79 senderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
80 )) {
81 return senderCertificate.getSerialized();
82 }
83 try {
84 final var certificate = dependencies.getAccountManager().getSenderCertificate();
85 this.senderCertificate = new SenderCertificate(certificate);
86 return certificate;
87 } catch (IOException | InvalidCertificateException e) {
88 logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
89 return null;
90 }
91 }
92
93 private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh) {
94 var selfProfile = noRefresh
95 ? account.getProfileStore().getProfile(account.getSelfRecipientId())
96 : profileProvider.getProfile(account.getSelfRecipientId());
97 if (selfProfile != null
98 && selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
99 return createUnrestrictedUnidentifiedAccess();
100 }
101 return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
102 }
103
104 private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId, boolean noRefresh) {
105 var targetProfile = noRefresh
106 ? account.getProfileStore().getProfile(recipientId)
107 : profileProvider.getProfile(recipientId);
108 if (targetProfile == null) {
109 return null;
110 }
111
112 var theirProfileKey = account.getProfileStore().getProfileKey(recipientId);
113 return getTargetUnidentifiedAccessKey(targetProfile, theirProfileKey);
114 }
115
116 private static byte[] getTargetUnidentifiedAccessKey(
117 final Profile targetProfile, final ProfileKey theirProfileKey
118 ) {
119 switch (targetProfile.getUnidentifiedAccessMode()) {
120 case ENABLED:
121 if (theirProfileKey == null) {
122 return null;
123 }
124
125 return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
126 case UNRESTRICTED:
127 return createUnrestrictedUnidentifiedAccess();
128 default:
129 return null;
130 }
131 }
132
133 public Optional<UnidentifiedAccessPair> getAccessForSync() {
134 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false);
135 var selfUnidentifiedAccessCertificate = getSenderCertificate();
136
137 if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
138 return Optional.absent();
139 }
140
141 try {
142 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey,
143 selfUnidentifiedAccessCertificate),
144 new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
145 } catch (InvalidCertificateException e) {
146 return Optional.absent();
147 }
148 }
149
150 public List<Optional<UnidentifiedAccessPair>> getAccessFor(List<RecipientId> recipients) {
151 return recipients.stream().map(this::getAccessFor).toList();
152 }
153
154 public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
155 return getAccessFor(recipient, false);
156 }
157
158 public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient, boolean noRefresh) {
159 var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient, noRefresh);
160 if (recipientUnidentifiedAccessKey == null) {
161 return Optional.absent();
162 }
163
164 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
165 var selfUnidentifiedAccessCertificate = getSenderCertificateFor(recipient);
166 if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
167 return Optional.absent();
168 }
169
170 try {
171 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey,
172 selfUnidentifiedAccessCertificate),
173 new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
174 } catch (InvalidCertificateException e) {
175 return Optional.absent();
176 }
177 }
178
179 private static byte[] createUnrestrictedUnidentifiedAccess() {
180 return UNRESTRICTED_KEY;
181 }
182 }