Skip to content

Commit 2ab3fd3

Browse files
TimUnderhayfitzyjoe
authored andcommitted
RSA Key Validation Support (TeslaGov#30)
* First stab at RSA validation. * Fix for build errors * Another build fix * Fix for key copy * Key length fix * Logging * Logging fix * Remove debug logs. Make validation of email optional with auth_jwt_validate_email * Fix for email validation option, now auth_jwt_email_validation * Changed back to auth_jwt_validate_email and additional conf merging code * One more email validation fix * More fixes to email validate * Another fix * Set getJwt logs to NGX_LOG_DEBUG * Updated README. Rearranged some code. * Added else error condition to avert compiler warning.
1 parent 8fcda49 commit 2ab3fd3

File tree

2 files changed

+89
-17
lines changed

2 files changed

+89
-17
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ This module requires several new nginx.conf directives, which can be specified i
1313
auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF";
1414
auth_jwt_loginurl "https://yourdomain.com/loginpage";
1515
auth_jwt_enabled on;
16+
auth_jwt_algorithm HS256; # or RS256
17+
auth_jwt_validate_email on; # or off
1618
```
1719

1820
So, a typical use would be to specify the key and loginurl on the main level and then only turn on the locations that you want to secure (not the login page). Unauthorized requests are given 302 "Moved Temporarily" responses with a ___location of the specified loginurl.
@@ -28,6 +30,34 @@ auth_jwt_validation_type COOKIE=rampartjwt;
2830
```
2931
By default the authorization header is used to provide a JWT for validation. However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT.
3032

33+
34+
35+
The default algorithm is 'HS256', for symmetric key validation. Also supported is 'RS256', for RSA 256-bit public key validation.
36+
37+
If using "auth_jwt_algorithm RS256;", then the 'auth_jwt_key' field must be set to your public key. That is the public key, rather than a PEM certificate. I.e.:
38+
39+
```
40+
auth_jwt_key "-----BEGIN PUBLIC KEY-----
41+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0aPPpS7ufs0bGbW9+OFQ
42+
RvJwb58fhi2BuHMd7Ys6m8D1jHW/AhDYrYVZtUnA60lxwSJ/ZKreYOQMlNyZfdqA
43+
rhYyyUkedDn8e0WsDvH+ocY0cMcxCCN5jItCwhIbIkTO6WEGrDgWTY57UfWDqbMZ
44+
4lMn42f77OKFoxsOA6CVvpsvrprBPIRPa25H2bJHODHEtDr/H519Y681/eCyeQE/
45+
1ibKL2cMN49O7nRAAaUNoFcO89Uc+GKofcad1TTwtTIwmSMbCLVkzGeExBCrBTQo
46+
wO6AxLijfWV/JnVxNMUiobiKGc/PP6T5PI70Uv67Y4FzzWTuhqmREb3/BlcbPwtM
47+
oQIDAQAB
48+
-----END PUBLIC KEY-----";
49+
```
50+
51+
52+
53+
By default, the module will attempt to validate the email address field of the JWT, then set the x-email header of the session, and will log an error if it isn't found. To disable this behavior, for instance if you are using a different user identifier property such as 'sub', set:
54+
55+
```
56+
auth_jwt_validate_email off;
57+
```
58+
59+
60+
3161
The Dockerfile builds all of the dependencies as well as the module, downloads a binary version of nginx, and runs the module as a dynamic module.
3262

3363
Have a look at build.sh, which creates the docker image and container and executes some test requests to illustrate that some pages are secured by the module and requre a valid JWT.

src/ngx_http_auth_jwt_module.c

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ typedef struct {
2424
ngx_flag_t auth_jwt_enabled;
2525
ngx_flag_t auth_jwt_redirect;
2626
ngx_str_t auth_jwt_validation_type;
27+
ngx_str_t auth_jwt_algorithm;
28+
ngx_flag_t auth_jwt_validate_email;
2729

2830
} ngx_http_auth_jwt_loc_conf_t;
2931

@@ -70,6 +72,20 @@ static ngx_command_t ngx_http_auth_jwt_commands[] = {
7072
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validation_type),
7173
NULL },
7274

75+
{ ngx_string("auth_jwt_algorithm"),
76+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
77+
ngx_conf_set_str_slot,
78+
NGX_HTTP_LOC_CONF_OFFSET,
79+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_algorithm),
80+
NULL },
81+
82+
{ ngx_string("auth_jwt_validate_email"),
83+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
84+
ngx_conf_set_flag_slot,
85+
NGX_HTTP_LOC_CONF_OFFSET,
86+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validate_email),
87+
NULL },
88+
7389
ngx_null_command
7490
};
7591

@@ -122,6 +138,8 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
122138
ngx_str_t email_t;
123139
time_t exp;
124140
time_t now;
141+
ngx_str_t auth_jwt_algorithm;
142+
int keylen;
125143

126144
jwtcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module);
127145

@@ -137,16 +155,34 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
137155
goto redirect;
138156
}
139157

