1
+ /*
2
+ * Tesla Government
3
+ * @author joefitz
4
+ */
5
+
6
+ #include <ngx_config.h>
7
+ #include <ngx_core.h>
8
+ #include <ngx_http.h>
9
+ #include <jwt.h>
10
+
11
+ #include <jansson.h>
12
+
13
+ typedef struct {
14
+ ngx_str_t auth_jwt_loginurl ;
15
+ ngx_str_t auth_jwt_key ;
16
+ ngx_flag_t auth_jwt_enabled ;
17
+ } ngx_http_auth_jwt_loc_conf_t ;
18
+
19
+ static ngx_int_t ngx_http_auth_jwt_init (ngx_conf_t * cf );
20
+ static ngx_int_t ngx_http_auth_jwt_handler (ngx_http_request_t * r );
21
+ static void * ngx_http_auth_jwt_create_loc_conf (ngx_conf_t * cf );
22
+ static char * ngx_http_auth_jwt_merge_loc_conf (ngx_conf_t * cf , void * parent , void * child );
23
+ static int hex_char_to_binary ( char ch , char * ret );
24
+ static int hex_to_binary ( const char * str , u_char * buf , int len );
25
+
26
+ static ngx_command_t ngx_http_auth_jwt_commands [] = {
27
+
28
+ { ngx_string ("auth_jwt_loginurl" ),
29
+ NGX_HTTP_MAIN_CONF |NGX_HTTP_SRV_CONF |NGX_HTTP_LOC_CONF |NGX_CONF_TAKE1 ,
30
+ ngx_conf_set_str_slot ,
31
+ NGX_HTTP_LOC_CONF_OFFSET ,
32
+ offsetof(ngx_http_auth_jwt_loc_conf_t , auth_jwt_loginurl ),
33
+ NULL },
34
+
35
+ { ngx_string ("auth_jwt_key" ),
36
+ NGX_HTTP_MAIN_CONF |NGX_HTTP_SRV_CONF |NGX_HTTP_LOC_CONF |NGX_CONF_TAKE1 ,
37
+ ngx_conf_set_str_slot ,
38
+ NGX_HTTP_LOC_CONF_OFFSET ,
39
+ offsetof(ngx_http_auth_jwt_loc_conf_t , auth_jwt_key ),
40
+ NULL },
41
+
42
+ { ngx_string ("auth_jwt_enabled" ),
43
+ NGX_HTTP_MAIN_CONF |NGX_HTTP_SRV_CONF |NGX_HTTP_LOC_CONF |NGX_CONF_FLAG ,
44
+ ngx_conf_set_flag_slot ,
45
+ NGX_HTTP_LOC_CONF_OFFSET ,
46
+ offsetof(ngx_http_auth_jwt_loc_conf_t , auth_jwt_enabled ),
47
+ NULL },
48
+
49
+ ngx_null_command
50
+ };
51
+
52
+
53
+ static ngx_http_module_t ngx_http_auth_jwt_module_ctx = {
54
+ NULL , /* preconfiguration */
55
+ ngx_http_auth_jwt_init , /* postconfiguration */
56
+
57
+ NULL , /* create main configuration */
58
+ NULL , /* init main configuration */
59
+
60
+ NULL , /* create server configuration */
61
+ NULL , /* merge server configuration */
62
+
63
+ ngx_http_auth_jwt_create_loc_conf , /* create ___location configuration */
64
+ ngx_http_auth_jwt_merge_loc_conf /* merge ___location configuration */
65
+ };
66
+
67
+
68
+ ngx_module_t ngx_http_auth_jwt_module = {
69
+ NGX_MODULE_V1 ,
70
+ & ngx_http_auth_jwt_module_ctx , /* module context */
71
+ ngx_http_auth_jwt_commands , /* module directives */
72
+ NGX_HTTP_MODULE , /* module type */
73
+ NULL , /* init master */
74
+ NULL , /* init module */
75
+ NULL , /* init process */
76
+ NULL , /* init thread */
77
+ NULL , /* exit thread */
78
+ NULL , /* exit process */
79
+ NULL , /* exit master */
80
+ NGX_MODULE_V1_PADDING
81
+ };
82
+
83
+
84
+ static ngx_int_t ngx_http_auth_jwt_handler (ngx_http_request_t * r )
85
+ {
86
+ ngx_int_t n ;
87
+ ngx_str_t jwtCookieName = ngx_string ("rampartjwt" );
88
+ ngx_str_t jwtCookieVal ;
89
+ char * jwtCookieValChrPtr ;
90
+ ngx_http_auth_jwt_loc_conf_t * jwtcf ;
91
+ u_char * keyBinary ;
92
+ jwt_t * jwt ;
93
+ int jwtParseReturnCode ;
94
+ jwt_alg_t alg ;
95
+ time_t exp ;
96
+ time_t now ;
97
+ ngx_str_t passportKeyCookieName = ngx_string ("PassportKey" );
98
+ ngx_str_t passportKeyCookieVal ;
99
+
100
+
101
+ jwtcf = ngx_http_get_module_loc_conf (r , ngx_http_auth_jwt_module );
102
+
103
+ if (!jwtcf -> auth_jwt_enabled )
104
+ {
105
+ return NGX_DECLINED ;
106
+ }
107
+
108
+ // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Key: %s, Enabled: %d",
109
+ // jwtcf->auth_jwt_key.data,
110
+ // jwtcf->auth_jwt_enabled);
111
+
112
+ // get the cookie
113
+ // TODO: the cookie name could be passed in dynamicallly
114
+ n = ngx_http_parse_multi_header_lines (& r -> headers_in .cookies , & jwtCookieName , & jwtCookieVal );
115
+ if (n == NGX_DECLINED )
116
+ {
117
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "failed to obtain rampartjwt cookie" );
118
+ goto redirect ;
119
+ }
120
+
121
+ // the cookie data is not necessarily null terminated... we need a null terminated character pointer
122
+ jwtCookieValChrPtr = ngx_alloc (jwtCookieVal .len + 1 , r -> connection -> log );
123
+ ngx_memcpy (jwtCookieValChrPtr , jwtCookieVal .data , jwtCookieVal .len );
124
+ * (jwtCookieValChrPtr + jwtCookieVal .len ) = '\0' ;
125
+
126
+ // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "rampartjwt: %s %d", jwtCookieValChrPtr, jwtCookieVal.len);
127
+
128
+ // convert key from hex to binary
129
+ keyBinary = ngx_alloc (jwtcf -> auth_jwt_key .len / 2 , r -> connection -> log );
130
+ if (0 != hex_to_binary ((char * )jwtcf -> auth_jwt_key .data , keyBinary , jwtcf -> auth_jwt_key .len ))
131
+ {
132
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "failed to turn hex key into binary" );
133
+ goto redirect ;
134
+ }
135
+
136
+ // validate the jwt
137
+ jwtParseReturnCode = jwt_decode (& jwt , jwtCookieValChrPtr , keyBinary , jwtcf -> auth_jwt_key .len / 2 );
138
+ if (jwtParseReturnCode != 0 )
139
+ {
140
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "failed to parse jwt" );
141
+ goto redirect ;
142
+ }
143
+
144
+ // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "parsed jwt:\n%s", jwt_dump_str(jwt, 1));
145
+
146
+ // validate the algorithm
147
+ alg = jwt_get_alg (jwt );
148
+ if (alg != JWT_ALG_HS256 )
149
+ {
150
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "invalid algorithm in jwt %d" , alg );
151
+ goto redirect ;
152
+ }
153
+
154
+ // validate the exp date of the JWT
155
+ exp = (time_t )jwt_get_grant_int (jwt , "exp" );
156
+ now = time (NULL );
157
+ if (exp < now )
158
+ {
159
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "the jwt has expired" );
160
+ goto redirect ;
161
+ }
162
+
163
+ // ensure that the user has a matching PassportKey cookie.
164
+ // this can be removed once we and our partners no longer use the PassportKey cookie
165
+ n = ngx_http_parse_multi_header_lines (& r -> headers_in .cookies , & passportKeyCookieName , & passportKeyCookieVal );
166
+ if (n == NGX_DECLINED ) {
167
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "failed to obtain passport cookie" );
168
+ goto redirect ;
169
+ };
170
+
171
+ // compare both cookies
172
+ if (ngx_strncmp (jwtCookieVal .data , passportKeyCookieVal .data , jwtCookieVal .len ))
173
+ {
174
+ ngx_log_error (NGX_LOG_ERR , r -> connection -> log , 0 , "PassportKey cookie does not match rampartjwt cookie" );
175
+ goto redirect ;
176
+ }
177
+
178
+ return NGX_OK ;
179
+
180
+ redirect :
181
+ r -> headers_out .___location = ngx_list_push (& r -> headers_out .headers );
182
+ if (r -> headers_out .___location == NULL ) {
183
+ ngx_http_finalize_request (r , NGX_HTTP_INTERNAL_SERVER_ERROR );
184
+ }
185
+ r -> headers_out .___location -> hash = 1 ;
186
+ r -> headers_out .___location -> key .len = sizeof ("Location" ) - 1 ;
187
+ r -> headers_out .___location -> key .data = (u_char * ) "Location" ;
188
+ r -> headers_out .___location -> value .len = jwtcf -> auth_jwt_loginurl .len ;
189
+ r -> headers_out .___location -> value .data = jwtcf -> auth_jwt_loginurl .data ;
190
+ return NGX_HTTP_MOVED_PERMANENTLY ;
191
+ }
192
+
193
+
194
+ static ngx_int_t ngx_http_auth_jwt_init (ngx_conf_t * cf )
195
+ {
196
+ ngx_http_handler_pt * h ;
197
+ ngx_http_core_main_conf_t * cmcf ;
198
+
199
+ cmcf = ngx_http_conf_get_module_main_conf (cf , ngx_http_core_module );
200
+
201
+ h = ngx_array_push (& cmcf -> phases [NGX_HTTP_ACCESS_PHASE ].handlers );
202
+ if (h == NULL ) {
203
+ return NGX_ERROR ;
204
+ }
205
+
206
+ * h = ngx_http_auth_jwt_handler ;
207
+
208
+ return NGX_OK ;
209
+ }
210
+
211
+
212
+ static void *
213
+ ngx_http_auth_jwt_create_loc_conf (ngx_conf_t * cf )
214
+ {
215
+ ngx_http_auth_jwt_loc_conf_t * conf ;
216
+
217
+ conf = ngx_pcalloc (cf -> pool , sizeof (ngx_http_auth_jwt_loc_conf_t ));
218
+ if (conf == NULL ) {
219
+ return NULL ;
220
+ }
221
+
222
+ // set the flag to unset
223
+ conf -> auth_jwt_enabled = (ngx_flag_t ) - 1 ;
224
+
225
+ ngx_conf_log_error (NGX_LOG_DEBUG , cf , 0 , "Created Location Configuration" );
226
+
227
+ return conf ;
228
+ }
229
+
230
+
231
+ static char *
232
+ ngx_http_auth_jwt_merge_loc_conf (ngx_conf_t * cf , void * parent , void * child )
233
+ {
234
+ ngx_http_auth_jwt_loc_conf_t * prev = parent ;
235
+ ngx_http_auth_jwt_loc_conf_t * conf = child ;
236
+
237
+ ngx_conf_merge_str_value (conf -> auth_jwt_loginurl , prev -> auth_jwt_loginurl , "" );
238
+ ngx_conf_merge_str_value (conf -> auth_jwt_key , prev -> auth_jwt_key , "" );
239
+
240
+
241
+ if (conf -> auth_jwt_enabled == ((ngx_flag_t ) - 1 )) {
242
+ conf -> auth_jwt_enabled = (prev -> auth_jwt_enabled == ((ngx_flag_t ) - 1 )) ? 0 : prev -> auth_jwt_enabled ;
243
+ }
244
+
245
+ ngx_conf_log_error (NGX_LOG_DEBUG , cf , 0 , "Merged Location Configuration" );
246
+
247
+ // ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "Key: %s, Enabled: %d",
248
+ // conf->auth_jwt_key.data,
249
+ // conf->auth_jwt_enabled);
250
+ return NGX_CONF_OK ;
251
+ }
252
+
253
+ static int
254
+ hex_char_to_binary ( char ch , char * ret )
255
+ {
256
+ ch = tolower ( ch );
257
+ if ( isdigit ( ch ) )
258
+ * ret = ch - '0' ;
259
+ else if ( ch >= 'a' && ch <= 'f' )
260
+ * ret = ( ch - 'a' ) + 10 ;
261
+ else if ( ch >= 'A' && ch <= 'F' )
262
+ * ret = ( ch - 'A' ) + 10 ;
263
+ else
264
+ return * ret = 0 ;
265
+ return 1 ;
266
+ }
267
+
268
+ static int
269
+ hex_to_binary ( const char * str , u_char * buf , int len ) {
270
+ u_char
271
+ * cpy = buf ;
272
+ char
273
+ low ,
274
+ high ;
275
+ int
276
+ odd = len % 2 ;
277
+
278
+ if (odd ) {
279
+ return -1 ;
280
+ }
281
+
282
+ for (int i = 0 ; i < len ; i += 2 ) {
283
+ hex_char_to_binary ( * (str + i ), & high );
284
+ hex_char_to_binary ( * (str + i + 1 ), & low );
285
+
286
+ * cpy ++ = low | (high << 4 );
287
+ }
288
+ return 0 ;
289
+ }
0 commit comments