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