/* secure-server -- A (broken) DTLS server example * * Copyright (C) 2011 Olaf Bergmann * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_DTLS #define SERVER_CERT_PEM "./server-cert.pem" #define SERVER_KEY_PEM "./server-key.pem" #define CA_CERT_PEM "./ca-cert.pem" #endif #ifdef HAVE_ASSERT_H # include #else # define assert(x) #endif /* HAVE_ASSERT_H */ static int quit=0; /* SIGINT handler: set quit to 1 for graceful termination */ void handle_sigint(int signum) { quit = 1; } int check_connect(int sockfd, char *buf, int buflen, struct sockaddr *src, int *ifindex) { /* for some reason, the definition in netinet/in.h is not exported */ #ifndef IN6_PKTINFO struct in6_pktinfo { struct in6_addr ipi6_addr; /* src/dst IPv6 address */ unsigned int ipi6_ifindex; /* send/recv interface index */ }; #endif size_t bytes; struct iovec iov[1] = { {buf, buflen} }; char cmsgbuf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; struct in6_pktinfo *p = NULL; struct msghdr msg = { 0 }; struct cmsghdr *cmsg; msg.msg_name = src; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); bytes = recvmsg(sockfd, &msg, MSG_DONTWAIT | MSG_PEEK); if (bytes < 0) { perror("recvmsg"); return bytes; } /* TODO: handle msg.msg_flags & MSG_TRUNC */ if (msg.msg_flags & MSG_CTRUNC) { fprintf(stderr, "control was truncated!\n"); return -1; } if (ifindex) { /* Here we try to retrieve the interface index where the packet was received */ *ifindex = 0; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { p = (struct in6_pktinfo *)(CMSG_DATA(cmsg)); *ifindex = p->ipi6_ifindex; break; } } } return bytes; } typedef enum { UNKNOWN=0, DTLS=1 } protocol_t; protocol_t demux_protocol(const char *buf, int len) { return DTLS; } #ifdef WITH_DTLS typedef enum { PEER_ST_ESTABLISHED, PEER_ST_PENDING, PEER_ST_CLOSED } peer_state_t; typedef struct { peer_state_t state; unsigned long h; SSL *ssl; } ssl_peer_t; #define MAX_SSL_PENDING 2 /* must be less than MAX_SSL_PEERS */ #define MAX_SSL_PEERS 10 /* MAX_SSL_PENDING of these might be pending */ ssl_peer_t *ssl_peer_storage[MAX_SSL_PEERS]; static int pending = 0; void check_peers() { typedef struct bio_dgram_data_st { union { struct sockaddr sa; struct sockaddr_in sa_in; struct sockaddr_in6 sa_in6; } peer; unsigned int connected; unsigned int _errno; unsigned int mtu; struct timeval next_timeout; struct timeval socket_timeout; } bio_dgram_data; struct sockaddr_in6 peer; int i; BIO *bio; for (i = 0; i < MAX_SSL_PEERS; i++) { if (ssl_peer_storage[i]) { if (!ssl_peer_storage[i]->ssl) fprintf(stderr, "invalid SSL object for peer %d!\n",i); else { bio = SSL_get_rbio(ssl_peer_storage[i]->ssl); if (bio) { (void) BIO_dgram_get_peer(bio, (struct sockaddr *)&peer); if (peer.sin6_port && ssl_peer_storage[i]->h != ntohs(peer.sin6_port)) { fprintf(stderr, " bio %p: port differs from hash: %d != %d! (%sconnected)\n", bio, ssl_peer_storage[i]->h, ntohs(((struct sockaddr_in6 *)&peer)->sin6_port), ((bio_dgram_data *)bio->ptr)->connected ? "" : "not "); } } } } } } /** Creates a hash value from the first num bytes of s, taking init as * initialization value. */ static inline unsigned long _hash(unsigned long init, const char *s, int num) { int c; while (num--) while ( (c = *s++) ) { init = ((init << 7) + init) + c; } return init; } static inline unsigned long hash_peer(const struct sockaddr *peer, int ifindex) { unsigned long h; /* initialize hash value to interface index */ h = _hash(0, (char *)&ifindex, sizeof(int)); #define CAST(TYPE,VAR) ((TYPE)VAR) assert(peer); switch (peer->sa_family) { case AF_INET: return ntohs(CAST(const struct sockaddr_in *, peer)->sin_port); h = _hash(h, (char *) &CAST(const struct sockaddr_in *, peer)->sin_addr, sizeof(struct in_addr)); h = _hash(h, (char *) &CAST(const struct sockaddr_in *, peer)->sin_port, sizeof(in_port_t)); break; case AF_INET6: return ntohs(CAST(const struct sockaddr_in6 *, peer)->sin6_port); h = _hash(h, (char *) &CAST(const struct sockaddr_in6 *, peer)->sin6_addr, sizeof(struct in6_addr)); h = _hash(h, (char *) &CAST(const struct sockaddr_in6 *, peer)->sin6_port, sizeof(in_port_t)); break; default: /* last resort */ h = _hash(h, (char *)peer, sizeof(struct sockaddr)); } return 42; return h; } /* Returns index of peer object for specified address/ifindex pair. */ int get_index_of_peer(const struct sockaddr *peer, int ifindex) { unsigned long h; int idx; #ifndef NDEBUG char addr[INET6_ADDRSTRLEN]; char port[6]; #endif if (!peer) return -1; h = hash_peer(peer,ifindex); for (idx = 0; idx < MAX_SSL_PEERS; idx++) { if (ssl_peer_storage[idx] && ssl_peer_storage[idx]->h == h) { #ifndef NDEBUG getnameinfo((struct sockaddr *)peer, sizeof(struct sockaddr_in6), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr, "get_index_of_peer: [%s]:%s => %lu\n", addr, port, h); #endif return idx; } } return -1; } SSL * get_ssl(SSL_CTX *ctx, int sockfd, struct sockaddr *src, int ifindex) { int idx; BIO *bio; SSL *ssl; #ifndef NDEBUG struct sockaddr_storage peer; char addr[INET6_ADDRSTRLEN]; char port[6]; int i; #endif idx = get_index_of_peer(src,ifindex); if (idx >= 0) { fprintf(stderr,"found peer %d ",idx); switch (ssl_peer_storage[idx]->state) { case PEER_ST_ESTABLISHED: fprintf(stderr,"established\n"); break; case PEER_ST_PENDING: fprintf(stderr,"pending\n"); break; case PEER_ST_CLOSED: fprintf(stderr,"closed\n"); break; default: OPENSSL_assert(0); } #ifndef NDEBUG memset(&peer, 0, sizeof(peer)); (void) BIO_dgram_get_peer(SSL_get_rbio(ssl_peer_storage[idx]->ssl), &peer); getnameinfo((struct sockaddr *)&peer, sizeof(peer), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr," [%s]:%s \n", addr, port); #endif return ssl_peer_storage[idx]->ssl; } /* none found, create new if sufficient space available */ if (pending < MAX_SSL_PENDING) { for (idx = 0; idx < MAX_SSL_PEERS; idx++) { if (ssl_peer_storage[idx] == NULL) { /* found space */ ssl = SSL_new(ctx); if (ssl) { bio = BIO_new_dgram(sockfd, BIO_NOCLOSE); if (!bio) { SSL_free(ssl); return NULL; } SSL_set_bio(ssl, bio, bio); SSL_set_options(ssl, SSL_OP_COOKIE_EXCHANGE); SSL_set_accept_state(ssl); ssl_peer_storage[idx] = (ssl_peer_t *) malloc(sizeof(ssl_peer_t)); if (!ssl_peer_storage[idx]) { SSL_free(ssl); return NULL; } ssl_peer_storage[idx]->state = PEER_ST_PENDING; ssl_peer_storage[idx]->h = hash_peer(src,ifindex); ssl_peer_storage[idx]->ssl = ssl; pending++; fprintf(stderr, "created new SSL peer %d for ssl object %p (storage: %p)\n", idx, ssl, ssl_peer_storage[idx]); #ifndef NDEBUG if (getnameinfo((struct sockaddr *)&src, sizeof(src), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) != 0) { perror("getnameinfo"); fprintf(stderr, "port was %u\n", ntohs(((struct sockaddr_in6 *)src)->sin6_port)); } else { fprintf(stderr," [%s]:%s \n", addr, port); } #endif OPENSSL_assert(ssl_peer_storage[idx]->ssl == ssl); fprintf(stderr,"%d objects pending\n", pending); check_peers(); return ssl; } } } } else { fprintf(stderr, "too many pending SSL objects\n"); return NULL; } fprintf(stderr, "too many peers\n"); return NULL; } /** Deletes peer stored at index idx and frees allocated memory. */ static inline void delete_peer(int idx) { if (idx < 0 || !ssl_peer_storage[idx]) return; if (ssl_peer_storage[idx]->state == PEER_ST_PENDING) pending--; OPENSSL_assert(ssl_peer_storage[idx]->ssl); SSL_free(ssl_peer_storage[idx]->ssl); free(ssl_peer_storage[idx]); ssl_peer_storage[idx] = NULL; printf("deleted peer %d\n",idx); } /** Deletes all closed objects from ssl_peer_storage. */ void remove_closed() { int idx; for (idx = 0; idx < MAX_SSL_PEERS; idx++) if (ssl_peer_storage[idx] && ssl_peer_storage[idx]->state == PEER_ST_CLOSED) delete_peer(idx); } #define min(a,b) ((a) < (b) ? (a) : (b)) unsigned int psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len) { static char keybuf[] = "secretPSK"; printf("psk_server_callback: check identity of client %s\n", identity); memcpy(psk, keybuf, min(strlen(keybuf), max_psk_len)); return min(strlen(keybuf), max_psk_len); } #endif #ifdef WITH_DTLS /** * This function tracks the status changes from libssl to manage local * object state. */ void info_callback(const SSL *ssl, int where, int ret) { int idx, i; struct sockaddr_storage peer; struct sockaddr_storage peer2; char addr[INET6_ADDRSTRLEN]; char port[6]; if (where & SSL_CB_LOOP) /* do not care for intermediary states */ return; memset(&peer, 0, sizeof(peer)); (void) BIO_dgram_get_peer(SSL_get_rbio(ssl), &peer); /* lookup SSL object */ /* FIXME: need to get the ifindex */ idx = get_index_of_peer((struct sockaddr *)&peer, 0); if (idx >= 0) fprintf(stderr, "info_callback: assert: %d < 0 || %p == %p (storage: %p)\n", idx, ssl, ssl_peer_storage[idx]->ssl, ssl_peer_storage[idx]); if (idx >= 0 && ssl != ssl_peer_storage[idx]->ssl) { getnameinfo((struct sockaddr *)&peer, sizeof(peer), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr," ssl: [%s]:%s ", addr, port); (void) BIO_dgram_get_peer(SSL_get_rbio(ssl_peer_storage[idx]->ssl), &peer2); getnameinfo((struct sockaddr *)&peer2, sizeof(peer2), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr," ssl_peer_storage[idx]->ssl: [%s]:%s\n", addr, port); fprintf(stderr, " hash:%lu h: %lu\n", hash_peer((const struct sockaddr *)&peer, 0), ssl_peer_storage[idx]->h); for (i = 0; i < MAX_SSL_PEERS; i++) { if (ssl_peer_storage[i]) { fprintf(stderr, "%02d: %p ssl: %p ", i, ssl_peer_storage[i] ,ssl_peer_storage[i]->ssl); (void) BIO_dgram_get_peer(SSL_get_rbio(ssl_peer_storage[i]->ssl), &peer2); getnameinfo((struct sockaddr *)&peer2, sizeof(peer2), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); fprintf(stderr," peer: [%s]:%s h: %lu\n", addr, port, ssl_peer_storage[i]->h); } } fprintf(stderr, "***** ASSERT FAILED ******\n"); memset(&peer, 0, sizeof(peer)); (void) BIO_dgram_get_peer(SSL_get_wbio(ssl), &peer); idx = get_index_of_peer((struct sockaddr *)&peer, 0); fprintf(stderr, " get_index_of_peer for wbio returns %d, type is %04x\n", idx, where); } #if 1 check_peers(); OPENSSL_assert((idx < 0) || (ssl == ssl_peer_storage[idx]->ssl)); #endif if (where & SSL_CB_ALERT) { #ifndef NDEBUG if (ret != 0) fprintf(stderr,"%s:%s:%s\n", SSL_alert_type_string(ret), SSL_alert_desc_string(ret), SSL_alert_desc_string_long(ret)); #endif /* examine alert type */ switch (*SSL_alert_type_string(ret)) { case 'F': /* move SSL object from pending to close */ if (idx >= 0) { ssl_peer_storage[idx]->state = PEER_ST_CLOSED; pending--; } break; case 'W': if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) { if (where == SSL_CB_WRITE_ALERT) fprintf(stderr,"sent CLOSE_NOTIFY\n"); else /* received CN */ fprintf(stderr,"received CLOSE_NOTIFY\n"); } break; default: /* handle unknown alert types */ #ifndef NDEBUG printf("not handled!\n"); #endif } } if (where & SSL_CB_HANDSHAKE_DONE) { /* move SSL object from pending to established */ printf("HANDSHAKE_DONE "); if (idx >= 0) { if (ssl_peer_storage[idx]->state == PEER_ST_PENDING) { ssl_peer_storage[idx]->state = PEER_ST_ESTABLISHED; pending--; printf("moved SSL object %d to ESTABLISHED\n", idx); printf("%d objects pending\n", pending); } else { #ifndef NDEBUG printf("huh, object %d was not pending? (%d)\n", idx, ssl_peer_storage[idx]->state); #endif } return; } return; } return; } #endif #ifdef WITH_DTLS /* checks if ssl object was closed and can be removed */ int check_close(SSL *ssl) { int res, err, idx; struct sockaddr_storage peer; memset(&peer, 0, sizeof(peer)); (void)BIO_dgram_get_peer(SSL_get_rbio(ssl), &peer); res = 0; if (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) { printf("SSL_RECEIVED_SHUTDOWN\n"); res = SSL_shutdown(ssl); if (res == 0) { printf("must call SSL_shutdown again\n"); res = SSL_shutdown(ssl); } if (res < 0) { err = SSL_get_error(ssl,res); fprintf(stderr, "shutdown: SSL error %d: %s\n", err, ERR_error_string(err, NULL)); } /* we can close the SSL object anyway */ /* FIXME: need to get ifindex from somewhere */ idx = get_index_of_peer((struct sockaddr *)&peer, 0); OPENSSL_assert(idx < 0 || ssl == ssl_peer_storage[idx]->ssl); if (idx >= 0) { ssl_peer_storage[idx]->state = PEER_ST_CLOSED; printf("moved SSL object %d to CLOSED\n",idx); } } return res; } int check_timeout() { int i, result, err; for (i = 0; i < MAX_SSL_PEERS; i++) { if (ssl_peer_storage[i]) { OPENSSL_assert(ssl_peer_storage[i]->ssl); result = DTLSv1_handle_timeout(ssl_peer_storage[i]->ssl); if (result < 0) { err = SSL_get_error(ssl_peer_storage[i]->ssl,result); fprintf(stderr, "dtls1_handle_timeout (%d): %s\n", err, ERR_error_string(err, NULL)); } } } /* remove outdated obbjects? */ return 0; } #endif /* WITH_DTLS */ int _read(SSL_CTX *ctx, int sockfd) { char buf[2000]; struct sockaddr_in6 src; int len, ifindex, i; char addr[INET6_ADDRSTRLEN]; char port[6]; socklen_t sz = sizeof(struct sockaddr_in6); #ifdef WITH_DTLS SSL *ssl; int err; #endif /* Retrieve remote address and interface index as well as the first few bytes of the message to demultiplex protocols. */ memset(&src, 0, sizeof(struct sockaddr_in6)); len = check_connect(sockfd, buf, 4, (struct sockaddr *)&src, &ifindex); if (len < 0) /* error */ return len; #ifndef NDEBUG fprintf(stderr,"received packet"); if (getnameinfo((struct sockaddr *)&src, sizeof(src), addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) == 0) fprintf(stderr," from [%s]:%s", addr, port); fprintf(stderr," on interface %d\n", ifindex); #endif switch (demux_protocol(buf, len)) { #ifdef WITH_DTLS case DTLS : ssl = get_ssl(ctx, sockfd, (struct sockaddr *)&src, ifindex); if (!ssl) { fprintf(stderr, "cannot create new SSL object\n"); /* return recv(sockfd, buf, sizeof(buf), MSG_DONTWAIT);*/ len = recvfrom(sockfd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&src, &sz); getnameinfo((struct sockaddr *)&src, sz, addr, sizeof(addr), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); printf("discarded %d bytes from [%s]:%s\n", len, addr, port); return len; } len = SSL_read(ssl, buf, sizeof(buf)); break; #endif case UNKNOWN: default : len = recv(sockfd, buf, sizeof(buf), MSG_DONTWAIT); } if (len > 0) { printf("here is the data:\n"); for (i=0; i 0) { /* read from socket */ if ( FD_ISSET( sockfd, &fds[READ]) ) { _read(ctx, sockfd); /* read received data */ } else if ( FD_ISSET( sockfd, &fds[WRITE]) ) { /* write to socket */ _write(ctx, sockfd); /* write data */ } } else { /* timeout */ check_timeout(); } remove_closed(); } end: #ifdef WITH_DTLS for (idx = 0; idx < MAX_SSL_PEERS; idx++) { if (ssl_peer_storage[idx] && ssl_peer_storage[idx]->ssl) { if (ssl_peer_storage[idx]->state == PEER_ST_ESTABLISHED) SSL_shutdown(ssl_peer_storage[idx]->ssl); SSL_free(ssl_peer_storage[idx]->ssl); } } SSL_CTX_free(ctx); #endif close(sockfd); /* don't care if we close stdin at this point */ return res; }