Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignatureV4 Eth node access sample code #1899

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@
import com.alphawallet.app.widget.SettingsItemView;
import com.google.firebase.crashlytics.FirebaseCrashlytics;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import com.amazonaws.services.s3.sample.auth.AWS4SignerBase;
import com.amazonaws.services.s3.sample.auth.AWS4SignerForAuthorizationHeader;
import com.amazonaws.services.s3.sample.util.BinaryUtils;
import com.amazonaws.services.s3.sample.util.HttpUtils;

import android.os.StrictMode;

public class SupportSettingsActivity extends BaseActivity {

private LinearLayout supportSettingsLayout;
Expand All @@ -24,12 +36,20 @@ public class SupportSettingsActivity extends BaseActivity {
private SettingsItemView facebook;
private SettingsItemView blog;
private SettingsItemView faq;
private SettingsItemView eth;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_generic_settings);

int SDK_INT = android.os.Build.VERSION.SDK_INT;
if (SDK_INT > 8) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
.permitAll().build();
StrictMode.setThreadPolicy(policy);
}

toolbar();
setTitle(getString(R.string.title_support));

Expand Down Expand Up @@ -74,6 +94,11 @@ private void initializeSettings() {
.withTitle(R.string.title_faq)
.withListener(this::onFaqClicked)
.build();
eth = new SettingsItemView.Builder(this)
.withIcon(R.drawable.ic_settings_tokenscript)
.withTitle(R.string.title_eth)
.withListener(this::onEthClicked)
.build();
}

private void addSettingsToLayout() {
Expand All @@ -94,6 +119,7 @@ private void addSettingsToLayout() {
supportSettingsLayout.addView(blog);
}
supportSettingsLayout.addView(faq);
supportSettingsLayout.addView(eth);
}

private void onTelegramClicked() {
Expand Down Expand Up @@ -182,6 +208,40 @@ private void onFaqClicked() {
new HelpRouter().open(this);
}

private void onEthClicked() {
String objectContent = "{\"jsonrpc\": \"2.0\", \"method\": \"web3_clientVersion\", \"params\": [], \"id\": 67}";
URL endpointUrl;
try {
endpointUrl = new URL("https://nd-4vmk4h4mczby7hics5pkg6c6xy.ethereum.managedblockchain.us-east-1.amazonaws.com");
} catch (MalformedURLException e) {
throw new RuntimeException("Unable to parse service endpoint: " + e.getMessage());
}
byte[] contentHash = AWS4SignerBase.hash(objectContent);
String contentHashString = BinaryUtils.toHex(contentHash);

Map<String, String> headers = new HashMap<String, String>();
headers.put("x-amz-content-sha256", contentHashString);
headers.put("content-length", "" + objectContent.length());
headers.put("x-amz-storage-class", "REDUCED_REDUNDANCY");

AWS4SignerForAuthorizationHeader signer = new AWS4SignerForAuthorizationHeader(
endpointUrl, "PUT", "managedblockchain", "us-east-1");
String authorization = signer.computeSignature(headers,
null, // no query parameters
contentHashString,
"",
"");

// express authorization for this as a header
headers.put("Authorization", authorization);

// make the call to Amazon S3
String response = HttpUtils.invokeHttpRequest(endpointUrl, "PUT", headers, objectContent);
System.out.println("--------- Response content ---------");
System.out.println(response);
System.out.println("------------------------------------");
}

private boolean isAppAvailable(String packageName) {
PackageManager pm = getPackageManager();
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package com.amazonaws.services.s3.sample.auth;

import java.net.URL;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SimpleTimeZone;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.amazonaws.services.s3.sample.util.HttpUtils;
import com.amazonaws.services.s3.sample.util.BinaryUtils;


/**
* Common methods and properties for all AWS4 signer variants
*/
public abstract class AWS4SignerBase {

/** SHA256 hash of an empty request body **/
public static final String EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";

public static final String SCHEME = "AWS4";
public static final String ALGORITHM = "HMAC-SHA256";
public static final String TERMINATOR = "aws4_request";

/** format strings for the date/time and date stamps required during signing **/
public static final String ISO8601BasicFormat = "yyyyMMdd'T'HHmmss'Z'";
public static final String DateStringFormat = "yyyyMMdd";

protected URL endpointUrl;
protected String httpMethod;
protected String serviceName;
protected String regionName;

protected final SimpleDateFormat dateTimeFormat;
protected final SimpleDateFormat dateStampFormat;

/**
* Create a new AWS V4 signer.
*
* @param endpointUri
* The service endpoint, including the path to any resource.
* @param httpMethod
* The HTTP verb for the request, e.g. GET.
* @param serviceName
* The signing name of the service, e.g. 's3'.
* @param regionName
* The system name of the AWS region associated with the
* endpoint, e.g. us-east-1.
*/
public AWS4SignerBase(URL endpointUrl, String httpMethod,
String serviceName, String regionName) {
this.endpointUrl = endpointUrl;
this.httpMethod = httpMethod;
this.serviceName = serviceName;
this.regionName = regionName;

dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat);
dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
dateStampFormat = new SimpleDateFormat(DateStringFormat);
dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
}

/**
* Returns the canonical collection of header names that will be included in
* the signature. For AWS4, all header names must be included in the process
* in sorted canonicalized order.
*/
protected static String getCanonicalizeHeaderNames(Map<String, String> headers) {
List<String> sortedHeaders = new ArrayList<String>();
sortedHeaders.addAll(headers.keySet());
Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);

StringBuilder buffer = new StringBuilder();
for (String header : sortedHeaders) {
if (buffer.length() > 0) buffer.append(";");
buffer.append(header.toLowerCase());
}

return buffer.toString();
}

/**
* Computes the canonical headers with values for the request. For AWS4, all
* headers must be included in the signing process.
*/
protected static String getCanonicalizedHeaderString(Map<String, String> headers) {
if ( headers == null || headers.isEmpty() ) {
return "";
}

// step1: sort the headers by case-insensitive order
List<String> sortedHeaders = new ArrayList<String>();
sortedHeaders.addAll(headers.keySet());
Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);

// step2: form the canonical header:value entries in sorted order.
// Multiple white spaces in the values should be compressed to a single
// space.
StringBuilder buffer = new StringBuilder();
for (String key : sortedHeaders) {
buffer.append(key.toLowerCase().replaceAll("\\s+", " ") + ":" + headers.get(key).replaceAll("\\s+", " "));
buffer.append("\n");
}

return buffer.toString();
}

