tls: add basic tls record parsers 09/41409/5
authorFlorin Coras <[email protected]>
Wed, 14 Aug 2024 03:26:10 +0000 (20:26 -0700)
committerFlorin Coras <[email protected]>
Wed, 14 Aug 2024 05:42:31 +0000 (22:42 -0700)
Type: improvement

Change-Id: Ia6a9f69b787950e3dbffd13ae577e499d6d2f55f
Signed-off-by: Florin Coras <[email protected]>
src/vnet/CMakeLists.txt
src/vnet/tls/tls_record.c [new file with mode: 0644]
src/vnet/tls/tls_record.h [new file with mode: 0644]

index 0195822..ca35d0d 100644 (file)
@@ -1053,10 +1053,13 @@ list(APPEND VNET_API_FILES session/session.api)
 
 list(APPEND VNET_SOURCES
   tls/tls.c
+  tls/tls_record.c
 )
 
 list(APPEND VNET_HEADERS
   tls/tls.h
+  tls/tls_inlines.h
+  tls/tls_record.h
   tls/tls_test.h
 )
 
diff --git a/src/vnet/tls/tls_record.c b/src/vnet/tls/tls_record.c
new file mode 100644 (file)
index 0000000..af7d54c
--- /dev/null
@@ -0,0 +1,279 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#include <vnet/tls/tls_record.h>
+
+/*
+ * rfc8446#section-4.1.2
+ *   struct {
+ *       ProtocolVersion legacy_version = 0x0303;    // TLS v1.2
+ *       Random random;
+ *       opaque legacy_session_id<0..32>;
+ *       CipherSuite cipher_suites<2..2^16-2>;
+ *       opaque legacy_compression_methods<1..2^8-1>;
+ *       Extension extensions<8..2^16-1>;
+ *   } ClientHello;
+ */
+tls_handshake_parse_error_t
+tls_handshake_client_hello_parse (u8 *b, int len,
+                                 tls_handshake_msg_info_t *info)
+{
+  u8 *p = b;
+
+  if (PREDICT_FALSE (len < 2 + 32 + 1 + 2 + 2 + 2))
+    return TLS_HS_PARSE_ERR_INVALID_LEN;
+  /* skip legacy version and random */
+  p += 2 + 32;
+  /* legacy_session_id */
+  info->legacy_session_id_len = *p;
+  info->legacy_session_id = p + 1;
+  p = info->legacy_session_id + info->legacy_session_id_len;
+  if (PREDICT_FALSE (p - b >= len))
+    return TLS_HS_PARSE_ERR_SESSION_ID_LEN;
+  /* cipher_suites */
+  info->cipher_suite_len = clib_net_to_host_u16 (*(u16 *) p);
+  info->cipher_suites = p + 2;
+  p = info->cipher_suites + info->cipher_suite_len;
+  if (PREDICT_FALSE (p - b >= len))
+    return TLS_HS_PARSE_ERR_CIPHER_SUITE_LEN;
+  /* legacy_compression_method, only support null */
+  if (PREDICT_FALSE (*p != 1 || *(p + 1) != 0))
+    return TLS_HS_PARSE_ERR_COMPRESSION_METHOD;
+  p += 2;
+  /* extensions */
+  info->extensions_len = clib_net_to_host_u16 (*(u16 *) p);
+  info->extensions = p + 2;
+  if (PREDICT_FALSE (info->extensions + info->extensions_len - b > len))
+    return TLS_HS_PARSE_ERR_CIPHER_SUITE_LEN;
+
+  return TLS_HS_PARSE_ERR_OK;
+}
+
+typedef tls_handshake_parse_error_t (*tls_handshake_msg_parser) (
+  u8 *b, int len, tls_handshake_msg_info_t *info);
+
+static tls_handshake_msg_parser tls_handshake_msg_parsers[] = {
+  [TLS_HS_CLIENT_HELLO] = tls_handshake_client_hello_parse,
+};
+
+static inline u32
+tls_handshake_ext_requested (const tls_handshake_ext_info_t *req_exts,
+                            u32 n_reqs, tls_handshake_ext_type_t ext_type)
+{
+  for (int i = 0; i < n_reqs; i++)
+    {
+      if (req_exts[i].type == ext_type)
+       return i;
+    }
+
+  return ~0;
+}
+
+tls_handshake_parse_error_t
+tls_hanshake_extensions_parse (tls_handshake_msg_info_t *info,
+                              tls_handshake_ext_info_t **exts)
+{
+  tls_handshake_ext_info_t *ext;
+  u16 ext_type, ext_len;
+  u8 *b, *b_end;
+
+  ASSERT (info->extensions != 0);
+
+  if (info->extensions_len < 2)
+    return TLS_HS_PARSE_ERR_EXTENSIONS_LEN;
+
+  b = info->extensions;
+  b_end = info->extensions + info->extensions_len;
+
+  while (b < b_end)
+    {
+      ext_type = clib_net_to_host_u16 (*(u16 *) b);
+      b += 2;
+      ext_len = clib_net_to_host_u16 (*(u16 *) b);
+      b += 2;
+
+      if (b + ext_len > b_end)
+       return TLS_HS_PARSE_ERR_EXTENSIONS_LEN;
+
+      vec_add2 (*exts, ext, 1);
+      ext->type = ext_type;
+      ext->len = ext_len;
+      ext->data = b;
+
+      b += ext_len;
+    }
+
+  return TLS_HS_PARSE_ERR_OK;
+}
+
+tls_handshake_parse_error_t
+tls_hanshake_extensions_try_parse (tls_handshake_msg_info_t *info,
+                                  tls_handshake_ext_info_t *req_exts,
+                                  u32 n_reqs)
+{
+  u8 *b, *b_end;
+  u16 ext_type, ext_len;
+  u32 n_found = 0, ext_pos;
+
+  ASSERT (info->extensions != 0);
+
+  if (info->extensions_len < 2)
+    return TLS_HS_PARSE_ERR_EXTENSIONS_LEN;
+
+  b = info->extensions;
+  b_end = info->extensions + info->extensions_len;
+
+  while (b < b_end && n_found < n_reqs)
+    {
+      ext_type = clib_net_to_host_u16 (*(u16 *) b);
+      b += 2;
+      ext_len = clib_net_to_host_u16 (*(u16 *) b);
+      b += 2;
+
+      if (b + ext_len > b_end)
+       return TLS_HS_PARSE_ERR_EXTENSIONS_LEN;
+
+      ext_pos = tls_handshake_ext_requested (req_exts, n_reqs, ext_type);
+      if (ext_pos == ~0)
+       {
+         b += ext_len;
+         continue;
+       }
+
+      req_exts[ext_pos].len = ext_len;
+      req_exts[ext_pos].data = b;
+
+      b += ext_len;
+      n_found++;
+    }
+
+  return TLS_HS_PARSE_ERR_OK;
+}
+
+tls_handshake_parse_error_t
+tls_handshake_message_try_parse (u8 *msg, int len,
+                                tls_handshake_msg_info_t *info)
+{
+  tls_handshake_msg_t *msg_hdr = (tls_handshake_msg_t *) msg;
+  u8 *b = msg_hdr->message;
+
+  info->len = tls_handshake_message_len (msg_hdr);
+  if (info->len > len)
+    return info->len > TLS_FRAGMENT_MAX_ENC_LEN ?
+            TLS_HS_PARSE_ERR_INVALID_LEN :
+            TLS_HS_PARSE_ERR_WANT_MORE;
+
+  if (msg_hdr->msg_type >= ARRAY_LEN (tls_handshake_msg_parsers) ||
+      !tls_handshake_msg_parsers[msg_hdr->msg_type])
+    return TLS_HS_PARSE_ERR_UNSUPPORTED;
+
+  return tls_handshake_msg_parsers[msg_hdr->msg_type](b, info->len, info);
+}
+
+/**
+ * As per rfc6066#section-3
+ *  struct {
+ *      NameType name_type;
+ *      select (name_type) {
+ *          case host_name: HostName;
+ *      } name;
+ *  } ServerName;
+ *
+ *  enum {
+ *      host_name(0), (255)
+ *  } NameType;
+ *
+ *  opaque HostName<1..2^16-1>;
+ *
+ *  struct {
+ *      ServerName server_name_list<1..2^16-1>
+ *  } ServerNameList;
+ */
+tls_handshake_parse_error_t
+tls_handshake_ext_sni_parse (tls_handshake_ext_info_t *ext_info,
+                            tls_handshake_ext_t *ext)
+{
+  tls_handshake_ext_sni_t *sni = (tls_handshake_ext_sni_t *) ext;
+  tls_handshake_ext_sni_sn_t *sn;
+  u16 n_names, sn_len;
+  u8 *b, *b_end;
+
+  b = ext_info->data;
+  b_end = b + ext_info->len;
+
+  sni->ext.type = ext_info->type;
+  sni->names = 0;
+  n_names = clib_net_to_host_u16 (*(u16 *) b);
+  b += 2;
+
+  while (b < b_end && vec_len (sni->names) < n_names)
+    {
+      /* only host name supported */
+      if (b[0] != 0)
+       return TLS_HS_PARSE_ERR_EXT_SNI_NAME_TYPE;
+
+      b++;
+      /* server name length */
+      sn_len = clib_net_to_host_u16 (*(u16 *) b);
+      if (sn_len > TLS_EXT_SNI_MAX_LEN)
+       return TLS_HS_PARSE_ERR_EXT_SNI_LEN;
+
+      b += 2;
+
+      vec_add2 (sni->names, sn, 1);
+      sn->name_type = 0;
+      vec_validate (sn->host_name, sn_len - 1);
+      clib_memcpy (sn->host_name, b, sn_len);
+
+      b += sn_len;
+    }
+
+  return TLS_HS_PARSE_ERR_OK;
+}
+
+typedef tls_handshake_parse_error_t (*tls_handshake_ext_parser) (
+  tls_handshake_ext_info_t *ext_info, tls_handshake_ext_t *ext);
+
+static tls_handshake_ext_parser tls_handshake_ext_parsers[] = {
+  [TLS_EXT_SERVER_NAME] = tls_handshake_ext_sni_parse,
+};
+
+tls_handshake_parse_error_t
+tls_handshake_ext_parse (tls_handshake_ext_info_t *ext_info,
+                        tls_handshake_ext_t *ext)
+{
+  if (ext_info->type >= ARRAY_LEN (tls_handshake_ext_parsers) ||
+      !tls_handshake_ext_parsers[ext_info->type])
+    return TLS_HS_PARSE_ERR_UNSUPPORTED;
+
+  return tls_handshake_ext_parsers[ext_info->type](ext_info, ext);
+}
+
+static void
+tls_handshake_ext_sni_free (tls_handshake_ext_t *ext)
+{
+  tls_handshake_ext_sni_t *sni = (tls_handshake_ext_sni_t *) ext;
+  tls_handshake_ext_sni_sn_t *sn;
+
+  vec_foreach (sn, sni->names)
+    vec_free (sn->host_name);
+
+  vec_free (sni->names);
+}
+
+typedef void (*tls_handshake_ext_free_fn) (tls_handshake_ext_t *ext);
+
+static tls_handshake_ext_free_fn tls_handshake_ext_free_fns[] = {
+  [TLS_EXT_SERVER_NAME] = tls_handshake_ext_sni_free,
+};
+
+void
+tls_handshake_ext_free (tls_handshake_ext_t *ext)
+{
+  if (ext->type >= ARRAY_LEN (tls_handshake_ext_free_fns) ||
+      !tls_handshake_ext_free_fns[ext->type])
+    return;
+
+  tls_handshake_ext_free_fns[ext->type](ext);
+}
diff --git a/src/vnet/tls/tls_record.h b/src/vnet/tls/tls_record.h
new file mode 100644 (file)
index 0000000..3f7723f
--- /dev/null
@@ -0,0 +1,250 @@
+
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_VNET_TLS_TLS_RECORD_H__
+#define SRC_VNET_TLS_TLS_RECORD_H__
+
+#include <vppinfra/clib.h>
+#include <vppinfra/error.h>
+
+/**
+ * TLS record types as per rfc8446#appendix-B.1
+ */
+#define foreach_tls_content_type                                              \
+  _ (INVALID, 0)                                                              \
+  _ (CHANGE_CIPHER_SPEC, 20)                                                  \
+  _ (ALERT, 21)                                                               \
+  _ (HANDSHAKE, 22)                                                           \
+  _ (APPLICATION_DATA, 23)                                                    \
+  _ (HEARTBEAT, 24) /* RFC 6520 */
+
+typedef enum tls_record_type_
+{
+#define _(sym, val) TLS_REC_##sym = val,
+  foreach_tls_content_type
+#undef _
+} __clib_packed tls_record_type_t;
+
+typedef struct tls_protocol_version_
+{
+  u8 major;
+  u8 minor;
+} __clib_packed tls_protocol_version_t;
+
+#define TLS_MAJOR_VERSION     3
+#define TLS_MINOR_VERSION_MIN 0 /**< SSLv3 */
+#define TLS_MINOR_VERSION_MAX 4 /**< TLS1.3 */
+
+typedef struct tls_record_header_
+{
+  tls_record_type_t type;        /**< content type */
+  tls_protocol_version_t version; /**< version (deprecated) */
+  u16 length;                    /**< fragment length */
+  u8 fragment[0];                /**< fragment/payload */
+} __clib_packed tls_record_header_t;
+
+#define TLS_FRAGMENT_MAX_LEN (1 << 14) /**< 16KB rfc8446 */
+/** rfc5246 (TLS1.2) allows 2048 bytes of protection */
+#define TLS12_FRAGMENT_MAX_ENC_LEN (TLS_FRAGMENT_MAX_LEN + (2 << 10))
+#define TLS13_FRAGMENT_MAX_ENC_LEN (TLS_FRAGMENT_MAX_LEN + 256)
+#define TLS_FRAGMENT_MAX_ENC_LEN   TLS12_FRAGMENT_MAX_ENC_LEN
+
+/*
+ * Handshake message types as per rfc8446#appendix-B.3
+ */
+#define foreach_tls_handshake_type                                            \
+  _ (HELLO_REQUEST, 0)                                                        \
+  _ (CLIENT_HELLO, 1)                                                         \
+  _ (SERVER_HELLO, 2)                                                         \
+  _ (HELLO_VERIFY_REQUEST, 3)                                                 \
+  _ (NEW_SESSION_TICKET, 4)                                                   \
+  _ (END_OF_EARLY_DATA, 5)                                                    \
+  _ (HELLO_RETRY_REQUEST, 6)                                                  \
+  _ (ENCRYPTED_EXTENSIONS, 8)                                                 \
+  _ (CERTIFICATE, 11)                                                         \
+  _ (SERVER_KEY_EXCHANGE, 12)                                                 \
+  _ (CERTIFICATE_REQUEST, 13)                                                 \
+  _ (SERVER_HELLO_DONE, 14)                                                   \
+  _ (CERTIFICATE_VERIFY, 15)                                                  \
+  _ (CLIENT_KEY_EXCHANGE, 16)                                                 \
+  _ (FINISHED, 20)                                                            \
+  _ (CERTIFICATE_URL, 21)                                                     \
+  _ (CERTIFICATE_STATUS, 22)                                                  \
+  _ (SUPPLEMENTAL_DATA, 23)                                                   \
+  _ (KEY_UPDATE, 24)                                                          \
+  _ (MESSAGE_HASH, 254)
+
+typedef enum tls_handshake_type_
+{
+#define _(sym, val) TLS_HS_##sym = val,
+  foreach_tls_handshake_type
+#undef _
+} tls_handshake_type_t;
+
+typedef struct
+{
+  u32 msg_type : 8; /**< message type */
+  u32 length : 24;  /**< message length */
+  u8 message[0];    /**< message contents */
+} __clib_packed tls_handshake_msg_t;
+
+static inline u32
+tls_handshake_message_len (tls_handshake_msg_t *msg)
+{
+  u8 *p = (u8 *) msg;
+  return p[1] << 16 | p[2] << 8 | p[3];
+}
+
+/**
+ * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
+ */
+#define foreach_tls_hanshake_extensions                                       \
+  _ (SERVER_NAME, 0)                                                          \
+  _ (MAX_FRAGMENT_LENGTH, 1)                                                  \
+  _ (STATUS_REQUEST, 5)                                                       \
+  _ (SUPPORTED_GROUPS, 10)                                                    \
+  _ (EC_POINT_FORMATS, 11)                                                    \
+  _ (SIGNATURE_ALGORITHMS, 13)                                                \
+  _ (APPLICATION_LAYER_PROTOCOL_NEGOTIATION, 16)                              \
+  _ (SIGNED_CERTIFICATE_TIMESTAMP, 18)                                        \
+  _ (CLIENT_CERTIFICATE_TYPE, 19)                                             \
+  _ (SERVER_CERTIFICATE_TYPE, 20)                                             \
+  _ (PADDING, 21)                                                             \
+  _ (TOKEN_BINDING, 24)                                                       \
+  _ (RECORD_SIZE_LIMIT, 28)                                                   \
+  _ (SESSION_TICKET, 35)                                                      \
+  _ (PRE_SHARED_KEY, 41)                                                      \
+  _ (EARLY_DATA, 42)                                                          \
+  _ (SUPPORTED_VERSIONS, 43)                                                  \
+  _ (COOKIE, 44)                                                              \
+  _ (PSK_KEY_EXCHANGE_MODES, 45)                                              \
+  _ (CERTIFICATE_AUTHORITIES, 47)                                             \
+  _ (OID_FILTERS, 48)                                                         \
+  _ (SIGNATURE_ALGORITHMS_CERT, 50)                                           \
+  _ (POST_HANDSHAKE_AUTH, 49)                                                 \
+  _ (KEY_SHARE, 51)                                                           \
+  _ (CONNECTION_ID, 54)                                                       \
+  _ (QUIC_TRANSPORT_PARAMETERS, 57)                                           \
+  _ (TICKET_REQUEST, 58)                                                      \
+  _ (DNSSEC_CHAIN, 59)
+
+typedef enum tls_handshake_extension_type_
+{
+#define _(sym, val) TLS_EXT_##sym = val,
+  foreach_tls_hanshake_extensions
+#undef _
+} tls_handshake_ext_type_t;
+
+/* Base struct for all extensions */
+typedef struct tls_handshake_ext_
+{
+  tls_handshake_ext_type_t type;
+  u8 extension[0];
+} tls_handshake_ext_t;
+
+typedef struct tls_handshake_ext_server_name_
+{
+  u8 name_type;
+  u8 *host_name;
+} tls_handshake_ext_sni_sn_t;
+
+typedef struct tls_handshake_ext_sni_
+{
+  tls_handshake_ext_t ext;
+  tls_handshake_ext_sni_sn_t *names;
+} tls_handshake_ext_sni_t;
+
+/* FQDN length as per rfc1035 */
+#define TLS_EXT_SNI_MAX_LEN 255
+
+#define foreach_tls_handshake_parse_error                                     \
+  _ (OK, "ok")                                                                \
+  _ (WANT_MORE, "want_more")                                                  \
+  _ (UNSUPPORTED, "unsupported")                                              \
+  _ (INVALID_LEN, "invalid_len")                                              \
+  _ (SESSION_ID_LEN, "session_id_len")                                        \
+  _ (CIPHER_SUITE_LEN, "cipher_suite_len")                                    \
+  _ (COMPRESSION_METHOD, "compression_method")                                \
+  _ (EXTENSIONS_LEN, "extensions_len")                                        \
+  _ (EXT_SNI_NAME_TYPE, "ext_sni_name_type")                                  \
+  _ (EXT_SNI_LEN, "ext_sni_len")
+
+typedef enum tls_handshake_parse_error_
+{
+#define _(sym, str) TLS_HS_PARSE_ERR_##sym,
+  foreach_tls_handshake_parse_error
+#undef _
+} tls_handshake_parse_error_t;
+
+typedef struct tls_hanshake_ext_info_
+{
+  tls_handshake_ext_type_t type;
+  u16 len;
+  u8 *data;
+} tls_handshake_ext_info_t;
+
+typedef struct tls_handshake_msg_info_
+{
+  tls_handshake_type_t type;
+  u32 len;
+  u8 legacy_session_id_len;
+  u8 *legacy_session_id;
+  u16 cipher_suite_len;
+  u8 *cipher_suites;
+  u16 extensions_len;
+  u8 *extensions;
+} tls_handshake_msg_info_t;
+
+static inline u8
+tls_record_type_is_valid (tls_record_type_t type)
+{
+  switch (type)
+    {
+    case TLS_REC_CHANGE_CIPHER_SPEC:
+    case TLS_REC_ALERT:
+    case TLS_REC_HANDSHAKE:
+    case TLS_REC_APPLICATION_DATA:
+    case TLS_REC_HEARTBEAT:
+      return 1;
+    default:
+      return 0;
+    }
+}
+
+static inline u8
+tls_record_hdr_is_valid (tls_record_header_t rec_hdr)
+{
+  u16 rec_len;
+
+  if (!tls_record_type_is_valid (rec_hdr.type))
+    return 0;
+
+  /* Support for SSLv3 and TLS1.0 to TLS1.3 */
+  if (rec_hdr.version.major != TLS_MAJOR_VERSION)
+    return 0;
+
+  rec_len = clib_net_to_host_u16 (rec_hdr.length);
+  if (rec_len == 0 || rec_len > TLS_FRAGMENT_MAX_ENC_LEN)
+    return 0;
+
+  return 1;
+}
+
+tls_handshake_parse_error_t
+tls_handshake_message_try_parse (u8 *msg, int len,
+                                tls_handshake_msg_info_t *info);
+tls_handshake_parse_error_t
+tls_hanshake_extensions_parse (tls_handshake_msg_info_t *info,
+                              tls_handshake_ext_info_t **exts);
+tls_handshake_parse_error_t
+tls_hanshake_extensions_try_parse (tls_handshake_msg_info_t *info,
+                                  tls_handshake_ext_info_t *req_exts,
+                                  u32 n_reqs);
+tls_handshake_parse_error_t
+tls_handshake_ext_parse (tls_handshake_ext_info_t *ext_info,
+                        tls_handshake_ext_t *ext);
+void tls_handshake_ext_free (tls_handshake_ext_t *ext);
+
+#endif /* SRC_VNET_TLS_TLS_RECORD_H__ */