[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

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



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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]