1 package org
.asamk
.signal
.manager
.util
;
3 import org
.asamk
.signal
.manager
.api
.CaptchaRequiredException
;
4 import org
.asamk
.signal
.manager
.api
.IncorrectPinException
;
5 import org
.asamk
.signal
.manager
.api
.NonNormalizedPhoneNumberException
;
6 import org
.asamk
.signal
.manager
.api
.Pair
;
7 import org
.asamk
.signal
.manager
.api
.PinLockedException
;
8 import org
.asamk
.signal
.manager
.api
.RateLimitException
;
9 import org
.asamk
.signal
.manager
.helper
.PinHelper
;
10 import org
.whispersystems
.signalservice
.api
.KbsPinData
;
11 import org
.whispersystems
.signalservice
.api
.SignalServiceAccountManager
;
12 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
13 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NoSuchSessionException
;
14 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonSuccessfulResponseCodeException
;
15 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.PushChallengeRequiredException
;
16 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.TokenNotAcceptedException
;
17 import org
.whispersystems
.signalservice
.internal
.ServiceResponse
;
18 import org
.whispersystems
.signalservice
.internal
.push
.LockedException
;
19 import org
.whispersystems
.signalservice
.internal
.push
.RegistrationSessionMetadataResponse
;
20 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
22 import java
.io
.IOException
;
23 import java
.util
.Locale
;
24 import java
.util
.function
.Consumer
;
26 public class NumberVerificationUtils
{
28 public static String
handleVerificationSession(
29 SignalServiceAccountManager accountManager
,
31 Consumer
<String
> sessionIdSaver
,
32 boolean voiceVerification
,
34 ) throws CaptchaRequiredException
, IOException
, RateLimitException
{
35 RegistrationSessionMetadataResponse sessionResponse
;
37 sessionResponse
= getValidSession(accountManager
, sessionId
);
38 } catch (PushChallengeRequiredException
|
39 org
.whispersystems
.signalservice
.api
.push
.exceptions
.CaptchaRequiredException e
) {
40 if (captcha
!= null) {
41 sessionResponse
= submitCaptcha(accountManager
, sessionId
, captcha
);
43 throw new CaptchaRequiredException("Captcha Required");
47 sessionId
= sessionResponse
.getBody().getId();
48 sessionIdSaver
.accept(sessionId
);
50 if (sessionResponse
.getBody().getVerified()) {
54 if (sessionResponse
.getBody().getAllowedToRequestCode()) {
58 final var nextAttempt
= voiceVerification
59 ? sessionResponse
.getBody().getNextCall()
60 : sessionResponse
.getBody().getNextSms();
61 if (nextAttempt
!= null && nextAttempt
> 0) {
62 final var timestamp
= sessionResponse
.getHeaders().getTimestamp() + nextAttempt
* 1000;
63 throw new RateLimitException(timestamp
);
66 final var nextVerificationAttempt
= sessionResponse
.getBody().getNextVerificationAttempt();
67 if (nextVerificationAttempt
!= null && nextVerificationAttempt
> 0) {
68 final var timestamp
= sessionResponse
.getHeaders().getTimestamp() + nextVerificationAttempt
* 1000;
69 throw new CaptchaRequiredException(timestamp
);
72 if (sessionResponse
.getBody().getRequestedInformation().contains("captcha")) {
73 if (captcha
!= null) {
74 sessionResponse
= submitCaptcha(accountManager
, sessionId
, captcha
);
76 if (!sessionResponse
.getBody().getAllowedToRequestCode()) {
77 throw new CaptchaRequiredException("Captcha Required");
84 public static void requestVerificationCode(
85 SignalServiceAccountManager accountManager
, String sessionId
, boolean voiceVerification
86 ) throws IOException
, CaptchaRequiredException
, NonNormalizedPhoneNumberException
{
87 final ServiceResponse
<RegistrationSessionMetadataResponse
> response
;
88 final var locale
= Utils
.getDefaultLocale(Locale
.US
);
89 if (voiceVerification
) {
90 response
= accountManager
.requestVoiceVerificationCode(sessionId
, locale
, false);
92 response
= accountManager
.requestSmsVerificationCode(sessionId
, locale
, false);
95 Utils
.handleResponseException(response
);
96 } catch (org
.whispersystems
.signalservice
.api
.push
.exceptions
.CaptchaRequiredException e
) {
97 throw new CaptchaRequiredException(e
.getMessage(), e
);
98 } catch (org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonNormalizedPhoneNumberException e
) {
99 throw new NonNormalizedPhoneNumberException("Phone number is not normalized ("
101 + "). Expected normalized: "
102 + e
.getNormalizedNumber(), e
);
106 public static Pair
<VerifyAccountResponse
, MasterKey
> verifyNumber(
107 String sessionId
, String verificationCode
, String pin
, PinHelper pinHelper
, Verifier verifier
108 ) throws IOException
, PinLockedException
, IncorrectPinException
{
109 verificationCode
= verificationCode
.replace("-", "");
111 final var response
= verifier
.verify(sessionId
, verificationCode
, null);
113 return new Pair
<>(response
, null);
114 } catch (LockedException e
) {
116 throw new PinLockedException(e
.getTimeRemaining());
119 KbsPinData registrationLockData
;
120 registrationLockData
= pinHelper
.getRegistrationLockData(pin
, e
);
121 if (registrationLockData
== null) {
125 var registrationLock
= registrationLockData
.getMasterKey().deriveRegistrationLock();
126 VerifyAccountResponse response
;
128 response
= verifier
.verify(sessionId
, verificationCode
, registrationLock
);
129 } catch (LockedException _e
) {
130 throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
133 return new Pair
<>(response
, registrationLockData
.getMasterKey());
137 private static RegistrationSessionMetadataResponse
validateSession(
138 final SignalServiceAccountManager accountManager
, final String sessionId
139 ) throws IOException
{
140 if (sessionId
== null || sessionId
.isEmpty()) {
141 throw new NoSuchSessionException();
143 return Utils
.handleResponseException(accountManager
.getRegistrationSession(sessionId
));
146 private static RegistrationSessionMetadataResponse
requestValidSession(
147 final SignalServiceAccountManager accountManager
148 ) throws NoSuchSessionException
, IOException
{
149 return Utils
.handleResponseException(accountManager
.createRegistrationSession(null, "", ""));
152 private static RegistrationSessionMetadataResponse
getValidSession(
153 final SignalServiceAccountManager accountManager
, final String sessionId
154 ) throws IOException
{
156 return validateSession(accountManager
, sessionId
);
157 } catch (NoSuchSessionException e
) {
158 return requestValidSession(accountManager
);
162 private static RegistrationSessionMetadataResponse
submitCaptcha(
163 SignalServiceAccountManager accountManager
, String sessionId
, String captcha
164 ) throws IOException
, CaptchaRequiredException
{
165 captcha
= captcha
== null ?
null : captcha
.replace("signalcaptcha://", "");
167 return Utils
.handleResponseException(accountManager
.submitCaptchaToken(sessionId
, captcha
));
168 } catch (PushChallengeRequiredException
|
169 org
.whispersystems
.signalservice
.api
.push
.exceptions
.CaptchaRequiredException
|
170 TokenNotAcceptedException _e
) {
171 throw new CaptchaRequiredException("Captcha not accepted");
172 } catch (NonSuccessfulResponseCodeException e
) {
173 if (e
.getCode() == 400) {
174 throw new CaptchaRequiredException("Captcha has invalid format");
180 public interface Verifier
{
182 VerifyAccountResponse
verify(
183 String sessionId
, String verificationCode
, String registrationLock
184 ) throws IOException
;