diff mbox

[for-2.4,4/5] chardev/irc: Add end-to-end encryption

Message ID 1427890157-18639-5-git-send-email-mreitz@redhat.com
State New
Headers show

Commit Message

Max Reitz April 1, 2015, 12:09 p.m. UTC
IRC being usable for remote configuration, offering a secure channel is
indispensable. As can be seen from its alias "Caesar's cipher", ROT13
has been in use since ancient times and has been employed for state and
military secrets, so it is definitely well-tested, stable and secure
enough to manage a continent-spanning empire.

Its security is further proven by its ubiquity. For instance, vim
supports encryption and decryption by hfvat gur t? pbzznaq.

However, some concerns have been raised in the past whether ROT13 and
consequently all of Caesar's ciphers are actually secure for data with
non-maximum entropy. Another cipher has shown in the past how to deal
with these accusations: As DES is considered insecure, Triple DES (3DES)
has been introduced. Likewise, this patch implements Triple ROT13 in
addition to simple ROT13 to greatly improve security.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 qapi-schema.json |  17 +++++++-
 qemu-char.c      | 129 ++++++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 130 insertions(+), 16 deletions(-)
diff mbox

Patch

diff --git a/qapi-schema.json b/qapi-schema.json
index e4f93fd..d365352 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2876,6 +2876,20 @@ 
 { 'type': 'ChardevRingbuf', 'data': { '*size'  : 'int' } }
 
 ##
+# @ChardevIrcEncryption
+#
+# Enumeration of supported encryption modes over IRC.
+#
+# @none:    No encryption
+# @weak:    ROT13
+# @strong:  Triple ROT13
+#
+# Since: 2.4
+##
+{ 'enum': 'ChardevIrcEncryption',
+  'data': [ 'none', 'weak', 'strong' ] }
+
+##
 # @ChardevIrc
 #
 # Configuration info for IRC chardevs.
@@ -2889,7 +2903,8 @@ 
 { 'type': 'ChardevIrc', 'data': { 'addr'        : 'SocketAddress',
                                   'nick'        : 'str',
                                   'channel'     : 'str',
-                                  'ssl'         : 'bool' } }
+                                  'ssl'         : 'bool',
+                                  'encryption'  : 'ChardevIrcEncryption' } }
 
 ##
 # @ChardevBackend:
diff --git a/qemu-char.c b/qemu-char.c
index 1030d5e..1fe149d 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -30,6 +30,7 @@ 
 #include "qmp-commands.h"
 #include "qapi/qmp-input-visitor.h"
 #include "qapi/qmp-output-visitor.h"
+#include "qapi/util.h"
 #include "qapi-visit.h"
 
 #include <unistd.h>
@@ -3321,8 +3322,78 @@  typedef struct {
     uint8_t *recvbuf;
     int recvbuf_idx;
     bool recv_line_skip;
+
+    ChardevIrcEncryption encryption;
 } IrcCharDriverState;
 
