Implementing SSL pinning using Trustkit with Google Cloud Endpoints - android

I am using Google Cloud Endpoints to interact with the app engine backend for my Android app. I want to implement public key/SSL pinning. It's easy to do this for Android N and above, but i want to implement pinning for earlier versions of Android. It seems like a common way to do this is using Trustkit.
The Getting Started notes on the Trustkit link, describe how to set it up by setting the SSLSocketFactory, for example
// HttpsUrlConnection
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname));
// OkHttp 2.x
OkHttpClient client =
new OkHttpClient()
.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname));
But i'm not sure how to apply this to Google Cloud Endpoints because of the way the connection is set up, for example here's how its set up for Google Cloud Endpoints in my code
import com.xxxx.backend.myApi.MyApi;
//I believe the MyApi class is generated automatically as part of the Google Cloud Endpoints integration in Android Studio.
class EndpointsAsyncTask extends AsyncTask<HashMap, Void, String> {
#Override
protected String doInBackground(HashMap... params) {
HashMap<String, String> dict = params[0];
String data = dict.get("dataString");
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), null)
.setRootUrl("https://xxxxx").setApplicationName("xxxx");
myApiService = builder.build();
//This calls the API method
return myApiService.customApiMethodName(data).execute().getDict().toString();
}
}
What I want to find out is how i can I implement SSL pinning in early versions of Android e.g. API16-API23 while still connecting to Google Cloud Endpoints in the same way my App does as shown above?
If possible i'd like to use Trustkit, maybe there is a different way i can set up how to connect with my Api methods so i can use it? If this is not possible is there an alternative to Trustkit i can use? or just a completely different way to implement SSL pinning while still using Google Cloud Endpoints in this way?

You would have to stop using the AndroidHttp helper and write your own, based off of it.
Instead of just calling new NetHttpTransport() or new ApacheHttpTransport(), you must use their builders instead, for example:
public static HttpTransport newCompatibleTransport(String hostname) {
SSLSocketFactory factory = TrustKit.getInstance().getSSLSocketFactory(serverHostname);
if (AndroidUtils.isMinimumSdkLevel(9)) {
return new NetHttpTransport.Builder()
.setSslSocketFactory(factory)
.build();
}
return new ApacheHttpTransport.Builder()
.setSslSocketFactory(factory)
.build();
}

Related

how to add aws signature in android and call aws api gateway

I have written this code but I'm getting an error. How can I get to work?
But the same token works with postman.
Error:
{"message":"The security token included in the request is invalid."}
Code :
public class test extends AppCompatActivity {
private final AWS4Signer signer = new AWS4Signer();
Request<?> aws;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
AWSCredentials credentials = new BasicAWSCredentials("AccessKey", "SecretKey");
aws = generateBasicRequest();
signer.setServiceName("execute-api");
signer.sign(aws, credentials);
new get_aws().execute();
}
private Request<?> generateBasicRequest() {
Request<?> request = new DefaultRequest<Void>("execute-api");
request.addHeader("Content-type", "application/json");
String securityToken = "Session Token";
request.addHeader("X-Amz-Security-Token", securityToken);
request.addHeader("Host", "********.amazonaws.com");
request.addHeader("x-amz-archive-description", "test test");
request.setResourcePath("/");
request.setEndpoint(URI.create("https://******.execute-api.****.amazonaws.com/data/all"));
return request;
}
private class get_aws extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
BufferedReader in = null;
String data = null;
try {
HttpClient httpclient = new DefaultHttpClient();
HttpGet request = new HttpGet();
request.addHeader("Authorization", aws.getHeaders().get("Authorization"));
request.addHeader("X-Amz-Date",request_aws.getHeaders().get("X-Amz-Date"));
request.addHeader("Content-Type","application/x-www-form-urlencoded");
URI website = new URI("https://********.execute-api.*******.amazonaws.com/data/all");
request.setURI(website);
HttpResponse response = httpclient.execute(request);
in = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
String line = in.readLine();
Log.d("line", line);
} catch (Exception e) {
Log.e("log_tag", "Error in http connection " + e.toString());
}
return null;
}
#Override
protected void onPostExecute(Void result) {
}
#Override
protected void onPreExecute() {
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
}
To answer your immediate question, AWS can generate a Java SDK from your API Gateway for you.
Using the generated SDK, you can then pass an AWSCredentialsProvider object into your SDK.
AWSCredentials credentials = new BasicAWSCredentials("AccessKey", "SecretKey");
ApiClientFactory factory = new ApiClientFactory()
.credentialsProvider(credentials);
But...
You should never ship IAM access keys in a shipped application. These credentials can be retrieved by anyone who has installed your application by opening the .apk file.
Those credentials can then be used to access any other AWS actions the associated IAM User has access to in your account. This means anyone with access to the application apk (ie: anyone who can download the app from the app store) has access to your AWS account.
Depending what problem you're trying to solve will dictate the correct solution to the problem.
My Lambda needs an IAM Role to run
This is a fairly common mistake to make with API gateway when people see the "Invoke with caller credentials" option from API Gateway.
Uncheck this box and the Lambda will run with the IAM Role you defined in Lambda.
If requests fail after doing this, you need to make sure API Gateway has permission to invoke your lambda.
Restrict API to the application itself without users
Your application can't keep a secret and you have no user credentials.
You should disable Authorization completely, this is effectively a public API.
Requiring an API Key (and usage plan) to rate limit your API can be useful, but keep in mind this is not a security measure as, again - your application can't keep that key secret.
You want users to log in first (no existing source of users)
This makes sense if your API call is only designed to be called by registered users.
You'll need to configure Cognito User Pools for this. This shouldn't be confused with Cognito Federated Identities - which focuses on a different part of the problem. You can use it to solve this, but trust me - you'll be happier if you don't go down that path.
To get cracking you'll need to take a few steps:
Create a User Pool (detailed settings explained here).
Configure a Cognito Authorizer on your API Gateway.
Create an App Client for your pool. Don't generate a client secret key when you do this.
Integrate with your Android application. There's a prebuilt Android example available from AWS for getting the client side going: AmazonCognitoYourUserPoolsDemo
You want users to log in first (existing source of users)
If you can use SAML or OAuth2.0 / OpenID Connect to authenticate your users, follow the instructions and then configure federation.
If not, this is possibly the time to consider Cognito Federated Identities, specifically using the Developer Authenticated Identities process. But again, I'd really recommend against it.
API Gateway & Cognito is a massive topic. Hopefully the instructions provided are a great entry point to the relevant parts of the documentation.
Have you tried to look at the examples from AWS https://github.com/awslabs/aws-sdk-android-samples with cognito credentials, I found them easier to use, in case you want to use your AccessKey and SecretKey, you can also use something like this
AWSCredentials credentials = new BasicAWSCredentials("AccessKey", "SecretKey");
AmazonS3Client sS3Client = new AmazonS3Client(credentials,Region.getRegion("Region"));

