Пример #1
0
int main(int argc, char **argv) {
	int res = KSI_UNKNOWN_ERROR;
	/* Signature read from the file. */
	KSI_Signature *sig = NULL;
	
	/* Signature extended to the publication. */
	KSI_Signature *ext = NULL;

	/* Hash of the data file. */
	KSI_DataHash *hsh = NULL;

	/* Hash value extracted from the signature. */
	KSI_DataHash *signHsh = NULL;

	/* Data file hasher. */
	KSI_DataHasher *hsr = NULL;

	/* Input file descriptor. */
	FILE *in = NULL;

	/* Buffer for reading the input. */
	unsigned char buf[1024];

	/* Length of the buffer content. */
	size_t buf_len;

	/* Verification info object. */
	const KSI_VerificationResult *info = NULL;

	/* File descriptor for logging. */
	FILE *logFile = NULL;

	const KSI_CertConstraint pubFileCertConstr[] = {
			{ KSI_CERT_EMAIL, "*****@*****.**"},
			{ NULL, NULL }
	};

	/* Init context. */
	res = KSI_CTX_new(&ksi);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to init KSI context.\n");
		goto cleanup;
	}

	logFile = fopen("ksi_verify.log", "w");
	if (logFile == NULL) {
		fprintf(stderr, "Unable to open log file.\n");
	}

	res = KSI_CTX_setDefaultPubFileCertConstraints(ksi, pubFileCertConstr);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to configure publications file cert constraints.\n");
		goto cleanup;
	}

	/* Configure the logger. */
	KSI_CTX_setLoggerCallback(ksi, KSI_LOG_StreamLogger, logFile);
	KSI_CTX_setLogLevel(ksi, KSI_LOG_DEBUG);

	KSI_LOG_info(ksi, "Using KSI version: '%s'", KSI_getVersion());

	/* Check parameters. */
	if (argc != 6) {
		fprintf(stderr, "Usage\n"
				"  %s <data file | -> <signature> <publication-str> <extender url> <pub-file url>\n", argv[0]);
		goto cleanup;
	}

	/* Configure extender. */
	res = KSI_CTX_setExtender(ksi, argv[4], "anon", "anon");
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to set extender parameters.\n");
		goto cleanup;
	}

	/* Set the publications file url. */
	res = KSI_CTX_setPublicationUrl(ksi, argv[4]);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to set publications file url.\n");
		goto cleanup;
	}

	printf("Reading signature... ");
	/* Read the signature. */
	res = KSI_Signature_fromFile(ksi, argv[2], &sig);
	if (res != KSI_OK) {
		printf("failed (%s)\n", KSI_getErrorString(res));
		goto cleanup;
	}
	printf("ok\n");

	printf("Verifying the signature with the publication... ");
	res = extendToPublication(sig, argv[3], &ext);
	switch (res) {
		case KSI_OK:
			printf("ok\n");
			break;
		case KSI_VERIFICATION_FAILURE:
			printf("failed\n");
			break;
		default:
			printf("failed (%s)\n", KSI_getErrorString(res));
			goto cleanup;
	}

	/* Create hasher. */
	res = KSI_Signature_createDataHasher(ext, &hsr);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to create data hasher.\n");
		goto cleanup;
	}

	if (strcmp(argv[1], "-")) {
		in = fopen(argv[1], "rb");
		if (in == NULL) {
			fprintf(stderr, "Unable to open data file '%s'.\n", argv[1]);
			goto cleanup;
		}
		/* Calculate the hash of the document. */
		while (!feof(in)) {
			buf_len = fread(buf, 1, sizeof(buf), in);
			res = KSI_DataHasher_add(hsr, buf, buf_len);
			if (res != KSI_OK) {
				fprintf(stderr, "Unable hash the document.\n");
				goto cleanup;
			}
		}

		/* Finalize the hash computation. */
		res = KSI_DataHasher_close(hsr, &hsh);
		if (res != KSI_OK) {
			fprintf(stderr, "Failed to close the hashing process.\n");
			goto cleanup;
		}

		res = KSI_Signature_getDocumentHash(sig, &signHsh);
		if (res != KSI_OK) goto cleanup;

		printf("Verifying document hash... ");
		if (!KSI_DataHash_equals(hsh, signHsh)) {
			printf("Wrong document!\n");
			goto cleanup;
		}
		printf("ok\n");
	} 

	res = KSI_Signature_getVerificationResult(ext, &info);
	if (res != KSI_OK) goto cleanup;

	if (info != NULL) {
		size_t i;
		printf("Verification info:\n");
		for (i = 0; i < KSI_VerificationResult_getStepResultCount(info); i++) {
			const KSI_VerificationStepResult *result = NULL;
			const char *desc = NULL;
			res = KSI_VerificationResult_getStepResult(info, i, &result);
			if (res != KSI_OK) goto cleanup;
			printf("\t0x%02x:\t%s", KSI_VerificationStepResult_getStep(result), KSI_VerificationStepResult_isSuccess(result) ? "OK" : "FAIL");
			desc = KSI_VerificationStepResult_getDescription(result);
			if (desc && *desc) {
				printf(" (%s)", desc);
			}
			printf("\n");
		}
	}

	res = KSI_OK;