/**
* Returns the canonical request string to go into the signer process; this
consists of several canonical sub-parts.
* @return
*/
protected static String getCanonicalRequest(URL endpoint,
String httpMethod,
String queryParameters,
String canonicalizedHeaderNames,
String canonicalizedHeaders,
String bodyHash) {
String canonicalRequest =
httpMethod + "\n" +
getCanonicalizedResourcePath(endpoint) + "\n" +
queryParameters + "\n" +
canonicalizedHeaders + "\n" +
canonicalizedHeaderNames + "\n" +
bodyHash;
return canonicalRequest;
}

/**
* Returns the canonicalized resource path for the service endpoint.
*/
protected static String getCanonicalizedResourcePath(URL endpoint) {
if ( endpoint == null ) {
return "/";
}
String path = endpoint.getPath();
if ( path == null || path.isEmpty() ) {
return "/";
}

String encodedPath = HttpUtils.urlEncode(path, true);
if (encodedPath.startsWith("/")) {
return encodedPath;
} else {
return "/".concat(encodedPath);
}
}

/**
* Examines the specified query string parameters and returns a
* canonicalized form.
* <p>
* The canonicalized query string is formed by first sorting all the query
* string parameters, then URI encoding both the key and value and then
* joining them, in order, separating key value pairs with an '&'.
*
* @param parameters
* The query string parameters to be canonicalized.
*
* @return A canonicalized form for the specified query string parameters.
*/
public static String getCanonicalizedQueryString(Map<String, String> parameters) {
if ( parameters == null || parameters.isEmpty() ) {
return "";
}

SortedMap<String, String> sorted = new TreeMap<String, String>();

Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
while (pairs.hasNext()) {
Map.Entry<String, String> pair = pairs.next();
String key = pair.getKey();
String value = pair.getValue();
sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false));
}

StringBuilder builder = new StringBuilder();
pairs = sorted.entrySet().iterator();
while (pairs.hasNext()) {
Map.Entry<String, String> pair = pairs.next();
builder.append(pair.getKey());
builder.append("=");
builder.append(pair.getValue());
if (pairs.hasNext()) {
builder.append("&");
}
}

return builder.toString();
}

protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope, String canonicalRequest) {
String stringToSign =
scheme + "-" + algorithm + "\n" +
dateTime + "\n" +
scope + "\n" +
BinaryUtils.toHex(hash(canonicalRequest));
return stringToSign;
}

/**
* Hashes the string contents (assumed to be UTF-8) using the SHA-256
* algorithm.
*/
public static byte[] hash(String text) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(text.getBytes("UTF-8"));
return md.digest();
} catch (Exception e) {
throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
}
}

/**
* Hashes the byte array using the SHA-256 algorithm.
*/
public static byte[] hash(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(data);
return md.digest();
} catch (Exception e) {
throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
}
}

protected static byte[] sign(String stringData, byte[] key, String algorithm) {
try {
byte[] data = stringData.getBytes("UTF-8");
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
} catch (Exception e) {
throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e);
}
}
}
Loading