/* * Copyright 2013 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package feign.sax.examples; import java.net.URI; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import feign.Request; import feign.RequestTemplate; import static feign.Util.UTF_8; // http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html public class AWSSignatureVersion4 { private static final String EMPTY_STRING_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; private static final SimpleDateFormat iso8601 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); static { iso8601.setTimeZone(TimeZone.getTimeZone("GMT")); } String region = "us-east-1"; String service = "iam"; String accessKey; String secretKey; public AWSSignatureVersion4(String accessKey, String secretKey) { this.accessKey = accessKey; this.secretKey = secretKey; } static byte[] hmacSHA256(String data, byte[] key) { try { String algorithm = "HmacSHA256"; Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data.getBytes(UTF_8)); } catch (Exception e) { throw new RuntimeException(e); } } private static String canonicalString(RequestTemplate input, String host) { StringBuilder canonicalRequest = new StringBuilder(); // HTTPRequestMethod + '\n' + canonicalRequest.append(input.method()).append('\n'); // CanonicalURI + '\n' + canonicalRequest.append(URI.create(input.url()).getPath()).append('\n'); // CanonicalQueryString + '\n' + canonicalRequest.append(input.queryLine().substring(1)); canonicalRequest.append('\n'); // CanonicalHeaders + '\n' + canonicalRequest.append("host:").append(host).append('\n'); canonicalRequest.append('\n'); // SignedHeaders + '\n' + canonicalRequest.append("host").append('\n'); // HexEncode(Hash(Payload)) String bodyText = input.charset() != null && input.body() != null ? new String(input.body(), input.charset()) : null; if (bodyText != null) { canonicalRequest.append(hex(sha256(bodyText))); } else { canonicalRequest.append(EMPTY_STRING_HASH); } return canonicalRequest.toString(); } private static String toSign(String timestamp, String credentialScope, String canonicalRequest) { StringBuilder toSign = new StringBuilder(); // Algorithm + '\n' + toSign.append("AWS4-HMAC-SHA256").append('\n'); // RequestDate + '\n' + toSign.append(timestamp).append('\n'); // CredentialScope + '\n' + toSign.append(credentialScope).append('\n'); // HexEncode(Hash(CanonicalRequest)) toSign.append(hex(sha256(canonicalRequest))); return toSign.toString(); } private static String hex(byte[] data) { StringBuilder result = new StringBuilder(data.length * 2); for (byte b : data) { result.append(String.format("%02x", b & 0xff)); } return result.toString(); } static byte[] sha256(String data) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); return digest.digest(data.getBytes(UTF_8)); } catch (Exception e) { throw new RuntimeException(e); } } public Request apply(RequestTemplate input) { if (!input.headers().isEmpty()) { throw new UnsupportedOperationException("headers not supported"); } if (input.body() != null) { throw new UnsupportedOperationException("body not supported"); } String host = URI.create(input.url()).getHost(); String timestamp; synchronized (iso8601) { timestamp = iso8601.format(new Date()); } String credentialScope = String.format("%s/%s/%s/%s", timestamp.substring(0, 8), region, service, "aws4_request"); input.query("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); input.query("X-Amz-Credential", accessKey + "/" + credentialScope); input.query("X-Amz-Date", timestamp); input.query("X-Amz-SignedHeaders", "host"); input.header("Host", host); String canonicalString = canonicalString(input, host); String toSign = toSign(timestamp, credentialScope, canonicalString); byte[] signatureKey = signatureKey(secretKey, timestamp); String signature = hex(hmacSHA256(toSign, signatureKey)); input.query("X-Amz-Signature", signature); return input.request(); } byte[] signatureKey(String secretKey, String timestamp) { byte[] kSecret = ("AWS4" + secretKey).getBytes(UTF_8); byte[] kDate = hmacSHA256(timestamp.substring(0, 8), kSecret); byte[] kRegion = hmacSHA256(region, kDate); byte[] kService = hmacSHA256(service, kRegion); byte[] kSigning = hmacSHA256("aws4_request", kService); return kSigning; } }