Skip to content

Commit fc37d47

Browse files
committed
Merge branch 'maint'
* maint: ssl: set verify error code in the case of verify_hostname failure x509: add error code and verify flags constants Remove taint support Restore compatibility with older versions of Ruby. Fix keyword argument separation issues in OpenSSL::SSL::SSLSocket#sys{read,write}_nonblock config: support .include directive
2 parents 58e9fb3 + ec65428 commit fc37d47

File tree

5 files changed

+229
-19
lines changed

5 files changed

+229
-19
lines changed

ext/openssl/ossl_ssl.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,14 @@ ossl_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
359359
rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status));
360360
return 0;
361361
}
362-
preverify_ok = ret == Qtrue;
362+
if (ret != Qtrue) {
363+
preverify_ok = 0;
364+
#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
365+
X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH);
366+
#else
367+
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED);
368+
#endif
369+
}
363370
}
364371

365372
return ossl_verify_cb_call(cb, preverify_ok, ctx);

ext/openssl/ossl_x509.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ Init_ossl_x509(void)
4444
Init_ossl_x509revoked();
4545
Init_ossl_x509store();
4646

47+
/* Constants are up-to-date with 1.1.1. */
48+
49+
/* Certificate verification error code */
4750
DefX509Const(V_OK);
51+
#if defined(X509_V_ERR_UNSPECIFIED) /* 1.0.1r, 1.0.2f, 1.1.0 */
52+
DefX509Const(V_ERR_UNSPECIFIED);
53+
#endif
4854
DefX509Const(V_ERR_UNABLE_TO_GET_ISSUER_CERT);
4955
DefX509Const(V_ERR_UNABLE_TO_GET_CRL);
5056
DefX509Const(V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE);
@@ -76,8 +82,73 @@ Init_ossl_x509(void)
7682
DefX509Const(V_ERR_AKID_SKID_MISMATCH);
7783
DefX509Const(V_ERR_AKID_ISSUER_SERIAL_MISMATCH);
7884
DefX509Const(V_ERR_KEYUSAGE_NO_CERTSIGN);
85+
DefX509Const(V_ERR_UNABLE_TO_GET_CRL_ISSUER);
86+
DefX509Const(V_ERR_UNHANDLED_CRITICAL_EXTENSION);
87+
DefX509Const(V_ERR_KEYUSAGE_NO_CRL_SIGN);
88+
DefX509Const(V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION);
89+
DefX509Const(V_ERR_INVALID_NON_CA);
90+
DefX509Const(V_ERR_PROXY_PATH_LENGTH_EXCEEDED);
91+
DefX509Const(V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE);
92+
DefX509Const(V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED);
93+
DefX509Const(V_ERR_INVALID_EXTENSION);
94+
DefX509Const(V_ERR_INVALID_POLICY_EXTENSION);
95+
DefX509Const(V_ERR_NO_EXPLICIT_POLICY);
96+
DefX509Const(V_ERR_DIFFERENT_CRL_SCOPE);
97+
DefX509Const(V_ERR_UNSUPPORTED_EXTENSION_FEATURE);
98+
DefX509Const(V_ERR_UNNESTED_RESOURCE);
99+
DefX509Const(V_ERR_PERMITTED_VIOLATION);
100+
DefX509Const(V_ERR_EXCLUDED_VIOLATION);
101+
DefX509Const(V_ERR_SUBTREE_MINMAX);
79102
DefX509Const(V_ERR_APPLICATION_VERIFICATION);
103+
DefX509Const(V_ERR_UNSUPPORTED_CONSTRAINT_TYPE);
104+
DefX509Const(V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX);
105+
DefX509Const(V_ERR_UNSUPPORTED_NAME_SYNTAX);
106+
DefX509Const(V_ERR_CRL_PATH_VALIDATION_ERROR);
107+
#if defined(X509_V_ERR_PATH_LOOP)
108+
DefX509Const(V_ERR_PATH_LOOP);
109+
#endif
110+
#if defined(X509_V_ERR_SUITE_B_INVALID_VERSION)
111+
DefX509Const(V_ERR_SUITE_B_INVALID_VERSION);
112+
DefX509Const(V_ERR_SUITE_B_INVALID_ALGORITHM);
113+
DefX509Const(V_ERR_SUITE_B_INVALID_CURVE);
114+
DefX509Const(V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM);
115+
DefX509Const(V_ERR_SUITE_B_LOS_NOT_ALLOWED);
116+
DefX509Const(V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256);
117+
#endif
118+
#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
119+
DefX509Const(V_ERR_HOSTNAME_MISMATCH);
120+
DefX509Const(V_ERR_EMAIL_MISMATCH);
121+
DefX509Const(V_ERR_IP_ADDRESS_MISMATCH);
122+
#endif
123+
#if defined(X509_V_ERR_DANE_NO_MATCH)
124+
DefX509Const(V_ERR_DANE_NO_MATCH);
125+
#endif
126+
#if defined(X509_V_ERR_EE_KEY_TOO_SMALL)
127+
DefX509Const(V_ERR_EE_KEY_TOO_SMALL);
128+
DefX509Const(V_ERR_CA_KEY_TOO_SMALL);
129+
DefX509Const(V_ERR_CA_MD_TOO_WEAK);
130+
#endif
131+
#if defined(X509_V_ERR_INVALID_CALL)
132+
DefX509Const(V_ERR_INVALID_CALL);
133+
#endif
134+
#if defined(X509_V_ERR_STORE_LOOKUP)
135+
DefX509Const(V_ERR_STORE_LOOKUP);
136+
#endif
137+
#if defined(X509_V_ERR_NO_VALID_SCTS)
138+
DefX509Const(V_ERR_NO_VALID_SCTS);
139+
#endif
140+
#if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION)
141+
DefX509Const(V_ERR_PROXY_SUBJECT_NAME_VIOLATION);
142+
#endif
143+
#if defined(X509_V_ERR_OCSP_VERIFY_NEEDED)
144+
DefX509Const(V_ERR_OCSP_VERIFY_NEEDED);
145+
DefX509Const(V_ERR_OCSP_VERIFY_FAILED);
146+
DefX509Const(V_ERR_OCSP_CERT_UNKNOWN);
147+
#endif
80148

