int GTHTTP_verifyTimestampHash(const GTTimestamp *ts,
                               const GTDataHash *hash,
                               const char *ext_url, GTTimestamp **ext_ts,
                               const GTPublicationsFile *pub, const char *pub_url,
                               int parse, GTVerificationInfo **ver)
{
    int res = GT_UNKNOWN_ERROR;
    GTPublicationsFile *pub_tmp = NULL;
    GTPubFileVerificationInfo *pub_ver = NULL;
    GTVerificationInfo *ver_tmp = NULL;
    GTTimestamp *ext = NULL;
    int is_ext = 0, is_new = 0;

    if (ts == NULL || hash == NULL || ver == NULL) {
        res = GT_INVALID_ARGUMENT;
        goto cleanup;
    }
    if ((pub != NULL) + (pub_url != NULL) != 1) {
        res = GT_INVALID_ARGUMENT;
        goto cleanup;
    }

    /* Ensure we have a valid publications file. */
    if (pub == NULL) {
        res = GTHTTP_getPublicationsFile(pub_url, &pub_tmp);
        if (res != GT_OK) {
            goto cleanup;
        }
        pub = pub_tmp;
    }
    res = GTPublicationsFile_verify(pub, &pub_ver);
    if (res != GT_OK) {
        goto cleanup;
    }

    /* Check internal consistency of the timestamp. */
    res = GTTimestamp_verify(ts, parse, &ver_tmp);
    if (res != GT_OK) {
        goto cleanup;
    }
    if (ver_tmp == NULL || ver_tmp->implicit_data == NULL) {
        res = GT_UNKNOWN_ERROR;
        goto cleanup;
    }
    if (ver_tmp->verification_errors != GT_NO_FAILURES) {
        goto cleanup;
    }

    /* Check document hash.
     * GT_WRONG_DOCUMENT means the hash did not match.
     * Everything else is some sort of system error. */
    res = GTTimestamp_checkDocumentHash(ts, hash);
    if (res == GT_OK) {
        ver_tmp->verification_status |= GT_DOCUMENT_HASH_CHECKED;
    } else if (res == GT_WRONG_DOCUMENT) {
        ver_tmp->verification_status |= GT_DOCUMENT_HASH_CHECKED;
        ver_tmp->verification_errors |= GT_WRONG_DOCUMENT_FAILURE;
        res = GT_OK;
        goto cleanup;
    } else {
        goto cleanup;
    }

    /* Whether the timestamp is extended. */
    is_ext = ((ver_tmp->verification_status & GT_PUBLIC_KEY_SIGNATURE_PRESENT) == 0);
    /* Whether it is too new to be extended. */
    is_new = (ver_tmp->implicit_data->registered_time > pub_ver->last_publication_time);

    /* If the timestamp is already extended, "promote" it.
     * If it is not extended, but is old enough, attempt to extend it. */
    if (is_ext) {
        ext = (GTTimestamp *) ts;
    } else if (!is_new && ext_url != NULL) {
        res = GTHTTP_extendTimestamp(ts, ext_url, &ext);
        /* If extending fails because of infrastructure failure, fall
         * back to signing key check. Else report errors. */
        if (res == GT_NONSTD_EXTEND_LATER || res == GT_NONSTD_EXTENSION_OVERDUE ||
                (res >= GTHTTP_IMPL_BASE && res <= GTHTTP_HIGHEST)) {
            res = GT_OK;
        }
        if (res != GT_OK) {
            goto cleanup;
        }
    }

    /* If we now have a new timestamp, check internal consistency and document hash. */
    if (ext != NULL && ext != ts) {
        /* Release the old verification info. */
        GTVerificationInfo_free(ver_tmp);
        ver_tmp = NULL;
        /* Re-check consistency. */
        res = GTTimestamp_verify(ext, parse, &ver_tmp);
        if (res != GT_OK) {
            goto cleanup;
        }
        if (ver_tmp == NULL || ver_tmp->implicit_data == NULL) {
            res = GT_UNKNOWN_ERROR;
            goto cleanup;
        }
        if (ver_tmp->verification_errors != GT_NO_FAILURES) {
            goto cleanup;
        }
        /* Re-check document hash. */
        res = GTTimestamp_checkDocumentHash(ts, hash);
        if (res == GT_OK) {
            ver_tmp->verification_status |= GT_DOCUMENT_HASH_CHECKED;
        } else if (res == GT_WRONG_DOCUMENT) {
            ver_tmp->verification_status |= GT_DOCUMENT_HASH_CHECKED;
            ver_tmp->verification_errors |= GT_WRONG_DOCUMENT_FAILURE;
            res = GT_OK;
            goto cleanup;
        } else {
            goto cleanup;
        }
    }

    if (ext != NULL) {
        /* If we now have an extended timestamp, check publication.
         * GT_TRUST_POINT_NOT_FOUND and GT_INVALID_TRUST_POINT mean it did not match.
         * Everything else is some sort of system error. */
        res = GTTimestamp_checkPublication(ext, pub);
        if (res == GT_OK) {
            ver_tmp->verification_status |= GT_PUBLICATION_CHECKED;
        } else if (res == GT_TRUST_POINT_NOT_FOUND || res == GT_INVALID_TRUST_POINT) {
            ver_tmp->verification_status |= GT_PUBLICATION_CHECKED;
            ver_tmp->verification_errors |= GT_NOT_VALID_PUBLICATION;
            res = GT_OK;
        }
    } else {
        /* Otherwise, check signing key.
         * GT_KEY_NOT_PUBLISHED and GT_CERT_TICKET_TOO_OLD mean key not valid.
         * Everything else is some sort of system error. */
        res = GTTimestamp_checkPublicKey(ts, ver_tmp->implicit_data->registered_time, pub);
        if (res == GT_OK) {
            ver_tmp->verification_status |= GT_PUBLICATION_CHECKED;
        } else if (res == GT_KEY_NOT_PUBLISHED || res == GT_CERT_TICKET_TOO_OLD) {
            ver_tmp->verification_status |= GT_PUBLICATION_CHECKED;
            ver_tmp->verification_errors |= GT_NOT_VALID_PUBLIC_KEY_FAILURE;
            res = GT_OK;
        }
    }

cleanup:

    if (res == GT_OK) {
        if (ext_ts != NULL && ext != NULL && ext != ts &&
                ver_tmp->verification_errors == GT_NO_FAILURES) {
            *ext_ts = ext;
            ext = NULL;
        }
        *ver = ver_tmp;
        ver_tmp = NULL;
    }

    if (ext != ts) {
        GTTimestamp_free(ext);
    }
    GTVerificationInfo_free(ver_tmp);
    GTPubFileVerificationInfo_free(pub_ver);
    GTPublicationsFile_free(pub_tmp);

    return res;
}
/* Helper function to create \p GTPubFileVerificationInfo. */
static int createPubFileVerificationInfo(
		const GTPublicationsFile *publications_file,
		GTPubFileVerificationInfo **verification_info)
{
	int res = GT_UNKNOWN_ERROR;
	int tmp_res = GT_UNKNOWN_ERROR;
	GTPubFileVerificationInfo *tmp_info = NULL;
	const GTPublicationsFile_Cell *cell = NULL;
	GTPublicationsFile_Cell cell_buf;
	unsigned char *cert_der = NULL;
	size_t cert_der_len;
	char *tmp_cert = NULL;

	assert(publications_file != NULL);
	assert(verification_info != NULL);

	tmp_info = GT_malloc(sizeof(GTPubFileVerificationInfo));
	if (tmp_info == NULL) {
		res = GT_OUT_OF_MEMORY;
		goto cleanup;
	}

	tmp_info->publications_count = publications_file->number_of_publications;
	tmp_info->key_hash_count = publications_file->number_of_key_hashes;
	tmp_info->certificate = NULL;

	if (tmp_info->publications_count < 1) {
		tmp_info->first_publication_time = -1;
		tmp_info->last_publication_time = -1;
	} else {
		tmp_res = getPublicationCell(publications_file, 0, &cell, &cell_buf);
		if (tmp_res != GT_OK) {
			res = tmp_res;
			goto cleanup;
		}
		tmp_info->first_publication_time = cell->publication_identifier;

		if (tmp_info->publications_count > 1) {
			tmp_res = getPublicationCell(publications_file,
					tmp_info->publications_count - 1, &cell, &cell_buf);
			if (tmp_res != GT_OK) {
				res = tmp_res;
				goto cleanup;
			}
			tmp_info->last_publication_time = cell->publication_identifier;
		} else {
			tmp_info->last_publication_time = tmp_info->first_publication_time;
		}
	}

	tmp_res = GTPublicationsFile_getSigningCert(publications_file,
			&cert_der, &cert_der_len);
	if (tmp_res != GT_OK) {
		res = tmp_res;
		goto cleanup;
	}

	tmp_cert = GT_base32Encode(cert_der, cert_der_len, 8);
	if (tmp_cert == NULL) {
		res = GT_OUT_OF_MEMORY;
		goto cleanup;
	}
	/* This string duplication is necessary because we dont want to return
	 * OPENSSL_malloc()-ed data in public API. */
	tmp_info->certificate = GT_malloc(strlen(tmp_cert) + 1);
	if (tmp_info->certificate == NULL) {
		res = GT_OUT_OF_MEMORY;
		goto cleanup;
	}
	strcpy(tmp_info->certificate, tmp_cert);

	*verification_info = tmp_info;
	tmp_info = NULL;
	res = GT_OK;

cleanup:
	GTPubFileVerificationInfo_free(tmp_info);
	GT_free(cert_der);
	OPENSSL_free(tmp_cert);

	return res;
}