Api calls in the app is not able to be debugged in Charles/Fiddler (Bypassing proxy)

My App is developed using Xamarin.Android. When I try to do API debugging using Charles, I am not able to see the APIs in Charles. But I can see APIs of the other Apps installed in my device.
I have set the proxy in the android device taking IP from my system.
Here is the code which I have used in the PCL code, which is used for both IOS and Android.
return new HttpClient(new AuthenticatedHttpClientHandler(new NativeMessageHandler()))
{
BaseAddress = new Uri(baseUrl),
Timeout = new TimeSpan(0, 0, timeout)
};
public class AuthenticatedHttpClientHandler : DelegatingHandler
{
public AuthenticatedHttpClientHandler(HttpMessageHandler innerHandler) : base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//------
}
}
This is common code written in PCL.
In IOS APIs are not getting bypassed from the proxy and able to track it in Charles.
The Mono HttpClient isn't using the proxy settings. Configure your app to use the native ClientHandler.
https://developer.xamarin.com/releases/android/xamarin.android_6/xamarin.android_6.1/#Native_HttpClientHandler
add a file to your project
insert XA_HTTP_CLIENT_HANDLER_TYPE=Xamarin.Android.Net.AndroidClientHandler
set Build Action to AndroidEnvironment
or create your HttpClient like:
var client = new HttpClient (new Xamarin.Android.Net.AndroidClientHandler ());

Authenticating your client to Cloud Endpoints without a Google Account login

