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
.account
.PreKeyUpload
;
15 import org
.whispersystems
.signalservice
.api
.push
.ServiceIdType
;
16 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.AuthorizationFailedException
;
17 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonSuccessfulResponseCodeException
;
18 import org
.whispersystems
.signalservice
.internal
.push
.OneTimePreKeyCounts
;
20 import java
.io
.IOException
;
21 import java
.util
.List
;
23 import static org
.asamk
.signal
.manager
.config
.ServiceConfig
.PREKEY_STALE_AGE
;
24 import static org
.asamk
.signal
.manager
.config
.ServiceConfig
.SIGNED_PREKEY_ROTATE_AGE
;
26 public class PreKeyHelper
{
28 private static final Logger logger
= LoggerFactory
.getLogger(PreKeyHelper
.class);
30 private final SignalAccount account
;
31 private final SignalDependencies dependencies
;
34 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
, final IdentityKeyPair identityKeyPair
83 ) throws IOException
{
84 OneTimePreKeyCounts preKeyCounts
;
86 preKeyCounts
= dependencies
.getAccountManager().getPreKeyCounts(serviceIdType
);
87 } catch (AuthorizationFailedException e
) {
88 logger
.debug("Failed to get pre key count, ignoring: " + e
.getClass().getSimpleName());
89 preKeyCounts
= new OneTimePreKeyCounts(0, 0);
92 return refreshPreKeysIfNecessary(serviceIdType
, identityKeyPair
, preKeyCounts
, false);
95 private boolean refreshPreKeysIfNecessary(
96 final ServiceIdType serviceIdType
,
97 final IdentityKeyPair identityKeyPair
,
98 final OneTimePreKeyCounts preKeyCounts
,
100 ) throws IOException
{
101 List
<PreKeyRecord
> preKeyRecords
= null;
102 if (force
|| preKeyCounts
.getEcCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
103 logger
.debug("Refreshing {} ec pre keys, because only {} of min {} pre keys remain",
105 preKeyCounts
.getEcCount(),
106 ServiceConfig
.PREKEY_MINIMUM_COUNT
);
107 preKeyRecords
= generatePreKeys(serviceIdType
);
110 SignedPreKeyRecord signedPreKeyRecord
= null;
111 if (force
|| signedPreKeyNeedsRefresh(serviceIdType
)) {
112 logger
.debug("Refreshing {} signed pre key.", serviceIdType
);
113 signedPreKeyRecord
= generateSignedPreKey(serviceIdType
, identityKeyPair
);
116 List
<KyberPreKeyRecord
> kyberPreKeyRecords
= null;
117 if (force
|| preKeyCounts
.getKyberCount() < ServiceConfig
.PREKEY_MINIMUM_COUNT
) {
118 logger
.debug("Refreshing {} kyber pre keys, because only {} of min {} pre keys remain",
120 preKeyCounts
.getKyberCount(),
121 ServiceConfig
.PREKEY_MINIMUM_COUNT
);
122 kyberPreKeyRecords
= generateKyberPreKeys(serviceIdType
, identityKeyPair
);
125 KyberPreKeyRecord lastResortKyberPreKeyRecord
= null;
126 if (force
|| lastResortKyberPreKeyNeedsRefresh(serviceIdType
)) {
127 logger
.debug("Refreshing {} last resort kyber pre key.", serviceIdType
);
128 lastResortKyberPreKeyRecord
= generateLastResortKyberPreKey(serviceIdType
,
130 kyberPreKeyRecords
== null ?
0 : kyberPreKeyRecords
.size());
133 if (signedPreKeyRecord
== null
134 && preKeyRecords
== null
135 && lastResortKyberPreKeyRecord
== null
136 && kyberPreKeyRecords
== null) {
140 final var preKeyUpload
= new PreKeyUpload(serviceIdType
,
143 lastResortKyberPreKeyRecord
,
145 var needsReset
= false;
147 dependencies
.getAccountManager().setPreKeys(preKeyUpload
);
149 if (preKeyRecords
!= null) {
150 account
.addPreKeys(serviceIdType
, preKeyRecords
);
152 if (signedPreKeyRecord
!= null) {
153 account
.addSignedPreKey(serviceIdType
, signedPreKeyRecord
);
155 } catch (Exception e
) {
156 logger
.warn("Failed to store new pre keys, resetting preKey id offset", e
);
157 account
.resetPreKeyOffsets(serviceIdType
);
161 if (kyberPreKeyRecords
!= null) {
162 account
.addKyberPreKeys(serviceIdType
, kyberPreKeyRecords
);
164 if (lastResortKyberPreKeyRecord
!= null) {
165 account
.addLastResortKyberPreKey(serviceIdType
, lastResortKyberPreKeyRecord
);
167 } catch (Exception e
) {
168 logger
.warn("Failed to store new kyber pre keys, resetting preKey id offset", e
);
169 account
.resetKyberPreKeyOffsets(serviceIdType
);
172 } catch (AuthorizationFailedException e
) {
173 // This can happen when the primary device has changed phone number
174 logger
.warn("Failed to updated pre keys: {}", e
.getMessage());
175 } catch (NonSuccessfulResponseCodeException e
) {
176 if (serviceIdType
!= ServiceIdType
.PNI
|| e
.getCode() != 422) {
179 logger
.warn("Failed to update PNI pre keys, ignoring.");
184 public void cleanOldPreKeys() {
185 cleanOldPreKeys(ServiceIdType
.ACI
);
186 cleanOldPreKeys(ServiceIdType
.PNI
);
189 private void cleanOldPreKeys(final ServiceIdType serviceIdType
) {
190 cleanSignedPreKeys(serviceIdType
);
191 cleanOneTimePreKeys(serviceIdType
);
194 private List
<PreKeyRecord
> generatePreKeys(ServiceIdType serviceIdType
) {
195 final var accountData
= account
.getAccountData(serviceIdType
);
196 final var offset
= accountData
.getPreKeyMetadata().getNextPreKeyId();
198 return KeyUtils
.generatePreKeyRecords(offset
);
201 private boolean signedPreKeyNeedsRefresh(ServiceIdType serviceIdType
) {
202 final var accountData
= account
.getAccountData(serviceIdType
);
204 final var activeSignedPreKeyId
= accountData
.getPreKeyMetadata().getActiveSignedPreKeyId();
205 if (activeSignedPreKeyId
== -1) {
209 final var signedPreKeyRecord
= accountData
.getSignedPreKeyStore().loadSignedPreKey(activeSignedPreKeyId
);
210 return signedPreKeyRecord
.getTimestamp() < System
.currentTimeMillis() - SIGNED_PREKEY_ROTATE_AGE
;
211 } catch (InvalidKeyIdException e
) {
216 private SignedPreKeyRecord
generateSignedPreKey(ServiceIdType serviceIdType
, IdentityKeyPair identityKeyPair
) {
217 final var accountData
= account
.getAccountData(serviceIdType
);
218 final var signedPreKeyId
= accountData
.getPreKeyMetadata().getNextSignedPreKeyId();
220 return KeyUtils
.generateSignedPreKeyRecord(signedPreKeyId
, identityKeyPair
.getPrivateKey());
223 private List
<KyberPreKeyRecord
> generateKyberPreKeys(
224 ServiceIdType serviceIdType
, final IdentityKeyPair identityKeyPair
226 final var accountData
= account
.getAccountData(serviceIdType
);
227 final var offset
= accountData
.getPreKeyMetadata().getNextKyberPreKeyId();
229 return KeyUtils
.generateKyberPreKeyRecords(offset
, identityKeyPair
.getPrivateKey());
232 private boolean lastResortKyberPreKeyNeedsRefresh(ServiceIdType serviceIdType
) {
233 final var accountData
= account
.getAccountData(serviceIdType
);
235 final var activeLastResortKyberPreKeyId
= accountData
.getPreKeyMetadata().getActiveLastResortKyberPreKeyId();
236 if (activeLastResortKyberPreKeyId
== -1) {
240 final var kyberPreKeyRecord
= accountData
.getKyberPreKeyStore()
241 .loadKyberPreKey(activeLastResortKyberPreKeyId
);
242 return kyberPreKeyRecord
.getTimestamp() < System
.currentTimeMillis() - SIGNED_PREKEY_ROTATE_AGE
;
243 } catch (InvalidKeyIdException e
) {
248 private KyberPreKeyRecord
generateLastResortKyberPreKey(
249 ServiceIdType serviceIdType
, IdentityKeyPair identityKeyPair
, final int offset
251 final var accountData
= account
.getAccountData(serviceIdType
);
252 final var signedPreKeyId
= accountData
.getPreKeyMetadata().getNextKyberPreKeyId() + offset
;
254 return KeyUtils
.generateKyberPreKeyRecord(signedPreKeyId
, identityKeyPair
.getPrivateKey());
257 private void cleanSignedPreKeys(ServiceIdType serviceIdType
) {
258 final var accountData
= account
.getAccountData(serviceIdType
);
260 final var activeSignedPreKeyId
= accountData
.getPreKeyMetadata().getActiveSignedPreKeyId();
261 accountData
.getSignedPreKeyStore().removeOldSignedPreKeys(activeSignedPreKeyId
);
263 final var activeLastResortKyberPreKeyId
= accountData
.getPreKeyMetadata().getActiveLastResortKyberPreKeyId();
264 accountData
.getKyberPreKeyStore().removeOldLastResortKyberPreKeys(activeLastResortKyberPreKeyId
);
267 private void cleanOneTimePreKeys(ServiceIdType serviceIdType
) {
268 long threshold
= System
.currentTimeMillis() - PREKEY_STALE_AGE
;
271 final var accountData
= account
.getAccountData(serviceIdType
);
272 accountData
.getPreKeyStore().deleteAllStaleOneTimeEcPreKeys(threshold
, minCount
);
273 accountData
.getKyberPreKeyStore().deleteAllStaleOneTimeKyberPreKeys(threshold
, minCount
);