Skip to content

Commit

Permalink
Implement registration pin lock with KBS
Browse files Browse the repository at this point in the history
Fixes #323
Fixes #268
  • Loading branch information
AsamK committed Dec 31, 2020
1 parent a52f6a6 commit 425626e
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 44 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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;

Expand All @@ -23,7 +24,7 @@ public int handleCommand(final Namespace ns, final Manager m) {
try {
m.setRegistrationLockPin(Optional.absent());
return 0;
} catch (IOException e) {
} catch (IOException | UnauthenticatedResponseException e) {
System.err.println("Remove pin error: " + e.getMessage());
return 3;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/asamk/signal/commands/SetPinCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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;

Expand All @@ -26,7 +27,7 @@ public int handleCommand(final Namespace ns, final Manager m) {
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;
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/asamk/signal/commands/VerifyCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
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;
Expand Down Expand Up @@ -31,6 +33,12 @@ public int handleCommand(final Namespace ns, final Manager m) {
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());
Expand Down
112 changes: 87 additions & 25 deletions src/main/java/org/asamk/signal/manager/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -82,6 +83,10 @@
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;
Expand All @@ -96,6 +101,7 @@
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;
Expand Down Expand Up @@ -138,6 +144,7 @@
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;
Expand All @@ -160,6 +167,7 @@
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;
Expand Down Expand Up @@ -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;

Expand All @@ -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,
Expand All @@ -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);

Expand Down Expand Up @@ -251,8 +261,8 @@ public SignalServiceAddress getSelfAddress() {
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(),
Expand All @@ -261,6 +271,18 @@ private SignalServiceAccountManager createSignalServiceAccountManager() {
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() {
Expand Down Expand Up @@ -366,8 +388,7 @@ public void register(boolean voiceVerification, String captcha) throws IOExcepti

// Resetting UUID, because registering doesn't work otherwise
account.setUuid(null);
accountManager = createSignalServiceAccountManager();
this.groupsV2Api = accountManager.getGroupsV2Api();
createSignalServiceAccountManager();

if (voiceVerification) {
accountManager.requestVoiceVerificationCode(Locale.getDefault(),
Expand All @@ -385,8 +406,9 @@ public void updateAccountAttributes() throws IOException {
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,
Expand Down Expand Up @@ -479,26 +501,39 @@ private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair)
}
}

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(),
Expand All @@ -509,13 +544,40 @@ public void verifyAccount(String verificationCode, String pin) throws IOExceptio
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();
}
Expand Down
25 changes: 13 additions & 12 deletions src/main/java/org/asamk/signal/manager/ServiceConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,13 +14,13 @@
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;

Expand All @@ -28,7 +29,8 @@

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;
Expand All @@ -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";
Expand All @@ -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);
Expand Down Expand Up @@ -110,8 +111,8 @@ static KeyStore getIasKeyStore() {

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);
}
}
Expand Down
Loading

0 comments on commit 425626e

Please sign in to comment.