├── b64 ├── cdecode.o ├── cencode.o ├── cdecode.h ├── cencode.h ├── cdecode.c └── cencode.c ├── config ├── CHANGELOG.md ├── LICENSE ├── README.md └── ngx_http_auth_yubikey_module.c /b64/cdecode.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderv32/ngx_http_auth_yubikey_module/HEAD/b64/cdecode.o -------------------------------------------------------------------------------- /b64/cencode.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanderv32/ngx_http_auth_yubikey_module/HEAD/b64/cencode.o -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_auth_yubikey_module 2 | HTTP_MODULES="$HTTP_MODULES ngx_http_auth_yubikey_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_auth_yubikey_module.c $ngx_addon_dir/b64/cencode.c $ngx_addon_dir/b64/cdecode.c" 4 | CORE_LIBS="$CORE_LIBS -lcurl -lykclient -I/usr/local/include" 5 | CFLAGS="-I$ngx_addon_dir/b64" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.1.1 (2014-10-03) 2 | ##Features: 3 | 4 | - Added "auth\_yubikey\_api_url". 5 | 6 | ##Bugfixes: 7 | 8 | - OTP replay against cached credentials fixed 9 | 10 | 11 | # 1.1.0 (2014-10-02) 12 | ##Bugfixes: 13 | 14 | - Compile against latest Nginx and latest Yubikey libraries. 15 | 16 | # 1.0.0 (2011-11-06) 17 | _Initial version_ 18 | 19 | ##Features: 20 | 21 | - Added the new feature "auth\_yubikey\_ttl" to cache 22 | the password for an amount of time. 23 | 24 | ##Bugfixes: 25 | 26 | - OTP replay was possible. 27 | 28 | -------------------------------------------------------------------------------- /b64/cdecode.h: -------------------------------------------------------------------------------- 1 | /* 2 | cdecode.h - c header for a base64 decoding algorithm 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | #ifndef BASE64_CDECODE_H 9 | #define BASE64_CDECODE_H 10 | 11 | typedef enum 12 | { 13 | step_a, step_b, step_c, step_d 14 | } base64_decodestep; 15 | 16 | typedef struct 17 | { 18 | base64_decodestep step; 19 | char plainchar; 20 | } base64_decodestate; 21 | 22 | void base64_init_decodestate(base64_decodestate* state_in); 23 | 24 | int base64_decode_value(char value_in); 25 | 26 | int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); 27 | 28 | #endif /* BASE64_CDECODE_H */ 29 | -------------------------------------------------------------------------------- /b64/cencode.h: -------------------------------------------------------------------------------- 1 | /* 2 | cencode.h - c header for a base64 encoding algorithm 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | #ifndef BASE64_CENCODE_H 9 | #define BASE64_CENCODE_H 10 | 11 | typedef enum 12 | { 13 | step_A, step_B, step_C 14 | } base64_encodestep; 15 | 16 | typedef struct 17 | { 18 | base64_encodestep step; 19 | char result; 20 | int stepcount; 21 | } base64_encodestate; 22 | 23 | void base64_init_encodestate(base64_encodestate* state_in); 24 | 25 | char base64_encode_value(char value_in); 26 | 27 | int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); 28 | 29 | int base64_encode_blockend(char* code_out, base64_encodestate* state_in); 30 | 31 | #endif /* BASE64_CENCODE_H */ 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Alexander Verhaar 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions 6 | * are met: 7 | * 1. Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * 2. Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * 13 | * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 | * SUCH DAMAGE. 24 | * 25 | */ 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nginx module to use a Yubikey for simple http authentication 2 | ============================================================ 3 |   4 | Requirements 5 | ----------- 6 | 7 | yubico-c-client (you can get it at [https://github.com/Yubico/yubico-c-client](https://github.com/Yubico/yubico-c-client "yubico-c-client")) 8 | curl (use the version of your OS) 9 | 10 | 11 | Compilation 12 | ----------- 13 | 14 | When compiling from source build as usual adding the -add-module option: 15 | 16 | ./configure --add-module=$PATH_TO_MODULE 17 | 18 | Configuration 19 | ------------- 20 | 21 | The module has the following directives: 22 | 23 | - "auth\_yubikey": This is the http authentication realm. 24 | 25 | - "auth\_yubikey\_client\_id": This is the client id provided by Yubico. 26 | 27 | - "auth\_yubikey\_secret\_key": This is the secret key provided by Yubico. 28 | 29 | - "auth\_yubikey\_file": Path to the user to key mapping file. The file 30 | contains the username and first 12 chars of your key (just press your 31 | yubikey once the module will ignore the rest). If this directive is 32 | not included every user with a valid key which is registered at the 33 | Yubico API can authenticate. 34 | 35 | - "auth\_yubikey\_ttl": Set the cache timeout is seconds for after the 36 | first login of the user. Default is set to 24 hours. If set to 37 | a low value the user needs to log-in every-time because of the replayed 38 | OTP. 39 | 40 | You have to obtain an Yubico API key at [https://upgrade.yubico.com/getapikey](https://upgrade.yubico.com/getapikey "Get API key") 41 | to get this module working. 42 | 43 | Examples 44 | -------- 45 | 46 | To protect everything under "/yubikey" you will add the following to the 47 | "nginx.conf" file: 48 | 49 | location /yubikey { 50 | auth_yubikey "Restricted Zone"; 51 | auth_yubikey_api_url "https://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s"; 52 | auth_yubikey_client_id "1234"; 53 | auth_yubikey_secret_key "1Ab+CdEfgHi/jkl2M3nOp4qrsT5="; 54 | auth_yubikey_file "/etc/yubikey.conf"; 55 | auth_yubikey_ttl "43200"; 56 | } 57 | 58 | In the file "/etc/yubikey.conf" put the username followed by a colon 59 | and after the colon just press your Yubikey once. 60 | 61 | **Example:** 62 | 63 | admin:ekuhubcruhrkrhkicucbevftickivilrfekvntkjbnvv 64 | -------------------------------------------------------------------------------- /b64/cdecode.c: -------------------------------------------------------------------------------- 1 | /* 2 | cdecoder.c - c source to a base64 decoding algorithm implementation 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | //#include 9 | #include 10 | 11 | int base64_decode_value(char value_in) 12 | { 13 | static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; 14 | static const char decoding_size = sizeof(decoding); 15 | value_in -= 43; 16 | if (value_in < 0 || value_in > decoding_size) return -1; 17 | return decoding[(int)value_in]; 18 | } 19 | 20 | void base64_init_decodestate(base64_decodestate* state_in) 21 | { 22 | state_in->step = step_a; 23 | state_in->plainchar = 0; 24 | } 25 | 26 | int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) 27 | { 28 | const char* codechar = code_in; 29 | char* plainchar = plaintext_out; 30 | char fragment; 31 | 32 | *plainchar = state_in->plainchar; 33 | 34 | switch (state_in->step) 35 | { 36 | while (1) 37 | { 38 | case step_a: 39 | do { 40 | if (codechar == code_in+length_in) 41 | { 42 | state_in->step = step_a; 43 | state_in->plainchar = *plainchar; 44 | return plainchar - plaintext_out; 45 | } 46 | fragment = (char)base64_decode_value(*codechar++); 47 | } while (fragment < 0); 48 | *plainchar = (fragment & 0x03f) << 2; 49 | case step_b: 50 | do { 51 | if (codechar == code_in+length_in) 52 | { 53 | state_in->step = step_b; 54 | state_in->plainchar = *plainchar; 55 | return plainchar - plaintext_out; 56 | } 57 | fragment = (char)base64_decode_value(*codechar++); 58 | } while (fragment < 0); 59 | *plainchar++ |= (fragment & 0x030) >> 4; 60 | *plainchar = (fragment & 0x00f) << 4; 61 | case step_c: 62 | do { 63 | if (codechar == code_in+length_in) 64 | { 65 | state_in->step = step_c; 66 | state_in->plainchar = *plainchar; 67 | return plainchar - plaintext_out; 68 | } 69 | fragment = (char)base64_decode_value(*codechar++); 70 | } while (fragment < 0); 71 | *plainchar++ |= (fragment & 0x03c) >> 2; 72 | *plainchar = (fragment & 0x003) << 6; 73 | case step_d: 74 | do { 75 | if (codechar == code_in+length_in) 76 | { 77 | state_in->step = step_d; 78 | state_in->plainchar = *plainchar; 79 | return plainchar - plaintext_out; 80 | } 81 | fragment = (char)base64_decode_value(*codechar++); 82 | } while (fragment < 0); 83 | *plainchar++ |= (fragment & 0x03f); 84 | } 85 | } 86 | /* control should not reach here */ 87 | return plainchar - plaintext_out; 88 | } 89 | -------------------------------------------------------------------------------- /b64/cencode.c: -------------------------------------------------------------------------------- 1 | /* 2 | cencoder.c - c source to a base64 encoding algorithm implementation 3 | 4 | This is part of the libb64 project, and has been placed in the public domain. 5 | For details, see http://sourceforge.net/projects/libb64 6 | */ 7 | 8 | //#include 9 | #include 10 | 11 | const int CHARS_PER_LINE = 72; 12 | 13 | void base64_init_encodestate(base64_encodestate* state_in) 14 | { 15 | state_in->step = step_A; 16 | state_in->result = 0; 17 | state_in->stepcount = 0; 18 | } 19 | 20 | char base64_encode_value(char value_in) 21 | { 22 | static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 23 | if (value_in > 63) return '='; 24 | return encoding[(int)value_in]; 25 | } 26 | 27 | int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) 28 | { 29 | const char* plainchar = plaintext_in; 30 | const char* const plaintextend = plaintext_in + length_in; 31 | char* codechar = code_out; 32 | char result; 33 | char fragment; 34 | 35 | result = state_in->result; 36 | 37 | switch (state_in->step) 38 | { 39 | while (1) 40 | { 41 | case step_A: 42 | if (plainchar == plaintextend) 43 | { 44 | state_in->result = result; 45 | state_in->step = step_A; 46 | return codechar - code_out; 47 | } 48 | fragment = *plainchar++; 49 | result = (fragment & 0x0fc) >> 2; 50 | *codechar++ = base64_encode_value(result); 51 | result = (fragment & 0x003) << 4; 52 | case step_B: 53 | if (plainchar == plaintextend) 54 | { 55 | state_in->result = result; 56 | state_in->step = step_B; 57 | return codechar - code_out; 58 | } 59 | fragment = *plainchar++; 60 | result |= (fragment & 0x0f0) >> 4; 61 | *codechar++ = base64_encode_value(result); 62 | result = (fragment & 0x00f) << 2; 63 | case step_C: 64 | if (plainchar == plaintextend) 65 | { 66 | state_in->result = result; 67 | state_in->step = step_C; 68 | return codechar - code_out; 69 | } 70 | fragment = *plainchar++; 71 | result |= (fragment & 0x0c0) >> 6; 72 | *codechar++ = base64_encode_value(result); 73 | result = (fragment & 0x03f) >> 0; 74 | *codechar++ = base64_encode_value(result); 75 | 76 | ++(state_in->stepcount); 77 | if (state_in->stepcount == CHARS_PER_LINE/4) 78 | { 79 | *codechar++ = '\n'; 80 | state_in->stepcount = 0; 81 | } 82 | } 83 | } 84 | /* control should not reach here */ 85 | return codechar - code_out; 86 | } 87 | 88 | int base64_encode_blockend(char* code_out, base64_encodestate* state_in) 89 | { 90 | char* codechar = code_out; 91 | 92 | switch (state_in->step) 93 | { 94 | case step_B: 95 | *codechar++ = base64_encode_value(state_in->result); 96 | *codechar++ = '='; 97 | *codechar++ = '='; 98 | break; 99 | case step_C: 100 | *codechar++ = base64_encode_value(state_in->result); 101 | *codechar++ = '='; 102 | break; 103 | case step_A: 104 | break; 105 | } 106 | *codechar++ = '\n'; 107 | 108 | return codechar - code_out; 109 | } 110 | -------------------------------------------------------------------------------- /ngx_http_auth_yubikey_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011, Alexander Verhaar 3 | * Copyright (C) 2009-2011, Igor Sysoev 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | * 26 | */ 27 | 28 | 29 | /* 30 | * Many part of this module are a sameless copy of the basic athentication 31 | * module of Igor (better good stolen than badly coded ;-) ). 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "b64/cencode.h" 41 | #include "b64/cdecode.h" 42 | 43 | #define NGX_HTTP_AUTH_BUF_SIZE 8192 44 | #define NGX_CACHED_CRED_SIZE 100 /* Authentication slots */ 45 | #define NGX_YUBIKEY_TTL 86400 /* Cached credentials ttl */ 46 | 47 | /* Cached credentials structure */ 48 | typedef struct { 49 | ngx_int_t ttl; 50 | u_char md5[MD5_DIGEST_LENGTH]; 51 | } ngx_cached_cred_t; 52 | 53 | typedef struct { 54 | ngx_str_t realm; 55 | ngx_str_t client_id; 56 | ngx_str_t secret_key; 57 | ngx_int_t ttl; 58 | ngx_http_complex_value_t user_file; 59 | ngx_str_t wsapi_url; 60 | 61 | ngx_uint_t count; 62 | ngx_cached_cred_t cached_cred[NGX_CACHED_CRED_SIZE]; 63 | } ngx_http_auth_yubikey_loc_conf_t; 64 | 65 | 66 | static ngx_int_t ngx_http_auth_yubikey_handler(ngx_http_request_t *r); 67 | static ngx_int_t ngx_http_auth_yubikey_otp_handler(ngx_http_request_t *r, 68 | void *config); 69 | static ngx_int_t ngx_http_auth_yubikey_set_realm(ngx_http_request_t *r, 70 | ngx_str_t *realm); 71 | static void *ngx_http_auth_yubikey_create_loc_conf(ngx_conf_t *cf); 72 | static char *ngx_http_auth_yubikey_merge_loc_conf(ngx_conf_t *cf, 73 | void *parent, void *child); 74 | static ngx_int_t ngx_http_auth_yubikey_init(ngx_conf_t *cf); 75 | static char *ngx_http_auth_yubikey(ngx_conf_t *cf, void *post, void *data); 76 | static char *ngx_http_auth_yubikey_user_file(ngx_conf_t *cf, ngx_command_t *cmd, 77 | void *conf); 78 | static void ngx_http_auth_yubikey_close(ngx_file_t *file); 79 | 80 | static ngx_conf_post_handler_pt ngx_http_auth_yubikey_p = ngx_http_auth_yubikey; 81 | 82 | static ngx_command_t ngx_http_auth_yubikey_commands[] = { 83 | 84 | { ngx_string("auth_yubikey"), 85 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF 86 | |NGX_CONF_TAKE1, 87 | ngx_conf_set_str_slot, 88 | NGX_HTTP_LOC_CONF_OFFSET, 89 | offsetof(ngx_http_auth_yubikey_loc_conf_t, realm), 90 | &ngx_http_auth_yubikey_p }, 91 | 92 | { ngx_string("auth_yubikey_client_id"), 93 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF 94 | |NGX_CONF_TAKE1, 95 | ngx_conf_set_str_slot, 96 | NGX_HTTP_LOC_CONF_OFFSET, 97 | offsetof(ngx_http_auth_yubikey_loc_conf_t, client_id), 98 | NULL }, 99 | 100 | { ngx_string("auth_yubikey_secret_key"), 101 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF 102 | |NGX_CONF_TAKE1, 103 | ngx_conf_set_str_slot, 104 | NGX_HTTP_LOC_CONF_OFFSET, 105 | offsetof(ngx_http_auth_yubikey_loc_conf_t, secret_key), 106 | NULL }, 107 | 108 | { ngx_string("auth_yubikey_ttl"), 109 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF 110 | |NGX_CONF_TAKE1, 111 | ngx_conf_set_num_slot, 112 | NGX_HTTP_LOC_CONF_OFFSET, 113 | offsetof(ngx_http_auth_yubikey_loc_conf_t, ttl), 114 | NULL }, 115 | 116 | { ngx_string("auth_yubikey_file"), 117 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF 118 | |NGX_CONF_TAKE1, 119 | ngx_http_auth_yubikey_user_file, 120 | NGX_HTTP_LOC_CONF_OFFSET, 121 | offsetof(ngx_http_auth_yubikey_loc_conf_t, user_file), 122 | NULL }, 123 | 124 | { ngx_string("auth_yubikey_api_url"), 125 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF 126 | |NGX_CONF_TAKE1, 127 | ngx_conf_set_str_slot, 128 | NGX_HTTP_LOC_CONF_OFFSET, 129 | offsetof(ngx_http_auth_yubikey_loc_conf_t, wsapi_url), 130 | NULL }, 131 | 132 | ngx_null_command 133 | }; 134 | 135 | 136 | static ngx_http_module_t ngx_http_auth_yubikey_module_ctx = { 137 | NULL, /* preconfiguration */ 138 | ngx_http_auth_yubikey_init, /* postconfiguration */ 139 | 140 | NULL, /* create main configuration */ 141 | NULL, /* init main configuration */ 142 | 143 | NULL, /* create server configuration */ 144 | NULL, /* merge server configuration */ 145 | 146 | ngx_http_auth_yubikey_create_loc_conf, /* create location configuration */ 147 | ngx_http_auth_yubikey_merge_loc_conf /* merge location configuration */ 148 | }; 149 | 150 | 151 | ngx_module_t ngx_http_auth_yubikey_module = { 152 | NGX_MODULE_V1, 153 | &ngx_http_auth_yubikey_module_ctx, /* module context */ 154 | ngx_http_auth_yubikey_commands, /* module directives */ 155 | NGX_HTTP_MODULE, /* module type */ 156 | NULL, /* init master */ 157 | NULL, /* init module */ 158 | NULL, /* init process */ 159 | NULL, /* init thread */ 160 | NULL, /* exit thread */ 161 | NULL, /* exit process */ 162 | NULL, /* exit master */ 163 | NGX_MODULE_V1_PADDING 164 | }; 165 | 166 | 167 | static ngx_int_t 168 | ngx_http_auth_yubikey_handler(ngx_http_request_t *r) 169 | { 170 | ngx_int_t rc; 171 | ngx_http_auth_yubikey_loc_conf_t *alcf; 172 | 173 | alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_yubikey_module); 174 | 175 | if (alcf->realm.len == 0 || alcf->client_id.len == 0 || 176 | alcf->secret_key.len == 0 || alcf->wsapi_url.len == 0) { 177 | return NGX_DECLINED; 178 | } 179 | 180 | rc = ngx_http_auth_basic_user(r); 181 | 182 | if (rc == NGX_DECLINED) { 183 | return ngx_http_auth_yubikey_set_realm(r, &alcf->realm); 184 | } 185 | 186 | if (rc == NGX_ERROR) { 187 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 188 | } 189 | 190 | return ngx_http_auth_yubikey_otp_handler(r, alcf); 191 | } 192 | 193 | 194 | static ngx_int_t 195 | ngx_http_auth_yubikey_otp_handler(ngx_http_request_t *r, void *conf) 196 | { 197 | size_t len; 198 | ssize_t n; 199 | ngx_fd_t fd; 200 | ngx_file_t file; 201 | ngx_int_t rc, declen=0, oldest; 202 | ngx_err_t err; 203 | ngx_uint_t level, i, j, cslot; 204 | ngx_md5_t md5; 205 | ngx_str_t user_file, user; 206 | u_char key[22], *p, *ykey, md5_buf[MD5_DIGEST_LENGTH]; 207 | u_char buf[NGX_HTTP_AUTH_BUF_SIZE]; 208 | u_char tmpbuf[NGX_HTTP_AUTH_BUF_SIZE]; 209 | base64_decodestate state; 210 | ykclient_t *ykc; 211 | 212 | ngx_http_auth_yubikey_loc_conf_t *alcf = conf; 213 | 214 | ngx_memset(buf, 0, sizeof(buf)); 215 | ngx_memset(md5_buf, 0, sizeof(md5_buf)); 216 | 217 | for(len=0; len < r->headers_in.user.len; len++) 218 | if ( r->headers_in.user.data[len] == ':' ) break; 219 | 220 | user.data = ngx_palloc(r->pool, len+1); 221 | user.len = len; 222 | 223 | p = ngx_cpymem(user.data, r->headers_in.user.data, len); 224 | *p = '\0'; 225 | 226 | if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { 227 | ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, 228 | "User: \"%s\" Password: \"%s\"", user.data, r->headers_in.passwd.data); 229 | } 230 | 231 | /* Read the user file if there is entry */ 232 | if (alcf->user_file.value.len > 0) { 233 | if (ngx_http_complex_value(r, &alcf->user_file, &user_file) != NGX_OK) { 234 | return NGX_ERROR; 235 | } 236 | 237 | fd = ngx_open_file(user_file.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); 238 | 239 | if (fd == NGX_INVALID_FILE) { 240 | err = ngx_errno; 241 | 242 | if (err == NGX_ENOENT) { 243 | level = NGX_LOG_ERR; 244 | rc = NGX_HTTP_FORBIDDEN; 245 | } else { 246 | level = NGX_LOG_CRIT; 247 | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; 248 | } 249 | 250 | ngx_log_error(level, r->connection->log, err, 251 | ngx_open_file_n " \"%s\" failed", user_file.data); 252 | 253 | return rc; 254 | } 255 | file.fd = fd; 256 | file.name = user_file; 257 | file.log = r->connection->log; 258 | 259 | n = ngx_read_file(&file, buf, NGX_HTTP_AUTH_BUF_SIZE, 0); 260 | if (n == NGX_ERROR) { 261 | ngx_http_auth_yubikey_close(&file); 262 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 263 | } 264 | 265 | ngx_http_auth_yubikey_close(&file); 266 | } 267 | 268 | /* Check if the credentials are already cached */ 269 | ngx_memset(&md5, 0, sizeof(md5)); 270 | ngx_md5_init(&md5); 271 | ngx_md5_update(&md5, r->headers_in.user.data, r->headers_in.user.len+46); 272 | ngx_md5_final(md5_buf, &md5); 273 | 274 | for(i=0; i < alcf->count; i++) { 275 | if ( alcf->cached_cred[i].md5 ) { 276 | if ( ngx_memcmp(alcf->cached_cred[i].md5, md5_buf, MD5_DIGEST_LENGTH) == 0 ) { 277 | /* User is already cached, check ttl */ 278 | /* timeout with time now */ 279 | if (alcf->cached_cred[i].ttl < time(NULL)) { 280 | if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { 281 | ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, 282 | "Found user %s. Cache (ttl %ud) expired", user.data, alcf->cached_cred[i].ttl); 283 | } 284 | return NGX_HTTP_UNAUTHORIZED; 285 | } else 286 | if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { 287 | ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, 288 | "Found user %s. Cache (ttl %ud) valid", user.data, alcf->cached_cred[i].ttl); 289 | } 290 | return NGX_OK; 291 | } 292 | } 293 | } 294 | 295 | /* User not found in cache, let's make an entry in the cache */ 296 | j = alcf->count; 297 | if ( alcf->count >= NGX_CACHED_CRED_SIZE ) { 298 | /* All entries in the cache are full, search oldest one and clear it */ 299 | oldest=-1; 300 | for(i=0; i < alcf->count; i++) { 301 | if (alcf->cached_cred[i].ttl < oldest ) { 302 | oldest = alcf->cached_cred[i].ttl; 303 | j = i; 304 | } 305 | } 306 | } 307 | alcf->cached_cred[j].ttl = time(NULL) + alcf->ttl; 308 | /*alcf->cached_cred[j].md5.data = ngx_palloc(r->pool, 17); 309 | alcf->cached_cred[j].md5.len = 17;*/ 310 | p = ngx_cpymem(alcf->cached_cred[j].md5, md5_buf, MD5_DIGEST_LENGTH); 311 | *p = '\0'; 312 | 313 | ngx_memset(tmpbuf, 0, sizeof(tmpbuf)); 314 | for(i=0; icached_cred[j].md5[i]); 316 | } 317 | 318 | if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { 319 | ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, 320 | "alcf : default ttl -> %ud ttl->%ud md5->%s %s", 321 | alcf->ttl,alcf->cached_cred[j].ttl, tmpbuf, r->headers_in.user.data); 322 | } 323 | 324 | cslot = j; 325 | alcf->count = ++j; 326 | 327 | /* Initialize base64 decoder */ 328 | base64_init_decodestate(&state); 329 | declen=base64_decode_block((char*)alcf->secret_key.data, strlen((char *)alcf->secret_key.data), (char*)key, &state); 330 | 331 | /* Check if auth_yubikey_client_id is set to a proper value */ 332 | if (ngx_atoi(alcf->client_id.data, alcf->client_id.len) <= 0) 333 | { 334 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 335 | "error: client identity must be a non-zero integer"); 336 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 337 | } 338 | 339 | /* Check if the password send to the server has a valid OTP length */ 340 | if (strlen((char*)r->headers_in.passwd.data) < 32) 341 | { 342 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 343 | "error: ModHex encoded token must be at least 32 characters."); 344 | return ngx_http_auth_yubikey_set_realm(r, &alcf->realm); 345 | } 346 | 347 | rc = ykclient_init (&ykc); 348 | if ( rc != YKCLIENT_OK) { 349 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 350 | "ykclient error: %s", 351 | ykclient_strerror(rc)); 352 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 353 | } 354 | 355 | /* Set WSAPI URL */ 356 | if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { 357 | ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, 358 | "WSApi url \"%s\"", alcf->wsapi_url.data); 359 | } 360 | ykclient_set_url_template(ykc, (char*)alcf->wsapi_url.data); 361 | ykclient_set_client (ykc, atoi((char*)alcf->client_id.data), declen, (char*)key); 362 | 363 | /* Yubikey exist on the server of Yubico, now check the key against the first 12 chars */ 364 | if (alcf->user_file.value.len > 0) { 365 | if ( strstr((char*)buf, (char*)user.data) ) { 366 | /* User found in file, check against key */ 367 | p = (u_char*)strstr((char*)buf, (char*)user.data); 368 | ykey = (u_char*)strstr((char*)p, ":")+1; 369 | *strstr((char*)ykey, "\n") = '\0'; 370 | 371 | if (ngx_strncmp(ykey, r->headers_in.passwd.data, 12) == 0) { 372 | rc = ykclient_request (ykc, (char*)r->headers_in.passwd.data); 373 | 374 | if (rc!=YKCLIENT_OK) { 375 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 376 | "ykclient error: %s", 377 | ykclient_strerror(rc)); 378 | ykclient_done (&ykc); 379 | ngx_memzero(&alcf->cached_cred[cslot], sizeof(ngx_cached_cred_t)); 380 | //return NGX_HTTP_FORBIDDEN; 381 | return ngx_http_auth_yubikey_set_realm(r, &alcf->realm); 382 | } 383 | } else { 384 | if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { 385 | r->headers_in.passwd.data[12] = '\0'; 386 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 387 | "User \"%s\" send \"%s\" is not using key \"%s\"", 388 | user.data, r->headers_in.passwd.data, ykey); 389 | } 390 | return NGX_HTTP_UNAUTHORIZED; 391 | } 392 | } else { 393 | /* User not found in file */ 394 | ykclient_done (&ykc); 395 | /* return ngx_http_auth_yubikey_set_realm(r, &alcf->realm); */ 396 | return NGX_HTTP_FORBIDDEN; 397 | } 398 | } 399 | 400 | ykclient_done (&ykc); 401 | 402 | /* Yes, we have a valid user and a valid OTP password */ 403 | return NGX_OK; 404 | } 405 | 406 | static ngx_int_t 407 | ngx_http_auth_yubikey_set_realm(ngx_http_request_t *r, ngx_str_t *realm) 408 | { 409 | r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); 410 | if (r->headers_out.www_authenticate == NULL) { 411 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 412 | } 413 | 414 | r->headers_out.www_authenticate->hash = 2; 415 | ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate"); 416 | r->headers_out.www_authenticate->value = *realm; 417 | 418 | return NGX_HTTP_UNAUTHORIZED; 419 | } 420 | 421 | static void * 422 | ngx_http_auth_yubikey_create_loc_conf(ngx_conf_t *cf) 423 | { 424 | ngx_http_auth_yubikey_loc_conf_t *conf; 425 | 426 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_yubikey_loc_conf_t)); 427 | if (conf == NULL) { 428 | return NULL; 429 | } 430 | conf->ttl = NGX_CONF_UNSET; 431 | 432 | return conf; 433 | } 434 | 435 | 436 | static char * 437 | ngx_http_auth_yubikey_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 438 | { 439 | ngx_http_auth_yubikey_loc_conf_t *prev = parent; 440 | ngx_http_auth_yubikey_loc_conf_t *conf = child; 441 | 442 | if (conf->realm.data == NULL) { 443 | conf->realm = prev->realm; 444 | } 445 | 446 | ngx_conf_merge_value(conf->ttl, prev->ttl, NGX_YUBIKEY_TTL); 447 | 448 | return NGX_CONF_OK; 449 | } 450 | 451 | 452 | static ngx_int_t 453 | ngx_http_auth_yubikey_init(ngx_conf_t *cf) 454 | { 455 | ngx_http_handler_pt *h; 456 | ngx_http_core_main_conf_t *cmcf; 457 | 458 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 459 | 460 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); 461 | if (h == NULL) { 462 | return NGX_ERROR; 463 | } 464 | 465 | *h = ngx_http_auth_yubikey_handler; 466 | 467 | return NGX_OK; 468 | } 469 | 470 | 471 | static char * 472 | ngx_http_auth_yubikey(ngx_conf_t *cf, void *post, void *data) 473 | { 474 | ngx_str_t *realm = data; 475 | 476 | size_t len; 477 | u_char *basic, *p; 478 | 479 | if (ngx_strcmp(realm->data, "off") == 0) { 480 | ngx_str_set(realm, ""); 481 | return NGX_CONF_OK; 482 | } 483 | 484 | len = sizeof("Basic realm=\"") - 1 + realm->len + 1; 485 | 486 | basic = ngx_pnalloc(cf->pool, len); 487 | if (basic == NULL) { 488 | return NGX_CONF_ERROR; 489 | } 490 | 491 | p = ngx_cpymem(basic, "Basic realm=\"", sizeof("Basic realm=\"") - 1); 492 | p = ngx_cpymem(p, realm->data, realm->len); 493 | *p = '"'; 494 | 495 | realm->len = len; 496 | realm->data = basic; 497 | 498 | return NGX_CONF_OK; 499 | } 500 | 501 | static char * 502 | ngx_http_auth_yubikey_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 503 | { 504 | ngx_http_auth_yubikey_loc_conf_t *alcf = conf; 505 | 506 | ngx_str_t *value; 507 | ngx_http_compile_complex_value_t ccv; 508 | 509 | if (alcf->user_file.value.len) { 510 | return "is duplicate"; 511 | } 512 | 513 | value = cf->args->elts; 514 | 515 | ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 516 | 517 | ccv.cf = cf; 518 | ccv.value = &value[1]; 519 | ccv.complex_value = &alcf->user_file; 520 | ccv.zero = 1; 521 | ccv.conf_prefix = 1; 522 | 523 | if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 524 | return NGX_CONF_ERROR; 525 | } 526 | 527 | return NGX_CONF_OK; 528 | } 529 | 530 | static void 531 | ngx_http_auth_yubikey_close(ngx_file_t *file) 532 | { 533 | if (ngx_close_file(file->fd) == NGX_FILE_ERROR) { 534 | ngx_log_error(NGX_LOG_ALERT, file->log, ngx_errno, 535 | ngx_close_file_n " \"%s\" failed", file->name.data); 536 | } 537 | } 538 | 539 | 540 | /* vim: set ts=4 sw=8 tw=0 noet :*/ 541 | --------------------------------------------------------------------------------