If you want to sign your code, you need a certificate. You can create a self-signed certificate, but then the users still need to accept your signature. Code signing is often used for eclipse and eclipse plugins. If you install a eclipse plugin, which is not signed, you always need to say “Install anyway…”. If your plugin would be signed - especially with a certificate that is trusted - this dialog wouldn’t show and the plugin is installed without notice.

When you develop a eclipse plugin within the official eclipse.org space, then you can use the CBI Maven signing plugin and the official service to sign your plugin. During the build, the jar files are sent to the webservice and retrieved back fully signed. This however only works within the build infrastructure of eclipse and not from outside.

If you develop a plugin at a separate space, e.g. on github, you need an own certificate. And there comes let’s encrypt into play: This allows to retrieve a official certificate for a domain you control. The certificate is usually used for TLS, but it can be used also for code signing.

Which domain do you control? You can use your github pages presence. Your own domain is <user>.github.io or <organization>.github.io. If you control the account on github, you can create a repository for your user or organization site. And for that, you can request a let’s encrypt certificate.

The certificate let’s encrypt issues are trusted, because they are signed with officially trusted certificates that you usually have it your trust store as root certificates. The certificates are only valid for 90 days. So usually, the certificates are renewed regularly and that’s automated. But it turns out, that you can use also an expired certificate for code signing, as long as you use a signature time stamp. With that, you don’t need to resign the jars when the certificate expires.

There is a free to use TSA (time stamp authority) server available from digicert: http://timestamp.digicert.com. More info about this digicert service can be found at RFC3161 compliant Time Stamp Authority (TSA) server.

The method of Trusted timestamping is explained in more detail in Wikipedia and described in RFC3161 Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP).

The idea is from spotbugs issue 779, where they also used a let’s encrypt certificate to sign the eclipse plugin.

Step by Step

  1. Install certbot, the ACME client to request certificates from let’s encrypt.

  2. Create a directory, in which all the certificates and logs are created. That way, you don’t need to run certbot as root:

    mkdir $(HOME)/letsencrypt
    cd $(HOME)/letsencrypt
    
  3. Run certbot to request a certificate and start manual verification

    certbot certonly -d <user>.github.io --manual --preferred-challenges http \
        --config-dir $(pwd) --work-dir $(pwd) --logs-dir $(pwd)
    

    This performs manual verification with the http-01-challenge. With that, let’s encrypt gives you a token, that you need to place at your website under http://<user>.github.io/.well-known/acme-challenge/<TOKEN>

    The token will be displayed in the console and the client waits for you to do the next step.

  4. Now it’s time to create the repository on github for your github pages site. Then create a new file called challenge (the name doesn’t really matter) with the following content:

    ---
    layout: none
    permalink: .well-known/acme-challenge/<TOKEN>
    ---
    <Complete-TOKEN>
    

    This trick to serve “.well-known” files on github pages was described in Using the .well-known/ folder on a GitHub Pages hosted Jekyll site and keybase / keybase-issues / #366 Github Pages compatibility.

    Now commit and wait until github pages is published. This should only take a few seconds. Then verify, that you can download the token file: http://<user>.github.io/.well-known/acme-challenge/<TOKEN>.

    Note, that github automatically redirects to https. But luckily, that is not a problem, as letsencrypt will follow these redirects (actually up to 10 redirects deep).

  5. Then back to the certbot console. You can now hit enter to continue the process. Let’s Encrypt will now download the token themselves and thus confirm, that you have indeed control over you github pages site.

    When this succeeds, the certificates are placed under archive/<user>.github.io. There are a couple of files created: Most important is privkey1.pem - this contains the private key for your certificate, which is needed to sign your code.

  6. The private key and the certificate is in PEM format. In order to use this key for code signing with jarsigner we need to convert it into a java keystore:

    PKCS12_PASSPHRASE=<secret-pw> \
        openssl pkcs12 -export -inkey archive/<user>.github.io/privkey1.pem \
        -in archive/<user>.github.io/fullchain1.pem  \
        -name code-signing \
        -password env:PKCS12_PASSPHRASE \
        -out code-signing.p12
    

    You can verify the contents of the PKCS12 keystore (you’ll need to enter the password):

    keytool -list -keystore code-signing.p12
    
  7. Now you can use code-signing.p12 to sign a jar file:

    jarsigner -verbose \
      -keystore code-signing.p12 \
      -storepass <secret-pw> \
      -keypass <secret-pw> \
      -tsa http://timestamp.digicert.com \
      path/to/plugin-jar.jar \
      code-signing
    

    Even though the warning “The signer certificate’s ExtendedKeyUsage extension doesn’t allow code signing.” is issued, the signing still works.

    With the time stamping, the signature is valid until the time stamping certificate from digicert expires which is currently in 2031.

  8. If your project is using Apache Maven, then you can use the maven-jarsigner-plugin which seamingless integrates jarsigner into the build process:

    <profile>
      <id>sign</id>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jarsigner-plugin</artifactId>
            <version>3.0.0</version>
            <configuration>
              <alias>code-signing</alias>
              <keystore>path/to/code-signing.p12</keystore>
              <keypass>${env.CI_SIGN_PASSPHRASE}</keypass>
              <storepass>${env.CI_SIGN_PASSPHRASE}</storepass>
              <tsa>http://timestamp.digicert.com</tsa>
            </configuration>
            <executions>
              <execution>
                <id>sign</id>
                <goals>
                  <goal>sign</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
    

    Most configuration options can also be set on the command line, e.g. via -Djarsigner.keystore=path/to/code-signing.p12.