mirror of
https://github.com/oxen-io/session-android.git
synced 2025-03-28 01:22:12 +00:00
226 lines
8.1 KiB
Java
226 lines
8.1 KiB
Java
package org.whispersystems.textsecure.push;
|
|
|
|
import android.content.Context;
|
|
import android.content.res.AssetManager;
|
|
import android.util.Base64;
|
|
import android.util.Log;
|
|
|
|
import com.google.thoughtcrimegson.Gson;
|
|
import org.whispersystems.textsecure.Release;
|
|
import org.whispersystems.textsecure.directory.DirectoryDescriptor;
|
|
import org.whispersystems.textsecure.directory.NumberFilter;
|
|
import org.whispersystems.textsecure.util.Util;
|
|
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
import javax.net.ssl.SSLContext;
|
|
import javax.net.ssl.TrustManagerFactory;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.security.KeyManagementException;
|
|
import java.security.KeyStore;
|
|
import java.security.KeyStoreException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.cert.CertificateException;
|
|
import java.util.zip.GZIPInputStream;
|
|
|
|
public class PushServiceSocket {
|
|
|
|
private static final String CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/%s";
|
|
private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/%s";
|
|
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s";
|
|
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
|
|
|
|
private static final String DIRECTORY_PATH = "/v1/directory/";
|
|
private static final String MESSAGE_PATH = "/v1/messages/";
|
|
|
|
private final String localNumber;
|
|
private final String password;
|
|
private final TrustManagerFactory trustManagerFactory;
|
|
|
|
public PushServiceSocket(Context context, String localNumber, String password) {
|
|
this.localNumber = localNumber;
|
|
this.password = password;
|
|
this.trustManagerFactory = initializeTrustManagerFactory(context);
|
|
}
|
|
|
|
public void createAccount(boolean voice) throws IOException, RateLimitException {
|
|
String path = voice ? CREATE_ACCOUNT_VOICE_PATH : CREATE_ACCOUNT_SMS_PATH;
|
|
makeRequest(String.format(path, localNumber), "POST", null);
|
|
}
|
|
|
|
public void verifyAccount(String verificationCode) throws IOException, RateLimitException {
|
|
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode), "PUT", null);
|
|
}
|
|
|
|
public void registerGcmId(String gcmRegistrationId) throws IOException, RateLimitException {
|
|
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
|
|
makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
|
|
}
|
|
|
|
public void unregisterGcmId(String gcmRegistrationId) throws IOException, RateLimitException {
|
|
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
|
|
makeRequest(REGISTER_GCM_PATH, "DELETE", new Gson().toJson(registration));
|
|
}
|
|
|
|
public void sendMessage(String recipient, String messageText)
|
|
throws IOException, RateLimitException
|
|
{
|
|
OutgoingGcmMessage message = new OutgoingGcmMessage(recipient, messageText);
|
|
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message));
|
|
GcmMessageResponse response = new Gson().fromJson(responseText, GcmMessageResponse.class);
|
|
|
|
if (response.getFailure().size() != 0)
|
|
throw new IOException("Got send failure: " + response.getFailure().get(0));
|
|
}
|
|
|
|
public void retrieveDirectory(Context context ) {
|
|
try {
|
|
DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null),
|
|
DirectoryDescriptor.class);
|
|
|
|
File directoryData = downloadExternalFile(context, directoryDescriptor.getUrl());
|
|
|
|
NumberFilter.getInstance(context).update(directoryData,
|
|
directoryDescriptor.getCapacity(),
|
|
directoryDescriptor.getHashCount(),
|
|
directoryDescriptor.getVersion());
|
|
|
|
} catch (IOException ioe) {
|
|
Log.w("PushServiceSocket", ioe);
|
|
} catch (RateLimitException e) {
|
|
Log.w("PushServiceSocket", e);
|
|
}
|
|
}
|
|
|
|
private File downloadExternalFile(Context context, String url) throws IOException {
|
|
File download = File.createTempFile("directory", ".dat", context.getFilesDir());
|
|
URL downloadUrl = new URL(url);
|
|
HttpsURLConnection connection = (HttpsURLConnection)downloadUrl.openConnection();
|
|
connection.setDoInput(true);
|
|
|
|
if (connection.getResponseCode() != 200) {
|
|
throw new IOException("Bad response: " + connection.getResponseCode());
|
|
}
|
|
|
|
OutputStream output = new FileOutputStream(download);
|
|
InputStream input = new GZIPInputStream(connection.getInputStream());
|
|
byte[] buffer = new byte[4096];
|
|
int read;
|
|
|
|
while ((read = input.read(buffer)) != -1) {
|
|
output.write(buffer, 0, read);
|
|
}
|
|
|
|
output.close();
|
|
|
|
return download;
|
|
}
|
|
|
|
private String makeRequest(String urlFragment, String method, String body)
|
|
throws IOException, RateLimitException
|
|
{
|
|
HttpURLConnection connection = getConnection(urlFragment, method);
|
|
|
|
if (body != null) {
|
|
connection.setDoOutput(true);
|
|
}
|
|
|
|
connection.connect();
|
|
|
|
if (body != null) {
|
|
Log.w("PushServiceSocket", method + " -- " + body);
|
|
OutputStream out = connection.getOutputStream();
|
|
out.write(body.getBytes());
|
|
out.close();
|
|
}
|
|
|
|
if (connection.getResponseCode() == 413) {
|
|
throw new RateLimitException("Rate limit exceeded: " + connection.getResponseCode());
|
|
}
|
|
|
|
if (connection.getResponseCode() != 200) {
|
|
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
|
|
}
|
|
|
|
return Util.readFully(connection.getInputStream());
|
|
}
|
|
|
|
private HttpURLConnection getConnection(String urlFragment, String method) throws IOException {
|
|
try {
|
|
SSLContext context = SSLContext.getInstance("TLS");
|
|
context.init(null, trustManagerFactory.getTrustManagers(), null);
|
|
|
|
URL url = new URL(String.format("%s%s", Release.PUSH_SERVICE_URL, urlFragment));
|
|
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
|
|
|
|
if (Release.ENFORCE_SSL) {
|
|
((HttpsURLConnection)connection).setSSLSocketFactory(context.getSocketFactory());
|
|
}
|
|
|
|
connection.setRequestMethod(method);
|
|
connection.setRequestProperty("Content-Type", "application/json");
|
|
|
|
if (password != null) {
|
|
connection.setRequestProperty("Authorization", getAuthorizationHeader());
|
|
}
|
|
|
|
return connection;
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
} catch (KeyManagementException e) {
|
|
throw new AssertionError(e);
|
|
} catch (MalformedURLException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
private String getAuthorizationHeader() {
|
|
try {
|
|
return "Basic " + new String(Base64.encode((localNumber + ":" + password).getBytes("UTF-8"), Base64.NO_WRAP));
|
|
} catch (UnsupportedEncodingException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
private TrustManagerFactory initializeTrustManagerFactory(Context context) {
|
|
try {
|
|
AssetManager assetManager = context.getAssets();
|
|
InputStream keyStoreInputStream = assetManager.open("whisper.store");
|
|
KeyStore trustStore = KeyStore.getInstance("BKS");
|
|
|
|
trustStore.load(keyStoreInputStream, "whisper".toCharArray());
|
|
|
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
|
|
trustManagerFactory.init(trustStore);
|
|
|
|
return trustManagerFactory;
|
|
} catch (KeyStoreException kse) {
|
|
throw new AssertionError(kse);
|
|
} catch (CertificateException e) {
|
|
throw new AssertionError(e);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
} catch (IOException ioe) {
|
|
throw new AssertionError(ioe);
|
|
}
|
|
}
|
|
|
|
private class GcmRegistrationId {
|
|
private String gcmRegistrationId;
|
|
|
|
public GcmRegistrationId() {}
|
|
|
|
public GcmRegistrationId(String gcmRegistrationId) {
|
|
this.gcmRegistrationId = gcmRegistrationId;
|
|
}
|
|
}
|
|
|
|
}
|