misp-bump/app/src/main/java/lu/circl/mispbump/restful_client/MispRestClient.java

526 lines
18 KiB
Java

package lu.circl.mispbump.restful_client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.NoRouteToHostException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.util.Iterator;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import lu.circl.mispbump.auxiliary.PreferenceManager;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
* Implementation of the RetroFit2 Misp client.
* In order to conveniently use this api some wrapper interfaces are implemented to return the requested API endpoint as java object.
*/
public class MispRestClient {
private static final String TAG = "restClient";
public interface AvailableCallback {
void available();
void unavailable(String error);
}
public interface UserCallback {
void success(User user);
void failure(String error);
}
public interface OrganisationCallback {
void success(Organisation organisation);
void failure(String error);
}
public interface OrganisationsCallback {
void success(Organisation[] organisations);
void failure(String error);
}
public interface ServerCallback {
void success(List<MispServer> servers);
void success(MispServer server);
void success(Server server);
void failure(String error);
}
private PreferenceManager preferenceManager;
private MispRestService mispRestService;
/**
* Initializes the rest client to communicate with a MISP instance.
*
* @param context needed to access the preferences for loading credentials
*/
public MispRestClient(Context context) {
preferenceManager = PreferenceManager.getInstance(context);
String url = preferenceManager.getServerUrl();
Log.i(TAG, "URL: " + url);
try {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(getUnsafeOkHttpClient())
.build();
mispRestService = retrofit.create(MispRestService.class);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
}
/**
* NOTE: for development only!
* <p>
* Accepts all certificates including self signed.
*
* @return {@link OkHttpClient} which accepts all certificates
*/
private OkHttpClient getUnsafeOkHttpClient() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
// create logging interceptor
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(interceptor);
// create authorization interceptor
builder.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request.Builder ongoing = chain.request().newBuilder();
ongoing.addHeader("Accept", "application/json");
ongoing.addHeader("Content-Type", "application/json");
ongoing.addHeader("Authorization", preferenceManager.getAutomationKey());
return chain.proceed(ongoing.build());
}
});
return builder.build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// status routes
/**
* Check via pyMispRoute if server is available
*
* @param callback {@link AvailableCallback}
*/
public void isAvailable(final AvailableCallback callback) {
Call<Version> call = mispRestService.pyMispVersion();
call.enqueue(new Callback<Version>() {
@Override
public void onResponse(Call<Version> call, Response<Version> response) {
if (!response.isSuccessful()) {
if (response.code() == 403) {
callback.available();
return;
}
callback.unavailable(extractError(response));
} else {
callback.available();
}
}
@Override
public void onFailure(Call<Version> call, Throwable t) {
callback.unavailable(extractError(t));
}
});
}
// user routes
/**
* Fetches information about the user that is associated with saved auth key.
*
* @param callback {@link UserCallback} wrapper to return user directly
*/
public void getMyUser(final UserCallback callback) {
Call<MispUser> call = mispRestService.getMyUserInformation();
call.enqueue(new Callback<MispUser>() {
@Override
public void onResponse(Call<MispUser> call, Response<MispUser> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
if (response.body() != null) {
callback.success(response.body().user);
} else {
callback.failure("response body was null");
}
}
}
@Override
public void onFailure(Call<MispUser> call, Throwable t) {
t.printStackTrace();
callback.failure(t.getMessage());
}
});
}
/**
* Get an user with specific ID.
*
* @param userId user identifier
* @param callback {@link UserCallback} wrapper to return user directly
*/
public void getUser(int userId, final UserCallback callback) {
Call<MispUser> call = mispRestService.getUser(userId);
call.enqueue(new Callback<MispUser>() {
@Override
public void onResponse(Call<MispUser> call, Response<MispUser> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
if (response.body() != null) {
callback.success(response.body().user);
} else {
callback.failure("response body was null");
}
}
}
@Override
public void onFailure(Call<MispUser> call, Throwable t) {
t.printStackTrace();
callback.failure(t.getMessage());
}
});
}
/**
* Add a given user to the MISP instance referenced by url in preferences.
*
* @param user user to add
* @param callback {@link UserCallback} wrapper to return the created user directly
*/
public void addUser(User user, final UserCallback callback) {
Call<MispUser> call = mispRestService.addUser(user);
call.enqueue(new Callback<MispUser>() {
@Override
public void onResponse(Call<MispUser> call, Response<MispUser> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
assert response.body() != null;
callback.success(response.body().user);
}
}
@Override
public void onFailure(Call<MispUser> call, Throwable t) {
callback.failure(t.getMessage());
}
});
}
// organisation routes
/**
* Get an organisation by a given organisation id.
*
* @param orgId organisation identifier
* @param callback {@link OrganisationCallback} wrapper to return a organisation directly
*/
public void getOrganisation(int orgId, final OrganisationCallback callback) {
Call<MispOrganisation> call = mispRestService.getOrganisation(orgId);
call.enqueue(new Callback<MispOrganisation>() {
@Override
public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
if (response.body() != null) {
callback.success(response.body().organisation);
} else {
callback.failure("Response body was nul");
}
}
}
@Override
public void onFailure(Call<MispOrganisation> call, Throwable t) {
callback.failure(t.getMessage());
}
});
}
public Organisation[] getAllOrganisations() throws IOException {
Call<List<MispOrganisation>> call = mispRestService.getAllOrganisations();
Response<List<MispOrganisation>> response = call.execute();
List<MispOrganisation> mispOrganisations = response.body();
Organisation[] organisations = new Organisation[mispOrganisations.size()];
for (int i = 0; i < mispOrganisations.size(); i++) {
organisations[i] = mispOrganisations.get(i).organisation;
}
return organisations;
// call.enqueue(new Callback<List<MispOrganisation>>() {
// @Override
// public void onResponse(Call<List<MispOrganisation>> call, Response<List<MispOrganisation>> response) {
// if (!response.isSuccessful()) {
// // TODO handle
// return;
// }
//
// List<MispOrganisation> mispOrganisations = response.body();
//
// assert mispOrganisations != null;
//
// Organisation[] organisations = new Organisation[mispOrganisations.size()];
//
// for (int i = 0; i < mispOrganisations.size(); i++) {
// organisations[i] = mispOrganisations.get(i).organisation;
// }
//
// callback.success(organisations);
// }
//
// @Override
// public void onFailure(Call<List<MispOrganisation>> call, Throwable t) {
// callback.failure(extractError(t));
// }
// });
}
/**
* Add a given organisation to the MISP instance referenced by url in preferences.
*
* @param organisation organisation to add
* @param callback {@link OrganisationCallback} wrapper to return the created organisation directly
*/
public void addOrganisation(Organisation organisation, final OrganisationCallback callback) {
Call<MispOrganisation> call = mispRestService.addOrganisation(organisation);
call.enqueue(new Callback<MispOrganisation>() {
@Override
public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
assert response.body() != null;
callback.success(response.body().organisation);
}
}
@Override
public void onFailure(Call<MispOrganisation> call, Throwable t) {
callback.failure(t.getMessage());
}
});
}
// server routes
/**
* Get all servers on MISP instance.
*
* @param callback {@link OrganisationCallback} wrapper to return a list of servers directly
*/
public void getServers(final ServerCallback callback) {
Call<List<MispServer>> call = mispRestService.getServers();
call.enqueue(new Callback<List<MispServer>>() {
@Override
public void onResponse(Call<List<MispServer>> call, Response<List<MispServer>> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
callback.success(response.body());
}
}
@Override
public void onFailure(Call<List<MispServer>> call, Throwable t) {
callback.failure(t.getMessage());
}
});
}
/**
* Add a server to the MISP instance
*
* @param server the server to create
* @param callback {@link ServerCallback} wrapper to return the created server directly
*/
public void addServer(Server server, final ServerCallback callback) {
Call<Server> call = mispRestService.addServer(server);
call.enqueue(new Callback<Server>() {
@Override
public void onResponse(Call<Server> call, Response<Server> response) {
if (!response.isSuccessful()) {
callback.failure(extractError(response));
} else {
callback.success(response.body());
}
}
@Override
public void onFailure(Call<Server> call, Throwable t) {
callback.failure(t.getMessage());
}
});
}
// error parsing
/**
* Converts error {@link Response}s to human readable info.
* @param response erroneous response
* @param <T> type of response
* @return human readable String that describes the error
*/
private <T> String extractError(Response<T> response) {
switch (response.code()) {
// bad request (malformed)
case 400:
return "Bad request";
// unauthorized
case 401:
return "Unauthorized";
// forbidden
case 403:
try {
assert response.errorBody() != null;
JSONObject jsonError = new JSONObject(response.errorBody().string());
String name = jsonError.getString("name") + "\n";
if (name.startsWith("Authentication failed")) {
return "Authentication failed";
}
String reasons = "";
JSONObject errorReasons = jsonError.getJSONObject("errors");
Iterator<String> errorKeys = errorReasons.keys();
while (errorKeys.hasNext()) {
reasons = reasons.concat(errorReasons.getString(errorKeys.next()) + "\n");
}
return name + reasons;
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "Could not parse (403) error";
// not found
case 404:
return "Not found";
}
return "Unknown error";
}
/**
* Converts a {@link Throwable} to a human readable error message.
* @param t throwable
* @return human readable String that describes the error.
*/
private String extractError(Throwable t) {
if (t.getCause() instanceof CertificateException) {
return "Trust anchor for certification path not found.\nSelf signed certificates are not supported.";
}
if (t instanceof SSLHandshakeException) {
return "SSL Handshake Error";
}
if (t instanceof NoRouteToHostException) {
return "Server is not available (no route to host)";
}
return t.getMessage();
}
}