Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making Cert-to-CRL bindings fully static #7094

Open
Tracked by #7312
aarongable opened this issue Sep 22, 2023 · 3 comments
Open
Tracked by #7312

Making Cert-to-CRL bindings fully static #7094

aarongable opened this issue Sep 22, 2023 · 3 comments
Assignees

Comments

@aarongable
Copy link
Contributor

Today, our certs are assorted into CRL shards at the time those shards are generated. The decision of which shard a cert will end up in is based on: the number of shards, the configured "chunk width", and the cert's notAfter timestamp. This is good, and prevents us from moving certs between shards unnecessarily.

However, it is not good enough for us to be able to include CRL URLs directly in our certs. In order to do that, the mapping needs to be fully static.

#7007 was a necessary prerequisite for this work.

Our design is as follows:


Phase 1: Populating the revokedCertificates Table

We create a new database table with the following schema:

CREATE TABLE `revokedCertificates` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`issuerID` bigint(20) NOT NULL,
`shardIdx` bigint(20) NOT NULL,
`notAfter` datetime NOT NULL,
`serial` varchar(255) NOT NULL,
`revokedDate` datetime NOT NULL,
`revokedReason` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `notAfter_idx` (`notAfter`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE(id)
(PARTITION p_start VALUES LESS THAN (MAXVALUE));

This table has all of the information necessary to look up revoked certificates quickly by shard, and all of the information necessary to populate a Revoked Certificate entry in a CRL. All columns are NOT NULL because rows will only be inserted when a certificate is actually revoked, and all of this information is available.

We augment the SA’s RevokeCertificate method to take a new parameter, the CRL shard to which the certificate belongs. If it receives a request which contains a shard index, it writes a row to the new revokedCertificates table in addition to its current update to the certificateStatus table.

We augment the RA with the same CRL bucketing configuration parameters as the crl-updater has, specifically NumShards and ShardWidth, which control which shard a certificate falls into. We also add to the RA a version of the shard computation code which can quickly determine the correct shard number given the config values above and a certificate’s notAfter date. When the RA processes a revocation request, it will compute the shard index for that certificate, and include it in the request to the SA, triggering the codepath described above.


Phase 2: Using the revokedCertificates Table

When Phase 1 has been deployed for more than 90 days, and we are sure that all unexpired revoked certificates have rows in the revokedCertificates table, we can begin reading from it instead of reading from the certificateStatus table.

We will augment the SA’s GetRevokedCerts method to take a new parameter, the CRL shard which is being queried. If it receives a request which contains a shard index, it will query the revokedCertificates table instead of the certificateStatus table, and will include the shard index as an additional WHERE clause in the query.

We then greatly simplify crl-updater’s query mechanism. Rather than using its configuration parameters to compute complex sets of ExpiresBefore and ExpiresAfter dates that contribute to each shard, it will instead send a single query per shard, including that shard ID in the request to the SA. The request will still include ExpiresAfter and ExpiresBefore timestamps, because the crl-updater is the only entity which knows how far back our look-back needs to go and what timestamp is being used as the cutoff for this CRL.

@aarongable
Copy link
Contributor Author

Implementation plan:

  1. Create the table, fully implement the updated versions of RevokeCertificate, UpdateRevokedCertificate, and GetRevokedCertificates. None of the new code will be active until other components begin supplying shard indices in their requests.
  2. Wait one deployment cycle, for the table to be created and the gRPC methods to be deployed.
  3. Add code to the RA (and admin-revoker?) to compute and supply a ShardIdx when revoking a certificate. This will activate the RevokeCertificate and UpdateRevokedCertificate code paths.
  4. Wait 100 days to ensure that all unexpired revoked certificates are represented in the new table.
  5. Add code to the crl-updater to request revoked certs by shard, rather than by chunk boundaries. This will activate the GetRevokedCertificates code path.
  6. Clean up old code paths.

This bug tracks all stages of this implementation plan.

@aarongable aarongable self-assigned this Sep 22, 2023
aarongable added a commit that referenced this issue Oct 2, 2023
Add a new "revokedCertificates" table to the database schema. This table
is similar to the existing "certificateStatus" table in many ways, but
the idea is that it will only have rows added to it when certificates
are revoked, not when they're issued. Thus, it will grow many orders of
magnitude slower than the certificateStatus table does. Eventually, it
will replace that table entirely.

The one column that revokedCertificates adds is the new "ShardIdx"
column, which is the CRL shard in which the revoked certificate will
appear. This way we can assign certificates to CRL shards at the time
they are revoked, and guarantee that they will never move to a different
shard even if we change the number of shards we produce. This will
eventually allow us to put CRL URLs directly into our certificates,
replacing OCSP URLs.

Add new logic to the SA's RevokeCertificate and UpdateRevokedCertificate
methods to handle this new table. If these methods receive a request
which specifies a CRL shard (our CRL shards are 1-indexed, so shard 0
does not exist), then they will ensure that the new revocation status is
written into both the certificateStatus and revokedCertificates tables.
This logic will not function until the RA is updated to take advantage
of it, so it is not a risk for it to appear in Boulder before the new
table has been created.

Also add new logic to the SA's GetRevokedCertificates method. Similar to
the above, this reads from the new table if the ShardIdx field is
supplied in the request message. This code will not operate until the
crl-updater is updated to include this field. We will not perform this
update for a minimum of 100 days after this code is deployed, to ensure
that all unexpired revoked certificates are present in the
revokedCertificates table.

Part of #7094
@aarongable
Copy link
Contributor Author

IN-9706 has been file to track creating the new table in prod. I've begun work on the RA code which cannot land until after the table actually exists.

aarongable added a commit that referenced this issue Nov 1, 2023
Add a new clock argument to the test-only ThrowAwayCert function, and
use that clock to generate reasonable notBefore and notAfter timestamps
in the resulting throwaway test cert. This is necessary to easily test
functions which rely on the expiration timestamp of the certificate,
such as upcoming work about computing CRL shards.

Part of #7094
aarongable added a commit that referenced this issue Nov 2, 2023
Make crl.GetChunkAtTIme an exported function, so that it can be used by
the RA in a future PR without making that PR bigger and harder to
review. Also move it from being a method to an independent function
which takes two new arguments to compensate for the loss of its
receiver.

Also move some tests from batch_test to updater_test, where they should
have been in the first place.

Part of #7094
@aarongable
Copy link
Contributor Author

We've always known that this project would involve touching the CA's CRL issuance code. However, I incorrectly believed that that work would be able to be put off until it was time to actually include CRL URLs in our certs. Comments on #7133 have made it clear that no, that work should actually happen now. I'm tracking that work in #7159, which blocks this bug.

aarongable added a commit that referenced this issue Feb 13, 2024
Move the CRL issuance logic -- building an x509.RevocationList template,
populating it with correctly-built extensions, linting it, and actually
signing it -- out of the //ca package and into the //issuance package.
This means that the CA's CRL code no longer needs to be able to reach
inside the issuance package to access its issuers and certificates (and
those fields will be able to be made private after the same is done for
OCSP issuance).

Additionally, improve the configuration of CRL issuance, create
additional checks on CRL's ThisUpdate and NextUpdate fields, and make it
possible for a CRL to contain two IssuingDistributionPoint URIs so that
we can migrate to shorter addresses.

IN-10045 tracks the corresponding production changes.

Fixes #7159
Part of #7296
Part of #7294
Part of #7094
Part of #7100
maksimsavrilov pushed a commit to plesk/boulder that referenced this issue Feb 14, 2024
Move the CRL issuance logic -- building an x509.RevocationList template,
populating it with correctly-built extensions, linting it, and actually
signing it -- out of the //ca package and into the //issuance package.
This means that the CA's CRL code no longer needs to be able to reach
inside the issuance package to access its issuers and certificates (and
those fields will be able to be made private after the same is done for
OCSP issuance).

Additionally, improve the configuration of CRL issuance, create
additional checks on CRL's ThisUpdate and NextUpdate fields, and make it
possible for a CRL to contain two IssuingDistributionPoint URIs so that
we can migrate to shorter addresses.

IN-10045 tracks the corresponding production changes.

Fixes letsencrypt#7159
Part of letsencrypt#7296
Part of letsencrypt#7294
Part of letsencrypt#7094
Part of letsencrypt#7100
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

No branches or pull requests

1 participant