In IMS Emulator (http://ltiapps.net/test/tc.php) on clicking of "Save Data", with the auto populated data the outh_signature is generated and put into as a hidden value in form frmLaunch(name='frmLaunch') form. I need to generate similar outh_signature programtically, but i am not able to generate the exact oauth_signature what the emulator is generating even though i use the same oauth_nounce and oauth_timestamp.. I am not sure what is the request body that i need to sent while generating signature..
To recreate the scenario follow below steps
- Hit the url http://ltiapps.net/test/tc.php
- Click clear Data and click ok on popup
- Select role as Learner and click save data
After saving data you will see a outh_signature hidden value with input id as "oauth_signature"
I tried to generate in below way but not able to get the expected signature.
import java.io.*; import java.net.URL; import java.net.URLEncoder; import java.util.LinkedHashMap; import java.util.Map; import java.util.TreeMap; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HttpsURLConnection; // Apache Commons Libraries used for the Nonce & Base64 import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; public class OAuthTest { public static void main(final String[] args) throws Exception { // Setup the variables necessary to create the OAuth 1.0 signature and make the request String httpMethod = "POST"; String consumerKey = "jisc.ac.uk"; String secret = "secret"; String signatureMethod = "HMAC-SHA1"; String body = ""; //mentioned in the description byte[] requestBody = null; URL url = new URL("http://ltiapps.net/test/tp.php"); // Set the Nonce and Timestamp parameters String nonce = "6d95eef168e568a530d1cd419a997952";//getNonce(); String timestamp = "1483470400";//getTimestamp(); System.out.println("Nonce:" + getNonce()); System.out.println("timestamp:" + getTimestamp()); // Set the request body if making a POST or PUT request if ("POST".equals(httpMethod) || "PUT".equals(httpMethod)) { requestBody = body.getBytes("UTF-8"); } // Create the OAuth parameter name/value pair Map<String, String> oauthParams = new LinkedHashMap<String, String>(); oauthParams.put("oauth_consumer_key", consumerKey); oauthParams.put("oauth_signature_method", signatureMethod); oauthParams.put("oauth_timestamp", timestamp); oauthParams.put("oauth_nonce", nonce); // Get the OAuth 1.0 Signature String signature = generateSignature(httpMethod, url, oauthParams, requestBody, secret); System.out.println(String.format("OAuth 1.0 Signature: %s", signature)); } private static String getNonce() { return RandomStringUtils.randomAlphanumeric(32); } private static String getTimestamp() { return Long.toString((System.currentTimeMillis() / 1000)); } private static String generateSignature( String httpMethod, URL url, Map<String, String> oauthParams, byte[] requestBody, String secret ) throws UnsupportedEncodingException { // Ensure the HTTP Method is upper-cased httpMethod = httpMethod.toUpperCase(); // Construct the URL-encoded OAuth parameter portion of the signature base string String encodedParams = normalizeParams(httpMethod, url, oauthParams, requestBody); // URL-encode the relative URL String encodedUri = URLEncoder.encode(url.getPath(), "UTF-8"); // Build the signature base string to be signed with the Consumer Secret String baseString = String.format("%s&%s&%s", httpMethod, encodedUri, encodedParams); return hmacSha1(baseString, secret); } private static String normalizeParams( String httpMethod, URL url, Map<String, String> oauthParams, byte[] requestBody ) throws UnsupportedEncodingException { // Sort the parameters in lexicographical order, 1st by Key then by Value Map<String, String> kvpParams = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER); kvpParams.putAll(oauthParams); // Place any query string parameters into a key value pair using equals ("=") to mark // the key/value relationship and join each parameter with an ampersand ("&") if (url.getQuery() != null) { for(String keyValue : url.getQuery().split("&")) { String[] p = keyValue.split("="); kvpParams.put(p[0],p[1]); } } // Include the body parameter if dealing with a POST or PUT request if ("POST".equals(httpMethod) || "PUT".equals(httpMethod)) { String body = Base64.encodeBase64String(requestBody).replaceAll("\r\n", ""); // url encode the body 2 times now before combining other params body = URLEncoder.encode(body, "UTF-8"); body = URLEncoder.encode(body, "UTF-8"); kvpParams.put("body", body); } // separate the key and values with a "=" // separate the kvp with a "&" StringBuilder combinedParams = new StringBuilder(); String delimiter=""; for(String key : kvpParams.keySet()) { combinedParams.append(delimiter); combinedParams.append(key); combinedParams.append("="); combinedParams.append(kvpParams.get(key)); delimiter="&"; } // url encode the entire string again before returning return URLEncoder.encode(combinedParams.toString(), "UTF-8"); } public static String hmacSha1(String value, String key) { String algorithm = "HmacSHA1"; try { // Get an hmac_sha1 key from the raw key bytes byte[] keyBytes = key.getBytes(); SecretKeySpec signingKey = new SecretKeySpec(keyBytes, algorithm); // Get an hmac_sha1 Mac instance and initialize with the signing key Mac mac = Mac.getInstance(algorithm); mac.init(signingKey); // Compute the hmac on input data bytes // byte[] rawHmac = mac.doFinal(value.getBytes()); // Convert raw bytes to Hex // byte[] hexBytes = new Hex().encode(rawHmac); return new String(Base64.encodeBase64(mac.doFinal(value.getBytes()))).trim(); // Covert array of Hex bytes to a String //return new String(hexBytes, "UTF-8"); } catch (Exception e) { throw new RuntimeException(e); } } }
pom.xml
<dependencies> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.0</version> </dependency>
I tried above program by sending request body as below and got oauth signature as 0YI3mBg7gmnWaz8YyISG4IoHVQ4= but expected is yuuvR1pVDm5xWOYhMtBcBBVTdf8=
Can you please let me know where i am going wrong..
Since you are using JAVA I would suggest that you utilize the basiclti-util library that IMSGlobal provides, it takes care of most of what you are doing and doesn't require you to reinvent the wheel
Add the following dependency to your pom
This library provides support for:
Tool Providers:
Tool Consumers:
To Verify an LTI Launch request sent by a Tool Consumer
If you are trying to sign an outgoing request use the following