1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package com.melloware.jukes.ws;
24
25 import java.io.UnsupportedEncodingException;
26
27 import java.net.URLDecoder;
28 import java.net.URLEncoder;
29
30 import java.security.InvalidKeyException;
31 import java.security.NoSuchAlgorithmException;
32
33 import java.text.DateFormat;
34 import java.text.SimpleDateFormat;
35
36 import java.util.Calendar;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.Map;
40 import java.util.SortedMap;
41 import java.util.TimeZone;
42 import java.util.TreeMap;
43
44 import javax.crypto.Mac;
45 import javax.crypto.spec.SecretKeySpec;
46
47 import org.apache.commons.codec.binary.Base64;
48
49
50
51
52
53 public class SignedRequestsHelper {
54
55
56
57 private static final String UTF8_CHARSET = "UTF-8";
58
59
60
61
62 private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
63
64
65
66
67
68 private static final String REQUEST_URI = "/onca/xml";
69
70
71
72
73
74 private static final String REQUEST_METHOD = "GET";
75
76 private String endpoint = null;
77 private String awsAccessKeyId = null;
78 private String awsSecretKey = null;
79
80 private SecretKeySpec secretKeySpec = null;
81 private Mac mac = null;
82
83
84
85
86
87
88
89
90 public static SignedRequestsHelper getInstance(
91 String endpoint,
92 String awsAccessKeyId,
93 String awsSecretKey
94 ) throws IllegalArgumentException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException
95 {
96 if (null == endpoint || endpoint.length() == 0)
97 { throw new IllegalArgumentException("endpoint is null or empty"); }
98 if (null == awsAccessKeyId || awsAccessKeyId.length() == 0)
99 { throw new IllegalArgumentException("awsAccessKeyId is null or empty"); }
100 if (null == awsSecretKey || awsSecretKey.length() == 0)
101 { throw new IllegalArgumentException("awsSecretKey is null or empty"); }
102
103 SignedRequestsHelper instance = new SignedRequestsHelper();
104 instance.endpoint = endpoint.toLowerCase();
105 instance.awsAccessKeyId = awsAccessKeyId;
106 instance.awsSecretKey = awsSecretKey;
107 byte[] secretyKeyBytes = instance.awsSecretKey.getBytes(UTF8_CHARSET);
108 instance.secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
109 instance.mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
110 instance.mac.init(instance.secretKeySpec);
111
112 return instance;
113 }
114
115
116
117
118 private SignedRequestsHelper() {}
119
120
121
122
123
124
125
126 public String sign(Map<String, String> params) {
127
128 params.put("AWSAccessKeyId", this.awsAccessKeyId);
129 params.put("Timestamp", this.timestamp());
130
131
132
133 SortedMap<String, String> sortedParamMap = new TreeMap<String, String>(params);
134
135
136 String canonicalQS = this.canonicalize(sortedParamMap);
137
138
139 String toSign =
140 REQUEST_METHOD + "\n"
141 + this.endpoint + "\n"
142 + REQUEST_URI + "\n"
143 + canonicalQS;
144
145
146 String hmac = this.hmac(toSign);
147 String sig = this.percentEncodeRfc3986(hmac);
148
149
150 String url =
151 "http://" + this.endpoint + REQUEST_URI + "?" + canonicalQS + "&Signature=" + sig;
152
153 return url;
154 }
155
156
157
158
159
160
161
162 public String sign(String queryString) {
163
164 Map<String, String> params = this.createParameterMap(queryString);
165
166
167 return this.sign(params);
168 }
169
170
171
172
173
174
175
176 private String hmac(String stringToSign) {
177 String signature = null;
178 byte[] data;
179 byte[] rawHmac;
180 try {
181 data = stringToSign.getBytes(UTF8_CHARSET);
182 rawHmac = mac.doFinal(data);
183 Base64 encoder = new Base64();
184 signature = new String(encoder.encode(rawHmac));
185 } catch (UnsupportedEncodingException e) {
186 throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
187 }
188 return signature;
189 }
190
191
192
193
194
195
196 private String timestamp() {
197 String timestamp = null;
198 Calendar cal = Calendar.getInstance();
199 DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
200 dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
201 timestamp = dfm.format(cal.getTime());
202 return timestamp;
203 }
204
205
206
207
208
209
210
211
212 private String canonicalize(SortedMap<String, String> sortedParamMap) {
213 if (sortedParamMap.isEmpty()) {
214 return "";
215 }
216
217 StringBuffer buffer = new StringBuffer();
218 Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
219
220 while (iter.hasNext()) {
221 Map.Entry<String, String> kvpair = iter.next();
222 buffer.append(percentEncodeRfc3986(kvpair.getKey()));
223 buffer.append("=");
224 buffer.append(percentEncodeRfc3986(kvpair.getValue()));
225 if (iter.hasNext()) {
226 buffer.append("&");
227 }
228 }
229 String cannoical = buffer.toString();
230 return cannoical;
231 }
232
233
234
235
236
237
238
239
240
241 private String percentEncodeRfc3986(String s) {
242 String out;
243 try {
244 out = URLEncoder.encode(s, UTF8_CHARSET)
245 .replace("+", "%20")
246 .replace("*", "%2A")
247 .replace("%7E", "~");
248 } catch (UnsupportedEncodingException e) {
249 out = s;
250 }
251 return out;
252 }
253
254
255
256
257
258
259
260
261 private Map<String, String> createParameterMap(String queryString) {
262 Map<String, String> map = new HashMap<String, String>();
263 String[] pairs = queryString.split("&");
264
265 for (String pair: pairs) {
266 if (pair.length() < 1) {
267 continue;
268 }
269
270 String[] tokens = pair.split("=",2);
271 for(int j=0; j<tokens.length; j++)
272 {
273 try {
274 tokens[j] = URLDecoder.decode(tokens[j], UTF8_CHARSET);
275 } catch (UnsupportedEncodingException e) {
276 }
277 }
278 switch (tokens.length) {
279 case 1: {
280 if (pair.charAt(0) == '=') {
281 map.put("", tokens[0]);
282 } else {
283 map.put(tokens[0], "");
284 }
285 break;
286 }
287 case 2: {
288 map.put(tokens[0], tokens[1]);
289 break;
290 }
291 }
292 }
293 return map;
294 }
295 }
296