Skip to content

Commit 1653ef1

Browse files
PEM key file support (TeslaGov#56)
* Fix newlines (sry win) in config * Add reading key file name, minify docker image size * Add key file path, minor name change * Fix memory leak * Fix indentation * Add RS256 key file documentation * Fix tests and config * Fix newlines * Add private keyfile fields inside ___location config * Add tests for rsa256 keyfile scenarios * Update readme Co-authored-by: Branimir Malesevic <[email protected]>
1 parent 59ed4f9 commit 1653ef1

File tree

5 files changed

+116
-23
lines changed

5 files changed

+116
-23
lines changed

Dockerfile

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,12 @@ RUN echo "enabled=1" >>/etc/yum.repos.d/nginx.repo
1919

2020
RUN yum -y update && \
2121
yum -y groupinstall 'Development Tools' && \
22-
yum -y install pcre-devel pcre zlib-devel openssl-devel wget cmake check-devel check && \
22+
yum -y install pcre-devel pcre zlib-devel openssl-devel wget cmake3 check-devel check && \
2323
yum -y install nginx-$NGINX_VERSION
2424

25-
# for compiling for rh-nginx110
26-
# yum -y install libxml2 libxslt libxml2-devel libxslt-devel gd gd-devel perl-ExtUtils-Embed
27-
2825
# for compiling for epel7
2926
RUN yum -y install libxml2 libxslt libxml2-devel libxslt-devel gd gd-devel perl-ExtUtils-Embed geoip geoip-devel google-perftools google-perftools-devel
3027

31-
# Jansson requires new cmake
32-
RUN yum -y install cmake3 && \
33-
alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake 10 \
34-
--slave /usr/local/bin/ctest ctest /usr/bin/ctest \
35-
--slave /usr/local/bin/cpack cpack /usr/bin/cpack \
36-
--slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake \
37-
--family cmake && \
38-
alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake3 20 \
39-
--slave /usr/local/bin/ctest ctest /usr/bin/ctest3 \
40-
--slave /usr/local/bin/cpack cpack /usr/bin/cpack3 \
41-
--slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake3 \
42-
--family cmake
43-
4428
RUN mkdir -p /root/dl
4529
WORKDIR /root/dl
4630

@@ -50,7 +34,7 @@ RUN wget https://github.com/akheron/jansson/archive/v$JANSSON_VERSION.zip && \
5034
rm v$JANSSON_VERSION.zip && \
5135
ln -sf jansson-$JANSSON_VERSION jansson && \
5236
cd /root/dl/jansson && \
53-
cmake . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \
37+
cmake3 . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \
5438
make && \
5539
make check && \
5640
make install
@@ -99,12 +83,14 @@ RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && \
9983
# Get nginx ready to run
10084
COPY resources/nginx.conf /etc/nginx/nginx.conf
10185
COPY resources/test-jwt-nginx.conf /etc/nginx/conf.d/test-jwt-nginx.conf
86+
COPY resources/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf
10287
RUN rm -rf /usr/share/nginx/html
10388
RUN cp -r /root/dl/nginx/html /usr/share/nginx
10489
RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure
10590
RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256
10691
RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-auth-header
10792
RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-no-redirect
93+
RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256-file
10894

10995
ENTRYPOINT ["/usr/sbin/nginx"]
11096

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ auth_jwt_loginurl "https://yourdomain.com/loginpage";
4545
auth_jwt_enabled on;
4646
auth_jwt_algorithm HS256; # or RS256
4747
auth_jwt_validate_email on; # or off
48+
auth_jwt_use_keyfile off; # or on
49+
auth_jwt_keyfile_path "/app/pub_key";
4850
```
4951

5052
The default algorithm is 'HS256', for symmetric key validation. When using HS256, the value for `auth_jwt_key` should be specified in binhex format. It is recommended to use at least 256 bits of data (32 pairs of hex characters or 64 characters in total) as in the example above. Note that using more than 512 bits will not increase the security. For key guidelines please see NIST Special Publication 800-107 Recommendation for Applications Using Approved Hash Algorithms, Section 5.3.2 The HMAC Key.
5153

52-
The configuration also supports the `auth_jwt_algorithm` 'RS256', for RSA 256-bit public key validation. If using "auth_jwt_algorithm RS256;", then the `auth_jwt_key` field must be set to your public key.
54+
The configuration also supports the `auth_jwt_algorithm` 'RS256', for RSA 256-bit public key validation. If using "auth_jwt_algorithm RS256;", then the `auth_jwt_key` field must be set to your public key **OR** `auth_jwt_use_keyfile` should be set to `on` with the `auth_jwt_keyfile_path` set to the public key path (nginx won't start if the `auth_jwt_use_keyfile` is set to `on` without a keyfile).
5355
That is the public key, rather than a PEM certificate. I.e.:
5456

5557
```
@@ -64,6 +66,13 @@ oQIDAQAB
6466
-----END PUBLIC KEY-----";
6567
```
6668

69+
**OR**
70+
71+
```
72+
auth_jwt_use_keyfile on;
73+
auth_jwt_keyfile_path "/etc/nginx/pub_key.pem";
74+
```
75+
6776
A typical use would be to specify the key and loginurl on the main level
6877
and then only turn on the locations that you want to secure (not the login page).
6978
Unauthorized requests are given 302 "Moved Temporarily" responses with a ___location of the specified loginurl.

resources/test-jwt-nginx.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ BwIDAQAB
4444
index index.html index.htm;
4545
}
4646

47+
___location ~ ^/secure-rs256-file/ {
48+
auth_jwt_enabled on;
49+
auth_jwt_validation_type AUTHORIZATION;
50+
auth_jwt_algorithm RS256;
51+
auth_jwt_redirect off;
52+
auth_jwt_use_keyfile on;
53+
auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf";
54+
root /usr/share/nginx;
55+
index index.html index.htm;
56+
}
57+
4758
___location / {
4859
root /usr/share/nginx/html;
4960
index index.html index.htm;

src/ngx_http_auth_jwt_module.c

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "ngx_http_auth_jwt_binary_converters.h"
1919
#include "ngx_http_auth_jwt_string.h"
2020

21+
#include <stdio.h>
22+
2123
typedef struct {
2224
ngx_str_t auth_jwt_loginurl;
2325
ngx_str_t auth_jwt_key;
@@ -26,7 +28,10 @@ typedef struct {
2628
ngx_str_t auth_jwt_validation_type;
2729
ngx_str_t auth_jwt_algorithm;
2830
ngx_flag_t auth_jwt_validate_email;
29-
31+
ngx_str_t auth_jwt_keyfile_path;
32+
ngx_flag_t auth_jwt_use_keyfile;
33+
// Private field for keyfile data
34+
ngx_str_t _auth_jwt_keyfile;
3035
} ngx_http_auth_jwt_loc_conf_t;
3136

3237
static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf);
@@ -86,6 +91,20 @@ static ngx_command_t ngx_http_auth_jwt_commands[] = {
8691
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validate_email),
8792
NULL },
8893

94+
{ ngx_string("auth_jwt_keyfile_path"),
95+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
96+
ngx_conf_set_str_slot,
97+
NGX_HTTP_LOC_CONF_OFFSET,
98+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_keyfile_path),
99+
NULL },
100+
101+
{ ngx_string("auth_jwt_use_keyfile"),
102+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
103+
ngx_conf_set_flag_slot,
104+
NGX_HTTP_LOC_CONF_OFFSET,
105+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_use_keyfile),
106+
NULL },
107+
89108
ngx_null_command
90109
};
91110

@@ -129,6 +148,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
129148
char* return_url;
130149
ngx_http_auth_jwt_loc_conf_t *jwtcf;
131150
u_char *keyBinary;
151+
// For clearing it later on
132152
jwt_t *jwt = NULL;
133153
int jwtParseReturnCode;
134154
jwt_alg_t alg;
@@ -177,8 +197,18 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
177197
else if ( auth_jwt_algorithm.len == sizeof("RS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "RS256", sizeof("RS256") - 1) == 0 )
178198
{
179199
// in this case, 'Binary' is a misnomer, as it is the public key string itself
180-
keyBinary = jwtcf->auth_jwt_key.data;
181-
keylen = jwtcf->auth_jwt_key.len;
200+
if (jwtcf->auth_jwt_use_keyfile == 1)
201+
{
202+
// Set to global variables
203+
// NOTE: check for keyBin == NULL skipped, unnecessary check; nginx should fail to start
204+
keyBinary = (u_char*)jwtcf->_auth_jwt_keyfile.data;
205+
keylen = jwtcf->_auth_jwt_keyfile.len;
206+
}
207+
else
208+
{
209+
keyBinary = jwtcf->auth_jwt_key.data;
210+
keylen = jwtcf->auth_jwt_key.len;
211+
}
182212
}
183213
else
184214
{
@@ -239,6 +269,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
239269

240270
jwt_free(jwt);
241271

272+
242273
return NGX_OK;
243274

244275
redirect:
@@ -358,7 +389,6 @@ static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf)
358389
return NGX_OK;
359390
}
360391

361-
362392
static void *
363393
ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf)
364394
{
@@ -374,12 +404,43 @@ ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf)
374404
conf->auth_jwt_enabled = (ngx_flag_t) -1;
375405
conf->auth_jwt_redirect = (ngx_flag_t) -1;
376406
conf->auth_jwt_validate_email = (ngx_flag_t) -1;
407+
conf->auth_jwt_use_keyfile = (ngx_flag_t) -1;
377408

378409
ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration");
379410

380411
return conf;
381412
}
382413

414+
// Loads the RSA256 public key into the ___location config struct
415+
static ngx_int_t
416+
loadAuthKey(ngx_conf_t *cf, ngx_http_auth_jwt_loc_conf_t* conf) {
417+
FILE *keyFile = fopen((const char*)conf->auth_jwt_keyfile_path.data, "rb");
418+
419+
// Check if file exists or is correctly opened
420+
if (keyFile == NULL)
421+
{
422+
ngx_log_error(NGX_LOG_ERR, cf->log, 0, "failed to open pub key file");
423+
return NGX_ERROR;
424+
}
425+
426+
// Read file length
427+
fseek(keyFile, 0, SEEK_END);
428+
long keySize = ftell(keyFile);
429+
fseek(keyFile, 0, SEEK_SET);
430+
431+
if (keySize == 0)
432+
{
433+
ngx_log_error(NGX_LOG_ERR, cf->log, 0, "invalid key file size, check the key file");
434+
return NGX_ERROR;
435+
}
436+
437+
conf->_auth_jwt_keyfile.data = ngx_palloc(cf->pool, keySize);
438+
fread(conf->_auth_jwt_keyfile.data, 1, keySize, keyFile);
439+
conf->_auth_jwt_keyfile.len = (int)keySize;
440+
441+
fclose(keyFile);
442+
return NGX_OK;
443+
}
383444

384445
static char *
385446
ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
@@ -391,6 +452,7 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
391452
ngx_conf_merge_str_value(conf->auth_jwt_key, prev->auth_jwt_key, "");
392453
ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, "");
393454
ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256");
455+
ngx_conf_merge_str_value(conf->auth_jwt_keyfile_path, prev->auth_jwt_keyfile_path, "");
394456
ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1);
395457

396458
if (conf->auth_jwt_enabled == ((ngx_flag_t) -1))
@@ -403,6 +465,26 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
403465
conf->auth_jwt_redirect = (prev->auth_jwt_redirect == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_redirect;
404466
}
405467

468+
if (conf->auth_jwt_use_keyfile == ((ngx_flag_t) -1))
469+
{
470+
conf->auth_jwt_use_keyfile = (prev->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_use_keyfile;
471+
}
472+
473+
// If the usage of the keyfile is specified, check if the key_path is also configured
474+
if (conf->auth_jwt_use_keyfile == 1)
475+
{
476+
if (ngx_strcmp(conf->auth_jwt_keyfile_path.data, "") != 0)
477+
{
478+
if (loadAuthKey(cf, conf) != NGX_OK)
479+
return NGX_CONF_ERROR;
480+
}
481+
else
482+
{
483+
ngx_log_error(NGX_LOG_ERR, cf->log, 0, "auth_jwt_keyfile_path not specified");
484+
return NGX_CONF_ERROR;
485+
}
486+
}
487+
406488
return NGX_CONF_OK;
407489
}
408490

test.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ main() {
2525
local MISSING_SUB_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmaXJzdE5hbWUiOiJoZWxsbyIsImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwicm9sZXMiOlsidGhpcyIsInRoYXQiLCJ0aGVvdGhlciJdLCJpc3MiOiJpc3N1ZXIiLCJwZXJzb25JZCI6Ijc1YmIzY2M3LWI5MzMtNDRmMC05M2M2LTE0N2IwODJmYWRiNSIsImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.lD6jUsazVtzeGhRTNeP_b2Zs6O798V2FQql11QOEI1Q
2626
local MISSING_EMAIL_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwiaXNzIjoiaXNzdWVyIiwicGVyc29uSWQiOiI3NWJiM2NjNy1iOTMzLTQ0ZjAtOTNjNi0xNDdiMDgyZmFkYjUiLCJleHAiOjE5MDg4MzUyMDAsImlhdCI6MTQ4ODgxOTYwMCwidXNlcm5hbWUiOiJoZWxsby53b3JsZCJ9.tJoAl_pvq95hK7GKqsp5TU462pLTbmSYZc1fAHzcqWM
2727
local VALID_RS256_JWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwgImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwgInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwgImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwgImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.cn5Gb75XL-r7TMsPuqzWoKZ06ZsyF_VZIG0Ohn8uZZFeF8dFUhSrEOYe8WFN6Eon8a8LC0OCI9eNdGiD4m_e9TD1Iz2juqaeos-6yd7SWuODr4YS8KD3cqfXndnLRPzp9PC_UIpATsbqOmxGDrRKvHsQq0TuIXImU3rM_m3kFJFgtoJFHx3KmZUo_Ozkyhhc6Pukikhy6odNAtEyLHP5_tabMXtkeAuIlG8dhjAxef4mJLexYFclG-vl7No5VBU4JrMbfgyxtobcYoE-bDIpmQHywrwo6Li7X0hgHJ17sfS3G2YMHmE-Ij_W2Lf9kf5r2r12DUvg44SLIfM58pCINQ
28+
local INVALID_RSA256_JWT=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ._aQmIBL4CVBxU1fNMOHp0kkagFaaX2TvAEenizytwd0
2829

2930
test_jwt "Insecure test" "/" "200"
3031

@@ -45,6 +46,10 @@ main() {
4546
test_jwt "Secure test with jwt cookie - with no email" "/secure/" "200" " --cookie \"rampartjwt=${MISSING_EMAIL_JWT}\""
4647

4748
test_jwt "Secure test with rs256 jwt cookie" "/secure-rs256/" "200" " --cookie \"rampartjwt=${VALID_RS256_JWT}\""
49+
50+
test_jwt "Secure test rsa256 from file with valid jwt" "/secure-rs256-file/" "200" "--header \"Authorization: Bearer ${VALID_RS256_JWT}\""
51+
52+
test_jwt "Secure test rsa256 from file with invalid jwt" "/secure-rs256-file/" "401" "--header \"Authorization: Bearer ${INVALID_RSA256_JWT}\""
4853
}
4954

5055
main "$@"

0 commit comments

Comments
 (0)