+static void caesar_cipher(uint8_t *dest, const uint8_t *src, size_t len,
+                          int shift)
+{
+    size_t i;
+
+    shift %= 26;
+    if (shift < 0) {
+        shift += 26;
+    }
+
+    for (i = 0; i < len; i++) {
+        if (isascii(src[i]) && isalpha(src[i])) {
+            dest[i] = (((src[i] & 0x1f) - 1 + shift) % 26 + 1)
+                    | (src[i] & ~0x1f);
+        } else {
+            dest[i] = src[i];
+        }
+    }
+}
+
+static void irc_encrypt(IrcCharDriverState *irc, uint8_t *buffer, size_t len)
+{
+    switch (irc->encryption) {
+    /* no encryption */
+    case CHARDEV_IRC_ENCRYPTION_NONE:
+        break;
+
+    /* ROT13 */
+    case CHARDEV_IRC_ENCRYPTION_WEAK:
+        caesar_cipher(buffer, buffer, len, 13);
+        break;
+
+    /* Triple ROT13 */
+    case CHARDEV_IRC_ENCRYPTION_STRONG:
+        caesar_cipher(buffer, buffer, len, 13);
+        caesar_cipher(buffer, buffer, len, 13);
+        caesar_cipher(buffer, buffer, len, 13);
+        break;
+
+    default:
+        return;
+    }
+}
+
+static void irc_decrypt(IrcCharDriverState *irc, uint8_t *buffer, size_t len)
+{
+    switch (irc->encryption) {
+    /* no encryption */
+    case CHARDEV_IRC_ENCRYPTION_NONE:
+        break;
+
+    /* ROT13 */
+    case CHARDEV_IRC_ENCRYPTION_WEAK:
+        caesar_cipher(buffer, buffer, len, -13);
+        break;
+
+    /* Triple ROT13 */
+    case CHARDEV_IRC_ENCRYPTION_STRONG:
+        caesar_cipher(buffer, buffer, len, -13);
+        caesar_cipher(buffer, buffer, len, -13);
+        caesar_cipher(buffer, buffer, len, -13);
+        break;
+
+    default:
+        return;
+    }
+}
+
 #ifdef CONFIG_GNUTLS
 static ssize_t irc_send(IrcCharDriverState *irc, const void *buf, size_t len)
 {
@@ -3447,6 +3518,9 @@  static int irc_chr_write(CharDriverState *s, const uint8_t *buf, int len)
         } else if (buf[i] == '\n') {
             if (irc->sendbuf) {
                 if (!irc->send_line_skip) {
+                    irc_encrypt(irc, irc->sendbuf + irc->sendbuf_prefixlen,
+                                irc->sendbuf_idx);
+
                     irc->sendbuf[irc->sendbuf_idx++] = '\r';
                     irc->sendbuf[irc->sendbuf_idx++] = '\n';
 
@@ -3486,6 +3560,9 @@  static int irc_chr_write(CharDriverState *s, const uint8_t *buf, int len)
                     irc->sendbuf[break_index] = '\r';
                     irc->sendbuf[break_index + 1] = '\n';
 
+                    irc_encrypt(irc, irc->sendbuf + irc->sendbuf_prefixlen,
+                                break_index);
+
                     irc_send(irc, irc->sendbuf, break_index + 2);
 
                     irc->sendbuf[break_index + 1] = replaced;
@@ -3573,22 +3650,27 @@  static gboolean irc_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
 
     in_query = !strcmp(real_dest, irc->nick);
 
-    /* ignore CTCP */
-    if (!strcmp(cmd, "PRIVMSG") && msg[0] != 1 &&
-        (in_query || (!strncmp(msg, irc->nick, strlen(irc->nick)) &&
-                      !isalnum(msg[strlen(irc->nick)]))))
-    {
-        const char *start = msg;
-
-        if (!in_query) {
-            start = msg + strlen(irc->nick) + 1;
-            while (*start && isspace(*start)) {
-                start++;
+    /* ignore plain-text CTCP */
+    if (!strcmp(cmd, "PRIVMSG") && msg[0] != 1) {
+        irc_decrypt(irc, (uint8_t *)msg, strlen(msg));
+
+        /* ignore encrypted CTCP as well */
+        if (in_query ? msg[0] != 1 :
+            !strncmp(msg, irc->nick, strlen(irc->nick)) &&
+            !isalnum(msg[strlen(irc->nick)]))
+        {
+            const char *start = msg;
+
+            if (!in_query) {
+                start = msg + strlen(irc->nick) + 1;
+                while (*start && isspace(*start)) {
+                    start++;
+                }
             }
-        }
 
-        qemu_chr_be_write(s, (uint8_t *)start, strlen(start));
-        qemu_chr_be_write(s, (uint8_t *)"\n", 1);
+            qemu_chr_be_write(s, (uint8_t *)start, strlen(start));
+            qemu_chr_be_write(s, (uint8_t *)"\n", 1);
+        }
     }
 
     g_free(buffer);
@@ -3985,9 +4067,11 @@  static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
 static void qemu_chr_parse_irc(QemuOpts *opts, ChardevBackend *backend,
                                Error **errp)
 {
-    const char *host, *port, *nick, *channel, *sockfd;
+    const char *host, *port, *nick, *channel, *sockfd, *encryption_raw;
+    ChardevIrcEncryption encryption;
     bool ssl;
     SocketAddress *addr;
+    Error *local_err = NULL;
 
     host    = qemu_opt_get(opts, "host");
     port    = qemu_opt_get(opts, "port") ?: "6667";
@@ -4000,6 +4084,15 @@  static void qemu_chr_parse_irc(QemuOpts *opts, ChardevBackend *backend,
     ssl     = false;
 #endif
 
+    encryption_raw = qemu_opt_get(opts, "encryption") ?: "none";
+    encryption = qapi_enum_parse(ChardevIrcEncryption_lookup, encryption_raw,
+                                 CHARDEV_IRC_ENCRYPTION_MAX,
+                                 CHARDEV_IRC_ENCRYPTION_NONE, &local_err);
+    if (local_err) {
+        error_propagate(errp, local_err);
+        return;
+    }
+
     if ((!host && !sockfd) || !nick || !channel) {
         error_setg(errp, "chardev: irc: Missing options");
         return;
@@ -4035,6 +4128,7 @@  static void qemu_chr_parse_irc(QemuOpts *opts, ChardevBackend *backend,
     backend->irc->nick = g_strdup(nick);
     backend->irc->channel = g_strdup(channel);
     backend->irc->ssl = ssl;
+    backend->irc->encryption = encryption;
 }
 
 typedef struct CharDriver {
@@ -4409,6 +4503,9 @@  QemuOptsList qemu_chardev_opts = {
             .name = "ssl",
             .type = QEMU_OPT_BOOL,
 #endif
+        },{
+            .name = "encryption",
+            .type = QEMU_OPT_STRING,
         },
         { /* end of list */ }
     },
@@ -4677,6 +4774,7 @@  static CharDriverState *qemu_chr_open_irc(ChardevIrc *irc, Error **errp)
 
     irc_cds->recvbuf = g_malloc(1024);
     irc_cds->recvbuf_idx = 0;
+    irc_cds->encryption = CHARDEV_IRC_ENCRYPTION_NONE;
 
 #ifdef CONFIG_GNUTLS
     if (irc->ssl) {
@@ -4800,6 +4898,7 @@  static CharDriverState *qemu_chr_open_irc(ChardevIrc *irc, Error **errp)
     }
 
     irc_cds->query = query;
+    irc_cds->encryption = irc->encryption;
 
     s = qemu_chr_alloc();
     s->chr_write = irc_chr_write;