From b3d93b59d26fa80123892302071d566ee8f30930 Mon Sep 17 00:00:00 2001 From: tb <> Date: Tue, 10 Dec 2024 08:40:30 +0000 Subject: Provide tls_peer_cert_common_name() There is currently no sane way of getting your hands on the common name or subject alternative name of the peer certificate from libtls. It is possible to extract it from the peer cert's PEM by hand, but that way lies madness. While the common name is close to being deprecated in the webpki, it is still the de facto standard to identify client certs. It would be nice to have a way to access the subject alternative names as well, but this is a lot more difficult to expose in a clean and sane C interface due to its multivaluedness. Initial diff from henning, with input from beck, jsing and myself henning and bluhm have plans of using this in syslogd. ok beck --- src/lib/libtls/tls.h | 3 +- src/lib/libtls/tls_conninfo.c | 14 ++++++- src/lib/libtls/tls_internal.h | 5 ++- src/lib/libtls/tls_peer.c | 10 ++++- src/lib/libtls/tls_verify.c | 97 ++++++++++++++++++++++++++++++++++--------- 5 files changed, 105 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/lib/libtls/tls.h b/src/lib/libtls/tls.h index 6b36886dc3..5a3a6254ab 100644 --- a/src/lib/libtls/tls.h +++ b/src/lib/libtls/tls.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tls.h,v 1.67 2024/08/02 15:00:01 tb Exp $ */ +/* $OpenBSD: tls.h,v 1.68 2024/12/10 08:40:30 tb Exp $ */ /* * Copyright (c) 2014 Joel Sing * @@ -200,6 +200,7 @@ int tls_close(struct tls *_ctx); int tls_peer_cert_provided(struct tls *_ctx); int tls_peer_cert_contains_name(struct tls *_ctx, const char *_name); +const char *tls_peer_cert_common_name(struct tls *_ctx); const char *tls_peer_cert_hash(struct tls *_ctx); const char *tls_peer_cert_issuer(struct tls *_ctx); const char *tls_peer_cert_subject(struct tls *_ctx); diff --git a/src/lib/libtls/tls_conninfo.c b/src/lib/libtls/tls_conninfo.c index bf525170f1..8fb56c92b7 100644 --- a/src/lib/libtls/tls_conninfo.c +++ b/src/lib/libtls/tls_conninfo.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tls_conninfo.c,v 1.27 2024/03/26 06:31:22 jsing Exp $ */ +/* $OpenBSD: tls_conninfo.c,v 1.28 2024/12/10 08:40:30 tb Exp $ */ /* * Copyright (c) 2015 Joel Sing * Copyright (c) 2015 Bob Beck @@ -118,6 +118,14 @@ tls_get_peer_cert_subject(struct tls *ctx, char **subject) return (0); } +static int +tls_get_peer_cert_common_name(struct tls *ctx, char **common_name) +{ + if (ctx->ssl_peer_cert == NULL) + return (-1); + return tls_get_common_name(ctx, ctx->ssl_peer_cert, NULL, common_name); +} + static int tls_get_peer_cert_times(struct tls *ctx, time_t *notbefore, time_t *notafter) @@ -158,6 +166,9 @@ tls_get_peer_cert_info(struct tls *ctx) goto err; if (tls_get_peer_cert_issuer(ctx, &ctx->conninfo->issuer) == -1) goto err; + if (tls_get_peer_cert_common_name(ctx, + &ctx->conninfo->common_name) == -1) + goto err; if (tls_get_peer_cert_times(ctx, &ctx->conninfo->notbefore, &ctx->conninfo->notafter) == -1) goto err; @@ -298,6 +309,7 @@ tls_conninfo_free(struct tls_conninfo *conninfo) free(conninfo->servername); free(conninfo->version); + free(conninfo->common_name); free(conninfo->hash); free(conninfo->issuer); free(conninfo->subject); diff --git a/src/lib/libtls/tls_internal.h b/src/lib/libtls/tls_internal.h index 5ff48ed7c9..8e566a34e0 100644 --- a/src/lib/libtls/tls_internal.h +++ b/src/lib/libtls/tls_internal.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tls_internal.h,v 1.85 2024/03/26 06:24:52 joshua Exp $ */ +/* $OpenBSD: tls_internal.h,v 1.86 2024/12/10 08:40:30 tb Exp $ */ /* * Copyright (c) 2014 Jeremie Courreges-Anglas * Copyright (c) 2014 Joel Sing @@ -129,6 +129,7 @@ struct tls_conninfo { int session_resumed; char *version; + char *common_name; char *hash; char *issuer; char *subject; @@ -238,6 +239,8 @@ struct tls_config *tls_config_new_internal(void); struct tls *tls_new(void); struct tls *tls_server_conn(struct tls *ctx); +int tls_get_common_name(struct tls *_ctx, X509 *_cert, const char *_in_name, + char **_out_common_name); int tls_check_name(struct tls *ctx, X509 *cert, const char *servername, int *match); int tls_configure_server(struct tls *ctx); diff --git a/src/lib/libtls/tls_peer.c b/src/lib/libtls/tls_peer.c index ec97a30838..6d63a529f2 100644 --- a/src/lib/libtls/tls_peer.c +++ b/src/lib/libtls/tls_peer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tls_peer.c,v 1.8 2017/04/10 17:11:13 jsing Exp $ */ +/* $OpenBSD: tls_peer.c,v 1.9 2024/12/10 08:40:30 tb Exp $ */ /* * Copyright (c) 2015 Joel Sing * Copyright (c) 2015 Bob Beck @@ -23,6 +23,14 @@ #include #include "tls_internal.h" +const char * +tls_peer_cert_common_name(struct tls *ctx) +{ + if (ctx->conninfo == NULL) + return (NULL); + return (ctx->conninfo->common_name); +} + const char * tls_peer_cert_hash(struct tls *ctx) { diff --git a/src/lib/libtls/tls_verify.c b/src/lib/libtls/tls_verify.c index 6b2a4fb82a..2935278383 100644 --- a/src/lib/libtls/tls_verify.c +++ b/src/lib/libtls/tls_verify.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tls_verify.c,v 1.31 2024/11/12 22:50:06 tb Exp $ */ +/* $OpenBSD: tls_verify.c,v 1.32 2024/12/10 08:40:30 tb Exp $ */ /* * Copyright (c) 2014 Jeremie Courreges-Anglas * @@ -210,19 +210,22 @@ tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name, } static int -tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, - int *cn_match) +tls_get_common_name_internal(X509 *cert, char **out_common_name, + unsigned int *out_tlserr, const char **out_errstr) { unsigned char *utf8_bytes = NULL; X509_NAME *subject_name; char *common_name = NULL; - union tls_addr addrbuf; int common_name_len; ASN1_STRING *data; int lastpos = -1; int rv = -1; - *cn_match = 0; + *out_tlserr = TLS_ERROR_UNKNOWN; + *out_errstr = "unknown"; + + free(*out_common_name); + *out_common_name = NULL; subject_name = X509_get_subject_name(cert); if (subject_name == NULL) @@ -244,10 +247,10 @@ tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, * more than one CN fed to us in the subject, treating the * certificate as hostile. */ - tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, - "error verifying name '%s': " + *out_tlserr = TLS_ERROR_UNKNOWN; + *out_errstr = "error getting common name: " "Certificate subject contains multiple Common Name fields, " - "probably a malicious or malformed certificate", name); + "probably a malicious or malformed certificate"; goto err; } @@ -257,10 +260,10 @@ tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, * Fail if we cannot encode the CN bytes as UTF-8. */ if ((common_name_len = ASN1_STRING_to_UTF8(&utf8_bytes, data)) < 0) { - tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, - "error verifying name '%s': " + *out_tlserr = TLS_ERROR_UNKNOWN; + *out_errstr = "error getting common name: " "Common Name field cannot be encoded as a UTF-8 string, " - "probably a malicious certificate", name); + "probably a malicious certificate"; goto err; } /* @@ -268,30 +271,85 @@ tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, * must be between 1 and 64 bytes long. */ if (common_name_len < 1 || common_name_len > 64) { - tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, - "error verifying name '%s': " + *out_tlserr = TLS_ERROR_UNKNOWN; + *out_errstr = "error getting common name: " "Common Name field has invalid length, " - "probably a malicious certificate", name); + "probably a malicious certificate"; goto err; } /* * Fail if the resulting text contains a NUL byte. */ if (memchr(utf8_bytes, 0, common_name_len) != NULL) { - tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, - "error verifying name '%s': " + *out_tlserr = TLS_ERROR_UNKNOWN; + *out_errstr = "error getting common name: " "NUL byte in Common Name field, " - "probably a malicious certificate", name); + "probably a malicious certificate"; goto err; } common_name = strndup(utf8_bytes, common_name_len); if (common_name == NULL) { - tls_set_error(ctx, TLS_ERROR_OUT_OF_MEMORY, - "out of memory"); + *out_tlserr = TLS_ERROR_OUT_OF_MEMORY; + *out_errstr = "out of memory"; + goto err; + } + + *out_common_name = common_name; + common_name = NULL; + + done: + if (*out_common_name == NULL) + *out_common_name = strdup(""); + if (*out_common_name == NULL) { + *out_tlserr = TLS_ERROR_OUT_OF_MEMORY; + *out_errstr = "out of memory"; goto err; } + rv = 0; + + err: + free(utf8_bytes); + free(common_name); + return rv; +} + +int +tls_get_common_name(struct tls *ctx, X509 *cert, const char *in_name, + char **out_common_name) +{ + unsigned int errcode = TLS_ERROR_UNKNOWN; + const char *errstr = "unknown"; + + if (tls_get_common_name_internal(cert, out_common_name, &errcode, + &errstr) == -1) { + const char *name = in_name; + const char *space = " "; + + if (name == NULL) + name = space = ""; + + tls_set_errorx(ctx, errcode, "%s%s%s", name, space, errstr); + return -1; + } + + return 0; +} + +static int +tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, + int *cn_match) +{ + char *common_name = NULL; + union tls_addr addrbuf; + int rv = -1; + + if (tls_get_common_name(ctx, cert, name, &common_name) == -1) + goto err; + if (strlen(common_name) == 0) + goto done; + /* * We don't want to attempt wildcard matching against IP addresses, * so perform a simple comparison here. @@ -310,7 +368,6 @@ tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, rv = 0; err: - free(utf8_bytes); free(common_name); return rv; } -- cgit v1.2.3-55-g6feb