servidor y cliente tcp utilizando SSL/TLS

Iniciado por loris24, Enero 10, 2026, 02:08:48 PM

Tema anterior - Siguiente tema

0 Miembros y 2 Visitantes están viendo este tema.

Enero 10, 2026, 02:08:48 PM Ultima modificación: Enero 10, 2026, 02:35:51 PM por loris24
Buenas chicos.

Continuo avanzando en el tema de redes y empiezo a traeros cosas mas avanzadas.

Esta vez os traigo sockets utilizando SSL/TLS.

Para introducir un poco de teoria... SSL/TLS no es mas que un protocolo que provee de un canal seguro a TCP.
Recordemos que la mayor virtud de TCP es que provee un canal "fiable" pero no seguro, no cifrado. TCP se encarga por supuesto
de la retransmision de las tramas y el seguimiento de las mismas mediante los ISN y las flags SYN y ACK, pero el payload del header TCP
viaja en texto plano, en bytes crudos; SSL/TLS es nuestro proveedor de seguridad, y os preguntareis los mas nuevos: ¿en que consiste? Pues
yo lo definiria como un "wrapper" sobre los sockets tcp y las versiones de TLS correspondientes para udp. Nosotros con SSL/TLS lo que hacemos
es iniciar la conexion como normalmente lo hariamos pero una vez iniciada, utilizamos la api de ssl para cifrar los datos que viajan a traves
de nuestro socket. No voy a extenderme mas en esto para no aburrir, pero el handshake TLS consiste en el siguiente proceso:

1-El cliente envia una cipher suite al servidor, que no es mas que un documento mediante el cual cliente le dice al servidor que algoritmos de cifrado soporta y el servidor "escogera" el mejor algoritmo de cifrado que ambos soportan.

2-Posteriormente el servidor envia el bien conocido certificado SSL al cliente. Esto no es mas que un documento emitido, generalmente, por un tercero
que garantiza que el servidor es quien dice ser. Un ejemplo de compañia que emite estos certificados es digicert. Esto resulta necesario puesto
que podemos pensar que un atacante puede hacerse pasar por nuestra entidad bancaria y dejarnos con el culo al aire. 

3-Una vez el cliente recibe el certificado comprueba que esta firmado por alguna de las entidades en quien este confia. Las entidades que hemos hablado en el punto anterior.

4-Cuando se termina este proceso de verificacion de identidad comenzamos el proceso para obtener nuestra clave para cifrar nuestros datos en el envio. Generalmente se utiliza un algoritmo de cifrado simetrico dada su mayor eficiencia con respecto a su allegado asimetrico. Para que ambas partes obtengan la clave secreta se utiliza un algoritmo de intercambio de claves como diffie-helman. Este proceso de iniciar el algoritmo de intercambio de claves es completamente transparente para nosotros. Lo unico que tenemos que hacer es encarganos de proveer un certificado a nuestro servidor y su clave privada para que pueda firmar la autenticacion de identidad de la que hablabamos en el punto 2.

Con esto teneis una idea aproximada de como es que se protegen vuestros datos cuando meteis vuestra tarjeta en una pasarela de pago o demas...

Bueno, basta de perorata y vayamos con el codigo.

El funcionamiento es sencillo, servidor y cliente, el cliente se conecta al servidor por puerto 9999 y le envia mensajes por consola.
El servidor los imprime por pantalla.

Os recomiendo que le echeis un vistazo a los mensajes con wireshark, asi podreis notar la diferencia con los anteriores codigos.

Para compilarlos necesitais la libreria de openssl, la instalais y luego, para compilar, ejecutais los comandos:

g++ You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login -o server -lssl -lcrypto          **esto es para el servidor
g++ You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login -o client -lssl -lcrypto          **esto es para el cliente

Tambien, teneis que crear un certificado ssl y firmarlo vosotros mismos. Antes de compilar el You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login y el You are not allowed to view links. You are not allowed to view links. Register or Login or You are not allowed to view links. Register or Login.

openssl req -x509 -newkey rsa:2048 -nodes -sha256 -keyout key.pem -out cert.pem -days 365

Esto os creara un key.pem y un cert.pem que ya vereis como se utilizan en el codigo del servidor.

Bueno, me despido. ¡Un saludo!


codigo del servidor:

Código: text
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <netdb.h>
#include <cstring>
#include <cstdlib>

//librerias para utilizar SSL/TLS
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>


#define IP_SERV NULL 
//especifico null porque quiero que mi SO me asigne la direccion de loopback para el servidor
#define IP_PORT "9999"




