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

certificate: add a get_extension helper #8892

Merged
merged 29 commits into from
May 10, 2023

Conversation

woodruffw
Copy link
Contributor

Breakout from #8873.

Needs tests, but just to get something up for review.

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
@woodruffw
Copy link
Contributor Author

Should be good to go now, modulo coverage -- I've rewritten existing Extensions usage into RawExtensions (and added a raw_ prefix as appropriate), with the new Extensions type being a duplicate-checking wrapper.

The existing exception behavior is preserved by having the Err(...) variant carry the duplicate OID.

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
@@ -13,6 +14,12 @@ pub struct Certificate<'a> {
pub signature: asn1::BitString<'a>,
}

impl<'a> Certificate<'a> {
pub fn extensions(&'a self) -> Result<Option<Extensions<'a>>, asn1::ObjectIdentifier> {
self.tbs_cert.extensions()
Copy link
Member

Choose a reason for hiding this comment

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

Not sure it's worth it to have the method on TBS, is there a use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That was mostly for tbs_precertificate_bytes, since we need TbcCertificate.extensions() in that context to perform the poison extension filtering. But maybe there's another way I should be getting the extensions there?

Copy link
Contributor Author

@woodruffw woodruffw May 8, 2023

Choose a reason for hiding this comment

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

Ah yep, I can get rid of this entirely.

Edit: Nope, I don't think I can directly get rid of this -- the TBS precert should have its extensions checked like the others but it doesn't go through the caching layer here (since all we're doing is munging the cert).

src/rust/cryptography-x509/src/certificate.rs Outdated Show resolved Hide resolved
src/rust/src/x509/certificate.rs Outdated Show resolved Hide resolved
src/rust/src/x509/certificate.rs Outdated Show resolved Hide resolved
Comment on lines 265 to 275
let extensions = match tbs_cert_list.extensions() {
Ok(exts) => exts,
Err(oid) => {
let oid_obj = oid_to_py_oid(py, &oid)?;
return Err(exceptions::DuplicateExtension::new_err((
format!("Duplicate {} extension found", oid),
oid_obj.into_py(py),
)));
}
};

Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't be repeated in every extensions method, it should be in parse_and_cache_extensions which can take the result of calling extensions() on the cert.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making sure I understand: in that case, parse_and_cache_extensions should take a Result<Option<Extensions>>? Because that'll be the return type from extensions().

(Or are you thinking it should take the RawExtensions, and then convert it to an Extensions internally? That would avoid passing a potential Err variant through as a parameter.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've done this with RawExtensions; the error handling is now pushed fully into parse_and_cache_extensions.

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
@alex
Copy link
Member

alex commented May 9, 2023

clippy

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
@woodruffw
Copy link
Contributor Author

GitHub is struggling today, but I've added some coverage for the new get_extension helper.

Signed-off-by: William Woodruff <william@trailofbits.com>
@woodruffw
Copy link
Contributor Author

woodruffw commented May 9, 2023

Remaining coverage items:

  • tbs_precertificate_bytes needs a testcase for duplicate extensions (these weren't previously handled)
    - [ ] Certificate::extensions needs a testcase

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
@woodruffw
Copy link
Contributor Author

This should be fully good to go: I've removed Certificate::extensions in favor of an extensional trait in that path validation library being scaffolded in #8873, which means we can punt on coverage.

Comment on lines +23 to +24
raw: Option<&RawExtensions<'a>>,
) -> Result<Option<Self>, asn1::ObjectIdentifier> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not super happy with this signature -- we end up pushing an Option through as a no-op to avoid having to destructure the optional extensions in multiple other places, but that results in more invalid states than are strictly needed here (and the smelly Result<Option<...>> type).

Copy link
Member

Choose a reason for hiding this comment

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

I think a reasonable follow up is, instead of as_raw(), we make Extensions into an iterator, and just handle the Option<> internally. But let's do that in a seperate PR, this one is already complicated enough.

Signed-off-by: William Woodruff <william@trailofbits.com>
Signed-off-by: William Woodruff <william@trailofbits.com>
Comment on lines +23 to +24
raw: Option<&RawExtensions<'a>>,
) -> Result<Option<Self>, asn1::ObjectIdentifier> {
Copy link
Member

Choose a reason for hiding this comment

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

I think a reasonable follow up is, instead of as_raw(), we make Extensions into an iterator, and just handle the Option<> internally. But let's do that in a seperate PR, this one is already complicated enough.

Comment on lines +222 to +229
Err(oid) => {
let oid_obj = oid_to_py_oid(py, &oid)?;
Err(exceptions::DuplicateExtension::new_err((
format!("Duplicate {} extension found", oid),
oid_obj.into_py(py),
))
.into())
}
Copy link
Member

Choose a reason for hiding this comment

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

To avoid needing to repeat this stanza anywhere we touch extensions, can we make extensions return a more-specific error type and then we can do a From impl on that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good idea -- I'll do that now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

212b75f is my attempt at this -- it saves us the repetition of this error clause in exchange for a new error enum + From implementation.

@@ -391,27 +390,30 @@ pub(crate) fn parse_and_cache_extensions<
>(
py: pyo3::Python<'p>,
cached_extensions: &mut Option<pyo3::PyObject>,
raw_exts: &Option<Extensions<'_>>,
raw_extensions: &Option<RawExtensions<'_>>,
Copy link
Member

Choose a reason for hiding this comment

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

I think we actually want this method to take an Option<Extensions> and then the callers can obtain the extensions, via an extensions() method on the various cryptography-x509 types. That way those types have the right methods that can be used from the validator crate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That SGTM, although if I understand correctly doing that will require us to make the Iterator change mentioned in #8892 (comment): without that change, we'd need to perform the fallible conversion from RawExtensions -> Extensions before parse_and_cache_extensions, defeating the point of having it inside of that function. Or did I miss something?

Copy link
Member

Choose a reason for hiding this comment

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

Ok, let's leave this change for a seperate PR.

Signed-off-by: William Woodruff <william@trailofbits.com>

extensions: unwrap -> expect

Signed-off-by: William Woodruff <william@trailofbits.com>
@alex alex merged commit 1ff6208 into pyca:main May 10, 2023
@woodruffw woodruffw deleted the tob-x509-get-extension branch May 10, 2023 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants