[Libvir] PATCH: 2/10: SASL encryption support

Daniel P. Berrange berrange at redhat.com
Thu Nov 29 17:17:07 UTC 2007


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  -=| 




More information about the libvir-list mailing list