summaryrefslogtreecommitdiffstats
path: root/lib/security/openssl.c
diff options
context:
space:
mode:
authorBrett Grandbois <brett.grandbois@opengear.com>2018-05-15 10:55:49 +1000
committerSamuel Mendoza-Jonas <sam@mendozajonas.com>2018-05-30 14:23:39 +1000
commit8b214b9d1c51f49d977e93b66378ed4f73790c8b (patch)
tree94bdcbd19c82e7d45a970e7cf6c80a2b20627f8d /lib/security/openssl.c
parentb1234ac9dd09c9ceaf929c9d4d738fd556525291 (diff)
downloadtalos-petitboot-8b214b9d1c51f49d977e93b66378ed4f73790c8b.tar.gz
talos-petitboot-8b214b9d1c51f49d977e93b66378ed4f73790c8b.zip
lib/security: add in openssl support
Refactor to export a generic API rather than specific gpg_ prefixes by changing gpg.h to security.h and renaming some of the exports. Break out the common and specific functionality into common.c and none.c/gpg.c/openssl.c for no/gpgme/openssl modes respectively. gpgme should work as before OpenSSL support works like this: The pb-lockdown file is a PKCS12 file or X509 certificate or PEM-encoded raw public key. To follow the current conventions the presence of a PKCS12 file as a lockdown signals decrypt mode because of the presence of the private key, anything else signals signature verification mode. The keyring path is currently ignored but in the future could be used to point to an X509 certificate chain for validity checking. Because of this self-signed certificates are currently supported and really just used as a public key container. Signature verification mode supports: * Cryptographic Message Syntax (CMS) as detached S/MIME, this is really more for consistency for the encryption mode (see below). This mode requires the lockdown file to be an X509 certificate. A sample creation command would be: openssl cms -sign -in (infile) -out (outfile) -binary -nocerts \ -inkey (private key) -signer (recipient certificate) * Raw signature digest as output from openssl dgst -sign command. This mode can have the lockdown file be an X509 certificate or a PEM raw public key but the digest algorithm must be pre-defined by the VERIFY_DIGEST configure argument. The default is SHA256. A sample creation command would be: openssl dgst -sign (private key) -out (outfile) -(digest mode) \ (infile) Decryption mode supports: * CMS signed-envelope as attached S/MIME. This is for consistency with the current expectation of no external file for decryption. Some future enhancement could be to come up with some proprietary external file format containing the cipher used, the encrypted cipher key, and the IV (if necessary). A sample creation command would be: openssl cms -sign -in (infile) -signer (recipient certificate) \ -binary -nocerts -nodetach -inkey (private key) | \ openssl cms -encrypt -(cipher mode) -out (outfile) \ (recipient certificate) The PKCS12 file is expecting the private key to have password of NULL or "" as there is currently no mechanism to supply a custom one. Signed-off-by: Brett Grandbois <brett.grandbois@opengear.com> Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Diffstat (limited to 'lib/security/openssl.c')
-rw-r--r--lib/security/openssl.c476
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;
+}
+
OpenPOWER on IntegriCloud