]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java
Store payment address of profiles
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / util / ProfileUtils.java
1 package org.asamk.signal.manager.util;
2
3 import org.asamk.signal.manager.api.Pair;
4 import org.asamk.signal.manager.storage.recipients.Profile;
5 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8 import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
9 import org.whispersystems.signalservice.api.crypto.ProfileCipher;
10 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
11
12 import java.util.Base64;
13 import java.util.HashSet;
14
15 public class ProfileUtils {
16
17 private final static Logger logger = LoggerFactory.getLogger(ProfileUtils.class);
18
19 public static Profile decryptProfile(
20 final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
21 ) {
22 var profileCipher = new ProfileCipher(profileKey);
23 try {
24 var name = decrypt(encryptedProfile.getName(), profileCipher);
25 var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher));
26 var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher));
27
28 final var nameParts = splitName(name);
29 return new Profile(System.currentTimeMillis(),
30 nameParts.first(),
31 nameParts.second(),
32 about,
33 aboutEmoji,
34 encryptedProfile.getAvatar(),
35 encryptedProfile.getPaymentAddress(),
36 getUnidentifiedAccessMode(encryptedProfile, profileCipher),
37 getCapabilities(encryptedProfile));
38 } catch (InvalidCiphertextException e) {
39 logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
40 return null;
41 }
42 }
43
44 public static Profile.UnidentifiedAccessMode getUnidentifiedAccessMode(
45 final SignalServiceProfile encryptedProfile, final ProfileCipher profileCipher
46 ) {
47 if (encryptedProfile.isUnrestrictedUnidentifiedAccess()) {
48 return Profile.UnidentifiedAccessMode.UNRESTRICTED;
49 }
50
51 if (encryptedProfile.getUnidentifiedAccess() != null && profileCipher != null) {
52 final var unidentifiedAccessVerifier = Base64.getDecoder().decode(encryptedProfile.getUnidentifiedAccess());
53 if (profileCipher.verifyUnidentifiedAccess(unidentifiedAccessVerifier)) {
54 return Profile.UnidentifiedAccessMode.ENABLED;
55 }
56 }
57
58 return Profile.UnidentifiedAccessMode.DISABLED;
59 }
60
61 public static HashSet<Profile.Capability> getCapabilities(final SignalServiceProfile encryptedProfile) {
62 final var capabilities = new HashSet<Profile.Capability>();
63 if (encryptedProfile.getCapabilities().isGv1Migration()) {
64 capabilities.add(Profile.Capability.gv1Migration);
65 }
66 if (encryptedProfile.getCapabilities().isStorage()) {
67 capabilities.add(Profile.Capability.storage);
68 }
69 if (encryptedProfile.getCapabilities().isSenderKey()) {
70 capabilities.add(Profile.Capability.senderKey);
71 }
72 if (encryptedProfile.getCapabilities().isAnnouncementGroup()) {
73 capabilities.add(Profile.Capability.announcementGroup);
74 }
75
76 return capabilities;
77 }
78
79 private static String decrypt(
80 final String encryptedName, final ProfileCipher profileCipher
81 ) throws InvalidCiphertextException {
82 try {
83 return encryptedName == null
84 ? null
85 : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
86 } catch (IllegalArgumentException e) {
87 return null;
88 }
89 }
90
91 private static Pair<String, String> splitName(String name) {
92 if (name == null) {
93 return new Pair<>(null, null);
94 }
95 String[] parts = name.split("\0");
96
97 return switch (parts.length) {
98 case 0 -> new Pair<>(null, null);
99 case 1 -> new Pair<>(parts[0], null);
100 default -> new Pair<>(parts[0], parts[1]);
101 };
102 }
103
104 static String trimZeros(String str) {
105 if (str == null) {
106 return null;
107 }
108
109 int pos = str.indexOf(0);
110 return pos == -1 ? str : str.substring(0, pos);
111 }
112 }