Skip to content

Commit

Permalink
Improve TLS parsing (#27)
Browse files Browse the repository at this point in the history
Task/Issue URL:https://app.asana.com/0/488551667048375/1205675418279581/f

### Description
Improve TLS parsing

### Steps to test this PR

#### Unit tests
* cd `netguard/src/test`
* `make`
* `./test_tls` should pass

#### Smoke Tests
- [x] from this branch, publish the library to maven local ie. `./gradlew clean assemble publishToMavenLocal`
- [x] In the DDG android app apply the following path
```diff
Subject: [PATCH] Maven local use
---
Index: build.gradle
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/build.gradle b/build.gradle
--- a/build.gradle	(revision d11f7491d7ab4b27223fd352f83c26be403e79ed)
+++ b/build.gradle	(revision 3b1fe446b5d33e4d8a7f400137134ea0b5a797d7)
@@ -40,6 +40,7 @@
     repositories {
         google()
         mavenCentral()
+        mavenLocal()
     }
     configurations.all {
         resolutionStrategy.force 'org.objenesis:objenesis:2.6'
Index: versions.properties
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>ISO-8859-1
===================================================================
diff --git a/versions.properties b/versions.properties
--- a/versions.properties	(revision d11f7491d7ab4b27223fd352f83c26be403e79ed)
+++ b/versions.properties	(revision 3b1fe446b5d33e4d8a7f400137134ea0b5a797d7)
@@ -55,7 +55,7 @@
 
 version.com.android.installreferrer..installreferrer=2.2
 
-version.com.duckduckgo.netguard..netguard-android=1.6.0
+version.com.duckduckgo.netguard..netguard-android=1.7.0-SNAPSHOT
 
 version.com.duckduckgo.synccrypto..sync-crypto-android=0.3.0
 
```
- [x] build DDG app
- [x] AppTP smoke tests
  • Loading branch information
aitorvs authored Oct 11, 2023
1 parent af6fb01 commit 31a9d64
Show file tree
Hide file tree
Showing 8 changed files with 729 additions and 127 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/cmake-build-debug/
**/cmake-build-debug/
.idea
/CMakeFiles/
CMakeCache.txt
src/.vscode/
*.o
1 change: 1 addition & 0 deletions android/netguard/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ add_library( netguard
../../../../../src/netguard/session.c
../../../../../src/netguard/ip.c
../../../../../src/netguard/tls.c
../../../../../src/netguard/tls_parser.c
../../../../../src/netguard/tcp.c
../../../../../src/netguard/udp.c
../../../../../src/netguard/icmp.c
Expand Down
4 changes: 1 addition & 3 deletions src/netguard/include/tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@

#define TLS_EXTENSION_TYPE_SERVER_NAME 0

void get_server_name(
int get_server_name(
const uint8_t *pkt,
size_t length,
void *daddr,
uint8_t version,
const uint8_t *tcp_payload,
char *server_name
);
Expand Down
123 changes: 1 addition & 122 deletions src/netguard/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ int is_sni_found_and_blocked(
memset(sn, 0, FQDN_LENGTH);
*sn = 0;

get_server_name(pkt, length, daddr, version, tls, sn);
get_server_name(pkt, length, tls, sn);

if (strlen(sn) == 0) {
log_print(PLATFORM_LOG_PRIORITY_INFO, "TLS server name not found");
Expand All @@ -29,124 +29,3 @@ int is_sni_found_and_blocked(
return is_domain_blocked(args, sn, uid);
}

void get_server_name(
const uint8_t *pkt,
size_t length,
void *daddr,
uint8_t version,
const uint8_t *tls,
char *server_name
) {
// ensure length is 0
*server_name = 0;

char dest[INET6_ADDRSTRLEN + 1];
inet_ntop(version == 4 ? AF_INET : AF_INET6, daddr, dest, sizeof(dest));

// Check TLS client hello header
uint8_t content_type = (uint8_t) *tls;
if (content_type >= 20 && content_type <= 24){
// extract TLS versions
uint8_t tls_major_version = (uint8_t) tls[1];
uint8_t tls_minor_version = (uint8_t) tls[2];

log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS header found %d, %d/%d", content_type, tls_major_version, tls_minor_version);

if (tls_major_version < 0x03){
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS %d does not have SNI header", tls_major_version);
} else if (content_type == TLS_TYPE_APPLICATION_DATA) { // content type application data
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS application data for address %s", dest);
} else if (content_type == TLS_TYPE_HANDSHAKE_RECORD) { // content type handshake
// handshake packet type
uint16_t tls_handshake_size = (tls[3] << 8 & 0xFF00) + (tls[4] & 0x00FF);
if (length - (tls - pkt) < tls_handshake_size + 5) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS header too short");
} else if (tls[5] == 1) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS packet ClientHello msg found");

// Extract host from ClientHello SNI extension header

// this skips the TLS header, time and Client Random - and starts with the session ID length
uint32_t index = 43;
uint8_t session_id_len = tls[index++];
index += session_id_len;

uint16_t cipher_suite_len = (tls[index] << 8 & 0xFF00) + (tls[index + 1] & 0x00FF);
index += 2;
index += cipher_suite_len;

uint16_t compression_method_len = tls[index++];
index += compression_method_len;

uint16_t extensions_len = (tls[index] << 8 & 0xFF00) + (tls[index + 1] & 0x00FF);
index += 2;
if (extensions_len == 0) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS ClientHello, no extensions found");
} else {
// Extension headers found
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS ClientHello extensions found");

uint32_t searched = 0;
uint8_t found = 0;

while (searched < extensions_len && index + 2 < length) {
uint16_t extension_type = (tls[index] << 8 & 0xFF00) + (tls[index + 1] & 0x00FF);
index += 2;

// Extension type is SERVER_NAME_EXTENSION_TYPE
if (extension_type == TLS_EXTENSION_TYPE_SERVER_NAME) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS ClientHello SNI found at %d", index);
found = 1;
break;
} else {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS extension type %d", extension_type);

if (index + 1 >= length) {
break;
}

uint16_t extension_len = (tls[index] << 8 & 0xFF00) + (tls[index + 1] & 0x00FF);
index += 2;
// skip to the next extension, if there is one
index += extension_len;

// record number of extension bytes searched
// which is the current extension length + 4 (2 bytes for type, 2 bytes for length)
searched += extension_len + 4;
}
}

if (found) {
// skip 5 bytes for data sizes and list entry type we don't need to know about
index += 5;

uint16_t server_name_len = (tls[index] << 8 & 0xFF00) + (tls[index + 1] & 0x00FF);
index += 2;

// This should not happen but just guarding against it
if (server_name_len > FQDN_LENGTH) {
log_print(PLATFORM_LOG_PRIORITY_WARN, "TLS SNI too long %d", server_name_len);
} else {
memcpy(server_name, &tls[index], server_name_len);
server_name[server_name_len] = 0;
if (is_valid_utf8(server_name)) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS server name (%d bytes) is %s (%s)", server_name_len, server_name, dest);
} else {
log_print(PLATFORM_LOG_PRIORITY_WARN, "TLS server name not valid UTF-8: %s (%d bytes)", server_name, server_name_len);
*server_name = 0;
}
}
}

}

} else {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS packet is not ClientHello msg %d", tls[5]);
}
} else {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS packet is not handshake packet");
}
} else{
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "TLS header NOT found");
}
}
212 changes: 212 additions & 0 deletions src/netguard/tls_parser.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@

