Skip to content

Instantly share code, notes, and snippets.

@libetl
Last active October 31, 2023 19:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save libetl/c9f93ae9d455bdd548a4517317ee843d to your computer and use it in GitHub Desktop.
Save libetl/c9f93ae9d455bdd548a4517317ee843d to your computer and use it in GitHub Desktop.
Vanilla http-client
package com.mycompany.tools.aws;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
class AWS4 {
public static class SignerRequestParams {
String serviceName;
String regionName;
Date date;
String signingAlgorithm = "AWS4-HMAC-SHA256";
public SignerRequestParams(String serviceName,
String regionName,
Date date) {
this.serviceName = serviceName;
this.regionName = regionName;
this.date = date;
}
public String getCredentialsScope() {
return dateFormat.format(date) + "/" +
regionName + "/" + serviceName +
"/aws4_request";
}
}
private static final List<String>
listOfHeadersToIgnoreInLowerCase =
Arrays.asList("connection", "x-amzn-trace-id",
"user-agent");
private static final int[] base16 =
new int[]{'0', '1', '2'
, '3', '4', '5', '6', '7', '8', '9',
'a', 'b'
, 'c', 'd', 'e', 'f'};
private static final int MASK_4BITS = (1 << 4) - 1;
private static final DateFormat dateFormat =
new SimpleDateFormat("yyyyMMdd");
static final DateFormat dateTimeFormat =
new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
static {
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
dateTimeFormat.setTimeZone(TimeZone.getTimeZone(
"UTC"));
}
public static Map<String, List<String>> sign(
Request request, Credentials credentials,
SignerRequestParams signerRequestParams)
throws IOException, NoSuchAlgorithmException,
URISyntaxException, InvalidKeyException {
Map<String, List<String>> headers =
new HashMap<String, List<String>>(
request.headers);
headers.put("X-Amz-Security-Token",
Collections.singletonList(
credentials.securityToken));
headers.put("host",
Collections.singletonList(
request.url.getHost()));
headers.put("x-amz-date",
Collections.singletonList(
dateTimeFormat.format(
signerRequestParams.date)));
InputStream payloadStream =
new ByteArrayInputStream(request.body);
byte[] contentBytes =
new byte[payloadStream.available()];
DataInputStream dataInputStream =
new DataInputStream(payloadStream);
dataInputStream.readFully(contentBytes);
String contentSha256 = sha256(contentBytes);
payloadStream.reset();
final Map<String, List<String>> headersToConsider =
new HashMap<>(request.headers);
headersToConsider.put("x-amz-security-token",
Collections.singletonList(
credentials.securityToken));
headersToConsider.put("x-amz-date",
Collections.singletonList(
dateTimeFormat.format(
signerRequestParams.date)));
headersToConsider.remove("user-agent");
final List<String> sortedHeaders =
new ArrayList<>(headersToConsider.keySet());
Collections.sort(sortedHeaders,
String.CASE_INSENSITIVE_ORDER);
StringBuilder headerKeysAsList =
new StringBuilder();
StringBuilder headersAsList = new StringBuilder();
for (String header : sortedHeaders) {
if (listOfHeadersToIgnoreInLowerCase.contains(
header.toLowerCase())) {
continue;
}
if (headerKeysAsList.length() > 0)
headerKeysAsList.append(";");
String key = header.toLowerCase().trim();
String value =
headersToConsider.get(header).get(0);
headersAsList.append(key.replaceAll("\\s+",
" ")).append(":");
if (value != null &&
value.trim().length() > 0) {
headersAsList.append(value.replaceAll(
"\\s+", " "));
}
headersAsList.append("\n");
headerKeysAsList.append(key);
}
String canonicalRequest =
request.method.name() + "\n" +
(request.url.getPath().trim()
.length() == 0 ? "/" :
request.url.getPath()) +
"\n" +
(request.url.toURI().getQuery() ==
null ? "" :
request.url.toURI()
.getQuery()) +
"\n" + headersAsList +
"\n" + headerKeysAsList +
"\n" + contentSha256;
final String stringToSign =
signerRequestParams.signingAlgorithm +
"\n" +
dateTimeFormat.format(
signerRequestParams.date) +
"\n" +
signerRequestParams.getCredentialsScope() +
"\n" +
sha256(canonicalRequest.getBytes());
byte[] kSecret =
("AWS4" + credentials.secretKey).getBytes(
StandardCharsets.UTF_8);
byte[] kDate =
sign(dateFormat.format(
signerRequestParams.date)
.getBytes(), kSecret, "HmacSHA256");
byte[] kRegion =
sign(signerRequestParams.regionName.getBytes(),
kDate, "HmacSHA256");
byte[] kService =
sign(signerRequestParams.serviceName.getBytes(),
kRegion, "HmacSHA256");
byte[] signingKey =
sign("aws4_request".getBytes(), kService,
"HmacSHA256");
final byte[] signature =
sign(stringToSign.getBytes(
StandardCharsets.UTF_8),
signingKey, "HmacSHA256");
final String signingCredentials =
credentials.accessKeyId + "/" +
signerRequestParams.getCredentialsScope();
final String credential =
"Credential=" + signingCredentials;
final String signerHeaders =
"SignedHeaders=" + headerKeysAsList;
final String signatureHeader =
"Signature=" + hex(signature);
headers.put("Authorization",
Collections.singletonList("AWS4-HMAC" +
"-SHA256 " + credential + ", " +
signerHeaders + ", " +
signatureHeader));
return headers;
}
private static String sha256(byte[] input)
throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA" +
"-256");
md.update(input);
byte[] hashedPayloadBytes = md.digest();
return hex(hashedPayloadBytes);
}
private static String hex(byte[] input) {
byte[] contentSha256Bytes =
new byte[input.length * 2];
for (int i = 0; i < input.length; i++) {
contentSha256Bytes[2 * i] =
(byte) base16[input[i] >>> 4 &
MASK_4BITS];
contentSha256Bytes[2 * i + 1] =
(byte) base16[input[i] & MASK_4BITS];
}
return new String(contentSha256Bytes);
}
private static byte[] sign(byte[] data, byte[] key,
String algorithm)
throws NoSuchAlgorithmException,
InvalidKeyException {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
}
}
package com.mycompany.tools.httpclient;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
class Request {
URL url;
HttpMethod method = HttpMethod.GET;
Map<String, List<String>> headers =
new HashMap<String, List<String>>();
byte[] body;
enum HttpMethod {
POST, GET, PUT, DELETE, PATCH, OPTIONS, HEAD,
CONNECT
}
public Request(URL url, HttpMethod method, byte[] body,
Map.Entry<String, String>... keyValue) {
this(url, method, body, convert(keyValue));
}
public static Request parse(String requestSpec)
throws MalformedURLException {
List<String> requestLines =
Arrays.asList(requestSpec.split("\n"));
String[] requestLineWords =
requestLines.get(0).trim().split(" ");
HttpMethod httpMethod =
HttpMethod.valueOf(requestLineWords[0]);
String uri = (requestLineWords.length < 2 ||
requestLineWords[1].startsWith("HTTP/")) ?
"/" : requestLineWords[1];
List<String> afterRequestLine =
requestLines.subList(2,
requestLines.size());
List<String> onlyHeaders =
afterRequestLine.subList(0,
afterRequestLine.indexOf(""));
List<String> onlyBody = afterRequestLine.subList(
afterRequestLine.indexOf("") + 1,
afterRequestLine.size());
Map<String, List<String>> headers = new HashMap<>();
for (int i = 0; i < onlyHeaders.size(); i++) {
String keyValue = onlyHeaders.get(i);
String headerName = keyValue.substring(0,
keyValue.indexOf(":")).toLowerCase()
.trim();
String headerValue = keyValue.substring(
keyValue.indexOf(":") + 1).trim();
if (headers.get(headerName) == null) {
headers.put(headerName, new ArrayList<>());
}
headers.get(headerName).add(headerValue);
}
String bodyAsString = "";
for (int i = 0; i < onlyBody.size(); i++) {
bodyAsString = bodyAsString + onlyBody.get(i);
}
URL url = new URL("https://" +
headers.get("host").get(0) + uri);
return new Request(url, httpMethod,
bodyAsString.getBytes(), headers);
}
private static Map<String, List<String>> convert(
Map.Entry<String, String>[] keyValue) {
Map<String, List<String>> headers1 =
new HashMap<String, List<String>>();
List<Map.Entry<String, String>> keyValues =
Arrays.asList(keyValue);
for (int i = 0; i < keyValues.size(); i++) {
headers1.put(keyValues.get(i).getKey(),
Collections.singletonList(
keyValues.get(i).getValue()));
}
return headers1;
}
public Request(URL url, HttpMethod method, byte[] body,
Map<String, List<String>> headers) {
this.url = url;
this.method = method;
this.headers = headers;
this.body = body;
}
Request withHeaders(
Map<String, List<String>> additionalHeaders) {
Map<String, List<String>> newHeaders =
new HashMap<String, List<String>>(headers);
newHeaders.putAll(additionalHeaders);
return new Request(url, method, body, newHeaders);
}
String run() throws IOException {
HttpURLConnection connection =
((HttpURLConnection) url.openConnection());
connection.setRequestMethod(method.name());
Iterator<Map.Entry<String, List<String>>>
headersIterator =
headers.entrySet().iterator();
while (headersIterator.hasNext()) {
Map.Entry<String, List<String>> header =
headersIterator.next();
connection.setRequestProperty(header.getKey(),
header.getValue().get(0));
}
if (body != null && body.length > 0) {
connection.setDoOutput(true);
connection.getOutputStream().write(body);
connection.getOutputStream().close();
}
InputStream result;
try {
result = connection.getInputStream();
} catch (IOException e) {
result = connection.getErrorStream();
}
if (result == null)
result = new ByteArrayInputStream(
new byte[0]);
StringBuilder response = new StringBuilder();
try (BufferedReader br = new BufferedReader(
new InputStreamReader(result, "utf-8"))) {
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
}
return response.toString();
}
public String toString() {
StringBuilder headersBuilder = new StringBuilder();
Iterator<Map.Entry<String, List<String>>>
headersIterator =
headers.entrySet().iterator();
while (headersIterator.hasNext()) {
Map.Entry<String, List<String>> header =
headersIterator.next();
headersBuilder.append(header.getKey());
headersBuilder.append(": ");
headersBuilder.append(header.getValue().get(0));
headersBuilder.append("\n");
}
return method.name() + " " +
url.getPath() +
(url.getQuery() == null ? "" :
url.getQuery()) + "\n" +
headersBuilder + "\n\n" +
(body == null ? "<EOF>" : new String(body));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment