├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── config ├── example ├── .gitignore └── nginx.conf ├── ngx_http_acme_lib.h ├── ngx_http_acme_module.c └── ngx_http_acme_module.h /.gitignore: -------------------------------------------------------------------------------- 1 | # NginX sources 2 | nginx-*/ 3 | 4 | # NginX install and runtime directory 5 | run/ 6 | 7 | # Vim swap files 8 | *.swp 9 | 10 | # Eclipse CDT 11 | .cproject 12 | .project 13 | .settings/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR := $(strip $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))) 2 | 3 | SRC_VERSION := nginx-1.11.0 4 | SRC_LINK := "http://nginx.org/download/$(SRC_VERSION).tar.gz" 5 | 6 | SRC_PATH := $(ROOT_DIR)/$(SRC_VERSION) 7 | RUN_PATH := $(ROOT_DIR)/run 8 | 9 | MODULE_SRC := $(ROOT_DIR)/ngx_http_acme_module.c $(ROOT_DIR)/ngx_http_acme_module.h $(ROOT_DIR)/ngx_http_acme_lib.h 10 | MODULE_CFG := $(ROOT_DIR)/config 11 | 12 | EXAMPLE_DIR := $(ROOT_DIR)/example 13 | EXAMPLE_CONFIG := $(EXAMPLE_DIR)/nginx.conf 14 | 15 | RUN_CONF_DIR := $(RUN_PATH)/conf 16 | RUN_CONFIG := $(RUN_CONF_DIR)/nginx.conf 17 | RUN_BIN := $(RUN_PATH)/sbin/nginx 18 | PID_FILE := $(RUN_PATH)/logs/nginx.pid 19 | 20 | SRC_MKFILE := $(SRC_PATH)/Makefile 21 | SRC_BIN := $(SRC_PATH)/objs/nginx 22 | 23 | CONFIGURE_OPTS := --prefix="$(RUN_PATH)" --with-http_ssl_module --add-module="$(ROOT_DIR)" 24 | 25 | # For debug output 26 | CONFIGURE_OPTS := $(CONFIGURE_OPTS) --with-debug 27 | 28 | # Dev variables 29 | EXAMPLE_CERT := $(EXAMPLE_DIR)/cert.pem 30 | EXAMPLE_CERT_KEY := $(EXAMPLE_DIR)/cert-key.pem 31 | ACME_DIR := $(RUN_CONF_DIR)/acme 32 | ACME_SERVER_NAME := ledev2.kbauer.at 33 | ACME_CERT_DIR := $(ACME_DIR)/live/$(ACME_SERVER_NAME) 34 | ACME_ACC_DIR := $(ACME_DIR)/accounts 35 | 36 | .PHONY: default source configure build install run \ 37 | clean kill reinstall clean-install clean-all \ 38 | run reconfigure clean-build clean-source 39 | 40 | # 41 | # Phony targets 42 | # 43 | 44 | default: build 45 | 46 | all: source install 47 | 48 | source: clean-source 49 | mkdir -p "$(SRC_PATH)" 50 | curl $(SRC_LINK) | tar xz 51 | 52 | configure: $(SRC_MKFILE) 53 | 54 | build: $(SRC_BIN) 55 | 56 | install: $(RUN_BIN) 57 | 58 | run: 59 | @test -f "$(RUN_BIN)" || (echo "You have to run 'make install' first"; exit 2) 60 | @test ! -f "$(PID_FILE)" || (echo "Error: NginX is already running"; exit 2) 61 | "$(RUN_BIN)" 62 | 63 | kill: 64 | test -f "$(PID_FILE)" && kill `cat "$(PID_FILE)"` || echo "Warning: NginX isn't running" 65 | 66 | clean-install: 67 | rm -rf "$(RUN_PATH)" 68 | 69 | reinstall: clean-install install 70 | 71 | clean-build: 72 | $(MAKE) -C "$(SRC_PATH)" clean 2>/dev/null || true 73 | 74 | reconfigure: clean-build configure 75 | 76 | rebuild: clean-build build 77 | 78 | clean: clean-install clean-build 79 | 80 | clean-source: 81 | rm -rf $(SRC_PATH) 82 | 83 | clean-all: clean clean-source 84 | 85 | # 86 | # File targets 87 | # 88 | 89 | $(SRC_MKFILE): $(MODULE_CFG) 90 | @test -d $(SRC_PATH) || (echo "You have to run 'make source' first to download the Nginx source code"; exit 2) 91 | cd "$(SRC_PATH)"; ./configure $(CONFIGURE_OPTS) 92 | 93 | $(SRC_BIN): $(SRC_MKFILE) $(MODULE_SRC) 94 | $(MAKE) -C "$(SRC_PATH)" 95 | 96 | $(RUN_BIN): $(SRC_BIN) 97 | $(MAKE) -C "$(SRC_PATH)" install 98 | # Install example files 99 | cp "$(EXAMPLE_CONFIG)" "$(RUN_CONFIG)" 100 | mkdir -p "$(ACME_CERT_DIR)" 101 | mkdir -p "$(ACME_ACC_DIR)" 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Encrypt module for Nginx 2 | 3 | ## Introduction 4 | 5 | [TODO] 6 | 7 | ## Dependencies 8 | 9 | ### Makefile dependencies 10 | 11 | To build this module you need all the tools installed which you would also need to 12 | build the Nginx server alone. In addition you need `curl` if you download the source code 13 | of Nginx using `make source` (recommended). 14 | 15 | ### Libraries 16 | 17 | The module uses libcurl for making the HTTP calls to the ACME server and libjansson for 18 | parsing and creating the JSON strings according to the ACME protocol. 19 | 20 | ## Installation 21 | 22 | 1. Download the NginX source code using: 23 | 24 | make source 25 | 26 | 2. Configure, build and install the server with the module: 27 | 28 | make install 29 | 30 | With this step the server is compiled and installed in the `./run` directory. 31 | Don't worry, nothing is installed on your system outside this directory. 32 | 33 | 3. Run the server: 34 | 35 | make run 36 | 37 | You can later stop the server with: 38 | 39 | make kill 40 | 41 | ## Configuration 42 | 43 | * The build process and the directories can be configured in the first few lines of 44 | the [Makefile](Makefile). 45 | 46 | * The server will run with a copy of the configuration file [example/nginx.conf](example/nginx.conf) 47 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_acme_module 2 | 3 | if test -n "$ngx_module_link"; then 4 | ngx_module_type=HTTP 5 | ngx_module_name=ngx_http_acme_module 6 | ngx_module_srcs="$ngx_addon_dir/ngx_http_acme_module.c" 7 | ngx_module_libs="-lcurl -ljansson" 8 | 9 | . auto/module 10 | else 11 | HTTP_MODULES="$HTTP_MODULES ngx_http_acme_module" 12 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_acme_module.c" 13 | CORE_LIBS="$CORE_LIBS -lcurl -ljansson" 14 | fi 15 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | cert.pem 2 | cert-key.pem -------------------------------------------------------------------------------- /example/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | #user nobody; 3 | worker_processes 1; 4 | daemon off; 5 | master_process off; 6 | error_log stderr notice; 7 | 8 | #error_log logs/error.log; 9 | #error_log logs/error.log notice; 10 | #error_log logs/error.log info; 11 | 12 | #pid logs/nginx.pid; 13 | 14 | 15 | events { 16 | worker_connections 1024; 17 | } 18 | 19 | 20 | http { 21 | include mime.types; 22 | default_type application/octet-stream; 23 | 24 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 25 | # '$status $body_bytes_sent "$http_referer" ' 26 | # '"$http_user_agent" "$http_x_forwarded_for"'; 27 | 28 | #access_log logs/access.log main; 29 | 30 | sendfile on; 31 | #tcp_nopush on; 32 | 33 | #keepalive_timeout 0; 34 | keepalive_timeout 65; 35 | 36 | #gzip on; 37 | 38 | server { 39 | listen 8080; 40 | server_name localhost; 41 | 42 | #charset koi8-r; 43 | 44 | #access_log logs/host.access.log main; 45 | 46 | location / { 47 | root html; 48 | index index.html index.htm; 49 | } 50 | 51 | #error_page 404 /404.html; 52 | 53 | # redirect server error pages to the static page /50x.html 54 | # 55 | error_page 500 502 503 504 /50x.html; 56 | location = /50x.html { 57 | root html; 58 | } 59 | 60 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 61 | # 62 | #location ~ \.php$ { 63 | # proxy_pass http://127.0.0.1; 64 | #} 65 | 66 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 67 | # 68 | #location ~ \.php$ { 69 | # root html; 70 | # fastcgi_pass 127.0.0.1:9000; 71 | # fastcgi_index index.php; 72 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 73 | # include fastcgi_params; 74 | #} 75 | 76 | # deny access to .htaccess files, if Apache's document root 77 | # concurs with nginx's one 78 | # 79 | #location ~ /\.ht { 80 | # deny all; 81 | #} 82 | } 83 | 84 | 85 | # another virtual host using mix of IP-, name-, and port-based configuration 86 | # 87 | #server { 88 | # listen 8000; 89 | # listen somename:8080; 90 | # server_name somename alias another.alias; 91 | 92 | # location / { 93 | # root html; 94 | # index index.html index.htm; 95 | # } 96 | #} 97 | 98 | 99 | # HTTPS server 100 | 101 | server { 102 | listen 4430 ssl; 103 | server_name ledev2.kbauer.at; 104 | 105 | #ssl_certificate cert.pem; 106 | #ssl_certificate_key cert-key.pem; 107 | 108 | acme; 109 | 110 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 111 | ssl_session_cache builtin:1000 shared:SSL:10m; 112 | ssl_session_timeout 5m; 113 | ssl_stapling on; 114 | 115 | ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !RC4 !EXP !PSK !SRP !CAMELLIA !SEED'; 116 | ssl_prefer_server_ciphers on; 117 | 118 | location / { 119 | root html; 120 | index index.html index.htm; 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /ngx_http_acme_lib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ngx_http_acme_lib.h 3 | * 4 | * Created on: Feb 26, 2016 5 | * Author: klaus 6 | */ 7 | 8 | #ifndef NGX_HTTP_ACME_LIB_H_ 9 | #define NGX_HTTP_ACME_LIB_H_ 10 | 11 | #define ACME_REPLAY_NONCE_HEADER "Replay-Nonce" 12 | #define ACME_TERMS_LINK_HEADER "terms-of-service" 13 | 14 | #endif /* NGX_HTTP_ACME_LIB_H_ */ 15 | -------------------------------------------------------------------------------- /ngx_http_acme_module.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_acme_module.c 3 | * @author Klaus Krapfenbauer 4 | * @date Fri Oct 30 14:57:23 UTC 2015 5 | * 6 | * @brief An ACME module for Nginx. 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include "ngx_http_acme_lib.h" 20 | #include "ngx_http_acme_module.h" 21 | 22 | /* 23 | * String constants 24 | */ 25 | #define ACME_REPLAY_NONCE_PREFIX_STRING ACME_REPLAY_NONCE_HEADER ": " 26 | #define ACME_TOS_PREFIX_STRING "Link: <" 27 | #define ACME_TOS_SUFFIX_STRING ">;rel=\"" ACME_TERMS_LINK_HEADER "\"" 28 | #define ACME_LOCATION_HEADER_PREFIX_STRING "Location: " 29 | 30 | /* 31 | * Temporary dev macros 32 | */ 33 | /* Nginx config directory; This should later be gathered from nginx */ 34 | #define ACME_DEV_CONF_DIR "conf" 35 | /* This should later be replaced with the value of the server_name directive of the core module */ 36 | #define ACME_DEV_SERVER_NAME "ledev2.kbauer.at" 37 | 38 | #define ACME_DEV_CERT_PATH ACME_DEV_CONF_DIR "/" ACME_DIR "/" ACME_LIVE_DIR "/" ACME_DEV_SERVER_NAME "/" ACME_CERT 39 | #define ACME_DEV_KEY_PATH ACME_DEV_CONF_DIR "/" ACME_DIR "/" ACME_LIVE_DIR "/" ACME_DEV_SERVER_NAME "/" ACME_CERT_KEY 40 | #define ACME_DEV_EXAMPLE_DIR "../example" 41 | #define ACME_DEV_FROM_CERT_PATH ACME_DEV_EXAMPLE_DIR "/cert.pem" 42 | #define ACME_DEV_FROM_KEY_PATH ACME_DEV_EXAMPLE_DIR "/cert-key.pem" 43 | 44 | /* 45 | * Function macros 46 | */ 47 | #define println_debug2(str, ngx_str, stream) \ 48 | fwrite("ACME DEBUG: ", sizeof(char), strlen("ACME DEBUG: "), stream); \ 49 | fwrite(str, sizeof(char), strlen(str), stream); \ 50 | fwrite((ngx_str)->data, sizeof(char), (ngx_str)->len, stream); \ 51 | fwrite("\n", sizeof(char), 1, stream); \ 52 | fflush(stream) 53 | #define println_debug(str, ngx_str) \ 54 | println_debug2(str, ngx_str, stdout) 55 | 56 | static char *ngx_http_acme(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 57 | static char *ngx_http_acme_main(ngx_conf_t *cf, void *conf); 58 | static char *ngx_http_acme_fetch_dir(ngx_conf_t *cf, void *conf, ngx_str_t *replay_nonce); 59 | static char *ngx_http_acme_new_reg(ngx_conf_t *cf, void *conf, ngx_str_t *replay_nonce, RSA *key); 60 | static char *ngx_http_acme_new_auth(ngx_conf_t *cf, void *conf, ngx_str_t *replay_nonce, RSA *key); 61 | static char *ngx_http_acme_request(ngx_conf_t *cf, void *conf, char *url, ngx_http_acme_http_method_t http_method, 62 | json_t *request_json, RSA *key, ngx_str_t *replay_nonce, json_t **response_json, 63 | ngx_http_acme_slist_t **response_headers); 64 | static char *ngx_http_acme_sign_json(ngx_conf_t *cf, void *conf, json_t *payload, RSA *key, ngx_str_t replay_nonce, json_t **flattened_jws); 65 | static char *ngx_http_acme_create_jwk(ngx_conf_t *cf, void *conf, RSA *key, json_t **jwk); 66 | //static char *ngx_http_acme_create_priv_jwk(ngx_conf_t *cf, void *conf, RSA *key, json_t **jwk); 67 | static char *ngx_http_acme_read_jwk(ngx_conf_t *cf, void *conf, ngx_str_t jwk_str, RSA **key); 68 | static char *ngx_http_acme_json_request(ngx_conf_t *cf, void *conf, char *url, ngx_http_acme_http_method_t http_method, 69 | json_t *request_json, json_t **response_json, ngx_http_acme_slist_t **response_headers); 70 | static char *ngx_http_acme_plain_request(ngx_conf_t *cf, void *conf, char *url, ngx_http_acme_http_method_t http_method, 71 | ngx_str_t request_data, ngx_str_t *response_data, ngx_http_acme_slist_t **response_headers); 72 | static size_t ngx_http_acme_header_callback(char *buffer, size_t size, size_t nitems, void *userdata); 73 | static ngx_int_t ngx_http_acme_init(ngx_conf_t *cf); 74 | 75 | //static ngx_http_acme_sdict_t *ngx_http_acme_sdict_append_kv_pair(ngx_http_acme_sdict_t *sdict, ngx_str_t key, ngx_str_t value); 76 | //static void ngx_http_acme_sdict_free_all(ngx_http_acme_sdict_t *sdict); 77 | static ngx_http_acme_slist_t *ngx_http_acme_slist_append_entry(ngx_http_acme_slist_t *slist, ngx_str_t value); 78 | static void ngx_http_acme_slist_free_all(ngx_http_acme_slist_t *slist); 79 | 80 | 81 | /** 82 | * This module provided directive: acme. 83 | * 84 | */ 85 | static ngx_command_t ngx_http_acme_commands[] = { 86 | 87 | { ngx_string("acme"), /* directive */ 88 | NGX_HTTP_SRV_CONF|NGX_CONF_NOARGS, /* location context and takes 89 | no arguments*/ 90 | ngx_http_acme, /* configuration setup function */ 91 | 0, /* No offset. Only one context is supported. */ 92 | 0, /* No offset when storing the module configuration on struct. */ 93 | NULL}, 94 | 95 | ngx_null_command /* command termination */ 96 | }; 97 | 98 | /* The module context. */ 99 | static ngx_http_module_t ngx_http_acme_module_ctx = { 100 | NULL, /* preconfiguration */ 101 | ngx_http_acme_init, /* postconfiguration */ 102 | 103 | NULL, /* create main configuration */ 104 | NULL, /* init main configuration */ 105 | 106 | NULL, /* create server configuration */ 107 | NULL, /* merge server configuration */ 108 | 109 | NULL, /* create location configuration */ 110 | NULL /* merge location configuration */ 111 | }; 112 | 113 | /* Module definition. */ 114 | ngx_module_t ngx_http_acme_module = { 115 | NGX_MODULE_V1, 116 | &ngx_http_acme_module_ctx, /* module context */ 117 | ngx_http_acme_commands, /* module directives */ 118 | NGX_HTTP_MODULE, /* module type */ 119 | NULL, /* init master */ 120 | NULL, /* init module */ 121 | NULL, /* init process */ 122 | NULL, /* init thread */ 123 | NULL, /* exit thread */ 124 | NULL, /* exit process */ 125 | NULL, /* exit master */ 126 | NGX_MODULE_V1_PADDING 127 | }; 128 | 129 | 130 | /** 131 | * Configuration setup function that installs the content handler. 132 | * 133 | * @param cf 134 | * Module configuration structure pointer. 135 | * @param cmd 136 | * Module directives structure pointer. 137 | * @param conf 138 | * Module configuration structure pointer. 139 | * @return string 140 | * Status of the configuration setup. 141 | */ 142 | static char *ngx_http_acme(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 143 | { 144 | ngx_http_ssl_srv_conf_t *sscf; /* pointer to core location configuration */ 145 | int ret; 146 | 147 | // TODO (KK) Pull the different parts out as own methods for readability 148 | 149 | /* 150 | * TODO (KK) Get the config directory path (e.g. /etc/nginx) 151 | */ 152 | 153 | 154 | /* 155 | * TODO (KK) Init acme dir (mkdirs) 156 | */ 157 | 158 | 159 | /* 160 | * ACME communication - getting a certificate 161 | */ 162 | 163 | if(ngx_http_acme_main(cf, conf) != NGX_CONF_OK) { 164 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while gathering certificate from ACME server"); 165 | return NGX_CONF_ERROR; 166 | } 167 | 168 | /* 169 | * TODO (KK) Install certificate (right now it just copies an example cert) 170 | */ 171 | { 172 | ngx_copy_file_t cpyf; 173 | 174 | ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Installing certificate and key"); 175 | 176 | cpyf.size = -1; 177 | cpyf.buf_size = 0; 178 | cpyf.access = NGX_FILE_DEFAULT_ACCESS; 179 | cpyf.time = -1; 180 | cpyf.log = cf->log; 181 | 182 | /* Copy certificate */ 183 | ret = ngx_copy_file((u_char *)ACME_DEV_FROM_CERT_PATH, (u_char *)ACME_DEV_CERT_PATH, &cpyf); 184 | 185 | /* Copy private key */ 186 | if(ret == NGX_OK) { 187 | /* Only 0600 access for private key */ 188 | cpyf.access = NGX_FILE_OWNER_ACCESS; 189 | 190 | ret = ngx_copy_file((u_char *)ACME_DEV_FROM_KEY_PATH, (u_char *)ACME_DEV_KEY_PATH, &cpyf); 191 | } 192 | 193 | if(ret != NGX_OK) { 194 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Installing the certificate or private key failed"); 195 | return NGX_CONF_ERROR; 196 | } 197 | } 198 | 199 | /* 200 | * Fool the SSL module into using the ACME certificates 201 | */ 202 | /* Get SSL module configuration */ 203 | sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); 204 | 205 | // TODO (KK) Report warning when ssl configs are not set (acme w/o ssl activated in the same server context is an error) 206 | // --> Maybe ignore acme config then and issue a warning 207 | 208 | if(sscf) { 209 | // ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Found SSL certificate path: %s", sscf->certificate.data); 210 | 211 | /* Spoof SSL cert */ 212 | sscf->certificates = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t)); 213 | if (sscf->certificates == NULL) { 214 | return NGX_CONF_ERROR; 215 | } 216 | ((ngx_str_t *) sscf->certificates->elts)[0] = (ngx_str_t) ngx_string(ACME_DIR "/" ACME_LIVE_DIR "/" ACME_DEV_SERVER_NAME "/" ACME_CERT); 217 | 218 | /* Spoof SSL cert key */ 219 | sscf->certificate_keys = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t)); 220 | if (sscf->certificate_keys == NULL) { 221 | return NGX_CONF_ERROR; 222 | } 223 | ((ngx_str_t *) sscf->certificate_keys->elts)[0] = (ngx_str_t) ngx_string(ACME_DIR "/" ACME_LIVE_DIR "/" ACME_DEV_SERVER_NAME "/" ACME_CERT); 224 | } 225 | 226 | 227 | return NGX_CONF_OK; 228 | } /* ngx_http_acme */ 229 | 230 | 231 | static char *ngx_http_acme_main(ngx_conf_t *cf, void *conf) 232 | { 233 | /* 234 | * Function's logic in pseudo code: 235 | * 236 | * if (certificate file exists) { 237 | * if (cert is not expired) { 238 | * return 239 | * } 240 | * } 241 | * 242 | * if (no account key exists) { 243 | * create account key 244 | * } else { 245 | * load account key from file 246 | * } 247 | * 248 | * if (there is no registration for this account key on the ACME server) { 249 | * register 250 | * } 251 | * 252 | * if (there is no authorization for this domain on this ACME account) { 253 | * authorize and solve the challenges 254 | * } 255 | * 256 | * if (the certificate exists but is expired) { 257 | * renew cert and return 258 | * } 259 | * 260 | * get the certificate from the server and return 261 | * 262 | */ 263 | 264 | int ret; 265 | ngx_str_t replay_nonce = ngx_null_string; 266 | 267 | /* 268 | * Load key pair for ACME account 269 | */ 270 | RSA *rsa = NULL; 271 | 272 | /* TODO (KK) Extract to own method */ 273 | if(1 /* if no account key exists */) 274 | { 275 | /* 276 | * Generate new key pair 277 | */ 278 | BIGNUM *e = NULL; 279 | 280 | ngx_log_error(NGX_LOG_NOTICE, cf->log, 0, "Generate RSA key"); 281 | 282 | // TODO (KK) Change key type to EVP_PKEY everywhere for being able to handle other keys than RSA 283 | rsa = RSA_new(); 284 | ret = BN_dec2bn(&e, ACME_ACCOUNT_RSA_EXP); 285 | if(ret == 0) { 286 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 287 | "Error creating the account key. OpenSSL error 0x%xl", ERR_get_error()); 288 | return NGX_CONF_ERROR; 289 | } 290 | 291 | ret = RSA_generate_key_ex(rsa, ACME_ACCOUNT_RSA_BITS, e, NULL); 292 | if(ret == 0) { 293 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 294 | "Error creating the account key. OpenSSL error 0x%xl", ERR_get_error()); 295 | return NGX_CONF_ERROR; 296 | } 297 | 298 | // TODO (KK) Save account key in file 299 | // ngx_http_acme_create_priv_jwk(ngx_conf_t *cf, void *conf, RSA *key, json_t **jwk) 300 | 301 | BN_free(e); 302 | } else { 303 | /* 304 | * Load existing account key from file 305 | */ 306 | // TODO (KK) Read JWK file to a string (and free it afterwards) 307 | 308 | if(ngx_http_acme_read_jwk(cf, conf, (ngx_str_t)ngx_null_string, &rsa) != NGX_CONF_OK) { 309 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to load the account key from file"); 310 | return NGX_CONF_ERROR; 311 | } 312 | } 313 | 314 | 315 | /* Fetch ACME dir - just to retrieve a replay nonce */ 316 | if(ngx_http_acme_fetch_dir(cf, conf, &replay_nonce) != NGX_CONF_OK) { 317 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to make directory request"); 318 | return NGX_CONF_ERROR; 319 | } 320 | 321 | /* Register an account */ 322 | if(ngx_http_acme_new_reg(cf, conf, &replay_nonce, rsa) != NGX_CONF_OK) { 323 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to register"); 324 | return NGX_CONF_ERROR; 325 | } 326 | 327 | /* Authorize for domain */ 328 | if(ngx_http_acme_new_auth(cf, conf, &replay_nonce, rsa) != NGX_CONF_OK) { 329 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to authorize"); 330 | return NGX_CONF_ERROR; 331 | } 332 | 333 | 334 | 335 | // json_t *test_obj; 336 | // json_t *test_output; 337 | // 338 | // /* TODO (KK) Test - remove later: Send request data */ 339 | // test_obj = json_pack("{s:s}", "test", "Test string"); 340 | // if(ngx_http_acme_json_request(cf, conf, "http://www.foaas.com/operations", GET, json_null(), &test_output, NULL) != NGX_CONF_OK) { 341 | // ngx_log_error(NGX_LOG_ERR, cf->log, 0, "JSON request failed"); 342 | // return NGX_CONF_ERROR; 343 | // } 344 | // 345 | // char *output_str = json_dumps(test_output, 0); 346 | // println_debug("Returned JSON string: ", &((ngx_str_t)ngx_string_dynamic(output_str))); 347 | // 348 | // json_decref(test_obj); 349 | // json_decref(test_output); 350 | // 351 | // /* TODO (KK) Test - remove later: Sign off JSON */ 352 | // test_obj = json_pack("{s:s}", "test", "Test string"); 353 | // if(ngx_http_acme_sign_json(cf, conf, test_obj, rsa, replay_nonce, &test_output) != NGX_CONF_OK) { 354 | // ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Creating JWS failed"); 355 | // return NGX_CONF_ERROR; 356 | // } 357 | // 358 | // output_str = json_dumps(test_output, 0); 359 | // println_debug("JWS string: ", &((ngx_str_t)ngx_string_dynamic(output_str))); 360 | // 361 | // json_decref(test_obj); 362 | // json_decref(test_output); 363 | // ngx_free(output_str); 364 | 365 | 366 | RSA_free(rsa); 367 | 368 | return NGX_CONF_OK; 369 | } /* ngx_http_acme_main */ 370 | 371 | 372 | static char *ngx_http_acme_fetch_dir(ngx_conf_t *cf, void *conf, ngx_str_t *replay_nonce) 373 | { 374 | json_t *response_json; 375 | ngx_http_acme_slist_t *response_headers = NULL; 376 | 377 | /* Make JSON request */ 378 | if(ngx_http_acme_request(cf, conf, ACME_SERVER "/directory", GET, json_null(), NULL, replay_nonce, &response_json, &response_headers) != NGX_CONF_OK) { 379 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while making JSON request"); 380 | return NGX_CONF_ERROR; 381 | } 382 | 383 | ngx_http_acme_slist_free_all(response_headers); 384 | json_decref(response_json); 385 | 386 | return NGX_CONF_OK; 387 | } /* ngx_http_acme_fetch_dir */ 388 | 389 | 390 | static char *ngx_http_acme_new_reg(ngx_conf_t *cf, void *conf, ngx_str_t *replay_nonce, RSA *key) 391 | { 392 | json_t *request_json; 393 | json_t *response_json; 394 | ngx_http_acme_slist_t *response_headers = NULL; 395 | ngx_http_acme_slist_t *response_headers2 = NULL; 396 | ngx_http_acme_slist_t *header = NULL; 397 | ngx_str_t tos_url = ngx_null_string; 398 | char *reg_location = NULL; 399 | size_t reg_location_len = 0; 400 | char *tmp, *max_addr, *min_addr; 401 | 402 | /* Assemble request */ 403 | request_json = json_pack("{s:s}", "resource", "new-reg"); 404 | 405 | /* Make JSON request */ 406 | if(ngx_http_acme_request(cf, conf, ACME_SERVER "/acme/new-reg", POST, request_json, key, replay_nonce, &response_json, &response_headers) != NGX_CONF_OK) { 407 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while making JSON request"); 408 | return NGX_CONF_ERROR; 409 | } 410 | 411 | /* Process response */ 412 | 413 | /* Search for and extract the location of the registration object */ 414 | for(header = response_headers; header != NULL; header = header->next) { 415 | if(header->value_len < strlen(ACME_LOCATION_HEADER_PREFIX_STRING)) 416 | continue; 417 | 418 | if(ngx_strncmp(header->value, ACME_LOCATION_HEADER_PREFIX_STRING, strlen(ACME_LOCATION_HEADER_PREFIX_STRING)) == 0) { 419 | /* Location header found, extract it */ 420 | reg_location_len = header->value_len - strlen(ACME_LOCATION_HEADER_PREFIX_STRING) + 1 /* for terminating null character */; 421 | reg_location = ngx_alloc(reg_location_len, cf->log); 422 | ngx_memcpy(reg_location, header->value + strlen(ACME_LOCATION_HEADER_PREFIX_STRING), reg_location_len - 1); 423 | reg_location[reg_location_len - 1] = '\0'; 424 | break; 425 | } 426 | } 427 | 428 | if(header == NULL) { 429 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Location of registration object not found in HTTP response headers"); 430 | return NGX_CONF_ERROR; 431 | } 432 | 433 | /* 434 | * Terms of service agreement 435 | */ 436 | 437 | /* Search for and extract terms-of-service url from response headers */ 438 | for(header = response_headers; header != NULL; header = header->next) { 439 | /* Check minimum length */ 440 | if(header->value_len < strlen(ACME_TOS_PREFIX_STRING ACME_TOS_SUFFIX_STRING)) 441 | continue; 442 | 443 | /* Check prefix */ 444 | if(ngx_strncmp(header->value, ACME_TOS_PREFIX_STRING, strlen(ACME_TOS_PREFIX_STRING)) != 0) 445 | continue; 446 | 447 | /* Search backwards */ 448 | min_addr = header->value + strlen(ACME_TOS_PREFIX_STRING); 449 | max_addr = header->value + header->value_len - strlen(ACME_TOS_SUFFIX_STRING); 450 | for(tmp = max_addr; tmp >= min_addr; tmp--) { 451 | if(ngx_strncmp(tmp, ACME_TOS_SUFFIX_STRING, strlen(ACME_TOS_SUFFIX_STRING)) == 0) { 452 | goto found; 453 | } 454 | } 455 | } 456 | 457 | /* No terms-of-service link found, so we don't need to agree to anything */ 458 | goto new_reg_end; 459 | 460 | found: 461 | tos_url.data = (u_char *) min_addr; 462 | tos_url.len = tmp - min_addr; 463 | 464 | /* Send a registration update to agree to the TOS */ 465 | 466 | /* Free local variables before reusing them */ 467 | json_decref(request_json); 468 | json_decref(response_json); 469 | 470 | /* Assemble request */ 471 | request_json = json_pack("{s:s,s:s%}", "resource", "reg", "agreement", tos_url.data, tos_url.len); 472 | 473 | /* Make JSON request */ 474 | if(ngx_http_acme_request(cf, conf, reg_location, POST, request_json, key, replay_nonce, &response_json, &response_headers2) != NGX_CONF_OK) { 475 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while making JSON request"); 476 | return NGX_CONF_ERROR; 477 | } 478 | ngx_http_acme_slist_free_all(response_headers2); 479 | 480 | new_reg_end: 481 | ngx_free(reg_location); 482 | json_decref(request_json); 483 | json_decref(response_json); 484 | ngx_http_acme_slist_free_all(response_headers); 485 | 486 | return NGX_CONF_OK; 487 | } /* ngx_http_acme_new_reg */ 488 | 489 | 490 | static char *ngx_http_acme_new_auth(ngx_conf_t *cf, void *conf, ngx_str_t *replay_nonce, RSA *key) 491 | { 492 | json_t *request_json; 493 | json_t *response_json; 494 | ngx_http_acme_slist_t *response_headers = NULL; 495 | 496 | /* Assemble request */ 497 | request_json = json_pack("{s:s, s:{s:s, s:s} }", "resource", "new-authz", 498 | "identifier", "type", "dns", "value", ACME_DEV_SERVER_NAME); 499 | 500 | /* Make JSON request */ 501 | if(ngx_http_acme_request(cf, conf, ACME_SERVER "/acme/new-authz", POST, request_json, key, replay_nonce, &response_json, &response_headers) != NGX_CONF_OK) { 502 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while making JSON request"); 503 | return NGX_CONF_ERROR; 504 | } 505 | 506 | /* Process response */ 507 | 508 | 509 | 510 | json_decref(request_json); 511 | json_decref(response_json); 512 | ngx_http_acme_slist_free_all(response_headers); 513 | 514 | return NGX_CONF_OK; 515 | } /* ngx_http_acme_new_auth */ 516 | 517 | 518 | static char *ngx_http_acme_request(ngx_conf_t *cf, void *conf, char *url, ngx_http_acme_http_method_t http_method, 519 | json_t *request_json, RSA *key, ngx_str_t *replay_nonce, json_t **response_json, 520 | ngx_http_acme_slist_t **response_headers) 521 | { 522 | json_t *signed_request_json; 523 | ngx_http_acme_slist_t *header = NULL; 524 | 525 | /* Sign JSON and create JWS from the request JSON data */ 526 | if(json_is_null(request_json)) { 527 | signed_request_json = json_null(); 528 | } else { 529 | if(ngx_http_acme_sign_json(cf, conf, request_json, key, *replay_nonce, &signed_request_json) != NGX_CONF_OK) { 530 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Creating JWS failed"); 531 | return NGX_CONF_ERROR; 532 | } 533 | } 534 | 535 | /* Make JSON request */ 536 | if(ngx_http_acme_json_request(cf, conf, url, http_method, signed_request_json, response_json, response_headers) != NGX_CONF_OK) { 537 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while making JSON request"); 538 | return NGX_CONF_ERROR; 539 | } 540 | 541 | if(replay_nonce->data != NULL) 542 | ngx_free(replay_nonce->data); 543 | ngx_str_null(replay_nonce); 544 | json_decref(signed_request_json); 545 | 546 | /* Search for and extract replay nonce from response headers */ 547 | for(header = *response_headers; header != NULL; header = header->next) { 548 | if(header->value_len < strlen(ACME_REPLAY_NONCE_PREFIX_STRING)) 549 | continue; 550 | 551 | if(ngx_strncmp(header->value, ACME_REPLAY_NONCE_PREFIX_STRING, strlen(ACME_REPLAY_NONCE_PREFIX_STRING)) == 0) { 552 | /* Replay nonce found, extract it */ 553 | replay_nonce->len = header->value_len - strlen(ACME_REPLAY_NONCE_PREFIX_STRING); 554 | replay_nonce->data = ngx_alloc(replay_nonce->len, cf->log); 555 | ngx_memcpy(replay_nonce->data, header->value + strlen(ACME_REPLAY_NONCE_PREFIX_STRING), replay_nonce->len); 556 | break; 557 | } 558 | } 559 | 560 | if(header == NULL) { 561 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "No Replay-Nonce found in HTTP response headers"); 562 | return NGX_CONF_ERROR; 563 | } 564 | 565 | return NGX_CONF_OK; 566 | } /* ngx_http_acme_create_jwk */ 567 | 568 | static char *ngx_http_acme_sign_json(ngx_conf_t *cf, void *conf, json_t *payload, RSA *key, ngx_str_t replay_nonce, json_t **flattened_jws) 569 | { 570 | /* 571 | * Structure according to RFC7515: 572 | * 573 | * { 574 | * "payload":"", 575 | * "protected":"", 576 | * "header":, 577 | * "signature":"" 578 | * } 579 | * 580 | * Example: 581 | * 582 | * { 583 | * "payload": 584 | * "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", 585 | * "protected":"eyJhbGciOiJFUzI1NiJ9", 586 | * "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, 587 | * "signature": 588 | * "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" 589 | * } 590 | */ 591 | 592 | /* 593 | * ACME restrictions: 594 | * The JWS MUST use the Flattened JSON Serialization 595 | * The JWS MUST be encoded using UTF-8 596 | * The JWS Header or Protected Header MUST include “alg” and “jwk” fields 597 | * The JWS MUST NOT have the value “none” in its “alg” field 598 | */ 599 | 600 | json_t *jwk; 601 | json_t *header; 602 | ngx_str_t encoded_protected_header, serialized_payload, encoded_payload, tmp; 603 | ngx_str_t signing_input, signature, encoded_signature; 604 | u_char *tmp_char_p; 605 | 606 | /* Variables for signing */ 607 | EVP_PKEY *evp_key; 608 | EVP_MD_CTX *mdctx = NULL; 609 | int ret = 0; 610 | 611 | /* 612 | * Encode payload 613 | */ 614 | 615 | serialized_payload = (ngx_str_t)ngx_string_dynamic(json_dumps(payload, 0)); 616 | encoded_payload.len = ngx_base64_encoded_length(serialized_payload.len); 617 | encoded_payload.data = ngx_alloc(encoded_payload.len, cf->log); 618 | ngx_encode_base64url(&encoded_payload, &serialized_payload); 619 | 620 | println_debug("Signing payload: ", &serialized_payload); 621 | 622 | /* 623 | * Create header 624 | */ 625 | 626 | /* jwk header */ 627 | if(ngx_http_acme_create_jwk(cf, conf, key, &jwk) != NGX_CONF_OK) { 628 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to create the JWK from the account key"); 629 | ngx_free(serialized_payload.data); 630 | ngx_free(encoded_payload.data); 631 | return NGX_CONF_ERROR; 632 | } 633 | 634 | /* Pack header into JSON */ 635 | header = json_pack("{s:s, s:s%, s:o}", "alg", "RS256", "nonce", replay_nonce.data, replay_nonce.len, "jwk", jwk); 636 | if(header == NULL) { 637 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error packing JWS header"); 638 | ngx_free(serialized_payload.data); 639 | ngx_free(encoded_payload.data); 640 | return NGX_CONF_ERROR; 641 | } 642 | 643 | /* Serialize and base64url encode header */ 644 | tmp = (ngx_str_t)ngx_string_dynamic(json_dumps(header, 0)); 645 | encoded_protected_header.len = ngx_base64_encoded_length(tmp.len); 646 | encoded_protected_header.data = ngx_alloc(encoded_protected_header.len, cf->log); 647 | ngx_encode_base64url(&encoded_protected_header, &tmp); 648 | ngx_free(tmp.data); 649 | json_decref(header); 650 | 651 | /* 652 | * Create signature 653 | */ 654 | 655 | /* Create signing input */ 656 | /* = ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload)) */ 657 | signing_input.len = encoded_protected_header.len + strlen(".") + encoded_payload.len; 658 | signing_input.data = ngx_alloc(signing_input.len, cf->log); 659 | tmp_char_p = ngx_copy(signing_input.data, encoded_protected_header.data, encoded_protected_header.len); 660 | tmp_char_p = ngx_copy(tmp_char_p, ".", strlen(".")); 661 | tmp_char_p = ngx_copy(tmp_char_p, encoded_payload.data, encoded_payload.len); 662 | 663 | /* Convert the RSA key to the EVP_PKEY structure */ 664 | evp_key = EVP_PKEY_new(); 665 | if(evp_key == NULL) { 666 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 667 | "Error signing the message digest for the JWS signature."); 668 | return NGX_CONF_ERROR; 669 | } 670 | 671 | if(EVP_PKEY_set1_RSA(evp_key, key) == 0) { 672 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 673 | "Error signing the message digest for the JWS signature."); 674 | return NGX_CONF_ERROR; 675 | } 676 | 677 | /* Create the message digest context */ 678 | ret = 0; 679 | mdctx = EVP_MD_CTX_create(); 680 | if(mdctx == NULL) 681 | goto err; 682 | 683 | /* Initialize the DigestSign operation - SHA-256 has been selected as the message digest function */ 684 | if(EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, evp_key) != 1) 685 | goto err; 686 | 687 | /* Call update with the message */ 688 | if(EVP_DigestSignUpdate(mdctx, signing_input.data, signing_input.len) != 1) 689 | goto err; 690 | 691 | /* Finalise the DigestSign operation */ 692 | /* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the */ 693 | /* signature. The length is returned in siglen. */ 694 | if(EVP_DigestSignFinal(mdctx, NULL, &signature.len) != 1) 695 | goto err; 696 | 697 | /* Allocate memory for the signature */ 698 | signature.data = ngx_alloc(signature.len, cf->log); 699 | 700 | /* Obtain the signature */ 701 | if(EVP_DigestSignFinal(mdctx, signature.data, &signature.len) != 1) 702 | goto err; 703 | 704 | /* Success */ 705 | ret = 1; 706 | 707 | err: 708 | if(ret != 1) { 709 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 710 | "Error signing the message digest for the JWS signature. OpenSSL error 0x%xl", ERR_get_error()); 711 | return NGX_CONF_ERROR; 712 | } 713 | 714 | /* Clean up */ 715 | EVP_MD_CTX_destroy(mdctx); 716 | EVP_PKEY_free(evp_key); 717 | 718 | /* base64url encode the signature */ 719 | encoded_signature.len = ngx_base64_encoded_length(signature.len); 720 | encoded_signature.data = ngx_alloc(encoded_signature.len, cf->log); 721 | ngx_encode_base64url(&encoded_signature, &signature); 722 | ngx_free(signature.data); 723 | 724 | /* 725 | * Create flattened JWS serialization 726 | */ 727 | 728 | *flattened_jws = json_pack("{s:s%,s:s%,s:s%}", 729 | "payload", encoded_payload.data, encoded_payload.len, 730 | "protected", encoded_protected_header.data, encoded_protected_header.len, 731 | "signature", encoded_signature.data, encoded_signature.len 732 | ); 733 | 734 | ngx_free(serialized_payload.data); 735 | // TODO (KK) Maybe this is too early for a free since the strings will be used in the flattened JWS (but when to free then?) 736 | ngx_free(encoded_payload.data); 737 | ngx_free(encoded_protected_header.data); 738 | ngx_free(encoded_signature.data); 739 | 740 | if(*flattened_jws == NULL) { 741 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error serializing flattened JWS"); 742 | return NGX_CONF_ERROR; 743 | } 744 | 745 | 746 | return NGX_CONF_OK; 747 | } /* ngx_http_acme_sign_json */ 748 | 749 | 750 | static char *ngx_http_acme_create_jwk(ngx_conf_t *cf, void *conf, RSA *key, json_t **jwk) 751 | { 752 | ngx_str_t e, n, tmp; 753 | 754 | /* Baser64url encode e */ 755 | tmp.len = BN_num_bytes(key->e); 756 | tmp.data = ngx_alloc(tmp.len, cf->log); 757 | tmp.len = BN_bn2bin(key->e, tmp.data); 758 | e.len = ngx_base64_encoded_length(tmp.len); 759 | e.data = ngx_alloc(e.len, cf->log); 760 | ngx_encode_base64url(&e, &tmp); 761 | ngx_free(tmp.data); 762 | 763 | /* Baser64url encode n */ 764 | tmp.len = BN_num_bytes(key->n); 765 | tmp.data = ngx_alloc(tmp.len, cf->log); 766 | tmp.len = BN_bn2bin(key->n, tmp.data); 767 | n.len = ngx_base64_encoded_length(tmp.len); 768 | n.data = ngx_alloc(n.len, cf->log); 769 | ngx_encode_base64url(&n, &tmp); 770 | ngx_free(tmp.data); 771 | 772 | *jwk = json_pack("{s:s, s:s%, s:s%}", "kty", "RSA", "e", e.data, e.len, "n", n.data, n.len); 773 | if(*jwk == NULL) { 774 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Failed to pack JWK"); 775 | return NGX_CONF_ERROR; 776 | } 777 | 778 | ngx_free(e.data); 779 | ngx_free(n.data); 780 | 781 | return NGX_CONF_OK; 782 | } /* ngx_http_acme_create_jwk */ 783 | 784 | //static char *ngx_http_acme_create_priv_jwk(ngx_conf_t *cf, void *conf, RSA *key, json_t **jwk) 785 | //{ 786 | // // TODO (KK) Create JWK with following information 787 | // // d, e, n, q, p, qi, dp, dq 788 | // // kty: RSA 789 | // 790 | // return NGX_CONF_OK; 791 | //} /* ngx_http_acme_create_priv_jwk */ 792 | 793 | static char *ngx_http_acme_read_jwk(ngx_conf_t *cf, void *conf, ngx_str_t jwk_str, RSA **key) 794 | { 795 | json_t *jwk; 796 | json_error_t error; 797 | 798 | /* 799 | * Deserialize JWK 800 | */ 801 | jwk = json_loadb((char *) jwk_str.data, jwk_str.len, 0, &error); 802 | free(jwk_str.data); 803 | ngx_str_null(&jwk_str); 804 | 805 | if(jwk == NULL) 806 | { 807 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 808 | "Error parsing JSON: on line %d: %s\n", error.line, error.text); 809 | return NGX_CONF_ERROR; 810 | } 811 | 812 | // TODO (KK) Form RSA struct from JWK 813 | 814 | return NGX_CONF_OK; 815 | } /* ngx_http_acme_create_jwk */ 816 | 817 | static char *ngx_http_acme_json_request(ngx_conf_t *cf, void *conf, char *url, ngx_http_acme_http_method_t http_method, 818 | json_t *request_json, json_t **response_json, ngx_http_acme_slist_t **response_headers) 819 | { 820 | ngx_str_t response_data; 821 | ngx_str_t request_data; 822 | char *tmp; 823 | 824 | json_error_t error; 825 | 826 | /* Convert request_json to string to provide it to the following method */ 827 | request_data = (ngx_str_t)ngx_null_string; 828 | if(!json_is_null(request_json)) { 829 | tmp = json_dumps(request_json, 0); 830 | if(tmp == NULL) { 831 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while creating request string from JSON\n"); 832 | return NGX_CONF_ERROR; 833 | } else { 834 | request_data.data = (u_char *)tmp; 835 | request_data.len = ngx_strlen(tmp); 836 | } 837 | } 838 | 839 | /* Make request */ 840 | if(ngx_http_acme_plain_request(cf, conf, url, http_method, request_data, &response_data, response_headers) != NGX_CONF_OK) { 841 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, "Error while making request\n"); 842 | return NGX_CONF_ERROR; 843 | } 844 | 845 | /* Now all the returned JSON is in the data variable */ 846 | 847 | /* 848 | * Parsing returned JSON 849 | */ 850 | 851 | /* Begin Jansson part */ 852 | 853 | if(response_data.len <= 0) { 854 | *response_json = json_null(); 855 | } else { 856 | *response_json = json_loadb((char *) response_data.data, response_data.len, 0, &error); 857 | } 858 | free(response_data.data); 859 | ngx_str_null(&response_data); 860 | 861 | if(*response_json == NULL) 862 | { 863 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 864 | "Error parsing JSON: on line %d: %s\n", error.line, error.text); 865 | return NGX_CONF_ERROR; 866 | } 867 | 868 | /* End Jansson part */ 869 | 870 | return NGX_CONF_OK; 871 | } /* ngx_http_acme_json_request */ 872 | 873 | static char *ngx_http_acme_plain_request(ngx_conf_t *cf, void *conf, char *url, ngx_http_acme_http_method_t http_method, 874 | ngx_str_t request_data, ngx_str_t *response_data, ngx_http_acme_slist_t **response_headers) 875 | { 876 | CURL *curl; 877 | CURLcode res; 878 | struct curl_slist *request_headers = NULL; 879 | 880 | FILE *response_data_stream; 881 | 882 | /* Begin cURL part */ 883 | 884 | curl_global_init(CURL_GLOBAL_DEFAULT); 885 | 886 | curl = curl_easy_init(); 887 | 888 | if(curl == NULL) 889 | return NGX_CONF_ERROR; 890 | 891 | curl_easy_setopt(curl, CURLOPT_URL, url); 892 | 893 | /* 894 | * Setting the HTTP method 895 | */ 896 | 897 | if(http_method == GET) { 898 | curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); 899 | } else if(http_method == POST) { 900 | curl_easy_setopt(curl, CURLOPT_POST, 1L); 901 | } 902 | 903 | /* 904 | * Setting the request data handling 905 | */ 906 | 907 | if(request_data.data != NULL) { 908 | 909 | // TODO (KK) Add method parameter for the header list to be dynamic in e.g. the content type, since it doesn't always have to be JSON ;) 910 | request_headers = curl_slist_append(request_headers, "Content-Type: application/json"); 911 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers); 912 | 913 | /* size of the data to copy from the buffer and send in the request */ 914 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request_data.len); 915 | 916 | /* send data from the local stack */ 917 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_data.data); 918 | 919 | println_debug("Request data: ", &request_data); 920 | } 921 | 922 | /* 923 | * Setting the response data handling 924 | */ 925 | 926 | /* Setup the stream for the response data */ 927 | response_data_stream = open_memstream((char **) &response_data->data, &response_data->len); 928 | 929 | if(response_data_stream == NULL) { 930 | curl_slist_free_all(request_headers); 931 | curl_easy_cleanup(curl); 932 | return NGX_CONF_ERROR; 933 | } 934 | 935 | /* 936 | * ATTENTION: Setting CURLOPT_WRITEDATA without CURLOPT_WRITEFUNCTION does not work on Windows 937 | * according to https://curl.haxx.se/libcurl/c/CURLOPT_WRITEDATA.html 938 | */ 939 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_data_stream); 940 | 941 | /* 942 | * Setting the response header handling 943 | */ 944 | 945 | if (response_headers != NULL) { 946 | curl_easy_setopt(curl, CURLOPT_HEADERDATA, response_headers); 947 | curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, 948 | ngx_http_acme_header_callback); 949 | } 950 | 951 | /* 952 | * Perform the request 953 | */ 954 | 955 | res = curl_easy_perform(curl); 956 | 957 | /* Check for errors */ 958 | if(res != CURLE_OK) { 959 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 960 | "Error while performing request: %s\n", curl_easy_strerror(res)); 961 | fclose(response_data_stream); 962 | curl_slist_free_all(request_headers); 963 | curl_easy_cleanup(curl); 964 | return NGX_CONF_ERROR; 965 | } 966 | 967 | /* always cleanup */ 968 | curl_easy_cleanup(curl); 969 | 970 | fclose(response_data_stream); 971 | 972 | /* free the custom headers */ 973 | curl_slist_free_all(request_headers); 974 | 975 | /* End cURL part */ 976 | 977 | println_debug("Response data: ", response_data); 978 | 979 | return NGX_CONF_OK; 980 | } /* ngx_http_acme_plain_request */ 981 | 982 | static size_t ngx_http_acme_header_callback(char *buffer, size_t size, size_t nitems, void *userdata) 983 | { 984 | ngx_http_acme_slist_t **slist = (ngx_http_acme_slist_t **) userdata; 985 | ngx_str_t entry; 986 | 987 | if(size * nitems <= 0) 988 | return 0; 989 | 990 | /* Subtract 2 from the length to trim off the \r\n */ 991 | entry.len = (size * nitems) - 2; 992 | entry.data = (u_char *) buffer; 993 | 994 | *slist = ngx_http_acme_slist_append_entry(*slist, entry); 995 | if(*slist == NULL) 996 | return 0; 997 | 998 | return nitems * size; 999 | } /* ngx_http_acme_header_callback */ 1000 | 1001 | /** 1002 | * TODO (KK) delete 1003 | * This entry point is too late, we will probably never use it. 1004 | */ 1005 | static ngx_int_t ngx_http_acme_init(ngx_conf_t *cf) 1006 | { 1007 | return NGX_OK; 1008 | } /* ngx_http_acme_init */ 1009 | 1010 | /* 1011 | * Utility functions 1012 | */ 1013 | 1014 | /* Dictionary functions */ 1015 | //static ngx_http_acme_sdict_t *ngx_http_acme_sdict_append_kv_pair(ngx_http_acme_sdict_t *sdict, ngx_str_t key, ngx_str_t value) 1016 | //{ 1017 | // ngx_http_acme_sdict_t *new_sdict, *last; 1018 | // 1019 | // if(sdict != NULL) 1020 | // for(last = sdict; last->next != NULL; last = last->next); 1021 | // 1022 | // new_sdict = malloc(sizeof(ngx_http_acme_sdict_t)); 1023 | // 1024 | // if(new_sdict == NULL) { 1025 | // fprintf(stderr, "Error while allocating new memory for the new dictionary entry"); 1026 | // return NULL; 1027 | // } 1028 | // 1029 | // // Fill out new entry 1030 | // new_sdict->key = (char *) key.data; 1031 | // new_sdict->key_len = key.len; 1032 | // new_sdict->value = (char *) value.data; 1033 | // new_sdict->value_len = value.len; 1034 | // new_sdict->next = NULL; 1035 | // 1036 | // // Add new entry to the list 1037 | // if(sdict == NULL) { 1038 | // sdict = new_sdict; 1039 | // } else { 1040 | // last->next = new_sdict; 1041 | // } 1042 | // 1043 | // return sdict; 1044 | //} /* ngx_http_acme_sdict_append_kv_pair */ 1045 | // 1046 | //static void ngx_http_acme_sdict_free_all(ngx_http_acme_sdict_t *sdict) 1047 | //{ 1048 | // ngx_http_acme_sdict_t *tmp; 1049 | // 1050 | // while(sdict != NULL) { 1051 | // tmp = sdict->next; 1052 | // free(sdict); 1053 | // sdict = tmp; 1054 | // } 1055 | //} /* ngx_http_acme_sdict_free_all */ 1056 | 1057 | 1058 | /* List functions */ 1059 | static ngx_http_acme_slist_t *ngx_http_acme_slist_append_entry(ngx_http_acme_slist_t *slist, ngx_str_t value) 1060 | { 1061 | ngx_http_acme_slist_t *new_slist, *last; 1062 | 1063 | if(slist != NULL) 1064 | for(last = slist; last->next != NULL; last = last->next); 1065 | 1066 | new_slist = malloc(sizeof(ngx_http_acme_slist_t)); 1067 | 1068 | if(new_slist == NULL) { 1069 | fprintf(stderr, "Error while allocating new memory for the new list entry"); 1070 | return NULL; 1071 | } 1072 | 1073 | /* Copy new entry */ 1074 | new_slist->value_len = value.len; 1075 | new_slist->next = NULL; 1076 | 1077 | new_slist->value = malloc(new_slist->value_len); 1078 | ngx_memcpy(new_slist->value, value.data, new_slist->value_len); 1079 | 1080 | /* Add new entry to the list */ 1081 | if(slist == NULL) { 1082 | slist = new_slist; 1083 | } else { 1084 | last->next = new_slist; 1085 | } 1086 | 1087 | return slist; 1088 | } /* ngx_http_acme_slist_append_entry */ 1089 | 1090 | static void ngx_http_acme_slist_free_all(ngx_http_acme_slist_t *slist) 1091 | { 1092 | ngx_http_acme_slist_t *tmp; 1093 | 1094 | while(slist != NULL) { 1095 | tmp = slist->next; 1096 | free(slist->value); 1097 | free(slist); 1098 | slist = tmp; 1099 | } 1100 | } /* ngx_http_acme_slist_free_all */ 1101 | 1102 | -------------------------------------------------------------------------------- /ngx_http_acme_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ngx_http_acme_module.h 3 | * 4 | * Created on: Feb 26, 2016 5 | * Author: klaus 6 | */ 7 | 8 | #ifndef NGX_HTTP_ACME_MODULE_H_ 9 | #define NGX_HTTP_ACME_MODULE_H_ 10 | 11 | /* 12 | * Makros which could also form config directives later 13 | */ 14 | #define ACME_DIR "acme" 15 | #define ACME_LIVE_DIR "live" 16 | #define ACME_CERT_TRUSTED "chain.pem" 17 | #define ACME_CERT_KEY "privkey.pem" 18 | #define ACME_CERT "fullchain.pem" 19 | #define ACME_ACCOUNT_RSA_BITS 2048 20 | #define ACME_ACCOUNT_RSA_EXP "65537" 21 | #define ACME_SERVER_DOMAIN "acme-staging.api.letsencrypt.org" 22 | #define ACME_SERVER "https://" ACME_SERVER_DOMAIN 23 | 24 | 25 | typedef enum { 26 | GET, 27 | POST 28 | } ngx_http_acme_http_method_t; 29 | 30 | /* linked-dictionary structure */ 31 | typedef struct ngx_http_acme_sdict { 32 | char *key; 33 | size_t key_len; 34 | char *value; 35 | size_t value_len; 36 | struct ngx_http_acme_sdict *next; 37 | } ngx_http_acme_sdict_t; 38 | 39 | /* linked-list structure */ 40 | typedef struct ngx_http_acme_slist { 41 | char *value; 42 | size_t value_len; 43 | struct ngx_http_acme_slist *next; 44 | } ngx_http_acme_slist_t; 45 | 46 | 47 | #define ngx_string_dynamic(str) { strlen(str), (u_char *) str } 48 | 49 | #endif /* NGX_HTTP_ACME_MODULE_H_ */ 50 | --------------------------------------------------------------------------------