summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac17
-rw-r--r--runtime/Makefile.am18
-rw-r--r--runtime/librsgt.c672
-rw-r--r--runtime/librsgt.h361
-rw-r--r--runtime/librsgt_read.c919
-rw-r--r--runtime/lmsig_gt.c216
-rw-r--r--runtime/lmsig_gt.h40
-rw-r--r--runtime/rsyslog.h1
-rw-r--r--runtime/sigprov.h37
-rw-r--r--tools/Makefile.am22
-rw-r--r--tools/logsigner.c159
-rw-r--r--tools/omfile.c144
-rw-r--r--tools/rsgtutil.c342
-rw-r--r--tools/rsgtutil.rst150
14 files changed, 3068 insertions, 30 deletions
diff --git a/configure.ac b/configure.ac
index 31b2ab8a..f444e5eb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -28,6 +28,7 @@ fi
AC_DISABLE_STATIC
AC_PROG_LIBTOOL
AC_CANONICAL_HOST
+AC_PATH_PROG([RST2MAN], [rst2man])
PKG_PROG_PKG_CONFIG
@@ -929,6 +930,21 @@ fi
AM_CONDITIONAL(ENABLE_RELP, test x$enable_relp = xyes)
+# GuardTime support
+AC_ARG_ENABLE(guardtime,
+ [AS_HELP_STRING([--enable-guardtime],[Enable GuardTime support @<:@default=no@:>@])],
+ [case "${enableval}" in
+ yes) enable_guardtime="yes" ;;
+ no) enable_guardtime="no" ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-guardtime) ;;
+ esac],
+ [enable_guardtime=no]
+)
+if test "x$enable_guardtime" = "xyes"; then
+ PKG_CHECK_MODULES(GUARDTIME, libgt >= 0.3.1)
+fi
+AM_CONDITIONAL(ENABLE_GUARDTIME, test x$enable_guardtime = xyes)
+
# RFC 3195 support
AC_ARG_ENABLE(rfc3195,
[AS_HELP_STRING([--enable-rfc3195],[Enable RFC3195 support @<:@default=no@:>@])],
@@ -1386,6 +1402,7 @@ echo " rsyslogd will be built: $enable_rsyslogd"
echo " GUI components will be built: $enable_gui"
echo " Unlimited select() support enabled: $enable_unlimited_select"
echo " uuid support enabled: $enable_uuid"
+echo " GuardTime signature support enabled: $enable_guardtime"
echo " anonymization support enabled: $enable_mmanon"
echo
echo "---{ input plugins }---"
diff --git a/runtime/Makefile.am b/runtime/Makefile.am
index fbc92d9c..c429394d 100644
--- a/runtime/Makefile.am
+++ b/runtime/Makefile.am
@@ -1,6 +1,6 @@
sbin_PROGRAMS =
man_MANS =
-noinst_LTLIBRARIES = librsyslog.la
+noinst_LTLIBRARIES = librsyslog.la librsgt.la
pkglib_LTLIBRARIES =
#pkglib_LTLIBRARIES = librsyslog.la
@@ -17,6 +17,7 @@ librsyslog_la_SOURCES = \
module-template.h \
im-helper.h \
obj-types.h \
+ sigprov.h \
nsd.h \
glbl.h \
glbl.c \
@@ -92,6 +93,7 @@ librsyslog_la_SOURCES = \
../template.h
# the files with ../ we need to work on - so that they either become part of the
# runtime or will no longer be needed. -- rgerhards, 2008-06-13
+#
if WITH_MODDIRS
librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/:$(moddirs)\" $(PTHREADS_CFLAGS) $(LIBEE_CFLAGS) -I\$(top_srcdir)/tools
@@ -172,6 +174,20 @@ lmnsd_gtls_la_LDFLAGS = -module -avoid-version
lmnsd_gtls_la_LIBADD = $(GNUTLS_LIBS)
endif
+#
+# support library for guardtime
+#
+if ENABLE_GUARDTIME
+ librsgt_la_SOURCES = librsgt.c librsgt_read.c
+
+ pkglib_LTLIBRARIES += lmsig_gt.la
+ lmsig_gt_la_SOURCES = lmsig_gt.c
+ lmsig_gt_la_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS)
+ lmsig_gt_la_LDFLAGS = -module -avoid-version
+ lmsig_gt_la_LIBADD = librsgt.la $(GUARDTIME_LIBS)
+endif
+
+
update-systemd:
curl http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.c > sd-daemon.c
curl http://cgit.freedesktop.org/systemd/plain/src/sd-daemon.h > sd-daemon.h
diff --git a/runtime/librsgt.c b/runtime/librsgt.c
new file mode 100644
index 00000000..e24c3769
--- /dev/null
+++ b/runtime/librsgt.c
@@ -0,0 +1,672 @@
+/* librsgt.c - rsyslog's guardtime support library
+ *
+ * Regarding the online algorithm for Merkle tree signing. Expected
+ * calling sequence is:
+ *
+ * sigblkConstruct
+ * for each signature block:
+ * sigblkInit
+ * for each record:
+ * sigblkAddRecord
+ * sigblkFinish
+ * sigblkDestruct
+ *
+ * Obviously, the next call after sigblkFinsh must either be to
+ * sigblkInit 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 Adiscon GmbH.
+ *
+ * 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>
+#define MAXFNAME 1024
+
+#include <gt_http.h>
+
+#include "librsgt.h"
+
+typedef unsigned char uchar;
+#ifndef VERSION
+#define VERSION "no-version"
+#endif
+
+
+void
+rsgtInit(char *usragent)
+{
+ int ret = GT_OK;
+
+ ret = GT_init();
+ if(ret != GT_OK) {
+ fprintf(stderr, "GT_init() failed: %d (%s)\n",
+ ret, GT_getErrorString(ret));
+ goto done;
+ }
+ ret = GTHTTP_init(usragent, 1);
+ if(ret != GT_OK) {
+ fprintf(stderr, "GTHTTP_init() failed: %d (%s)\n",
+ ret, GTHTTP_getErrorString(ret));
+ goto done;
+ }
+done: return;
+}
+
+void
+rsgtExit(void)
+{
+ GTHTTP_finalize();
+ GT_finalize();
+}
+
+
+static inline gtfile
+rsgtfileConstruct(gtctx ctx)
+{
+ gtfile gf;
+ if((gf = calloc(1, sizeof(struct gtfile_s))) == NULL)
+ goto done;
+ gf->ctx = ctx;
+ gf->hashAlg = ctx->hashAlg;
+ gf->bKeepRecordHashes = ctx->bKeepRecordHashes;
+ gf->bKeepTreeHashes = ctx->bKeepTreeHashes;
+ gf->x_prev = NULL;
+
+done: return gf;
+}
+
+static inline void
+tlvbufPhysWrite(gtfile gf)
+{
+ ssize_t lenBuf;
+ ssize_t iTotalWritten;
+ ssize_t iWritten;
+ char *pWriteBuf;
+
+ lenBuf = gf->tlvIdx;
+ pWriteBuf = gf->tlvBuf;
+ iTotalWritten = 0;
+ do {
+ iWritten = write(gf->fd, pWriteBuf, lenBuf);
+ if(iWritten < 0) {
+ //char errStr[1024];
+ int err = errno;
+ iWritten = 0; /* we have written NO bytes! */
+ /* rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGPRINTF("log file (%d) write error %d: %s\n", pThis->fd, err, errStr);
+ */
+ if(err == EINTR) {
+ /*NO ERROR, just continue */;
+ } else {
+ goto finalize_it; //ABORT_FINALIZE(RS_RET_IO_ERROR);
+ /* FIXME: flag error */
+ }
+ }
+ /* advance buffer to next write position */
+ iTotalWritten += iWritten;
+ lenBuf -= iWritten;
+ pWriteBuf += iWritten;
+ } while(lenBuf > 0); /* Warning: do..while()! */
+
+finalize_it:
+ gf->tlvIdx = 0;
+}
+
+static inline void
+tlvbufChkWrite(gtfile gf)
+{
+ if(gf->tlvIdx == sizeof(gf->tlvBuf)) {
+ tlvbufPhysWrite(gf);
+ }
+}
+
+
+/* write to TLV file buffer. If buffer is full, an actual call occurs. Else
+ * output is written only on flush or close.
+ */
+static inline void
+tlvbufAddOctet(gtfile gf, int8_t octet)
+{
+ tlvbufChkWrite(gf);
+ gf->tlvBuf[gf->tlvIdx++] = octet;
+}
+static inline void
+tlvbufAddOctetString(gtfile gf, uint8_t *octet, int size)
+{
+ int i;
+ for(i = 0 ; i < size ; ++i)
+ tlvbufAddOctet(gf, octet[i]);
+}
+/* return the actual length in to-be-written octets of an integer */
+static inline uint8_t
+tlvbufGetInt64OctetSize(uint64_t val)
+{
+ if(val >> 56)
+ return 8;
+ if((val >> 48) & 0xff)
+ return 7;
+ if((val >> 40) & 0xff)
+ return 6;
+ if((val >> 32) & 0xff)
+ return 5;
+ if((val >> 24) & 0xff)
+ return 4;
+ if((val >> 16) & 0xff)
+ return 3;
+ if((val >> 8) & 0xff)
+ return 2;
+ return 1;
+}
+static inline void
+tlvbufAddInt64(gtfile gf, uint64_t val)
+{
+ uint8_t doWrite = 0;
+ if(val >> 56)
+ tlvbufAddOctet(gf, (val >> 56) & 0xff), doWrite = 1;
+ if(doWrite || ((val >> 48) & 0xff))
+ tlvbufAddOctet(gf, (val >> 48) & 0xff), doWrite = 1;
+ if(doWrite || ((val >> 40) & 0xff))
+ tlvbufAddOctet(gf, (val >> 40) & 0xff), doWrite = 1;
+ if(doWrite || ((val >> 32) & 0xff))
+ tlvbufAddOctet(gf, (val >> 32) & 0xff), doWrite = 1;
+ if(doWrite || ((val >> 24) & 0xff))
+ tlvbufAddOctet(gf, (val >> 24) & 0xff), doWrite = 1;
+ if(doWrite || ((val >> 16) & 0xff))
+ tlvbufAddOctet(gf, (val >> 16) & 0xff), doWrite = 1;
+ if(doWrite || ((val >> 8) & 0xff))
+ tlvbufAddOctet(gf, (val >> 8) & 0xff), doWrite = 1;
+ tlvbufAddOctet(gf, val & 0xff);
+}
+
+
+void
+tlv8Write(gtfile gf, int flags, int tlvtype, int len)
+{
+ tlvbufAddOctet(gf, (flags << 5)|tlvtype);
+ tlvbufAddOctet(gf, len & 0xff);
+}
+
+void
+tlv16Write(gtfile gf, int flags, int tlvtype, uint16_t len)
+{
+ uint16_t typ;
+ typ = ((flags|1) << 13)|tlvtype;
+ tlvbufAddOctet(gf, typ >> 8);
+ tlvbufAddOctet(gf, typ & 0xff);
+ tlvbufAddOctet(gf, (len >> 8) & 0xff);
+ tlvbufAddOctet(gf, len & 0xff);
+}
+
+void
+tlvFlush(gtfile gf)
+{
+ if(gf->tlvIdx != 0)
+ tlvbufPhysWrite(gf);
+}
+
+void
+tlvWriteHash(gtfile gf, uint16_t tlvtype, GTDataHash *r)
+{
+ unsigned tlvlen;
+
+ tlvlen = 1 + r->digest_length;
+ tlv16Write(gf, 0x00, tlvtype, tlvlen);
+ tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg));
+ tlvbufAddOctetString(gf, r->digest, r->digest_length);
+}
+
+void
+tlvWriteBlockSig(gtfile gf, uchar *der, uint16_t lenDer)
+{
+ unsigned tlvlen;
+ uint8_t tlvlenRecords;
+
+ tlvlenRecords = tlvbufGetInt64OctetSize(gf->nRecords);
+ tlvlen = 2 + 1 /* hash algo TLV */ +
+ 2 + hashOutputLengthOctets(gf->hashAlg) /* iv */ +
+ 2 + 1 + gf->lenBlkStrtHash /* last hash */ +
+ 2 + tlvlenRecords /* rec-count */ +
+ 4 + lenDer /* rfc-3161 */;
+ /* write top-level TLV object (block-sig */
+ tlv16Write(gf, 0x00, 0x0902, tlvlen);
+ /* and now write the children */
+ //FIXME: flags???
+ /* hash-algo */
+ tlv8Write(gf, 0x00, 0x00, 1);
+ tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg));
+ /* block-iv */
+ tlv8Write(gf, 0x00, 0x01, hashOutputLengthOctets(gf->hashAlg));
+ tlvbufAddOctetString(gf, gf->IV, hashOutputLengthOctets(gf->hashAlg));
+ /* last-hash */
+ tlv8Write(gf, 0x00, 0x02, gf->lenBlkStrtHash+1);
+ tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg));
+ tlvbufAddOctetString(gf, gf->blkStrtHash, gf->lenBlkStrtHash);
+ /* rec-count */
+ tlv8Write(gf, 0x00, 0x03, tlvlenRecords);
+ tlvbufAddInt64(gf, gf->nRecords);
+ /* rfc-3161 */
+ tlv16Write(gf, 0x00, 0x906, lenDer);
+ tlvbufAddOctetString(gf, der, lenDer);
+}
+
+/* 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 void
+readStateFile(gtfile gf)
+{
+ int fd;
+ struct rsgtstatefile sf;
+
+ fd = open((char*)gf->statefilename, O_RDONLY|O_NOCTTY|O_CLOEXEC, 0600);
+ if(fd == -1) goto err;
+
+ if(read(fd, &sf, sizeof(sf)) != sizeof(sf)) goto err;
+ if(strncmp(sf.hdr, "GTSTAT10", 8)) goto err;
+
+ gf->lenBlkStrtHash = sf.lenHash;
+ gf->blkStrtHash = calloc(1, gf->lenBlkStrtHash);
+ if(read(fd, gf->blkStrtHash, gf->lenBlkStrtHash)
+ != gf->lenBlkStrtHash) {
+ free(gf->blkStrtHash);
+ goto err;
+ }
+return;
+
+err:
+ gf->lenBlkStrtHash = hashOutputLengthOctets(gf->hashAlg);
+ gf->blkStrtHash = calloc(1, gf->lenBlkStrtHash);
+}
+
+/* persist all information that we need to re-open and append
+ * to a log signature file.
+ */
+static void
+writeStateFile(gtfile gf)
+{
+ int fd;
+ struct rsgtstatefile sf;
+
+ fd = open((char*)gf->statefilename,
+ O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0600);
+ if(fd == -1)
+ goto done;
+
+ memcpy(sf.hdr, "GTSTAT10", 8);
+ sf.hashID = hashIdentifier(gf->hashAlg);
+ sf.lenHash = gf->x_prev->digest_length;
+ /* 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, gf->x_prev->digest, gf->x_prev->digest_length)){};
+ close(fd);
+done: return;
+}
+
+
+void tlvClose(gtfile gf)
+{
+ tlvFlush(gf);
+ close(gf->fd);
+ gf->fd = -1;
+ writeStateFile(gf);
+}
+
+
+/* note: if file exists, the last hash for chaining must
+ * be read from file.
+ */
+void tlvOpen(gtfile gf, char *hdr, unsigned lenHdr)
+{
+ gf->fd = open((char*)gf->sigfilename,
+ O_WRONLY|O_APPEND|O_NOCTTY|O_CLOEXEC, 0600);
+ if(gf->fd == -1) {
+ /* looks like we need to create a new file */
+ gf->fd = open((char*)gf->sigfilename,
+ O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600);
+ // FIXME: check fd == -1
+ memcpy(gf->tlvBuf, hdr, lenHdr);
+ gf->tlvIdx = lenHdr;
+ } else {
+ gf->tlvIdx = 0; /* header already present! */
+ }
+ /* we now need to obtain the last previous hash, so that
+ * we can continue the hash chain.
+ */
+ readStateFile(gf);
+}
+
+/*
+ * 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
+ */
+void
+seedIV(gtfile gf)
+{
+ int hashlen;
+ int fd;
+
+ hashlen = hashOutputLengthOctets(gf->hashAlg);
+ gf->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("/dev/urandom", O_RDONLY)) > 0) {
+ if(read(fd, gf->IV, hashlen)) {}; /* keep compiler happy */
+ close(fd);
+ }
+}
+
+gtctx
+rsgtCtxNew(void)
+{
+ gtctx ctx;
+ ctx = calloc(1, sizeof(struct gtctx_s));
+ ctx->hashAlg = GT_HASHALG_SHA256;
+ ctx->timestamper = strdup(
+ "http://stamper.guardtime.net/gt-signingservice");
+ return ctx;
+}
+
+/* either returns gtfile object or NULL if something went wrong */
+gtfile
+rsgtCtxOpenFile(gtctx ctx, unsigned char *logfn)
+{
+ gtfile gf;
+ char fn[MAXFNAME+1];
+
+ if((gf = rsgtfileConstruct(ctx)) == NULL)
+ goto done;
+
+ snprintf(fn, sizeof(fn), "%s.gtsig", logfn);
+ fn[MAXFNAME] = '\0'; /* be on save side */
+ gf->sigfilename = (uchar*) strdup(fn);
+ snprintf(fn, sizeof(fn), "%s.gtstate", logfn);
+ fn[MAXFNAME] = '\0'; /* be on save side */
+ gf->statefilename = (uchar*) strdup(fn);
+ tlvOpen(gf, LOGSIGHDR, sizeof(LOGSIGHDR)-1);
+done: return gf;
+}
+
+
+/* returns 0 on succes, 1 if algo is unknown */
+int
+rsgtSetHashFunction(gtctx ctx, char *algName)
+{
+ int r = 0;
+ if(!strcmp(algName, "SHA2-256"))
+ ctx->hashAlg = GT_HASHALG_SHA256;
+ else if(!strcmp(algName, "SHA2-384"))
+ ctx->hashAlg = GT_HASHALG_SHA384;
+ else if(!strcmp(algName, "SHA2-512"))
+ ctx->hashAlg = GT_HASHALG_SHA512;
+ else if(!strcmp(algName, "SHA1"))
+ ctx->hashAlg = GT_HASHALG_SHA1;
+ else if(!strcmp(algName, "RIPEMD-160"))
+ ctx->hashAlg = GT_HASHALG_RIPEMD160;
+ else if(!strcmp(algName, "SHA2-224"))
+ ctx->hashAlg = GT_HASHALG_SHA224;
+ else
+ r = 1;
+ return r;
+}
+
+void
+rsgtfileDestruct(gtfile gf)
+{
+ if(gf == NULL)
+ goto done;
+
+ if(gf->bInBlk)
+ sigblkFinish(gf);
+ tlvClose(gf);
+ free(gf->sigfilename);
+ free(gf);
+done: return;
+}
+
+void
+rsgtCtxDel(gtctx ctx)
+{
+ if(ctx != NULL)
+ free(ctx);
+}
+
+/* new sigblk is initialized, but maybe in existing ctx */
+void
+sigblkInit(gtfile gf)
+{
+ seedIV(gf);
+ memset(gf->roots_valid, 0, sizeof(gf->roots_valid)/sizeof(char));
+ gf->nRoots = 0;
+ gf->nRecords = 0;
+ gf->bInBlk = 1;
+}
+
+
+/* concat: add IV to buffer */
+static inline void
+bufAddIV(gtfile gf, uchar *buf, size_t *len)
+{
+ memcpy(buf+*len, gf->IV, hashOutputLengthOctets(gf->hashAlg));
+ *len += sizeof(gf->IV);
+}
+
+
+/* concat: add hash to buffer */
+static inline void
+bufAddHash(gtfile gf, uchar *buf, size_t *len, GTDataHash *hash)
+{
+ if(hash == NULL) {
+ // TODO: how to get the REAL HASH ID? --> add field!
+ buf[*len] = hashIdentifier(gf->hashAlg);
+ ++(*len);
+ memcpy(buf+*len, gf->blkStrtHash, gf->lenBlkStrtHash);
+ *len += gf->lenBlkStrtHash;
+ } else {
+ buf[*len] = hashIdentifier(gf->hashAlg);
+ ++(*len);
+ memcpy(buf+*len, hash->digest, hash->digest_length);
+ *len += hash->digest_length;
+ }
+}
+/* concat: add tree level to buffer */
+static inline void
+bufAddLevel(uchar *buf, size_t *len, uint8_t level)
+{
+ memcpy(buf+*len, &level, sizeof(level));
+ *len += sizeof(level);
+}
+
+
+void
+hash_m(gtfile gf, GTDataHash **m)
+{
+#warning Overall: check GT API return states!
+ // m = hash(concat(gf->x_prev, IV));
+ uchar concatBuf[16*1024];
+ size_t len = 0;
+
+ bufAddHash(gf, concatBuf, &len, gf->x_prev);
+ bufAddIV(gf, concatBuf, &len);
+ GTDataHash_create(gf->hashAlg, concatBuf, len, m);
+}
+
+void
+hash_r(gtfile gf, GTDataHash **r, const uchar *rec, const size_t len)
+{
+ // r = hash(canonicalize(rec));
+ GTDataHash_create(gf->hashAlg, rec, len, r);
+}
+
+
+void
+hash_node(gtfile gf, GTDataHash **node, GTDataHash *m, GTDataHash *r,
+ uint8_t level)
+{
+ // x = hash(concat(m, r, 0)); /* hash leaf */
+ uchar concatBuf[16*1024];
+ size_t len = 0;
+
+ bufAddHash(gf, concatBuf, &len, m);
+ bufAddHash(gf, concatBuf, &len, r);
+ bufAddLevel(concatBuf, &len, level);
+ GTDataHash_create(gf->hashAlg, concatBuf, len, node);
+}
+
+void
+sigblkAddRecord(gtfile gf, const uchar *rec, const size_t len)
+{
+ GTDataHash *x; /* current hash */
+ GTDataHash *m, *r, *t;
+ uint8_t j;
+
+ hash_m(gf, &m);
+ hash_r(gf, &r, rec, len);
+ if(gf->bKeepRecordHashes)
+ tlvWriteHash(gf, 0x0900, r);
+ hash_node(gf, &x, m, r, 1); /* hash leaf */
+ /* persists x here if Merkle tree needs to be persisted! */
+ if(gf->bKeepTreeHashes)
+ tlvWriteHash(gf, 0x0901, x);
+ /* add x to the forest as new leaf, update roots list */
+ t = x;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(gf->roots_valid[j] == 0) {
+ gf->roots_hash[j] = t;
+ gf->roots_valid[j] = 1;
+ t = NULL;
+ break;
+ } else if(t != NULL) {
+ /* hash interim node */
+ hash_node(gf, &t, gf->roots_hash[j], t, j+2);
+ gf->roots_valid[j] = 0;
+ GTDataHash_free(gf->roots_hash[j]);
+ // TODO: check if this is correct location (paper!)
+ if(gf->bKeepTreeHashes)
+ tlvWriteHash(gf, 0x0901, t);
+ }
+ }
+ if(t != NULL) {
+ /* new level, append "at the top" */
+ gf->roots_hash[gf->nRoots] = t;
+ gf->roots_valid[gf->nRoots] = 1;
+ ++gf->nRoots;
+ assert(gf->nRoots < MAX_ROOTS);
+ t = NULL;
+ }
+ gf->x_prev = x; /* single var may be sufficient */
+ ++gf->nRecords;
+
+ /* cleanup */
+ /* note: x is freed later as part of roots cleanup */
+ GTDataHash_free(m);
+ GTDataHash_free(r);
+
+ if(gf->nRecords == gf->blockSizeLimit) {
+ sigblkFinish(gf);
+ sigblkInit(gf);
+ }
+}
+
+static void
+timestampIt(gtfile gf, GTDataHash *hash)
+{
+ unsigned char *der;
+ size_t lenDer;
+ int r = GT_OK;
+ GTTimestamp *timestamp = NULL;
+
+ /* Get the timestamp. */
+ r = GTHTTP_createTimestampHash(hash, gf->ctx->timestamper, &timestamp);
+
+ if(r != GT_OK) {
+ fprintf(stderr, "GTHTTP_createTimestampHash() failed: %d (%s)\n",
+ r, GTHTTP_getErrorString(r));
+ goto done;
+ }
+
+ /* Encode timestamp. */
+ r = GTTimestamp_getDEREncoded(timestamp, &der, &lenDer);
+ if(r != GT_OK) {
+ fprintf(stderr, "GTTimestamp_getDEREncoded() failed: %d (%s)\n",
+ r, GT_getErrorString(r));
+ goto done;
+ }
+
+ tlvWriteBlockSig(gf, der, lenDer);
+
+done:
+ GT_free(der);
+ GTTimestamp_free(timestamp);
+}
+
+
+void
+sigblkFinish(gtfile gf)
+{
+ GTDataHash *root, *rootDel;
+ int8_t j;
+
+ if(gf->nRecords == 0)
+ goto done;
+
+ root = NULL;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(root == NULL) {
+ root = gf->roots_valid[j] ? gf->roots_hash[j] : NULL;
+ gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */
+ } else if(gf->roots_valid[j]) {
+ rootDel = root;
+ hash_node(gf, &root, gf->roots_hash[j], root, j+2);
+ gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */
+ GTDataHash_free(rootDel);
+ }
+ }
+ timestampIt(gf, root);
+
+ free(gf->blkStrtHash);
+ gf->lenBlkStrtHash = gf->x_prev->digest_length;
+ gf->blkStrtHash = malloc(gf->lenBlkStrtHash);
+ memcpy(gf->blkStrtHash, gf->x_prev->digest, gf->lenBlkStrtHash);
+done:
+ gf->bInBlk = 0;
+}
diff --git a/runtime/librsgt.h b/runtime/librsgt.h
new file mode 100644
index 00000000..98384bb9
--- /dev/null
+++ b/runtime/librsgt.h
@@ -0,0 +1,361 @@
+/* librsgt.h - rsyslog's guardtime support library
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * 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.
+ */
+#ifndef INCLUDED_LIBRSGT_H
+#define INCLUDED_LIBRSGT_H
+#include <gt_base.h>
+
+/* Max number of roots inside the forest. This permits blocks of up to
+ * 2^MAX_ROOTS records. We assume that 64 is sufficient for all use
+ * cases ;) [and 64 is not really a waste of memory, so we do not even
+ * try to work with reallocs and such...]
+ */
+#define MAX_ROOTS 64
+#define LOGSIGHDR "LOGSIG10"
+
+/* context for gt calls. This primarily serves as a container for the
+ * config settings. The actual file-specific data is kept in gtfile.
+ */
+struct gtctx_s {
+ enum GTHashAlgorithm hashAlg;
+ uint8_t bKeepRecordHashes;
+ uint8_t bKeepTreeHashes;
+ uint64_t blockSizeLimit;
+ char *timestamper;
+};
+typedef struct gtctx_s *gtctx;
+
+/* this describes a file, as far as librsgt is concerned */
+struct gtfile_s {
+ gtctx ctx;
+ /* the following data items are mirrored from gtctx to
+ * increase cache hit ratio (they are frequently accesed).
+ */
+ enum GTHashAlgorithm hashAlg;
+ uint8_t bKeepRecordHashes;
+ uint8_t bKeepTreeHashes;
+ /* end mirrored properties */
+ uint64_t blockSizeLimit;
+ uint8_t *IV; /* initial value for blinding masks */
+ GTDataHash *x_prev; /* last leaf hash (maybe of previous block) --> preserve on term */
+ unsigned char *sigfilename;
+ unsigned char *statefilename;
+ int fd;
+ unsigned char *blkStrtHash; /* last hash from previous block */
+ uint16_t lenBlkStrtHash;
+ uint64_t nRecords; /* current number of records in current block */
+ uint64_t bInBlk; /* are we currently inside a blk --> need to finish on close */
+ int8_t nRoots;
+ /* algo engineering: roots structure is split into two arrays
+ * in order to improve cache hits.
+ */
+ int8_t roots_valid[MAX_ROOTS];
+ GTDataHash *roots_hash[MAX_ROOTS];
+ /* data members for the associated TLV file */
+ char tlvBuf[4096];
+ int tlvIdx; /* current index into tlvBuf */
+};
+typedef struct gtfile_s *gtfile;
+typedef struct gterrctx_s gterrctx_t;
+typedef struct imprint_s imprint_t;
+typedef struct block_sig_s block_sig_t;
+
+/* The following structure describes the "error context" to be used
+ * for verification and similiar reader functions. While verifying,
+ * we need some information (like filenames or block numbers) that
+ * is not readily available from the other objects (or not even known
+ * to librsgt). In order to provide meaningful error messages, this
+ * information must be passed in from the external callers. In order
+ * to centralize information (and make it more manageable), we use
+ * ths error context here, which contains everything needed to
+ * generate good error messages. Members of this structure are
+ * maintained both by library users (the callers) as well as
+ * the library itself. Who does what simply depends on who has
+ * the relevant information.
+ */
+struct gterrctx_s {
+ FILE *fp; /**< file for error messages */
+ char *filename;
+ uint8_t verbose;
+ uint64_t recNumInFile;
+ uint64_t recNum;
+ uint64_t blkNum;
+ uint8_t treeLevel;
+ GTDataHash *computedHash;
+ GTDataHash *lefthash, *righthash; /* hashes to display if tree hash fails */
+ imprint_t *fileHash;
+ int gtstate; /* status from last relevant GT.*() function call */
+ char *errRec;
+ char *frstRecInBlk; /* This holds the first message seen inside the current block */
+};
+
+struct imprint_s {
+ uint8_t hashID;
+ int len;
+ uint8_t *data;
+};
+
+#define SIGID_RFC3161 0
+struct block_sig_s {
+ uint8_t hashID;
+ uint8_t sigID; /* what type of *signature*? */
+ uint8_t *iv;
+ imprint_t lastHash;
+ uint64_t recCount;
+ struct {
+ struct {
+ uint8_t *data;
+ size_t len; /* must be size_t due to GT API! */
+ } der;
+ } sig;
+};
+
+
+/* the following defines the gtstate file record. Currently, this record
+ * is fixed, we may change that over time.
+ */
+struct rsgtstatefile {
+ char hdr[8]; /* must be "GTSTAT10" */
+ uint8_t hashID;
+ uint8_t lenHash;
+ /* after that, the hash value is contained within the file */
+};
+
+/* error states */
+#define RSGTE_IO 1 /* any kind of io error */
+#define RSGTE_FMT 2 /* data fromat error */
+#define RSGTE_INVLTYP 3 /* invalid TLV type record (unexcpected at this point) */
+#define RSGTE_OOM 4 /* ran out of memory */
+#define RSGTE_LEN 5 /* error related to length records */
+// 6 may be reused!
+#define RSGTE_INVLD_RECCNT 7/* mismatch between actual records and records
+ given in block-sig record */
+#define RSGTE_INVLHDR 8/* invalid file header */
+#define RSGTE_EOF 9 /* specific EOF */
+#define RSGTE_MISS_REC_HASH 10 /* record hash missing when expected */
+#define RSGTE_MISS_TREE_HASH 11 /* tree hash missing when expected */
+#define RSGTE_INVLD_REC_HASH 12 /* invalid record hash (failed verification) */
+#define RSGTE_INVLD_TREE_HASH 13 /* invalid tree hash (failed verification) */
+#define RSGTE_INVLD_REC_HASHID 14 /* invalid record hash ID (failed verification) */
+#define RSGTE_INVLD_TREE_HASHID 15 /* invalid tree hash ID (failed verification) */
+#define RSGTE_MISS_BLOCKSIG 16 /* block signature record missing when expected */
+#define RSGTE_INVLD_TIMESTAMP 17 /* RFC3161 timestamp is invalid */
+#define RSGTE_TS_DERDECODE 18 /* error DER-Decoding a timestamp */
+
+/* the following function maps RSGTE_* state to a string - must be updated
+ * whenever a new state is added.
+ * Note: it is thread-safe to call this function, as it returns a pointer
+ * into constant memory pool.
+ */
+static inline char *
+RSGTE2String(int err)
+{
+ switch(err) {
+ case 0:
+ return "success";
+ case RSGTE_IO:
+ return "i/o error";
+ case RSGTE_FMT:
+ return "data format error";
+ case RSGTE_INVLTYP:
+ return "invalid/unexpected tlv record type";
+ case RSGTE_OOM:
+ return "out of memory";
+ case RSGTE_LEN:
+ return "length record problem";
+ case RSGTE_INVLD_RECCNT:
+ return "mismatch between actual record count and number in block signature record";
+ case RSGTE_INVLHDR:
+ return "invalid file header";
+ case RSGTE_EOF:
+ return "EOF";
+ case RSGTE_MISS_REC_HASH:
+ return "record hash missing";
+ case RSGTE_MISS_TREE_HASH:
+ return "tree hash missing";
+ case RSGTE_INVLD_REC_HASH:
+ return "record hash mismatch";
+ case RSGTE_INVLD_TREE_HASH:
+ return "tree hash mismatch";
+ case RSGTE_INVLD_REC_HASHID:
+ return "invalid record hash ID";
+ case RSGTE_INVLD_TREE_HASHID:
+ return "invalid tree hash ID";
+ case RSGTE_MISS_BLOCKSIG:
+ return "missing block signature record";
+ case RSGTE_INVLD_TIMESTAMP:
+ return "RFC3161 timestamp invalid";
+ case RSGTE_TS_DERDECODE:
+ return "error DER-decoding RFC3161 timestamp";
+ default:
+ return "unknown error";
+ }
+}
+
+
+static inline uint16_t
+hashOutputLengthOctets(uint8_t hashID)
+{
+ switch(hashID) {
+ case GT_HASHALG_SHA1: /* paper: SHA1 */
+ return 20;
+ case GT_HASHALG_RIPEMD160: /* paper: RIPEMD-160 */
+ return 20;
+ case GT_HASHALG_SHA224: /* paper: SHA2-224 */
+ return 28;
+ case GT_HASHALG_SHA256: /* paper: SHA2-256 */
+ return 32;
+ case GT_HASHALG_SHA384: /* paper: SHA2-384 */
+ return 48;
+ case GT_HASHALG_SHA512: /* paper: SHA2-512 */
+ return 64;
+ default:return 32;
+ }
+}
+
+static inline uint8_t
+hashIdentifier(uint8_t hashID)
+{
+ switch(hashID) {
+ case GT_HASHALG_SHA1: /* paper: SHA1 */
+ return 0x00;
+ case GT_HASHALG_RIPEMD160: /* paper: RIPEMD-160 */
+ return 0x02;
+ case GT_HASHALG_SHA224: /* paper: SHA2-224 */
+ return 0x03;
+ case GT_HASHALG_SHA256: /* paper: SHA2-256 */
+ return 0x01;
+ case GT_HASHALG_SHA384: /* paper: SHA2-384 */
+ return 0x04;
+ case GT_HASHALG_SHA512: /* paper: SHA2-512 */
+ return 0x05;
+ default:return 0xff;
+ }
+}
+static inline char *
+hashAlgName(uint8_t hashID)
+{
+ switch(hashID) {
+ case GT_HASHALG_SHA1:
+ return "SHA1";
+ case GT_HASHALG_RIPEMD160:
+ return "RIPEMD-160";
+ case GT_HASHALG_SHA224:
+ return "SHA2-224";
+ case GT_HASHALG_SHA256:
+ return "SHA2-256";
+ case GT_HASHALG_SHA384:
+ return "SHA2-384";
+ case GT_HASHALG_SHA512:
+ return "SHA2-512";
+ default:return "[unknown]";
+ }
+}
+static inline enum GTHashAlgorithm
+hashID2Alg(uint8_t hashID)
+{
+ switch(hashID) {
+ case 0x00:
+ return GT_HASHALG_SHA1;
+ case 0x02:
+ return GT_HASHALG_RIPEMD160;
+ case 0x03:
+ return GT_HASHALG_SHA224;
+ case 0x01:
+ return GT_HASHALG_SHA256;
+ case 0x04:
+ return GT_HASHALG_SHA384;
+ case 0x05:
+ return GT_HASHALG_SHA512;
+ default:
+ return 0xff;
+ }
+}
+static inline char *
+sigTypeName(uint8_t sigID)
+{
+ switch(sigID) {
+ case SIGID_RFC3161:
+ return "RFC3161";
+ default:return "[unknown]";
+ }
+}
+static inline uint16_t
+getIVLen(block_sig_t *bs)
+{
+ return hashOutputLengthOctets(bs->hashID);
+}
+static inline void
+rsgtSetTimestamper(gtctx ctx, char *timestamper)
+{
+ free(ctx->timestamper);
+ ctx->timestamper = strdup(timestamper);
+}
+static inline void
+rsgtSetBlockSizeLimit(gtctx ctx, uint64_t limit)
+{
+ ctx->blockSizeLimit = limit;
+}
+static inline void
+rsgtSetKeepRecordHashes(gtctx ctx, int val)
+{
+ ctx->bKeepRecordHashes = val;
+}
+static inline void
+rsgtSetKeepTreeHashes(gtctx ctx, int val)
+{
+ ctx->bKeepTreeHashes = val;
+}
+
+int rsgtSetHashFunction(gtctx ctx, char *algName);
+void rsgtInit(char *usragent);
+void rsgtExit(void);
+gtctx rsgtCtxNew(void);
+gtfile rsgtCtxOpenFile(gtctx ctx, unsigned char *logfn);
+void rsgtfileDestruct(gtfile gf);
+void rsgtCtxDel(gtctx ctx);
+void sigblkInit(gtfile gf);
+void sigblkAddRecord(gtfile gf, const unsigned char *rec, const size_t len);
+void sigblkFinish(gtfile gf);
+/* reader functions */
+int rsgt_tlvrdHeader(FILE *fp, unsigned char *hdr);
+int rsgt_tlvrd(FILE *fp, uint16_t *tlvtype, uint16_t *tlvlen, void *obj);
+void rsgt_tlvprint(FILE *fp, uint16_t tlvtype, void *obj, uint8_t verbose);
+void rsgt_printBLOCK_SIG(FILE *fp, block_sig_t *bs, uint8_t verbose);
+int rsgt_getBlockParams(FILE *fp, uint8_t bRewind, block_sig_t **bs, uint8_t *bHasRecHashes, uint8_t *bHasIntermedHashes);
+int rsgt_chkFileHdr(FILE *fp, char *expect);
+gtfile rsgt_vrfyConstruct_gf(void);
+void rsgt_vrfyBlkInit(gtfile gf, block_sig_t *bs, uint8_t bHasRecHashes, uint8_t bHasIntermedHashes);
+int rsgt_vrfy_nextRec(block_sig_t *bs, gtfile gf, FILE *sigfp, unsigned char *rec, size_t lenRec, gterrctx_t *ectx);
+int verifyBLOCK_SIG(block_sig_t *bs, gtfile gf, FILE *sigfp, gterrctx_t *ectx);
+void rsgt_errctxInit(gterrctx_t *ectx);
+void rsgt_errctxExit(gterrctx_t *ectx);
+void rsgt_errctxSetErrRec(gterrctx_t *ectx, char *rec);
+void rsgt_errctxFrstRecInBlk(gterrctx_t *ectx, char *rec);
+
+
+/* TODO: replace these? */
+void hash_m(gtfile gf, GTDataHash **m);
+void hash_r(gtfile gf, GTDataHash **r, const unsigned char *rec, const size_t len);
+void hash_node(gtfile gf, GTDataHash **node, GTDataHash *m, GTDataHash *r, uint8_t level);
+extern char *rsgt_read_puburl; /**< url of publication server */
+extern uint8_t rsgt_read_showVerified;
+
+#endif /* #ifndef INCLUDED_LIBRSGT_H */
diff --git a/runtime/librsgt_read.c b/runtime/librsgt_read.c
new file mode 100644
index 00000000..e32ae454
--- /dev/null
+++ b/runtime/librsgt_read.c
@@ -0,0 +1,919 @@
+/* librsgt_read.c - rsyslog's guardtime support library
+ * This includes functions used for reading signature (and
+ * other related) files.
+ *
+ * This part of the library uses C stdio and expects that the
+ * caller will open and close the file to be read itself.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * 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 <stdio.h>
+#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 <gt_http.h>
+
+#include "librsgt.h"
+
+typedef unsigned char uchar;
+#ifndef VERSION
+#define VERSION "no-version"
+#endif
+#define MAXFNAME 1024
+
+static int rsgt_read_debug = 0;
+char *rsgt_read_puburl = "http://verify.guardtime.com/gt-controlpublications.bin";
+uint8_t rsgt_read_showVerified = 0;
+
+/* macro to obtain next char from file including error tracking */
+#define NEXTC if((c = fgetc(fp)) == EOF) { \
+ r = feof(fp) ? RSGTE_EOF : RSGTE_IO; \
+ goto done; \
+ }
+
+/* check return state of operation and abort, if non-OK */
+#define CHKr(code) if((r = code) != 0) goto done
+
+
+/* if verbose==0, only the first and last two octets are shown,
+ * otherwise everything.
+ */
+static void
+outputHexBlob(FILE *fp, uint8_t *blob, uint16_t len, uint8_t verbose)
+{
+ unsigned i;
+ if(verbose || len <= 8) {
+ for(i = 0 ; i < len ; ++i)
+ fprintf(fp, "%2.2x", blob[i]);
+ } else {
+ fprintf(fp, "%2.2x%2.2x%2.2x[...]%2.2x%2.2x%2.2x",
+ blob[0], blob[1], blob[2],
+ blob[len-3], blob[len-2], blob[len-1]);
+ }
+}
+
+static inline void
+outputHash(FILE *fp, char *hdr, uint8_t *data, uint16_t len, uint8_t verbose)
+{
+ fprintf(fp, "%s", hdr);
+ outputHexBlob(fp, data, len, verbose);
+ fputc('\n', fp);
+}
+
+void
+rsgt_errctxInit(gterrctx_t *ectx)
+{
+ ectx->fp = NULL;
+ ectx->filename = NULL;
+ ectx->recNum = 0;
+ ectx->recNumInFile = 0;
+ ectx->blkNum = 0;
+ ectx->verbose = 0;
+ ectx->errRec = NULL;
+ ectx->frstRecInBlk = NULL;
+}
+void
+rsgt_errctxExit(gterrctx_t *ectx)
+{
+ free(ectx->filename);
+ free(ectx->frstRecInBlk);
+}
+
+/* note: we do not copy the record, so the caller MUST not destruct
+ * it before processing of the record is completed. To remove the
+ * current record without setting a new one, call this function
+ * with rec==NULL.
+ */
+void
+rsgt_errctxSetErrRec(gterrctx_t *ectx, char *rec)
+{
+ ectx->errRec = strdup(rec);
+}
+/* This stores the block's first record. Here we copy the data,
+ * as the caller will usually not preserve it long enough.
+ */
+void
+rsgt_errctxFrstRecInBlk(gterrctx_t *ectx, char *rec)
+{
+ free(ectx->frstRecInBlk);
+ ectx->frstRecInBlk = strdup(rec);
+}
+
+static void
+reportError(int errcode, gterrctx_t *ectx)
+{
+ if(ectx->fp != NULL) {
+ fprintf(ectx->fp, "%s[%llu:%llu:%llu]: error[%u]: %s\n",
+ ectx->filename,
+ (long long unsigned) ectx->blkNum, (long long unsigned) ectx->recNum,
+ (long long unsigned) ectx->recNumInFile,
+ errcode, RSGTE2String(errcode));
+ if(ectx->frstRecInBlk != NULL)
+ fprintf(ectx->fp, "\tBlock Start Record.: '%s'\n", ectx->frstRecInBlk);
+ if(ectx->errRec != NULL)
+ fprintf(ectx->fp, "\tRecord in Question.: '%s'\n", ectx->errRec);
+ if(ectx->computedHash != NULL) {
+ outputHash(ectx->fp, "\tComputed Hash......: ", ectx->computedHash->digest,
+ ectx->computedHash->digest_length, ectx->verbose);
+ }
+ if(ectx->fileHash != NULL) {
+ outputHash(ectx->fp, "\tSignature File Hash: ", ectx->fileHash->data,
+ ectx->fileHash->len, ectx->verbose);
+ }
+ if(errcode == RSGTE_INVLD_TREE_HASH ||
+ errcode == RSGTE_INVLD_TREE_HASHID) {
+ fprintf(ectx->fp, "\tTree Level.........: %d\n", (int) ectx->treeLevel);
+ outputHash(ectx->fp, "\tTree Left Hash.....: ", ectx->lefthash->digest,
+ ectx->lefthash->digest_length, ectx->verbose);
+ outputHash(ectx->fp, "\tTree Right Hash....: ", ectx->righthash->digest,
+ ectx->righthash->digest_length, ectx->verbose);
+ }
+ if(errcode == RSGTE_INVLD_TIMESTAMP ||
+ errcode == RSGTE_TS_DERDECODE) {
+ fprintf(ectx->fp, "\tPublication Server.: %s\n", rsgt_read_puburl);
+ fprintf(ectx->fp, "\tGT Verify Timestamp: [%u]%s\n",
+ ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate));
+ }
+ }
+}
+
+/* obviously, this is not an error-reporting function. We still use
+ * ectx, as it has most information we need.
+ */
+static void
+reportVerifySuccess(gterrctx_t *ectx)
+{
+ if(ectx->fp != NULL) {
+ fprintf(ectx->fp, "%s[%llu:%llu:%llu]: block signature successfully verified\n",
+ ectx->filename,
+ (long long unsigned) ectx->blkNum, (long long unsigned) ectx->recNum,
+ (long long unsigned) ectx->recNumInFile);
+ if(ectx->frstRecInBlk != NULL)
+ fprintf(ectx->fp, "\tBlock Start Record.: '%s'\n", ectx->frstRecInBlk);
+ if(ectx->errRec != NULL)
+ fprintf(ectx->fp, "\tBlock End Record...: '%s'\n", ectx->errRec);
+ fprintf(ectx->fp, "\tGT Verify Timestamp: [%u]%s\n",
+ ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate));
+ }
+}
+
+
+/**
+ * Read a header from a binary file.
+ * @param[in] fp file pointer for processing
+ * @param[in] hdr buffer for the header. Must be 9 bytes
+ * (8 for header + NUL byte)
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_tlvrdHeader(FILE *fp, uchar *hdr)
+{
+ int r;
+ if(fread(hdr, 8, 1, fp) != 1) {
+ r = RSGTE_IO;
+ goto done;
+ }
+ hdr[8] = '\0';
+ r = 0;
+done: return r;
+}
+
+/* read type & length */
+static int
+rsgt_tlvrdTL(FILE *fp, uint16_t *tlvtype, uint16_t *tlvlen)
+{
+ int r = 1;
+ int c;
+
+ NEXTC;
+ *tlvtype = c & 0x1f;
+ if(c & 0x20) { /* tlv16? */
+ NEXTC;
+ *tlvtype = (*tlvtype << 8) | c;
+ NEXTC;
+ *tlvlen = c << 8;
+ NEXTC;
+ *tlvlen |= c;
+ } else {
+ NEXTC;
+ *tlvlen = c;
+ }
+ if(rsgt_read_debug)
+ printf("read tlvtype %4.4x, len %u\n", (unsigned) *tlvtype,
+ (unsigned) *tlvlen);
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvrdOctetString(FILE *fp, uint8_t **data, size_t len)
+{
+ size_t i;
+ int c, r = 1;
+ if((*data = (uint8_t*)malloc(len)) == NULL) {r=RSGTE_OOM;goto done;}
+ for(i = 0 ; i < len ; ++i) {
+ NEXTC;
+ (*data)[i] = c;
+ }
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvrdSkipVal(FILE *fp, size_t len)
+{
+ size_t i;
+ int c, r = 1;
+ for(i = 0 ; i < len ; ++i) {
+ NEXTC;
+ }
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvrdHASH_ALGO(FILE *fp, uint8_t *hashAlg)
+{
+ int r = 1;
+ int c;
+ uint16_t tlvtype, tlvlen;
+
+ CHKr(rsgt_tlvrdTL(fp, &tlvtype, &tlvlen));
+ if(!(tlvtype == 0x00 && tlvlen == 1)) {
+ r = RSGTE_FMT;
+ goto done;
+ }
+ NEXTC;
+ *hashAlg = c;
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvrdBLOCK_IV(FILE *fp, uint8_t **iv)
+{
+ int r = 1;
+ uint16_t tlvtype, tlvlen;
+
+ CHKr(rsgt_tlvrdTL(fp, &tlvtype, &tlvlen));
+ if(!(tlvtype == 0x01)) {
+ r = RSGTE_INVLTYP;
+ goto done;
+ }
+ CHKr(rsgt_tlvrdOctetString(fp, iv, tlvlen));
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvrdLAST_HASH(FILE *fp, imprint_t *imp)
+{
+ int r = 1;
+ int c;
+ uint16_t tlvtype, tlvlen;
+
+ CHKr(rsgt_tlvrdTL(fp, &tlvtype, &tlvlen));
+ if(!(tlvtype == 0x02)) { r = RSGTE_INVLTYP; goto done; }
+ NEXTC;
+ imp->hashID = c;
+ if(tlvlen != 1 + hashOutputLengthOctets(imp->hashID)) {
+ r = RSGTE_LEN;
+ goto done;
+ }
+ imp->len = tlvlen - 1;
+ CHKr(rsgt_tlvrdOctetString(fp, &imp->data, tlvlen-1));
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvrdREC_COUNT(FILE *fp, uint64_t *cnt, size_t *lenInt)
+{
+ int r = 1;
+ int i;
+ int c;
+ uint64_t val;
+ uint16_t tlvtype, tlvlen;
+
+ if((r = rsgt_tlvrdTL(fp, &tlvtype, &tlvlen)) != 0) goto done;
+ if(!(tlvtype == 0x03 && tlvlen <= 8)) { r = RSGTE_INVLTYP; goto done; }
+ *lenInt = tlvlen;
+ val = 0;
+ for(i = 0 ; i < tlvlen ; ++i) {
+ NEXTC;
+ val = (val << 8) + c;
+ }
+ *cnt = val;
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvrdSIG(FILE *fp, block_sig_t *bs)
+{
+ int r = 1;
+ uint16_t tlvtype, tlvlen;
+
+ CHKr(rsgt_tlvrdTL(fp, &tlvtype, &tlvlen));
+ if(!(tlvtype == 0x0906)) { r = RSGTE_INVLTYP; goto done; }
+ bs->sig.der.len = tlvlen;
+ bs->sigID = SIGID_RFC3161;
+ CHKr(rsgt_tlvrdOctetString(fp, &(bs->sig.der.data), tlvlen));
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvrdBLOCK_SIG(FILE *fp, block_sig_t **blocksig, uint16_t tlvlen)
+{
+ int r = 1;
+ size_t lenInt = 0;
+ uint16_t sizeRead;
+ block_sig_t *bs;
+ if((bs = calloc(1, sizeof(block_sig_t))) == NULL) {
+ r = RSGTE_OOM;
+ goto done;
+ }
+ CHKr(rsgt_tlvrdHASH_ALGO(fp, &(bs->hashID)));
+ CHKr(rsgt_tlvrdBLOCK_IV(fp, &(bs->iv)));
+ CHKr(rsgt_tlvrdLAST_HASH(fp, &(bs->lastHash)));
+ CHKr(rsgt_tlvrdREC_COUNT(fp, &(bs->recCount), &lenInt));
+ CHKr(rsgt_tlvrdSIG(fp, bs));
+ sizeRead = 2 + 1 /* hash algo TLV */ +
+ 2 + getIVLen(bs) /* iv */ +
+ 2 + 1 + bs->lastHash.len /* last hash */ +
+ 2 + lenInt /* rec-count */ +
+ 4 + bs->sig.der.len /* rfc-3161 */;
+ if(sizeRead != tlvlen) {
+ r = RSGTE_LEN;
+ goto done;
+ }
+ *blocksig = bs;
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvrdIMPRINT(FILE *fp, imprint_t **imprint, uint16_t tlvlen)
+{
+ int r = 1;
+ imprint_t *imp;
+ int c;
+
+ if((imp = calloc(1, sizeof(imprint_t))) == NULL) {
+ r = RSGTE_OOM;
+ goto done;
+ }
+ if((imp->data = calloc(1, sizeof(imprint_t))) == NULL) {
+ r = RSGTE_OOM;
+ goto done;
+ }
+
+ NEXTC;
+ imp->hashID = c;
+ if(tlvlen != 1 + hashOutputLengthOctets(imp->hashID)) {
+ r = RSGTE_LEN;
+ goto done;
+ }
+ imp->len = tlvlen - 1;
+ CHKr(rsgt_tlvrdOctetString(fp, &imp->data, tlvlen-1));
+
+ *imprint = imp;
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvrdRecHash(FILE *fp, imprint_t **imp)
+{
+ int r;
+ uint16_t tlvtype, tlvlen;
+
+ if((r = rsgt_tlvrdTL(fp, &tlvtype, &tlvlen)) != 0) goto done;
+ if(tlvtype != 0x0900) {
+ r = RSGTE_MISS_REC_HASH;
+ goto done;
+ }
+ if((r = rsgt_tlvrdIMPRINT(fp, imp, tlvlen)) != 0) goto done;
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvrdTreeHash(FILE *fp, imprint_t **imp)
+{
+ int r;
+ uint16_t tlvtype, tlvlen;
+
+ if((r = rsgt_tlvrdTL(fp, &tlvtype, &tlvlen)) != 0) goto done;
+ if(tlvtype != 0x0901) {
+ r = RSGTE_MISS_TREE_HASH;
+ goto done;
+ }
+ if((r = rsgt_tlvrdIMPRINT(fp, imp, tlvlen)) != 0) goto done;
+ r = 0;
+done: return r;
+}
+
+/* read BLOCK_SIG during verification phase */
+static int
+rsgt_tlvrdVrfyBlockSig(FILE *fp, block_sig_t **bs)
+{
+ int r;
+ uint16_t tlvtype, tlvlen;
+
+ if((r = rsgt_tlvrdTL(fp, &tlvtype, &tlvlen)) != 0) goto done;
+ if(tlvtype != 0x0902) {
+ r = RSGTE_MISS_BLOCKSIG;
+ goto done;
+ }
+ if((r = rsgt_tlvrdBLOCK_SIG(fp, bs, tlvlen)) != 0) goto done;
+ r = 0;
+done: return r;
+}
+
+/**;
+ * Read the next "object" from file. This usually is
+ * a single TLV, but may be something larger, for
+ * example in case of a block-sig TLV record.
+ * Unknown type records are ignored (or run aborted
+ * if we are not permitted to skip).
+ *
+ * @param[in] fp file pointer for processing
+ * @param[out] tlvtype type of tlv record (top-level for
+ * structured objects.
+ * @param[out] tlvlen length of the tlv record value
+ * @param[out] obj pointer to object; This is a proper
+ * tlv record structure, which must be casted
+ * by the caller according to the reported type.
+ * The object must be freed by the caller (TODO: better way?)
+ *
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_tlvrd(FILE *fp, uint16_t *tlvtype, uint16_t *tlvlen, void *obj)
+{
+ int r = 1;
+
+ if((r = rsgt_tlvrdTL(fp, tlvtype, tlvlen)) != 0) goto done;
+ switch(*tlvtype) {
+ case 0x0900:
+ r = rsgt_tlvrdIMPRINT(fp, obj, *tlvlen);
+ if(r != 0) goto done;
+ break;
+ case 0x0901:
+ r = rsgt_tlvrdIMPRINT(fp, obj, *tlvlen);
+ if(r != 0) goto done;
+ break;
+ case 0x0902:
+ r = rsgt_tlvrdBLOCK_SIG(fp, obj, *tlvlen);
+ if(r != 0) goto done;
+ break;
+ }
+ r = 0;
+done: return r;
+}
+
+/* return if a blob is all zero */
+static inline int
+blobIsZero(uint8_t *blob, uint16_t len)
+{
+ int i;
+ for(i = 0 ; i < len ; ++i)
+ if(blob[i] != 0)
+ return 0;
+ return 1;
+}
+
+static void
+rsgt_printIMPRINT(FILE *fp, char *name, imprint_t *imp, uint8_t verbose)
+{
+ fprintf(fp, "%s", name);
+ outputHexBlob(fp, imp->data, imp->len, verbose);
+ fputc('\n', fp);
+}
+
+static void
+rsgt_printREC_HASH(FILE *fp, imprint_t *imp, uint8_t verbose)
+{
+ rsgt_printIMPRINT(fp, "[0x0900]Record hash: ",
+ imp, verbose);
+}
+
+static void
+rsgt_printINT_HASH(FILE *fp, imprint_t *imp, uint8_t verbose)
+{
+ rsgt_printIMPRINT(fp, "[0x0901]Tree hash..: ",
+ imp, verbose);
+}
+
+/**
+ * Output a human-readable representation of a block_sig_t
+ * to proviced file pointer. This function is mainly inteded for
+ * debugging purposes or dumping tlv files.
+ *
+ * @param[in] fp file pointer to send output to
+ * @param[in] bsig ponter to block_sig_t to output
+ * @param[in] verbose if 0, abbreviate blob hexdump, else complete
+ */
+void
+rsgt_printBLOCK_SIG(FILE *fp, block_sig_t *bs, uint8_t verbose)
+{
+ fprintf(fp, "[0x0902]Block Signature Record:\n");
+ fprintf(fp, "\tPrevious Block Hash:\n");
+ fprintf(fp, "\t Algorithm..: %s\n", hashAlgName(bs->lastHash.hashID));
+ fprintf(fp, "\t Hash.......: ");
+ outputHexBlob(fp, bs->lastHash.data, bs->lastHash.len, verbose);
+ fputc('\n', fp);
+ if(blobIsZero(bs->lastHash.data, bs->lastHash.len))
+ fprintf(fp, "\t NOTE: New Hash Chain Start!\n");
+ fprintf(fp, "\tHash Algorithm: %s\n", hashAlgName(bs->hashID));
+ fprintf(fp, "\tIV............: ");
+ outputHexBlob(fp, bs->iv, getIVLen(bs), verbose);
+ fputc('\n', fp);
+ fprintf(fp, "\tRecord Count..: %llu\n", bs->recCount);
+ fprintf(fp, "\tSignature Type: %s\n", sigTypeName(bs->sigID));
+ fprintf(fp, "\tSignature Len.: %u\n", bs->sig.der.len);
+ fprintf(fp, "\tSignature.....: ");
+ outputHexBlob(fp, bs->sig.der.data, bs->sig.der.len, verbose);
+ fputc('\n', fp);
+}
+
+
+/**
+ * Output a human-readable representation of a tlv object.
+ *
+ * @param[in] fp file pointer to send output to
+ * @param[in] tlvtype type of tlv object (record)
+ * @param[in] verbose if 0, abbreviate blob hexdump, else complete
+ */
+void
+rsgt_tlvprint(FILE *fp, uint16_t tlvtype, void *obj, uint8_t verbose)
+{
+ switch(tlvtype) {
+ case 0x0900:
+ rsgt_printREC_HASH(fp, obj, verbose);
+ break;
+ case 0x0901:
+ rsgt_printINT_HASH(fp, obj, verbose);
+ break;
+ case 0x0902:
+ rsgt_printBLOCK_SIG(fp, obj, verbose);
+ break;
+ default:fprintf(fp, "unknown tlv record %4.4x\n", tlvtype);
+ break;
+ }
+}
+
+
+/**
+ * Read block parameters. This detects if the block contains the
+ * individual log hashes, the intermediate hashes and the overall
+ * block paramters (from the signature block). As we do not have any
+ * begin of block record, we do not know e.g. the hash algorithm or IV
+ * until reading the block signature record. And because the file is
+ * purely sequential and variable size, we need to read all records up to
+ * the next signature record.
+ * If a caller intends to verify a log file based on the parameters,
+ * he must re-read the file from the begining (we could keep things
+ * in memory, but this is impractical for large blocks). In order
+ * to facitate this, the function permits to rewind to the original
+ * read location when it is done.
+ *
+ * @param[in] fp file pointer of tlv file
+ * @param[in] bRewind 0 - do not rewind at end of procesing, 1 - do so
+ * @param[out] bs block signature record
+ * @param[out] bHasRecHashes 0 if record hashes are present, 1 otherwise
+ * @param[out] bHasIntermedHashes 0 if intermediate hashes are present,
+ * 1 otherwise
+ *
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_getBlockParams(FILE *fp, uint8_t bRewind, block_sig_t **bs,
+ uint8_t *bHasRecHashes, uint8_t *bHasIntermedHashes)
+{
+ int r;
+ uint64_t nRecs = 0;
+ uint16_t tlvtype, tlvlen;
+ uint8_t bDone = 0;
+ off_t rewindPos = 0;
+
+ if(bRewind)
+ rewindPos = ftello(fp);
+ *bHasRecHashes = 0;
+ *bHasIntermedHashes = 0;
+ *bs = NULL;
+
+ while(!bDone) { /* we will err out on EOF */
+ if((r = rsgt_tlvrdTL(fp, &tlvtype, &tlvlen)) != 0) goto done;
+ switch(tlvtype) {
+ case 0x0900:
+ ++nRecs;
+ *bHasRecHashes = 1;
+ rsgt_tlvrdSkipVal(fp, tlvlen);
+ break;
+ case 0x0901:
+ *bHasIntermedHashes = 1;
+ rsgt_tlvrdSkipVal(fp, tlvlen);
+ break;
+ case 0x0902:
+ r = rsgt_tlvrdBLOCK_SIG(fp, bs, tlvlen);
+ if(r != 0) goto done;
+ bDone = 1;
+ break;
+ default:fprintf(fp, "unknown tlv record %4.4x\n", tlvtype);
+ break;
+ }
+ }
+
+ if(*bHasRecHashes && (nRecs != (*bs)->recCount)) {
+ r = RSGTE_INVLD_RECCNT;
+ goto done;
+ }
+
+ if(bRewind) {
+ if(fseeko(fp, rewindPos, SEEK_SET) != 0) {
+ r = RSGTE_IO;
+ goto done;
+ }
+ }
+done:
+ return r;
+}
+
+
+/**
+ * Read the file header and compare it to the expected value.
+ * The file pointer is placed right after the header.
+ * @param[in] fp file pointer of tlv file
+ * @param[in] excpect expected header (e.g. "LOGSIG10")
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_chkFileHdr(FILE *fp, char *expect)
+{
+ int r;
+ char hdr[9];
+
+ if((r = rsgt_tlvrdHeader(fp, (uchar*)hdr)) != 0) goto done;
+ if(strcmp(hdr, expect))
+ r = RSGTE_INVLHDR;
+ else
+ r = 0;
+done:
+ return r;
+}
+
+gtfile
+rsgt_vrfyConstruct_gf(void)
+{
+ gtfile gf;
+ if((gf = calloc(1, sizeof(struct gtfile_s))) == NULL)
+ goto done;
+ gf->x_prev = NULL;
+
+done: return gf;
+}
+
+void
+rsgt_vrfyBlkInit(gtfile gf, block_sig_t *bs, uint8_t bHasRecHashes, uint8_t bHasIntermedHashes)
+{
+ gf->hashAlg = hashID2Alg(bs->hashID);
+ gf->bKeepRecordHashes = bHasRecHashes;
+ gf->bKeepTreeHashes = bHasIntermedHashes;
+ free(gf->IV);
+ gf->IV = malloc(getIVLen(bs));
+ memcpy(gf->IV, bs->iv, getIVLen(bs));
+ free(gf->blkStrtHash);
+ gf->lenBlkStrtHash = bs->lastHash.len;
+ gf->blkStrtHash = malloc(gf->lenBlkStrtHash);
+ memcpy(gf->blkStrtHash, bs->lastHash.data, gf->lenBlkStrtHash);
+}
+
+static int
+rsgt_vrfy_chkRecHash(gtfile gf, FILE *sigfp, GTDataHash *recHash, gterrctx_t *ectx)
+{
+ int r = 0;
+ imprint_t *imp;
+
+ if((r = rsgt_tlvrdRecHash(sigfp, &imp)) != 0)
+ reportError(r, ectx);
+ goto done;
+ if(imp->hashID != hashIdentifier(gf->hashAlg)) {
+ reportError(r, ectx);
+ r = RSGTE_INVLD_REC_HASHID;
+ goto done;
+ }
+ if(memcmp(imp->data, recHash->digest,
+ hashOutputLengthOctets(imp->hashID))) {
+ r = RSGTE_INVLD_REC_HASH;
+ ectx->computedHash = recHash;
+ ectx->fileHash = imp;
+ reportError(r, ectx);
+ ectx->computedHash = NULL, ectx->fileHash = NULL;
+ goto done;
+ }
+ r = 0;
+done:
+ return r;
+}
+
+static int
+rsgt_vrfy_chkTreeHash(gtfile gf, FILE *sigfp, GTDataHash *hash, gterrctx_t *ectx)
+{
+ int r = 0;
+ imprint_t *imp;
+
+ if((r = rsgt_tlvrdTreeHash(sigfp, &imp)) != 0) {
+ reportError(r, ectx);
+ goto done;
+ }
+ if(imp->hashID != hashIdentifier(gf->hashAlg)) {
+ reportError(r, ectx);
+ r = RSGTE_INVLD_TREE_HASHID;
+ goto done;
+ }
+ if(memcmp(imp->data, hash->digest,
+ hashOutputLengthOctets(imp->hashID))) {
+ r = RSGTE_INVLD_TREE_HASH;
+ ectx->computedHash = hash;
+ ectx->fileHash = imp;
+ reportError(r, ectx);
+ ectx->computedHash = NULL, ectx->fileHash = NULL;
+ goto done;
+ }
+ r = 0;
+done:
+ return r;
+}
+
+int
+rsgt_vrfy_nextRec(block_sig_t *bs, gtfile gf, FILE *sigfp, unsigned char *rec,
+ size_t len, gterrctx_t *ectx)
+{
+ int r = 0;
+ GTDataHash *x; /* current hash */
+ GTDataHash *m, *recHash, *t;
+ uint8_t j;
+
+ hash_m(gf, &m);
+ hash_r(gf, &recHash, rec, len);
+ if(gf->bKeepRecordHashes) {
+ r = rsgt_vrfy_chkRecHash(gf, sigfp, recHash, ectx);
+ if(r != 0) goto done;
+ }
+ hash_node(gf, &x, m, recHash, 1); /* hash leaf */
+ if(gf->bKeepTreeHashes) {
+ ectx->treeLevel = 0;
+ ectx->lefthash = m;
+ ectx->righthash = recHash;
+ r = rsgt_vrfy_chkTreeHash(gf, sigfp, x, ectx);
+ if(r != 0) goto done;
+ }
+ /* add x to the forest as new leaf, update roots list */
+ t = x;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(gf->roots_valid[j] == 0) {
+ gf->roots_hash[j] = t;
+ gf->roots_valid[j] = 1;
+ t = NULL;
+ break;
+ } else if(t != NULL) {
+ /* hash interim node */
+ ectx->treeLevel = j+1;
+ ectx->righthash = t;
+ hash_node(gf, &t, gf->roots_hash[j], t, j+2);
+ gf->roots_valid[j] = 0;
+ if(gf->bKeepTreeHashes) {
+ ectx->lefthash = gf->roots_hash[j];
+ r = rsgt_vrfy_chkTreeHash(gf, sigfp, t, ectx);
+ if(r != 0) goto done; /* mem leak ok, we terminate! */
+ }
+ GTDataHash_free(gf->roots_hash[j]);
+ }
+ }
+ if(t != NULL) {
+ /* new level, append "at the top" */
+ gf->roots_hash[gf->nRoots] = t;
+ gf->roots_valid[gf->nRoots] = 1;
+ ++gf->nRoots;
+ assert(gf->nRoots < MAX_ROOTS);
+ t = NULL;
+ }
+ gf->x_prev = x; /* single var may be sufficient */
+ ++gf->nRecords;
+
+ /* cleanup */
+ /* note: x is freed later as part of roots cleanup */
+ GTDataHash_free(m);
+ GTDataHash_free(recHash);
+done:
+ return r;
+}
+
+
+/* TODO: think about merging this with the writer. The
+ * same applies to the other computation algos.
+ */
+static int
+verifySigblkFinish(gtfile gf, GTDataHash **pRoot)
+{
+ GTDataHash *root, *rootDel;
+ int8_t j;
+ int r;
+
+ if(gf->nRecords == 0)
+ goto done;
+
+ root = NULL;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(root == NULL) {
+ root = gf->roots_valid[j] ? gf->roots_hash[j] : NULL;
+ gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */
+ } else if(gf->roots_valid[j]) {
+ rootDel = root;
+ hash_node(gf, &root, gf->roots_hash[j], root, j+2);
+ gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */
+ GTDataHash_free(rootDel);
+ }
+ }
+
+ free(gf->blkStrtHash);
+ gf->blkStrtHash = NULL;
+ *pRoot = root;
+ r = 0;
+done:
+ gf->bInBlk = 0;
+ return r;
+}
+
+/* verify the root hash. This also means we need to compute the
+ * Merkle tree root for the current block.
+ */
+int
+verifyBLOCK_SIG(block_sig_t *bs, gtfile gf, FILE *sigfp, gterrctx_t *ectx)
+{
+ int r;
+ int gtstate;
+ block_sig_t *file_bs;
+ GTTimestamp *timestamp = NULL;
+ GTVerificationInfo *vrfyInf;
+ GTDataHash *root = NULL;
+
+ if((r = verifySigblkFinish(gf, &root)) != 0)
+ goto done;
+ if((r = rsgt_tlvrdVrfyBlockSig(sigfp, &file_bs)) != 0)
+ goto done;
+ if(ectx->recNum != bs->recCount) {
+ r = RSGTE_INVLD_RECCNT;
+ goto done;
+ }
+
+ gtstate = GTTimestamp_DERDecode(file_bs->sig.der.data,
+ file_bs->sig.der.len, &timestamp);
+ if(gtstate != GT_OK) {
+ r = RSGTE_TS_DERDECODE;
+ ectx->gtstate = gtstate;
+ goto done;
+ }
+
+ gtstate = GTHTTP_verifyTimestampHash(timestamp, root, NULL,
+ NULL, NULL, rsgt_read_puburl, 0, &vrfyInf);
+ if(! (gtstate == GT_OK
+ && vrfyInf->verification_errors == GT_NO_FAILURES) ) {
+ r = RSGTE_INVLD_TIMESTAMP;
+ ectx->gtstate = gtstate;
+ goto done;
+ }
+
+ r = 0;
+ if(rsgt_read_showVerified)
+ reportVerifySuccess(ectx);
+done:
+ if(r != 0)
+ reportError(r, ectx);
+ if(timestamp != NULL)
+ GTTimestamp_free(timestamp);
+ return r;
+}
diff --git a/runtime/lmsig_gt.c b/runtime/lmsig_gt.c
new file mode 100644
index 00000000..d61f3a86
--- /dev/null
+++ b/runtime/lmsig_gt.c
@@ -0,0 +1,216 @@
+/* lmsig_gt.c
+ *
+ * An implementation of the sigprov interface for GuardTime.
+ *
+ * Copyright 2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * 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.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "module-template.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "sigprov.h"
+#include "lmsig_gt.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+
+/* tables for interfacing with the v6 config system */
+static struct cnfparamdescr cnfpdescr[] = {
+ { "sig.hashfunction", eCmdHdlrGetWord, 0 },
+ { "sig.timestampservice", eCmdHdlrGetWord, 0 },
+ { "sig.block.sizelimit", eCmdHdlrSize, 0 },
+ { "sig.keeprecordhashes", eCmdHdlrBinary, 0 },
+ { "sig.keeptreehashes", eCmdHdlrBinary, 0 }
+};
+static struct cnfparamblk pblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescr)/sizeof(struct cnfparamdescr),
+ cnfpdescr
+ };
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(lmsig_gt)
+ dbgprintf("DDDD: lmsig_gt: called construct\n");
+ pThis->ctx = rsgtCtxNew();
+ENDobjConstruct(lmsig_gt)
+
+
+/* destructor for the lmsig_gt object */
+BEGINobjDestruct(lmsig_gt) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(lmsig_gt)
+ dbgprintf("DDDD: lmsig_gt: called destruct\n");
+ENDobjDestruct(lmsig_gt)
+
+
+/* apply all params from param block to us. This must be called
+ * after construction, but before the OnFileOpen() entry point.
+ * Defaults are expected to have been set during construction.
+ */
+rsRetVal
+SetCnfParam(void *pT, struct nvlst *lst)
+{
+ lmsig_gt_t *pThis = (lmsig_gt_t*) pT;
+ int i;
+ uchar *cstr;
+ struct cnfparamvals *pvals;
+ pvals = nvlstGetParams(lst, &pblk, NULL);
+ if(Debug) {
+ dbgprintf("sig param blk in lmsig_gt:\n");
+ cnfparamsPrint(&pblk, pvals);
+ }
+
+ for(i = 0 ; i < pblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(pblk.descr[i].name, "sig.hashfunction")) {
+ cstr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ if(rsgtSetHashFunction(pThis->ctx, (char*)cstr) != 0) {
+ errmsg.LogError(0, RS_RET_ERR, "Hash function "
+ "'%s' unknown - using default", cstr);
+ }
+ free(cstr);
+ } else if(!strcmp(pblk.descr[i].name, "sig.timestampservice")) {
+ cstr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ rsgtSetTimestamper(pThis->ctx, (char*) cstr);
+ free(cstr);
+ } else if(!strcmp(pblk.descr[i].name, "sig.block.sizelimit")) {
+ rsgtSetBlockSizeLimit(pThis->ctx, pvals[i].val.d.n);
+ } else if(!strcmp(pblk.descr[i].name, "sig.keeprecordhashes")) {
+ rsgtSetKeepRecordHashes(pThis->ctx, pvals[i].val.d.n);
+ } else if(!strcmp(pblk.descr[i].name, "sig.keeptreehashes")) {
+ rsgtSetKeepTreeHashes(pThis->ctx, pvals[i].val.d.n);
+ } else {
+ DBGPRINTF("lmsig_gt: program error, non-handled "
+ "param '%s'\n", pblk.descr[i].name);
+ }
+ }
+ cnfparamvalsDestruct(pvals, &pblk);
+ return RS_RET_OK;
+}
+
+
+static rsRetVal
+OnFileOpen(void *pT, uchar *fn, void *pGF)
+{
+ lmsig_gt_t *pThis = (lmsig_gt_t*) pT;
+ gtfile *pgf = (gtfile*) pGF;
+ DEFiRet;
+dbgprintf("DDDD: onFileOpen: %s\n", fn);
+
+ *pgf = rsgtCtxOpenFile(pThis->ctx, fn);
+ sigblkInit(*pgf);
+
+ RETiRet;
+}
+
+/* Note: we assume that the record is terminated by a \n.
+ * As of the GuardTime paper, \n is not part of the signed
+ * message, so we subtract one from the record size. This
+ * may cause issues with non-standard formats, but let's
+ * see how things evolve (the verifier will not work in
+ * any case when the records are not \n delimited...).
+ * rgerhards, 2013-03-17
+ */
+static rsRetVal
+OnRecordWrite(void *pF, uchar *rec, rs_size_t lenRec)
+{
+ DEFiRet;
+dbgprintf("DDDD: onRecordWrite (%d): %s\n", lenRec-1, rec);
+ sigblkAddRecord(pF, rec, lenRec-1);
+
+ RETiRet;
+}
+
+static rsRetVal
+OnFileClose(void *pF)
+{
+ DEFiRet;
+dbgprintf("DDDD: onFileClose\n");
+ rsgtfileDestruct(pF);
+
+ RETiRet;
+}
+
+BEGINobjQueryInterface(lmsig_gt)
+CODESTARTobjQueryInterface(lmsig_gt)
+ if(pIf->ifVersion != sigprovCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+ pIf->Construct = (rsRetVal(*)(void*)) lmsig_gtConstruct;
+ pIf->SetCnfParam = SetCnfParam;
+ pIf->Destruct = (rsRetVal(*)(void*)) lmsig_gtDestruct;
+ pIf->OnFileOpen = OnFileOpen;
+ pIf->OnRecordWrite = OnRecordWrite;
+ pIf->OnFileClose = OnFileClose;
+finalize_it:
+ENDobjQueryInterface(lmsig_gt)
+
+
+BEGINObjClassExit(lmsig_gt, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(lmsig_gt)
+ /* release objects we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+
+ rsgtExit();
+ENDObjClassExit(lmsig_gt)
+
+
+BEGINObjClassInit(lmsig_gt, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ rsgtInit("rsyslogd " VERSION);
+ENDObjClassInit(lmsig_gt)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ lmsig_gtClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(lmsig_gtClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
diff --git a/runtime/lmsig_gt.h b/runtime/lmsig_gt.h
new file mode 100644
index 00000000..665e6a8e
--- /dev/null
+++ b/runtime/lmsig_gt.h
@@ -0,0 +1,40 @@
+/* An implementation of the sigprov interface for GuardTime.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * 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.
+ */
+#ifndef INCLUDED_LMSIG_GT_H
+#define INCLUDED_LMSIG_GT_H
+#include "sigprov.h"
+#include "librsgt.h"
+
+/* interface is defined in sigprov.h, we just implement it! */
+#define lmsig_gtCURR_IF_VERSION sigprovCURR_IF_VERSION
+typedef sigprov_if_t lmsig_gt_if_t;
+
+/* the lmsig_gt object */
+struct lmsig_gt_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ gtctx ctx; /* librsgt context - contains all we need */
+};
+typedef struct lmsig_gt_s lmsig_gt_t;
+
+/* prototypes */
+PROTOTYPEObj(lmsig_gt);
+
+#endif /* #ifndef INCLUDED_LMSIG_GT_H */
diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h
index e7a5dffb..5ba6ede7 100644
--- a/runtime/rsyslog.h
+++ b/runtime/rsyslog.h
@@ -402,6 +402,7 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth
RS_RET_INVLD_MODE = -2311,/**< invalid mode specified in configuration */
RS_RET_INVLD_ANON_BITS = -2312,/**< mmanon: invalid number of bits to anonymize specified */
RS_RET_REPLCHAR_IGNORED = -2313,/**< mmanon: replacementChar parameter is ignored */
+ RS_RET_SIGPROV_ERR = -2320,/**< error in signature provider */
/* RainerScript error messages (range 1000.. 1999) */
RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */
diff --git a/runtime/sigprov.h b/runtime/sigprov.h
new file mode 100644
index 00000000..82587b7d
--- /dev/null
+++ b/runtime/sigprov.h
@@ -0,0 +1,37 @@
+/* The interface definition for (file) signature providers.
+ *
+ * This is just an abstract driver interface, which needs to be
+ * implemented by concrete classes.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * 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.
+ */
+#ifndef INCLUDED_SIGPROV_H
+#define INCLUDED_SIGPROV_H
+
+/* interface */
+BEGINinterface(sigprov) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(void *ppThis);
+ rsRetVal (*SetCnfParam)(void *ppThis, struct nvlst *lst);
+ rsRetVal (*Destruct)(void *ppThis);
+ rsRetVal (*OnFileOpen)(void *pThis, uchar *fn, void *pFileInstData);
+ rsRetVal (*OnRecordWrite)(void *pFileInstData, uchar *rec, rs_size_t lenRec);
+ rsRetVal (*OnFileClose)(void *pFileInstData);
+ENDinterface(sigprov)
+#define sigprovCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+#endif /* #ifndef INCLUDED_SIGPROV_H */
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 9d9bd352..21a32868 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -43,6 +43,10 @@ rsyslogd_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
rsyslogd_LDADD = ../grammar/libgrammar.la ../runtime/librsyslog.la $(ZLIB_LIBS) $(PTHREADS_LIBS) $(RSRT_LIBS) $(SOL_LIBS) $(LIBEE_LIBS) $(LIBLOGNORM_LIBS) $(LIBUUID_LIBS)
rsyslogd_LDFLAGS = -export-dynamic
+EXTRA_DIST = $(man_MANS) \
+ rsgtutil.rst \
+ recover_qi.pl
+
if ENABLE_DIAGTOOLS
sbin_PROGRAMS += rsyslog_diag_hostname msggen zpipe
rsyslog_diag_hostname_SOURCES = gethostn.c
@@ -58,7 +62,19 @@ logctl_SOURCES = logctl.c
logctl_CPPFLAGS = $(LIBMONGO_CLIENT_CFLAGS)
logctl_LDADD = $(LIBMONGO_CLIENT_LIBS)
endif
+if ENABLE_GUARDTIME
+bin_PROGRAMS += rsgtutil
+#bin_PROGRAMS += logsigner rsgtutil
+#logsigner = logsigner.c
+#logsigner_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS)
+#logsigner_LDADD = ../runtime/librsgt.la $(GUARDTIME_LIBS)
+rsgtutil = rsgtutil.c
+rsgtutil_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS)
+rsgtutil_LDADD = ../runtime/librsgt.la $(GUARDTIME_LIBS)
+rsgtutil.1: rsgtutil.rst
+ $(AM_V_GEN) $(RST2MAN) $< $@
+man1_MANS = rsgtutil.1
+CLEANFILES = rsgtutil.1
+EXTRA_DIST+= rsgtutil.1
+endif
endif
-
-EXTRA_DIST = $(man_MANS) \
- recover_qi.pl
diff --git a/tools/logsigner.c b/tools/logsigner.c
new file mode 100644
index 00000000..f6887696
--- /dev/null
+++ b/tools/logsigner.c
@@ -0,0 +1,159 @@
+/* This is a tool for offline signing logfiles via the guardtime API.
+ *
+ * NOTE: this currently is only a PoC and WiP! NOT suitable for
+ * production use!
+ *
+ * Current hardcoded timestamper (use this if you do not have an
+ * idea of which one else to use):
+ * http://stamper.guardtime.net/gt-signingservice
+ * Check the GuardTime website for the URLs of nearest public services.
+ *
+ * Copyright 2013 Adiscon GmbH
+ *
+ * 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 exprs 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 <gt_base.h>
+#include <gt_http.h>
+
+#include "librsgt.h"
+
+
+#if 0
+void
+outputhash(GTDataHash *hash)
+{
+ int i;
+ for(i = 0 ; i < hash->digest_length ; ++i)
+ printf("%2.2x", hash->digest[i]);
+ printf("\n");
+}
+
+void
+timestampIt(GTDataHash *hash)
+{
+ int r = GT_OK;
+ GTTimestamp *timestamp = NULL;
+ unsigned char *der = NULL;
+ char *sigFile = "logsigner.TIMESTAMP";
+ size_t der_len;
+
+ /* Get the timestamp. */
+ r = GTHTTP_createTimestampHash(hash,
+ "http://stamper.guardtime.net/gt-signingservice", &timestamp);
+
+ if(r != GT_OK) {
+ fprintf(stderr, "GTHTTP_createTimestampHash() failed: %d (%s)\n",
+ r, GTHTTP_getErrorString(r));
+ goto done;
+ }
+
+ /* Encode timestamp. */
+ r = GTTimestamp_getDEREncoded(timestamp, &der, &der_len);
+ if(r != GT_OK) {
+ fprintf(stderr, "GTTimestamp_getDEREncoded() failed: %d (%s)\n",
+ r, GT_getErrorString(r));
+ goto done;
+ }
+
+ /* Save DER-encoded timestamp to file. */
+ r = GT_saveFile(sigFile, der, der_len);
+ if(r != GT_OK) {
+ fprintf(stderr, "Cannot save timestamp to file %s: %d (%s)\n",
+ sigFile, r, GT_getErrorString(r));
+ if(r == GT_IO_ERROR) {
+ fprintf(stderr, "\t%d (%s)\n", errno, strerror(errno));
+ }
+ goto done;
+ }
+ printf("Timestamping succeeded!\n");
+done:
+ GT_free(der);
+ GTTimestamp_free(timestamp);
+}
+
+
+void
+sign(const char *buf, const size_t len)
+{
+ int r;
+ GTDataHash *hash = NULL;
+
+ printf("hash for '%s' is ", buf);
+ r = GTDataHash_create(GT_HASHALG_SHA256, (const unsigned char*)buf, len, &hash);
+ if(r != GT_OK) {
+ fprintf(stderr, "GTTDataHash_create() failed: %d (%s)\n",
+ r, GT_getErrorString(r));
+ goto done;
+ }
+ outputhash(hash);
+ timestampIt(hash); /* of course, this needs to be moved to once at end ;) */
+done: GTDataHash_free(hash);
+}
+#endif
+
+void
+processFile(char *name)
+{
+ FILE *fp;
+ size_t len;
+ char line[64*1024+1];
+ gtctx ctx = NULL;
+
+ ctx = rsgtCtxNew((unsigned char*)"SIGFILE", GT_HASHALG_SHA256);
+ sigblkInit(ctx);
+ if(!strcmp(name, "-"))
+ fp = stdin;
+ else
+ fp = fopen(name, "r");
+
+ while(1) {
+ if(fgets(line, sizeof(line), fp) == NULL) {
+ if(!feof(fp))
+ perror(name);
+ break;
+ }
+ len = strlen(line);
+ if(line[len-1] == '\n') {
+ --len;
+ line[len] = '\0';
+ }
+ //sign(line, len);
+ sigblkAddRecord(ctx, (unsigned char*)line, len);
+ }
+
+ if(fp != stdin)
+ fclose(fp);
+ sigblkFinish(ctx);
+ rsgtCtxDel(ctx);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ rsgtInit("rsyslog logsigner " VERSION);
+ processFile("-");
+ rsgtExit();
+ return 0;
+}
diff --git a/tools/omfile.c b/tools/omfile.c
index 1c65fc59..faf3c24f 100644
--- a/tools/omfile.c
+++ b/tools/omfile.c
@@ -17,7 +17,7 @@
* pipes. These have been moved to ompipe, to reduced the entanglement
* between the two different functionalities. -- rgerhards
*
- * Copyright 2007-2012 Adiscon GmbH.
+ * Copyright 2007-2013 Adiscon GmbH.
*
* This file is part of rsyslog.
*
@@ -69,6 +69,7 @@
#include "unicode-helper.h"
#include "atomic.h"
#include "statsobj.h"
+#include "sigprov.h"
MODULE_TYPE_OUTPUT
MODULE_TYPE_NOKEEP
@@ -118,6 +119,7 @@ getClockFileAccess(void)
struct s_dynaFileCacheEntry {
uchar *pName; /* name currently open, if dynamic name */
strm_t *pStrm; /* our output stream */
+ void *sigprovFileData; /* opaque data ptr for provider use */
uint64 clkTickAccessed;/* for LRU - based on clockFileAccess */
};
typedef struct s_dynaFileCacheEntry dynaFileCacheEntry;
@@ -143,6 +145,12 @@ typedef struct _instanceData {
gid_t fileGID;
gid_t dirGID;
int bFailOnChown; /* fail creation if chown fails? */
+ uchar *sigprovName; /* signature provider */
+ uchar *sigprovNameFull;/* full internal signature provider name */
+ sigprov_if_t sigprov; /* ptr to signature provider interface */
+ void *sigprovData; /* opaque data ptr for provider use */
+ void *sigprovFileData;/* opaque data ptr for file instance */
+ sbool useSigprov; /* quicker than checkig ptr (1 vs 8 bytes!) */
int iCurrElt; /* currently active cache element (-1 = none) */
int iCurrCacheSize; /* currently cache size (1-based) */
int iDynaFileCacheSize; /* size of file handle cache */
@@ -228,7 +236,8 @@ static struct cnfparamdescr actpdescr[] = {
{ "sync", eCmdHdlrBinary, 0 }, /* legacy: actionfileenablesync */
{ "file", eCmdHdlrString, 0 }, /* either "file" or ... */
{ "dynafile", eCmdHdlrString, 0 }, /* "dynafile" MUST be present */
- { "template", eCmdHdlrGetWord, 0 },
+ { "sig.provider", eCmdHdlrGetWord, 0 },
+ { "template", eCmdHdlrGetWord, 0 }
};
static struct cnfparamblk actpblk =
{ CNFPARAMBLK_VERSION,
@@ -416,15 +425,16 @@ finalize_it:
* if the entry should be d_free()ed and 0 if not.
*/
static rsRetVal
-dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry)
+dynaFileDelCacheEntry(instanceData *pData, int iEntry, int bFreeEntry)
{
+ dynaFileCacheEntry **pCache = pData->dynCache;
DEFiRet;
ASSERT(pCache != NULL);
if(pCache[iEntry] == NULL)
FINALIZE;
- DBGPRINTF("Removed entry %d for file '%s' from dynaCache.\n", iEntry,
+ DBGPRINTF("Removing entry %d for file '%s' from dynaCache.\n", iEntry,
pCache[iEntry]->pName == NULL ? UCHAR_CONSTANT("[OPEN FAILED]") : pCache[iEntry]->pName);
if(pCache[iEntry]->pName != NULL) {
@@ -434,8 +444,10 @@ dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry)
if(pCache[iEntry]->pStrm != NULL) {
strm.Destruct(&pCache[iEntry]->pStrm);
- if(pCache[iEntry]->pStrm != NULL) /* safety check -- TODO: remove if no longer necessary */
- abort();
+ if(pData->useSigprov) {
+ pData->sigprov.OnFileClose(pCache[iEntry]->sigprovFileData);
+ pCache[iEntry]->sigprovFileData = NULL;
+ }
}
if(bFreeEntry) {
@@ -460,7 +472,7 @@ dynaFileFreeCacheEntries(instanceData *pData)
BEGINfunc;
for(i = 0 ; i < pData->iCurrCacheSize ; ++i) {
- dynaFileDelCacheEntry(pData->dynCache, i, 1);
+ dynaFileDelCacheEntry(pData, i, 1);
}
pData->iCurrElt = -1; /* invalidate current element */
ENDfunc;
@@ -481,6 +493,29 @@ static void dynaFileFreeCache(instanceData *pData)
}
+/* close current file */
+static rsRetVal
+closeFile(instanceData *pData)
+{
+ DEFiRet;
+ if(pData->useSigprov) {
+ pData->sigprov.OnFileClose(pData->sigprovFileData);
+ pData->sigprovFileData = NULL;
+ }
+ strm.Destruct(&pData->pStrm);
+ RETiRet;
+}
+
+
+/* This prepares the signature provider to process a file */
+static rsRetVal
+sigprovPrepare(instanceData *pData, uchar *fn)
+{
+ DEFiRet;
+ pData->sigprov.OnFileOpen(pData->sigprovData, fn, &pData->sigprovFileData);
+ RETiRet;
+}
+
/* This is now shared code for all types of files. It simply prepares
* file access, which, among others, means the the file wil be opened
* and any directories in between will be created (based on config, of
@@ -563,11 +598,14 @@ prepareFile(instanceData *pData, uchar *newFileName)
if(pData->pszSizeLimitCmd != NULL)
CHKiRet(strm.SetpszSizeLimitCmd(pData->pStrm, ustrdup(pData->pszSizeLimitCmd)));
CHKiRet(strm.ConstructFinalize(pData->pStrm));
+
+ if(pData->useSigprov)
+ sigprovPrepare(pData, szNameBuf);
finalize_it:
if(iRet != RS_RET_OK) {
if(pData->pStrm != NULL) {
- strm.Destruct(&pData->pStrm);
+ closeFile(pData);
}
}
RETiRet;
@@ -598,9 +636,7 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
pCache = pData->dynCache;
- /* first check, if we still have the current file
- * I *hope* this will be a performance enhancement.
- */
+ /* first check, if we still have the current file */
if( (pData->iCurrElt != -1)
&& !ustrcmp(newFileName, pCache[pData->iCurrElt]->pName)) {
/* great, we are all set */
@@ -622,9 +658,11 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
if(iFirstFree == -1)
iFirstFree = i;
} else { /* got an element, let's see if it matches */
- if(!ustrcmp(newFileName, pCache[i]->pName)) { // RG: name == NULL?
+ if(!ustrcmp(newFileName, pCache[i]->pName)) {
/* we found our element! */
pData->pStrm = pCache[i]->pStrm;
+ if(pData->useSigprov)
+ pData->sigprovFileData = pCache[i]->sigprovFileData;
pData->iCurrElt = i;
pCache[i]->clkTickAccessed = getClockFileAccess(); /* update "timestamp" for LRU */
FINALIZE;
@@ -651,7 +689,7 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
* but it could be triggered in the common case of a failed open() system call.
* rgerhards, 2010-03-22
*/
- pData->pStrm = NULL;
+ pData->pStrm = pData->sigprovFileData = NULL;
if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) {
/* there is space left, so set it to that index */
@@ -664,14 +702,11 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
* The cache array is only updated after the open was successful. -- rgerhards, 2010-03-21
*/
if(iFirstFree == -1) {
- dynaFileDelCacheEntry(pCache, iOldest, 0);
+ dynaFileDelCacheEntry(pData, iOldest, 0);
STATSCOUNTER_INC(pData->ctrEvict, pData->mutCtrEvict);
iFirstFree = iOldest; /* this one *is* now free ;) */
} else {
/* we need to allocate memory for the cache structure */
- /* TODO: performance note: we could alloc all entries on startup, thus saving malloc
- * overhead -- this may be something to consider in v5...
- */
CHKmalloc(pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry)));
}
@@ -694,10 +729,12 @@ prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
}
if((pCache[iFirstFree]->pName = ustrdup(newFileName)) == NULL) {
- strm.Destruct(&pData->pStrm); /* need to free failed entry! */
+ closeFile(pData); /* need to free failed entry! */
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
pCache[iFirstFree]->pStrm = pData->pStrm;
+ if(pData->useSigprov)
+ pCache[iFirstFree]->sigprovFileData = pData->sigprovFileData;
pCache[iFirstFree]->clkTickAccessed = getClockFileAccess();
pData->iCurrElt = iFirstFree;
DBGPRINTF("Added new entry %d for file cache, file '%s'.\n", iFirstFree, newFileName);
@@ -722,7 +759,9 @@ doWrite(instanceData *pData, uchar *pszBuf, int lenBuf)
DBGPRINTF("write to stream, pData->pStrm %p, lenBuf %d\n", pData->pStrm, lenBuf);
if(pData->pStrm != NULL){
CHKiRet(strm.Write(pData->pStrm, pszBuf, lenBuf));
- FINALIZE;
+ if(pData->useSigprov) {
+ CHKiRet(pData->sigprov.OnRecordWrite(pData->sigprovFileData, pszBuf, lenBuf));
+ }
}
finalize_it:
@@ -730,10 +769,7 @@ finalize_it:
}
-/* rgerhards 2004-11-11: write to a file output. This
- * will be called for all outputs using file semantics,
- * for example also for pipes.
- */
+/* rgerhards 2004-11-11: write to a file output. */
static rsRetVal
writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData)
{
@@ -841,7 +877,14 @@ CODESTARTfreeInstance
if(pData->bDynamicName) {
dynaFileFreeCache(pData);
} else if(pData->pStrm != NULL)
- strm.Destruct(&pData->pStrm);
+ closeFile(pData);
+ if(pData->useSigprov) {
+ pData->sigprov.Destruct(&pData->sigprovData);
+ obj.ReleaseObj(__FILE__, pData->sigprovNameFull+2, pData->sigprovNameFull,
+ (void*) &pData->sigprov);
+ free(pData->sigprovName);
+ free(pData->sigprovNameFull);
+ }
ENDfreeInstance
@@ -907,6 +950,8 @@ setInstParamDefaults(instanceData *pData)
pData->iIOBufSize = IOBUF_DFLT_SIZE;
pData->iFlushInterval = FLUSH_INTRVL_DFLT;
pData->bUseAsyncWriter = USE_ASYNCWRITER_DFLT;
+ pData->sigprovName = NULL;
+ pData->useSigprov = 0;
}
@@ -946,6 +991,48 @@ finalize_it:
RETiRet;
}
+static inline void
+initSigprov(instanceData *pData, struct nvlst *lst)
+{
+ uchar szDrvrName[1024];
+
+ if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmsig_%s", pData->sigprovName)
+ == sizeof(szDrvrName)) {
+ errmsg.LogError(0, RS_RET_ERR, "omfile: signature provider "
+ "name is too long: '%s' - signatures disabled",
+ pData->sigprovName);
+ goto done;
+ }
+ pData->sigprovNameFull = ustrdup(szDrvrName);
+
+ pData->sigprov.ifVersion = sigprovCURR_IF_VERSION;
+ /* The pDrvrName+2 below is a hack to obtain the object name. It
+ * safes us to have yet another variable with the name without "lm" in
+ * front of it. If we change the module load interface, we may re-think
+ * about this hack, but for the time being it is efficient and clean enough.
+ */
+ if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pData->sigprov)
+ != RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_LOAD_ERROR, "omfile: could not load "
+ "signature provider '%s' - signatures disabled",
+ szDrvrName);
+ goto done;
+ }
+
+ if(pData->sigprov.Construct(&pData->sigprovData) != RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_SIGPROV_ERR, "omfile: error constructing "
+ "signature provider %s dataset - signatures disabled",
+ szDrvrName);
+ goto done;
+ }
+ pData->sigprov.SetCnfParam(pData->sigprovData, lst);
+
+ dbgprintf("loaded signature provider %s, data instance at %p\n",
+ szDrvrName, pData->sigprovData);
+ pData->useSigprov = 1;
+done: return;
+}
+
BEGINnewActInst
struct cnfparamvals *pvals;
uchar *tplToUse;
@@ -1013,6 +1100,8 @@ CODESTARTnewActInst
pData->bDynamicName = 1;
} else if(!strcmp(actpblk.descr[i].name, "template")) {
pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(actpblk.descr[i].name, "sig.provider")) {
+ pData->sigprovName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
} else {
dbgprintf("omfile: program error, non-handled "
"param '%s'\n", actpblk.descr[i].name);
@@ -1025,6 +1114,10 @@ CODESTARTnewActInst
ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
}
+ if(pData->sigprovName != NULL) {
+ initSigprov(pData, lst);
+ }
+
tplToUse = ustrdup((pData->tplName == NULL) ? getDfltTpl() : pData->tplName);
CHKiRet(OMSRsetEntry(*ppOMSR, 0, tplToUse, OMSR_NO_RQD_TPL_OPTS));
@@ -1167,8 +1260,7 @@ CODESTARTdoHUP
dynaFileFreeCacheEntries(pData);
} else {
if(pData->pStrm != NULL) {
- strm.Destruct(&pData->pStrm);
- pData->pStrm = NULL;
+ closeFile(pData);
}
}
ENDdoHUP
diff --git a/tools/rsgtutil.c b/tools/rsgtutil.c
new file mode 100644
index 00000000..9d9f3568
--- /dev/null
+++ b/tools/rsgtutil.c
@@ -0,0 +1,342 @@
+/* This is a tool for dumpoing the content of GuardTime TLV
+ * files in a (somewhat) human-readable manner.
+ *
+ * Copyright 2013 Adiscon GmbH
+ *
+ * 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 exprs 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 <gt_base.h>
+#include <gt_http.h>
+#include <getopt.h>
+
+#include "librsgt.h"
+
+typedef unsigned char uchar;
+
+static enum { MD_DUMP, MD_DETECT_FILE_TYPE, MD_SHOW_SIGBLK_PARAMS,
+ MD_VERIFY
+} mode = MD_DUMP;
+static int verbose = 0;
+
+static void
+dumpFile(char *name)
+{
+ FILE *fp;
+ uchar hdr[9];
+ uint16_t tlvtype, tlvlen;
+ void *obj;
+ int r = -1;
+
+ if(!strcmp(name, "-"))
+ fp = stdin;
+ else {
+ printf("Processing file %s:\n", name);
+ if((fp = fopen(name, "r")) == NULL) {
+ perror(name);
+ goto err;
+ }
+ }
+ if((r = rsgt_tlvrdHeader(fp, hdr)) != 0) goto err;
+ printf("File Header: '%s'\n", hdr);
+ while(1) { /* we will err out on EOF */
+ if((r = rsgt_tlvrd(fp, &tlvtype, &tlvlen, &obj)) != 0) {
+ if(feof(fp))
+ break;
+ else
+ goto err;
+ }
+ rsgt_tlvprint(stdout, tlvtype, obj, verbose);
+ }
+
+ if(fp != stdin)
+ fclose(fp);
+ return;
+err: fprintf(stderr, "error %d processing file %s\n", r, name);
+}
+
+static void
+showSigblkParams(char *name)
+{
+ FILE *fp;
+ block_sig_t *bs;
+ uint8_t bHasRecHashes, bHasIntermedHashes;
+ uint64_t blkCnt = 0;
+ int r = -1;
+
+ if(!strcmp(name, "-"))
+ fp = stdin;
+ else {
+ if((fp = fopen(name, "r")) == NULL) {
+ perror(name);
+ goto err;
+ }
+ }
+ if((r = rsgt_chkFileHdr(fp, "LOGSIG10")) != 0) goto err;
+
+ while(1) { /* we will err out on EOF */
+ if((r = rsgt_getBlockParams(fp, 0, &bs, &bHasRecHashes,
+ &bHasIntermedHashes)) != 0)
+ goto err;
+ ++blkCnt;
+ rsgt_printBLOCK_SIG(stdout, bs, verbose);
+ printf("\t***META INFORMATION:\n");
+ printf("\tBlock Nbr in File...: %llu\n", blkCnt);
+ printf("\tHas Record Hashes...: %d\n", bHasRecHashes);
+ printf("\tHas Tree Hashes.....: %d\n", bHasIntermedHashes);
+ }
+
+ if(fp != stdin)
+ fclose(fp);
+ return;
+err:
+ if(r != RSGTE_EOF)
+ fprintf(stderr, "error %d processing file %s\n", r, name);
+}
+
+static void
+detectFileType(char *name)
+{
+ FILE *fp;
+ char *typeName;
+ char hdr[9];
+ int r = -1;
+
+ if(!strcmp(name, "-"))
+ fp = stdin;
+ else {
+ if((fp = fopen(name, "r")) == NULL) {
+ perror(name);
+ goto err;
+ }
+ }
+ if((r = rsgt_tlvrdHeader(fp, (uchar*)hdr)) != 0) goto err;
+ if(!strcmp(hdr, "LOGSIG10"))
+ typeName = "Log Signature File, Version 10";
+ else if(!strcmp(hdr, "GTSTAT10"))
+ typeName = "rsyslog GuardTime Signature State File, Version 10";
+ else
+ typeName = "unknown";
+
+ printf("%s: %s [%s]\n", name, hdr, typeName);
+
+ if(fp != stdin)
+ fclose(fp);
+ return;
+err: fprintf(stderr, "error %d processing file %s\n", r, name);
+}
+
+static inline int
+doVerifyRec(FILE *logfp, FILE *sigfp, block_sig_t *bs, gtfile gf, gterrctx_t *ectx, uint8_t bInBlock)
+{
+ int r;
+ size_t lenRec;
+ char rec[128*1024];
+
+ if(fgets(rec, sizeof(rec), logfp) == NULL) {
+ r = feof(logfp) ? RSGTE_EOF : RSGTE_IO;
+ goto done;
+ }
+ lenRec = strlen(rec);
+ if(rec[lenRec-1] == '\n') {
+ rec[lenRec-1] = '\0';
+ --lenRec;
+ rsgt_errctxSetErrRec(ectx, rec);
+ }
+
+ /* we need to preserve the first record of each block for
+ * error-reporting purposes (bInBlock==0 meanst start of block)
+ */
+ if(bInBlock == 0)
+ rsgt_errctxFrstRecInBlk(ectx, rec);
+
+ r = rsgt_vrfy_nextRec(bs, gf, sigfp, (unsigned char*)rec, lenRec, ectx);
+done:
+ return r;
+}
+
+/* note: here we need to have the LOG file name, not signature! */
+static void
+verify(char *name)
+{
+ FILE *logfp = NULL, *sigfp = NULL;
+ block_sig_t *bs;
+ gtfile gf;
+ uint8_t bHasRecHashes, bHasIntermedHashes;
+ uint8_t bInBlock;
+ int r = 0;
+ char sigfname[4096];
+ gterrctx_t ectx;
+
+ if(!strcmp(name, "-")) {
+ fprintf(stderr, "verify mode cannot work on stdin\n");
+ goto err;
+ } else {
+ snprintf(sigfname, sizeof(sigfname), "%s.gtsig", name);
+ sigfname[sizeof(sigfname)-1] = '\0';
+ if((logfp = fopen(name, "r")) == NULL) {
+ perror(name);
+ goto err;
+ }
+ if((sigfp = fopen(sigfname, "r")) == NULL) {
+ perror(name);
+ goto err;
+ }
+ }
+
+ rsgtInit("rsyslog rsgtutil " VERSION);
+ rsgt_errctxInit(&ectx);
+ ectx.verbose = verbose;
+ ectx.fp = stderr;
+ ectx.filename = strdup(sigfname);
+
+ if((r = rsgt_chkFileHdr(sigfp, "LOGSIG10")) != 0) goto err;
+
+ gf = rsgt_vrfyConstruct_gf();
+ if(gf == NULL) {
+ fprintf(stderr, "error initializing signature file structure\n");
+ goto err;
+ }
+
+ bInBlock = 0;
+ ectx.blkNum = 0;
+ ectx.recNumInFile = 0;
+
+ while(!feof(logfp)) {
+ if(bInBlock == 0) {
+ if((r = rsgt_getBlockParams(sigfp, 1, &bs, &bHasRecHashes,
+ &bHasIntermedHashes)) != 0)
+ goto err;
+ rsgt_vrfyBlkInit(gf, bs, bHasRecHashes, bHasIntermedHashes);
+ ectx.recNum = 0;
+ ++ectx.blkNum;
+ }
+ ++ectx.recNum, ++ectx.recNumInFile;
+ if((r = doVerifyRec(logfp, sigfp, bs, gf, &ectx, bInBlock)) != 0)
+ goto err;
+ if(ectx.recNum == bs->recCount) {
+ verifyBLOCK_SIG(bs, gf, sigfp, &ectx);
+ bInBlock = 0;
+ } else bInBlock = 1;
+ }
+
+ fclose(logfp);
+ fclose(sigfp);
+ rsgtExit();
+ rsgt_errctxExit(&ectx);
+ return;
+err:
+ if(logfp != NULL)
+ fclose(logfp);
+ if(sigfp != NULL)
+ fclose(sigfp);
+ if(r != RSGTE_EOF)
+ fprintf(stderr, "error %d processing file %s\n", r, name);
+ rsgtExit();
+ rsgt_errctxExit(&ectx);
+}
+
+static void
+processFile(char *name)
+{
+ switch(mode) {
+ case MD_DETECT_FILE_TYPE:
+ detectFileType(name);
+ break;
+ case MD_DUMP:
+ dumpFile(name);
+ break;
+ case MD_SHOW_SIGBLK_PARAMS:
+ showSigblkParams(name);
+ break;
+ case MD_VERIFY:
+ verify(name);
+ break;
+ }
+}
+
+
+static struct option long_options[] =
+{
+ {"dump", no_argument, NULL, 'D'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {"detect-file-type", no_argument, NULL, 'T'},
+ {"show-sigblock-params", no_argument, NULL, 'B'},
+ {"verify", no_argument, NULL, 't'}, /* 't' as in "test signatures" */
+ {"publications-server", optional_argument, NULL, 'P'},
+ {"show-verified", no_argument, NULL, 's'},
+ {NULL, 0, NULL, 0}
+};
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int opt;
+
+ while(1) {
+ opt = getopt_long(argc, argv, "DvVTBtPs", long_options, NULL);
+ if(opt == -1)
+ break;
+ switch(opt) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 's':
+ rsgt_read_showVerified = 1;
+ break;
+ case 'V':
+ fprintf(stderr, "rsgtutil " VERSION "\n");
+ exit(0);
+ case 'D':
+ mode = MD_DUMP;
+ break;
+ case 'B':
+ mode = MD_SHOW_SIGBLK_PARAMS;
+ break;
+ case 'P':
+ rsgt_read_puburl = optarg;
+ break;
+ case 'T':
+ mode = MD_DETECT_FILE_TYPE;
+ break;
+ case 't':
+ mode = MD_VERIFY;
+ break;
+ case '?':
+ break;
+ default:fprintf(stderr, "getopt_long() returns unknown value %d\n", opt);
+ return 1;
+ }
+ }
+
+ if(optind == argc)
+ processFile("-");
+ else {
+ for(i = optind ; i < argc ; ++i)
+ processFile(argv[i]);
+ }
+
+ return 0;
+}
diff --git a/tools/rsgtutil.rst b/tools/rsgtutil.rst
new file mode 100644
index 00000000..c5782c5a
--- /dev/null
+++ b/tools/rsgtutil.rst
@@ -0,0 +1,150 @@
+========
+rsgtutil
+========
+
+-----------------------------------
+Manage (GuardTime) Signed Log Files
+-----------------------------------
+
+:Author: Rainer Gerhards <rgerhards@adiscon.com>
+:Date: 2013-03-22
+:Manual section: 1
+
+SYNOPSIS
+========
+
+::
+
+ rsgtutil [OPTIONS] [FILE] ...
+
+
+DESCRIPTION
+===========
+
+This tool performs various maintenance operations on signed log files.
+It specifically supports the GuardTime signature provider.
+
+The *rsgtutil* tool is the primary tool to verify log file signatures,
+dump signature file contents and carry out other maintenance operations.
+The tool offers different operation modes, which are selected via
+command line options.
+
+The processing of multiple files is permitted. Depending on operation
+mode, either the signature file or the base log file must be specified.
+Within a single call, only a single operations mode is permitted. To
+use different modes on different files, multiple calles, one for each
+mode, must be made.
+
+If no file is specified on the command line, stdin is used instead. Note
+that not all operation modes support stdin.
+
+OPTIONS
+=======
+
+-D, --dump
+ Select "dump" operations mode.
+
+-t, --verify
+ Select "verify" operations mode.
+
+-T, --detect-file-type
+ Select "detect-file-type" operations mode.
+
+-B, --show-sigblock-params
+ Select "show-sigblock-params" operations mode.
+
+-s, --show-verified
+ Prints out information about correctly verified blocks (by default, only
+ errors are printed).
+
+-v, --verbose
+ Select verbose mode. Most importantly, hashes and signatures are printed
+ in full length (can be **very** lengthy) rather than the usual abbreviation.
+
+-P <URL>, --publications-server <URL>
+ Sets the publications server. If not set but required by the operation a
+ default server is used. The default server is not necessarily optimal
+ in regard to performance and reliability.
+
+
+OPERATION MODES
+===============
+
+The operation mode specifies what exactly the tool does with the provided
+files. The default operation mode is "dump", but this may change in the future.
+Thus, it is recommended to always set the operations mode explicitely. If
+multiple operations mode are set on the command line, results are
+unpredictable.
+
+dump
+----
+
+The provided *signature* files are dumped. For each top-level record, the*u
+type code is printed as well as q short description. If there is additional
+information available, it will be printed in tab-indented lines below the
+main record dump. The actual *log* files need not to be present.
+
+verify
+------
+
+This mode does not work with stdin. On the command line, the *log* file names
+are specified. The corresponding *signature* files (ending on ".gtsig") must also
+be preset at the same location as the log file. In verify mode, both the log
+and signature file is read and the validity of the log file checked. If verification
+errors are detected these are printed and processing of the file aborted. By default,
+each file is verified individually, without taking cross-file hash chains into
+account (so the order of files on the command line does not matter).
+
+Note that the actual amount of what can be verified depends on the parameters with
+which the signature file was written. If record and tree hashes are present, they
+will be verified and thus fine-granular error reporting is possible. If they are
+not present, only the block signature itself is verified.
+
+By default, only errors are printed. To also print successful verifications, use the
+**--show-verified** option.
+
+
+detect-file-type
+----------------
+This mode is used to detect the type of some well-know files used inside the
+signature system. The detection is based on the file header. This mode is
+primarily a debug aid.
+
+
+show-sigblock-params
+--------------------
+This mode is used to print signature block parameters. It is similar to *dump*
+mode, but will ignore everything except signature blocks. Also, some additional
+meta information is printed. This mode is primarily a debug aid.
+
+EXIT CODES
+==========
+
+The command returns an exit code of 0 if everything went fine, and some
+other code in case of failures.
+
+
+EXAMPLES
+========
+
+**rsgtutil --verify logfile**
+
+This verifies the file "logfile" via its associated signature file
+"logfile.gtsig". If errors are detected, these are reported to stderr.
+Otherwise, rsgtutil terminates without messages.
+
+**rsgtutil --dump logfile.gtsig**
+
+This dumps the content of the signature file "logfile.gtsig". The
+actual log file is not being processed and does not even need to be
+present.
+
+SEE ALSO
+========
+**rsyslogd(8)**
+
+COPYRIGHT
+=========
+
+This page is part of the *rsyslog* project, and is available under
+LGPLv2.