From f145ee0d691d476e6f2ab332cac14e54a3496919 Mon Sep 17 00:00:00 2001 From: Thierry Bordaz Date: Dec 19 2019 14:29:22 +0000 Subject: Ticket 50794: [RFE] Use keyring to manage starting password/pin Bug Description: Currently an instance with security enabled can be automatically restarted only if it exists a pin.txt file. This RFE is to allow to restart a without pin.txt and without prompting the NSS password Fix Description: See http://www.port389.org/docs/389ds/design/protect-NSS-db.html https://pagure.io/389-ds-base/issue/50794 Reviewed by: ? Platforms tested: F30 Flag Day: no Doc impact: no --- diff --git a/Makefile.am b/Makefile.am index 026687e..acb053c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -25,6 +25,7 @@ TSAN_CFLAGS = @tsan_cflags@ UBSAN_CFLAGS = @ubsan_cflags@ SYSTEMD_DEFINES = @systemd_defs@ +KEYRING_DEFINES = @keyring_defs@ CMOCKA_INCLUDES = $(CMOCKA_CFLAGS) @@ -147,7 +148,7 @@ AM_CFLAGS = $(DEBUG_CFLAGS) $(GCCSEC_CFLAGS) $(ASAN_CFLAGS) $(MSAN_CFLAGS) $(TSA AM_CXXFLAGS = $(DEBUG_CXXFLAGS) $(GCCSEC_CFLAGS) $(ASAN_CFLAGS) $(MSAN_CFLAGS) $(TSAN_CFLAGS) $(UBSAN_CFLAGS) # Flags for Directory Server # WARNING: This needs a clean up, because slap.h is a horrible mess and is publically exposed! -DSPLUGIN_CPPFLAGS = $(DS_DEFINES) $(DS_INCLUDES) $(PATH_DEFINES) $(SYSTEMD_DEFINES) @openldap_inc@ $(NSS_CFLAGS) $(NSPR_INCLUDES) $(SYSTEMD_CFLAGS) +DSPLUGIN_CPPFLAGS = $(DS_DEFINES) $(DS_INCLUDES) $(PATH_DEFINES) $(SYSTEMD_DEFINES) $(KEYRING_DEFINES) @openldap_inc@ $(NSS_CFLAGS) $(NSPR_INCLUDES) $(SYSTEMD_CFLAGS) # This should give access to internal headers only for tests!!! DSINTERNAL_CPPFLAGS = -I$(srcdir)/include/ldaputil # Flags for Datastructure Library @@ -161,6 +162,7 @@ PROFILING_LINKS = @profiling_links@ NSPR_LINK = $(NSPR_LIBS) NSS_LINK = $(NSS_LIBS) +SVRCORE_LINK = @keyring_links@ # with recent versions of openldap - if you link with both ldap_r and ldap, the # shared lib _fini for one will stomp on the other, and the program will crash @@ -831,7 +833,8 @@ endif # For scripts that are "as is". dist_bin_SCRIPTS = ldap/admin/src/scripts/ds-replcheck \ - ldap/admin/src/scripts/ds-logpipe.py + ldap/admin/src/scripts/ds-logpipe.py \ + ldap/admin/src/scripts/ds-keyring.py dist_bin_SCRIPTS += ldap/admin/src/logconv.pl @@ -1162,11 +1165,13 @@ libsvrcore_la_SOURCES = \ src/svrcore/src/std.c \ src/svrcore/src/systemd-ask-pass.c \ src/svrcore/src/std-systemd.c \ + src/svrcore/src/keyring-ask-pass.c \ + src/svrcore/src/std-keyring.c \ src/svrcore/src/user.c libsvrcore_la_LDFLAGS = $(AM_LDFLAGS) libsvrcore_la_CPPFLAGS = $(AM_CPPFLAGS) $(SVRCORE_INCLUDES) $(DSPLUGIN_CPPFLAGS) -libsvrcore_la_LIBADD = $(NSS_LINK) $(NSPR_LINK) +libsvrcore_la_LIBADD = $(NSS_LINK) $(NSPR_LINK) $(SVRCORE_LINK) #------------------------ # libsds @@ -2150,7 +2155,7 @@ ns_slapd_SOURCES = ldap/servers/slapd/abandon.c \ ns_slapd_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS) $(SASL_CFLAGS) $(SVRCORE_INCLUDES) ns_slapd_LDADD = libslapd.la libldaputil.la libsvrcore.la $(LDAPSDK_LINK) $(NSS_LINK) $(LIBADD_DL) \ - $(NSPR_LINK) $(SASL_LINK) $(LIBNSL) $(LIBSOCKET) $(THREADLIB) $(SYSTEMD_LIBS) $(EVENT_LINK) + $(NSPR_LINK) $(SASL_LINK) $(LIBNSL) $(LIBSOCKET) $(THREADLIB) $(SYSTEMD_LIBS) $(EVENT_LINK) $(SVRCORE_LINK) ns_slapd_DEPENDENCIES = libslapd.la libldaputil.la # We need to link ns-slapd with the C++ compiler on HP-UX since we load # some C++ shared libraries (such as icu). @@ -2316,6 +2321,7 @@ fixupcmd = sed \ -e 's,@with_fhs_opt\@,@with_fhs_opt@,g' \ -e 's,@with_selinux\@,@with_selinux@,g' \ -e 's,@with_systemd\@,$(WITH_SYSTEMD),g' \ + -e 's,@with_keyring\@,$(WITH_KEYRING),g' \ -e 's,@tmpfiles_d\@,$(tmpfiles_d),g' \ -e 's,@perlexec\@,@perlexec@,g' \ -e 's,@pythonexec\@,@pythonexec@,g' \ diff --git a/configure.ac b/configure.ac index 982bae8..e98d608 100644 --- a/configure.ac +++ b/configure.ac @@ -253,6 +253,22 @@ fi AC_SUBST([profiling_defs]) AC_SUBST([profiling_links]) +# Pull in profiling. +AC_MSG_CHECKING(for --disable-keyring) +AC_ARG_ENABLE(keyring, AS_HELP_STRING([--disable-keyring], [Disable keyring storage for sensitive data (default: no)]), + [], [ disable_keyring=no ]) +AC_MSG_RESULT($disable_keyring) +if test "$disable_keyring" = yes ; then + keyring_links="" + keyring_defs="" +else + AC_DEFINE([WITH_KEYRING], [1], [build svrcore to use keyring]) + keyring_links="-lkeyutils" + keyring_defs="-DWITH_KEYRING" +fi +AC_SUBST([keyring_links]) +AC_SUBST([keyring_defs]) + # these enables are for optional or experimental features AC_MSG_CHECKING(for --enable-pam-passthru) AC_ARG_ENABLE(pam-passthru, diff --git a/ldap/admin/src/scripts/ds-keyring.py b/ldap/admin/src/scripts/ds-keyring.py new file mode 100755 index 0000000..db6cfe3 --- /dev/null +++ b/ldap/admin/src/scripts/ds-keyring.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 + +# Authors: +# Dinesh Prasanth M K +# Thierry Bordaz for RHDS adaptations +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License or +# (at your option) any later version. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright (C) 2019 Red Hat, Inc. +# All rights reserved. + + +""" +Script that prompts user for password during server startup +and store them on user's keyring +""" + +import getopt +import logging +import os +import subprocess +import sys +import re +import pdb + +from lib389.keyring import Keyring + +logger = logging.getLogger(__name__) + +logging.basicConfig(format='%(levelname)s: %(message)s') + +# Create a instance of Keyring +keyring = Keyring() + + +def split_entries(entry): + return entry.split(',') + + +def print_help(): + print('Usage: ds-keyring []') + print() + print(' Instance config file (i.e. dse.ldif)') + print(' Action to perform') + print() + print(' ds-keyring commands:') + print(' --get_key Get value stored in keyring') + print(' --set_key Set value stored in keyring') + print(' --clear Clear values stored in keyring') + print(' --help Show help message') + print() + + +try: + opts, _ = getopt.getopt(sys.argv[1:], 's:g:ch', ['set_key=', 'get_key=', 'clear', 'help']) + +except getopt.GetoptError as e: + logger.error(e) + print_help() + sys.exit(1) + +for o, a in opts: + + if o in ('-c', '--clear'): + keyring.clear_keyring() + sys.exit() + + elif o in ('-h', '--help'): + print_help() + sys.exit() + + elif o in ('-s', '--set_key'): + obj = re.compile('^.*/slapd-(.*)/.*$') + instance_name = obj.sub("\\1", a) + if not os.path.exists(a): + print('RHDS: dse.ldif does not exist for %s ' % instance_name) + sys.exit() + + # Based on nsslapd-security parameter ('on', 'off', default 'off') + # If security is disabled, no need to care about NSS password + security_enabled = False + with open(a) as f: + datafile = f.readlines() + for line in datafile: + if 'nsslapd-security' in line.lower(): + security_val = line.split(':')[1].lower() + security_val = re.sub(' +','', security_val) + security_val = re.sub('\n','', security_val) + if security_val == 'on': + security_enabled = True + break + + if not security_enabled: + print('RHDS (%s): NSS password ignored - security is off' % instance_name) + sys.exit() + + # Security is enabled + # + # If it exists a pin.txt file, this is fine. Admin accepts to use a clear text + # password stored in a local file. + # + # If not, then prompt the NSS password unless it already exist one in keyring + token = "Internal (Software) Token" + + pin_file = "/etc/dirsrv/slapd-%s/pin.txt" % instance_name + if os.path.exists(pin_file): + print('RHDS (%s): Keyring is not used because it exists pin.txt' % instance_name) + pass + else: + # A RHDS key description looks like "Internal (Software) Token:master1:password" + key_name = "%s:%s:password" % (token, instance_name) + try: + key_id = keyring.get_key_id(key_name=key_name) + print('RHDS (%s): Initialization of NSS using a password stored in keyring' % instance_name) + except subprocess.CalledProcessError: + prompt = '[' + instance_name + '] Please provide the password for cert/key Database:' + cmd_ask_password = ['systemd-ask-password', prompt] + entered_pass = subprocess.check_output(cmd_ask_password) + + keyring.put_password(key_name=key_name, password=entered_pass) + print('RHDS (%s): Prompted NSS password is now stored in keyring' % instance_name) + pass + sys.exit() + + elif o in ('-g', '--get_key'): + obj = re.compile('^.*/slapd-(.*)/.*$') + instance_name = obj.sub("\\1", a) + token = "Internal (Software) Token" + + # A RHDS key description looks like "Internal (Software) Token:master1:password" + key_name = "%s:%s:password" % (token, instance_name) + try: + key_id = keyring.get_key_id(key_name=key_name) + print('RHDS (%s): key desc %s ==> key ID %s' % (instance_name, key_name, key_id)) + except subprocess.CalledProcessError: + print("RHDS (%s): key desc \"%s\" does not exist" % key_name) + sys.exit() + + else: + logger.error('option %s not recognized', o) + print_help() + sys.exit(1) diff --git a/ldap/servers/slapd/detach.c b/ldap/servers/slapd/detach.c index d5c95a0..781d242 100644 --- a/ldap/servers/slapd/detach.c +++ b/ldap/servers/slapd/detach.c @@ -121,7 +121,7 @@ set_workingdir(void) } int -detach(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info) +detach(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info, char *nss_default_slot_token, char *keyring_nss_password) { int i, sd; @@ -150,7 +150,7 @@ detach(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t * vattr_global_lock_create(); /* call this right after the fork, but before closing stdin */ - if (slapd_do_all_nss_ssl_init(slapd_exemode, importexport_encrypt, s_port, ports_info)) { + if (slapd_do_all_nss_ssl_init(slapd_exemode, importexport_encrypt, s_port, ports_info, nss_default_slot_token, keyring_nss_password)) { return 1; } @@ -183,7 +183,7 @@ detach(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t * */ vattr_global_lock_create(); - if (slapd_do_all_nss_ssl_init(slapd_exemode, importexport_encrypt, s_port, ports_info)) { + if (slapd_do_all_nss_ssl_init(slapd_exemode, importexport_encrypt, s_port, ports_info, nss_default_slot_token, keyring_nss_password)) { return 1; } if (set_workingdir()) { diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c index bebd3cf..ec6a4fa 100644 --- a/ldap/servers/slapd/main.c +++ b/ldap/servers/slapd/main.c @@ -50,6 +50,7 @@ union semun #include #include #endif +#define NEED_TOK_PBE /* see slap.h - defines tokPBE and ptokPBE */ #include "slap.h" #include "slapi-plugin.h" #include "prinit.h" @@ -59,6 +60,7 @@ union semun #include "getopt_ext.h" #include "fe.h" #include +#include #ifdef LINUX /* For mallopt. Should be removed soon. */ @@ -508,11 +510,129 @@ write_start_pid_file(void) return -1; } +static int +keyring_get_password(char *configdir, char **nss_default_slot_token, char **keyring_nss_password) +{ + char pin_data[4096] = {0}; + int ret = -1; /* by default it fails */ + long keyid; + char *token = NULL; + char *serverId = NULL; + char key_type[] = "password"; + char *keyring_key = NULL; /* concatenate of 'token:serverId:key_type */ + + if (configdir == NULL) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "No configdir\n"); + goto cleanup; + } + + if (keyring_nss_password == NULL) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "not possible to store retrieved password\n"); + goto cleanup; + } + *keyring_nss_password = NULL; + + if (nss_default_slot_token == NULL) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "not possible to store NSS default slot token\n"); + goto cleanup; + } + *nss_default_slot_token = NULL; + + /* Retrieve the default slot token: Internal (Software) Token */ + token = slapi_ch_strdup(ptokPBE); + if (token == NULL) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "not possible to copy NSS default slot token: %s\n", ptokPBE); + goto cleanup; + } + /* ptokPBE contains tailing ' ' that we need to remove */ + for (int i = (strlen(token) - 1); token[i] == ' '; i--) token[i] = '\0'; + + /* retrieve the serverId: /etc/dirsrv/slapd- */ + serverId = PL_strstr(configdir, "slapd-"); + if (serverId) { + serverId = slapi_ch_strdup(serverId + PL_strlen("slapd-")); + } else { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "not possible to retrieve serverID from %s (missing \"slapd-\")\n", + configdir); + goto cleanup; + } + + /* + * Keyring stores key/pin pairs + * + * The format of the key is :: + * for example: + * : Internal (Software) Token + * : master1 + * : password + * + * svrcore is the framework used by DS to get the NSS password. So the pin retrieved + * from keyring is stored in a svrcore plugin: keyring plugin. + * + * In svrcore the pin is stored/retrieved using the token of the NSS db default slot: 'Internal (Software) Token'. + * The NSS db default slot is created with this token (ptokPBE) during slapd_nss_init. + * When authenticating to NSS db (slapd_pk11_authenticate) it lookup the default slot + * and use the same token (ptokPBE) (without tailing spaces), to retrieve the pin (getPin svrcore) + */ + keyring_key = slapi_ch_smprintf("%s:%s:%s", token, serverId, key_type); + if (keyring_key == NULL) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "Fail to create the keyring key (\"%s:%s:%s\")\n", + token, serverId, key_type); + goto cleanup; + } + + /* retrieve the keyid from the key description + * there is no destination else it requires a Selinux + * module to grant Write right + */ + keyid = keyctl_search(KEY_SPEC_USER_KEYRING, "user", keyring_key, 0 /* no destination */); + if (keyid != -1) { + long rc; + + /* selinux permissive => EACCES */ + rc = keyctl_read((key_serial_t) keyid, pin_data, sizeof (pin_data)); + + if (rc <= 0) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "Fail to read NSS database password pin_data=%s (%ld)\n", pin_data, rc); + } else if (rc > 0) { + slapi_log_err(SLAPI_LOG_ERR, "keyring_get_password", "pin_data=%s (%ld)\n", pin_data, rc); + pin_data[rc] = '\0'; + if (pin_data[rc - 1] == '\n') { + /* in case of systemd-tty-ask-password-agent + * the returned string ends with '\n' + */ + pin_data[rc - 1] = '\0'; + } + *nss_default_slot_token = token; + *keyring_nss_password = slapi_ch_strdup(pin_data); + ret = 0; + } + } else { + slapi_log_err(SLAPI_LOG_INFO, "keyring_get_password", "Keyring does not contain NSS database password or access denied (rc=%ld)\n", keyid); + } + +cleanup: + if (token && ret) { + /* in case of failure (ret==-1) the token + * is NOT referenced by nss_default_slot_token. It can be freed + */ + slapi_ch_free((void **) &token); + } + if (serverId) slapi_ch_free((void **) &serverId); + if (keyring_key) slapi_ch_free((void **) &keyring_key); + + return ret; +} + int main(int argc, char **argv) { int return_value = 0; struct main_config mcfg = {0}; + char *nss_default_slot_token = NULL; /* contains the "Internal (Software) Token" */ + char *keyring_nss_password = NULL; /* contains the password of the NSS db + * it is retrieved from keyring before detach + */ /* Set a number of defaults */ mcfg.slapd_exemode = SLAPD_EXEMODE_UNKNOWN; @@ -786,6 +906,16 @@ main(int argc, char **argv) } } + /* check keyring only in case security was enabled */ + if (slapdFrontendConfig->security) { + /* nss_default_slot_token and keyring_nss_password are retrieved before detach + * Then the are transmitted to the deamon that will use them to initialize security + */ + if (keyring_get_password(slapdFrontendConfig->configdir, &nss_default_slot_token, &keyring_nss_password)) { + slapi_log_err(SLAPI_LOG_INFO, "main", "Password of NSS database not retrieved from keyring\n"); + } + } + /* Now, sockets are open, so we can safely change identity now */ return_value = main_setuid(slapdFrontendConfig->localuser); if (0 != return_value) { @@ -793,7 +923,6 @@ main(int argc, char **argv) slapdFrontendConfig->localuser); exit(1); } - /* * Detach ourselves from the terminal (unless running in debug mode). * We must detach before we start any threads since detach forks() on @@ -801,7 +930,7 @@ main(int argc, char **argv) * Have to detach after ssl_init - the user may be prompted for the PIN * on the terminal, so it must be open. */ - if (detach(mcfg.slapd_exemode, mcfg.importexport_encrypt, mcfg.s_port, &ports_info)) { + if (detach(mcfg.slapd_exemode, mcfg.importexport_encrypt, mcfg.s_port, &ports_info, nss_default_slot_token, keyring_nss_password)) { return_value = 1; goto cleanup; } @@ -2841,7 +2970,7 @@ force_to_disable_security(const char *what, int *init_ssl, daemon_ports_t *ports modes (or just not detaching). */ int -slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info) +slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info, char *nss_default_slot_token, char *keyring_nss_password) { /* * Initialise NSS once for the whole slapd process, whether SSL @@ -2860,7 +2989,9 @@ slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_por * modules can assume NSS is available */ if (slapd_nss_init((slapd_exemode == SLAPD_EXEMODE_SLAPD), - (slapd_exemode != SLAPD_EXEMODE_REFERRAL) /* have config? */)) { + (slapd_exemode != SLAPD_EXEMODE_REFERRAL) /* have config? */, + nss_default_slot_token, + keyring_nss_password)) { if (force_to_disable_security("NSS", &init_ssl, ports_info)) { return 1; } diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h index 75259e4..1627e74 100644 --- a/ldap/servers/slapd/proto-slap.h +++ b/ldap/servers/slapd/proto-slap.h @@ -629,7 +629,7 @@ void do_delete(Slapi_PBlock *pb); /* * detach.c */ -int detach(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info); +int detach(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info, char *nss_default_slot_token, char *keyring_nss_password); void close_all_files(void); void raise_process_limits(void); @@ -1088,7 +1088,7 @@ void do_search(Slapi_PBlock *pb); /* * ssl.c */ -int slapd_nss_init(int init_ssl, int config_available); +int slapd_nss_init(int init_ssl, int config_available, char *nss_default_slot_token, char *keyring_nss_password); int slapd_ssl_init(void); int slapd_ssl_init2(PRFileDesc **fd, int startTLS); int slapd_security_library_is_initialized(void); @@ -1503,7 +1503,7 @@ int32_t disk_get_info(char *dir, uint64_t *total_space, uint64_t *avail_space, u #if defined(hpux) void signal2sigaction(int s, void *a); #endif -int slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info); +int slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_port, daemon_ports_t *ports_info, char *nss_default_slot_token, char *keyring_nss_password); /* * pagedresults.c diff --git a/ldap/servers/slapd/ssl.c b/ldap/servers/slapd/ssl.c index 6a07f1a..69ad198 100644 --- a/ldap/servers/slapd/ssl.c +++ b/ldap/servers/slapd/ssl.c @@ -62,6 +62,16 @@ static SSLVersionRange slapdNSSVersions; and use the same naming scheme */ static char *dongle_file_name = NULL; +static char *nss_token = NULL; /* contains the "Internal (Software) Token" + * the value is set before detach() + * to be used by the daemon to initialize security + */ +static char *nss_password = NULL; /* contains the password of the NSS db that was + * stored in keyring. + * It was set before detach() (because of + * because of keyring access) to be used by the + * daemon to initialized security + */ static int _security_library_initialized = 0; static int _ssl_listener_initialized = 0; @@ -925,7 +935,7 @@ slapi_getSSLVersion_str(PRUint16 vnum, char *buf, size_t bufsize) * we do not need a prefix any more. */ int -slapd_nss_init(int init_ssl __attribute__((unused)), int config_available __attribute__((unused))) +slapd_nss_init(int init_ssl __attribute__((unused)), int config_available __attribute__((unused)), char *nss_default_slot_token, char *keyring_nss_password) { SECStatus secStatus; PRErrorCode errorCode; @@ -987,6 +997,12 @@ slapd_nss_init(int init_ssl __attribute__((unused)), int config_available __attr dongle_file_name = slapi_ch_smprintf("%s/pin.txt", certdir); + /* it consums the string */ + nss_token = nss_default_slot_token; + + /* it consums the string */ + nss_password = keyring_nss_password; + if (secStatus != SECSuccess) { errorCode = PR_GetError(); slapd_SSL_error("NSS initialization failed (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s): " @@ -1068,12 +1084,27 @@ slapd_nss_init(int init_ssl __attribute__((unused)), int config_available __attr return rv; } +/* Function used by svrcore plugins to log messages in DS logs */ +static void +svrcore_log_err(char *fmt, char *arg1, char *arg2, char *arg3) +{ + slapi_log_err(SLAPI_LOG_ERR, "Security Initialization", + fmt, + arg1 ? arg1 : "", + arg2 ? arg2 : "", + arg3 ? arg3 : ""); +} + static int svrcore_setup(void) { PRErrorCode errorCode; int rv = 0; -#ifdef WITH_SYSTEMD +#ifdef WITH_KEYRING + SVRCOREStdKeyringPinObj *StdPinObj; + StdPinObj = (SVRCOREStdKeyringPinObj *)SVRCORE_GetRegisteredPinObj(); + +#elif WITH_SYSTEMD SVRCOREStdSystemdPinObj *StdPinObj; StdPinObj = (SVRCOREStdSystemdPinObj *)SVRCORE_GetRegisteredPinObj(); #else @@ -1084,11 +1115,13 @@ svrcore_setup(void) if (StdPinObj) { return 0; /* already registered */ } -#ifdef WITH_SYSTEMD +#ifdef WITH_KEYRING + if (SVRCORE_CreateStdKeyringPinObj(&StdPinObj, dongle_file_name, nss_token, nss_password, PR_TRUE, PR_TRUE, PR_TRUE /* Keyring */, 90, svrcore_log_err) != SVRCORE_Success) { +#elif WITH_SYSTEMD if (SVRCORE_CreateStdSystemdPinObj(&StdPinObj, dongle_file_name, PR_TRUE, PR_TRUE, 90) != SVRCORE_Success) { #else if (SVRCORE_CreateStdPinObj(&StdPinObj, dongle_file_name, PR_TRUE) != SVRCORE_Success) { -#endif +#endif /* WITH_KEYRING */ errorCode = PR_GetError(); slapd_SSL_warn("Unable to create PinObj (" SLAPI_COMPONENT_NAME_NSPR " error %d - %s)", errorCode, slapd_pr_strerror(errorCode)); diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in index 481d349..14a38d3 100644 --- a/rpm/389-ds-base.spec.in +++ b/rpm/389-ds-base.spec.in @@ -607,6 +607,7 @@ exit 0 %{_mandir}/man1/dbscan.1.gz %{_bindir}/ds-replcheck %{_mandir}/man1/ds-replcheck.1.gz +%{_bindir}/ds-keyring.py %{_bindir}/ds-logpipe.py %{_mandir}/man1/ds-logpipe.py.1.gz %{_bindir}/ldclt diff --git a/src/lib389/lib389/keyring.py b/src/lib389/lib389/keyring.py new file mode 100644 index 0000000..3c3bedf --- /dev/null +++ b/src/lib389/lib389/keyring.py @@ -0,0 +1,108 @@ +# Authors: +# Dinesh Prasanth M K +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License or +# (at your option) any later version. +# +# 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright (C) 2019 Red Hat, Inc. +# All rights reserved. +# + + +""" +Module that provides interface to access Kernel Keyring +""" +import subprocess + + +class Keyring(object): + """ + Utility class to deal with keyrings. This class is a simple wrapper + against the `keyutils` package. Defaults to user keyring `@u` and key + type `user` + """ + + def __init__(self, keyring='@u', key_type='user'): + self.keyring = keyring + self.key_type = key_type + + def put_password(self, key_name, password): + """ + Save a password to the keyring + + :param key_name: Name of they key + :type key_name: str + :param password: Password value to be stored + :type password: bytearray + :return: Key ID, Error (if any) + :rtype: (bytearray, bytearray) + """ + + cmd = ['keyctl', 'padd', self.key_type, key_name, self.keyring] + + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + + return p.communicate(input=password) + + def get_key_id(self, key_name): + """ + Retrieve key ID from the provided key name + + :param key_name: Name of the key to search + :type key_name: str + :return: key_ID + :rtype: int + """ + + cmd = ['keyctl', 'search', self.keyring, self.key_type, key_name] + + return subprocess.check_output(cmd).decode('utf-8').strip() + + def get_password(self, key_name, output_format='raw'): + """ + Retrieve password in the given format + + :param key_name: The value of the key to be retrieved + :type key_name: str + :param output_format: Retrieval format: hex or raw (default) + :type output_format: str + :return: Value of the key in specified format + :rtype: str + """ + + if output_format.lower() == 'raw': + mode = 'pipe' + elif output_format.lower() == 'hex': + mode = 'read' + else: + raise AttributeError('output_format must be one of [\'raw\', \'hex\'].') + + key_id = self.get_key_id(key_name) + + cmd = ['keyctl', mode, key_id] + + return subprocess.check_output(cmd).decode('utf-8') + + def clear_keyring(self): + """ + Clear the default keyring + + :return: Return code + :rtype: int + """ + cmd = ['keyctl', 'clear', self.keyring] + return subprocess.check_call(cmd) diff --git a/src/svrcore/src/Makefile.am b/src/svrcore/src/Makefile.am index f74dfab..1d03396 100644 --- a/src/svrcore/src/Makefile.am +++ b/src/svrcore/src/Makefile.am @@ -24,7 +24,9 @@ libsvrcore_la_SOURCES = alt.c \ std.c \ systemd-ask-pass.c \ std-systemd.c \ + keyring-ask-pass.c \ + std-keyring.c \ user.c -libsvrcore_la_LDFLAGS = -version-info 0:0:0 @NSS_LIBS@ @NSPR_LIBS@ +libsvrcore_la_LDFLAGS = -version-info 0:0:0 @NSS_LIBS@ @NSPR_LIBS@ -lkeyutils libsvrcore_la_CFLAGS = @NSS_CFLAGS@ @NSPR_CFLAGS@ @SYSTEMD_CFLAGS@ diff --git a/src/svrcore/src/keyring-ask-pass.c b/src/svrcore/src/keyring-ask-pass.c new file mode 100644 index 0000000..084c6e3 --- /dev/null +++ b/src/svrcore/src/keyring-ask-pass.c @@ -0,0 +1,166 @@ +/* + * Copyright (C) 1998 Netscape Communications Corporation. + * All Rights Reserved. + * + * Copyright 2016 Red Hat, Inc. and/or its affiliates. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * systemd-ask-pass.c - SVRCORE module for reading the PIN from systemd integrations. + */ + +#if HAVE_CONFIG_H +#include +#endif + +/* For socket.h to work correct, we need to define __USE_GNU */ +#define _GNU_SOURCE 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define PASS_MAX 256 * sizeof(char) +#define NSEC_PER_USEC ((uint64_t) 1000ULL) +#define USEC_PER_SEC ((uint64_t) 1000000ULL) + +struct SVRCOREKeyringPinObj +{ + SVRCOREPinObj base; + char *nss_token; + char *pin; + svrcore_log_err_t log_callback; + uint64_t timeout; +}; +static const struct SVRCOREPinMethods vtable; + +SVRCOREError +SVRCORE_CreateKeyringPinObj(SVRCOREKeyringPinObj **out, char *nss_token, char *nss_password, uint64_t timeout, svrcore_log_err_t log_callback) +{ +#ifdef WITH_KEYRING + + SVRCOREError err = SVRCORE_Success; + SVRCOREKeyringPinObj *obj = NULL; + + do { // This is used like a "with" statement, to avoid a goto. + obj = (SVRCOREKeyringPinObj *)malloc(sizeof(SVRCOREKeyringPinObj)); + if (obj == NULL) { + err = SVRCORE_NoMemory_Error; + break; + } + + obj->base.methods = &vtable; + obj->nss_token = strdup(nss_token); + obj->pin = strdup(nss_password); + obj->log_callback = log_callback; + if (timeout == 0) { + obj->timeout = 90; + } else { + obj->timeout = timeout; + } + + } while (0); + + // If error, destrop it, and return err + if (err != SVRCORE_Success) { + SVRCORE_DestroyKeyringPinObj(obj); + obj = NULL; + } + + *out = obj; + return err; +#else // keyring + return SVRCORE_MissingFeature; +#endif // keyring +} + +static char * +getPin(SVRCOREPinObj *obj, const char *token, PRBool retry) +{ + char *pin = NULL; + SVRCOREKeyringPinObj *kobj = (SVRCOREKeyringPinObj *) obj; + int max_retry = 10; /* used to validate the retrieved password */ + + +#ifdef WITH_KEYRING + if (!strcmp(token, kobj->nss_token)) { + pin = strdup(kobj->pin); + } + + /* Attempt to create a Pin Storage object. This checks the + * password. + */ + do { + SVRCOREPk11PinStore *store; + SVRCOREError err; + + err = SVRCORE_CreatePk11PinStore(&store, token, pin); + SVRCORE_DestroyPk11PinStore(store); + + if (err == SVRCORE_Success) { + break; + } else if (err == SVRCORE_IncorrectPassword_Error) { + /* retry*/ + max_retry--; + } else { + break; + } + + } while(max_retry); + + if ((max_retry == 0) && kobj->log_callback) { + /* The password retrieved from keyring is invalid + * It should be changed + */ + kobj->log_callback("Keyring fails to check the NSS password: \"%s\"\n", + pin, 0, 0); + kobj->log_callback("You may try: keyctl search @u user \"%s\" --> \n", + token, 0, 0); + kobj->log_callback("Then: keyctl unlink @u\n", + 0, 0, 0); + pin = NULL; + } + + return pin; +#else // Keyring + return NULL; +#endif // Keyring +} + +void +SVRCORE_DestroyKeyringPinObj(SVRCOREKeyringPinObj *obj) +{ +#ifdef WITH_KEYRING + if (obj) { + if (obj->nss_token) free(obj->nss_token); + if (obj->pin) free(obj->pin); + free(obj); + } +#endif // Keyring +} + +static void +destroyObject(SVRCOREPinObj *obj) +{ + SVRCORE_DestroyKeyringPinObj((SVRCOREKeyringPinObj*)obj); +} + +static const SVRCOREPinMethods vtable = +{ 0, 0, destroyObject, getPin }; + diff --git a/src/svrcore/src/std-keyring.c b/src/svrcore/src/std-keyring.c new file mode 100644 index 0000000..80e4ffc --- /dev/null +++ b/src/svrcore/src/std-keyring.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 1998 Netscape Communications Corporation. + * All Rights Reserved. + * + * Copyright 2016 Red Hat, Inc. and/or its affiliates. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * std-keyring.c - Extension of the STD module to integrate file, tty and keyring + */ + + +#if HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + + +/* ------------------------------------------------------------ */ +/* + * SVRCOREStdKeyringPinObj implementation + */ +struct SVRCOREStdKeyringPinObj +{ + SVRCOREPinObj base; + SVRCORECachedPinObj *cache; + SVRCOREAltPinObj *alt; + SVRCOREFilePinObj *file; + SVRCOREUserPinObj *user; + SVRCOREAltPinObj *systemdalt; + SVRCORESystemdPinObj *systemd; + SVRCOREAltPinObj *keyringalt; + SVRCOREKeyringPinObj *keyring; + char *keyring_nss_token; + char *keyring_nss_password; + svrcore_log_err_t log_callback; + + SVRCOREPinObj *top; +}; +static const SVRCOREPinMethods vtable; + +/* ------------------------------------------------------------ */ +SVRCOREError +SVRCORE_CreateStdKeyringPinObj( + SVRCOREStdKeyringPinObj **out, + const char *filename, const char *nss_token, const char *nss_password, PRBool cachePINs, + PRBool systemdPINs, PRBool keyringPINs, uint64_t timeout, svrcore_log_err_t log_callback) +{ + + SVRCOREError err = SVRCORE_Success; + SVRCOREStdKeyringPinObj *obj = 0; + + do { + SVRCOREPinObj *top; + + obj = (SVRCOREStdKeyringPinObj *)malloc(sizeof (SVRCOREStdKeyringPinObj)); + if (!obj) { err = SVRCORE_NoMemory_Error; break; } + + obj->base.methods = &vtable; + + obj->cache = 0; + obj->alt = 0; + obj->file = 0; + obj->user = 0; + obj->systemdalt = 0; + obj->systemd = 0; + obj->keyringalt = 0; + obj->keyring = 0; + obj->keyring_nss_token = 0; + obj->keyring_nss_password = 0; + obj->log_callback = log_callback; + + err = SVRCORE_CreateUserPinObj(&obj->user); + if (err) { + break; + } + // Automatically detect if we are on an interactive session or not + +#ifdef DEBUG + printf("std-keyring:create() -> interactive %d \n", isatty(fileno(stdin))); +#endif + + // During testing, we want to disable this sometimes ... + // SVRCORE_SetUserPinInteractive(obj->user, PR_FALSE); + SVRCORE_SetUserPinInteractive(obj->user, isatty(fileno(stdin))); + + top = (SVRCOREPinObj*)obj->user; + + /* If filename is provided, splice it into the chain */ + if (filename) + { + err = SVRCORE_CreateFilePinObj(&obj->file, filename); + if (err) { + break; + } + + err = SVRCORE_CreateAltPinObj(&obj->alt, + (SVRCOREPinObj*)obj->file, top); + if (err) { + break; + } + + top = (SVRCOREPinObj*)obj->alt; + } + +#ifdef WITH_SYSTEMD + if (systemdPINs) { + err = SVRCORE_CreateSystemdPinObj(&obj->systemd, timeout); + if (err) { + break; + } + // Now make a second "alt" object. If pin and user fail, we call systemd +#ifdef DEBUG + printf("std-keyring:create() -> Creating systemd alt pin object \n"); +#endif + err = SVRCORE_CreateAltPinObj(&obj->systemdalt, + top, (SVRCOREPinObj*)obj->systemd); + if (err) { + break; + } + top = (SVRCOREPinObj *)obj->systemdalt; + } +#endif /* WITH_SYTEMD */ + + if (keyringPINs && nss_token && nss_password) { + /* Use of nss_token/nss_password (key/value pair) retrieved in keyring */ + if (nss_token) { + obj->keyring_nss_token = strdup(nss_token); + } + if (nss_password) { + obj->keyring_nss_password = strdup(nss_password); + } + if (log_callback) log_callback("Keyring enabled: NSS password registered for slot token \"%s\"\n", + obj->keyring_nss_token, 0, 0); +#ifdef DEBUG + printf("std-keyring:create() -> Creating keyring pin object \n"); +#endif + err = SVRCORE_CreateKeyringPinObj(&obj->keyring, obj->keyring_nss_token, obj->keyring_nss_password, timeout, log_callback); + if (err) { + break; + } + // Now make a second "alt" object. if keyring fails let's try in that order file/user/systemd + err = SVRCORE_CreateAltPinObj(&obj->keyringalt, + (SVRCOREPinObj*)obj->keyring, top); + if (err) { + break; + } + top = (SVRCOREPinObj *)obj->keyringalt; + } + + /* Create cache object if requested */ + if (cachePINs) + { + err = SVRCORE_CreateCachedPinObj(&obj->cache, top); + if (err) { + break; + } + + top = (SVRCOREPinObj*)obj->cache; + } + + obj->top = top; + } while(0); + + *out = obj; + + if (err != SVRCORE_Success) + { + SVRCORE_DestroyStdKeyringPinObj(obj); + *out = NULL; + } + + return err; +} + +void +SVRCORE_DestroyStdKeyringPinObj( + SVRCOREStdKeyringPinObj *obj) +{ +#ifdef WITH_KEYRING + if (!obj) return; + + if (obj->user) SVRCORE_DestroyUserPinObj(obj->user); + if (obj->file) SVRCORE_DestroyFilePinObj(obj->file); + if (obj->alt) SVRCORE_DestroyAltPinObj(obj->alt); + if (obj->cache) SVRCORE_DestroyCachedPinObj(obj->cache); + if (obj->systemd) SVRCORE_DestroySystemdPinObj(obj->systemd); + if (obj->systemdalt) SVRCORE_DestroyAltPinObj(obj->systemdalt); + if (obj->keyring) SVRCORE_DestroyKeyringPinObj(obj->keyring); + if (obj->keyringalt) SVRCORE_DestroyAltPinObj(obj->keyringalt); + if (obj->keyring_nss_token) free(obj->keyring_nss_token); + if (obj->keyring_nss_password) free(obj->keyring_nss_password); + + free(obj); +#endif // Keyring +} + +/* ------------------------------------------------------------ */ + +void +SVRCORE_SetStdKeyringPinInteractive(SVRCOREStdKeyringPinObj *obj, PRBool i) +{ +#ifdef WITH_KEYRING + SVRCORE_SetUserPinInteractive(obj->user, i); +#endif // Systemd +} + +/* ------------------------------------------------------------ */ +/* + * SVRCORE_StdKeyringPinGetPin + */ +SVRCOREError +SVRCORE_StdKeyringPinGetPin(char **pin, SVRCOREStdKeyringPinObj *obj, + const char *nss_password) +{ +#ifdef WITH_KEYRING +#ifdef DEBUG + printf("std-keyring:stdkeyring-getpin() -> starting \n"); +#endif + /* Make sure caching is turned on */ + if (!obj->cache) + { + *pin = 0; + return SVRCORE_NoSuchToken_Error; + } + + return SVRCORE_CachedPinGetPin(pin, obj->cache, nss_password); +#else // keyring + return SVRCORE_MissingFeature; +#endif // keyring +} + +/* ------------------------------------------------------------ */ +/* + * vtable methods + */ +static void +destroyObject(SVRCOREPinObj *obj) +{ + SVRCORE_DestroyStdKeyringPinObj((SVRCOREStdKeyringPinObj*)obj); +} + +static char * +getPin(SVRCOREPinObj *pinObj, const char *tokenName, PRBool retry) +{ + char *pin = NULL; + char *fullToken; +#ifdef DEBUG + printf("std-keyring:getpin() -> starting \n"); +#endif + SVRCOREStdKeyringPinObj *obj = (SVRCOREStdKeyringPinObj*)pinObj; + pin = SVRCORE_GetPin(obj->top, tokenName, retry); + + return pin; +} + +/* + * VTable + */ +static const SVRCOREPinMethods vtable = +{ 0, 0, destroyObject, getPin }; + diff --git a/src/svrcore/src/svrcore.h b/src/svrcore/src/svrcore.h index ec91174..a13abc8 100644 --- a/src/svrcore/src/svrcore.h +++ b/src/svrcore/src/svrcore.h @@ -263,6 +263,37 @@ void SVRCORE_DestroyStdSystemdPinObj(SVRCOREStdSystemdPinObj *obj); /* ------------------------------------------------------------ */ +typedef void (*svrcore_log_err_t)(char *fmt, char *arg1, char *arg2, char *arg3); + + +typedef struct SVRCOREKeyringPinObj SVRCOREKeyringPinObj; + +SVRCOREError +SVRCORE_CreateKeyringPinObj(SVRCOREKeyringPinObj **out, char *nss_token, char *nss_password, uint64_t timeout, svrcore_log_err_t log_callback); + +void +SVRCORE_DestroyKeyringPinObj(SVRCOREKeyringPinObj *obj); + +typedef struct SVRCOREStdKeyringPinObj SVRCOREStdKeyringPinObj; +typedef void (*svrcore_log_err_t)(char *fmt, char *arg1, char *arg2, char *arg3); + +SVRCOREError +SVRCORE_CreateStdKeyringPinObj( + SVRCOREStdKeyringPinObj **out, + const char *filename, const char *nss_token, const char *nss_password, PRBool cachePINs, + PRBool systemdPINs, PRBool keyringPINs, uint64_t timeout, svrcore_log_err_t log_callback); + +void +SVRCORE_SetStdKeyringPinInteractive(SVRCOREStdKeyringPinObj *obj, PRBool i); + +SVRCOREError +SVRCORE_StdKeyringPinGetPin(char **pin, SVRCOREStdKeyringPinObj *obj, + const char *nss_password); + +void +SVRCORE_DestroyStdKeyringPinObj( + SVRCOREStdKeyringPinObj *obj); +/* ------------------------------------------------------------ */ /* * Implements SVRCORESecurePinStore interface */ diff --git a/wrappers/ds_systemd_ask_password_acl.in b/wrappers/ds_systemd_ask_password_acl.in index 59bffc5..af335ae 100644 --- a/wrappers/ds_systemd_ask_password_acl.in +++ b/wrappers/ds_systemd_ask_password_acl.in @@ -14,21 +14,28 @@ # If we don't do this, we can't prompt for the password! # If you want this script to go away, fix the bugzilla so we don't need it! +PHASE=${2:-pre} # Make sure we have the path to the dse.ldif if [ -z $1 ] then - echo "usage: ${0} /etc/dirsrv/slapd-/dse.ldif" + echo "usage: ${0} /etc/dirsrv/slapd-/dse.ldif [pre|post]" exit 1 fi # Grep the user out DS_USER=`grep 'nsslapd-localuser: ' $1 | awk '{print $2}'` +if [ "${PHASE}" = "pre" ] +then + ACTION="-m u:${DS_USER}:rwx" +else + ACTION="-x u:${DS_USER}" +fi # Now apply the acl if [ -d /var/run/systemd/ask-password ] then - setfacl -m u:${DS_USER}:rwx /var/run/systemd/ask-password + echo setfacl ${ACTION} /var/run/systemd/ask-password fi diff --git a/wrappers/systemd.template.service.in b/wrappers/systemd.template.service.in index 3f2f23a..a5c201e 100644 --- a/wrappers/systemd.template.service.in +++ b/wrappers/systemd.template.service.in @@ -13,8 +13,11 @@ NotifyAccess=all EnvironmentFile=-@initconfigdir@/@package_name@ EnvironmentFile=-@initconfigdir@/@package_name@-%i PIDFile=/run/@package_name@/slapd-%i.pid -ExecStartPre=@libexecdir@/ds_systemd_ask_password_acl @instconfigdir@/slapd-%i/dse.ldif +ExecStartPre=@libexecdir@/ds_systemd_ask_password_acl @instconfigdir@/slapd-%i/dse.ldif pre +ExecStartPost=@libexecdir@/ds_systemd_ask_password_acl @instconfigdir@/slapd-%i/dse.ldif post +ExecStartPre=/usr/bin/ds-keyring.py --set_key @instconfigdir@/slapd-%i/dse.ldif ExecStart=@sbindir@/ns-slapd -D @instconfigdir@/slapd-%i -i /run/@package_name@/slapd-%i.pid +KeyringMode=shared [Install] WantedBy=multi-user.target