Skip to content

Commit 5baa534

Browse files
committed
Support CAPTCHA tokens when requesting a verification code.
Reflects signalapp/libsignal-service-java@5ac14f4 Also part of Ensure NonSuccessfulReponseCodeException knows the response code. signalapp/Signal-Android@4482bfc#diff-507a5191768a990795f0355668f7c384a88bfd18a4448c29c37ea80ca547f39b Also some bug fixes for CDS
1 parent c5d23fd commit 5baa534

31 files changed

+288
-109
lines changed

libsignal-service-dotnet/SignalServiceAccountManager.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,25 +128,35 @@ public async Task SetGcmId(CancellationToken token,string gcmRegistrationId)// t
128128
/// an SMS verification code to this Signal user.
129129
/// </summary>
130130
/// <returns></returns>
131-
public async Task RequestSmsVerificationCode(CancellationToken token)// throws IOException
131+
public async Task RequestSmsVerificationCode(string? captchaToken, CancellationToken? token = null)// throws IOException
132132
{
133-
await pushServiceSocket.CreateAccount(token, false);
133+
if (token == null)
134+
{
135+
token = CancellationToken.None;
136+
}
137+
138+
await pushServiceSocket.RequestSmsVerificationCodeAsync(captchaToken, token.Value);
134139
}
135140

136141
/// <summary>
137142
/// Request a Voice verification code. On success, the server will
138143
/// make a voice call to this Signal user.
139144
/// </summary>
140145
/// <returns></returns>
141-
public async Task RequestVoiceVerificationCode(CancellationToken token)// throws IOException
146+
public async Task RequestVoiceVerificationCode(string? captchaToken, CancellationToken? token = null)// throws IOException
142147
{
143-
await pushServiceSocket.CreateAccount(token, true);
148+
if (token == null)
149+
{
150+
token = CancellationToken.None;
151+
}
152+
153+
await pushServiceSocket.RequestVoiceVerificationCodeAsync(captchaToken, token.Value);
144154
}
145155

146156
/// <summary>
147157
/// Verify a Signal Service account with a received SMS or voice verification code.
148158
/// </summary>
149-
/// <param name="token">The cacellation token</param>
159+
/// <param name="token">The cancellation token</param>
150160
/// <param name="verificationCode">The verification code received via SMS or Voice
151161
/// <see cref="RequestSmsVerificationCode(CancellationToken)"/> and <see cref="RequestVoiceVerificationCode(CancellationToken)"/></param>
152162
/// <param name="signalingKey">52 random bytes. A 32 byte AES key and a 20 byte Hmac256 key, concatenated.</param>

libsignal-service-dotnet/contacts/crypto/RemoteAttestationCipher.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static void VerifyIasSignature(string certificates, string signatureBody,
8383
throw new SignatureException($"Unexpected signed quote version {signatureBodyEntity.Version}");
8484
}
8585

86-
if (Enumerable.SequenceEqual(ByteUtil.trim(signatureBodyEntity.IsvEnclaveQuoteBody, 432), ByteUtil.trim(quote.QuoteBytes, 432)))
86+
if (!Enumerable.SequenceEqual(ByteUtil.trim(signatureBodyEntity.IsvEnclaveQuoteBody, 432), ByteUtil.trim(quote.QuoteBytes, 432)))
8787
{
8888
throw new SignatureException($"Signed quote is not the same as RA quote: {Hex.ToStringCondensed(signatureBodyEntity.IsvEnclaveQuoteBody!)} vs {Hex.ToStringCondensed(quote.QuoteBytes)}");
8989
}
@@ -93,7 +93,7 @@ public static void VerifyIasSignature(string certificates, string signatureBody,
9393
throw new SignatureException($"Quote status is: {signatureBodyEntity.IsvEnclaveQuoteStatus}");
9494
}
9595

96-
DateTime datetime = DateTime.ParseExact(signatureBodyEntity.Timestamp, "yyy-MM-ddTHH:mm:ss.FFFFFF", CultureInfo.InvariantCulture);
96+
DateTime datetime = DateTime.ParseExact(signatureBodyEntity.Timestamp, "yyyy-MM-ddTHH:mm:ss.FFFFFF", CultureInfo.InvariantCulture);
9797
datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc);
9898
if (datetime.AddDays(1) < DateTime.UtcNow)
9999
{

libsignal-service-dotnet/contacts/crypto/SignatureBodyEntity.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ namespace libsignalservice.contacts.crypto
55
internal class SignatureBodyEntity
66
{
77
[JsonProperty("isvEnclaveQuoteBody")]
8-
public byte[]? IsvEnclaveQuoteBody { get; }
8+
public byte[]? IsvEnclaveQuoteBody { get; private set; }
99

1010
[JsonProperty("isvEnclaveQuoteStatus")]
11-
public string? IsvEnclaveQuoteStatus { get; }
11+
public string? IsvEnclaveQuoteStatus { get; private set; }
1212

1313
[JsonProperty("version")]
14-
public long Version { get; }
14+
public long Version { get; private set; }
1515

1616
[JsonProperty("timestamp")]
17-
public string? Timestamp { get; }
17+
public string? Timestamp { get; private set; }
1818
}
1919
}

