1 package org
.asamk
.signal
.manager
.storage
.prekeys
;
3 import org
.asamk
.signal
.manager
.storage
.Database
;
4 import org
.asamk
.signal
.manager
.storage
.Utils
;
5 import org
.signal
.libsignal
.protocol
.InvalidKeyIdException
;
6 import org
.signal
.libsignal
.protocol
.InvalidMessageException
;
7 import org
.signal
.libsignal
.protocol
.state
.KyberPreKeyRecord
;
8 import org
.slf4j
.Logger
;
9 import org
.slf4j
.LoggerFactory
;
10 import org
.whispersystems
.signalservice
.api
.SignalServiceKyberPreKeyStore
;
11 import org
.whispersystems
.signalservice
.api
.push
.ServiceIdType
;
13 import java
.sql
.Connection
;
14 import java
.sql
.ResultSet
;
15 import java
.sql
.SQLException
;
16 import java
.util
.List
;
18 public class KyberPreKeyStore
implements SignalServiceKyberPreKeyStore
{
20 private static final String TABLE_KYBER_PRE_KEY
= "kyber_pre_key";
21 private final static Logger logger
= LoggerFactory
.getLogger(KyberPreKeyStore
.class);
23 private final Database database
;
24 private final int accountIdType
;
26 public static void createSql(Connection connection
) throws SQLException
{
27 // When modifying the CREATE statement here, also add a migration in AccountDatabase.java
28 try (final var statement
= connection
.createStatement()) {
29 statement
.executeUpdate("""
30 CREATE TABLE kyber_pre_key (
31 _id INTEGER PRIMARY KEY,
32 account_id_type INTEGER NOT NULL,
33 key_id INTEGER NOT NULL,
34 serialized BLOB NOT NULL,
35 is_last_resort INTEGER NOT NULL,
36 UNIQUE(account_id_type, key_id)
42 public KyberPreKeyStore(final Database database
, final ServiceIdType serviceIdType
) {
43 this.database
= database
;
44 this.accountIdType
= Utils
.getAccountIdType(serviceIdType
);
48 public KyberPreKeyRecord
loadKyberPreKey(final int keyId
) throws InvalidKeyIdException
{
49 final var kyberPreKey
= getPreKey(keyId
);
50 if (kyberPreKey
== null) {
51 throw new InvalidKeyIdException("No such kyber pre key record: " + keyId
);
57 public List
<KyberPreKeyRecord
> loadKyberPreKeys() {
62 WHERE p.account_id_type = ?
64 ).formatted(TABLE_KYBER_PRE_KEY
);
65 try (final var connection
= database
.getConnection()) {
66 try (final var statement
= connection
.prepareStatement(sql
)) {
67 statement
.setInt(1, accountIdType
);
68 return Utils
.executeQueryForStream(statement
, this::getKyberPreKeyRecordFromResultSet
).toList();
70 } catch (SQLException e
) {
71 throw new RuntimeException("Failed read from kyber_pre_key store", e
);
76 public List
<KyberPreKeyRecord
> loadLastResortKyberPreKeys() {
81 WHERE p.account_id_type = ? AND p.is_last_resort = TRUE
83 ).formatted(TABLE_KYBER_PRE_KEY
);
84 try (final var connection
= database
.getConnection()) {
85 try (final var statement
= connection
.prepareStatement(sql
)) {
86 statement
.setInt(1, accountIdType
);
87 return Utils
.executeQueryForStream(statement
, this::getKyberPreKeyRecordFromResultSet
).toList();
89 } catch (SQLException e
) {
90 throw new RuntimeException("Failed read from kyber_pre_key store", e
);
95 public void storeLastResortKyberPreKey(final int keyId
, final KyberPreKeyRecord
record) {
96 storeKyberPreKey(keyId
, record, true);
100 public void storeKyberPreKey(final int keyId
, final KyberPreKeyRecord
record) {
101 storeKyberPreKey(keyId
, record, false);
104 public void storeKyberPreKey(final int keyId
, final KyberPreKeyRecord
record, final boolean isLastResort
) {
107 INSERT INTO %s (account_id_type, key_id, serialized, is_last_resort)
110 ).formatted(TABLE_KYBER_PRE_KEY
);
111 try (final var connection
= database
.getConnection()) {
112 try (final var statement
= connection
.prepareStatement(sql
)) {
113 statement
.setInt(1, accountIdType
);
114 statement
.setInt(2, keyId
);
115 statement
.setBytes(3, record.serialize());
116 statement
.setBoolean(4, isLastResort
);
117 statement
.executeUpdate();
119 } catch (SQLException e
) {
120 throw new RuntimeException("Failed update kyber_pre_key store", e
);
125 public boolean containsKyberPreKey(final int keyId
) {
126 return getPreKey(keyId
) != null;
130 public void markKyberPreKeyUsed(final int keyId
) {
134 WHERE p.account_id_type = ? AND p.key_id = ? AND p.is_last_resort = FALSE
136 ).formatted(TABLE_KYBER_PRE_KEY
);
137 try (final var connection
= database
.getConnection()) {
138 try (final var statement
= connection
.prepareStatement(sql
)) {
139 statement
.setInt(1, accountIdType
);
140 statement
.setInt(2, keyId
);
141 statement
.executeUpdate();
143 } catch (SQLException e
) {
144 throw new RuntimeException("Failed update kyber_pre_key store", e
);
149 public void removeKyberPreKey(final int keyId
) {
153 WHERE p.account_id_type = ? AND p.key_id = ?
155 ).formatted(TABLE_KYBER_PRE_KEY
);
156 try (final var connection
= database
.getConnection()) {
157 try (final var statement
= connection
.prepareStatement(sql
)) {
158 statement
.setInt(1, accountIdType
);
159 statement
.setInt(2, keyId
);
160 statement
.executeUpdate();
162 } catch (SQLException e
) {
163 throw new RuntimeException("Failed update kyber_pre_key store", e
);
167 public void removeAllKyberPreKeys() {
171 WHERE p.account_id_type = ?
173 ).formatted(TABLE_KYBER_PRE_KEY
);
174 try (final var connection
= database
.getConnection()) {
175 try (final var statement
= connection
.prepareStatement(sql
)) {
176 statement
.setInt(1, accountIdType
);
177 statement
.executeUpdate();
179 } catch (SQLException e
) {
180 throw new RuntimeException("Failed update kyber_pre_key store", e
);
184 private KyberPreKeyRecord
getPreKey(int keyId
) {
189 WHERE p.account_id_type = ? AND p.key_id = ?
191 ).formatted(TABLE_KYBER_PRE_KEY
);
192 try (final var connection
= database
.getConnection()) {
193 try (final var statement
= connection
.prepareStatement(sql
)) {
194 statement
.setInt(1, accountIdType
);
195 statement
.setInt(2, keyId
);
196 return Utils
.executeQueryForOptional(statement
, this::getKyberPreKeyRecordFromResultSet
).orElse(null);
198 } catch (SQLException e
) {
199 throw new RuntimeException("Failed read from kyber_pre_key store", e
);
203 private KyberPreKeyRecord
getKyberPreKeyRecordFromResultSet(ResultSet resultSet
) throws SQLException
{
205 final var serialized
= resultSet
.getBytes("serialized");
206 return new KyberPreKeyRecord(serialized
);
207 } catch (InvalidMessageException e
) {
213 public void deleteAllStaleOneTimeKyberPreKeys(final long threshold
, final int minCount
) {
218 public void markAllOneTimeKyberPreKeysStaleIfNecessary(final long staleTime
) {