drogonframework / drogonframework/drogon
mTLS support broken - crashes with VerifyMode or doesn't enforce without caPath
- Dominant language
- C++
- Stars
- 14.3k
- Forks
- 1.4k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 14
Description
Hi, me and my team have been developing several servers with both WebSockets and HTTP controllers using the Drogon framework. It has proven very useful and helpful, significantly simplifying our work, so let me first congratulate Drogon team for this great tool!
However, we believe to have found several issues that cannot be easily bypassed without directly modifying the Drogon or Trantor source code.
## Problem
Mutual TLS (mTLS) is broken in Drogon. There are two related issues apparently:
1. **Using `VerifyMode: Require` in `sslConfCmds` causes server crash after TLS handshake**
2. **Using `ClientCAFile` without `VerifyMode` doesn't enforce client certificates**
## Issue 1: Server crashes with VerifyMode
**Configuration:**
```cpp
std::vector> ssl_config = {
{"ClientCAFile", "/path/to/ca.pem"},
{"VerifyMode", "Require"}
};
app.addListener("0.0.0.0", 8444, true, cert, key, false, ssl_config);
```
**Result:** Server crashes silently after TLS handshake completes.
**Test with openssl s_client:**
```bash
openssl s_client -connect localhost:8444 -cert client.pem -key client-key.pem -CAfile ca.pem
```
**Output:**
```
SSL handshake has read 1278 bytes and written 1826 bytes
Verification: OK
Verify return code: 0 (ok)
---
error:0A000126:SSL routines:ssl3_read_n:unexpected eof while reading
```
**Observations:**
- TLS handshake completes successfully
- Client certificate is verified (Verification: OK)
- Server sends "Acceptable client certificate CA names"
- Connection closes immediately after handshake
## Issue 2: ClientCAFile alone doesn't enforce mTLS
**Configuration:**
```cpp
std::vector> ssl_config = {
{"ClientCAFile", "/path/to/ca.pem"}
// No VerifyMode
};
app.addListener("0.0.0.0", 8444, true, cert, key, false, ssl_config);
```
**Result:** Server works but **never requests client certificates**. I guess this is expected.
**Test:**
```bash
# Without client cert - should fail but doesn't
curl -k https://localhost:8444/endpoint
# Works! No client cert requested
```
## What may work
Using trantor's TLSPolicy directly (example from `trantor/tests/MTLSServer.cc`):
```cpp
auto policy = TLSPolicy::defaultServerPolicy(cert, key);
policy->setCaPath(ca_path).setValidate(true);
server.enableSSL(policy);
```
If this example is correct, this might request and validate client certificates without crashes.
## Root Cause
Looking at `ListenerManager.cc` lines 145-155:
```cpp
auto policy = trantor::TLSPolicy::defaultServerPolicy(cert, key);
policy->setConfCmds(cmds).setUseOldTLS(listener.useOldTLS_);
serverPtr->enableSSL(std::move(policy));
```
**Problem:** `policy->setCaPath()` is never called.
In `trantor/net/inner/tlsprovider/OpenSSLProvider.cc` lines 730-757, the mTLS setup code only executes when:
```cpp
if (!policy.getCaPath().empty())
```
**When using SSL_CONF_cmd:**
- `ClientCAFile` loads CA for OpenSSL but doesn't set `policy.getCaPath()`
- `VerifyMode: Require` enables enforcement in OpenSSL
- But `policy.getCaPath()` remains empty
- Trantor's mTLS initialization code never runs
- Result: incomplete state that causes crash
**When using only `ClientCAFile`:**
- `policy.getCaPath()` is empty
- Trantor's mTLS code doesn't run
- No verification mode is set
- Result: server doesn't request client certificates
## Expected Behavior
Either:
1. SSL_CONF_cmd should work properly without crashes
2. Or `addListener()` should expose `caPath` parameter like trantor's native API
## Workaround
None that works properly. Invalid VerifyMode values are silently ignored, making server work but without any mTLS enforcement.
## Environment
- Drogon: 1.9.11
- OS: Ubuntu 24.04
- Compiler: GCC 11.4
- OpenSSL: 3.0.2
Contributor guide
Assessment
This issue has not been assessed yet.