libsignal-service-dotnet/contacts/entities/DiscoveryResponse.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ namespace libsignalservice.contacts.entities
66
internal class DiscoveryResponse
77
{
88
[JsonProperty("requestId")]
9-
public byte[]? RequestId { get; }
9+
public byte[]? RequestId { get; private set; }
1010

1111
[JsonProperty("iv")]
12-
public byte[]? Iv { get; }
12+
public byte[]? Iv { get; private set; }
1313

1414
[JsonProperty("data")]
15-
public byte[]? Data { get; }
15+
public byte[]? Data { get; private set; }
1616

1717
[JsonProperty("mac")]
18-
public byte[]? Mac { get; }
18+
public byte[]? Mac { get; private set; }
1919

2020
public override string ToString()
2121
{

libsignal-service-dotnet/contacts/entities/MultiRemoteAttestationResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ namespace libsignalservice.contacts.entities
66
internal class MultiRemoteAttestationResponse
77
{
88
[JsonProperty("attestations")]
9-
public Dictionary<string, RemoteAttestationResponse>? Attestations { get; }
9+
public Dictionary<string, RemoteAttestationResponse>? Attestations { get; private set; }
1010
}
1111
}

libsignal-service-dotnet/push/DeviceLimitExceededException.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22

33
namespace libsignalservice.push
44
{
5-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
6-
public class DeviceLimitExceededException : NonSuccessfulResponseCodeException
5+
internal class DeviceLimitExceededException : NonSuccessfulResponseCodeException
76
{
87
public DeviceLimit Limit { get; set; }
98

10-
public DeviceLimitExceededException(DeviceLimit deviceLimit)
9+
public DeviceLimitExceededException(DeviceLimit deviceLimit) : base(411)
1110
{
12-
this.Limit = deviceLimit;
11+
Limit = deviceLimit;
1312
}
1413
}
15-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
1614
}
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
using libsignalservice.push.exceptions;
42

53
namespace libsignalservice.push
64
{
7-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
8-
public class LockedException : Exception
5+
internal class LockedException : NonSuccessfulResponseCodeException
96
{
107
public int Length { get; }
118
public long TimeRemaining { get; }
129

13-
public LockedException(int length, long timeRemaining)
10+
public LockedException(int length, long timeRemaining) : base(423)
1411
{
1512
Length = length;
1613
TimeRemaining = timeRemaining;
1714
}
1815
}
19-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
2016
}

libsignal-service-dotnet/push/PushServiceSocket.cs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,50 @@ public PushServiceSocket(SignalServiceConfiguration serviceUrls,
8787
this.contactDiscoveryClients = CreateConnectionHolders(SignalConnectionInformation.SignalContactDiscoveryUrls);
8888
}
8989

90-
public async Task<bool> CreateAccount(CancellationToken token, bool voice)
90+
public async Task RequestSmsVerificationCodeAsync(string? captchaToken, CancellationToken? token = null)
9191
{
92-
string path = voice ? CREATE_ACCOUNT_VOICE_PATH : CREATE_ACCOUNT_SMS_PATH;
93-
await MakeServiceRequestAsync(token, string.Format(path, CredentialsProvider.User), "GET", null);
94-
return true;
92+
if (token == null)
93+
{
94+
token = CancellationToken.None;
95+
}
96+
97+
string path = string.Format(CREATE_ACCOUNT_SMS_PATH, CredentialsProvider.User);
98+
99+
if (captchaToken != null)
100+
{
101+
path += $"?captcha={captchaToken}";
102+
}
103+
104+
await MakeServiceRequestAsync(token.Value, path, "GET", null, (responseCode) =>
105+
{
106+
if (responseCode == 402)
107+
{
108+
throw new CaptchaRequiredException();
109+
}
110+
});
111+
}
112+
113+
public async Task RequestVoiceVerificationCodeAsync(string? captchaToken, CancellationToken? token = null)
114+
{
115+
if (token == null)
116+
{
117+
token = CancellationToken.None;
118+
}
119+
120+
string path = string.Format(CREATE_ACCOUNT_VOICE_PATH, CredentialsProvider.User);
121+
122+
if (captchaToken != null)
123+
{
124+
path += $"?captcha={captchaToken}";
125+
}
126+
127+
await MakeServiceRequestAsync(token.Value, path, "GET", null, (responseCode) =>
128+
{
129+
if (responseCode == 402)
130+
{
131+
throw new CaptchaRequiredException();
132+
}
133+
});
95134
}
96135

97136
public async Task<bool> VerifyAccountCode(CancellationToken token, string verificationCode, string signalingKey, uint registrationId, bool fetchesMessages, string pin,
@@ -181,7 +220,7 @@ public async Task<SendMessageResponse> SendMessage(OutgoingPushMessageList bundl
181220

182221
try
183222
{
184-
string responseText = await MakeServiceRequestAsync(string.Format(MESSAGE_PATH, bundle.Destination), "PUT", JsonUtil.ToJson(bundle), unidentifiedAccess, token);
223+
string responseText = await MakeServiceRequestAsync(string.Format(MESSAGE_PATH, bundle.Destination), "PUT", JsonUtil.ToJson(bundle), EmptyResponseCodeHandler, unidentifiedAccess, token);
185224
return JsonUtil.FromJson<SendMessageResponse>(responseText);
186225
}
187226
catch (NotFoundException nfe)
@@ -258,7 +297,7 @@ public async Task<List<PreKeyBundle>> GetPreKeys(SignalServiceAddress destinatio
258297
path = path + "?relay=" + destination.Relay;
259298
}
260299

261-
string responseText = await MakeServiceRequestAsync(path, "GET", null, unidentifiedAccess, token.Value);
300+
string responseText = await MakeServiceRequestAsync(path, "GET", null, EmptyResponseCodeHandler, unidentifiedAccess, token.Value);
262301
PreKeyResponse response = JsonUtil.FromJson<PreKeyResponse>(responseText);
263302
List<PreKeyBundle> bundles = new List<PreKeyBundle>();
264303

@@ -427,12 +466,12 @@ public async Task<SignalServiceProfile> RetrieveProfile(SignalServiceAddress tar
427466

428467
try
429468
{
430-
string response = await MakeServiceRequestAsync(string.Format(PROFILE_PATH, target.E164number), "GET", null, unidentifiedAccess, token.Value);
469+
string response = await MakeServiceRequestAsync(string.Format(PROFILE_PATH, target.E164number), "GET", null, EmptyResponseCodeHandler, unidentifiedAccess, token.Value);
431470
return JsonUtil.FromJson<SignalServiceProfile>(response);
432471
}
433472
catch (Exception e)
434473
{
435-
throw new NonSuccessfulResponseCodeException("Unable to parse entity: "+e.Message);
474+
throw new MalformedResponseException("Unable to parse entity", e);
436475
}
437476
}
438477

@@ -457,7 +496,7 @@ public async Task SetProfileAvatar(CancellationToken token, ProfileAvatarData pr
457496
}
458497
catch (IOException e)
459498
{
460-
throw new NonSuccessfulResponseCodeException("Unable to parse entity ("+e.Message+")");
499+
throw new MalformedResponseException("Unable to parse entity", e);
461500
}
462501

463502
if (profileAvatar != null)
@@ -547,7 +586,7 @@ public async Task<DiscoveryResponse> GetContactDiscoveryRegisteredUsersAsync(str
547586
}
548587
else
549588
{
550-
throw new NonSuccessfulResponseCodeException("Empty response!");
589+
throw new MalformedResponseException("Empty response!");
551590
}
552591
}
553592

@@ -688,9 +727,14 @@ private async Task<byte[]> UploadAttachment(CancellationToken token, string meth
688727
return (digest, encryptedData);
689728
}
690729

691-
private async Task<string> MakeServiceRequestAsync(CancellationToken token, string urlFragment, string method, string? body)
730+
private async Task<string> MakeServiceRequestAsync(CancellationToken token, string urlFragment, string method, string? body, Action<int>? responseCodeHandler = null)
692731
{
693-
return await MakeServiceRequestAsync(urlFragment, method, body, null, token);
732+
if (responseCodeHandler == null)
733+
{
734+
responseCodeHandler = EmptyResponseCodeHandler;
735+
}
736+
737+
return await MakeServiceRequestAsync(urlFragment, method, body, responseCodeHandler, null, token);
694738
}
695739

696740

@@ -705,7 +749,8 @@ private async Task<string> MakeServiceRequestAsync(CancellationToken token, stri
705749
/// <returns></returns>
706750
/// <exception cref="NonSuccessfulResponseCodeException"></exception>
707751
/// <exception cref="PushNetworkException"></exception>
708-
private async Task<string> MakeServiceRequestAsync(string urlFragment, string method, string? body, UnidentifiedAccess? unidentifiedAccess, CancellationToken? token = null)
752+
/// <exception cref="MalformedResponseException"></exception>
753+
private async Task<string> MakeServiceRequestAsync(string urlFragment, string method, string? body, Action<int> responseCodeHandler, UnidentifiedAccess? unidentifiedAccess, CancellationToken? token = null)
709754
{
710755
if (token == null)
711756
{
@@ -728,13 +773,15 @@ private async Task<string> MakeServiceRequestAsync(string urlFragment, string me
728773
throw new PushNetworkException(ioe);
729774
}
730775

776+
responseCodeHandler.Invoke((int)responseCode);
777+
731778
switch ((uint)responseCode)
732779
{
733780
case 413: // HttpStatusCode.RequestEntityTooLarge
734781
throw new RateLimitException("Rate limit exceeded: " + responseCode);
735782
case 401: // HttpStatusCode.Unauthorized
736783
case 403: // HttpStatusCode.Forbidden
737-
throw new AuthorizationFailedException("Authorization failed!");
784+
throw new AuthorizationFailedException((int)responseCode, "Authorization failed!");
738785
case 404: // HttpStatusCode.NotFound
739786
throw new NotFoundException("Not found");
740787
case 409: // HttpStatusCode.Conflict
@@ -789,8 +836,8 @@ private async Task<string> MakeServiceRequestAsync(string urlFragment, string me
789836

790837
if (responseCode != HttpStatusCode.OK && responseCode != HttpStatusCode.NoContent) // 200 & 204
791838
{
792-
throw new NonSuccessfulResponseCodeException("Bad response: " + (int)responseCode + " " +
793-
responseMessage);
839+
throw new NonSuccessfulResponseCodeException((int)responseCode,
840+
$"Bad response: {(int)responseCode} {responseMessage}");
794841
}
795842

796843
return responseBody;
@@ -925,7 +972,7 @@ private async Task<HttpResponseMessage> MakeRequestAsync(ConnectionHolder connec
925972
{
926973
case 401:
927974
case 403:
928-
throw new AuthorizationFailedException("Authorization failed!");
975+
throw new AuthorizationFailedException((int)response.StatusCode, "Authorization failed!");
929976
case 409:
930977
throw new RemoteAttestationResponseExpiredException("Remote attestation response expired");
931978
case 429:
@@ -934,11 +981,12 @@ private async Task<HttpResponseMessage> MakeRequestAsync(ConnectionHolder connec
934981

935982
if (response.Content != null)
936983
{
937-
throw new NonSuccessfulResponseCodeException($"Response: {await response.Content.ReadAsStringAsync()}");
984+
throw new NonSuccessfulResponseCodeException((int)response.StatusCode,
985+
$"Response: {await response.Content.ReadAsStringAsync()}");
938986
}
939987
else
940988
{
941-
throw new NonSuccessfulResponseCodeException($"Response: null");
989+
throw new NonSuccessfulResponseCodeException((int)response.StatusCode, $"Response: null");
942990
}
943991
}
944992

@@ -968,6 +1016,10 @@ private T GetRandom<T>(T[] connections)
9681016
{
9691017
return connections[Util.generateRandomNumber() % connections.Length];
9701018
}
1019+
1020+
private void EmptyResponseCodeHandler(int responseCode)
1021+
{
1022+
}
9711023
}
9721024

9731025
internal class GcmRegistrationId

libsignal-service-dotnet/push/RemoteAttestationUtil.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static async Task<Dictionary<string, RemoteAttestation>> GetAndVerifyMult
3737

3838
if (response.Attestations!.Count == 0 || response.Attestations.Count > 3)
3939
{
40-
throw new NonSuccessfulResponseCodeException($"Incorrect number of attestations: {response.Attestations.Count}");
40+
throw new MalformedResponseException($"Incorrect number of attestations: {response.Attestations.Count}");
4141
}
4242

4343
foreach (var entry in response.Attestations)
@@ -67,7 +67,7 @@ private static async Task<ResponsePair> MakeAttestationRequestAsync(PushServiceS
6767

6868
if (body == null)
6969
{
70-
throw new NonSuccessfulResponseCodeException("Empty response!");
70+
throw new MalformedResponseException("Empty response!");
7171
}
7272

7373
return new ResponsePair(await body.ReadAsStringAsync(), ParseCookies(response));
Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
using System;
2-
31
namespace libsignalservice.push.exceptions
42
{
5-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
6-
public class AuthorizationFailedException : Exception
3+
public class AuthorizationFailedException : NonSuccessfulResponseCodeException
74
{
8-
public AuthorizationFailedException(String s)
9-
: base(s)
5+
public AuthorizationFailedException(int code, string s) : base(code, s)
106
{
117
}
12-
}
13-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
8+
}
149
}

0 commit comments

Comments
 (0)