In the Nginx Module System: The Prelations, I mentioned that I will talk about how to write and compile an Nginx module.

Recently, we need to use RSA keys to encrypt data and provide an open interface, so we have a Nginx RSA encryption and decrypt module.

Writing in the front

It is not difficult to implement a function with RSA encryption and decryption, use any “popular” language, call the relevant function library/module to operate, generally only need two steps:

  • Creating or loading an RSA KEY(s).
  • Set the parameters such as algorithm, calculate and get the result.

However, even if we need to provide the simplest open interface without authentication, there will be a lot of additional work to do, such as: how to provide basic Web services, how to design cloud architecture, how to efficiently use server resources, how to ensure the quality of service, how to monitor… However, most of these things are not what we originally intended and require extra writing or a lot of setup. Is there a more convenient solution?

As we all know, Nginx is a high-performance Web server that is easy to deploy and has strong scalability. If we add the functionality we need on it, the need for an open interface can be achieved with minimal cost, right?

Other programmes and deficiencies in the community

Before implementation, I did some simple research in the open source community and found that there are two implementations of this requirement:

  • Github.com/spacewander…
  • Github.com/LittleLiByt…

The two implementations are both based on the module of OpenResty derived from Nginx. The important difference between the two is that the former only supports private key decrypting based on public key encryption, while the private key encryption and public key decrypting are not supported. The latter has completed this function. The implementation difference is the heavy use of LUA FFI, which allows external C libraries to be called from LUA code, and the import of OpenSSL functions for RSA encryption and decrypting into the LUA module, while the latter uses the same scheme, but the difference is that, The self-built RSA library was used to call the OpenSSL function library, which made great progress in the execution efficiency (during the pressure test, the CPU reached 20%+ at one time).

Whether the author is not “familiar” with C language like me or prefers to use LUA, the code directly uses the example code of Ravishanker Kusuma, a foreign technology blogger in March 2014: RSA Encryption & Decryption Example with OpenSSL in C.

The example code above may have inspired the first library solution in this article. In 123, 2014, the original author Doujiang24 submitted the same encryption and decrypting methods as the above article: github.com/spacewander… In contrast to the original author’s sample code, you can see in this submission that the author has added load handling for “private keys containing password protection.”

But since you’re already thinking about compiling C libraries, why not take it a step further? This is especially true for such persistent features. There are obviously more performance benefits to be gained by compiling directly into Nginx modules.

Write the Nginx module: complete the basic calculation part

In order to make the article as simple as possible, I will be a certain simplification and adjustment of the above code, only to achieve the private key encryption function, and the external use of variables to do the cleaning operation, reduce the original code memory overflow problem, other functions to achieve similar, here is not much to describe.

Considering the unreadable content after RSA encryption, a simple Base64 encoding implementation was introduced as an extra.

#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/buffer.h>

#include <stdio.h>
#include <string.h>

char privateKey[] = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n"
                    "MIIJrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQICTqoISmC8M0CAggA\n". ."onL8DKhku9s/5NB+eEVC3v4JubSfph0GEiVemMIQxMI2\n"
                    "-----END ENCRYPTED PRIVATE KEY-----\n";

char *passphrase = "soulteary.com";

int encrypt(unsigned char *data, int data_len, unsigned char *encrypted)
{
    BIO *keybio;
    keybio = BIO_new_mem_buf(privateKey, - 1);

    RSA *rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, passphrase);
    int ret_len = RSA_private_encrypt(data_len, data, encrypted, rsa, RSA_PKCS1_PADDING);

    BIO_free(keybio);
    RSA_free(rsa);

    return ret_len;
}

char *base64(const unsigned char *input, int length)
{
    BIO *bmem, *b64;
    BUF_MEM *bptr;

    b64 = BIO_new(BIO_f_base64());
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, input, length);
    BIO_flush(b64);
    BIO_get_mem_ptr(b64, &bptr);

    char *buff = (char *)malloc(bptr->length);
    memcpy(buff, bptr->data, bptr->length - 1);
    buff[bptr->length - 1] = 0;

    BIO_free_all(b64);

    return buff;
}

