Skip to content

x509: add minimal support for CRL building#2538

Merged
botovq merged 1 commit into
rust-openssl:masterfrom
ocdlroux:feat/crl-full
Jun 4, 2026
Merged

x509: add minimal support for CRL building#2538
botovq merged 1 commit into
rust-openssl:masterfrom
ocdlroux:feat/crl-full

Conversation

@DavidFontaineOcd

Copy link
Copy Markdown
Contributor

Summary

This pull request introduces initial support for Certificate Revocation List (CRL) functionality. The current implementation of the library exposes many X.509 certificate features, but the APIs related to CRL creation and manipulation are still missing. This PR fills part of that gap by adding the essential types and extensions required to build and manage CRLs programmatically.

Motivation

The OpenSSL C API provides full support for generating, parsing, and extending CRLs. However, rust-openssl currently lacks high-level Rust bindings for these components. As a result, users cannot create CRLs, add revoked certificate entries, or include the standard CRL-related extensions expected in real-world PKI workflows. This PR aims to improve feature parity with OpenSSL and enable broader CRL use cases within Rust applications.

What’s Added

This contribution introduces wrappers and bindings for several key X.509 structures used in CRL creation:

  • X509RevokedBuilder
  • AccessDescriptionBuilder
  • DistributionPointBuilder
  • DistributionPointNameBuilder

Additionally, the PR adds support for CRL extensions and CRL entry extensions, which are necessary for producing standards-compliant CRLs (e.g., CRL Number, Authority Key Identifier, CRL Reason Codes, Issuing Distribution Point).

Benefits

Enables programmatic creation and customization of CRLs in Rust
Improves parity with the OpenSSL CRL API
Helps developers build complete PKI workflows without relying on external tools

@ocdlroux

Copy link
Copy Markdown

It seems the CI overfailed with some HTTP 503 from GitHub, some of the cases need fixing though.

@botovq

botovq commented Dec 11, 2025

Copy link
Copy Markdown
Contributor

necessary for producing standards-compliant CRLs

I'm sorry, but I don't follow. An RFC 5280-compliant CRL only needs the AKI and the crlNumber per 5.2. I think those two should be enough, certainly for a first PR.

@ocdlroux

Copy link
Copy Markdown

Hi :)

I'm sorry, but I don't follow. An RFC 5280-compliant CRL only needs the
AKI and the crlNumber per 5.2. I think those two should be enough, 
certainly for a first PR.

Indeed. This pull request emerges from the fact that we implemented some CRL-related code in our company codebase out-of-tree, and since the upstream crate missed them and our hierarchy agreed to it, we figured we'd give some of the work back to the community.

This is why we implemented more extensions than strictly necessary. We're happy to split the PR in multiple chunks if you prefer it this way.


About some of the failing CIs because of ASN1_{INTEGER,ENUMERATED}_set-related c_int sizing, we're probably going to use from_bn instead, and probably remove back the ffi for ASN1_INTEGER_new, is that the idiomatic way ?

Don't hesitate if you have any preference on how you want the contributions formatted !
Thanks.

@botovq

botovq commented Dec 11, 2025

Copy link
Copy Markdown
Contributor

Understood. Thanks a lot for upstreaming your changes.

Indeed, splitting this up into smaller chunks will be most welcome. Then we can focus on correctness both on the standardization side and on the implementation side.

As a first step, I would recommend you omit all the things that are not required to be in the tbsCertList by RFC 5280: the builder sets the CRL version to 2 (the number 1) and ensures that nextUpdate is present (and in proper 5280 time format) as well as the CRL number extension (an ASN.1 integer greater than or equal to 0 and less than 2^159) and an Authority Key Identifier extension, and that both are non-critical. For the list of revoked certificates, let's do only userCertificate and revocationDate and make sure that these follow the usual rules. Reason codes can be added later.

And yes, for the CRL number you probably need to make a detour via a bignum and can therefore omit the addition of ASN1_INTEGER_new().

For the reason code, I think you can leave your approach but cast to c_long rather than i64, that's safe since there are only 10 of them, but let's deal with this and other enumerated types once the first PR is in good shape.

@ocdlroux

Copy link
Copy Markdown

Thanks for your answer !

the builder sets the CRL version to 2 (the number 1) and ensures that nextUpdate is present (and in proper 5280 time format) as well as the CRL number extension (an ASN.1 integer greater than or equal to 0 and less than 2^159) and an Authority Key Identifier extension, and that both are non-critical.

To be sure, are you recommending we nuke our X509CrlBuilder::set_version and make build fallible and the only method handling ffi calls (like the extension builders) ? This would diverge from the X509Builder design, which doesn't seem to check anything while building. If so, is there some builder pattern you'd like us to mimick from somewhere else in the codebase ?

@botovq

botovq commented Dec 12, 2025

Copy link
Copy Markdown
Contributor

The ecosystem suffers quite a bit from non-compliant certificates and CRLs that overly flexible tools like the openssl command line let non-experts create.

New code issuing certificates and CRLs should strive to be as standards-compliant as reasonably possible. I do not think it makes sense at all to issue a CRL that isn't version 2 in the present day and age, so I don't see the need for a set_version() in the builder. (I'm not convinced it made sense to make the certificate version customizable a decade ago - it should always have been 3, but we can't fix that now)

I wold have created a build() that fails if one of the two required extensions is missing one way or the other. Moving the ffi calls there makes sense, but these could also stay in the builder components setting the extensions on the X509 and having a check in build() to make sure the extensions were set.

