[#1154] Find the OpenIDM certificate by comparing names, not their string forms (#1162)
## Problem
The OpenIDM account change notification handler looks up the OpenIDM
certificate in its truststore by the configured
`certificate-subject-dn`. It did not find the certificate when the
subject contains `=`, repeated spaces, an `EMAILADDRESS` (or `DC`)
attribute, or a multi-valued RDN. The handler then rejects its
configuration with `ERR_OPENIDM_PWSYNC_INVALID_SERVERKEYALIAS`
("certificate-subject-dn '%s' is not found in provided truststore") and
cannot start, because it needs that certificate's public key to encrypt
the passwords it sends to OpenIDM. See #1154.
## Cause
`getServerCertificate()` compared two strings case-insensitively:
`DN.toString()` of the configured DN and
`X500Principal.getName(CANONICAL)` of each certificate subject. The two
are different serialisations of the same name:
- `DN.toString()` escapes `=` in a value (since #201), the canonical
form does not.
- The canonical form collapses internal spaces.
- The canonical form sorts the AVAs of a multi-valued RDN.
- The canonical form writes `EMAILADDRESS` as an OID with a BER hex
string, and so does it with `DC`, which the JDK encodes as an IA5String.
## Change
- The lookup moves into a package-private `findCertificateBySubject(DN,
X509Certificate...)`, which compares names instead of strings: `new
X500Principal(subjectDN.toString()).equals(cert.getSubjectX500Principal())`.
`X500Principal.equals()` compares the canonical forms of both sides and
parses OpenDJ's `\=` escape.
- If the JDK cannot parse the configured DN (an attribute type it has no
keyword for, such as `mail`), the method returns `null` and the handler
reports the same `ERR_OPENIDM_PWSYNC_INVALID_SERVERKEYALIAS` as before,
instead of letting an unchecked `IllegalArgumentException` escape. Such
a DN could not match a certificate before either: the canonical form
writes those attribute types as OIDs.
- `getServerCertificate()` keeps its error message and behaviour
otherwise.
Out of scope: a configured value written as a hex string
(`CN=#04026869,…`) still does not match, because OpenDJ keeps a hex
string as raw bytes instead of BER-decoding it. That is the parser issue
tracked in #1153.
## Test
`OpenidmAccountStatusNotificationHandlerTestCase` is the module's first
unit test (the module now declares the `build-tools` test dependency for
the surefire listener). It builds real self-signed certificates with
BouncyCastle, which comes with `opendj-server-legacy`, from an
`X500Principal`, so the subject is encoded as `keytool` encodes it.
Mockito cannot mock `X509Certificate` on Java 17+.
It checks that the certificate is found for the sample configuration's
subject and for each row of the issue's table (`=`, repeated spaces,
multi-valued RDN, `EMAILADDRESS`), plus `UID`/`DC`, and for a configured
DN in another case and AVA order. It also checks that no certificate is
found when no subject matches and when the JDK cannot parse the
configured DN.
- Without the fix: 6 of 9 fail, exactly the cases the issue predicts.
The sample configuration's subject and the negative cases pass.
- With the fix: 9 of 9 pass.
- With the `catch` removed, the unparseable-DN test fails with
`IllegalArgumentException: improperly specified input name`.
Fixes #1154