]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Manager.java
Command to check if number is registered (#391)
[signal-cli] / src / main / java / org / asamk / signal / manager / Manager.java
index 5ed8fc4e506d852fccafb4144174e69d8025402a..e964d21830694efb7a8ee07d6a5db140bd6a5ad8 100644 (file)
@@ -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,11 +167,11 @@ 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;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -194,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;
 
@@ -210,8 +219,9 @@ public class Manager implements Closeable {
     private final UnidentifiedAccessHelper unidentifiedAccessHelper;
     private final ProfileHelper profileHelper;
     private final GroupHelper groupHelper;
+    private PinHelper pinHelper;
 
-    public Manager(
+    Manager(
             SignalAccount account,
             PathConfig pathConfig,
             SignalServiceConfiguration serviceConfiguration,
@@ -223,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);
 
@@ -252,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(),
@@ -262,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() {
@@ -362,13 +383,34 @@ public class Manager implements Closeable {
         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(),
@@ -386,8 +428,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,
@@ -480,26 +523,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(),
@@ -510,13 +566,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();
     }
@@ -810,7 +893,7 @@ public class Manager implements Closeable {
             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;
@@ -965,7 +1048,7 @@ public class Manager implements Closeable {
         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 {
@@ -1007,14 +1090,14 @@ public class Manager implements Closeable {
                 .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,
@@ -1141,7 +1224,7 @@ public class Manager implements Closeable {
     private void sendExpirationTimerUpdate(SignalServiceAddress address) throws IOException {
         final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
                 .asExpirationUpdate();
-        sendMessage(messageBuilder, Collections.singleton(address));
+        sendMessage(messageBuilder, List.of(address));
     }
 
     /**
@@ -1434,7 +1517,7 @@ public class Manager implements Closeable {
                             .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
@@ -1477,7 +1560,7 @@ public class Manager implements Closeable {
                 message.getTimestamp(),
                 message,
                 message.getExpiresInSeconds(),
-                Collections.singletonMap(recipient, unidentifiedAccess.isPresent()),
+                Map.of(recipient, unidentifiedAccess.isPresent()),
                 false);
         SignalServiceSyncMessage syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
 
@@ -2085,7 +2168,7 @@ public class Manager implements Closeable {
                                         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()) {