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

Legacy TLS hostname validation (OpenSSL < 1.0.2) #2734

Conversation

ysbaddaden
Copy link
Contributor

closes #2717

This went a little too well: the manual badssl specs passed first time. This is weird. Also, there is no verification that the Common Name match does work, indeed. Maybe we could add specs with a few certificates with or without SAN entries to verify the methods actually work correctly (in isolation).

Note: I introduced a Socket.ip?(str) method to validate IPv4 and IPv6 addresses. Maybe we'd like a better format. The implementation relies on the POSIX inet_pton function, which I expect to be faster than a regular expression.

def self.validate_hostname(hostname : String, server_cert : LibCrypto::X509)
return Result::Error if server_cert.null?
result = matches_subject_alternative_name(hostname, server_cert)
result = matches_common_name(hostname, server_cert) if result.no_san_present?
Copy link
Member

@jhass jhass Jun 3, 2016

Choose a reason for hiding this comment

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

Are we sure that we need to require the hostname in the SANs if they're present? So far my understanding was that while it's recommended to repeat the CN in the SANs, it's not required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently, yes, this is what cURL does:

an alternative name field existed, but didn't match and then we MUST fail
https://github.com/curl/curl/blob/6cabd78531f80d5c6cd942ed1aa97eaa5ec080df/lib/x509asn1.c#L1137

Copy link
Member

Choose a reason for hiding this comment

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

Looks like that's a fairly recent change http://stackoverflow.com/a/5937270/2199687

But okay with me then.

@jhass
Copy link
Member

jhass commented Jun 3, 2016

Looks good overall, thank you!

The biggest work left here is indeed generating or including some self signed certificates and test against them, which I would very much like to see. The ruby testsuite I linked in #2717 does exactly that.

@ysbaddaden ysbaddaden force-pushed the openssl-legacy-verify-certificate-hostname branch from 6b057a6 to c7fdbeb Compare June 4, 2016 09:43
pattern = pattern.chomp('.').downcase
hostname = hostname.chomp('.').downcase

if ::Socket.ip?(hostname)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is wrong: this should be in matches_subject_alternative_name since the SAN will be either DNS (2) or IP (7), and I only take DNS (ie. domain name), and CN only contains a domain name.

@ysbaddaden ysbaddaden force-pushed the openssl-legacy-verify-certificate-hostname branch from c7fdbeb to 4100dbb Compare June 6, 2016 20:29
@ysbaddaden
Copy link
Contributor Author

Here comes a better (and hopefully final) rework, with basic OpenSSL::X509 wrapper classes (Certificate, Name, Extension) in order to write proper specs to verify a hostname against a certificate (SAN, CN).

The X509 wrapper classes are marked as :nodoc: because they'll need far more work to be any usable (set version, issuer, public key + PublicKey wrapper, CA, verify and sign methods...). They're just good enough for my use case: create certificates with SAN extensions and a CN subject so I can test that HostnameValidation is working as expected.

OpenSSL::SSL::HostnameValidation.validate_hostname("::1", openssl_create_cert(san: "IP:::2")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("0:0:0:0:0:0:0:1", openssl_create_cert(san: "IP:::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("fe80:0:0:0:0:0:0:1", openssl_create_cert(san: "IP:fe80::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("fe80:0:0:0:0:0:0:2", openssl_create_cert(san: "IP:fe80::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
Copy link
Member

@jhass jhass Jun 6, 2016

Choose a reason for hiding this comment

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

Should for example fe80::1 validate against itself too? Or for a more curious case, should fe80:0::1 validate against fe80::0:1? Talking about different valid representations of the same IP here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added.

@ysbaddaden ysbaddaden force-pushed the openssl-legacy-verify-certificate-hostname branch from 4100dbb to ed89de8 Compare June 6, 2016 20:50
#
# Required for OpenSSL <= 1.0.1 only. Newer versions should use
# `LibSSL.x509_verify_param_set1_ip_asc` and
# `LibSSL.x509_verify_param_set1_host` instead.
Copy link
Member

Choose a reason for hiding this comment

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

Might be worth to mention that OpenSSL::SSL::Socket does use either automatically.

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 made the method protected, since it's very unlikely to be used directly.

@ysbaddaden ysbaddaden force-pushed the openssl-legacy-verify-certificate-hostname branch 2 times, most recently from ed48d40 to d0c052d Compare June 6, 2016 21:28
@ysbaddaden ysbaddaden force-pushed the openssl-legacy-verify-certificate-hostname branch from d0c052d to f050e96 Compare June 7, 2016 08:24
@ysbaddaden
Copy link
Contributor Author

Done?

@jhass
Copy link
Member

jhass commented Jun 7, 2016

I think so yes, just waiting for Travis :)

OpenSSL::SSL::HostnameValidation.validate_hostname("0:0:0:0:0:0:0:1", openssl_create_cert(san: "IP:::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("fe80:0:0:0:0:0:0:1", openssl_create_cert(san: "IP:fe80::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("fe80:0:0:0:0:0:0:2", openssl_create_cert(san: "IP:fe80::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("fe80:0:1", openssl_create_cert(san: "IP:fe80:0::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
Copy link
Member

@jhass jhass Jun 7, 2016

Choose a reason for hiding this comment

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

Oh wait, while this one is properly a good one too, fe80:0:1 is not actually valid, right? I was more looking towards different non-expanded formattings of the same IP, to repeat for example fe80:0::1 and fe80::0:1, which both are a representation for fe80:0:0:0:0:0:0:1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Argh. Typo.

Copy link
Member

@jhass jhass Jun 7, 2016

Choose a reason for hiding this comment

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

Do you want to correct it or should I just do it after the merge? Nvm, I'll do.

@jhass jhass merged commit f050e96 into crystal-lang:master Jun 7, 2016
jhass added a commit that referenced this pull request Jun 7, 2016
…icate-hostname

Legacy TLS hostname validation (OpenSSL < 1.0.2)
@jhass
Copy link
Member

jhass commented Jun 7, 2016

Thank you ❤️

@ysbaddaden ysbaddaden deleted the openssl-legacy-verify-certificate-hostname branch June 7, 2016 09:07
@ysbaddaden
Copy link
Contributor Author

yay 🙌

@asterite
Copy link
Member

asterite commented Jun 7, 2016

@jhass @ysbaddaden So much love for you, guys! 🙇

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

Successfully merging this pull request may close these issues.

TLS hostname verification for OpenSSL < 1.0.2
3 participants