Skip to content

Commit 6897e2a

Browse files
---
yaml --- r: 35779 b: refs/heads/autosynth-dataproc c: 16f18c6 h: refs/heads/master i: 35777: a4445a1 35775: ef22aa5
1 parent e071ab2 commit 6897e2a

14 files changed

Lines changed: 727 additions & 42 deletions

File tree

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ refs/tags/v0.78.0: 62d4bd30605ab3578f9a08d84487fb0b33ac2ff5
165165
refs/tags/v0.79.0: 82287b570708748c411d05c40f3932cff9606feb
166166
refs/tags/v0.80.0: f745e744d38e4fe636f34d0e04795ba3d014287d
167167
refs/tags/v0.81.0: ed3a0c85339ea6b73560b9a570abfbb76b93a263
168-
refs/heads/autosynth-dataproc: fcb9b8d3bbc04a93e3daea8ca6f19ce17cbfed18
168+
refs/heads/autosynth-dataproc: 16f18c676d4fe9842d05c4dadb04451f86bfd73c
169169
refs/heads/autosynth-securitycenter: b24087060036e623e57d2454ba5dabeaf1e530c5
170170
refs/heads/autosynth-talent: 4ca901879f86aab61091cea52e8a9b653639df24
171171
refs/tags/v0.82.0: 7b9807d5d0a400c757b8905fee768be4c85eba25

