]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/IdentityHelper.java
Reduce use of unknown serviceIds
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / IdentityHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.api.TrustLevel;
4 import org.asamk.signal.manager.storage.SignalAccount;
5 import org.asamk.signal.manager.storage.recipients.RecipientId;
6 import org.asamk.signal.manager.util.Utils;
7 import org.signal.libsignal.protocol.IdentityKey;
8 import org.signal.libsignal.protocol.fingerprint.Fingerprint;
9 import org.signal.libsignal.protocol.fingerprint.FingerprintParsingException;
10 import org.signal.libsignal.protocol.fingerprint.FingerprintVersionMismatchException;
11 import org.signal.libsignal.protocol.fingerprint.ScannableFingerprint;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
14 import org.whispersystems.signalservice.api.messages.SendMessageResult;
15 import org.whispersystems.signalservice.api.push.ServiceId;
16
17 import java.io.IOException;
18 import java.util.Arrays;
19 import java.util.function.BiFunction;
20
21 import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
22
23 public class IdentityHelper {
24
25 private final static Logger logger = LoggerFactory.getLogger(IdentityHelper.class);
26
27 private final SignalAccount account;
28 private final Context context;
29
30 public IdentityHelper(final Context context) {
31 this.account = context.getAccount();
32 this.context = context;
33 }
34
35 public boolean trustIdentityVerified(RecipientId recipientId, byte[] fingerprint) {
36 return trustIdentity(recipientId,
37 (serviceId, identityKey) -> Arrays.equals(identityKey.serialize(), fingerprint),
38 TrustLevel.TRUSTED_VERIFIED);
39 }
40
41 public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, String safetyNumber) {
42 return trustIdentity(recipientId,
43 (serviceId, identityKey) -> safetyNumber.equals(computeSafetyNumber(serviceId, identityKey)),
44 TrustLevel.TRUSTED_VERIFIED);
45 }
46
47 public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, byte[] safetyNumber) {
48 return trustIdentity(recipientId, (serviceId, identityKey) -> {
49 final var fingerprint = computeSafetyNumberForScanning(serviceId, identityKey);
50 try {
51 return fingerprint != null && fingerprint.compareTo(safetyNumber);
52 } catch (FingerprintVersionMismatchException | FingerprintParsingException e) {
53 return false;
54 }
55 }, TrustLevel.TRUSTED_VERIFIED);
56 }
57
58 public boolean trustIdentityAllKeys(RecipientId recipientId) {
59 return trustIdentity(recipientId, (serviceId, identityKey) -> true, TrustLevel.TRUSTED_UNVERIFIED);
60 }
61
62 public String computeSafetyNumber(ServiceId serviceId, IdentityKey theirIdentityKey) {
63 final Fingerprint fingerprint = computeSafetyNumberFingerprint(serviceId, theirIdentityKey);
64 return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
65 }
66
67 public ScannableFingerprint computeSafetyNumberForScanning(ServiceId serviceId, IdentityKey theirIdentityKey) {
68 final Fingerprint fingerprint = computeSafetyNumberFingerprint(serviceId, theirIdentityKey);
69 return fingerprint == null ? null : fingerprint.getScannableFingerprint();
70 }
71
72 private Fingerprint computeSafetyNumberFingerprint(
73 final ServiceId serviceId, final IdentityKey theirIdentityKey
74 ) {
75 final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId);
76 final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
77
78 if (capabilities.getUuid()) {
79 if (serviceId.isUnknown()) {
80 return null;
81 }
82 return Utils.computeSafetyNumberForUuid(account.getAci(),
83 account.getAciIdentityKeyPair().getPublicKey(),
84 serviceId,
85 theirIdentityKey);
86 }
87 if (address.number().isEmpty()) {
88 return null;
89 }
90 return Utils.computeSafetyNumberForNumber(account.getNumber(),
91 account.getAciIdentityKeyPair().getPublicKey(),
92 address.number().get(),
93 theirIdentityKey);
94 }
95
96 private boolean trustIdentity(
97 RecipientId recipientId, BiFunction<ServiceId, IdentityKey, Boolean> verifier, TrustLevel trustLevel
98 ) {
99 final var serviceId = account.getRecipientAddressResolver()
100 .resolveRecipientAddress(recipientId)
101 .serviceId()
102 .orElse(null);
103 if (serviceId == null) {
104 return false;
105 }
106 var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
107 if (identity == null) {
108 return false;
109 }
110
111 if (!verifier.apply(serviceId, identity.getIdentityKey())) {
112 return false;
113 }
114
115 account.getIdentityKeyStore().setIdentityTrustLevel(serviceId, identity.getIdentityKey(), trustLevel);
116 try {
117 final var address = context.getRecipientHelper()
118 .resolveSignalServiceAddress(account.getRecipientResolver().resolveRecipient(serviceId));
119 context.getSyncHelper().sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
120 } catch (IOException e) {
121 logger.warn("Failed to send verification sync message: {}", e.getMessage());
122 }
123
124 return true;
125 }
126
127 public void handleIdentityFailure(
128 final RecipientId recipientId,
129 final ServiceId serviceId,
130 final SendMessageResult.IdentityFailure identityFailure
131 ) {
132 final var identityKey = identityFailure.getIdentityKey();
133 if (identityKey != null) {
134 account.getIdentityKeyStore().saveIdentity(serviceId, identityKey);
135 } else {
136 // Retrieve profile to get the current identity key from the server
137 context.getProfileHelper().refreshRecipientProfile(recipientId);
138 }
139 }
140 }