/* This file is part of TALER Copyright (C) 2020 Taler Systems SA TALER 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; either version 3, or (at your option) any later version. TALER 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 TALER; see the file COPYING. If not, see */ /** * @file util/crypto_helper_denom.c * @brief utility functions for running out-of-process private key operations * @author Christian Grothoff */ #include "platform.h" #include "taler_util.h" #include "taler_extensions.h" #include "taler_signatures.h" #include "taler-exchange-secmod-rsa.h" #include struct TALER_CRYPTO_DenominationHelper { /** * Function to call with updates to available key material. */ TALER_CRYPTO_DenominationKeyStatusCallback dkc; /** * Closure for @e dkc */ void *dkc_cls; /** * Socket address of the denomination helper process. * Used to reconnect if the connection breaks. */ struct sockaddr_un sa; /** * Socket address of this process. */ struct sockaddr_un my_sa; /** * Template for @e my_sa. */ char *template; /** * The UNIX domain socket, -1 if we are currently not connected. */ int sock; /** * Have we ever been sync'ed? */ bool synced; /** * Age Mask that applies to this denomination. */ struct TALER_AgeMask age_mask; }; /** * Disconnect from the helper process. Updates * @e sock field in @a dh. * * @param[in,out] dh handle to tear down connection of */ static void do_disconnect (struct TALER_CRYPTO_DenominationHelper *dh) { GNUNET_break (0 == close (dh->sock)); if (0 != unlink (dh->my_sa.sun_path)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", dh->my_sa.sun_path); dh->sock = -1; } /** * Try to connect to the helper process. Updates * @e sock field in @a dh. * * @param[in,out] dh handle to establish connection for */ static void try_connect (struct TALER_CRYPTO_DenominationHelper *dh) { char *tmpdir; if (-1 != dh->sock) return; dh->sock = socket (AF_UNIX, SOCK_DGRAM, 0); if (-1 == dh->sock) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "socket"); return; } tmpdir = GNUNET_DISK_mktemp (dh->template); if (NULL == tmpdir) { do_disconnect (dh); return; } /* we use >= here because we want the sun_path to always be 0-terminated */ if (strlen (tmpdir) >= sizeof (dh->sa.sun_path)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "PATHS", "TALER_RUNTIME_DIR", "path too long"); GNUNET_free (tmpdir); do_disconnect (dh); return; } dh->my_sa.sun_family = AF_UNIX; strncpy (dh->my_sa.sun_path, tmpdir, sizeof (dh->sa.sun_path) - 1); if (0 != unlink (tmpdir)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", tmpdir); if (0 != bind (dh->sock, (const struct sockaddr *) &dh->my_sa, sizeof (dh->my_sa))) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "bind", tmpdir); do_disconnect (dh); GNUNET_free (tmpdir); return; } /* Fix permissions on client UNIX domain socket, just in case umask() is not set to enable group write */ { char path[sizeof (dh->my_sa.sun_path) + 1]; strncpy (path, dh->my_sa.sun_path, sizeof (path) - 1); path[sizeof (dh->my_sa.sun_path)] = '\0'; if (0 != chmod (path, S_IRUSR | S_IWUSR | S_IWGRP)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "chmod", path); } } GNUNET_free (tmpdir); { struct GNUNET_MessageHeader hdr = { .size = htons (sizeof (hdr)), .type = htons (TALER_HELPER_RSA_MT_REQ_INIT) }; ssize_t ret; ret = sendto (dh->sock, &hdr, sizeof (hdr), 0, (const struct sockaddr *) &dh->sa, sizeof (dh->sa)); if (ret < 0) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "sendto", dh->sa.sun_path); do_disconnect (dh); return; } /* We are using SOCK_DGRAM, partial writes should not be possible */ GNUNET_break (((size_t) ret) == sizeof (hdr)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Successfully sent REQ_INIT\n"); } } struct TALER_CRYPTO_DenominationHelper * TALER_CRYPTO_helper_denom_connect ( const struct GNUNET_CONFIGURATION_Handle *cfg, TALER_CRYPTO_DenominationKeyStatusCallback dkc, void *dkc_cls) { struct TALER_CRYPTO_DenominationHelper *dh; char *unixpath; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "taler-exchange-secmod-rsa", "UNIXPATH", &unixpath)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler-exchange-secmod-rsa", "UNIXPATH"); return NULL; } /* we use >= here because we want the sun_path to always be 0-terminated */ if (strlen (unixpath) >= sizeof (dh->sa.sun_path)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "taler-exchange-secmod-rsa", "UNIXPATH", "path too long"); GNUNET_free (unixpath); return NULL; } dh = GNUNET_new (struct TALER_CRYPTO_DenominationHelper); dh->dkc = dkc; dh->dkc_cls = dkc_cls; dh->sa.sun_family = AF_UNIX; strncpy (dh->sa.sun_path, unixpath, sizeof (dh->sa.sun_path) - 1); GNUNET_free (unixpath); dh->sock = -1; { char *tmpdir; char *template; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "taler-exchange-secmod-rsa", "CLIENT_DIR", &tmpdir)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler-exchange-secmod-rsa", "CLIENT_DIR"); return NULL; } GNUNET_asprintf (&template, "%s/cli", tmpdir); /* We expect the service to create the client directory */ if (GNUNET_OK != GNUNET_DISK_directory_test (tmpdir, GNUNET_YES)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to read secmod client directory (%s)\n", tmpdir); GNUNET_free (dh); GNUNET_free (template); GNUNET_free (tmpdir); return NULL; } GNUNET_free (tmpdir); dh->template = template; if (strlen (template) >= sizeof (dh->sa.sun_path)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "PATHS", "TALER_RUNTIME_DIR", "path too long"); TALER_CRYPTO_helper_denom_disconnect (dh); return NULL; } /* Extract the age groups from the config, if the extension has been set, * and serialize them into the age mask */ if (GNUNET_OK != TALER_get_age_mask (cfg, &dh->age_mask)) { /* FIXME: maybe more specific error? */ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "extensions", /* FIXME: right section etc? */ "age-restriction", "invalid age groups"); TALER_CRYPTO_helper_denom_disconnect (dh); return NULL; } } TALER_CRYPTO_helper_denom_poll (dh); return dh; } /** * Handle a #TALER_HELPER_RSA_MT_AVAIL message from the helper. * * @param dh helper context * @param hdr message that we received * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue handle_mt_avail (struct TALER_CRYPTO_DenominationHelper *dh, const struct GNUNET_MessageHeader *hdr) { const struct TALER_CRYPTO_RsaKeyAvailableNotification *kan = (const struct TALER_CRYPTO_RsaKeyAvailableNotification *) hdr; const char *buf = (const char *) &kan[1]; const char *section_name; if (sizeof (*kan) > ntohs (hdr->size)) { GNUNET_break_op (0); return GNUNET_SYSERR; } if (ntohs (hdr->size) != sizeof (*kan) + ntohs (kan->pub_size) + ntohs (kan->section_name_len)) { GNUNET_break_op (0); return GNUNET_SYSERR; } section_name = &buf[ntohs (kan->pub_size)]; if ('\0' != section_name[ntohs (kan->section_name_len) - 1]) { GNUNET_break_op (0); return GNUNET_SYSERR; } { struct TALER_DenominationPublicKey denom_pub; struct TALER_DenominationHash h_denom_pub; denom_pub.cipher = TALER_DENOMINATION_RSA; denom_pub.age_mask = dh->age_mask; denom_pub.details.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_decode (buf, ntohs (kan->pub_size)); if (NULL == denom_pub.details.rsa_public_key) { GNUNET_break_op (0); return GNUNET_SYSERR; } TALER_denom_pub_hash (&denom_pub, &h_denom_pub); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received RSA key %s (%s)\n", GNUNET_h2s (&h_denom_pub.hash), section_name); if (GNUNET_OK != TALER_exchange_secmod_denom_verify ( &h_denom_pub, section_name, GNUNET_TIME_absolute_ntoh (kan->anchor_time), GNUNET_TIME_relative_ntoh (kan->duration_withdraw), &kan->secm_pub, &kan->secm_sig)) { GNUNET_break_op (0); GNUNET_CRYPTO_rsa_public_key_free (denom_pub.details.rsa_public_key); return GNUNET_SYSERR; } dh->dkc (dh->dkc_cls, section_name, GNUNET_TIME_absolute_ntoh (kan->anchor_time), GNUNET_TIME_relative_ntoh (kan->duration_withdraw), &h_denom_pub, &denom_pub, &kan->secm_pub, &kan->secm_sig); GNUNET_CRYPTO_rsa_public_key_free (denom_pub.details.rsa_public_key); } return GNUNET_OK; } /** * Handle a #TALER_HELPER_RSA_MT_PURGE message from the helper. * * @param dh helper context * @param hdr message that we received * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue handle_mt_purge (struct TALER_CRYPTO_DenominationHelper *dh, const struct GNUNET_MessageHeader *hdr) { const struct TALER_CRYPTO_RsaKeyPurgeNotification *pn = (const struct TALER_CRYPTO_RsaKeyPurgeNotification *) hdr; if (sizeof (*pn) != ntohs (hdr->size)) { GNUNET_break_op (0); return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received revocation of denomination key %s\n", GNUNET_h2s (&pn->h_denom_pub.hash)); dh->dkc (dh->dkc_cls, NULL, GNUNET_TIME_UNIT_ZERO_ABS, GNUNET_TIME_UNIT_ZERO, &pn->h_denom_pub, NULL, NULL, NULL); return GNUNET_OK; } /** * Wait until the socket is ready to read. * * @param dh helper to wait for * @return false on timeout (after 1s) */ static bool await_read_ready (struct TALER_CRYPTO_DenominationHelper *dh) { /* wait for reply with 1s timeout */ struct pollfd pfd = { .fd = dh->sock, .events = POLLIN }; sigset_t sigmask; struct timespec ts = { .tv_sec = 1 }; int ret; GNUNET_assert (0 == sigemptyset (&sigmask)); GNUNET_assert (0 == sigaddset (&sigmask, SIGTERM)); GNUNET_assert (0 == sigaddset (&sigmask, SIGHUP)); ret = ppoll (&pfd, 1, &ts, &sigmask); if ( (-1 == ret) && (EINTR != errno) ) GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "ppoll"); return (0 < ret); } void TALER_CRYPTO_helper_denom_poll (struct TALER_CRYPTO_DenominationHelper *dh) { char buf[UINT16_MAX]; ssize_t ret; unsigned int retry_limit = 3; const struct GNUNET_MessageHeader *hdr = (const struct GNUNET_MessageHeader *) buf; int flag = MSG_DONTWAIT; try_connect (dh); if (-1 == dh->sock) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Cannot poll denom helper: socket down\n"); return; /* give up */ } while (1) { ret = recv (dh->sock, buf, sizeof (buf), flag); if (ret < 0) { if (EAGAIN == errno) { /* EAGAIN should only happen if we did not already go through this loop */ GNUNET_assert (0 != flag); if (dh->synced) break; if (! await_read_ready (dh)) { /* timeout AND not synced => full reconnect */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Restarting connection to RSA helper, did not come up properly\n"); do_disconnect (dh); if (0 == retry_limit) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Cannot poll denom helper: retry limit reached\n"); return; /* give up */ } try_connect (dh); if (-1 == dh->sock) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Cannot poll denom helper: failed to connect\n"); return; /* give up */ } retry_limit--; flag = MSG_DONTWAIT; } else { flag = 0; /* syscall must be non-blocking this time */ } continue; /* try again */ } GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "recv"); do_disconnect (dh); return; } retry_limit = 10; flag = MSG_DONTWAIT; if ( (ret < sizeof (struct GNUNET_MessageHeader)) || (ret != ntohs (hdr->size)) ) { GNUNET_break_op (0); do_disconnect (dh); return; } switch (ntohs (hdr->type)) { case TALER_HELPER_RSA_MT_AVAIL: if (GNUNET_OK != handle_mt_avail (dh, hdr)) { GNUNET_break_op (0); do_disconnect (dh); return; } break; case TALER_HELPER_RSA_MT_PURGE: if (GNUNET_OK != handle_mt_purge (dh, hdr)) { GNUNET_break_op (0); do_disconnect (dh); return; } break; case TALER_HELPER_RSA_SYNCED: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Now synchronized with RSA helper\n"); dh->synced = true; break; default: GNUNET_break_op (0); do_disconnect (dh); return; } } } struct TALER_BlindedDenominationSignature TALER_CRYPTO_helper_denom_sign ( struct TALER_CRYPTO_DenominationHelper *dh, const struct TALER_DenominationHash *h_denom_pub, const void *msg, size_t msg_size, enum TALER_ErrorCode *ec) { struct TALER_BlindedDenominationSignature ds = { .details.blinded_rsa_signature = NULL }; { char buf[sizeof (struct TALER_CRYPTO_SignRequest) + msg_size]; struct TALER_CRYPTO_SignRequest *sr = (struct TALER_CRYPTO_SignRequest *) buf; ssize_t ret; try_connect (dh); if (-1 == dh->sock) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to connect to helper\n"); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; return ds; } sr->header.size = htons (sizeof (buf)); sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN); sr->reserved = htonl (0); sr->h_denom_pub = *h_denom_pub; memcpy (&sr[1], msg, msg_size); ret = sendto (dh->sock, buf, sizeof (buf), 0, (const struct sockaddr *) &dh->sa, sizeof (dh->sa)); if (ret < 0) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "sendto"); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; return ds; } /* We are using SOCK_DGRAM, partial writes should not be possible */ GNUNET_break (((size_t) ret) == sizeof (buf)); } while (1) { char buf[UINT16_MAX]; ssize_t ret; const struct GNUNET_MessageHeader *hdr = (const struct GNUNET_MessageHeader *) buf; if (! await_read_ready (dh)) { do_disconnect (dh); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Timeout waiting for helper\n"); *ec = TALER_EC_GENERIC_TIMEOUT; return ds; } ret = recv (dh->sock, buf, sizeof (buf), 0); if (ret < 0) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "recv"); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; return ds; } if ( (ret < sizeof (struct GNUNET_MessageHeader)) || (ret != ntohs (hdr->size)) ) { GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } switch (ntohs (hdr->type)) { case TALER_HELPER_RSA_MT_RES_SIGNATURE: if (ret < sizeof (struct TALER_CRYPTO_SignResponse)) { GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } { const struct TALER_CRYPTO_SignResponse *sr = (const struct TALER_CRYPTO_SignResponse *) buf; struct GNUNET_CRYPTO_RsaSignature *rsa_signature; rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (&sr[1], ret - sizeof (*sr)); if (NULL == rsa_signature) { GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } *ec = TALER_EC_NONE; ds.cipher = TALER_DENOMINATION_RSA; ds.details.blinded_rsa_signature = rsa_signature; return ds; } case TALER_HELPER_RSA_MT_RES_SIGN_FAILURE: if (ret != sizeof (struct TALER_CRYPTO_SignFailure)) { GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } { const struct TALER_CRYPTO_SignFailure *sf = (const struct TALER_CRYPTO_SignFailure *) buf; *ec = (enum TALER_ErrorCode) ntohl (sf->ec); return ds; } case TALER_HELPER_RSA_MT_AVAIL: if (GNUNET_OK != handle_mt_avail (dh, hdr)) { GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } break; /* while(1) loop ensures we recvfrom() again */ case TALER_HELPER_RSA_MT_PURGE: if (GNUNET_OK != handle_mt_purge (dh, hdr)) { GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } break; /* while(1) loop ensures we recvfrom() again */ default: GNUNET_break_op (0); do_disconnect (dh); *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; return ds; } } } void TALER_CRYPTO_helper_denom_revoke ( struct TALER_CRYPTO_DenominationHelper *dh, const struct TALER_DenominationHash *h_denom_pub) { struct TALER_CRYPTO_RevokeRequest rr = { .header.size = htons (sizeof (rr)), .header.type = htons (TALER_HELPER_RSA_MT_REQ_REVOKE), .h_denom_pub = *h_denom_pub }; ssize_t ret; try_connect (dh); if (-1 == dh->sock) return; /* give up */ ret = sendto (dh->sock, &rr, sizeof (rr), 0, (const struct sockaddr *) &dh->sa, sizeof (dh->sa)); if (ret < 0) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "sendto"); do_disconnect (dh); return; } /* We are using SOCK_DGRAM, partial writes should not be possible */ GNUNET_break (((size_t) ret) == sizeof (rr)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Requested revocation of denomination key %s\n", GNUNET_h2s (&h_denom_pub->hash)); } void TALER_CRYPTO_helper_denom_disconnect ( struct TALER_CRYPTO_DenominationHelper *dh) { if (-1 != dh->sock) do_disconnect (dh); GNUNET_free (dh->template); GNUNET_free (dh); } /* end of crypto_helper_denom.c */