]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java
Add UnregisteredRecipientException
[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 public List<Optional<UnidentifiedAccessPair>> getAccessFor(List<RecipientId> recipients) {
48 return recipients.stream().map(this::getAccessFor).toList();
49 }
50
51 public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
52 return getAccessFor(recipient, false);
53 }
54
55 public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipientId, boolean noRefresh) {
56 var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipientId, noRefresh);
57 if (recipientUnidentifiedAccessKey == null) {
58 logger.trace("Unidentified access not available for {}", recipientId);
59 return Optional.absent();
60 }
61
62 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
63 if (selfUnidentifiedAccessKey == null) {
64 logger.trace("Unidentified access not available for self");
65 return Optional.absent();
66 }
67
68 var senderCertificate = getSenderCertificateFor(recipientId);
69 if (senderCertificate == null) {
70 logger.trace("Unidentified access not available due to missing sender certificate");
71 return Optional.absent();
72 }
73
74 try {
75 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey,
76 senderCertificate), new UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate)));
77 } catch (InvalidCertificateException e) {
78 return Optional.absent();
79 }
80 }
81
82 public Optional<UnidentifiedAccessPair> getAccessForSync() {
83 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false);
84 var selfUnidentifiedAccessCertificate = getSenderCertificate();
85
86 if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
87 return Optional.absent();
88 }
89
90 try {
91 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey,
92 selfUnidentifiedAccessCertificate),
93 new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
94 } catch (InvalidCertificateException e) {
95 return Optional.absent();
96 }
97 }
98
99 private byte[] getSenderCertificateFor(final RecipientId recipientId) {
100 final var sharingMode = account.getConfigurationStore().getPhoneNumberSharingMode();
101 if (sharingMode == PhoneNumberSharingMode.EVERYBODY || (
102 sharingMode == PhoneNumberSharingMode.CONTACTS
103 && account.getContactStore().getContact(recipientId) != null
104 )) {
105 logger.trace("Using normal sender certificate for message to {}", recipientId);
106 return getSenderCertificate();
107 } else {
108 logger.trace("Using phone number privacy sender certificate for message to {}", recipientId);
109 return getSenderCertificateForPhoneNumberPrivacy();
110 }
111 }
112
113 private byte[] getSenderCertificateForPhoneNumberPrivacy() {
114 if (privacySenderCertificate != null && System.currentTimeMillis() < (
115 privacySenderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
116 )) {
117 return privacySenderCertificate.getSerialized();
118 }
119 try {
120 final var certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
121 privacySenderCertificate = new SenderCertificate(certificate);
122 return certificate;
123 } catch (IOException | InvalidCertificateException e) {
124 logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
125 return null;
126 }
127 }
128
129 private byte[] getSenderCertificate() {
130 if (senderCertificate != null && System.currentTimeMillis() < (
131 senderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
132 )) {
133 return senderCertificate.getSerialized();
134 }
135 try {
136 final var certificate = dependencies.getAccountManager().getSenderCertificate();
137 this.senderCertificate = new SenderCertificate(certificate);
138 return certificate;
139 } catch (IOException | InvalidCertificateException e) {
140 logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
141 return null;
142 }
143 }
144
145 private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh) {
146 var selfProfile = noRefresh
147 ? account.getProfileStore().getProfile(account.getSelfRecipientId())
148 : profileProvider.getProfile(account.getSelfRecipientId());
149 if (selfProfile != null
150 && selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
151 return createUnrestrictedUnidentifiedAccess();
152 }
153 return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
154 }
155
156 private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId, boolean noRefresh) {
157 var targetProfile = noRefresh
158 ? account.getProfileStore().getProfile(recipientId)
159 : profileProvider.getProfile(recipientId);
160 if (targetProfile == null) {
161 return null;
162 }
163
164 var theirProfileKey = account.getProfileStore().getProfileKey(recipientId);
165 return getTargetUnidentifiedAccessKey(targetProfile, theirProfileKey);
166 }
167
168 private static byte[] getTargetUnidentifiedAccessKey(
169 final Profile targetProfile, final ProfileKey theirProfileKey
170 ) {
171 switch (targetProfile.getUnidentifiedAccessMode()) {
172 case ENABLED:
173 if (theirProfileKey == null) {
174 return null;
175 }
176
177 return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
178 case UNRESTRICTED:
179 return createUnrestrictedUnidentifiedAccess();
180 default:
181 return null;
182 }
183 }
184
185 private static byte[] createUnrestrictedUnidentifiedAccess() {
186 return UNRESTRICTED_KEY;
187 }
188 }