On Thu, Nov 29, 2007 at 05:17:07PM +0000, Daniel P. Berrange wrote: > 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. Rebased to latest CVS. 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. */ Dan. -- |=- 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