JetBrains Platform
Plugin and extension development for JetBrains products.
Guest Post: The Plugin Obfuscation Experience
This is a guest blog post by Tamás Balog, developer of the WireMocha plugin.
Developing and releasing a paid IDE plugin can be rewarding not just from the financial perspective but also because the process gives you better insight into how JetBrains Marketplace functions and how self-billing in this sphere works. You also gain experience with different technologies and techniques that are essential or at least useful for a paid plugin, one of which is obfuscation.
What is obfuscation?
Here’s a quote from the website of GuardSquare, whose ProGuard tool I discuss extensively in this article:
“Code obfuscation is the process of making applications difficult or impossible to decompile or disassemble, and the retrieved application code more difficult for humans to parse.”
Although various obfuscation techniques are available (replacing keywords with meaningless ones, string encryption, etc.), they all have the same purpose. They aim to decrease the possibility of others obtaining your source code, or if they did manage to get their hands on the obfuscated version, to make their efforts to interpret it as difficult as possible.
Why use an obfuscator?
When I entered the freelance realm a couple of months ago, I decided to make a paid plugin that could become one of my sources of income.
I love open-source development, but when it comes to building a business, you have to make efforts to protect your product.
Obfuscation tools
The JetBrains Marketplace documentation provides a list of available tools for obfuscation.
At JetBrains, they use Zelix KlassMaster, so naturally, that was my go-to. Ultimately, there were a few reasons why I decided against using that tool, with the main one being its price. As a new small business owner, I didn’t want to spend so much money on something I wasn’t sure I could properly make use of or quickly figure out the inner workings of.
So, I wanted to test out a free tool instead, and since I had no previous experience with obfuscation, I decided to try ProGuard, which was listed in the Marketplace documentation.
It is an open-source tool with Gradle integration and well-written documentation that offers examples for various use cases and application types, which I definitely appreciate. So after playing around with it for a bit, I was like: “yeah, let’s stick with this”.
Challenges
In the next few sections, I’d like to describe what you can expect when working with code obfuscation, without delving too much into implementation details.
Plugin extension obfuscation
Plugins extend IDEs by providing extensions, which are implementations of extension points exposed by the platform or other plugins. Extensions are defined in the plugin.xml and other plugin descriptor files in the <extensions>...</extensions>
element and are referenced by fully qualified class names. It is highly recommended to obfuscate extension classes, as keeping them intact may reveal their meaning and make reverse engineering the plugin much easier for crackers.
Actions and similar components (except custom extension points) fall into the same category as extensions, and it is recommended to obfuscate them too.
In addition to obfuscating extension classes, we need to adjust their names in the plugin descriptor files. Fortunately, ProGuard makes achieving that very simple with the –adaptresourcefilecontents
option, for example,-adaptresourcefilecontents META-INF/plugin.xml
If this option is provided, ProGuard will replace class names in plugin.xml with the obfuscated ones. Make sure that you provide all the files that reference the class names of your extensions. For more details see Processing resource files in the ProGuard documentation.
Custom extension points as entry points
There are certain parts of a code base, so-called entry points, that have to be left unobfuscated (kept in their original forms), so that wherever your application code is accessed from, whether that’s another application or platform, it can actually be accessed and executed.
These entry points can come in many forms depending on the application type, for instance, the public static void main(String[] args)
method for common Java applications. For IntelliJ-based IDE plugins, these are implementations of extension points, which are implemented by other plugins to extend your plugin’s functionality.
As these classes are actually your plugin’s public API, their names and all public members should be kept unobfuscated. This can be achieved with -keep and related ProGuard options.
To find these extension point classes, you need to look at what classes are registered in your plugin.xml and related files in the <extensionPoints>...</extensionPoints>
element.
In general, almost all other parts of your plugin can be obfuscated and hidden away from curious eyes.
Resource files
File templates
In my plugin, I have code and file templates that I retrieve programmatically by their names. Unfortunately, the platform couldn’t see those files. They were not in the full list of templates when I queried them.
I was not sure whether I specified the template names properly during retrieval (whether to include the .java.ft extension, just .java, or neither), or whether something was missing in the obfuscation configuration.
It turned out that ProGuard removes directory entries from the output JAR file by default, though these directories are still physically in the JAR file. Thus, when the directory resources called fileTemplates were queried by the underlying ClassLoader
, the one in my plugin wasn’t found. Adding the -keepdirectories
option to the ProGuard configuration resolved this issue.
Inspection and intention descriptions
By default, inspection description HTML files have the short names of their implementation classes, while intention description HTML files are located in directories with names that are identical to those of their implementation classes.
If left as they are, they would reveal class names, and the platform would still try to access them using their obfuscated names, so it is necessary to alter and hide them:
- Inspection description files may be removed entirely, and instead, their contents can be provided as static descriptions via their implementation classes (the
getStaticDescription()
method). - Directories of intention descriptions can be renamed and their names specified in the plugin.xml file within the
<intentionAction>...</intentionAction>
element.
Debugging
Unfortunately, debugging obfuscated code is either impossible or extremely difficult, so I typically used a not-obfuscated plugin archive to debug any issues. When working with file templates, I instead had to add logging instructions into my code to retrieve some information on the state of the plugin and the platform.
But, even if you execute the obfuscated version of your plugin, it is still possible to debug classes in the IntelliJ Platform’s code base.
Plugin Verifier
The IntelliJ Plugin Verifier is great at ensuring the quality and the compatibility of your plugin, but you need to be careful how you configure ProGuard, otherwise, it may result in compatibility problems.
Since my plugin is based on the IntelliJ Platform Plugin Template, I experienced some of these problems during the first GitHub Actions CI execution. The Plugin Verifier report showed several compatibility problems, which, based on the output messages, seemed to be due to code obfuscation and the replaced identifiers in the code.
After some investigation, I found out that replaced identifiers were not actually the cause of these issues.
Originally, I wanted to include as many options as possible in my ProGuard configuration, so that it would become greatly optimized, smaller, and as uncrackable as possible. But as it turned out, the -mergeinterfacesaggressively
config option makes changes that cause certain parts of the output code to be identified as compatibility problems, regardless of whether they actually affected how your plugin functions. So, I’d recommend not including this option in your configuration.
Stack traces and keyword mapping
Keyword replacement during obfuscation means that your exception stack traces will contain those keywords too, making them difficult to read and comprehend.
To be able to make sense of them, there are a couple of things you need to do:
- Configure ProGuard to save the mapping file that contains what each keyword was replaced with.
- Make this file available as a downloadable artifact in your CI build, so that you have access to it anytime, in case you need it.
It is worth testing this out with some dummy exceptions beforehand to see an example of the kind of information your customers might forward you about an exception thrown by your plugin.
Testing
Since I was about to release an obfuscated plugin archive and the very first version of my plugin, I wanted to make sure that none of the features were broken due to misconfiguration, so I did “exhaustive” manual testing of the obfuscated version to make sure everything was in place and worked as expected.
I admit that this can be tedious, but I found it extremely useful because it helped me:
- Discover ProGuard configuration issues.
- Find problems with the plugin’s functionality.
- Simplify my code base and increase test coverage.
- Improve the plugin’s user experience in some cases.
Testing is particularly useful because there are simply so many areas where one may run into issues involving obfuscation – serialization, custom extension points, custom UI forms, potentially, and areas I don’t even know about.
Closing thoughts
That was how I managed to make my plugin harder to crack with obfuscation. If you are interested in a more detailed explanation that includes the ProGuard configuration and code snippets, I have a separate article, Obfuscating plugins with ProGuard.
It’s also worth mentioning that even if your plugin doesn’t need obfuscation, ProGuard can perform shrinking and optimization. This means it can be a good addition to free plugins as well, helping you reduce their size and increase their performance.
If you have an idea for a blog post that will be helpful and informative for plugin developers, we will be more than happy to work with you to publish it. Learn more about submitting a guest post in the Marketplace documentation.