Debugging
Introduction
Cryptography necessarily makes data unreadable, and as a result, this makes debugging especially challenging. MTE is no exception to this. It is very difficult to determine what might be wrong because the developer typically gets no sense of whether the data is "close" to correct or not. The result is either correct or not.
MTE does have some features that can assist the developer in determining what is wrong. Some of these features depend on which features of MTE you are using, but some apply to all MTE configurations. The following sections describe common problems and strategies for determining the cause and resolving the issue.
General Suggestions
Status
Almost all MTE functions return a status. It is extremely important that the status is always captured and checked. Ignoring the status, even for operations that may almost never fail, makes debugging very difficult because the error you get later may have been caused by an earlier error, which if checked, would be much easier to figure out. Once you get to the token does not exist error, there are many possible causes, some of which are caused by earlier unhandled errors.
Licensing
Always call the license init function and check the status, even if you do not require a license code (e.g., for the trial version). When a license code is not required, the arguments to the function are just ignored, but you will get a success status, and your code will be future-proofed in case you need a license code later.
MTE Options
The MTE library can be built with runtime options support or buildtime options. The buildtime options are used by most customers to reduce the library size and make it even more efficient for the exact options they need. If buildtime options are used, the MTE encoder and decoder must be initialized with the options that are actually built in; if mismatched options are passed an error or crash will occur. If your library has runtime options capability, any options may be used.
Most language interfaces contain constructor or initializer functions that are written to automatically use the correct set of buildtime options if that is how the library was built or a default set of options if runtime options are enabled. Always use these helpers where possible to avoid the possibility of passing the wrong options. This future-proofs your code to work against any set of buildtime options if you decide to change at some point.
The C language interface does not have this capability, so it is instead recommended to write the same logic in your code. Use the MTE functions that can query the built-in options to determine at runtime the appropriate options to use. Refer to the C++ language interface for an example, or look at the C demo applications in the SDK.
Token Does Not Exist
The token does not exist error is by far the most common problem. There can be many causes for this, including attacks or communications issues, but during development, the cause is almost always a programming error of some kind. Following are the most common errors.
Entropy Reuse
A common problem involves developers attempting to reuse entropy when creating both an encoder and decoder on an endpoint for two-way communication. In almost all language interfaces the entropy is zeroized when used, so you cannot pass the same entropy to two different MTE instances as the second one will get an all-zero entropy, not the intended entropy.
This will lead to token does not exist errors if the paired endpoint initialized in the opposite order. Or worse, it will lead to using all-zero entropy, which gives no security.
The correct solution to this problem is not to make a copy of the entropy. The correct solution is to have separate entropy for each communication direction.
Communication Issues
Unlike basic encryption, the MTE technology will generally fail completely if the decoder receives something other than exactly what was encoded. This could be changed data or truncated data.
A useful technique is to print out the encoded version immediately after encoding and before transmission and printing it again immediately before attempting to decode. If there is any difference, this must be resolved.
If you are using the Managed-Key Encryption Add-On in chunk mode, make sure you are sending all the chunks. Make sure you are sending the chunks in the correct order. Finally, make sure you are sending the results of the finalize function as well, not just the encrypted data.
Use Save State
The MTE save state feature is a very useful debugging feature as well as serving its main purpose of allowing the state to be saved and restored later. The saved state actually contains every bit of information MTE needs to encode or decode a message. As an aside, it is important to stress again that the saved state must be protected as if it was the entropy or an encryption key or other similar piece of critically sensitive data.
The internal state of the encoder and decoder will match when they are in sync and will not match when they are not in sync. If the encoder and decoder are not in sync, the decoder will not recognize the encoded message, resulting in the token does not exist error.
A useful technique with saved state is to put calls to save state before/after certain operations on both the encoder and decoder and compare the saved states at each point. Where the saved state mismatches you will start to have problems, and you can then see why. The key points to check the state are:
- After instantiation. The instantiation call uses the entropy, nonce, and personalization string to create the initial state. A call to save state after the instantiation gives you that initial internal state. The encoder and decoder must have the same initial state. If they do not, the most likely problem is that they did not both get exactly the same entropy, nonce, and/or personalization string.
- Before encode/decode. A call to save state before encode or decode shows the current internal state that will be used to perform the encode or decode. As with instantiation, the states must be the same, meaning the encoder and decoder are in sync.
There is one caveat to the saved state being identical. If you are using async sequencing and some async messages have been seen, the decoder's saved state includes information about that in the 5th through 12th bytes, and the encoder will have all zeros in those positions. However, all other bytes must be the same.
Use A Debug Decoder
It is sometimes helpful to create a decoder just for debug purposes and simply decode immediately after encoding to make sure that works. To make this work you will have to copy your entropy to avoid the [entropy reuse](#entropy Reuse) issue. Make sure you remove this debug decoder once the problem has been fixed.
If the decode immediately after encode fails, this almost always means there is a status not being properly checked, resulting in an error earlier somewhere that is the actual cause of the problem you are seeing later as token does not exist.