static char *ngx_http_acme_create_jwk(ngx_conf_t *cf, void *conf, RSA *key, json_t **jwk)
{
    ngx_str_t e, n, tmp;

    /* Baser64url encode e */
    tmp.len = BN_num_bytes(key->e);
    tmp.data = ngx_alloc(tmp.len, cf->log);
    tmp.len = BN_bn2bin(key->e, tmp.data);
    e.len = ngx_base64_encoded_length(tmp.len);
    e.data = ngx_alloc(e.len, cf->log);
    ngx_encode_base64url(&e, &tmp);
    ngx_free(tmp.data);

    /* Baser64url encode n */
    tmp.len = BN_num_bytes(key->n);
    tmp.data = ngx_alloc(tmp.len, cf->log);
    tmp.len = BN_bn2bin(key->n, tmp.data);
    n.len = ngx_base64_encoded_length(tmp.len);
    n.data = ngx_alloc(n.len, cf->log);
    ngx_encode_base64url(&n, &tmp);
    ngx_free(tmp.data);

    *jwk = json_pack("{s:s, s:s%, s:s%}", "kty", "RSA", "e", e.data, e.len, "n", n.data, n.len);
    if(*jwk == NULL) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to pack JWK");
        return NGX_CONF_ERROR;
    }

    ngx_free(e.data);
    ngx_free(n.data);

    return NGX_CONF_OK;
} /* ngx_http_acme_create_jwk */
ngx_int_t
ngx_http_set_misc_set_encode_base64url(ngx_http_request_t *r, ngx_str_t *res,
    ngx_http_variable_value_t *v)
{
    ngx_str_t        src;

    src.len = v->len;
    src.data = v->data;

    res->len = ngx_base64url_encoded_length(v->len);
    res->data = ngx_palloc(r->pool, res->len);
    if (res->data == NULL) {
        return NGX_ERROR;
    }

#if defined(nginx_version) && nginx_version >= 1005010
    ngx_encode_base64url(res, &src);
#else
    u_int i;
    ngx_encode_base64(res, &src);
    for (i=0;i<res->len;i++) {
        switch (res->data[i]) {
            case '+': res->data[i]='-'; break;
            case '/': res->data[i]='_'; break;
            case '=': res->len--; break;
        }
    }
#endif

    return NGX_OK;
}
static int
ngx_http_lua_ngx_encode_base64url(lua_State *L)
{
    ngx_str_t                p, src;

    if (lua_gettop(L) != 1) {
        return luaL_error(L, "expecting one argument");
    }

    if (lua_isnil(L, 1)) {
        src.data = (u_char *) "";
        src.len = 0;

    } else {
        src.data = (u_char *) luaL_checklstring(L, 1, &src.len);
    }

    p.len = ngx_base64_encoded_length(src.len);

    p.data = lua_newuserdata(L, p.len);

    ngx_encode_base64url(&p, &src);

    lua_pushlstring(L, (char *) p.data, p.len);

    return 1;
}
static char *ngx_http_acme_sign_json(ngx_conf_t *cf, void *conf, json_t *payload, RSA *key, ngx_str_t replay_nonce, json_t **flattened_jws)
{
    /*
     * Structure according to RFC7515:
     *
     * {
     *  "payload":"<payload contents>",
     *  "protected":"<integrity-protected header contents>",
     *  "header":<non-integrity-protected header contents>,
     *  "signature":"<signature contents>"
     * }
     *
     * Example:
     *
     * {
     *  "payload":
     *   "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
     *  "protected":"eyJhbGciOiJFUzI1NiJ9",
     *  "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
     *  "signature":
     *   "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
     * }
     */

    /*
     * ACME restrictions:
     * The JWS MUST use the Flattened JSON Serialization
     * The JWS MUST be encoded using UTF-8
     * The JWS Header or Protected Header MUST include “alg” and “jwk” fields
     * The JWS MUST NOT have the value “none” in its “alg” field
     */

    json_t *jwk;
    json_t *header;
    ngx_str_t encoded_protected_header, serialized_payload, encoded_payload, tmp;
    ngx_str_t signing_input, signature, encoded_signature;
    u_char *tmp_char_p;

    /* Variables for signing */
    EVP_PKEY *evp_key;
    EVP_MD_CTX *mdctx = NULL;
    int ret = 0;

    /*
     * Encode payload
     */

    serialized_payload = (ngx_str_t)ngx_string_dynamic(json_dumps(payload, 0));
    encoded_payload.len = ngx_base64_encoded_length(serialized_payload.len);
    encoded_payload.data = ngx_alloc(encoded_payload.len, cf->log);
    ngx_encode_base64url(&encoded_payload, &serialized_payload);

    println_debug("Signing payload: ", &serialized_payload);

    /*
     * Create header
     */

    /* jwk header */
    if(ngx_http_acme_create_jwk(cf, conf, key, &jwk) != NGX_CONF_OK) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to create the JWK from the account key");
        ngx_free(serialized_payload.data);
        ngx_free(encoded_payload.data);
        return NGX_CONF_ERROR;
    }

    /* Pack header into JSON */
    header = json_pack("{s:s, s:s%, s:o}", "alg", "RS256", "nonce", replay_nonce.data, replay_nonce.len, "jwk", jwk);
    if(header == NULL) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error packing JWS header");
        ngx_free(serialized_payload.data);
        ngx_free(encoded_payload.data);
        return NGX_CONF_ERROR;
    }

    /* Serialize and base64url encode header */
    tmp = (ngx_str_t)ngx_string_dynamic(json_dumps(header, 0));
    encoded_protected_header.len = ngx_base64_encoded_length(tmp.len);
    encoded_protected_header.data = ngx_alloc(encoded_protected_header.len, cf->log);
    ngx_encode_base64url(&encoded_protected_header, &tmp);
    ngx_free(tmp.data);
    json_decref(header);

    /*
     * Create signature
     */

    /* Create signing input */
    /* = ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload)) */
    signing_input.len = encoded_protected_header.len + strlen(".") + encoded_payload.len;
    signing_input.data = ngx_alloc(signing_input.len, cf->log);
    tmp_char_p = ngx_copy(signing_input.data, encoded_protected_header.data, encoded_protected_header.len);
    tmp_char_p = ngx_copy(tmp_char_p, ".", strlen("."));
    tmp_char_p = ngx_copy(tmp_char_p, encoded_payload.data, encoded_payload.len);

    /* Convert the RSA key to the EVP_PKEY structure */
    evp_key = EVP_PKEY_new();
    if(evp_key == NULL) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0,
                "Error signing the message digest for the JWS signature.");
        return NGX_CONF_ERROR;
    }

    if(EVP_PKEY_set1_RSA(evp_key, key) == 0) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0,
                "Error signing the message digest for the JWS signature.");
        return NGX_CONF_ERROR;
    }

    /* Create the message digest context */
    ret = 0;
    mdctx = EVP_MD_CTX_create();
    if(mdctx == NULL)
        goto err;

    /* Initialize the DigestSign operation - SHA-256 has been selected as the message digest function */
    if(EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, evp_key) != 1)
        goto err;

    /* Call update with the message */
    if(EVP_DigestSignUpdate(mdctx, signing_input.data, signing_input.len) != 1)
        goto err;

    /* Finalise the DigestSign operation */
    /* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the */
    /* signature. The length is returned in siglen. */
    if(EVP_DigestSignFinal(mdctx, NULL, &signature.len) != 1)
        goto err;

    /* Allocate memory for the signature */
    signature.data = ngx_alloc(signature.len, cf->log);

    /* Obtain the signature */
    if(EVP_DigestSignFinal(mdctx, signature.data, &signature.len) != 1)
        goto err;

    /* Success */
    ret = 1;

    err:
    if(ret != 1) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0,
                "Error signing the message digest for the JWS signature. OpenSSL error 0x%xl", ERR_get_error());
        return NGX_CONF_ERROR;
    }

    /* Clean up */
    EVP_MD_CTX_destroy(mdctx);
    EVP_PKEY_free(evp_key);

    /* base64url encode the signature */
    encoded_signature.len = ngx_base64_encoded_length(signature.len);
    encoded_signature.data = ngx_alloc(encoded_signature.len, cf->log);
    ngx_encode_base64url(&encoded_signature, &signature);
    ngx_free(signature.data);

    /*
     * Create flattened JWS serialization
     */

    *flattened_jws = json_pack("{s:s%,s:s%,s:s%}",
            "payload", encoded_payload.data, encoded_payload.len,
            "protected", encoded_protected_header.data, encoded_protected_header.len,
            "signature", encoded_signature.data, encoded_signature.len
            );

    ngx_free(serialized_payload.data);
    // TODO (KK) Maybe this is too early for a free since the strings will be used in the flattened JWS (but when to free then?)
    ngx_free(encoded_payload.data);
    ngx_free(encoded_protected_header.data);
    ngx_free(encoded_signature.data);

    if(*flattened_jws == NULL) {
        ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error serializing flattened JWS");
        return NGX_CONF_ERROR;
    }


    return NGX_CONF_OK;
} /* ngx_http_acme_sign_json */
static ngx_int_t
ngx_secure_token_cht_get_var(
	ngx_http_request_t *r,
	ngx_http_variable_value_t *v,
	uintptr_t data)
{
	ngx_secure_token_cht_token_t* token = (void*)data;
	ngx_str_t expires_str;
	ngx_str_t md5hash_str;
	ngx_str_t token_str;
	ngx_str_t acl;
	ngx_md5_t md5;
	u_char end_time_buf[NGX_INT32_LEN];
	u_char md5hash_buf[MD5_DIGEST_LENGTH];
	u_char token_buf[ngx_base64_encoded_length(MD5_DIGEST_LENGTH)];
	time_t end_time;
	size_t result_size;
	u_char* p;
	ngx_int_t rc;

	// get the acl
	rc = ngx_http_secure_token_get_acl(r, token->acl, &acl);
	if (rc != NGX_OK)
	{
		return rc;
	}

	// get the end time
	end_time = token->end.val;
	if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE)
	{
		end_time += ngx_time();
	}
	expires_str.data = end_time_buf;
	expires_str.len = ngx_sprintf(end_time_buf, "%uD", (uint32_t)end_time) - end_time_buf;
	
	// calculate the signature
	ngx_md5_init(&md5);
	ngx_md5_update(&md5, acl.data, acl.len);
	ngx_md5_update(&md5, token->key.data, token->key.len);
	ngx_md5_update(&md5, expires_str.data, expires_str.len);
	ngx_md5_final(md5hash_buf, &md5);

	md5hash_str.data = md5hash_buf;
	md5hash_str.len = sizeof(md5hash_buf);

	token_str.data = token_buf;
	ngx_encode_base64url(&token_str, &md5hash_str);

	// get the result size
	result_size = sizeof(TOKEN_PART1) + token_str.len + sizeof(TOKEN_PART2) + expires_str.len;

	// allocate the result
	p = ngx_pnalloc(r->pool, result_size);
	if (p == NULL)
	{
		return NGX_ERROR;
	}

	v->data = p;

	// build the result
	p = ngx_copy(p, TOKEN_PART1, sizeof(TOKEN_PART1) - 1);
	p = ngx_copy(p, token_str.data, token_str.len);
	p = ngx_copy(p, TOKEN_PART2, sizeof(TOKEN_PART2) - 1);
	p = ngx_copy(p, expires_str.data, expires_str.len);
	*p = '\0';

	v->len = p - v->data;
	v->valid = 1;
	v->no_cacheable = 0;
	v->not_found = 0;

	return NGX_OK;
}