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; }