branches/autosynth-dataproc/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,29 @@
3232
public class CanonicalExtensionHeadersSerializer {
3333

3434
private static final char HEADER_SEPARATOR = ':';
35+
private static final char HEADER_NAME_SEPARATOR = ';';
36+
37+
private final Storage.SignUrlOption.SignatureVersion signatureVersion;
38+
39+
public CanonicalExtensionHeadersSerializer(
40+
Storage.SignUrlOption.SignatureVersion signatureVersion) {
41+
this.signatureVersion = signatureVersion;
42+
}
43+
44+
public CanonicalExtensionHeadersSerializer() {
45+
// TODO switch this when V4 becomes default
46+
this.signatureVersion = Storage.SignUrlOption.SignatureVersion.V2;
47+
}
3548

3649
public StringBuilder serialize(Map<String, String> canonicalizedExtensionHeaders) {
3750

3851
StringBuilder serializedHeaders = new StringBuilder();
3952

4053
if (canonicalizedExtensionHeaders == null || canonicalizedExtensionHeaders.isEmpty()) {
41-
4254
return serializedHeaders;
4355
}
4456

45-
// Make all custom header names lowercase.
46-
Map<String, String> lowercaseHeaders = new HashMap<>();
47-
for (String headerName : new ArrayList<>(canonicalizedExtensionHeaders.keySet())) {
48-
49-
String lowercaseHeaderName = headerName.toLowerCase();
50-
51-
// If present, remove the x-goog-encryption-key and x-goog-encryption-key-sha256 headers.
52-
if ("x-goog-encryption-key".equals(lowercaseHeaderName)
53-
|| "x-goog-encryption-key-sha256".equals(lowercaseHeaderName)) {
54-
55-
continue;
56-
}
57-
58-
lowercaseHeaders.put(lowercaseHeaderName, canonicalizedExtensionHeaders.get(headerName));
59-
}
57+
Map<String, String> lowercaseHeaders = getLowercaseHeaders(canonicalizedExtensionHeaders);
6058

6159
// Sort all custom headers by header name using a lexicographical sort by code point value.
6260
List<String> sortedHeaderNames = new ArrayList<>(lowercaseHeaders.keySet());
@@ -81,4 +79,47 @@ public StringBuilder serialize(Map<String, String> canonicalizedExtensionHeaders
8179
// Concatenate all custom headers
8280
return serializedHeaders;
8381
}
82+
83+
public StringBuilder serializeHeaderNames(Map<String, String> canonicalizedExtensionHeaders) {
84+
StringBuilder serializedHeaders = new StringBuilder();
85+
86+
if (canonicalizedExtensionHeaders == null || canonicalizedExtensionHeaders.isEmpty()) {
87+
return serializedHeaders;
88+
}
89+
Map<String, String> lowercaseHeaders = getLowercaseHeaders(canonicalizedExtensionHeaders);
90+
91+
List<String> sortedHeaderNames = new ArrayList<>(lowercaseHeaders.keySet());
92+
Collections.sort(sortedHeaderNames);
93+
94+
for (String headerName : sortedHeaderNames) {
95+
serializedHeaders.append(headerName).append(HEADER_NAME_SEPARATOR);
96+
}
97+
98+
serializedHeaders.setLength(serializedHeaders.length() - 1); // remove trailing semicolon
99+
100+
return serializedHeaders;
101+
}
102+
103+
private Map<String, String> getLowercaseHeaders(
104+
Map<String, String> canonicalizedExtensionHeaders) {
105+
// Make all custom header names lowercase.
106+
Map<String, String> lowercaseHeaders = new HashMap<>();
107+
for (String headerName : new ArrayList<>(canonicalizedExtensionHeaders.keySet())) {
108+
109+
String lowercaseHeaderName = headerName.toLowerCase();
110+
111+
// If present and we're V2, remove the x-goog-encryption-key and x-goog-encryption-key-sha256
112+
// headers. (CSEK headers are allowed for V4)
113+
if (Storage.SignUrlOption.SignatureVersion.V2.equals(signatureVersion)
114+
&& ("x-goog-encryption-key".equals(lowercaseHeaderName)
115+
|| "x-goog-encryption-key-sha256".equals(lowercaseHeaderName))) {
116+
117+
continue;
118+
}
119+
120+
lowercaseHeaders.put(lowercaseHeaderName, canonicalizedExtensionHeaders.get(headerName));
121+
}
122+
123+
return lowercaseHeaders;
124+
}
84125
}

branches/autosynth-dataproc/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,16 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020

21+
import com.google.common.collect.ImmutableMap;
22+
import com.google.common.hash.Hashing;
23+
import com.google.common.net.UrlEscapers;
2124
import java.net.URI;
25+
import java.nio.charset.StandardCharsets;
26+
import java.text.SimpleDateFormat;
27+
import java.util.Date;
28+
import java.util.HashMap;
2229
import java.util.Map;
30+
import java.util.TimeZone;
2331

2432
/**
2533
* Signature Info holds payload components of the string that requires signing.
@@ -31,30 +39,70 @@
3139
public class SignatureInfo {
3240

3341
public static final char COMPONENT_SEPARATOR = '\n';
42+
public static final String GOOG4_RSA_SHA256 = "GOOG4-RSA-SHA256";
43+
public static final String SCOPE = "/auto/storage/goog4_request";
3444

3545
private final HttpMethod httpVerb;
3646
private final String contentMd5;
3747
private final String contentType;
3848
private final long expiration;
3949
private final Map<String, String> canonicalizedExtensionHeaders;
4050
private final URI canonicalizedResource;
51+
private final Storage.SignUrlOption.SignatureVersion signatureVersion;
52+
private final String accountEmail;
53+
private final long timestamp;
54+
55+
private final String yearMonthDay;
56+
private final String exactDate;
4157

4258
private SignatureInfo(Builder builder) {
4359
this.httpVerb = builder.httpVerb;
4460
this.contentMd5 = builder.contentMd5;
4561
this.contentType = builder.contentType;
4662
this.expiration = builder.expiration;
47-
this.canonicalizedExtensionHeaders = builder.canonicalizedExtensionHeaders;
4863
this.canonicalizedResource = builder.canonicalizedResource;
64+
this.signatureVersion = builder.signatureVersion;
65+
this.accountEmail = builder.accountEmail;
66+
this.timestamp = builder.timestamp;
67+
68+
if (Storage.SignUrlOption.SignatureVersion.V4.equals(signatureVersion)
69+
&& (!builder.canonicalizedExtensionHeaders.containsKey("host"))) {
70+
canonicalizedExtensionHeaders =
71+
new ImmutableMap.Builder<String, String>()
72+
.putAll(builder.canonicalizedExtensionHeaders)
73+
.put("host", "storage.googleapis.com")
74+
.build();
75+
} else {
76+
canonicalizedExtensionHeaders = builder.canonicalizedExtensionHeaders;
77+
}
78+
79+
Date date = new Date(timestamp);
80+
81+
SimpleDateFormat yearMonthDayFormat = new SimpleDateFormat("yyyyMMdd");
82+
SimpleDateFormat exactDateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
83+
84+
yearMonthDayFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
85+
exactDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
86+
87+
yearMonthDay = yearMonthDayFormat.format(date);
88+
exactDate = exactDateFormat.format(date);
4989
}
5090

5191
/**
5292
* Constructs payload to be signed.
5393
*
54-
* @return paylod to sign
94+
* @return payload to sign
5595
* @see <a href="https://cloud.google.com/storage/docs/access-control#Signed-URLs">Signed URLs</a>
5696
*/
5797
public String constructUnsignedPayload() {
98+
// TODO reverse order when V4 becomes default
99+
if (Storage.SignUrlOption.SignatureVersion.V4.equals(signatureVersion)) {
100+
return constructV4UnsignedPayload();
101+
}
102+
return constructV2UnsignedPayload();
103+
}
104+
105+
private String constructV2UnsignedPayload() {
58106
StringBuilder payload = new StringBuilder();
59107

60108
payload.append(httpVerb.name()).append(COMPONENT_SEPARATOR);
@@ -67,19 +115,72 @@ public String constructUnsignedPayload() {
67115
payload.append(contentType);
68116
}
69117
payload.append(COMPONENT_SEPARATOR);
70-
71118
payload.append(expiration).append(COMPONENT_SEPARATOR);
72119

73120
if (canonicalizedExtensionHeaders != null) {
74121
payload.append(
75-
new CanonicalExtensionHeadersSerializer().serialize(canonicalizedExtensionHeaders));
122+
new CanonicalExtensionHeadersSerializer(Storage.SignUrlOption.SignatureVersion.V2)
123+
.serialize(canonicalizedExtensionHeaders));
76124
}
77125

78126
payload.append(canonicalizedResource);
79127

80128
return payload.toString();
81129
}
82130

