From: Nikos Mavrogiannopoulos Date: Sun, 13 Mar 2011 17:11:39 +0000 (+0100) Subject: Added photuris-like resource protection on the server. Added gnutls_dtls_cookie_send... X-Git-Tag: gnutls_2_99_0~121 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=541aed3982a0b4e6d968ff2d1a38cfd4306f2d2b;p=thirdparty%2Fgnutls.git Added photuris-like resource protection on the server. Added gnutls_dtls_cookie_send(), gnutls_dtls_cookie_verify() and gnutls_dtls_cookie_set() to avoid initializing a session before cookie is verified. --- diff --git a/lib/gnutls_dtls.c b/lib/gnutls_dtls.c index 36262c95ae..708e262c65 100644 --- a/lib/gnutls_dtls.c +++ b/lib/gnutls_dtls.c @@ -366,6 +366,8 @@ void gnutls_dtls_set_mtu (gnutls_session_t session, unsigned int mtu) * application data. I.e. DTLS headers are subtracted from the * actual MTU. * + * Returns: the maximum allowed transfer unit. + * **/ unsigned int gnutls_dtls_get_mtu (gnutls_session_t session) { @@ -377,3 +379,244 @@ int ret; else return session->internals.dtls.mtu - RECORD_HEADER_SIZE(session); } + +#define COOKIE_SIZE 19 +#define COOKIE_MAC_SIZE 16 + +/* record seq || hsk read seq || hsk write seq || MAC + * 1 byte 1 byte 1 byte 16 bytes + * + * total 19 bytes + */ + +#define C_HASH GNUTLS_MAC_SHA1 +#define C_HASH_SIZE 20 + +/** + * gnutls_dtls_cookie_send: + * @key: is a random key to be used at cookie generation + * @client_data: contains data identifying the client (i.e. address) + * @client_data_size: The size of client's data + * @cookie: The previous cookie returned by gnutls_dtls_cookie_verify() + * @ptr: A transport pointer to be used by @push_func + * @push_func: A function that will be used to reply + * + * This function can be used to prevent denial of service + * attacks to a DTLS server by requiring the client to + * reply using a cookie sent by this function. That way + * it can be ensured that a client we allocated resources + * for (i.e. #gnutls_session_t) is the one that the + * original incoming packet was originated from. + * + * Returns: the number of bytes sent, or a negative error code. + * + **/ +int gnutls_dtls_cookie_send(gnutls_datum_t* key, void* client_data, size_t client_data_size, + gnutls_cookie_st* cookie, + gnutls_transport_ptr_t ptr, gnutls_push_func push_func) +{ + +opaque hvr[20+DTLS_HANDSHAKE_HEADER_SIZE+COOKIE_SIZE]; +int hvr_size = 0, ret; +uint8_t digest[C_HASH_SIZE]; + + if (key == NULL || key->data == NULL || key->size == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + +/* send + * struct { + * ContentType type - 1 byte GNUTLS_HANDSHAKE; + * ProtocolVersion version; - 2 bytes (254,255) + * uint16 epoch; - 2 bytes (0, 0) + * uint48 sequence_number; - 4 bytes (0,0,0,0) + * uint16 length; - 2 bytes (COOKIE_SIZE+1+2)+DTLS_HANDSHAKE_HEADER_SIZE + * opaque fragment[DTLSPlaintext.length]; + * } DTLSPlaintext; + * + * + * struct { + * HandshakeType msg_type; 1 byte - GNUTLS_HANDSHAKE_HELLO_VERIFY_REQUEST + * uint24 length; - COOKIE_SIZE+3 + * uint16 message_seq; - 2 bytes (0,0) + * uint24 fragment_offset; - 3 bytes (0,0,0) + * uint24 fragment_length; - same as length + * } + * + * struct { + * ProtocolVersion server_version; + * opaque cookie<0..32>; + * } HelloVerifyRequest; + */ + + hvr[hvr_size++] = GNUTLS_HANDSHAKE; + /* version */ + hvr[hvr_size++] = 254; + hvr[hvr_size++] = 255; + + /* epoch + seq */ + memset(&hvr[hvr_size], 0, 8); + hvr_size += 8; + + /* length */ + _gnutls_write_uint16(DTLS_HANDSHAKE_HEADER_SIZE+COOKIE_SIZE+3, &hvr[hvr_size]); + hvr_size += 2; + + /* now handshake headers */ + hvr[hvr_size++] = GNUTLS_HANDSHAKE_HELLO_VERIFY_REQUEST; + _gnutls_write_uint24(COOKIE_SIZE+3, &hvr[hvr_size]); + hvr_size += 3; + + /* handshake seq */ + memset(&hvr[hvr_size], 0, 2); + hvr_size += 2; + + _gnutls_write_uint24(0, &hvr[hvr_size]); + hvr_size += 3; + + _gnutls_write_uint24(COOKIE_SIZE+3, &hvr[hvr_size]); + hvr_size += 3; + + /* version */ + hvr[hvr_size++] = 254; + hvr[hvr_size++] = 255; + hvr[hvr_size++] = COOKIE_SIZE; + + ret = _gnutls_hmac_fast(C_HASH, key->data, key->size, client_data, client_data_size, digest); + if (ret < 0) + return gnutls_assert_val(ret); + + if (cookie && cookie->cookie_size > 3) + { + hvr[hvr_size++] = cookie->cookie[0]+1/* record */; + hvr[hvr_size++] = cookie->cookie[1]+1/* hsk read seq*/; + hvr[hvr_size++] = cookie->cookie[2]+1/* hsk write seq */; + } + else + { + hvr[hvr_size++] = 1; + hvr[hvr_size++] = 1; + hvr[hvr_size++] = 1; + } + + memcpy(&hvr[hvr_size], digest, COOKIE_MAC_SIZE); + hvr_size+= COOKIE_MAC_SIZE; + + ret = push_func(ptr, hvr, hvr_size); + if (ret < 0) + ret = GNUTLS_E_PUSH_ERROR; + + return ret; +} + +/** + * gnutls_dtls_cookie_verify: + * @key: is a random key to be used at cookie generation + * @client_data: contains data identifying the client (i.e. address) + * @client_data_size: The size of client's data + * @msg: An incoming message that initiates a connection. + * @msg_size: The size of the message. + * @cookie: The cookie of this client. + * + * This function will verify an incoming message for + * a valid cookie. If a valid cookie is returned then + * it should be associated with the session using + * gnutls_dtls_cookie_set(); + * + * Returns: zero on success, or a negative error code. + * + **/ +int gnutls_dtls_cookie_verify(gnutls_datum_t* key, + void* client_data, size_t client_data_size, + void* _msg, size_t msg_size, gnutls_cookie_st* out) +{ +gnutls_datum_t cookie; +int sid_size; +int pos, ret; +uint8_t * msg = _msg; +uint8_t digest[C_HASH_SIZE]; + + if (key == NULL || key->data == NULL || key->size == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + /* format: + * version - 2 bytes + * random - 32 bytes + * session_id - 1 byte length + content + * cookie - 1 byte length + content + */ + + pos = 34+DTLS_RECORD_HEADER_SIZE+DTLS_HANDSHAKE_HEADER_SIZE; + + if (msg_size < pos+1) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + sid_size = msg[pos++]; + + if (sid_size > 32 || msg_size < pos+sid_size+1) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + pos += sid_size; + cookie.size = msg[pos++]; + + if (msg_size < pos+cookie.size+1) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + cookie.data = &msg[pos]; + if (cookie.size != COOKIE_SIZE) + { + if (cookie.size > 0) _gnutls_audit_log("Received cookie with illegal size %d. Expected %d\n", (int)cookie.size, COOKIE_SIZE); + return gnutls_assert_val(GNUTLS_E_BAD_COOKIE); + } + + ret = _gnutls_hmac_fast(C_HASH, key->data, key->size, client_data, client_data_size, digest); + if (ret < 0) + return gnutls_assert_val(ret); + + if (memcmp(digest, &cookie.data[3], COOKIE_MAC_SIZE) != 0) + return gnutls_assert_val(GNUTLS_E_BAD_COOKIE); + + memcpy(out->cookie, cookie.data, cookie.size); + out->cookie_size = cookie.size; + + return 0; +} + +/** + * gnutls_dtls_cookie_set: + * @session: a new session + * @cookie: contains the client's cookie + * + * This function will associate the received cookie by + * the client, with the newly established session. + * + * Returns: zero on success, or a negative error code. + * + **/ +void gnutls_dtls_cookie_set(gnutls_session_t session, gnutls_cookie_st* st) +{ + record_parameters_st *params; + int ret; + + if (st == NULL || st->cookie_size == 0) + return; + + /* we do not care about read_params, since we accept anything + * the peer sends. + */ + ret = _gnutls_epoch_get (session, EPOCH_WRITE_CURRENT, ¶ms); + if (ret < 0) + return; + + if (st->cookie_size < 3) + return; + + params->write.sequence_number.i[7] = st->cookie[0]; + + session->internals.dtls.hsk_read_seq = st->cookie[1]; + session->internals.dtls.hsk_write_seq = st->cookie[2]; + +fprintf(stderr, "record send seq: %d\n", (int)st->cookie[0]); +fprintf(stderr, "hsk read seq: %d\n", (int)st->cookie[1]); +fprintf(stderr, "hsk write seq: %d\n", (int)st->cookie[2]); + +} diff --git a/lib/gnutls_errors.c b/lib/gnutls_errors.c index 4d499ab3ac..d9548e3884 100644 --- a/lib/gnutls_errors.c +++ b/lib/gnutls_errors.c @@ -173,6 +173,7 @@ static const gnutls_error_entry error_algorithms[] = { ERROR_ENTRY (N_("The given memory buffer is too short to hold parameters."), GNUTLS_E_SHORT_MEMORY_BUFFER, 1), ERROR_ENTRY (N_("The request is invalid."), GNUTLS_E_INVALID_REQUEST, 1), + ERROR_ENTRY (N_("The cookie was bad."), GNUTLS_E_BAD_COOKIE, 1), ERROR_ENTRY (N_("An illegal parameter has been received."), GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER, 1), ERROR_ENTRY (N_("Error while reading file."), GNUTLS_E_FILE_ERROR, 1), diff --git a/lib/includes/gnutls/dtls.h b/lib/includes/gnutls/dtls.h index 9177cf85d1..9f5dde91f1 100644 --- a/lib/includes/gnutls/dtls.h +++ b/lib/includes/gnutls/dtls.h @@ -36,12 +36,30 @@ extern "C" { #endif +#define GNUTLS_COOKIE_KEY_SIZE 16 + void gnutls_dtls_set_timeouts (gnutls_session_t session, unsigned int retrans_timeout, unsigned int total_timeout); unsigned int gnutls_dtls_get_mtu (gnutls_session_t session); void gnutls_dtls_set_mtu (gnutls_session_t session, unsigned int mtu); +typedef struct { + unsigned char cookie[255]; + size_t cookie_size; +} gnutls_cookie_st; + +int gnutls_dtls_cookie_send(gnutls_datum_t* key, void* client_data, size_t client_data_size, + gnutls_cookie_st* cookie, + gnutls_transport_ptr_t ptr, gnutls_push_func push_func); + + +int gnutls_dtls_cookie_verify(gnutls_datum_t* key, + void* client_data, size_t client_data_size, + void* _msg, size_t msg_size, gnutls_cookie_st* cookie); + +void gnutls_dtls_cookie_set(gnutls_session_t session, gnutls_cookie_st* st); + #ifdef __cplusplus } #endif diff --git a/lib/libgnutls.map b/lib/libgnutls.map index b91d291bf9..aa76fde3ae 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -696,6 +696,10 @@ GNUTLS_3_0_0 { gnutls_dtls_get_mtu; gnutls_dtls_set_mtu; gnutls_record_recv_seq; + gnutls_key_generate; + gnutls_dtls_cookie_verify; + gnutls_dtls_cookie_send; + gnutls_dtls_cookie_set; } GNUTLS_2_12; GNUTLS_PRIVATE { diff --git a/src/udp-serv.c b/src/udp-serv.c index f77961c840..9202565dba 100644 --- a/src/udp-serv.c +++ b/src/udp-serv.c @@ -29,11 +29,23 @@ int udp_server(const char* name, int port, int mtu) char buffer[MAX_BUFFER]; priv_data_st priv; gnutls_session_t session; + gnutls_datum_t cookie_key; + gnutls_cookie_st cookie; unsigned char sequence[8]; + ret = gnutls_key_generate(&cookie_key, GNUTLS_COOKIE_KEY_SIZE); + if (ret < 0) + { + fprintf(stderr, "Cannot generate key\n"); + exit(1); + } + ret = listen_socket (name, port, SOCK_DGRAM); if (ret < 0) - exit (1); + { + fprintf(stderr, "Cannot listen\n"); + exit (1); + } for (;;) { @@ -43,16 +55,39 @@ int udp_server(const char* name, int port, int mtu) continue; cli_addr_size = sizeof(cli_addr); - ret = recvfrom(sock, buffer, 1, MSG_PEEK, (struct sockaddr*)&cli_addr, &cli_addr_size); - if (ret == 1) - printf ("Accepted connection from %s\n", + ret = recvfrom(sock, buffer, sizeof(buffer), MSG_PEEK, (struct sockaddr*)&cli_addr, &cli_addr_size); + if (ret > 0) + { + memset(&cookie, 0, sizeof(cookie)); + ret = gnutls_dtls_cookie_verify(&cookie_key, &cli_addr, sizeof(cli_addr), buffer, ret, &cookie); + if (ret < 0) /* cookie not valid */ + { + priv_data_st s; + + memset(&s,0,sizeof(s)); + s.fd = sock; + s.cli_addr = (void*)&cli_addr; + s.cli_addr_size = sizeof(cli_addr); + + printf("Sending hello verify request to %s\n", human_addr ((struct sockaddr *) + &cli_addr, sizeof(cli_addr), buffer, sizeof(buffer))); + gnutls_dtls_cookie_send(&cookie_key, &cli_addr, sizeof(cli_addr), &cookie, (gnutls_transport_ptr_t)&s, push_func); + + /* discard peeked data*/ + recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&cli_addr, &cli_addr_size); + usleep(100); + continue; + } + printf ("Accepted connection from %s\n", human_addr ((struct sockaddr *) &cli_addr, sizeof(cli_addr), buffer, sizeof (buffer))); + } else continue; session = initialize_session(1); + gnutls_dtls_cookie_set(session, &cookie); if (mtu) gnutls_dtls_set_mtu(session, mtu); priv.session = session;