/* sec_mod_server: * @config: server configuration * @socket_file: the name of the socket * @cmd_fd: socket to exchange commands with main * @cmd_fd_sync: socket to received sync commands from main * * This is the main part of the security module. * It creates the unix domain socket identified by @socket_file * and then accepts connections from the workers to it. Then * it serves commands requested on the server's private key. * * When the operation is decrypt the provided data are * decrypted and sent back to worker. The sign operation * signs the provided data. * * The security module's reply to the worker has the * following format: * byte[0-5]: length (uint32_t) * byte[5-total]: data (signature or decrypted data) * * The reason for having this as a separate process * is to avoid any bug on the workers to leak the key. * It is not part of main because workers are spawned * from main, and thus should be prevented from accessing * parts the key in stack or heap that was not zeroized. * Other than that it allows the main server to spawn * clients fast without becoming a bottleneck due to private * key operations. */ void sec_mod_server(void *main_pool, struct perm_cfg_st *perm_config, const char *socket_file, int cmd_fd, int cmd_fd_sync) { struct sockaddr_un sa; socklen_t sa_len; int cfd, ret, e, n; unsigned buffer_size; uid_t uid; uint8_t *buffer; int sd; sec_mod_st *sec; void *sec_mod_pool; fd_set rd_set; pid_t pid; #ifdef HAVE_PSELECT struct timespec ts; #else struct timeval ts; #endif sigset_t emptyset, blockset; #ifdef DEBUG_LEAKS talloc_enable_leak_report_full(); #endif sigemptyset(&blockset); sigemptyset(&emptyset); sigaddset(&blockset, SIGALRM); sigaddset(&blockset, SIGTERM); sigaddset(&blockset, SIGINT); sigaddset(&blockset, SIGHUP); sec_mod_pool = talloc_init("sec-mod"); if (sec_mod_pool == NULL) { seclog(sec, LOG_ERR, "error in memory allocation"); exit(1); } sec = talloc_zero(sec_mod_pool, sec_mod_st); if (sec == NULL) { seclog(sec, LOG_ERR, "error in memory allocation"); exit(1); } sec->perm_config = talloc_steal(sec, perm_config); sec->config = sec->perm_config->config; tls_cache_init(sec, &sec->tls_db); sup_config_init(sec); memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; strlcpy(sa.sun_path, socket_file, sizeof(sa.sun_path)); remove(socket_file); #define SOCKET_FILE sa.sun_path /* we no longer need the main pool after this point. */ talloc_free(main_pool); ocsignal(SIGHUP, handle_sighup); ocsignal(SIGINT, handle_sigterm); ocsignal(SIGTERM, handle_sigterm); ocsignal(SIGALRM, handle_alarm); sec_auth_init(sec, perm_config); sec->cmd_fd = cmd_fd; sec->cmd_fd_sync = cmd_fd_sync; #ifdef HAVE_PKCS11 ret = gnutls_pkcs11_reinit(); if (ret < 0) { seclog(sec, LOG_WARNING, "error in PKCS #11 reinitialization: %s", gnutls_strerror(ret)); } #endif if (sec_mod_client_db_init(sec) == NULL) { seclog(sec, LOG_ERR, "error in client db initialization"); exit(1); } sd = socket(AF_UNIX, SOCK_STREAM, 0); if (sd == -1) { e = errno; seclog(sec, LOG_ERR, "could not create socket '%s': %s", SOCKET_FILE, strerror(e)); exit(1); } set_cloexec_flag(sd, 1); umask(066); ret = bind(sd, (struct sockaddr *)&sa, SUN_LEN(&sa)); if (ret == -1) { e = errno; seclog(sec, LOG_ERR, "could not bind socket '%s': %s", SOCKET_FILE, strerror(e)); exit(1); } ret = chown(SOCKET_FILE, perm_config->uid, perm_config->gid); if (ret == -1) { e = errno; seclog(sec, LOG_INFO, "could not chown socket '%s': %s", SOCKET_FILE, strerror(e)); } ret = listen(sd, 1024); if (ret == -1) { e = errno; seclog(sec, LOG_ERR, "could not listen to socket '%s': %s", SOCKET_FILE, strerror(e)); exit(1); } ret = load_keys(sec, 1); if (ret < 0) { seclog(sec, LOG_ERR, "error loading private key files"); exit(1); } sigprocmask(SIG_BLOCK, &blockset, &sig_default_set); alarm(MAINTAINANCE_TIME); seclog(sec, LOG_INFO, "sec-mod initialized (socket: %s)", SOCKET_FILE); for (;;) { check_other_work(sec); FD_ZERO(&rd_set); n = 0; FD_SET(cmd_fd, &rd_set); n = MAX(n, cmd_fd); FD_SET(cmd_fd_sync, &rd_set); n = MAX(n, cmd_fd_sync); FD_SET(sd, &rd_set); n = MAX(n, sd); #ifdef HAVE_PSELECT ts.tv_nsec = 0; ts.tv_sec = 120; ret = pselect(n + 1, &rd_set, NULL, NULL, &ts, &emptyset); #else ts.tv_usec = 0; ts.tv_sec = 120; sigprocmask(SIG_UNBLOCK, &blockset, NULL); ret = select(n + 1, &rd_set, NULL, NULL, &ts); sigprocmask(SIG_BLOCK, &blockset, NULL); #endif if (ret == 0 || (ret == -1 && errno == EINTR)) continue; if (ret < 0) { e = errno; seclog(sec, LOG_ERR, "Error in pselect(): %s", strerror(e)); exit(1); } /* we do a new allocation, to also use it as pool for the * parsers to use */ buffer_size = MAX_MSG_SIZE; buffer = talloc_size(sec, buffer_size); if (buffer == NULL) { seclog(sec, LOG_ERR, "error in memory allocation"); exit(1); } /* we use two fds for communication with main. The synchronous is for * ping-pong communication which each request is answered immediated. The * async is for messages sent back and forth in no particular order */ if (FD_ISSET(cmd_fd_sync, &rd_set)) { ret = serve_request_main(sec, cmd_fd_sync, buffer, buffer_size); if (ret < 0 && ret == ERR_BAD_COMMAND) { seclog(sec, LOG_ERR, "error processing sync command from main"); exit(1); } } if (FD_ISSET(cmd_fd, &rd_set)) { ret = serve_request_main(sec, cmd_fd, buffer, buffer_size); if (ret < 0 && ret == ERR_BAD_COMMAND) { seclog(sec, LOG_ERR, "error processing async command from main"); exit(1); } } if (FD_ISSET(sd, &rd_set)) { sa_len = sizeof(sa); cfd = accept(sd, (struct sockaddr *)&sa, &sa_len); if (cfd == -1) { e = errno; if (e != EINTR) { seclog(sec, LOG_DEBUG, "sec-mod error accepting connection: %s", strerror(e)); goto cont; } } set_cloexec_flag (cfd, 1); /* do not allow unauthorized processes to issue commands */ ret = check_upeer_id("sec-mod", sec->perm_config->debug, cfd, perm_config->uid, perm_config->gid, &uid, &pid); if (ret < 0) { seclog(sec, LOG_INFO, "rejected unauthorized connection"); } else { memset(buffer, 0, buffer_size); serve_request_worker(sec, cfd, pid, buffer, buffer_size); } close(cfd); } cont: talloc_free(buffer); #ifdef DEBUG_LEAKS talloc_report_full(sec, stderr); #endif } }
void sec_mod_server(struct cfg_st* config, const char* socket_file) { struct sockaddr_un sa; socklen_t sa_len; int cfd, ret, e; unsigned i, buffer_size, type; gnutls_privkey_t *key; uint8_t *buffer; unsigned key_size = config->key_size; struct pin_st pins; gnutls_datum_t data, out; uint16_t length; struct iovec iov[2]; int sd; #if defined(SO_PEERCRED) && defined(HAVE_STRUCT_UCRED) struct ucred cr; socklen_t cr_len; #endif ocsignal(SIGHUP, SIG_IGN); ocsignal(SIGINT, SIG_DFL); ocsignal(SIGTERM, SIG_DFL); #ifdef HAVE_PKCS11 ret = gnutls_pkcs11_reinit(); if (ret < 0) { syslog(LOG_WARNING, "error in PKCS #11 reinitialization: %s", gnutls_strerror(ret)); } #endif buffer_size = 8*1024; buffer = malloc(buffer_size); if (buffer == NULL) { syslog(LOG_ERR, "error in memory allocation"); exit(1); } memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", socket_file); remove(socket_file); sd = socket(AF_UNIX, SOCK_STREAM, 0); if (sd == -1) { e = errno; syslog(LOG_ERR, "could not create socket '%s': %s", socket_file, strerror(e)); exit(1); } umask(066); ret = bind(sd, (struct sockaddr *)&sa, SUN_LEN(&sa)); if (ret == -1) { e = errno; syslog(LOG_ERR, "could not bind socket '%s': %s", socket_file, strerror(e)); exit(1); } ret = chown(socket_file, config->uid, config->gid); if (ret == -1) { e = errno; syslog(LOG_ERR, "could not chown socket '%s': %s", socket_file, strerror(e)); } ret = listen(sd, 1024); if (ret == -1) { e = errno; syslog(LOG_ERR, "could not listen to socket '%s': %s", socket_file, strerror(e)); exit(1); } ret = load_pins(config, &pins); if (ret < 0) { syslog(LOG_ERR, "error loading PIN files"); exit(1); } key = malloc(sizeof(*key)*config->key_size); if (key == NULL) { syslog(LOG_ERR, "error in memory allocation"); exit(1); } /* read private keys */ for (i=0;i<key_size;i++) { ret = gnutls_privkey_init(&key[i]); GNUTLS_FATAL_ERR(ret); /* load the private key */ if (gnutls_url_is_supported(config->key[i]) != 0) { gnutls_privkey_set_pin_function (key[i], pin_callback, &pins); ret = gnutls_privkey_import_url(key[i], config->key[i], 0); GNUTLS_FATAL_ERR(ret); } else { ret = gnutls_load_file(config->key[i], &data); if (ret < 0) { syslog(LOG_ERR, "error loading file '%s'", config->key[i]); GNUTLS_FATAL_ERR(ret); } ret = gnutls_privkey_import_x509_raw(key[i], &data, GNUTLS_X509_FMT_PEM, NULL, 0); GNUTLS_FATAL_ERR(ret); gnutls_free(data.data); } } syslog(LOG_INFO, "sec-mod initialized (socket: %s)", socket_file); for (;;) { sa_len = sizeof(sa); cfd = accept(sd, (struct sockaddr *)&sa, &sa_len); if (cfd == -1) { e = errno; syslog(LOG_ERR, "sec-mod error accepting connection: %s", strerror(e)); continue; } #if defined(SO_PEERCRED) && defined(HAVE_STRUCT_UCRED) cr_len = sizeof(cr); ret = getsockopt(cfd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len); if (ret == -1) { e = errno; syslog(LOG_ERR, "sec-mod error obtaining peer credentials: %s", strerror(e)); goto cont; } syslog(LOG_DEBUG, "sec-mod received request from pid %u and uid %u", (unsigned)cr.pid, (unsigned)cr.uid); if (cr.uid != config->uid || cr.gid != config->gid) { syslog(LOG_ERR, "sec-mod received unauthorized request from pid %u and uid %u", (unsigned)cr.pid, (unsigned)cr.uid); goto cont; } #endif /* read request */ ret = recv(cfd, buffer, buffer_size, 0); if (ret == 0) goto cont; else if (ret <= 2) { e = errno; syslog(LOG_ERR, "error receiving sec-mod data: %s", strerror(e)); goto cont; } /* calculate */ i = buffer[0]; type = buffer[1]; if (i >= key_size) { syslog(LOG_ERR, "sec-mod received out-of-bounds key index"); goto cont; } data.data = &buffer[2]; data.size = ret - 2; if (type == 'S') { #if GNUTLS_VERSION_NUMBER >= 0x030200 ret = gnutls_privkey_sign_hash(key[i], 0, GNUTLS_PRIVKEY_SIGN_FLAG_TLS1_RSA, &data, &out); #else ret = gnutls_privkey_sign_raw_data(key[i], 0, &data, &out); #endif } else if (type == 'D') { ret = gnutls_privkey_decrypt_data(key[i], 0, &data, &out); } else { syslog(LOG_ERR, "unknown type 0x%.2x", type); goto cont; } if (ret < 0) { syslog(LOG_ERR, "sec-mod error in crypto operation: %s", gnutls_strerror(ret)); goto cont; } /* write reply */ length = out.size; iov[0].iov_base = &length; iov[0].iov_len = 2; iov[1].iov_base = out.data; iov[1].iov_len = out.size; ret = writev(cfd, iov, 2); if (ret == -1) { e = errno; syslog(LOG_ERR, "sec-mod error in writev: %s", strerror(e)); } gnutls_free(out.data); cont: close(cfd); } }
void doit(void) { int ret; const char *lib; gnutls_privkey_t key; gnutls_datum_t sig = {NULL, 0}, data; pid_t pid; data.data = (void*)"\x38\x17\x0c\x08\xcb\x45\x8f\xd4\x87\x9c\x34\xb6\xf6\x08\x29\x4c\x50\x31\x2b\xbb"; data.size = 20; ret = global_init(); if (ret != 0) { fail("%d: %s\n", ret, gnutls_strerror(ret)); exit(1); } gnutls_global_set_log_function(tls_log_func); if (debug) gnutls_global_set_log_level(4711); lib = getenv("P11MOCKLIB1"); if (lib == NULL) lib = P11LIB; ret = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); if (ret != 0) { fail("%d: %s\n", ret, gnutls_strerror(ret)); exit(1); } ret = gnutls_pkcs11_add_provider(lib, NULL); if (ret != 0) { fail("%d: %s\n", ret, gnutls_strerror(ret)); exit(1); } ret = gnutls_privkey_init(&key); assert(ret>=0); gnutls_privkey_set_pin_function(key, pin_func, NULL); ret = gnutls_privkey_import_url(key, "pkcs11:object=test", GNUTLS_PKCS11_OBJ_FLAG_LOGIN); if (ret < 0) { fail("%d: %s\n", ret, gnutls_strerror(ret)); exit(1); } ret = gnutls_privkey_sign_hash(key, GNUTLS_DIG_SHA1, 0, &data, &sig); if (ret < 0) { fail("%d: %s\n", ret, gnutls_strerror(ret)); exit(1); } gnutls_free(sig.data); pid = fork(); if (pid != 0) { int status; assert(waitpid(pid, &status, 0) >= 0); if (WEXITSTATUS(status) != 0) { fail("child return status was unexpected: %d\n", WEXITSTATUS(status)); exit(1); } } else { /* child */ ret = gnutls_pkcs11_reinit(); assert(ret == 0); ret = gnutls_privkey_sign_hash(key, GNUTLS_DIG_SHA1, 0, &data, &sig); if (ret < 0) { fail("%d: %s\n", ret, gnutls_strerror(ret)); exit(1); } gnutls_free(sig.data); gnutls_privkey_deinit(key); gnutls_pkcs11_deinit(); gnutls_global_deinit(); exit(0); } if (debug) printf("done\n\n\n"); gnutls_privkey_deinit(key); gnutls_pkcs11_deinit(); gnutls_global_deinit(); }