]> nmode's Git Repositories - signal-cli/blobdiff - src/main/java/org/asamk/signal/manager/Manager.java
Fix pin hash version to match android
[signal-cli] / src / main / java / org / asamk / signal / manager / Manager.java
index 25b2ec54080e5e3d753c4b9c565715860ed04e10..576dbb5a68a5b61ede1d391aaba91ee9820a6b7d 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,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() {
@@ -295,7 +317,7 @@ public class Manager implements Closeable {
         PathConfig pathConfig = PathConfig.createDefault(settingsPath);
 
         if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
-            IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
+            IdentityKeyPair identityKey = KeyUtils.generateIdentityKeyPair();
             int registrationId = KeyHelper.generateRegistrationId(false);
 
             ProfileKey profileKey = KeyUtils.createProfileKey();
@@ -361,13 +383,30 @@ 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);
+
+        Set<String> registeredUsers = contactDetails.stream()
+                .map(ContactTokenDetails::getNumber)
+                .collect(Collectors.toSet());
+
+        return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
+    }
+
     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(),
@@ -385,8 +424,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 +519,38 @@ 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 +561,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();
     }
@@ -714,7 +793,7 @@ public class Manager implements Closeable {
     }
 
     private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
-        GroupInfo g = account.getGroupStore().getGroup(groupId);
+        GroupInfo g = getGroup(groupId);
         if (g == null) {
             throw new GroupNotFoundException(groupId);
         }
@@ -725,7 +804,7 @@ public class Manager implements Closeable {
     }
 
     private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
-        GroupInfo g = account.getGroupStore().getGroup(groupId);
+        GroupInfo g = getGroup(groupId);
         if (g == null) {
             throw new GroupNotFoundException(groupId);
         }
@@ -1157,7 +1236,7 @@ public class Manager implements Closeable {
      * Change the expiration timer for a group
      */
     public void setExpirationTimer(GroupId groupId, int messageExpirationTimer) {
-        GroupInfo g = account.getGroupStore().getGroup(groupId);
+        GroupInfo g = getGroup(groupId);
         if (g instanceof GroupInfoV1) {
             GroupInfoV1 groupInfoV1 = (GroupInfoV1) g;
             groupInfoV1.messageExpirationTime = messageExpirationTimer;
@@ -1570,7 +1649,7 @@ public class Manager implements Closeable {
             if (message.getGroupContext().get().getGroupV1().isPresent()) {
                 SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
                 GroupIdV1 groupId = GroupId.v1(groupInfo.getGroupId());
-                GroupInfo group = account.getGroupStore().getGroup(groupId);
+                GroupInfo group = getGroup(groupId);
                 if (group == null || group instanceof GroupInfoV1) {
                     GroupInfoV1 groupV1 = (GroupInfoV1) group;
                     switch (groupInfo.getType()) {
@@ -1741,7 +1820,7 @@ public class Manager implements Closeable {
         final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
 
         GroupIdV2 groupId = GroupUtils.getGroupIdV2(groupSecretParams);
-        GroupInfo groupInfo = account.getGroupStore().getGroup(groupId);
+        GroupInfo groupInfo = getGroup(groupId);
         final GroupInfoV2 groupInfoV2;
         if (groupInfo instanceof GroupInfoV1) {
             // Received a v2 group message for a v1 group, we need to locally migrate the group
@@ -1999,7 +2078,7 @@ public class Manager implements Closeable {
                     }
                 }
                 GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get());
-                GroupInfo group = account.getGroupStore().getGroup(groupId);
+                GroupInfo group = getGroup(groupId);
                 if (group != null && group.isBlocked()) {
                     return true;
                 }
@@ -2382,7 +2461,7 @@ public class Manager implements Closeable {
         try {
             try (OutputStream fos = new FileOutputStream(groupsFile)) {
                 DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos);
-                for (GroupInfo record : account.getGroupStore().getGroups()) {
+                for (GroupInfo record : getGroups()) {
                     if (record instanceof GroupInfoV1) {
                         GroupInfoV1 groupInfo = (GroupInfoV1) record;
                         out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
@@ -2491,7 +2570,7 @@ public class Manager implements Closeable {
             }
         }
         List<byte[]> groupIds = new ArrayList<>();
-        for (GroupInfo record : account.getGroupStore().getGroups()) {
+        for (GroupInfo record : getGroups()) {
             if (record.isBlocked()) {
                 groupIds.add(record.getGroupId().serialize());
             }
@@ -2518,7 +2597,13 @@ public class Manager implements Closeable {
     }
 
     public GroupInfo getGroup(GroupId groupId) {
-        return account.getGroupStore().getGroup(groupId);
+        final GroupInfo group = account.getGroupStore().getGroup(groupId);
+        if (group instanceof GroupInfoV2 && ((GroupInfoV2) group).getGroup() == null) {
+            final GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(((GroupInfoV2) group).getMasterKey());
+            ((GroupInfoV2) group).setGroup(groupHelper.getDecryptedGroup(groupSecretParams));
+            account.getGroupStore().updateGroup(group);
+        }
+        return group;
     }
 
     public List<IdentityInfo> getIdentities() {