140-
// convert key from hex to binary
141-
keyBinary = ngx_palloc(r->pool, jwtcf->auth_jwt_key.len / 2);
142-
if (0 != hex_to_binary((char *)jwtcf->auth_jwt_key.data, keyBinary, jwtcf->auth_jwt_key.len))
158+
// convert key from hex to binary, if a symmetric key
159+
160+
auth_jwt_algorithm = jwtcf->auth_jwt_algorithm;
161+
if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == sizeof("HS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "HS256", sizeof("HS256") - 1)==0))
162+
{
163+
keylen = jwtcf->auth_jwt_key.len / 2;
164+
keyBinary = ngx_palloc(r->pool, keylen);
165+
if (0 != hex_to_binary((char *)jwtcf->auth_jwt_key.data, keyBinary, jwtcf->auth_jwt_key.len))
166+
{
167+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to turn hex key into binary");
168+
goto redirect;
169+
}
170+
}
171+
else if ( auth_jwt_algorithm.len == sizeof("RS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "RS256", sizeof("RS256") - 1) == 0 )
143172
{
144-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to turn hex key into binary");
173+
// in this case, 'Binary' is a misnomer, as it is the public key string itself
174+
keyBinary = ngx_palloc(r->pool, jwtcf->auth_jwt_key.len);
175+
ngx_memcpy(keyBinary, jwtcf->auth_jwt_key.data, jwtcf->auth_jwt_key.len);
176+
keylen = jwtcf->auth_jwt_key.len;
177+
}
178+
else
179+
{
180+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unsupported algorithm");
145181
goto redirect;
146182
}
147183

148184
// validate the jwt
149-
jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, jwtcf->auth_jwt_key.len / 2);
185+
jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, keylen);
150186
if (jwtParseReturnCode != 0)
151187
{
152188
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt");
@@ -155,7 +191,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
155191

156192
// validate the algorithm
157193
alg = jwt_get_alg(jwt);
158-
if (alg != JWT_ALG_HS256)
194+
if (alg != JWT_ALG_HS256 && alg != JWT_ALG_RS256)
159195
{
160196
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg);
161197
goto redirect;
@@ -182,15 +218,18 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
182218
set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t);
183219
}
184220

185-
email = jwt_get_grant(jwt, "emailAddress");
186-
if (email == NULL)
221+
if (jwtcf->auth_jwt_validate_email == 1)
187222
{
188-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain an email address");
189-
}
190-
else
191-
{
192-
email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email);
193-
set_custom_header_in_headers_out(r, &emailHeaderName, &email_t);
223+
email = jwt_get_grant(jwt, "emailAddress");
224+
if (email == NULL)
225+
{
226+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain an email address");
227+
}
228+
else
229+
{
230+
email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email);
231+
set_custom_header_in_headers_out(r, &emailHeaderName, &email_t);
232+
}
194233
}
195234

196235
return NGX_OK;
@@ -321,6 +360,7 @@ ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf)
321360
// set the flag to unset
322361
conf->auth_jwt_enabled = (ngx_flag_t) -1;
323362
conf->auth_jwt_redirect = (ngx_flag_t) -1;
363+
conf->auth_jwt_validate_email = (ngx_flag_t) -1;
324364

325365
ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration");
326366

@@ -337,6 +377,8 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
337377
ngx_conf_merge_str_value(conf->auth_jwt_loginurl, prev->auth_jwt_loginurl, "");
338378
ngx_conf_merge_str_value(conf->auth_jwt_key, prev->auth_jwt_key, "");
339379
ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, "");
380+
ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256");
381+
ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1);
340382

341383
if (conf->auth_jwt_enabled == ((ngx_flag_t) -1))
342384
{
@@ -360,22 +402,22 @@ static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type)
360402
ngx_int_t n;
361403
ngx_str_t authorizationHeaderStr;
362404

363-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "auth_jwt_validation_type.len %d", auth_jwt_validation_type.len);
405+
ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "auth_jwt_validation_type.len %d", auth_jwt_validation_type.len);
364406

365407
if (auth_jwt_validation_type.len == 0 || (auth_jwt_validation_type.len == sizeof("AUTHORIZATION") - 1 && ngx_strncmp(auth_jwt_validation_type.data, "AUTHORIZATION", sizeof("AUTHORIZATION") - 1)==0))
366408
{
367409
// using authorization header
368410
authorizationHeader = search_headers_in(r, authorizationHeaderName.data, authorizationHeaderName.len);
369411
if (authorizationHeader != NULL)
370412
{
371-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len);
413+
ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len);
372414

373415
authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1;
374416
authorizationHeaderStr.len = authorizationHeader->value.len - (sizeof("Bearer ") - 1);
375417

376418
jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr);
377419

378-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Authorization header: %s", jwtCookieValChrPtr);
420+
ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtCookieValChrPtr);
379421
}
380422
}
381423
else if (auth_jwt_validation_type.len > sizeof("COOKIE=") && ngx_strncmp(auth_jwt_validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1)==0)

0 commit comments

Comments
 (0)