While writing an LTI integration tool, I decided to run a battery of true LTI payloads from various LMS systems and see if I could produce the same signature. While running these payloads via automation, I found that some payloads would fail to produce the same signature. After ensuring that my encoding was using the agreed standard RFC 3986, I looked to narrow in on a specific LMS like Moodle or Blackboard. The signature fails were not confined to a specific LMS, but rather all contained the same single non-visible character.
The payloads failing to produce a correct signature happened to contain a carriage return (\r) followed by a new line(\n). The \r\n line termination sequence is commonly used by Microsoft. Suspicious of the carriage return, I removed it from the payloads, then reprocessed the signatures. It was successful. So now understanding what caused the problem, I decided to narrow in to the why.
LTI Process (Simple Version)
The LTI flow originates from the LMS also known as the Tool Consumer (TC), the TC will generate a payload of data consisting of key/value pairs and then sign the data using a shared secret. This signature uses OAuth. Once the data has been created it is generally injected into a user's browser page inside a form element of type POST and an action pointing to the Tool Provider's (TP) endpoint. Once this data is in the form, it will be submitted either automatically or manually. The receipient, which is the TP, takes this payload of data and verifies the data has not been changed by using the shared secret to recreate the signature. If the signature matches the one provided, the data can be trusted.
Dirty Little Microsoft Browser
The problem we see is actually caused by the browser attempting to format the data as it is injected into the form and newlines are preceded by a carriage return. This actually makes sense if the browser is going to display the data to the user, as Windows uses these carriage returns.
At some point, it appears that the issue was corrected for Edge, but current LTI payloads make me suspect that it is still an issue. (See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16221536/)
Unfortunately, we can't just always remove carriage returns prior to signing our payload. This is because a lot of data in the payload is, in fact, entered by an user who might be using a browser that uses carriage returns. In this case, the data will most likely be signed with the carriage returns in place as few implementations clean up these returns prior to signing.
In order to handle these carriage return injections, we are going to have to check both possibilities. First, we assume that the data provided is correct for signing. The payloads may not contain carriage returns or the possibly no newlines at all. If this fails, we second try to remove only carriage returns that are immediately followed by a newline character.