With the TLS socket, all data is encrypted on the wire. The TCP socket though is still clear text. Fortunately some SASL authentication mechanism can also supply encryption capabilities. This is called SSF in SASL terminology. This patch mandates the use of an SSF capable SASL authentiction mechanism on the TCP socket. This in effect restricts you to a choise between GSSAPI and DIGEST-MD5 as your SASL auth methods (the latter is a user/password based scheme). We also disallow anonymous & plaintext auth methods. If you really want to run the TCP socket in clear text & with anonymous auth, simply turn off SASL altogether. Since TLS already provides encryptiuon, if you run SASL over the TLS socket, we don't place any restrictions on the choice of SASL auth mechanism. On the server side I have removed the 'direction' field of the client object. This was only used on the TLS socket & was intended to track whether the handshake process was waiting to receive or send. Rather than try to set this in various places throughout the daemon code, we simply query the neccessary direction at the one point where we register a FD event handle with poll(). This makes the code clearer to follow & reduces the chance of accidentally messing up the state. The send & receive functions previously would call read vs gnutls_record_recv and write vs gnutls_record_send depending on the type of socket. If there is a SASL SSF layer enabled, we have to first pass the outgoing data through the sasl_encode() API, and pass incoming data through sasl_decode(). So the send/recive APIs have changed a little, to deal with this codec need and thus there is also some more state being tracked per connection - we may have to cache the output for sasl_decode for future calls depending on how much data we need in short term. NB, the SSF layer lets you choose a strength factor from 0 -> a large number and the docs all talk about * 0 = no protection * 1 = integrity protection only * 40 = 40-bit DES or 40-bit RC2/RC4 * 56 = DES * 112 = triple-DES * 128 = 128-bit RC2/RC4/BLOWFISH * 256 = baseline AES This is incredibly misleading. The GSSAPI mechanism in SASL will never report a strength of anything other than 56. Even if it is using triple-DES. The true strength of the GSSAPI/Kerberos impl is impossible to figure out from the SASL apis. To ensure that Kerberos uses strong encryption, you need to make sure that the Kerberos principles issued only have the 3-DES cipher/keys present. If you are truely paranoid though, you always have the option of using TLS (which gives at least 128 bit ciphers, often 256 bit), and then just using Kerberos for auth and ignore the SASL SSF layer. A subsequent patch will make it possible to configure this stuff. qemud/internal.h | 31 +++-- qemud/qemud.c | 248 +++++++++++++++++++++++++++++++++----------- qemud/remote.c | 106 ++++++++++++++++++ src/remote_internal.c | 279 ++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 538 insertions(+), 126 deletions(-) diff -r 5a37498017ac qemud/internal.h --- a/qemud/internal.h Wed Nov 28 23:00:04 2007 -0500 +++ b/qemud/internal.h Wed Nov 28 23:00:47 2007 -0500 @@ -73,10 +73,17 @@ enum qemud_mode { QEMUD_MODE_TLS_HANDSHAKE, }; -/* These have to remain compatible with gnutls_record_get_direction. */ -enum qemud_tls_direction { - QEMUD_TLS_DIRECTION_READ = 0, - QEMUD_TLS_DIRECTION_WRITE = 1, +/* Whether we're passing reads & writes through a sasl SSF */ +enum qemud_sasl_ssf { + QEMUD_SASL_SSF_NONE = 0, + QEMUD_SASL_SSF_READ = 1, + QEMUD_SASL_SSF_WRITE = 2, +}; + +enum qemud_sock_type { + QEMUD_SOCK_TYPE_UNIX = 0, + QEMUD_SOCK_TYPE_TCP = 1, + QEMUD_SOCK_TYPE_TLS = 2, }; /* Stores the per-client connection state */ @@ -90,13 +97,18 @@ struct qemud_client { struct sockaddr_storage addr; socklen_t addrlen; - /* If set, TLS is required on this socket. */ - int tls; - gnutls_session_t session; - enum qemud_tls_direction direction; + int type; /* qemud_sock_type */ + gnutls_session_t tlssession; int auth; #if HAVE_SASL sasl_conn_t *saslconn; + int saslSSF; + const char *saslDecoded; + unsigned int saslDecodedLength; + unsigned int saslDecodedOffset; + const char *saslEncoded; + unsigned int saslEncodedLength; + unsigned int saslEncodedOffset; #endif unsigned int incomingSerial; @@ -121,8 +133,7 @@ struct qemud_socket { struct qemud_socket { int fd; int readonly; - /* If set, TLS is required on this socket. */ - int tls; + int type; /* qemud_sock_type */ int auth; int port; struct qemud_socket *next; diff -r 5a37498017ac qemud/qemud.c --- a/qemud/qemud.c Wed Nov 28 23:00:04 2007 -0500 +++ b/qemud/qemud.c Wed Nov 28 23:00:47 2007 -0500 @@ -463,6 +463,7 @@ static int qemudListenUnix(struct qemud_ sock->readonly = readonly; sock->port = -1; + sock->type = QEMUD_SOCK_TYPE_UNIX; if ((sock->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { qemudLog(QEMUD_ERR, "Failed to create socket: %s", @@ -577,7 +578,7 @@ static int static int remoteListenTCP (struct qemud_server *server, const char *port, - int tls, + int type, int auth) { int fds[2]; @@ -606,7 +607,7 @@ remoteListenTCP (struct qemud_server *se server->nsockets++; sock->fd = fds[i]; - sock->tls = tls; + sock->type = type; sock->auth = auth; if (getsockname(sock->fd, (struct sockaddr *)(&sa), &salen) < 0) @@ -745,10 +746,10 @@ static struct qemud_server *qemudInitial if (ipsock) { #if HAVE_SASL - if (listen_tcp && remoteListenTCP (server, tcp_port, 0, REMOTE_AUTH_SASL) < 0) + if (listen_tcp && remoteListenTCP (server, tcp_port, QEMUD_SOCK_TYPE_TCP, REMOTE_AUTH_SASL) < 0) goto cleanup; #else - if (listen_tcp && remoteListenTCP (server, tcp_port, 0, REMOTE_AUTH_NONE) < 0) + if (listen_tcp && remoteListenTCP (server, tcp_port, QEMUD_SOCK_TYPE_TCP, REMOTE_AUTH_NONE) < 0) goto cleanup; #endif @@ -756,7 +757,7 @@ static struct qemud_server *qemudInitial if (remoteInitializeGnuTLS () < 0) goto cleanup; - if (remoteListenTCP (server, tls_port, 1, REMOTE_AUTH_NONE) < 0) + if (remoteListenTCP (server, tls_port, QEMUD_SOCK_TYPE_TLS, REMOTE_AUTH_NONE) < 0) goto cleanup; } } @@ -789,7 +790,7 @@ static struct qemud_server *qemudInitial */ sock = server->sockets; while (sock) { - if (sock->port != -1 && sock->tls) { + if (sock->port != -1 && sock->type == QEMUD_SOCK_TYPE_TLS) { port = sock->port; break; } @@ -981,7 +982,7 @@ remoteCheckAccess (struct qemud_client * int found, err; /* Verify client certificate. */ - if (remoteCheckCertificate (client->session) == -1) { + if (remoteCheckCertificate (client->tlssession) == -1) { qemudLog (QEMUD_ERR, "remoteCheckCertificate: failed to verify client's certificate"); if (!tls_no_verify_certificate) return -1; else qemudLog (QEMUD_INFO, "remoteCheckCertificate: tls_no_verify_certificate is set so the bad certificate is ignored"); @@ -1033,7 +1034,6 @@ remoteCheckAccess (struct qemud_client * client->bufferOffset = 0; client->buffer[0] = '\1'; client->mode = QEMUD_MODE_TX_PACKET; - client->direction = QEMUD_TLS_DIRECTION_WRITE; return 0; } @@ -1065,12 +1065,12 @@ static int qemudDispatchServer(struct qe client->magic = QEMUD_CLIENT_MAGIC; client->fd = fd; client->readonly = sock->readonly; - client->tls = sock->tls; + client->type = sock->type; client->auth = sock->auth; memcpy (&client->addr, &addr, sizeof addr); client->addrlen = addrlen; - if (!client->tls) { + if (client->type != QEMUD_SOCK_TYPE_TLS) { client->mode = QEMUD_MODE_RX_HEADER; client->bufferLength = REMOTE_MESSAGE_HEADER_XDR_LEN; @@ -1079,15 +1079,15 @@ static int qemudDispatchServer(struct qe } else { int ret; - client->session = remoteInitializeTLSSession (); - if (client->session == NULL) + client->tlssession = remoteInitializeTLSSession (); + if (client->tlssession == NULL) goto cleanup; - gnutls_transport_set_ptr (client->session, + gnutls_transport_set_ptr (client->tlssession, (gnutls_transport_ptr_t) (long) fd); /* Begin the TLS handshake. */ - ret = gnutls_handshake (client->session); + ret = gnutls_handshake (client->tlssession); if (ret == 0) { /* Unlikely, but ... Next step is to check the certificate. */ if (remoteCheckAccess (client) == -1) @@ -1099,7 +1099,6 @@ static int qemudDispatchServer(struct qe /* Most likely. */ client->mode = QEMUD_MODE_TLS_HANDSHAKE; client->bufferLength = -1; - client->direction = gnutls_record_get_direction (client->session); if (qemudRegisterClientEvent (server, client, 0) < 0) goto cleanup; @@ -1117,7 +1116,7 @@ static int qemudDispatchServer(struct qe return 0; cleanup: - if (client->session) gnutls_deinit (client->session); + if (client->tlssession) gnutls_deinit (client->tlssession); close (fd); free (client); return -1; @@ -1150,24 +1149,21 @@ static void qemudDispatchClientFailure(s #if HAVE_SASL if (client->saslconn) sasl_dispose(&client->saslconn); #endif - if (client->tls && client->session) gnutls_deinit (client->session); + if (client->tlssession) gnutls_deinit (client->tlssession); close(client->fd); free(client); } -static int qemudClientRead(struct qemud_server *server, - struct qemud_client *client) { - int ret, len; - char *data; - - data = client->buffer + client->bufferOffset; - len = client->bufferLength - client->bufferOffset; +static int qemudClientReadBuf(struct qemud_server *server, + struct qemud_client *client, + char *data, unsigned len) { + int ret; /*qemudDebug ("qemudClientRead: len = %d", len);*/ - if (!client->tls) { + if (!client->tlssession) { if ((ret = read (client->fd, data, len)) <= 0) { if (ret == 0 || errno != EAGAIN) { if (ret != 0) @@ -1177,8 +1173,7 @@ static int qemudClientRead(struct qemud_ return -1; } } else { - ret = gnutls_record_recv (client->session, data, len); - client->direction = gnutls_record_get_direction (client->session); + ret = gnutls_record_recv (client->tlssession, data, len); if (qemudRegisterClientEvent (server, client, 1) < 0) qemudDispatchClientFailure (server, client); else if (ret <= 0) { @@ -1193,9 +1188,79 @@ static int qemudClientRead(struct qemud_ } } + return ret; +} + +static int qemudClientReadPlain(struct qemud_server *server, + struct qemud_client *client) { + int ret; + ret = qemudClientReadBuf(server, client, + client->buffer + client->bufferOffset, + client->bufferLength - client->bufferOffset); + if (ret < 0) + return ret; client->bufferOffset += ret; return 0; } + +#if HAVE_SASL +static int qemudClientReadSASL(struct qemud_server *server, + struct qemud_client *client) { + int got, want; + + /* We're doing a SSF data read, so now its times to ensure + * future writes are under SSF too. + * + * cf remoteSASLCheckSSF in remote.c + */ + client->saslSSF |= QEMUD_SASL_SSF_WRITE; + + /* Need to read some more data off the wire */ + if (client->saslDecoded == NULL) { + char encoded[8192]; + int encodedLen = sizeof(encoded); + encodedLen = qemudClientReadBuf(server, client, encoded, encodedLen); + + if (encodedLen < 0) + return -1; + + sasl_decode(client->saslconn, encoded, encodedLen, + &client->saslDecoded, &client->saslDecodedLength); + + client->saslDecodedOffset = 0; + } + + /* Some buffered decoded data to return now */ + got = client->saslDecodedLength - client->saslDecodedOffset; + want = client->bufferLength - client->bufferOffset; + + if (want > got) + want = got; + + memcpy(client->buffer + client->bufferOffset, + client->saslDecoded + client->saslDecodedOffset, want); + client->saslDecodedOffset += want; + client->bufferOffset += want; + + if (client->saslDecodedOffset == client->saslDecodedLength) { + client->saslDecoded = NULL; + client->saslDecodedOffset = client->saslDecodedLength = 0; + } + + return 0; +} +#endif + +static int qemudClientRead(struct qemud_server *server, + struct qemud_client *client) { +#if HAVE_SASL + if (client->saslSSF & QEMUD_SASL_SSF_READ) + return qemudClientReadSASL(server, client); + else +#endif + return qemudClientReadPlain(server, client); +} + static void qemudDispatchClientRead(struct qemud_server *server, struct qemud_client *client) { @@ -1239,7 +1304,6 @@ static void qemudDispatchClientRead(stru client->mode = QEMUD_MODE_RX_PAYLOAD; client->bufferLength = len - REMOTE_MESSAGE_HEADER_XDR_LEN; client->bufferOffset = 0; - if (client->tls) client->direction = QEMUD_TLS_DIRECTION_READ; if (qemudRegisterClientEvent(server, client, 1) < 0) { qemudDispatchClientFailure(server, client); @@ -1267,7 +1331,7 @@ static void qemudDispatchClientRead(stru int ret; /* Continue the handshake. */ - ret = gnutls_handshake (client->session); + ret = gnutls_handshake (client->tlssession); if (ret == 0) { /* Finished. Next step is to check the certificate. */ if (remoteCheckAccess (client) == -1) @@ -1279,7 +1343,6 @@ static void qemudDispatchClientRead(stru gnutls_strerror (ret)); qemudDispatchClientFailure (server, client); } else { - client->direction = gnutls_record_get_direction (client->session); if (qemudRegisterClientEvent (server ,client, 1) < 0) qemudDispatchClientFailure (server, client); } @@ -1294,15 +1357,11 @@ static void qemudDispatchClientRead(stru } -static int qemudClientWrite(struct qemud_server *server, - struct qemud_client *client) { - int ret, len; - char *data; - - data = client->buffer + client->bufferOffset; - len = client->bufferLength - client->bufferOffset; - - if (!client->tls) { +static int qemudClientWriteBuf(struct qemud_server *server, + struct qemud_client *client, + const char *data, int len) { + int ret; + if (!client->tlssession) { if ((ret = write(client->fd, data, len)) == -1) { if (errno != EAGAIN) { qemudLog (QEMUD_ERR, "write: %s", strerror (errno)); @@ -1311,8 +1370,7 @@ static int qemudClientWrite(struct qemud return -1; } } else { - ret = gnutls_record_send (client->session, data, len); - client->direction = gnutls_record_get_direction (client->session); + ret = gnutls_record_send (client->tlssession, data, len); if (qemudRegisterClientEvent (server, client, 1) < 0) qemudDispatchClientFailure (server, client); else if (ret < 0) { @@ -1324,9 +1382,69 @@ static int qemudClientWrite(struct qemud return -1; } } - + return ret; +} + + +static int qemudClientWritePlain(struct qemud_server *server, + struct qemud_client *client) { + int ret = qemudClientWriteBuf(server, client, + client->buffer + client->bufferOffset, + client->bufferLength - client->bufferOffset); + if (ret < 0) + return -1; client->bufferOffset += ret; return 0; +} + + +#if HAVE_SASL +static int qemudClientWriteSASL(struct qemud_server *server, + struct qemud_client *client) { + int ret; + + /* Not got any pending encoded data, so we need to encode raw stuff */ + if (client->saslEncoded == NULL) { + int err; + err = sasl_encode(client->saslconn, + client->buffer + client->bufferOffset, + client->bufferLength - client->bufferOffset, + &client->saslEncoded, + &client->saslEncodedLength); + + client->saslEncodedOffset = 0; + } + + /* Send some of the encoded stuff out on the wire */ + ret = qemudClientWriteBuf(server, client, + client->saslEncoded + client->saslEncodedOffset, + client->saslEncodedLength - client->saslEncodedOffset); + + if (ret < 0) + return -1; + + /* Note how much we sent */ + client->saslEncodedOffset += ret; + + /* Sent all encoded, so update raw buffer to indicate completion */ + if (client->saslEncodedOffset == client->saslEncodedLength) { + client->saslEncoded = NULL; + client->saslEncodedOffset = client->saslEncodedLength = 0; + client->bufferOffset = client->bufferLength; + } + + return 0; +} +#endif + +static int qemudClientWrite(struct qemud_server *server, + struct qemud_client *client) { +#if HAVE_SASL + if (client->saslSSF & QEMUD_SASL_SSF_WRITE) + return qemudClientWriteSASL(server, client); + else +#endif + return qemudClientWritePlain(server, client); } @@ -1341,7 +1459,6 @@ static void qemudDispatchClientWrite(str client->mode = QEMUD_MODE_RX_HEADER; client->bufferLength = REMOTE_MESSAGE_HEADER_XDR_LEN; client->bufferOffset = 0; - if (client->tls) client->direction = QEMUD_TLS_DIRECTION_READ; if (qemudRegisterClientEvent (server, client, 1) < 0) qemudDispatchClientFailure (server, client); @@ -1354,7 +1471,7 @@ static void qemudDispatchClientWrite(str int ret; /* Continue the handshake. */ - ret = gnutls_handshake (client->session); + ret = gnutls_handshake (client->tlssession); if (ret == 0) { /* Finished. Next step is to check the certificate. */ if (remoteCheckAccess (client) == -1) @@ -1366,7 +1483,6 @@ static void qemudDispatchClientWrite(str gnutls_strerror (ret)); qemudDispatchClientFailure (server, client); } else { - client->direction = gnutls_record_get_direction (client->session); if (qemudRegisterClientEvent (server, client, 1)) qemudDispatchClientFailure (server, client); } @@ -1406,25 +1522,37 @@ static int qemudRegisterClientEvent(stru static int qemudRegisterClientEvent(struct qemud_server *server, struct qemud_client *client, int removeFirst) { + int mode; + switch (client->mode) { + case QEMUD_MODE_TLS_HANDSHAKE: + if (gnutls_record_get_direction (client->tlssession) == 0) + mode = POLLIN; + else + mode = POLLOUT; + break; + + case QEMUD_MODE_RX_HEADER: + case QEMUD_MODE_RX_PAYLOAD: + mode = POLLIN; + break; + + case QEMUD_MODE_TX_PACKET: + mode = POLLOUT; + break; + + default: + return -1; + } + if (removeFirst) if (virEventRemoveHandleImpl(client->fd) < 0) return -1; - if (client->tls) { - if (virEventAddHandleImpl(client->fd, - (client->direction ? - POLLOUT : POLLIN) | POLLERR | POLLHUP, - qemudDispatchClientEvent, - server) < 0) - return -1; - } else { - if (virEventAddHandleImpl(client->fd, - (client->mode == QEMUD_MODE_TX_PACKET ? - POLLOUT : POLLIN) | POLLERR | POLLHUP, - qemudDispatchClientEvent, - server) < 0) - return -1; - } + if (virEventAddHandleImpl(client->fd, + mode | POLLERR | POLLHUP, + qemudDispatchClientEvent, + server) < 0) + return -1; return 0; } diff -r 5a37498017ac qemud/remote.c --- a/qemud/remote.c Wed Nov 28 23:00:04 2007 -0500 +++ b/qemud/remote.c Wed Nov 28 23:00:47 2007 -0500 @@ -284,7 +284,6 @@ remoteDispatchClientRequest (struct qemu client->mode = QEMUD_MODE_TX_PACKET; client->bufferLength = len; client->bufferOffset = 0; - if (client->tls) client->direction = QEMUD_TLS_DIRECTION_WRITE; } /* An error occurred during the dispatching process itself (ie. not @@ -369,7 +368,6 @@ remoteDispatchSendError (struct qemud_cl client->mode = QEMUD_MODE_TX_PACKET; client->bufferLength = len; client->bufferOffset = 0; - if (client->tls) client->direction = QEMUD_TLS_DIRECTION_WRITE; } static void @@ -2042,6 +2040,7 @@ remoteDispatchAuthSaslInit (struct qemud remote_auth_sasl_init_ret *ret) { const char *mechlist = NULL; + sasl_security_properties_t secprops; int err; struct sockaddr_storage sa; socklen_t salen; @@ -2097,6 +2096,60 @@ remoteDispatchAuthSaslInit (struct qemud return -2; } + /* Inform SASL that we've got an external SSF layer from TLS */ + if (client->type == QEMUD_SOCK_TYPE_TLS) { + gnutls_cipher_algorithm_t cipher; + sasl_ssf_t ssf; + + cipher = gnutls_cipher_get(client->tlssession); + if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) { + qemudLog(QEMUD_ERR, "cannot TLS get cipher size"); + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -2; + } + ssf *= 8; /* tls key size is bytes, sasl wants bits */ + + err = sasl_setprop(client->saslconn, SASL_SSF_EXTERNAL, &ssf); + if (err != SASL_OK) { + qemudLog(QEMUD_ERR, "cannot set SASL external SSF %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -2; + } + } + + memset (&secprops, 0, sizeof secprops); + if (client->type == QEMUD_SOCK_TYPE_TLS || + client->type == QEMUD_SOCK_TYPE_UNIX) { + /* If we've got TLS or UNIX domain sock, we don't care about SSF */ + secprops.min_ssf = 0; + secprops.max_ssf = 0; + secprops.maxbufsize = 8192; + secprops.security_flags = 0; + } else { + /* Plain TCP, better get an SSF layer */ + secprops.min_ssf = 56; /* Good enough to require kerberos */ + secprops.max_ssf = 100000; /* Arbitrary big number */ + secprops.maxbufsize = 8192; + /* Forbid any anonymous or trivially crackable auth */ + secprops.security_flags = + SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; + } + + err = sasl_setprop(client->saslconn, SASL_SEC_PROPS, &secprops); + if (err != SASL_OK) { + qemudLog(QEMUD_ERR, "cannot set SASL security props %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -2; + } + err = sasl_listmech(client->saslconn, NULL, /* Don't need to set user */ "", /* Prefix */ @@ -2126,6 +2179,49 @@ remoteDispatchAuthSaslInit (struct qemud return 0; } + +/* We asked for an SSF layer, so sanity check that we actaully + * got what we asked for */ +static int +remoteSASLCheckSSF (struct qemud_client *client, + remote_message_header *req) { + const void *val; + int err, ssf; + + if (client->type == QEMUD_SOCK_TYPE_TLS || + client->type == QEMUD_SOCK_TYPE_UNIX) + return 0; /* TLS or UNIX domain sockets trivially OK */ + + err = sasl_getprop(client->saslconn, SASL_SSF, &val); + if (err != SASL_OK) { + qemudLog(QEMUD_ERR, "cannot query SASL ssf on connection %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -1; + } + ssf = *(const int *)val; + REMOTE_DEBUG("negotiated an SSF of %d", ssf); + if (ssf < 56) { /* 56 is good for Kerberos */ + qemudLog(QEMUD_ERR, "negotiated SSF %d was not strong enough", ssf); + remoteDispatchFailAuth(client, req); + sasl_dispose(&client->saslconn); + client->saslconn = NULL; + return -1; + } + + /* Only setup for read initially, because we're about to send an RPC + * reply which must be in plain text. When the next incoming RPC + * arrives, we'll switch on writes too + * + * cf qemudClientReadSASL in qemud.c + */ + client->saslSSF = QEMUD_SASL_SSF_READ; + + /* We have a SSF !*/ + return 0; +} /* * This starts the SASL authentication negotiation. @@ -2192,6 +2288,9 @@ remoteDispatchAuthSaslStart (struct qemu if (err == SASL_CONTINUE) { ret->complete = 0; } else { + if (remoteSASLCheckSSF(client, req) < 0) + return -2; + REMOTE_DEBUG("Authentication successful %d", client->fd); ret->complete = 1; client->auth = REMOTE_AUTH_NONE; @@ -2263,6 +2362,9 @@ remoteDispatchAuthSaslStep (struct qemud if (err == SASL_CONTINUE) { ret->complete = 0; } else { + if (remoteSASLCheckSSF(client, req) < 0) + return -2; + REMOTE_DEBUG("Authentication successful %d", client->fd); ret->complete = 1; client->auth = REMOTE_AUTH_NONE; diff -r 5a37498017ac src/remote_internal.c --- a/src/remote_internal.c Wed Nov 28 23:00:04 2007 -0500 +++ b/src/remote_internal.c Wed Nov 28 23:00:47 2007 -0500 @@ -79,6 +79,9 @@ struct private_data { FILE *debugLog; /* Debug remote protocol */ #if HAVE_SASL sasl_conn_t *saslconn; /* SASL context */ + const char *saslDecoded; + unsigned int saslDecodedLength; + unsigned int saslDecodedOffset; #endif }; @@ -2907,15 +2910,14 @@ static char *addrToString(struct sockadd /* Perform the SASL authentication process * - * XXX negotiate a session encryption layer for non-TLS sockets * XXX fetch credentials from a libvirt client app callback - * XXX max packet size spec * XXX better mechanism negotiation ? Ask client app ? */ static int remoteAuthSASL (virConnectPtr conn, struct private_data *priv, int in_open) { sasl_conn_t *saslconn = NULL; + sasl_security_properties_t secprops; remote_auth_sasl_init_ret iret; remote_auth_sasl_start_args sargs; remote_auth_sasl_start_ret sret; @@ -2929,6 +2931,8 @@ remoteAuthSASL (virConnectPtr conn, stru struct sockaddr_storage sa; socklen_t salen; char *localAddr, *remoteAddr; + const void *val; + sasl_ssf_t ssf; remoteDebug(priv, "Client initialize SASL authentication"); /* Sets up the SASL library as a whole */ @@ -2984,6 +2988,51 @@ remoteAuthSASL (virConnectPtr conn, stru VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, "Failed to create SASL client context: %d (%s)", err, sasl_errstring(err, NULL, NULL)); + return -1; + } + + /* Initialize some connection props we care about */ + if (priv->uses_tls) { + gnutls_cipher_algorithm_t cipher; + + cipher = gnutls_cipher_get(priv->session); + if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_INTERNAL_ERROR, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "invalid cipher size for TLS session"); + sasl_dispose(&saslconn); + return -1; + } + ssf *= 8; /* key size is bytes, sasl wants bits */ + + remoteDebug(priv, "Setting external SSF %d", ssf); + err = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf); + if (err != SASL_OK) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_INTERNAL_ERROR, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "cannot set external SSF %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + sasl_dispose(&saslconn); + return -1; + } + } + + memset (&secprops, 0, sizeof secprops); + /* If we've got TLS, we don't care about SSF */ + secprops.min_ssf = priv->uses_tls ? 0 : 56; /* Equiv to DES supported by all Kerberos */ + secprops.max_ssf = priv->uses_tls ? 0 : 100000; /* Very strong ! AES == 256 */ + secprops.maxbufsize = 100000; + /* If we're not TLS, then forbid any anonymous or trivially crackable auth */ + secprops.security_flags = priv->uses_tls ? 0 : + SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; + + err = sasl_setprop(saslconn, SASL_SEC_PROPS, &secprops); + if (err != SASL_OK) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_INTERNAL_ERROR, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "cannot set security props %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + sasl_dispose(&saslconn); return -1; } @@ -3103,9 +3152,30 @@ remoteAuthSASL (virConnectPtr conn, stru } } + /* Check for suitable SSF if non-TLS */ + if (!priv->uses_tls) { + err = sasl_getprop(saslconn, SASL_SSF, &val); + if (err != SASL_OK) { + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "cannot query SASL ssf on connection %d (%s)", + err, sasl_errstring(err, NULL, NULL)); + sasl_dispose(&saslconn); + return -1; + } + ssf = *(const int *)val; + remoteDebug(priv, "SASL SSF value %d", ssf); + if (ssf < 56) { /* 56 == DES level, good for Kerberos */ + __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE, + VIR_ERR_AUTH_FAILED, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0, + "negotiation SSF %d was not strong enough", ssf); + sasl_dispose(&saslconn); + return -1; + } + } + remoteDebug(priv, "SASL authentication complete"); - /* XXX keep this around for wire encoding */ - sasl_dispose(&saslconn); + priv->saslconn = saslconn; return 0; } #endif /* HAVE_SASL */ @@ -3306,11 +3376,11 @@ call (virConnectPtr conn, struct private } static int -really_write (virConnectPtr conn, struct private_data *priv, - int in_open /* if we are in virConnectOpen */, - char *bytes, int len) -{ - char *p; +really_write_buf (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + const char *bytes, int len) +{ + const char *p; int err; p = bytes; @@ -3348,55 +3418,156 @@ really_write (virConnectPtr conn, struct } static int +really_write_plain (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + char *bytes, int len) +{ + return really_write_buf(conn, priv, in_open, bytes, len); +} + +#if HAVE_SASL +static int +really_write_sasl (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + char *bytes, int len) +{ + const char *output; + unsigned int outputlen; + int err; + + err = sasl_encode(priv->saslconn, bytes, len, &output, &outputlen); + if (err != SASL_OK) { + return -1; + } + + return really_write_buf(conn, priv, in_open, output, outputlen); +} +#endif + +static int +really_write (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + char *bytes, int len) +{ +#if HAVE_SASL + if (priv->saslconn) + return really_write_sasl(conn, priv, in_open, bytes, len); + else +#endif + return really_write_plain(conn, priv, in_open, bytes, len); +} + +static int +really_read_buf (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + char *bytes, int len) +{ + int err; + + if (priv->uses_tls) { + tlsreread: + err = gnutls_record_recv (priv->session, bytes, len); + if (err < 0) { + if (err == GNUTLS_E_INTERRUPTED) + goto tlsreread; + error (in_open ? NULL : conn, + VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err)); + return -1; + } + if (err == 0) { + error (in_open ? NULL : conn, + VIR_ERR_RPC, "socket closed unexpectedly"); + return -1; + } + return err; + } else { + reread: + err = read (priv->sock, bytes, len); + if (err == -1) { + if (errno == EINTR) + goto reread; + error (in_open ? NULL : conn, + VIR_ERR_SYSTEM_ERROR, strerror (errno)); + return -1; + } + if (err == 0) { + error (in_open ? NULL : conn, + VIR_ERR_RPC, "socket closed unexpectedly"); + return -1; + } + return err; + } + + return 0; +} + +static int +really_read_plain (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + char *bytes, int len) +{ + do { + int ret = really_read_buf (conn, priv, in_open, bytes, len); + if (ret < 0) + return -1; + + len -= ret; + bytes += ret; + } while (len > 0); + + return 0; +} + +#if HAVE_SASL +static int +really_read_sasl (virConnectPtr conn, struct private_data *priv, + int in_open /* if we are in virConnectOpen */, + char *bytes, int len) +{ + do { + int want, got; + if (priv->saslDecoded == NULL) { + char encoded[8192]; + int encodedLen = sizeof(encoded); + int err, ret; + ret = really_read_buf (conn, priv, in_open, encoded, encodedLen); + if (ret < 0) + return -1; + + err = sasl_decode(priv->saslconn, encoded, ret, + &priv->saslDecoded, &priv->saslDecodedLength); + } + + got = priv->saslDecodedLength - priv->saslDecodedOffset; + want = len; + if (want > got) + want = got; + + memcpy(bytes, priv->saslDecoded + priv->saslDecodedOffset, want); + priv->saslDecodedOffset += want; + if (priv->saslDecodedOffset == priv->saslDecodedLength) { + priv->saslDecoded = NULL; + priv->saslDecodedOffset = priv->saslDecodedLength = 0; + } + bytes += want; + len -= want; + } while (len > 0); + + return 0; +} +#endif + +static int really_read (virConnectPtr conn, struct private_data *priv, int in_open /* if we are in virConnectOpen */, char *bytes, int len) { - char *p; - int err; - - p = bytes; - if (priv->uses_tls) { - do { - err = gnutls_record_recv (priv->session, p, len); - if (err < 0) { - if (err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN) - continue; - error (in_open ? NULL : conn, - VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err)); - return -1; - } - if (err == 0) { - error (in_open ? NULL : conn, - VIR_ERR_RPC, "socket closed unexpectedly"); - return -1; - } - len -= err; - p += err; - } - while (len > 0); - } else { - do { - err = read (priv->sock, p, len); - if (err == -1) { - if (errno == EINTR || errno == EAGAIN) - continue; - error (in_open ? NULL : conn, - VIR_ERR_SYSTEM_ERROR, strerror (errno)); - return -1; - } - if (err == 0) { - error (in_open ? NULL : conn, - VIR_ERR_RPC, "socket closed unexpectedly"); - return -1; - } - len -= err; - p += err; - } - while (len > 0); - } - - return 0; +#if HAVE_SASL + if (priv->saslconn) + return really_read_sasl (conn, priv, in_open, bytes, len); + else +#endif + return really_read_plain (conn, priv, in_open, bytes, len); } /* For errors internal to this library. */ -- |=- Red Hat, Engineering, Emerging Technologies, Boston. +1 978 392 2496 -=| |=- Perl modules: http://search.cpan.org/~danberr/ -=| |=- Projects: http://freshmeat.net/~danielpb/ -=| |=- GnuPG: 7D3B9505 F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 -=| -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list