mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-27 16:57:49 +08:00
plugin_signing.md: cleanup, rework, add Gradle 2.x reference
This commit is contained in:
parent
07d89986fa
commit
fac4cd7c4d
@ -1,12 +1,13 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<!-- Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
|
||||
# Plugin Signing
|
||||
|
||||
<link-summary>Plugin signing ensures that end users get unmodified plugin distribution that was actually published to JetBrains Marketplace.</link-summary>
|
||||
<link-summary>Plugin signing ensures that end users install an unmodified plugin distribution.</link-summary>
|
||||
|
||||
Plugin Signing is a mechanism introduced in the 2021.2 release cycle to increase security in [JetBrains Marketplace](https://plugins.jetbrains.com) and all of our IntelliJ-based IDEs.
|
||||
Plugin Signing is a mechanism introduced in the 2021.2 release cycle to increase security on the [JetBrains Marketplace](https://plugins.jetbrains.com)
|
||||
and all IntelliJ-based IDEs.
|
||||
|
||||
The Marketplace signing is designed to ensure that plugins are not modified over the course of the publishing and delivery pipeline.
|
||||
The JetBrains Marketplace signing process is designed to ensure that plugins are not modified over the course of the publishing and delivery pipeline.
|
||||
If the author does not sign the plugin or has a revoked certificate, a warning dialog will appear in the IDE during installation.
|
||||
|
||||
On our side, we will check if the public part of a key is present, and we will verify the signature.
|
||||
@ -16,37 +17,48 @@ This is similar to the [Google upload key](https://developer.android.com/studio/
|
||||
|
||||
To be sure a file has not been modified, the file will be signed twice – first by the plugin author, then by JetBrains Marketplace.
|
||||
|
||||
The plugin author's sign-verify process is as follows:
|
||||
<procedure title="Signing and Verification Process">
|
||||
|
||||
- A plugin author generates a key pair ~~and uploads the public part to JetBrains Marketplace~~ (this feature is not yet available).
|
||||
- A build tool signs a plugin file during the assembly process.
|
||||
- The user uploads the plugin file to JetBrains Marketplace.
|
||||
- JetBrains Marketplace checks if the public key is present in the user profile.
|
||||
- The plugin author generates a key pair ~~and uploads the public part to JetBrains Marketplace~~ (not available yet).
|
||||
- A build tool signs the plugin distribution file during the assembly process.
|
||||
- The user uploads the plugin distribution file to the [JetBrains Marketplace](https://plugins.jetbrains.com).
|
||||
- JetBrains Marketplace checks if the public key is present in the user's profile.
|
||||
- JetBrains Marketplace verifies the signature.
|
||||
- The JetBrains sign-verify process is as follows:
|
||||
- JetBrains CA is used as the source of truth here.
|
||||
- Its public part will be added to the IDE Java TrustStore, while the private part will be used only once to generate an intermediate certificate.
|
||||
- The private key of JetBrains CA is super-secret; in fact, we've already said too much.
|
||||
- The JetBrains signing and verification process is as follows:
|
||||
- JetBrains CA is used as the source of truth.
|
||||
- Its public part will be added to the IDE's Java TrustStore, while the private part is used only once to generate an intermediate certificate.
|
||||
- The private key of the JetBrains CA is supersecret; in fact, we've already said too much.
|
||||
|
||||
</procedure>
|
||||
|
||||
The intermediate certificate issues a certificate that will be used to sign plugins.
|
||||
This way, it will be possible to re-generate this certificate without access to JetBrains CA's super-secret private key.
|
||||
This way, it will be possible to re-generate this certificate without access to JetBrains CA's supersecret private key.
|
||||
The private key of the intermediate certificate is issued and kept in the AWS Certificate Manager, and no application has access to it; people's access is also limited.
|
||||
So now we have an AWS-based Intermediate CA.
|
||||
The public part of the intermediate certificate will be added to the plugin file together with the signing certificate.
|
||||
The public part of the intermediate certificate will be added to the plugin distribution file together with the signing certificate.
|
||||
|
||||
The certificate used to sign plugins is stored securely, too.
|
||||
JetBrains Marketplace uses AWS KMS as a signature provider to sign plugin files.
|
||||
JetBrains Marketplace uses AWS KMS as a signature provider to sign plugin distribution files.
|
||||
|
||||
## Signing Methods
|
||||
|
||||
To provide a suitable method for plugin signing, we have introduced the [Marketplace ZIP Signer](https://github.com/JetBrains/marketplace-zip-signer) library.
|
||||
It can be executed using the [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task provided by the [](tools_gradle_intellij_plugin.md) if your project is Gradle-based.
|
||||
Alternatively, a standalone [CLI Tool](#cli-tool) can be used.
|
||||
For signing, the [Marketplace ZIP Signer](https://github.com/JetBrains/marketplace-zip-signer) library is used.
|
||||
For Gradle-based projects, it can be used conveniently using the provided tasks via [](#gradle-integration).
|
||||
Alternatively, a standalone [](#cli-tool) can be used.
|
||||
|
||||
Both methods require a private certificate key to be already present.
|
||||
Both methods require a private certificate key to be present.
|
||||
|
||||
### Generate Private Key
|
||||
|
||||
> Never commit your credentials to the Version Control System!
|
||||
> Instead, use environment variables, like:
|
||||
> ```
|
||||
> token.set(providers.environmentVariable("PUBLISH_TOKEN"))
|
||||
> password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
|
||||
> ```
|
||||
>
|
||||
{style="warning" title="Storing Secrets"}
|
||||
|
||||
To generate an RSA <path>private.pem</path> private key, run the `openssl genpkey` command in the terminal, as below:
|
||||
|
||||
```bash
|
||||
@ -65,8 +77,11 @@ openssl rsa\
|
||||
-out private.pem
|
||||
````
|
||||
|
||||
At this point, the generated <path>private.pem</path> content should be provided to the [`signPlugin.privateKey`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey) property.
|
||||
Provided password should be specified as the [`signPlugin.password`](tools_gradle_intellij_plugin.md#tasks-signplugin-password) property in the [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) configuration.
|
||||
At this point, the generated <path>private.pem</path> content should be provided to the
|
||||
`signPlugin.privateKey` property
|
||||
([2.x](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-privateKey), [1.x](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey)).
|
||||
The provided password should be specified as the `signPlugin.password` property
|
||||
([2.x](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-password), [1.x](tools_gradle_intellij_plugin.md#tasks-signplugin-password)).
|
||||
|
||||
As a next step, we will generate a <path>chain.crt</path> certificate chain with:
|
||||
|
||||
@ -79,16 +94,39 @@ openssl req\
|
||||
-out chain.crt
|
||||
```
|
||||
|
||||
The content of the <path>chain.crt</path> file will be used for the [`signPlugin.certificateChain`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain) property.
|
||||
The content of the <path>chain.crt</path> file will be used for the `signPlugin.certificateChain` property
|
||||
([2.x](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-certificateChain), [1.x](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain)).
|
||||
|
||||
> Information about generating a public key based on the private key will be added later, when uploading public keys to JetBrains Marketplace is available.
|
||||
> Information about generating a public key based on the private key will be added later when uploading public keys to JetBrains Marketplace becomes available.
|
||||
|
||||
### Gradle IntelliJ Plugin
|
||||
### Gradle Integration
|
||||
|
||||
In version `1.x`, the Gradle IntelliJ Plugin provides the [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task, which will be executed automatically right before the [`publishPlugin`](tools_gradle_intellij_plugin.md#tasks-publishplugin) task when [`signPlugin.certificateChain`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain) and [`signPlugin.privateKey`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey) signing properties are specified.
|
||||
#### IntelliJ Platform Gradle Plugin (2.x)
|
||||
|
||||
The [](tools_intellij_platform_gradle_plugin.md) provides the [`signPlugin`](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin) task,
|
||||
which will be executed automatically right before the [`publishPlugin`](tools_intellij_platform_gradle_plugin_tasks.md#publishPlugin) task
|
||||
when [`signPlugin.certificateChain`](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-certificateChain) and [`signPlugin.privateKey`](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-privateKey) properties are specified.
|
||||
Otherwise, it'll be skipped.
|
||||
|
||||
An example [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task configuration may look like:
|
||||
See [`intellijPlatform.signing`](tools_intellij_platform_gradle_plugin_extension.md#intellijPlatform-signing) for configuration reference.
|
||||
|
||||
##### Configuration using Files
|
||||
|
||||
Instead of using the `signPlugin.certificateChain` and `signPlugin.privateKey` properties which expect the certificate chain and key to be provided directly,
|
||||
it's also possible to specify the paths to the files containing the certificate chain and key content.
|
||||
Use [`signPlugin.certificateChainFile`](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-certificateChainFile) and [`signPlugin.privateKeyFile`](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin-privateKeyFile) properties instead.
|
||||
|
||||
|
||||
#### Gradle IntelliJ Plugin (1.x)
|
||||
{collapsible="true" default-state="collapsed"}
|
||||
|
||||
<primary-label ref="Obsolete"/>
|
||||
|
||||
The [](tools_gradle_intellij_plugin.md) provides the [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task, which will be executed automatically right before the [`publishPlugin`](tools_gradle_intellij_plugin.md#tasks-publishplugin) task
|
||||
when [`signPlugin.certificateChain`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain) and [`signPlugin.privateKey`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey) properties are specified.
|
||||
Otherwise, it'll be skipped.
|
||||
|
||||
An example [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task configuration may look like this:
|
||||
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
@ -151,15 +189,10 @@ publishPlugin {
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
> Do not commit your credentials to the Version Control System! To avoid that, you may use environment variables, like:
|
||||
> ```
|
||||
> token.set(providers.environmentVariable("PUBLISH_TOKEN"))
|
||||
> password.set(providers.environmentVariable("PRIVATE_KEY_PASSWORD"))
|
||||
> ```
|
||||
>
|
||||
{style="warning"}
|
||||
##### Using Files
|
||||
|
||||
Instead of using the [`signPlugin.privateKey`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey) and [`signPlugin.certificateChain`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain) properties which expect the key and certificate chain content to be provided directly, it's also possible to specify the paths to the files containing the key and certificate chain content.
|
||||
Instead of using the [`signPlugin.privateKey`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey) and [`signPlugin.certificateChain`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain) properties which expect the key and certificate chain
|
||||
content to be provided directly, it's also possible to specify the paths to the files containing the key and certificate chain content.
|
||||
To do that, use the [`signPlugin.privateKeyFile`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekeyfile) and [`signPlugin.certificateChainFile`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechainfile) properties instead.
|
||||
|
||||
<tabs group="languages">
|
||||
@ -199,7 +232,9 @@ publishPlugin {
|
||||
|
||||
To avoid storing hard-coded values in the project configuration, the most suitable method for local development would be using environment variables provided within the _Run/Debug Configuration_.
|
||||
|
||||
To specify secrets like `PUBLISH_TOKEN` and values required for the [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task, modify your Gradle configuration as follows:
|
||||
To specify secrets like `PUBLISH_TOKEN` and values required for the
|
||||
`signPlugin` Gradle task ([2.x](tools_intellij_platform_gradle_plugin_tasks.md#signPlugin), [1.x](tools_gradle_intellij_plugin.md#tasks-signplugin)),
|
||||
modify your Gradle configuration as follows:
|
||||
|
||||
<tabs group="languages">
|
||||
<tab title="Kotlin" group-key="kotlin">
|
||||
@ -234,20 +269,22 @@ publishPlugin {
|
||||
</tab>
|
||||
</tabs>
|
||||
|
||||
In the <control>Run/Debug Configuration</control> for [`publishPlugin`](tools_gradle_intellij_plugin.md#tasks-publishplugin) Gradle task, provide <control>Environment Variables</control> using relevant environment variable names:
|
||||
In the <control>Run/Debug Configuration</control> for `publishPlugin` Gradle task
|
||||
([2.x](tools_intellij_platform_gradle_plugin_tasks.md#publishPlugin), [1.x](tools_gradle_intellij_plugin.md#tasks-publishplugin)),
|
||||
provide <control>Environment Variables</control> using relevant environment variable names:
|
||||
|
||||

|
||||
|
||||
> Note that both the private key and certificate chain are multi-line values.
|
||||
> It is necessary to transform them first using Base64 encoding before providing the single-line field in the <control>Environment Variables</control> panel.
|
||||
>
|
||||
> [`signPlugin.privateKey`](tools_gradle_intellij_plugin.md#tasks-signplugin-privatekey) and [`signPlugin.certificateChain`](tools_gradle_intellij_plugin.md#tasks-signplugin-certificatechain) properties will automatically detect and decode the Base64-encoded values.
|
||||
> The corresponding Gradle task properties will automatically detect and decode the Base64-encoded values.
|
||||
>
|
||||
> {style="warning"}
|
||||
{style="warning" title="Encoding Values"}
|
||||
|
||||
### CLI Tool
|
||||
|
||||
CLI tool is required if you don't rely on the Gradle IntelliJ Plugin – i.e., when working with [Themes](developing_themes.md).
|
||||
CLI tool is required if you don't rely on either Gradle plugin – i.e., when working with [Themes](developing_themes.md).
|
||||
|
||||
To get the latest Marketplace ZIP Signer CLI Tool, visit the [JetBrains/marketplace-zip-signer](https://github.com/JetBrains/marketplace-zip-signer/releases) GitHub Releases page.
|
||||
After downloading the <path>marketplace-zip-signer-cli.jar</path>, execute it as below:
|
||||
@ -263,32 +300,33 @@ java -jar marketplace-zip-signer-cli.jar sign\
|
||||
|
||||
## Signing for Custom Repositories
|
||||
|
||||
Signing plugins hosted on a custom repository can be accomplished for added trust between the repository and installation.
|
||||
However, unlike Marketplace, the custom repository will not re-sign the plugin with the JetBrains key.
|
||||
Signing plugins hosted on a [](custom_plugin_repository.md) can be achieved for added trust between the repository and installation.
|
||||
However, unlike JetBrains Marketplace, the custom repository will not re-sign the plugin with the JetBrains key.
|
||||
Instead, a trusted private CA or self-signed certificate can be used to sign and validate plugins.
|
||||
|
||||
### Verification
|
||||
|
||||
Before looking at how we can sign a plugin, let's first review how verification works when a non-JetBrains certificate is used.
|
||||
As of 2021.2, during verification, IntelliJ-based IDEs check if the plugin was signed by the JetBrains CA certificate or any public key provided by the user via <ui-path>Settings | Plugins | Manage Plugin Certificates</ui-path>. In 2021.2.1, a system property has been added: `intellij.plugins.truststore`, pointing to a trusted JKS TrustStore.
|
||||
As of 2021.2, during verification, IntelliJ-based IDEs check if the plugin was signed with the JetBrains CA certificate or any public key provided by the user via <ui-path>Settings | Plugins | Manage Plugin Certificates</ui-path>.
|
||||
In 2021.2.1, a system property has been added: `intellij.plugins.truststore`, pointing to a trusted JKS TrustStore.
|
||||
During verification, the plugin's public key is extracted from the signature.
|
||||
The last certificate entry in the chain matched against the certificates stored in one of the storages from above.
|
||||
|
||||
### Using a Trusted Internal CA
|
||||
|
||||
If an internal CA is available, you can use this to generate certificates for signing.
|
||||
If an internal CA is available, it can be used to generate certificates for signing.
|
||||
When choosing this route, the certificate chain includes the root CA public key at the end of the chain.
|
||||
|
||||
With this approach, existing internal TrustStores may exist and could be used.
|
||||
Be sure when choosing a TrustStore that the CAs are limited to the internal CAs you trust.
|
||||
Using a TrustStore with public CAs can expose the users to an attack vector.
|
||||
Using a TrustStore with public CAs can expose users to an attack vector.
|
||||
|
||||
If adding a TrustStore to a user's environment is not possible, the user may also add the root CAs public key to <ui-path>Settings | Plugins | Manage Plugin Certificates</ui-path>.
|
||||
|
||||
### Using Self-Signed Certificates
|
||||
|
||||
Using a self-signed certificate is an option if no internal CAs exist.
|
||||
To generate the key and public key, see: [Generate Private Key](#generate-private-key)
|
||||
To generate the key and public key, see [](#generate-private-key).
|
||||
|
||||
If providing users with a TrustStore, you can generate one with the public key using `keytool`:
|
||||
|
||||
@ -301,11 +339,12 @@ Otherwise, users may add the public key manually to <ui-path>Settings | Plugins
|
||||
|
||||
## Plugin Signature Verification
|
||||
|
||||
To verify the signature of a plugin, you can use the [`verifyPluginSignature`](tools_gradle_intellij_plugin.md#tasks-verifypluginsignature) task.
|
||||
The signature of a plugin can be verified using the `verifyPluginSignature` Gradle task
|
||||
([2.x](tools_intellij_platform_gradle_plugin_tasks.md#verifyPluginSignature), [1.x](tools_gradle_intellij_plugin.md#tasks-verifypluginsignature)).
|
||||
|
||||
By default, this task will use the same certificate chain as provided to the [`signPlugin`](tools_gradle_intellij_plugin.md#tasks-signplugin) task in the previous section.
|
||||
By default, it will use the same certificate chain as provided to the `signPlugin` Gradle task (see [](#gradle-integration)).
|
||||
|
||||
To verify the signature using [CLI tool](#cli-tool), execute the `verify` command as below:
|
||||
To verify the signature using the [CLI tool](#cli-tool), execute the `verify` command as below:
|
||||
|
||||
```bash
|
||||
java -jar marketplace-zip-signer-cli.jar verify\
|
||||
|
Loading…
x
Reference in New Issue
Block a user