Skip to content

Commit 54b8c72

Browse files
committed
tlshd: Match ingress certificates with defined TLS session tags
TLS session tags are defined in /etc/tlshd/tags.d. Each tag one or more filter expressions that match against fields in the x.509 certificate presented by a client peer. When a handshake is successful, tlshd parses the peer's certificate and applies the filters to the fields in the certificate. When the tag's set of filters all match, the tag's name is added to the tag list for the session. A subsequent patch will pass the list to the kernel upon handshake completion. Suggested-by: Benjamin Coddington <bcodding@redhat.com> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
1 parent fc3bb8c commit 54b8c72

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

src/tlshd/server.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ static void tlshd_tls13_server_x509_handshake(struct tlshd_handshake_parms *parm
313313
}
314314
}
315315

316+
tlsdh_tags_x509_match_session(session);
317+
316318
gnutls_deinit(session);
317319

318320
out_free_certs:

src/tlshd/tags.c

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,3 +1008,224 @@ void tlshd_tags_shutdown(void)
10081008
NULL);
10091009
g_ptr_array_free(tlshd_tags_filter_all, TRUE);
10101010
}
1011+
1012+
static bool tlshd_tags_x509_match_subject(struct tlshd_tags_filter *filter,
1013+
gnutls_x509_crt_t *cert)
1014+
{
1015+
gnutls_datum_t dn;
1016+
int ret;
1017+
1018+
ret = gnutls_x509_crt_get_dn3(*cert, &dn, 0);
1019+
if (ret != GNUTLS_E_SUCCESS) {
1020+
tlshd_log_gnutls_error(ret);
1021+
return false;
1022+
}
1023+
1024+
/* XXX: Do we need to consult filter->fi_match_type here ? */
1025+
1026+
if (!g_pattern_match(filter->fi_pattern, dn.size,
1027+
(const gchar *)dn.data, NULL)) {
1028+
tlshd_log_debug("Filter '%s' did not match subject '%s'",
1029+
filter->fi_name, dn.data);
1030+
gnutls_free(dn.data);
1031+
return false;
1032+
}
1033+
tlshd_log_debug("Filter '%s' matched subject '%s'",
1034+
filter->fi_name, dn.data);
1035+
gnutls_free(dn.data);
1036+
return true;
1037+
}
1038+
1039+
static bool tlshd_tags_x509_match_issuer(struct tlshd_tags_filter *filter,
1040+
gnutls_x509_crt_t *cert)
1041+
{
1042+
gnutls_datum_t dn;
1043+
int ret;
1044+
1045+
ret = gnutls_x509_crt_get_issuer_dn3(*cert, &dn, 0);
1046+
if (ret != GNUTLS_E_SUCCESS) {
1047+
tlshd_log_gnutls_error(ret);
1048+
return false;
1049+
}
1050+
1051+
/* XXX: Do we need to consult filter->fi_match_type here ? */
1052+
1053+
if (!g_pattern_match(filter->fi_pattern, dn.size,
1054+
(const gchar *)dn.data, NULL)) {
1055+
tlshd_log_debug("Filter '%s' did not match issuer '%s'",
1056+
filter->fi_name, dn.data);
1057+
gnutls_free(dn.data);
1058+
return false;
1059+
}
1060+
tlshd_log_debug("Filter '%s' matched issuer '%s'",
1061+
filter->fi_name, dn.data);
1062+
gnutls_free(dn.data);
1063+
return true;
1064+
}
1065+
1066+
static bool tlshd_tags_x509_match_serial(struct tlshd_tags_filter *filter,
1067+
gnutls_x509_crt_t *cert)
1068+
{
1069+
unsigned char raw[40];
1070+
char *c, buf[100];
1071+
size_t i, size;
1072+
int ret;
1073+
1074+
size = sizeof(raw);
1075+
ret = gnutls_x509_crt_get_serial(*cert, raw, &size);
1076+
if (ret != GNUTLS_E_SUCCESS) {
1077+
tlshd_log_gnutls_error(ret);
1078+
return false;
1079+
}
1080+
1081+
c = buf;
1082+
for (i = 0; i < size; i++) {
1083+
sprintf(c, "%.2x", raw[i]);
1084+
c += 2;
1085+
}
1086+
*c = '\0';
1087+
1088+
/* XXX: Do we need to consult filter->fi_match_type here ? */
1089+
1090+
if (!g_pattern_match(filter->fi_pattern, strlen(buf), buf, NULL)) {
1091+
tlshd_log_debug("Filter '%s' did not match serial '%s'",
1092+
filter->fi_name, buf);
1093+
return false;
1094+
}
1095+
tlshd_log_debug("Filter '%s' matched serial '%s'",
1096+
filter->fi_name, buf);
1097+
return true;
1098+
}
1099+
1100+
static bool tlshd_tags_x509_match_san(struct tlshd_tags_filter *filter,
1101+
gnutls_x509_crt_t *cert)
1102+
{
1103+
char buf[256]; /* XXX: Dynamically-allocate this buffer */
1104+
unsigned int i;
1105+
size_t size;
1106+
int ret;
1107+
1108+
tlshd_log_debug("SAN filter: '%s'", filter->fi_name);
1109+
1110+
for (ret = 0, i = 0; ret >= 0; i++) {
1111+
size = sizeof(buf);
1112+
ret = gnutls_x509_crt_get_subject_alt_name(*cert, i, buf,
1113+
&size, NULL);
1114+
if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
1115+
break;
1116+
1117+
switch (ret) {
1118+
case GNUTLS_SAN_DNSNAME:
1119+
tlshd_log_debug("DNS: %s", buf);
1120+
break;
1121+
case GNUTLS_SAN_RFC822NAME:
1122+
tlshd_log_debug("RFC822: %s", buf);
1123+
break;
1124+
case GNUTLS_SAN_URI:
1125+
tlshd_log_debug("URI: %s", buf);
1126+
break;
1127+
case GNUTLS_SAN_IPADDRESS:
1128+
tlshd_log_debug("IP: %s", buf);
1129+
break;
1130+
case GNUTLS_SAN_OTHERNAME:
1131+
tlshd_log_debug("Other: %s", buf);
1132+
break;
1133+
case GNUTLS_SAN_DN:
1134+
tlshd_log_debug("DN: %s", buf);
1135+
break;
1136+
case GNUTLS_SAN_REGISTERED_ID:
1137+
tlshd_log_debug("Registered ID: %s", buf);
1138+
break;
1139+
case GNUTLS_SAN_OTHERNAME_XMPP:
1140+
tlshd_log_debug("Other XMPP: %s", buf);
1141+
break;
1142+
case GNUTLS_SAN_OTHERNAME_KRB5PRINCIPAL:
1143+
tlshd_log_debug("Other Krb5: %s", buf);
1144+
break;
1145+
case GNUTLS_SAN_OTHERNAME_MSUSERPRINCIPAL:
1146+
tlshd_log_debug("Other MS User principal: %s", buf);
1147+
break;
1148+
1149+
default:
1150+
if (ret < 0) {
1151+
tlshd_log_gnutls_error(ret);
1152+
break;
1153+
}
1154+
}
1155+
}
1156+
1157+
return false;
1158+
}
1159+
1160+
struct tlshd_tags_match_args {
1161+
struct tlshd_tags_tag *ma_tag;
1162+
gnutls_x509_crt_t *ma_cert;
1163+
bool ma_matched;
1164+
};
1165+
1166+
static void tlshd_tags_x509_match_filters_cb(gpointer data, gpointer user_data)
1167+
{
1168+
struct tlshd_tags_filter *filter = (struct tlshd_tags_filter *)data;
1169+
struct tlshd_tags_match_args *args = (struct tlshd_tags_match_args *)user_data;
1170+
1171+
/* A previous filter failed to match, no need to check any further */
1172+
if (!args->ma_matched)
1173+
return;
1174+
1175+
if (strcmp(filter->fi_field, "subject") == 0)
1176+
args->ma_matched = tlshd_tags_x509_match_subject(filter, args->ma_cert);
1177+
else if (strcmp(filter->fi_field, "issuer") == 0)
1178+
args->ma_matched = tlshd_tags_x509_match_issuer(filter, args->ma_cert);
1179+
else if (strcmp(filter->fi_field, "serial") == 0)
1180+
args->ma_matched = tlshd_tags_x509_match_serial(filter, args->ma_cert);
1181+
else if (strcmp(filter->fi_field, "san") == 0)
1182+
args->ma_matched = tlshd_tags_x509_match_san(filter, args->ma_cert);
1183+
}
1184+
1185+
static void tlshd_tags_x509_match_cb(gpointer data, gpointer user_data)
1186+
{
1187+
struct tlshd_tags_match_args args = {
1188+
.ma_tag = (struct tlshd_tags_tag *)data,
1189+
.ma_cert = (gnutls_x509_crt_t *)user_data,
1190+
.ma_matched = true,
1191+
};
1192+
1193+
g_ptr_array_foreach(args.ma_tag->ta_filters,
1194+
tlshd_tags_x509_match_filters_cb, (gpointer)&args);
1195+
args.ma_tag->ta_matched = args.ma_matched;
1196+
}
1197+
1198+
/**
1199+
* tlshd_tags_x509_match_session - match certificate against configured tags
1200+
* @session: session with incoming x.509 certificates
1201+
*
1202+
* Side-effect: The tt_matched boolean is set in each tag in the
1203+
* global tag list that is matched. When this function is called
1204+
* in a child process, the parent's tag list is not changed (the
1205+
* parent's tag list is copied-on-write after the child forks).
1206+
*/
1207+
void tlsdh_tags_x509_match_session(gnutls_session_t session)
1208+
{
1209+
const gnutls_datum_t *cert_list;
1210+
unsigned int num_certs = 0;
1211+
gnutls_x509_crt_t peercert;
1212+
1213+
if (!tlshd_tags_tag_all)
1214+
return;
1215+
if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
1216+
return;
1217+
cert_list = gnutls_certificate_get_peers(session, &num_certs);
1218+
if (num_certs == 0)
1219+
return;
1220+
1221+
/*
1222+
* The first certificate in the returned list is the peer's
1223+
* certificate. That is the only one that is checked against
1224+
* our tags list.
1225+
*/
1226+
gnutls_x509_crt_init(&peercert);
1227+
gnutls_x509_crt_import(peercert, &cert_list[0], GNUTLS_X509_FMT_DER);
1228+
g_ptr_array_foreach(tlshd_tags_tag_all,
1229+
tlshd_tags_x509_match_cb, (gpointer)&peercert);
1230+
gnutls_x509_crt_deinit(peercert);
1231+
}

src/tlshd/tlshd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ extern void tlshd_quic_serverhello_handshake(struct tlshd_handshake_parms *parms
121121

122122
/* tags.c */
123123
extern void tlshd_tags_read_configuration(const char *tagsdir);
124+
extern void tlsdh_tags_x509_match_session(gnutls_session_t session);
124125
extern void tlshd_tags_shutdown(void);
125126

126127
#ifdef HAVE_GNUTLS_QUIC

0 commit comments

Comments
 (0)