Explicitly store pni signature verified

This commit is contained in:
AsamK 2026-07-31 13:17:33 +02:00
parent e78521bb5c
commit 854cb35d15
7 changed files with 138 additions and 14 deletions

View File

@ -498,10 +498,11 @@ public final class IncomingMessageHandler {
}
logger.debug("Verified association of ACI {} with PNI {}", aci, pni);
account.getRecipientTrustedResolver()
final var recipientId = account.getRecipientTrustedResolver()
.resolveRecipientTrusted(Optional.of(ACI.from(aci.getRawUuid())),
Optional.of(pni),
senderAddress.getNumber());
account.getRecipientStore().markPniSignatureVerified(recipientId);
return true;
}

View File

@ -33,7 +33,7 @@ import java.util.UUID;
public class AccountDatabase extends Database {
private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
private static final long DATABASE_VERSION = 29;
private static final long DATABASE_VERSION = 30;
private AccountDatabase(final HikariDataSource dataSource) {
super(logger, DATABASE_VERSION, dataSource);
@ -635,6 +635,14 @@ public class AccountDatabase extends Database {
""");
}
}
if (oldVersion < 30) {
logger.debug("Updating database: Create pni_signature_verified column");
try (final var statement = connection.createStatement()) {
statement.executeUpdate("""
ALTER TABLE recipient ADD pni_signature_verified INTEGER NOT NULL DEFAULT FALSE;
""");
}
}
}
private static void createUuidMappingTable(

View File

@ -100,6 +100,7 @@ public class LegacyRecipientStore2 {
profile,
null,
null,
false,
null);
}).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));

View File

@ -25,6 +25,8 @@ public class Recipient {
private final Long unregisteredTimestamp;
private final boolean pniSignatureVerified;
private final byte[] storageRecord;
public Recipient(
@ -36,6 +38,7 @@ public class Recipient {
final Profile profile,
final Boolean discoverable,
final Long unregisteredTimestamp,
final boolean pniSignatureVerified,
final byte[] storageRecord
) {
this.recipientId = recipientId;
@ -46,6 +49,7 @@ public class Recipient {
this.profile = profile;
this.discoverable = discoverable;
this.unregisteredTimestamp = unregisteredTimestamp;
this.pniSignatureVerified = pniSignatureVerified;
this.storageRecord = storageRecord;
}
@ -58,6 +62,7 @@ public class Recipient {
profile = builder.profile;
discoverable = builder.discoverable;
unregisteredTimestamp = builder.unregisteredTimestamp;
pniSignatureVerified = builder.pniSignatureVerified;
storageRecord = builder.storageRecord;
}
@ -73,6 +78,9 @@ public class Recipient {
builder.profileKey = copy.getProfileKey();
builder.expiringProfileKeyCredential = copy.getExpiringProfileKeyCredential();
builder.profile = copy.getProfile();
builder.discoverable = copy.getDiscoverable();
builder.unregisteredTimestamp = copy.getUnregisteredTimestamp();
builder.pniSignatureVerified = copy.isPniSignatureVerified();
builder.storageRecord = copy.getStorageRecord();
return builder;
}
@ -113,6 +121,10 @@ public class Recipient {
return unregisteredTimestamp == null;
}
public boolean isPniSignatureVerified() {
return pniSignatureVerified;
}
public byte[] getStorageRecord() {
return storageRecord;
}
@ -127,12 +139,19 @@ public class Recipient {
&& Objects.equals(contact, recipient.contact)
&& Objects.equals(profileKey, recipient.profileKey)
&& Objects.equals(expiringProfileKeyCredential, recipient.expiringProfileKeyCredential)
&& Objects.equals(profile, recipient.profile);
&& Objects.equals(profile, recipient.profile)
&& pniSignatureVerified == recipient.pniSignatureVerified;
}
@Override
public int hashCode() {
return Objects.hash(recipientId, address, contact, profileKey, expiringProfileKeyCredential, profile);
return Objects.hash(recipientId,
address,
contact,
profileKey,
expiringProfileKeyCredential,
profile,
pniSignatureVerified);
}
public static final class Builder {
@ -145,6 +164,7 @@ public class Recipient {
private Profile profile;
private Boolean discoverable;
private Long unregisteredTimestamp;
private boolean pniSignatureVerified;
private byte[] storageRecord;
private Builder() {
@ -190,6 +210,11 @@ public class Recipient {
return this;
}
public Builder withPniSignatureVerified(final boolean val) {
pniSignatureVerified = val;
return this;
}
public Builder withStorageRecord(final byte[] val) {
storageRecord = val;
return this;

View File

@ -54,14 +54,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private static final int MAX_RECIPIENT_CACHE_SIZE = 2000;
private final Map<ServiceId, RecipientWithAddress> recipientAddressCache = Collections.synchronizedMap(
new LinkedHashMap<>(16, 0.75f, true) {
private final Map<ServiceId, RecipientWithAddress> recipientAddressCache = Collections.synchronizedMap(new LinkedHashMap<>(
16,
0.75f,
true) {
@Override
protected boolean removeEldestEntry(Map.Entry<ServiceId, RecipientWithAddress> eldest) {
return size() > MAX_RECIPIENT_CACHE_SIZE;
}
});
@Override
protected boolean removeEldestEntry(Map.Entry<ServiceId, RecipientWithAddress> eldest) {
return size() > MAX_RECIPIENT_CACHE_SIZE;
}
});
public static void createSql(Connection connection) throws SQLException {
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
@ -80,6 +82,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
profile_key BLOB,
profile_key_credential BLOB,
needs_pni_signature INTEGER NOT NULL DEFAULT FALSE,
pni_signature_verified INTEGER NOT NULL DEFAULT FALSE,
given_name TEXT,
family_name TEXT,
@ -372,6 +375,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
SELECT r._id,
r.number, r.aci, r.pni, r.username,
r.profile_key, r.profile_key_credential,
r.pni_signature_verified,
r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
r.discoverable,
@ -392,6 +396,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
SELECT r._id,
r.number, r.aci, r.pni, r.username,
r.profile_key, r.profile_key_credential,
r.pni_signature_verified,
r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
r.discoverable,
@ -441,6 +446,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
SELECT r._id,
r.number, r.aci, r.pni, r.username,
r.profile_key, r.profile_key_credential,
r.pni_signature_verified,
r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
r.discoverable,
@ -961,6 +967,69 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
}
}
public void markPniSignatureVerified(final RecipientId recipientId) {
logger.debug("Marking {} as pni signature verified", recipientId);
try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
if (storePniSignatureVerified(connection, recipientId, true)) {
rotateStorageId(connection, recipientId);
}
connection.commit();
} catch (SQLException e) {
throw new RuntimeException("Failed update recipient store", e);
}
}
public boolean storePniSignatureVerified(
final Connection connection,
final RecipientId recipientId,
final boolean value
) throws SQLException {
return storePniSignatureVerified(connection, recipientId, value, false);
}
private boolean storePniSignatureVerified(
final Connection connection,
final RecipientId recipientId,
final boolean value,
final boolean force
) throws SQLException {
if (!force && isPniSignatureVerified(connection, recipientId) == value) {
return false;
}
final var sql = (
"""
UPDATE %s
SET pni_signature_verified = ?
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setBoolean(1, value);
statement.setLong(2, recipientId.id());
statement.executeUpdate();
}
return true;
}
private boolean isPniSignatureVerified(
final Connection connection,
final RecipientId recipientId
) throws SQLException {
final var sql = (
"""
SELECT pni_signature_verified
FROM %s
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setLong(1, recipientId.id());
return Utils.executeQuerySingleRow(statement, resultSet -> resultSet.getBoolean("pni_signature_verified"));
}
}
public boolean needsPniSignature(final RecipientId recipientId) {
try (final var connection = database.getConnection()) {
final var sql = (
@ -1255,6 +1324,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final List<RecipientId> toBeMergedRecipientIds
) throws SQLException {
for (final var toBeMergedRecipientId : toBeMergedRecipientIds) {
if (isPniSignatureVerified(connection, toBeMergedRecipientId)) {
storePniSignatureVerified(connection, recipientId, true, true);
}
recipientMergeHandler.mergeRecipients(connection, recipientId, toBeMergedRecipientId);
deleteRecipient(connection, toBeMergedRecipientId);
recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(toBeMergedRecipientId));
@ -1349,7 +1421,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final var sql = (
"""
UPDATE %s
SET number = NULL, aci = NULL, pni = NULL, username = NULL, storage_id = NULL
SET number = NULL,
aci = NULL,
pni = NULL,
username = NULL,
storage_id = NULL,
pni_signature_verified = FALSE
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
@ -1365,10 +1442,18 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
final RecipientAddress address
) throws SQLException {
recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId));
final var existingAddress = resolveRecipientAddress(connection, recipientId);
final var keepPniSignatureVerified = Objects.equals(existingAddress.aci(), address.aci()) && Objects.equals(
existingAddress.pni(),
address.pni());
final var sql = (
"""
UPDATE %s
SET number = ?, aci = ?, pni = ?, username = ?
SET number = ?,
aci = ?,
pni = ?,
username = ?,
pni_signature_verified = ?
WHERE _id = ?
"""
).formatted(TABLE_RECIPIENT);
@ -1377,7 +1462,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
statement.setString(2, address.aci().map(ACI::toString).orElse(null));
statement.setString(3, address.pni().map(PNI::toString).orElse(null));
statement.setString(4, address.username().orElse(null));
statement.setLong(5, recipientId.id());
statement.setBoolean(5, keepPniSignatureVerified && isPniSignatureVerified(connection, recipientId));
statement.setLong(6, recipientId.id());
statement.executeUpdate();
}
rotateStorageId(connection, recipientId);
@ -1595,6 +1681,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
getProfileFromResultSet(resultSet),
getDiscoverableFromResultSet(resultSet),
getUnregisteredTimestampFromResultSet(resultSet),
resultSet.getBoolean("pni_signature_verified"),
getStorageRecordFromResultSet(resultSet));
}

View File

@ -357,6 +357,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
logger.warn("Received invalid contact identity key from storage");
}
}
account.getRecipientStore().storePniSignatureVerified(connection, recipientId, contactProto.pniSignatureVerified);
account.getRecipientStore()
.storeStorageRecord(connection, recipientId, contactRecord.getId(), contactProto.encode());
}

View File

@ -100,6 +100,7 @@ public final class StorageSyncModels {
final var builder = SignalContactRecord.Companion.newBuilder(recipient.getStorageRecord())
.e164(address.number().orElse(""))
.username(address.username().orElse(""))
.pniSignatureVerified(recipient.isPniSignatureVerified())
.profileKey(recipient.getProfileKey() == null
? ByteString.EMPTY
: ByteString.of(recipient.getProfileKey().serialize()));