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

add a method to collect the DNS names from a certificate #65

Closed
wants to merge 1 commit into from

Conversation

Geal
Copy link

@Geal Geal commented Dec 28, 2017

Fix #64

I am not too happy about it yet, I'd like to remove the ToString implementation for DNSName, since there's a Into<&str> for DNSNameRef.
Unfortunately, I have not been able to make the function return a vector of DNSNameRefyet.
For reference, the code I'm currently trying to build is:

#[cfg(feature = "std")]
pub fn list_cert_dns_names<'a>(cert: &'e super::EndEntityCert<'a>)
                                   -> Result<Vec<DNSNameRef<'a>>, Error> {
    let cert = &cert.inner;
    let names = std::cell::RefCell::new(Vec::new());

    iterate_names(cert.subject, cert.subject_alt_name, Ok(()), &|name| {
        match name {
            GeneralName::DNSName(presented_id) => {
                names.borrow_mut().push(DNSNameRef(presented_id));
            },
            _ => ()
        }
        NameIteration::KeepGoing
    }).map(|_| names.into_inner())
}

But there's a conflict between the lifetime of name (created in iterate_names and passed to the closure) and the lifetime of the DNSNameRef returned as result.

Copy link
Owner

@briansmith briansmith left a comment

Choose a reason for hiding this comment

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

Looks pretty good.

In addition to this test, we should also have the following test edge cases:

  1. There are no DNS names in the cert, but the subject common name looks like a DNS name ("CN=example.com"). This should return an empty result.
  2. There are multiple subjectAltNames, and the last one is an invalid DNS name in subjectAltNames. You can create one of these certificates by starting with a valid certificate and modifying it in a hex editor or similar, e.g. replace s/./:/ in "example.com".

src/name.rs Outdated
@@ -61,6 +64,13 @@ impl<'a> From<DNSNameRef<'a>> for DNSName {
}
}

#[cfg(feature = "std")]
impl std::string::ToString for DNSName {
Copy link
Owner

Choose a reason for hiding this comment

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

I think this might make sense as Into<String> but otherwise there's already another way of doing this, right? x.as_ref().into().into(); for x: DNSName. So I think we should avoid adding this.

In particular, one of my overall goals is to encourage people to use stronger types than String and str for values and so I try to minimize the convenience features for getting those types from stronger types.

Copy link
Author

Choose a reason for hiding this comment

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

I removed it

src/name.rs Outdated
pub fn list_cert_dns_names<'names>(cert: &super::EndEntityCert<'names>)
-> Result<Vec<DNSName>, Error> {
let cert = &cert.inner;
let names = std::cell::RefCell::new(Vec::new());
Copy link
Owner

Choose a reason for hiding this comment

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

Why not let mut names = Vec::new()? I don't think we should need RefCell here.

Copy link
Author

Choose a reason for hiding this comment

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

I thought so too, but I get the following error when I do it: error[E0387]: cannot borrow data mutably in a captured outer variable in an Fn closure

let cert = webpki::EndEntityCert::from(ee_input)
.expect("should parse netflix end entity certificate correctly");

let mut expected_names = std::collections::HashSet::new();
Copy link
Owner

Choose a reason for hiding this comment

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

NIT:

You can write this as:

let expected_names = {
    const EXPECTED_NAMES: &'static [&'static str] = &[
        "account.netflix.com",
        ....
       "www.netflix.com",
    ];
    HashSet::from_iter(EXPECTED_NAMES.iter())
};

Copy link
Author

Choose a reason for hiding this comment

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

done

src/webpki.rs Outdated
/// Requires the `std` default feature; i.e. this isn't available in
/// `#![no_std]` configurations.
#[cfg(feature = "std")]
pub fn list_dns_names(&self) -> Result<std::vec::Vec<DNSName>, Error> {
Copy link
Owner

Choose a reason for hiding this comment

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

I think this should be called dns_names() instead of list_dns_names().

Copy link
Author

Choose a reason for hiding this comment

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

done

let names: std::collections::HashSet<String> = dns_names.drain(..).map(|name| {
let n: webpki::DNSName = name.into();
n.to_string()
}).collect();
Copy link
Owner

Choose a reason for hiding this comment

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

// Ensure that converting the list to a set doesn't throw away
// any duplicates that aren't supposed to be there
assert_eq!(actual_names.len(), expected_names.len());

let actual_names = HashSet::from_iter(actual_names.iter()
    .map(|dns_name| dns_name.as_ref().into())) // Maybe `into::<&str>` for clarity.

Copy link
Author

Choose a reason for hiding this comment

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

added

@briansmith
Copy link
Owner

Unfortunately, I have not been able to make the function return a vector of DNSNameRef yet.

I will look into it now.

@Geal
Copy link
Author

Geal commented Jul 26, 2018

I'll add more integration tests as you suggested, then it should be good to go :)

@Geal
Copy link
Author

Geal commented Jul 26, 2018

the new tests are there, and the build failures are apparently unrelated to this PR (*ring* compilation failures on appveyor, and the beta toolchain not installed for the 2 xcode builds in travis).

@Geal
Copy link
Author

Geal commented Aug 13, 2018

hello, friendly ping on this :)
let me know if there's anything to change

@norcalli
Copy link

norcalli commented Sep 2, 2018

The build failures seem a little off on this. For AppVeyor, I tried compiling his branch on 1.19.0 and got different errors. For travis-ci, the only build failure was on Rust: beta Xcode 7.2, where the error was

error: toolchain 'beta' is not installed
info: caused by: not a directory: '/Users/travis/.rustup/toolchains/beta-x86_64-apple-darwin'

Which seems to be a failure of the build system, not the code, so it seems like this branch should be ready to merge/final review.

@briansmith
Copy link
Owner

@Geal I don't know why I didn't comment on this here, but please see #66. At a minimum, we should have a test that demonstrates what happens when there is only a wildcard *.example.com and another test thta demonstrates what happens when there are both wildcard and non-wildcard subjectAltNames.

@briansmith
Copy link
Owner

The ideal state of wildcard support is hinted at in #66: Distinguish wildcard and non-wildcard entries with distinct enum variants of a new type. I would be willing to defer that ideal wildcard support to another PR but we at least need to decide to do when this function encounters a wildcard entry and we need the tests. It's probably less work to solve it all at once properly. WDYT?

@Geal
Copy link
Author

Geal commented Sep 28, 2018 via email

@briansmith
Copy link
Owner

Thanks for contributing this! This seems to have been subsumed by #91 so I'm optimistically closing this in favor of that one.

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