int main(int argc, char ** argv){

	//esto es simplemente para inicializar la libreria de openssl
	SSL_library_init();
	OpenSSL_add_all_algorithms();	//carga todos los algoritmos de cifrado
	SSL_load_error_strings();

	SSL_CTX *contexto = SSL_CTX_new(TLS_server_method());		//esto se va a utilizar para el handshake tls
	if(!contexto){
		std::cerr << "An error has ocurred while creating context" << std::endl;
		return -1;
	}


	int res_cert = SSL_CTX_use_certificate_file(contexto, "cert.pem", SSL_FILETYPE_PEM);
	int res_privkey = SSL_CTX_use_PrivateKey_file(contexto, "key.pem", SSL_FILETYPE_PEM);

	if(!res_cert || !res_privkey){
		std::cerr << "An error has ocurred while loading cert and key" << std::endl;
		ERR_print_errors_fp(stderr);
		return -1;
	}


	//creamos nuestro socket con normalidad
	struct addrinfo base;
	memset(&base, 0, sizeof base);
	base.ai_family = AF_INET; //IPV4
	base.ai_socktype = SOCK_STREAM; //TCP
	base.ai_flags = AI_PASSIVE; //server
	struct addrinfo *res;


	if(getaddrinfo(IP_SERV, IP_PORT, &base, &res) < 0){
		std::cerr << "An error has ocurred in getaddrinfo()" << std::endl;
		return -1;
	}

	int sock_listen = socket(
		res -> ai_family,
		res -> ai_socktype,
		res -> ai_protocol
		);

	if(sock_listen < 0){
		std::cerr << "An error has ocurred while creating socket" << std::endl;
		return -1;
	}

	if(bind(sock_listen, res -> ai_addr, res -> ai_addrlen) < 0){
		std::cerr << "An error has ocurred while binding ip to host" << std::endl;
		return -1;
	}

	if(listen(sock_listen, 10) < 0){
		std::cerr << "An error has ocurred while listening" << std::endl;
		return -1;
	}

	//creamos estas estructuras para obtener la informacion del cliente
	struct sockaddr_storage client_info;
	socklen_t size_client = sizeof(client_info);


	int sock_client = accept(
		sock_listen,
		(struct sockaddr *)&client_info,
		&size_client
		);

	if(sock_client < 0){
		std::cerr << "An error has ocurred while accepting new connections" << std::endl;
		return -1;
	}


	SSL *ssl = SSL_new(contexto);
	if(!ssl){
		std::cerr << "An error has ocurred while creating SSL objetct" << std::endl;
		return -1;
	}

	SSL_set_fd(ssl, sock_client);
	if(SSL_accept(ssl) <= 0){
		std::cerr << "SSL_accept() failed" << std::endl;
		ERR_print_errors_fp(stderr);
		SSL_shutdown(ssl);
		close(sock_client);
		SSL_free(ssl);
		return -1;
	}

	//vamos a empezar a recibir mensajes

	bool terminado = false;

	while(!terminado){
		char buff[1024];
		int bytes_received = SSL_read(ssl, buff, 1024);

		if(bytes_received < 0){
			std::cerr << "An error has ocurred while doing SSL_read()" << std::endl;
			break;
		}else if(bytes_received == 0){
			std::cout << "Connection closed" << std::endl;
			break;
		}else{
			printf("%.*s", bytes_received, buff);
		}
		
	}

	SSL_shutdown(ssl);
	close(sock_client);
	SSL_free(ssl);

	SSL_CTX_free(contexto),
	close(sock_listen);


	return 0;
}

Codigo del cliente:
Código: text
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <netdb.h>
#include <cstring>
#include <cstdlib>
#include <sys/select.h>

//librerias para utilizar SSL/TLS
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>


#define IP_SERV "127.0.0.1" 
#define IP_PORT "9999"


int main(int argc, char **argv){


	SSL_library_init();
	OpenSSL_add_all_algorithms();	//carga todos los algoritmos de cifrado
	SSL_load_error_strings();

	SSL_CTX *contexto = SSL_CTX_new(TLS_client_method());
	if(!contexto){
		/*
		Se puede utilizar la funcion de la libreria
		openssl para obtener los errores que han ocurrido
		*/
		std::cerr << "An error has ocurred while creating context" << std::endl;
		return -1;

	}


	//creamos nuestro socket con normalidad

	struct addrinfo base;
	memset(&base, 0, sizeof base);
	base.ai_socktype = SOCK_STREAM;
	struct addrinfo *res;

	if(getaddrinfo(IP_SERV, IP_PORT, &base, &res) < 0){
		std::cerr << "An error has ocurred while using getaddrinfo()" << std::endl;
		return -1;
	}

	int sock_client = socket(
		res -> ai_family,
		res -> ai_socktype,
		res -> ai_protocol
		);
	
	if(sock_client < 0){
		std::cerr << "An error has ocurred while creating socket" << std::endl;
		return -1;
	}

	if(connect(sock_client, res -> ai_addr, res -> ai_addrlen) < 0){
		std::cerr << "An error has ocurred while connecting to serv" << std::endl;
		return -1;
	}
	freeaddrinfo(res); //liberamos espacio que ya no necesitamos

	/*
	aqui deberiamos de comprobar los certificados del servidor
	pero este paso nos lo vamos a saltar
	*/

	SSL *ssl = SSL_new(contexto);
	if(!ssl){
		std::cerr << "SSL_new() failed" << std::endl;
	}
	SSL_set_fd(ssl, sock_client);

	if(SSL_connect(ssl) <= 0){
		std::cerr << "An error has ocurred while doing the tls handshake" << std::endl;
		ERR_print_errors_fp(stderr);
		SSL_shutdown(ssl);
		close(sock_client);
		SSL_free(ssl);
		return -1;
	}



	//ya podemos utilizar nuestro socket ssl
	bool terminado = false;
	while(!terminado){

		//manejamos entrada por stdin
		fd_set out;
		FD_ZERO(&out);
		FD_SET(1, &out);
		int max_fd = 1;
		int res_select = select(max_fd + 1, &out, 0, 0, 0);

		if(res_select < 0){
			std::cerr << "An error has ocurred with select" << std::endl;
			break;
		}else{

			char buff[1024];
			if(!fgets(buff, 1024, stdin)) break;

			int bytes_sent = SSL_write(ssl, buff, 1024);

			if(bytes_sent == 0){
				std::cout << "Connection closed" <<std::endl;
				break;
			}else if(bytes_sent < 0){
				std::cerr << "An error has ocurred with SSL_write()" << std::endl;
				break;
			}else{
				std::cout << "Bytes sent:" << bytes_sent << std::endl;
			}
		}
	}


	SSL_shutdown(ssl);
	close(sock_client);
	SSL_free(ssl);
	SSL_CTX_free(contexto);


	return 0;

}