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