M README.md => README.md +1 -0
@@ 84,6 84,7 @@ Server connection options:
--tls-disable Set server TLS disabled
--tls-verify=MODE Set server TLS peer certificate verification mode
--tls-cert-ca=PATH Set server TLS ca cert file path
+ --tls-cert-client=PATH Set server TLS client cert file path
```
Commands:
M docs/rirc.1 => docs/rirc.1 +3 -0
@@ 62,6 62,9 @@ Set \fIserver\fP TLS peer certificate verification \fImode\fP:
.TP
.BI --tls-cert-ca= path
Set \fIserver\fP TLS ca cert file \fIpath\fP
+.TP
+.BI --tls-cert-client= path
+Set \fIserver\fP TLS client cert file \fIpath\fP
.SH USAGE
.TS
l .
M src/io.c => src/io.c +48 -9
@@ 106,6 106,7 @@ struct connection
const char *host;
const char *port;
const char *tls_cert_ca;
+ const char *tls_cert_client;
enum io_state {
IO_ST_INVALID,
IO_ST_DXED, /* Socket disconnected, passive */
@@ 118,9 119,11 @@ struct connection
mbedtls_ctr_drbg_context tls_ctr_drbg;
mbedtls_entropy_context tls_entropy;
mbedtls_net_context net_ctx;
+ mbedtls_pk_context tls_pk_ctx;
mbedtls_ssl_config tls_conf;
mbedtls_ssl_context tls_ctx;
- mbedtls_x509_crt tls_x509_crt;
+ mbedtls_x509_crt tls_x509_crt_ca;
+ mbedtls_x509_crt tls_x509_crt_client;
pthread_mutex_t mtx;
pthread_t tid;
uint32_t flags;
@@ 170,6 173,7 @@ connection(
const char *host,
const char *port,
const char *tls_cert_ca,
+ const char *tls_cert_client,
uint32_t flags)
{
struct connection *cx;
@@ 182,6 186,7 @@ connection(
cx->host = strdup(host);
cx->port = strdup(port);
cx->tls_cert_ca = (tls_cert_ca ? strdup(tls_cert_ca) : NULL);
+ cx->tls_cert_client = (tls_cert_client ? strdup(tls_cert_client) : NULL);
cx->st_cur = IO_ST_DXED;
cx->st_new = IO_ST_INVALID;
PT_CF(pthread_mutex_init(&(cx->mtx), NULL));
@@ 196,6 201,7 @@ connection_free(struct connection *cx)
free((void*)cx->host);
free((void*)cx->port);
free((void*)cx->tls_cert_ca);
+ free((void*)cx->tls_cert_client);
free(cx);
}
@@ 442,9 448,12 @@ io_state_cxed(struct connection *cx)
if (cx->flags & IO_TLS_ENABLED) {
mbedtls_ctr_drbg_free(&(cx->tls_ctr_drbg));
mbedtls_entropy_free(&(cx->tls_entropy));
+ mbedtls_pk_free(&(cx->tls_pk_ctx));
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
- mbedtls_x509_crt_free(&(cx->tls_x509_crt));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_client));
+
}
return IO_ST_CXNG;
@@ 484,9 493,11 @@ io_state_ping(struct connection *cx)
if (cx->flags & IO_TLS_ENABLED) {
mbedtls_ctr_drbg_free(&(cx->tls_ctr_drbg));
mbedtls_entropy_free(&(cx->tls_entropy));
+ mbedtls_pk_free(&(cx->tls_pk_ctx));
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
- mbedtls_x509_crt_free(&(cx->tls_x509_crt));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_client));
}
return IO_ST_CXNG;
@@ 795,9 806,11 @@ io_tls_establish(struct connection *cx)
mbedtls_ctr_drbg_init(&(cx->tls_ctr_drbg));
mbedtls_entropy_init(&(cx->tls_entropy));
+ mbedtls_pk_init(&(cx->tls_pk_ctx));
mbedtls_ssl_init(&(cx->tls_ctx));
mbedtls_ssl_config_init(&(cx->tls_conf));
- mbedtls_x509_crt_init(&(cx->tls_x509_crt));
+ mbedtls_x509_crt_init(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_init(&(cx->tls_x509_crt_client));
if ((ret = mbedtls_ssl_config_defaults(
&(cx->tls_conf),
@@ 828,16 841,40 @@ io_tls_establish(struct connection *cx)
goto err;
}
+ if (cx->tls_cert_client) {
+
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_client), cx->tls_cert_client)) < 0) {
+ io_error(cx, " .. Failed to load client cert: '%s': %s", cx->tls_cert_client, io_tls_err(ret));
+ goto err;
+ }
+
+ if ((ret = mbedtls_pk_parse_keyfile(
+ &(cx->tls_pk_ctx),
+ cx->tls_cert_client,
+ NULL,
+ mbedtls_ctr_drbg_random,
+ &(cx->tls_ctr_drbg))))
+ {
+ io_error(cx, " .. Failed to load client cert key: '%s': %s", cx->tls_cert_client, io_tls_err(ret));
+ goto err;
+ }
+
+ if ((ret = mbedtls_ssl_conf_own_cert(&(cx->tls_conf), &(cx->tls_x509_crt_client), &(cx->tls_pk_ctx)))) {
+ io_error(cx, " .. Failed to configure client cert: '%s': %s", cx->tls_cert_client, io_tls_err(ret));
+ goto err;
+ }
+ }
+
if (cx->tls_cert_ca) {
- if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt), cx->tls_cert_ca)) < 0) {
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_ca), cx->tls_cert_ca)) < 0) {
io_error(cx, " .. Failed to load ca cert: '%s': %s", cx->tls_cert_ca, io_tls_err(ret));
goto err;
}
} else if (ca_cert_path && *ca_cert_path) {
- if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt), ca_cert_path)) < 0) {
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_ca), ca_cert_path)) < 0) {
io_error(cx, " .. Failed to load ca cert: '%s': %s", ca_cert_path, io_tls_err(ret));
goto err;
}
@@ 845,7 882,7 @@ io_tls_establish(struct connection *cx)
} else {
for (size_t i = 0; i < ARR_LEN(ca_cert_paths); i++) {
- if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt), ca_cert_paths[i])) >= 0) {
+ if ((ret = mbedtls_x509_crt_parse_file(&(cx->tls_x509_crt_ca), ca_cert_paths[i])) >= 0) {
break;
} else if (i == ARR_LEN(ca_cert_paths)) {
io_error(cx, " .. Failed to load ca cert: %s", io_tls_err(ret));
@@ 859,7 896,7 @@ io_tls_establish(struct connection *cx)
if (cx->flags & IO_TLS_VRFY_DISABLED) {
mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_NONE);
} else {
- mbedtls_ssl_conf_ca_chain(&(cx->tls_conf), &(cx->tls_x509_crt), NULL);
+ mbedtls_ssl_conf_ca_chain(&(cx->tls_conf), &(cx->tls_x509_crt_ca), NULL);
if (cx->flags & IO_TLS_VRFY_OPTIONAL)
mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_OPTIONAL);
@@ 921,9 958,11 @@ err:
mbedtls_ctr_drbg_free(&(cx->tls_ctr_drbg));
mbedtls_entropy_free(&(cx->tls_entropy));
+ mbedtls_pk_free(&(cx->tls_pk_ctx));
mbedtls_ssl_config_free(&(cx->tls_conf));
mbedtls_ssl_free(&(cx->tls_ctx));
- mbedtls_x509_crt_free(&(cx->tls_x509_crt));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_ca));
+ mbedtls_x509_crt_free(&(cx->tls_x509_crt_client));
mbedtls_net_free(&(cx->net_ctx));
return -1;
M src/io.h => src/io.h +1 -0
@@ 90,6 90,7 @@ struct connection* connection(
const char*, /* host */
const char*, /* port */
const char*, /* tls_cert_ca */
+ const char*, /* tls_cert_client */
uint32_t); /* flags */
void connection_free(struct connection*);
M src/rirc.c => src/rirc.c +38 -27
@@ 81,6 81,7 @@ static const char *const rirc_help =
"\n --tls-disable Set server TLS disabled"
"\n --tls-verify=MODE Set server TLS peer certificate verification mode"
"\n --tls-cert-ca=PATH Set server TLS ca cert file path"
+"\n --tls-cert-client=PATH Set server TLS client cert file path"
"\n";
static const char *const rirc_version =
@@ 107,6 108,7 @@ rirc_opt_str(char c)
case 'x': return "--tls-disable";
case 'y': return "--tls-verify";
case 'z': return "--tls-cert-ca";
+ case '0': return "--tls-cert-client";
default:
fatal("unknown option flag '%c'", c);
}
@@ 144,6 146,7 @@ rirc_parse_args(int argc, char **argv)
const char *nicks;
const char *chans;
const char *tls_cert_ca;
+ const char *tls_cert_client;
int ipv;
int tls;
int tls_vrfy;
@@ 151,21 154,22 @@ rirc_parse_args(int argc, char **argv)
} cli_servers[MAX_CLI_SERVERS];
struct option long_opts[] = {
- {"server", required_argument, 0, 's'},
- {"port", required_argument, 0, 'p'},
- {"pass", required_argument, 0, 'w'},
- {"username", required_argument, 0, 'u'},
- {"realname", required_argument, 0, 'r'},
- {"mode", required_argument, 0, 'm'},
- {"nicks", required_argument, 0, 'n'},
- {"chans", required_argument, 0, 'c'},
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'v'},
- {"ipv4", no_argument, 0, '4'},
- {"ipv6", no_argument, 0, '6'},
- {"tls-disable", no_argument, 0, 'x'},
- {"tls-verify", required_argument, 0, 'y'},
- {"tls-cert-ca", required_argument, 0, 'z'},
+ {"server", required_argument, 0, 's'},
+ {"port", required_argument, 0, 'p'},
+ {"pass", required_argument, 0, 'w'},
+ {"username", required_argument, 0, 'u'},
+ {"realname", required_argument, 0, 'r'},
+ {"mode", required_argument, 0, 'm'},
+ {"nicks", required_argument, 0, 'n'},
+ {"chans", required_argument, 0, 'c'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {"ipv4", no_argument, 0, '4'},
+ {"ipv6", no_argument, 0, '6'},
+ {"tls-disable", no_argument, 0, 'x'},
+ {"tls-verify", required_argument, 0, 'y'},
+ {"tls-cert-ca", required_argument, 0, 'z'},
+ {"tls-cert-client", required_argument, 0, '0'},
{0, 0, 0, 0}
};
@@ 187,18 191,19 @@ rirc_parse_args(int argc, char **argv)
return -1;
}
- cli_servers[n_servers - 1].host = optarg;
- cli_servers[n_servers - 1].port = NULL;
- cli_servers[n_servers - 1].pass = NULL;
- cli_servers[n_servers - 1].username = default_username;
- cli_servers[n_servers - 1].realname = default_realname;
- cli_servers[n_servers - 1].mode = NULL;
- cli_servers[n_servers - 1].nicks = default_nicks;
- cli_servers[n_servers - 1].chans = NULL;
- cli_servers[n_servers - 1].tls_cert_ca = NULL;
- cli_servers[n_servers - 1].ipv = IO_IPV_UNSPEC;
- cli_servers[n_servers - 1].tls = IO_TLS_ENABLED;
- cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
+ cli_servers[n_servers - 1].host = optarg;
+ cli_servers[n_servers - 1].port = NULL;
+ cli_servers[n_servers - 1].pass = NULL;
+ cli_servers[n_servers - 1].username = default_username;
+ cli_servers[n_servers - 1].realname = default_realname;
+ cli_servers[n_servers - 1].mode = NULL;
+ cli_servers[n_servers - 1].nicks = default_nicks;
+ cli_servers[n_servers - 1].chans = NULL;
+ cli_servers[n_servers - 1].tls_cert_ca = NULL;
+ cli_servers[n_servers - 1].tls_cert_client = NULL;
+ cli_servers[n_servers - 1].ipv = IO_IPV_UNSPEC;
+ cli_servers[n_servers - 1].tls = IO_TLS_ENABLED;
+ cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
break;
#define CHECK_SERVER_OPTARG(OPT_C, REQ) \
@@ 283,6 288,11 @@ rirc_parse_args(int argc, char **argv)
cli_servers[n_servers - 1].tls_cert_ca = optarg;
break;
+ case '0': /* Set server TLS client cert file path */
+ CHECK_SERVER_OPTARG(opt_c, 1);
+ cli_servers[n_servers - 1].tls_cert_client = optarg;
+ break;
+
#undef CHECK_SERVER_OPTARG
case 'h':
@@ 336,6 346,7 @@ rirc_parse_args(int argc, char **argv)
cli_servers[i].host,
cli_servers[i].port,
cli_servers[i].tls_cert_ca,
+ cli_servers[i].tls_cert_client,
flags);
if (server_list_add(state_server_list(), cli_servers[i].s)) {
M src/state.c => src/state.c +22 -10
@@ 630,15 630,16 @@ command(struct channel *c, char *buf)
int ret;
struct server *s;
- const char *host = str;
- const char *port = NULL;
- const char *pass = NULL;
- const char *username = default_username;
- const char *realname = default_realname;
- const char *mode = NULL;
- const char *nicks = default_nicks;
- const char *chans = NULL;
- const char *tls_cert_ca = NULL;
+ const char *host = str;
+ const char *port = NULL;
+ const char *pass = NULL;
+ const char *username = default_username;
+ const char *realname = default_realname;
+ const char *mode = NULL;
+ const char *nicks = default_nicks;
+ const char *chans = NULL;
+ const char *tls_cert_ca = NULL;
+ const char *tls_cert_client = NULL;
int ipv = IO_IPV_UNSPEC;
int tls = IO_TLS_ENABLED;
int tls_vrfy = IO_TLS_VRFY_REQUIRED;
@@ 708,6 709,11 @@ command(struct channel *c, char *buf)
action(action_error, "connect: '--tls-cert-ca' requires an argument");
return;
}
+ } else if (!strcmp(str, "--tls-cert-client")) {
+ if (!(tls_cert_client = irc_strsep(&buf))) {
+ action(action_error, "connect: '--tls-cert-client' requires an argument");
+ return;
+ }
} else {
action(action_error, "connect: unknown option '%s'", str);
return;
@@ 737,7 743,13 @@ command(struct channel *c, char *buf)
return;
}
- s->connection = connection(s, host, port, tls_cert_ca, (ipv | tls | tls_vrfy));
+ s->connection = connection(
+ s,
+ host,
+ port,
+ tls_cert_ca,
+ tls_cert_client,
+ (ipv | tls | tls_vrfy));
if ((ret = io_cx(s->connection)))
server_error(s, "failed to connect: %s", io_err(ret));
M test/io.mock.c => test/io.mock.c +2 -1
@@ 37,12 37,13 @@ io_sendf(struct connection *c, const char *fmt, ...)
}
struct connection*
-connection(const void *o, const char *h, const char *p, const char *ca, uint32_t f)
+connection(const void *o, const char *h, const char *p, const char *ca, const char *cl, uint32_t f)
{
UNUSED(o);
UNUSED(h);
UNUSED(p);
UNUSED(ca);
+ UNUSED(cl);
UNUSED(f);
return NULL;
}
M test/state.c => test/state.c +6 -0
@@ 251,6 251,11 @@ test_command_connect(void)
assert_strcmp(current_channel()->name, "host-1");
INP_C(0x0A);
+ INP_COMMAND(":connect host --tls-cert-client");
+ assert_strcmp(action_message(), "connect: '--tls-cert-client' requires an argument");
+ assert_strcmp(current_channel()->name, "host-1");
+ INP_C(0x0A);
+
/* Test invalid arguments */
INP_COMMAND(":connect host xyz");
assert_strcmp(action_message(), ":connect [hostname [options]]");
@@ 316,6 321,7 @@ test_command_connect(void)
" --tls-verify optional"
" --tls-verify required"
" --tls-cert-ca path"
+ " --tls-cert-client path"
);
s = current_channel()->server;