Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3d20332a9e3d
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: c9f397e3766a
Choose a head ref
  • 5 commits
  • 15 files changed
  • 2 contributors

Commits on Jun 6, 2016

  1. Copy the full SHA
    b04b467 View commit details
  2. Copy the full SHA
    0090da1 View commit details

Commits on Jun 7, 2016

  1. Copy the full SHA
    f050e96 View commit details
  2. Merge pull request #2734 from ysbaddaden/openssl-legacy-verify-certif…

    …icate-hostname
    
    Legacy TLS hostname validation (OpenSSL < 1.0.2)
    jhass committed Jun 7, 2016

    Verified

    This commit was signed with the committer’s verified signature.
    jhass Jonne Haß
    Copy the full SHA
    cec922f View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    jhass Jonne Haß
    Copy the full SHA
    c9f397e View commit details
100 changes: 100 additions & 0 deletions spec/std/openssl/ssl/hostname_validation_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
require "spec"
require "openssl/ssl/hostname_validation"

def openssl_create_cert(subject = nil, san = nil)
cert = OpenSSL::X509::Certificate.new
cert.subject = subject if subject
cert.add_extension(OpenSSL::X509::Extension.new("subjectAltName", san)) if san
cert.to_unsafe
end

describe OpenSSL::SSL::HostnameValidation do
describe "validate_hostname" do
it "matches IP from certificate SAN entries" do
OpenSSL::SSL::HostnameValidation.validate_hostname("192.168.1.1", openssl_create_cert(san: "IP:192.168.1.1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("192.168.1.2", openssl_create_cert(san: "IP:192.168.1.1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("::1", openssl_create_cert(san: "IP:::1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
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)
OpenSSL::SSL::HostnameValidation.validate_hostname("fe80:0:1", openssl_create_cert(san: "IP:fe80:0::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::MatchFound)
end

it "matches domains from certificate SAN entries" do
OpenSSL::SSL::HostnameValidation.validate_hostname("example.com", openssl_create_cert(san: "DNS:example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("example.org", openssl_create_cert(san: "DNS:example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("foo.example.com", openssl_create_cert(san: "DNS:*.example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
end

it "verifies all SAN entries" do
OpenSSL::SSL::HostnameValidation.validate_hostname("example.com", openssl_create_cert(san: "DNS:example.com,DNS:example.org")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("10.0.3.1", openssl_create_cert(san: "IP:192.168.1.1,IP:10.0.3.1")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("example.com", openssl_create_cert(san: "IP:192.168.1.1,DNS:example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
end

it "fallbacks to CN entry (unless SAN entry is defined)" do
OpenSSL::SSL::HostnameValidation.validate_hostname("example.com", openssl_create_cert(subject: "CN=example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("example.com", openssl_create_cert(san: "DNS:example.org", subject: "CN=example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchNotFound)
OpenSSL::SSL::HostnameValidation.validate_hostname("example.org", openssl_create_cert(san: "DNS:example.org", subject: "CN=example.com")).should eq(OpenSSL::SSL::HostnameValidation::Result::MatchFound)
end
end

describe "matches_hostname?" do
it "skips trailing dot" do
OpenSSL::SSL::HostnameValidation.matches_hostname?("example.com.", "example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("example.com", "example.com.").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?(".example.com", "example.com").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?("example.com", ".example.com").should be_false
end

it "normalizes case" do
OpenSSL::SSL::HostnameValidation.matches_hostname?("exAMPLE.cOM", "EXample.Com").should be_true
end

it "literal matches" do
OpenSSL::SSL::HostnameValidation.matches_hostname?("example.com", "example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("example.com", "www.example.com").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?("www.example.com", "www.example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("foo.bar.example.com", "bar.example.com").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?("foo.bar.example.com", "foo.bar.example.com").should be_true
end

it "wildcard matches according to RFC6125, section 6.4.3" do
OpenSSL::SSL::HostnameValidation.matches_hostname?("*.com", "example.com").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?("bar.*.example.com", "bar.foo.example.com").should be_false

OpenSSL::SSL::HostnameValidation.matches_hostname?("*.example.com", "foo.example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("*.example.com", "foo.example.org").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?("*.example.com", "bar.foo.example.com").should be_false

OpenSSL::SSL::HostnameValidation.matches_hostname?("baz*.example.com", "baz1.example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("baz*.example.com", "baz.example.com").should be_false

OpenSSL::SSL::HostnameValidation.matches_hostname?("*baz.example.com", "foobaz.example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("*baz.example.com", "baz.example.com").should be_false

OpenSSL::SSL::HostnameValidation.matches_hostname?("b*z.example.com", "buzz.example.com").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("b*z.example.com", "bz.example.com").should be_false

OpenSSL::SSL::HostnameValidation.matches_hostname?("192.168.0.1", "192.168.0.1").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("*.168.0.1", "192.168.0.1").should be_false
end

it "matches IDNA label" do
OpenSSL::SSL::HostnameValidation.matches_hostname?("*.example.org", "xn--kcry6tjko.example.org").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("*.xn--kcry6tjko.example.org", "foo.xn--kcry6tjko.example.org").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?("xn--*.example.org", "xn--kcry6tjko.example.org").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?("xn--kcry6tjko*.example.org", "xn--kcry6tjkofoo.example.org").should be_false
end

it "matches leading dot" do
OpenSSL::SSL::HostnameValidation.matches_hostname?(".example.org", "example.org").should be_false
OpenSSL::SSL::HostnameValidation.matches_hostname?(".example.org", "xn--kcry6tjko.example.org").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?(".example.org", "foo.example.org").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?(".example.org", "foo.bar.example.org").should be_true
OpenSSL::SSL::HostnameValidation.matches_hostname?(".example.org", "foo.example.com").should be_false
end
end
end
22 changes: 22 additions & 0 deletions spec/std/openssl/x509/certificate_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "spec"
require "openssl"

describe OpenSSL::X509::Certificate do
it "subject" do
cert = OpenSSL::X509::Certificate.new
cert.subject = "CN=Nobody/DC=example"
cert.subject.to_a.should eq([{"CN", "Nobody"}, {"DC", "example"}])
end

it "extension" do
cert = OpenSSL::X509::Certificate.new

cert.add_extension OpenSSL::X509::Extension.new("subjectAltName", "IP:127.0.0.1")
cert.extensions.map(&.oid).should eq ["subjectAltName"]
cert.extensions.map(&.value).should eq ["IP Address:127.0.0.1"]

cert.add_extension OpenSSL::X509::Extension.new("subjectAltName", "DNS:localhost.localdomain")
cert.extensions.map(&.oid).should eq ["subjectAltName", "subjectAltName"]
cert.extensions.map(&.value).should eq ["IP Address:127.0.0.1", "DNS:localhost.localdomain"]
end
end
26 changes: 26 additions & 0 deletions spec/std/openssl/x509/name_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require "spec"
require "openssl"

describe "OpenSSL::X509::Name" do
it "parse" do
name = OpenSSL::X509::Name.parse("CN=nobody/DC=example")
name.to_a.should eq([{"CN", "nobody"}, {"DC", "example"}])

expect_raises(OpenSSL::Error) do
OpenSSL::X509::Name.parse("CN=nobody/Unknown=Value")
end
end

it "add_entry" do
name = OpenSSL::X509::Name.new
name.to_a.size.should eq(0)

name.add_entry "CN", "Nobody"
name.to_a.should eq([{"CN", "Nobody"}])

name.add_entry "DC", "Example"
name.to_a.should eq([{"CN", "Nobody"}, {"DC", "Example"}])

expect_raises(OpenSSL::Error) { name.add_entry "UNKNOWN", "Value" }
end
end
63 changes: 63 additions & 0 deletions spec/std/socket_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,69 @@
require "spec"
require "socket"

describe Socket do
# Tests from libc-test:
# http://repo.or.cz/libc-test.git/blob/master:/src/functional/inet_pton.c
assert "ip?" do
# dotted-decimal notation
Socket.ip?("0.0.0.0").should be_true
Socket.ip?("127.0.0.1").should be_true
Socket.ip?("10.0.128.31").should be_true
Socket.ip?("255.255.255.255").should be_true

# numbers-and-dots notation, but not dotted-decimal
# Socket.ip?("1.2.03.4").should be_false # fails on darwin
Socket.ip?("1.2.0x33.4").should be_false
Socket.ip?("1.2.0XAB.4").should be_false
Socket.ip?("1.2.0xabcd").should be_false
Socket.ip?("1.0xabcdef").should be_false
Socket.ip?("00377.0x0ff.65534").should be_false

# invalid
Socket.ip?(".1.2.3").should be_false
Socket.ip?("1..2.3").should be_false
Socket.ip?("1.2.3.").should be_false
Socket.ip?("1.2.3.4.5").should be_false
Socket.ip?("1.2.3.a").should be_false
Socket.ip?("1.256.2.3").should be_false
Socket.ip?("1.2.4294967296.3").should be_false
Socket.ip?("1.2.-4294967295.3").should be_false
Socket.ip?("1.2. 3.4").should be_false

# ipv6
Socket.ip?(":").should be_false
Socket.ip?("::").should be_true
Socket.ip?("::1").should be_true
Socket.ip?(":::").should be_false
Socket.ip?(":192.168.1.1").should be_false
Socket.ip?("::192.168.1.1").should be_true
Socket.ip?("0:0:0:0:0:0:192.168.1.1").should be_true
Socket.ip?("0:0::0:0:0:192.168.1.1").should be_true
# Socket.ip?("::012.34.56.78").should be_false # fails on darwin
Socket.ip?(":ffff:192.168.1.1").should be_false
Socket.ip?("::ffff:192.168.1.1").should be_true
Socket.ip?(".192.168.1.1").should be_false
Socket.ip?(":.192.168.1.1").should be_false
Socket.ip?("a:0b:00c:000d:E:F::").should be_true
# Socket.ip?("a:0b:00c:000d:0000e:f::").should be_false # fails on GNU libc
Socket.ip?("1:2:3:4:5:6::").should be_true
Socket.ip?("1:2:3:4:5:6:7::").should be_true
Socket.ip?("1:2:3:4:5:6:7:8::").should be_false
Socket.ip?("1:2:3:4:5:6:7::9").should be_false
Socket.ip?("::1:2:3:4:5:6").should be_true
Socket.ip?("::1:2:3:4:5:6:7").should be_true
Socket.ip?("::1:2:3:4:5:6:7:8").should be_false
Socket.ip?("a:b::c:d:e:f").should be_true
Socket.ip?("ffff:c0a8:5e4").should be_false
Socket.ip?(":ffff:c0a8:5e4").should be_false
Socket.ip?("0:0:0:0:0:ffff:c0a8:5e4").should be_true
Socket.ip?("0:0:0:0:ffff:c0a8:5e4").should be_false
Socket.ip?("0::ffff:c0a8:5e4").should be_true
Socket.ip?("::0::ffff:c0a8:5e4").should be_false
Socket.ip?("c0a8").should be_false
end
end

describe Socket::IPAddress do
it "transforms an IPv4 address into a C struct and back again" do
addr1 = Socket::IPAddress.new(Socket::Family::INET, "127.0.0.1", 8080.to_i16)
71 changes: 68 additions & 3 deletions src/openssl/lib_crypto.cr
Original file line number Diff line number Diff line change
@@ -7,6 +7,12 @@ lib LibCrypto
alias ULong = LibC::ULong
alias SizeT = LibC::SizeT

type X509 = Void*
type X509_EXTENSION = Void*
type X509_NAME = Void*
type X509_NAME_ENTRY = Void*
type X509_STORE_CTX = Void*

struct Bio
method : Void*
callback : (Void*, Int, Char*, Int, Long, Long) -> Long
@@ -77,11 +83,19 @@ lib LibCrypto
alias EVP_CIPHER_CTX = Void*

alias ASN1_OBJECT = Void*
alias ASN1_STRING = Char*

fun obj_txt2obj = OBJ_txt2obj(s : Char*, no_name : Int) : ASN1_OBJECT
fun obj_nid2sn = OBJ_nid2sn(n : Int) : Char*
fun obj_obj2nid = OBJ_obj2nid(obj : ASN1_OBJECT) : Int
fun obj_ln2nid = OBJ_ln2nid(ln : Char*) : Int
fun obj_sn2nid = OBJ_sn2nid(sn : Char*) : Int

fun obj_txt2obj = OBJ_txt2obj(s : UInt8*, no_name : Int32) : ASN1_OBJECT
fun obj_nid2sn = OBJ_nid2sn(n : Int32) : UInt8*
fun obj_obj2nid = OBJ_obj2nid(obj : ASN1_OBJECT) : Int32
fun asn1_object_free = ASN1_OBJECT_free(obj : ASN1_OBJECT)
fun asn1_string_data = ASN1_STRING_data(x : ASN1_STRING) : Char*
fun asn1_string_length = ASN1_STRING_length(x : ASN1_STRING) : Int
fun asn1_string_print = ASN1_STRING_print(out : Bio*, v : ASN1_STRING) : Int
fun i2t_asn1_object = i2t_ASN1_OBJECT(buf : Char*, buf_len : Int, a : ASN1_OBJECT) : Int

struct EVP_MD_CTX_Struct
digest : EVP_MD
@@ -170,6 +184,57 @@ lib LibCrypto
alias EC_KEY = Void*
fun ec_key_new_by_curve_name = EC_KEY_new_by_curve_name(nid : Int) : EC_KEY
fun ec_key_free = EC_KEY_free(key : EC_KEY)

struct GENERAL_NAME
type : LibC::Int
value : ASN1_STRING
end

# GEN_URI = 6
GEN_DNS = 2
GEN_IPADD = 7

NID_undef = 0
NID_commonName = 13
NID_subject_alt_name = 85

fun sk_free(st : Void*)
fun sk_num = sk_num(x0 : Void*) : Int
fun sk_pop_free(st : Void*, callback : (Void*) ->)
fun sk_value = sk_value(x0 : Void*, x1 : Int) : Void*

fun x509_dup = X509_dup(a : X509) : X509
fun x509_free = X509_free(a : X509)
fun x509_get_subject_name = X509_get_subject_name(a : X509) : X509_NAME
fun x509_new = X509_new : X509
fun x509_set_subject_name = X509_set_subject_name(x : X509, name : X509_NAME) : Int
fun x509_store_ctx_get_current_cert = X509_STORE_CTX_get_current_cert(x : X509_STORE_CTX) : X509
fun x509_verify_cert = X509_verify_cert(x : X509_STORE_CTX) : Int
fun x509_add_ext = X509_add_ext(x : X509, ex : X509_EXTENSION, loc : Int) : X509_EXTENSION
fun x509_get_ext = X509_get_ext(x : X509, idx : Int) : X509_EXTENSION
fun x509_get_ext_count = X509_get_ext_count(x : X509) : Int
fun x509_get_ext_d2i = X509_get_ext_d2i(x : X509, nid : Int, crit : Int*, idx : Int*) : Void*

MBSTRING_UTF8 = 0x1000

fun x509_name_add_entry_by_txt = X509_NAME_add_entry_by_txt(name : X509_NAME, field : Char*, type : Int, bytes : Char*, len : Int, loc : Int, set : Int) : X509_NAME
fun x509_name_dup = X509_NAME_dup(a : X509_NAME) : X509_NAME
fun x509_name_entry_count = X509_NAME_entry_count(name : X509_NAME) : Int
fun x509_name_free = X509_NAME_free(a : X509_NAME)
fun x509_name_get_entry = X509_NAME_get_entry(name : X509_NAME, loc : Int) : X509_NAME_ENTRY
fun x509_name_get_index_by_nid = X509_NAME_get_index_by_NID(name : X509_NAME, nid : Int, lastpos : Int) : Int
fun x509_name_new = X509_NAME_new : X509_NAME

fun x509_name_entry_get_data = X509_NAME_ENTRY_get_data(ne : X509_NAME_ENTRY) : ASN1_STRING
fun x509_name_entry_get_object = X509_NAME_ENTRY_get_object(ne : X509_NAME_ENTRY) : ASN1_OBJECT

fun x509_extension_dup = X509_EXTENSION_dup(a : X509_EXTENSION) : X509_EXTENSION
fun x509_extension_free = X509_EXTENSION_free(a : X509_EXTENSION)
fun x509_extension_get_object = X509_EXTENSION_get_object(a : X509_EXTENSION) : ASN1_OBJECT
fun x509_extension_get_data = X509_EXTENSION_get_data(a : X509_EXTENSION) : ASN1_STRING
fun x509_extension_create_by_nid = X509_EXTENSION_create_by_NID(ex : X509_EXTENSION, nid : Int, crit : Int, data : ASN1_STRING) : X509_EXTENSION
fun x509v3_ext_nconf_nid = X509V3_EXT_nconf_nid(conf : Void*, ctx : Void*, ext_nid : Int, value : Char*) : X509_EXTENSION
fun x509v3_ext_print = X509V3_EXT_print(out : Bio*, ext : X509_EXTENSION, flag : Int, indent : Int) : Int
end

{% if `(pkg-config --atleast-version=1.0.2 libcrypto && echo -n true) || echo -n false` == "true" %}
9 changes: 6 additions & 3 deletions src/openssl/lib_ssl.cr
Original file line number Diff line number Diff line change
@@ -10,9 +10,9 @@ lib LibSSL
type SSLMethod = Void*
type SSLContext = Void*
type SSL = Void*
type X509StoreContext = Void*

alias VerifyCallback = (Int, X509StoreContext) -> Int
alias VerifyCallback = (Int, LibCrypto::X509_STORE_CTX) -> Int
alias CertVerifyCallback = (LibCrypto::X509_STORE_CTX, Void*) -> Int

enum SSLFileType
PEM = 1
@@ -161,6 +161,9 @@ lib LibSSL

@[Raises]
fun ssl_ctx_load_verify_locations = SSL_CTX_load_verify_locations(ctx : SSLContext, ca_file : UInt8*, ca_path : UInt8*) : Int

# hostname validation for OpenSSL <= 1.0.1
fun ssl_ctx_set_cert_verify_callback = SSL_CTX_set_cert_verify_callback(ctx : SSLContext, callback : CertVerifyCallback, arg : Void*)
end

{% if `(pkg-config --atleast-version=1.0.2 libssl && echo -n true) || echo -n false` == "true" %}
@@ -173,7 +176,7 @@ lib LibSSL
end
{% end %}

{% if LibSSL::OPENSSL_102 && LibCrypto::OPENSSL_102 %}
{% if LibSSL::OPENSSL_102 %}
lib LibSSL
alias ALPNCallback = (SSL, Char**, Char*, Char*, Int, Void*) -> Int
alias X509VerifyParam = LibCrypto::X509VerifyParam
1 change: 1 addition & 0 deletions src/openssl/openssl.cr
Original file line number Diff line number Diff line change
@@ -80,3 +80,4 @@ require "./bio"
require "./ssl/*"
require "./digest/*"
require "./md5"
require "./x509"
Loading