Candle at the Pool

Dev.Java

Access alternate certificates with acme4j

On January 11 2021, Let's Encrypt will change the default intermediate certificate from the cross-sign IdenTrust DST Root X3 certificate to their own ISRG Root X1 certificate.

The good news: The ISRG certificate is widely trusted by browsers by now, so the transition will be unnoticed by most users.

The bad news: The ISRG certificate is not included in Android devices before "Nougat" 7.1. These devices will show an error when trying to access sites that are signed with the new intermediate certificate. According to Let's Encrypt, stunning 34% of the Android devices out there shall be affected.

To mitigate the problem, Let's Encrypt provides an alternate certificate that is still cross-signed with the IdenTrust DST Root X3 certificate. If you have a web service that is accessed by a relevant number of older Android devices, you may want to use that alternate certificate. It will be available until September 29 2021. The IdenTrust DST Root X3 certificate itself will expire after that date, so this is a hard limit. Let's hope that the problem is going to be solved on Android side in time.

As acme4j fully implements the RFC 8555, it is easy to change your code so it will use the alternate certificate. Based on the acme4j example, this code block will use the first alternate certificate if present, and falls back to the main certificate if not:

Certificate certificate = order.getCertificate();
certificate = certificate.getAlternateCertificates().stream()
        .findFirst()
        .orElse(certificate);

Remember to remove the workaround after September 29 2021 January 2024, so you won't accidentally use other alternate certificates that may become available in the future.

PS: getAlternateCertificates() was added to the latest acme4j v2.11. If you have an older version, fear not: you just need to have a Login object, so you can bind the alternate certificate yourself. This is how it would look like in the example client:

Login login = session.login(acct.getLocation(), userKeyPair);

Certificate certificate = order.getCertificate();
certificate = certificate.getAlternates().stream()
        .map(login::bindCertificate)
        .findFirst()
        .orElse(certificate);

UPDATE: Let's Encrypt found a way to extend the Android compatibility until January 2024. However, this extension may only work for Android devices. To quote the article:

The new cross-sign will be somewhat novel because it extends beyond the expiration of DST Root CA X3. This solution works because Android intentionally does not enforce the expiration dates of certificates used as trust anchors.

Little Java Regex Cookbook

Regular expressions, or short "regex", are a pattern of characters and metacharacters that can be used for matching strings. For example, the pattern "gr[ae]y" matches both the strings "gray" and "grey".

While regular expressions are an integral part of other popular languages, they have been introduced to the Java world rather late with the release of Java 1.4 in 2002. Perl, certainly the mother language of modern regexes, already turned 15 that year.

Regexes are sometimes hard to understand, but once you got the hang of them, they will soon become your weapon of choice when you have to deal with texts.

In this article, I focus on Java code patterns for common scenarios. If you have never heard of regular expressions before, the Wikipedia article and the Pattern JavaDoc are good starting points. The Regex Crossword site is a great place for working out your regex muscles.

Continue reading...
Fluido skin and Markdown syntax highlighting

doxia-module-markdown is a Maven plugin that enables Markdown in Maven documentations. In version 1.8, the developers have moved from Pegdown to Flexmark as Markdown parser. It's a good choice, as Flexmark is considerable faster and is still being maintained, while Pegdown officially reached its end of life in 2016.

However, with that switch, code blocks are not syntax highlighted any more. The reason is that the maven-fluido-skin uses code-prettify for syntax highlighting. It runs browser-side, highlighting all <pre> and <code> blocks selecting the prettyprint CSS class. This was true with Pegdown, but Flexmark selects a source class instead.

An obvious workaround is to use a small JavaScript snippet that adds a prettyprint class to all blocks selecting a source class, and then running prettyPrint() again. To do so, this block needs to be added to the <head> section of site.xml:

<head>
  <!-- Workaround for https://issues.apache.org/jira/browse/DOXIA-571 -->
  <![CDATA[<script type="text/javascript">
    $(document).ready(function () {
      $(".source").addClass("prettyprint");
      prettyPrint();
    });
  </script>]]>
</head>

It's hard to tell if this is a bug in doxia-module-markdown or in maven-fluido-skin. Also see my bug report at Apache's JIRA.


This was a Google Plus comment that I have now moved to my blog as Google Plus is going to be closed. Thanks to Clement Escoffier for the inspiration.

How acme4j handles POST-as-GET requests

In the latest ACME draft 15, Let's Encrypt introduced POST-as-GET requests. It is a breaking change that is not downward compatible to previous drafts.

This brought me into an uncomfortable position. While the Pebble server enforces the use of POST-as-GET, other servers don't support it yet, like the Let's Encrypt server. For this reason, acme4j needs to support both the pre-draft-15 GET requests and the post-draft-15 POST-as-GET requests. Luckily I have found a solution that is totally transparent to the user, at least as long as no other ACME server is used.

This is how acme4j v2.4 works:

  • If you connect to Boulder via an acme://letsencrypt.org URI, acme4j falls back to a compatibility mode that still sends GET requests. Let's Encrypt has announced a sunset date for GET requests on November 1st, 2019. You are safe to use acme4j v2.4 (and older versions) up to this date.
  • If you connect to a Pebble server via an acme://pebble URI, the new POST-as-GET requests are used.
  • If you connect to a different server implementation via http: or https: URI, acme4j sends POST-as-GET requests now. This is likely going to fail at runtime, if the server you connect to does not support draft-15 yet.
  • As a temporary workaround, you can add a postasget=false parameter to the server URI (e.g. https://localhost:14000/dir?postasget=false) to make acme4j enter the fallback mode and send GET requests again.

As soon as Let's Encrypt supports POST-as-GET on their production servers, I will remove the fallback mode from acme4j again. It just clutters the code, and I also have no proper way to test it after that.

Hint: Before updating acme4j, always have a look at the migration guide. It will show you where you can expect compatibility issues.

Errors in GitLab SonarQube plugin

The sonar-gitlab-plugin is an useful plugin to connect SonarQube with GitLab. After pushing, the branch is inspected by SonarQube, and code smells are immediately commented in the commit.

Unfortunately, the error messages of that plugin are a little difficult to understand. It took me a while to collect this little cookbook of error fixes.

  • Unable found project for null project name
    In SonarQube, as Administrator, select the project. Then in Administration ⭢ General Settings ⭢ GitLab, enter the project ID of your project and save it. The project ID is the group name in GitLab, followed by a slash and the name of the project, e.g. shred/timemachine.

  • Unable found project for mygroup/myproject
    In SonarQube, check that the project ID is correct and there are no spelling mistakes. In GitLab, make sure that SonarQube's GitLab user is actually a member of the project, and that the user has Developer rights. I hit a strange bug in GitLab here. The SonarQube user was a member of the project, but still this error occured. When logging in as the SonarQube user, the project was not on the roll of projects. Removing and adding Developer rights to the user didn't help. The only thing that finally worked was to add the SonarQube user to a different project, even if just for a moment. It seems to be a caching problem in GitLab.

  • Multiple found projects for mygroup/myproject
    You should never see this error, but if you do, be more specific with the projectID.