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
.SignalServiceAccountManager
;
11 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
12 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NoSuchSessionException
;
13 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonSuccessfulResponseCodeException
;
14 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.PushChallengeRequiredException
;
15 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.TokenNotAcceptedException
;
16 import org
.whispersystems
.signalservice
.internal
.ServiceResponse
;
17 import org
.whispersystems
.signalservice
.internal
.push
.LockedException
;
18 import org
.whispersystems
.signalservice
.internal
.push
.RegistrationSessionMetadataResponse
;
19 import org
.whispersystems
.signalservice
.internal
.push
.VerifyAccountResponse
;
21 import java
.io
.IOException
;
22 import java
.util
.Locale
;
23 import java
.util
.function
.Consumer
;
25 public class NumberVerificationUtils
{
27 public static String
handleVerificationSession(
28 SignalServiceAccountManager accountManager
,
30 Consumer
<String
> sessionIdSaver
,
31 boolean voiceVerification
,
33 ) throws CaptchaRequiredException
, IOException
, RateLimitException
{
34 RegistrationSessionMetadataResponse sessionResponse
;
36 sessionResponse
= getValidSession(accountManager
, sessionId
);
37 } catch (PushChallengeRequiredException
|
38 org
.whispersystems
.signalservice
.api
.push
.exceptions
.CaptchaRequiredException e
) {
39 if (captcha
!= null) {
40 sessionResponse
= submitCaptcha(accountManager
, sessionId
, captcha
);
42 throw new CaptchaRequiredException("Captcha Required");
46 sessionId
= sessionResponse
.getBody().getId();
47 sessionIdSaver
.accept(sessionId
);
49 if (sessionResponse
.getBody().getVerified()) {
53 if (sessionResponse
.getBody().getAllowedToRequestCode()) {
57 final var nextAttempt
= voiceVerification
58 ? sessionResponse
.getBody().getNextCall()
59 : sessionResponse
.getBody().getNextSms();
60 if (nextAttempt
!= null && nextAttempt
> 0) {
61 final var timestamp
= sessionResponse
.getHeaders().getTimestamp() + nextAttempt
* 1000;
62 throw new RateLimitException(timestamp
);
65 final var nextVerificationAttempt
= sessionResponse
.getBody().getNextVerificationAttempt();
66 if (nextVerificationAttempt
!= null && nextVerificationAttempt
> 0) {
67 final var timestamp
= sessionResponse
.getHeaders().getTimestamp() + nextVerificationAttempt
* 1000;
68 throw new CaptchaRequiredException(timestamp
);
71 if (sessionResponse
.getBody().getRequestedInformation().contains("captcha")) {
72 if (captcha
!= null) {
73 sessionResponse
= submitCaptcha(accountManager
, sessionId
, captcha
);
75 if (!sessionResponse
.getBody().getAllowedToRequestCode()) {
76 throw new CaptchaRequiredException("Captcha Required");
83 public static void requestVerificationCode(
84 SignalServiceAccountManager accountManager
, String sessionId
, boolean voiceVerification
85 ) throws IOException
, CaptchaRequiredException
, NonNormalizedPhoneNumberException
{
86 final ServiceResponse
<RegistrationSessionMetadataResponse
> response
;
87 final var locale
= Utils
.getDefaultLocale(Locale
.US
);
88 if (voiceVerification
) {
89 response
= accountManager
.requestVoiceVerificationCode(sessionId
, locale
, false);
91 response
= accountManager
.requestSmsVerificationCode(sessionId
, locale
, false);
94 Utils
.handleResponseException(response
);
95 } catch (org
.whispersystems
.signalservice
.api
.push
.exceptions
.CaptchaRequiredException e
) {
96 throw new CaptchaRequiredException(e
.getMessage(), e
);
97 } catch (org
.whispersystems
.signalservice
.api
.push
.exceptions
.NonNormalizedPhoneNumberException e
) {
98 throw new NonNormalizedPhoneNumberException("Phone number is not normalized ("
100 + "). Expected normalized: "
101 + e
.getNormalizedNumber(), e
);
105 public static Pair
<VerifyAccountResponse
, MasterKey
> verifyNumber(
106 String sessionId
, String verificationCode
, String pin
, PinHelper pinHelper
, Verifier verifier
107 ) throws IOException
, PinLockedException
, IncorrectPinException
{
108 verificationCode
= verificationCode
.replace("-", "");
110 final var response
= verifier
.verify(sessionId
, verificationCode
, null);
112 return new Pair
<>(response
, null);
113 } catch (LockedException e
) {
115 throw new PinLockedException(e
.getTimeRemaining());
118 final var registrationLockData
= pinHelper
.getRegistrationLockData(pin
, e
);
119 if (registrationLockData
== null) {
123 var registrationLock
= registrationLockData
.getMasterKey().deriveRegistrationLock();
124 VerifyAccountResponse response
;
126 response
= verifier
.verify(sessionId
, verificationCode
, registrationLock
);
127 } catch (LockedException _e
) {
128 throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
131 return new Pair
<>(response
, registrationLockData
.getMasterKey());
135 private static RegistrationSessionMetadataResponse
validateSession(
136 final SignalServiceAccountManager accountManager
, final String sessionId
137 ) throws IOException
{
138 if (sessionId
== null || sessionId
.isEmpty()) {
139 throw new NoSuchSessionException();
141 return Utils
.handleResponseException(accountManager
.getRegistrationSession(sessionId
));
144 private static RegistrationSessionMetadataResponse
requestValidSession(
145 final SignalServiceAccountManager accountManager
146 ) throws NoSuchSessionException
, IOException
{
147 return Utils
.handleResponseException(accountManager
.createRegistrationSession(null, "", ""));
150 private static RegistrationSessionMetadataResponse
getValidSession(
151 final SignalServiceAccountManager accountManager
, final String sessionId
152 ) throws IOException
{
154 return validateSession(accountManager
, sessionId
);
155 } catch (NoSuchSessionException e
) {
156 return requestValidSession(accountManager
);
160 private static RegistrationSessionMetadataResponse
submitCaptcha(
161 SignalServiceAccountManager accountManager
, String sessionId
, String captcha
162 ) throws IOException
, CaptchaRequiredException
{
163 captcha
= captcha
== null ?
null : captcha
.replace("signalcaptcha://", "");
165 return Utils
.handleResponseException(accountManager
.submitCaptchaToken(sessionId
, captcha
));
166 } catch (PushChallengeRequiredException
|
167 org
.whispersystems
.signalservice
.api
.push
.exceptions
.CaptchaRequiredException
|
168 TokenNotAcceptedException _e
) {
169 throw new CaptchaRequiredException("Captcha not accepted");
170 } catch (NonSuccessfulResponseCodeException e
) {
171 if (e
.getCode() == 400) {
172 throw new CaptchaRequiredException("Captcha has invalid format");
178 public interface Verifier
{
180 VerifyAccountResponse
verify(
181 String sessionId
, String verificationCode
, String registrationLock
182 ) throws IOException
;