"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "methods":[{"name":"<init>","parameterTypes":[] }]
+ "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getId","parameterTypes":[] }, {"name":"getJsonrpc","parameterTypes":[] }, {"name":"getMethod","parameterTypes":[] }, {"name":"getParams","parameterTypes":[] }]
},
{
"name":"org.asamk.signal.jsonrpc.JsonRpcResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "methods":[{"name":"<init>","parameterTypes":[] }]
+ "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getSignature","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArrayDeserializer",
"name":"org.whispersystems.signalservice.internal.contacts.crypto.SignatureBodyEntity",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest",
"name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"getData","parameterTypes":[] }, {"name":"getIv","parameterTypes":[] }, {"name":"getMac","parameterTypes":[] }, {"name":"getRequestId","parameterTypes":[] }, {"name":"getType","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.MultiRemoteAttestationResponse",
"name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationRequest",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"getClientPublic","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.TokenResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.devices.DeviceNameProtos$DeviceName",
"name":"org.whispersystems.signalservice.internal.push.AuthCredentials",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.push.CdsiAuthResponse",
"allDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
+{
+ "name":"org.whispersystems.signalservice.internal.push.GcmRegistrationId",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true,
+ "queryAllDeclaredConstructors":true
+},
{
"name":"org.whispersystems.signalservice.internal.push.GetAciByUsernameResponse",
"allDeclaredFields":true,
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "methods":[{"name":"<init>","parameterTypes":[] }]
+ "methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getKeyId","parameterTypes":[] }, {"name":"getPublicKey","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeyDeserializer",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
- "methods":[{"name":"getAccountAttributes","parameterTypes":[] }, {"name":"getRecoveryPassword","parameterTypes":[] }, {"name":"getSessionId","parameterTypes":[] }, {"name":"getSkipDeviceTransfer","parameterTypes":[] }]
+ "methods":[{"name":"getAccountAttributes","parameterTypes":[] }, {"name":"getAciIdentityKey","parameterTypes":[] }, {"name":"getAciPqLastResortPreKey","parameterTypes":[] }, {"name":"getAciSignedPreKey","parameterTypes":[] }, {"name":"getGcmToken","parameterTypes":[] }, {"name":"getPniIdentityKey","parameterTypes":[] }, {"name":"getPniPqLastResortPreKey","parameterTypes":[] }, {"name":"getPniSignedPreKey","parameterTypes":[] }, {"name":"getRecoveryPassword","parameterTypes":[] }, {"name":"getSessionId","parameterTypes":[] }, {"name":"getSkipDeviceTransfer","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.push.ReserveUsernameRequest",
"name":"org.whispersystems.signalservice.internal.push.VerifyAccountResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
+ "allDeclaredConstructors":true,
+ "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.whispersystems.signalservice.internal.push.WhoAmIResponse",
account.setNumber(number);
account.setAci(aci);
account.setPni(pni);
- if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null && account.getPni() != null) {
+ if (account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
}
account.getRecipientTrustedResolver().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
public void refreshPreKeysIfNecessary(ServiceIdType serviceIdType) throws IOException {
final var preKeyCounts = dependencies.getAccountManager().getPreKeyCounts(serviceIdType);
if (preKeyCounts.getEcCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+ logger.debug("Refreshing {} ec pre keys, because only {} of {} 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 {} pre keys remain",
+ serviceIdType,
+ preKeyCounts.getEcCount(),
+ ServiceConfig.PREKEY_MINIMUM_COUNT);
refreshKyberPreKeys(serviceIdType);
}
}
- public void refreshPreKeys() throws IOException {
- refreshPreKeys(ServiceIdType.ACI);
- refreshPreKeys(ServiceIdType.PNI);
- }
-
private void refreshPreKeys(ServiceIdType serviceIdType) throws IOException {
final var identityKeyPair = account.getIdentityKeyPair(serviceIdType);
if (identityKeyPair == null) {
private List<PreKeyRecord> generatePreKeys(ServiceIdType serviceIdType) {
final var offset = account.getPreKeyIdOffset(serviceIdType);
- var records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
+ var records = KeyUtils.generatePreKeyRecords(offset);
account.addPreKeys(serviceIdType, records);
return records;
private SignedPreKeyRecord generateSignedPreKey(ServiceIdType serviceIdType, IdentityKeyPair identityKeyPair) {
final var signedPreKeyId = account.getNextSignedPreKeyId(serviceIdType);
- var record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
+ var record = KeyUtils.generateSignedPreKeyRecord(signedPreKeyId, identityKeyPair);
account.addSignedPreKey(serviceIdType, record);
return record;
) {
final var offset = account.getKyberPreKeyIdOffset(serviceIdType);
- var records = KeyUtils.generateKyberPreKeyRecords(offset,
- ServiceConfig.PREKEY_BATCH_SIZE,
- identityKeyPair.getPrivateKey());
+ var records = KeyUtils.generateKyberPreKeyRecords(offset, identityKeyPair.getPrivateKey());
account.addKyberPreKeys(serviceIdType, records);
return records;
}
void refreshPreKeys() throws IOException {
- context.getPreKeyHelper().refreshPreKeys();
+ context.getPreKeyHelper().refreshPreKeysIfNecessary();
}
@Override
import org.asamk.signal.manager.helper.AccountFileUpdater;
import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.storage.SignalAccount;
+import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.NumberVerificationUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
+import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
+import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
import java.io.IOException;
import java.util.function.Consumer;
+import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
+
public class RegistrationManagerImpl implements RegistrationManager {
private final static Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
public void verifyAccount(
String verificationCode, String pin
) throws IOException, PinLockedException, IncorrectPinException {
- var sessionId = account.getSessionId(account.getNumber());
- final var result = NumberVerificationUtils.verifyNumber(sessionId,
+ if (account.getPniIdentityKeyPair() == null) {
+ account.setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
+ }
+
+ final var aciPreKeys = generatePreKeysForType(ServiceIdType.ACI);
+ final var pniPreKeys = generatePreKeysForType(ServiceIdType.PNI);
+ final var result = NumberVerificationUtils.verifyNumber(account.getSessionId(account.getNumber()),
verificationCode,
pin,
pinHelper,
- this::verifyAccountWithCode);
+ (sessionId1, verificationCode1, registrationLock) -> verifyAccountWithCode(sessionId1,
+ verificationCode1,
+ registrationLock,
+ aciPreKeys,
+ pniPreKeys));
final var response = result.first();
final var masterKey = result.second();
if (masterKey == null) {
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
final var aci = ACI.parseOrNull(response.getUuid());
final var pni = PNI.parseOrNull(response.getPni());
- account.finishRegistration(aci, pni, masterKey, pin);
+ account.finishRegistration(aci, pni, masterKey, pin, aciPreKeys, pniPreKeys);
accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
ManagerImpl m = null;
}
private VerifyAccountResponse verifyAccountWithCode(
- final String sessionId, final String verificationCode, final String registrationLock
+ final String sessionId,
+ final String verificationCode,
+ final String registrationLock,
+ final PreKeyCollection aciPreKeys,
+ final PreKeyCollection pniPreKeys
) throws IOException {
try {
Utils.handleResponseException(accountManager.verifyAccount(verificationCode, sessionId));
return Utils.handleResponseException(accountManager.registerAccount(sessionId,
null,
account.getAccountAttributes(registrationLock),
+ aciPreKeys,
+ pniPreKeys,
+ null,
true));
}
+ private PreKeyCollection generatePreKeysForType(ServiceIdType serviceIdType) {
+ final var accountData = account.getAccountData(serviceIdType);
+ final var keyPair = accountData.getIdentityKeyPair();
+ final var preKeyMetadata = accountData.getPreKeyMetadata();
+
+ final var preKeyIdOffset = preKeyMetadata.getPreKeyIdOffset();
+ final var oneTimeEcPreKeys = KeyUtils.generatePreKeyRecords(preKeyIdOffset);
+ final var nextSignedPreKeyId = preKeyMetadata.getNextSignedPreKeyId();
+ final var signedPreKey = KeyUtils.generateSignedPreKeyRecord(nextSignedPreKeyId, keyPair);
+
+ final var privateKey = keyPair.getPrivateKey();
+ final var kyberPreKeyIdOffset = preKeyMetadata.getKyberPreKeyIdOffset();
+ final var oneTimeKyberPreKeys = KeyUtils.generateKyberPreKeyRecords(kyberPreKeyIdOffset, privateKey);
+ final var lastResortKyberPreKeyId = (kyberPreKeyIdOffset + oneTimeKyberPreKeys.size()) % PREKEY_MAXIMUM_ID;
+ final var lastResortKyberPreKey = KeyUtils.generateKyberPreKeyRecord(lastResortKyberPreKeyId, privateKey);
+
+ return new PreKeyCollection(keyPair,
+ nextSignedPreKeyId,
+ preKeyIdOffset,
+ lastResortKyberPreKeyId,
+ kyberPreKeyIdOffset,
+ serviceIdType,
+ keyPair.getPublicKey(),
+ signedPreKey,
+ oneTimeEcPreKeys,
+ lastResortKyberPreKey,
+ oneTimeKyberPreKeys);
+ }
+
@Override
public void close() {
if (account != null) {
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.account.AccountAttributes;
+import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.ACI;
save();
}
+ private void setPreKeys(ServiceIdType serviceIdType, PreKeyCollection preKeyCollection) {
+ final var accountData = getAccountData(serviceIdType);
+ final var preKeyMetadata = accountData.getPreKeyMetadata();
+ preKeyMetadata.nextSignedPreKeyId = preKeyCollection.getNextSignedPreKeyId();
+ preKeyMetadata.preKeyIdOffset = preKeyCollection.getEcOneTimePreKeyIdOffset();
+ preKeyMetadata.kyberPreKeyIdOffset = preKeyCollection.getOneTimeKyberPreKeyIdOffset();
+ preKeyMetadata.activeLastResortKyberPreKeyId = preKeyCollection.getLastResortKyberPreKeyId();
+
+ accountData.getPreKeyStore().removeAllPreKeys();
+ accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
+ accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
+
+ addPreKeys(serviceIdType, preKeyCollection.getOneTimeEcPreKeys());
+ addSignedPreKey(serviceIdType, preKeyCollection.getSignedPreKey());
+ addKyberPreKeys(serviceIdType, preKeyCollection.getOneTimeKyberPreKeys());
+ addLastResortKyberPreKey(serviceIdType, preKeyCollection.getLastResortKyberPreKey());
+
+ save();
+ }
+
private static SignalAccount createLinkedAccount(
File dataPath,
String accountPath,
setProfileKey(KeyUtils.createProfileKey());
}
getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
- if (isPrimaryDevice() && getPniIdentityKeyPair() == null && getPni() != null) {
+ if (isPrimaryDevice() && getPniIdentityKeyPair() == null) {
setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
}
}
public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
final var accountData = getAccountData(serviceIdType);
final var preKeyMetadata = accountData.getPreKeyMetadata();
+ logger.debug("Adding {} {} pre keys with offset {}",
+ records.size(),
+ serviceIdType,
+ preKeyMetadata.preKeyIdOffset);
for (var record : records) {
if (preKeyMetadata.preKeyIdOffset != record.getId()) {
logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyMetadata.preKeyIdOffset);
public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
final var accountData = getAccountData(serviceIdType);
final var preKeyMetadata = accountData.getPreKeyMetadata();
+ logger.debug("Adding {} signed pre key with offset {}", serviceIdType, preKeyMetadata.nextSignedPreKeyId);
if (preKeyMetadata.nextSignedPreKeyId != record.getId()) {
logger.error("Invalid signed pre key id {}, expected {}",
record.getId(),
public void addKyberPreKeys(ServiceIdType serviceIdType, List<KyberPreKeyRecord> records) {
final var accountData = getAccountData(serviceIdType);
final var preKeyMetadata = accountData.getPreKeyMetadata();
+ logger.debug("Adding {} {} kyber pre keys with offset {}",
+ records.size(),
+ serviceIdType,
+ preKeyMetadata.kyberPreKeyIdOffset);
for (var record : records) {
if (preKeyMetadata.kyberPreKeyIdOffset != record.getId()) {
logger.error("Invalid kyber pre key id {}, expected {}",
public void addLastResortKyberPreKey(ServiceIdType serviceIdType, KyberPreKeyRecord record) {
final var accountData = getAccountData(serviceIdType);
final var preKeyMetadata = accountData.getPreKeyMetadata();
+ logger.debug("Adding {} last resort kyber pre key with offset {}",
+ serviceIdType,
+ preKeyMetadata.kyberPreKeyIdOffset);
if (preKeyMetadata.kyberPreKeyIdOffset != record.getId()) {
logger.error("Invalid last resort kyber pre key id {}, expected {}",
record.getId(),
return phoneNumberUnlisted == null || !phoneNumberUnlisted;
}
- public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
+ public void finishRegistration(
+ final ACI aci,
+ final PNI pni,
+ final MasterKey masterKey,
+ final String pin,
+ final PreKeyCollection aciPreKeys,
+ final PreKeyCollection pniPreKeys
+ ) {
this.pinMasterKey = masterKey;
this.storageManifestVersion = -1;
this.setStorageManifest(null);
this.lastReceiveTimestamp = 0;
save();
- clearAllPreKeys();
+ setPreKeys(ServiceIdType.ACI, aciPreKeys);
+ setPreKeys(ServiceIdType.PNI, pniPreKeys);
aciAccountData.getSessionStore().archiveAllSessions();
pniAccountData.getSessionStore().archiveAllSessions();
getSenderKeyStore().deleteAll();
getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
trustSelfIdentity(ServiceIdType.ACI);
- if (getPniIdentityKeyPair() == null) {
- setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
- } else {
- trustSelfIdentity(ServiceIdType.PNI);
- }
+ trustSelfIdentity(ServiceIdType.PNI);
}
private void trustSelfIdentity(ServiceIdType serviceIdType) {
void call();
}
- private static class PreKeyMetadata {
+ public static class PreKeyMetadata {
private int preKeyIdOffset = 1;
private int nextSignedPreKeyId = 1;
private int kyberPreKeyIdOffset = 1;
private int activeLastResortKyberPreKeyId = -1;
+
+ public int getPreKeyIdOffset() {
+ return preKeyIdOffset;
+ }
+
+ public int getNextSignedPreKeyId() {
+ return nextSignedPreKeyId;
+ }
+
+ public int getKyberPreKeyIdOffset() {
+ return kyberPreKeyIdOffset;
+ }
+
+ public int getActiveLastResortKyberPreKeyId() {
+ return activeLastResortKyberPreKeyId;
+ }
}
public class AccountData {
import java.util.Base64;
import java.util.List;
+import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_BATCH_SIZE;
import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
public class KeyUtils {
return new IdentityKeyPair(djbIdentityKey, djbPrivateKey);
}
- public static List<PreKeyRecord> generatePreKeyRecords(final int offset, final int batchSize) {
- var records = new ArrayList<PreKeyRecord>(batchSize);
- for (var i = 0; i < batchSize; i++) {
+ public static List<PreKeyRecord> generatePreKeyRecords(final int offset) {
+ var records = new ArrayList<PreKeyRecord>(PREKEY_BATCH_SIZE);
+ for (var i = 0; i < PREKEY_BATCH_SIZE; i++) {
var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID;
var keyPair = Curve.generateKeyPair();
var record = new PreKeyRecord(preKeyId, keyPair);
}
public static SignedPreKeyRecord generateSignedPreKeyRecord(
- final IdentityKeyPair identityKeyPair, final int signedPreKeyId
+ final int signedPreKeyId, final IdentityKeyPair identityKeyPair
) {
var keyPair = Curve.generateKeyPair();
byte[] signature;
}
public static List<KyberPreKeyRecord> generateKyberPreKeyRecords(
- final int offset, final int batchSize, final ECPrivateKey privateKey
+ final int offset, final ECPrivateKey privateKey
) {
- var records = new ArrayList<KyberPreKeyRecord>(batchSize);
- for (var i = 0; i < batchSize; i++) {
+ var records = new ArrayList<KyberPreKeyRecord>(PREKEY_BATCH_SIZE);
+ for (var i = 0; i < PREKEY_BATCH_SIZE; i++) {
var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID;
records.add(generateKyberPreKeyRecord(preKeyId, privateKey));
}
library("logback", "ch.qos.logback", "logback-classic").version("1.4.8")
- library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_73")
+ library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_74")
library("protobuf", "com.google.protobuf", "protobuf-javalite").version("3.23.0")
library("sqlite", "org.xerial", "sqlite-jdbc").version("3.42.0.0")
library("hikari", "com.zaxxer", "HikariCP").version("5.0.1")