diff options
Diffstat (limited to '')
-rw-r--r-- | runtime/lib_ksils12.c | 2173 |
1 files changed, 2173 insertions, 0 deletions
diff --git a/runtime/lib_ksils12.c b/runtime/lib_ksils12.c new file mode 100644 index 0000000..489f7fd --- /dev/null +++ b/runtime/lib_ksils12.c @@ -0,0 +1,2173 @@ +/* lib_ksils12.c - rsyslog's KSI-LS12 support library + * + * Regarding the online algorithm for Merkle tree signing. Expected + * calling sequence is: + * + * sigblkConstruct + * for each signature block: + * sigblkInitKSI + * for each record: + * sigblkAddRecordKSI + * sigblkFinishKSI + * sigblkDestruct + * + * Obviously, the next call after sigblkFinsh must either be to + * sigblkInitKSI or sigblkDestruct (if no more signature blocks are + * to be emitted, e.g. on file close). sigblkDestruct saves state + * information (most importantly last block hash) and sigblkConstruct + * reads (or initilizes if not present) it. + * + * Copyright 2013-2018 Adiscon GmbH and Guardtime, Inc. + * + * This file is part of rsyslog. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * -or- + * see COPYING.ASL20 in the source distribution + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdarg.h> +#include <limits.h> + +#include <ksi/ksi.h> +#include <ksi/tlv_element.h> +#include <ksi/hash.h> +#include <ksi/net_async.h> +#include <ksi/net_ha.h> +#include <ksi/net_uri.h> +#include <ksi/signature_builder.h> +#include "rsyslog.h" +#include "errmsg.h" +#include "lib_ksils12.h" +#include "lib_ksi_queue.h" + +#ifndef VERSION +#define VERSION "no-version" +#endif + +#define KSI_BUF_SIZE 4096 + +static const char *blockFileSuffix = ".logsig.parts/blocks.dat"; +static const char *sigFileSuffix = ".logsig.parts/block-signatures.dat"; +static const char *ls12FileSuffix = ".logsig"; +static const char *blockCloseReason = "com.guardtime.blockCloseReason"; + + +#define LS12_FILE_HEADER "LOGSIG12" +#define LS12_BLOCKFILE_HEADER "LOG12BLK" +#define LS12_SIGFILE_HEADER "LOG12SIG" +#define LS12_SIGNATURE_TIMEOUT 60 + +/* Worker queue item type identifier */ +typedef enum QITEM_type_en { + QITEM_SIGNATURE_REQUEST = 0x00, + QITEM_CLOSE_FILE, + QITEM_NEW_FILE, + QITEM_QUIT +} QITEM_type; + +/* Worker queue item status identifier */ +typedef enum QITEM_status_en { + /* State assigned to any item added to queue (initial state). */ + QITEM_WAITING = 0x00, + + /* State assigned to #QITEM_SIGNATURE_REQUEST item when it is sent out. */ + QITEM_SENT, + + /* State assigned to #QITEM_SIGNATURE_REQUEST item when request failed or succeeded. */ + QITEM_DONE +} QITEM_status; + + +/* Worker queue job item */ +typedef struct QueueItem_st { + QITEM_type type; + QITEM_status status; + KSI_DataHash *root; + FILE *file; /* To keep track of the target signature file. */ + uint64_t intarg1; /* Block time limit or record count or not used. */ + uint64_t intarg2; /* Level of the sign request or not used. */ + KSI_AsyncHandle *respHandle; + int ksi_status; + time_t request_time; +} QueueItem; + +static bool queueAddCloseFile(rsksictx ctx, ksifile kf); +static bool queueAddNewFile(rsksictx ctx, ksifile kf); +static bool queueAddQuit(rsksictx ctx); +static bool queueAddSignRequest(rsksictx ctx, ksifile kf, KSI_DataHash *root, unsigned level); +static int sigblkFinishKSINoSignature(ksifile ksi, const char *reason); + +void *signer_thread(void *arg); + +static void __attribute__((format(printf, 2, 3))) +report(rsksictx ctx, const char *errmsg, ...) { + char buf[1024]; + int r; + va_list args; + va_start(args, errmsg); + + r = vsnprintf(buf, sizeof (buf), errmsg, args); + buf[sizeof(buf)-1] = '\0'; + va_end(args); + + if(ctx->logFunc == NULL) + return; + + if(r>0 && r<(int)sizeof(buf)) + ctx->logFunc(ctx->usrptr, (uchar*)buf); + else + ctx->logFunc(ctx->usrptr, (uchar*)errmsg); +} + +static void +reportErr(rsksictx ctx, const char *const errmsg) +{ + if(ctx->errFunc == NULL) + goto done; + ctx->errFunc(ctx->usrptr, (uchar*)errmsg); +done: return; +} + +static const char * +level2str(int level) { + switch (level) { + case KSI_LOG_DEBUG: return "DEBUG"; + case KSI_LOG_INFO: return "INFO"; + case KSI_LOG_NOTICE: return "NOTICE"; + case KSI_LOG_WARN: return "WARN"; + case KSI_LOG_ERROR: return "ERROR"; + default: return "UNKNOWN LOG LEVEL"; + } +} + +void +reportKSIAPIErr(rsksictx ctx, ksifile ksi, const char *apiname, int ecode) +{ + char errbuf[4096]; + char ksi_errbuf[4096]; + KSI_ERR_getBaseErrorMessage(ctx->ksi_ctx, ksi_errbuf, sizeof(ksi_errbuf), NULL, NULL); + snprintf(errbuf, sizeof(errbuf), "%s[%s:%d]: %s (%s)", + (ksi == NULL) ? (uchar*) "" : ksi->blockfilename, + apiname, ecode, KSI_getErrorString(ecode), ksi_errbuf); + + errbuf[sizeof(errbuf)-1] = '\0'; + reportErr(ctx, errbuf); +} + +void +rsksisetErrFunc(rsksictx ctx, void (*func)(void*, uchar *), void *usrptr) +{ + ctx->usrptr = usrptr; + ctx->errFunc = func; +} + +void +rsksisetLogFunc(rsksictx ctx, void (*func)(void*, uchar *), void *usrptr) +{ + ctx->usrptr = usrptr; + ctx->logFunc = func; +} + +static ksifile +rsksifileConstruct(rsksictx ctx) { + ksifile ksi = NULL; + if ((ksi = calloc(1, sizeof (struct ksifile_s))) == NULL) + goto done; + ksi->ctx = ctx; + ksi->hashAlg = ctx->hashAlg; + ksi->blockTimeLimit = ctx->blockTimeLimit; + ksi->blockSizeLimit = 1 << (ctx->effectiveBlockLevelLimit - 1); + ksi->bKeepRecordHashes = ctx->bKeepRecordHashes; + ksi->bKeepTreeHashes = ctx->bKeepTreeHashes; + ksi->lastLeaf[0] = ctx->hashAlg; + +done: + return ksi; +} + +/* return the actual length in to-be-written octets of an integer */ +static uint8_t +tlvGetIntSize(uint64_t val) { + uint8_t n = 0; + while (val != 0) { + val >>= 8; + n++; + } + return n; +} + +static int +tlvWriteOctetString(FILE *f, const uint8_t *data, uint16_t len) { + if (fwrite(data, len, 1, f) != 1) + return RSGTE_IO; + return 0; +} + +static int +tlvWriteHeader8(FILE *f, int flags, uint8_t tlvtype, int len) { + unsigned char buf[2]; + assert((flags & RSGT_TYPE_MASK) == 0); + assert((tlvtype & RSGT_TYPE_MASK) == tlvtype); + buf[0] = (flags & ~RSGT_FLAG_TLV16) | tlvtype; + buf[1] = len & 0xff; + + return tlvWriteOctetString(f, buf, 2); +} + +static int +tlvWriteHeader16(FILE *f, int flags, uint16_t tlvtype, uint16_t len) +{ + uint16_t typ; + unsigned char buf[4]; + assert((flags & RSGT_TYPE_MASK) == 0); + assert((tlvtype >> 8 & RSGT_TYPE_MASK) == (tlvtype >> 8)); + typ = ((flags | RSGT_FLAG_TLV16) << 8) | tlvtype; + + buf[0] = typ >> 8; + buf[1] = typ & 0xff; + buf[2] = (len >> 8) & 0xff; + buf[3] = len & 0xff; + + return tlvWriteOctetString(f, buf, 4); +} + +static int +tlvGetHeaderSize(uint16_t tag, size_t size) { + if (tag <= RSGT_TYPE_MASK && size <= 0xff) + return 2; + if ((tag >> 8) <= RSGT_TYPE_MASK && size <= 0xffff) + return 4; + return 0; +} + +static int +tlvWriteHeader(FILE *f, int flags, uint16_t tlvtype, uint16_t len) { + int headersize = tlvGetHeaderSize(tlvtype, flags); + if (headersize == 2) + return tlvWriteHeader8(f, flags, tlvtype, len); + else if (headersize == 4) + return tlvWriteHeader16(f, flags, tlvtype, len); + else + return 0; +} + +static int +tlvWriteOctetStringTLV(FILE *f, int flags, uint16_t tlvtype, const uint8_t *data, uint16_t len) { + if (tlvWriteHeader(f, flags, tlvtype, len) != 0) + return RSGTE_IO; + + if (fwrite(data, len, 1, f) != 1) + return RSGTE_IO; + + return 0; +} + +static int +tlvWriteInt64TLV(FILE *f, int flags, uint16_t tlvtype, uint64_t val) { + unsigned char buf[8]; + uint8_t count = tlvGetIntSize(val); + uint64_t nTmp; + + if (tlvWriteHeader(f, flags, tlvtype, count) != 0) + return RSGTE_IO; + + nTmp = val; + for (int i = count - 1; i >= 0; i--) { + buf[i] = 0xFF & nTmp; + nTmp = nTmp >> 8; + } + + if (fwrite(buf, count, 1, f) != 1) + return RSGTE_IO; + + return 0; +} + +static int +tlvWriteHashKSI(ksifile ksi, uint16_t tlvtype, KSI_DataHash *rec) { + int r; + const unsigned char *imprint; + size_t imprint_len; + r = KSI_DataHash_getImprint(rec, &imprint, &imprint_len); + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHash_getImprint", r); + return r; + } + + return tlvWriteOctetStringTLV(ksi->blockFile, 0, tlvtype, imprint, imprint_len); +} + +static int +tlvWriteBlockHdrKSI(ksifile ksi) { + unsigned tlvlen; + uint8_t hash_algo = ksi->hashAlg; + int r; + + tlvlen = 2 + 1 /* hash algo TLV */ + + 2 + KSI_getHashLength(ksi->hashAlg) /* iv */ + + 2 + KSI_getHashLength(ksi->lastLeaf[0]) + 1; + /* last hash */; + + /* write top-level TLV object block-hdr */ + CHKr(tlvWriteHeader(ksi->blockFile, 0x00, 0x0901, tlvlen)); + + /* hash-algo */ + CHKr(tlvWriteOctetStringTLV(ksi->blockFile, 0x00, 0x01, &hash_algo, 1)); + + /* block-iv */ + CHKr(tlvWriteOctetStringTLV(ksi->blockFile, 0x00, 0x02, + ksi->IV, KSI_getHashLength(ksi->hashAlg))); + + /* last-hash */ + CHKr(tlvWriteOctetStringTLV(ksi->blockFile, 0x00, 0x03, + ksi->lastLeaf, KSI_getHashLength(ksi->lastLeaf[0]) + 1)); +done: + return r; +} + +static int +tlvWriteKSISigLS12(FILE *outfile, size_t record_count, uchar *der, uint16_t lenDer) { + int r = 0; + int totalSize = 2 + tlvGetIntSize(record_count) + 4 + lenDer; + + CHKr(tlvWriteHeader(outfile, 0x00, 0x0904, totalSize)); + CHKr(tlvWriteInt64TLV(outfile, 0x00, 0x01, record_count)); + CHKr(tlvWriteOctetStringTLV(outfile, 0x00, 0x0905, der, lenDer)); +done: + return r; +} + +static int +tlvWriteNoSigLS12(FILE *outfile, size_t record_count, const KSI_DataHash *hash, const char *errorText) { + int r = 0; + int totalSize = 0; + int noSigSize = 0; + const unsigned char *imprint = NULL; + size_t imprintLen = 0; + + KSI_DataHash_getImprint(hash, &imprint, &imprintLen); + + noSigSize = 2 + imprintLen + (errorText ? (2 + strlen(errorText) + 1) : 0); + totalSize = 2 + tlvGetIntSize(record_count) + 2 + noSigSize; + + CHKr(tlvWriteHeader(outfile, 0x00, 0x0904, totalSize)); + CHKr(tlvWriteInt64TLV(outfile, 0x00, 0x01, record_count)); + CHKr(tlvWriteHeader(outfile, 0x00, 0x02, noSigSize)); + CHKr(tlvWriteOctetStringTLV(outfile, 0x00, 0x01, imprint, imprintLen)); + if (errorText) + CHKr(tlvWriteOctetStringTLV(outfile, 0x00, 0x02, (uint8_t*) errorText, strlen(errorText) + 1)); +done: + return r; +} + +static int +tlvCreateMetadata(ksifile ksi, uint64_t record_index, const char *key, + const char *value, unsigned char *buffer, size_t *len) { + int r = 0; + KSI_TlvElement *metadata = NULL, *attrib_tlv = NULL; + KSI_Utf8String *key_tlv = NULL, *value_tlv = NULL; + KSI_Integer *index_tlv = NULL; + + CHKr(KSI_TlvElement_new(&metadata)); + metadata->ftlv.tag = 0x0911; + + CHKr(KSI_Integer_new(ksi->ctx->ksi_ctx, record_index, &index_tlv)); + CHKr(KSI_TlvElement_setInteger(metadata, 0x01, index_tlv)); + + CHKr(KSI_TlvElement_new(&attrib_tlv)); + attrib_tlv->ftlv.tag = 0x02; + + CHKr(KSI_Utf8String_new(ksi->ctx->ksi_ctx, key, strlen(key) + 1, &key_tlv)); + CHKr(KSI_TlvElement_setUtf8String(attrib_tlv, 0x01, key_tlv)); + + CHKr(KSI_Utf8String_new(ksi->ctx->ksi_ctx, value, strlen(value) + 1, &value_tlv)); + CHKr(KSI_TlvElement_setUtf8String(attrib_tlv, 0x02, value_tlv)); + + CHKr(KSI_TlvElement_setElement(metadata, attrib_tlv)); + + CHKr(KSI_TlvElement_serialize(metadata, buffer, 0xFFFF, len, 0)); + +done: + if (metadata) KSI_TlvElement_free(metadata); + if (attrib_tlv) KSI_TlvElement_free(attrib_tlv); + if (key_tlv) KSI_Utf8String_free(key_tlv); + if (value_tlv) KSI_Utf8String_free(value_tlv); + if (index_tlv) KSI_Integer_free(index_tlv); + + return r; +} + +#define KSI_FILE_AMOUNT_INC 32 + +static int +rsksiExpandRegisterIfNeeded(rsksictx ctx, size_t inc) { + int ret = RSGTE_INTERNAL; + ksifile *tmp = NULL; + + if (ctx == NULL || inc == 0) { + return RSGTE_INTERNAL; + } + + if (ctx->ksiCount < ctx->ksiCapacity) { + return RSGTE_SUCCESS; + } + + /* If needed allocate memory for the buffer. */ + tmp = (ksifile*)realloc(ctx->ksi, sizeof(ksifile) * (ctx->ksiCapacity + inc)); + if (tmp == NULL) { + ret = RSGTE_OOM; + goto done; + } + + /* Make sure that allocated pointers are all set to NULL. */ + memset(tmp + ctx->ksiCapacity, 0, sizeof(ksifile) * inc); + + /* Update buffer capacity. */ + ctx->ksiCapacity += inc; + ctx->ksi = tmp; + tmp = NULL; + ret = RSGTE_SUCCESS; + +done: + free(tmp); + return ret; +} + +static int +rsksiRegisterKsiFile(rsksictx ctx, ksifile ksi) { + int ret = RSGTE_INTERNAL; + + if (ctx == NULL || ksi == NULL) { + return RSGTE_INTERNAL; + } + + /* To be extra sure that ksifile buffer is initialized correctly, clear variables. */ + if (ctx->ksi == NULL) { + ctx->ksiCount = 0; + ctx->ksiCapacity = 0; + } + + ret = rsksiExpandRegisterIfNeeded(ctx, KSI_FILE_AMOUNT_INC); + if (ret != RSGTE_SUCCESS) goto done; + + ctx->ksi[ctx->ksiCount] = ksi; + ctx->ksiCount++; + ret = RSGTE_SUCCESS; + +done: + return ret; +} + +static int +rsksiDeregisterKsiFile(rsksictx ctx, ksifile ksi) { + int ret = RSGTE_INTERNAL; + size_t i = 0; + + if (ctx == NULL || ksi == NULL) { + return RSGTE_INTERNAL; + } + + + for (i = 0; i < ctx->ksiCount; i++) { + if (ctx->ksi[i] != NULL && ctx->ksi[i] == ksi) { + size_t lastElement = ctx->ksiCount - 1; + + if (i != lastElement) { + ctx->ksi[i] = ctx->ksi[lastElement]; + } + + ctx->ksi[lastElement] = NULL; + + ctx->ksiCount--; + ret = RSGTE_SUCCESS; + goto done; + } + } + +done: + return ret; +} + +/* support for old platforms - graceful degrade */ +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif +/* read rsyslog log state file; if we cannot access it or the + * contents looks invalid, we flag it as non-present (and thus + * begin a new hash chain). + * The context is initialized accordingly. + */ +static bool +ksiReadStateFile(ksifile ksi) { + int fd = -1; + struct rsksistatefile sf; + bool ret = false; + + fd = open((char*)ksi->statefilename, O_RDONLY|O_NOCTTY|O_CLOEXEC, 0600); + if (fd == -1) + goto done; + + if (read(fd, &sf, sizeof (sf)) != sizeof (sf)) goto done; + if (strncmp(sf.hdr, "KSISTAT10", 9)) goto done; + + if (KSI_getHashLength(sf.hashID) != sf.lenHash || + KSI_getHashLength(sf.hashID) > KSI_MAX_IMPRINT_LEN - 1) + goto done; + + if (read(fd, ksi->lastLeaf + 1, sf.lenHash) != sf.lenHash) + goto done; + + ksi->lastLeaf[0] = sf.hashID; + ret = true; + +done: + if (!ret) { + memset(ksi->lastLeaf, 0, sizeof (ksi->lastLeaf)); + ksi->lastLeaf[0] = ksi->hashAlg; + } + + if (fd != -1) + close(fd); + return ret; +} + +/* persist all information that we need to re-open and append + * to a log signature file. + */ +static void +ksiWwriteStateFile(ksifile ksi) +{ + int fd; + struct rsksistatefile sf; + + fd = open((char*)ksi->statefilename, + O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, ksi->ctx->fCreateMode); + if(fd == -1) + goto done; + if (ksi->ctx->fileUID != (uid_t) - 1 || ksi->ctx->fileGID != (gid_t) - 1) { + /* we need to set owner/group */ + if (fchown(fd, ksi->ctx->fileUID, ksi->ctx->fileGID) != 0) { + report(ksi->ctx, "lmsig_ksi: chown for file '%s' failed: %s", + ksi->statefilename, strerror(errno)); + } + } + + memcpy(sf.hdr, "KSISTAT10", 9); + sf.hashID = ksi->hashAlg; + sf.lenHash = KSI_getHashLength(ksi->lastLeaf[0]); + /* if the write fails, we cannot do anything against that. We check + * the condition just to keep the compiler happy. + */ + if(write(fd, &sf, sizeof(sf))){}; + if (write(fd, ksi->lastLeaf + 1, sf.lenHash)) { + }; + close(fd); +done: + return; +} + + +static int +ksiCloseSigFile(ksifile ksi) { + fclose(ksi->blockFile); + ksi->blockFile = NULL; + if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) + queueAddCloseFile(ksi->ctx, ksi); + + ksiWwriteStateFile(ksi); + return 0; +} + +static int mkpath(char* path, mode_t mode, uid_t uid, gid_t gid) { + + if(path == NULL) + return 1; + + for (char *p = strchr(path + 1, '/'); p; p = strchr(p + 1, '/')) { + *p = '\0'; + if (mkdir(path, mode) == 0) { + if (uid != (uid_t) -1 || gid != (uid_t) -1) { + if (chown(path, uid, gid)) { + LogError(errno, RS_RET_IO_ERROR, + "ksils12 signatures: could not change to " + "configured owner - files may be unaccessible"); + } + } + } + else if (errno != EEXIST) { + *p = '/'; + return -1; + } + + *p = '/'; + } + return 0; +} + +static FILE* +ksiCreateFile(rsksictx ctx, const char *path, uid_t uid, gid_t gid, int mode, bool lockit, const char* header) { + int fd = -1; + struct stat stat_st; + FILE *f = NULL; + struct flock lock = {F_WRLCK, SEEK_SET, 0, 0, 0}; + + if(path ==NULL) + return NULL; + + if (mkpath((char*) path, ctx->fDirCreateMode, ctx->dirUID, ctx->dirGID) != 0) { + report(ctx, "ksiCreateFile: mkpath failed for %s", path); + goto done; + } + + fd = open(path, O_RDWR | O_APPEND | O_NOCTTY | O_CLOEXEC, 0600); + if (fd == -1) { + fd = open(path, O_RDWR | O_CREAT | O_NOCTTY | O_CLOEXEC, mode); + if (fd == -1) { + report(ctx, "creating file '%s' failed: %s", path, strerror(errno)); + goto done; + } + + if (uid != (uid_t) - 1 || gid != (gid_t) - 1) { + if (fchown(fd, uid, gid) != 0) { + report(ctx, "lmsig_ksi: chown for file '%s' failed: %s", + path, strerror(errno)); + } + } + } + + if (lockit && fcntl(fd, F_SETLK, &lock) != 0) + report(ctx, "fcntl error: %s", strerror(errno)); + + f = fdopen(fd, "a"); + if (f == NULL) { + report(ctx, "fdopen for '%s' failed: %s", path, strerror(errno)); + goto done; + } + + setvbuf(f, NULL, _IOFBF, KSI_BUF_SIZE); + + if (fstat(fd, &stat_st) == -1) { + reportErr(ctx, "ksiOpenSigFile: can not stat file"); + goto done; + } + + if (stat_st.st_size == 0 && header != NULL) { + if(fwrite(header, strlen(header), 1, f) != 1) { + report(ctx, "ksiOpenSigFile: fwrite for file %s failed: %s", + path, strerror(errno)); + goto done; + } + } + /* Write header immediately as when using dynafile it is possible that the same + * file is opened 2x in sequence (caused by small dynafile cache where files are + * frequently closed and reopened). If the header already exists double header is + * not written. The content of the file is ordered by signer thread. + */ + fflush(f); +done: + return f; +} + +static void handle_ksi_config(rsksictx ctx, KSI_AsyncService *as, KSI_Config *config) { + int res = KSI_UNKNOWN_ERROR; + KSI_Integer *intValue = NULL; + + if (KSI_Config_getMaxRequests(config, &intValue) == KSI_OK && intValue != NULL) { + ctx->max_requests = KSI_Integer_getUInt64(intValue); + report(ctx, "KSI gateway has reported a max requests value of %llu", + (long long unsigned) ctx->max_requests); + + if(as) { + /* libksi expects size_t. */ + size_t optValue = 0; + + optValue = ctx->max_requests; + res = KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_MAX_REQUEST_COUNT, + (void*)optValue); + if(res != KSI_OK) + reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_setOption(max_request)", res); + + optValue = 3 * ctx->max_requests * ctx->blockSigTimeout; + KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_REQUEST_CACHE_SIZE, + (void*)optValue); + } + } + + intValue = NULL; + if(KSI_Config_getMaxLevel(config, &intValue) == KSI_OK && intValue != NULL) { + uint64_t newLevel = 0; + newLevel = KSI_Integer_getUInt64(intValue); + report(ctx, "KSI gateway has reported a max level value of %llu", + (long long unsigned) newLevel); + newLevel=MIN(newLevel, ctx->blockLevelLimit); + if(ctx->effectiveBlockLevelLimit != newLevel) { + report(ctx, "Changing the configured block level limit from %llu to %llu", + (long long unsigned) ctx->effectiveBlockLevelLimit, + (long long unsigned) newLevel); + ctx->effectiveBlockLevelLimit = newLevel; + } + else if(newLevel < 2) { + report(ctx, "KSI gateway has reported an invalid level limit value (%llu), " + "plugin disabled", (long long unsigned) newLevel); + ctx->disabled = true; + } + } + + intValue = NULL; + if (KSI_Config_getAggrPeriod(config, &intValue) == KSI_OK && intValue != NULL) { + uint64_t newThreadSleep = 0; + newThreadSleep = KSI_Integer_getUInt64(intValue); + report(ctx, "KSI gateway has reported an aggregation period value of %llu", + (long long unsigned) newThreadSleep); + + newThreadSleep = MIN(newThreadSleep, ctx->threadSleepms); + if(ctx->threadSleepms != newThreadSleep) { + report(ctx, "Changing async signer thread sleep from %llu to %llu", + (long long unsigned) ctx->threadSleepms, + (long long unsigned) newThreadSleep); + ctx->threadSleepms = newThreadSleep; + } + } +} + +static int +isAggrConfNeeded(rsksictx ctx) { + time_t now = 0; + + now = time(NULL); + + if ((uint64_t)ctx->tConfRequested + ctx->confInterval <= (uint64_t)now || ctx->tConfRequested == 0) { + ctx->tConfRequested = now; + return 1; + } + + return 0; +} + +/* note: if file exists, the last hash for chaining must + * be read from file. + */ +static int +ksiOpenSigFile(ksifile ksi) { + int r = 0, tmpRes = 0; + const char *header; + FILE* signatureFile = NULL; + + if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) + header = LS12_BLOCKFILE_HEADER; + else + header = LS12_FILE_HEADER; + + ksi->blockFile = ksiCreateFile(ksi->ctx, (char*) ksi->blockfilename, ksi->ctx->fileUID, + ksi->ctx->fileGID, ksi->ctx->fCreateMode, true, header); + + if (ksi->blockFile == NULL) { + r = RSGTE_IO; + goto done; + } + + /* create the file for ksi signatures if needed */ + if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) { + signatureFile = ksiCreateFile(ksi->ctx, (char*) ksi->ksifilename, ksi->ctx->fileUID, + ksi->ctx->fileGID, ksi->ctx->fCreateMode, true, LS12_SIGFILE_HEADER); + + if (signatureFile == NULL) { + r = RSGTE_IO; + goto done; + } + + ksi->sigFile = signatureFile; + queueAddNewFile(ksi->ctx, ksi); + } + + /* we now need to obtain the last previous hash, so that + * we can continue the hash chain. We do not check for error + * as a state file error can be recovered by graceful degredation. + */ + ksiReadStateFile(ksi); + + if (ksi->ctx->syncMode == LOGSIG_SYNCHRONOUS) { + if (isAggrConfNeeded(ksi->ctx)) { + KSI_Config *config = NULL; + + tmpRes = KSI_receiveAggregatorConfig(ksi->ctx->ksi_ctx, &config); + if (tmpRes == KSI_OK) { + handle_ksi_config(ksi->ctx, NULL, config); + } else { + reportKSIAPIErr(ksi->ctx, NULL, "KSI_receiveAggregatorConfig", tmpRes); + } + KSI_Config_free(config); + } + } + +done: return r; +} + +/* + * As of some Linux and security expert I spoke to, /dev/urandom + * provides very strong random numbers, even if it runs out of + * entropy. As far as he knew, this is save for all applications + * (and he had good proof that I currently am not permitted to + * reproduce). -- rgerhards, 2013-03-04 + */ +static void +seedIVKSI(ksifile ksi) +{ + int hashlen; + int fd; + const char *rnd_device = ksi->ctx->random_source ? ksi->ctx->random_source : "/dev/urandom"; + + hashlen = KSI_getHashLength(ksi->hashAlg); + ksi->IV = malloc(hashlen); /* do NOT zero-out! */ + /* if we cannot obtain data from /dev/urandom, we use whatever + * is present at the current memory location as random data. Of + * course, this is very weak and we should consider a different + * option, especially when not running under Linux (for Linux, + * unavailability of /dev/urandom is just a theoretic thing, it + * will always work...). -- TODO -- rgerhards, 2013-03-06 + */ + if ((fd = open(rnd_device, O_RDONLY)) >= 0) { + if(read(fd, ksi->IV, hashlen) == hashlen) {}; /* keep compiler happy */ + close(fd); + } +} + +static int +create_signer_thread(rsksictx ctx) { + int r; + if (ctx->signer_state != SIGNER_STARTED) { + if ((r = pthread_mutex_init(&ctx->module_lock, 0))) + report(ctx, "pthread_mutex_init: %s", strerror(r)); + ctx->signer_queue = ProtectedQueue_new(10); + + ctx->signer_state = SIGNER_INIT; + if ((r = pthread_create(&ctx->signer_thread, NULL, signer_thread, ctx))) { + report(ctx, "pthread_create: %s", strerror(r)); + ctx->signer_state = SIGNER_IDLE; + return RSGTE_INTERNAL; + } + + /* Lock until init. */ + while(*((volatile int*)&ctx->signer_state) & SIGNER_INIT); + + if (ctx->signer_state != SIGNER_STARTED) { + return RSGTE_INTERNAL; + } + } + + return RSGTE_SUCCESS; +} + +rsksictx +rsksiCtxNew(void) { + rsksictx ctx; + ctx = calloc(1, sizeof (struct rsksictx_s)); + KSI_CTX_new(&ctx->ksi_ctx); // TODO: error check (probably via a generic macro?) + ctx->hasher = NULL; + ctx->hashAlg = KSI_getHashAlgorithmByName("default"); + ctx->blockTimeLimit = 0; + ctx->bKeepTreeHashes = false; + ctx->bKeepRecordHashes = true; + ctx->max_requests = (1 << 8); + ctx->blockSigTimeout = 10; + ctx->confInterval = 3600; + ctx->tConfRequested = 0; + ctx->threadSleepms = 1000; + ctx->errFunc = NULL; + ctx->usrptr = NULL; + ctx->fileUID = -1; + ctx->fileGID = -1; + ctx->dirUID = -1; + ctx->dirGID = -1; + ctx->fCreateMode = 0644; + ctx->fDirCreateMode = 0700; +#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22 + ctx->roundCount = 0; + ctx->bRoundLock = 0; +#endif + ctx->syncMode = LOGSIG_SYNCHRONOUS; + ctx->signer_state = SIGNER_IDLE; + ctx->disabled = false; + ctx->ksi = NULL; + + /*if (pthread_mutex_init(&ctx->module_lock, 0)) + report(ctx, "pthread_mutex_init: %s", strerror(errno)); + ctx->signer_queue = ProtectedQueue_new(10);*/ + + /* Creating a thread this way works only in daemon mode but not when being run + interactively when not forked */ + /*ret = pthread_atfork(NULL, NULL, create_signer_thread); + if (ret != 0) + report(ctx, "pthread_atfork error: %s", strerror(ret));*/ + + return ctx; +} + +static +int rsksiStreamLogger(void *logCtx, int logLevel, const char *message) +{ + char time_buf[32]; + struct tm *tm_info; + time_t timer; + FILE *f = (FILE *)logCtx; + + timer = time(NULL); + + tm_info = localtime(&timer); + if (tm_info == NULL) { + return KSI_UNKNOWN_ERROR; + } + + if (f != NULL) { + flockfile(f); /* for thread safety */ + if (strftime(time_buf, sizeof(time_buf), "%d.%m.%Y %H:%M:%S", tm_info)) { + if (fprintf(f, "%s [%s] %lu - %s\n", level2str(logLevel), + time_buf, pthread_self(), message) > 0) { } + } + funlockfile(f); + } + + return KSI_OK; +} + +int +rsksiInitModule(rsksictx ctx) { + int res = 0; + + if(ctx->debugFileName != NULL) { + ctx->debugFile = fopen(ctx->debugFileName, "w"); + if(ctx->debugFile) { + res = KSI_CTX_setLoggerCallback(ctx->ksi_ctx, rsksiStreamLogger, ctx->debugFile); + if (res != KSI_OK) + reportKSIAPIErr(ctx, NULL, "Unable to set logger callback", res); + res = KSI_CTX_setLogLevel(ctx->ksi_ctx, ctx->debugLevel); + if (res != KSI_OK) + reportKSIAPIErr(ctx, NULL, "Unable to set log level", res); + } + else { + report(ctx, "Could not open logfile %s: %s", ctx->debugFileName, strerror(errno)); + } + } + + KSI_CTX_setOption(ctx->ksi_ctx, KSI_OPT_AGGR_HMAC_ALGORITHM, (void*)((size_t)ctx->hmacAlg)); + + return create_signer_thread(ctx); +} + +/* either returns ksifile object or NULL if something went wrong */ +ksifile +rsksiCtxOpenFile(rsksictx ctx, unsigned char *logfn) +{ + int ret = RSGTE_INTERNAL; + ksifile ksi; + char fn[MAXFNAME+1]; + + if (ctx->disabled) + return NULL; + + pthread_mutex_lock(&ctx->module_lock); + + /* The thread cannot be be created in rsksiCtxNew because in daemon mode the + process forks after rsksiCtxNew and the thread disappears */ + if (ctx->signer_state != SIGNER_STARTED) { + ret = rsksiInitModule(ctx); + if (ret != RSGTE_SUCCESS) { + report(ctx, "Unable to init. KSI module, signing service disabled"); + ctx->disabled = true; + pthread_mutex_unlock(&ctx->module_lock); + return NULL; + } + } + + if ((ksi = rsksifileConstruct(ctx)) == NULL) + goto done; + + snprintf(fn, sizeof (fn), "%s.ksistate", logfn); + fn[MAXFNAME] = '\0'; /* be on safe side */ + ksi->statefilename = (uchar*) strdup(fn); + + if (ctx->syncMode == LOGSIG_ASYNCHRONOUS) { + /* filename for blocks of hashes*/ + snprintf(fn, sizeof (fn), "%s%s", logfn, blockFileSuffix); + fn[MAXFNAME] = '\0'; /* be on safe side */ + ksi->blockfilename = (uchar*) strdup(fn); + + /* filename for KSI signatures*/ + snprintf(fn, sizeof (fn), "%s%s", logfn, sigFileSuffix); + fn[MAXFNAME] = '\0'; /* be on safe side */ + ksi->ksifilename = (uchar*) strdup(fn); + } else if (ctx->syncMode == LOGSIG_SYNCHRONOUS) { + snprintf(fn, sizeof (fn), "%s%s", logfn, ls12FileSuffix); + fn[MAXFNAME] = '\0'; /* be on safe side */ + ksi->blockfilename = (uchar*) strdup(fn); + } + + if (ksiOpenSigFile(ksi) != 0) { + reportErr(ctx, "signature file open failed"); + /* Free memory */ + free(ksi); + ksi = NULL; + } + +done: + /* Register ksi file in rsksictx for keeping track of block timeouts. */ + rsksiRegisterKsiFile(ctx, ksi); + pthread_mutex_unlock(&ctx->module_lock); + return ksi; +} + + +/* Returns RSGTE_SUCCESS on success, error code otherwise. If algo is unknown or + * is not trusted, default hash function is used. + */ +int +rsksiSetHashFunction(rsksictx ctx, char *algName) { + if (ctx == NULL || algName == NULL) { + return RSGTE_INTERNAL; + } + + int r, id = KSI_getHashAlgorithmByName(algName); + if (!KSI_isHashAlgorithmSupported(id)) { + report(ctx, "Hash function '%s' is not supported - using default", algName); + ctx->hashAlg = KSI_getHashAlgorithmByName("default"); + } else { + if(!KSI_isHashAlgorithmTrusted(id)) { + report(ctx, "Hash function '%s' is not trusted - using default", algName); + ctx->hashAlg = KSI_getHashAlgorithmByName("default"); + } + else + ctx->hashAlg = id; + } + + if ((r = KSI_DataHasher_open(ctx->ksi_ctx, ctx->hashAlg, &ctx->hasher)) != KSI_OK) { + reportKSIAPIErr(ctx, NULL, "KSI_DataHasher_open", r); + ctx->disabled = true; + return r; + } + + return RSGTE_SUCCESS; +} + +int +rsksiSetHmacFunction(rsksictx ctx, char *algName) { + int id = KSI_getHashAlgorithmByName(algName); + if (!KSI_isHashAlgorithmSupported(id)) { + report(ctx, "HMAC function '%s' is not supported - using default", algName); + ctx->hmacAlg = KSI_getHashAlgorithmByName("default"); + } else { + if(!KSI_isHashAlgorithmTrusted(id)) { + report(ctx, "HMAC function '%s' is not trusted - using default", algName); + ctx->hmacAlg = KSI_getHashAlgorithmByName("default"); + } + else + ctx->hmacAlg = id; + } + return 0; +} + +int +rsksifileDestruct(ksifile ksi) { + int r = 0; + rsksictx ctx = NULL; + if (ksi == NULL) + return RSGTE_INTERNAL; + + pthread_mutex_lock(&ksi->ctx->module_lock); + + ctx = ksi->ctx; + + /* Deregister ksifile so it is not used by signer thread anymore. Note that files are not closed yet! */ + rsksiDeregisterKsiFile(ctx, ksi); + + if (!ksi->disabled && ksi->bInBlk) { + sigblkAddMetadata(ksi, blockCloseReason, "Block closed due to file closure."); + r = sigblkFinishKSI(ksi); + } + /* Note that block file is closed immediately but signature file will be closed + * by the signer thread scheduled by signer thread work queue. + */ + if(!ksi->disabled) + r = ksiCloseSigFile(ksi); + free(ksi->blockfilename); + free(ksi->statefilename); + free(ksi->ksifilename); + + free(ksi); + + pthread_mutex_unlock(&ctx->module_lock); + return r; +} + +/* This can only be used when signer thread has terminated or within the thread. */ +static void +rsksifileForceFree(ksifile ksi) { + if (ksi == NULL) return; + + if (ksi->sigFile != NULL) fclose(ksi->sigFile); + if (ksi->blockFile != NULL) fclose(ksi->blockFile); + free(ksi->blockfilename); + free(ksi->statefilename); + free(ksi->ksifilename); + free(ksi); + return; +} + +/* This can only be used when signer thread has terminated or within the thread. */ +static void +rsksictxForceFreeSignatures(rsksictx ctx) { + size_t i = 0; + + if (ctx == NULL || ctx->ksi == NULL) return; + + for (i = 0; i < ctx->ksiCount; i++) { + if (ctx->ksi[i] != NULL) { + rsksifileForceFree(ctx->ksi[i]); + ctx->ksi[i] = NULL; + } + } + + ctx->ksiCount = 0; + return; +} + +/* This can only be used when signer thread has terminated or within the thread. */ +static int +rsksictxForceCloseWithoutSig(rsksictx ctx, const char *reason) { + size_t i = 0; + if (ctx == NULL || ctx->ksi == NULL) return RSGTE_INTERNAL; + for (i = 0; i < ctx->ksiCount; i++) { + if (ctx->ksi[i] != NULL) { + int ret = RSGTE_INTERNAL; + + /* Only if block contains records, create metadata, close the block and add + * no signature marker. Closing block without record will produce redundant + * blocks that needs to be signed afterward. + */ + if (ctx->ksi[i]->nRecords > 0) { + ret = sigblkFinishKSINoSignature(ctx->ksi[i], reason); + if (ret != RSGTE_SUCCESS) return ret; + } + + /* Free files and remove object from the list. */ + rsksifileForceFree(ctx->ksi[i]); + ctx->ksi[i] = NULL; + } + } + + ctx->ksiCount = 0; + return RSGTE_SUCCESS; +} + +void +rsksiCtxDel(rsksictx ctx) { + if (ctx == NULL) + return; + + /* Note that even in sync. mode signer thread is created and needs to be closed + * correctly. + */ + if (ctx->signer_state == SIGNER_STARTED) { + queueAddQuit(ctx); + /* Wait until thread closes to be able to safely free the resources. */ + pthread_join(ctx->signer_thread, NULL); + ProtectedQueue_free(ctx->signer_queue); + pthread_mutex_destroy(&ctx->module_lock); + } + + free(ctx->aggregatorUri); + free(ctx->aggregatorId); + free(ctx->aggregatorKey); + free(ctx->debugFileName); + + if (ctx->random_source) + free(ctx->random_source); + + KSI_DataHasher_free(ctx->hasher); + KSI_CTX_free(ctx->ksi_ctx); + + if(ctx->debugFile!=NULL) + fclose(ctx->debugFile); + + /* After signer thread is terminated there should be no open signature files, + * but to be extra sure that all files are closed, recheck the list of opened + * signature files. + */ + rsksictxForceFreeSignatures(ctx); + free(ctx->ksi); + + free(ctx); +} + +/* new sigblk is initialized, but maybe in existing ctx */ +void +sigblkInitKSI(ksifile ksi) +{ + if(ksi == NULL) goto done; + seedIVKSI(ksi); + memset(ksi->roots, 0, sizeof (ksi->roots)); + ksi->nRoots = 0; + ksi->nRecords = 0; + ksi->bInBlk = 1; + ksi->blockStarted = time(NULL); //TODO: maybe milli/nanoseconds should be used + ksi->blockSizeLimit = 1 << (ksi->ctx->effectiveBlockLevelLimit - 1); + + /* flush the optional debug file when starting a new block */ + if(ksi->ctx->debugFile != NULL) + fflush(ksi->ctx->debugFile); + +done: + return; +} + +int +sigblkCreateMask(ksifile ksi, KSI_DataHash **m) { + int r = 0; + + CHKr(KSI_DataHasher_reset(ksi->ctx->hasher)); + CHKr(KSI_DataHasher_add(ksi->ctx->hasher, ksi->lastLeaf, KSI_getHashLength(ksi->lastLeaf[0]) + 1)); + CHKr(KSI_DataHasher_add(ksi->ctx->hasher, ksi->IV, KSI_getHashLength(ksi->hashAlg))); + CHKr(KSI_DataHasher_close(ksi->ctx->hasher, m)); + +done: + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHasher", r); + r = RSGTE_HASH_CREATE; + } + return r; +} +int +sigblkCreateHash(ksifile ksi, KSI_DataHash **out, const uchar *rec, const size_t len) { + int r = 0; + + CHKr(KSI_DataHasher_reset(ksi->ctx->hasher)); + CHKr(KSI_DataHasher_add(ksi->ctx->hasher, rec, len)); + CHKr(KSI_DataHasher_close(ksi->ctx->hasher, out)); + +done: + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHasher", r); + r = RSGTE_HASH_CREATE; + } + + return r; +} + + +int +sigblkHashTwoNodes(ksifile ksi, KSI_DataHash **out, KSI_DataHash *left, KSI_DataHash *right, + uint8_t level) { + int r = 0; + + CHKr(KSI_DataHasher_reset(ksi->ctx->hasher)); + CHKr(KSI_DataHasher_addImprint(ksi->ctx->hasher, left)); + CHKr(KSI_DataHasher_addImprint(ksi->ctx->hasher, right)); + CHKr(KSI_DataHasher_add(ksi->ctx->hasher, &level, 1)); + CHKr(KSI_DataHasher_close(ksi->ctx->hasher, out)); + +done: + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHash_create", r); + r = RSGTE_HASH_CREATE; + } + + return r; +} +int +sigblkAddMetadata(ksifile ksi, const char *key, const char *value) { + unsigned char buffer[0xFFFF]; + size_t encoded_size = 0; + int ret = 0; + + tlvCreateMetadata(ksi, ksi->nRecords, key, value, buffer, &encoded_size); + sigblkAddLeaf(ksi, buffer, encoded_size, true); + + return ret; +} + +int +sigblkAddRecordKSI(ksifile ksi, const uchar *rec, const size_t len) { + int ret = 0; + if (ksi == NULL || ksi->disabled) + return 0; + + pthread_mutex_lock(&ksi->ctx->module_lock); + + if ((ret = sigblkAddLeaf(ksi, rec, len, false)) != 0) + goto done; + + if (ksi->nRecords == ksi->blockSizeLimit) { + sigblkFinishKSI(ksi); + sigblkInitKSI(ksi); + } + +done: + pthread_mutex_unlock(&ksi->ctx->module_lock); + return ret; +} + + +int +sigblkAddLeaf(ksifile ksi, const uchar *leafData, const size_t leafLength, bool metadata) { + + KSI_DataHash *mask, *leafHash, *treeNode, *tmpTreeNode; + uint8_t j; + const unsigned char *pTmp; + size_t len; + + int r = 0; + + if (ksi == NULL || ksi->disabled) goto done; + CHKr(sigblkCreateMask(ksi, &mask)); + CHKr(sigblkCreateHash(ksi, &leafHash, leafData, leafLength)); + + if(ksi->nRecords == 0) + tlvWriteBlockHdrKSI(ksi); + + /* metadata record has to be written into the block file too*/ + if (metadata) + tlvWriteOctetString(ksi->blockFile, leafData, leafLength); + + if (ksi->bKeepRecordHashes) + tlvWriteHashKSI(ksi, 0x0902, leafHash); + + /* normal leaf and metadata record are hashed in different order */ + if (!metadata) { /* hash leaf */ + if ((r = sigblkHashTwoNodes(ksi, &treeNode, mask, leafHash, 1)) != 0) goto done; + } else { + if ((r = sigblkHashTwoNodes(ksi, &treeNode, leafHash, mask, 1)) != 0) goto done; + } + + /* persists x here if Merkle tree needs to be persisted! */ + if(ksi->bKeepTreeHashes) + tlvWriteHashKSI(ksi, 0x0903, treeNode); + + KSI_DataHash_getImprint(treeNode, &pTmp, &len); + memcpy(ksi->lastLeaf, pTmp, len); + + for(j = 0 ; j < ksi->nRoots ; ++j) { + if (ksi->roots[j] == NULL) { + ksi->roots[j] = treeNode; + treeNode = NULL; + break; + } else if (treeNode != NULL) { + /* hash interim node */ + tmpTreeNode = treeNode; + r = sigblkHashTwoNodes(ksi, &treeNode, ksi->roots[j], tmpTreeNode, j + 2); + KSI_DataHash_free(ksi->roots[j]); + ksi->roots[j] = NULL; + KSI_DataHash_free(tmpTreeNode); + if (r != 0) goto done; + if(ksi->bKeepTreeHashes) + tlvWriteHashKSI(ksi, 0x0903, treeNode); + } + } + if (treeNode != NULL) { + /* new level, append "at the top" */ + ksi->roots[ksi->nRoots] = treeNode; + ++ksi->nRoots; + assert(ksi->nRoots < MAX_ROOTS); + treeNode = NULL; + } + ++ksi->nRecords; + + /* cleanup (x is cleared as part of the roots array) */ + KSI_DataHash_free(mask); + KSI_DataHash_free(leafHash); + +done: + return r; +} + +static int +sigblkCheckTimeOut(rsksictx ctx) { + int ret = RSGTE_INTERNAL; + time_t now; + char buf[KSI_BUF_SIZE]; + size_t i = 0; + + if (ctx == NULL) { + return RSGTE_INTERNAL; + } + + pthread_mutex_lock(&ctx->module_lock); + + if (ctx->ksi == NULL || ctx->disabled || !ctx->blockTimeLimit) { + ret = RSGTE_SUCCESS; + goto done; + } + + now = time(NULL); + + for (i = 0; i < ctx->ksiCount; i++) { + ksifile ksi = ctx->ksi[i]; + + if (ksi == NULL) continue; /* To avoide unexpected crash. */ + if (!ksi->bInBlk) continue; /* Not inside a block, nothing to close nor sign. */ + if ((time_t) (ksi->blockStarted + ctx->blockTimeLimit) > now) continue; + + snprintf(buf, KSI_BUF_SIZE, "Block closed due to reaching time limit %d", ctx->blockTimeLimit); + sigblkAddMetadata(ksi, blockCloseReason, buf); + sigblkFinishKSI(ksi); + sigblkInitKSI(ksi); + } + +done: + pthread_mutex_unlock(&ctx->module_lock); + return ret; +} + + +static int +sigblkSign(ksifile ksi, KSI_DataHash *hash, int level) +{ + unsigned char *der = NULL; + size_t lenDer = 0; + int r = KSI_OK; + int ret = 0; + KSI_Signature *sig = NULL; + + /* Sign the root hash. */ + r = KSI_Signature_signAggregated(ksi->ctx->ksi_ctx, hash, level, &sig); + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "KSI_Signature_createAggregated", r); + ret = 1; + goto signing_done; + } + + /* Serialize Signature. */ + r = KSI_Signature_serialize(sig, &der, &lenDer); + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "KSI_Signature_serialize", r); + ret = 1; + lenDer = 0; + goto signing_done; + } + +signing_done: + /* if signing failed the signature will be written as zero size */ + if (r == KSI_OK) { + r = tlvWriteKSISigLS12(ksi->blockFile, ksi->nRecords, der, lenDer); + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteKSISigLS12", r); + ret = 1; + } + } else + r = tlvWriteNoSigLS12(ksi->blockFile, ksi->nRecords, hash, KSI_getErrorString(r)); + + if (r != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteBlockSigKSI", r); + ret = 1; + } + + if (sig != NULL) + KSI_Signature_free(sig); + if (der != NULL) + KSI_free(der); + return ret; +} + +unsigned +sigblkCalcLevel(unsigned leaves) { + unsigned level = 0; + unsigned c = leaves; + while (c > 1) { + level++; + c >>= 1; + } + + if (1 << level < (int)leaves) + level++; + + return level; +} + +static int +sigblkFinishTree(ksifile ksi, KSI_DataHash **hsh) { + int ret = RSGTE_INTERNAL; + KSI_DataHash *root = NULL; + KSI_DataHash *rootDel = NULL; + int8_t j = 0; + + if (ksi == NULL || hsh == NULL) { + goto done; + } + + if (ksi->nRecords == 0) { + ret = RSGTE_SUCCESS; + goto done; + } + + root = NULL; + for(j = 0 ; j < ksi->nRoots ; ++j) { + if(root == NULL) { + root = ksi->roots[j]; + ksi->roots[j] = NULL; + } else if (ksi->roots[j] != NULL) { + rootDel = root; + root = NULL; + ret = sigblkHashTwoNodes(ksi, &root, ksi->roots[j], rootDel, j + 2); + KSI_DataHash_free(ksi->roots[j]); + ksi->roots[j] = NULL; + KSI_DataHash_free(rootDel); + rootDel = NULL; + if(ksi->bKeepTreeHashes) { + tlvWriteHashKSI(ksi, 0x0903, root); + } + if(ret != KSI_OK) goto done; /* checks sigblkHashTwoNodes() result! */ + } + } + + *hsh = root; + root = NULL; + ret = RSGTE_SUCCESS; + +done: + KSI_DataHash_free(root); + KSI_DataHash_free(rootDel); + return ret; +} + + +int +sigblkFinishKSI(ksifile ksi) +{ + KSI_DataHash *root = NULL; + int ret = RSGTE_INTERNAL; + unsigned level = 0; + + if (ksi == NULL) { + goto done; + } + + if (ksi->nRecords == 0) { + ret = RSGTE_SUCCESS; + goto done; + } + + ret = sigblkFinishTree(ksi, &root); + if (ret != RSGTE_SUCCESS) goto done; + + //Multiplying leaves count by 2 to account for blinding masks + level=sigblkCalcLevel(2 * ksi->nRecords); + + //in case of async mode we append the root hash to signer queue + if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) { + ret = tlvWriteNoSigLS12(ksi->blockFile, ksi->nRecords, root, NULL); + if (ret != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteNoSigLS12", ret); + goto done; + } + + queueAddSignRequest(ksi->ctx, ksi, root, level); + root = NULL; + } else { + sigblkSign(ksi, root, level); + } + + ret = RSGTE_SUCCESS; + +done: + KSI_DataHash_free(root); + free(ksi->IV); + ksi->IV = NULL; + ksi->bInBlk = 0; + return ret; +} + +static int +sigblkFinishKSINoSignature(ksifile ksi, const char *reason) +{ + KSI_DataHash *root = NULL; + int ret = RSGTE_INTERNAL; + + if (ksi == NULL || ksi->ctx == NULL || + (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS && ksi->sigFile == NULL) || + ksi->blockFile == NULL || reason == NULL) { + goto done; + } + + ret = sigblkAddMetadata(ksi, blockCloseReason, reason); + if (ret != RSGTE_SUCCESS) goto done; + + ret = sigblkFinishTree(ksi, &root); + if (ret != RSGTE_SUCCESS) goto done; + + ret = tlvWriteNoSigLS12(ksi->blockFile, ksi->nRecords, root, reason); + if (ret != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteNoSigLS12", ret); + goto done; + } + + if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) { + ret = tlvWriteNoSigLS12(ksi->sigFile, ksi->nRecords, root, reason); + if (ret != KSI_OK) { + reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteNoSigLS12", ret); + goto done; + } + } + + ret = RSGTE_SUCCESS; + +done: + KSI_DataHash_free(root); + free(ksi->IV); + ksi->IV=NULL; + ksi->bInBlk = 0; + return ret; +} + +int +rsksiSetAggregator(rsksictx ctx, char *uri, char *loginid, char *key) { + int r; + char *strTmp, *strTmpUri; + + /* only use the strings if they are not empty */ + ctx->aggregatorUri = (uri != NULL && strlen(uri) != 0) ? strdup(uri) : NULL; + ctx->aggregatorId = (loginid != NULL && strlen(loginid) != 0) ? strdup(loginid) : NULL; + ctx->aggregatorKey = (key != NULL && strlen(key) != 0) ? strdup(key) : NULL; + + /* split the URI string up for possible HA endpoints */ + strTmp = ctx->aggregatorUri; + while((strTmpUri = strsep(&strTmp, "|") ) != NULL) { + if(ctx->aggregatorEndpointCount >= KSI_CTX_HA_MAX_SUBSERVICES) { + report(ctx, "Maximum number (%d) of service endoints reached, ignoring endpoint: %s", + KSI_CTX_HA_MAX_SUBSERVICES, strTmpUri); + } + else { + ctx->aggregatorEndpoints[ctx->aggregatorEndpointCount] = strTmpUri; + ctx->aggregatorEndpointCount++; + } + } + + r = KSI_CTX_setAggregator(ctx->ksi_ctx, ctx->aggregatorUri, ctx->aggregatorId, ctx->aggregatorKey); + if(r != KSI_OK) { + ctx->disabled = true; + reportKSIAPIErr(ctx, NULL, "KSI_CTX_setAggregator", r); + return KSI_INVALID_ARGUMENT; + } + + return r; +} + + +int +rsksiSetDebugFile(rsksictx ctx, char *val) { + if(!val) + return KSI_INVALID_ARGUMENT; + + ctx->debugFileName=strdup(val); + return KSI_OK; +} + +static bool +add_queue_item(rsksictx ctx, QITEM_type type, KSI_DataHash *root, FILE *sigFile, uint64_t intarg1, uint64_t intarg2) { + QueueItem *qi = (QueueItem*) malloc(sizeof (QueueItem)); + if (!qi) { + ctx->disabled = true; + return false; + } + + qi->root = root; + qi->file = sigFile; + + qi->type = type; + qi->status = QITEM_WAITING; + qi->intarg1 = intarg1; + qi->intarg2 = intarg2; + qi->respHandle = NULL; + qi->ksi_status = KSI_UNKNOWN_ERROR; + qi->request_time = time(NULL); + if (ProtectedQueue_addItem(ctx->signer_queue, qi) == false) { + ctx->disabled = true; + free(qi); + return false; + } + return true; +} + +static bool +queueAddCloseFile(rsksictx ctx, ksifile ksi) { + return add_queue_item(ctx, QITEM_CLOSE_FILE, NULL, ksi->sigFile, 0, 0); +} + +static bool +queueAddNewFile(rsksictx ctx, ksifile ksi) { + return add_queue_item(ctx, QITEM_NEW_FILE, NULL, ksi->sigFile, time(NULL) + ctx->blockTimeLimit, 0); +} + +static bool +queueAddQuit(rsksictx ctx) { + return add_queue_item(ctx, QITEM_QUIT, NULL, NULL, 0, 0); +} + +static bool +queueAddSignRequest(rsksictx ctx, ksifile ksi, KSI_DataHash *root, unsigned level) { + return add_queue_item(ctx, QITEM_SIGNATURE_REQUEST, root, ksi->sigFile, ksi->nRecords, level); +} + +static bool +save_response(rsksictx ctx, FILE* outfile, QueueItem *item) { + bool ret = false; + KSI_Signature *sig = NULL; + unsigned char *raw = NULL; + size_t raw_len; + int res = KSI_OK; + + if(item->respHandle != NULL && item->ksi_status == KSI_OK) { + CHECK_KSI_API(KSI_AsyncHandle_getSignature(item->respHandle, &sig), ctx, + "KSI_AsyncHandle_getSignature"); + CHECK_KSI_API(KSI_Signature_serialize(sig, &raw, &raw_len), ctx, + "KSI_Signature_serialize"); + tlvWriteKSISigLS12(outfile, item->intarg1, raw, raw_len); + KSI_free(raw); + } + else { + tlvWriteNoSigLS12(outfile, item->intarg1, item->root, KSI_getErrorString(item->ksi_status)); + } + ret = true; + +cleanup: + if(res != KSI_OK) + tlvWriteNoSigLS12(outfile, item->intarg1, item->root, KSI_getErrorString(res)); + + KSI_Signature_free(sig); + + return ret; +} + +static KSI_DataHash* clone_hash(KSI_CTX *ksi_ctx, const KSI_DataHash* hash) { + int res = KSI_UNKNOWN_ERROR; + const unsigned char *imprint = NULL; + size_t imprint_len = 0; + KSI_DataHash* tmp = NULL; + + if (hash == NULL) return NULL; + res = KSI_DataHash_getImprint(hash, &imprint, &imprint_len); + if (res != KSI_OK) return NULL; + res = KSI_DataHash_fromImprint(ksi_ctx, imprint, imprint_len, &tmp); + if (res != KSI_OK) return NULL; + + return tmp; +} + +static bool +process_requests_async(rsksictx ctx, KSI_CTX *ksi_ctx, KSI_AsyncService *as) { + bool ret = false; + QueueItem *item = NULL; + int res = KSI_OK, tmpRes; + KSI_AsyncHandle *reqHandle = NULL; + KSI_AsyncHandle *respHandle = NULL; + KSI_DataHash *clonedHash = NULL; + KSI_AggregationReq *req = NULL; + KSI_Config *config = NULL; + KSI_Integer *level; + long extError; + KSI_Utf8String *errorMsg; + int state, ksi_status; + unsigned i; + size_t p; + + KSI_AsyncService_getPendingCount(as, &p); + + /* Check if there are pending/available responses and associate them with the request items */ + while(true) { + respHandle = NULL; + item = NULL; + tmpRes=KSI_AsyncService_run(as, &respHandle, &p); + if(tmpRes!=KSI_OK) + reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_run", tmpRes); + + if (respHandle == NULL) { /* nothing received */ + break; + } + +#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22 + if (p != 0 && ctx->roundCount > 0) { + ctx->roundCount--; + } else { + ctx->bRoundLock = 0; + ctx->roundCount = 0; + } +#endif + state = KSI_ASYNC_STATE_UNDEFINED; + + CHECK_KSI_API(KSI_AsyncHandle_getState(respHandle, &state), ctx, "KSI_AsyncHandle_getState"); + + if(state == KSI_ASYNC_STATE_PUSH_CONFIG_RECEIVED) { + res = KSI_AsyncHandle_getConfig(respHandle, &config); + if(res == KSI_OK) { + handle_ksi_config(ctx, as, config); + KSI_AsyncHandle_free(respHandle); + } else + reportKSIAPIErr(ctx, NULL, "KSI_AsyncHandle_getConfig", res); + } + else if(state == KSI_ASYNC_STATE_RESPONSE_RECEIVED) { + CHECK_KSI_API(KSI_AsyncHandle_getRequestCtx(respHandle, (const void**)&item), ctx, + "KSI_AsyncHandle_getRequestCtx"); + item->respHandle = respHandle; + item->ksi_status = KSI_OK; + } + else if(state == KSI_ASYNC_STATE_ERROR) { + CHECK_KSI_API(KSI_AsyncHandle_getRequestCtx(respHandle, (const void**)&item), ctx, + "KSI_AsyncHandle_getRequestCtx"); + errorMsg = NULL; + KSI_AsyncHandle_getError(respHandle, &ksi_status); + KSI_AsyncHandle_getExtError(respHandle, &extError); + KSI_AsyncHandle_getErrorMessage(respHandle, &errorMsg); + report(ctx, "Asynchronous request returned error %s (%d), %lu %s", + KSI_getErrorString(ksi_status), ksi_status, extError, + errorMsg ? KSI_Utf8String_cstr(errorMsg) : ""); + KSI_AsyncHandle_free(respHandle); + + if(item) + item->ksi_status = ksi_status; + } + + if(item) + item->status = QITEM_DONE; + } + + KSI_AsyncService_getPendingCount(as, &p); + + /* Send all the new requests in the back of the queue to the server */ + for(i = 0; i < ProtectedQueue_count(ctx->signer_queue); i++) { + item = NULL; + if(!ProtectedQueue_getItem(ctx->signer_queue, i, (void**)&item) || !item) + continue; + /* ingore non request queue items */ + if(item->type != QITEM_SIGNATURE_REQUEST) + continue; + + /* stop at first processed item */ + if(item->status != QITEM_WAITING) + continue; + + /* Due to a bug in libksi it is possible that async signer may send out + * more signing requests than permitted by the gateway. Workaround is to + * keep track of signing requests here. + */ +#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22 + if (ctx->roundCount >= ctx->max_requests) ctx->bRoundLock = 1; + if (ctx->bRoundLock) break; +#endif + + + /* The data hash is produced in another thread by another KSI_CTX and + * libksi internal uses KSI_DataHash cache to reduce the amount of + * memory allocations by recycling old objects. Lets clone the hash + * value with current KSI_CTX as we can not be sure that this thread is + * not affecting the data hash cache operated by another thread. + */ + clonedHash = clone_hash(ksi_ctx, item->root); + CHECK_KSI_API(KSI_AggregationReq_new(ksi_ctx, &req), ctx, "KSI_AggregationReq_new"); + CHECK_KSI_API(KSI_AggregationReq_setRequestHash((KSI_AggregationReq*)req, + clonedHash), ctx, + "KSI_AggregationReq_setRequestHash"); + clonedHash = NULL; + CHECK_KSI_API(KSI_Integer_new(ksi_ctx, item->intarg2, &level), ctx, + "KSI_Integer_new"); + CHECK_KSI_API(KSI_AggregationReq_setRequestLevel(req, level), ctx, + "KSI_AggregationReq_setRequestLevel"); + CHECK_KSI_API(KSI_AsyncAggregationHandle_new(ksi_ctx, req, &reqHandle), ctx, + "KSI_AsyncAggregationHandle_new"); + CHECK_KSI_API(KSI_AsyncHandle_setRequestCtx(reqHandle, (void*)item, NULL), ctx, + "KSI_AsyncRequest_setRequestContext"); + res = KSI_AsyncService_addRequest(as, reqHandle); /* this can fail because of throttling */ + + if (res == KSI_OK) { + item->status = QITEM_SENT; +#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22 + ctx->roundCount++; +#endif + } else { + reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_addRequest", res); + KSI_AsyncHandle_free(reqHandle); + item->status = QITEM_DONE; + item->ksi_status = res; + break; + } + + if (i != 0 && i % ctx->max_requests == 0) { + CHECK_KSI_API(KSI_AsyncService_run(as, NULL, NULL), ctx, + "KSI_AsyncService_run"); + } + } + CHECK_KSI_API(KSI_AsyncService_run(as, NULL, NULL), ctx, + "KSI_AsyncService_run"); + + /* Save all consequent fulfilled responses in the front of the queue to the signature file */ + while(ProtectedQueue_count(ctx->signer_queue)) { + item = NULL; + if(!ProtectedQueue_getItem(ctx->signer_queue, 0, (void**)&item)) + break; + + if(!item) { + ProtectedQueue_popFront(ctx->signer_queue, (void**) &item); + continue; + } + + /* stop at first non request queue item (maybe file close/open, quit) */ + if(item->type!=QITEM_SIGNATURE_REQUEST) + break; + + /* stop at first unfinished queue item because the signatures need to be ordered */ + if(item->status != QITEM_DONE) + break; + + ProtectedQueue_popFront(ctx->signer_queue, (void**) &item); + save_response(ctx, item->file, item); + fflush(item->file); + /* the main thread has to be locked when the hash is freed to avoid a race condition */ + /* TODO: this need more elegant solution, hash should be detached from creation context*/ + pthread_mutex_lock(&ctx->module_lock); + KSI_DataHash_free(item->root); + KSI_AsyncHandle_free(item->respHandle); + free(item); + pthread_mutex_unlock(&ctx->module_lock); + } + + ret = true; + +cleanup: + KSI_DataHash_free(clonedHash); + KSI_AsyncService_getPendingCount(as, &p); + return ret; +} + +/* This can only be used when signer thread has terminated or within the thread. */ +static bool +rsksictxCloseAllPendingBlocksWithoutSignature(rsksictx ctx, const char *reason) { + bool ret = false; + QueueItem *item = NULL; + int res = KSI_OK; + + /* Save all consequent fulfilled responses in the front of the queue to the signature file */ + while(ProtectedQueue_count(ctx->signer_queue)) { + item = NULL; + ProtectedQueue_popFront(ctx->signer_queue, (void**) &item); + + if(item == NULL) { + continue; + } + + /* Skip non request queue item. */ + if(item->type == QITEM_SIGNATURE_REQUEST) { + res = tlvWriteNoSigLS12(item->file, item->intarg1, item->root, reason); + if (res != KSI_OK) { + reportKSIAPIErr(ctx, NULL, "tlvWriteNoSigLS12", res); + ret = false; + goto cleanup; + } + fflush(item->file); + } + + KSI_DataHash_free(item->root); + KSI_AsyncHandle_free(item->respHandle); + free(item); + } + + ret = true; + +cleanup: + return ret; +} + + +static void +request_async_config(rsksictx ctx, KSI_CTX *ksi_ctx, KSI_AsyncService *as) { + KSI_Config *cfg = NULL; + KSI_AsyncHandle *cfgHandle = NULL; + KSI_AggregationReq *cfgReq = NULL; + int res; + bool bSuccess = false; + + CHECK_KSI_API(KSI_AggregationReq_new(ksi_ctx, &cfgReq), ctx, "KSI_AggregationReq_new"); + CHECK_KSI_API(KSI_Config_new(ksi_ctx, &cfg), ctx, "KSI_Config_new"); + CHECK_KSI_API(KSI_AggregationReq_setConfig(cfgReq, cfg), ctx, "KSI_AggregationReq_setConfig"); + CHECK_KSI_API(KSI_AsyncAggregationHandle_new(ksi_ctx, cfgReq, &cfgHandle), ctx, + "KSI_AsyncAggregationHandle_new"); + CHECK_KSI_API(KSI_AsyncService_addRequest(as, cfgHandle), ctx, "KSI_AsyncService_addRequest"); + + bSuccess = true; + +cleanup: + if(!bSuccess) { + if(cfgHandle) + KSI_AsyncHandle_free(cfgHandle); + else if(cfgReq) + KSI_AggregationReq_free(cfgReq); + else if(cfg) + KSI_Config_free(cfg); + } +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +void *signer_thread(void *arg) { + int res = KSI_UNKNOWN_ERROR; + rsksictx ctx = (rsksictx) arg; + KSI_CTX *ksi_ctx = NULL; + KSI_AsyncService *as = NULL; + size_t size_t_value = 0; + size_t ksiFileCount = 0; + int endpoints = 0; + bool bSleep = true; + + + CHECK_KSI_API(KSI_CTX_new(&ksi_ctx), ctx, "KSI_CTX_new"); + CHECK_KSI_API(KSI_CTX_setAggregator(ksi_ctx, + ctx->aggregatorUri, ctx->aggregatorId, ctx->aggregatorKey), + ctx, "KSI_CTX_setAggregator"); + + if(ctx->debugFile) { + res = KSI_CTX_setLoggerCallback(ksi_ctx, rsksiStreamLogger, ctx->debugFile); + if (res != KSI_OK) + reportKSIAPIErr(ctx, NULL, "Unable to set logger callback", res); + res = KSI_CTX_setLogLevel(ksi_ctx, ctx->debugLevel); + if (res != KSI_OK) + reportKSIAPIErr(ctx, NULL, "Unable to set log level", res); + } + + CHECK_KSI_API(KSI_CTX_setOption(ksi_ctx, KSI_OPT_AGGR_HMAC_ALGORITHM, (void*)((size_t)ctx->hmacAlg)), + ctx, "KSI_CTX_setOption"); + + res = KSI_SigningHighAvailabilityService_new(ksi_ctx, &as); + if (res != KSI_OK) { + reportKSIAPIErr(ctx, NULL, "KSI_SigningAsyncService_new", res); + } + else { + int i = 0; + for (i = 0; i < ctx->aggregatorEndpointCount; i++) { + res = KSI_AsyncService_addEndpoint(as, + ctx->aggregatorEndpoints[i], ctx->aggregatorId, ctx->aggregatorKey); + if (res != KSI_OK) { + //This can fail if the protocol is not supported by async api. + reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_addEndpoint", res); + continue; + } + + endpoints++; + } + } + + if(endpoints == 0) { /* no endpoint accepted, deleting the service */ + report(ctx, "No endpoints added, signing service disabled"); + ctx->disabled = true; + KSI_AsyncService_free(as); + as=NULL; + goto cleanup; + } + + /* Lets use buffer value, as libksi requires size_t. */ + size_t_value = ctx->max_requests; + KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_REQUEST_CACHE_SIZE, + (void*)size_t_value); + size_t_value = ctx->blockSigTimeout; + KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_SND_TIMEOUT, + (void*)size_t_value); + + + ctx->signer_state = SIGNER_STARTED; + while (true) { + QueueItem *item = NULL; + + if (isAggrConfNeeded(ctx)) { + request_async_config(ctx, ksi_ctx, as); + } + + /* Wait for a work item or timeout*/ + if (bSleep) { + ProtectedQueue_waitForItem(ctx->signer_queue, NULL, ctx->threadSleepms); + } + bSleep = true; + + /* Check for block time limit. */ + sigblkCheckTimeOut(ctx); + + /* in case there are no items go around*/ + if (ProtectedQueue_count(ctx->signer_queue) == 0) { + process_requests_async(ctx, ksi_ctx, as); + continue; + } + + /* process signing requests only if there is an open signature file */ + if(ksiFileCount > 0) { + /* check for pending/unsent requests in asynchronous service */ + if(!process_requests_async(ctx, ksi_ctx, as)) { + // probably fatal error, disable signing, error should be already reported + ctx->disabled = true; + goto cleanup; + } + } + /* if there are sig. requests still in the front, then we have to start over*/ + if (ProtectedQueue_peekFront(ctx->signer_queue, (void**) &item) + && item->type == QITEM_SIGNATURE_REQUEST) + continue; + + /* Handle other types of work items */ + if (ProtectedQueue_popFront(ctx->signer_queue, (void**) &item) != 0) { + /* There is no point to sleep after processing non request type item + * as there is great possibility that next item can already be + * processed. */ + bSleep = false; + + if (item->type == QITEM_CLOSE_FILE) { + if (item->file) { + fclose(item->file); + item->file = NULL; + } + + if (ksiFileCount > 0) ksiFileCount--; + } else if (item->type == QITEM_NEW_FILE) { + ksiFileCount++; + } else if (item->type == QITEM_QUIT) { + free(item); + + /* Will look into work queue for pending KSI signatures and will output + * unsigned block marker instead of actual KSI signature to finalize this + * thread quickly. + */ + rsksictxCloseAllPendingBlocksWithoutSignature(ctx, + "Signing not finished due to sudden closure of lmsig_ksi-ls12 module."); + rsksictxForceCloseWithoutSig(ctx, + "Block closed due to sudden closure of lmsig_ksi-ls12 module."); + + goto cleanup; + } + + free(item); + } + } + +cleanup: + + KSI_AsyncService_free(as); + KSI_CTX_free(ksi_ctx); + ctx->signer_state = SIGNER_STOPPED; + + return NULL; +} +#pragma GCC diagnostic push |