1 package org
.asamk
.signal
.manager
.helper
;
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
;
17 import java
.io
.IOException
;
18 import java
.util
.List
;
19 import java
.util
.concurrent
.TimeUnit
;
21 import static org
.asamk
.signal
.manager
.util
.Utils
.handleResponseException
;
23 public class UnidentifiedAccessHelper
{
25 private static final Logger logger
= LoggerFactory
.getLogger(UnidentifiedAccessHelper
.class);
26 private static final long CERTIFICATE_EXPIRATION_BUFFER
= TimeUnit
.DAYS
.toMillis(1);
27 private static final byte[] UNRESTRICTED_KEY
= new byte[16];
29 private final SignalAccount account
;
30 private final SignalDependencies dependencies
;
31 private final Context context
;
33 private SenderCertificate privacySenderCertificate
;
34 private SenderCertificate senderCertificate
;
36 public UnidentifiedAccessHelper(final Context context
) {
37 this.account
= context
.getAccount();
38 this.dependencies
= context
.getDependencies();
39 this.context
= context
;
42 public void rotateSenderCertificates() {
43 privacySenderCertificate
= null;
44 senderCertificate
= null;
47 public List
<SealedSenderAccess
> getSealedSenderAccessFor(List
<RecipientId
> recipients
) {
48 return recipients
.stream().map(this::getAccessFor
).map(SealedSenderAccess
::forIndividual
).toList();
51 public @Nullable SealedSenderAccess
getSealedSenderAccessFor(RecipientId recipient
) {
52 return getSealedSenderAccessFor(recipient
, false);
55 public @Nullable SealedSenderAccess
getSealedSenderAccessFor(RecipientId recipient
, boolean noRefresh
) {
56 return SealedSenderAccess
.forIndividual(getAccessFor(recipient
, noRefresh
));
59 public List
<UnidentifiedAccess
> getAccessFor(List
<RecipientId
> recipients
) {
60 return recipients
.stream().map(this::getAccessFor
).toList();
63 private @Nullable UnidentifiedAccess
getAccessFor(RecipientId recipient
) {
64 return getAccessFor(recipient
, false);
67 private @Nullable UnidentifiedAccess
getAccessFor(RecipientId recipientId
, boolean noRefresh
) {
68 var recipientUnidentifiedAccessKey
= getTargetUnidentifiedAccessKey(recipientId
, noRefresh
);
69 if (recipientUnidentifiedAccessKey
== null) {
70 logger
.trace("Unidentified access not available for {}", recipientId
);
74 var selfUnidentifiedAccessKey
= getSelfUnidentifiedAccessKey(noRefresh
);
75 if (selfUnidentifiedAccessKey
== null) {
76 logger
.trace("Unidentified access not available for self");
80 var senderCertificate
= getSenderCertificateFor(recipientId
);
81 if (senderCertificate
== null) {
82 logger
.trace("Unidentified access not available due to missing sender certificate");
87 return new UnidentifiedAccess(recipientUnidentifiedAccessKey
, senderCertificate
, false);
88 } catch (InvalidCertificateException e
) {
93 private byte[] getSenderCertificateFor(final RecipientId recipientId
) {
94 final var sharingMode
= account
.getConfigurationStore().getPhoneNumberSharingMode();
95 if (sharingMode
== PhoneNumberSharingMode
.EVERYBODY
|| (
96 sharingMode
== PhoneNumberSharingMode
.CONTACTS
97 && account
.getContactStore().getContact(recipientId
) != null
99 logger
.trace("Using normal sender certificate for message to {}", recipientId
);
100 return getSenderCertificate();
102 logger
.trace("Using phone number privacy sender certificate for message to {}", recipientId
);
103 return getSenderCertificateForPhoneNumberPrivacy();
107 private byte[] getSenderCertificateForPhoneNumberPrivacy() {
108 if (privacySenderCertificate
!= null && System
.currentTimeMillis() < (
109 privacySenderCertificate
.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
111 return privacySenderCertificate
.getSerialized();
114 final var certificate
= handleResponseException(dependencies
.getCertificateApi()
115 .getSenderCertificateForPhoneNumberPrivacy());
116 privacySenderCertificate
= new SenderCertificate(certificate
);
118 } catch (IOException
| InvalidCertificateException e
) {
119 logger
.warn("Failed to get sender certificate (pnp), ignoring: {}", e
.getMessage());
124 private byte[] getSenderCertificate() {
125 if (senderCertificate
!= null && System
.currentTimeMillis() < (
126 senderCertificate
.getExpiration() - CERTIFICATE_EXPIRATION_BUFFER
128 return senderCertificate
.getSerialized();
131 final var certificate
= handleResponseException(dependencies
.getCertificateApi().getSenderCertificate());
132 this.senderCertificate
= new SenderCertificate(certificate
);
134 } catch (IOException
| InvalidCertificateException e
) {
135 logger
.warn("Failed to get sender certificate, ignoring: {}", e
.getMessage());
140 private byte[] getSelfUnidentifiedAccessKey(boolean noRefresh
) {
141 var selfProfile
= noRefresh
142 ? account
.getProfileStore().getProfile(account
.getSelfRecipientId())
143 : context
.getProfileHelper().getSelfProfile();
144 if (selfProfile
!= null
145 && selfProfile
.getUnidentifiedAccessMode() == Profile
.UnidentifiedAccessMode
.UNRESTRICTED
) {
146 return createUnrestrictedUnidentifiedAccess();
148 return UnidentifiedAccess
.deriveAccessKeyFrom(account
.getProfileKey());
151 private byte[] getTargetUnidentifiedAccessKey(RecipientId recipientId
, boolean noRefresh
) {
152 var targetProfile
= noRefresh
153 ? account
.getProfileStore().getProfile(recipientId
)
154 : context
.getProfileHelper().getRecipientProfile(recipientId
);
155 if (targetProfile
== null) {
159 var theirProfileKey
= account
.getProfileStore().getProfileKey(recipientId
);
160 return getTargetUnidentifiedAccessKey(targetProfile
, theirProfileKey
);
163 private static byte[] getTargetUnidentifiedAccessKey(
164 final Profile targetProfile
,
165 final ProfileKey theirProfileKey
167 return switch (targetProfile
.getUnidentifiedAccessMode()) {
168 case ENABLED
-> theirProfileKey
== null ?
null : UnidentifiedAccess
.deriveAccessKeyFrom(theirProfileKey
);
169 case UNRESTRICTED
-> createUnrestrictedUnidentifiedAccess();
174 private static byte[] createUnrestrictedUnidentifiedAccess() {
175 return UNRESTRICTED_KEY
;