Skip to content

Commit 6be288b

Browse files
author
RTLcoil
authored
Add support of SHA-256 algorithm in calculation of auth signatures (#215)
1 parent 6a7fa23 commit 6be288b

File tree

12 files changed

+211
-43
lines changed

12 files changed

+211
-43
lines changed

cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public String signedPreloadedImage(Map result) {
130130
}
131131

132132
public String apiSignRequest(Map<String, Object> paramsToSign, String apiSecret) {
133-
return Util.produceSignature(paramsToSign, apiSecret);
133+
return Util.produceSignature(paramsToSign, apiSecret, config.signatureAlgorithm);
134134
}
135135

136136
/**
@@ -139,7 +139,7 @@ public String apiSignRequest(Map<String, Object> paramsToSign, String apiSecret)
139139
* Cloudinary can asynchronously process your e.g. image uploads requests. This is achieved by calling back API you
140140
* specified during preparing of upload request as soon as it has been processed. See Upload Notifications in
141141
* Cloudinary documentation for more details. In order to make sure it is Cloudinary calling your API back, hashed
142-
* message authentication codes (HMAC's) based on SHA-1 hashing function and configured Cloudinary API secret key
142+
* message authentication codes (HMAC's) based on agreed hashing function and configured Cloudinary API secret key
143143
* are used for signing the requests.
144144
*
145145
* The following method serves as a convenient utility to perform the verification procedure.
@@ -151,14 +151,14 @@ public String apiSignRequest(Map<String, Object> paramsToSign, String apiSecret)
151151
* @return whether request signature is valid or not
152152
*/
153153
public boolean verifyNotificationSignature(String body, String timestamp, String signature, long validFor) {
154-
return new NotificationRequestSignatureVerifier(config.apiSecret).verifySignature(body, timestamp, signature, validFor);
154+
return new NotificationRequestSignatureVerifier(config.apiSecret, config.signatureAlgorithm).verifySignature(body, timestamp, signature, validFor);
155155
}
156156

157157
/**
158158
* Verifies that Cloudinary API response is genuine by checking its signature.
159159
*
160160
* Cloudinary can add a signature value in the response to API methods returning public id's and versions. In order
161-
* to make sure it is genuine Cloudinary response, hashed message authentication codes (HMAC's) based on SHA-1 hashing
161+
* to make sure it is genuine Cloudinary response, hashed message authentication codes (HMAC's) based on agreed hashing
162162
* function and configured Cloudinary API secret key are used for signing the responses.
163163
*
164164
* The following method serves as a convenient utility to perform the verification procedure.
@@ -169,7 +169,7 @@ public boolean verifyNotificationSignature(String body, String timestamp, String
169169
* @return whether response signature is valid or not
170170
*/
171171
public boolean verifyApiResponseSignature(String publicId, String version, String signature) {
172-
return new ApiResponseSignatureVerifier(config.apiSecret).verifySignature(publicId, version, signature);
172+
return new ApiResponseSignatureVerifier(config.apiSecret, config.signatureAlgorithm).verifySignature(publicId, version, signature);
173173
}
174174

175175
public void signRequest(Map<String, Object> params, Map<String, Object> options) {

cloudinary-core/src/main/java/com/cloudinary/Configuration.java

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class Configuration {
2020
public final static String VERSION = "1.0.2";
2121
public final static String USER_AGENT = "cld-android-" + VERSION;
2222
public static final boolean DEFAULT_IS_LONG_SIGNATURE = false;
23+
public static final SignatureAlgorithm DEFAULT_SIGNATURE_ALGORITHM = SignatureAlgorithm.SHA1;
24+
25+
private static final String CONFIG_PROP_SIGNATURE_ALGORITHM = "signature_algorithm";
2326

2427
public String cloudName;
2528
public String apiKey;
@@ -43,11 +46,32 @@ public class Configuration {
4346
public AuthToken authToken;
4447
public boolean forceVersion = true;
4548
public boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE;
49+
public SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;
4650

4751
public Configuration() {
4852
}
4953

50-
private Configuration(String cloudName, String apiKey, String apiSecret, String secureDistribution, String cname, String uploadPrefix, boolean secure, boolean privateCdn, boolean cdnSubdomain, boolean shorten, String callback, String proxyHost, int proxyPort, Boolean secureCdnSubdomain, boolean useRootPath, int timeout, boolean loadStrategies, boolean forceVersion, boolean longUrlSignature) {
54+
private Configuration(
55+
String cloudName,
56+
String apiKey,
57+
String apiSecret,
58+
String secureDistribution,
59+
String cname,
60+
String uploadPrefix,
61+
boolean secure,
62+
boolean privateCdn,
63+
boolean cdnSubdomain,
64+
boolean shorten,
65+
String callback,
66+
String proxyHost,
67+
int proxyPort,
68+
Boolean secureCdnSubdomain,
69+
boolean useRootPath,
70+
int timeout,
71+
boolean loadStrategies,
72+
boolean forceVersion,
73+
boolean longUrlSignature,
74+
SignatureAlgorithm signatureAlgorithm) {
5175
this.cloudName = cloudName;
5276
this.apiKey = apiKey;
5377
this.apiSecret = apiSecret;
@@ -67,6 +91,7 @@ private Configuration(String cloudName, String apiKey, String apiSecret, String
6791
this.loadStrategies = loadStrategies;
6892
this.forceVersion = forceVersion;
6993
this.longUrlSignature = longUrlSignature;
94+
this.signatureAlgorithm = signatureAlgorithm;
7095
}
7196

7297
@SuppressWarnings("rawtypes")
@@ -104,6 +129,7 @@ public void update(Map config) {
104129
this.properties.putAll(properties);
105130
}
106131
this.longUrlSignature = ObjectUtils.asBoolean(config.get("long_url_signature"), DEFAULT_IS_LONG_SIGNATURE);
132+
this.signatureAlgorithm = SignatureAlgorithm.valueOf(ObjectUtils.asString(config.get(CONFIG_PROP_SIGNATURE_ALGORITHM), DEFAULT_SIGNATURE_ALGORITHM.name()));
107133
}
108134

109135
@SuppressWarnings("rawtypes")
@@ -133,6 +159,7 @@ public Map<String, Object> asMap() {
133159
map.put("force_version", forceVersion);
134160
map.put("properties", new HashMap<String,Object>(properties));
135161
map.put("long_url_signature", longUrlSignature);
162+
map.put(CONFIG_PROP_SIGNATURE_ALGORITHM, signatureAlgorithm.toString());
136163
return map;
137164
}
138165

@@ -162,6 +189,7 @@ public Configuration(Configuration other) {
162189
this.loadStrategies = other.loadStrategies;
163190
this.properties.putAll(other.properties);
164191
this.longUrlSignature = other.longUrlSignature;
192+
this.signatureAlgorithm = other.signatureAlgorithm;
165193
}
166194

167195
/**
@@ -272,6 +300,7 @@ public static class Builder {
272300
private AuthToken authToken;
273301
private boolean forceVersion = true;
274302
private boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE;
303+
private SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;
275304

276305
/**
277306
* Set the HTTP connection timeout.
@@ -288,7 +317,27 @@ public Builder setTimeout(int timeout) {
288317
* Creates a {@link Configuration} with the arguments supplied to this builder
289318
*/
290319
public Configuration build() {
291-
final Configuration configuration = new Configuration(cloudName, apiKey, apiSecret, secureDistribution, cname, uploadPrefix, secure, privateCdn, cdnSubdomain, shorten, callback, proxyHost, proxyPort, secureCdnSubdomain, useRootPath, timeout, loadStrategies, forceVersion, longUrlSignature);
320+
final Configuration configuration = new Configuration(
321+
cloudName,
322+
apiKey,
323+
apiSecret,
324+
secureDistribution,
325+
cname,
326+
uploadPrefix,
327+
secure,
328+
privateCdn,
329+
cdnSubdomain,
330+
shorten,
331+
callback,
332+
proxyHost,
333+
proxyPort,
334+
secureCdnSubdomain,
335+
useRootPath,
336+
timeout,
337+
loadStrategies,
338+
forceVersion,
339+
longUrlSignature,
340+
signatureAlgorithm);
292341
configuration.clientHints = clientHints;
293342
return configuration;
294343
}
@@ -412,6 +461,11 @@ public Builder setIsLongUrlSignature(boolean isLong) {
412461
return this;
413462
}
414463

464+
public Builder setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
465+
this.signatureAlgorithm = signatureAlgorithm;
466+
return this;
467+
}
468+
415469
/**
416470
* Initialize builder from existing {@link Configuration}
417471
*
@@ -440,6 +494,7 @@ public Builder from(Configuration other) {
440494
this.authToken = other.authToken == null ? null : other.authToken.copy();
441495
this.forceVersion = other.forceVersion;
442496
this.longUrlSignature = other.longUrlSignature;
497+
this.signatureAlgorithm = other.signatureAlgorithm;
443498
return this;
444499
}
445500
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.cloudinary;
2+
3+
/**
4+
* Defines supported algorithms for generating/verifying hashed message authentication codes (HMAC).
5+
*/
6+
public enum SignatureAlgorithm {
7+
SHA1("SHA-1"),
8+
SHA256("SHA-256");
9+
10+
private final String algorithmId;
11+
12+
SignatureAlgorithm(String algorithmId) {
13+
this.algorithmId = algorithmId;
14+
}
15+
16+
public String getAlgorithmId() {
17+
return algorithmId;
18+
}
19+
}

cloudinary-core/src/main/java/com/cloudinary/Url.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import java.net.MalformedURLException;
55
import java.net.URL;
66
import java.net.URLDecoder;
7-
import java.security.MessageDigest;
8-
import java.security.NoSuchAlgorithmException;
97
import java.util.ArrayList;
108
import java.util.HashMap;
119
import java.util.List;
@@ -19,6 +17,8 @@
1917
import com.cloudinary.utils.ObjectUtils;
2018
import com.cloudinary.utils.StringUtils;
2119

20+
import static com.cloudinary.SignatureAlgorithm.SHA256;
21+
2222
public class Url {
2323
private final Cloudinary cloudinary;
2424
private final Configuration config;
@@ -389,19 +389,14 @@ public String generate(String source) {
389389

390390

391391
if (signUrl && (authToken == null || authToken.equals(AuthToken.NULL_AUTH_TOKEN))) {
392-
MessageDigest md = null;
393-
try {
394-
md = MessageDigest.getInstance(longUrlSignature ? "SHA-256" : "SHA-1");
395-
} catch (NoSuchAlgorithmException e) {
396-
throw new RuntimeException("Unexpected exception", e);
397-
}
392+
SignatureAlgorithm signatureAlgorithm = longUrlSignature ? SHA256 : config.signatureAlgorithm;
398393

399394
String toSign = StringUtils.join(new String[]{transformationStr, sourceToSign}, "/");
400395
toSign = StringUtils.removeStartingChars(toSign, '/');
401396
toSign = StringUtils.mergeSlashesInUrl(toSign);
402397

403-
byte[] digest = md.digest(Util.getUTF8Bytes(toSign + this.config.apiSecret));
404-
signature = Base64Coder.encodeURLSafeString(digest);
398+
byte[] hash = Util.hash(toSign + this.config.apiSecret, signatureAlgorithm);
399+
signature = Base64Coder.encodeURLSafeString(hash);
405400
signature = "s--" + signature.substring(0, longUrlSignature ? 32 : 8) + "--";
406401
}
407402

cloudinary-core/src/main/java/com/cloudinary/Util.java

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -323,18 +323,36 @@ public static byte[] getUTF8Bytes(String string) {
323323

324324
/**
325325
* Calculates signature, or hashed message authentication code (HMAC) of provided parameters name-value pairs and
326-
* secret value using SHA-1 hashing algorithm.
326+
* secret value using default hashing algorithm (SHA1).
327327
* <p>
328328
* Argument for hashing function is built by joining sorted parameter name-value pairs into single string in the
329329
* same fashion as HTTP GET method uses, and concatenating the result with secret value in the end. Method supports
330330
* arrays/collections as parameter values. In this case, the elements of array/collection are joined into single
331331
* comma-delimited string prior to inclusion into the result.
332332
*
333-
* @param paramsToSign parameter name-value pairs list represented as instance of {@link Map}
334-
* @param apiSecret secret value
333+
* @param paramsToSign parameter name-value pairs list represented as instance of {@link Map}
334+
* @param apiSecret secret value
335335
* @return hex-string representation of signature calculated based on provided parameters map and secret
336336
*/
337337
public static String produceSignature(Map<String, Object> paramsToSign, String apiSecret) {
338+
return produceSignature(paramsToSign, apiSecret, SignatureAlgorithm.SHA1);
339+
}
340+
341+
/**
342+
* Calculates signature, or hashed message authentication code (HMAC) of provided parameters name-value pairs and
343+
* secret value using specified hashing algorithm.
344+
* <p>
345+
* Argument for hashing function is built by joining sorted parameter name-value pairs into single string in the
346+
* same fashion as HTTP GET method uses, and concatenating the result with secret value in the end. Method supports
347+
* arrays/collections as parameter values. In this case, the elements of array/collection are joined into single
348+
* comma-delimited string prior to inclusion into the result.
349+
*
350+
* @param paramsToSign parameter name-value pairs list represented as instance of {@link Map}
351+
* @param apiSecret secret value
352+
* @param signatureAlgorithm type of hashing algorithm to use for calculation of HMAC
353+
* @return hex-string representation of signature calculated based on provided parameters map and secret
354+
*/
355+
public static String produceSignature(Map<String, Object> paramsToSign, String apiSecret, SignatureAlgorithm signatureAlgorithm) {
338356
Collection<String> params = new ArrayList<String>();
339357
for (Map.Entry<String, Object> param : new TreeMap<String, Object>(paramsToSign).entrySet()) {
340358
if (param.getValue() instanceof Collection) {
@@ -348,13 +366,22 @@ public static String produceSignature(Map<String, Object> paramsToSign, String a
348366
}
349367
}
350368
String to_sign = StringUtils.join(params, "&");
351-
MessageDigest md = null;
369+
byte[] hash = Util.hash(to_sign + apiSecret, signatureAlgorithm);
370+
return StringUtils.encodeHexString(hash);
371+
}
372+
373+
/**
374+
* Computes hash from input string using specified algorithm.
375+
*
376+
* @param input string which to compute hash from
377+
* @param signatureAlgorithm algorithm to use for computing hash
378+
* @return array of bytes of computed hash value
379+
*/
380+
public static byte[] hash(String input, SignatureAlgorithm signatureAlgorithm) {
352381
try {
353-
md = MessageDigest.getInstance("SHA-1");
382+
return MessageDigest.getInstance(signatureAlgorithm.getAlgorithmId()).digest(Util.getUTF8Bytes(input));
354383
} catch (NoSuchAlgorithmException e) {
355384
throw new RuntimeException("Unexpected exception", e);
356385
}
357-
byte[] digest = md.digest(getUTF8Bytes(to_sign + apiSecret));
358-
return StringUtils.encodeHexString(digest);
359386
}
360387
}

cloudinary-core/src/main/java/com/cloudinary/api/signing/ApiResponseSignatureVerifier.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cloudinary.api.signing;
22

3+
import com.cloudinary.SignatureAlgorithm;
34
import com.cloudinary.Util;
45
import com.cloudinary.utils.ObjectUtils;
56
import com.cloudinary.utils.StringUtils;
@@ -11,6 +12,7 @@
1112
*/
1213
public class ApiResponseSignatureVerifier {
1314
private final String secretKey;
15+
private final SignatureAlgorithm signatureAlgorithm;
1416

1517
/**
1618
* Initializes new instance of {@code ApiResponseSignatureVerifier} class with a secret key required to perform
@@ -24,6 +26,23 @@ public ApiResponseSignatureVerifier(String secretKey) {
2426
}
2527

2628
this.secretKey = secretKey;
29+
this.signatureAlgorithm = SignatureAlgorithm.SHA1;
30+
}
31+
32+
/**
33+
* Initializes new instance of {@code ApiResponseSignatureVerifier} class with a secret key required to perform
34+
* API response signatures verification.
35+
*
36+
* @param secretKey shared secret key string which is used to sign and verify authenticity of API responses
37+
* @param signatureAlgorithm type of hashing algorithm to use for calculation of HMACs
38+
*/
39+
public ApiResponseSignatureVerifier(String secretKey, SignatureAlgorithm signatureAlgorithm) {
40+
if (StringUtils.isBlank(secretKey)) {
41+
throw new IllegalArgumentException("Secret key is required");
42+
}
43+
44+
this.secretKey = secretKey;
45+
this.signatureAlgorithm = signatureAlgorithm;
2746
}
2847

2948
/**
@@ -41,6 +60,6 @@ public ApiResponseSignatureVerifier(String secretKey) {
4160
public boolean verifySignature(String publicId, String version, String signature) {
4261
return Util.produceSignature(ObjectUtils.asMap(
4362
"public_id", emptyIfNull(publicId),
44-
"version", emptyIfNull(version)), secretKey).equals(signature);
63+
"version", emptyIfNull(version)), secretKey, signatureAlgorithm).equals(signature);
4564
}
4665
}

cloudinary-core/src/main/java/com/cloudinary/api/signing/NotificationRequestSignatureVerifier.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.cloudinary.api.signing;
22

3+
import com.cloudinary.SignatureAlgorithm;
4+
35
import static com.cloudinary.utils.StringUtils.emptyIfNull;
46

57
/**
@@ -15,7 +17,17 @@ public class NotificationRequestSignatureVerifier {
1517
* @param secretKey shared secret key string which is used to sign and verify authenticity of notifications
1618
*/
1719
public NotificationRequestSignatureVerifier(String secretKey) {
18-
this.signedPayloadValidator = new SignedPayloadValidator(secretKey);
20+
this.signedPayloadValidator = new SignedPayloadValidator(secretKey, SignatureAlgorithm.SHA1);
21+
}
22+
23+
/**
24+
* Initializes new instance of {@code NotificationRequestSignatureVerifier} with secret key value.
25+
*
26+
* @param secretKey shared secret key string which is used to sign and verify authenticity of notifications
27+
* @param signatureAlgorithm type of hashing algorithm to use for calculation of HMACs
28+
*/
29+
public NotificationRequestSignatureVerifier(String secretKey, SignatureAlgorithm signatureAlgorithm) {
30+
this.signedPayloadValidator = new SignedPayloadValidator(secretKey, signatureAlgorithm);
1931
}
2032

2133
/**

0 commit comments

Comments
 (0)