Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cloudinary-core/src/main/java/com/cloudinary/Cloudinary.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public String signedPreloadedImage(Map result) {
}

public String apiSignRequest(Map<String, Object> paramsToSign, String apiSecret) {
return Util.produceSignature(paramsToSign, apiSecret);
return Util.produceSignature(paramsToSign, apiSecret, config.signatureAlgorithm);
}

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

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

public void signRequest(Map<String, Object> params, Map<String, Object> options) {
Expand Down
59 changes: 57 additions & 2 deletions cloudinary-core/src/main/java/com/cloudinary/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public class Configuration {
public final static String VERSION = "1.0.2";
public final static String USER_AGENT = "cld-android-" + VERSION;
public static final boolean DEFAULT_IS_LONG_SIGNATURE = false;
public static final SignatureAlgorithm DEFAULT_SIGNATURE_ALGORITHM = SignatureAlgorithm.SHA1;

private static final String CONFIG_PROP_SIGNATURE_ALGORITHM = "signature_algorithm";

public String cloudName;
public String apiKey;
Expand All @@ -43,11 +46,32 @@ public class Configuration {
public AuthToken authToken;
public boolean forceVersion = true;
public boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE;
public SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;

public Configuration() {
}

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) {
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,
SignatureAlgorithm signatureAlgorithm) {
this.cloudName = cloudName;
this.apiKey = apiKey;
this.apiSecret = apiSecret;
Expand All @@ -67,6 +91,7 @@ private Configuration(String cloudName, String apiKey, String apiSecret, String
this.loadStrategies = loadStrategies;
this.forceVersion = forceVersion;
this.longUrlSignature = longUrlSignature;
this.signatureAlgorithm = signatureAlgorithm;
}

@SuppressWarnings("rawtypes")
Expand Down Expand Up @@ -104,6 +129,7 @@ public void update(Map config) {
this.properties.putAll(properties);
}
this.longUrlSignature = ObjectUtils.asBoolean(config.get("long_url_signature"), DEFAULT_IS_LONG_SIGNATURE);
this.signatureAlgorithm = SignatureAlgorithm.valueOf(ObjectUtils.asString(config.get(CONFIG_PROP_SIGNATURE_ALGORITHM), DEFAULT_SIGNATURE_ALGORITHM.name()));
}

@SuppressWarnings("rawtypes")
Expand Down Expand Up @@ -133,6 +159,7 @@ public Map<String, Object> asMap() {
map.put("force_version", forceVersion);
map.put("properties", new HashMap<String,Object>(properties));
map.put("long_url_signature", longUrlSignature);
map.put(CONFIG_PROP_SIGNATURE_ALGORITHM, signatureAlgorithm.toString());
return map;
}

Expand Down Expand Up @@ -162,6 +189,7 @@ public Configuration(Configuration other) {
this.loadStrategies = other.loadStrategies;
this.properties.putAll(other.properties);
this.longUrlSignature = other.longUrlSignature;
this.signatureAlgorithm = other.signatureAlgorithm;
}

/**
Expand Down Expand Up @@ -272,6 +300,7 @@ public static class Builder {
private AuthToken authToken;
private boolean forceVersion = true;
private boolean longUrlSignature = DEFAULT_IS_LONG_SIGNATURE;
private SignatureAlgorithm signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;

/**
* Set the HTTP connection timeout.
Expand All @@ -288,7 +317,27 @@ public Builder setTimeout(int timeout) {
* Creates a {@link Configuration} with the arguments supplied to this builder
*/
public Configuration build() {
final Configuration configuration = new Configuration(cloudName, apiKey, apiSecret, secureDistribution, cname, uploadPrefix, secure, privateCdn, cdnSubdomain, shorten, callback, proxyHost, proxyPort, secureCdnSubdomain, useRootPath, timeout, loadStrategies, forceVersion, longUrlSignature);
final Configuration configuration = new Configuration(
cloudName,
apiKey,
apiSecret,
secureDistribution,
cname,
uploadPrefix,
secure,
privateCdn,
cdnSubdomain,
shorten,
callback,
proxyHost,
proxyPort,
secureCdnSubdomain,
useRootPath,
timeout,
loadStrategies,
forceVersion,
longUrlSignature,
signatureAlgorithm);
configuration.clientHints = clientHints;
return configuration;
}
Expand Down Expand Up @@ -412,6 +461,11 @@ public Builder setIsLongUrlSignature(boolean isLong) {
return this;
}

public Builder setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
return this;
}

/**
* Initialize builder from existing {@link Configuration}
*
Expand Down Expand Up @@ -440,6 +494,7 @@ public Builder from(Configuration other) {
this.authToken = other.authToken == null ? null : other.authToken.copy();
this.forceVersion = other.forceVersion;
this.longUrlSignature = other.longUrlSignature;
this.signatureAlgorithm = other.signatureAlgorithm;
return this;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.cloudinary;

/**
* Defines supported algorithms for generating/verifying hashed message authentication codes (HMAC).
*/
public enum SignatureAlgorithm {
SHA1("SHA-1"),
SHA256("SHA-256");

private final String algorithmId;

SignatureAlgorithm(String algorithmId) {
this.algorithmId = algorithmId;
}

public String getAlgorithmId() {
return algorithmId;
}
}
15 changes: 5 additions & 10 deletions cloudinary-core/src/main/java/com/cloudinary/Url.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -19,6 +17,8 @@
import com.cloudinary.utils.ObjectUtils;
import com.cloudinary.utils.StringUtils;

import static com.cloudinary.SignatureAlgorithm.SHA256;

public class Url {
private final Cloudinary cloudinary;
private final Configuration config;
Expand Down Expand Up @@ -389,19 +389,14 @@ public String generate(String source) {


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

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

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

Expand Down
41 changes: 34 additions & 7 deletions cloudinary-core/src/main/java/com/cloudinary/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,36 @@ public static byte[] getUTF8Bytes(String string) {

/**
* Calculates signature, or hashed message authentication code (HMAC) of provided parameters name-value pairs and
* secret value using SHA-1 hashing algorithm.
* secret value using default hashing algorithm (SHA1).
* <p>
* Argument for hashing function is built by joining sorted parameter name-value pairs into single string in the
* same fashion as HTTP GET method uses, and concatenating the result with secret value in the end. Method supports
* arrays/collections as parameter values. In this case, the elements of array/collection are joined into single
* comma-delimited string prior to inclusion into the result.
*
* @param paramsToSign parameter name-value pairs list represented as instance of {@link Map}
* @param apiSecret secret value
* @param paramsToSign parameter name-value pairs list represented as instance of {@link Map}
* @param apiSecret secret value
* @return hex-string representation of signature calculated based on provided parameters map and secret
*/
public static String produceSignature(Map<String, Object> paramsToSign, String apiSecret) {
return produceSignature(paramsToSign, apiSecret, SignatureAlgorithm.SHA1);
}

/**
* Calculates signature, or hashed message authentication code (HMAC) of provided parameters name-value pairs and
* secret value using specified hashing algorithm.
* <p>
* Argument for hashing function is built by joining sorted parameter name-value pairs into single string in the
* same fashion as HTTP GET method uses, and concatenating the result with secret value in the end. Method supports
* arrays/collections as parameter values. In this case, the elements of array/collection are joined into single
* comma-delimited string prior to inclusion into the result.
*
* @param paramsToSign parameter name-value pairs list represented as instance of {@link Map}
* @param apiSecret secret value
* @param signatureAlgorithm type of hashing algorithm to use for calculation of HMAC
* @return hex-string representation of signature calculated based on provided parameters map and secret
*/
public static String produceSignature(Map<String, Object> paramsToSign, String apiSecret, SignatureAlgorithm signatureAlgorithm) {
Collection<String> params = new ArrayList<String>();
for (Map.Entry<String, Object> param : new TreeMap<String, Object>(paramsToSign).entrySet()) {
if (param.getValue() instanceof Collection) {
Expand All @@ -348,13 +366,22 @@ public static String produceSignature(Map<String, Object> paramsToSign, String a
}
}
String to_sign = StringUtils.join(params, "&");
MessageDigest md = null;
byte[] hash = Util.hash(to_sign + apiSecret, signatureAlgorithm);
return StringUtils.encodeHexString(hash);
}

/**
* Computes hash from input string using specified algorithm.
*
* @param input string which to compute hash from
* @param signatureAlgorithm algorithm to use for computing hash
* @return array of bytes of computed hash value
*/
public static byte[] hash(String input, SignatureAlgorithm signatureAlgorithm) {
try {
md = MessageDigest.getInstance("SHA-1");
return MessageDigest.getInstance(signatureAlgorithm.getAlgorithmId()).digest(Util.getUTF8Bytes(input));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unexpected exception", e);
}
byte[] digest = md.digest(getUTF8Bytes(to_sign + apiSecret));
return StringUtils.encodeHexString(digest);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.cloudinary.api.signing;

import com.cloudinary.SignatureAlgorithm;
import com.cloudinary.Util;
import com.cloudinary.utils.ObjectUtils;
import com.cloudinary.utils.StringUtils;
Expand All @@ -11,6 +12,7 @@
*/
public class ApiResponseSignatureVerifier {
private final String secretKey;
private final SignatureAlgorithm signatureAlgorithm;

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

this.secretKey = secretKey;
this.signatureAlgorithm = SignatureAlgorithm.SHA1;
}

/**
* Initializes new instance of {@code ApiResponseSignatureVerifier} class with a secret key required to perform
* API response signatures verification.
*
* @param secretKey shared secret key string which is used to sign and verify authenticity of API responses
* @param signatureAlgorithm type of hashing algorithm to use for calculation of HMACs
*/
public ApiResponseSignatureVerifier(String secretKey, SignatureAlgorithm signatureAlgorithm) {
if (StringUtils.isBlank(secretKey)) {
throw new IllegalArgumentException("Secret key is required");
}

this.secretKey = secretKey;
this.signatureAlgorithm = signatureAlgorithm;
}

/**
Expand All @@ -41,6 +60,6 @@ public ApiResponseSignatureVerifier(String secretKey) {
public boolean verifySignature(String publicId, String version, String signature) {
return Util.produceSignature(ObjectUtils.asMap(
"public_id", emptyIfNull(publicId),
"version", emptyIfNull(version)), secretKey).equals(signature);
"version", emptyIfNull(version)), secretKey, signatureAlgorithm).equals(signature);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.cloudinary.api.signing;

import com.cloudinary.SignatureAlgorithm;

import static com.cloudinary.utils.StringUtils.emptyIfNull;

/**
Expand All @@ -15,7 +17,17 @@ public class NotificationRequestSignatureVerifier {
* @param secretKey shared secret key string which is used to sign and verify authenticity of notifications
*/
public NotificationRequestSignatureVerifier(String secretKey) {
this.signedPayloadValidator = new SignedPayloadValidator(secretKey);
this.signedPayloadValidator = new SignedPayloadValidator(secretKey, SignatureAlgorithm.SHA1);
}

/**
* Initializes new instance of {@code NotificationRequestSignatureVerifier} with secret key value.
*
* @param secretKey shared secret key string which is used to sign and verify authenticity of notifications
* @param signatureAlgorithm type of hashing algorithm to use for calculation of HMACs
*/
public NotificationRequestSignatureVerifier(String secretKey, SignatureAlgorithm signatureAlgorithm) {
this.signedPayloadValidator = new SignedPayloadValidator(secretKey, signatureAlgorithm);
}

/**
Expand Down
Loading