]> 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.api.PhoneNumberSharingMode;
4 import org.asamk.signal.manager.api.Profile;
5 import org.asamk.signal.manager.internal.SignalDependencies;
6 import org.asamk.signal.manager.storage.SignalAccount;
7 import org.asamk.signal.manager.storage.recipients.RecipientId;
8 import org.jetbrains.annotations.Nullable;
9 import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
10 import org.signal.libsignal.metadata.certificate.SenderCertificate;
11 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14 import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
15 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
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 static final Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class);
24 private static final 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<SealedSenderAccess> getSealedSenderAccessFor(List<RecipientId> recipients) {
46 return recipients.stream().map(this::getAccessFor).map(SealedSenderAccess::forIndividual).toList();
47 }
48
49 public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient) {
50 return getSealedSenderAccessFor(recipient, false);
51 }
52
53 public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient, boolean noRefresh) {
54 return SealedSenderAccess.forIndividual(getAccessFor(recipient, noRefresh));
55 }
56
57 public List<UnidentifiedAccess> getAccessFor(List<RecipientId> recipients) {
58 return recipients.stream().map(this::getAccessFor).toList();
59 }
60
61 private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipient) {
62 return getAccessFor(recipient, false);
63 }
64
65 private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipientId, boolean noRefresh) {
66 var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipientId, noRefresh);
67 if (recipientUnidentifiedAccessKey == null) {
68 logger.trace("Unidentified access not available for {}", recipientId);
69 return null;
70 }
71
72 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh);
73 if (selfUnidentifiedAccessKey == null) {
74 logger.trace("Unidentified access not available for self");
75 return null;
76 }
77
78 var senderCertificate = getSenderCertificateFor(recipientId);
79 if (senderCertificate == null) {
80 logger.trace("Unidentified access not available due to missing sender certificate");
81 return null;
82 }
83
84 try {
85 return new UnidentifiedAccess(recipientUnidentifiedAccessKey, senderCertificate, false);
86 } catch (InvalidCertificateException e) {
87 return null;
88 }
89 }
90
91 private byte[] getSenderCertificateFor(final RecipientId recipientId) {
92 final var sharingMode = account.getConfigurationStore().getPhoneNumberSharingMode();
93 if (sharingMode == PhoneNumberSharingMode.EVERYBODY || (
94 sharingMode == PhoneNumberSharingMode.CONTACTS
95 && account.getContactStore().getContact(recipientId) != null
96 )) {
97 logger.trace("Using normal sender certificate for message to {}", recipientId);
98 return getSenderCertificate();
99 } else {
100 logger.trace("Using phone number privacy sender certificate for message to {}", recipientId);
101 return getSenderCertificateForPhoneNumberPrivacy();
102 }
103 }
104
105 private byte[] getSenderCertificateForPhoneNumberPrivacy() {
106 if (privacySenderCertificate != null && System.currentTimeMillis() < (
107 privacySenderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
108 )) {
109 return privacySenderCertificate.getSerialized();
110 }
111 try {
112 final var certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
113 privacySenderCertificate = new SenderCertificate(certificate);
114 return certificate;
115 } catch (IOException | InvalidCertificateException e) {
116 logger.warn("Failed to get sender certificate (pnp), ignoring: {}", e.getMessage());
117 return null;
118 }
119 }
120
121 private byte[] getSenderCertificate() {
122 if (senderCertificate != null && System.currentTimeMillis() < (
123 senderCertificate.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
124 )) {
125 return senderCertificate.getSerialized();
126 }
127 try {
128 final var certificate = dependencies.getAccountManager().getSenderCertificate();
129 this.senderCertificate = new SenderCertificate(certificate);
130 return certificate;
131 } catch (IOException | InvalidCertificateException e) {
132 logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
133 return null;
134 }
135 }
136
137 private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh) {
138 var selfProfile = noRefresh
139 ? account.getProfileStore().getProfile(account.getSelfRecipientId())
140 : context.getProfileHelper().getSelfProfile();
141 if (selfProfile != null
142 && selfProfile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED) {
143 return createUnrestrictedUnidentifiedAccess();
144 }
145 return UnidentifiedAccess.deriveAccessKeyFrom(account.getProfileKey());
146 }
147
148 private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId, boolean noRefresh) {
149 var targetProfile = noRefresh
150 ? account.getProfileStore().getProfile(recipientId)
151 : context.getProfileHelper().getRecipientProfile(recipientId);
152 if (targetProfile == null) {
153 return null;
154 }
155
156 var theirProfileKey = account.getProfileStore().getProfileKey(recipientId);
157 return getTargetUnidentifiedAccessKey(targetProfile, theirProfileKey);
158 }
159
160 private static byte[] getTargetUnidentifiedAccessKey(
161 final Profile targetProfile, final ProfileKey theirProfileKey
162 ) {
163 return switch (targetProfile.getUnidentifiedAccessMode()) {
164 case ENABLED -> theirProfileKey == null ? null : UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
165 case UNRESTRICTED -> createUnrestrictedUnidentifiedAccess();
166 default -> null;
167 };
168 }
169
170 private static byte[] createUnrestrictedUnidentifiedAccess() {
171 return UNRESTRICTED_KEY;
172 }
173 }