131+
private String constructV4UnsignedPayload() {
132+
StringBuilder payload = new StringBuilder();
133+
134+
payload.append(GOOG4_RSA_SHA256).append(COMPONENT_SEPARATOR);
135+
payload.append(exactDate).append(COMPONENT_SEPARATOR);
136+
payload.append(yearMonthDay).append(SCOPE).append(COMPONENT_SEPARATOR);
137+
payload.append(constructV4CanonicalRequestHash());
138+
139+
return payload.toString();
140+
}
141+
142+
private String constructV4CanonicalRequestHash() {
143+
StringBuilder canonicalRequest = new StringBuilder();
144+
145+
CanonicalExtensionHeadersSerializer serializer =
146+
new CanonicalExtensionHeadersSerializer(Storage.SignUrlOption.SignatureVersion.V4);
147+
148+
canonicalRequest.append(httpVerb.name()).append(COMPONENT_SEPARATOR);
149+
canonicalRequest.append(canonicalizedResource).append(COMPONENT_SEPARATOR);
150+
canonicalRequest.append(constructV4QueryString()).append(COMPONENT_SEPARATOR);
151+
canonicalRequest
152+
.append(serializer.serialize(canonicalizedExtensionHeaders))
153+
.append(COMPONENT_SEPARATOR);
154+
canonicalRequest
155+
.append(serializer.serializeHeaderNames(canonicalizedExtensionHeaders))
156+
.append(COMPONENT_SEPARATOR);
157+
canonicalRequest.append("UNSIGNED-PAYLOAD");
158+
159+
return Hashing.sha256()
160+
.hashString(canonicalRequest.toString(), StandardCharsets.UTF_8)
161+
.toString();
162+
}
163+
164+
public String constructV4QueryString() {
165+
StringBuilder signedHeaders =
166+
new CanonicalExtensionHeadersSerializer(Storage.SignUrlOption.SignatureVersion.V4)
167+
.serializeHeaderNames(canonicalizedExtensionHeaders);
168+
169+
StringBuilder queryString = new StringBuilder();
170+
queryString.append("X-Goog-Algorithm=").append(GOOG4_RSA_SHA256).append("&");
171+
queryString.append(
172+
"X-Goog-Credential="
173+
+ UrlEscapers.urlFormParameterEscaper()
174+
.escape(accountEmail + "/" + yearMonthDay + SCOPE)
175+
+ "&");
176+
queryString.append("X-Goog-Date=" + exactDate + "&");
177+
queryString.append("X-Goog-Expires=" + expiration + "&");
178+
queryString.append(
179+
"X-Goog-SignedHeaders="
180+
+ UrlEscapers.urlFormParameterEscaper().escape(signedHeaders.toString()));
181+
return queryString.toString();
182+
}
183+
83184
public HttpMethod getHttpVerb() {
84185
return httpVerb;
85186
}
@@ -104,6 +205,18 @@ public URI getCanonicalizedResource() {
104205
return canonicalizedResource;
105206
}
106207