cleanup:

	if (logFile != NULL) fclose(logFile);
	if (res != KSI_OK && ksi != NULL) {
		KSI_ERR_statusDump(ksi, stderr);
	}

	if (in != NULL) fclose(in);

	KSI_Signature_free(sig);
	KSI_Signature_free(ext);
	KSI_DataHasher_free(hsr);
	KSI_DataHash_free(hsh);
	KSI_CTX_free(ksi);

	return res;
}
Пример #2
0
static int verifyOnline(KSI_CTX *ctx, KSI_Signature *sig) {
	int res = KSI_UNKNOWN_ERROR;

	KSI_ExtendReq *req = NULL;
	KSI_Integer *start = NULL;
	KSI_Integer *end = NULL;
	KSI_RequestHandle *handle = NULL;
	KSI_DataHash *extHash = NULL;
	KSI_DataHash *calHash = NULL;
	KSI_ExtendResp *resp = NULL;
	KSI_Integer *status = NULL;
	KSI_CalendarHashChain *calChain = NULL;
	KSI_DataHash *rootHash = NULL;
	KSI_DataHash *pubHash = NULL;
	KSI_VerificationStep step = KSI_VERIFY_CALCHAIN_ONLINE;
	KSI_VerificationResult *info = &sig->verificationResult;

	KSI_LOG_info(sig->ctx, "Verifying signature online.");

	/* Extract start time */
	res = KSI_CalendarHashChain_getAggregationTime(sig->calendarChain, &start);
	if (res != KSI_OK) goto cleanup;

	/* Clone the start time object */
	KSI_Integer_ref(start);

	if (sig->verificationResult.useUserPublication) {
		/* Extract end time. */
		res = KSI_PublicationData_getTime(sig->verificationResult.userPublication, &end);
		if (res != KSI_OK) goto cleanup;
	}
	res = KSI_createExtendRequest(sig->ctx, start, end, &req);
	if (res != KSI_OK) goto cleanup;

	res = KSI_sendExtendRequest(ctx, req, &handle);
	if (res != KSI_OK) goto cleanup;

	res = KSI_RequestHandle_perform(handle);
	if (res != KSI_OK) {
		KSI_pushError(ctx,res, NULL);
		goto cleanup;
	}

	res = KSI_RequestHandle_getExtendResponse(handle, &resp);
	if (res != KSI_OK) goto cleanup;

	/* Verify the correctness of the response. */
	res = KSI_ExtendResp_verifyWithRequest(resp, req);
	if (res != KSI_OK) {
		KSI_pushError(ctx, res, NULL);
		goto cleanup;
	}

	res = KSI_ExtendResp_getStatus(resp, &status);
	if (res != KSI_OK) goto cleanup;

	/* Verify status. */
	if (status != NULL && !KSI_Integer_equalsUInt(status, 0)) {
		KSI_Utf8String *respErr = NULL;
		char errm[1024];

		res = KSI_ExtendResp_getErrorMsg(resp, &respErr);
		if (res != KSI_OK) goto cleanup;

		KSI_snprintf(errm, sizeof(errm), "Extend failure from server: '%s'", KSI_Utf8String_cstr(respErr));

		res = KSI_VerificationResult_addFailure(info, step, errm);
		goto cleanup;
	}

	res = KSI_ExtendResp_getCalendarHashChain(resp, &calChain);
	if (res != KSI_OK) goto cleanup;

	res = KSI_CalendarHashChain_getInputHash(calChain, &extHash);
	if (res != KSI_OK) goto cleanup;

	res = KSI_CalendarHashChain_getInputHash(sig->calendarChain, &calHash);
	if (res != KSI_OK) goto cleanup;

	if (!KSI_DataHash_equals(extHash, calHash)) {
		res = KSI_VerificationResult_addFailure(info, step, "Extender returned different input hash for calendar hash chain.");
		goto cleanup;
	}

	if (sig->verificationResult.useUserPublication) {
		res = KSI_CalendarHashChain_aggregate(calChain, &rootHash);
		if (res != KSI_OK) goto cleanup;

		if (!KSI_DataHash_equals(rootHash, pubHash)) {
			res = KSI_VerificationResult_addFailure(info, step, "External publication imprint mismatch.");
			goto cleanup;
		}
	}

	res = KSI_VerificationResult_addSuccess(info, step, "Verified online.");

cleanup:

	KSI_Integer_free(start);
	KSI_ExtendReq_free(req);
	KSI_RequestHandle_free(handle);
	KSI_ExtendResp_free(resp);

	return res;
}
Пример #3
0
static int verifyPublicationWithPubString(KSI_CTX *ctx, KSI_Signature *sig) {
	int res = KSI_UNKNOWN_ERROR;
	KSI_VerificationStep step = KSI_VERIFY_PUBLICATION_WITH_PUBSTRING;
	KSI_VerificationResult *info = &sig->verificationResult;
	KSI_Integer *time1 = NULL;
	KSI_Integer *time2 = NULL;
	KSI_DataHash *hsh1 = NULL;
	KSI_DataHash *hsh2 = NULL;


	if (sig->publication == NULL || sig->verificationResult.useUserPublication == false) {
		res = KSI_OK;
		goto cleanup;
	}


	KSI_LOG_info(ctx, "Verifying publication with publication string");

	if (sig->verificationResult.userPublication == NULL) {
		res = KSI_INVALID_ARGUMENT;
		goto cleanup;
	}

	res = KSI_PublicationData_getTime(sig->verificationResult.userPublication, &time1);
	if (res != KSI_OK) {
		res = KSI_INVALID_ARGUMENT;
		goto cleanup;
	}

	res = KSI_PublicationData_getImprint(sig->verificationResult.userPublication, &hsh1);
	if (res != KSI_OK) {
		res = KSI_INVALID_ARGUMENT;
		goto cleanup;
	}

	res = KSI_PublicationData_getTime(sig->publication->publishedData, &time2);
	if (res != KSI_OK) {
		res = KSI_INVALID_ARGUMENT;
		goto cleanup;
	}

	res = KSI_PublicationData_getImprint(sig->publication->publishedData, &hsh2);
	if (res != KSI_OK) {
		res = KSI_INVALID_ARGUMENT;
		goto cleanup;
	}

	if (KSI_Integer_compare(time1, time2) != 0) {
		KSI_LOG_debug(ctx, "Publication time from publication record:", time2);
		KSI_LOG_debug(ctx, "Publication time from user publication  :", time1);
		res = KSI_VerificationResult_addFailure(info, step, "Publication not trusted.");
		goto cleanup;
	}

	if (KSI_DataHash_equals(hsh1, hsh2) != 1) {
		KSI_LOG_logDataHash(ctx, KSI_LOG_DEBUG, "Root hash from publication record:", hsh2);
		KSI_LOG_logDataHash(ctx, KSI_LOG_DEBUG, "Root hash from user publication:", hsh1);
		res = KSI_VerificationResult_addFailure(info, step, "Publication not trusted.");
		goto cleanup;
	}

	res = KSI_VerificationResult_addSuccess(info, step, "Publication trusted.");


cleanup:

	return res;
}
Пример #4
0
static int verifyCalAuthRec(KSI_CTX *ctx, KSI_Signature *sig) {
	int res = KSI_UNKNOWN_ERROR;
	KSI_OctetString *certId = NULL;
	KSI_PKICertificate *cert = NULL;
	KSI_OctetString *signatureValue = NULL;
	KSI_Utf8String *sigtype = NULL;
	const unsigned char *rawSignature = NULL;
	size_t rawSignature_len;
	unsigned char *rawData = NULL;
	size_t rawData_len;
	KSI_VerificationStep step = KSI_VERIFY_CALAUTHREC_WITH_SIGNATURE;
	KSI_VerificationResult *info = &sig->verificationResult;


	if (sig->calendarAuthRec == NULL) {
		res = KSI_OK;
		goto cleanup;
	}

	KSI_LOG_info(sig->ctx, "Verifying calendar authentication record.");

	res = KSI_PKISignedData_getCertId(sig->calendarAuthRec->signatureData, &certId);
	if (res != KSI_OK) goto cleanup;

	if (certId == NULL) {
		res = KSI_INVALID_FORMAT;
		goto cleanup;
	}

	res = initPublicationsFile(&sig->verificationResult, ctx);
	if (res != KSI_OK) goto cleanup;

	res = KSI_PublicationsFile_getPKICertificateById(sig->verificationResult.publicationsFile, certId, &cert);
	if (res != KSI_OK) goto cleanup;

	if (cert == NULL) {
		res = KSI_VerificationResult_addFailure(info, step, "Certificate not found.");
		goto cleanup;
	}

	res = KSI_PKISignedData_getSignatureValue(sig->calendarAuthRec->signatureData, &signatureValue);
	if (res != KSI_OK) goto cleanup;

	res = KSI_OctetString_extract(signatureValue, &rawSignature, &rawSignature_len);
	if (res != KSI_OK) goto cleanup;

	res = KSI_TLV_serialize(sig->calendarAuthRec->pubData->baseTlv, &rawData, &rawData_len);
	if (res != KSI_OK) goto cleanup;

	res = KSI_PKISignedData_getSigType(sig->calendarAuthRec->signatureData, &sigtype);
	if (res != KSI_OK) goto cleanup;


	res = KSI_PKITruststore_verifyRawSignature(sig->ctx, rawData, rawData_len, KSI_Utf8String_cstr(sigtype), rawSignature, rawSignature_len, cert);

	if (res != KSI_OK) {
		res = KSI_VerificationResult_addFailure(info, step, "Calendar authentication record signature not verified.");
		goto cleanup;
	}

	res = KSI_VerificationResult_addSuccess(info, step, "Calendar authentication record verified.");

cleanup:

	KSI_free(rawData);

	return res;
}
Пример #5
0
static int verifyInternallyAggregationChain(KSI_Signature *sig) {
	int res = KSI_UNKNOWN_ERROR;
	KSI_DataHash *hsh = NULL;
	KSI_DataHash *inputHash = NULL;
	int level;
	size_t i;
	size_t successCount = 0;
	KSI_VerificationStep step = KSI_VERIFY_AGGRCHAIN_INTERNALLY;
	KSI_VerificationResult *info = &sig->verificationResult;
	const KSI_AggregationHashChain *prevChain = NULL;

	/* Aggregate aggregation chains. */
	hsh = NULL;

	/* The aggregation level might not be 0 in case of local aggregation. */
	if (sig->verificationResult.docAggrLevel > 0xff) {
		KSI_pushError(sig->ctx, res = KSI_INVALID_FORMAT, "Aggregation level can't be larger than 0xff.");
		goto cleanup;
	}

	level = (int)sig->verificationResult.docAggrLevel;

	KSI_LOG_info(sig->ctx, "Verifying aggregation hash chain internal consistency.");

	if (sig->rfc3161 != NULL) {
		KSI_LOG_info(sig->ctx, "Using input hash calculated from RFC 3161 for aggregation.");
		res = rfc3161_getInputToAggreChain(sig, &inputHash);
		if (res != KSI_OK) goto cleanup;

		res = rfc3161_verify(sig);
		if (res != KSI_OK){
			res = KSI_VerificationResult_addFailure(info, step, "RFC 3161 does not belong to this aggregation hash chain.");
			goto cleanup;
		}
	}

	/* Aggregate all the aggregation chains. */
	for (i = 0; i < KSI_AggregationHashChainList_length(sig->aggregationChainList); i++) {
		const KSI_AggregationHashChain* aggregationChain = NULL;
		KSI_DataHash *tmpHash = NULL;


		res = KSI_AggregationHashChainList_elementAt(sig->aggregationChainList, i, (KSI_AggregationHashChain **)&aggregationChain);
		if (res != KSI_OK || aggregationChain == NULL) {
			if (res == KSI_OK) res = KSI_INVALID_STATE;
			goto cleanup;
		}

		if (prevChain != NULL) {
			/* Verify aggregation time. */
			if (!KSI_Integer_equals(aggregationChain->aggregationTime, prevChain->aggregationTime)) {
				res = KSI_VerificationResult_addFailure(info, step, "Aggregation hash chain's from different aggregation rounds.");
				goto cleanup;
			}

			/* Verify chain index length. */
			if (KSI_IntegerList_length(prevChain->chainIndex) != KSI_IntegerList_length(aggregationChain->chainIndex) + 1) {
				res = KSI_VerificationResult_addFailure(info, step, "Unexpected chain index length in aggregation chain.");
				goto cleanup;
			} else {
				unsigned j;
				for (j = 0; j < KSI_IntegerList_length(aggregationChain->chainIndex); j++) {
					KSI_Integer *chainIndex1 = NULL;
					KSI_Integer *chainIndex2 = NULL;

					res = KSI_IntegerList_elementAt(prevChain->chainIndex, j, &chainIndex1);
					if (res != KSI_OK) goto cleanup;

					res = KSI_IntegerList_elementAt(aggregationChain->chainIndex, j, &chainIndex2);
					if (res != KSI_OK) goto cleanup;

					if (!KSI_Integer_equals(chainIndex1, chainIndex2)) {
						res = KSI_VerificationResult_addFailure(info, step, "Aggregation chain chain index is not continuation of previous chain index.");
						goto cleanup;
					}
				}
			}
		}

		if (i == 0 && inputHash != NULL){
			if (!KSI_DataHash_equals(inputHash, aggregationChain->inputHash)) {
				KSI_LOG_logDataHash(sig->ctx, KSI_LOG_DEBUG, "Input hash from RFC 3161 :", inputHash);
				KSI_LOG_logDataHash(sig->ctx, KSI_LOG_DEBUG, "Expected input hash      :", aggregationChain->inputHash);
				res = KSI_VerificationResult_addFailure(info, step, "Aggregation hash chain's input hash does not match with RFC 3161 input hash.");
				goto cleanup;
			}
		}

		if (hsh != NULL) {
			/* Validate input hash */
			if (!KSI_DataHash_equals(hsh, aggregationChain->inputHash)) {
				KSI_LOG_logDataHash(sig->ctx, KSI_LOG_DEBUG, "Calculated hash", hsh);
				KSI_LOG_logDataHash(sig->ctx, KSI_LOG_DEBUG, "  Expected hash", aggregationChain->inputHash);
				break;
			}
		}

		res = KSI_HashChain_aggregate(aggregationChain->ctx, aggregationChain->chain, aggregationChain->inputHash, level, (int)KSI_Integer_getUInt64(aggregationChain->aggrHashId), &level, &tmpHash);
		if (res != KSI_OK) goto cleanup;

		/* TODO! Instead of freeing the object - reuse it */
		if (hsh != NULL) {
			KSI_DataHash_free(hsh);
		}

		hsh = tmpHash;


		++successCount;

		prevChain = aggregationChain;
	}

	/* First verify internal calculations. */
	if (successCount != KSI_AggregationHashChainList_length(sig->aggregationChainList)) {
		res = KSI_VerificationResult_addFailure(info, step, "Aggregation hash chain calculation failed.");
		goto cleanup;
	}

	sig->verificationResult.aggregationHash = hsh;
	hsh = NULL;

	res = KSI_VerificationResult_addSuccess(info,  step,"Aggregation chain internally consistent.");

cleanup:

	KSI_DataHash_free(hsh);
	KSI_DataHash_free(inputHash);

	return res;
}
Пример #6
0
int main(int argc, char **argv) {
	KSI_CTX *ksi = NULL;
	int res = KSI_UNKNOWN_ERROR;

	FILE *in = NULL;
	FILE *out = NULL;

	KSI_DataHasher *hsr = NULL;
	KSI_DataHash *hsh = NULL;
	KSI_Signature *sign = NULL;

	unsigned char *raw = NULL;
	unsigned raw_len;

	unsigned char buf[1024];
	unsigned buf_len;

	char *signerIdentity = NULL;

	FILE *logFile = NULL;

	/* Handle command line parameters */
	/* Handle command line parameters */
	if (argc != 7) {
		fprintf(stderr, "Usage:\n"
				"  %s <in-data-file> <out-sign-file> <aggregator-uri> <user> <pass> <pub-file url | -> \n", argv[0]);
		res = KSI_INVALID_ARGUMENT;
		goto cleanup;
	}

	/* Input file */
	in = fopen(argv[1], "rb");
	if (in == NULL) {
		fprintf(stderr, "Unable to open input file '%s'\n", argv[1]);
		res = KSI_IO_ERROR;
		goto cleanup;
	}

	/* Create new KSI context for this thread. */
	res = KSI_CTX_new(&ksi);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to create context.\n");
		goto cleanup;
	}

	logFile = fopen("ksi_sign_aggr.log", "w");
	if (logFile == NULL) {
		fprintf(stderr, "Unable to open log file.\n");
	}

	KSI_CTX_setLoggerCallback(ksi, KSI_LOG_StreamLogger, logFile);
	KSI_CTX_setLogLevel(ksi, KSI_LOG_DEBUG);

	KSI_LOG_info(ksi, "Using KSI version: '%s'", KSI_getVersion());

	res = KSI_CTX_setAggregator(ksi, argv[3], argv[4], argv[5]);
	if (res != KSI_OK) goto cleanup;

	/* Check publications file url. */
	if (strncmp("-", argv[6], 1)) {
		res = KSI_CTX_setPublicationUrl(ksi, argv[6]);
		if (res != KSI_OK) {
			fprintf(stderr, "Unable to set publications file url.\n");
			goto cleanup;
		}
	}

	/* Create a data hasher using default algorithm. */
	res = KSI_DataHasher_open(ksi, KSI_getHashAlgorithmByName("default"), &hsr);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to create hasher.\n");
		goto cleanup;
	}

	/* Read the input file and calculate the hash of its contents. */
	while (!feof(in)) {
		buf_len = (unsigned)fread(buf, 1, sizeof(buf), in);

		/* Add  next block to the calculation. */
		res = KSI_DataHasher_add(hsr, buf, buf_len);
		if (res != KSI_OK) {
			fprintf(stderr, "Unable to add data to hasher.\n");
			goto cleanup;
		}
	}

	/* Close the data hasher and retreive the data hash. */
	res = KSI_DataHasher_close(hsr, &hsh);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to create hash.\n");
		goto cleanup;
	}

	/* Sign the data hash. */
	res = KSI_Signature_createAggregated(ksi, hsh, 4, &sign);
