1

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

  1. Hit the url http://ltiapps.net/test/tc.php
  2. Click clear Data and click ok on popup
  3. Select role as Learner and click save data
  4. 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=

version=LTI-1p0&reset=&endpoint=http://ltiapps.net/test/tp.php&register=http://ltiapps.net/test/tp.php&key=jisc.ac.uk&secret=secret&lti_message_type=basic-lti-launch-request&message_type=&tool=&lti_version=LTI-1p0&launch_presentation_locale=&launch_presentation_document_target=&launch_presentation_width=&launch_presentation_height=&launch_presentation_css_url=&launch_presentation_return_url=&custom=&ext=&signaturemethod=HMAC-SHA1&accept_media_types=&accept_presentation_document_targets=embed,frame,iframe,window,popup,overlay,none&content_item_return_url=http://ltiapps.net/test/tc-content.php&accept_unsigned=&accept_multiple=&accept_copy_advice=&auto_create=&title=&text=&data=&tool_consumer_instance_guid=&tool_consumer_instance_name=&tool_consumer_instance_description=&tool_consumer_instance_url=&tool_consumer_instance_contact_email=&tool_consumer_info_product_family_code=&tool_consumer_info_version=&context_id=&context_type=&a_context_type=&context_title=&context_label=&lis_course_offering_sourcedid=&lis_course_section_sourcedid=&resource_link_id=429785226&resource_link_title=&resource_link_description=&user_id=&lis_person_name_given=&lis_person_name_family=&lis_person_name_full=&lis_person_contact_email_primary=&lis_person_sourcedid=&roles=Learner&a_role=&user_image=&mentors=&username=&lis_outcome_service_url=&lis_result_sourcedid=&ext_ims_lis_basic_outcome_url=&ext_ims_lis_resultvalue_sourcedids=&ext_ims_lis_memberships_url=&ext_ims_lis_memberships_id=&ext_ims_lti_tool_setting_url=&ext_ims_lti_tool_setting_id=&setting=&custom_tc_profile_url=&custom_system_setting_url=&custom_context_setting_url=&custom_link_setting_url=&custom_lineitems_url=&custom_results_url=&custom_lineitem_url=&custom_result_url=&custom_context_memberships_url=&custom_link_memberships_url=&custom_caliper_federated_session_id=&custom_caliper_eventstore_url=&custom_caliper_api_key=

Can you please let me know where i am going wrong..

CNKR
  • 548
  • 4
  • 17

1 Answers1

1

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

<dependency>
  <groupId>org.imsglobal</groupId>
  <artifactId>basiclti-util</artifactId>
  <version>1.1.2</version>
</dependency>

This library provides support for:

Tool Providers:

  • Verifying an LTI launch request
  • Sending LTI 1.1 Outcomes request (xml-based)
  • AspectJ launch verifiers for easy integration with Spring-web.

Tool Consumers:

  • Creating a valid LTI launch request

To Verify an LTI Launch request sent by a Tool Consumer

HttpServletRequest request; // java servlet request
LtiVerifier ltiVerifier = new LtiOauthVerifier();
String key = request.getParameter("oauth_consumer_key");
String secret = // retrieve corresponding secret for key from db
LtiVerificationResult ltiResult = ltiVerifier.verify(request, secret);

If you are trying to sign an outgoing request use the following

Map<String, String> signedParameters = new LtiOauthSigner().signParameters(parameters, key, secret, url, "POST");
pfranza
  • 3,066
  • 2
  • 19
  • 32
  • is it possible that this lib does not work when the parameters are passed as a FORM POST? It seems only to read get parameters – wutzebaer Aug 17 '17 at 11:14
  • @wutzebaer Form posts are fine, that is what I use almost exclusively. – pfranza Aug 17 '17 at 11:18
  • do you use ltiVerifier.verifyParameters or ltiVerifier.verify? – wutzebaer Aug 17 '17 at 11:43
  • Generally I just use `.verify(request, secret)` and pass in the entire `HttpServletRequest` but have on occasion needed to use `verifyParameters` .. they produce the same result. – pfranza Aug 17 '17 at 11:50
  • Is it possible that you form parameters are passed as query string? because verify() does not read the request body, of course this is important because otherwise the body is consumed and no more available for the rest of the app, i think verifyParameters() is my way to go – wutzebaer Aug 17 '17 at 12:25