1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.config
.ServiceConfig
;
4 import org
.asamk
.signal
.manager
.internal
.SignalDependencies
;
5 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
6 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
7 import org
.signal
.libsignal
.protocol
.IdentityKeyPair
;
8 import org
.signal
.libsignal
.protocol
.InvalidKeyIdException
;
9 import org
.signal
.libsignal
.protocol
.state
.KyberPreKeyRecord
;
10 import org
.signal
.libsignal
.protocol
.state
.PreKeyRecord
;
11 import org
.signal
.libsignal
.protocol
.state
.SignedPreKeyRecord
;
12 import org
.slf4j
.Logger
;
13 import org
.slf4j
.LoggerFactory
;
14 import org
.whispersystems
.signalservice
.api
.NetworkResultUtil
;
15 import org
.whispersystems
.signalservice
.api
.account
.PreKeyUpload
;
16 import org
.whispersystems
.signalservice
.api
.keys
.OneTimePreKeyCounts
;
17 import org
.whispersystems
.signalservice
.api
.push
.ServiceIdType
;
18 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
19 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonSuccessfulResponseCodeException
;
21 import java
.io
.IOException
;
22 import java
.util
.List
;
24 import static org
.asamk
.signal
.manager
.config
.ServiceConfig
.PREKEY_STALE_AGE
;
25 import static org
.asamk
.signal
.manager
.config
.ServiceConfig
.SIGNED_PREKEY_ROTATE_AGE
;
26 import static org
.asamk
.signal
.manager
.util
.Utils
.handleResponseException
;
28 public class PreKeyHelper
{
30 private static final Logger logger
= LoggerFactory
.getLogger(PreKeyHelper
.class);
32 private final SignalAccount account
;
33 private final SignalDependencies dependencies
;
35 public PreKeyHelper(final SignalAccount account
, final SignalDependencies dependencies
) {
36 this.account
= account
;
37 this.dependencies
= dependencies
;
40 public void refreshPreKeysIfNecessary() throws IOException
{
41 refreshPreKeysIfNecessary(ServiceIdType
.ACI
);
42 refreshPreKeysIfNecessary(ServiceIdType
.PNI
);
45 public void forceRefreshPreKeys() throws IOException
{
46 forceRefreshPreKeys(ServiceIdType
.ACI
);
47 forceRefreshPreKeys(ServiceIdType
.PNI
);
50 public void refreshPreKeysIfNecessary(ServiceIdType serviceIdType
) throws IOException
{
51 final var identityKeyPair
= account
.getIdentityKeyPair(serviceIdType
);
52 if (identityKeyPair
== null) {
55 final var accountId
= account
.getAccountId(serviceIdType
);
56 if (accountId
== null) {
60 if (refreshPreKeysIfNecessary(serviceIdType
, identityKeyPair
)) {
61 refreshPreKeysIfNecessary(serviceIdType
, identityKeyPair
);
65 public void forceRefreshPreKeys(ServiceIdType serviceIdType
) throws IOException
{
66 final var identityKeyPair
= account
.getIdentityKeyPair(serviceIdType
);
67 if (identityKeyPair
== null) {
70 final var accountId
= account
.getAccountId(serviceIdType
);
71 if (accountId
== null) {
75 final var counts
= new OneTimePreKeyCounts(0, 0);
76 if (refreshPreKeysIfNecessary(serviceIdType
, identityKeyPair
, counts
, true)) {
77 refreshPreKeysIfNecessary(serviceIdType
, identityKeyPair
, counts
, true);
81 private boolean refreshPreKeysIfNecessary(
82 final ServiceIdType serviceIdType
,
83 final IdentityKeyPair identityKeyPair
84 ) throws IOException
{
85 OneTimePreKeyCounts preKeyCounts
;
87 preKeyCounts
= handleResponseException(dependencies
.getKeysApi().getAvailablePreKeyCounts(serviceIdType
));
88 } catch (AuthorizationFailedException e
) {
89 logger
.debug("Failed to get pre key count, ignoring: " + e
.getClass().getSimpleName());
90 preKeyCounts
= new OneTimePreKeyCounts(0, 0);
93 return refreshPreKeysIfNecessary(serviceIdType
, identityKeyPair
, preKeyCounts
, false);
96 private boolean refreshPreKeysIfNecessary(
97 final ServiceIdType serviceIdType
,
98 final IdentityKeyPair identityKeyPair
,
99 final OneTimePreKeyCounts preKeyCounts
,
101 ) throws IOException
{
102 List
<PreKeyRecord
> preKeyRecords
= null;
103 if (force
|| preKeyCounts
.getEcCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
104 logger
.debug("Refreshing {} ec pre keys, because only {} of min {} pre keys remain",
106 preKeyCounts
.getEcCount(),
107 ServiceConfig
.PREKEY_MINIMUM_COUNT
);
108 preKeyRecords
= generatePreKeys(serviceIdType
);
111 SignedPreKeyRecord signedPreKeyRecord
= null;
112 if (force
|| signedPreKeyNeedsRefresh(serviceIdType
)) {
113 logger
.debug("Refreshing {} signed pre key.", serviceIdType
);
114 signedPreKeyRecord
= generateSignedPreKey(serviceIdType
, identityKeyPair
);
117 List
<KyberPreKeyRecord
> kyberPreKeyRecords
= null;
118 if (force
|| preKeyCounts
.getKyberCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
119 logger
.debug("Refreshing {} kyber pre keys, because only {} of min {} pre keys remain",
121 preKeyCounts
.getKyberCount(),
122 ServiceConfig
.PREKEY_MINIMUM_COUNT
);
123 kyberPreKeyRecords
= generateKyberPreKeys(serviceIdType
, identityKeyPair
);
126 KyberPreKeyRecord lastResortKyberPreKeyRecord
= null;
127 if (force
|| lastResortKyberPreKeyNeedsRefresh(serviceIdType
)) {
128 logger
.debug("Refreshing {} last resort kyber pre key.", serviceIdType
);
129 lastResortKyberPreKeyRecord
= generateLastResortKyberPreKey(serviceIdType
,
131 kyberPreKeyRecords
== null ?
0 : kyberPreKeyRecords
.size());
134 if (signedPreKeyRecord
== null
135 && preKeyRecords
== null
136 && lastResortKyberPreKeyRecord
== null
137 && kyberPreKeyRecords
== null) {
141 final var preKeyUpload
= new PreKeyUpload(serviceIdType
,
144 lastResortKyberPreKeyRecord
,
146 var needsReset
= false;
148 NetworkResultUtil
.toPreKeysLegacy(dependencies
.getKeysApi().setPreKeys(preKeyUpload
));
150 if (preKeyRecords
!= null) {
151 account
.addPreKeys(serviceIdType
, preKeyRecords
);
153 if (signedPreKeyRecord
!= null) {
154 account
.addSignedPreKey(serviceIdType
, signedPreKeyRecord
);
156 } catch (Exception e
) {
157 logger
.warn("Failed to store new pre keys, resetting preKey id offset", e
);
158 account
.resetPreKeyOffsets(serviceIdType
);
162 if (kyberPreKeyRecords
!= null) {
163 account
.addKyberPreKeys(serviceIdType
, kyberPreKeyRecords
);
165 if (lastResortKyberPreKeyRecord
!= null) {
166 account
.addLastResortKyberPreKey(serviceIdType
, lastResortKyberPreKeyRecord
);
168 } catch (Exception e
) {
169 logger
.warn("Failed to store new kyber pre keys, resetting preKey id offset", e
);
170 account
.resetKyberPreKeyOffsets(serviceIdType
);
173 } catch (AuthorizationFailedException e
) {
174 // This can happen when the primary device has changed phone number
175 logger
.warn("Failed to updated pre keys: {}", e
.getMessage());
176 } catch (NonSuccessfulResponseCodeException e
) {
177 if (serviceIdType
!= ServiceIdType
.PNI
|| e
.code
!= 422) {
180 logger
.warn("Failed to set PNI pre keys, ignoring for now. Account needs to be reregistered to fix this.");
185 public void cleanOldPreKeys() {
186 cleanOldPreKeys(ServiceIdType
.ACI
);
187 cleanOldPreKeys(ServiceIdType
.PNI
);
190 private void cleanOldPreKeys(final ServiceIdType serviceIdType
) {
191 cleanSignedPreKeys(serviceIdType
);
192 cleanOneTimePreKeys(serviceIdType
);
195 private List
<PreKeyRecord
> generatePreKeys(ServiceIdType serviceIdType
) {
196 final var accountData
= account
.getAccountData(serviceIdType
);
197 final var offset
= accountData
.getPreKeyMetadata().getNextPreKeyId();
199 return KeyUtils
.generatePreKeyRecords(offset
);
202 private boolean signedPreKeyNeedsRefresh(ServiceIdType serviceIdType
) {
203 final var accountData
= account
.getAccountData(serviceIdType
);
205 final var activeSignedPreKeyId
= accountData
.getPreKeyMetadata().getActiveSignedPreKeyId();
206 if (activeSignedPreKeyId
== -1) {
210 final var signedPreKeyRecord
= accountData
.getSignedPreKeyStore().loadSignedPreKey(activeSignedPreKeyId
);
211 return signedPreKeyRecord
.getTimestamp() < System
.currentTimeMillis() - SIGNED_PREKEY_ROTATE_AGE
;
212 } catch (InvalidKeyIdException e
) {
217 private SignedPreKeyRecord
generateSignedPreKey(ServiceIdType serviceIdType
, IdentityKeyPair identityKeyPair
) {
218 final var accountData
= account
.getAccountData(serviceIdType
);
219 final var signedPreKeyId
= accountData
.getPreKeyMetadata().getNextSignedPreKeyId();
221 return KeyUtils
.generateSignedPreKeyRecord(signedPreKeyId
, identityKeyPair
.getPrivateKey());
224 private List
<KyberPreKeyRecord
> generateKyberPreKeys(
225 ServiceIdType serviceIdType
,
226 final IdentityKeyPair identityKeyPair
228 final var accountData
= account
.getAccountData(serviceIdType
);
229 final var offset
= accountData
.getPreKeyMetadata().getNextKyberPreKeyId();
231 return KeyUtils
.generateKyberPreKeyRecords(offset
, identityKeyPair
.getPrivateKey());
234 private boolean lastResortKyberPreKeyNeedsRefresh(ServiceIdType serviceIdType
) {
235 final var accountData
= account
.getAccountData(serviceIdType
);
237 final var activeLastResortKyberPreKeyId
= accountData
.getPreKeyMetadata().getActiveLastResortKyberPreKeyId();
238 if (activeLastResortKyberPreKeyId
== -1) {
242 final var kyberPreKeyRecord
= accountData
.getKyberPreKeyStore()
243 .loadKyberPreKey(activeLastResortKyberPreKeyId
);
244 return kyberPreKeyRecord
.getTimestamp() < System
.currentTimeMillis() - SIGNED_PREKEY_ROTATE_AGE
;
245 } catch (InvalidKeyIdException e
) {
250 private KyberPreKeyRecord
generateLastResortKyberPreKey(
251 ServiceIdType serviceIdType
,
252 IdentityKeyPair identityKeyPair
,
255 final var accountData
= account
.getAccountData(serviceIdType
);
256 final var signedPreKeyId
= accountData
.getPreKeyMetadata().getNextKyberPreKeyId() + offset
;
258 return KeyUtils
.generateKyberPreKeyRecord(signedPreKeyId
, identityKeyPair
.getPrivateKey());
261 private void cleanSignedPreKeys(ServiceIdType serviceIdType
) {
262 final var accountData
= account
.getAccountData(serviceIdType
);
264 final var activeSignedPreKeyId
= accountData
.getPreKeyMetadata().getActiveSignedPreKeyId();
265 accountData
.getSignedPreKeyStore().removeOldSignedPreKeys(activeSignedPreKeyId
);
267 final var activeLastResortKyberPreKeyId
= accountData
.getPreKeyMetadata().getActiveLastResortKyberPreKeyId();
268 accountData
.getKyberPreKeyStore().removeOldLastResortKyberPreKeys(activeLastResortKyberPreKeyId
);
271 private void cleanOneTimePreKeys(ServiceIdType serviceIdType
) {
272 long threshold
= System
.currentTimeMillis() - PREKEY_STALE_AGE
;
275 final var accountData
= account
.getAccountData(serviceIdType
);
276 accountData
.getPreKeyStore().deleteAllStaleOneTimeEcPreKeys(threshold
, minCount
);
277 accountData
.getKyberPreKeyStore().deleteAllStaleOneTimeKyberPreKeys(threshold
, minCount
);