mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-02-28 11:00:49 +01:00
libnm/crypto: add OpenSSL crypto backend
Based-on-patch-by: Joel Holdsworth <joel.holdsworth@vcatechnology.com> https://mail.gnome.org/archives/networkmanager-list/2015-November/msg00062.html
This commit is contained in:
parent
dc26e65928
commit
432f39feaa
4 changed files with 356 additions and 4 deletions
23
Makefile.am
23
Makefile.am
|
|
@ -1548,8 +1548,27 @@ src_libnm_crypto_libnm_crypto_nss_la_LIBADD = \
|
|||
$(NSS_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_CRYPTO_OPENSSL
|
||||
if WITH_OPENSSL
|
||||
libnm_crypto_lib = src/libnm-crypto/libnm-crypto-openssl.la
|
||||
else
|
||||
check_ltlibraries += src/libnm-crypto/libnm-crypto-openssl.la
|
||||
endif
|
||||
|
||||
src_libnm_crypto_libnm_crypto_openssl_la_SOURCES = src/libnm-crypto/nm-crypto-openssl.c
|
||||
src_libnm_crypto_libnm_crypto_openssl_la_CPPFLAGS = \
|
||||
$(src_libnm_core_impl_libnm_core_impl_la_CPPFLAGS) \
|
||||
$(OPENSSL_CFLAGS)
|
||||
src_libnm_crypto_libnm_crypto_openssl_la_LDFLAGS = \
|
||||
$(src_libnm_core_impl_libnm_core_impl_la_LDFLAGS)
|
||||
src_libnm_crypto_libnm_crypto_openssl_la_LIBADD = \
|
||||
$(GLIB_LIBS) \
|
||||
$(OPENSSL_LIBS)
|
||||
endif
|
||||
|
||||
if !WITH_GNUTLS
|
||||
if !WITH_NSS
|
||||
if !WITH_OPENSSL
|
||||
libnm_crypto_lib = src/libnm-crypto/libnm-crypto-null.la
|
||||
else
|
||||
check_ltlibraries += src/libnm-crypto/libnm-crypto-null.la
|
||||
|
|
@ -1557,6 +1576,9 @@ endif
|
|||
else
|
||||
check_ltlibraries += src/libnm-crypto/libnm-crypto-null.la
|
||||
endif
|
||||
else
|
||||
check_ltlibraries += src/libnm-crypto/libnm-crypto-null.la
|
||||
endif
|
||||
|
||||
src_libnm_crypto_libnm_crypto_null_la_SOURCES = src/libnm-crypto/nm-crypto-null.c
|
||||
src_libnm_crypto_libnm_crypto_null_la_CPPFLAGS = \
|
||||
|
|
@ -1576,6 +1598,7 @@ EXTRA_DIST += \
|
|||
src/libnm-crypto/meson.build \
|
||||
src/libnm-crypto/nm-crypto-gnutls.c \
|
||||
src/libnm-crypto/nm-crypto-nss.c \
|
||||
src/libnm-crypto/nm-crypto-openssl.c \
|
||||
$(NULL)
|
||||
|
||||
###############################################################################
|
||||
|
|
|
|||
16
configure.ac
16
configure.ac
|
|
@ -707,7 +707,7 @@ fi
|
|||
AM_CONDITIONAL(WITH_FIREWALLD_ZONE, test "${enable_firewalld_zone}" = "yes")
|
||||
|
||||
PKG_CHECK_MODULES(GNUTLS, [gnutls >= 2.12], [have_crypto_gnutls=yes], [have_crypto_gnutls=no])
|
||||
PKG_CHECK_MODULES(NSS, [nss], [have_crypto_nss=yes], [have_crypto_nss=yes])
|
||||
PKG_CHECK_MODULES(NSS, [nss], [have_crypto_nss=yes], [have_crypto_nss=no])
|
||||
if test "${have_crypto_nss}" = "yes"; then
|
||||
# Work around a pkg-config bug (fdo #29801) where exists != usable
|
||||
FOO=`$PKG_CONFIG --cflags --libs nss`
|
||||
|
|
@ -715,11 +715,14 @@ if test "${have_crypto_nss}" = "yes"; then
|
|||
have_crypto_nss=no
|
||||
fi
|
||||
fi
|
||||
PKG_CHECK_MODULES(OPENSSL, [libcrypto >= 1.0.1a], [have_crypto_openssl=yes], [have_crypto_openssl=no])
|
||||
|
||||
AM_CONDITIONAL(HAVE_CRYPTO_GNUTLS, test "${have_crypto_gnutls}" = 'yes')
|
||||
AM_CONDITIONAL(HAVE_CRYPTO_NSS, test "${have_crypto_nss}" = 'yes')
|
||||
AM_CONDITIONAL(HAVE_CRYPTO_OPENSSL, test "${have_crypto_openssl}" = 'yes')
|
||||
|
||||
AC_ARG_WITH(crypto,
|
||||
AS_HELP_STRING([--with-crypto=nss|gnutls|null],
|
||||
AS_HELP_STRING([--with-crypto=nss|gnutls|openssl|null],
|
||||
[Cryptography library to use for certificate and key operations]),
|
||||
with_crypto=$withval,
|
||||
with_crypto=nss)
|
||||
|
|
@ -731,13 +734,18 @@ elif test "$with_crypto" = 'gnutls'; then
|
|||
if test "${have_crypto_gnutls}" != "yes"; then
|
||||
AC_MSG_ERROR([No usable gnutls found for --with-crypto=gnutls])
|
||||
fi
|
||||
elif test "$with_crypto" = 'openssl'; then
|
||||
if test "${have_crypto_openssl}" != "yes"; then
|
||||
AC_MSG_ERROR([No usable openssl found for --with-crypto=openssl])
|
||||
fi
|
||||
elif test "$with_crypto" = 'null'; then
|
||||
:
|
||||
else
|
||||
AC_MSG_ERROR([Please choose either 'nss', 'gnutls' or 'null' for certificate and crypto operations])
|
||||
AC_MSG_ERROR([Please choose either 'nss', 'gnutls', 'openssl' or 'null' for certificate and crypto operations])
|
||||
fi
|
||||
AM_CONDITIONAL(WITH_NSS, test "$with_crypto" = 'nss')
|
||||
AM_CONDITIONAL(WITH_GNUTLS, test "$with_crypto" = 'gnutls')
|
||||
AM_CONDITIONAL(WITH_OPENSSL, test "$with_crypto" = 'openssl')
|
||||
|
||||
GLIB_MAKEFILE='$(top_srcdir)/Makefile.glib'
|
||||
AC_SUBST(GLIB_MAKEFILE)
|
||||
|
|
@ -1439,7 +1447,7 @@ echo " valgrind: $with_valgrind $with_valgrind_suppressions"
|
|||
echo " code coverage: $enable_code_coverage"
|
||||
echo " LTO: $enable_lto"
|
||||
echo " linker garbage collection: $enable_ld_gc"
|
||||
echo " crypto: $with_crypto (have-gnutls: $have_crypto_gnutls, have-nss: $have_crypto_nss)"
|
||||
echo " crypto: $with_crypto (have-gnutls: $have_crypto_gnutls, have-nss: $have_crypto_nss, have-openssl $have_crypto_openssl)"
|
||||
echo " sanitizers: $sanitizers"
|
||||
echo " Mozilla Public Suffix List: $with_libpsl"
|
||||
echo " eBPF: $have_ebpf"
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ src/libnm-core-impl/nm-vpn-plugin-info.c
|
|||
src/libnm-crypto/nm-crypto-gnutls.c
|
||||
src/libnm-crypto/nm-crypto-nss.c
|
||||
src/libnm-crypto/nm-crypto-null.c
|
||||
src/libnm-crypto/nm-crypto-openssl.c
|
||||
src/libnm-crypto/nm-crypto.c
|
||||
src/libnm-glib-aux/nm-shared-utils.c
|
||||
src/libnm-log-core/nm-logging.c
|
||||
|
|
|
|||
320
src/libnm-crypto/nm-crypto-openssl.c
Normal file
320
src/libnm-crypto/nm-crypto-openssl.c
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Joel Holdsworth <joel.holdsworth@vcatechnology.com>
|
||||
* Copyright (C) 2015 VCA Technology Ltd.
|
||||
*/
|
||||
|
||||
#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
|
||||
|
||||
#include "nm-crypto-impl.h"
|
||||
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
gboolean
|
||||
_nm_crypto_init(GError **error)
|
||||
{
|
||||
static gboolean initialized = FALSE;
|
||||
|
||||
if (initialized)
|
||||
return TRUE;
|
||||
|
||||
CRYPTO_malloc_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
ENGINE_load_builtin_engines();
|
||||
|
||||
initialized = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static const EVP_CIPHER *
|
||||
get_cipher(const char *cipher, GError **error)
|
||||
{
|
||||
if (strcmp(cipher, CIPHER_DES_EDE3_CBC) == 0)
|
||||
return EVP_des_ede3_cbc();
|
||||
else if (strcmp(cipher, CIPHER_DES_CBC) == 0)
|
||||
return EVP_des_cbc();
|
||||
else if (strcmp(cipher, CIPHER_AES_CBC) == 0)
|
||||
return EVP_aes_128_cbc();
|
||||
else {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_UNKNOWN_CIPHER,
|
||||
_("Private key cipher '%s' was unknown."),
|
||||
cipher);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
crypto_decrypt(const char *cipher,
|
||||
int key_type,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
const char *iv,
|
||||
const gsize iv_len,
|
||||
const char *key,
|
||||
const gsize key_len,
|
||||
gsize *out_len,
|
||||
GError **error)
|
||||
{
|
||||
const EVP_CIPHER *evp_cipher = NULL;
|
||||
EVP_CIPHER_CTX ctx;
|
||||
char *output = NULL;
|
||||
gboolean success = FALSE;
|
||||
gsize real_iv_len = 0;
|
||||
int initial_len = 0, final_len = 0;
|
||||
|
||||
if (!(evp_cipher = get_cipher(cipher, error)))
|
||||
return NULL;
|
||||
|
||||
real_iv_len = EVP_CIPHER_iv_length(evp_cipher);
|
||||
if (iv_len < real_iv_len) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_INVALID_DATA,
|
||||
_("Invalid IV length (must be at least %zd)."),
|
||||
real_iv_len);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_init(&ctx);
|
||||
if (!EVP_DecryptInit_ex(&ctx,
|
||||
evp_cipher,
|
||||
NULL,
|
||||
(const unsigned char *) key,
|
||||
(const unsigned char *) iv)) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Failed to initialize the decryption cipher context."));
|
||||
goto out;
|
||||
}
|
||||
|
||||
output = g_malloc0(data_len);
|
||||
|
||||
if (!EVP_DecryptUpdate(&ctx, (unsigned char *) output, &initial_len, data, data_len)) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Failed to decrypt the private key."));
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Finalise decryption, and check the padding */
|
||||
if (!EVP_DecryptFinal_ex(&ctx, (unsigned char *) output + initial_len, &final_len)) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Failed to finalize decryption of the private key."));
|
||||
goto out;
|
||||
}
|
||||
|
||||
*out_len = initial_len + final_len;
|
||||
success = TRUE;
|
||||
|
||||
out:
|
||||
if (!success && output) {
|
||||
/* Don't expose key material */
|
||||
memset(output, 0, data_len);
|
||||
g_free(output);
|
||||
output = NULL;
|
||||
}
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
return output;
|
||||
}
|
||||
|
||||
char *
|
||||
crypto_encrypt(const char *cipher,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
const char *iv,
|
||||
const gsize iv_len,
|
||||
const char *key,
|
||||
gsize key_len,
|
||||
gsize *out_len,
|
||||
GError **error)
|
||||
{
|
||||
const EVP_CIPHER *evp_cipher = NULL;
|
||||
EVP_CIPHER_CTX ctx;
|
||||
char *output = NULL;
|
||||
gboolean success = FALSE;
|
||||
gsize pad_len, output_len;
|
||||
int initial_len = 0, final_len = 0;
|
||||
|
||||
if (!(evp_cipher = get_cipher(cipher, error)))
|
||||
return NULL;
|
||||
|
||||
/* If data_len % ivlen == 0, then we add another complete block
|
||||
* onto the end so that the decrypter knows there's padding.
|
||||
*/
|
||||
pad_len = iv_len - (data_len % iv_len);
|
||||
output_len = data_len + pad_len;
|
||||
output = g_malloc0(output_len);
|
||||
|
||||
EVP_CIPHER_CTX_init(&ctx);
|
||||
if (!EVP_EncryptInit_ex(&ctx,
|
||||
evp_cipher,
|
||||
NULL,
|
||||
(const unsigned char *) key,
|
||||
(const unsigned char *) iv)) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Failed to initialize the encryption cipher context."));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!EVP_EncryptUpdate(&ctx, (unsigned char *) output, &initial_len, data, data_len)) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Failed to encrypt the private key."));
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Finalise encryption, and add the padding */
|
||||
if (!EVP_EncryptFinal_ex(&ctx, (unsigned char *) output + initial_len, &final_len)) {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Failed to finalize encryption of the private key."));
|
||||
goto out;
|
||||
}
|
||||
|
||||
*out_len = initial_len + final_len;
|
||||
success = TRUE;
|
||||
|
||||
out:
|
||||
if (!success && output) {
|
||||
/* Don't expose key material */
|
||||
memset(output, 0, output_len);
|
||||
g_free(output);
|
||||
output = NULL;
|
||||
}
|
||||
EVP_CIPHER_CTX_cleanup(&ctx);
|
||||
return output;
|
||||
}
|
||||
|
||||
NMCryptoFileFormat
|
||||
crypto_verify_cert(const unsigned char *data, gsize len, GError **error)
|
||||
{
|
||||
BIO *in = NULL;
|
||||
X509 *x = NULL;
|
||||
|
||||
/* Try PEM */
|
||||
in = BIO_new_mem_buf((void *) data, len);
|
||||
x = PEM_read_bio_X509_AUX(in, NULL, NULL, NULL);
|
||||
BIO_free(in);
|
||||
X509_free(x);
|
||||
if (x)
|
||||
return NM_CRYPTO_FILE_FORMAT_X509;
|
||||
|
||||
/* Try DER */
|
||||
in = BIO_new_mem_buf((void *) data, len);
|
||||
x = d2i_X509_bio(in, NULL);
|
||||
BIO_free(in);
|
||||
X509_free(x);
|
||||
if (x)
|
||||
return NM_CRYPTO_FILE_FORMAT_X509;
|
||||
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_INVALID_DATA,
|
||||
_("Couldn't decode certificate"));
|
||||
return NM_CRYPTO_FILE_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
gboolean
|
||||
crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error)
|
||||
{
|
||||
BIO *in = NULL;
|
||||
PKCS12 *p12 = NULL;
|
||||
gboolean success = FALSE;
|
||||
|
||||
g_return_val_if_fail(data != NULL, FALSE);
|
||||
|
||||
in = BIO_new_mem_buf((void *) data, data_len);
|
||||
p12 = d2i_PKCS12_bio(in, NULL);
|
||||
BIO_free(in);
|
||||
|
||||
if (!p12) {
|
||||
/* Currently only DER format PKCS12 files are supported. */
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_INVALID_DATA,
|
||||
_("Couldn't decode PKCS#12 file"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (password) {
|
||||
if (!(success = PKCS12_verify_mac(p12, password, -1)))
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_DECRYPTION_FAILED,
|
||||
_("Couldn't verify PKCS#12 file."));
|
||||
} else
|
||||
success = TRUE;
|
||||
|
||||
out:
|
||||
if (p12)
|
||||
PKCS12_free(p12);
|
||||
return success;
|
||||
}
|
||||
|
||||
gboolean
|
||||
crypto_verify_pkcs8(const guint8 *data,
|
||||
gsize data_len,
|
||||
gboolean is_encrypted,
|
||||
const char *password,
|
||||
GError **error)
|
||||
{
|
||||
BIO *in = NULL;
|
||||
X509_SIG *p8 = NULL;
|
||||
PKCS8_PRIV_KEY_INFO *p8inf = NULL;
|
||||
|
||||
g_return_val_if_fail(data != NULL, FALSE);
|
||||
|
||||
if (is_encrypted) {
|
||||
in = BIO_new_mem_buf((void *) data, data_len);
|
||||
p8 = d2i_PKCS8_bio(in, NULL);
|
||||
BIO_free(in);
|
||||
|
||||
if (p8) {
|
||||
X509_SIG_free(p8);
|
||||
return TRUE;
|
||||
} else {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_INVALID_DATA,
|
||||
_("Couldn't decode PKCS#8 file"));
|
||||
}
|
||||
} else {
|
||||
in = BIO_new_mem_buf((void *) data, data_len);
|
||||
p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(in, NULL);
|
||||
BIO_free(in);
|
||||
|
||||
if (p8inf) {
|
||||
PKCS8_PRIV_KEY_INFO_free(p8inf);
|
||||
return p8inf->broken == 0;
|
||||
} else {
|
||||
g_set_error(error,
|
||||
NM_CRYPTO_ERROR,
|
||||
NM_CRYPTO_ERROR_INVALID_DATA,
|
||||
_("Couldn't decode PKCS#8 file"));
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
|
||||
{
|
||||
RAND_bytes(buffer, buffer_len);
|
||||
buffer_len = (buffer_len > 16) ? 16 : buffer_len;
|
||||
return TRUE;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue