Unable to generate oauth_signature for my IMS LTI request

1.6k views Asked by At

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..

1

There are 1 answers

6
pfranza On

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");