1 package org
.asamk
.signal
.manager
.helper
;
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
;
17 import java
.io
.IOException
;
18 import java
.util
.List
;
19 import java
.util
.concurrent
.TimeUnit
;
21 public class UnidentifiedAccessHelper
{
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];
27 private final SignalAccount account
;
28 private final SignalDependencies dependencies
;
29 private final SelfProfileKeyProvider selfProfileKeyProvider
;
30 private final ProfileProvider profileProvider
;
32 private SenderCertificate privacySenderCertificate
;
33 private SenderCertificate senderCertificate
;
35 public UnidentifiedAccessHelper(
36 final SignalAccount account
,
37 final SignalDependencies dependencies
,
38 final SelfProfileKeyProvider selfProfileKeyProvider
,
39 final ProfileProvider profileProvider
41 this.account
= account
;
42 this.dependencies
= dependencies
;
43 this.selfProfileKeyProvider
= selfProfileKeyProvider
;
44 this.profileProvider
= profileProvider
;
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
53 logger
.debug("Using normal sender certificate for message to {}", recipientId
);
54 return getSenderCertificate();
56 logger
.debug("Using phone number privacy sender certificate for message to {}", recipientId
);
57 return getSenderCertificateForPhoneNumberPrivacy();
61 private byte[] getSenderCertificateForPhoneNumberPrivacy() {
62 if (privacySenderCertificate
!= null && System
.currentTimeMillis() < (
63 privacySenderCertificate
.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
65 return privacySenderCertificate
.getSerialized();
68 final var certificate
= dependencies
.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
69 privacySenderCertificate
= new SenderCertificate(certificate
);
71 } catch (IOException
| InvalidCertificateException e
) {
72 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
77 private byte[] getSenderCertificate() {
78 if (senderCertificate
!= null && System
.currentTimeMillis() < (
79 senderCertificate
.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
81 return senderCertificate
.getSerialized();
84 final var certificate
= dependencies
.getAccountManager().getSenderCertificate();
85 this.senderCertificate
= new SenderCertificate(certificate
);
87 } catch (IOException
| InvalidCertificateException e
) {
88 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
93 private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh
) {
94 var selfProfile
= noRefresh
95 ? account
.getProfileStore().getProfile(account
.getSelfRecipientId())
96 : profileProvider
.getProfile(account
.getSelfRecipientId());
97 if (selfProfile
!= null
98 && selfProfile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
) {
99 return createUnrestrictedUnidentifiedAccess();
101 return UnidentifiedAccess
.deriveAccessKeyFrom(selfProfileKeyProvider
.getProfileKey());
104 private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId
, boolean noRefresh
) {
105 var targetProfile
= noRefresh
106 ? account
.getProfileStore().getProfile(recipientId
)
107 : profileProvider
.getProfile(recipientId
);
108 if (targetProfile
== null) {
112 var theirProfileKey
= account
.getProfileStore().getProfileKey(recipientId
);
113 return getTargetUnidentifiedAccessKey(targetProfile
, theirProfileKey
);
116 private static byte[] getTargetUnidentifiedAccessKey(
117 final Profile targetProfile
, final ProfileKey theirProfileKey
119 switch (targetProfile
.getUnidentifiedAccessMode()) {
121 if (theirProfileKey
== null) {
125 return UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
127 return createUnrestrictedUnidentifiedAccess();
133 public Optional
<UnidentifiedAccessPair
> getAccessForSync() {
134 var selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey(false);
135 var selfUnidentifiedAccessCertificate
= getSenderCertificate();
137 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
138 return Optional
.absent();
142 return Optional
.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey
,
143 selfUnidentifiedAccessCertificate
),
144 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)));
145 } catch (InvalidCertificateException e
) {
146 return Optional
.absent();
150 public List
<Optional
<UnidentifiedAccessPair
>> getAccessFor(List
<RecipientId
> recipients
) {
151 return recipients
.stream().map(this::getAccessFor
).toList();
154 public Optional
<UnidentifiedAccessPair
> getAccessFor(RecipientId recipient
) {
155 return getAccessFor(recipient
, false);
158 public Optional
<UnidentifiedAccessPair
> getAccessFor(RecipientId recipient
, boolean noRefresh
) {
159 var recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipient
, noRefresh
);
160 if (recipientUnidentifiedAccessKey
== null) {
161 return Optional
.absent();
164 var selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey(noRefresh
);
165 var selfUnidentifiedAccessCertificate
= getSenderCertificateFor(recipient
);
166 if (selfUnidentifiedAccessKey
== null || selfUnidentifiedAccessCertificate
== null) {
167 return Optional
.absent();
171 return Optional
.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey
,
172 selfUnidentifiedAccessCertificate
),
173 new UnidentifiedAccess(selfUnidentifiedAccessKey
, selfUnidentifiedAccessCertificate
)));
174 } catch (InvalidCertificateException e
) {
175 return Optional
.absent();
179 private static byte[] createUnrestrictedUnidentifiedAccess() {
180 return UNRESTRICTED_KEY
;