Two CA certificates can hold the same public key and the same subject and still not be interchangeable. One is self-signed, the other is cross-signed by the old root, and their issuer names, serial numbers and signatures differ. A leaf can bind to one of them specifically through its Authority Key Identifier. That binding stays invisible until a client picks the wrong chain and the handshake fails for no obvious reason.

Work these five checks in order: each one rules out a different failure.

Check 1: draw both chains before touching the template

Write the paths down. The old population trusts Old Root A, which issued CA B, which issued the server leaf. The new population trusts the self-signed CA B directly. Both B certificates carry the same key and subject, which is exactly why a person reading a subject line sees one CA where the client sees two.

The cross-signed copy also has to be a real CA certificate, with basic constraints and key usage that permit signing.

Check 2: compare the AKI fields, not just the key

The leaf issuer name has to match B subject, and that part usually holds. The Authority Key Identifier is where the two copies diverge, because it can carry a key identifier, an authority certificate issuer and a serial number. The key identifier matches both copies, since the key is identical. The issuer and serial fields match exactly one, which is why a chain that builds against the self-signed copy can fail against the cross-signed one.

Inspect the leaf instead of guessing: openssl x509 -in server.pem -noout -ext authorityKeyIdentifier.

Then compare both candidates with openssl x509 -in b-cross.pem -noout -text | grep -A2 "Authority Key Identifier", and the same command against the self-signed copy.

If the leaf carries issuer and serial, only the certificate those values describe completes the chain, whichever copy a client happened to load.

Check 3: change issuance policy, never signed bytes

Extensions live inside the signature. Adding, removing or editing one on an existing leaf is not a repair, it is a different certificate, and it needs reissuance. The change belongs in the issuance policy: authorityKeyIdentifier = keyid,issuer:always makes new leaves carry the certificate-specific binding, while keyid alone keeps the looser form that suits a deliberate cross-signing transition. Decide which one your PKI wants before the next batch is signed.

Editing a shared template also changes every future certificate, not only the server in front of you.

Check 4: verify each trust population separately

Test the old path and the new path as separate runs. openssl verify -purpose sslserver -verify_hostname vpn.example -CAfile old-root.pem -untrusted b-cross-signed.pem server.pem exercises the cross-signed route, and the same command with the new root and no untrusted file exercises the self-signed route. Mixing both roots into one bundle hides which chain was selected. Repeat with real clients afterwards, because a VPN backend can prefer a chain or pin a certificate in ways openssl does not.

Deliver the intermediate through the service configuration the client actually reads, not through a wiki page.

Check 5: keep the old path on a retirement clock

Cross-signing keeps an old trust path alive by design, which is both the point and the risk. It does not remediate a compromised old root, and it does not remove that root from any client. Write down when the old path stops granting trust, and confirm that day actually arrives.

If every client can take the new root before the leaves move, a plain dual-trust rollout is simpler than cross-signing, and it leaves one fewer thing to retire.

Lahatsoratra mifandraika