diff options
Diffstat (limited to 'lib/security/openssl.c')
-rw-r--r-- | lib/security/openssl.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/lib/security/openssl.c b/lib/security/openssl.c new file mode 100644 index 0000000..03ea332 --- /dev/null +++ b/lib/security/openssl.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2018 Opengear + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include <stdbool.h> +#include <stdlib.h> +#include <assert.h> +#include <dirent.h> +#include <string.h> +#include <fcntl.h> +#include <locale.h> +#include <sys/types.h> + +#include <log/log.h> +#include <file/file.h> +#include <talloc/talloc.h> +#include <url/url.h> +#include <util/util.h> +#include <i18n/i18n.h> + +#include <openssl/conf.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/cms.h> +#include <openssl/pkcs12.h> + +#include "security.h" + +static const EVP_MD *s_verify_md = NULL; + +static __attribute__((constructor)) void crypto_init(void) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OPENSSL_no_config(); + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + ERR_load_CMS_strings(); +#endif + + s_verify_md = EVP_get_digestbyname(VERIFY_DIGEST); + if (!s_verify_md) + pb_log("Specified OpenSSL digest '%s' not found\n", VERIFY_DIGEST); + +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static __attribute__((destructor)) void crypto_fini(void) +{ + EVP_cleanup(); + ERR_free_strings(); +} +#endif + +static int pb_log_print_errors_cb(const char *str, + size_t len __attribute__((unused)), + void *u __attribute__((unused))) +{ + pb_log(" %s\n", str); + return 0; +} + +static int get_pkcs12(FILE *keyfile, X509 **cert, EVP_PKEY **priv) +{ + PKCS12 *p12 = NULL; + int ok = 0; + + rewind(keyfile); + + p12 = d2i_PKCS12_fp(keyfile, NULL); + if (p12) { + /* + * annoying but NULL and "" are two valid but different + * default passwords + */ + if (!PKCS12_parse(p12, NULL, priv, cert, NULL) && + !PKCS12_parse(p12, "", priv, cert, NULL)) { + pb_log("%s: Error parsing OpenSSL PKCS12:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } else + ok = 1; + + PKCS12_free(p12); + } + + return ok; +} + +static X509 *get_cert(FILE *keyfile) +{ + EVP_PKEY *priv = NULL; + X509 *cert = NULL; + + if (get_pkcs12(keyfile, &cert, &priv)) { + EVP_PKEY_free(priv); + } else { + rewind(keyfile); + ERR_clear_error(); + cert = PEM_read_X509(keyfile, NULL, NULL, NULL); + } + + return cert; +} + +static STACK_OF(X509) *get_cert_stack(FILE *keyfile) +{ + STACK_OF(X509) *certs = sk_X509_new_null(); + X509 *cert = NULL; + + if (certs) { + cert = get_cert(keyfile); + if (cert) + sk_X509_push(certs, get_cert(keyfile)); + } else { + pb_log("%s: Error allocating OpenSSL X509 stack:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } + + return certs; +} + + +static EVP_PKEY *get_public_key(FILE *keyfile) +{ + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + + /* + * walk through supported file types looking for a public key: + * + * 1. PKCS12 + * 2. PEM encoded X509 + * 3. PEM encoded raw public key + * + * someday in the future maybe utilize the keyring_path + * as an input for X509_STORE_load_locations for certificate + * validity checking + */ + + cert = get_cert(keyfile); + if (cert) { + pkey = X509_get_pubkey(cert); + X509_free(cert); + } else { + rewind(keyfile); + ERR_clear_error(); + pkey = PEM_read_PUBKEY(keyfile, NULL, NULL, NULL); + } + + /* handles both cases */ + if (!pkey) { + pb_log("%s: Error loading OpenSSL public key:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } + + return pkey; +} + +int decrypt_file(const char *filename, + FILE *authorized_signatures_handle, + const char *keyring_path __attribute__((unused))) +{ + BIO *content_bio = NULL, *file_bio = NULL, *out_bio = NULL; + STACK_OF(X509) *certs = NULL; + CMS_ContentInfo *cms = NULL; + EVP_PKEY *priv = NULL; + X509 *cert = NULL; + int nok = -1; + char *outptr; + long outl; + int bytes; + + if (!get_pkcs12(authorized_signatures_handle, &cert, &priv)) { + pb_log("%s: Error opening OpenSSL decrypt authorization file:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + file_bio = BIO_new_file(filename, "r"); + if (!file_bio) { + pb_log("%s: Error opening OpenSSL decrypt cipher file '%s':\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + out_bio = BIO_new(BIO_s_mem()); + if (!out_bio) { + pb_log("%s: Error allocating OpenSSL decrypt output buffer:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* right now only support signed-envelope CMS */ + + cms = SMIME_read_CMS(file_bio, &content_bio); + if (!cms) { + pb_log("%s: Error parsing OpenSSL CMS decrypt '%s'\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + BIO_free(content_bio); + content_bio = BIO_new(BIO_s_mem()); + if (!content_bio) { + pb_log("%s: Error allocating OpenSSL decrypt content buffer:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + if (!CMS_decrypt(cms, priv, cert, NULL, out_bio, 0)) { + pb_log("%s: Error in OpenSSL CMS decrypt '%s'\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + certs = sk_X509_new_null(); + if (!certs) { + pb_log("%s: Error allocating OpenSSL X509 stack:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + sk_X509_push(certs, cert); + + CMS_ContentInfo_free(cms); + + cms = SMIME_read_CMS(out_bio, &content_bio); + if (!cms) { + pb_log("%s: Error parsing OpenSSL CMS decrypt verify:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* this is a mem BIO so failure is 0 or -1 */ + if (BIO_reset(out_bio) < 1) { + pb_log("%s: Error resetting OpenSSL decrypt output buffer:\n", + __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* in this mode its attached content */ + if (!CMS_verify(cms, certs, NULL, content_bio, out_bio, + CMS_NO_SIGNER_CERT_VERIFY | CMS_BINARY)) { + pb_log("%s: Failed OpenSSL CMS decrypt verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* reopen the file so we force a truncation */ + BIO_free(file_bio); + file_bio = BIO_new_file(filename, "w"); + if (!file_bio) { + pb_log("%s: Error opening OpenSSL decrypt output file '%s'\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + outl = BIO_get_mem_data(out_bio, &outptr); + + while (outl) { + bytes = BIO_write(file_bio, outptr, outl); + if (bytes > 0) { + outl -= (long)bytes; + outptr += bytes; + + } else if (bytes < 0) { + pb_log("%s: OpenSSL decrypt output write failure on file '%s':\n", + __func__, filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + } + + if (!outl) + nok = 0; + +out: + if (cms) + CMS_ContentInfo_free(cms); + BIO_free(file_bio); + BIO_free(content_bio); + BIO_free(out_bio); + X509_free(cert); + sk_X509_free(certs); + EVP_PKEY_free(priv); + return nok; +} + +int verify_file_signature(const char *plaintext_filename, + const char *signature_filename, + FILE *authorized_signatures_handle, + const char *keyring_path __attribute__((unused))) +{ + BIO *signature_bio = NULL, *plaintext_bio = NULL, *content_bio = NULL; + STACK_OF(X509) *certs = NULL; + CMS_ContentInfo *cms = NULL; + ssize_t bytes_read = -1; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + char *sigbuf = NULL; + char rdbuf[8192]; + int nok = -1; + int siglen; + + plaintext_bio = BIO_new_file(plaintext_filename, "r"); + if (!plaintext_bio) { + pb_log("%s: Error opening OpenSSL verify plaintext file '%s'\n", + __func__, plaintext_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + signature_bio = BIO_new_file(signature_filename, "r"); + if (!signature_bio) { + pb_log("%s: Error opening OpenSSL verify signature file '%s'\n", + __func__, signature_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + /* first check CMS */ + cms = SMIME_read_CMS(signature_bio, &content_bio); + if (cms) { + certs = get_cert_stack(authorized_signatures_handle); + + /* + * this has to always be detached, which means we always + * ignore content_bio and we have to set the NO_SIGNER_CERT_VERIFY + * until such time we implement the keyring_path as a X509_STORE + */ + + if (!CMS_verify(cms, certs, NULL, plaintext_bio, NULL, + CMS_DETACHED | CMS_NO_SIGNER_CERT_VERIFY | CMS_BINARY)) { + pb_log("%s: Failed OpenSSL CMS verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + nok = 0; + + } else { + + /* for explicit dgst mode we need an explicit md defined */ + if (!s_verify_md) + goto out; + + ctx = EVP_MD_CTX_create(); + + if (!ctx) { + pb_log("%s: Error allocating OpenSSL MD ctx:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + pkey = get_public_key(authorized_signatures_handle); + if (!pkey) + goto out; + + if (EVP_DigestVerifyInit(ctx, NULL, s_verify_md, NULL, pkey) < 1) { + pb_log("%s: Error initializing OpenSSL verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + + while (bytes_read) { + bytes_read = BIO_read(plaintext_bio, rdbuf, 8192); + if (bytes_read > 0) { + if (EVP_DigestVerifyUpdate(ctx, rdbuf, (size_t)(bytes_read)) < 1) { + pb_log("%s: OpenSSL digest update failure on file '%s':\n", + __func__, plaintext_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + } else if (bytes_read < 0) { + pb_log("%s: OpenSSL read failure on file '%s':\n", + __func__, plaintext_filename); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + goto out; + } + } + + /* + * can't do signature buffer as an update so have to read in whole file + * would be handy if there was some sort of BIO_read_all but there + * doesn't seem to be so rather than reinvent the wheel close it and + * use the existing support + */ + BIO_free(signature_bio); + signature_bio = NULL; + + if (read_file(NULL, signature_filename, &sigbuf, &siglen)) { + pb_log("%s: Error reading OpenSSL signature file '%s'\n", + __func__, signature_filename); + goto out; + } + + if (EVP_DigestVerifyFinal(ctx, (unsigned char*)sigbuf, siglen)) + nok = 0; + else { + pb_log("%s: Error finalizing OpenSSL verify:\n", __func__); + ERR_print_errors_cb(&pb_log_print_errors_cb, NULL); + } + } + +out: + if (cms) + CMS_ContentInfo_free(cms); + talloc_free(sigbuf); + sk_X509_free(certs); + BIO_free(plaintext_bio); + BIO_free(signature_bio); + BIO_free(content_bio); + EVP_PKEY_free(pkey); + EVP_MD_CTX_destroy(ctx); + return nok; +} + +int lockdown_status(void) +{ + /* + * if it's a PKCS12 then we're in decrypt mode since we have the + * private key, otherwise it's sign mode + * + * someday add in support for runtime determination based on what + * files come back in the async sig file load? + */ + FILE *authorized_signatures_handle = NULL; + int ret = PB_LOCKDOWN_SIGN; + PKCS12 *p12 = NULL; + + if (access(LOCKDOWN_FILE, F_OK) == -1) + return PB_LOCKDOWN_NONE; + + /* determine lockdown type */ + + authorized_signatures_handle = fopen(LOCKDOWN_FILE, "r"); + if (authorized_signatures_handle) { + p12 = d2i_PKCS12_fp(authorized_signatures_handle, NULL); + if (p12) { + ret = PB_LOCKDOWN_DECRYPT; + PKCS12_free(p12); + } + fclose(authorized_signatures_handle); + } + + return ret; +} + |