int main(a)
{

    char raw[2048 / 8] = "{\"key\": \"val\"}";
    unsigned char encrypted[4098] = {};

    int encrypted_length = encrypt(raw, strlen(raw), encrypted);
    if (encrypted_length == - 1)
    {
        printf("Encrypt failed");
        exit(0);
    }

    char *result = base64(encrypted, encrypted_length);

    printf("%s\n", result);

    exit(0);
}
Copy the code

To test the functionality more easily, save the above contents as encrypt.c, and then write a Dockerfile for the basic calculation part of the compile test (note the compile order here to avoid a failed compile) :

The FROM alpine: 3.13 AS Builder RUN cat/etc/apk/repositories | sed -e"s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/" | tee /etc/apk/repositories
RUN apk add openssl-dev libressl-dev gcc g++
COPY ./encrypt.c /
RUN cd / && \
	gcc encrypt.c -lssl -lcrypto -o encrypt && \
	cp encrypt /bin/
Copy the code

Build a temporary container using docker build -t test. Then test the program using docker run –rm -it test encrypt. You will get something similar to this.

rFKPHoJPR4iExWgx6EFzLVA4uISNyxYDtmOlGT+e6SBy9SPDve4o5YNzSbX1grnj
bCFwvp80SwJzs1yaFzChDxo7HTdV3hK3syba+8zHw05FBeuw4/q8zn4e+KAv5QjE
KzrQvMlzE1XsPrbI+IjJpqGIrpy57VBVr8CpmT/RajqZ42fy/cgn429i3NJhlckW
vVPbY7x3vcXC/5FcRwR9hqPJ2qVXulVH/SxQ422bmLigFHwnWjT0qnDVTvgQFeQd
1edmJzPgbhycmGPvCdjRvN80eEX8lp3Oz92uACXfeReab26R0vhgGysPv3w97vdN
TPmt9l1eyeWSnYR/gWU9HSM7FbAJyvKLp5h07X4AYyf2uDl7DxWIJp+ZG/IxzCzt
2IzKN2siq2bqvJEqR7+wDS2ttgCzD2ogmMMiQFgAa1yDruRasSJbV408n7STmE6h
Z6klL8C+zXwgOLYnDi7bNVUhz6BkqHrt+utKql6zr0lKdOywwqElOQJeostBknH5
OCcoazuu+ZOOeAT3DoRLRVqHp/v75aIW9nYJmNjCqgonYrw9flKezh04nlSCjDaF
DYJKVUSsL0mAhGMxfcVWrEzWOTAIOvg6U3vshSpDKk3KCaRhamWxCMVItfBNeeHL
T8ZYF0tHz/cLkBm1wSNOuTxaGQRD/ZH0lSQGP8Aq4x4=
Copy the code

After verifying that the basic functionality works, let’s look at how to write it as a module that Nginx can call.

Write the Nginx module: rewrite the Nginx module as required

Instead of letting Nginx retrieve data from the request and dynamically change the results of the calculation, we’ll just make a simple code tweak that allows Nginx to call the functions we implemented earlier.

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/buffer.h>

char privateKey[] = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n"
                    "MIIJrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQICTqoISmC8M0CAggA\n". ."onL8DKhku9s/5NB+eEVC3v4JubSfph0GEiVemMIQxMI2\n"
                    "-----END ENCRYPTED PRIVATE KEY-----\n";

char *passphrase = "soulteary.com";
int rsa_encrypt(unsigned char *data, int data_len, unsigned char *encrypted)
{
    BIO *keybio;
    keybio = BIO_new_mem_buf(privateKey, - 1);
    OpenSSL_add_all_algorithms();
    RSA *rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, passphrase);

    int encrypted_length = RSA_private_encrypt(data_len, data, encrypted, rsa, RSA_PKCS1_PADDING);

    BIO_free(keybio);
    RSA_free(rsa);

    return encrypted_length;
}

char *base64(const unsigned char *input, int length)
{
    BIO *bmem, *b64;
    BUF_MEM *bptr;

    b64 = BIO_new(BIO_f_base64());
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, input, length);
    BIO_flush(b64);
    BIO_get_mem_ptr(b64, &bptr);

    char *buff = (char *)malloc(bptr->length);
    memcpy(buff, bptr->data, bptr->length - 1);
    buff[bptr->length - 1] = 0;

    BIO_free_all(b64);

    return buff;
}

