This patch adds support for authenticating both client and server using a pre-shared passphrase using SRP (Secure Remote Password) over TLS (see RFC 5054) using GnuTLS. Both usbip and usbipd now accept a shared secret as a command line argument. Currently, the established TLS connection is only used to perform a secure handshake and dropped before the socket is passed to the kernel. The code may be extended to exchange a session key over TLS and pass it to the kernel to perform IPsec. Signed-off-by: Dominik Paulus <dominik.paulus@xxxxxx> Signed-off-by: Tobias Polzer <tobias.polzer@xxxxxx> --- drivers/staging/usbip/userspace/configure.ac | 14 ++ drivers/staging/usbip/userspace/doc/usbip.8 | 6 + drivers/staging/usbip/userspace/doc/usbipd.8 | 6 + drivers/staging/usbip/userspace/src/usbip.c | 30 ++- drivers/staging/usbip/userspace/src/usbip_attach.c | 2 +- drivers/staging/usbip/userspace/src/usbip_list.c | 2 +- .../staging/usbip/userspace/src/usbip_network.c | 82 ++++++++ .../staging/usbip/userspace/src/usbip_network.h | 9 +- drivers/staging/usbip/userspace/src/usbipd.c | 217 ++++++++++++++++++--- 9 files changed, 335 insertions(+), 33 deletions(-) diff --git a/drivers/staging/usbip/userspace/configure.ac b/drivers/staging/usbip/userspace/configure.ac index 2be4060..7bba496 100644 --- a/drivers/staging/usbip/userspace/configure.ac +++ b/drivers/staging/usbip/userspace/configure.ac @@ -84,6 +84,20 @@ AC_ARG_WITH([tcp-wrappers], AC_DEFINE([HAVE_LIBWRAP], [1], [use tcp wrapper])], [AC_MSG_RESULT([no]); LIBS="$saved_LIBS"])]) +# Checks for the GnuTLS library +AC_ARG_WITH([gnutls], + [AS_HELP_STRING([--with-gnutls], + [use the GnuTLS library for authentication])], + dnl [ACTION-IF-GIVEN] + [if test "$withval" = "yes"; then + PKG_CHECK_MODULES([GNUTLS], [gnutls]) + AC_DEFINE([HAVE_GNUTLS], [1], [use gnutls]) + CFLAGS="$CFLAGS $GNUTLS_CFLAGS" + LDFLAGS="$LDFLAGS $GNUTLS_LIBS" + fi + ], + ) + # Sets directory containing usb.ids. AC_ARG_WITH([usbids-dir], [AS_HELP_STRING([--with-usbids-dir=DIR], diff --git a/drivers/staging/usbip/userspace/doc/usbip.8 b/drivers/staging/usbip/userspace/doc/usbip.8 index a6097be..b5050ed 100644 --- a/drivers/staging/usbip/userspace/doc/usbip.8 +++ b/drivers/staging/usbip/userspace/doc/usbip.8 @@ -29,6 +29,12 @@ Log to syslog. Connect to PORT on remote host (used for attach and list --remote). .PP +.HP +\fB\-\-auth\fR +.IP +Set the password to be used for client authentication. See usbipd(8) for more information. +.PP + .SH COMMANDS .HP \fBversion\fR diff --git a/drivers/staging/usbip/userspace/doc/usbipd.8 b/drivers/staging/usbip/userspace/doc/usbipd.8 index ac4635d..b2b9eee 100644 --- a/drivers/staging/usbip/userspace/doc/usbipd.8 +++ b/drivers/staging/usbip/userspace/doc/usbipd.8 @@ -54,6 +54,12 @@ If no FILE specified, use /var/run/usbipd.pid Listen on TCP/IP port PORT. .PP +.HP +\fB\-s\fR, \fB\-\-auth\fR +.IP +Sets the password to be used for client authentication. If -a is used, the server will only accept connections from authenticated clients. Note: USB traffic will still be unencrypted, this currently only serves for authentication. +.PP + \fB\-h\fR, \fB\-\-help\fR .IP Print the program help message and exit. diff --git a/drivers/staging/usbip/userspace/src/usbip.c b/drivers/staging/usbip/userspace/src/usbip.c index 04a5f20..8a5de83 100644 --- a/drivers/staging/usbip/userspace/src/usbip.c +++ b/drivers/staging/usbip/userspace/src/usbip.c @@ -25,6 +25,12 @@ #include <getopt.h> #include <syslog.h> +#include "../config.h" + +#ifdef HAVE_GNUTLS +#include <gnutls/gnutls.h> +#endif + #include "usbip_common.h" #include "usbip_network.h" #include "usbip.h" @@ -35,8 +41,12 @@ static int usbip_version(int argc, char *argv[]); static const char usbip_version_string[] = PACKAGE_STRING; static const char usbip_usage_string[] = - "usbip [--debug] [--log] [--tcp-port PORT] [version]\n" - " [help] <command> <args>\n"; + "usbip " +#ifdef HAVE_GNUTLS + "[--auth PASSWORD] " +#endif + "[--debug] [--log] [--tcp-port PORT]\n" + " [version] [help] <command> <args>\n"; static void usbip_usage(void) { @@ -142,6 +152,7 @@ int main(int argc, char *argv[]) { "debug", no_argument, NULL, 'd' }, { "log", no_argument, NULL, 'l' }, { "tcp-port", required_argument, NULL, 't' }, + { "auth", required_argument, NULL, 's' }, { NULL, 0, NULL, 0 } }; @@ -152,12 +163,25 @@ int main(int argc, char *argv[]) usbip_use_stderr = 1; opterr = 0; for (;;) { - opt = getopt_long(argc, argv, "+dlt:", opts, NULL); + opt = getopt_long(argc, argv, "+dls:t:", opts, NULL); if (opt == -1) break; switch (opt) { + case 's': +#ifdef HAVE_GNUTLS + usbip_srp_password = optarg; + rc = gnutls_global_init(); + if (rc < 0) { + err("Unable to initialize GnuTLS library: %s", + gnutls_strerror(rc)); + return EXIT_FAILURE; + } +#else + err("usbip has been compiled without GnuTLS support"); +#endif + break; case 'd': usbip_use_debug = 1; break; diff --git a/drivers/staging/usbip/userspace/src/usbip_attach.c b/drivers/staging/usbip/userspace/src/usbip_attach.c index 0858411..2363e56 100644 --- a/drivers/staging/usbip/userspace/src/usbip_attach.c +++ b/drivers/staging/usbip/userspace/src/usbip_attach.c @@ -175,7 +175,7 @@ static int attach_device(char *host, char *busid) int rc; int rhport; - sockfd = usbip_net_tcp_connect(host, usbip_port_string); + sockfd = usbip_net_connect(host); if (sockfd < 0) { err("tcp connect"); return -1; diff --git a/drivers/staging/usbip/userspace/src/usbip_list.c b/drivers/staging/usbip/userspace/src/usbip_list.c index 237e099..e4fa5b8 100644 --- a/drivers/staging/usbip/userspace/src/usbip_list.c +++ b/drivers/staging/usbip/userspace/src/usbip_list.c @@ -131,7 +131,7 @@ static int list_exported_devices(char *host) int rc; int sockfd; - sockfd = usbip_net_tcp_connect(host, usbip_port_string); + sockfd = usbip_net_connect(host); if (sockfd < 0) { err("could not connect to %s:%s: %s", host, usbip_port_string, gai_strerror(sockfd)); diff --git a/drivers/staging/usbip/userspace/src/usbip_network.c b/drivers/staging/usbip/userspace/src/usbip_network.c index e78279c..eda641f 100644 --- a/drivers/staging/usbip/userspace/src/usbip_network.c +++ b/drivers/staging/usbip/userspace/src/usbip_network.c @@ -25,11 +25,20 @@ #include <netinet/tcp.h> #include <unistd.h> +#include "../config.h" + +#ifdef HAVE_GNUTLS +#include <gnutls/gnutls.h> +#endif + #include "usbip_common.h" #include "usbip_network.h" int usbip_port = 3240; char *usbip_port_string = "3240"; +#ifdef HAVE_GNUTLS +char *usbip_srp_password; +#endif void usbip_setup_port_number(char *arg) { @@ -297,3 +306,76 @@ int usbip_net_tcp_connect(char *hostname, char *service) return sockfd; } + +#ifdef HAVE_GNUTLS +int usbip_net_srp_handshake(int sockfd) +{ + int ret; + gnutls_session_t session; + gnutls_srp_client_credentials_t srp_cred; + + ret = gnutls_srp_allocate_client_credentials(&srp_cred); + if (ret < 0) + return ret; + + gnutls_srp_set_client_credentials(srp_cred, "dummyuser", + usbip_srp_password); + + ret = gnutls_init(&session, GNUTLS_CLIENT); + if (ret < 0) { + gnutls_srp_free_client_credentials(srp_cred); + return ret; + } + + gnutls_priority_set_direct(session, "NORMAL:+SRP", NULL); + + gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred); + gnutls_transport_set_int (session, sockfd); + + do { + ret = gnutls_handshake(session); + } while (ret < 0 && !gnutls_error_is_fatal(ret)); + + gnutls_bye(session, GNUTLS_SHUT_RDWR); + + gnutls_deinit(session); + gnutls_srp_free_client_credentials(srp_cred); + + return ret; +} +#endif + +/* + * Connect to the server. Performs the TCP connection attempt + * and - if necessary - the TLS handshake used for authentication. + */ +int usbip_net_connect(char *hostname) +{ + int sockfd; + + sockfd = usbip_net_tcp_connect(hostname, usbip_port_string); + if (sockfd < 0) + return sockfd; + +#ifdef HAVE_GNUTLS + if (usbip_srp_password) { + int rc; + + rc = usbip_net_send_op_common(sockfd, OP_REQ_STARTTLS, 0); + if (rc < 0) { + err("usbip_net_send_op_common failed"); + return EAI_SYSTEM; + } + + rc = usbip_net_srp_handshake(sockfd); + if (rc < 0) { + err("Unable to perform TLS handshake (wrong password?): %s", + gnutls_strerror(rc)); + close(sockfd); + return EAI_SYSTEM; + } + } +#endif + + return sockfd; +} diff --git a/drivers/staging/usbip/userspace/src/usbip_network.h b/drivers/staging/usbip/userspace/src/usbip_network.h index f19ae19..758656b 100644 --- a/drivers/staging/usbip/userspace/src/usbip_network.h +++ b/drivers/staging/usbip/userspace/src/usbip_network.h @@ -16,6 +16,7 @@ extern int usbip_port; extern char *usbip_port_string; +extern char *usbip_srp_password; void usbip_setup_port_number(char *arg); /* ---------------------------------------------------------------------- */ @@ -168,6 +169,12 @@ struct op_devlist_reply_extra { usbip_net_pack_uint32_t(pack, &(reply)->ndev);\ } while (0) +/* ---------------------------------------------------------------------- */ +/* Initiate encrypted connection. */ +#define OP_STARTTLS 0x06 +#define OP_REQ_STARTTLS (OP_REQUEST | OP_STARTTLS) +#define OP_REP_STARTTLS (OP_REPLY | OP_STARTTLS) + void usbip_net_pack_uint32_t(int pack, uint32_t *num); void usbip_net_pack_uint16_t(int pack, uint16_t *num); void usbip_net_pack_usb_device(int pack, struct usbip_usb_device *udev); @@ -181,6 +188,6 @@ int usbip_net_set_reuseaddr(int sockfd); int usbip_net_set_nodelay(int sockfd); int usbip_net_set_keepalive(int sockfd); int usbip_net_set_v6only(int sockfd); -int usbip_net_tcp_connect(char *hostname, char *port); +int usbip_net_connect(char *hostname); #endif /* __USBIP_NETWORK_H */ diff --git a/drivers/staging/usbip/userspace/src/usbipd.c b/drivers/staging/usbip/userspace/src/usbipd.c index 7980f8b..8db2f27 100644 --- a/drivers/staging/usbip/userspace/src/usbipd.c +++ b/drivers/staging/usbip/userspace/src/usbipd.c @@ -32,6 +32,11 @@ #include <sys/socket.h> #include <netinet/in.h> +#ifdef HAVE_GNUTLS +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#endif + #ifdef HAVE_LIBWRAP #include <tcpd.h> #endif @@ -63,6 +68,11 @@ static const char usbipd_help_string[] = " -6, --ipv6\n" " Bind to IPv6. Default is both.\n" "\n" +#ifdef HAVE_GNUTLS + " -sPASSWORD, --auth PASSWORD\n" + " Set PASSWORD as key used for authentication.\n" + "\n" +#endif " -D, --daemon\n" " Run as a daemon process.\n" "\n" @@ -82,6 +92,78 @@ static const char usbipd_help_string[] = " -v, --version\n" " Show version.\n"; +static int need_auth; +#ifdef HAVE_GNUTLS +static gnutls_datum_t srp_salt, srp_verifier; +static gnutls_srp_server_credentials_t srp_cred; + +#define SRP_GROUP gnutls_srp_2048_group_generator +#define SRP_PRIME gnutls_srp_2048_group_prime + +static int net_srp_callback(gnutls_session_t sess, const char *username, + gnutls_datum_t *nsalt, gnutls_datum_t *nverifier, gnutls_datum_t *g, + gnutls_datum_t *n) +{ + /* + * GnuTLS expects us to allocate all data returned from callbacks + * using gnutls_malloc(), thus, we have to create a fresh copy of + * our static credentials for every connection. + */ + nsalt->data = gnutls_malloc(srp_salt.size); + nverifier->data = gnutls_malloc(srp_verifier.size); + if (nsalt->data == NULL || nverifier->data == NULL) { + gnutls_free(nsalt->data); + gnutls_free(nverifier->data); + return -1; + } + nsalt->size = srp_salt.size; + nverifier->size = srp_verifier.size; + memcpy(nverifier->data, srp_verifier.data, srp_verifier.size); + memcpy(nsalt->data, srp_salt.data, srp_salt.size); + + *g = SRP_GROUP; + *n = SRP_PRIME; + + /* We only have a single session, thus, ignore it */ + (void) sess; + + if (strcmp(username, "dummyuser")) + /* User invalid, stored dummy data in g and n. */ + return 1; + + return 0; +} + +static int net_srp_server_handshake(int connfd) +{ + int ret; + gnutls_session_t session; + + if (gnutls_init(&session, GNUTLS_SERVER) != 0) + return -1; + gnutls_priority_set_direct(session, "NORMAL:-KX-ALL:+SRP", NULL); + if (gnutls_credentials_set(session, GNUTLS_CRD_SRP, srp_cred) != 0) + return -1; + + gnutls_transport_set_int(session, connfd); + + do { + ret = gnutls_handshake(session); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) + err("GnuTLS handshake failed (%s)", gnutls_strerror(ret)); + else + info("GnuTLS handshake completed"); + + if (gnutls_bye(session, GNUTLS_SHUT_RDWR) != 0) + err("Unable to shutdown TLS connection."); + gnutls_deinit(session); + + return ret; +} +#endif + static void usbipd_help(void) { printf("%s\n", usbipd_help_string); @@ -238,14 +320,7 @@ static int recv_request_devlist(int connfd) static int recv_pdu(int connfd) { - uint16_t code = OP_UNSPEC; - int ret; - - ret = usbip_net_recv_op_common(connfd, &code); - if (ret < 0) { - dbg("could not receive opcode: %#0x", code); - return -1; - } + int auth = !need_auth, cont = 1, ret; ret = usbip_host_refresh_device_list(); if (ret < 0) { @@ -253,25 +328,64 @@ static int recv_pdu(int connfd) return -1; } - info("received request: %#0x(%d)", code, connfd); - switch (code) { - case OP_REQ_DEVLIST: - ret = recv_request_devlist(connfd); - break; - case OP_REQ_IMPORT: - ret = recv_request_import(connfd); - break; - case OP_REQ_DEVINFO: - case OP_REQ_CRYPKEY: - default: - err("received an unknown opcode: %#0x", code); - ret = -1; - } + /* + * Process opcodes. We might receive more than one, as the + * client might send STARTTLS first + */ + while (cont) { + uint16_t code = OP_UNSPEC; - if (ret == 0) - info("request %#0x(%d): complete", code, connfd); - else - info("request %#0x(%d): failed", code, connfd); + ret = usbip_net_recv_op_common(connfd, &code); + if (ret < 0) { + dbg("could not receive opcode: %#0x", code); + return -1; + } + + info("received request: %#0x(%d)", code, connfd); + + /* We require an authenticated encryption */ + if (!auth && code != OP_REQ_STARTTLS) { + usbip_net_send_op_common(connfd, OP_REPLY, ST_NA); + return -1; + } + + switch (code) { +#ifdef HAVE_GNUTLS + case OP_REQ_STARTTLS: + if (!need_auth) { + ret = -1; + err("Unexpected TLS handshake attempt (client " + "uses password, server doesn't)"); + } else { + ret = net_srp_server_handshake(connfd); + if (ret != 0) + err("TLS handshake failed"); + auth = 1; + } + break; +#endif + case OP_REQ_DEVLIST: + ret = recv_request_devlist(connfd); + cont = 0; + break; + case OP_REQ_IMPORT: + ret = recv_request_import(connfd); + cont = 0; + break; + case OP_REQ_DEVINFO: + case OP_REQ_CRYPKEY: + default: + err("received an unknown opcode: %#0x", code); + ret = -1; + } + + if (ret == 0) + info("request %#0x(%d): complete", code, connfd); + else { + info("request %#0x(%d): failed", code, connfd); + break; + } + } return ret; } @@ -577,6 +691,37 @@ static int do_standalone_mode(int daemonize, int ipv4, int ipv6) return 0; } +#ifdef HAVE_GNUTLS +static int usbip_init_gnutls(void) +{ + int ret; + + gnutls_global_init(); + + srp_salt.data = gnutls_malloc(16); + if (!srp_salt.data) + return GNUTLS_E_MEMORY_ERROR; + + ret = gnutls_rnd(GNUTLS_RND_NONCE, srp_salt.data, 16); + if (ret < 0) + return ret; + srp_salt.size = 16; + + ret = gnutls_srp_allocate_server_credentials(&srp_cred); + if (ret < 0) + return ret; + + ret = gnutls_srp_verifier("dummyuser", optarg, &srp_salt, &SRP_GROUP, + &SRP_PRIME, &srp_verifier); + if (ret < 0) + return ret; + + gnutls_srp_set_server_credentials_function(srp_cred, net_srp_callback); + + return GNUTLS_E_SUCCESS; +} +#endif + int main(int argc, char *argv[]) { static const struct option longopts[] = { @@ -589,6 +734,7 @@ int main(int argc, char *argv[]) { "tcp-port", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, + { "auth", required_argument, NULL, 's' }, { NULL, 0, NULL, 0 } }; @@ -601,6 +747,9 @@ int main(int argc, char *argv[]) int daemonize = 0; int ipv4 = 0, ipv6 = 0; int opt, rc = -1; +#ifdef HAVE_GNUTLS + int ret; +#endif pid_file = NULL; usbip_use_stderr = 1; @@ -611,7 +760,7 @@ int main(int argc, char *argv[]) cmd = cmd_standalone_mode; for (;;) { - opt = getopt_long(argc, argv, "46DdP::t:hv", longopts, NULL); + opt = getopt_long(argc, argv, "46s:DdP::t:hv", longopts, NULL); if (opt == -1) break; @@ -623,6 +772,20 @@ int main(int argc, char *argv[]) case '6': ipv6 = 1; break; + case 's': +#ifdef HAVE_GNUTLS + need_auth = 1; + ret = usbip_init_gnutls(); + if (ret < 0) { + err("Unable to initialize GnuTLS: %s", + gnutls_strerror(ret)); + return EXIT_FAILURE; + } + break; +#else + err("usbipd has been compiled without GnuTLS support"); + break; +#endif case 'D': daemonize = 1; break; -- 1.8.4 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel