import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
public final static int PREKEY_MINIMUM_COUNT = 10;
public final static int PREKEY_BATCH_SIZE = 100;
public final static int PREKEY_MAXIMUM_ID = Medium.MAX_VALUE;
+ public static final long PREKEY_ARCHIVE_AGE = TimeUnit.DAYS.toMillis(30);
+ public static final long PREKEY_STALE_AGE = TimeUnit.DAYS.toMillis(90);
+ public static final long SIGNED_PREKEY_ROTATE_AGE = TimeUnit.DAYS.toMillis(2);
+
public final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
public final static long MAX_ENVELOPE_SIZE = 0;
public final static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.libsignal.protocol.IdentityKeyPair;
+import org.signal.libsignal.protocol.InvalidKeyIdException;
import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import java.io.IOException;
import java.util.List;
+import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_STALE_AGE;
+import static org.asamk.signal.manager.config.ServiceConfig.SIGNED_PREKEY_ROTATE_AGE;
+
public class PreKeyHelper {
private final static Logger logger = LoggerFactory.getLogger(PreKeyHelper.class);
}
public void refreshPreKeysIfNecessary(ServiceIdType serviceIdType) throws IOException {
- OneTimePreKeyCounts preKeyCounts;
- try {
- preKeyCounts = dependencies.getAccountManager().getPreKeyCounts(serviceIdType);
- } catch (AuthorizationFailedException e) {
- logger.debug("Failed to get pre key count, ignoring: " + e.getClass().getSimpleName());
- preKeyCounts = new OneTimePreKeyCounts(0, 0);
- }
- if (preKeyCounts.getEcCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
- logger.debug("Refreshing {} ec pre keys, because only {} of min {} pre keys remain",
- serviceIdType,
- preKeyCounts.getEcCount(),
- ServiceConfig.PREKEY_MINIMUM_COUNT);
- refreshPreKeys(serviceIdType);
- }
- if (preKeyCounts.getKyberCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
- logger.debug("Refreshing {} kyber pre keys, because only {} of min {} pre keys remain",
- serviceIdType,
- preKeyCounts.getKyberCount(),
- ServiceConfig.PREKEY_MINIMUM_COUNT);
- refreshKyberPreKeys(serviceIdType);
- }
- }
-
- private void refreshPreKeys(ServiceIdType serviceIdType) throws IOException {
final var identityKeyPair = account.getIdentityKeyPair(serviceIdType);
if (identityKeyPair == null) {
return;
if (accountId == null) {
return;
}
+
+ OneTimePreKeyCounts preKeyCounts;
try {
- refreshPreKeys(serviceIdType, identityKeyPair);
+ preKeyCounts = dependencies.getAccountManager().getPreKeyCounts(serviceIdType);
+ } catch (AuthorizationFailedException e) {
+ logger.debug("Failed to get pre key count, ignoring: " + e.getClass().getSimpleName());
+ preKeyCounts = new OneTimePreKeyCounts(0, 0);
+ }
+
+ SignedPreKeyRecord signedPreKeyRecord = null;
+ List<PreKeyRecord> preKeyRecords = null;
+ KyberPreKeyRecord lastResortKyberPreKeyRecord = null;
+ List<KyberPreKeyRecord> kyberPreKeyRecords = null;
+
+ try {
+ if (preKeyCounts.getEcCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+ logger.debug("Refreshing {} ec pre keys, because only {} of min {} pre keys remain",
+ serviceIdType,
+ preKeyCounts.getEcCount(),
+ ServiceConfig.PREKEY_MINIMUM_COUNT);
+ preKeyRecords = generatePreKeys(serviceIdType);
+ }
+ if (signedPreKeyNeedsRefresh(serviceIdType)) {
+ logger.debug("Refreshing {} signed pre key.", serviceIdType);
+ signedPreKeyRecord = generateSignedPreKey(serviceIdType, identityKeyPair);
+ }
} catch (Exception e) {
logger.warn("Failed to store new pre keys, resetting preKey id offset", e);
account.resetPreKeyOffsets(serviceIdType);
- refreshPreKeys(serviceIdType, identityKeyPair);
+ preKeyRecords = generatePreKeys(serviceIdType);
+ signedPreKeyRecord = generateSignedPreKey(serviceIdType, identityKeyPair);
+ }
+
+ try {
+ if (preKeyCounts.getKyberCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+ logger.debug("Refreshing {} kyber pre keys, because only {} of min {} pre keys remain",
+ serviceIdType,
+ preKeyCounts.getKyberCount(),
+ ServiceConfig.PREKEY_MINIMUM_COUNT);
+ kyberPreKeyRecords = generateKyberPreKeys(serviceIdType, identityKeyPair);
+ }
+ if (lastResortKyberPreKeyNeedsRefresh(serviceIdType)) {
+ logger.debug("Refreshing {} last resort kyber pre key.", serviceIdType);
+ lastResortKyberPreKeyRecord = generateLastResortKyberPreKey(serviceIdType, identityKeyPair);
+ }
+ } catch (Exception e) {
+ logger.warn("Failed to store new kyber pre keys, resetting preKey id offset", e);
+ account.resetKyberPreKeyOffsets(serviceIdType);
+ kyberPreKeyRecords = generateKyberPreKeys(serviceIdType, identityKeyPair);
+ lastResortKyberPreKeyRecord = generateLastResortKyberPreKey(serviceIdType, identityKeyPair);
+ }
+
+ if (signedPreKeyRecord != null
+ || preKeyRecords != null
+ || lastResortKyberPreKeyRecord != null
+ || kyberPreKeyRecords != null) {
+ final var preKeyUpload = new PreKeyUpload(serviceIdType,
+ identityKeyPair.getPublicKey(),
+ signedPreKeyRecord,
+ preKeyRecords,
+ lastResortKyberPreKeyRecord,
+ kyberPreKeyRecords);
+ dependencies.getAccountManager().setPreKeys(preKeyUpload);
}
- }
- private void refreshPreKeys(
- final ServiceIdType serviceIdType, final IdentityKeyPair identityKeyPair
- ) throws IOException {
- final var oneTimePreKeys = generatePreKeys(serviceIdType);
- final var signedPreKeyRecord = generateSignedPreKey(serviceIdType, identityKeyPair);
-
- final var preKeyUpload = new PreKeyUpload(serviceIdType,
- identityKeyPair.getPublicKey(),
- signedPreKeyRecord,
- oneTimePreKeys,
- null,
- null);
- dependencies.getAccountManager().setPreKeys(preKeyUpload);
+ cleanSignedPreKeys((serviceIdType));
+ cleanOneTimePreKeys(serviceIdType);
}
private List<PreKeyRecord> generatePreKeys(ServiceIdType serviceIdType) {
return records;
}
+ private boolean signedPreKeyNeedsRefresh(ServiceIdType serviceIdType) {
+ final var accountData = account.getAccountData(serviceIdType);
+
+ final var activeSignedPreKeyId = accountData.getPreKeyMetadata().getActiveSignedPreKeyId();
+ if (activeSignedPreKeyId == -1) {
+ return true;
+ }
+ try {
+ final var signedPreKeyRecord = accountData.getSignedPreKeyStore().loadSignedPreKey(activeSignedPreKeyId);
+ return signedPreKeyRecord.getTimestamp() < System.currentTimeMillis() - SIGNED_PREKEY_ROTATE_AGE;
+ } catch (InvalidKeyIdException e) {
+ return true;
+ }
+ }
+
private SignedPreKeyRecord generateSignedPreKey(ServiceIdType serviceIdType, IdentityKeyPair identityKeyPair) {
final var signedPreKeyId = account.getNextSignedPreKeyId(serviceIdType);
return record;
}
- private void refreshKyberPreKeys(ServiceIdType serviceIdType) throws IOException {
- final var identityKeyPair = account.getIdentityKeyPair(serviceIdType);
- if (identityKeyPair == null) {
- return;
- }
- final var accountId = account.getAccountId(serviceIdType);
- if (accountId == null) {
- return;
- }
- try {
- refreshKyberPreKeys(serviceIdType, identityKeyPair);
- } catch (Exception e) {
- logger.warn("Failed to store new pre keys, resetting preKey id offset", e);
- account.resetKyberPreKeyOffsets(serviceIdType);
- refreshKyberPreKeys(serviceIdType, identityKeyPair);
- }
- }
-
- private void refreshKyberPreKeys(
- final ServiceIdType serviceIdType, final IdentityKeyPair identityKeyPair
- ) throws IOException {
- final var oneTimePreKeys = generateKyberPreKeys(serviceIdType, identityKeyPair);
- final var lastResortPreKeyRecord = generateLastResortKyberPreKey(serviceIdType, identityKeyPair);
-
- final var preKeyUpload = new PreKeyUpload(serviceIdType,
- identityKeyPair.getPublicKey(),
- null,
- null,
- lastResortPreKeyRecord,
- oneTimePreKeys);
- dependencies.getAccountManager().setPreKeys(preKeyUpload);
- }
-
private List<KyberPreKeyRecord> generateKyberPreKeys(
ServiceIdType serviceIdType, final IdentityKeyPair identityKeyPair
) {
return records;
}
+ private boolean lastResortKyberPreKeyNeedsRefresh(ServiceIdType serviceIdType) {
+ final var accountData = account.getAccountData(serviceIdType);
+
+ final var activeLastResortKyberPreKeyId = accountData.getPreKeyMetadata().getActiveLastResortKyberPreKeyId();
+ if (activeLastResortKyberPreKeyId == -1) {
+ return true;
+ }
+ try {
+ final var kyberPreKeyRecord = accountData.getKyberPreKeyStore()
+ .loadKyberPreKey(activeLastResortKyberPreKeyId);
+ return kyberPreKeyRecord.getTimestamp() < System.currentTimeMillis() - SIGNED_PREKEY_ROTATE_AGE;
+ } catch (InvalidKeyIdException e) {
+ return true;
+ }
+ }
+
private KyberPreKeyRecord generateLastResortKyberPreKey(
ServiceIdType serviceIdType, IdentityKeyPair identityKeyPair
) {
return record;
}
+
+ private void cleanSignedPreKeys(ServiceIdType serviceIdType) {
+ final var accountData = account.getAccountData(serviceIdType);
+
+ final var activeSignedPreKeyId = accountData.getPreKeyMetadata().getActiveSignedPreKeyId();
+ accountData.getSignedPreKeyStore().removeOldSignedPreKeys(activeSignedPreKeyId);
+
+ final var activeLastResortKyberPreKeyId = accountData.getPreKeyMetadata().getActiveLastResortKyberPreKeyId();
+ accountData.getKyberPreKeyStore().removeOldLastResortKyberPreKeys(activeLastResortKyberPreKeyId);
+ }
+
+ private void cleanOneTimePreKeys(ServiceIdType serviceIdType) {
+ long threshold = System.currentTimeMillis() - PREKEY_STALE_AGE;
+ int minCount = 200;
+
+ final var accountData = account.getAccountData(serviceIdType);
+ accountData.getPreKeyStore().deleteAllStaleOneTimeEcPreKeys(threshold, minCount);
+ accountData.getKyberPreKeyStore().deleteAllStaleOneTimeKyberPreKeys(threshold, minCount);
+ }
}
public class AccountDatabase extends Database {
private final static Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
- private static final long DATABASE_VERSION = 15;
+ private static final long DATABASE_VERSION = 16;
private AccountDatabase(final HikariDataSource dataSource) {
super(logger, DATABASE_VERSION, dataSource);
""");
}
}
+ if (oldVersion < 16) {
+ logger.debug("Updating database: Adding stale_timestamp prekey field");
+ try (final var statement = connection.createStatement()) {
+ statement.executeUpdate("""
+ ALTER TABLE pre_key ADD COLUMN stale_timestamp INTEGER;
+ ALTER TABLE kyber_pre_key ADD COLUMN stale_timestamp INTEGER;
+ ALTER TABLE kyber_pre_key ADD COLUMN timestamp INTEGER DEFAULT 0;
+ """);
+ }
+ }
}
}
} else {
aciAccountData.preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
}
+ if (rootNode.hasNonNull("activeSignedPreKeyId")) {
+ aciAccountData.preKeyMetadata.activeSignedPreKeyId = rootNode.get("activeSignedPreKeyId").asInt(-1);
+ } else {
+ aciAccountData.preKeyMetadata.activeSignedPreKeyId = -1;
+ }
if (rootNode.hasNonNull("pniPreKeyIdOffset")) {
pniAccountData.preKeyMetadata.preKeyIdOffset = rootNode.get("pniPreKeyIdOffset").asInt(1);
} else {
} else {
pniAccountData.preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
}
+ if (rootNode.hasNonNull("pniActiveSignedPreKeyId")) {
+ pniAccountData.preKeyMetadata.activeSignedPreKeyId = rootNode.get("pniActiveSignedPreKeyId").asInt(-1);
+ } else {
+ pniAccountData.preKeyMetadata.activeSignedPreKeyId = -1;
+ }
if (rootNode.hasNonNull("kyberPreKeyIdOffset")) {
aciAccountData.preKeyMetadata.kyberPreKeyIdOffset = rootNode.get("kyberPreKeyIdOffset").asInt(1);
} else {
.put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
.put("preKeyIdOffset", aciAccountData.getPreKeyMetadata().preKeyIdOffset)
.put("nextSignedPreKeyId", aciAccountData.getPreKeyMetadata().nextSignedPreKeyId)
+ .put("activeSignedPreKeyId", aciAccountData.getPreKeyMetadata().activeSignedPreKeyId)
.put("pniPreKeyIdOffset", pniAccountData.getPreKeyMetadata().preKeyIdOffset)
.put("pniNextSignedPreKeyId", pniAccountData.getPreKeyMetadata().nextSignedPreKeyId)
+ .put("pniActiveSignedPreKeyId", pniAccountData.getPreKeyMetadata().activeSignedPreKeyId)
.put("kyberPreKeyIdOffset", aciAccountData.getPreKeyMetadata().kyberPreKeyIdOffset)
.put("activeLastResortKyberPreKeyId",
aciAccountData.getPreKeyMetadata().activeLastResortKyberPreKeyId)
final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
preKeyMetadata.preKeyIdOffset = getRandomPreKeyIdOffset();
preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
+ preKeyMetadata.activeSignedPreKeyId = -1;
save();
}
records.size(),
serviceIdType,
preKeyMetadata.preKeyIdOffset);
+ accountData.signalProtocolStore.markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
for (var record : records) {
if (preKeyMetadata.preKeyIdOffset != record.getId()) {
logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyMetadata.preKeyIdOffset);
}
accountData.getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
preKeyMetadata.nextSignedPreKeyId = (preKeyMetadata.nextSignedPreKeyId + 1) % PREKEY_MAXIMUM_ID;
+ preKeyMetadata.activeSignedPreKeyId = record.getId();
save();
}
records.size(),
serviceIdType,
preKeyMetadata.kyberPreKeyIdOffset);
+ accountData.signalProtocolStore.markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
for (var record : records) {
if (preKeyMetadata.kyberPreKeyIdOffset != record.getId()) {
logger.error("Invalid kyber pre key id {}, expected {}",
private int preKeyIdOffset = 1;
private int nextSignedPreKeyId = 1;
+ private int activeSignedPreKeyId = -1;
private int kyberPreKeyIdOffset = 1;
private int activeLastResortKyberPreKeyId = -1;
return nextSignedPreKeyId;
}
+ public int getActiveSignedPreKeyId() {
+ return activeSignedPreKeyId;
+ }
+
public int getKyberPreKeyIdOffset() {
return kyberPreKeyIdOffset;
}
SignalAccount.this::isMultiDevice));
}
- private PreKeyStore getPreKeyStore() {
+ public PreKeyStore getPreKeyStore() {
return getOrCreate(() -> preKeyStore,
() -> preKeyStore = new PreKeyStore(getAccountDatabase(), serviceIdType));
}
- private SignedPreKeyStore getSignedPreKeyStore() {
+ public SignedPreKeyStore getSignedPreKeyStore() {
return getOrCreate(() -> signedPreKeyStore,
() -> signedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), serviceIdType));
}
- private KyberPreKeyStore getKyberPreKeyStore() {
+ public KyberPreKeyStore getKyberPreKeyStore() {
return getOrCreate(() -> kyberPreKeyStore,
() -> kyberPreKeyStore = new KyberPreKeyStore(getAccountDatabase(), serviceIdType));
}
import java.sql.SQLException;
import java.util.List;
+import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_ARCHIVE_AGE;
+
public class KyberPreKeyStore implements SignalServiceKyberPreKeyStore {
private static final String TABLE_KYBER_PRE_KEY = "kyber_pre_key";
key_id INTEGER NOT NULL,
serialized BLOB NOT NULL,
is_last_resort INTEGER NOT NULL,
+ stale_timestamp INTEGER,
+ timestamp INTEGER DEFAULT 0,
UNIQUE(account_id_type, key_id)
) STRICT;
""");
public void storeKyberPreKey(final int keyId, final KyberPreKeyRecord record, final boolean isLastResort) {
final var sql = (
"""
- INSERT INTO %s (account_id_type, key_id, serialized, is_last_resort)
- VALUES (?, ?, ?, ?)
+ INSERT INTO %s (account_id_type, key_id, serialized, is_last_resort, timestamp)
+ VALUES (?, ?, ?, ?, ?)
"""
).formatted(TABLE_KYBER_PRE_KEY);
try (final var connection = database.getConnection()) {
statement.setInt(2, keyId);
statement.setBytes(3, record.serialize());
statement.setBoolean(4, isLastResort);
+ statement.setLong(5, record.getTimestamp());
statement.executeUpdate();
}
} catch (SQLException e) {
}
}
+ public void removeOldLastResortKyberPreKeys(int activeLastResortKyberPreKeyId) {
+ final var sql = (
+ """
+ DELETE FROM %s AS p
+ WHERE p._id IN (
+ SELECT p._id
+ FROM %s AS p
+ WHERE p.account_id_type = ?
+ AND p.is_last_resort = TRUE
+ AND p.key_id != ?
+ AND p.timestamp < ?
+ ORDER BY p.timestamp DESC
+ LIMIT -1 OFFSET 1
+ )
+ """
+ ).formatted(TABLE_KYBER_PRE_KEY, TABLE_KYBER_PRE_KEY);
+ try (final var connection = database.getConnection()) {
+ try (final var statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, accountIdType);
+ statement.setInt(2, activeLastResortKyberPreKeyId);
+ statement.setLong(3, System.currentTimeMillis() - PREKEY_ARCHIVE_AGE);
+ statement.executeUpdate();
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed update kyber_pre_key store", e);
+ }
+ }
+
@Override
public void deleteAllStaleOneTimeKyberPreKeys(final long threshold, final int minCount) {
- //TODO
+ final var sql = (
+ """
+ DELETE FROM %s AS p
+ WHERE p.account_id_type = ?1
+ AND p.stale_timestamp < ?2
+ AND p.is_last_resort = FALSE
+ AND p._id NOT IN (
+ SELECT _id
+ FROM %s p2
+ WHERE p2.account_id_type = ?1
+ ORDER BY
+ CASE WHEN p2.stale_timestamp IS NULL THEN 1 ELSE 0 END DESC,
+ p2.stale_timestamp DESC,
+ p2._id DESC
+ LIMIT ?3
+ )
+ """
+ ).formatted(TABLE_KYBER_PRE_KEY, TABLE_KYBER_PRE_KEY);
+ try (final var connection = database.getConnection()) {
+ try (final var statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, accountIdType);
+ statement.setLong(2, threshold);
+ statement.setInt(3, minCount);
+ final var rowCount = statement.executeUpdate();
+ if (rowCount > 0) {
+ logger.debug("Deleted {} stale one time kyber pre keys", rowCount);
+ }
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed update kyber_pre_key store", e);
+ }
}
@Override
public void markAllOneTimeKyberPreKeysStaleIfNecessary(final long staleTime) {
- //TODO
+ final var sql = (
+ """
+ UPDATE %s
+ SET stale_timestamp = ?
+ WHERE account_id_type = ? AND stale_timestamp IS NULL AND is_last_resort = FALSE
+ """
+ ).formatted(TABLE_KYBER_PRE_KEY);
+ try (final var connection = database.getConnection()) {
+ try (final var statement = connection.prepareStatement(sql)) {
+ statement.setLong(1, staleTime);
+ statement.setInt(2, accountIdType);
+ statement.executeUpdate();
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed update kyber_pre_key store", e);
+ }
}
}
import org.signal.libsignal.protocol.state.PreKeyRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.SignalServicePreKeyStore;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
-public class PreKeyStore implements org.signal.libsignal.protocol.state.PreKeyStore {
+public class PreKeyStore implements SignalServicePreKeyStore {
private static final String TABLE_PRE_KEY = "pre_key";
private final static Logger logger = LoggerFactory.getLogger(PreKeyStore.class);
key_id INTEGER NOT NULL,
public_key BLOB NOT NULL,
private_key BLOB NOT NULL,
+ stale_timestamp INTEGER,
UNIQUE(account_id_type, key_id)
) STRICT;
""");
return null;
}
}
+
+ @Override
+ public void deleteAllStaleOneTimeEcPreKeys(final long threshold, final int minCount) {
+ final var sql = (
+ """
+ DELETE FROM %s AS p
+ WHERE p.account_id_type = ?1
+ AND p.stale_timestamp < ?2
+ AND p._id NOT IN (
+ SELECT _id
+ FROM %s AS p2
+ WHERE p2.account_id_type = ?1
+ ORDER BY
+ CASE WHEN p2.stale_timestamp IS NULL THEN 1 ELSE 0 END DESC,
+ p2.stale_timestamp DESC,
+ p2._id DESC
+ LIMIT ?3
+ )
+ """
+ ).formatted(TABLE_PRE_KEY, TABLE_PRE_KEY);
+ try (final var connection = database.getConnection()) {
+ try (final var statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, accountIdType);
+ statement.setLong(2, threshold);
+ statement.setInt(3, minCount);
+ final var rowCount = statement.executeUpdate();
+ if (rowCount > 0) {
+ logger.debug("Deleted {} stale one time pre keys", rowCount);
+ }
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed update pre_key store", e);
+ }
+ }
+
+ @Override
+ public void markAllOneTimeEcPreKeysStaleIfNecessary(final long staleTime) {
+ final var sql = (
+ """
+ UPDATE %s
+ SET stale_timestamp = ?
+ WHERE account_id_type = ? AND stale_timestamp IS NULL
+ """
+ ).formatted(TABLE_PRE_KEY);
+ try (final var connection = database.getConnection()) {
+ try (final var statement = connection.prepareStatement(sql)) {
+ statement.setLong(1, staleTime);
+ statement.setInt(2, accountIdType);
+ statement.executeUpdate();
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed update pre_key store", e);
+ }
+ }
}
import java.util.List;
import java.util.Objects;
+import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_ARCHIVE_AGE;
+
public class SignedPreKeyStore implements org.signal.libsignal.protocol.state.SignedPreKeyStore {
private static final String TABLE_SIGNED_PRE_KEY = "signed_pre_key";
}
}
+ public void removeOldSignedPreKeys(int activePreKeyId) {
+ final var sql = (
+ """
+ DELETE FROM %s AS p
+ WHERE p._id IN (
+ SELECT p._id
+ FROM %s AS p
+ WHERE p.account_id_type = ?
+ AND p.key_id != ?
+ AND p.timestamp < ?
+ ORDER BY p.timestamp DESC
+ LIMIT -1 OFFSET 1
+ )
+ """
+ ).formatted(TABLE_SIGNED_PRE_KEY, TABLE_SIGNED_PRE_KEY);
+ try (final var connection = database.getConnection()) {
+ try (final var statement = connection.prepareStatement(sql)) {
+ statement.setInt(1, accountIdType);
+ statement.setInt(2, activePreKeyId);
+ statement.setLong(3, System.currentTimeMillis() - PREKEY_ARCHIVE_AGE);
+ statement.executeUpdate();
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed update signed_pre_key store", e);
+ }
+ }
+
void addLegacySignedPreKeys(final Collection<SignedPreKeyRecord> signedPreKeys) {
logger.debug("Migrating legacy signedPreKeys to database");
long start = System.nanoTime();
import org.signal.libsignal.protocol.state.IdentityKeyStore;
import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
import org.signal.libsignal.protocol.state.PreKeyRecord;
-import org.signal.libsignal.protocol.state.PreKeyStore;
import org.signal.libsignal.protocol.state.SessionRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.protocol.state.SignedPreKeyStore;
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
import org.whispersystems.signalservice.api.SignalServiceKyberPreKeyStore;
+import org.whispersystems.signalservice.api.SignalServicePreKeyStore;
import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore;
import org.whispersystems.signalservice.api.SignalServiceSessionStore;
import org.whispersystems.signalservice.api.push.DistributionId;
public class SignalProtocolStore implements SignalServiceAccountDataStore {
- private final PreKeyStore preKeyStore;
+ private final SignalServicePreKeyStore preKeyStore;
private final SignedPreKeyStore signedPreKeyStore;
private final SignalServiceKyberPreKeyStore kyberPreKeyStore;
private final SignalServiceSessionStore sessionStore;
private final Supplier<Boolean> isMultiDevice;
public SignalProtocolStore(
- final PreKeyStore preKeyStore,
+ final SignalServicePreKeyStore preKeyStore,
final SignedPreKeyStore signedPreKeyStore,
final SignalServiceKyberPreKeyStore kyberPreKeyStore,
final SignalServiceSessionStore sessionStore,
}
@Override
- public void deleteAllStaleOneTimeEcPreKeys(final long l, final int i) {
- // TODO
+ public void deleteAllStaleOneTimeEcPreKeys(final long threshold, final int minCount) {
+ preKeyStore.deleteAllStaleOneTimeEcPreKeys(threshold, minCount);
}
@Override
- public void markAllOneTimeEcPreKeysStaleIfNecessary(final long l) {
- // TODO
+ public void markAllOneTimeEcPreKeysStaleIfNecessary(final long staleTime) {
+ preKeyStore.markAllOneTimeEcPreKeysStaleIfNecessary(staleTime);
}
}