Hi, I'm trying to simulate a DTLS client and server communication over SCTP as transport. I've used sample code provided by GNUTLS and modified it a little bit to achieve my desired result. I was referring to "RFC 4347 Figure 1. Message flights for full handshake" for verifying the Handshake procedure whether its taking place properly or not.
I've used following link "https://help.ubuntu.com/community/GnuTLS" to create secret keys and certificate for server and i did not create certificate for client as its optional part in handshake. The initial steps are taking place properly except the part that i'm not able to see "Finished" message from either of the side. So i believe its not completed without Finished message. I might be wrong but please verify. There is one message which is "encrypted alert". I'm not able to understand its role in communication. What is requirement of sending this message after sending encrypted data or any control message? I'm sharing the code which i'm using to simulate this scenario. In addition to that i'm also sharing the wireshark trace which i captured while running this simulation. In case you want secret keys and certificate then please let me know. You'll need to use decode as option to view the DTLS packets otherwise they will appear as m3ua packets in the trace which i've shared. GNUTLS Version : 3.2.15 Nettle Version: 2.7.1 GMP Version: 5.1.1-2 OS: Fedora 19 Kernel: 3.14.4-100.fc19.x86_64 Thanks, Sandeep
/* This example code is placed in the public domain. */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <gnutls/gnutls.h>
#include <gnutls/dtls.h>
#include <netinet/sctp.h>
/* A very basic Datagram TLS client, over UDP with X.509 authentication.
*/
#define MAX_BUF 1024
#define CAFILE "/etc/ssl/certs/ca-bundle.crt"
#define MSG "GET / HTTP/1.0\r\n\r\n"
//extern int udp_connect(void);
//extern void udp_close(int sd);
extern int verify_certificate_callback(gnutls_session_t session);
static void tls_log_func(int level, const char *str)
{
printf(" |<%d>| %s", level, str);
}
int main(int argc, char ** argv)
{
int ret, sd, ii;
gnutls_session_t session;
char buffer[MAX_BUF + 1];
const char *err;
gnutls_certificate_credentials_t xcred;
struct sockaddr_in servaddr;
if (argc<2)
{
printf("Usage is : <exe-name> <server-ip> \n");
exit(-1) ;
}
if (gnutls_check_version("3.1.4") == NULL)
{
fprintf(stderr, "GnuTLS 3.1.4 is required for this example\n");
exit(1);
}
gnutls_global_set_log_function(tls_log_func);
gnutls_global_set_log_level(10);
gnutls_global_init();
/* X509 stuff */
gnutls_certificate_allocate_credentials(&xcred);
/* sets the trusted cas file */
gnutls_certificate_set_x509_trust_file(xcred, CAFILE, GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_verify_function(xcred, verify_certificate_callback);
/* Initialize TLS session */
gnutls_init(&session, GNUTLS_CLIENT | GNUTLS_DATAGRAM);
/* Use default priorities */
ret = gnutls_priority_set_direct(session, "NORMAL", &err);
if (ret < 0)
{
if (ret == GNUTLS_E_INVALID_REQUEST)
{
fprintf(stderr, "Syntax error at: %s\n", err);
}
exit(1);
}
/* put the x509 credentials to the current session */
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
gnutls_server_name_set(session, GNUTLS_NAME_DNS, "lxc-logQpjLrM", strlen("lxc-logQpjLrM"));
/* connect to the peer */
// sd = udp_connect();
//sd = socket( AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP );
sd = socket( AF_INET, SOCK_STREAM , IPPROTO_SCTP );
/*
struct sctp_authchunk auth;
auth.sauth_chunk = 0; // send auth packets for all data messages
ret = setsockopt(sd, IPPROTO_SCTP, SCTP_AUTH_CHUNK, &auth, sizeof(struct sctp_authchunk));
*/
//struct sctp_authchunk sac;
//if (setsockopt(sd, IPPROTO_SCTP, SCTP_AUTH_CHUNK, &sac, (socklen_t)sizeof(struct sctp_authchunk)) < 0)
// perror("setsockopt");
servaddr.sin_family = AF_INET;
printf ("Connecting to %s", argv[1]);
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(2905);
ret = connect( sd, (struct sockaddr *)&servaddr,sizeof(servaddr));
if (ret == -1)
{
perror("Error:");
fprintf(stderr,"connect to server failed\n");
exit(1);
}
printf("SCTP-Connect Done\n");
gnutls_transport_set_int(session, sd);
/* set the connection MTU */
gnutls_dtls_set_mtu(session, 1500);
gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
printf("perform dtls handshake\n");
/* Perform the TLS handshake */
do
{
ret = gnutls_handshake(session);
}
while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
/* Note that DTLS may also receive GNUTLS_E_LARGE_PACKET */
printf("dtls handshake ret value:%d\n",ret);
if (ret < 0)
{
fprintf(stderr, "*** Handshake failed\n");
gnutls_perror(ret);
goto end;
}
else
{
char *desc;
desc = gnutls_session_get_desc(session);
printf("- Session info: %s\n", desc);
gnutls_free(desc);
}
gnutls_record_send(session, MSG, strlen(MSG));
// gnutls_record_send(session, MSG, strlen(MSG));
// gnutls_record_send(session, MSG, strlen(MSG));
ret = gnutls_record_recv(session, buffer, MAX_BUF);
if (ret == 0)
{
printf("- Peer has closed the TLS connection\n");
goto end;
}
else if (ret < 0 && gnutls_error_is_fatal(ret) == 0)
{
fprintf(stderr, "*** Warning: %s\n", gnutls_strerror(ret));
}
else if (ret < 0)
{
fprintf(stderr, "*** Error: %s\n", gnutls_strerror(ret));
goto end;
}
if (ret > 0)
{
printf("- Received %d bytes: ", ret);
for (ii = 0; ii < ret; ii++)
{
fputc(buffer[ii], stdout);
}
fputs("\n", stdout);
}
/* It is suggested not to use GNUTLS_SHUT_RDWR in DTLS
* connections because the peer's closure message might
* be lost */
gnutls_bye(session, GNUTLS_SHUT_WR);
end:
// udp_close(sd);
close(sd);
gnutls_deinit(session);
gnutls_certificate_free_credentials(xcred);
gnutls_global_deinit();
return 0;
}
Makefile
Description: Binary data
/*
* =====================================================================================
*
* Filename: sctp_server.c
*
* Description: this is a one-to-one socket implementation of dtls over sctp
*
* Version: 1.0
* Created: Sunday 15 June 2014 06:34:50 PM IST
* Revision: none
* Compiler: gcc
*
* Author: Daniel Raj
* Organization: Mobileum
*
* =====================================================================================
*/
/* This example code is placed in the public domain. */
/* To enable socket features used for Solaris SCTP socket. */
#ifdef __sun
#define _XPG4_2
#define __EXTENSIONS__
#endif
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/dtls.h>
#include <netinet/sctp.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <netdb.h>
#define KEYFILE "key.pem"
#define CERTFILE "cert.pem"
#define CAFILE "/etc/ssl/certs/ca-bundle.crt"
#define CRLFILE "crl.pem"
/* This is a sample DTLS echo server, using X.509 authentication.
* Note that error checking is minimal to simplify the example.
*/
#define MAX_BUFFER 2048
#define PORT 2905
typedef struct {
gnutls_session_t session;
int fd;
struct sockaddr *cli_addr;
socklen_t cli_addr_size;
} priv_data_st;
static int pull_timeout_func(gnutls_transport_ptr_t ptr, unsigned int ms);
static ssize_t push_func(gnutls_transport_ptr_t p, const void *data, size_t size);
static ssize_t pull_func(gnutls_transport_ptr_t p, void *data, size_t size);
static const char *human_addr(const struct sockaddr *sa, socklen_t salen,char *buf, size_t buflen);
static int wait_for_connection(int fd);
static int generate_dh_params(void);
/* Use global credentials and parameters to simplify
* the example. */
static gnutls_certificate_credentials_t x509_cred;
static gnutls_priority_t priority_cache;
static gnutls_dh_params_t dh_params;
static void tls_log_func(int level, const char *str)
{
printf(" |<%d>| %s", level, str);
}
void handle_sctp_event(struct msghdr *str);
static void tls_audit_log_func(gnutls_session_t *session, const char * str)
{
fprintf(stderr, "|<%p>| %s", session, str);
return;
}
int main(void)
{
int listen_sd;
int sock, ret;
struct sockaddr_in sa_serv;
struct sockaddr_in cli_addr;
socklen_t cli_addr_size;
gnutls_session_t session;
priv_data_st priv;
gnutls_datum_t cookie_key;
gnutls_dtls_prestate_st prestate;
int mtu = 1400;
unsigned char buffer[MAX_BUFFER];
unsigned char sequence[18];
const char * err;
/* this must be called once in the program
*/
gnutls_global_set_log_function(tls_log_func);
gnutls_global_set_log_level(10);
// gnutls_global_set_audit_log_function(tls_audit_log_func);
gnutls_global_init();
gnutls_certificate_allocate_credentials(&x509_cred);
gnutls_certificate_set_x509_trust_file(x509_cred, CAFILE, GNUTLS_X509_FMT_PEM);
gnutls_certificate_set_x509_crl_file(x509_cred, CRLFILE, GNUTLS_X509_FMT_PEM);
ret = gnutls_certificate_set_x509_key_file(x509_cred, CERTFILE, KEYFILE, GNUTLS_X509_FMT_PEM);
if (ret < 0)
{
printf(" No certificate or key were found\n");
exit(1);
}
generate_dh_params();
gnutls_certificate_set_dh_params(x509_cred, dh_params);
gnutls_priority_init(&priority_cache, "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.0:%SERVER_PRECEDENCE", NULL);
gnutls_key_generate(&cookie_key, GNUTLS_COOKIE_KEY_SIZE);
/* Socket operations
*/
listen_sd = socket(PF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (listen_sd < 0 )
{
perror("Socket Error::");
exit (-5);
}
memset(&sa_serv, '\0', sizeof(sa_serv));
sa_serv.sin_family = PF_INET;
sa_serv.sin_addr.s_addr = inet_addr("172.16.130.65");
sa_serv.sin_port = htons(PORT);
{ /* DTLS requires the IP don't fragment (DF) bit to be set */
#if defined(IP_DONTFRAG)
int optval = 1;
setsockopt(listen_sd, IPPROTO_IP, IP_DONTFRAG,
(const void *) &optval, sizeof(optval));
printf(" Dont Fragment :)\n");
#elif defined(IP_MTU_DISCOVER)
int optval = IP_PMTUDISC_DO;
setsockopt(listen_sd, IPPROTO_IP, IP_MTU_DISCOVER,
(const void *) &optval, sizeof(optval));
printf(" IP_MTU_DISCOVER set\n");
#endif
}
if ( bind(listen_sd, (struct sockaddr *) &sa_serv, sizeof(sa_serv)) < 0 )
{
fprintf(stderr, "Bind failed [ %s ]\n", strerror(errno));
perror (" Bind Error:");
exit (-1);
}
printf(" Socket number is %d\n",listen_sd);
printf(" SCTP server ready. Listening to port '%d'.\n\n", PORT);
ret=listen(listen_sd,100);
if(ret <0)
{
printf(" listen failed\n");
exit(-1);
}
printf(" Waiting for connection...\n");
while(1)
{
sock=accept(listen_sd,NULL,0);
if(sock>0)
{
printf(" **Connection accepted\n");
break; // come out of the loop
}
}
printf(" SCTP Connection Established\n");
struct sctp_event_subscribe events;
/* Events to be notified for */
memset(&events, 0, sizeof(events));
events.sctp_data_io_event = 1;
events.sctp_association_event = 1;
events.sctp_send_failure_event = 1;
events.sctp_address_event = 1;
events.sctp_peer_error_event = 1;
events.sctp_shutdown_event = 1;
setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
struct sctp_status st;
socklen_t st_len;
st_len=sizeof(st);
memset(&st,'\0',sizeof(st));
//st.sstat_assoc_id =0;
if(getsockopt(sock, IPPROTO_SCTP, SCTP_STATUS, &st,&st_len) < 0)
{
perror(" getsockopt");
}
for (;;)
{
struct sctp_sndrcvinfo srinfo;
int flags = 0 ;
cli_addr_size = sizeof(cli_addr);
ret = sctp_recvmsg(sock,buffer, MAX_BUFFER,(struct sockaddr *) &cli_addr, &cli_addr_size, &srinfo, &flags);
struct msghdr* msg = (struct msghdr *)buffer;
printf (" Size of message %d\n", ret) ;
if (flags & MSG_NOTIFICATION)
{
printf(" sctp event\n") ;
handle_sctp_event(msg) ;
continue;
}
if (ret > 0)
{
memset(&prestate, 0, sizeof(prestate));
ret = gnutls_dtls_cookie_verify(&cookie_key, &cli_addr, sizeof(cli_addr), buffer, ret, &prestate);
printf (" Sending hello verify request to %s\n", human_addr((struct sockaddr *)&cli_addr,sizeof(cli_addr), buffer, sizeof(buffer)));
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 (" Re-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),&prestate,(gnutls_transport_ptr_t)&s, push_func);
/* discard peeked data */
//ret=recvfrom(sock, buffer, sizeof(buffer), 0,
recvfrom(sock, buffer, sizeof(buffer), 0,(struct sockaddr *) &cli_addr,&cli_addr_size);
usleep(100);
continue;
}
printf(" Accepted DTLS connection from %s\n", human_addr((struct sockaddr *)&cli_addr, sizeof(cli_addr),buffer, sizeof(buffer)));
}
else
{
printf (" Recvfrom failed \n");
perror(" recvfrom");
goto end;
}
gnutls_init(&session, GNUTLS_SERVER | GNUTLS_DATAGRAM);
gnutls_priority_set(session, priority_cache);
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_dtls_prestate_set(session, &prestate);
gnutls_dtls_set_mtu(session, mtu);
priv.session = session;
priv.fd = sock;
priv.cli_addr = (struct sockaddr *) &cli_addr;
priv.cli_addr_size = sizeof(cli_addr);
gnutls_transport_set_ptr(session, &priv);
gnutls_transport_set_push_function(session, push_func);
gnutls_transport_set_pull_function(session, pull_func);
gnutls_transport_set_pull_timeout_function(session, pull_timeout_func);
do
{
ret = gnutls_handshake(session);
}
while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
/* Note that DTLS may also receive GNUTLS_E_LARGE_PACKET.
* In that case the MTU should be adjusted.
*/
if (ret < 0)
{
fprintf(stderr, "Error in handshake(): %s\n", gnutls_strerror(ret));
gnutls_deinit(session);
continue;
}
printf(" - DTLS Handshake was completed\n");
for (;;)
{
do
{
ret = gnutls_record_recv_seq(session, buffer,MAX_BUFFER, sequence);
}
while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
if (ret < 0 && gnutls_error_is_fatal(ret) == 0)
{
fprintf(stderr, "*** Warning: %s\n", gnutls_strerror(ret));
continue;
}
else if (ret < 0)
{
fprintf(stderr, "Error in recv(): %s\n", gnutls_strerror(ret));
break;
}
if (ret == 0)
{
printf(" EOF\n\n");
break;
}
buffer[ret] = 0;
printf (" received[%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x]: %s\n",
sequence[0], sequence[1], sequence[2],
sequence[3], sequence[4], sequence[5],
sequence[6], sequence[7], buffer);
/* reply back */
ret = gnutls_record_send(session, buffer, ret);
if (ret < 0)
{
fprintf(stderr, "Error in send(): %s\n", gnutls_strerror(ret));
break;
}
}
gnutls_bye(session, GNUTLS_SHUT_WR);
gnutls_deinit(session);
}
end:
close(listen_sd);
gnutls_certificate_free_credentials(x509_cred);
gnutls_priority_deinit(priority_cache);
gnutls_global_deinit();
return 0;
}
static int wait_for_connection(int fd)
{
fd_set rd, wr;
int n;
FD_ZERO(&rd);
FD_ZERO(&wr);
FD_SET(fd, &rd);
/* waiting part */
n = select(fd + 1, &rd, &wr, NULL, NULL);
if (n == -1 && errno == EINTR)
return -1;
if (n < 0)
{
perror(" select()");
exit(1);
}
return fd;
}
/* Wait for data to be received within a timeout period in milliseconds
*/
static int pull_timeout_func(gnutls_transport_ptr_t ptr, unsigned int ms)
{
fd_set rfds;
struct timeval tv;
priv_data_st *priv = ptr;
struct sockaddr_in cli_addr;
socklen_t cli_addr_size;
int ret;
char c;
FD_ZERO(&rfds);
FD_SET(priv->fd, &rfds);
tv.tv_sec = 0;
tv.tv_usec = ms * 1000;
while (tv.tv_usec >= 1000000)
{
tv.tv_usec -= 1000000;
tv.tv_sec++;
}
ret = select(priv->fd + 1, &rfds, NULL, NULL, &tv);
if (ret <= 0)
return ret;
/* only report ok if the next message is from the peer we expect
* from
*/
cli_addr_size = sizeof(cli_addr);
ret = recvfrom(priv->fd, &c, 1, MSG_PEEK, (struct sockaddr *) &cli_addr, &cli_addr_size);
if (ret > 0)
{
if (cli_addr_size == priv->cli_addr_size && memcmp(&cli_addr, priv->cli_addr, sizeof(cli_addr)) == 0)
return 1;
}
return 0;
}
static ssize_t push_func(gnutls_transport_ptr_t p, const void *data, size_t size)
{
int ret;
priv_data_st *priv = p;
ret = sendto(priv->fd, data, size, 0, priv->cli_addr, priv->cli_addr_size);
if(ret<0)
{
printf(" sendto failed for fd: with return value:\n",priv->fd,ret);
}
return ret;
}
static ssize_t pull_func(gnutls_transport_ptr_t p, void *data, size_t size)
{
priv_data_st *priv = p;
struct sockaddr_in cli_addr;
socklen_t cli_addr_size;
char buffer[64];
int ret;
cli_addr_size = sizeof(cli_addr);
ret = recvfrom(priv->fd, data, size, 0, (struct sockaddr *) &cli_addr, &cli_addr_size);
if (ret == -1)
return ret;
if (cli_addr_size == priv->cli_addr_size && memcmp(&cli_addr, priv->cli_addr, sizeof(cli_addr)) == 0)
return ret;
printf(" Denied connection from %s\n", human_addr((struct sockaddr *) &cli_addr, sizeof(cli_addr), buffer, sizeof(buffer)));
gnutls_transport_set_errno(priv->session, EAGAIN);
return -1;
}
static const char *human_addr(const struct sockaddr *sa, socklen_t salen, char *buf, size_t buflen)
{
const char *save_buf = buf;
size_t l;
if (!buf || !buflen)
return NULL;
*buf = '\0';
switch (sa->sa_family)
{
#if HAVE_IPV6
case AF_INET6:
snprintf(buf, buflen, "IPv6 ");
break;
#endif
case AF_INET:
snprintf(buf, buflen, "IPv4 ");
break;
}
l = strlen(buf);
buf += l;
buflen -= l;
if (getnameinfo(sa, salen, buf, buflen, NULL, 0, NI_NUMERICHOST) != 0)
return NULL;
l = strlen(buf);
buf += l;
buflen -= l;
strncat(buf, " port ", buflen);
l = strlen(buf);
buf += l;
buflen -= l;
if (getnameinfo(sa, salen, NULL, 0, buf, buflen, NI_NUMERICSERV) != 0)
return NULL;
return save_buf;
}
static int generate_dh_params(void)
{
int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH,
GNUTLS_SEC_PARAM_LEGACY);
/* Generate Diffie-Hellman parameters - for use with DHE
* kx algorithms. When short bit length is used, it might
* be wise to regenerate parameters often.
*/
gnutls_dh_params_init(&dh_params);
gnutls_dh_params_generate2(dh_params, bits);
return 0;
}
void handle_sctp_event (struct msghdr *buf)
{
struct sctp_assoc_change *sac;
struct sctp_send_failed *ssf;
struct sctp_paddr_change *spc;
struct sctp_remote_error *sre;
union sctp_notification *snp;
struct sockaddr_in *sin;
const char *ap;
char addrbuf[INET6_ADDRSTRLEN];
struct sockaddr_in6 *sin6;
snp=(union sctp_notification*)buf;
switch (snp->sn_header.sn_type)
{
case SCTP_ASSOC_CHANGE:
sac = &snp->sn_assoc_change;
printf(" ^^^ assoc_change: state=%hu, error=%hu, instr=%hu outstr=%hu\n", sac->sac_state, sac->sac_error,
sac->sac_inbound_streams, sac->sac_outbound_streams);
break;
case SCTP_SEND_FAILED:
ssf = &snp->sn_send_failed;
printf(" ^^^ sendfailed: len=%hu err=%d\n", ssf->ssf_length, ssf->ssf_error);
break;
case SCTP_PEER_ADDR_CHANGE:
spc = &snp->sn_paddr_change;
if (spc->spc_aaddr.ss_family == AF_INET)
{
sin = (struct sockaddr_in *)&spc->spc_aaddr;
ap = inet_ntop(AF_INET, &sin->sin_addr, addrbuf, INET6_ADDRSTRLEN);
}
else
{
sin6 = (struct sockaddr_in6 *)&spc->spc_aaddr;
ap = inet_ntop(AF_INET6, &sin6->sin6_addr, addrbuf, INET6_ADDRSTRLEN);
}
printf(" ^^^ intf_change: %s state=%d, error=%d\n", ap, spc->spc_state, spc->spc_error);
break;
case SCTP_REMOTE_ERROR:
sre = &snp->sn_remote_error;
printf(" ^^^ remote_error: err=%hu len=%hu\n", ntohs(sre->sre_error), ntohs(sre->sre_length));
break;
case SCTP_SHUTDOWN_EVENT:
printf(" ^^^ shutdown event\n");
break;
default:
printf(" unknown type: %hu\n", snp->sn_header.sn_type);
break;
}
}
GNUTLS-STREAMBASED-HANDSHAKE.pcap
Description: application/vnd.tcpdump.pcap
_______________________________________________ Gnutls-help mailing list [email protected] http://lists.gnupg.org/mailman/listinfo/gnutls-help
