Skip to content

Commit

Permalink
Merge cec4a96 into 126d643
Browse files Browse the repository at this point in the history
  • Loading branch information
najmsheikh committed Jun 14, 2019
2 parents 126d643 + cec4a96 commit 25f1c8f
Show file tree
Hide file tree
Showing 17 changed files with 1,191 additions and 12 deletions.
4 changes: 4 additions & 0 deletions button-merchant/build.gradle
Expand Up @@ -57,6 +57,10 @@ android {
unitTests.returnDefaultValues = true
unitTests.includeAndroidResources = true
}

sourceSets {
test.resources.srcDirs += 'src/test/res'
}
}

configurations {
Expand Down
@@ -0,0 +1,143 @@
/*
* AmazonCertificateProvider.java
*
* Copyright (c) 2019 Button, Inc. (https://usebutton.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

package com.usebutton.merchant;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* Certificate provider for Amazon Certificate Manager. The public keys and root certificates
* provided are for ACM CA1, CA2, CA3, and CA4.
*
* @see CertificateProvider
*/
final class AmazonCertificateProvider extends CertificateProvider {

private static final String[] HASHED_KEYS = {
"++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=",
"f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=",
"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="
};
private static final Set<String> HASHED_KEYSET = new HashSet<>(Arrays.asList(HASHED_KEYS));

private static final String[] CERTIFICATES = {
"-----BEGIN CERTIFICATE-----\n"
+ "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n"
+ "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n"
+ "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n"
+ "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n"
+ "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n"
+ "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n"
+ "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n"
+ "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n"
+ "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n"
+ "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n"
+ "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n"
+ "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n"
+ "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n"
+ "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n"
+ "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n"
+ "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n"
+ "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n"
+ "rqXRfboQnoZsG4q5WTP468SQvvG5\n"
+ "-----END CERTIFICATE-----\n",

"-----BEGIN CERTIFICATE-----\n"
+ "MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF\n"
+ "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n"
+ "b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL\n"
+ "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n"
+ "b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK\n"
+ "gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ\n"
+ "W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg\n"
+ "1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K\n"
+ "8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r\n"
+ "2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me\n"
+ "z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR\n"
+ "8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj\n"
+ "mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz\n"
+ "7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6\n"
+ "+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI\n"
+ "0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\n"
+ "Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm\n"
+ "UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2\n"
+ "LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY\n"
+ "+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS\n"
+ "k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl\n"
+ "7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm\n"
+ "btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl\n"
+ "urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+\n"
+ "fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63\n"
+ "n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE\n"
+ "76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H\n"
+ "9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT\n"
+ "4PsJYGw=\n"
+ "-----END CERTIFICATE-----\n",

"-----BEGIN CERTIFICATE-----\n"
+ "MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\n"
+ "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n"
+ "Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\n"
+ "A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n"
+ "Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl\n"
+ "ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j\n"
+ "QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr\n"
+ "ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr\n"
+ "BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\n"
+ "YyRIHN8wfdVoOw==\n"
+ "-----END CERTIFICATE-----\n",

"-----BEGIN CERTIFICATE-----\n"
+ "MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5\n"
+ "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n"
+ "Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\n"
+ "A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n"
+ "Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi\n"
+ "9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk\n"
+ "M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB\n"
+ "/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB\n"
+ "MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\n"
+ "CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW\n"
+ "1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n"
+ "-----END CERTIFICATE-----"
};

@Override
String[] getCertificates() {
return CERTIFICATES;
}

@Override
Set<String> getPublicKeys() {
return HASHED_KEYSET;
}

@Override
String getProviderName() {
return "amazon";
}
}
@@ -0,0 +1,40 @@
/*
* AndroidEncoder.java
*
* Copyright (c) 2019 Button, Inc. (https://usebutton.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

package com.usebutton.merchant;

import android.util.Base64;

/**
* Encoder implementation for the Android platform.
*
* @see Encoder
*/
public class AndroidEncoder implements Encoder {
@Override
public String encodeBase64ToString(byte[] bytes) {
return Base64.encodeToString(bytes, Base64.NO_WRAP);
}
}
Expand Up @@ -25,13 +25,20 @@

package com.usebutton.merchant;

import android.net.http.X509TrustManagerExtensions;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.util.Log;

import com.usebutton.merchant.exception.ButtonNetworkException;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.json.JSONException;
import org.json.JSONObject;

Expand All @@ -41,11 +48,15 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
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.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
Expand All @@ -60,23 +71,25 @@ final class ButtonApiImpl implements ButtonApi {
private static final String CONTENT_TYPE_JSON = "application/json";

private final String userAgent;
private final SSLManager sslManager;

@VisibleForTesting
String baseUrl = "https://api.usebutton.com";

private static ButtonApi buttonApi;

static ButtonApi getInstance(String userAgent) {
static ButtonApi getInstance(String userAgent, SSLManager sslManager) {
if (buttonApi == null) {
buttonApi = new ButtonApiImpl(userAgent);
buttonApi = new ButtonApiImpl(userAgent, sslManager);
}

return buttonApi;
}

@VisibleForTesting
ButtonApiImpl(String userAgent) {
ButtonApiImpl(String userAgent, SSLManager sslManager) {
this.userAgent = userAgent;
this.sslManager = sslManager;
}

@Nullable
Expand All @@ -85,7 +98,7 @@ static ButtonApi getInstance(String userAgent) {
public PostInstallLink getPendingLink(String applicationId, String ifa,
boolean limitAdTrackingEnabled, Map<String, String> signalsMap) throws
ButtonNetworkException {
HttpURLConnection urlConnection = null;
HttpsURLConnection urlConnection = null;

try {
// create request body
Expand All @@ -97,7 +110,7 @@ public PostInstallLink getPendingLink(String applicationId, String ifa,

// setup url connection
final URL url = new URL(baseUrl + "/v1/web/deferred-deeplink");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection = (HttpsURLConnection) url.openConnection();
initializeUrlConnection(urlConnection);

// write request body
Expand Down Expand Up @@ -157,6 +170,10 @@ public PostInstallLink getPendingLink(String applicationId, String ifa,
} catch (JSONException e) {
Log.e(TAG, "JSONException has occurred", e);
throw new ButtonNetworkException(e);
} catch (CertificateException | KeyStoreException
| NoSuchAlgorithmException | KeyManagementException e) {
Log.e(TAG, e.getClass().getSimpleName() + " has occurred", e);
throw new ButtonNetworkException(e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
Expand All @@ -166,20 +183,47 @@ public PostInstallLink getPendingLink(String applicationId, String ifa,
return null;
}

private void initializeUrlConnection(HttpURLConnection urlConnection) throws ProtocolException {
private void initializeUrlConnection(HttpsURLConnection urlConnection) throws
CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException,
KeyManagementException {

urlConnection.setConnectTimeout(CONNECT_TIMEOUT);
urlConnection.setReadTimeout(READ_TIMEOUT);
urlConnection.setRequestProperty("User-Agent", getUserAgent());
urlConnection.setRequestProperty("Accept", CONTENT_TYPE_JSON);
urlConnection.setRequestProperty("Content-Type", CONTENT_TYPE_JSON);
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);

// Public key pinning is only available API 17 and above
// Default to CA pinning for older Android versions
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
Set<String> keys = sslManager.getCertificateProvider().getPublicKeys();

TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
X509TrustManager trustManager = null;
for (TrustManager tm : trustManagerFactory.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
trustManager = (X509TrustManager) tm;
break;
}
}
X509TrustManagerExtensions extension = new X509TrustManagerExtensions(trustManager);
Encoder encoder = new AndroidEncoder();

SSLUtils.validatePinning(extension, encoder, urlConnection, keys);
} else {
SSLContext sslContext = sslManager.getSecureContext();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
}
}

@Override
public Void postActivity(String applicationId, String sourceToken, String timestamp,
Order order) throws ButtonNetworkException {
HttpURLConnection urlConnection = null;
HttpsURLConnection urlConnection = null;

try {
// create request body
Expand All @@ -194,7 +238,7 @@ public Void postActivity(String applicationId, String sourceToken, String timest

// setup url connection
final URL url = new URL(baseUrl + "/v1/activity/order");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection = (HttpsURLConnection) url.openConnection();
initializeUrlConnection(urlConnection);

// write request body
Expand Down Expand Up @@ -242,6 +286,10 @@ public Void postActivity(String applicationId, String sourceToken, String timest
} catch (JSONException e) {
Log.e(TAG, "JSONException has occurred", e);
throw new ButtonNetworkException(e);
} catch (CertificateException | KeyStoreException
| NoSuchAlgorithmException | KeyManagementException e) {
Log.e(TAG, e.getClass().getSimpleName() + " has occurred", e);
throw new ButtonNetworkException(e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
Expand Down
Expand Up @@ -163,7 +163,11 @@ private static ButtonRepository getButtonRepository(Context context) {

DeviceManager deviceManager = getDeviceManager(context);

ButtonApi buttonApi = ButtonApiImpl.getInstance(deviceManager.getUserAgent());
CertificateProvider provider = new AmazonCertificateProvider();

SSLManager sslManager = SSLManagerImpl.getInstance(provider, null);

ButtonApi buttonApi = ButtonApiImpl.getInstance(deviceManager.getUserAgent(), sslManager);

return ButtonRepositoryImpl.getInstance(buttonApi, persistenceManager, executorService);
}
Expand Down

0 comments on commit 25f1c8f

Please sign in to comment.