]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java
4bfead1305a6725088500a4da5578d7c786de9f0
[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.RecipientId;
7 import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
8 import org.signal.libsignal.metadata.certificate.SenderCertificate;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11 import org.whispersystems.libsignal.util.guava.Optional;
12 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
13 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
14
15 import java.io.IOException;
16 import java.util.List;
17 import java.util.concurrent.TimeUnit;
18 import java.util.stream.Collectors;
19
20 import static org.whispersystems.signalservice.internal.util.Util.getSecretBytes;
21
22 public class UnidentifiedAccessHelper {
23
24 private final static Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class);
25 private final static long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit.DAYS.toMillis(1);
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() {
94 return UnidentifiedAccess.deriveAccessKeyFrom(selfProfileKeyProvider.getProfileKey());
95 }
96
97 public byte[] getTargetUnidentifiedAccessKey(RecipientId recipient) {
98 var targetProfile = profileProvider.getProfile(recipient);
99 if (targetProfile == null) {
100 return null;
101 }
102
103 switch (targetProfile.getUnidentifiedAccessMode()) {
104 case ENABLED:
105 var theirProfileKey = account.getProfileStore().getProfileKey(recipient);
106 if (theirProfileKey == null) {
107 return null;
108 }
109
110 return UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey);
111 case UNRESTRICTED:
112 return createUnrestrictedUnidentifiedAccess();
113 default:
114 return null;
115 }
116 }
117
118 public Optional<UnidentifiedAccessPair> getAccessForSync() {
119 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
120 var selfUnidentifiedAccessCertificate = getSenderCertificate();
121
122 if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
123 return Optional.absent();
124 }
125
126 try {
127 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey,
128 selfUnidentifiedAccessCertificate),
129 new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
130 } catch (InvalidCertificateException e) {
131 return Optional.absent();
132 }
133 }
134
135 public List<Optional<UnidentifiedAccessPair>> getAccessFor(List<RecipientId> recipients) {
136 return recipients.stream().map(this::getAccessFor).collect(Collectors.toList());
137 }
138
139 public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
140 var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
141 var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
142 var selfUnidentifiedAccessCertificate = getSenderCertificateFor(recipient);
143
144 if (recipientUnidentifiedAccessKey == null
145 || selfUnidentifiedAccessKey == null
146 || selfUnidentifiedAccessCertificate == null) {
147 return Optional.absent();
148 }
149
150 try {
151 return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey,
152 selfUnidentifiedAccessCertificate),
153 new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate)));
154 } catch (InvalidCertificateException e) {
155 return Optional.absent();
156 }
157 }
158
159 private static byte[] createUnrestrictedUnidentifiedAccess() {
160 return getSecretBytes(16);
161 }
162 }