I have been doing extensive research on how to authenticate your client (Android, iOS, web-app) with Cloud Endpoints without requiring your user to use their Google account login the way the documentation shows you.
The reason for this is that I want to secure my API or "lock it down" to only my specified clients. Sometimes I will have an app that does not have a user login. I would hate to pester my user to now sign in just so my API is secure. Or other times, I just want to manage my own users like on a website and not use Google+, Facebook, or whatever else login authentication.
To start, let me first show the way you can authenticate your Android app with your Cloud Endpoints API using the Google Accounts login as specified in the documentation. After that I will show you my findings and a potential area for a solution which I need help with.
(1) Specify the client IDs (clientIds) of apps authorized to make requests to your API backend and (2) add a User parameter to all exposed methods to be protected by authorization.
public class Constants {
public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com";
public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com";
public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
}
import com.google.api.server.spi.auth.common.User; //import for the User object
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
public class MyEndpoint {
/** A simple endpoint method that takes a name and says Hi back */
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
(3) In Android call the API method in an Asynctask making sure to pass in the credential variable in the Builder:
class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
private static MyApi myApiService = null;
private Context context;
#Override
protected String doInBackground(Pair<Context, String>... params) {
credential = GoogleAccountCredential.usingAudience(this,
"server:client_id:1-web-app.apps.googleusercontent.com");
credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
if(myApiService == null) { // Only do this once
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), credential)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
// end options for devappserver
myApiService = builder.build();
}
context = params[0].first;
String name = params[0].second;
try {
return myApiService.sayHi(name).execute().getData();
} catch (IOException e) {
return e.getMessage();
}
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(context, result, Toast.LENGTH_LONG).show();
}
}
What is happening is that in your Android app you are showing the Google account picker first, storing that Google account email in you shared preferences, and then later setting it as part of the GoogleAccountCredential object (more info on how to do that here).
The Google App Engine server receives your request and checks it. If the Android Client is one of the ones you specified in the #Api notation, then the server will inject the com.google.api.server.spi.auth.common.User object into your API method. It is now your responsibility to check if that User object is null or not inside your API method. If the User object is null, you should throw an exception in your method to prevent it from running. If you do not do this check, your API method will execute (a no-no if you are trying to restrict access to it).
You can get your ANDROID_CLIENT_ID by going to your Google Developers Console. There, you provide the package name of your Android App and the SHA1 which generates for you an android client id for you to use in your #Api annotation (or put it in a class Constants like specified above for usability).
I have done some extensive testing with all of the above and here is what I found:
If you specify a bogus or invalid Android clientId in your #Api annotation, the User object will be null in your API method. If you are doing a check for if (user == null) throw new UnauthorizedException("User is Not Valid"); then your API method will not run.
This is surprising because it appears there is some behind the scenes validation going on in Cloud Endpoints that check whether the Android ClientId is valid or not. If it is invalid, it won't return the User object - even if the end user logged in to their Google account and the GoogleAccountCredential was valid.
My question is, does anyone know how I can check for that type of ClientId validation on my own in my Cloud Endpoints methods? Could that information be passed around in an HttpHeader for example?
Another injected type in Cloud Endpoints is the javax.servlet.http.HttpServletRequest. You can get the request like this in your API method:
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name, HttpServletRequest req) throws UnauthorizedException {
String Auth = req.getHeader("Authorization");//always null based on my tests
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
But I am not sure if the necessary information is there or how to get it.
Certainly somewhere there must be some data that tells us if the Client is an authorized and specified one in the #Api clientIds.
This way, you could lock-down your API to your Android app (and potentially other clients) without ever having to pester your end users to log in (or just create your own simple username + password login).
For all of this to work though, you would have to pass in null in the third argument of your Builder like this:
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
Then in your API method extract whether or not the call came from an authenticated client, and either throw an exception or run whatever code you wanted to.
I know this is possible because when using a GoogleAccountCredential in the Builder, somehow Cloud Endpoints knows whether or not the call came from an authenticated client and then either injects its User object into the API method or not based on that.
Could that information be in the header or body somehow? If so, how can I get it out to later check if it is there or not in my API method?
Note: I read the other posts on this topic. They offer ways to pass in your own authentication token - which is fine - but your .apk will still not be secure if someone decompiles it. I think if my hypothesis works, you will be able to lock-down your Cloud Endpoints API to a client without any logins.
Custom Authentication for Google Cloud Endpoints (instead of OAuth2)
Authenticate my "app" to Google Cloud Endpoints not a "user"
Google Cloud Endpoints without Google Accounts
EDIT:
We used Gold Support for the Google Cloud Platform and have been talking back and forth with their support team for weeks. This is their final answer for us:
"Unfortunately, I haven't had any luck on this. I've asked around my
team, and checked all of the documentation. It looks like using OAuth2
is your only option. The reason is because the endpoint servers handle
the authentication before it reaches your app. This means you wouldn't
be able to develop your own authentication flow, and would get results
much like what you were seeing with the tokens.
I would be happy to submit a feature request for you. If you could
provide a little more information about why the OAuth2 flow doesn't
work for your customers, I can put the rest of the information
together and submit it to the product manager."
(frowny face) - however, maybe it is still possible?
I have implemented Endpoint Auth using a custom header "Authorization" and it works just fine. In my case this token is set after login but should work all the same with your app. Check your tests because the value should be there.
The way to retrieve that header is indeed:
String Auth = req.getHeader("Authorization");
You could take it a step further and define your own implementations of an Authenticator and apply it to your secure API calls.
So you don't have any user specific info, but just want to ensure that only your app is able to communicate with your backend...
This is what i think,
change
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
to
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.ANDROID_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
The Client ID is generated from the signature of your app. It can't be replicated. If you only allow your endpoints to accept requests from the Android App, your problem would be solved.
Tell me if this works.
Faced the same problem to find a solution to call my API safely from my endpoints, without using Google Account. We can't decompile an IOS App (Bundle), but decompile an Android App is so simple..
The solution I found is not perfect but do the job pretty good:
On android APP, I just create an constant String variable, named APIKey, with simply content (For example "helloworld145698")
Then I encrypt it with sha1, next md5, and finally sha1 (Order and frequency of encryption up to you) and store the variable on SharedPref (For Android) in private mode (Do this action on an random class in your App) It's this result encrypted I authorize on my Backend !
On my backend, I just add a parameter (named token for exemple) on every request
Example:
#ApiMethod(name = "sayHi")
public void sayHi(#Named("name") String name, #Named("Token") String token) {
if (token == tokenStoreOnAPIServer) {
//Allow it
} else {
//Refuse it and print error
}
}
On android, active ProGuard for obfuscated your code. It will be really unreadable for anyone who tried to decompile your app (Reverse engineering is really hardcore)
Not THE perfect secure solution, but it works, and it will be really really (really) difficult to find the real API key for anyone who try to read your code after decompilation.

How to cancel ongoing request in retrofit when retrofit.client.UrlConnectionClient is used as client?

I am using retrofit for http calls in my Android application and retrofit.client.UrlConnectionClient as the client while building the adapter.
RestAdapter.Builder builder = new RestAdapter.Builder()
.setEndpoint(url)
.setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(
new Client.Provider() {
public Client get() {
return new UrlConnectionClient() {
#Override
protected HttpURLConnection openConnection(Request request)
throws IOException {
HttpURLConnection connection = super.openConnection(request);
connection.setConnectTimeout(connectionTimeout);
return connection;
}
I wanted to set the timeout so I have used UrlConnectionClient as my client. I could not find a way with other clients like OkHttp.
Question is : How can I cancel the ongoing request ?.
I have seen a similar post # Using Square's Retrofit Client, is it possible to cancel an in progress request? If so how? but my code would get really complex if I try to add my own executors and try to cancel the request using that. I am looking if there is slightly a better way with my existing code.
I also see that Retorofit V2.0 has plan for Retry and Cancel but not sure when that would be released..https://github.com/square/retrofit/issues/297
Need help !
In fact I also need a way to retry with the same code.
This has been available since 2.0.0-beta2 (https://github.com/square/retrofit/releases/tag/parent-2.0.0-beta2). I don't know if there is a doc that explains that but here is the link to API:
http://square.github.io/retrofit/2.x/retrofit/retrofit/Call.html#cancel--
'Call' API allows to do Retry as well by 'Clone'ing the request.

Set headers using spring android resttemplate and android annotations

I am currently playing with Spring Android Resttemplate to interact with a java-backed REST API. Actually, I am using android annotations to send http calls to this back-end service and I must say it rocks. Basically, Android annotations allows you to define an interface for the service calls and the http methods to be used for each api call available : it will generate all the boiler-plate code related to low-level stuff like marshalling/unmarshalling, calling the right http method according to the interface definition.
Now, I would like to set some headers to http requests : How can I achieve this knowing that I only have a reference to the Service interface defining all the calls ?
I can also have reference to the RestTemplate object but it seems there is now way of setting the headers.
any help would really be appreciated
thanks
The way I approached it is by creating an instance of ApiClient in the application class and set a custom REST template.
In my case I was using Jackson for JSON message conversion:
RestTemplate restTemplate = new RestTemplate(fac);
MappingJacksonHttpMessageConverter converter =
new MappingJacksonHttpMessageConverter();
converter.getObjectMapper().configure(Feature.UNWRAP_ROOT_VALUE, true);
restTemplate
.getMessageConverters()
.add(converter);
mClient.setRestTemplate(restTemplate);
My request factory fac then looks like this:
ClientHttpRequestFactory fac = new HttpComponentsClientHttpRequestFactory() {
#Override
protected HttpUriRequest createHttpRequest(HttpMethod httpMethod, URI uri) {
HttpUriRequest uriRequest = super.createHttpRequest(httpMethod, uri);
// Add request headers
uriRequest.addHeader(
"Content-Type",
MediaType.APPLICATION_JSON_VALUE);
return uriRequest;
}
#Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod)
throws IOException {
if (Config.DEBUG_REQUESTS) {
Log.d(TAG, uri);
}
return super.createRequest(uri, httpMethod);
}
};
WARNING
Although this works on all Android devices in our office, I've recently discovered that headers don't appear to be added with all devices! I'm not sure why this is (or which devices specifically), but I'm looking in to it and will try to update this answer when I've found a resolution.

Categories

Resources