Skip to content

Commit cc9cbfe

Browse files
golf1052Sanders Lauture
authored andcommitted
Support for contact discovery service - Part 1
Reflects signalapp/libsignal-service-java@69bd207 SigningCertificate.cs isn't fully implemented. There's some changes because of what looks like bugs in Java Part 1.
1 parent 210ec6a commit cc9cbfe

26 files changed

+1178
-69
lines changed

libsignal-service-dotnet-tests/ConnectionTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using libsignalservice;
1+
using libsignalservice;
22
using libsignalservice.configuration;
33
using libsignalservice.push.exceptions;
44
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -14,7 +14,7 @@ namespace libsignal_service_dotnet_tests
1414
public class ConnectionTest
1515
{
1616
public static SignalServiceUrl[] ServiceUrls = new SignalServiceUrl[] { new SignalServiceUrl("https://textsecure-service.whispersystems.org") };
17-
public static SignalServiceConfiguration ServiceConfiguration = new SignalServiceConfiguration(ServiceUrls, null);
17+
public static SignalServiceConfiguration ServiceConfiguration = new SignalServiceConfiguration(ServiceUrls, null, null);
1818
public static string UserAgent = "libsignal-service-dotnet-tests";
1919

2020
[TestMethod]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace libsignalservice
2+
{
3+
public static class HelperMethods
4+
{
5+
// taken from https://stackoverflow.com/a/14333437/6681022
6+
public static string ByteArrayToHexString(byte[]? bytes)
7+
{
8+
if (bytes == null)
9+
{
10+
return string.Empty;
11+
}
12+
13+
char[] c = new char[bytes.Length * 2];
14+
int b;
15+
for (int i = 0; i < bytes.Length; i++)
16+
{
17+
b = bytes[i] >> 4;
18+
c[i * 2] = (char)(55 + b + (((b-10)>>31)&-7));
19+
b = bytes[i] & 0xF;
20+
c[i * 2 + 1] = (char)(55 + b + (((b-10)>>31)&-7));
21+
}
22+
return new string(c);
23+
}
24+
}
25+
}

libsignal-service-dotnet/SignalServiceAccountManager.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
using libsignal.util;
77
using libsignal_service_dotnet.messages.calls;
88
using libsignalservice.configuration;
9+
using libsignalservice.contacts.crypto;
10+
using libsignalservice.contacts.entities;
911
using libsignalservice.crypto;
1012
using libsignalservice.messages.multidevice;
1113
using libsignalservice.push;
1214
using libsignalservice.push.http;
1315
using libsignalservice.util;
1416
using libsignalservice.websocket;
17+
using org.whispersystems.curve25519;
18+
using Org.BouncyCastle.Crypto;
1519
using Strilanc.Value;
1620
using System;
1721
using System.Collections.Generic;
@@ -248,6 +252,61 @@ public async Task<List<ContactTokenDetails>> GetContacts(CancellationToken token
248252
return activeTokens;
249253
}
250254

255+
public async Task<IList<string>> GetRegisteredUsers(IList<string> e164numbers, string mrenclave, CancellationToken? token = null)
256+
{
257+
if (token == null)
258+
{
259+
token = CancellationToken.None;
260+
}
261+
try
262+
{
263+
string authorizationToken = await PushServiceSocket.GetContactDiscoveryAuthorization(token);
264+
Curve25519 curve = Curve25519.getInstance(Curve25519.BEST);
265+
org.whispersystems.curve25519.Curve25519KeyPair keyPair = curve.generateKeyPair();
266+
267+
ContactDiscoveryCipher cipher = new ContactDiscoveryCipher();
268+
RemoteAttestationRequest attestationRequest = new RemoteAttestationRequest(keyPair.getPublicKey());
269+
(RemoteAttestationResponse, IList<string>) attestationResponse = await PushServiceSocket.GetContactDiscoveryRemoteAttestation(authorizationToken, attestationRequest, mrenclave, token);
270+
271+
RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, attestationResponse.Item1.ServerEphemeralPublic!, attestationResponse.Item1.ServerStaticPublic!);
272+
Quote quote = new Quote(attestationResponse.Item1.Quote!);
273+
byte[] requestId = cipher.GetRequestId(keys, attestationResponse.Item1);
274+
275+
cipher.VerifyServerQuote(quote, attestationResponse.Item1.ServerStaticPublic!, mrenclave);
276+
cipher.VerifyIasSignature(attestationResponse.Item1.Certificates!, attestationResponse.Item1.SignatureBody!, attestationResponse.Item1.Signature!, quote);
277+
278+
RemoteAttestation remoteAttestation = new RemoteAttestation(requestId, keys);
279+
List<string> addressBook = new List<string>();
280+
281+
foreach (string e164number in e164numbers)
282+
{
283+
addressBook.Add(e164number.Substring(1));
284+
}
285+
286+
DiscoveryRequest request = cipher.CreateDiscoveryRequest(addressBook, remoteAttestation);
287+
DiscoveryResponse response = await PushServiceSocket.GetContactDiscoveryRegisteredUsers(authorizationToken, request, attestationResponse.Item2, mrenclave, token);
288+
byte[] data = cipher.GetDiscoveryResponseData(response, remoteAttestation);
289+
290+
IEnumerator<string> addressBookIterator = addressBook.GetEnumerator();
291+
List<string> results = new List<string>();
292+
293+
foreach (byte aData in data)
294+
{
295+
addressBookIterator.MoveNext();
296+
string candidate = addressBookIterator.Current;
297+
298+
if (aData != 0)
299+
results.Add($"+{candidate}");
300+
}
301+
302+
return results;
303+
}
304+
catch (InvalidCipherTextException ex)
305+
{
306+
throw new UnauthenticatedResponseException(ex);
307+
}
308+
}
309+
251310
/// <summary>
252311
/// Request a UUID from the server for linking as a new device.
253312
/// Called by the new device.
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
using libsignalservice.push;
42