#include <stdio.h>
#include <stdint.h>
#include <string.h> /* strncpy() */
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
//#include <netinet/in6.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include "platform.h"
#include "tls.h"

static int parse_tls_server_name(const uint8_t *data, size_t data_len, char *server_name);
static int parse_extensions(const uint8_t*, size_t, char *);
static int parse_server_name_extension(const uint8_t*, size_t, char *);

#define TLS_HEADER_LEN 5 // size of the TLS Record Header
#ifndef MIN
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif


/**
* Parse a TLS packet for the Server Name Indication extension in the client hello handshake.
* Returns the first server name found
*
* @param data the TLS packet
* @param data_len the TLS packet length
* @param server_name pointer to the server name static array. This method does not allocate memory for it
*
* @returns
* >=0 length of the server name found
* -1 incomplete TLS request
* -2 no SNI header found
* -3 invalid TLS client hello
* -4 invalid TLs packet
*/
static int parse_tls_server_name(const uint8_t *data, size_t data_len, char *server_name) {
*server_name = 0;

if (data_len < TLS_HEADER_LEN) {
return -1;
}

/*
* Check for SSL 2.0 compatible Client Hello
*
* High bit of first byte (length) and content type is Client Hello
*
* See RFC5246 Appendix E.2
*/
if ((data[0] & 0x80) && (data[2] == 1)) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "Received SSL 2.0 Client Hello which can not support SNI.");
return -2;
}