@ocdlroux

ocdlroux commented Dec 15, 2025

Copy link
Copy Markdown

Hi again,

We're reworking the CrlBuilder from what you stated, and:

  • We were wondering, if the two required extensions aren't present, should we assert! with an explanation message (and with adequate documentation) to let the developer know that this shouldn't be used without those extensions or do we just return an Err ?
  • Since the builder is going to contain only rust members until the build should it take the pkey & digest to construct the Crl and sign it in one go ?

Thanks.

Edit: If we do need to construct errors, what is the idiomatic way doing that also ? e.g. for the 2^159 check for CrlNumber ?

@DavidFontaineOcd

Copy link
Copy Markdown
Contributor Author

Hi,

We saw that support for version 1.0.2 was removed, so we’ve updated the branch accordingly.
Feel free to reach out about any other concerns.

Thanks (and happy new year by the way 🎉 ) !

@DavidFontaineOcd DavidFontaineOcd changed the title Feat/crl full x509: add minimal support for CRL building Jan 8, 2026
@botovq

botovq commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Great, thank you. Apologies for the silence - it's been a crazy few weeks. I'll endeavour to give you feedback soon.

@botovq botovq left a comment

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.

I think we're getting quite close. I few small nits and one suggestion regarding empty list of revoked.

Comment thread openssl/src/x509/extension.rs Outdated
Comment on lines +570 to +571
number >= BigNum::from_u32(0)? && number < max,
"CrlNumber must have n ASN.1 integer greater than or equal to 0 and less than 2^159"

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
number >= BigNum::from_u32(0)? && number < max,
"CrlNumber must have n ASN.1 integer greater than or equal to 0 and less than 2^159"
!number.is_negative() && number < max,
"CrlNumber must be an ASN.1 integer greater than or equal to 0 and less than 2^159"

Comment thread openssl/src/x509/mod.rs Outdated
}
}

/// Set the lastUpdate time indicating when the CRL was issued.

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.

lastUpdate is an OpenSSL-ism. The standard name is thisUpdate

Suggested change
/// Set the lastUpdate time indicating when the CRL was issued.
/// Set the lastUpdate (thisUpdate) time indicating when the CRL was issued.

Comment thread openssl/src/x509/mod.rs Outdated
unsafe { cvt(ffi::X509_CRL_set1_nextUpdate(self.0.as_ptr(), t.as_ptr())).map(|_| ()) }
}

/// Adds an X509 extension value to the CRL.

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
/// Adds an X509 extension value to the CRL.
/// Add an X509 extension value to the CRL.

Comment thread openssl/src/x509/mod.rs Outdated
self.append_extension2(&extension)
}

/// Adds an X509 extension value to the CRL.

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
/// Adds an X509 extension value to the CRL.
/// Add an X509 extension value to the CRL.

Comment thread openssl/src/x509/mod.rs
}

/// Consumes the builder, returning the `X509Revoked`.
pub fn build(self) -> X509Revoked {

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.

I wonder if this should not refuse building an empty list of revoked certificates per RFC 5280, 5.1.2.6, "When there are no revoked certificates, the revoked certificates list MUST be absent."

@DavidFontaineOcd DavidFontaineOcd force-pushed the feat/crl-full branch 4 times, most recently from a9ac7ac to 796cee4 Compare January 19, 2026 10:28
@DavidFontaineOcd

Copy link
Copy Markdown
Contributor Author

Thank you for your review.

I applied your suggestion, and added an other assert in the CrlBuilder regarding the empty revoked list.
I hope this matches what you had in mind.

@botovq botovq left a comment

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.

One last thing for you to fix. Then I believe we're done as far as code is concerned.

Before we can land this we will have to fix the min-version test either by pinning some crate (random?) or bumping the MSRV. I'll ask you to rebase once more once that's done.

Comment thread openssl/src/x509/mod.rs Outdated
Comment on lines +1911 to +1912
revoked.is_some_and(|r| !r.is_empty()),
"Revoked should not be empty or null"

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
revoked.is_some_and(|r| !r.is_empty()),
"Revoked should not be empty or null"
// XXX - switch to is_none_or() once MSRV is 1.82.
revoked.is_none() || revoked.is_some_and(|r| !r.is_empty()),
"Revoked must be absent or non-empty"

If we had MSRV 1.82, we could write this as revoked.is_none_or(|r| !r.is_empty()), but since we still have 1.70, this needs to be spelled out explicitly.

The reason is that the revokedCertificates element is OPTIONAL:

 revokedCertificates     SEQUENCE OF SEQUENCE  {
             userCertificate         CertificateSerialNumber,
             revocationDate          Time,
             crlEntryExtensions      Extensions OPTIONAL
                                      -- if present, version MUST be v2
                                  }  OPTIONAL,

per RFC 5280, section 5.1.2.6, which states: "When there are no revoked certificates, the revoked certificates list MUST be absent.", so the None case is valid.

@DavidFontaineOcd

Copy link
Copy Markdown
Contributor Author

Hi,

Sorry it took me a very long time to rebase the last changes.

Everything seems OK here.
Let me know if there’s anything I can do.

@botovq botovq left a comment

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.

Thanks for reviving this - I could have sworn I had merged this months ago... I reread the code and I am happy to merge once the CI is green.

If you wish to replace the asserts with the new ErrorStack::internal_error() you could do that as a follow-up.

@botovq botovq merged commit 9fac317 into rust-openssl:master Jun 4, 2026
173 of 174 checks passed
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.

3 participants