1
0
mirror of https://github.com/chylex/Blog.git synced 2025-09-14 11:32:12 +02:00

Compare commits

..

1 Commits

7 changed files with 32 additions and 156 deletions

View File

@@ -74,12 +74,12 @@
<span class="site-footer-owner">
<a href="https://chylex.com">My Website</a>
&nbsp;&middot;&nbsp;
<a href="https://chylex.bsky.social/">Bluesky</a>
&nbsp;&middot;&nbsp;
<a href="https://mastodon.chylex.com/@chylex">Mastodon</a>
<a href="https://twitter.com/chylexmc">Twitter</a>
&nbsp;&middot;&nbsp;
<a href="https://github.com/chylex">GitHub</a>
&nbsp;&middot;&nbsp;
<a href="https://patreon.com/chylex">Patreon</a>
&nbsp;&middot;&nbsp;
<a href="https://ko-fi.com/chylex">Ko-fi</a>
&nbsp;&middot;&nbsp;
<a href="{{ '/feed.xml' | relative_url }}">RSS</a>

View File

@@ -1,43 +1,39 @@
---
title: "The Ultimate Guide to Securing MySQL / MariaDB + PHP with SSL"
subtitle: "%pub"
date: 2023-04-14
commentid: 4
date: 2023-04-10
#commentid: 1
---
By default, all communication between a MySQL or MariaDB server and its clients is unencrypted. That's fine if both the database server and client are on the same machine, or connected by a network you fully control, but as soon as anything touches the internet — or even an internal data center network the [NSA may have secretly tapped into](https://www.washingtonpost.com/world/national-security/nsa-infiltrates-links-to-yahoo-google-data-centers-worldwide-snowden-documents-say/2013/10/30/e51d661e-4166-11e3-8b74-d89d714ca4dd_story.html) — all bets are off, and you need SSL to ensure that the communication cannot be spied on or tampered with. This post will guide you through the entire process in an abundance of detail.
To be clear, I'm no expert on cryptography, and I certainly wasn't expecting to be writing a whole tutorial on setting up SSL for MySQL / MariaDB + PHP within hours of getting mine to work, but while researching this I found so much outdated or just straight up bad information that I took time to dig deep into every step of the process. While reading this guide, you should never feel you're being told to run a command or change some code without an explanation that justifies it.
To be clear, I'm no expert on cryptography, and I certainly wasn't expecting to be writing a whole tutorial on setting up SSL for MySQL / MariaDB + PHP within hours of getting mine to work, but while researching this I found so much outdated or just straight up bad information that I took time to dig deep into every step of the process. While reading this guide, you should never feel you're being told to run a command or change some code without an explanation that justifies the decision.
# 0. Prerequisites
I will assume that you have a MySQL or MariaDB server, access to its configuration files, and PHP scripts that connect to it. I will also assume you are always using the most recent version of all software involved. I cannot predict any future software updates that invalidate something in this post, so if you run into issues, please let me know in the comments.
Many parts of this guide are based on the official documentation of [MySQL](https://dev.mysql.com/doc/refman/8.0/en/creating-ssl-files-using-openssl.html) and [MariaDB](https://mariadb.com/kb/en/certificate-creation-with-openssl/).
I will assume that you have a MySQL or MariaDB server, access to its configuration files, and PHP scripts that connect to it. I will also assume you are always using the most recent version of all software involved. Unfortunately, I cannot predict any changes that invalidate something in this post, so if you run into any issues in the future, please let me know.
Now, get yourself a Linux machine with `openssl` and let's get started.
# 1. Generating Certificates
Let's establish some terminology. SSL uses "asymmetric cryptography" to establish a secure communication channel. Every *entity* needs a key pair — a private key, and a public key — that each serve two functions:
1. The public key is used to encrypt messages. Only the private key can decrypt those messages.
2. The private key is used to sign messages. The public key can verify those signatures.
This section is largely based on the official documentation of [MySQL](https://dev.mysql.com/doc/refman/8.0/en/creating-ssl-files-using-openssl.html) and [MariaDB](https://mariadb.com/kb/en/certificate-creation-with-openssl/).
The public key will be embedded in a *public key certificate*, which contains additional information about the identity of its owner. The private key and public key certificate will be generated using ~~magic~~ a bunch of math, and stored in separate files.
Let's establish some basic terminology. We will be using "asymmetric cryptography", in which every entity needs a key pair — a private key, and a public key. The private key is used to encrypt or sign messages, and the public key is used on the receiving end to decrypt messages and verify signatures. For SSL, when we generate public keys, they will be immediately turned into public key *certificates* which contain some additional information besides just the public key itself.
From now on, I will simplify the terminology and use "key" to mean the private key, and "certificate" or "cert" to mean the public key certificate. Later we will see this simplified terminology used in both MySQL / MariaDB configuration, and in the PHP code for configuring the database connection.
Every entity will end up with a file containing the *private key*, and a file containing the *public key certificate*. From now on, I will simplify the terminology and use "key" to mean the private key, and "certificate" or "cert" to mean the public key certificate. Later we will see this simplified terminology used in both MySQL / MariaDB configuration, and in the PHP code for configuring the database connection.
Before generating any keys of certificates, we must pick an *algorithm*. Both official documentations use 2048-bit RSA. As far as RSA goes, 2048 bits is the absolute minimum nowadays. It might be a good idea to increase the key size to 3072, 4096, or even more bits for better resilience against progress in (non-quantum) computing performance, but that comes at the expense of spending more time establishing every connection. The world seems to be moving away from RSA and towards elliptic curves, which have several benefits including much smaller keys, so we might as well use the current best thing. Based on recommendations from [Trail of Bits](https://blog.trailofbits.com/2019/07/08/fuck-rsa/) and [Soatok](https://soatok.blog/2022/05/19/guidance-for-choosing-an-elliptic-curve-signature-algorithm-in-2022/), I will use Ed25519.
Before generating anything, we must pick an *algorithm*. Both official documentations use 2048-bit RSA. As far as RSA goes, 2048 bits is the absolute minimum nowadays. It might be a good idea to increase the key size to 3072, 4096, or even more bits for better resilience against progress in (non-quantum) computing performance, but that comes at the expense of spending more time establishing every connection. The world seems to be moving away from RSA and towards elliptic curves, which have much smaller keys and other benefits, so we might as well use the current best thing. Based on the recommendations from [Trail of Bits](https://blog.trailofbits.com/2019/07/08/fuck-rsa/) and [Soatok](https://soatok.blog/2022/05/19/guidance-for-choosing-an-elliptic-curve-signature-algorithm-in-2022/), I will use Ed25519.
## Certificate Authority
First, we roleplay as a "Certificate Authority" (CA), the first *entity* for which we will be generating a key and a certificate. If you hate fun, you can take inspiration from modern "Free-to-play" games and simply pay a *real* Certificate Authority to skip this part.
First, we roleplay as a "Certificate Authority" (CA), the first entity on our list. If you hate fun, you can take inspiration from modern "Free-to-play" games and simply pay a *real* Certificate Authority to skip this part.
The purpose of the CA is to *sign other certificates*, using the (private) CA key. We will then distribute the (public) CA certificate to every computer that either hosts the database server or connects to it. When these *other certificates* are sent and received over an untrusted network, the CA certificate will verify their signature. If the signature is valid, we know that it must have been signed by our CA — which we trust — so the *other certificate* can also be trusted. If the signature is not valid, it means the *other certificate* has been forged or otherwise corrupted, and it is impossible to establish secure and trusted communication with the database server.
We will generate a "CA key" and a "CA certificate" (which, as a reminder, is what I'm calling the private key and public key certificate respectively) out of ~~magic~~ a bunch of math. The purpose of the CA is to *sign* other certificates. We will distribute the CA certificate to every computer that either hosts the database server or connects to it. When these *other certificates* are sent and received over an untrusted network, our trusty CA certificate will verify their signature. If the signature is valid, because we know we can trust the CA, we can also trust these *other certificates*. If the signature is not valid, it means the *other certificate* has been forged or otherwise corrupted, and it is impossible to establish secure and trusted communication to the database server.
It's terminal time. Navigate to a folder where you want your certificates.
This snippet generates a private key using the Ed25519 algorithm, and saves it to a `ca_key.pem` file.
This snippet generates a private key for our Certificate Authority, and saves it to a `ca_key.pem` file.
```bash
openssl genpkey -algorithm ed25519 > ca_key.pem
```
@@ -49,9 +45,9 @@ openssl req -new -x509 -nodes -days 365000 -subj "/C=US/O=organization/OU=organi
Let's go through what this means:
1. `openssl req -new` means we are creating a new "certificate signing request".
2. `-x509` means that the "certificate signing request" will be immediately turned into a self-signed certificate in the X.509 format. Otherwise, it would be saved as a file, and we'd need another command to sign it.
2. `-x509` means that the "certificate signing request" will be immediately turned into a self-signed certificate in the X.509 format.
3. `-nodes` ("no DES") disables encryption of the certificate. If encryption is enabled, you will be asked for a passphrase, and then must provide this passphrase whenever you want to use the certificate. Encrypting certificates provides more security at the expense of convenience, but I cannot find any evidence that either MySQL or MariaDB supports encrypted certificates, so we don't have a choice here.
4. `-days 365000` sets the expiry time to roughly 1000 years, which is fairly optimistic considering how humanity is going. You can use shorter expiry times, but you need to think about renewal and this s\*\*t is already complicated enough. Shorter expiry times can help if your private key leaks and you never realize, but recommended expiry times are often in the realm of months or years, which is plenty of time for an attacker to cause harm. Moreover, if you do realize your private key leaked, you can regenerate all keys and certificates and swap them out, which should be relatively easy since they only reside on computers you fully control.
4. `-days 365000` sets the expiry time to roughly 1000 years, which is fairly optimistic considering how humanity is going. You can use shorter expiry times, but you need to think about renewal and this s\*\*t is already complicated enough. Shorter expiry times can help if your private key leaks and you never realize, but recommended expiry times are often in the realm of months or years, which is plenty of time for an attacker to cause harm. Moreover, if you do realize your private key leaked, you can simply regenerate all keys and certificates and swap them out easily — since you have full control over your PHP scripts.
5. `-subj ...` is the spicy stuff. It is a slash-delimited sequence of key-value pairs of various fields embedded in the certificate. Most are optional, but useful for documenting where the certificate came from. There are standards for these, but since you are the only one using these certificates, for the unimportant fields just type in whatever doesn't crash `openssl`.
- `C` is a two-letter country code. The lesser-known your country is, the more I recommend setting it.
- `O` is the organization name. Despite my lack of organization, I set it to my nickname.
@@ -66,16 +62,16 @@ We end up with a private key `ca_key.pem` that we need to keep secret, and a pub
## Server Certificates
Let's now generate a key and a certificate for the database server. When a client connects to the database server, the server will automatically send its certificate to the client. After the client verifies that the server certificate was signed by the CA certificate, it will use the server certificate to encrypt some secret information which only the server can decrypt. This secret information is used to establish a new, symmetrically encrypted communication channel. You might be asking why we are establishing a new encrypted channel when we already have one, and there are two reasons:
Let's now do the same thing, but this time generate a key and a certificate for the database server. When a client connects to the database server, the server will automatically send its certificate to the client. After the client verifies the server certificate against the CA certificate, it will use the server certificate to encrypt some secret information, which only the server can decrypt. This secret information is used to establish a new, symmetrically encrypted communication channel. You might be asking why we are establishing a new encrypted channel when we already have one, and there are two reasons:
1. The client may not have its own key and certificate, in which case the server has no way to encrypt messages in a way that only the client can decrypt.
2. Asymmetric encryption needs a lot more computing resources than symmetric encryption.
Now that we've finished our educational side-quest, let's generate a key and a *certificate signing request*.
Now that we've finished our educational side-quest, let's generate a key and a *certificate request*.
```bash
openssl req -newkey ed25519 -nodes -subj "/C=US/O=organization/OU=organizational_unit/CN=server_hostname" -keyout "server_key.pem" -out "server_req.pem"
```
Most of these are the same as before. The differences are:
Most of these arguments are the same as before. The differences are:
1. `-newkey` is like `-new`, but it also generates a private key at the same time as the certificate signing request. Note that this time there is no `-x509`, so we do actually create a certificate signing request that we will sign later.
2. `ed25519` uses the Ed25519 algorithm for the private key.
3. `-subj ...` is largely the same, **but!**
@@ -99,13 +95,13 @@ openssl x509 -req -days 365000 -set_serial 1 -CA ca_cert.pem -CAkey ca_key.pem -
New thingies to learn!
1. `openssl x509` begins a command related to X.509 certificates. You may be wondering — we already did a bunch of things with X.509 certificates, why have we not seen this command yet? Anyway,
2. `-req` means we are turning a certificate signing request into a certificate. Very different from `req`.
2. `-req` means we are turning a certificate request into a certificate. Very different from `req`.
3. `-set_serial` specifies a serial number for the certificate. This number should be **unique for our CA**. This server certificate uses serial number `1`, the next certificate we make with the same CA will use serial number `2`, etc.
4. `-CA` is a path to the CA certificate.
5. `-CAkey` is a path to the CA key, and also the fourth f\*\*\*ing way to capitalize arguments in the `openssl` command.
6. `-in` is the file name of the certificate signing request.
7. `-extfile` is a path to a file containing X.509 v3 extensions. We use the default OpenSSL configuration file.
8. `-extensions usr_cert` then uses a particular set of X.509 v3 extensions that the default configuration file uses for non-CA certificates.
8. `-extensions usr_cert` then uses a particular set of X.509 v3 extensions that the default configuration file intends to be used when a CA signs a certificate request.
The last two arguments, `-extfile` and `-extensions`, are what's needed to generate a version 3 certificate. Omitting them would generate a version 1 certificate, which works fine if your database server and client use OpenSSL, but may not work with a different SSL library.
@@ -118,11 +114,11 @@ rm "server_req.pem"
## Client Certificates
Client certificates are optional. We could not use them at all, or use the same key and certificate everywhere, or generate a new key and certificate for each client (PHP application).
Client certificates are optional. We can not use them at all, use the same key and certificate everywhere, or generate a new key and certificate for each client (PHP application).
The process for generating a client key and certificate is the same as for the server, we just need to change a few parts:
1. In file names, substitute `server_` for `client_` or `client_1_` or whatever else makes sense to you.
2. Increase the serial number. The server certificate used `1`, so the first client certificate will use `2`.
1. Change file names, substituting `server_` for `client_` (for example).
2. Increase the serial number to `2`.
3. While you technically don't need to use a different `CN` than the one in the server certificate — as in, it will not throw errors — if someone steals your client key, they will be able to impersonate your database server by using the client key and certificate in place of the server key and certificate. By changing the `CN` to something different from your server hostname, attempting to impersonate your database server would fail, because as explained earlier, the clients check that the server certificate's `CN` field matches the server hostname.
Here, have a convenient snippet with all three commands. Don't forget to change the `-subj`.
@@ -136,7 +132,7 @@ rm "client_req.pem"
## Verify Certificates
This command will verify that one or more certificates have been signed by the CA. The `-CAfile` argument is the same as `-CA` in the previous commands, because Big Documentation needs you to never remember how commands work. Every argument after that is a path to a certificate file that we want to verify.
As long as you used the same file naming convention, this command will verify that the certificates are valid. The `-CAfile` argument is the same as `-CA` in the previous command, because Big Documentation needs you to never remember how commands work.
```bash
openssl verify -CAfile ca_cert.pem server_cert.pem client_cert.pem
```
@@ -200,7 +196,7 @@ MySQL also warns us when the CA certificate is self-signed:
Wait, `ca.pem` is not what we named our CA certificate file! It turns out that MySQL generates its own self-signed certificates if we don't configure your own. MySQL also silently ignores files in its configuration folders if it doesn't like them, for example files whose extension is not `.cnf`. If you see log files referring to `ca.pem` instead of the path to `ca_cert.pem` you configured, it means your configuration was not read.
On the other hand, MariaDB seems to only log errors when it comes to SSL configuration. We will see some if, for example, we are using Docker Compose, and we forgot that `docker compose restart` does not mount newly specified volumes, so the database server could not find any of the key and certificate files in the new `/certs` volume. Just a hypothetical.
On the other hand, MariaDB seems to only log errors when it comes to SSL configuration. We will see some if, for example, we are using Docker Compose, and we forgot that `docker compose restart` does not mount newly specified volumes, so the database server could not find any of the key and certificate files in `/certs`. Just a hypothetical.
At this point, it's *possible* to connect with SSL, but it's not *required*. The good news is that's exactly what we (I) wanted, because SSL requirements can be set individually per database user, so we can set up a test user to make sure this all works. Or we could appropriate an existing user used by a service nobody will notice temporarily disappearing because something was misconfigured. Again, just a hypothetical.
@@ -303,7 +299,7 @@ foreach ($db->query('SHOW STATUS LIKE \'Ssl_%\'', PDO::FETCH_ASSOC)->fetchAll()
For most people, just getting SSL to work at all is enough. However, if we wanted to strengthen security even more, we could use a client key and certificate.
Similarly to how the client can verify the server's identity using the server certificate, the server is able to verify the client's identity using the client certificate. Assuming we have a client key and certificate, we will need to put the `client_key.pem` and `client_cert.pem` files somewhere our PHP script can access them — for example, into the `secrets` folder, next to the `ca_cert.pem` file. The rest is easy — we just add a few additional arguments or options in the functions we are already calling.
Similarly to how the client can verify the server's identity using the server certificate, the server is able to verify the client's identity using the client certificate. Assuming our client key and certificate has been generated, we will need to put the `client_key.pem` and `client_cert.pem` files somewhere our PHP script can access them — for example, into the `secrets` folder, next to the `ca_cert.pem` file. The rest is easy — we just add a few additional parameters or options in the functions we are already calling.
### MySQLi
@@ -343,22 +339,22 @@ $db = new PDO('mysql:host='.DB_HOSTNAME.';dbname='.DB_DATABASE.';charset=utf8mb4
With SSL working, we should require a user on the database server to use SSL for every connection.
```sql
ALTER USER 'username'@'%' REQUIRE SSL
ALTER USER 'my_user'@'%' REQUIRE SSL
```
The next, optional step could be to require the database user to connect with any valid client certificate.
```sql
ALTER USER 'username'@'%' REQUIRE X509
ALTER USER 'my_user'@'%' REQUIRE X509
```
We could require client certificates to have a specific "subject" (contents of the `-subj` argument earlier). If we took the earlier example command for generating a client certificate literally, the subject of the certificate would be `/C=US/O=organization/OU=organizational_unit/CN=client_name` and the SQL command to require it would look like this:
```sql
ALTER USER 'username'@'%' REQUIRE SUBJECT '/C=US/O=organization/OU=organizational_unit/CN=client_name'
ALTER USER 'my_user'@'%' REQUIRE SUBJECT '/C=US/O=organization/OU=organizational_unit/CN=client_name'
```
Finally, we could not only require a specific "subject" for the client certificate, but also for the CA certificate that signed it. Again, using the earlier example literally, if the subject of the CA certificate is `/C=US/O=organization/OU=organizational_unit/CN=common_name` then the SQL command will look like this:
```sql
ALTER USER 'username'@'%' REQUIRE SUBJECT '/C=US/O=organization/OU=organizational_unit/CN=client_name' AND ISSUER '/C=US/O=organization/OU=organizational_unit/CN=common_name'
ALTER USER 'my_user'@'%' REQUIRE SUBJECT '/C=US/O=organization/OU=organizational_unit/CN=client_name' AND ISSUER '/C=US/O=organization/OU=organizational_unit/CN=common_name'
```
Actually, I lied — there's even more possibilities. We could, for example, require the client to use a specific cipher. [MariaDB documentation](https://mariadb.com/kb/en/securing-connections-for-client-and-server/#requiring-tls-for-specific-user-accounts-from-specific-hosts) covers this. However, the defaults — especially with TLSv1.3 — should be fine.
@@ -397,6 +393,4 @@ Note that I did not measure the performance impact of encrypting and decrypting
# 5. Wow, Security
I hope this has been helpful. If you noticed any problems with the post, don't hesitate to post a comment.
If this post saved you some frustration (although probably not time, considering its length), you can share it and/or support me on [Ko-fi](https://ko-fi.com/chylex).
I hope this has been helpful. If you noticed any problems with the post, don't hesitate to post a comment. If this post saved you some frustration (although probably not time, considering its length), you can share it and/or support me on [Ko-fi](https://ko-fi.com/chylex).

View File

@@ -1,118 +0,0 @@
---
title: "JetBrains Writerside EAP Expiration Bypass"
subtitle: "%pub"
date: 2025-04-22
commentid: 5
---
JetBrains has [discontinued the Writerside IDE](https://blog.jetbrains.com/writerside/2025/03/sunsetting-writerside-ide/) in favor of the Writerside plugin. Since the IDE has always been in the Early Access Program (EAP), its builds expire after 30 days, meaning they cant be used indefinitely. Personally, I prefer a separate IDE for writing documentation, and the plugin has some UX annoyances I don't want to deal with, so let's bypass the EAP expiration!
Im using Writerside 2024.3 EAP (243.22562.371), but the guide should give you enough information to adapt to changes in other versions.
# Finding the Expiration Check
![Dialog shown when opening an expired build of Writerside EAP]({{ '/assets/img/jetbrains-writerside-eap-expiration-bypass/expiration-dialog.png' | relative_url }})
Writerside, like other JetBrains IDEs, uses Java Swing. That makes it easy to find the code used to show a modal dialog — such as the one warning you that the EAP build expired. Every modal dialog spawns an event loop, which will appear in the stack trace.
Once Writerside is running and the "Writerside EAP Build Expired" dialog is visible, find the process ID (PID) and run `jstack <pid>` to dump the stack trace. You can use `jstack` from Writerside's installation directory:
```cmd
%LOCALAPPDATA%\Programs\Writerside\jbr\bin\jstack.exe 12345
```
Look for the `AWT-EventQueue-0` thread. I highlighted the important parts of the stack trace with arrows.
```stacktrace
"AWT-EventQueue-0" #72 [21032] prio=6 os_prio=0 cpu=1031.25ms elapsed=323.16s tid=0x0000019c1a2f3c40 nid=21032 waiting on condition [0x0000009afb8fc000]
java.lang.Thread.State: WAITING (parking)
(unimportant lines)
at java.awt.EventQueue.getNextEvent(java.desktop/EventQueue.java:573)
at com.intellij.ide.IdeEventQueue.getNextEvent(IdeEventQueue.kt:466)
at java.awt.EventDispatchThread.pumpOneEventForFilters(java.desktop/EventDispatchThread.java:194)
at java.awt.EventDispatchThread.pumpEventsForFilter(java.desktop/EventDispatchThread.java:128)
at java.awt.EventDispatchThread.pumpEventsForFilter(java.desktop/EventDispatchThread.java:121)
at java.awt.WaitDispatchSupport$2.run(java.desktop/WaitDispatchSupport.java:191)
at java.awt.WaitDispatchSupport$4.run(java.desktop/WaitDispatchSupport.java:236)
at java.awt.WaitDispatchSupport$4.run(java.desktop/WaitDispatchSupport.java:234)
at java.security.AccessController.executePrivileged(java.base@21.0.5/AccessController.java:778)
at java.security.AccessController.doPrivileged(java.base@21.0.5/AccessController.java:319)
at java.awt.WaitDispatchSupport.enter(java.desktop/WaitDispatchSupport.java:234)
--> at java.awt.Dialog.show(java.desktop/Dialog.java:1079)
at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl$MyDialog.show(DialogWrapperPeerImpl.java:890)
at com.intellij.openapi.ui.impl.DialogWrapperPeerImpl.show(DialogWrapperPeerImpl.java:472)
at com.intellij.openapi.ui.DialogWrapper.doShow(DialogWrapper.java:1772)
at com.intellij.openapi.ui.DialogWrapper.show(DialogWrapper.java:1721)
at com.intellij.ui.messages.AlertMessagesManager.showMessageDialog(AlertMessagesManager.kt:70)
at com.intellij.ui.messages.MessagesServiceImpl.showMessageDialog(MessagesServiceImpl.java:54)
at com.intellij.openapi.ui.Messages.showDialog(Messages.java:274)
at com.intellij.openapi.ui.Messages.showDialog(Messages.java:290)
at com.intellij.openapi.ui.Messages.showDialog(Messages.java:305)
--> at com.intellij.ide.V.a.VP.p(VP.java:320)
--> at com.intellij.ide.V.a.VP.H(VP.java:308)
at com.intellij.ide.V.a.VP$$Lambda/0x0000019ba1c3ab78.run(Unknown Source)
(unimportant lines)
```
The top of the stack trace is processing an event queue spawned by `java.awt.Dialog.show`. A few lines below is the main point of interest — two methods related to the expiration check, that made the dialog appear: `com.intellij.ide.V.a.VP.p` and `com.intellij.ide.V.a.VP.H`.
# Patching the Expiration Check
To remove the expiration check, I will use [Recaf](https://github.com/Col-E/Recaf) 2.21.4.
To save you some time, the `VP` class is found inside `Writerside/lib/product.jar`. On Windows, this would be in `%LOCALAPPDATA%\Programs\Writerside\lib\`.
Open `product.jar` in Recaf and search for `com/intellij/ide/V/a/VP`.
![Screenshot of Recaf with the searched class]({{ '/assets/img/jetbrains-writerside-eap-expiration-bypass/recaf-class.png' | relative_url }})
Opening the class will attempt to decompile it. Due to heavy obfuscation, the decompiled code or line numbers from the stack trace are mostly useless, so right-click the `VP` tab and switch to the Table view.
The `p` method that calls `Messages.showDialog` can be difficult to find since its name is shared with several other methods, but the `H` method is unique.
![Screenshot of Recaf with the searched method]({{ '/assets/img/jetbrains-writerside-eap-expiration-bypass/recaf-method.png' | relative_url }})
Right-click the `H` method, select "Edit with assembler", and replace everything after the first line with `RETURN`. Press `Ctrl+S` to save, and close the editor.
```
DEFINE PRIVATE SYNTHETIC H()V
RETURN
```
> In Recaf 4, you can use the "Edit - Make no-op" option in the context menu instead.
Use "File - Export program" to export the patched `product.jar`, and replace the original file. You should now be able to use Writerside forever!
# Addendum: Fixing the Git Tool Window
The 243.22562.371 build of Writerside has an annoying bug, where the Git tool window doesn't show a diff preview depending on its location. This was caused by [commit c456622](https://github.com/JetBrains/intellij-community/commit/c4566222c3c4ca2bb08080ae3d476d65331a8ec4), and is also easy to fix using Recaf.
The buggy code is in `Writerside/lib/modules/intellij.platform.vcs.impl.jar`, in these two classes:
- `com/intellij/openapi/vcs/changes/ChangesViewManager$ChangesViewToolWindowPanel`
- `com/intellij/openapi/vcs/changes/shelf/ShelvedChangesViewManager$ShelfToolWindowPanel`
Both classes have an `updatePanelLayout` method that calls `ChangesViewContentManager.isToolWindowTabVertical`, stores the result in a local variable, and hides the preview diff if the variable is `true`.
```java
boolean isVertical = isToolWindowTabVertical(myProject, LOCAL_CHANGES);
boolean hasSplitterPreview = !isVertical;
```
Edit each `updatePanelLayout` method with assembler and search for the `isToolWindowTabVertical` call. The surrounding bytecode instructions are almost the same in both classes.
```
ALOAD this
GETFIELD com/intellij/openapi/vcs/changes/ChangesViewManager$ChangesViewToolWindowPanel.myProject ...
LDC "Local Changes"
INVOKESTATIC com/intellij/openapi/vcs/changes/ui/ChangesViewContentManager.isToolWindowTabVertical(...)Z
ISTORE isVertical
```
Replace these instructions — except for the `ISTORE` instruction — with `ICONST_0`, and save.
```
ICONST_0
ISTORE isVertical
```
This forces the `isVertical` variable to always be `false`, preventing the preview diff from being hidden.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB