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