From patchwork Wed Apr 1 12:09:16 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Max Reitz X-Patchwork-Id: 457243 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4766F14018C for ; Wed, 1 Apr 2015 23:12:50 +1100 (AEDT) Received: from localhost ([::1]:52085 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YdHVo-0008Sf-DU for incoming@patchwork.ozlabs.org; Wed, 01 Apr 2015 08:12:48 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:35423) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YdHSl-0003vw-Hq for qemu-devel@nongnu.org; Wed, 01 Apr 2015 08:09:41 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YdHSf-0000oh-6K for qemu-devel@nongnu.org; Wed, 01 Apr 2015 08:09:39 -0400 Received: from mx1.redhat.com ([209.132.183.28]:44475) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YdHSe-0000no-Vt for qemu-devel@nongnu.org; Wed, 01 Apr 2015 08:09:33 -0400 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id t31C9We9014558 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL) for ; Wed, 1 Apr 2015 08:09:32 -0400 Received: from localhost (ovpn-116-114.ams2.redhat.com [10.36.116.114]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id t31C9Ux7004604 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Wed, 1 Apr 2015 08:09:31 -0400 From: Max Reitz To: qemu-devel@nongnu.org Date: Wed, 1 Apr 2015 14:09:16 +0200 Message-Id: <1427890157-18639-5-git-send-email-mreitz@redhat.com> In-Reply-To: <1427890157-18639-1-git-send-email-mreitz@redhat.com> References: <1427890157-18639-1-git-send-email-mreitz@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: Kevin Wolf , Paolo Bonzini , Markus Armbruster , Max Reitz Subject: [Qemu-devel] [PATCH for-2.4 4/5] chardev/irc: Add end-to-end encryption X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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 --- qapi-schema.json | 17 +++++++- qemu-char.c | 129 ++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 130 insertions(+), 16 deletions(-) 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 @@ -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;