From patchwork Sat Jan 13 21:34:40 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Aloni X-Patchwork-Id: 10162449 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 24AE66029B for ; Sat, 13 Jan 2018 21:37:36 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1743928AFA for ; Sat, 13 Jan 2018 21:37:36 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0B7FA28B2B; Sat, 13 Jan 2018 21:37:36 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.4 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED, T_DKIM_INVALID, URIBL_BLACK autolearn=ham version=3.3.1 Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.wl.linuxfoundation.org (Postfix) with SMTP id 4E33A28AFA for ; Sat, 13 Jan 2018 21:37:34 +0000 (UTC) Received: (qmail 12066 invoked by uid 550); 13 Jan 2018 21:36:39 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Delivered-To: mailing list kernel-hardening@lists.openwall.com Received: (qmail 11916 invoked from network); 13 Jan 2018 21:36:34 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernelim-com.20150623.gappssmtp.com; s=20150623; h=from:to:subject:date:message-id:in-reply-to:references; bh=YtWzUWHe4+sY3vpBmkUHMkWYrLCpmuAUamBIxYRiHjE=; b=VFHR5tnKHgQKJdBM2h0hIL69IKRE/rHSmzA5FO0IHJPKD7HLRlbrfUR84GO/0jXnlp Cv+N4eDxcOOqAT0G066qb33uxywsa8hdKJx0uGjiIA3zTRb+KuPiXyHk3DF0wJGMSOMU Ezg5gzdoIk408m4q8On4QSgvE40oIqo+oMCT+fkGHERg1iSI5XCDQf2UJkSGIsVFXIEM QCW5+i+AUj3rSTifeuum/CtU2PszWWEeogmFtb20Z63QDeIAgb25WvaaC6PPkcbWFcju F89po3MVdnv84mOgJxCrYZghygckfDeouAT1Rax4/iROuu7ui9FtUoj0kI0KhukHOl04 +siQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references; bh=YtWzUWHe4+sY3vpBmkUHMkWYrLCpmuAUamBIxYRiHjE=; b=SUPhuF6xRqiGzvM07rSXvCULTXgjZJ/p7HpFR8kuMmmSU8QUGeLkSIQRJ2m8MxftUE wNqGIV2oThv2QvFrvrCECAWe+AV7dpSRJOebC/pBP3VUJJ+HSMOKcJjDJozGYQDNJC9/ /QsBbrOLF64vYX+8+vXoOLV/t3csdjS4UQJFbsdOcKVcgAIbf4Wk6yad5LhT+RPMLDEL GXNqdlnAcPv+91QnWQk2Wqhigf3qys+CQ+gG41kuCTQr+VdGb34LQD0WjZ98YmTvYjoe VEsbjK36fNAlag7LTpXwiZ0uy+5SYEe0DouooqjYZFs5BvB7yF46w/Tnb+R5HZMwN+8k VFNQ== X-Gm-Message-State: AKwxytdqE/sLN8bL6tKyD5yoRVK1SG3GJJ73MJwtkiIJ1nocndWyhogr Y3DqBP336D1HWbBBjpNaS8URIFbv X-Google-Smtp-Source: ACJfBotF/P9z32M/DLH9GB4l6Waw2BhqkqBVaBdPjOQxe+vq8Fvhgo7OWXCxZSIDkQ0RDkQTJCdlOg== X-Received: by 10.28.210.194 with SMTP id j185mr6671165wmg.145.1515879382994; Sat, 13 Jan 2018 13:36:22 -0800 (PST) From: Dan Aloni To: linux-kernel@vger.kernel.org, kernel-hardening@lists.openwall.com Date: Sat, 13 Jan 2018 23:34:40 +0200 Message-Id: <20180113213441.52047-7-dan@kernelim.com> X-Mailer: git-send-email 2.14.3 In-Reply-To: <20180113213441.52047-1-dan@kernelim.com> References: <20180113213441.52047-1-dan@kernelim.com> Subject: [kernel-hardening] [PATCHv2 6/7] tools: add dmesg decryption program X-Virus-Scanned: ClamAV using ClamSMTP Example execution: dmesg | dmesg-decipher Signed-off-by: Dan Aloni --- tools/Makefile | 9 +- tools/kmsg/.gitignore | 1 + tools/kmsg/Makefile | 14 ++ tools/kmsg/dmesg-decipher.c | 354 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 tools/kmsg/.gitignore create mode 100644 tools/kmsg/Makefile create mode 100644 tools/kmsg/dmesg-decipher.c diff --git a/tools/Makefile b/tools/Makefile index be02c8b904db..5a661e4c9012 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -18,6 +18,7 @@ help: @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' @echo ' kvm_stat - top-like utility for displaying kvm statistics' + @echo ' kmsg - A tool for decrypting a dmesg using a private key' @echo ' leds - LEDs tools' @echo ' liblockdep - user-space wrapper for kernel locking-validator' @echo ' bpf - misc BPF tools' @@ -91,6 +92,9 @@ freefall: FORCE kvm_stat: FORCE $(call descend,kvm/$@) +kmsg: FORCE + $(call descend,kmsg) + all: acpi cgroup cpupower gpio hv firewire liblockdep \ perf selftests spi turbostat usb \ virtio vm bpf x86_energy_perf_policy \ @@ -167,6 +171,9 @@ tmon_clean: freefall_clean: $(call descend,laptop/freefall,clean) +kmsg_clean: + $(call descend,kmsg,clean) + build_clean: $(call descend,build,clean) @@ -174,6 +181,6 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \ perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ - gpio_clean objtool_clean leds_clean wmi_clean + gpio_clean objtool_clean leds_clean wmi_clean kmsg_clean .PHONY: FORCE diff --git a/tools/kmsg/.gitignore b/tools/kmsg/.gitignore new file mode 100644 index 000000000000..a5b4e26b8d0b --- /dev/null +++ b/tools/kmsg/.gitignore @@ -0,0 +1 @@ +dmesg-decipher diff --git a/tools/kmsg/Makefile b/tools/kmsg/Makefile new file mode 100644 index 000000000000..9f4ef7b11798 --- /dev/null +++ b/tools/kmsg/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +CC := $(CROSS_COMPILE)gcc + +CFLAGS := -O2 -Wall $$(pkg-config --libs openssl) + +PROGS := dmesg-decipher + +%: %.c + $(CC) $(CFLAGS) -o $@ $^ + +all: $(PROGS) + +clean: + rm -fr $(PROGS) diff --git a/tools/kmsg/dmesg-decipher.c b/tools/kmsg/dmesg-decipher.c new file mode 100644 index 000000000000..1ad2b0a27402 --- /dev/null +++ b/tools/kmsg/dmesg-decipher.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dmesg-decipher.c + * + * A sample utility to decrypt an encrypted dmesg output, for + * development with kernels having kmsg encryption enabled. + * + * base64 decoding code taken from lib/base64-armor.c + * + * Copyright (c) Dan Aloni, 2017 + * + * Compile with: + * + * gcc -O2 -Wall $(pkg-config --libs openssl) \ + * dmesg-decipher -o dmesg-decipher + */ + +#include +#include +#include + +#include +#include +#include +#include + +/* + * The following is based on code from: + * + * https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption + */ +static int aes_256_gcm_decrypt(unsigned char *ciphertext, size_t ciphertext_len, + unsigned char *aad, size_t aad_len, + unsigned char *tag, unsigned char *key, + unsigned char *iv, size_t iv_len, + unsigned char *plaintext) +{ + EVP_CIPHER_CTX *ctx; + int len; + int plaintext_len; + int ret = -1; + + /* Create and initialise the context */ + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return -1; + + /* Initialise the decryption operation. */ + if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL)) + goto free; + + /* Set IV length. Not necessary if this is 12 bytes (96 bits) */ + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)) + goto free; + + /* Initialise key and IV */ + if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) + goto free; + + /* Provide any AAD data. This can be called zero or more times as + * required + */ + if (aad_len != 0) + if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len)) + goto free; + + /* Provide the message to be decrypted, and obtain the plaintext output. + * EVP_DecryptUpdate can be called multiple times if necessary + */ + if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, + ciphertext_len)) + goto free; + plaintext_len = len; + + /* Set expected tag value. Works in OpenSSL 1.0.1d and later */ + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag)) + goto free; + + /* Finalise the decryption. A positive return value indicates success, + * anything else is a failure - the plaintext is not trustworthy. + */ + ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len); + +free: + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + if (ret > 0) { + /* Success */ + plaintext_len += len; + return plaintext_len; + } + + /* Verify failed */ + return -1; +} + +static int decode_bits(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A'; + if (c >= 'a' && c <= 'z') + return c - 'a' + 26; + if (c >= '0' && c <= '9') + return c - '0' + 52; + if (c == '+') + return 62; + if (c == '/') + return 63; + if (c == '=') + return 0; /* just non-negative, please */ + return -EINVAL; +} + +static int base64_unarmor(char *dst, int dst_max, const char *src, + const char *end) +{ + int olen = 0; + + while (src < end) { + int a, b, c, d; + + if (src[0] == '\n') { + src++; + continue; + } + if (src + 4 > end) + return -EINVAL; + a = decode_bits(src[0]); + b = decode_bits(src[1]); + c = decode_bits(src[2]); + d = decode_bits(src[3]); + if (a < 0 || b < 0 || c < 0 || d < 0) + return -EINVAL; + + if (dst_max < 1) + return -ENOSPC; + *dst++ = (a << 2) | (b >> 4); + dst_max--; + if (src[2] == '=') + return olen + 1; + if (dst_max < 1) + return -ENOSPC; + *dst++ = ((b & 15) << 4) | (c >> 2); + dst_max--; + if (src[3] == '=') + return olen + 2; + if (dst_max < 1) + return -ENOSPC; + *dst++ = ((c & 3) << 6) | d; + dst_max--; + olen += 3; + src += 4; + } + return olen; +} + +static int parse_int_regex_match(const char *source, regmatch_t match, + size_t *output) +{ + char decimal_number[0x10] = { + 0, + }; + size_t len = match.rm_eo - match.rm_so; + + if (len >= sizeof(decimal_number)) + return -1; + + memcpy(&decimal_number[0], &source[match.rm_so], len); + + *output = atoi(decimal_number); + return 0; +} + +static const char session_key_pattern[] = "(.*)K:([0-9a-zA-Z~+/=]+)"; +static const char message_pattern[] = + "(.*)M:([0-9a-zA-Z~+/=]+),([0-9]+),([0-9]+)"; + +static int decrypt_message(char *line, regmatch_t *matches, uint8_t *sess_key) +{ + char plain_text[0x1000], *enc; + uint8_t cipher_msg_bin[0x1000]; + size_t cipher_msg_size = sizeof(cipher_msg_bin); + size_t cipher_size; + const regmatch_t prefix = matches[1]; + const regmatch_t ciphermsg = matches[2]; + const regmatch_t auth_str_len = matches[3]; + const regmatch_t iv_str_len = matches[4]; + size_t auth_len; + size_t iv_len; + int ret; + + ret = parse_int_regex_match(line, auth_str_len, &auth_len); + if (ret) + return -1; + + ret = parse_int_regex_match(line, iv_str_len, &iv_len); + if (ret) + return -1; + + for (enc = &line[ciphermsg.rm_so]; enc < &line[ciphermsg.rm_eo]; enc++) + if (*enc == '~') + *enc = '\n'; + + ret = base64_unarmor((char *)cipher_msg_bin, cipher_msg_size, + &line[ciphermsg.rm_so], &line[ciphermsg.rm_eo]); + if (ret < 0) { + fprintf(stderr, "error decoding base64 message (code = %d)\n", + ret); + return -1; + } + + cipher_msg_size = ret; + + if (iv_len >= cipher_msg_size || auth_len >= cipher_msg_size + || auth_len + iv_len > cipher_msg_size) { + return -1; + } + + cipher_size = cipher_msg_size - auth_len - iv_len; + + ret = aes_256_gcm_decrypt(/* Ciphertext */ + (uint8_t *)cipher_msg_bin, cipher_size, + + /* AAD */ + NULL, 0, + + /* tag */ + (uint8_t *)&cipher_msg_bin[cipher_size], + + /* key */ + sess_key, + + /* IV */ + (uint8_t *)&cipher_msg_bin[cipher_size + + auth_len], + iv_len, + + /* Plain text */ + (uint8_t *)plain_text); + if (ret > 0) { + fwrite(line, prefix.rm_eo, 1, stdout); + fwrite(plain_text, ret, 1, stdout); + fwrite("\n", 1, 1, stdout); + } + + return ret; +} + +int main(int argc, char **argv) +{ + BIO *tbio = NULL; + RSA *rsa; + int ret = 1; + char line[0x1000]; + uint8_t enc_sess_key[0x200]; + uint8_t sess_key[0x200] = { + 0, + }; + bool got_key = false; + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + + regex_t session_key_regex; + regex_t message_regex; + + ret = regcomp(&session_key_regex, session_key_pattern, REG_EXTENDED); + if (ret) + goto err; + + ret = regcomp(&message_regex, message_pattern, REG_EXTENDED); + if (ret) + goto err; + + if (argc < 2) { + fprintf(stderr, "not enough parameters\n"); + return -1; + } + + /* Read in recipient certificate and private key */ + tbio = BIO_new_file(argv[1], "r"); + if (!tbio) { + fprintf(stderr, "BIO_new_file - error\n"); + goto err; + } + + rsa = PEM_read_bio_RSAPrivateKey(tbio, NULL, NULL, NULL); + if (!rsa) + goto err; + + while (true) { + regmatch_t matches[5]; + + if (!fgets(line, sizeof(line), stdin)) + break; + + if (!got_key + && !regexec(&session_key_regex, line, 5, matches, 0)) { + const regmatch_t match = matches[2]; + size_t enc_sess_key_size = sizeof(enc_sess_key); + char *enc; + + for (enc = &line[match.rm_so]; enc < &line[match.rm_eo]; + enc++) + if (*enc == '~') + *enc = '\n'; + + ret = base64_unarmor( + (char *)&enc_sess_key, enc_sess_key_size, + &line[match.rm_so], &line[match.rm_eo]); + if (ret < 0) { + fprintf(stderr, + "error decoding session key" + " (code = %d)\n", + ret); + return -1; + } + + enc_sess_key_size = ret; + + ret = RSA_private_decrypt(enc_sess_key_size, + enc_sess_key, sess_key, rsa, + RSA_PKCS1_PADDING); + if (ret < 0) + goto err; + + got_key = true; + } + + if (!regexec(&message_regex, line, 5, matches, 0)) { + if (!got_key) { + fprintf(stderr, + "session key must precede messages\n"); + break; + } + + ret = decrypt_message(line, matches, sess_key); + if (ret < 0) { + fprintf(stderr, + "error decrypting message" + " (code = %d)\n", + ret); + break; + } + } + } + + regfree(&session_key_regex); + regfree(&message_regex); + +err: + return -1; +}