Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754907AbeAMVhV (ORCPT + 1 other); Sat, 13 Jan 2018 16:37:21 -0500 Received: from mail-wm0-f65.google.com ([74.125.82.65]:35292 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754482AbeAMVgY (ORCPT ); Sat, 13 Jan 2018 16:36:24 -0500 X-Google-Smtp-Source: ACJfBotF/P9z32M/DLH9GB4l6Waw2BhqkqBVaBdPjOQxe+vq8Fvhgo7OWXCxZSIDkQ0RDkQTJCdlOg== From: Dan Aloni To: linux-kernel@vger.kernel.org, kernel-hardening@lists.openwall.com Subject: [PATCHv2 6/7] tools: add dmesg decryption program 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> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Return-Path: 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; +} -- 2.14.3