mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
parent
a52f6a6657
commit
425626ef94
10 changed files with 254 additions and 44 deletions
|
@ -18,7 +18,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_15'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.67'
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.68'
|
||||
implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1'
|
||||
implementation 'com.github.hypfvieh:dbus-java:3.2.4'
|
||||
implementation 'org.slf4j:slf4j-simple:1.7.30'
|
||||
|
|
|
@ -5,6 +5,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
|
|||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -23,7 +24,7 @@ public class RemovePinCommand implements LocalCommand {
|
|||
try {
|
||||
m.setRegistrationLockPin(Optional.absent());
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | UnauthenticatedResponseException e) {
|
||||
System.err.println("Remove pin error: " + e.getMessage());
|
||||
return 3;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
|
|||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -26,7 +27,7 @@ public class SetPinCommand implements LocalCommand {
|
|||
String registrationLockPin = ns.getString("registrationLockPin");
|
||||
m.setRegistrationLockPin(Optional.of(registrationLockPin));
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | UnauthenticatedResponseException e) {
|
||||
System.err.println("Set pin error: " + e.getMessage());
|
||||
return 3;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import net.sourceforge.argparse4j.inf.Namespace;
|
|||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -31,6 +33,12 @@ public class VerifyCommand implements LocalCommand {
|
|||
System.err.println("Verification failed! This number is locked with a pin. Hours remaining until reset: "
|
||||
+ (e.getTimeRemaining() / 1000 / 60 / 60));
|
||||
System.err.println("Use '--pin PIN_CODE' to specify the registration lock PIN");
|
||||
return 1;
|
||||
} catch (KeyBackupServicePinException e) {
|
||||
System.err.println("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining());
|
||||
return 1;
|
||||
} catch (KeyBackupSystemNoDataException e) {
|
||||
System.err.println("Verification failed! No KBS data.");
|
||||
return 3;
|
||||
} catch (IOException e) {
|
||||
System.err.println("Verify error: " + e.getMessage());
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.asamk.signal.manager.groups.GroupNotFoundException;
|
|||
import org.asamk.signal.manager.groups.GroupUtils;
|
||||
import org.asamk.signal.manager.groups.NotAGroupMemberException;
|
||||
import org.asamk.signal.manager.helper.GroupHelper;
|
||||
import org.asamk.signal.manager.helper.PinHelper;
|
||||
import org.asamk.signal.manager.helper.ProfileHelper;
|
||||
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
|
||||
import org.asamk.signal.manager.storage.SignalAccount;
|
||||
|
@ -82,6 +83,10 @@ import org.whispersystems.libsignal.util.KeyHelper;
|
|||
import org.whispersystems.libsignal.util.Medium;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
|
@ -96,6 +101,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException
|
|||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
|
@ -138,6 +144,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalServiceConf
|
|||
import org.whispersystems.signalservice.internal.contacts.crypto.Quote;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedQuoteException;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||
|
@ -160,6 +167,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -193,6 +201,8 @@ public class Manager implements Closeable {
|
|||
|
||||
private final SignalServiceConfiguration serviceConfiguration;
|
||||
private final String userAgent;
|
||||
|
||||
// TODO make configurable
|
||||
private final boolean discoverableByPhoneNumber = true;
|
||||
private final boolean unrestrictedUnidentifiedAccess = false;
|
||||
|
||||
|
@ -209,6 +219,7 @@ public class Manager implements Closeable {
|
|||
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
|
||||
private final ProfileHelper profileHelper;
|
||||
private final GroupHelper groupHelper;
|
||||
private PinHelper pinHelper;
|
||||
|
||||
Manager(
|
||||
SignalAccount account,
|
||||
|
@ -222,8 +233,7 @@ public class Manager implements Closeable {
|
|||
this.userAgent = userAgent;
|
||||
this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create(
|
||||
serviceConfiguration)) : null;
|
||||
this.accountManager = createSignalServiceAccountManager();
|
||||
this.groupsV2Api = accountManager.getGroupsV2Api();
|
||||
createSignalServiceAccountManager();
|
||||
|
||||
this.account.setResolver(this::resolveSignalServiceAddress);
|
||||
|
||||
|
@ -251,8 +261,8 @@ public class Manager implements Closeable {
|
|||
return account.getSelfAddress();
|
||||
}
|
||||
|
||||
private SignalServiceAccountManager createSignalServiceAccountManager() {
|
||||
return new SignalServiceAccountManager(serviceConfiguration,
|
||||
private void createSignalServiceAccountManager() {
|
||||
this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
|
||||
new DynamicCredentialsProvider(account.getUuid(),
|
||||
account.getUsername(),
|
||||
account.getPassword(),
|
||||
|
@ -261,6 +271,18 @@ public class Manager implements Closeable {
|
|||
userAgent,
|
||||
groupsV2Operations,
|
||||
timer);
|
||||
this.groupsV2Api = accountManager.getGroupsV2Api();
|
||||
this.pinHelper = new PinHelper(createKeyBackupService());
|
||||
}
|
||||
|
||||
private KeyBackupService createKeyBackupService() {
|
||||
KeyStore keyStore = ServiceConfig.getIasKeyStore();
|
||||
|
||||
return accountManager.getKeyBackupService(keyStore,
|
||||
ServiceConfig.KEY_BACKUP_ENCLAVE_NAME,
|
||||
ServiceConfig.KEY_BACKUP_SERVICE_ID,
|
||||
ServiceConfig.KEY_BACKUP_MRENCLAVE,
|
||||
10);
|
||||
}
|
||||
|
||||
private IdentityKeyPair getIdentityKeyPair() {
|
||||
|
@ -366,8 +388,7 @@ public class Manager implements Closeable {
|
|||
|
||||
// Resetting UUID, because registering doesn't work otherwise
|
||||
account.setUuid(null);
|
||||
accountManager = createSignalServiceAccountManager();
|
||||
this.groupsV2Api = accountManager.getGroupsV2Api();
|
||||
createSignalServiceAccountManager();
|
||||
|
||||
if (voiceVerification) {
|
||||
accountManager.requestVoiceVerificationCode(Locale.getDefault(),
|
||||
|
@ -385,8 +406,9 @@ public class Manager implements Closeable {
|
|||
accountManager.setAccountAttributes(account.getSignalingKey(),
|
||||
account.getSignalProtocolStore().getLocalRegistrationId(),
|
||||
true,
|
||||
account.getRegistrationLockPin(),
|
||||
account.getRegistrationLock(),
|
||||
// set legacy pin only if no KBS master key is set
|
||||
account.getPinMasterKey() == null ? account.getRegistrationLockPin() : null,
|
||||
account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
|
||||
unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
|
||||
unrestrictedUnidentifiedAccess,
|
||||
capabilities,
|
||||
|
@ -479,26 +501,39 @@ public class Manager implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
public void verifyAccount(String verificationCode, String pin) throws IOException {
|
||||
public void verifyAccount(
|
||||
String verificationCode,
|
||||
String pin
|
||||
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
|
||||
verificationCode = verificationCode.replace("-", "");
|
||||
account.setSignalingKey(KeyUtils.createSignalingKey());
|
||||
// TODO make unrestricted unidentified access configurable
|
||||
VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode,
|
||||
account.getSignalingKey(),
|
||||
account.getSignalProtocolStore().getLocalRegistrationId(),
|
||||
true,
|
||||
pin,
|
||||
null,
|
||||
unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
|
||||
unrestrictedUnidentifiedAccess,
|
||||
capabilities,
|
||||
discoverableByPhoneNumber);
|
||||
VerifyAccountResponse response;
|
||||
try {
|
||||
response = verifyAccountWithCode(verificationCode, pin, null);
|
||||
} catch (LockedException e) {
|
||||
if (pin == null) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
KbsPinData registrationLockData = pinHelper.getRegistrationLockData(pin, e);
|
||||
if (registrationLockData == null) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
String registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock();
|
||||
try {
|
||||
response = verifyAccountWithCode(verificationCode, null, registrationLock);
|
||||
} catch (LockedException _e) {
|
||||
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
|
||||
}
|
||||
account.setPinMasterKey(registrationLockData.getMasterKey());
|
||||
}
|
||||
|
||||
UUID uuid = UuidUtil.parseOrNull(response.getUuid());
|
||||
// TODO response.isStorageCapable()
|
||||
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
|
||||
|
||||
account.setRegistered(true);
|
||||
account.setUuid(uuid);
|
||||
account.setUuid(UuidUtil.parseOrNull(response.getUuid()));
|
||||
account.setRegistrationLockPin(pin);
|
||||
account.getSignalProtocolStore()
|
||||
.saveIdentity(account.getSelfAddress(),
|
||||
|
@ -509,13 +544,40 @@ public class Manager implements Closeable {
|
|||
account.save();
|
||||
}
|
||||
|
||||
public void setRegistrationLockPin(Optional<String> pin) throws IOException {
|
||||
private VerifyAccountResponse verifyAccountWithCode(
|
||||
final String verificationCode, final String legacyPin, final String registrationLock
|
||||
) throws IOException {
|
||||
return accountManager.verifyAccountWithCode(verificationCode,
|
||||
account.getSignalingKey(),
|
||||
account.getSignalProtocolStore().getLocalRegistrationId(),
|
||||
true,
|
||||
legacyPin,
|
||||
registrationLock,
|
||||
unidentifiedAccessHelper.getSelfUnidentifiedAccessKey(),
|
||||
unrestrictedUnidentifiedAccess,
|
||||
capabilities,
|
||||
discoverableByPhoneNumber);
|
||||
}
|
||||
|
||||
public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
|
||||
if (pin.isPresent()) {
|
||||
final MasterKey masterKey = account.getPinMasterKey() != null
|
||||
? account.getPinMasterKey()
|
||||
: KeyUtils.createMasterKey();
|
||||
|
||||
pinHelper.setRegistrationLockPin(pin.get(), masterKey);
|
||||
|
||||
account.setRegistrationLockPin(pin.get());
|
||||
throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
|
||||
account.setPinMasterKey(masterKey);
|
||||
} else {
|
||||
account.setRegistrationLockPin(null);
|
||||
// Remove legacy registration lock
|
||||
accountManager.removeRegistrationLockV1();
|
||||
|
||||
// Remove KBS Pin
|
||||
pinHelper.removeRegistrationLockPin();
|
||||
|
||||
account.setRegistrationLockPin(null);
|
||||
account.setPinMasterKey(null);
|
||||
}
|
||||
account.save();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.asamk.signal.manager;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.signal.zkgroup.ServerPublicParams;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
|
@ -13,13 +14,13 @@ import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupSe
|
|||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -28,7 +29,8 @@ import okhttp3.Interceptor;
|
|||
|
||||
public class ServiceConfig {
|
||||
|
||||
final static String UNIDENTIFIED_SENDER_TRUST_ROOT = "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF";
|
||||
final static byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder()
|
||||
.decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF");
|
||||
final static int PREKEY_MINIMUM_COUNT = 20;
|
||||
final static int PREKEY_BATCH_SIZE = 100;
|
||||
final static int MAX_ATTACHMENT_SIZE = 150 * 1024 * 1024;
|
||||
|
@ -37,6 +39,11 @@ public class ServiceConfig {
|
|||
|
||||
final static String CDS_MRENCLAVE = "c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15";
|
||||
|
||||
final static String KEY_BACKUP_ENCLAVE_NAME = "fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe";
|
||||
final static byte[] KEY_BACKUP_SERVICE_ID = Hex.decode(
|
||||
"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe");
|
||||
final static String KEY_BACKUP_MRENCLAVE = "a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87";
|
||||
|
||||
private final static String URL = "https://textsecure-service.whispersystems.org";
|
||||
private final static String CDN_URL = "https://cdn.signal.org";
|
||||
private final static String CDN2_URL = "https://cdn2.signal.org";
|
||||
|
@ -48,18 +55,12 @@ public class ServiceConfig {
|
|||
|
||||
private final static Optional<Dns> dns = Optional.absent();
|
||||
|
||||
private final static String zkGroupServerPublicParamsHex = "AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=";
|
||||
private final static byte[] zkGroupServerPublicParams;
|
||||
private final static byte[] zkGroupServerPublicParams = Base64.getDecoder()
|
||||
.decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=");
|
||||
|
||||
static final AccountAttributes.Capabilities capabilities;
|
||||
|
||||
static {
|
||||
try {
|
||||
zkGroupServerPublicParams = Base64.decode(zkGroupServerPublicParamsHex);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
boolean zkGroupAvailable;
|
||||
try {
|
||||
new ServerPublicParams(zkGroupServerPublicParams);
|
||||
|
@ -110,8 +111,8 @@ public class ServiceConfig {
|
|||
|
||||
static ECPublicKey getUnidentifiedSenderTrustRoot() {
|
||||
try {
|
||||
return Curve.decodePoint(Base64.decode(UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
|
90
src/main/java/org/asamk/signal/manager/helper/PinHelper.java
Normal file
90
src/main/java/org/asamk/signal/manager/helper/PinHelper.java
Normal file
|
@ -0,0 +1,90 @@
|
|||
package org.asamk.signal.manager.helper;
|
||||
|
||||
import org.asamk.signal.manager.util.PinHashing;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
|
||||
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PinHelper {
|
||||
|
||||
private final KeyBackupService keyBackupService;
|
||||
|
||||
public PinHelper(final KeyBackupService keyBackupService) {
|
||||
this.keyBackupService = keyBackupService;
|
||||
}
|
||||
|
||||
public void setRegistrationLockPin(
|
||||
String pin, MasterKey masterKey
|
||||
) throws IOException, UnauthenticatedResponseException {
|
||||
final KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
final HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
|
||||
|
||||
pinChangeSession.setPin(hashedPin, masterKey);
|
||||
pinChangeSession.enableRegistrationLock(masterKey);
|
||||
}
|
||||
|
||||
public void removeRegistrationLockPin() throws IOException, UnauthenticatedResponseException {
|
||||
final KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
|
||||
pinChangeSession.removePin();
|
||||
}
|
||||
|
||||
public KbsPinData getRegistrationLockData(
|
||||
String pin,
|
||||
LockedException e
|
||||
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
|
||||
String basicStorageCredentials = e.getBasicStorageCredentials();
|
||||
if (basicStorageCredentials == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getRegistrationLockData(pin, basicStorageCredentials);
|
||||
}
|
||||
|
||||
private KbsPinData getRegistrationLockData(
|
||||
String pin,
|
||||
String basicStorageCredentials
|
||||
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
|
||||
TokenResponse tokenResponse = keyBackupService.getToken(basicStorageCredentials);
|
||||
if (tokenResponse == null || tokenResponse.getTries() == 0) {
|
||||
throw new IOException("KBS Account locked");
|
||||
}
|
||||
|
||||
KbsPinData registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse);
|
||||
if (registrationLockData == null) {
|
||||
throw new AssertionError("Failed to restore master key");
|
||||
}
|
||||
return registrationLockData;
|
||||
}
|
||||
|
||||
private KbsPinData restoreMasterKey(
|
||||
String pin, String basicStorageCredentials, TokenResponse tokenResponse
|
||||
) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
|
||||
if (pin == null) return null;
|
||||
|
||||
if (basicStorageCredentials == null) {
|
||||
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
|
||||
}
|
||||
|
||||
KeyBackupService.RestoreSession session = keyBackupService.newRegistrationSession(basicStorageCredentials,
|
||||
tokenResponse);
|
||||
|
||||
try {
|
||||
HashedPin hashedPin = PinHashing.hashPin(pin, session);
|
||||
KbsPinData kbsData = session.restorePin(hashedPin);
|
||||
if (kbsData == null) {
|
||||
throw new AssertionError("Null not expected");
|
||||
}
|
||||
return kbsData;
|
||||
} catch (UnauthenticatedResponseException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ import org.whispersystems.libsignal.state.PreKeyRecord;
|
|||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.util.Medium;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
|
@ -66,6 +67,7 @@ public class SignalAccount implements Closeable {
|
|||
private boolean isMultiDevice = false;
|
||||
private String password;
|
||||
private String registrationLockPin;
|
||||
private MasterKey pinMasterKey;
|
||||
private String signalingKey;
|
||||
private ProfileKey profileKey;
|
||||
private int preKeyIdOffset;
|
||||
|
@ -217,6 +219,10 @@ public class SignalAccount implements Closeable {
|
|||
password = Utils.getNotNullNode(rootNode, "password").asText();
|
||||
JsonNode pinNode = rootNode.get("registrationLockPin");
|
||||
registrationLockPin = pinNode == null || pinNode.isNull() ? null : pinNode.asText();
|
||||
JsonNode pinMasterKeyNode = rootNode.get("pinMasterKey");
|
||||
pinMasterKey = pinMasterKeyNode == null || pinMasterKeyNode.isNull()
|
||||
? null
|
||||
: new MasterKey(Base64.decode(pinMasterKeyNode.asText()));
|
||||
if (rootNode.has("signalingKey")) {
|
||||
signalingKey = Utils.getNotNullNode(rootNode, "signalingKey").asText();
|
||||
}
|
||||
|
@ -345,6 +351,7 @@ public class SignalAccount implements Closeable {
|
|||
.put("isMultiDevice", isMultiDevice)
|
||||
.put("password", password)
|
||||
.put("registrationLockPin", registrationLockPin)
|
||||
.put("pinMasterKey", pinMasterKey == null ? null : Base64.encodeBytes(pinMasterKey.serialize()))
|
||||
.put("signalingKey", signalingKey)
|
||||
.put("preKeyIdOffset", preKeyIdOffset)
|
||||
.put("nextSignedPreKeyId", nextSignedPreKeyId)
|
||||
|
@ -456,14 +463,18 @@ public class SignalAccount implements Closeable {
|
|||
return registrationLockPin;
|
||||
}
|
||||
|
||||
public String getRegistrationLock() {
|
||||
return null; // TODO implement KBS
|
||||
}
|
||||
|
||||
public void setRegistrationLockPin(final String registrationLockPin) {
|
||||
this.registrationLockPin = registrationLockPin;
|
||||
}
|
||||
|
||||
public MasterKey getPinMasterKey() {
|
||||
return pinMasterKey;
|
||||
}
|
||||
|
||||
public void setPinMasterKey(final MasterKey pinMasterKey) {
|
||||
this.pinMasterKey = pinMasterKey;
|
||||
}
|
||||
|
||||
public String getSignalingKey() {
|
||||
return signalingKey;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.asamk.signal.manager.util;
|
|||
import org.asamk.signal.util.RandomUtils;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
public class KeyUtils {
|
||||
|
@ -30,6 +31,10 @@ public class KeyUtils {
|
|||
return getSecretBytes(32);
|
||||
}
|
||||
|
||||
public static MasterKey createMasterKey() {
|
||||
return MasterKey.createNew(RandomUtils.getSecureRandom());
|
||||
}
|
||||
|
||||
private static String getSecret(int size) {
|
||||
byte[] secret = getSecretBytes(size);
|
||||
return Base64.encodeBytes(secret);
|
||||
|
|
31
src/main/java/org/asamk/signal/manager/util/PinHashing.java
Normal file
31
src/main/java/org/asamk/signal/manager/util/PinHashing.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package org.asamk.signal.manager.util;
|
||||
|
||||
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
||||
import org.bouncycastle.crypto.params.Argon2Parameters;
|
||||
import org.whispersystems.signalservice.api.KeyBackupService;
|
||||
import org.whispersystems.signalservice.api.kbs.HashedPin;
|
||||
import org.whispersystems.signalservice.internal.registrationpin.PinHasher;
|
||||
|
||||
public final class PinHashing {
|
||||
|
||||
private PinHashing() {
|
||||
}
|
||||
|
||||
public static HashedPin hashPin(String pin, KeyBackupService.HashSession hashSession) {
|
||||
final Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id).withParallelism(1)
|
||||
.withIterations(32)
|
||||
.withVersion(13)
|
||||
.withMemoryAsKB(16 * 1024)
|
||||
.withSalt(hashSession.hashSalt())
|
||||
.build();
|
||||
|
||||
final Argon2BytesGenerator generator = new Argon2BytesGenerator();
|
||||
generator.init(params);
|
||||
|
||||
return PinHasher.hashPin(PinHasher.normalize(pin), password -> {
|
||||
byte[] output = new byte[64];
|
||||
generator.generateBytes(password, output);
|
||||
return output;
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue