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;
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;
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;
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;
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;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
private final SignalServiceConfiguration serviceConfiguration;
private final String userAgent;
+
+ // TODO make configurable
private final boolean discoverableByPhoneNumber = true;
private final boolean unrestrictedUnidentifiedAccess = false;
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper;
private final GroupHelper groupHelper;
+ private PinHelper pinHelper;
- public Manager(
+ Manager(
SignalAccount account,
PathConfig pathConfig,
SignalServiceConfiguration serviceConfiguration,
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);
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(),
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() {
return account.isRegistered();
}
+ /**
+ * This is used for checking a set of phone numbers for registration on Signal
+ *
+ * @param numbers The set of phone number in question
+ * @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
+ * @throws IOException if its unable to check if the users are registered
+ */
+ public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException {
+ // Note "contactDetails" has no optionals. It only gives us info on users who are registered
+ List<ContactTokenDetails> contactDetails = this.accountManager.getContacts(numbers);
+
+ // Make the initial map with all numbers set to false for now
+ Map<String, Boolean> usersRegistered = numbers.stream().collect(Collectors.toMap(x -> x, x -> false));
+
+ // Override the contacts we did obtain
+ for (ContactTokenDetails contactDetail : contactDetails) {
+ usersRegistered.put(contactDetail.getNumber(), true);
+ }
+
+ return usersRegistered;
+ }
+
public void register(boolean voiceVerification, String captcha) throws IOException {
account.setPassword(KeyUtils.createPassword());
// Resetting UUID, because registering doesn't work otherwise
account.setUuid(null);
- accountManager = createSignalServiceAccountManager();
- this.groupsV2Api = accountManager.getGroupsV2Api();
+ createSignalServiceAccountManager();
if (voiceVerification) {
accountManager.requestVoiceVerificationCode(Locale.getDefault(),
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,
}
}
- 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(),
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();
}
GroupInfoV2 gv2 = groupHelper.createGroupV2(name, members, avatarFile);
if (gv2 == null) {
GroupInfoV1 gv1 = new GroupInfoV1(GroupIdV1.createRandom());
- gv1.addMembers(Collections.singleton(account.getSelfAddress()));
+ gv1.addMembers(List.of(account.getSelfAddress()));
updateGroupV1(gv1, name, members, avatarFile);
messageBuilder = getGroupUpdateMessageBuilder(gv1);
g = gv1;
SignalServiceDataMessage.Builder messageBuilder = getGroupUpdateMessageBuilder(g);
// Send group message only to the recipient who requested it
- return sendMessage(messageBuilder, Collections.singleton(recipient));
+ return sendMessage(messageBuilder, List.of(recipient));
}
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
.asGroupMessage(group.build());
// Send group info request message to the recipient who sent us a message with this groupId
- return sendMessage(messageBuilder, Collections.singleton(recipient));
+ return sendMessage(messageBuilder, List.of(recipient));
}
void sendReceipt(
SignalServiceAddress remoteAddress, long messageId
) throws IOException, UntrustedIdentityException {
SignalServiceReceiptMessage receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
- Collections.singletonList(messageId),
+ List.of(messageId),
System.currentTimeMillis());
createMessageSender().sendReceipt(remoteAddress,
private void sendExpirationTimerUpdate(SignalServiceAddress address) throws IOException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.asExpirationUpdate();
- sendMessage(messageBuilder, Collections.singleton(address));
+ sendMessage(messageBuilder, List.of(address));
}
/**
.saveIdentity(resolveSignalServiceAddress(e.getIdentifier()),
e.getIdentityKey(),
TrustLevel.UNTRUSTED);
- return new Pair<>(timestamp, Collections.emptyList());
+ return new Pair<>(timestamp, List.of());
}
} else {
// Send to all individually, so sync messages are sent correctly
message.getTimestamp(),
message,
message.getExpiresInSeconds(),
- Collections.singletonMap(recipient, unidentifiedAccess.isPresent()),
+ Map.of(recipient, unidentifiedAccess.isPresent()),
false);
SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
syncGroup.removeMember(account.getSelfAddress());
} else {
// Add ourself to the member set as it's marked as active
- syncGroup.addMembers(Collections.singleton(account.getSelfAddress()));
+ syncGroup.addMembers(List.of(account.getSelfAddress()));
}
syncGroup.blocked = g.isBlocked();
if (g.getColor().isPresent()) {