static char *ngx_http_encrypt(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_encrypt_handler(ngx_http_request_t *r);

static ngx_command_t ngx_http_encrypt_commands[] = {
    {ngx_string("encrypt"),
     NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
     ngx_http_encrypt,
     0.0.NULL},
    ngx_null_command};

static ngx_str_t will_encrypt_string;

static ngx_http_module_t ngx_http_encrypt_module_ctx = {
    NULL./* preconfiguration */
    NULL./* postconfiguration */

    NULL./* create main configuration */
    NULL./* init main configuration */

    NULL./* create server configuration */
    NULL./* merge server configuration */

    NULL./* create location configuration */
    NULL  /* merge location configuration */
};

ngx_module_t ngx_http_encrypt_module = {
    NGX_MODULE_V1,
    &ngx_http_encrypt_module_ctx, /* module context */
    ngx_http_encrypt_commands,    /* module directives */
    NGX_HTTP_MODULE,              /* module type */
    NULL./* init master */
    NULL./* init module */
    NULL./* init process */
    NULL./* init thread */
    NULL./* exit thread */
    NULL./* exit process */
    NULL./* exit master */
    NGX_MODULE_V1_PADDING};

static ngx_int_t ngx_http_encrypt_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;

    /* we response to 'GET' and 'HEAD' requests only */
    if(! (r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {return NGX_HTTP_NOT_ALLOWED;
    }

    /* discard request body, since we don't need it here */
    rc = ngx_http_discard_request_body(r);

    if(rc ! = NGX_OK) {return rc;
    }

    /* set the 'Content-type' header */
    r->headers_out.content_type_len = sizeof("text/html") - 1;
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *)"text/html";

    /* send the header only, if the request type is http 'HEAD' */
    if (r->method == NGX_HTTP_HEAD)
    {
        r->headers_out.status = NGX_HTTP_OK;
        r->headers_out.content_length_n = will_encrypt_string.len;

        return ngx_http_send_header(r);
    }

    /* allocate a buffer for your response body */
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    char *preset_words = "hi";
    will_encrypt_string.data = (u_char *)preset_words;
    will_encrypt_string.len = strlen(preset_words);

    unsigned char *test_data = (unsigned char *)will_encrypt_string.data;
    int data_len = will_encrypt_string.len;

    unsigned char rsa_encrypted[4096] = {};
    int encrypted_length = rsa_encrypt(test_data, data_len, rsa_encrypted);

    char *base64_data = base64(rsa_encrypted, encrypted_length);
    will_encrypt_string.data = (u_char *)base64_data;
    will_encrypt_string.len = ngx_strlen(will_encrypt_string.data);

    /* attach this buffer to the buffer chain */
    out.buf = b;
    out.next = NULL;

    /* adjust the pointers of the buffer */
    b->pos = will_encrypt_string.data;
    b->last = will_encrypt_string.data + will_encrypt_string.len;
    b->memory = 1;   /* this buffer is in memory */
    b->last_buf = 1; /* this is the last buffer in the buffer chain */

    /* set the status line */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = will_encrypt_string.len;

    /* send the headers of your response */
    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
    {
        return rc;
    }

    /* send the buffer chain of your response */
    return ngx_http_output_filter(r, &out);
}

static char *ngx_http_encrypt(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_encrypt_handler;

    return NGX_CONF_OK;
}
Copy the code

Save the above as ngx_http_encrypt_module.c, and then write the declaration files required for the module:

ngx_addon_name=ngx_http_encrypt_module

if test -n "$ngx_module_link"; then
    ngx_module_type=HTTP
    ngx_module_name=$ngx_addon_name
    ngx_module_incs=
    ngx_module_deps=
    ngx_module_srcs="$ngx_addon_dir/ngx_http_encrypt_module.c"
    ngx_module_libs=
   . auto/module
else
    HTTP_MODULES="$HTTP_MODULES ngx_http_encrypt_module"
    NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_encrypt_module.c"
fi
Copy the code

Before compiling the file, let’s write the Nginx configuration file:

load_module modules/ngx_http_encrypt_module.so;

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log main; keepalive_timeout 65; gzip on; server { listen 80; server_name localhost; location / { encrypt; }}}Copy the code