//	res = KSI_createSignature(ksi, hsh, &sign);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to sign %d.\n", res);
		goto cleanup;
	}

	res = KSI_Signature_verifyAggregated(sign, ksi, 4);
	if (res != KSI_OK) {
		fprintf(stderr, "Failed to verify signature.\n");
		goto cleanup;
	}

	/* Output the signer id */
	res = KSI_Signature_getSignerIdentity(sign, &signerIdentity);
	if (res == KSI_OK) {
		printf("Signer id: %s\n", signerIdentity);
	} else {
		fprintf(stderr, "Unable to extract signer identity.\n");
	}
    
	/* Serialize the signature. */
	res = KSI_Signature_serialize(sign, &raw, &raw_len);
	if (res != KSI_OK) {
		fprintf(stderr, "Unable to serialize signature.");
		goto cleanup;
	}

	/* Output file */
	out = fopen(argv[2], "wb");
	if (out == NULL) {
		fprintf(stderr, "Unable to open input file '%s'\n", argv[2]);
		res = KSI_IO_ERROR;
		goto cleanup;
	}

	/* Write the signature file. */
	if (!fwrite(raw, 1, raw_len, out)) {
		fprintf(stderr, "Unable to write output file.\n");
		res = KSI_IO_ERROR;
		goto cleanup;
	}

	/* Only print message when signature output is not stdout. */
	if (out != NULL) {
		printf("Signature saved.\n");
	}

	res = KSI_OK;

cleanup:

	if (logFile != NULL) fclose(logFile);

	if (res != KSI_OK && ksi != NULL) {
		KSI_ERR_statusDump(ksi, stderr);
	}

	if (in != NULL) fclose(in);
	if (out != NULL) fclose(out);

	KSI_free(signerIdentity);

	KSI_Signature_free(sign);
	KSI_DataHash_free(hsh);
	KSI_DataHasher_free(hsr);

	KSI_free(raw);

	KSI_CTX_free(ksi);

	return res;

}