char* KSI_OctetString_toString(const KSI_OctetString *id, char separator, char *buf, size_t buf_len) { int res = 0; const unsigned char *raw = NULL; size_t raw_len; size_t written = 0; size_t i = 0; if (id == NULL || buf == NULL || buf_len == 0) { return NULL; } res = KSI_OctetString_extract(id, &raw, &raw_len); if(res != KSI_OK || raw == NULL) return NULL; for (i = 0; i < raw_len; i++) { if(buf_len - written <= 0) return NULL; if(separator == '\0' || i == raw_len - 1) written += KSI_snprintf(buf + written, buf_len - written, "%02x", raw[i]); else written += KSI_snprintf(buf + written, buf_len - written, "%02x%c", raw[i], separator); if (written == 0) return NULL; } return buf; }
static void addInput(CuTest *tc, KSI_BlockSigner *bs, int genMeta) { int res = KSI_UNKNOWN_ERROR; size_t i; KSI_DataHash *hsh = NULL; KSI_MetaData *md = NULL; for (i = 0; input_data[i] != NULL; i++) { res = KSI_DataHash_create(ctx, input_data[i], strlen(input_data[i]), KSI_HASHALG_SHA2_256, &hsh); CuAssert(tc, "Unable to create data hash.", res == KSI_OK && hsh != NULL); if (genMeta) { char clientId[100]; KSI_snprintf(clientId, sizeof(clientId), "Client-%d", i); res = createMetaData(clientId, &md); CuAssert(tc, "Unable to create metadata.", res == KSI_OK && md != NULL); res = KSI_BlockSigner_addLeaf(bs, hsh, 0, md, NULL); CuAssert(tc, "Unable to add leaf with meta data.", res == KSI_OK); KSI_MetaData_free(md); md = NULL; } else { res = KSI_BlockSigner_add(bs, hsh); CuAssert(tc, "Unable to add data hash to the block signer.", res == KSI_OK); } KSI_DataHash_free(hsh); hsh = NULL; } }
static void testMaskingInput(CuTest *tc) { static const unsigned char diceRolls[] = {0xd5, 0x58, 0xaf, 0xfa, 0x80, 0x67, 0xf4, 0x2c, 0xd9, 0x48, 0x36, 0x21, 0xd1, 0xab, 0xae, 0x23, 0xed, 0xd6, 0xca, 0x04, 0x72, 0x7e, 0xcf, 0xc7, 0xdb, 0xc7, 0x6b, 0xde, 0x34, 0x77, 0x1e, 0x53}; int res; KSI_BlockSigner *bs = NULL; KSI_OctetString *iv = NULL; KSI_DataHash *zero = NULL; size_t i; struct { KSI_CTX *ctx; KSI_HashAlgorithm algo_id; KSI_DataHash *prevHash; KSI_OctetString *iv; KSI_BlockSigner **bs; int expectedRes; } tests[] = { {NULL, KSI_HASHALG_SHA3_512, NULL, NULL, NULL, KSI_INVALID_ARGUMENT}, {NULL, KSI_HASHALG_SHA3_512, NULL, NULL, &bs, KSI_INVALID_ARGUMENT}, {NULL, KSI_HASHALG_SHA3_512, NULL, iv, &bs, KSI_INVALID_ARGUMENT}, {NULL, KSI_HASHALG_SHA3_512, zero, NULL, &bs, KSI_INVALID_ARGUMENT}, {ctx, KSI_HASHALG_SHA3_512, NULL, NULL, &bs, KSI_UNAVAILABLE_HASH_ALGORITHM}, {NULL, KSI_HASHALG_SHA2_512, NULL, NULL, NULL, KSI_INVALID_ARGUMENT}, {NULL, KSI_HASHALG_SHA2_512, NULL, NULL, &bs, KSI_INVALID_ARGUMENT}, {NULL, KSI_HASHALG_SHA2_512, NULL, iv, &bs, KSI_INVALID_ARGUMENT}, {NULL, KSI_HASHALG_SHA2_512, zero, NULL, &bs, KSI_INVALID_ARGUMENT}, {ctx, KSI_HASHALG_SHA2_512, zero, NULL, &bs, KSI_OK}, {NULL, -1, NULL, NULL, NULL, -1} }; /* Create zero hash. */ res = KSI_DataHash_createZero(ctx, KSI_HASHALG_SHA2_512, &zero); CuAssert(tc, "Unable to create zero hash.", res == KSI_OK && zero != NULL); /* Create random initial vector. */ res = KSI_OctetString_new(ctx, diceRolls, sizeof(diceRolls), &iv); CuAssert(tc, "Unable to create initial vector.", res == KSI_OK && iv != NULL); res = KSI_BlockSigner_new(ctx, KSI_HASHALG_SHA1, zero, iv, &bs); CuAssert(tc, "Unable to create block signer instance with masking.", res == KSI_OK && bs != NULL); for (i = 0; tests[i].expectedRes != -1; i++) { res = KSI_BlockSigner_new(tests[i].ctx, tests[i].algo_id, tests[i].prevHash, tests[i].iv, tests[i].bs); KSI_BlockSigner_free(bs); bs = NULL; if (res != tests[i].expectedRes) { char buf[1000]; KSI_snprintf(buf, sizeof(buf), "Unexpected result @%i (expected = '%s', but was '%s').", i, KSI_getErrorString(tests[i].expectedRes), KSI_getErrorString(res)); CuFail(tc, buf); } } KSI_OctetString_free(iv); KSI_DataHash_free(zero); }
KSI_END_TLV_TEMPLATE static char *track_str(struct tlv_track_s *tr, size_t tr_len, size_t tr_size, char *buf, size_t buf_len) { size_t len = 0; size_t i; /* Make sure, the return value is null-terminated. */ buf[0] = '\0'; /* Generate the printable result string, by separating values with "->" */ for (i = 0; i < tr_len && i < tr_size; i++) { if (i != 0) len += KSI_snprintf(buf + len, buf_len - len, "->"); len += KSI_snprintf(buf + len, buf_len - len, "[0x%02x]%s", tr[i].tag, tr[i].desc != NULL ? tr[i].desc : ""); } /* Just in case the buffer was too short, but in real life, this should not happen with correct KSI objects. */ if (tr_len >= tr_size) { KSI_snprintf(buf + len, buf_len - len, "->..."); } return buf; }
char *KSI_PublicationData_toString(KSI_PublicationData *t, char *buffer, size_t buffer_len) { int res = KSI_UNKNOWN_ERROR; char *ret = NULL; size_t len = 0; char *pubStr = NULL; char tmp[256]; res = KSI_PublicationData_toBase32(t, &pubStr); if (res != KSI_OK) { KSI_LOG_debug(t->ctx, "Unable to convert publication data to base 32: %s (%d)", KSI_getErrorString(res), res); goto cleanup; } len += KSI_snprintf(buffer + len, buffer_len - len, "Publication string: %s\nPublication date: %s", pubStr, KSI_Integer_toDateString(t->time, tmp, sizeof(tmp))); KSI_snprintf(buffer + len, buffer_len - len, "\nPublished hash: %s", KSI_DataHash_toString(t->imprint, tmp, sizeof(tmp))); ret = buffer; cleanup: KSI_free(pubStr); return ret; }
char *KSI_PublicationRecord_toString(KSI_PublicationRecord *t, char *buffer, size_t buffer_len) { int res = KSI_UNKNOWN_ERROR; char *ret = NULL; char tmp[256]; size_t len = 0; size_t i; len += KSI_snprintf(buffer + len, buffer_len - len, "%s", KSI_PublicationData_toString(t->publishedData, tmp, sizeof(tmp))); for (i = 0; i < KSI_Utf8StringList_length(t->publicationRef); i++) { KSI_Utf8String *ref = NULL; res = KSI_Utf8StringList_elementAt(t->publicationRef, i, &ref); if (res != KSI_OK) goto cleanup; len += KSI_snprintf(buffer + len, buffer_len - len, "\nRef: %s", KSI_Utf8String_cstr(ref)); } ret = buffer; cleanup: return ret; }
char* KSI_PKICertificate_toString(KSI_PKICertificate *cert, char *buf, unsigned buf_len){ char *ret = NULL; char strSubjectname[256]; char strIssuerName[256]; if (cert == NULL || buf == NULL) goto cleanup; CertGetNameString(cert->x509, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, strSubjectname, sizeof(strSubjectname)); CertGetNameString(cert->x509, CERT_NAME_SIMPLE_DISPLAY_TYPE , CERT_NAME_ISSUER_FLAG, 0,strIssuerName, sizeof(strIssuerName)); KSI_snprintf(buf, buf_len, "Subject: '%s', Issuer '%s'.", strSubjectname, strIssuerName); ret = buf; cleanup: return ret; }
char *KSI_DataHash_toString(const KSI_DataHash *hsh, char *buf, size_t buf_len) { char *ret = NULL; size_t i; size_t len = 0; if (hsh == NULL || buf == NULL) goto cleanup; for (i = 0; i < hsh->imprint_length && len < buf_len; i++) { len += KSI_snprintf(buf + len, buf_len - len, "%02x", hsh->imprint[i]); } ret = buf; cleanup: return ret; }
static void testObjectSerialization(CuTest *tc, const char *sample, int (*parse)(KSI_CTX *, unsigned char *, size_t, void **), int (*serialize)(void *, unsigned char **, size_t *), void (*objFree)(void *)) { int res; void *pdu = NULL; unsigned char in[0xffff + 4]; size_t in_len; unsigned char *out = NULL; size_t out_len; FILE *f = NULL; char errm[1024]; f = fopen(sample, "rb"); KSI_snprintf(errm, sizeof(errm), "Unable to open pdu file: '%s'.", sample); CuAssert(tc, errm, f != NULL); in_len = (unsigned)fread(in, 1, sizeof(in), f); fclose(f); KSI_snprintf(errm, sizeof(errm), "Unable to read pdu file: '%s'.", sample); CuAssert(tc, errm, in_len > 0); res = parse(ctx, in, in_len, &pdu); KSI_snprintf(errm, sizeof(errm), "Unable to parse pdu: '%s'.", sample); CuAssert(tc, errm, res == KSI_OK && pdu != NULL); res = serialize(pdu, &out, &out_len); KSI_snprintf(errm, sizeof(errm), "Unable to serialize pdu: '%s'.", sample); CuAssert(tc, errm, res == KSI_OK && out != NULL && out_len > 0); KSI_snprintf(errm, sizeof(errm), "Serialized pdu length mismatch: '%s'.", sample); CuAssert(tc, errm, res == KSI_OK && out_len == in_len); KSI_snprintf(errm, sizeof(errm), "Serialised pdu content mismatch: '%s'.", sample); CuAssert(tc, errm, !KSITest_memcmp(in, out, in_len)); KSI_free(out); objFree(pdu); }
int KSI_DataHasher_open(KSI_CTX *ctx, KSI_HashAlgorithm algo_id, KSI_DataHasher **hasher) { int res = KSI_UNKNOWN_ERROR; KSI_DataHasher *tmp_hasher = NULL; CRYPTO_HASH_CTX *tmp_cryptoCTX = NULL; HCRYPTPROV tmp_CSP = 0; KSI_ERR_clearErrors(ctx); if (ctx == NULL || hasher == NULL){ res = KSI_INVALID_ARGUMENT; goto cleanup; } /*Test if hash algorithm is valid*/ if (!KSI_isHashAlgorithmSupported(algo_id)) { KSI_pushError(ctx, res = KSI_UNAVAILABLE_HASH_ALGORITHM, NULL); goto cleanup; } /*Create new abstract data hasher object*/ tmp_hasher = KSI_new(KSI_DataHasher); if (tmp_hasher == NULL) { KSI_pushError(ctx, res = KSI_OUT_OF_MEMORY, NULL); goto cleanup; } tmp_hasher->hashContext = NULL; tmp_hasher->ctx = ctx; tmp_hasher->algorithm = algo_id; tmp_hasher->closeExisting = closeExisting; /*Create new helper context for crypto api*/ res = CRYPTO_HASH_CTX_new(&tmp_cryptoCTX); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } /*Create new crypto service provider (CSP)*/ if (!CryptAcquireContext(&tmp_CSP, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)){ char errm[1024]; KSI_snprintf(errm, sizeof(errm), "Wincrypt Error (%d)", GetLastError()); KSI_pushError(ctx, res = KSI_CRYPTO_FAILURE, errm); goto cleanup; } /*Set CSP in helper struct*/ tmp_cryptoCTX->pt_CSP = tmp_CSP; /*Set helper struct in abstract struct*/ tmp_hasher->hashContext = tmp_cryptoCTX; res = KSI_DataHasher_reset(tmp_hasher); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } *hasher = tmp_hasher; tmp_hasher = NULL; tmp_cryptoCTX = NULL; tmp_CSP = 0; res = KSI_OK; cleanup: KSI_DataHasher_free(tmp_hasher); if (tmp_CSP) CryptReleaseContext(tmp_CSP, 0); CRYPTO_HASH_CTX_free(tmp_cryptoCTX); return res; }
static void testMedaData(CuTest *tc) { #define TEST_AGGR_RESPONSE_FILE "resource/tlv/test_meta_data_response.tlv" int res = KSI_UNKNOWN_ERROR; KSI_BlockSigner *bs = NULL; KSI_MetaData *md = NULL; char data[] = "LAPTOP"; char *clientId[] = { "Alice", "Bob", "Claire", NULL }; size_t i; KSI_DataHash *hsh = NULL; KSI_BlockSignerHandle *hndl[] = {NULL, NULL, NULL}; KSI_Signature *sig = NULL; char *id = NULL; res = KSI_DataHash_create(ctx, data, strlen(data), KSI_HASHALG_SHA2_256, &hsh); CuAssert(tc, "Unable to create data hash.", res == KSI_OK && hsh != NULL); res = KSI_BlockSigner_new(ctx, KSI_HASHALG_SHA2_256, NULL, NULL, &bs); CuAssert(tc, "Unable to create block signer instance.", res == KSI_OK && bs != NULL); for (i = 0; clientId[i] != NULL; i++) { res = createMetaData(clientId[i], &md); CuAssert(tc, "Unable to create meta-data.", res == KSI_OK && md != NULL); res = KSI_BlockSigner_addLeaf(bs, hsh, 0, md, &hndl[i]); CuAssert(tc, "Unable to add leaf to the block signer.", res == KSI_OK && hndl[i] != NULL); KSI_MetaData_free(md); md = NULL; } res = KSI_CTX_setAggregator(ctx, getFullResourcePathUri(TEST_AGGR_RESPONSE_FILE), TEST_USER, TEST_PASS); CuAssert(tc, "Unable to set aggregator file URI.", res == KSI_OK); res = KSI_BlockSigner_close(bs, NULL); CuAssert(tc, "Unable to close the blocksigner.", res == KSI_OK); /* Loop over all the handles, and extract the signature. */ for (i = 0; clientId[i] != NULL; i++) { char expId[0xff]; /* Extract the signature. */ res = KSI_BlockSignerHandle_getSignature(hndl[i], &sig); CuAssert(tc, "Unable to extract signature.", res == KSI_OK && sig != NULL); /* Verify the signature. */ res = KSI_verifySignature(ctx, sig); CuAssert(tc, "Unable to verify the extracted signature.", res == KSI_OK); /* Extract the id attribution. */ res = KSI_Signature_getSignerIdentity(sig, &id); CuAssert(tc, "Unable to extract the signer identity.", res == KSI_OK && id != NULL); /* Create the expected id value. */ KSI_snprintf(expId, sizeof(expId), "%s :: %s", "GT :: GT :: release test :: anon http", clientId[i]); CuAssert(tc, "Client id not what expected.", !strcmp(id, expId)); /* Cleanup. */ KSI_Signature_free(sig); sig = NULL; KSI_free(id); id = NULL; KSI_BlockSignerHandle_free(hndl[i]); } KSI_DataHash_free(hsh); KSI_MetaData_free(md); KSI_BlockSigner_free(bs); #undef TEST_AGGR_RESPONSE_FILE }
static int performN(KSI_NetworkClient *client, KSI_RequestHandle **arr, size_t arr_len) { int res = KSI_UNKNOWN_ERROR; CURLM *cm = NULL; CURLMcode cres; size_t i; int count; char buf[1024]; fd_set fdread; fd_set fdwrite; fd_set fdexcep; int maxfd = -1; struct timeval timeout; FD_ZERO(&fdread); FD_ZERO(&fdwrite); FD_ZERO(&fdexcep); if (client == NULL || (arr == NULL && arr_len != 0)) { res = KSI_INVALID_ARGUMENT; goto cleanup; } KSI_ERR_clearErrors(client->ctx); KSI_LOG_debug(client->ctx, "Starting cURL multi perform."); timeout.tv_sec = 0; timeout.tv_usec = 100; cm = curl_multi_init(); if (cm == NULL) { KSI_pushError(client->ctx, res = KSI_OUT_OF_MEMORY, NULL); goto cleanup; } for (i = 0; i < arr_len; i++) { CurlNetHandleCtx *pctx = arr[i]->implCtx; cres = curl_multi_add_handle(cm, pctx->curl); if (cres != CURLM_OK) { KSI_snprintf(buf, sizeof(buf), "Curl error occurred: %s", curl_multi_strerror(cres)); KSI_pushError(client->ctx, res = KSI_UNKNOWN_ERROR, buf); goto cleanup; } } curl_multi_setopt(cm, CURLMOPT_PIPELINING, 1); do { cres = curl_multi_fdset(cm, &fdread, &fdwrite, &fdexcep, &maxfd); if (cres != CURLM_OK) { KSI_snprintf(buf, sizeof(buf), "Curl error occurred: %s", curl_multi_strerror(cres)); KSI_pushError(client->ctx, res = KSI_UNKNOWN_ERROR, buf); goto cleanup; } select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); cres = curl_multi_perform(cm, &count); if (cres != CURLM_OK && cres != CURLM_CALL_MULTI_PERFORM) { KSI_snprintf(buf, sizeof(buf), "Curl error occurred: %s", curl_multi_strerror(cres)); KSI_pushError(client->ctx, res = KSI_UNKNOWN_ERROR, buf); goto cleanup; } } while (count > 0 || cres == CURLM_CALL_MULTI_PERFORM); /* Remove the handles from the multi container. */ for (i = 0; i < arr_len; i++) { CurlNetHandleCtx *pctx = arr[i]->implCtx; cres = curl_multi_remove_handle(cm, pctx->curl); if (cres != CURLM_OK) { KSI_snprintf(buf, sizeof(buf), "Curl error occurred: %s", curl_multi_strerror(cres)); KSI_pushError(client->ctx, res = KSI_UNKNOWN_ERROR, buf); goto cleanup; } arr[i]->response = pctx->raw; pctx->raw = NULL; arr[i]->response_length = pctx->len; arr[i]->completed = true; res = updateStatus(arr[i]); if (res != KSI_OK) goto cleanup; } KSI_LOG_debug(client->ctx, "Finished cURL multi perform."); res = KSI_OK; cleanup: if (cm != NULL) curl_multi_cleanup(cm); return res; }
static int sendRequest(KSI_NetworkClient *client, KSI_RequestHandle *handle, char *url) { int res = KSI_UNKNOWN_ERROR; CurlNetHandleCtx *implCtx = NULL; KSI_HttpClient *http = client->impl; char mimeTypeHeader[1024]; if (client == NULL || client->ctx == NULL || handle == NULL || url == NULL) { res = KSI_INVALID_ARGUMENT; goto cleanup; } KSI_ERR_clearErrors(client->ctx); res = CurlNetHandleCtx_new(client->ctx, &implCtx); if (res != KSI_OK) { KSI_pushError(client->ctx, res, NULL); goto cleanup; } KSI_LOG_debug(handle->ctx, "Curl: Preparing request to: %s", url); implCtx->curl = curl_easy_init(); if (implCtx->curl == NULL) { KSI_pushError(client->ctx, res = KSI_OUT_OF_MEMORY, "Unable to init CURL."); goto cleanup; } curl_easy_setopt(implCtx->curl, CURLOPT_VERBOSE, 0); curl_easy_setopt(implCtx->curl, CURLOPT_WRITEFUNCTION, receiveDataFromLibCurl); curl_easy_setopt(implCtx->curl, CURLOPT_NOPROGRESS, 1); /* Make sure cURL won't use signals. */ curl_easy_setopt(implCtx->curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(implCtx->curl, CURLOPT_ERRORBUFFER, implCtx->curlErr); if (http->agentName != NULL) { curl_easy_setopt(implCtx->curl, CURLOPT_USERAGENT, http->agentName); } if (http->mimeType != NULL) { KSI_snprintf(mimeTypeHeader, sizeof(mimeTypeHeader) ,"Content-Type: %s", http->mimeType); implCtx->httpHeaders = curl_slist_append(implCtx->httpHeaders, mimeTypeHeader); curl_easy_setopt(implCtx->curl, CURLOPT_HTTPHEADER, implCtx->httpHeaders); } if (handle->request != NULL) { curl_easy_setopt(implCtx->curl, CURLOPT_POST, 1); curl_easy_setopt(implCtx->curl, CURLOPT_POSTFIELDS, (char *)handle->request); curl_easy_setopt(implCtx->curl, CURLOPT_POSTFIELDSIZE, (long)handle->request_length); } else { curl_easy_setopt(implCtx->curl, CURLOPT_POST, 0); } curl_easy_setopt(implCtx->curl, CURLOPT_WRITEDATA, implCtx); curl_easy_setopt(implCtx->curl, CURLOPT_CONNECTTIMEOUT, http->connectionTimeoutSeconds); curl_easy_setopt(implCtx->curl, CURLOPT_TIMEOUT, http->readTimeoutSeconds); curl_easy_setopt(implCtx->curl, CURLOPT_URL, url); handle->readResponse = curlReceive; handle->client = client; res = KSI_RequestHandle_setImplContext(handle, implCtx, (void (*)(void *))CurlNetHandleCtx_free); if (res != KSI_OK) { KSI_pushError(handle->ctx, res, NULL); goto cleanup; } implCtx = NULL; res = KSI_OK; cleanup: CurlNetHandleCtx_free(implCtx); return res; }
/** * Prepares request and opens a session handle. */ static int wininetSendRequest(KSI_NetworkClient *client, KSI_RequestHandle *handle, char *url) { int res; KSI_CTX *ctx = NULL; wininetNetHandleCtx *wininetHandle = NULL; KSI_HttpClient *http = (KSI_HttpClient *)client; HINTERNET internetHandle; char msg[1024]; char *scheme = NULL; char *hostName = NULL; char *query = NULL; int port = 0; if (client == NULL || handle == NULL || url == NULL) { res = KSI_INVALID_ARGUMENT; goto cleanup; } ctx = handle->ctx; KSI_ERR_clearErrors(ctx); if (http->implCtx == NULL) { res = KSI_INVALID_ARGUMENT; KSI_pushError(ctx, res, "Network client http implementation context not set."); goto cleanup; } /*Initializing of wininet helper struct*/ res = wininetNetHandleCtx_new(&wininetHandle); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } wininetHandle->ctx = ctx; internetHandle = http->implCtx; res = KSI_UriSplitBasic(url, &scheme, &hostName, &port, &query); if(res != KSI_OK){ KSI_snprintf(msg, sizeof(msg), "WinINet: Unable to crack url '%s'.", url); KSI_pushError(ctx, res, msg); goto cleanup; } if(scheme == NULL || strcmp("http", scheme) != 0 && strcmp("https", scheme) != 0){ KSI_snprintf(msg, sizeof(msg), "WinINet: unknown Internet scheme '%s'.", scheme); KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, msg); goto cleanup; } if(hostName == NULL || query == NULL){ KSI_snprintf(msg, sizeof(msg), "WinINet: Invalid url '%s'.", url); KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, msg); goto cleanup; } if (handle->request_length > LONG_MAX) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, "WinINet: Request too long."); goto cleanup; } KSI_LOG_debug(ctx, "WinINet: Sending request to: %s.", url); /*Preparing session handle*/ //Opens an HTTP session for a given site wininetHandle->session_handle = InternetConnectA(internetHandle, hostName, port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); if (wininetHandle->session_handle == NULL) { WININET_ERROR(ctx, GetLastError(), KSI_NETWORK_ERROR, "WinINet: Unable to initialize connection handle."); } wininetHandle->request_handle = HttpOpenRequestA(wininetHandle->session_handle, (handle->request == NULL ? "GET" : "POST"), query, NULL, NULL, NULL, (strcmp("https", scheme) == 0 ? INTERNET_FLAG_SECURE : 0), 0); if (wininetHandle->request_handle == NULL){ WININET_ERROR(ctx, GetLastError(), KSI_NETWORK_ERROR, "WinINet: Unable to initialize request handle."); } /*TODO Timeout is set, but seems to have no effect*/ if (http->connectionTimeoutSeconds >= 0) { DWORD dw = (http->connectionTimeoutSeconds == 0 ? 0xFFFFFFFF : http->connectionTimeoutSeconds * 1000); if (!InternetSetOption(wininetHandle->request_handle, INTERNET_OPTION_CONNECT_TIMEOUT, &dw, sizeof(dw))){ WININET_ERROR(ctx, GetLastError(), KSI_NETWORK_ERROR, "WinINet: Unable to set connection timeout."); } } if (http->readTimeoutSeconds >= 0) { DWORD dw = (http->readTimeoutSeconds == 0 ? 0xFFFFFFFF : http->readTimeoutSeconds * 1000); if (!InternetSetOption(wininetHandle->request_handle, INTERNET_OPTION_SEND_TIMEOUT, &dw, sizeof(dw))){ WININET_ERROR(ctx, GetLastError(), KSI_NETWORK_ERROR, "WinINet: Unable to set send timeout."); } if (!InternetSetOption(wininetHandle->request_handle, INTERNET_OPTION_RECEIVE_TIMEOUT, &dw, sizeof(dw))){ WININET_ERROR(ctx, GetLastError(), KSI_NETWORK_ERROR, "WinINet: Unable to set receive timeout."); } } handle->readResponse = wininetReceive; handle->client = client; res = KSI_RequestHandle_setImplContext(handle, wininetHandle, (void (*)(void *))wininetNetHandleCtx_free); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } wininetHandle = NULL; res = KSI_OK; cleanup: wininetNetHandleCtx_free(wininetHandle); KSI_free(query); KSI_free(hostName); KSI_free(scheme); return res; }
static int construct(KSI_CTX *ctx, KSI_TLV *tlv, const void *payload, const KSI_TlvTemplate *tmpl, struct tlv_track_s *tr, size_t tr_len, const size_t tr_size) { int res = KSI_UNKNOWN_ERROR; KSI_TLV *tmp = NULL; void *payloadp = NULL; int isNonCritical = 0; int isForward = 0; size_t template_len = 0; bool templateHit[MAX_TEMPLATE_SIZE]; bool groupHit[2] = {false, false}; bool oneOf[2] = {false, false}; size_t i; char buf[1000]; KSI_ERR_clearErrors(ctx); if (ctx == NULL || tlv == NULL || payload == NULL || tmpl == NULL || tr == NULL) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, NULL); goto cleanup; } /* Calculate the template length. */ template_len = getTemplateLength(tmpl); if (template_len == 0) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, "A template may not be empty."); goto cleanup; } if (template_len > MAX_TEMPLATE_SIZE) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, "Template too big."); goto cleanup; } memset(templateHit, 0, sizeof(templateHit)); for (i = 0; i < template_len; i++) { if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_NO_SERIALIZE) != 0) continue; payloadp = NULL; res = tmpl[i].getValue(payload, &payloadp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } if (payloadp != NULL) { /* Register for tracking. */ if (tr_len < tr_size) { tr[tr_len].tag = tmpl[i].tag; tr[tr_len].desc = tmpl[i].descr; } templateHit[i] = true; if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G0) != 0) groupHit[0] = true; if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G1) != 0) groupHit[1] = true; if (FLAGSET(tmpl[i], KSI_TLV_TMPL_FLG_MOST_ONE_G0)) { if (oneOf[0]) { char errm[1000]; KSI_snprintf(errm, sizeof(errm), "Mutually exclusive elements present within group 0 (%s).", track_str(tr, tr_len, tr_size, buf, sizeof(buf))); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); } oneOf[0] = true; } if (FLAGSET(tmpl[i], KSI_TLV_TMPL_FLG_MOST_ONE_G1)) { if (oneOf[1]) { char errm[1000]; KSI_snprintf(errm, sizeof(errm), "Mutually exclusive elements present within group 1 (%s).", track_str(tr, tr_len, tr_size, buf, sizeof(buf))); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); } oneOf[1] = true; } isNonCritical = (tmpl[i].flags & KSI_TLV_TMPL_FLG_NONCRITICAL) != 0; isForward = (tmpl[i].flags & KSI_TLV_TMPL_FLG_FORWARD) != 0; switch (tmpl[i].type) { case KSI_TLV_TEMPLATE_OBJECT: if (tmpl[i].toTlv == NULL) { KSI_pushError(ctx, res = KSI_UNKNOWN_ERROR, "Invalid template: toTlv not set."); goto cleanup; } if (tmpl[i].listLength != NULL) { int j; for (j = 0; j < tmpl[i].listLength(payloadp); j++) { void *listElement = NULL; res = tmpl[i].listElementAt(payloadp, j, &listElement); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = tmpl[i].toTlv(ctx, listElement, tmpl[i].tag, isNonCritical, isForward != 0, &tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = KSI_TLV_appendNestedTlv(tlv, tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } tmp = NULL; } } else { res = tmpl[i].toTlv(ctx, payloadp, tmpl[i].tag, isNonCritical, isForward, &tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = KSI_TLV_appendNestedTlv(tlv, tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } tmp = NULL; } break; case KSI_TLV_TEMPLATE_COMPOSITE: if (tmpl[i].listLength != NULL) { int j; for (j = 0; j < tmpl[i].listLength(payloadp); j++) { void *listElement = NULL; res = KSI_TLV_new(ctx, KSI_TLV_PAYLOAD_TLV, tmpl[i].tag, isNonCritical, isForward, &tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = tmpl[i].listElementAt(payloadp, j, &listElement); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = construct(ctx, tmp, listElement, tmpl[i].subTemplate, tr, tr_len + 1, tr_size); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = KSI_TLV_appendNestedTlv(tlv, tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } tmp = NULL; } } else { res = KSI_TLV_new(ctx, KSI_TLV_PAYLOAD_TLV, tmpl[i].tag, isNonCritical, isForward, &tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = construct(ctx, tmp, payloadp, tmpl[i].subTemplate, tr, tr_len + 1, tr_size); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = KSI_TLV_appendNestedTlv(tlv, tmp); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } tmp = NULL; } break; default: KSI_LOG_error(ctx, "Unimplemented template type: %d", tmpl[i].type); KSI_pushError(ctx, res = KSI_UNKNOWN_ERROR, "Unimplemented template type."); goto cleanup; } } } /* Check that every mandatory component was present. */ for (i = 0; i < template_len; i++) { char errm[1000]; if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_MANDATORY) != 0 && !templateHit[i]) { KSI_snprintf(errm, sizeof(errm), "Mandatory element missing: %s->[0x%02x]%s", track_str(tr, tr_len, tr_size, buf, sizeof(buf)), tmpl[i].tag, tmpl[i].descr == NULL ? "" : tmpl[i].descr); KSI_LOG_debug(ctx, "%s", errm); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); goto cleanup; } if (((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G0) != 0 && !groupHit[0]) || ((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G1) != 0 && !groupHit[1])) { KSI_snprintf(errm, sizeof(errm), "Mandatory group missing: %s->[0x%02x]%s", track_str(tr, tr_len, tr_size, buf, sizeof(buf)), tmpl[i].tag, tmpl[i].descr == NULL ? "" : tmpl[i].descr); KSI_LOG_debug(ctx, "%s", errm); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); goto cleanup; } } res = KSI_OK; cleanup: KSI_nofree(payloadp); KSI_TLV_free(tmp); return res; }
static int extractGenerator(KSI_CTX *ctx, void *payload, void *generatorCtx, const KSI_TlvTemplate *tmpl, int (*generator)(void *, KSI_TLV **), struct tlv_track_s *tr, size_t tr_len, size_t tr_size) { int res = KSI_UNKNOWN_ERROR; KSI_TLV *tlv = NULL; char buf[1024]; void *voidVal = NULL; void *compositeVal = NULL; void *valuep = NULL; KSI_TLV *tlvVal = NULL; size_t template_len = 0; bool templateHit[MAX_TEMPLATE_SIZE]; bool groupHit[2] = {false, false}; bool oneOf[2] = {false, false}; size_t i; size_t tmplStart = 0; KSI_ERR_clearErrors(ctx); if (ctx == NULL || payload == NULL || generatorCtx == NULL || tmpl == NULL || generator == NULL || tr == NULL) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, NULL); goto cleanup; } /* Analyze the template. */ template_len = getTemplateLength(tmpl); if (template_len == 0) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, "Empty template suggests invalid state."); goto cleanup; } /* Make sure there will be no buffer overflow. */ if (template_len > MAX_TEMPLATE_SIZE) { KSI_pushError(ctx, res = KSI_INVALID_ARGUMENT, "Template too big"); goto cleanup; } memset(templateHit, 0, sizeof(templateHit)); while (1) { int matchCount = 0; res = generator(generatorCtx, &tlv); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } if (tlv == NULL) break; KSI_LOG_trace(ctx, "Starting to parse TLV[0x%02x]", KSI_TLV_getTag(tlv)); if (tr_len < tr_size) { tr[tr_len].tag = KSI_TLV_getTag(tlv); tr[tr_len].desc = NULL; } for (i = tmplStart; i < template_len; i++) { if (tmpl[i].tag != KSI_TLV_getTag(tlv)) continue; if (i == tmplStart && !tmpl[i].multiple) tmplStart++; tr[tr_len].desc = tmpl[i].descr; matchCount++; templateHit[i] = true; if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G0) != 0) groupHit[0] = true; if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G1) != 0) groupHit[1] = true; if (FLAGSET(tmpl[i], KSI_TLV_TMPL_FLG_MOST_ONE_G0)) { if (oneOf[0]) { KSI_pushError(ctx, res = KSI_INVALID_FORMAT, "Mutually exclusive elements present within group 0."); goto cleanup; } oneOf[0] = true; } if (FLAGSET(tmpl[i], KSI_TLV_TMPL_FLG_MOST_ONE_G1)) { if (oneOf[1]) { KSI_pushError(ctx, res = KSI_INVALID_FORMAT, "Mutually exclusive elements present within group 0."); goto cleanup; } oneOf[1] = true; } valuep = NULL; if (tmpl[i].getValue != NULL) { /* Validate the value has not been set */ res = tmpl[i].getValue(payload, (void **)&valuep); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } } if (valuep != NULL && !tmpl[i].multiple) { compositeVal = NULL; KSI_LOG_error(ctx, "Multiple occurrences of a unique tag 0x%02x", tmpl[i].tag); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, "To avoid memory leaks, a value may not be set more than once while parsing."); goto cleanup; } /* Parse the current TLV */ switch (tmpl[i].type) { case KSI_TLV_TEMPLATE_OBJECT: KSI_LOG_trace(ctx, "Detected object template for TLV value extraction."); if (tmpl[i].fromTlv == NULL) { KSI_pushError(ctx, res = KSI_UNKNOWN_ERROR, "Invalid template: fromTlv not set."); goto cleanup; } res = tmpl[i].fromTlv(tlv, &voidVal); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = storeObjectValue(ctx, &tmpl[i], payload, voidVal); if (res != KSI_OK) { tmpl[i].destruct(voidVal); // FIXME: Make sure, it is a valid pointer. goto cleanup; } break; case KSI_TLV_TEMPLATE_COMPOSITE: { KSI_LOG_trace(ctx, "Detected composite template for TLV value extraction."); res = tmpl[i].construct(ctx, &compositeVal); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } res = extract(ctx, compositeVal, tlv, tmpl[i].subTemplate, tr, tr_len + 1, tr_size); if (res != KSI_OK) { KSI_LOG_error(ctx, "Unable to parse composite TLV: %s", track_str(tr, tr_len, tr_size, buf, sizeof(buf))); tmpl[i].destruct(compositeVal); // FIXME: Make sure is is a valid pointer. goto cleanup; } res = storeObjectValue(ctx, &tmpl[i], payload, (void *)compositeVal); if (res != KSI_OK) { KSI_pushError(ctx, res, NULL); goto cleanup; } /* Reset the buffer. */ break; } default: KSI_LOG_error(ctx, "No template found."); /* Should not happen, but just in case. */ KSI_pushError(ctx, res = KSI_UNKNOWN_ERROR, "Undefined template type"); goto cleanup; } if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_MORE_DEFS) == 0) break; } /* Check if a match was found, an raise an error if the TLV is marked as critical. */ if (matchCount == 0 && !KSI_TLV_isNonCritical(tlv)) { char errm[1024]; KSI_snprintf(errm, sizeof(errm), "Unknown critical tag: %s", track_str(tr, tr_len + 1, tr_size, buf, sizeof(buf))); KSI_LOG_error(ctx, errm); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); goto cleanup; } } /* Check that every mandatory component was present. */ for (i = 0; i < template_len; i++) { char errm[100]; if ((tmpl[i].flags & KSI_TLV_TMPL_FLG_MANDATORY) != 0 && !templateHit[i]) { KSI_snprintf(errm, sizeof(errm), "Mandatory element missing: %s->[0x%x]%s", track_str(tr, tr_len, tr_size, buf, sizeof(buf)), tmpl[i].tag, tmpl[i].descr != NULL ? tmpl[i].descr : ""); KSI_LOG_debug(ctx, "%s", errm); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); goto cleanup; } if (((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G0) != 0 && !groupHit[0]) || ((tmpl[i].flags & KSI_TLV_TMPL_FLG_LEAST_ONE_G1) != 0 && !groupHit[1])) { KSI_snprintf(errm, sizeof(errm), "Mandatory group missing: %s->[0x%x]%s", track_str(tr, tr_len, tr_size, buf, sizeof(buf)), tmpl[i].tag, tmpl[i].descr != NULL ? tmpl[i].descr : ""); KSI_LOG_debug(ctx, "%s", errm); KSI_pushError(ctx, res = KSI_INVALID_FORMAT, errm); goto cleanup; } } res = KSI_OK; cleanup: KSI_TLV_free(tlvVal); return res; }
int main(int argc, char **argv) { KSI_CTX *ksi = NULL; int res = KSI_UNKNOWN_ERROR; KSI_DataHash *hsh = NULL; KSI_RequestHandle *handle[REQUESTS]; KSI_NetworkClient *http = NULL; FILE *logFile = NULL; size_t i; KSI_DataHasher *hsr = NULL; const KSI_CertConstraint pubFileCertConstr[] = { { KSI_CERT_EMAIL, "*****@*****.**"}, { NULL, NULL } }; struct { size_t ok; size_t nok; } stat; stat.ok = 0; stat.nok = 0; /* 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; } 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. */ res = OpenLogging(ksi, "multi_curl.log", &logFile); if (res != KSI_OK) goto cleanup; KSI_LOG_info(ksi, "Using KSI version: '%s'", KSI_getVersion()); /* Check publications file url. */ res = KSI_CTX_setPublicationUrl(ksi, "http://verify.guardtime.com/ksi-publications.bin"); if (res != KSI_OK) { fprintf(stderr, "Unable to set publications file url.\n"); goto cleanup; } res = KSI_HttpClient_new(ksi, &http); if (res != KSI_OK) { fprintf(stderr, "Unable to create http client.\n"); goto cleanup; } res = KSI_HttpClient_setAggregator(http, "http://ksigw.test.guardtime.com:3332", "anon", "anon"); if (res != KSI_OK) { fprintf(stderr, "Unable to set aggregator url.\n"); goto cleanup; } KSI_HttpClient_setReadTimeoutSeconds(http, 10); KSI_HttpClient_setConnectTimeoutSeconds(http, 10); for (i = 0; i < REQUESTS; i++) { char buf[100]; size_t len; KSI_AggregationReq *req = NULL; len = KSI_snprintf(buf, sizeof(buf), "Hello %d", i); res = KSI_DataHash_create(ksi, buf, len, KSI_getHashAlgorithmByName("default"), &hsh); if (res != KSI_OK) { fprintf(stderr, "Unable to create hash."); goto cleanup; } res = KSI_AggregationReq_new(ksi, &req); if (res != KSI_OK) { fprintf(stderr, "Unable to create request."); goto cleanup; } res = KSI_AggregationReq_setRequestHash(req, hsh); if (res != KSI_OK) { fprintf(stderr, "Unable to set request hash."); goto cleanup; } res = KSI_NetworkClient_sendSignRequest(http, req, &handle[i]); if (res != KSI_OK) { fprintf(stderr, "Unable to send aggregation request."); goto cleanup; } KSI_AggregationReq_free(req); } res = KSI_NetworkClient_performAll(http, handle, REQUESTS); if (res != KSI_OK) { fprintf(stderr, "Unable to perform requests."); goto cleanup; } for (i = 0; i < REQUESTS; i++) { KSI_AggregationResp *resp = NULL; res = KSI_RequestHandle_getAggregationResponse(handle[i], &resp); if (res != KSI_OK) { const KSI_RequestHandleStatus *st = NULL; res = KSI_RequestHandle_getResponseStatus(handle[i], &st); if (res == KSI_OK) { printf("Status code = %ld: %s\n", st->code, st->errm); } KSI_ERR_statusDump(ksi, stdout); stat.nok++; } else { stat.ok ++; } KSI_AggregationResp_free(resp); } printf("Requests:\n" " Successful: %llu\n" " Failed: %llu\n" " TOTAL: %llu\n", (unsigned long long)stat.ok, (unsigned long long)stat.nok, (unsigned long long)(stat.ok + stat.nok)); res = KSI_OK; cleanup: if (logFile != NULL) fclose(logFile); if (res != KSI_OK && ksi != NULL) { KSI_ERR_statusDump(ksi, stderr); } KSI_DataHash_free(hsh); KSI_DataHasher_free(hsr); KSI_CTX_free(ksi); return res; }
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; }