149+
/* Certificate verify flags */
150+
/* Set by Store#flags= and StoreContext#flags=. */
151+
DefX509Const(V_FLAG_USE_CHECK_TIME);
81152
/* Set by Store#flags= and StoreContext#flags=. Enables CRL checking for the
82153
* certificate chain leaf. */
83154
DefX509Const(V_FLAG_CRL_CHECK);
@@ -122,6 +193,26 @@ Init_ossl_x509(void)
122193
* Enabled by default in OpenSSL >= 1.1.0. */
123194
DefX509Const(V_FLAG_TRUSTED_FIRST);
124195
#endif
196+
#if defined(X509_V_FLAG_SUITEB_128_LOS_ONLY)
197+
/* Set by Store#flags= and StoreContext#flags=.
198+
* Enables Suite B 128 bit only mode. */
199+
DefX509Const(V_FLAG_SUITEB_128_LOS_ONLY);
200+
#endif
201+
#if defined(X509_V_FLAG_SUITEB_192_LOS)
202+
/* Set by Store#flags= and StoreContext#flags=.
203+
* Enables Suite B 192 bit only mode. */
204+
DefX509Const(V_FLAG_SUITEB_192_LOS);
205+
#endif
206+
#if defined(X509_V_FLAG_SUITEB_128_LOS)
207+
/* Set by Store#flags= and StoreContext#flags=.
208+
* Enables Suite B 128 bit mode allowing 192 bit algorithms. */
209+
DefX509Const(V_FLAG_SUITEB_128_LOS);
210+
#endif
211+
#if defined(X509_V_FLAG_PARTIAL_CHAIN)
212+
/* Set by Store#flags= and StoreContext#flags=.
213+
* Allows partial chains if at least one certificate is in trusted store. */
214+
DefX509Const(V_FLAG_PARTIAL_CHAIN);
215+
#endif
125216
#if defined(X509_V_FLAG_NO_ALT_CHAINS)
126217
/* Set by Store#flags= and StoreContext#flags=. Suppresses searching for
127218
* a alternative chain. No effect in OpenSSL >= 1.1.0. */

