[Libvir] PATCH: 2/10: SASL encryption support
Daniel P. Berrange
berrange at redhat.com
Fri Nov 30 22:33:20 UTC 2007
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 -=|
More information about the libvir-list
mailing list