[Libvir] PATCH: 3/4: Wire encryption with SASL

Daniel P. Berrange berrange at redhat.com
Fri Nov 2 13:32:52 UTC 2007


On Fri, Nov 02, 2007 at 01:09:26AM +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 - you have to trust that the underlying Kerberos impl was 
> compiled with suitably strong ciphers - all modern OS use strong ciphers in
> Kebreros so I don't think this is a huge issue. 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. This is an site admin config choice.
> 
> 
>  qemud/internal.h      |   17 ++-
>  qemud/qemud.c         |  207 +++++++++++++++++++++++++++++-------
>  qemud/remote.c        |   97 ++++++++++++++++-
>  src/remote_internal.c |  283 ++++++++++++++++++++++++++++++++++++++++----------
>  4 files changed, 503 insertions(+), 101 deletions(-)

diff -r 1052e0b6c97b qemud/internal.h
--- a/qemud/internal.h	Thu Nov 01 16:28:22 2007 -0400
+++ b/qemud/internal.h	Thu Nov 01 16:28:26 2007 -0400
@@ -67,10 +67,11 @@ 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,
 };
 
 /* Stores the per-client connection state */
@@ -87,10 +88,16 @@ struct qemud_client {
     /* If set, TLS is required on this socket. */
     int tls;
     gnutls_session_t session;
-    enum qemud_tls_direction direction;
     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;
diff -r 1052e0b6c97b qemud/qemud.c
--- a/qemud/qemud.c	Thu Nov 01 16:28:22 2007 -0400
+++ b/qemud/qemud.c	Thu Nov 01 16:28:26 2007 -0400
@@ -701,7 +701,9 @@ static struct qemud_server *qemudInitial
     struct qemud_socket *sock;
     char sockname[PATH_MAX];
     char roSockname[PATH_MAX];
+#if HAVE_SASL
     int err;
+#endif
 
     if (!(server = calloc(1, sizeof(struct qemud_server)))) {
         qemudLog(QEMUD_ERR, "Failed to allocate struct qemud_server");
@@ -1029,7 +1031,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;
 }
 
@@ -1095,7 +1096,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;
@@ -1153,13 +1153,10 @@ static void qemudDispatchClientFailure(s
 
 
 
-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);*/
 
@@ -1174,7 +1171,6 @@ static int qemudClientRead(struct qemud_
         }
     } else {
         ret = gnutls_record_recv (client->session, data, len);
-        client->direction = gnutls_record_get_direction (client->session);
         if (qemudRegisterClientEvent (server, client, 1) < 0)
             qemudDispatchClientFailure (server, client);
         else if (ret <= 0) {
@@ -1189,9 +1185,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) {
 
@@ -1235,7 +1301,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);
@@ -1275,7 +1340,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);
         }
@@ -1290,14 +1354,10 @@ 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;
-
+static int qemudClientWriteBuf(struct qemud_server *server,
+                               struct qemud_client *client,
+                               const char *data, int len) {
+    int ret;
     if (!client->tls) {
         if ((ret = write(client->fd, data, len)) == -1) {
             if (errno != EAGAIN) {
@@ -1308,7 +1368,6 @@ static int qemudClientWrite(struct qemud
         }
     } else {
         ret = gnutls_record_send (client->session, data, len);
-        client->direction = gnutls_record_get_direction (client->session);
         if (qemudRegisterClientEvent (server, client, 1) < 0)
             qemudDispatchClientFailure (server, client);
         else if (ret < 0) {
@@ -1320,9 +1379,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);
 }
 
 
@@ -1337,7 +1456,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);
@@ -1362,7 +1480,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);
         }
@@ -1402,25 +1519,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->session) == 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 1052e0b6c97b qemud/remote.c
--- a/qemud/remote.c	Thu Nov 01 16:28:22 2007 -0400
+++ b/qemud/remote.c	Thu Nov 01 16:32:06 2007 -0400
@@ -283,7 +283,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
@@ -368,7 +367,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
@@ -2034,6 +2032,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;
@@ -2089,6 +2088,51 @@ remoteDispatchAuthSaslInit (struct qemud
         return -2;
     }
 
+    /* Inform SASL that we've got an external SSF layer from TLS */
+    if (client->tls) {
+        gnutls_cipher_algorithm_t cipher;
+        sasl_ssf_t ssf;
+
+        cipher = gnutls_cipher_get(client->session);
+        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 we've got TLS, we don't care about SSF */
+    secprops.min_ssf = client->tls ? 0 : 56; /* Good enough to require kerberos */
+    secprops.max_ssf = client->tls ? 0 : 100000; /* Arbitrary big number */
+    secprops.maxbufsize = 8192;
+    /* If we're not TLS, then forbid any anonymous or trivially crackable auth */
+    secprops.security_flags = client->tls ? 0 :
+        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 */
@@ -2117,6 +2161,45 @@ 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;
+
+    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.
@@ -2183,6 +2266,11 @@ remoteDispatchAuthSaslStart (struct qemu
     if (err == SASL_CONTINUE) {
         ret->complete = 0;
     } else {
+        /* If non-TLS, check a suitable SSF was negotiated */
+        if (!client->tls &&
+            remoteSASLCheckSSF(client, req) < 0)
+            return -2;
+
         REMOTE_DEBUG("Authentication successful %d", client->fd);
         ret->complete = 1;
         client->auth = REMOTE_AUTH_NONE;
@@ -2254,6 +2342,11 @@ remoteDispatchAuthSaslStep (struct qemud
     if (err == SASL_CONTINUE) {
         ret->complete = 0;
     } else {
+        /* If non-TLS, check a suitable SSF was negotiated */
+        if (!client->tls &&
+            remoteSASLCheckSSF(client, req) < 0)
+            return -2;
+
         REMOTE_DEBUG("Authentication successful %d", client->fd);
         ret->complete = 1;
         client->auth = REMOTE_AUTH_NONE;
diff -r 1052e0b6c97b src/remote_internal.c
--- a/src/remote_internal.c	Thu Nov 01 16:28:22 2007 -0400
+++ b/src/remote_internal.c	Thu Nov 01 16:32:48 2007 -0400
@@ -78,6 +78,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
 };
 
@@ -145,6 +148,7 @@ remoteStartup(void)
     return 0;
 }
 
+#if HAVE_SASL
 static void
 remoteDebug(struct private_data *priv, const char *msg,...)
 {
@@ -157,6 +161,7 @@ remoteDebug(struct private_data *priv, c
     va_end(args);
     fprintf(priv->debugLog, "\n");
 }
+#endif
 
 
 /**
@@ -2904,15 +2909,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;
+    sasl_security_properties_t secprops;
     remote_auth_sasl_init_ret iret;
     remote_auth_sasl_start_args sargs;
     remote_auth_sasl_start_ret sret;
@@ -2926,6 +2930,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 */
@@ -2965,7 +2971,7 @@ remoteAuthSASL (virConnectPtr conn, stru
         free(localAddr);
         return -1;
     }
-    printf("'%s' '%s' '%s'\n", priv->hostname, localAddr, remoteAddr);
+
     /* Setup a handle for being a client */
     err = sasl_client_new("libvirt",
                           priv->hostname,
@@ -2981,6 +2987,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;
     }
 
@@ -3100,9 +3151,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
@@ -3294,11 +3366,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;
@@ -3336,55 +3408,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