lib/openssl/config.rb

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,29 +76,44 @@ def get_key_string(data, section, key) # :nodoc:
7676
def parse_config_lines(io)
7777
section = 'default'
7878
data = {section => {}}
79-
while definition = get_definition(io)
79+
io_stack = [io]
80+
while definition = get_definition(io_stack)
8081
definition = clear_comments(definition)
8182
next if definition.empty?
82-
if definition[0] == ?[
83+
case definition
84+
when /\A\[/
8385
if /\[([^\]]*)\]/ =~ definition
8486
section = $1.strip
8587
data[section] ||= {}
8688
else
8789
raise ConfigError, "missing close square bracket"
8890
end
89-
else
90-
if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition
91-
if $2
92-
section = $1
93-
key = $2
94-
else
95-
key = $1
91+
when /\A\.include (\s*=\s*)?(.+)\z/
92+
path = $2
93+
if File.directory?(path)
94+
files = Dir.glob(File.join(path, "*.{cnf,conf}"), File::FNM_EXTGLOB)
95+
else
96+
files = [path]
97+
end
98+
99+
files.each do |filename|
100+
begin
101+
io_stack << StringIO.new(File.read(filename))
102+
rescue
103+
raise ConfigError, "could not include file '%s'" % filename
96104
end
97-
value = unescape_value(data, section, $3)
98-
(data[section] ||= {})[key] = value.strip
105+
end
106+
when /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/
107+
if $2
108+
section = $1
109+
key = $2
99110
else
100-
raise ConfigError, "missing equal sign"
111+
key = $1
101112
end
113+
value = unescape_value(data, section, $3)
114+
(data[section] ||= {})[key] = value.strip
115+
else
116+
raise ConfigError, "missing equal sign"
102117
end
103118
end
104119
data
@@ -211,10 +226,10 @@ def clear_comments(line)
211226
scanned.join
212227
end
213228

214-
def get_definition(io)
215-
if line = get_line(io)
229+
def get_definition(io_stack)
230+
if line = get_line(io_stack)
216231
while /[^\\]\\\z/ =~ line
217-
if extra = get_line(io)
232+
if extra = get_line(io_stack)
218233
line += extra
219234
else
220235
break
@@ -224,9 +239,12 @@ def get_definition(io)
224239
end
225240
end
226241

227-
def get_line(io)
228-
if line = io.gets
229-
line.gsub(/[\r\n]*/, '')
242+
def get_line(io_stack)
243+
while io = io_stack.last
244+
if line = io.gets
245+
return line.gsub(/[\r\n]*/, '')
246+
end
247+
io_stack.pop
230248
end
231249
end
232250
end

test/openssl/test_config.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,49 @@ def test_s_parse_format
120120
assert_equal("error in line 7: missing close square bracket", excn.message)
121121
end
122122

123+
def test_s_parse_include
124+
in_tmpdir("ossl-config-include-test") do |dir|
125+
Dir.mkdir("child")
126+
File.write("child/a.conf", <<~__EOC__)
127+
[default]
128+
file-a = a.conf
129+
[sec-a]
130+
a = 123
131+
__EOC__
132+
File.write("child/b.cnf", <<~__EOC__)
133+
[default]
134+
file-b = b.cnf
135+
[sec-b]
136+
b = 123
137+
__EOC__
138+
File.write("include-child.conf", <<~__EOC__)
139+
key_outside_section = value_a
140+
.include child
141+
__EOC__
142+
143+
include_file = <<~__EOC__
144+
[default]
145+
file-main = unnamed
146+
[sec-main]
147+
main = 123
148+
.include = include-child.conf
149+
__EOC__
150+
151+
# Include a file by relative path
152+
c1 = OpenSSL::Config.parse(include_file)
153+
assert_equal(["default", "sec-a", "sec-b", "sec-main"], c1.sections.sort)
154+
assert_equal(["file-main", "file-a", "file-b"], c1["default"].keys)
155+
assert_equal({"a" => "123"}, c1["sec-a"])
156+
assert_equal({"b" => "123"}, c1["sec-b"])
157+
assert_equal({"main" => "123", "key_outside_section" => "value_a"}, c1["sec-main"])
158+
159+
# Relative paths are from the working directory
160+
assert_raise(OpenSSL::ConfigError) do
161+
Dir.chdir("child") { OpenSSL::Config.parse(include_file) }
162+
end
163+
end
164+
end
165+
123166
def test_s_load
124167
# alias of new
125168
c = OpenSSL::Config.load
@@ -315,6 +358,17 @@ def test_clone
315358
assert_not_equal(@it.sections.sort, c.sections.sort)
316359
end
317360
end
361+
362+
private
363+
364+
def in_tmpdir(*args)
365+
Dir.mktmpdir(*args) do |dir|
366+
dir = File.realpath(dir)
367+
Dir.chdir(dir) do
368+
yield dir
369+
end
370+
end
371+
end
318372
end
319373

320374
end

test/openssl/test_ssl.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,46 @@ def test_verify_hostname_on_connect
939939
end
940940
end
941941

942+
def test_verify_hostname_failure_error_code
943+
ctx_proc = proc { |ctx|
944+
exts = [
945+
["keyUsage", "keyEncipherment,digitalSignature", true],
946+
["subjectAltName", "DNS:a.example.com"],
947+
]
948+
ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
949+
ctx.key = @svr_key
950+
}
951+
952+
start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
953+
verify_callback_ok = verify_callback_err = nil
954+
955+
ctx = OpenSSL::SSL::SSLContext.new
956+
ctx.verify_hostname = true
957+
ctx.cert_store = OpenSSL::X509::Store.new
958+
ctx.cert_store.add_cert(@ca_cert)
959+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
960+
ctx.verify_callback = -> (preverify_ok, store_ctx) {
961+
verify_callback_ok = preverify_ok
962+
verify_callback_err = store_ctx.error
963+
preverify_ok
964+
}
965+
966+
begin
967+
sock = TCPSocket.new("127.0.0.1", port)
968+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
969+
ssl.hostname = "b.example.com"
970+
assert_handshake_error { ssl.connect }
971+
assert_equal false, verify_callback_ok
972+
code_expected = openssl?(1, 0, 2) || defined?(OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH) ?
973+
OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH :
974+
OpenSSL::X509::V_ERR_CERT_REJECTED
975+
assert_equal code_expected, verify_callback_err
976+
ensure
977+
sock&.close
978+
end
979+
end
980+
end
981+
942982
def test_connect_certificate_verify_failed_exception_message
943983
start_server(ignore_listener_error: true) { |port|
944984
ctx = OpenSSL::SSL::SSLContext.new

0 commit comments

Comments
 (0)