mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-15 21:05:08 +00:00
8e7a31b65f
ERR_load_crypto_strings is deprecated in OpenSSL 1.1, and OpenSSL 1.1 generally does not require explicit initialization. However, we do need to ensure that initialization is done before entering capability mode so call OPENSSL_init_crypto instead. Also include header needed for ERR_error_string. Reviewed by: vangyzen Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D40343
433 lines
10 KiB
C
433 lines
10 KiB
C
/*-
|
|
* Copyright (c) 2016 Konrad Witaszczyk <def@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/capsicum.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/kerneldump.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <ctype.h>
|
|
#include <capsicum_helpers.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/engine.h>
|
|
|
|
#include "pjdlog.h"
|
|
|
|
#define DECRYPTCORE_CRASHDIR "/var/crash"
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
|
|
pjdlog_exitx(1,
|
|
"usage: decryptcore [-fLv] -p privatekeyfile -k keyfile -e encryptedcore -c core\n"
|
|
" decryptcore [-fLv] [-d crashdir] -p privatekeyfile -n dumpnr");
|
|
}
|
|
|
|
static int
|
|
wait_for_process(pid_t pid)
|
|
{
|
|
int status;
|
|
|
|
if (waitpid(pid, &status, WUNTRACED | WEXITED) == -1) {
|
|
pjdlog_errno(LOG_ERR, "Unable to wait for a child process");
|
|
return (1);
|
|
}
|
|
|
|
if (WIFEXITED(status))
|
|
return (WEXITSTATUS(status));
|
|
|
|
return (1);
|
|
}
|
|
|
|
static struct kerneldumpkey *
|
|
read_key(int kfd)
|
|
{
|
|
struct kerneldumpkey *kdk;
|
|
ssize_t size;
|
|
size_t kdksize;
|
|
|
|
PJDLOG_ASSERT(kfd >= 0);
|
|
|
|
kdksize = sizeof(*kdk);
|
|
kdk = calloc(1, kdksize);
|
|
if (kdk == NULL) {
|
|
pjdlog_errno(LOG_ERR, "Unable to allocate kernel dump key");
|
|
goto failed;
|
|
}
|
|
|
|
size = read(kfd, kdk, kdksize);
|
|
if (size == (ssize_t)kdksize) {
|
|
kdk->kdk_encryptedkeysize = dtoh32(kdk->kdk_encryptedkeysize);
|
|
kdksize += (size_t)kdk->kdk_encryptedkeysize;
|
|
kdk = realloc(kdk, kdksize);
|
|
if (kdk == NULL) {
|
|
pjdlog_errno(LOG_ERR, "Unable to reallocate kernel dump key");
|
|
goto failed;
|
|
}
|
|
size += read(kfd, &kdk->kdk_encryptedkey,
|
|
kdk->kdk_encryptedkeysize);
|
|
}
|
|
if (size != (ssize_t)kdksize) {
|
|
pjdlog_errno(LOG_ERR, "Unable to read key");
|
|
goto failed;
|
|
}
|
|
|
|
return (kdk);
|
|
failed:
|
|
free(kdk);
|
|
return (NULL);
|
|
}
|
|
|
|
static bool
|
|
decrypt(int ofd, const char *privkeyfile, const char *keyfile,
|
|
const char *input)
|
|
{
|
|
uint8_t buf[KERNELDUMP_BUFFER_SIZE], key[KERNELDUMP_KEY_MAX_SIZE],
|
|
chachaiv[4 * 4];
|
|
EVP_CIPHER_CTX *ctx;
|
|
const EVP_CIPHER *cipher;
|
|
FILE *fp;
|
|
struct kerneldumpkey *kdk;
|
|
RSA *privkey;
|
|
int ifd, kfd, olen, privkeysize;
|
|
ssize_t bytes;
|
|
pid_t pid;
|
|
|
|
PJDLOG_ASSERT(ofd >= 0);
|
|
PJDLOG_ASSERT(privkeyfile != NULL);
|
|
PJDLOG_ASSERT(keyfile != NULL);
|
|
PJDLOG_ASSERT(input != NULL);
|
|
|
|
ctx = NULL;
|
|
privkey = NULL;
|
|
|
|
/*
|
|
* Decrypt a core dump in a child process so we can unlink a partially
|
|
* decrypted core if the child process fails.
|
|
*/
|
|
pid = fork();
|
|
if (pid == -1) {
|
|
pjdlog_errno(LOG_ERR, "Unable to create child process");
|
|
close(ofd);
|
|
return (false);
|
|
}
|
|
|
|
if (pid > 0) {
|
|
close(ofd);
|
|
return (wait_for_process(pid) == 0);
|
|
}
|
|
|
|
kfd = open(keyfile, O_RDONLY);
|
|
if (kfd == -1) {
|
|
pjdlog_errno(LOG_ERR, "Unable to open %s", keyfile);
|
|
goto failed;
|
|
}
|
|
ifd = open(input, O_RDONLY);
|
|
if (ifd == -1) {
|
|
pjdlog_errno(LOG_ERR, "Unable to open %s", input);
|
|
goto failed;
|
|
}
|
|
fp = fopen(privkeyfile, "r");
|
|
if (fp == NULL) {
|
|
pjdlog_errno(LOG_ERR, "Unable to open %s", privkeyfile);
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* Obsolescent OpenSSL only knows about /dev/random, and needs to
|
|
* pre-seed before entering cap mode. For whatever reason,
|
|
* RSA_pub_encrypt uses the internal PRNG.
|
|
*/
|
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
{
|
|
unsigned char c[1];
|
|
RAND_bytes(c, 1);
|
|
}
|
|
ERR_load_crypto_strings();
|
|
#else
|
|
OPENSSL_init_crypto(0, NULL);
|
|
#endif
|
|
|
|
caph_cache_catpages();
|
|
if (caph_enter() < 0) {
|
|
pjdlog_errno(LOG_ERR, "Unable to enter capability mode");
|
|
goto failed;
|
|
}
|
|
|
|
privkey = RSA_new();
|
|
if (privkey == NULL) {
|
|
pjdlog_error("Unable to allocate an RSA structure: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
goto failed;
|
|
}
|
|
ctx = EVP_CIPHER_CTX_new();
|
|
if (ctx == NULL)
|
|
goto failed;
|
|
|
|
kdk = read_key(kfd);
|
|
close(kfd);
|
|
if (kdk == NULL)
|
|
goto failed;
|
|
|
|
privkey = PEM_read_RSAPrivateKey(fp, &privkey, NULL, NULL);
|
|
fclose(fp);
|
|
if (privkey == NULL) {
|
|
pjdlog_error("Unable to read data from %s.", privkeyfile);
|
|
goto failed;
|
|
}
|
|
|
|
privkeysize = RSA_size(privkey);
|
|
if (privkeysize != (int)kdk->kdk_encryptedkeysize) {
|
|
pjdlog_error("RSA modulus size mismatch: equals %db and should be %ub.",
|
|
8 * privkeysize, 8 * kdk->kdk_encryptedkeysize);
|
|
goto failed;
|
|
}
|
|
|
|
switch (kdk->kdk_encryption) {
|
|
case KERNELDUMP_ENC_AES_256_CBC:
|
|
cipher = EVP_aes_256_cbc();
|
|
break;
|
|
case KERNELDUMP_ENC_CHACHA20:
|
|
cipher = EVP_chacha20();
|
|
break;
|
|
default:
|
|
pjdlog_error("Invalid encryption algorithm.");
|
|
goto failed;
|
|
}
|
|
|
|
if (RSA_private_decrypt(kdk->kdk_encryptedkeysize,
|
|
kdk->kdk_encryptedkey, key, privkey,
|
|
RSA_PKCS1_OAEP_PADDING) != sizeof(key) &&
|
|
/* Fallback to deprecated, formerly-used PKCS 1.5 padding. */
|
|
RSA_private_decrypt(kdk->kdk_encryptedkeysize,
|
|
kdk->kdk_encryptedkey, key, privkey,
|
|
RSA_PKCS1_PADDING) != sizeof(key)) {
|
|
pjdlog_error("Unable to decrypt key: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
goto failed;
|
|
}
|
|
RSA_free(privkey);
|
|
privkey = NULL;
|
|
|
|
if (kdk->kdk_encryption == KERNELDUMP_ENC_CHACHA20) {
|
|
/*
|
|
* OpenSSL treats the IV as 4 little-endian 32 bit integers.
|
|
*
|
|
* The first two represent a 64-bit counter, where the low half
|
|
* is the first 32-bit word.
|
|
*
|
|
* Start at counter block zero...
|
|
*/
|
|
memset(chachaiv, 0, 4 * 2);
|
|
/*
|
|
* And use the IV specified by the dump.
|
|
*/
|
|
memcpy(&chachaiv[4 * 2], kdk->kdk_iv, 4 * 2);
|
|
EVP_DecryptInit_ex(ctx, cipher, NULL, key, chachaiv);
|
|
} else
|
|
EVP_DecryptInit_ex(ctx, cipher, NULL, key, kdk->kdk_iv);
|
|
EVP_CIPHER_CTX_set_padding(ctx, 0);
|
|
|
|
explicit_bzero(key, sizeof(key));
|
|
|
|
do {
|
|
bytes = read(ifd, buf, sizeof(buf));
|
|
if (bytes < 0) {
|
|
pjdlog_errno(LOG_ERR, "Unable to read data from %s",
|
|
input);
|
|
goto failed;
|
|
}
|
|
|
|
if (bytes > 0) {
|
|
if (EVP_DecryptUpdate(ctx, buf, &olen, buf,
|
|
bytes) == 0) {
|
|
pjdlog_error("Unable to decrypt core.");
|
|
goto failed;
|
|
}
|
|
} else {
|
|
if (EVP_DecryptFinal_ex(ctx, buf, &olen) == 0) {
|
|
pjdlog_error("Unable to decrypt core.");
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
if (olen > 0 && write(ofd, buf, olen) != olen) {
|
|
pjdlog_errno(LOG_ERR, "Unable to write core");
|
|
goto failed;
|
|
}
|
|
} while (bytes > 0);
|
|
|
|
explicit_bzero(buf, sizeof(buf));
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
exit(0);
|
|
failed:
|
|
explicit_bzero(key, sizeof(key));
|
|
explicit_bzero(buf, sizeof(buf));
|
|
RSA_free(privkey);
|
|
if (ctx != NULL)
|
|
EVP_CIPHER_CTX_free(ctx);
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char core[PATH_MAX], encryptedcore[PATH_MAX], keyfile[PATH_MAX];
|
|
const char *crashdir, *dumpnr, *privatekey;
|
|
int ch, debug, error, ofd;
|
|
size_t ii;
|
|
bool force, usesyslog;
|
|
|
|
error = 1;
|
|
|
|
pjdlog_init(PJDLOG_MODE_STD);
|
|
pjdlog_prefix_set("(decryptcore) ");
|
|
|
|
debug = 0;
|
|
*core = '\0';
|
|
crashdir = NULL;
|
|
dumpnr = NULL;
|
|
*encryptedcore = '\0';
|
|
force = false;
|
|
*keyfile = '\0';
|
|
privatekey = NULL;
|
|
usesyslog = false;
|
|
while ((ch = getopt(argc, argv, "Lc:d:e:fk:n:p:v")) != -1) {
|
|
switch (ch) {
|
|
case 'L':
|
|
usesyslog = true;
|
|
break;
|
|
case 'c':
|
|
if (strlcpy(core, optarg, sizeof(core)) >= sizeof(core))
|
|
pjdlog_exitx(1, "Core file path is too long.");
|
|
break;
|
|
case 'd':
|
|
crashdir = optarg;
|
|
break;
|
|
case 'e':
|
|
if (strlcpy(encryptedcore, optarg,
|
|
sizeof(encryptedcore)) >= sizeof(encryptedcore)) {
|
|
pjdlog_exitx(1, "Encrypted core file path is too long.");
|
|
}
|
|
break;
|
|
case 'f':
|
|
force = true;
|
|
break;
|
|
case 'k':
|
|
if (strlcpy(keyfile, optarg, sizeof(keyfile)) >=
|
|
sizeof(keyfile)) {
|
|
pjdlog_exitx(1, "Key file path is too long.");
|
|
}
|
|
break;
|
|
case 'n':
|
|
dumpnr = optarg;
|
|
break;
|
|
case 'p':
|
|
privatekey = optarg;
|
|
break;
|
|
case 'v':
|
|
debug++;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc != 0)
|
|
usage();
|
|
|
|
/* Verify mutually exclusive options. */
|
|
if ((crashdir != NULL || dumpnr != NULL) &&
|
|
(*keyfile != '\0' || *encryptedcore != '\0' || *core != '\0')) {
|
|
usage();
|
|
}
|
|
|
|
/*
|
|
* Set key, encryptedcore and core file names using crashdir and dumpnr.
|
|
*/
|
|
if (dumpnr != NULL) {
|
|
for (ii = 0; ii < strnlen(dumpnr, PATH_MAX); ii++) {
|
|
if (isdigit((int)dumpnr[ii]) == 0)
|
|
usage();
|
|
}
|
|
|
|
if (crashdir == NULL)
|
|
crashdir = DECRYPTCORE_CRASHDIR;
|
|
PJDLOG_VERIFY(snprintf(keyfile, sizeof(keyfile),
|
|
"%s/key.%s", crashdir, dumpnr) > 0);
|
|
PJDLOG_VERIFY(snprintf(core, sizeof(core),
|
|
"%s/vmcore.%s", crashdir, dumpnr) > 0);
|
|
PJDLOG_VERIFY(snprintf(encryptedcore, sizeof(encryptedcore),
|
|
"%s/vmcore_encrypted.%s", crashdir, dumpnr) > 0);
|
|
}
|
|
|
|
if (privatekey == NULL || *keyfile == '\0' || *encryptedcore == '\0' ||
|
|
*core == '\0') {
|
|
usage();
|
|
}
|
|
|
|
if (usesyslog)
|
|
pjdlog_mode_set(PJDLOG_MODE_SYSLOG);
|
|
pjdlog_debug_set(debug);
|
|
|
|
if (force && unlink(core) == -1 && errno != ENOENT) {
|
|
pjdlog_errno(LOG_ERR, "Unable to remove old core");
|
|
goto out;
|
|
}
|
|
ofd = open(core, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
|
if (ofd == -1) {
|
|
pjdlog_errno(LOG_ERR, "Unable to open %s", core);
|
|
goto out;
|
|
}
|
|
|
|
if (!decrypt(ofd, privatekey, keyfile, encryptedcore)) {
|
|
if (unlink(core) == -1 && errno != ENOENT)
|
|
pjdlog_errno(LOG_ERR, "Unable to remove core");
|
|
goto out;
|
|
}
|
|
|
|
error = 0;
|
|
out:
|
|
pjdlog_fini();
|
|
exit(error);
|
|
}
|