53
namespace libsignalservice.configuration
64
{
7-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
85
public class SignalCdnUrl : SignalUrl
96
{
10-
public SignalCdnUrl(string url, string hostHeader) : base(url, hostHeader) { }
7+
public SignalCdnUrl(string url) : base(url)
8+
{
9+
}
10+
11+
public SignalCdnUrl(string url, string? hostHeader) : base(url, hostHeader)
12+
{
13+
}
1114
}
12-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
1315
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using libsignalservice.push;
2+
3+
namespace libsignalservice.configuration
4+
{
5+
public class SignalContactDiscoveryUrl : SignalUrl
6+
{
7+
public SignalContactDiscoveryUrl(string url) : base(url)
8+
{
9+
}
10+
11+
public SignalContactDiscoveryUrl(string url, string? hostHeader) : base(url, hostHeader)
12+
{
13+
}
14+
}
15+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
namespace libsignalservice.configuration
1+
namespace libsignalservice.configuration
22
{
3-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
43
public class SignalServiceConfiguration
54
{
65
public SignalServiceUrl[] SignalServiceUrls { get; }
76
public SignalCdnUrl[] SignalCdnUrls { get; }
7+
public SignalContactDiscoveryUrl[] SignalContactDiscoveryUrls { get; }
88

9-
public SignalServiceConfiguration(SignalServiceUrl[] signalServiceUrls, SignalCdnUrl[] signalCdnUrls)
9+
public SignalServiceConfiguration(SignalServiceUrl[] signalServiceUrls, SignalCdnUrl[] signalCdnUrls, SignalContactDiscoveryUrl[] signalContactDiscoveryUrls)
1010
{
1111
SignalServiceUrls = signalServiceUrls;
1212
SignalCdnUrls = signalCdnUrls;
13+
SignalContactDiscoveryUrls = signalContactDiscoveryUrls;
1314
}
1415
}
15-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
1616
}
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
namespace libsignalservice.configuration
1+
using libsignalservice.push;
2+
3+
namespace libsignalservice.configuration
24
{
3-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
4-
public class SignalServiceUrl : SignalUrl
5-
{
6-
public SignalServiceUrl(string url): this(url, null) {}
7-
8-
public SignalServiceUrl(string url, string hostHeader) : base(url, hostHeader) {}
5+
public class SignalServiceUrl : SignalUrl
6+
{
7+
public SignalServiceUrl(string url) : base(url) {}
8+
9+
public SignalServiceUrl(string url, string? hostHeader) : base(url, hostHeader) {}
910
}
10-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
11-
}
11+
}
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
1+
using libsignalservice.push;
42

53
namespace libsignalservice.configuration
64
{
7-
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
85
public class SignalUrl
96
{
107
public string Url { get; }
118
public string? HostHeader { get; }
129

13-
public SignalUrl(string url) : this(url, null) { }
10+
public SignalUrl(string url) : this(url, null)
11+
{
12+
}
1413

15-
public SignalUrl(string url, string hostHeader)
14+
public SignalUrl(string url, string? hostHeader)
1615
{
1716
Url = url;
1817
HostHeader = hostHeader;
1918
}
2019
}
21-
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
2220
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using libsignalservice.contacts.crypto;
6+
using libsignalservice.contacts.entities;
7+
using libsignalservice.push;
8+
using org.whispersystems.curve25519;
9+
using Org.BouncyCastle.Crypto;
10+
11+
namespace libsignalservice.contacts
12+
{
13+
public class ContactDiscoveryClient
14+
{
15+
private readonly PushServiceSocket socket;
16+
17+
public ContactDiscoveryClient(PushServiceSocket socket)
18+
{
19+
this.socket = socket;
20+
}
21+
22+
public async Task<RemoteAttestation> GetRemoteAttestation(string mrenclave, CancellationToken? token)
23+
{
24+
if (token == null)
25+
{
26+
token = CancellationToken.None;
27+
}
28+
try
29+
{
30+
Curve25519 curve = Curve25519.getInstance(Curve25519.BEST);
31+
Curve25519KeyPair keyPair = curve.generateKeyPair();
32+
33+
ContactDiscoveryCipher cipher = new ContactDiscoveryCipher();
34+
RemoteAttestationRequest request = new RemoteAttestationRequest(keyPair.getPublicKey());
35+
RemoteAttestationResponse response = (await socket.GetContactDiscoveryRemoteAttestation("", request, mrenclave, token.Value)).Item1;
36+
37+
RemoteAttestationKeys keys = new RemoteAttestationKeys(keyPair, response.ServerEphemeralPublic!, response.ServerStaticPublic!);
38+
Quote quote = new Quote(response.Quote!);
39+
byte[] requestId = cipher.GetRequestId(keys, response);
40+
41+
cipher.VerifyServerQuote(quote, response.ServerStaticPublic!, mrenclave);
42+
cipher.VerifyIasSignature(response.Certificates!, response.SignatureBody!, response.Signature!, quote);
43+
44+
return new RemoteAttestation(requestId, keys);
45+
}
46+
catch (InvalidCipherTextException ex)
47+
{
48+
throw new UnauthenticatedResponseException(ex);
49+
}
50+
}
51+
52+
/// <summary>
53+
///
54+
/// </summary>
55+
/// <param name="addressBook"></param>
56+
/// <param name="remoteAttestation"></param>
57+
/// <param name="mrenclave"></param>
58+
/// <param name="token"></param>
59+
/// <returns></returns>
60+
/// <exception cref="IOException"></exception>
61+
public async Task<IList<string>> GetRegisteredUsers(IList<string> addressBook, RemoteAttestation remoteAttestation, string mrenclave, CancellationToken? token)
62+
{
63+
if (token == null)
64+
{
65+
token = CancellationToken.None;
66+
}
67+
try
68+
{
69+
ContactDiscoveryCipher cipher = new ContactDiscoveryCipher();
70+
DiscoveryRequest request = cipher.CreateDiscoveryRequest(addressBook, remoteAttestation);
71+
DiscoveryResponse response = await socket.GetContactDiscoveryRegisteredUsers("", request, new List<string>(), mrenclave, token.Value);
72+
byte[] data = cipher.GetDiscoveryResponseData(response, remoteAttestation);
73+
74+
IEnumerator<string> addressBookIterator = addressBook.GetEnumerator();
75+
List<string> results = new List<string>();
76+
77+
foreach (byte aData in data)
78+
{
79+
addressBookIterator.MoveNext();
80+
string candidate = addressBookIterator.Current;
81+
82+
if (aData != 0)
83+
results.Add(candidate);
84+
}
85+
86+
return results;
87+
}
88+
catch (InvalidCipherTextException ex)
89+
{
90+
throw new IOException(null, ex);
91+
}
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)