I use the default Nginx configuration to make a simple change. In the header of the file, I declare to load the dynamic module that will be generated next. In the location configuration, I add my own encrypt directive, and save the above contents as nginx.conf for later use.

Then, with the help of the tool image I mentioned in my previous article “How to efficiently use Nginx Three-way Modules in the Container age,” you can quickly compile and develop Nginx modules. Write a Dockerfile for compiling plug-ins:

FROM soulteary/ prebuild-nginx-modules :base-1.21.1-alpine AS Builder COPY SRC /config /usr/src/encrypt/ COPY src/ngx_http_encrypt_module.c /usr/src/encrypt/ WORKDIR /usr/src/nginx RUN CONFARGS=$(nginx -V 2>&1 | sed -n -e's/^.*arguments: //p') \
    CONFARGS=${CONFARGS/-Os -fomit-frame-pointer -g/-Os} && \
    echo $CONFARGS && \
    ./configure --with-compat $CONFARGS--add-dynamic-module=.. /encrypt && \ make modules FROM nginx:1.21.1-alpine COPY nginx.conf /etc/nginx/nginx.conf COPY -- FROM =Builder /usr/src/nginx/objs/ngx_http_encrypt_module.so /etc/nginx/modules/Copy the code

Again, run the docker build -t test. Build a base image and run the docker run –rm -it -p 8080:80 test to test the functionality. The browser or terminal accesses the local port 8080 and, unsurprisingly, the rSA-encrypted content we expect to see is displayed in the correct Base64 encoding.

Stress testing with software such as WRK shows that compared to the Lua solution using pure Nginx modules, the CPU load is reduced from around 20% to single digits and even stabilized at less than 1%. The results are quite impressive.

Write Nginx modules: improve the module to support parameters

Next, let’s do one of the most general functions that allows the module to accept the request parameters and dynamically calculate the results based on the changes in the parameters.

. /* allocate a bufferfor your response body */
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL)
{
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

ngx_str_t var_user = ngx_string("arg_user");
ngx_uint_t key_user = ngx_hash_key(var_user.data, var_user.len);
ngx_http_variable_value_t *val_user = ngx_http_get_variable(r, &var_user, key_user);

if(val_user ! = NULL && val_user->not_found ! = 1) { char *tpl; asprintf(&tpl,"{\"name\":\"%s\"}", (char *)val_user->data);

    will_encrypt_string.data = (u_char *)tpl;
    will_encrypt_string.len = strlen(tpl);
    free(tpl);
}
else
{
    char *preset_words = "hi"; will_encrypt_string.data = (u_char *)preset_words; will_encrypt_string.len = strlen(preset_words); } unsigned char *test_data = (unsigned char *)will_encrypt_string.data; int data_len = will_encrypt_string.len; .Copy the code

The above code shows how to read the user parameters in a GET request and safely splitthe parameters into a string template for computation. Even if you’re not familiar with C, the above code will allow you to quickly adjust the logic of your calculations to the way you expect.

If you want modules that can be reused quickly in a non-compiled environment, you can refer to the other Nginx plugin source code that “implements Nginx to read static files” to remove certificates compiled into modules. However, if you have a CI environment where it is easy to continuously get the latest build results and compile certificates into modules, you can achieve higher absolute performance.

other

As for the development of Nginx module, I personally suggest reading the Nginx official development documents and the Nginx source code. Compared with the old documents at home and abroad, it can save a lot of time.

If you have specific requirements for the length of encrypted content and need to encrypt extremely long content, I suggest you read this article and consider using EVP functions instead of Simple Public Key Encryption with RSA and OpenSSL.

The last

In the next article, I’ll talk about where I use the features in this article.

–EOF


We have a little toss group, which gathered hundreds of people like to toss.

Without advertising, we would talk about hardware and software, HomeLab, programming issues, and occasionally share information about technical salons in the group.

Like to toss friends welcome to scan code to add friends. (Add friends, please note the real name, indicate the source and purpose, otherwise it will not be approved)

All that stuff about being in a group


This article is licensed under a “CC BY 4.0” license. You are welcome to reprint or modify this article, but the source must be noted. 4.0 International (CC BY 4.0)

By Su Yang

Creation time: August 16, 2021 statistical word count: 11701 words reading time: 24 minutes to read this article links: soulteary.com/2021/08/16/…