1 package com.github.davidmoten.aws.lw.client.internal.auth;
2
3 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.ALGORITHM;
4 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.SCHEME;
5 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.TERMINATOR;
6 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.dateStampFormat;
7 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.getCanonicalRequest;
8 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.getCanonicalizeHeaderNames;
9 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.getCanonicalizedHeaderString;
10 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.getCanonicalizedQueryString;
11 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.getStringToSign;
12 import static com.github.davidmoten.aws.lw.client.internal.auth.AwsSignatureVersion4.sign;
13
14 import java.net.URL;
15 import java.nio.charset.StandardCharsets;
16 import java.util.Date;
17 import java.util.Map;
18
19 import com.github.davidmoten.aws.lw.client.internal.util.Util;
20
21
22
23
24 public final class Aws4SignerForChunkedUpload {
25
26
27
28
29
30 public static final String STREAMING_BODY_SHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
31
32 private static final String CLRF = "\r\n";
33 private static final String CHUNK_STRING_TO_SIGN_PREFIX = "AWS4-HMAC-SHA256-PAYLOAD";
34 private static final String CHUNK_SIGNATURE_HEADER = ";chunk-signature=";
35 private static final int SIGNATURE_LENGTH = 64;
36 private static final byte[] FINAL_CHUNK = new byte[0];
37
38
39
40
41
42
43 private String lastComputedSignature;
44
45
46
47
48
49 private String dateTimeStamp;
50
51
52
53
54 private String scope;
55
56
57
58
59
60 private byte[] signingKey;
61 private final URL endpointUrl;
62 private final String httpMethod;
63 private final String serviceName;
64 private final String regionName;
65
66 public Aws4SignerForChunkedUpload(URL endpointUrl, String httpMethod, String serviceName,
67 String regionName) {
68 this.endpointUrl = endpointUrl;
69 this.httpMethod = httpMethod;
70 this.serviceName = serviceName;
71 this.regionName = regionName;
72 }
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public String computeSignature(Map<String, String> headers, Map<String, String> queryParameters,
93 String bodyHash, String awsAccessKey, String awsSecretKey) {
94
95
96 Date now = new Date();
97 this.dateTimeStamp = AwsSignatureVersion4.dateTimeFormat().format(now);
98
99
100 headers.put("x-amz-date", dateTimeStamp);
101
102 String hostHeader = endpointUrl.getHost();
103 int port = endpointUrl.getPort();
104 if (port > -1) {
105 hostHeader = hostHeader.concat(":" + port);
106 }
107 headers.put("Host", hostHeader);
108
109
110
111 String canonicalizedHeaderNames = getCanonicalizeHeaderNames(headers);
112 String canonicalizedHeaders = getCanonicalizedHeaderString(headers);
113
114
115 String canonicalizedQueryParameters = getCanonicalizedQueryString(queryParameters);
116
117
118 String canonicalRequest = getCanonicalRequest(endpointUrl, httpMethod,
119 canonicalizedQueryParameters, canonicalizedHeaderNames, canonicalizedHeaders,
120 bodyHash);
121 System.out.println("--------- Canonical request --------");
122 System.out.println(canonicalRequest);
123 System.out.println("------------------------------------");
124
125
126 String dateStamp = dateStampFormat().format(now);
127 this.scope = dateStamp + "/" + regionName + "/" + serviceName + "/" + TERMINATOR;
128 String stringToSign = getStringToSign(SCHEME, ALGORITHM, dateTimeStamp, scope,
129 canonicalRequest);
130 System.out.println("--------- String to sign -----------");
131 System.out.println(stringToSign);
132 System.out.println("------------------------------------");
133
134
135 byte[] kSecret = (SCHEME + awsSecretKey).getBytes(StandardCharsets.UTF_8);
136 byte[] kDate = sign(dateStamp, kSecret);
137 byte[] kRegion = sign(regionName, kDate);
138 byte[] kService = sign(serviceName, kRegion);
139 this.signingKey = sign(TERMINATOR, kService);
140 byte[] signature = sign(stringToSign, signingKey);
141
142
143 lastComputedSignature = Util.toHex(signature);
144
145 String credentialsAuthorizationHeader = "Credential=" + awsAccessKey + "/" + scope;
146 String signedHeadersAuthorizationHeader = "SignedHeaders=" + canonicalizedHeaderNames;
147 String signatureAuthorizationHeader = "Signature=" + lastComputedSignature;
148
149 String authorizationHeader = SCHEME + "-" + ALGORITHM + " " + credentialsAuthorizationHeader
150 + ", " + signedHeadersAuthorizationHeader + ", " + signatureAuthorizationHeader;
151
152 return authorizationHeader;
153 }
154
155
156
157
158
159
160
161
162
163
164 public static long calculateChunkedContentLength(long originalLength, long chunkSize) {
165 if (originalLength <= 0) {
166 throw new IllegalArgumentException("Nonnegative content length expected.");
167 }
168
169 long maxSizeChunks = originalLength / chunkSize;
170 long remainingBytes = originalLength % chunkSize;
171 return maxSizeChunks * calculateChunkHeaderLength(chunkSize)
172 + (remainingBytes > 0 ? calculateChunkHeaderLength(remainingBytes) : 0)
173 + calculateChunkHeaderLength(0);
174 }
175
176
177
178
179
180
181
182
183
184
185 private static long calculateChunkHeaderLength(long chunkDataSize) {
186 return Long.toHexString(chunkDataSize).length() + CHUNK_SIGNATURE_HEADER.length()
187 + SIGNATURE_LENGTH + CLRF.length() + chunkDataSize + CLRF.length();
188 }
189
190
191
192
193
194
195
196
197
198
199
200
201 public byte[] constructSignedChunk(int userDataLen, byte[] userData) {
202
203
204
205
206 byte[] dataToChunk;
207 if (userDataLen == 0) {
208 dataToChunk = FINAL_CHUNK;
209 } else {
210 if (userDataLen < userData.length) {
211
212 dataToChunk = new byte[userDataLen];
213 System.arraycopy(userData, 0, dataToChunk, 0, userDataLen);
214 } else {
215 dataToChunk = userData;
216 }
217 }
218
219 StringBuilder chunkHeader = new StringBuilder();
220
221
222 chunkHeader.append(Integer.toHexString(dataToChunk.length));
223
224
225 String nonsigExtension = "";
226
227
228
229
230
231
232 String chunkStringToSign = CHUNK_STRING_TO_SIGN_PREFIX + "\n" + dateTimeStamp + "\n" + scope
233 + "\n" + lastComputedSignature + "\n" + Util.toHex(Util.sha256(nonsigExtension))
234 + "\n" + Util.toHex(Util.sha256(dataToChunk));
235
236
237 String chunkSignature = Util.toHex(AwsSignatureVersion4.sign(chunkStringToSign, signingKey));
238
239
240 lastComputedSignature = chunkSignature;
241
242
243
244
245 chunkHeader.append(nonsigExtension + CHUNK_SIGNATURE_HEADER + chunkSignature);
246 chunkHeader.append(CLRF);
247
248 byte[] header = chunkHeader.toString().getBytes(StandardCharsets.UTF_8);
249 byte[] trailer = CLRF.getBytes(StandardCharsets.UTF_8);
250 byte[] signedChunk = new byte[header.length + dataToChunk.length + trailer.length];
251 System.arraycopy(header, 0, signedChunk, 0, header.length);
252 System.arraycopy(dataToChunk, 0, signedChunk, header.length, dataToChunk.length);
253 System.arraycopy(trailer, 0, signedChunk, header.length + dataToChunk.length,
254 trailer.length);
255
256
257 return signedChunk;
258 }
259 }