208+
public Storage.SignUrlOption.SignatureVersion getSignatureVersion() {
209+
return signatureVersion;
210+
}
211+
212+
public long getTimestamp() {
213+
return timestamp;
214+
}
215+
216+
public String getAccountEmail() {
217+
return accountEmail;
218+
}
219+
107220
public static final class Builder {
108221

109222
private final HttpMethod httpVerb;
@@ -112,6 +225,9 @@ public static final class Builder {
112225
private final long expiration;
113226
private Map<String, String> canonicalizedExtensionHeaders;
114227
private final URI canonicalizedResource;
228+
private Storage.SignUrlOption.SignatureVersion signatureVersion;
229+
private String accountEmail;
230+
private long timestamp;
115231

116232
/**
117233
* Constructs builder.
@@ -134,6 +250,9 @@ public Builder(SignatureInfo signatureInfo) {
134250
this.expiration = signatureInfo.expiration;
135251
this.canonicalizedExtensionHeaders = signatureInfo.canonicalizedExtensionHeaders;
136252
this.canonicalizedResource = signatureInfo.canonicalizedResource;
253+
this.signatureVersion = signatureInfo.signatureVersion;
254+
this.accountEmail = signatureInfo.accountEmail;
255+
this.timestamp = signatureInfo.timestamp;
137256
}
138257

139258
public Builder setContentMd5(String contentMd5) {
@@ -155,12 +274,41 @@ public Builder setCanonicalizedExtensionHeaders(
155274
return this;
156275
}
157276

277+
public Builder setSignatureVersion(Storage.SignUrlOption.SignatureVersion signatureVersion) {
278+
this.signatureVersion = signatureVersion;
279+
280+
return this;
281+
}
282+
283+
public Builder setAccountEmail(String accountEmail) {
284+
this.accountEmail = accountEmail;
285+
286+
return this;
287+
}
288+
289+
public Builder setTimestamp(long timestamp) {
290+
this.timestamp = timestamp;
291+
292+
return this;
293+
}
294+
158295
/** Creates an {@code SignatureInfo} object from this builder. */
159296
public SignatureInfo build() {
160297
checkArgument(httpVerb != null, "Required HTTP method");
161298
checkArgument(canonicalizedResource != null, "Required canonicalized resource");
162299
checkArgument(expiration >= 0, "Expiration must be greater than or equal to zero");
163300

301+
if (Storage.SignUrlOption.SignatureVersion.V4.equals(signatureVersion)) {
302+
checkArgument(accountEmail != null, "Account email required to use V4 signing");
303+
checkArgument(timestamp > 0, "Timestamp required to use V4 signing");
304+
checkArgument(
305+
expiration <= 604800, "Expiration can't be longer than 7 days to use V4 signing");
306+
}
307+
308+
if (canonicalizedExtensionHeaders == null) {
309+
canonicalizedExtensionHeaders = new HashMap<>();
310+
}
311+
164312
return new SignatureInfo(this);
165313
}
166314
}

0 commit comments

Comments
 (0)