]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java
Reformat files
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / util / ProfileUtils.java
index 1136244459cb55b4a6c1f44d5a83f19433fae35d..def75049b03b1f626431ce7defd62bfb5f1c1f49 100644 (file)
 package org.asamk.signal.manager.util;
 
-import org.asamk.signal.manager.storage.profiles.SignalProfile;
-import org.signal.zkgroup.profiles.ProfileKey;
+import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.api.PhoneNumberSharingMode;
+import org.asamk.signal.manager.api.Profile;
+import org.signal.libsignal.protocol.IdentityKey;
+import org.signal.libsignal.protocol.InvalidKeyException;
+import org.signal.libsignal.protocol.ecc.ECPublicKey;
+import org.signal.libsignal.zkgroup.profiles.ProfileKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
 import org.whispersystems.signalservice.api.crypto.ProfileCipher;
 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
+import org.whispersystems.signalservice.internal.push.PaymentAddress;
 
+import java.io.IOException;
 import java.util.Base64;
+import java.util.HashSet;
+import java.util.Optional;
 
 public class ProfileUtils {
 
-    public static SignalProfile decryptProfile(
-            final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
-    ) {
-        ProfileCipher profileCipher = new ProfileCipher(profileKey);
+    private static final Logger logger = LoggerFactory.getLogger(ProfileUtils.class);
+
+    public static Profile decryptProfile(final ProfileKey profileKey, final SignalServiceProfile encryptedProfile) {
+        var profileCipher = new ProfileCipher(profileKey);
+        IdentityKey identityKey = null;
         try {
-            String name = decryptName(encryptedProfile.getName(), profileCipher);
-            String about = decryptName(encryptedProfile.getAbout(), profileCipher);
-            String aboutEmoji = decryptName(encryptedProfile.getAboutEmoji(), profileCipher);
-            String unidentifiedAccess;
-            try {
-                unidentifiedAccess = encryptedProfile.getUnidentifiedAccess() == null
-                        || !profileCipher.verifyUnidentifiedAccess(Base64.getDecoder()
-                        .decode(encryptedProfile.getUnidentifiedAccess()))
-                        ? null
-                        : encryptedProfile.getUnidentifiedAccess();
-            } catch (IllegalArgumentException e) {
-                unidentifiedAccess = null;
-            }
-            return new SignalProfile(encryptedProfile.getIdentityKey(),
-                    name,
+            identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey()), 0);
+        } catch (InvalidKeyException e) {
+            logger.debug("Failed to decode identity key in profile, can't verify payment address", e);
+        }
+
+        try {
+            var name = decryptString(encryptedProfile.getName(), profileCipher);
+            var about = decryptString(encryptedProfile.getAbout(), profileCipher);
+            var aboutEmoji = decryptString(encryptedProfile.getAboutEmoji(), profileCipher);
+
+            final var nameParts = splitName(name);
+            final var remotePhoneNumberSharing = decryptBoolean(encryptedProfile.getPhoneNumberSharing(),
+                    profileCipher).map(v -> v ? PhoneNumberSharingMode.EVERYBODY : PhoneNumberSharingMode.NOBODY)
+                    .orElse(null);
+            return new Profile(System.currentTimeMillis(),
+                    nameParts.first(),
+                    nameParts.second(),
                     about,
                     aboutEmoji,
-                    unidentifiedAccess,
-                    encryptedProfile.isUnrestrictedUnidentifiedAccess(),
-                    encryptedProfile.getCapabilities());
+                    encryptedProfile.getAvatar(),
+                    identityKey == null || encryptedProfile.getPaymentAddress() == null
+                            ? null
+                            : decryptAndVerifyMobileCoinAddress(encryptedProfile.getPaymentAddress(),
+                                    profileCipher,
+                                    identityKey.getPublicKey()),
+                    getUnidentifiedAccessMode(encryptedProfile, profileCipher),
+                    getCapabilities(encryptedProfile),
+                    remotePhoneNumberSharing);
         } catch (InvalidCiphertextException e) {
+            logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
+            return null;
+        }
+    }
+
+    public static Profile.UnidentifiedAccessMode getUnidentifiedAccessMode(
+            final SignalServiceProfile encryptedProfile,
+            final ProfileCipher profileCipher
+    ) {
+        if (encryptedProfile.isUnrestrictedUnidentifiedAccess()) {
+            return Profile.UnidentifiedAccessMode.UNRESTRICTED;
+        }
+
+        if (encryptedProfile.getUnidentifiedAccess() != null && profileCipher != null) {
+            final var unidentifiedAccessVerifier = Base64.getDecoder().decode(encryptedProfile.getUnidentifiedAccess());
+            if (profileCipher.verifyUnidentifiedAccess(unidentifiedAccessVerifier)) {
+                return Profile.UnidentifiedAccessMode.ENABLED;
+            }
+        }
+
+        return Profile.UnidentifiedAccessMode.DISABLED;
+    }
+
+    public static HashSet<Profile.Capability> getCapabilities(final SignalServiceProfile encryptedProfile) {
+        final var capabilities = new HashSet<Profile.Capability>();
+        if (encryptedProfile.getCapabilities().isStorage()) {
+            capabilities.add(Profile.Capability.storage);
+        }
+
+        return capabilities;
+    }
+
+    private static String decryptString(
+            final String encrypted,
+            final ProfileCipher profileCipher
+    ) throws InvalidCiphertextException {
+        try {
+            return encrypted == null ? null : profileCipher.decryptString(Base64.getDecoder().decode(encrypted));
+        } catch (IllegalArgumentException e) {
             return null;
         }
     }
 
-    private static String decryptName(
-            final String encryptedName, final ProfileCipher profileCipher
+    private static Optional<Boolean> decryptBoolean(
+            final String encrypted,
+            final ProfileCipher profileCipher
     ) throws InvalidCiphertextException {
         try {
-            return encryptedName == null
-                    ? null
-                    : new String(profileCipher.decryptName(Base64.getDecoder().decode(encryptedName)));
+            return encrypted == null
+                    ? Optional.empty()
+                    : profileCipher.decryptBoolean(Base64.getDecoder().decode(encrypted));
         } catch (IllegalArgumentException e) {
+            return Optional.empty();
+        }
+    }
+
+    private static byte[] decryptAndVerifyMobileCoinAddress(
+            final byte[] encryptedPaymentAddress,
+            final ProfileCipher profileCipher,
+            final ECPublicKey publicKey
+    ) throws InvalidCiphertextException {
+        byte[] decrypted;
+        try {
+            decrypted = profileCipher.decryptWithLength(encryptedPaymentAddress);
+        } catch (IOException e) {
+            logger.debug("Failed to decrypt payment address", e);
+            return null;
+        }
+
+        PaymentAddress paymentAddress;
+        try {
+            paymentAddress = PaymentAddress.ADAPTER.decode(decrypted);
+        } catch (IOException e) {
+            logger.debug("Failed to parse payment address", e);
             return null;
         }
+
+        return PaymentUtils.verifyPaymentsAddress(paymentAddress, publicKey);
+    }
+
+    private static Pair<String, String> splitName(String name) {
+        if (name == null) {
+            return new Pair<>(null, null);
+        }
+        String[] parts = name.split("\0");
+
+        return switch (parts.length) {
+            case 0 -> new Pair<>(null, null);
+            case 1 -> new Pair<>(parts[0], null);
+            default -> new Pair<>(parts[0], parts[1]);
+        };
     }
 }