I am working the Apache Cloudstack API. I am generally working with the org.apache.commons.codec.binary.Base64 base64 encoder. In generating the command I use the following code (from the example given):
private String generateUrl(String commands) throws Exception {
//Signature: This is the hashed signature of the Base URL that is generated using a combination of the user’s Secret Key and the HMAC SHA-1 hashing algorithm.
/*
1. For each field-value pair (as separated by a '&') in the Command String, URL encode each value so that it can be safely sent via HTTP GET.
2. Lower case the entire Command String and sort it alphabetically via the field for each field-value pair. The result of this step would look like the following.
3. Take the sorted Command String and run it through the HMAC SHA-1 hashing algorithm (most programming languages offer a utility method to do this) with the user’s Secret Key. Base64 encode the resulting byte array in UTF-8 so that it can be safely transmitted via HTTP. The final string produced after Base64 encoding should be "Lxx1DM40AjcXU%2FcaiK8RAP0O1hU%3D".
By reconstructing the final URL in the format Base URL+API Path+Command String+Signature, the final URL should look like:
*/
commands += "&id=" + apiReferenceId;
commands += "&response=json";
// Step 1: Make sure your APIKey is toLowerCased and URL encoded
String encodedApiKey = URLEncoder.encode(apiKey.toLowerCase(), "UTF-8");
// Step 2: toLowerCase all the parameters, URL encode each parameter value, and the sort the parameters in alphabetical order
// Please note that if any parameters with a '&' as a value will cause this test client to fail since we are using '&' to delimit
// the string
List<String> sortedParams = new ArrayList<String>();
sortedParams.add("apikey="+encodedApiKey);
StringTokenizer st = new StringTokenizer(commands, "&");
while (st.hasMoreTokens()) {
String paramValue = st.nextToken().toLowerCase();
String param = paramValue.substring(0, paramValue.indexOf("="));
String value = URLEncoder.encode(paramValue.substring(paramValue.indexOf("=")+1, paramValue.length()), "UTF-8");
sortedParams.add(param + "=" + value);
}
Collections.sort(sortedParams);
System.out.println("Sorted Parameters: " + sortedParams);
// Step 3: Construct the sorted URL and sign and URL encode the sorted URL with your secret key
String sortedUrl = null;
boolean first = true;
for (String param : sortedParams) {
if (first) {
sortedUrl = param;
first = false;
} else {
sortedUrl = sortedUrl + "&" + param;
}
}
Logger.debug("sorted URL : " + sortedUrl);
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
mac.init(keySpec);
mac.update(sortedUrl.getBytes());
byte[] encryptedBytes = mac.doFinal();
String encodedSignature = URLEncoder.encode(Base64.encodeBase64String(encryptedBytes), "UTF-8");
// Step 4: Construct the final URL we want to send to the CloudStack Management Server
// Final result should look like:
// http(s)://://client/api?&apiKey=&signature=
String finalUrl = apiUrl + "?" + commands + "&apiKey=" + apiKey + "&signature=" + encodedSignature;
return finalUrl;
}
However, this generates a 401 error.
<?xml version="1.0" encoding="UTF-8"?><stopvirtualmachineresponse cloud-stack-version="4.2.1"><errorcode>401</errorcode><errortext>unable to verify user credentials and/or request signature</errortext></stopvirtualmachineresponse>
When I use:
String encodedSignature = URLEncoder.encode(org.postgresql.util.Base64.encodeBytes(encryptedBytes), "UTF-8");
it works fine. Is there a way I can standardise on the Apache Commons, or is there a specific reason it fails when I use that library?