Skip to content

Commit c2601b9

Browse files
karesclaude
andcommitted
[fix] verify_result reports correct error with VERIFY_NONE (#25)
C OpenSSL always runs ssl_verify_cert_chain and stores the result; VERIFY_NONE only suppresses the handshake abort. JRuby was skipping verification entirely when VERIFY_NONE was set, leaving verify_result at the bogus default of 1. Now always runs verification to populate the result (e.g. V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT for self-signed certs) and only throws CertificateException when VERIFY_PEER is set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bdcb9a3 commit c2601b9

2 files changed

Lines changed: 104 additions & 11 deletions

File tree

src/main/java/org/jruby/ext/openssl/SSLContext.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ public SSLContext(Ruby runtime, RubyClass type) {
310310
private PKey t_key;
311311
private X509Cert t_cert;
312312

313-
private int verifyResult = 1; /* avoid 0 (= X509_V_OK) just in case */
313+
private int verifyResult = X509Utils.V_OK; // C OpenSSL initializes to X509_V_OK
314314

315315
//private int sessionCacheMode; // 2 default on MRI
316316
private int sessionCacheSize; // 20480
@@ -1217,18 +1217,23 @@ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
12171217
}
12181218

12191219
// c: ssl_verify_cert_chain
1220+
// C OpenSSL always runs verification to populate verify_result
1221+
// (accessible via SSL_get_verify_result), but only aborts the
1222+
// handshake when VERIFY_PEER is set. VERIFY_NONE still records
1223+
// the error (e.g. V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) for
1224+
// inspection after connect.
12201225
private void checkTrusted(final String purpose, final X509Certificate[] chain) throws CertificateException {
12211226
if ( chain != null && chain.length > 0 ) {
1222-
if ( (internalContext.verifyMode & SSL.VERIFY_PEER) != 0 ) {
1223-
// verify_peer
1224-
final StoreContext storeContext = internalContext.createStoreContext(purpose);
1225-
if ( storeContext == null ) {
1227+
final StoreContext storeContext = internalContext.createStoreContext(purpose);
1228+
if ( storeContext == null ) {
1229+
if ( (internalContext.verifyMode & SSL.VERIFY_PEER) != 0 ) {
12261230
throw new CertificateException("couldn't initialize store");
12271231
}
1228-
storeContext.setCertificate(chain[0]);
1229-
storeContext.setChain(chain);
1230-
verifyChain(storeContext);
1232+
return;
12311233
}
1234+
storeContext.setCertificate(chain[0]);
1235+
storeContext.setChain(chain);
1236+
verifyChain(storeContext, (internalContext.verifyMode & SSL.VERIFY_PEER) != 0);
12321237
} else {
12331238
if ( (internalContext.verifyMode & SSL.VERIFY_FAIL_IF_NO_PEER_CERT) != 0 ) {
12341239
// fail if no peer cert
@@ -1237,7 +1242,7 @@ private void checkTrusted(final String purpose, final X509Certificate[] chain) t
12371242
}
12381243
}
12391244

1240-
private void verifyChain(final StoreContext storeContext) throws CertificateException {
1245+
private void verifyChain(final StoreContext storeContext, final boolean raiseOnFailure) throws CertificateException {
12411246
final int ok;
12421247
try {
12431248
ok = storeContext.verifyCertificate();
@@ -1247,11 +1252,14 @@ private void verifyChain(final StoreContext storeContext) throws CertificateExce
12471252
if ( storeContext.getError() == X509Utils.V_OK ) {
12481253
internalContext.setLastVerifyResult(X509Utils.V_ERR_CERT_REJECTED);
12491254
}
1250-
throw new CertificateException("certificate verify failed", e);
1255+
if ( raiseOnFailure ) {
1256+
throw new CertificateException("certificate verify failed", e);
1257+
}
1258+
return;
12511259
}
12521260

12531261
internalContext.setLastVerifyResult(storeContext.getError());
1254-
if ( ok == 0 ) {
1262+
if ( ok == 0 && raiseOnFailure ) {
12551263
throw new CertificateException("certificate verify failed");
12561264
}
12571265
}

src/test/ruby/ssl/test_ssl.rb

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,91 @@ def test_verify_hostname_not_enforced_when_disabled
189189
end
190190
end
191191

192+
# GH-25: verify_result should report the actual verification error even
193+
# when VERIFY_NONE is set. C OpenSSL always runs ssl_verify_cert_chain
194+
# and stores the result; VERIFY_NONE only suppresses the handshake abort.
195+
196+
def test_verify_result_with_verify_none_self_signed
197+
# Self-signed cert: verify_result should be V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
198+
self_key = OpenSSL::PKey::RSA.new(2048)
199+
now = Time.now
200+
self_cert = issue_cert(
201+
OpenSSL::X509::Name.parse("/CN=Self Signed"), self_key, 10,
202+
[["keyUsage", "keyEncipherment,digitalSignature", true]],
203+
nil, nil, not_before: now, not_after: now + 1800)
204+
205+
@svr_cert = self_cert
206+
@svr_key = self_key
207+
start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port|
208+
ctx = OpenSSL::SSL::SSLContext.new
209+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
210+
sock = TCPSocket.new("127.0.0.1", port)
211+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
212+
ssl.connect
213+
assert_equal OpenSSL::X509::V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, ssl.verify_result
214+
ensure
215+
ssl&.close rescue nil
216+
sock&.close rescue nil
217+
end
218+
end
219+
220+
def test_verify_result_with_verify_none_valid_cert
221+
# Valid cert signed by trusted CA: verify_result should be V_OK
222+
start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port|
223+
ctx = OpenSSL::SSL::SSLContext.new
224+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
225+
ctx.cert_store = OpenSSL::X509::Store.new
226+
ctx.cert_store.add_cert(@ca_cert)
227+
sock = TCPSocket.new("127.0.0.1", port)
228+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
229+
ssl.connect
230+
assert_equal OpenSSL::X509::V_OK, ssl.verify_result
231+
ensure
232+
ssl&.close rescue nil
233+
sock&.close rescue nil
234+
end
235+
end
236+
237+
def test_verify_result_with_verify_peer_valid_cert
238+
# VERIFY_PEER with trusted CA: connect succeeds, verify_result is V_OK
239+
start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port|
240+
ctx = OpenSSL::SSL::SSLContext.new
241+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
242+
ctx.cert_store = OpenSSL::X509::Store.new
243+
ctx.cert_store.add_cert(@ca_cert)
244+
sock = TCPSocket.new("127.0.0.1", port)
245+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
246+
ssl.connect
247+
assert_equal OpenSSL::X509::V_OK, ssl.verify_result
248+
ensure
249+
ssl&.close rescue nil
250+
sock&.close rescue nil
251+
end
252+
end
253+
254+
def test_verify_result_with_verify_peer_self_signed
255+
# VERIFY_PEER with self-signed cert: connect should fail
256+
self_key = OpenSSL::PKey::RSA.new(2048)
257+
now = Time.now
258+
self_cert = issue_cert(
259+
OpenSSL::X509::Name.parse("/CN=Self Signed"), self_key, 10,
260+
[["keyUsage", "keyEncipherment,digitalSignature", true]],
261+
nil, nil, not_before: now, not_after: now + 1800)
262+
263+
@svr_cert = self_cert
264+
@svr_key = self_key
265+
start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port|
266+
ctx = OpenSSL::SSL::SSLContext.new
267+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
268+
sock = TCPSocket.new("127.0.0.1", port)
269+
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
270+
assert_raise(OpenSSL::SSL::SSLError) { ssl.connect }
271+
ensure
272+
ssl&.close rescue nil
273+
sock&.close rescue nil
274+
end
275+
end
276+
192277
def test_post_connect_check_with_anon_ciphers
193278
unless OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384
194279
return skip('OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384 not enabled')

0 commit comments

Comments
 (0)