]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java
6b925c692233ac27c41a0b50e10d0b5f39153a90
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / PinHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.api.IncorrectPinException;
4 import org.asamk.signal.manager.api.Pair;
5 import org.asamk.signal.manager.util.PinHashing;
6 import org.signal.libsignal.protocol.InvalidKeyException;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 import org.whispersystems.signalservice.api.KbsPinData;
10 import org.whispersystems.signalservice.api.KeyBackupService;
11 import org.whispersystems.signalservice.api.KeyBackupServicePinException;
12 import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
13 import org.whispersystems.signalservice.api.kbs.MasterKey;
14 import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
15 import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
16 import org.whispersystems.signalservice.internal.push.LockedException;
17
18 import java.io.IOException;
19 import java.util.Collection;
20 import java.util.stream.Stream;
21
22 public class PinHelper {
23
24 private final static Logger logger = LoggerFactory.getLogger(PinHelper.class);
25
26 private final KeyBackupService keyBackupService;
27 private final Collection<KeyBackupService> fallbackKeyBackupServices;
28
29 public PinHelper(
30 final KeyBackupService keyBackupService, final Collection<KeyBackupService> fallbackKeyBackupServices
31 ) {
32 this.keyBackupService = keyBackupService;
33 this.fallbackKeyBackupServices = fallbackKeyBackupServices;
34 }
35
36 public void setRegistrationLockPin(
37 String pin, MasterKey masterKey
38 ) throws IOException {
39 final var pinChangeSession = keyBackupService.newPinChangeSession();
40 final var hashedPin = PinHashing.hashPin(pin, pinChangeSession);
41
42 try {
43 pinChangeSession.setPin(hashedPin, masterKey);
44 } catch (UnauthenticatedResponseException e) {
45 throw new IOException(e);
46 }
47 pinChangeSession.enableRegistrationLock(masterKey);
48 }
49
50 public void migrateRegistrationLockPin(String pin, MasterKey masterKey) throws IOException {
51 setRegistrationLockPin(pin, masterKey);
52
53 for (final var keyBackupService : fallbackKeyBackupServices) {
54 try {
55 final var pinChangeSession = keyBackupService.newPinChangeSession();
56 pinChangeSession.removePin();
57 } catch (Exception e) {
58 logger.warn("Failed to remove PIN from fallback KBS: {}", e.getMessage());
59 }
60 }
61 }
62
63 public void removeRegistrationLockPin() throws IOException {
64 final var pinChangeSession = keyBackupService.newPinChangeSession();
65 pinChangeSession.disableRegistrationLock();
66 try {
67 pinChangeSession.removePin();
68 } catch (UnauthenticatedResponseException e) {
69 throw new IOException(e);
70 }
71 }
72
73 public KbsPinData getRegistrationLockData(
74 String pin, LockedException e
75 ) throws IOException, IncorrectPinException {
76 var basicStorageCredentials = e.getBasicStorageCredentials();
77 if (basicStorageCredentials == null) {
78 return null;
79 }
80
81 try {
82 return getRegistrationLockData(pin, basicStorageCredentials);
83 } catch (KeyBackupSystemNoDataException ex) {
84 throw new IOException(e);
85 } catch (KeyBackupServicePinException ex) {
86 throw new IncorrectPinException(ex.getTriesRemaining());
87 }
88 }
89
90 private KbsPinData getRegistrationLockData(
91 String pin, String basicStorageCredentials
92 ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
93 var tokenResponsePair = getTokenResponse(basicStorageCredentials);
94 final var tokenResponse = tokenResponsePair.first();
95 final var keyBackupService = tokenResponsePair.second();
96
97 var registrationLockData = restoreMasterKey(pin, basicStorageCredentials, tokenResponse, keyBackupService);
98 if (registrationLockData == null) {
99 throw new AssertionError("Failed to restore master key");
100 }
101 return registrationLockData;
102 }
103
104 private Pair<TokenResponse, KeyBackupService> getTokenResponse(String basicStorageCredentials) throws IOException {
105 final var keyBackupServices = Stream.concat(Stream.of(keyBackupService), fallbackKeyBackupServices.stream())
106 .toList();
107 for (final var keyBackupService : keyBackupServices) {
108 var tokenResponse = keyBackupService.getToken(basicStorageCredentials);
109 if (tokenResponse != null && tokenResponse.getTries() > 0) {
110 return new Pair<>(tokenResponse, keyBackupService);
111 }
112 }
113 throw new IOException("KBS Account locked, maximum pin attempts reached.");
114 }
115
116 private KbsPinData restoreMasterKey(
117 String pin,
118 String basicStorageCredentials,
119 TokenResponse tokenResponse,
120 final KeyBackupService keyBackupService
121 ) throws IOException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
122 if (pin == null) return null;
123
124 if (basicStorageCredentials == null) {
125 throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
126 }
127
128 var session = keyBackupService.newRegistrationSession(basicStorageCredentials, tokenResponse);
129
130 try {
131 var hashedPin = PinHashing.hashPin(pin, session);
132 var kbsData = session.restorePin(hashedPin);
133 if (kbsData == null) {
134 throw new AssertionError("Null not expected");
135 }
136 return kbsData;
137 } catch (UnauthenticatedResponseException | InvalidKeyException e) {
138 throw new IOException(e);
139 }
140 }
141 }