1 package org
.asamk
.signal
.manager
.util
;
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
;
12 import java
.util
.Base64
;
13 import java
.util
.HashSet
;
15 public class ProfileUtils
{
17 private final static Logger logger
= LoggerFactory
.getLogger(ProfileUtils
.class);
19 public static Profile
decryptProfile(
20 final ProfileKey profileKey
, final SignalServiceProfile encryptedProfile
22 var profileCipher
= new ProfileCipher(profileKey
);
24 var name
= decrypt(encryptedProfile
.getName(), profileCipher
);
25 var about
= trimZeros(decrypt(encryptedProfile
.getAbout(), profileCipher
));
26 var aboutEmoji
= trimZeros(decrypt(encryptedProfile
.getAboutEmoji(), profileCipher
));
28 final var nameParts
= splitName(name
);
29 return new Profile(System
.currentTimeMillis(),
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
);
44 public static Profile
.UnidentifiedAccessMode
getUnidentifiedAccessMode(
45 final SignalServiceProfile encryptedProfile
, final ProfileCipher profileCipher
47 if (encryptedProfile
.isUnrestrictedUnidentifiedAccess()) {
48 return Profile
.UnidentifiedAccessMode
.UNRESTRICTED
;
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
;
58 return Profile
.UnidentifiedAccessMode
.DISABLED
;
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
);
66 if (encryptedProfile
.getCapabilities().isStorage()) {
67 capabilities
.add(Profile
.Capability
.storage
);
69 if (encryptedProfile
.getCapabilities().isSenderKey()) {
70 capabilities
.add(Profile
.Capability
.senderKey
);
72 if (encryptedProfile
.getCapabilities().isAnnouncementGroup()) {
73 capabilities
.add(Profile
.Capability
.announcementGroup
);
79 private static String
decrypt(
80 final String encryptedName
, final ProfileCipher profileCipher
81 ) throws InvalidCiphertextException
{
83 return encryptedName
== null
85 : new String(profileCipher
.decrypt(Base64
.getDecoder().decode(encryptedName
)));
86 } catch (IllegalArgumentException e
) {
91 private static Pair
<String
, String
> splitName(String name
) {
93 return new Pair
<>(null, null);
95 String
[] parts
= name
.split("\0");
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]);
104 static String
trimZeros(String str
) {
109 int pos
= str
.indexOf(0);
110 return pos
== -1 ? str
: str
.substring(0, pos
);