From 022bc99eb129d6ecc489d53541df1b94dc34e900 Mon Sep 17 00:00:00 2001 From: Yuta Iwama Date: Fri, 3 Apr 2020 17:10:46 +0900 Subject: [PATCH] Allow client auth with cert chain Signed-off-by: Yuta Iwama --- lib/fluent/plugin_helper/socket.rb | 20 ++- .../data/cert/cert_chains/ca-cert-key.pem | 27 ++++ .../data/cert/cert_chains/ca-cert.pem | 20 +++ .../data/cert/cert_chains/cert-key.pem | 27 ++++ .../data/cert/cert_chains/cert.pem | 40 +++++ test/plugin_helper/data/cert/generate_cert.rb | 38 +++++ test/plugin_helper/test_socket.rb | 152 ++++++++++++++++++ 7 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 test/plugin_helper/data/cert/cert_chains/ca-cert-key.pem create mode 100644 test/plugin_helper/data/cert/cert_chains/ca-cert.pem create mode 100644 test/plugin_helper/data/cert/cert_chains/cert-key.pem create mode 100644 test/plugin_helper/data/cert/cert_chains/cert.pem create mode 100644 test/plugin_helper/test_socket.rb diff --git a/lib/fluent/plugin_helper/socket.rb b/lib/fluent/plugin_helper/socket.rb index 193313996a..d40db8a30a 100644 --- a/lib/fluent/plugin_helper/socket.rb +++ b/lib/fluent/plugin_helper/socket.rb @@ -146,8 +146,15 @@ def socket_create_tls( context.verify_mode = OpenSSL::SSL::VERIFY_PEER context.cert_store = cert_store context.verify_hostname = true if verify_fqdn && fqdn && context.respond_to?(:verify_hostname=) - context.cert = OpenSSL::X509::Certificate.new(File.read(cert_path)) if cert_path context.key = OpenSSL::PKey::read(File.read(private_key_path), private_key_passphrase) if private_key_path + + if cert_path + certs = socket_certificates_from_file(cert_path) + context.cert = certs.shift + unless certs.empty? + context.extra_chain_cert = certs + end + end end Fluent::TLS.set_version_to_context(context, version, min_version, max_version) @@ -186,6 +193,17 @@ def socket_create_tls( end end + def socket_certificates_from_file(path) + data = File.read(path) + pattern = Regexp.compile('-+BEGIN CERTIFICATE-+\r?\n(?:[^-]*\r?\n)+-+END CERTIFICATE-+\r?\n?', Regexp::MULTILINE) + list = [] + data.scan(pattern) { |match| list << OpenSSL::X509::Certificate.new(match) } + if list.length == 0 + log.warn "cert_path does not contain a valid certificate" + end + list + end + def self.tls_verify_result_name(code) case code when OpenSSL::X509::V_OK then 'V_OK' diff --git a/test/plugin_helper/data/cert/cert_chains/ca-cert-key.pem b/test/plugin_helper/data/cert/cert_chains/ca-cert-key.pem new file mode 100644 index 0000000000..2b3a81a620 --- /dev/null +++ b/test/plugin_helper/data/cert/cert_chains/ca-cert-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA21in8qRqBrfYlRHr1N6eGq3d4CnKe+lTt0sGbab1670s0+h1 +lGtjWny6gLSnt5PBY36CUoFB8MtRciVG2zUhB+dAZe4Mst5dzJO6cONT/l/USbCV +rQ/x8m3YzeJNj89fCZ7/7po8CpEfn16BMAd6TMXZZ8O+DCgv3SliEyxpi9CLg2PK +wAV4lwktbGenYadWLFWjv5a+QElFGH/tu5jUDhggoisoQGyZCls2Vmbr6RH+uiy8 +js3gxy+5YgbuwkrQtjbfwxe5yEsd6RgzO8oLqlowCwCrn4CNaXFwaZa/vqcWzY4L +qiEWLfmFpFcSZdoWjr1WHsJS/PM3AyJea8nAnQIDAQABAoIBAGkMpaqsmWbMR7rl +EVgqoffPCzMfcK01ivV+xf5f9ulG+aAndaB2aefdUojvfF+MMRNQdGPFKeqDxWbw +eWXkpQQe+ZWXk5darfubSLBl/0UVahs8qgJvX4WmnC3GUzUrsK1v68y/K0A4TrfJ +z/9LpYP9QWjTs0IpQPsfpavfGlFt1TJvPxmomn7D+n0N6zzD3kFlGdeCSYwtPURR +dJ7ifX2vU4E8UtSm7gLrXlmAl1O7bJjFvL3+QlzKDM4M1WJpA9MkmM2icKxdYpew +biRmIiUHdV4soad2Aq58v4q5UgdMqGI8f+fWPO9qgXN7AKbT9NkXr1w/pziOt5rq +/77lbKECgYEA9bmOSA1LfRRclJCaoWWu4PiaXdvMHvsNC9r8NUGuwmiosEMKHFEJ +z8wvzVXprziXAwDTwqkPJmKseNPC8WCq2GfNu7lqJ1V1wHxpzHKhiAWhs7rE7skW +mF2xn2Tocba/L3Nb+K0tXhOZ7daduz94YXFhmRtsutZ9JvFv3G+QmHkCgYEA5IS4 ++cOFyha486U9jXyLcPmZXZY9ql4ILuKuQ5pHA7CBO/zU2behuwcainW6P0WzbhKs +CP8xR5W/otqcaHZxy6hg9/nUFVVZIR/0jgKyxCqJA1W49jt23gykmU6q0vj7haZJ +QMhcAbleHBC7YvYoMTpTMe7tZP4YsFNysBn76EUCgYBqtFAn07YjM7NcREsRqSE+ +ylXmSisijOxGaKq6ybIE9APEvufmEf7LwKRFa3hVwaI6CKLsVhOhHJo+wd5WiR7H +aJQ7X7HMMN04YA5lXKXudluYu5MHCkWIlq8qQ1x4/N2a0mJu42ze/G4MjPTjuhUh +Y2X5YaJepAOm5JMpyzykKQKBgQDj9eaU+dBgLdSo8UD7ALAVrlio/HRdnNoq82SF ++cQ30P7KucgXvFDxQv/d+d0mu0BoYOYPP4uIbsEyE0SODQIt+LVrCmTgNzjni3op +pFVyzT/K/Nu7fsxwbEpSySAtv8UhqSVQI89sxN81vhdAfHDR0u4lVMSqx7QXSdeS +Bwm9xQKBgQDSeoHoXbmiqRBss9Em69JPwWRZOc9eLns+fFJ1x/WkhY4bnnVXTYkZ +LzRN4ICq18xHy8egbl93uxDcjkMxi3Wj64QZcygI9gWmDBW2hdMpEArR/grsM/FH +DYu9KUMhm5T1KtXtV3izSVCprthdGjbKSbmR6hGw3n9Z1uP6V1rnXQ== +-----END RSA PRIVATE KEY----- diff --git a/test/plugin_helper/data/cert/cert_chains/ca-cert.pem b/test/plugin_helper/data/cert/cert_chains/ca-cert.pem new file mode 100644 index 0000000000..b96f85d409 --- /dev/null +++ b/test/plugin_helper/data/cert/cert_chains/ca-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgILAJJ76Kpa6DYtsncwDQYJKoZIhvcNAQELBQAwUzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MR8w +HQYDVQQDDBZjYS50ZXN0aW5nLmZsdWVudGQub3JnMCAXDTcwMDEwMTAwMDAwMFoY +DzIxMTgxMDI3MDgwNjU3WjBTMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAU +BgNVBAcMDU1vdW50YWluIFZpZXcxHzAdBgNVBAMMFmNhLnRlc3RpbmcuZmx1ZW50 +ZC5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbWKfypGoGt9iV +EevU3p4ard3gKcp76VO3SwZtpvXrvSzT6HWUa2NafLqAtKe3k8FjfoJSgUHwy1Fy +JUbbNSEH50Bl7gyy3l3Mk7pw41P+X9RJsJWtD/HybdjN4k2Pz18Jnv/umjwKkR+f +XoEwB3pMxdlnw74MKC/dKWITLGmL0IuDY8rABXiXCS1sZ6dhp1YsVaO/lr5ASUUY +f+27mNQOGCCiKyhAbJkKWzZWZuvpEf66LLyOzeDHL7liBu7CStC2Nt/DF7nISx3p +GDM7yguqWjALAKufgI1pcXBplr++pxbNjguqIRYt+YWkVxJl2haOvVYewlL88zcD +Il5rycCdAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AGPhEAc+vb9PHyet9t5gKJTZF7S4x207hKZ0zLqCQCLM7VjrTVBh/TUFT+saiPsv +4k2TYSZKInYXh6tkSzdGlRyzk166hF1eOTOae/92Da5PEQMT0AC+SDRDHXrNTyD8 +ZxP2xOl0j+FNeKQDdNL+PCU56PYehY40dYZ/EjRyu4VjKaSCMHcVx7eMTyxU3wtB +HbOa8UJdUXEjn4h7icz4aca22aqLedXNaDr6YX+mo25t1jsTO7cEMfr4Ce7GvxdA +msey/b5jnsKcpL7/2uYz5+owD39JZQBRQt+1YAzIU+BNiz8yzdXJO4aQpGWTflIB +jO31o7x4loEvaBTQbX3iP2g= +-----END CERTIFICATE----- diff --git a/test/plugin_helper/data/cert/cert_chains/cert-key.pem b/test/plugin_helper/data/cert/cert_chains/cert-key.pem new file mode 100644 index 0000000000..5029a1d79e --- /dev/null +++ b/test/plugin_helper/data/cert/cert_chains/cert-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAzQtGZgClcBIDKC0U1sQpznVV9HzAZANDVazrDGA8Euf8Kx4X +dnVoE+HzwAviL4VLDPzNBLyoZ2wf3wg6D+1UAhA0DqPRCihhfJqBX0WgewakSl5a +wrc1XIXtiMxcWE/5+XtodjO71gECBgmd/l0UtdFY7mMt/gArH8PQf29ABGiB1zzn +d0MNgA0r2OlXICe4kDzTHqDBv7nFgQFro2vkI/QJD5fABbb62HRWgUfQ13DUShSG +H5FPkMxTkCs5GShyjYCC5IhI+AKx5RWvb4kuMRsxWC7JHc/XKPdbeLkNWRCFzTnO +OR09Qdp45tBbXtX0IjInxsnNOBzW0zTSg0xUtwIDAQABAoIBAG4N5zNIlYOZp2gh +ClZb47SU9hXL/9euiK2rql1yKcxcB9V8yUsjqUFCvfoOZtDq0mWeKsyoFhusxU6I +s+FomPaii85vzvuMwQaIR3hDfueJoRTpn/1zKIkIuX37cnVUN+/YdTE8g01SLSvg +bZThkQQl4X3SbhUvMfZSu84qgEncdtJ08zx81CB1Fpn0HIjpmpRM1F9jjDwtkVUw +1gVQm9g7fwbgRfqSUyKkEqKvCVYnLQx3X5inuAEps4zIzAXF8RtxFymyHfExzZqr +Pj5uYjTbf/wkaU9nWQzXeCbRTz3ZrjvFnisFt1HI2uijd1DjJ7CB7Ls752RWekXS +Yc9FFwECgYEA/gZ3Hx4dIb6NW6UmPKjiQex/tVqwCRaxGtpA7qensf187N2nn4FD +jdQJ69XmoDRKr2X+e7tsgKpETGufdDQ8MNBHz2krfqXVSlsEIOjeLfRRqhh+8Bcr +flri0sumVbk0X9bnXzyCc4AGcRhtT9U69HCMTlly2z0pjIlVvmFXARECgYEAzqNV +FSHHbEU+5zToJK0jyTaE/F+u9n5YKDcRN9RrKrEYcoUzKbIYJrUulbRGfsqAxY5L +KtIDRI9NmPDBYqBwqSFnm9LcLn8CgQH/rRSFJv1Iw+sPHd/Nrk1KWjpO0OYRgfiB +oxcgGSzHp0OZP+e7Trtq1TKW6VyadEsPZ7oKeUcCgYB9TiMkrm4gXybLtkOOWKCD +dG3qv7lmQlNKs66kCv+lxS0CirRM8i6on5flRbZmAGV28BEAaAu1zEe0isI1SC8I +xTUnEvHpn1P/QbZfpX8zm/lMtpinRkamJZ8N7Hc4ggtb2152lBqlbtm+oBYL81sJ +iRss6uLFUv5T3Mr3Bn0sgQKBgGr4xQf+h6V2J307t12dQCRfE/MueX3jpDGVaFV1 +otDkAxrt97GDH9uR+f7H56KlpIohAqq1M7nfUbV2FTbAhfIYd/GD9DYhzCMK7Ngm +AlRP1MaPvjCh9nFgU7hn7PtZzwBwrHPIefZuZyEg7onVpfK5NTIPUW6XYOIJJX12 +IwvrAoGAGwg2Cq6Vs/KkiCDxoy42HVZLS/DS/iHfUTAXtekF88qwT1zHFqF1ImvO ++FEUx8IbxY1oifSQc3Jw9HYnmYqmFW1PThNG3CwyaOGgtlEFcvhHsNjLRGJSQbN2 +cUYBe2hHXii7OL8T8kIvLlCAKGA8IIfPGhsu3uUtrvMBo5c0WmQ= +-----END RSA PRIVATE KEY----- diff --git a/test/plugin_helper/data/cert/cert_chains/cert.pem b/test/plugin_helper/data/cert/cert_chains/cert.pem new file mode 100644 index 0000000000..0dcc949fe6 --- /dev/null +++ b/test/plugin_helper/data/cert/cert_chains/cert.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDQjCCAiqgAwIBAgILANkmymLgb5e04tcwDQYJKoZIhvcNAQELBQAwVDELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MSAw +HgYDVQQDDBdjYTIudGVzdGluZy5mbHVlbnRkLm9yZzAgFw03MDAxMDEwMDAwMDBa +GA8yMTE4MTAyNzA4MDY1N1owVzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYw +FAYDVQQHDA1Nb3VudGFpbiBWaWV3MSMwIQYDVQQDDBpzZXJ2ZXIudGVzdGluZy5m +bHVlbnRkLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM0LRmYA +pXASAygtFNbEKc51VfR8wGQDQ1Ws6wxgPBLn/CseF3Z1aBPh88AL4i+FSwz8zQS8 +qGdsH98IOg/tVAIQNA6j0QooYXyagV9FoHsGpEpeWsK3NVyF7YjMXFhP+fl7aHYz +u9YBAgYJnf5dFLXRWO5jLf4AKx/D0H9vQARogdc853dDDYANK9jpVyAnuJA80x6g +wb+5xYEBa6Nr5CP0CQ+XwAW2+th0VoFH0Ndw1EoUhh+RT5DMU5ArORkoco2AguSI +SPgCseUVr2+JLjEbMVguyR3P1yj3W3i5DVkQhc05zjkdPUHaeObQW17V9CIyJ8bJ +zTgc1tM00oNMVLcCAwEAAaMQMA4wDAYDVR0TBAUwAwEBADANBgkqhkiG9w0BAQsF +AAOCAQEAMzoxZJqhu8zyKI20Hz5SAqva+jcHbZbR9AXlt4LWQEttsXy4S52XLZBB +4Es/Fah4lyou9R1xdUfF6iGBV6HgDMIuh+QYfqA/jJ4HXNMKLtDXakS8xYp2A0Wn +g0EnjGx44DNweBkKGB9asldgwOM7MYMoQvS3Nkyu3oFqpfLpuWpWB/3Vw3PMGEdx +r/c8Ev5XoVtl2+dXszsEp2jwUOFo8+DZzFdBZdA2F7RmcP+zxbAHIw/jHf/ptDGg +NtzHmLhxiLblGlJHw/4a0HhXa453jzLsD1+hQpqBRUNbXBp1qpjqEr5SF1iVNGpZ +peWFawp53MkIBXRls66ViJsl//fPBg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIKRbRr9ZfmrY72TjANBgkqhkiG9w0BAQsFADBTMQswCQYD +VQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxHzAd +BgNVBAMMFmNhLnRlc3RpbmcuZmx1ZW50ZC5vcmcwIBcNNzAwMTAxMDAwMDAwWhgP +MjExODEwMjcwODA2NTdaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQG +A1UEBwwNTW91bnRhaW4gVmlldzEgMB4GA1UEAwwXY2EyLnRlc3RpbmcuZmx1ZW50 +ZC5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6gnGxD4RQuAA1 +fQNMl9mctiIvcJ0Dz5f3JtCJlKKqBDKJ17py7tjFZ2MZ5vytIl1DUU2oxHew8F/u +U8s+8ljmL3seiXx2kMCOxCvSW3JYUQrzbTHKszna7Ffy94SzpRDh/4/2RGatnSqi +n4fRpalrchhH6Ds6v486o7YcwEKPR2dlD+Ltd/V3lMAOAmoRoe2aQlLudOUQjHJU +YWGim8XS2dNAEy4WR5DvjYEA+CwKEX7f9p0Dihs8FKBjB2TvBwUkdfILLEnwA1rs +r0coUt3kwYt9TYNYv6/SZYvFtaYjM3BmLVHI3mtEchaA+Tij4V4gRZ573Ai1+Fb8 +rxn+oTsVAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AHID7VD2PS7svmCXRg4qe7MkhA0veIadZRE6WZgJo3Z64p9jUWu/Y3ZrlZuDg0qI +yV65UgWrgUOiGN6RZmI5A850DH3ryjvQElPHWjRavm0GtrOxFhHYpX8N7DRFW7t0 +7mld7YvbKRj0BrovU3avPC1P9w8FpegMKh48HsoRCO3NhHvYwtAf2B767UuAaqnI +DW9rMPRTZv+Pv+G+kJN+updOs6HiHFxeS8Bwb8GlADcwWNGMqyJJyKmaefYVzDKt +xfBiKiwev6Hm8RVTk1H3rKVZox/ZkDiZfZhmBJqO4mdDR4LLFIRIPqjUPxENsKFJ +MyrDcwH+0Aq9ZSiRaVyfCkQ= +-----END CERTIFICATE----- diff --git a/test/plugin_helper/data/cert/generate_cert.rb b/test/plugin_helper/data/cert/generate_cert.rb index 81f8a8edd6..519eae0787 100644 --- a/test/plugin_helper/data/cert/generate_cert.rb +++ b/test/plugin_helper/data/cert/generate_cert.rb @@ -7,6 +7,7 @@ module CertUtil WITHOUT_CA_DIR = './without_ca'.freeze WITH_CA_DIR = './with_ca'.freeze +WITH_CERT_CHAIN_DIR = './cert_chains'.freeze CA_OPTION = { private_key_length: 2048, @@ -83,5 +84,42 @@ def create_with_ca create_server_pair_signed_by_ca(ca_cert_pass_path, ca_key_pass_path, 'orange', cert_pass_path, cert_key_pass_path, 'apple') end +def create_cert_pair_chained_with_root_ca(ca_cert_path, ca_key_path, ca_key_passphrase, cert_path, private_key_path, passphrase) + root_cert, root_key, _ = CertUtil.cert_option_generate_ca_pair_self_signed(CA_OPTION) + write_cert_and_key(ca_cert_path, root_cert, ca_key_path, root_key, ca_key_passphrase) + + intermediate_ca_options = CA_OPTION.dup + intermediate_ca_options[:common_name] = 'ca2.testing.fluentd.org' + chain_cert, chain_key = CertUtil.cert_option_generate_pair(intermediate_ca_options, root_cert.subject) + chain_cert.add_extension(OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(true)]))) + chain_cert.sign(root_key, 'sha256') + + cert, server_key, _ = CertUtil.cert_option_generate_pair(SERVER_OPTION, chain_cert.subject) + cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(false)])) + cert.sign(chain_key, 'sha256') + + # write chained cert + File.open(cert_path, 'w') do |f| + f.write(cert.to_pem) + f.write(chain_cert.to_pem) + end + + key_str = passphrase ? server_key.export(OpenSSL::Cipher.new("AES-256-CBC"), passphrase) : server_key.export + File.open(private_key_path, "w") { |f| f.write(key_str) } + File.chmod(0600, cert_path, private_key_path) +end + +def create_cert_chain + FileUtils.mkdir_p(WITH_CERT_CHAIN_DIR) + ca_cert_path = File.join(WITH_CERT_CHAIN_DIR, 'ca-cert.pem') + ca_key_path = File.join(WITH_CERT_CHAIN_DIR, 'ca-cert-key.pem') + + cert_path = File.join(WITH_CERT_CHAIN_DIR, 'cert.pem') + private_key_path = File.join(WITH_CERT_CHAIN_DIR, 'cert-key.pem') + + create_server_pair_chained_with_root_ca(ca_cert_path, ca_key_path, nil, cert_path, private_key_path, nil) +end + create_without_ca create_with_ca +create_cert_chain diff --git a/test/plugin_helper/test_socket.rb b/test/plugin_helper/test_socket.rb new file mode 100644 index 0000000000..f1fbc91a42 --- /dev/null +++ b/test/plugin_helper/test_socket.rb @@ -0,0 +1,152 @@ +require_relative '../helper' +require 'fluent/plugin_helper/socket' +require 'fluent/plugin/base' +require 'fluent/plugin_helper/cert_option' # to create certs for tests + +require 'socket' +require 'openssl' + +class SocketHelperTest < Test::Unit::TestCase + PORT = unused_port + CERT_DIR = File.expand_path(File.dirname(__FILE__) + '/data/cert/without_ca') + CA_CERT_DIR = File.expand_path(File.dirname(__FILE__) + '/data/cert/with_ca') + CERT_CHAINS_DIR = File.expand_path(File.dirname(__FILE__) + '/data/cert/cert_chains') + + class SocketHelperTestPlugin < Fluent::Plugin::TestBase + helpers :socket + end + + module CertUtil + extend Fluent::PluginHelper::CertOption + end + + class EchoTLSServer + def initialize(host = '127.0.0.1', port = PORT, cert_path: nil, private_key_path: nil, ca_path: nil) + server = TCPServer.open(host, port) + ctx = OpenSSL::SSL::SSLContext.new + if cert_path + certs = CertUtil.cert_option_certificates_from_file(cert_path) + ctx.cert = certs.shift + unless certs.empty? + ctx.extra_chain_cert = certs + end + end + + cert_store = OpenSSL::X509::Store.new + cert_store.set_default_paths + if ca_path + cert_store.add_file(ca_path) + end + ctx.cert_store = cert_store + + ctx.key = OpenSSL::PKey::RSA.new(File.open(private_key_path)) if private_key_path + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + # ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE + ctx.verify_hostname = false + + @server = OpenSSL::SSL::SSLServer.new(server, ctx) + @thread = nil + @r, @w = IO.pipe + end + + def start + do_start + + if block_given? + begin + yield + join + rescue + # stop + raise + end + end + end + + def stop + unless @w.closed? + @w.write('stop') + end + + @server.close unless @server.closed? + @r.close unless @r.closed? + + @thread.join + end + + def join(timeout = 10) + @thread.join(timeout) + [@server, @w, @r].each do |s| + next if s.closed? + s.close + end + end + + private + + def do_start + @thread = Thread.new(@server) do |s| + socks, _, _ = IO.select([s.accept, @r], nil, nil, 1) + + if socks.include?(@r) + break + end + + sock = socks.first + buf = +'' + loop do + b = sock.read_nonblock(1024, nil, exception: false) + if b == :wait_readable || b.nil? + break + end + buf << b + end + + sock.write(buf) + sock.close + end + end + end + + test 'with self-signed cert/key pair' do + cert_path = File.join(CERT_DIR, 'cert.pem') + private_key_path = File.join(CERT_DIR, 'cert-key.pem') + + EchoTLSServer.new(cert_path: cert_path, private_key_path: private_key_path).start do + client = SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', PORT, verify_fqdn: false, cert_paths: [cert_path]) + client.write('hello') + assert_equal 'hello', client.readpartial(100) + client.close + end + end + + test 'with cert/key signed by self-signed CA' do + cert_path = File.join(CA_CERT_DIR, 'cert.pem') + private_key_path = File.join(CA_CERT_DIR, 'cert-key.pem') + + ca_cert_path = File.join(CA_CERT_DIR, 'ca-cert.pem') + + EchoTLSServer.new(cert_path: cert_path, private_key_path: private_key_path).start do + client = SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', PORT, verify_fqdn: false, cert_paths: [ca_cert_path]) + client.write('hello') + assert_equal 'hello', client.readpartial(100) + client.close + end + end + + test 'with cert/key signed by self-signed CA in server and client cert chain' do + cert_path = File.join(CERT_DIR, 'cert.pem') + private_key_path = File.join(CERT_DIR, 'cert-key.pem') + + client_ca_cert_path = File.join(CERT_CHAINS_DIR, 'ca-cert.pem') + client_cert_path = File.join(CERT_CHAINS_DIR, 'cert.pem') + client_private_key_path = File.join(CERT_CHAINS_DIR, 'cert-key.pem') + + EchoTLSServer.new(cert_path: cert_path, private_key_path: private_key_path, ca_path: client_ca_cert_path).start do + client = SocketHelperTestPlugin.new.socket_create_tls('127.0.0.1', PORT, verify_fqdn: false, cert_path: client_cert_path, private_key_path: client_private_key_path, cert_paths: [cert_path]) + client.write('hello') + assert_equal 'hello', client.readpartial(100) + client.close + end + end +end