uint8_t content_type = (uint8_t) *data;
if (content_type != 0x16) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "Request did not begin with TLS handshake.");
return -3;
}

uint8_t tls_version_major = data[1];
uint8_t tls_version_minor = data[2];
if (tls_version_major < 3) {
// receive handshake that can't support SNI
return -2;
}

/* TLS record length */
size_t len = ((size_t)data[3] << 8) + (size_t)data[4] + TLS_HEADER_LEN;
data_len = MIN(len, data_len);
if (data_len < len) {
// purposely don't return as we have checks later on
log_print(PLATFORM_LOG_PRIORITY_WARN, "TLS data length smaller than expected, proceed anyways");
}

/* handshake */
size_t pos = TLS_HEADER_LEN;
if (pos + 1 > data_len) {
return -4;
}

if (data[pos] != 0x1) {
// not a client hello
return -4;
}

/* Skip past fixed length records:
1 Handshake Type
3 Length
2 Version (again)
32 Random
to Session ID Length
*/
pos += 38;

// Session ID
if (pos + 1 > data_len) return -4;
len = (size_t)data[pos];
pos += 1 + len;

/* Cipher Suites */
if (pos + 2 > data_len) return -4;
len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1];
pos += 2 + len;

/* Compression Methods */
if (pos + 1 > data_len) return -4;
len = (size_t)data[pos];
pos += 1 + len;

if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
// "Received SSL 3.0 handshake without extensions"
return -2;
}

/* Extensions */
if (pos + 2 > data_len) {
return -4;
}
len = ((size_t)data[pos] << 8) + (size_t)data[pos + 1];
pos += 2;

if (pos + len > data_len) {
return -4;
}
return parse_extensions(data + pos, len, server_name);
}

static int parse_extensions(const uint8_t *data, size_t data_len, char *hostname) {
size_t pos = 0;
size_t len;

/* Parse each 4 bytes for the extension header */
while (pos + 4 <= data_len) {
/* Extension Length */
len = ((size_t)data[pos + 2] << 8) +
(size_t)data[pos + 3];

/* Check if it's a server name extension */
if (data[pos] == 0x00 && data[pos + 1] == 0x00) {
/* There can be only one extension of each type, so we break
our state and move p to beinnging of the extension here */
if (pos + 4 + len > data_len)
return -5;
return parse_server_name_extension(data + pos + 4, len, hostname);
}
pos += 4 + len; /* Advance to the next extension header */
}
/* Check we ended where we expected to */
if (pos != data_len)
return -5;

return -2;
}

static int parse_server_name_extension(const uint8_t *data, size_t data_len, char *hostname) {
size_t pos = 2; /* skip server name list length */
size_t len;

while (pos + 3 < data_len) {
len = ((size_t)data[pos + 1] << 8) +
(size_t)data[pos + 2];

if (pos + 3 + len > data_len) {
return -4;
}

switch (data[pos]) { /* name type */
case 0x00: /* host_name */
strncpy(hostname, (const char *)(data + pos + 3), len);
(hostname)[len] = '\0';
return len;
default:
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "Unknown server name extension name type: %d", data[pos]);
}
pos += 3 + len;
}
/* Check we ended where we expected to */
if (pos != data_len) {
return -4;
}

return -2;
}

int get_server_name(
const uint8_t *pkt,
size_t length,
const uint8_t *tls,
char *server_name
) {
size_t data_len = length - (tls - pkt);
int error_code = parse_tls_server_name(tls, data_len, server_name);
if (error_code >= 0) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "Found server name %s", server_name);
} else if (error_code == -1) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "Incomplete TLs request");
} else if (error_code == -2) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "No SNI header found");
} else if (error_code == -3) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "invalid TLS client hello");
} else if (error_code == -4) {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "invalid TLS packet");
} else {
log_print(PLATFORM_LOG_PRIORITY_DEBUG, "Unknown error");
}

return error_code;
}
18 changes: 18 additions & 0 deletions src/test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CC = gcc
CFLAGS = -Wall -Wimplicit-function-declaration -I../netguard/include

SRC = test_tls.c ../netguard/tls_parser.c
OBJ = $(SRC:.c=.o)
EXECUTABLE = test_tls

all: $(EXECUTABLE)

$(EXECUTABLE): $(OBJ)
$(CC) $(CFLAGS) $(OBJ) -o $@ $(LDFLAGS)

%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -f $(OBJ) $(EXECUTABLE)

Loading

0 comments on commit 31a9d64

Please sign in to comment.