1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.api
.RecipientIdentifier
;
5 import org
.asamk
.signal
.manager
.api
.UnregisteredRecipientException
;
6 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
7 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
8 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
9 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
10 import org
.signal
.libsignal
.protocol
.InvalidKeyException
;
11 import org
.slf4j
.Logger
;
12 import org
.slf4j
.LoggerFactory
;
13 import org
.whispersystems
.signalservice
.api
.push
.ACI
;
14 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
15 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
16 import org
.whispersystems
.signalservice
.api
.services
.CdsiV2Service
;
17 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.Quote
;
18 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedQuoteException
;
19 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
21 import java
.io
.IOException
;
22 import java
.security
.SignatureException
;
23 import java
.util
.Collection
;
24 import java
.util
.HashMap
;
25 import java
.util
.HashSet
;
27 import java
.util
.Optional
;
30 public class RecipientHelper
{
32 private final static Logger logger
= LoggerFactory
.getLogger(RecipientHelper
.class);
34 private final SignalAccount account
;
35 private final SignalDependencies dependencies
;
36 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
38 public RecipientHelper(final Context context
) {
39 this.account
= context
.getAccount();
40 this.dependencies
= context
.getDependencies();
41 this.serviceEnvironmentConfig
= dependencies
.getServiceEnvironmentConfig();
44 public SignalServiceAddress
resolveSignalServiceAddress(RecipientId recipientId
) {
45 final var address
= account
.getRecipientAddressResolver().resolveRecipientAddress(recipientId
);
46 if (address
.number().isEmpty() || address
.serviceId().isPresent()) {
47 return address
.toSignalServiceAddress();
50 // Address in recipient store doesn't have a uuid, this shouldn't happen
51 // Try to retrieve the uuid from the server
52 final var number
= address
.number().get();
55 aci
= getRegisteredUser(number
);
56 } catch (UnregisteredRecipientException e
) {
57 logger
.warn("Failed to get uuid for e164 number: {}", number
);
58 // Return SignalServiceAddress with unknown UUID
59 return address
.toSignalServiceAddress();
60 } catch (IOException e
) {
61 logger
.warn("Failed to get uuid for e164 number: {}", number
, e
);
62 // Return SignalServiceAddress with unknown UUID
63 return address
.toSignalServiceAddress();
65 return account
.getRecipientAddressResolver()
66 .resolveRecipientAddress(account
.getRecipientResolver().resolveRecipient(aci
))
67 .toSignalServiceAddress();
70 public RecipientId
resolveRecipient(final SignalServiceAddress address
) {
71 return account
.getRecipientResolver().resolveRecipient(address
);
74 public Set
<RecipientId
> resolveRecipients(Collection
<RecipientIdentifier
.Single
> recipients
) throws UnregisteredRecipientException
{
75 final var recipientIds
= new HashSet
<RecipientId
>(recipients
.size());
76 for (var number
: recipients
) {
77 final var recipientId
= resolveRecipient(number
);
78 recipientIds
.add(recipientId
);
83 public RecipientId
resolveRecipient(final RecipientIdentifier
.Single recipient
) throws UnregisteredRecipientException
{
84 if (recipient
instanceof RecipientIdentifier
.Uuid uuidRecipient
) {
85 return account
.getRecipientResolver().resolveRecipient(ServiceId
.from(uuidRecipient
.uuid()));
87 final var number
= ((RecipientIdentifier
.Number
) recipient
).number();
88 return account
.getRecipientStore().resolveRecipient(number
, () -> {
90 return getRegisteredUser(number
);
91 } catch (Exception e
) {
98 public RecipientId
refreshRegisteredUser(RecipientId recipientId
) throws IOException
, UnregisteredRecipientException
{
99 final var address
= resolveSignalServiceAddress(recipientId
);
100 if (address
.getNumber().isEmpty()) {
103 final var number
= address
.getNumber().get();
104 final var uuid
= getRegisteredUser(number
);
105 return account
.getRecipientTrustedResolver().resolveRecipientTrusted(new SignalServiceAddress(uuid
, number
));
108 public Map
<String
, ACI
> getRegisteredUsers(final Set
<String
> numbers
) throws IOException
{
109 Map
<String
, ACI
> registeredUsers
;
111 registeredUsers
= getRegisteredUsersV2(numbers
, true);
112 } catch (IOException e
) {
113 logger
.warn("CDSI request failed, trying fallback to CDS", e
);
114 registeredUsers
= getRegisteredUsersV1(numbers
);
117 // Store numbers as recipients, so we have the number/uuid association
118 registeredUsers
.forEach((number
, aci
) -> account
.getRecipientTrustedResolver()
119 .resolveRecipientTrusted(new SignalServiceAddress(aci
, number
)));
121 return registeredUsers
;
124 private ACI
getRegisteredUser(final String number
) throws IOException
, UnregisteredRecipientException
{
125 final Map
<String
, ACI
> aciMap
;
127 aciMap
= getRegisteredUsers(Set
.of(number
));
128 } catch (NumberFormatException e
) {
129 throw new UnregisteredRecipientException(new org
.asamk
.signal
.manager
.api
.RecipientAddress(null, number
));
131 final var uuid
= aciMap
.get(number
);
133 throw new UnregisteredRecipientException(new org
.asamk
.signal
.manager
.api
.RecipientAddress(null, number
));
138 private Map
<String
, ACI
> getRegisteredUsersV1(final Set
<String
> numbers
) throws IOException
{
139 final Map
<String
, ACI
> registeredUsers
;
141 registeredUsers
= dependencies
.getAccountManager()
142 .getRegisteredUsers(ServiceConfig
.getIasKeyStore(),
144 serviceEnvironmentConfig
.getCdsMrenclave());
145 } catch (Quote
.InvalidQuoteFormatException
| UnauthenticatedQuoteException
| SignatureException
|
146 UnauthenticatedResponseException
| InvalidKeyException
| NumberFormatException e
) {
147 throw new IOException(e
);
149 return registeredUsers
;
152 private Map
<String
, ACI
> getRegisteredUsersV2(final Set
<String
> numbers
, boolean useCompat
) throws IOException
{
153 // Only partial refresh is implemented here
154 final CdsiV2Service
.Response response
;
156 response
= dependencies
.getAccountManager()
157 .getRegisteredUsersWithCdsi(Set
.of(),
159 account
.getRecipientStore().getServiceIdToProfileKeyMap(),
162 serviceEnvironmentConfig
.getCdsiMrenclave(),
164 // Not storing for partial refresh
166 } catch (NumberFormatException e
) {
167 throw new IOException(e
);
169 logger
.debug("CDSI request successful, quota used by this request: {}", response
.getQuotaUsedDebugOnly());
171 final var registeredUsers
= new HashMap
<String
, ACI
>();
172 response
.getResults().forEach((key
, value
) -> {
173 if (value
.getAci().isPresent()) {
174 registeredUsers
.put(key
, value
.getAci().get());
177 return registeredUsers
;
180 private ACI
getRegisteredUserByUsername(String username
) throws IOException
{
181 return dependencies
.getAccountManager().getAciByUsername(username
);