~rcr/rirc

62bd337228e97c54534bb8a9efede99fb2d8a5c5 — Richard Robbins 10 days ago cb46e02
add --tls-cert-client flag
8 files changed, 121 insertions(+), 47 deletions(-)

M README.md
M docs/rirc.1
M src/io.c
M src/io.h
M src/rirc.c
M src/state.c
M test/io.mock.c
M test/state.c
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;