Skip to content

Support standard certificate chains - #1561

Open
IdelsTak wants to merge 6 commits into
qzind:masterfrom
IdelsTak:issue-803-standard-x509-chains
Open

IdelsTak wants to merge 6 commits into
qzind:masterfrom
IdelsTak:issue-803-standard-x509-chains

Conversation

@IdelsTak

@IdelsTak IdelsTak commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

@tresf, this pr is meant to close #803

  • accept standard concatenated PEM chains
  • support multiple intermediates
  • keep the legacy separator and configured trust behavior

@IdelsTak

IdelsTak commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Compatibility and security notes:

  • first certificate stays the signing identity
  • later certificates are path candidates only
  • configured roots stay the only trust anchors
  • legacy bundles and saved allow/block rows still load

Tests' coverage includes standard and legacy chains, two intermediates, persistence, malformed input, expiry, CRL, and embedded-root isolation

Comment thread src/qz/auth/Certificate.java Resolved
Comment thread src/qz/auth/Certificate.java Resolved
Comment thread test/qz/auth/CertificateTests.java Resolved
Comment thread test/qz/auth/CertificateTests.java Resolved
Comment thread test/qz/auth/CertificateTests.java Resolved
Comment thread test/qz/auth/CertificateTests.java Resolved
Comment thread test/qz/auth/CertificateTests.java Resolved
Comment thread test/qz/auth/CertificateTests.java Resolved
@tresf

tresf commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

@IdelsTak thanks. The improvements look great. As for the implementation, I have a concern about single-chain certs / OutOfBoundsException. I've left a code comment about that.

The unit tests are very comprehensive, thank you. I will give you time to reply to my code comments before making any changes myself.

Some cosmetic changes...

  • legacy-chain -> chain-legacy, standard-chain -> chain-standard, etc
  • legacy-leaf -> leaf-legacy, direct-leaf -> leaf-direct
    • BTW - Can you either rename these or add some code comments to what these are meant to represent?
  • root-ca -> ca-root
    • BTW - Can you either rename this or add a code comments? For example if it's expected for a leafs to be part of a chain, the resource names should probably hint towards that. Maybe they do but it's a bit hard at a glance. One example may be ca-acme and then leaf-acme or something. Anything that makes it easier to read from a distance. Thoughts welcome here.
  • .pem -> .txt or .crt please. Our internal documentation uses digital-certificate.txt, override.crt and private-key.pem so we should match in our tests for consistency.

@IdelsTak

IdelsTak commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author

@tresf, review updates are in 394beab

I also renamed the fixtures by role and kept the root-list concurrency change out of this PR

Could you take another look when convenient?

"ca-root.crt", true);
}

private void assertNames(String resource, String... expected) throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private void assertNames(String resource, String... expected) throws Exception {
private void assertNames(String resource, String... expectedCns) throws Exception {

List<X509Certificate> certificates = Certificate.parseCertificates(read(resource));

Assert.assertEquals(certificates.size(), expected.length);
for(int i = 0; i < expected.length; i++) {

@tresf tresf Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will still pass under the following 'failure' scenarios:

  • when only one expected value is sent
  • when the certificates contain more values than the expected values

}

private void assertTrust(String certificateData, String rootResource,
boolean trusted) throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
boolean trusted) throws Exception {
boolean trustExpected) throws Exception {


public class CertificateSerializationTests {
@Test
public void testStandard() throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called testStandard() however the standard and most common workflow is for an existing trusted certificate (such as our demo cert) to be tested against the rootCA that's built into Certificate.java. Do we have coverage for this? If so, do you think we should have that as the standard test?


@Test
public void testSelfSigned() throws Exception {
assertTrust(read("leaf-self-signed.crt"), "ca-root.crt", false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this is a negative test? Our most common use-case for self-signed is more like this:

Suggested change
assertTrust(read("leaf-self-signed.crt"), "ca-root.crt", false);
assertTrust(read("leaf-self-signed.crt"), "leaf-self-signed.crt", true);

}

@Test
public void testUnrelated() throws Exception {

@tresf tresf Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.... these may make more sense to just make an overload that uses the default caCert with explicit clear() and setTrustBuiltIn(). We should explicitly add setTrustBuiltIn() coverage anyway per https://github.com/qzind/tray/pull/1561/changes#r4425314285.

@tresf

tresf commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Thanks for the improvements. Code is perfect but unit tests still need some work. Left more feedback.

}

@Test
public void testDuplicate() throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain what this unit-test does?

}

@Test
public void testReversed() throws Exception {

@tresf tresf Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... what is this testing exactly? Seems cursed and the fact that it's expected to pass is even stranger. I understand detecting and failing this scenario is a bit more nuanced, but let's at least leave a code comment that this is cursed behavior so that if we ever guard against this in the future it's clear from the unit tests comments that it's not expected to pass, just sort of does accidentally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Whitelisting certificates does not support standard X.509 chains

2 participants