From 3ec7c1ab21c27be9eb58ca83171a1880a375e796 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sat, 21 Oct 2017 06:36:35 +0800 Subject: [PATCH] ACME client plugin This adds support for a plugin that can be attached to a vhost to acquire and maintain its TLS cert automatically. It works the same with both OpenSSL and mbedTLS backends, but they can't share auth keys, delete the 'auth.jwk' file as it is in the example JSON when switching between libs --- .travis.yml | 2 +- CMakeLists.txt | 12 + READMEs/README.lwsws.md | 17 + READMEs/README.plugin-acme.md | 180 ++ lib/lextable-strings.h | 1 + lib/lextable.h | 930 +++++------ lib/libwebsockets.h | 10 +- lib/plat/lws-plat-esp32.c | 23 + lib/plat/lws-plat-esp8266.c | 8 + lib/plat/lws-plat-optee.c | 7 + lib/plat/lws-plat-unix.c | 14 + lib/plat/lws-plat-win.c | 12 + lib/tls/mbedtls/server.c | 2 +- .../acme-client/protocol_lws_acme_client.c | 1465 +++++++++++++++++ 14 files changed, 2221 insertions(+), 462 deletions(-) create mode 100644 READMEs/README.plugin-acme.md create mode 100644 plugins/acme-client/protocol_lws_acme_client.c diff --git a/.travis.yml b/.travis.yml index 15316a56..b331ca15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ env: global: - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI=" matrix: - - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITH_HTTP2=1" + - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1" - LWS_METHOD=default - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON" - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON" diff --git a/CMakeLists.txt b/CMakeLists.txt index ba97eca4..4ede4b51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ option(LWS_WITH_PEER_LIMITS "Track peers and restrict resources a single peer ca option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF) option(LWS_WITH_RANGES "Support http ranges (RFC7233)" ON) option(LWS_WITH_SERVER_STATUS "Support json + jscript server monitoring" OFF) +option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF) # # TLS library options... all except mbedTLS are basically OpenSSL variants. # @@ -175,6 +176,12 @@ if (LWS_WITH_LWSWS) set(LWS_WITH_PEER_LIMITS 1) endif() +if (LWS_WITH_ACME) + set (LWS_WITHOUT_CLIENT 0) + set (LWS_WITHOUT_SERVER 0) + set (LWS_WITH_JWS 1) +endif() + if (LWS_WITH_JWS) set(LWS_WITH_LEJP 1) endif() @@ -1711,6 +1718,11 @@ if (NOT LWS_WITHOUT_CLIENT) "plugins/protocol_client_loopback_test.c" "" "") endif(NOT LWS_WITHOUT_CLIENT) +if (LWS_WITH_ACME) + create_plugin(protocol_lws_acme_client "" + "plugins/acme-client/protocol_lws_acme_client.c" "" "") +endif() + if (LWS_WITH_GENERIC_SESSIONS) create_plugin(protocol_generic_sessions "" "plugins/generic-sessions/protocol_generic_sessions.c" diff --git a/READMEs/README.lwsws.md b/READMEs/README.lwsws.md index 03b6ff13..6e5ed9b0 100644 --- a/READMEs/README.lwsws.md +++ b/READMEs/README.lwsws.md @@ -79,6 +79,12 @@ on port 7681, non-SSL is provided. To set it up # sudo lwsws ``` +@section lwswsacme Using Letsencrypt or other ACME providers + +Lws supports automatic provisioning and renewal of TLS certificates. + +See ./READMEs/README.plugin-acme.md for examples of how to set it up on an lwsws vhost. + @section lwsogo Other Global Options - `reject-service-keywords` allows you to return an HTTP error code and message of your choice @@ -430,6 +436,17 @@ The file should be readable by lwsws, and for a little bit of extra security not have a file suffix, so lws would reject to serve it even if it could find it on a mount. +@section lwswscc Requiring a Client Cert on a vhost + +You can make a vhost insist to get a client certificate from the peer before +allowing the connection with + +``` + "client-cert-required": "1" +``` + +the connection will only proceed if the client certificate was signed by the +same CA as the server has been told to trust. @section lwswspl Lwsws Plugins diff --git a/READMEs/README.plugin-acme.md b/READMEs/README.plugin-acme.md new file mode 100644 index 00000000..8d3af4cc --- /dev/null +++ b/READMEs/README.plugin-acme.md @@ -0,0 +1,180 @@ +lws-acme-client Plugin +====================== + +## Introduction + +lws-acme-client is a protcol plugin for libwebsockets that implements an +ACME client able to communicate with let's encrypt and other certificate +providers. + +It implements `tls-sni-01` challenge, and is able to provision tls certificates +"from thin air" that are accepted by all the major browsers. It also manages +re-requesting the certificate when it only has two weeks left to run. + +It works with both the OpenSSL and mbedTLS backends. + +## Overview for use + +You need to: + + - Provide name resolution to the IP with your server, ie, myserver.com needs to + resolve to the IP that hosts your server + + - Enable port forwarding / external firewall access to your port, usually 443 + + - Enable the "lws-acme-client" plugin on the vhosts you want it to manage + certs for + + - Add per-vhost options describing what should be in the certificate + +After that the plugin will sort everything else out. + +## Example lwsws setup + +``` + "vhosts": [ { + "name": "home.warmcat.com", + "port": "443", + "host-ssl-cert": "/etc/lwsws/acme/home.warmcat.com.crt.pem", + "host-ssl-key": "/etc/lwsws/acme/home.warmcat.com.key.pem", + "ignore-missing-cert": "1", + "access-log": "/var/log/lwsws/test-access-log", + "ws-protocols": [{ + "lws-acme-client": { + "auth-path": "/etc/lwsws/acme/auth.jwk", + "cert-path": "/etc/lwsws/acme/home.warmcat.com.crt.pem", + "key-path": "/etc/lwsws/acme/home.warmcat.com.key.pem", + "directory-url": "https://acme-staging.api.letsencrypt.org/directory", + "country": "TW", + "state": "Taipei", + "locality": "Xiaobitan", + "organization": "Crash Barrier Ltd", + "common-name": "home.warmcat.com", + "email": "andy@warmcat.com" + }, + ... +``` + +## Required PVOs + +Notice that the `"host-ssl-cert"` and `"host-ssl-key"` entries have the same +meaning as usual, they point to your certificate and private key. However +because the ACME plugin can provision these, you should also mark the vhost with +`"ignore-missing-cert" : "1"`, so lwsws will ignore what will initially be +missing certificate / keys on that vhost, and will set about creating the +necessary certs and keys instead of erroring out. + +You must make sure the directories mentioned here exist, lws doesn't create them +for you. They should be 0700 root:root, even if you drop lws privileges. + +If you are implementing support in code, this corresponds to making sure the +vhost creating `info.options` has the `LWS_SERVER_OPTION_IGNORE_MISSING_CERT` +bit set. + +Similarly, in code, the each of the per-vhost options shown above can be +provided in a linked-list of structs at vhost creation time. See +`./test-apps/test-server-v2.0.c` for example code for providing pvos. + +### auth-path + +This is where the plugin will store the auth keys it generated. + +### cert-path + +Where the plugin will store the certificate file. Should match `host-ssl-cert` +that the vhost wants to use. + +The path should include at least one 0700 root:root directory. + +### key-path + +Where the plugin will store the certificate keys. Again it should match +`host-ssl-key` the vhost is trying to use. + +The path should include at least one 0700 root:root directory. + +### directory-url + +This defines the URL of the certification server you will get your +certificates from. For let's encrypt, they have a "practice" one + + - `https://acme-staging.api.letsencrypt.org/directory` + +and they have a "real" one + + - `https://acme-v01.api.letsencrypt.org/directory` + +the main difference is the CA certificate for the real one is in most browsers +already, but the staging one's CA certificate isn't. The staging server will +also let you abuse it more in terms of repeated testing etc. + +It's recommended you confirm expected operation with the staging directory-url, +and then switch to the "real" URL. + +### common-name + +Your server DNS name, like "libwebsockets.org". The remote ACME server will +use this to find your server to perform the SNI challenges. + +### email + +The contact email address for the certificate. + +## Optional PVOs + +These are not included in the cert by letsencrypt + +### country + +Two-letter country code for the certificate + +### state + +State "or province" for the certificate + +### locality + +Locality for the certificate + +### organization + +Your company name + +## Security / Key storage considerations + +The `lws-acme-client` plugin is able to provision and update your certificate +and keys in an entirely root-only storage environment, even though lws runs +as a different uid / gid with no privileges to access the storage dir. + +It does this by opening and holding two WRONLY fds on "update paths" inside the +root directory structure for each cert and key it manages; these are the normal +cert and key paths with `.upd` appended. If during the time the server is up +the certs become within two weeks of expiry, the `lws-acme-client` plugin will +negotiate new certs and write them to the file descriptors. + +Next time the server starts, if it sees `.upd` cert and keys, it will back up +the old ones and copy them into place as the new ones, before dropping privs. + +To also handle the long-uptime server case, lws will update the vhost with the +new certs using in-memory temporary copies of the cert and key after updating +the cert. + +In this way the cert and key live in root-only storage but the vhost is kept up +to date dynamically with any cert changes as well. + +## Multiple vhosts using same cert + +In the case you have multiple vhosts using of the same cert, just attach +the `lws-acme-client` plugin to one instance. When the cert updates, all the +vhosts are informed and vhosts using the same filepath to access the cert will +be able to update their cert. + +## Implementation point + +You will need to remove the auth keys when switching from OpenSSL to +mbedTLS. They will be regenerated automatically. It's the file at this +path: + +``` +"auth-path": "/etc/lwsws/acme/auth.jwk", +``` diff --git a/lib/lextable-strings.h b/lib/lextable-strings.h index ab42c3e4..035a822d 100644 --- a/lib/lextable-strings.h +++ b/lib/lextable-strings.h @@ -98,6 +98,7 @@ STORE_IN_ROM static const char * const set[] = { "connect ", "head ", "te:", /* http/2 wants it to reject it */ + "replay-nonce:", /* ACME */ "", /* not matchable */ diff --git a/lib/lextable.h b/lib/lextable.h index f940afd2..d048b948 100644 --- a/lib/lextable.h +++ b/lib/lextable.h @@ -10,15 +10,15 @@ 0x69 /* 'i' */, 0x70, 0x01 /* (to 0x018B state 163) */, 0x64 /* 'd' */, 0x19, 0x02 /* (to 0x0237 state 265) */, 0x72 /* 'r' */, 0x22, 0x02 /* (to 0x0243 state 270) */, - 0x3A /* ':' */, 0x53, 0x02 /* (to 0x0277 state 299) */, - 0x65 /* 'e' */, 0xDF, 0x02 /* (to 0x0306 state 409) */, - 0x66 /* 'f' */, 0xFB, 0x02 /* (to 0x0325 state 425) */, - 0x6C /* 'l' */, 0x1D, 0x03 /* (to 0x034A state 458) */, - 0x6D /* 'm' */, 0x40, 0x03 /* (to 0x0370 state 484) */, - 0x74 /* 't' */, 0xAF, 0x03 /* (to 0x03E2 state 578) */, - 0x76 /* 'v' */, 0xD0, 0x03 /* (to 0x0406 state 606) */, - 0x77 /* 'w' */, 0xDD, 0x03 /* (to 0x0416 state 614) */, - 0x78 /* 'x' */, 0x04, 0x04 /* (to 0x0440 state 650) */, + 0x3A /* ':' */, 0x56, 0x02 /* (to 0x027A state 299) */, + 0x65 /* 'e' */, 0xE2, 0x02 /* (to 0x0309 state 409) */, + 0x66 /* 'f' */, 0xFE, 0x02 /* (to 0x0328 state 425) */, + 0x6C /* 'l' */, 0x20, 0x03 /* (to 0x034D state 458) */, + 0x6D /* 'm' */, 0x43, 0x03 /* (to 0x0373 state 484) */, + 0x74 /* 't' */, 0xB2, 0x03 /* (to 0x03E5 state 578) */, + 0x76 /* 'v' */, 0xD3, 0x03 /* (to 0x0409 state 606) */, + 0x77 /* 'w' */, 0xE0, 0x03 /* (to 0x0419 state 614) */, + 0x78 /* 'x' */, 0x07, 0x04 /* (to 0x0443 state 650) */, 0x08, /* fail */ /* pos 0040: 1 */ 0xE5 /* 'e' -> */, /* pos 0041: 2 */ 0xF4 /* 't' -> */, @@ -26,8 +26,8 @@ /* pos 0043: 4 */ 0x00, 0x00 /* - terminal marker 0 - */, /* pos 0045: 5 */ 0x6F /* 'o' */, 0x0D, 0x00 /* (to 0x0052 state 6) */, 0x72 /* 'r' */, 0x95, 0x01 /* (to 0x01DD state 211) */, - 0x61 /* 'a' */, 0xDD, 0x03 /* (to 0x0428 state 631) */, - 0x75 /* 'u' */, 0xDF, 0x03 /* (to 0x042D state 635) */, + 0x61 /* 'a' */, 0xE0, 0x03 /* (to 0x042B state 631) */, + 0x75 /* 'u' */, 0xE2, 0x03 /* (to 0x0430 state 635) */, 0x08, /* fail */ /* pos 0052: 6 */ 0xF3 /* 's' -> */, /* pos 0053: 7 */ 0xF4 /* 't' -> */, @@ -45,7 +45,7 @@ /* pos 0064: 17 */ 0x00, 0x02 /* - terminal marker 2 - */, /* pos 0066: 18 */ 0x6F /* 'o' */, 0x0A, 0x00 /* (to 0x0070 state 19) */, 0x74 /* 't' */, 0xBF, 0x00 /* (to 0x0128 state 110) */, - 0x65 /* 'e' */, 0xF8, 0x03 /* (to 0x0464 state 676) */, + 0x65 /* 'e' */, 0xFB, 0x03 /* (to 0x0467 state 676) */, 0x08, /* fail */ /* pos 0070: 19 */ 0xF3 /* 's' -> */, /* pos 0071: 20 */ 0xF4 /* 't' -> */, @@ -64,15 +64,15 @@ /* pos 008b: 27 */ 0xE3 /* 'c' -> */, /* pos 008c: 28 */ 0xF4 /* 't' -> */, /* pos 008d: 29 */ 0x69 /* 'i' */, 0x07, 0x00 /* (to 0x0094 state 30) */, - 0x20 /* ' ' */, 0xD2, 0x03 /* (to 0x0462 state 675) */, + 0x20 /* ' ' */, 0xD5, 0x03 /* (to 0x0465 state 675) */, 0x08, /* fail */ /* pos 0094: 30 */ 0xEF /* 'o' -> */, /* pos 0095: 31 */ 0xEE /* 'n' -> */, /* pos 0096: 32 */ 0xBA /* ':' -> */, /* pos 0097: 33 */ 0x00, 0x04 /* - terminal marker 4 - */, /* pos 0099: 34 */ 0x70 /* 'p' */, 0x0A, 0x00 /* (to 0x00A3 state 35) */, - 0x73 /* 's' */, 0x5F, 0x03 /* (to 0x03FB state 596) */, - 0x72 /* 'r' */, 0x97, 0x03 /* (to 0x0436 state 642) */, + 0x73 /* 's' */, 0x62, 0x03 /* (to 0x03FE state 596) */, + 0x72 /* 'r' */, 0x9A, 0x03 /* (to 0x0439 state 642) */, 0x08, /* fail */ /* pos 00a3: 35 */ 0xE7 /* 'g' -> */, /* pos 00a4: 36 */ 0xF2 /* 'r' -> */, @@ -88,11 +88,11 @@ /* pos 00af: 46 */ 0xBA /* ':' -> */, /* pos 00b0: 47 */ 0x00, 0x06 /* - terminal marker 6 - */, /* pos 00b2: 48 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x00B9 state 49) */, - 0x74 /* 't' */, 0x13, 0x03 /* (to 0x03C8 state 553) */, + 0x74 /* 't' */, 0x16, 0x03 /* (to 0x03CB state 553) */, 0x08, /* fail */ /* pos 00b9: 49 */ 0x63 /* 'c' */, 0x0A, 0x00 /* (to 0x00C3 state 50) */, - 0x72 /* 'r' */, 0xFC, 0x02 /* (to 0x03B8 state 539) */, - 0x74 /* 't' */, 0xFF, 0x02 /* (to 0x03BE state 544) */, + 0x72 /* 'r' */, 0xFF, 0x02 /* (to 0x03BB state 539) */, + 0x74 /* 't' */, 0x02, 0x03 /* (to 0x03C1 state 544) */, 0x08, /* fail */ /* pos 00c3: 50 */ 0xAD /* '-' -> */, /* pos 00c4: 51 */ 0xF7 /* 'w' -> */, @@ -111,8 +111,8 @@ 0x70 /* 'p' */, 0x38, 0x00 /* (to 0x010F state 88) */, 0x61 /* 'a' */, 0x3F, 0x00 /* (to 0x0119 state 97) */, 0x6E /* 'n' */, 0x44, 0x00 /* (to 0x0121 state 104) */, - 0x76 /* 'v' */, 0x86, 0x01 /* (to 0x0266 state 284) */, - 0x6F /* 'o' */, 0x8C, 0x01 /* (to 0x026F state 292) */, + 0x76 /* 'v' */, 0x89, 0x01 /* (to 0x0269 state 284) */, + 0x6F /* 'o' */, 0x8F, 0x01 /* (to 0x0272 state 292) */, 0x08, /* fail */ /* pos 00e7: 62 */ 0xF2 /* 'r' -> */, /* pos 00e8: 63 */ 0xE1 /* 'a' -> */, @@ -137,7 +137,7 @@ /* pos 00fe: 82 */ 0xF9 /* 'y' -> */, /* pos 00ff: 83 */ 0x31 /* '1' */, 0x0A, 0x00 /* (to 0x0109 state 84) */, 0x32 /* '2' */, 0x0A, 0x00 /* (to 0x010C state 86) */, - 0x3A /* ':' */, 0x5F, 0x01 /* (to 0x0264 state 283) */, + 0x3A /* ':' */, 0x62, 0x01 /* (to 0x0267 state 283) */, 0x08, /* fail */ /* pos 0109: 84 */ 0xBA /* ':' -> */, /* pos 010a: 85 */ 0x00, 0x0A /* - terminal marker 10 - */, @@ -173,7 +173,7 @@ /* pos 0131: 113 */ 0xB1 /* '1' -> */, /* pos 0132: 114 */ 0xAE /* '.' -> */, /* pos 0133: 115 */ 0x31 /* '1' */, 0x07, 0x00 /* (to 0x013A state 116) */, - 0x30 /* '0' */, 0x1B, 0x03 /* (to 0x0451 state 660) */, + 0x30 /* '0' */, 0x1E, 0x03 /* (to 0x0454 state 660) */, 0x08, /* fail */ /* pos 013a: 116 */ 0xA0 /* ' ' -> */, /* pos 013b: 117 */ 0x00, 0x0F /* - terminal marker 15 - */, @@ -190,8 +190,8 @@ /* pos 0147: 128 */ 0x00, 0x10 /* - terminal marker 16 - */, /* pos 0149: 129 */ 0x63 /* 'c' */, 0x0D, 0x00 /* (to 0x0156 state 130) */, 0x75 /* 'u' */, 0xAC, 0x00 /* (to 0x01F8 state 230) */, - 0x67 /* 'g' */, 0x7D, 0x01 /* (to 0x02CC state 358) */, - 0x6C /* 'l' */, 0x7E, 0x01 /* (to 0x02D0 state 361) */, + 0x67 /* 'g' */, 0x80, 0x01 /* (to 0x02CF state 358) */, + 0x6C /* 'l' */, 0x81, 0x01 /* (to 0x02D3 state 361) */, 0x08, /* fail */ /* pos 0156: 130 */ 0xE3 /* 'c' -> */, /* pos 0157: 131 */ 0xE5 /* 'e' -> */, @@ -214,7 +214,7 @@ /* pos 0171: 144 */ 0xEC /* 'l' -> */, /* pos 0172: 145 */ 0xAD /* '-' -> */, /* pos 0173: 146 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x017A state 147) */, - 0x61 /* 'a' */, 0x48, 0x01 /* (to 0x02BE state 345) */, + 0x61 /* 'a' */, 0x4B, 0x01 /* (to 0x02C1 state 345) */, 0x08, /* fail */ /* pos 017a: 147 */ 0xE5 /* 'e' -> */, /* pos 017b: 148 */ 0xF1 /* 'q' -> */, @@ -236,11 +236,11 @@ /* pos 018c: 164 */ 0xAD /* '-' -> */, /* pos 018d: 165 */ 0x6D /* 'm' */, 0x0D, 0x00 /* (to 0x019A state 166) */, 0x6E /* 'n' */, 0x20, 0x00 /* (to 0x01B0 state 181) */, - 0x72 /* 'r' */, 0x9E, 0x01 /* (to 0x0331 state 435) */, - 0x75 /* 'u' */, 0xA2, 0x01 /* (to 0x0338 state 441) */, + 0x72 /* 'r' */, 0xA1, 0x01 /* (to 0x0334 state 435) */, + 0x75 /* 'u' */, 0xA5, 0x01 /* (to 0x033B state 441) */, 0x08, /* fail */ /* pos 019a: 166 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x01A1 state 167) */, - 0x61 /* 'a' */, 0x8E, 0x01 /* (to 0x032B state 430) */, + 0x61 /* 'a' */, 0x91, 0x01 /* (to 0x032E state 430) */, 0x08, /* fail */ /* pos 01a1: 167 */ 0xE4 /* 'd' -> */, /* pos 01a2: 168 */ 0xE9 /* 'i' -> */, @@ -269,8 +269,8 @@ /* pos 01ba: 191 */ 0x00, 0x14 /* - terminal marker 20 - */, /* pos 01bc: 192 */ 0x65 /* 'e' */, 0x0D, 0x00 /* (to 0x01C9 state 193) */, 0x6C /* 'l' */, 0x14, 0x00 /* (to 0x01D3 state 202) */, - 0x63 /* 'c' */, 0xEB, 0x00 /* (to 0x02AD state 330) */, - 0x72 /* 'r' */, 0xF1, 0x00 /* (to 0x02B6 state 338) */, + 0x63 /* 'c' */, 0xEE, 0x00 /* (to 0x02B0 state 330) */, + 0x72 /* 'r' */, 0xF4, 0x00 /* (to 0x02B9 state 338) */, 0x08, /* fail */ /* pos 01c9: 193 */ 0xEE /* 'n' -> */, /* pos 01ca: 194 */ 0xE3 /* 'c' -> */, @@ -291,7 +291,7 @@ /* pos 01da: 209 */ 0xBA /* ':' -> */, /* pos 01db: 210 */ 0x00, 0x16 /* - terminal marker 22 - */, /* pos 01dd: 211 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x01E4 state 212) */, - 0x6F /* 'o' */, 0x9E, 0x01 /* (to 0x037E state 497) */, + 0x6F /* 'o' */, 0xA1, 0x01 /* (to 0x0381 state 497) */, 0x08, /* fail */ /* pos 01e4: 212 */ 0xE7 /* 'g' -> */, /* pos 01e5: 213 */ 0xED /* 'm' -> */, @@ -335,13 +335,13 @@ /* pos 020f: 251 */ 0xAD /* '-' -> */, /* pos 0210: 252 */ 0x6C /* 'l' */, 0x10, 0x00 /* (to 0x0220 state 253) */, 0x74 /* 't' */, 0x1E, 0x00 /* (to 0x0231 state 260) */, - 0x64 /* 'd' */, 0xC0, 0x00 /* (to 0x02D6 state 366) */, - 0x65 /* 'e' */, 0xCA, 0x00 /* (to 0x02E3 state 378) */, - 0x72 /* 'r' */, 0xE3, 0x00 /* (to 0x02FF state 403) */, + 0x64 /* 'd' */, 0xC3, 0x00 /* (to 0x02D9 state 366) */, + 0x65 /* 'e' */, 0xCD, 0x00 /* (to 0x02E6 state 378) */, + 0x72 /* 'r' */, 0xE6, 0x00 /* (to 0x0302 state 403) */, 0x08, /* fail */ /* pos 0220: 253 */ 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x022A state 254) */, - 0x61 /* 'a' */, 0xCA, 0x00 /* (to 0x02ED state 387) */, - 0x6F /* 'o' */, 0xD0, 0x00 /* (to 0x02F6 state 395) */, + 0x61 /* 'a' */, 0xCD, 0x00 /* (to 0x02F0 state 387) */, + 0x6F /* 'o' */, 0xD3, 0x00 /* (to 0x02F9 state 395) */, 0x08, /* fail */ /* pos 022a: 254 */ 0xEE /* 'n' -> */, /* pos 022b: 255 */ 0xE7 /* 'g' -> */, @@ -355,7 +355,7 @@ /* pos 0234: 263 */ 0xBA /* ':' -> */, /* pos 0235: 264 */ 0x00, 0x1C /* - terminal marker 28 - */, /* pos 0237: 265 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x023E state 266) */, - 0x65 /* 'e' */, 0xF6, 0x01 /* (to 0x0430 state 637) */, + 0x65 /* 'e' */, 0xF9, 0x01 /* (to 0x0433 state 637) */, 0x08, /* fail */ /* pos 023e: 266 */ 0xF4 /* 't' -> */, /* pos 023f: 267 */ 0xE5 /* 'e' -> */, @@ -369,437 +369,449 @@ /* pos 024c: 273 */ 0xE5 /* 'e' -> */, /* pos 024d: 274 */ 0xBA /* ':' -> */, /* pos 024e: 275 */ 0x00, 0x1E /* - terminal marker 30 - */, -/* pos 0250: 276 */ 0x66 /* 'f' */, 0x07, 0x00 /* (to 0x0257 state 277) */, - 0x74 /* 't' */, 0x5A, 0x01 /* (to 0x03AD state 529) */, +/* pos 0250: 276 */ 0x66 /* 'f' */, 0x0A, 0x00 /* (to 0x025A state 277) */, + 0x74 /* 't' */, 0x5D, 0x01 /* (to 0x03B0 state 529) */, + 0x70 /* 'p' */, 0x19, 0x02 /* (to 0x046F state 682) */, 0x08, /* fail */ -/* pos 0257: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x025E state 278) */, - 0x72 /* 'r' */, 0x4D, 0x01 /* (to 0x03A7 state 524) */, +/* pos 025a: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0261 state 278) */, + 0x72 /* 'r' */, 0x4D, 0x01 /* (to 0x03AA state 524) */, 0x08, /* fail */ -/* pos 025e: 278 */ 0xF2 /* 'r' -> */, -/* pos 025f: 279 */ 0xE5 /* 'e' -> */, -/* pos 0260: 280 */ 0xF2 /* 'r' -> */, -/* pos 0261: 281 */ 0xBA /* ':' -> */, -/* pos 0262: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, -/* pos 0264: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, -/* pos 0266: 284 */ 0xE5 /* 'e' -> */, -/* pos 0267: 285 */ 0xF2 /* 'r' -> */, -/* pos 0268: 286 */ 0xF3 /* 's' -> */, -/* pos 0269: 287 */ 0xE9 /* 'i' -> */, -/* pos 026a: 288 */ 0xEF /* 'o' -> */, -/* pos 026b: 289 */ 0xEE /* 'n' -> */, -/* pos 026c: 290 */ 0xBA /* ':' -> */, -/* pos 026d: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, -/* pos 026f: 292 */ 0xF2 /* 'r' -> */, -/* pos 0270: 293 */ 0xE9 /* 'i' -> */, -/* pos 0271: 294 */ 0xE7 /* 'g' -> */, -/* pos 0272: 295 */ 0xE9 /* 'i' -> */, -/* pos 0273: 296 */ 0xEE /* 'n' -> */, -/* pos 0274: 297 */ 0xBA /* ':' -> */, -/* pos 0275: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, -/* pos 0277: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0284 state 300) */, - 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x028E state 309) */, - 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0295 state 315) */, - 0x73 /* 's' */, 0x1A, 0x00 /* (to 0x029A state 319) */, +/* pos 0261: 278 */ 0xF2 /* 'r' -> */, +/* pos 0262: 279 */ 0xE5 /* 'e' -> */, +/* pos 0263: 280 */ 0xF2 /* 'r' -> */, +/* pos 0264: 281 */ 0xBA /* ':' -> */, +/* pos 0265: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, +/* pos 0267: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, +/* pos 0269: 284 */ 0xE5 /* 'e' -> */, +/* pos 026a: 285 */ 0xF2 /* 'r' -> */, +/* pos 026b: 286 */ 0xF3 /* 's' -> */, +/* pos 026c: 287 */ 0xE9 /* 'i' -> */, +/* pos 026d: 288 */ 0xEF /* 'o' -> */, +/* pos 026e: 289 */ 0xEE /* 'n' -> */, +/* pos 026f: 290 */ 0xBA /* ':' -> */, +/* pos 0270: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, +/* pos 0272: 292 */ 0xF2 /* 'r' -> */, +/* pos 0273: 293 */ 0xE9 /* 'i' -> */, +/* pos 0274: 294 */ 0xE7 /* 'g' -> */, +/* pos 0275: 295 */ 0xE9 /* 'i' -> */, +/* pos 0276: 296 */ 0xEE /* 'n' -> */, +/* pos 0277: 297 */ 0xBA /* ':' -> */, +/* pos 0278: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, +/* pos 027a: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0287 state 300) */, + 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x0291 state 309) */, + 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0298 state 315) */, + 0x73 /* 's' */, 0x1A, 0x00 /* (to 0x029D state 319) */, 0x08, /* fail */ -/* pos 0284: 300 */ 0xF5 /* 'u' -> */, -/* pos 0285: 301 */ 0xF4 /* 't' -> */, -/* pos 0286: 302 */ 0xE8 /* 'h' -> */, -/* pos 0287: 303 */ 0xEF /* 'o' -> */, -/* pos 0288: 304 */ 0xF2 /* 'r' -> */, -/* pos 0289: 305 */ 0xE9 /* 'i' -> */, -/* pos 028a: 306 */ 0xF4 /* 't' -> */, -/* pos 028b: 307 */ 0xF9 /* 'y' -> */, -/* pos 028c: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, -/* pos 028e: 309 */ 0xE5 /* 'e' -> */, -/* pos 028f: 310 */ 0xF4 /* 't' -> */, -/* pos 0290: 311 */ 0xE8 /* 'h' -> */, -/* pos 0291: 312 */ 0xEF /* 'o' -> */, -/* pos 0292: 313 */ 0xE4 /* 'd' -> */, -/* pos 0293: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, -/* pos 0295: 315 */ 0xE1 /* 'a' -> */, -/* pos 0296: 316 */ 0xF4 /* 't' -> */, -/* pos 0297: 317 */ 0xE8 /* 'h' -> */, -/* pos 0298: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, -/* pos 029a: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x02A1 state 320) */, - 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02A7 state 325) */, +/* pos 0287: 300 */ 0xF5 /* 'u' -> */, +/* pos 0288: 301 */ 0xF4 /* 't' -> */, +/* pos 0289: 302 */ 0xE8 /* 'h' -> */, +/* pos 028a: 303 */ 0xEF /* 'o' -> */, +/* pos 028b: 304 */ 0xF2 /* 'r' -> */, +/* pos 028c: 305 */ 0xE9 /* 'i' -> */, +/* pos 028d: 306 */ 0xF4 /* 't' -> */, +/* pos 028e: 307 */ 0xF9 /* 'y' -> */, +/* pos 028f: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, +/* pos 0291: 309 */ 0xE5 /* 'e' -> */, +/* pos 0292: 310 */ 0xF4 /* 't' -> */, +/* pos 0293: 311 */ 0xE8 /* 'h' -> */, +/* pos 0294: 312 */ 0xEF /* 'o' -> */, +/* pos 0295: 313 */ 0xE4 /* 'd' -> */, +/* pos 0296: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, +/* pos 0298: 315 */ 0xE1 /* 'a' -> */, +/* pos 0299: 316 */ 0xF4 /* 't' -> */, +/* pos 029a: 317 */ 0xE8 /* 'h' -> */, +/* pos 029b: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, +/* pos 029d: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x02A4 state 320) */, + 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02AA state 325) */, 0x08, /* fail */ -/* pos 02a1: 320 */ 0xE8 /* 'h' -> */, -/* pos 02a2: 321 */ 0xE5 /* 'e' -> */, -/* pos 02a3: 322 */ 0xED /* 'm' -> */, -/* pos 02a4: 323 */ 0xE5 /* 'e' -> */, -/* pos 02a5: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, -/* pos 02a7: 325 */ 0xE1 /* 'a' -> */, -/* pos 02a8: 326 */ 0xF4 /* 't' -> */, -/* pos 02a9: 327 */ 0xF5 /* 'u' -> */, -/* pos 02aa: 328 */ 0xF3 /* 's' -> */, -/* pos 02ab: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, -/* pos 02ad: 330 */ 0xE8 /* 'h' -> */, -/* pos 02ae: 331 */ 0xE1 /* 'a' -> */, -/* pos 02af: 332 */ 0xF2 /* 'r' -> */, -/* pos 02b0: 333 */ 0xF3 /* 's' -> */, -/* pos 02b1: 334 */ 0xE5 /* 'e' -> */, -/* pos 02b2: 335 */ 0xF4 /* 't' -> */, -/* pos 02b3: 336 */ 0xBA /* ':' -> */, -/* pos 02b4: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, -/* pos 02b6: 338 */ 0xE1 /* 'a' -> */, -/* pos 02b7: 339 */ 0xEE /* 'n' -> */, -/* pos 02b8: 340 */ 0xE7 /* 'g' -> */, -/* pos 02b9: 341 */ 0xE5 /* 'e' -> */, -/* pos 02ba: 342 */ 0xF3 /* 's' -> */, -/* pos 02bb: 343 */ 0xBA /* ':' -> */, -/* pos 02bc: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, -/* pos 02be: 345 */ 0xEC /* 'l' -> */, -/* pos 02bf: 346 */ 0xEC /* 'l' -> */, -/* pos 02c0: 347 */ 0xEF /* 'o' -> */, -/* pos 02c1: 348 */ 0xF7 /* 'w' -> */, -/* pos 02c2: 349 */ 0xAD /* '-' -> */, -/* pos 02c3: 350 */ 0xEF /* 'o' -> */, -/* pos 02c4: 351 */ 0xF2 /* 'r' -> */, -/* pos 02c5: 352 */ 0xE9 /* 'i' -> */, -/* pos 02c6: 353 */ 0xE7 /* 'g' -> */, -/* pos 02c7: 354 */ 0xE9 /* 'i' -> */, -/* pos 02c8: 355 */ 0xEE /* 'n' -> */, -/* pos 02c9: 356 */ 0xBA /* ':' -> */, -/* pos 02ca: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, -/* pos 02cc: 358 */ 0xE5 /* 'e' -> */, -/* pos 02cd: 359 */ 0xBA /* ':' -> */, -/* pos 02ce: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, -/* pos 02d0: 361 */ 0xEC /* 'l' -> */, -/* pos 02d1: 362 */ 0xEF /* 'o' -> */, -/* pos 02d2: 363 */ 0xF7 /* 'w' -> */, -/* pos 02d3: 364 */ 0xBA /* ':' -> */, -/* pos 02d4: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, -/* pos 02d6: 366 */ 0xE9 /* 'i' -> */, -/* pos 02d7: 367 */ 0xF3 /* 's' -> */, -/* pos 02d8: 368 */ 0xF0 /* 'p' -> */, -/* pos 02d9: 369 */ 0xEF /* 'o' -> */, -/* pos 02da: 370 */ 0xF3 /* 's' -> */, -/* pos 02db: 371 */ 0xE9 /* 'i' -> */, -/* pos 02dc: 372 */ 0xF4 /* 't' -> */, -/* pos 02dd: 373 */ 0xE9 /* 'i' -> */, -/* pos 02de: 374 */ 0xEF /* 'o' -> */, -/* pos 02df: 375 */ 0xEE /* 'n' -> */, -/* pos 02e0: 376 */ 0xBA /* ':' -> */, -/* pos 02e1: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, -/* pos 02e3: 378 */ 0xEE /* 'n' -> */, -/* pos 02e4: 379 */ 0xE3 /* 'c' -> */, -/* pos 02e5: 380 */ 0xEF /* 'o' -> */, -/* pos 02e6: 381 */ 0xE4 /* 'd' -> */, -/* pos 02e7: 382 */ 0xE9 /* 'i' -> */, -/* pos 02e8: 383 */ 0xEE /* 'n' -> */, -/* pos 02e9: 384 */ 0xE7 /* 'g' -> */, -/* pos 02ea: 385 */ 0xBA /* ':' -> */, -/* pos 02eb: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, -/* pos 02ed: 387 */ 0xEE /* 'n' -> */, -/* pos 02ee: 388 */ 0xE7 /* 'g' -> */, -/* pos 02ef: 389 */ 0xF5 /* 'u' -> */, -/* pos 02f0: 390 */ 0xE1 /* 'a' -> */, -/* pos 02f1: 391 */ 0xE7 /* 'g' -> */, -/* pos 02f2: 392 */ 0xE5 /* 'e' -> */, -/* pos 02f3: 393 */ 0xBA /* ':' -> */, -/* pos 02f4: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, -/* pos 02f6: 395 */ 0xE3 /* 'c' -> */, -/* pos 02f7: 396 */ 0xE1 /* 'a' -> */, -/* pos 02f8: 397 */ 0xF4 /* 't' -> */, -/* pos 02f9: 398 */ 0xE9 /* 'i' -> */, -/* pos 02fa: 399 */ 0xEF /* 'o' -> */, -/* pos 02fb: 400 */ 0xEE /* 'n' -> */, -/* pos 02fc: 401 */ 0xBA /* ':' -> */, -/* pos 02fd: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, -/* pos 02ff: 403 */ 0xE1 /* 'a' -> */, -/* pos 0300: 404 */ 0xEE /* 'n' -> */, -/* pos 0301: 405 */ 0xE7 /* 'g' -> */, -/* pos 0302: 406 */ 0xE5 /* 'e' -> */, -/* pos 0303: 407 */ 0xBA /* ':' -> */, -/* pos 0304: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, -/* pos 0306: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x030D state 410) */, - 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x0312 state 414) */, +/* pos 02a4: 320 */ 0xE8 /* 'h' -> */, +/* pos 02a5: 321 */ 0xE5 /* 'e' -> */, +/* pos 02a6: 322 */ 0xED /* 'm' -> */, +/* pos 02a7: 323 */ 0xE5 /* 'e' -> */, +/* pos 02a8: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, +/* pos 02aa: 325 */ 0xE1 /* 'a' -> */, +/* pos 02ab: 326 */ 0xF4 /* 't' -> */, +/* pos 02ac: 327 */ 0xF5 /* 'u' -> */, +/* pos 02ad: 328 */ 0xF3 /* 's' -> */, +/* pos 02ae: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, +/* pos 02b0: 330 */ 0xE8 /* 'h' -> */, +/* pos 02b1: 331 */ 0xE1 /* 'a' -> */, +/* pos 02b2: 332 */ 0xF2 /* 'r' -> */, +/* pos 02b3: 333 */ 0xF3 /* 's' -> */, +/* pos 02b4: 334 */ 0xE5 /* 'e' -> */, +/* pos 02b5: 335 */ 0xF4 /* 't' -> */, +/* pos 02b6: 336 */ 0xBA /* ':' -> */, +/* pos 02b7: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, +/* pos 02b9: 338 */ 0xE1 /* 'a' -> */, +/* pos 02ba: 339 */ 0xEE /* 'n' -> */, +/* pos 02bb: 340 */ 0xE7 /* 'g' -> */, +/* pos 02bc: 341 */ 0xE5 /* 'e' -> */, +/* pos 02bd: 342 */ 0xF3 /* 's' -> */, +/* pos 02be: 343 */ 0xBA /* ':' -> */, +/* pos 02bf: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, +/* pos 02c1: 345 */ 0xEC /* 'l' -> */, +/* pos 02c2: 346 */ 0xEC /* 'l' -> */, +/* pos 02c3: 347 */ 0xEF /* 'o' -> */, +/* pos 02c4: 348 */ 0xF7 /* 'w' -> */, +/* pos 02c5: 349 */ 0xAD /* '-' -> */, +/* pos 02c6: 350 */ 0xEF /* 'o' -> */, +/* pos 02c7: 351 */ 0xF2 /* 'r' -> */, +/* pos 02c8: 352 */ 0xE9 /* 'i' -> */, +/* pos 02c9: 353 */ 0xE7 /* 'g' -> */, +/* pos 02ca: 354 */ 0xE9 /* 'i' -> */, +/* pos 02cb: 355 */ 0xEE /* 'n' -> */, +/* pos 02cc: 356 */ 0xBA /* ':' -> */, +/* pos 02cd: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, +/* pos 02cf: 358 */ 0xE5 /* 'e' -> */, +/* pos 02d0: 359 */ 0xBA /* ':' -> */, +/* pos 02d1: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, +/* pos 02d3: 361 */ 0xEC /* 'l' -> */, +/* pos 02d4: 362 */ 0xEF /* 'o' -> */, +/* pos 02d5: 363 */ 0xF7 /* 'w' -> */, +/* pos 02d6: 364 */ 0xBA /* ':' -> */, +/* pos 02d7: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, +/* pos 02d9: 366 */ 0xE9 /* 'i' -> */, +/* pos 02da: 367 */ 0xF3 /* 's' -> */, +/* pos 02db: 368 */ 0xF0 /* 'p' -> */, +/* pos 02dc: 369 */ 0xEF /* 'o' -> */, +/* pos 02dd: 370 */ 0xF3 /* 's' -> */, +/* pos 02de: 371 */ 0xE9 /* 'i' -> */, +/* pos 02df: 372 */ 0xF4 /* 't' -> */, +/* pos 02e0: 373 */ 0xE9 /* 'i' -> */, +/* pos 02e1: 374 */ 0xEF /* 'o' -> */, +/* pos 02e2: 375 */ 0xEE /* 'n' -> */, +/* pos 02e3: 376 */ 0xBA /* ':' -> */, +/* pos 02e4: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, +/* pos 02e6: 378 */ 0xEE /* 'n' -> */, +/* pos 02e7: 379 */ 0xE3 /* 'c' -> */, +/* pos 02e8: 380 */ 0xEF /* 'o' -> */, +/* pos 02e9: 381 */ 0xE4 /* 'd' -> */, +/* pos 02ea: 382 */ 0xE9 /* 'i' -> */, +/* pos 02eb: 383 */ 0xEE /* 'n' -> */, +/* pos 02ec: 384 */ 0xE7 /* 'g' -> */, +/* pos 02ed: 385 */ 0xBA /* ':' -> */, +/* pos 02ee: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, +/* pos 02f0: 387 */ 0xEE /* 'n' -> */, +/* pos 02f1: 388 */ 0xE7 /* 'g' -> */, +/* pos 02f2: 389 */ 0xF5 /* 'u' -> */, +/* pos 02f3: 390 */ 0xE1 /* 'a' -> */, +/* pos 02f4: 391 */ 0xE7 /* 'g' -> */, +/* pos 02f5: 392 */ 0xE5 /* 'e' -> */, +/* pos 02f6: 393 */ 0xBA /* ':' -> */, +/* pos 02f7: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, +/* pos 02f9: 395 */ 0xE3 /* 'c' -> */, +/* pos 02fa: 396 */ 0xE1 /* 'a' -> */, +/* pos 02fb: 397 */ 0xF4 /* 't' -> */, +/* pos 02fc: 398 */ 0xE9 /* 'i' -> */, +/* pos 02fd: 399 */ 0xEF /* 'o' -> */, +/* pos 02fe: 400 */ 0xEE /* 'n' -> */, +/* pos 02ff: 401 */ 0xBA /* ':' -> */, +/* pos 0300: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, +/* pos 0302: 403 */ 0xE1 /* 'a' -> */, +/* pos 0303: 404 */ 0xEE /* 'n' -> */, +/* pos 0304: 405 */ 0xE7 /* 'g' -> */, +/* pos 0305: 406 */ 0xE5 /* 'e' -> */, +/* pos 0306: 407 */ 0xBA /* ':' -> */, +/* pos 0307: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, +/* pos 0309: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x0310 state 410) */, + 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x0315 state 414) */, 0x08, /* fail */ -/* pos 030d: 410 */ 0xE1 /* 'a' -> */, -/* pos 030e: 411 */ 0xE7 /* 'g' -> */, -/* pos 030f: 412 */ 0xBA /* ':' -> */, -/* pos 0310: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, -/* pos 0312: 414 */ 0xF0 /* 'p' -> */, -/* pos 0313: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x031A state 416) */, - 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x031F state 420) */, +/* pos 0310: 410 */ 0xE1 /* 'a' -> */, +/* pos 0311: 411 */ 0xE7 /* 'g' -> */, +/* pos 0312: 412 */ 0xBA /* ':' -> */, +/* pos 0313: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, +/* pos 0315: 414 */ 0xF0 /* 'p' -> */, +/* pos 0316: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x031D state 416) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0322 state 420) */, 0x08, /* fail */ -/* pos 031a: 416 */ 0xE3 /* 'c' -> */, -/* pos 031b: 417 */ 0xF4 /* 't' -> */, -/* pos 031c: 418 */ 0xBA /* ':' -> */, -/* pos 031d: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, -/* pos 031f: 420 */ 0xF2 /* 'r' -> */, -/* pos 0320: 421 */ 0xE5 /* 'e' -> */, -/* pos 0321: 422 */ 0xF3 /* 's' -> */, -/* pos 0322: 423 */ 0xBA /* ':' -> */, -/* pos 0323: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, -/* pos 0325: 425 */ 0xF2 /* 'r' -> */, -/* pos 0326: 426 */ 0xEF /* 'o' -> */, -/* pos 0327: 427 */ 0xED /* 'm' -> */, -/* pos 0328: 428 */ 0xBA /* ':' -> */, -/* pos 0329: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, -/* pos 032b: 430 */ 0xF4 /* 't' -> */, -/* pos 032c: 431 */ 0xE3 /* 'c' -> */, -/* pos 032d: 432 */ 0xE8 /* 'h' -> */, -/* pos 032e: 433 */ 0xBA /* ':' -> */, -/* pos 032f: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, -/* pos 0331: 435 */ 0xE1 /* 'a' -> */, -/* pos 0332: 436 */ 0xEE /* 'n' -> */, -/* pos 0333: 437 */ 0xE7 /* 'g' -> */, -/* pos 0334: 438 */ 0xE5 /* 'e' -> */, -/* pos 0335: 439 */ 0xBA /* ':' -> */, -/* pos 0336: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, -/* pos 0338: 441 */ 0xEE /* 'n' -> */, -/* pos 0339: 442 */ 0xED /* 'm' -> */, -/* pos 033a: 443 */ 0xEF /* 'o' -> */, -/* pos 033b: 444 */ 0xE4 /* 'd' -> */, -/* pos 033c: 445 */ 0xE9 /* 'i' -> */, -/* pos 033d: 446 */ 0xE6 /* 'f' -> */, -/* pos 033e: 447 */ 0xE9 /* 'i' -> */, -/* pos 033f: 448 */ 0xE5 /* 'e' -> */, -/* pos 0340: 449 */ 0xE4 /* 'd' -> */, -/* pos 0341: 450 */ 0xAD /* '-' -> */, -/* pos 0342: 451 */ 0xF3 /* 's' -> */, -/* pos 0343: 452 */ 0xE9 /* 'i' -> */, -/* pos 0344: 453 */ 0xEE /* 'n' -> */, -/* pos 0345: 454 */ 0xE3 /* 'c' -> */, -/* pos 0346: 455 */ 0xE5 /* 'e' -> */, -/* pos 0347: 456 */ 0xBA /* ':' -> */, -/* pos 0348: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, -/* pos 034a: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x0354 state 459) */, - 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x0362 state 472) */, - 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x0367 state 476) */, +/* pos 031d: 416 */ 0xE3 /* 'c' -> */, +/* pos 031e: 417 */ 0xF4 /* 't' -> */, +/* pos 031f: 418 */ 0xBA /* ':' -> */, +/* pos 0320: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, +/* pos 0322: 420 */ 0xF2 /* 'r' -> */, +/* pos 0323: 421 */ 0xE5 /* 'e' -> */, +/* pos 0324: 422 */ 0xF3 /* 's' -> */, +/* pos 0325: 423 */ 0xBA /* ':' -> */, +/* pos 0326: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, +/* pos 0328: 425 */ 0xF2 /* 'r' -> */, +/* pos 0329: 426 */ 0xEF /* 'o' -> */, +/* pos 032a: 427 */ 0xED /* 'm' -> */, +/* pos 032b: 428 */ 0xBA /* ':' -> */, +/* pos 032c: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, +/* pos 032e: 430 */ 0xF4 /* 't' -> */, +/* pos 032f: 431 */ 0xE3 /* 'c' -> */, +/* pos 0330: 432 */ 0xE8 /* 'h' -> */, +/* pos 0331: 433 */ 0xBA /* ':' -> */, +/* pos 0332: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, +/* pos 0334: 435 */ 0xE1 /* 'a' -> */, +/* pos 0335: 436 */ 0xEE /* 'n' -> */, +/* pos 0336: 437 */ 0xE7 /* 'g' -> */, +/* pos 0337: 438 */ 0xE5 /* 'e' -> */, +/* pos 0338: 439 */ 0xBA /* ':' -> */, +/* pos 0339: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, +/* pos 033b: 441 */ 0xEE /* 'n' -> */, +/* pos 033c: 442 */ 0xED /* 'm' -> */, +/* pos 033d: 443 */ 0xEF /* 'o' -> */, +/* pos 033e: 444 */ 0xE4 /* 'd' -> */, +/* pos 033f: 445 */ 0xE9 /* 'i' -> */, +/* pos 0340: 446 */ 0xE6 /* 'f' -> */, +/* pos 0341: 447 */ 0xE9 /* 'i' -> */, +/* pos 0342: 448 */ 0xE5 /* 'e' -> */, +/* pos 0343: 449 */ 0xE4 /* 'd' -> */, +/* pos 0344: 450 */ 0xAD /* '-' -> */, +/* pos 0345: 451 */ 0xF3 /* 's' -> */, +/* pos 0346: 452 */ 0xE9 /* 'i' -> */, +/* pos 0347: 453 */ 0xEE /* 'n' -> */, +/* pos 0348: 454 */ 0xE3 /* 'c' -> */, +/* pos 0349: 455 */ 0xE5 /* 'e' -> */, +/* pos 034a: 456 */ 0xBA /* ':' -> */, +/* pos 034b: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, +/* pos 034d: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x0357 state 459) */, + 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x0365 state 472) */, + 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x036A state 476) */, 0x08, /* fail */ -/* pos 0354: 459 */ 0xF3 /* 's' -> */, -/* pos 0355: 460 */ 0xF4 /* 't' -> */, -/* pos 0356: 461 */ 0xAD /* '-' -> */, -/* pos 0357: 462 */ 0xED /* 'm' -> */, -/* pos 0358: 463 */ 0xEF /* 'o' -> */, -/* pos 0359: 464 */ 0xE4 /* 'd' -> */, -/* pos 035a: 465 */ 0xE9 /* 'i' -> */, -/* pos 035b: 466 */ 0xE6 /* 'f' -> */, -/* pos 035c: 467 */ 0xE9 /* 'i' -> */, -/* pos 035d: 468 */ 0xE5 /* 'e' -> */, -/* pos 035e: 469 */ 0xE4 /* 'd' -> */, -/* pos 035f: 470 */ 0xBA /* ':' -> */, -/* pos 0360: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, -/* pos 0362: 472 */ 0xEE /* 'n' -> */, -/* pos 0363: 473 */ 0xEB /* 'k' -> */, -/* pos 0364: 474 */ 0xBA /* ':' -> */, -/* pos 0365: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, -/* pos 0367: 476 */ 0xE3 /* 'c' -> */, -/* pos 0368: 477 */ 0xE1 /* 'a' -> */, -/* pos 0369: 478 */ 0xF4 /* 't' -> */, -/* pos 036a: 479 */ 0xE9 /* 'i' -> */, -/* pos 036b: 480 */ 0xEF /* 'o' -> */, -/* pos 036c: 481 */ 0xEE /* 'n' -> */, -/* pos 036d: 482 */ 0xBA /* ':' -> */, -/* pos 036e: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, -/* pos 0370: 484 */ 0xE1 /* 'a' -> */, -/* pos 0371: 485 */ 0xF8 /* 'x' -> */, -/* pos 0372: 486 */ 0xAD /* '-' -> */, -/* pos 0373: 487 */ 0xE6 /* 'f' -> */, -/* pos 0374: 488 */ 0xEF /* 'o' -> */, -/* pos 0375: 489 */ 0xF2 /* 'r' -> */, -/* pos 0376: 490 */ 0xF7 /* 'w' -> */, -/* pos 0377: 491 */ 0xE1 /* 'a' -> */, -/* pos 0378: 492 */ 0xF2 /* 'r' -> */, -/* pos 0379: 493 */ 0xE4 /* 'd' -> */, -/* pos 037a: 494 */ 0xF3 /* 's' -> */, -/* pos 037b: 495 */ 0xBA /* ':' -> */, -/* pos 037c: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, -/* pos 037e: 497 */ 0xF8 /* 'x' -> */, -/* pos 037f: 498 */ 0xF9 /* 'y' -> */, -/* pos 0380: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x0387 state 500) */, - 0x20 /* ' ' */, 0xBB, 0x00 /* (to 0x043E state 649) */, +/* pos 0357: 459 */ 0xF3 /* 's' -> */, +/* pos 0358: 460 */ 0xF4 /* 't' -> */, +/* pos 0359: 461 */ 0xAD /* '-' -> */, +/* pos 035a: 462 */ 0xED /* 'm' -> */, +/* pos 035b: 463 */ 0xEF /* 'o' -> */, +/* pos 035c: 464 */ 0xE4 /* 'd' -> */, +/* pos 035d: 465 */ 0xE9 /* 'i' -> */, +/* pos 035e: 466 */ 0xE6 /* 'f' -> */, +/* pos 035f: 467 */ 0xE9 /* 'i' -> */, +/* pos 0360: 468 */ 0xE5 /* 'e' -> */, +/* pos 0361: 469 */ 0xE4 /* 'd' -> */, +/* pos 0362: 470 */ 0xBA /* ':' -> */, +/* pos 0363: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, +/* pos 0365: 472 */ 0xEE /* 'n' -> */, +/* pos 0366: 473 */ 0xEB /* 'k' -> */, +/* pos 0367: 474 */ 0xBA /* ':' -> */, +/* pos 0368: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, +/* pos 036a: 476 */ 0xE3 /* 'c' -> */, +/* pos 036b: 477 */ 0xE1 /* 'a' -> */, +/* pos 036c: 478 */ 0xF4 /* 't' -> */, +/* pos 036d: 479 */ 0xE9 /* 'i' -> */, +/* pos 036e: 480 */ 0xEF /* 'o' -> */, +/* pos 036f: 481 */ 0xEE /* 'n' -> */, +/* pos 0370: 482 */ 0xBA /* ':' -> */, +/* pos 0371: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, +/* pos 0373: 484 */ 0xE1 /* 'a' -> */, +/* pos 0374: 485 */ 0xF8 /* 'x' -> */, +/* pos 0375: 486 */ 0xAD /* '-' -> */, +/* pos 0376: 487 */ 0xE6 /* 'f' -> */, +/* pos 0377: 488 */ 0xEF /* 'o' -> */, +/* pos 0378: 489 */ 0xF2 /* 'r' -> */, +/* pos 0379: 490 */ 0xF7 /* 'w' -> */, +/* pos 037a: 491 */ 0xE1 /* 'a' -> */, +/* pos 037b: 492 */ 0xF2 /* 'r' -> */, +/* pos 037c: 493 */ 0xE4 /* 'd' -> */, +/* pos 037d: 494 */ 0xF3 /* 's' -> */, +/* pos 037e: 495 */ 0xBA /* ':' -> */, +/* pos 037f: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, +/* pos 0381: 497 */ 0xF8 /* 'x' -> */, +/* pos 0382: 498 */ 0xF9 /* 'y' -> */, +/* pos 0383: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x038A state 500) */, + 0x20 /* ' ' */, 0xBB, 0x00 /* (to 0x0441 state 649) */, 0x08, /* fail */ -/* pos 0387: 500 */ 0xE1 /* 'a' -> */, -/* pos 0388: 501 */ 0xF5 /* 'u' -> */, -/* pos 0389: 502 */ 0xF4 /* 't' -> */, -/* pos 038a: 503 */ 0xE8 /* 'h' -> */, -/* pos 038b: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0392 state 505) */, - 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x039C state 514) */, +/* pos 038a: 500 */ 0xE1 /* 'a' -> */, +/* pos 038b: 501 */ 0xF5 /* 'u' -> */, +/* pos 038c: 502 */ 0xF4 /* 't' -> */, +/* pos 038d: 503 */ 0xE8 /* 'h' -> */, +/* pos 038e: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0395 state 505) */, + 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x039F state 514) */, 0x08, /* fail */ -/* pos 0392: 505 */ 0xEE /* 'n' -> */, -/* pos 0393: 506 */ 0xF4 /* 't' -> */, -/* pos 0394: 507 */ 0xE9 /* 'i' -> */, -/* pos 0395: 508 */ 0xE3 /* 'c' -> */, -/* pos 0396: 509 */ 0xE1 /* 'a' -> */, -/* pos 0397: 510 */ 0xF4 /* 't' -> */, -/* pos 0398: 511 */ 0xE5 /* 'e' -> */, -/* pos 0399: 512 */ 0xBA /* ':' -> */, -/* pos 039a: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, -/* pos 039c: 514 */ 0xF2 /* 'r' -> */, -/* pos 039d: 515 */ 0xE9 /* 'i' -> */, -/* pos 039e: 516 */ 0xFA /* 'z' -> */, -/* pos 039f: 517 */ 0xE1 /* 'a' -> */, -/* pos 03a0: 518 */ 0xF4 /* 't' -> */, -/* pos 03a1: 519 */ 0xE9 /* 'i' -> */, -/* pos 03a2: 520 */ 0xEF /* 'o' -> */, -/* pos 03a3: 521 */ 0xEE /* 'n' -> */, -/* pos 03a4: 522 */ 0xBA /* ':' -> */, -/* pos 03a5: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, -/* pos 03a7: 524 */ 0xE5 /* 'e' -> */, -/* pos 03a8: 525 */ 0xF3 /* 's' -> */, -/* pos 03a9: 526 */ 0xE8 /* 'h' -> */, -/* pos 03aa: 527 */ 0xBA /* ':' -> */, -/* pos 03ab: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, -/* pos 03ad: 529 */ 0xF2 /* 'r' -> */, -/* pos 03ae: 530 */ 0xF9 /* 'y' -> */, -/* pos 03af: 531 */ 0xAD /* '-' -> */, -/* pos 03b0: 532 */ 0xE1 /* 'a' -> */, -/* pos 03b1: 533 */ 0xE6 /* 'f' -> */, -/* pos 03b2: 534 */ 0xF4 /* 't' -> */, -/* pos 03b3: 535 */ 0xE5 /* 'e' -> */, -/* pos 03b4: 536 */ 0xF2 /* 'r' -> */, -/* pos 03b5: 537 */ 0xBA /* ':' -> */, -/* pos 03b6: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, -/* pos 03b8: 539 */ 0xF6 /* 'v' -> */, -/* pos 03b9: 540 */ 0xE5 /* 'e' -> */, -/* pos 03ba: 541 */ 0xF2 /* 'r' -> */, -/* pos 03bb: 542 */ 0xBA /* ':' -> */, -/* pos 03bc: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, -/* pos 03be: 544 */ 0xAD /* '-' -> */, -/* pos 03bf: 545 */ 0xE3 /* 'c' -> */, -/* pos 03c0: 546 */ 0xEF /* 'o' -> */, -/* pos 03c1: 547 */ 0xEF /* 'o' -> */, -/* pos 03c2: 548 */ 0xEB /* 'k' -> */, -/* pos 03c3: 549 */ 0xE9 /* 'i' -> */, -/* pos 03c4: 550 */ 0xE5 /* 'e' -> */, -/* pos 03c5: 551 */ 0xBA /* ':' -> */, -/* pos 03c6: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, -/* pos 03c8: 553 */ 0xF2 /* 'r' -> */, -/* pos 03c9: 554 */ 0xE9 /* 'i' -> */, -/* pos 03ca: 555 */ 0xE3 /* 'c' -> */, -/* pos 03cb: 556 */ 0xF4 /* 't' -> */, -/* pos 03cc: 557 */ 0xAD /* '-' -> */, -/* pos 03cd: 558 */ 0xF4 /* 't' -> */, -/* pos 03ce: 559 */ 0xF2 /* 'r' -> */, -/* pos 03cf: 560 */ 0xE1 /* 'a' -> */, -/* pos 03d0: 561 */ 0xEE /* 'n' -> */, -/* pos 03d1: 562 */ 0xF3 /* 's' -> */, -/* pos 03d2: 563 */ 0xF0 /* 'p' -> */, -/* pos 03d3: 564 */ 0xEF /* 'o' -> */, -/* pos 03d4: 565 */ 0xF2 /* 'r' -> */, -/* pos 03d5: 566 */ 0xF4 /* 't' -> */, -/* pos 03d6: 567 */ 0xAD /* '-' -> */, -/* pos 03d7: 568 */ 0xF3 /* 's' -> */, -/* pos 03d8: 569 */ 0xE5 /* 'e' -> */, -/* pos 03d9: 570 */ 0xE3 /* 'c' -> */, -/* pos 03da: 571 */ 0xF5 /* 'u' -> */, -/* pos 03db: 572 */ 0xF2 /* 'r' -> */, -/* pos 03dc: 573 */ 0xE9 /* 'i' -> */, -/* pos 03dd: 574 */ 0xF4 /* 't' -> */, -/* pos 03de: 575 */ 0xF9 /* 'y' -> */, -/* pos 03df: 576 */ 0xBA /* ':' -> */, -/* pos 03e0: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, -/* pos 03e2: 578 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x03E9 state 579) */, - 0x65 /* 'e' */, 0x84, 0x00 /* (to 0x0469 state 680) */, +/* pos 0395: 505 */ 0xEE /* 'n' -> */, +/* pos 0396: 506 */ 0xF4 /* 't' -> */, +/* pos 0397: 507 */ 0xE9 /* 'i' -> */, +/* pos 0398: 508 */ 0xE3 /* 'c' -> */, +/* pos 0399: 509 */ 0xE1 /* 'a' -> */, +/* pos 039a: 510 */ 0xF4 /* 't' -> */, +/* pos 039b: 511 */ 0xE5 /* 'e' -> */, +/* pos 039c: 512 */ 0xBA /* ':' -> */, +/* pos 039d: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, +/* pos 039f: 514 */ 0xF2 /* 'r' -> */, +/* pos 03a0: 515 */ 0xE9 /* 'i' -> */, +/* pos 03a1: 516 */ 0xFA /* 'z' -> */, +/* pos 03a2: 517 */ 0xE1 /* 'a' -> */, +/* pos 03a3: 518 */ 0xF4 /* 't' -> */, +/* pos 03a4: 519 */ 0xE9 /* 'i' -> */, +/* pos 03a5: 520 */ 0xEF /* 'o' -> */, +/* pos 03a6: 521 */ 0xEE /* 'n' -> */, +/* pos 03a7: 522 */ 0xBA /* ':' -> */, +/* pos 03a8: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, +/* pos 03aa: 524 */ 0xE5 /* 'e' -> */, +/* pos 03ab: 525 */ 0xF3 /* 's' -> */, +/* pos 03ac: 526 */ 0xE8 /* 'h' -> */, +/* pos 03ad: 527 */ 0xBA /* ':' -> */, +/* pos 03ae: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, +/* pos 03b0: 529 */ 0xF2 /* 'r' -> */, +/* pos 03b1: 530 */ 0xF9 /* 'y' -> */, +/* pos 03b2: 531 */ 0xAD /* '-' -> */, +/* pos 03b3: 532 */ 0xE1 /* 'a' -> */, +/* pos 03b4: 533 */ 0xE6 /* 'f' -> */, +/* pos 03b5: 534 */ 0xF4 /* 't' -> */, +/* pos 03b6: 535 */ 0xE5 /* 'e' -> */, +/* pos 03b7: 536 */ 0xF2 /* 'r' -> */, +/* pos 03b8: 537 */ 0xBA /* ':' -> */, +/* pos 03b9: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, +/* pos 03bb: 539 */ 0xF6 /* 'v' -> */, +/* pos 03bc: 540 */ 0xE5 /* 'e' -> */, +/* pos 03bd: 541 */ 0xF2 /* 'r' -> */, +/* pos 03be: 542 */ 0xBA /* ':' -> */, +/* pos 03bf: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, +/* pos 03c1: 544 */ 0xAD /* '-' -> */, +/* pos 03c2: 545 */ 0xE3 /* 'c' -> */, +/* pos 03c3: 546 */ 0xEF /* 'o' -> */, +/* pos 03c4: 547 */ 0xEF /* 'o' -> */, +/* pos 03c5: 548 */ 0xEB /* 'k' -> */, +/* pos 03c6: 549 */ 0xE9 /* 'i' -> */, +/* pos 03c7: 550 */ 0xE5 /* 'e' -> */, +/* pos 03c8: 551 */ 0xBA /* ':' -> */, +/* pos 03c9: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, +/* pos 03cb: 553 */ 0xF2 /* 'r' -> */, +/* pos 03cc: 554 */ 0xE9 /* 'i' -> */, +/* pos 03cd: 555 */ 0xE3 /* 'c' -> */, +/* pos 03ce: 556 */ 0xF4 /* 't' -> */, +/* pos 03cf: 557 */ 0xAD /* '-' -> */, +/* pos 03d0: 558 */ 0xF4 /* 't' -> */, +/* pos 03d1: 559 */ 0xF2 /* 'r' -> */, +/* pos 03d2: 560 */ 0xE1 /* 'a' -> */, +/* pos 03d3: 561 */ 0xEE /* 'n' -> */, +/* pos 03d4: 562 */ 0xF3 /* 's' -> */, +/* pos 03d5: 563 */ 0xF0 /* 'p' -> */, +/* pos 03d6: 564 */ 0xEF /* 'o' -> */, +/* pos 03d7: 565 */ 0xF2 /* 'r' -> */, +/* pos 03d8: 566 */ 0xF4 /* 't' -> */, +/* pos 03d9: 567 */ 0xAD /* '-' -> */, +/* pos 03da: 568 */ 0xF3 /* 's' -> */, +/* pos 03db: 569 */ 0xE5 /* 'e' -> */, +/* pos 03dc: 570 */ 0xE3 /* 'c' -> */, +/* pos 03dd: 571 */ 0xF5 /* 'u' -> */, +/* pos 03de: 572 */ 0xF2 /* 'r' -> */, +/* pos 03df: 573 */ 0xE9 /* 'i' -> */, +/* pos 03e0: 574 */ 0xF4 /* 't' -> */, +/* pos 03e1: 575 */ 0xF9 /* 'y' -> */, +/* pos 03e2: 576 */ 0xBA /* ':' -> */, +/* pos 03e3: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, +/* pos 03e5: 578 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x03EC state 579) */, + 0x65 /* 'e' */, 0x84, 0x00 /* (to 0x046C state 680) */, 0x08, /* fail */ -/* pos 03e9: 579 */ 0xE1 /* 'a' -> */, -/* pos 03ea: 580 */ 0xEE /* 'n' -> */, -/* pos 03eb: 581 */ 0xF3 /* 's' -> */, -/* pos 03ec: 582 */ 0xE6 /* 'f' -> */, -/* pos 03ed: 583 */ 0xE5 /* 'e' -> */, -/* pos 03ee: 584 */ 0xF2 /* 'r' -> */, -/* pos 03ef: 585 */ 0xAD /* '-' -> */, -/* pos 03f0: 586 */ 0xE5 /* 'e' -> */, -/* pos 03f1: 587 */ 0xEE /* 'n' -> */, -/* pos 03f2: 588 */ 0xE3 /* 'c' -> */, -/* pos 03f3: 589 */ 0xEF /* 'o' -> */, -/* pos 03f4: 590 */ 0xE4 /* 'd' -> */, -/* pos 03f5: 591 */ 0xE9 /* 'i' -> */, -/* pos 03f6: 592 */ 0xEE /* 'n' -> */, -/* pos 03f7: 593 */ 0xE7 /* 'g' -> */, -/* pos 03f8: 594 */ 0xBA /* ':' -> */, -/* pos 03f9: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, -/* pos 03fb: 596 */ 0xE5 /* 'e' -> */, -/* pos 03fc: 597 */ 0xF2 /* 'r' -> */, -/* pos 03fd: 598 */ 0xAD /* '-' -> */, -/* pos 03fe: 599 */ 0xE1 /* 'a' -> */, -/* pos 03ff: 600 */ 0xE7 /* 'g' -> */, -/* pos 0400: 601 */ 0xE5 /* 'e' -> */, -/* pos 0401: 602 */ 0xEE /* 'n' -> */, -/* pos 0402: 603 */ 0xF4 /* 't' -> */, -/* pos 0403: 604 */ 0xBA /* ':' -> */, -/* pos 0404: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, -/* pos 0406: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x040D state 607) */, - 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0412 state 611) */, +/* pos 03ec: 579 */ 0xE1 /* 'a' -> */, +/* pos 03ed: 580 */ 0xEE /* 'n' -> */, +/* pos 03ee: 581 */ 0xF3 /* 's' -> */, +/* pos 03ef: 582 */ 0xE6 /* 'f' -> */, +/* pos 03f0: 583 */ 0xE5 /* 'e' -> */, +/* pos 03f1: 584 */ 0xF2 /* 'r' -> */, +/* pos 03f2: 585 */ 0xAD /* '-' -> */, +/* pos 03f3: 586 */ 0xE5 /* 'e' -> */, +/* pos 03f4: 587 */ 0xEE /* 'n' -> */, +/* pos 03f5: 588 */ 0xE3 /* 'c' -> */, +/* pos 03f6: 589 */ 0xEF /* 'o' -> */, +/* pos 03f7: 590 */ 0xE4 /* 'd' -> */, +/* pos 03f8: 591 */ 0xE9 /* 'i' -> */, +/* pos 03f9: 592 */ 0xEE /* 'n' -> */, +/* pos 03fa: 593 */ 0xE7 /* 'g' -> */, +/* pos 03fb: 594 */ 0xBA /* ':' -> */, +/* pos 03fc: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, +/* pos 03fe: 596 */ 0xE5 /* 'e' -> */, +/* pos 03ff: 597 */ 0xF2 /* 'r' -> */, +/* pos 0400: 598 */ 0xAD /* '-' -> */, +/* pos 0401: 599 */ 0xE1 /* 'a' -> */, +/* pos 0402: 600 */ 0xE7 /* 'g' -> */, +/* pos 0403: 601 */ 0xE5 /* 'e' -> */, +/* pos 0404: 602 */ 0xEE /* 'n' -> */, +/* pos 0405: 603 */ 0xF4 /* 't' -> */, +/* pos 0406: 604 */ 0xBA /* ':' -> */, +/* pos 0407: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, +/* pos 0409: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x0410 state 607) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0415 state 611) */, 0x08, /* fail */ -/* pos 040d: 607 */ 0xF2 /* 'r' -> */, -/* pos 040e: 608 */ 0xF9 /* 'y' -> */, -/* pos 040f: 609 */ 0xBA /* ':' -> */, -/* pos 0410: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, -/* pos 0412: 611 */ 0xE1 /* 'a' -> */, -/* pos 0413: 612 */ 0xBA /* ':' -> */, -/* pos 0414: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, -/* pos 0416: 614 */ 0xF7 /* 'w' -> */, -/* pos 0417: 615 */ 0xF7 /* 'w' -> */, -/* pos 0418: 616 */ 0xAD /* '-' -> */, -/* pos 0419: 617 */ 0xE1 /* 'a' -> */, -/* pos 041a: 618 */ 0xF5 /* 'u' -> */, -/* pos 041b: 619 */ 0xF4 /* 't' -> */, -/* pos 041c: 620 */ 0xE8 /* 'h' -> */, -/* pos 041d: 621 */ 0xE5 /* 'e' -> */, -/* pos 041e: 622 */ 0xEE /* 'n' -> */, -/* pos 041f: 623 */ 0xF4 /* 't' -> */, -/* pos 0420: 624 */ 0xE9 /* 'i' -> */, -/* pos 0421: 625 */ 0xE3 /* 'c' -> */, -/* pos 0422: 626 */ 0xE1 /* 'a' -> */, -/* pos 0423: 627 */ 0xF4 /* 't' -> */, -/* pos 0424: 628 */ 0xE5 /* 'e' -> */, -/* pos 0425: 629 */ 0xBA /* ':' -> */, -/* pos 0426: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, -/* pos 0428: 631 */ 0xF4 /* 't' -> */, -/* pos 0429: 632 */ 0xE3 /* 'c' -> */, -/* pos 042a: 633 */ 0xE8 /* 'h' -> */, -/* pos 042b: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, -/* pos 042d: 635 */ 0xF4 /* 't' -> */, -/* pos 042e: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, -/* pos 0430: 637 */ 0xEC /* 'l' -> */, -/* pos 0431: 638 */ 0xE5 /* 'e' -> */, -/* pos 0432: 639 */ 0xF4 /* 't' -> */, -/* pos 0433: 640 */ 0xE5 /* 'e' -> */, -/* pos 0434: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, -/* pos 0436: 642 */ 0xE9 /* 'i' -> */, -/* pos 0437: 643 */ 0xAD /* '-' -> */, -/* pos 0438: 644 */ 0xE1 /* 'a' -> */, -/* pos 0439: 645 */ 0xF2 /* 'r' -> */, -/* pos 043a: 646 */ 0xE7 /* 'g' -> */, -/* pos 043b: 647 */ 0xF3 /* 's' -> */, -/* pos 043c: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, -/* pos 043e: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, -/* pos 0440: 650 */ 0xAD /* '-' -> */, -/* pos 0441: 651 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x0448 state 652) */, - 0x66 /* 'f' */, 0x10, 0x00 /* (to 0x0454 state 662) */, +/* pos 0410: 607 */ 0xF2 /* 'r' -> */, +/* pos 0411: 608 */ 0xF9 /* 'y' -> */, +/* pos 0412: 609 */ 0xBA /* ':' -> */, +/* pos 0413: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, +/* pos 0415: 611 */ 0xE1 /* 'a' -> */, +/* pos 0416: 612 */ 0xBA /* ':' -> */, +/* pos 0417: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, +/* pos 0419: 614 */ 0xF7 /* 'w' -> */, +/* pos 041a: 615 */ 0xF7 /* 'w' -> */, +/* pos 041b: 616 */ 0xAD /* '-' -> */, +/* pos 041c: 617 */ 0xE1 /* 'a' -> */, +/* pos 041d: 618 */ 0xF5 /* 'u' -> */, +/* pos 041e: 619 */ 0xF4 /* 't' -> */, +/* pos 041f: 620 */ 0xE8 /* 'h' -> */, +/* pos 0420: 621 */ 0xE5 /* 'e' -> */, +/* pos 0421: 622 */ 0xEE /* 'n' -> */, +/* pos 0422: 623 */ 0xF4 /* 't' -> */, +/* pos 0423: 624 */ 0xE9 /* 'i' -> */, +/* pos 0424: 625 */ 0xE3 /* 'c' -> */, +/* pos 0425: 626 */ 0xE1 /* 'a' -> */, +/* pos 0426: 627 */ 0xF4 /* 't' -> */, +/* pos 0427: 628 */ 0xE5 /* 'e' -> */, +/* pos 0428: 629 */ 0xBA /* ':' -> */, +/* pos 0429: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, +/* pos 042b: 631 */ 0xF4 /* 't' -> */, +/* pos 042c: 632 */ 0xE3 /* 'c' -> */, +/* pos 042d: 633 */ 0xE8 /* 'h' -> */, +/* pos 042e: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, +/* pos 0430: 635 */ 0xF4 /* 't' -> */, +/* pos 0431: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, +/* pos 0433: 637 */ 0xEC /* 'l' -> */, +/* pos 0434: 638 */ 0xE5 /* 'e' -> */, +/* pos 0435: 639 */ 0xF4 /* 't' -> */, +/* pos 0436: 640 */ 0xE5 /* 'e' -> */, +/* pos 0437: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, +/* pos 0439: 642 */ 0xE9 /* 'i' -> */, +/* pos 043a: 643 */ 0xAD /* '-' -> */, +/* pos 043b: 644 */ 0xE1 /* 'a' -> */, +/* pos 043c: 645 */ 0xF2 /* 'r' -> */, +/* pos 043d: 646 */ 0xE7 /* 'g' -> */, +/* pos 043e: 647 */ 0xF3 /* 's' -> */, +/* pos 043f: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, +/* pos 0441: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, +/* pos 0443: 650 */ 0xAD /* '-' -> */, +/* pos 0444: 651 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x044B state 652) */, + 0x66 /* 'f' */, 0x10, 0x00 /* (to 0x0457 state 662) */, 0x08, /* fail */ -/* pos 0448: 652 */ 0xE5 /* 'e' -> */, -/* pos 0449: 653 */ 0xE1 /* 'a' -> */, -/* pos 044a: 654 */ 0xEC /* 'l' -> */, -/* pos 044b: 655 */ 0xAD /* '-' -> */, -/* pos 044c: 656 */ 0xE9 /* 'i' -> */, -/* pos 044d: 657 */ 0xF0 /* 'p' -> */, -/* pos 044e: 658 */ 0xBA /* ':' -> */, -/* pos 044f: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, -/* pos 0451: 660 */ 0xA0 /* ' ' -> */, -/* pos 0452: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, -/* pos 0454: 662 */ 0xEF /* 'o' -> */, -/* pos 0455: 663 */ 0xF2 /* 'r' -> */, -/* pos 0456: 664 */ 0xF7 /* 'w' -> */, -/* pos 0457: 665 */ 0xE1 /* 'a' -> */, -/* pos 0458: 666 */ 0xF2 /* 'r' -> */, -/* pos 0459: 667 */ 0xE4 /* 'd' -> */, -/* pos 045a: 668 */ 0xE5 /* 'e' -> */, -/* pos 045b: 669 */ 0xE4 /* 'd' -> */, -/* pos 045c: 670 */ 0xAD /* '-' -> */, -/* pos 045d: 671 */ 0xE6 /* 'f' -> */, -/* pos 045e: 672 */ 0xEF /* 'o' -> */, -/* pos 045f: 673 */ 0xF2 /* 'r' -> */, -/* pos 0460: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, -/* pos 0462: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, -/* pos 0464: 676 */ 0xE1 /* 'a' -> */, -/* pos 0465: 677 */ 0xE4 /* 'd' -> */, -/* pos 0466: 678 */ 0xA0 /* ' ' -> */, -/* pos 0467: 679 */ 0x00, 0x52 /* - terminal marker 82 - */, -/* pos 0469: 680 */ 0xBA /* ':' -> */, -/* pos 046a: 681 */ 0x00, 0x53 /* - terminal marker 83 - */, -/* total size 1132 bytes */ +/* pos 044b: 652 */ 0xE5 /* 'e' -> */, +/* pos 044c: 653 */ 0xE1 /* 'a' -> */, +/* pos 044d: 654 */ 0xEC /* 'l' -> */, +/* pos 044e: 655 */ 0xAD /* '-' -> */, +/* pos 044f: 656 */ 0xE9 /* 'i' -> */, +/* pos 0450: 657 */ 0xF0 /* 'p' -> */, +/* pos 0451: 658 */ 0xBA /* ':' -> */, +/* pos 0452: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, +/* pos 0454: 660 */ 0xA0 /* ' ' -> */, +/* pos 0455: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, +/* pos 0457: 662 */ 0xEF /* 'o' -> */, +/* pos 0458: 663 */ 0xF2 /* 'r' -> */, +/* pos 0459: 664 */ 0xF7 /* 'w' -> */, +/* pos 045a: 665 */ 0xE1 /* 'a' -> */, +/* pos 045b: 666 */ 0xF2 /* 'r' -> */, +/* pos 045c: 667 */ 0xE4 /* 'd' -> */, +/* pos 045d: 668 */ 0xE5 /* 'e' -> */, +/* pos 045e: 669 */ 0xE4 /* 'd' -> */, +/* pos 045f: 670 */ 0xAD /* '-' -> */, +/* pos 0460: 671 */ 0xE6 /* 'f' -> */, +/* pos 0461: 672 */ 0xEF /* 'o' -> */, +/* pos 0462: 673 */ 0xF2 /* 'r' -> */, +/* pos 0463: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, +/* pos 0465: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, +/* pos 0467: 676 */ 0xE1 /* 'a' -> */, +/* pos 0468: 677 */ 0xE4 /* 'd' -> */, +/* pos 0469: 678 */ 0xA0 /* ' ' -> */, +/* pos 046a: 679 */ 0x00, 0x52 /* - terminal marker 82 - */, +/* pos 046c: 680 */ 0xBA /* ':' -> */, +/* pos 046d: 681 */ 0x00, 0x53 /* - terminal marker 83 - */, +/* pos 046f: 682 */ 0xEC /* 'l' -> */, +/* pos 0470: 683 */ 0xE1 /* 'a' -> */, +/* pos 0471: 684 */ 0xF9 /* 'y' -> */, +/* pos 0472: 685 */ 0xAD /* '-' -> */, +/* pos 0473: 686 */ 0xEE /* 'n' -> */, +/* pos 0474: 687 */ 0xEF /* 'o' -> */, +/* pos 0475: 688 */ 0xEE /* 'n' -> */, +/* pos 0476: 689 */ 0xE3 /* 'c' -> */, +/* pos 0477: 690 */ 0xE5 /* 'e' -> */, +/* pos 0478: 691 */ 0xBA /* ':' -> */, +/* pos 0479: 692 */ 0x00, 0x54 /* - terminal marker 84 - */, +/* total size 1147 bytes */ diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index cebbb9bc..df232cd3 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -3858,6 +3858,8 @@ enum lws_token_indexes { WSI_TOKEN_CONNECT = 81, WSI_TOKEN_HEAD_URI = 82, WSI_TOKEN_TE = 83, + WSI_TOKEN_REPLAY_NONCE = 84, + /****** add new things just above ---^ ******/ /* use token storage to stash these internally, not for @@ -5583,7 +5585,13 @@ enum { LWS_TLS_REQ_ELEMENT_COMMON_NAME, LWS_TLS_REQ_ELEMENT_EMAIL, - LWS_TLS_REQ_ELEMENT_COUNT + LWS_TLS_REQ_ELEMENT_COUNT, + LWS_TLS_SET_DIR_URL = LWS_TLS_REQ_ELEMENT_COUNT, + LWS_TLS_SET_AUTH_PATH, + LWS_TLS_SET_CERT_PATH, + LWS_TLS_SET_KEY_PATH, + + LWS_TLS_TOTAL_COUNT }; /** diff --git a/lib/plat/lws-plat-esp32.c b/lib/plat/lws-plat-esp32.c index 88ab7682..210368f8 100644 --- a/lib/plat/lws-plat-esp32.c +++ b/lib/plat/lws-plat-esp32.c @@ -1773,3 +1773,26 @@ uint16_t lws_esp32_sine_interp(int n) sine_lu((n >> 4) + 1) * (n & 15)) / 15; } +/* we write vhostname.cert.pem and vhostname.key.pem, 0 return means OK */ + +int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + char name[64]; + int n; + + lws_snprintf(name, sizeof(name) - 1, "%s-%s.pem", vhost->name, + is_key ? "key" : "cert"); + + if (nvs_open("lws-station", NVS_READWRITE, &nvh)) + return 1; + + n = nvs_set_blob(nvh, ssl_names[n], pss->buffer, pss->file_length); + if (n) + nvs_commit(nvh); + + nvs_close(nvh); + + return n; +} diff --git a/lib/plat/lws-plat-esp8266.c b/lib/plat/lws-plat-esp8266.c index 60623e3d..7a1e3b34 100644 --- a/lib/plat/lws-plat-esp8266.c +++ b/lib/plat/lws-plat-esp8266.c @@ -706,3 +706,11 @@ lws_plat_init(struct lws_context *context, return 0; } + +int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + return 1; +} +} diff --git a/lib/plat/lws-plat-optee.c b/lib/plat/lws-plat-optee.c index 4863ba05..524046b5 100644 --- a/lib/plat/lws-plat-optee.c +++ b/lib/plat/lws-plat-optee.c @@ -319,3 +319,10 @@ lws_plat_init(struct lws_context *context, return 0; } + +int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + return 1; +} diff --git a/lib/plat/lws-plat-unix.c b/lib/plat/lws-plat-unix.c index 82935421..d64afa0e 100644 --- a/lib/plat/lws-plat-unix.c +++ b/lib/plat/lws-plat-unix.c @@ -863,3 +863,17 @@ lws_plat_init(struct lws_context *context, return 0; } + +int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + int n; + + n = write(fd, buf, len); + + fsync(fd); + lseek(fd, 0, SEEK_SET); + + return n != len; +} diff --git a/lib/plat/lws-plat-win.c b/lib/plat/lws-plat-win.c index 7cbb7df7..b140c6ee 100644 --- a/lib/plat/lws-plat-win.c +++ b/lib/plat/lws-plat-win.c @@ -749,3 +749,15 @@ int fork(void) exit(0); } +int +lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf, + int len) +{ + int n; + + n = write(fd, buf, len); + + lseek(fd, 0, SEEK_SET); + + return n != len; +} diff --git a/lib/tls/mbedtls/server.c b/lib/tls/mbedtls/server.c index af35bbfe..33efc87c 100644 --- a/lib/tls/mbedtls/server.c +++ b/lib/tls/mbedtls/server.c @@ -78,7 +78,7 @@ lws_mbedtls_sni_cb(void *arg, mbedtls_ssl_context *mbedtls_ctx, return 0; } - lwsl_notice("SNI: Found: %s:%d\n", servername, vh->listen_port); + lwsl_info("SNI: Found: %s:%d\n", servername, vh->listen_port); /* select the ssl ctx from the selected vhost for this conn */ SSL_set_SSL_CTX(ssl, vhost->ssl_ctx); diff --git a/plugins/acme-client/protocol_lws_acme_client.c b/plugins/acme-client/protocol_lws_acme_client.c new file mode 100644 index 00000000..aa0ca798 --- /dev/null +++ b/plugins/acme-client/protocol_lws_acme_client.c @@ -0,0 +1,1465 @@ +/* + * libwebsockets ACME client protocol plugin + * + * Copyright (C) 2017 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * + * Acme is in a big messy transition at the moment from a homebrewed api + * to an IETF one. The old repo for the homebrew api (they currently + * implement) is marked up as deprecated and "not accurate[ly] reflect[ing]" + * what they implement, but the IETF standard, currently at v7 is not yet + * implemented at let's encrypt (ETA Jan 2018). + * + * This implementation follows draft 7 of the IETF standard, and falls back + * to whatever differences exist for Boulder's tls-sni-01 challenge. The + * tls-sni-02 support is there but nothing to test it against at the time of + * writing (Nov 1 2017). + */ + +#if !defined (LWS_PLUGIN_STATIC) +#define LWS_DLL +#define LWS_INTERNAL +#include "../lib/libwebsockets.h" +#endif + +#include +#include + +typedef enum { + ACME_STATE_DIRECTORY, /* get the directory JSON using GET + parse */ + ACME_STATE_NEW_REG, /* register a new RSA key + email combo */ + ACME_STATE_NEW_AUTH, /* start the process to request a cert */ + ACME_STATE_ACCEPT_CHALL, /* notify server ready for one challenge */ + ACME_STATE_POLLING, /* he should be trying our challenge */ + ACME_STATE_POLLING_CSR, /* sent CSR, checking result */ + + ACME_STATE_FINISHED +} lws_acme_state; + +struct acme_connection { + char buf[4096]; + char replay_nonce[64]; + char chall_token[64]; + char challenge_uri[256]; + char status[16]; + char san_a[100]; + char san_b[100]; + char urls[6][100]; /* directory contents */ + lws_acme_state state; + struct lws_client_connect_info i; + struct lejp_ctx jctx; + struct lws_context_creation_info ci; + struct lws_vhost *vhost; + + struct lws *cwsi; + + const char *real_vh_name; + const char *real_vh_iface; + + char *alloc_privkey_pem; + + char *dest; + int pos; + int len; + int resp; + int cpos; + + int real_vh_port; + int goes_around; + + size_t len_privkey_pem; + + unsigned int yes:2; + unsigned int use:1; + unsigned int is_sni_02:1; +}; + +struct per_vhost_data__lws_acme_client { + struct lws_context *context; + struct lws_vhost *vhost; + const struct lws_protocols *protocol; + + /* + * the vhd is allocated for every vhost using the plugin. + * But ac is only allocated when we are doing the server auth. + */ + struct acme_connection *ac; + + struct lws_jwk jwk; + struct lws_genrsa_ctx rsactx; + + char *pvo_data; + char *pvop[LWS_TLS_TOTAL_COUNT]; + int count_live_pss; + char *dest; + int pos; + int len; + + int fd_updated_cert; /* these are opened while we have root... */ + int fd_updated_key; /* ...if nonempty next startup will replace old */ +}; + +static int +callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); + +#define LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT \ + { \ + "lws-acme-client", \ + callback_acme_client, \ + 0, \ + 512, \ + 0, NULL, 0 \ + } + +static const struct lws_protocols acme_protocols[] = { + LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT, + { NULL, NULL, 0, 0, 0, NULL, 0 } +}; + +/* directory JSON parsing */ + +static const char * const jdir_tok[] = { + "key-change", + "meta.terms-of-service", + "new-authz", + "new-cert", + "new-reg", + "revoke-cert", +}; +enum enum_jhdr_tok { + JAD_KEY_CHANGE_URL, + JAD_TOS_URL, + JAD_NEW_AUTHZ_URL, + JAD_NEW_CERT_URL, + JAD_NEW_REG_URL, + JAD_REVOKE_CERT_URL, +}; +static signed char +cb_dir(struct lejp_ctx *ctx, char reason) +{ + struct per_vhost_data__lws_acme_client *s = + (struct per_vhost_data__lws_acme_client *)ctx->user; + + if (reason == LEJPCB_VAL_STR_START && ctx->path_match) { + s->pos = 0; + s->len = sizeof(s->ac->urls[0]) - 1; + s->dest = s->ac->urls[ctx->path_match - 1]; + + return 0; + } + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + if (s->pos + ctx->npos > s->len) { + lwsl_notice("url too long\n"); + + return -1; + } + + memcpy(s->dest + s->pos, ctx->buf, ctx->npos); + s->pos += ctx->npos; + s->dest[s->pos] = '\0'; + + return 0; +} + +/* authz JSON parsing */ + +static const char * const jauthz_tok[] = { + "identifier.type", + "identifier.value", + "status", + "expires", + "challenges[].type", + "challenges[].status", + "challenges[].uri", + "challenges[].token", +}; +enum enum_jauthz_tok { + JAAZ_ID_TYPE, + JAAZ_ID_VALUE, + JAAZ_STATUS, + JAAZ_EXPIRES, + JAAZ_CHALLENGES_TYPE, + JAAZ_CHALLENGES_STATUS, + JAAZ_CHALLENGES_URI, + JAAZ_CHALLENGES_TOKEN, +}; +static signed char +cb_authz(struct lejp_ctx *ctx, char reason) +{ + struct acme_connection *s = (struct acme_connection *)ctx->user; + + if (reason == LEJPCB_CONSTRUCTED) { + s->yes = 0; + s->use = 0; + s->chall_token[0] = '\0'; + s->is_sni_02 = 0; + } + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case JAAZ_ID_TYPE: + break; + case JAAZ_ID_VALUE: + break; + case JAAZ_STATUS: + break; + case JAAZ_EXPIRES: + break; + case JAAZ_CHALLENGES_TYPE: + if (s->is_sni_02) + break; + s->use = !strcmp(ctx->buf, "tls-sni-01") || + !strcmp(ctx->buf, "tls-sni-02"); + s->is_sni_02 = !strcmp(ctx->buf, "tls-sni-02"); + break; + case JAAZ_CHALLENGES_STATUS: + strncpy(s->status, ctx->buf, sizeof(s->status) - 1); + break; + case JAAZ_CHALLENGES_URI: + if (s->use) { + strncpy(s->challenge_uri, ctx->buf, + sizeof(s->challenge_uri) - 1); + s->yes |= 2; + } + break; + case JAAZ_CHALLENGES_TOKEN: + lwsl_notice("JAAZ_CHALLENGES_TOKEN: %s %d\n", ctx->buf, s->use); + if (s->use) { + strncpy(s->chall_token, ctx->buf, + sizeof(s->chall_token) - 1); + s->yes |= 1; + } + break; + } + + return 0; +} + +/* challenge accepted JSON parsing */ + +static const char * const jchac_tok[] = { + "type", + "status", + "uri", + "token", +}; +enum enum_jchac_tok { + JCAC_TYPE, + JCAC_STATUS, + JCAC_URI, + JCAC_TOKEN, +}; +static signed char +cb_chac(struct lejp_ctx *ctx, char reason) +{ + struct acme_connection *s = (struct acme_connection *)ctx->user; + + if (reason == LEJPCB_CONSTRUCTED) { + s->yes = 0; + s->use = 0; + } + + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case JCAC_TYPE: + if (strcmp(ctx->buf, "tls-sni-01") && + strcmp(ctx->buf, "tls-sni-02")) + return 1; + break; + case JCAC_STATUS: + strncpy(s->status, ctx->buf, sizeof(s->status) - 1); + break; + case JCAC_URI: + s->yes |= 2; + break; + case JCAC_TOKEN: + strncpy(s->chall_token, ctx->buf, + sizeof(s->chall_token) - 1); + s->yes |= 1; + break; + } + + return 0; +} + +/* https://github.com/letsencrypt/boulder/blob/release/docs/acme-divergences.md + * + * 7.1: + * + * Boulder does not implement the new-order resource. + * Instead of new-order Boulder implements the new-cert resource that is + * defined in draft-ietf-acme-02 Section 6.5. + * + * Boulder also doesn't implement the new-nonce endpoint. + * + * Boulder implements the new-account resource only under the new-reg key. + * + * Boulder implements Link: rel="next" headers from new-reg to new-authz, and + * new-authz to new-cert, as specified in draft-02, but these links are not + * provided in the latest draft, and clients should use URLs from the directory + * instead. + * + * Boulder does not provide the "index" link relation pointing at the + * directory URL. + * + * (ie, just use new-cert instead of new-order, use the directory for links) + */ + + +/* + * Notice: trashes i and url + */ +static struct lws * +lws_acme_client_connect(struct lws_context *context, struct lws_vhost *vh, + struct lws **pwsi, struct lws_client_connect_info *i, + char *url, const char *method) +{ + const char *prot, *p; + char path[200], _url[256]; + + memset(i, 0, sizeof(*i)); + i->port = 443; + strncpy(_url, url, sizeof(_url) - 1); + _url[sizeof(_url) - 1] = '\0'; + if (lws_parse_uri(_url, &prot, &i->address, &i->port, &p)) { + lwsl_err("unable to parse uri %s\n", url); + + return NULL; + } + + /* add back the leading / on path */ + path[0] = '/'; + strncpy(path + 1, p, sizeof(path) - 2); + path[sizeof(path) - 1] = '\0'; + i->path = path; + i->context = context; + i->vhost = vh; + i->ssl_connection = 1; + i->host = i->address; + i->origin = i->address; + i->method = method; + i->pwsi = pwsi; + i->protocol = "lws-acme-client"; + + return lws_client_connect_via_info(i); +} + +static void +lws_acme_finished(struct per_vhost_data__lws_acme_client *vhd) +{ + lwsl_notice("finishing up jws stuff\n"); + + if (vhd->ac) { + if (vhd->ac->vhost) + lws_vhost_destroy(vhd->ac->vhost); + if (vhd->ac->alloc_privkey_pem) + free(vhd->ac->alloc_privkey_pem); + free(vhd->ac); + } + + lws_genrsa_destroy(&vhd->rsactx); + lws_jwk_destroy(&vhd->jwk); + + vhd->ac = NULL; +} + +static const char * const pvo_names[] = { + "country", + "state", + "locality", + "organization", + "common-name", + "email", + "directory-url", + "auth-path", + "cert-path", + "key-path", +}; + +static int +callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_vhost_data__lws_acme_client *vhd = + (struct per_vhost_data__lws_acme_client *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start, + *end = buf + sizeof(buf) - 1, digest[32]; + unsigned char **pp = (unsigned char **)in, *pend = in + len; + const char *content_type = "application/jose+json"; + const struct lws_protocol_vhost_options *pvo; + struct acme_connection *ac = NULL; + struct lws_genhash_ctx hctx; + struct lws *cwsi; + int n, m; + + if (vhd) + ac = vhd->ac; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct per_vhost_data__lws_acme_client)); + vhd->context = lws_get_context(wsi); + vhd->protocol = lws_get_protocol(wsi); + vhd->vhost = lws_get_vhost(wsi); + + /* compute how much we need to hold all the pvo payloads */ + m = 0; + pvo = (const struct lws_protocol_vhost_options *)in; + while (pvo) { + m += strlen(pvo->value) + 1; + pvo = pvo->next; + } + p = vhd->pvo_data = malloc(m); + if (!p) + return -1; + + pvo = (const struct lws_protocol_vhost_options *)in; + while (pvo) { + start = p; + n = strlen(pvo->value) + 1; + memcpy(start, pvo->value, n); + p += n; + + for (m = 0; m < (int)ARRAY_SIZE(pvo_names); m++) + if (!strcmp(pvo->name, pvo_names[m])) + vhd->pvop[m] = start; + + pvo = pvo->next; + } + + n = 0; + for (m = 0; m < (int)ARRAY_SIZE(pvo_names); m++) + if (!vhd->pvop[m] && m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME) { + lwsl_notice("%s: require pvo '%s'\n", __func__, + pvo_names[m]); + n |= 1; + } else + lwsl_info(" %s: %s\n", pvo_names[m], + vhd->pvop[m]); + if (n) { + free(vhd->pvo_data); + vhd->pvo_data = NULL; + + return -1; + } + + /* + * load (or create) the registration keypair while we + * still have root + */ + if (lws_jwk_load(&vhd->jwk, + vhd->pvop[LWS_TLS_SET_AUTH_PATH])) { + strcpy(vhd->jwk.keytype, "RSA"); + n = lws_genrsa_new_keypair(lws_get_context(wsi), + &vhd->rsactx, &vhd->jwk.el, + 4096); + if (n) { + lwsl_notice("failed to create keypair\n"); + + return 1; + } + + if (lws_jwk_save(&vhd->jwk, + vhd->pvop[LWS_TLS_SET_AUTH_PATH])) { + lwsl_notice("unable to save %s\n", + vhd->pvop[LWS_TLS_SET_AUTH_PATH]); + + return 1; + } + } + /* + * in case we do an update, open the update files while we + * still have root + */ + lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", + vhd->pvop[LWS_TLS_SET_CERT_PATH]); + vhd->fd_updated_cert = open(buf, LWS_O_WRONLY | LWS_O_CREAT | + LWS_O_TRUNC, 0600); + if (vhd->fd_updated_cert < 0) { + lwsl_err("unable to create update cert file %s\n", buf); + return -1; + } + lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", + vhd->pvop[LWS_TLS_SET_KEY_PATH]); + vhd->fd_updated_key = open(buf, LWS_O_WRONLY | LWS_O_CREAT | + LWS_O_TRUNC, 0600); + if (vhd->fd_updated_key < 0) { + lwsl_err("unable to create update key file %s\n", buf); + return -1; + } + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + if (vhd && vhd->pvo_data) { + free(vhd->pvo_data); + vhd->pvo_data = NULL; + } + if (vhd) + lws_acme_finished(vhd); + break; + + case LWS_CALLBACK_VHOST_CERT_AGING: + if (!vhd) + break; + /* + * Somebody is telling us about a cert some vhost is using. + * + * First see if the cert is getting close enough to expiry that + * we *want* to do something about it. + */ + if ((int)(ssize_t)len > 14) + break; + /* + * ...is this a vhost we were configured on? + */ + if (vhd->vhost != (struct lws_vhost *)in) + break; + + /* ...and we were given enough info to do the update? */ + + if (!vhd->pvop[LWS_TLS_REQ_ELEMENT_COUNTRY]) + break; + + /* + * ...well... we should try to do something about it then... + */ + lwsl_notice("%s: ACME cert needs updating: " + "vhost %s: %dd left\n", __func__, + lws_get_vhost_name(in), (int)(ssize_t)len); + + vhd->ac = ac = malloc(sizeof(*vhd->ac)); + memset(ac, 0, sizeof(*ac)); + + /* + * So if we don't have it, the first job is get the directory. + * + * If we already have the directory, jump straight into trying + * to register our key. + * + * We always try to register the keys... if it's not the first + * time, we will get a JSON body in the (legal, nonfatal) + * response like this + * + * { + * "type": "urn:acme:error:malformed", + * "detail": "Registration key is already in use", + * "status": 409 + * } + */ + if (!ac->urls[0][0]) { + ac->state = ACME_STATE_DIRECTORY; + lws_snprintf(buf, sizeof(buf) - 1, "%s", + vhd->pvop[LWS_TLS_SET_DIR_URL]); + } else { + ac->state = ACME_STATE_NEW_REG; + lws_snprintf(buf, sizeof(buf) - 1, "%s", + ac->urls[JAD_NEW_REG_URL]); + } + + ac->real_vh_port = lws_get_vhost_port((struct lws_vhost *)in); + ac->real_vh_name = lws_get_vhost_name((struct lws_vhost *)in); + ac->real_vh_iface = lws_get_vhost_iface((struct lws_vhost *)in); + + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, "GET"); + if (!cwsi) { + lwsl_notice("%s: acme connect failed\n", __func__); + free(vhd->ac); + vhd->ac = NULL; + } + break; + + /* + * Client + */ + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + lwsl_notice("%s: CLIENT_ESTABLISHED\n", __func__); + break; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + lwsl_notice("%s: CLIENT_CONNECTION_ERROR\n", __func__); + break; + + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + lwsl_notice("%s: CLOSED_CLIENT_HTTP\n", __func__); + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: + lwsl_notice("lws_http_client_http_response %d\n", + lws_http_client_http_response(wsi)); + if (!ac) + break; + ac->resp = lws_http_client_http_response(wsi); + /* we get a new nonce each time */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_REPLAY_NONCE) && + lws_hdr_copy(wsi, ac->replay_nonce, + sizeof(ac->replay_nonce), + WSI_TOKEN_REPLAY_NONCE) < 0) { + lwsl_notice("%s: nonce too large\n", __func__); + + return -1; + } + + switch (ac->state) { + case ACME_STATE_DIRECTORY: + lejp_construct(&ac->jctx, cb_dir, vhd, jdir_tok, + ARRAY_SIZE(jdir_tok)); + break; + case ACME_STATE_NEW_REG: + break; + case ACME_STATE_NEW_AUTH: + lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok, + ARRAY_SIZE(jauthz_tok)); + break; + + case ACME_STATE_POLLING: + case ACME_STATE_ACCEPT_CHALL: + lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok, + ARRAY_SIZE(jchac_tok)); + break; + + case ACME_STATE_POLLING_CSR: + ac->cpos = 0; + if (ac->resp != 201) + break; + /* + * He acknowledges he will create the cert... + * get the URL to GET it from in the Location + * header. + */ + if (lws_hdr_copy(wsi, ac->challenge_uri, + sizeof(ac->challenge_uri), + WSI_TOKEN_HTTP_LOCATION) < 0) { + lwsl_notice("%s: missing cert location:\n", + __func__); + + goto failed; + } + + lwsl_notice("told to fetch cert from %s\n", + ac->challenge_uri); + break; + + default: + break; + } + break; + + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: + if (!ac) + break; + switch (ac->state) { + + case ACME_STATE_DIRECTORY: + break; + case ACME_STATE_NEW_REG: + p += lws_snprintf(p, end - p, "{" + "\"resource\":\"new-reg\"," + "\"contact\":[" + "\"mailto:%s\"" + "],\"agreement\":\"%s\"" + "}", + vhd->pvop[LWS_TLS_REQ_ELEMENT_EMAIL], + ac->urls[JAD_TOS_URL]); +pkt_add_hdrs: + ac->len = lws_jws_create_packet(&vhd->jwk, + start, p - start, + ac->replay_nonce, + &ac->buf[LWS_PRE], + sizeof(ac->buf) - + LWS_PRE); + if (ac->len < 0) { + ac->len = 0; + lwsl_notice("lws_jws_create_packet failed\n"); + goto failed; + } + ac->pos = 0; + if (ac->state == ACME_STATE_POLLING_CSR) + content_type = "application/pkix-cert"; + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (uint8_t *)content_type, 21, pp, pend)) + goto failed; + + n = sprintf(buf, "%d", ac->len); + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH, + (uint8_t *)buf, n, pp, pend)) + goto failed; + + lws_client_http_body_pending(wsi, 1); + lws_callback_on_writable(wsi); + break; + case ACME_STATE_NEW_AUTH: + p += lws_snprintf(p, end - p, + "{" + "\"resource\":\"new-authz\"," + "\"identifier\":{" + "\"type\":\"http-01\"," + "\"value\":\"%s\"" + "}" + "}", ac->real_vh_name); + goto pkt_add_hdrs; + + case ACME_STATE_ACCEPT_CHALL: + /* + * Several of the challenges in this document makes use + * of a key authorization string. A key authorization + * expresses a domain holder's authorization for a + * specified key to satisfy a specified challenge, by + * concatenating the token for the challenge with a key + * fingerprint, separated by a "." character: + * + * key-authz = token || '.' || + * base64(JWK_Thumbprint(accountKey)) + * + * The "JWK_Thumbprint" step indicates the computation + * specified in [RFC7638], using the SHA-256 digest. As + * specified in the individual challenges below, the + * token for a challenge is a JSON string comprised + * entirely of characters in the base64 alphabet. + * The "||" operator indicates concatenation of strings. + * + * keyAuthorization (required, string): The key + * authorization for this challenge. This value MUST + * match the token from the challenge and the client's + * account key. + * + * draft acme-01 tls-sni-01: + * + * { + * "keyAuthorization": "evaGxfADs...62jcerQ", + * } (Signed as JWS) + * + * draft acme-07 tls-sni-02: + * + * POST /acme/authz/1234/1 + * Host: example.com + * Content-Type: application/jose+json + * + * { + * "protected": base64url({ + * "alg": "ES256", + * "kid": "https://example.com/acme/acct/1", + * "nonce": "JHb54aT_KTXBWQOzGYkt9A", + * "url": "https://example.com/acme/authz/1234/1" + * }), + * "payload": base64url({ + * "keyAuthorization": "evaGxfADs...62jcerQ" + * }), + * "signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4" + * } + * + * On receiving a response, the server MUST verify that + * the key authorization in the response matches the + * "token" value in the challenge and the client's + * account key. If they do not match, then the server + * MUST return an HTTP error in response to the POST + * request in which the client sent the challenge. + */ + + lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); + p = start; + end = &buf[sizeof(buf) - 1]; + + p += lws_snprintf(p, end - p, + "{\"resource\":\"challenge\"," + "\"type\":\"tls-sni-0%d\"," + "\"keyAuthorization\":\"%s.", + 1 + ac->is_sni_02, + ac->chall_token); + n = lws_jws_base64_enc(digest, 32, p, end - p); + if (n < 0) + goto failed; + p += n; + p += lws_snprintf(p, end - p, "\"}"); + puts(start); + goto pkt_add_hdrs; + + case ACME_STATE_POLLING: + break; + + case ACME_STATE_POLLING_CSR: + /* + * "To obtain a certificate for the domain, the agent + * constructs a PKCS#10 Certificate Signing Request that + * asks the Let’s Encrypt CA to issue a certificate for + * example.com with a specified public key. As usual, + * the CSR includes a signature by the private key + * corresponding to the public key in the CSR. The agent + * also signs the whole CSR with the authorized + * key for example.com so that the Let’s Encrypt CA + * knows it’s authorized." + * + * IOW we must create a new RSA keypair which will be + * the cert public + private key, and put the public + * key in the CSR. The CSR, just for transport, is also + * signed with our JWK, showing that as the owner of the + * authorized JWK, the request should be allowed. + * + * The cert comes back with our public key in it showing + * that the owner of the matching private key (we + * created that keypair) is the owner of the cert. + * + * We feed the CSR the elements we want in the cert, + * like the CN etc, and it gives us the b64URL-encoded + * CSR and the PEM-encoded (public +)private key in + * memory buffers. + */ + if (ac->goes_around) + break; + + p += lws_snprintf(p, end - p, + "{\"resource\":\"new-cert\"," + "\"csr\":\""); + n = lws_tls_acme_sni_csr_create(vhd->context, + (const char **) + vhd->pvop, + (uint8_t *)p, end - p, + &ac->alloc_privkey_pem, + &ac->len_privkey_pem); + if (n < 0) { + lwsl_notice("CSR generation failed\n"); + goto failed; + } + p += n; + p += lws_snprintf(p, end - p, "\"}"); + puts(start); + goto pkt_add_hdrs; + + default: + break; + } + break; + + case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: + lwsl_notice("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n"); + if (!ac) + break; + if (ac->pos == ac->len) + break; + + ac->buf[LWS_PRE + ac->len] = '\0'; + if (lws_write(wsi, (uint8_t *)ac->buf + LWS_PRE, + ac->len, LWS_WRITE_HTTP_FINAL) < 0) + return -1; + lwsl_notice("wrote %d\n", ac->len); + ac->pos = ac->len; + lws_client_http_body_pending(wsi, 0); + break; + + /* chunked content */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + if (!ac) + return -1; + switch (ac->state) { + case ACME_STATE_POLLING: + case ACME_STATE_ACCEPT_CHALL: + case ACME_STATE_NEW_AUTH: + case ACME_STATE_DIRECTORY: + ((char *)in)[len] = '\0'; + puts(in); + m = (int)(signed char)lejp_parse(&ac->jctx, + (uint8_t *)in, len); + if (m < 0 && m != LEJP_CONTINUE) { + lwsl_notice("lejp parse failed %d\n", m); + goto failed; + } + break; + case ACME_STATE_NEW_REG: + ((char *)in)[len] = '\0'; + puts(in); + break; + case ACME_STATE_POLLING_CSR: + /* it should be the DER cert! */ + if (ac->cpos + len > sizeof(ac->buf)) { + lwsl_notice("Incoming cert is too large!\n"); + goto failed; + } + memcpy(&ac->buf[ac->cpos], in, len); + ac->cpos += len; + break; + default: + break; + } + break; + + /* unchunked content */ + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + lwsl_notice("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n", __func__); + { + char buffer[2048 + LWS_PRE]; + char *px = buffer + LWS_PRE; + int lenx = sizeof(buffer) - LWS_PRE; + + if (lws_http_client_read(wsi, &px, &lenx) < 0) + return -1; + } + break; + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + lwsl_notice("%s: COMPLETED_CLIENT_HTTP\n", __func__); + + if (!ac) + return -1; + switch (ac->state) { + case ACME_STATE_DIRECTORY: + lejp_destruct(&ac->jctx); + + /* check dir validity */ + + for (n = 0; n < 6; n++) + lwsl_notice(" %d: %s\n", n, ac->urls[n]); + + /* + * So... having the directory now... we try to + * register our keys next. It's OK if it ends up + * they're already registered... this eliminates any + * gaps where we stored the key but registration did + * not complete for some reason... + */ + ac->state = ACME_STATE_NEW_REG; + + strcpy(buf, ac->urls[JAD_NEW_REG_URL]); + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, + "POST"); + if (!cwsi) + lwsl_notice("%s: failed to connect to acme\n", + __func__); + break; + + case ACME_STATE_NEW_REG: + if ((ac->resp >= 200 && ac->resp < 299) || + ac->resp == 409) { + /* + * Our account already existed, or exists now. + * + * Move on to requesting a cert auth. + */ + ac->state = ACME_STATE_NEW_AUTH; + + strcpy(buf, ac->urls[JAD_NEW_AUTHZ_URL]); + cwsi = lws_acme_client_connect(vhd->context, + vhd->vhost, &ac->cwsi, + &ac->i, buf, "POST"); + if (!cwsi) + lwsl_notice("%s: failed to connect\n", + __func__); + break; + } else { + lwsl_notice("new-reg replied %d\n", ac->resp); + goto failed; + } + break; + + case ACME_STATE_NEW_AUTH: + lejp_destruct(&ac->jctx); + lwsl_notice("chall: %s\n", ac->chall_token); + + /* tls-sni-01 ... what a mess. + * The stuff in + * https://tools.ietf.org/html/ + * draft-ietf-acme-acme-01#section-7.3 + * "requires" n but it's missing from let's encrypt + * tls-sni-01 challenge. The go docs say that they just + * implement one hashing round regardless + * https://godoc.org/golang.org/x/crypto/acme + * + * The go way is what is actually implemented today by + * letsencrypt + * + * "A client responds to this challenge by constructing + * a key authorization from the "token" value provided + * in the challenge and the client's account key. The + * client first computes the SHA-256 digest Z0 of the + * UTF8-encoded key authorization, and encodes Z0 in + * UTF-8 lower-case hexadecimal form." + */ + + /* tls-sni-02 + * + * SAN A MUST be constructed as follows: compute the + * SHA-256 digest of the UTF-8-encoded challenge token + * and encode it in lowercase hexadecimal form. The + * dNSName is "x.y.token.acme.invalid", where x + * is the first half of the hexadecimal representation + * and y is the second half. + */ + + memset(&ac->ci, 0, sizeof(ac->ci)); + + /* first compute the key authorization */ + + lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); + p = start; + end = &buf[sizeof(buf) - 1]; + + p += lws_snprintf(p, end - p, "%s.", ac->chall_token); + n = lws_jws_base64_enc(digest, 32, p, end - p); + if (n < 0) + goto failed; + p += n; + + if (lws_genhash_init(&hctx, LWS_GENHASH_TYPE_SHA256)) + return -1; + + if (lws_genhash_update(&hctx, (uint8_t *)start, + lws_ptr_diff(p, start))) { + lws_genhash_destroy(&hctx, NULL); + + return -1; + } + if (lws_genhash_destroy(&hctx, digest)) + return -1; + + p = buf; + for (n = 0; n < 32; n++) { + p += lws_snprintf(p, end - p, "%02x", + digest[n] & 0xff); + if (n == (32 / 2) - 1) + p = buf + 64; + } + + p = ac->san_a; + if (ac->is_sni_02) { + lws_snprintf(p, sizeof(ac->san_a), + "%s.%s.token.acme.invalid", + buf, buf + 64); + + /* + * SAN B MUST be constructed as follows: compute + * the SHA-256 digest of the UTF-8 encoded key + * authorization and encode it in lowercase + * hexadecimal form. The dNSName is + * "x.y.ka.acme.invalid" where x is the first + * half of the hexadecimal representation and y + * is the second half. + */ + lws_jwk_rfc7638_fingerprint(&vhd->jwk, + (char *)digest); + + p = buf; + for (n = 0; n < 32; n++) { + p += lws_snprintf(p, end - p, "%02x", + digest[n] & 0xff); + if (n == (32 / 2) - 1) + p = buf + 64; + } + + p = ac->san_b; + lws_snprintf(p, sizeof(ac->san_b), + "%s.%s.ka.acme.invalid", + buf, buf + 64); + } else { + lws_snprintf(p, sizeof(ac->san_a), + "%s.%s.acme.invalid", buf, buf + 64); + ac->san_b[0] = '\0'; + } + + lwsl_notice("san_a: '%s'\n", ac->san_a); + lwsl_notice("san_b: '%s'\n", ac->san_b); + + /* + * tls-sni-01: + * + * The client then configures the TLS server at the + * domain such that when a handshake is initiated with + * the Server Name Indication extension set to + * "..acme.invalid", the + * corresponding generated certificate is presented. + * + * tls-sni-02: + * + * The client MUST ensure that the certificate is + * served to TLS connections specifying a Server Name + * Indication (SNI) value of SAN A. + */ + ac->ci.vhost_name = ac->san_a; + + /* + * we bind to exact iface of real vhost, so we can + * share the listen socket by SNI + */ + ac->ci.iface = ac->real_vh_iface; + + /* listen on the same port as the vhost that triggered + * us */ + ac->ci.port = ac->real_vh_port; + /* Skip filling in any x509 info into the ssl_ctx. + * It will be done at the callback + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS + * in this callback handler (below) + */ + ac->ci.options = LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX | + LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + /* make ourselves protocols[0] for the new vhost */ + ac->ci.protocols = acme_protocols; + /* + * vhost .user points to the ac associated with the + * temporary vhost + */ + ac->ci.user = ac; + + ac->vhost = lws_create_vhost(lws_get_context(wsi), + &ac->ci); + if (!ac->vhost) + goto failed; + + /* + * The challenge-specific vhost is up... let the ACME + * server know we are ready to roll... + */ + + ac->state = ACME_STATE_ACCEPT_CHALL; + ac->goes_around = 0; + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, + ac->challenge_uri, + "POST"); + if (!cwsi) { + lwsl_notice("%s: failed to connect\n", + __func__); + goto failed; + } + break; + + case ACME_STATE_ACCEPT_CHALL: + /* + * he returned something like this (which we parsed) + * + * { + * "type": "tls-sni-01", + * "status": "pending", + * "uri": "https://acme-staging.api.letsencrypt.org/ + * acme/challenge/xCt7bT3FaxoIQU3Qry87t5h + * uKDcC-L-0ERcD5DLAZts/71100507", + * "token": "j2Vs-vLI_dsza4A35SFHIU03aIe2PzFRijbqCY + * dIVeE", + * "keyAuthorization": "j2Vs-vLI_dsza4A35SFHIU03aIe2 + * PzFRijbqCYdIVeE.nmOtdFd8Jikn6K8NnYYmT5 + * vCM_PwSDT8nLdOYoFXhRU" + * } + * + */ + lwsl_notice("%s: COMPLETED accept chall: %s\n", + __func__, ac->challenge_uri); +poll_again: + ac->state = ACME_STATE_POLLING; + + if (ac->goes_around++ == 10) { + lwsl_notice("%s: too many chall retries\n", + __func__); + + goto failed; + } + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, + ac->challenge_uri, + "GET"); + if (!cwsi) { + lwsl_notice("%s: failed to connect\n", + __func__); + goto failed; + } + break; + + case ACME_STATE_POLLING: + + if (ac->resp == 202 && + strcmp(ac->status, "invalid") && + strcmp(ac->status, "valid")) { + lwsl_notice("status: %s\n", ac->status); + goto poll_again; + } + + if (!strcmp(ac->status, "invalid")) { + lwsl_notice("%s: polling failed\n", __func__); + goto failed; + } + + lwsl_notice("Authorization accepted\n"); + + /* + * our authorization was validated... so delete the + * temp SNI vhost now its job is done + */ + if (ac->vhost) + lws_vhost_destroy(ac->vhost); + ac->vhost = NULL; + + /* + * now our JWK is accepted as authorized to make + * requests for the domain, next move is create the + * CSR signed with the JWK, and send it to the ACME + * server to request the actual certs. + */ + ac->state = ACME_STATE_POLLING_CSR; + ac->goes_around = 0; + + strcpy(buf, ac->urls[JAD_NEW_CERT_URL]); + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, + "POST"); + if (!cwsi) { + lwsl_notice("%s: failed to connect to acme\n", + __func__); + + goto failed; + } + break; + + case ACME_STATE_POLLING_CSR: + /* + * (after POSTing the CSR)... + * + * If the CA decides to issue a certificate, then the + * server creates a new certificate resource and + * returns a URI for it in the Location header field + * of a 201 (Created) response. + * + * HTTP/1.1 201 Created + * Location: https://example.com/acme/cert/asdf + * + * If the certificate is available at the time of the + * response, it is provided in the body of the response. + * If the CA has not yet issued the certificate, the + * body of this response will be empty. The client + * should then send a GET request to the certificate URI + * to poll for the certificate. As long as the + * certificate is unavailable, the server MUST provide a + * 202 (Accepted) response and include a Retry-After + * header to indicate when the server believes the + * certificate will be issued. + */ + if (ac->resp < 200 || ac->resp > 202) { + lwsl_notice("CSR poll failed on resp %d\n", + ac->resp); + goto failed; + } + + if (ac->resp == 200) { + char *pp; + int max; + + lwsl_notice("The cert was sent..\n"); + /* + * That means we have the issued cert DER in + * ac->buf, length in ac->cpos; and the key in + * ac->alloc_privkey_pem, length in + * ac->len_privkey_pem. + * + * We write out a PEM copy of the cert, and a + * PEM copy of the private key, using the + * write-only fds we opened while we still + * had root. + * + * Estimate the size of the PEM version of the + * cert and allocate a temp buffer for it. + * + * This is a bit complicated because first we + * drop the b64url version into the buffer at + * +384, then we add the header at 0 and move + * lines of it back + '\n' to make PEM. + * + * This avoids the need for two fullsize + * allocations. + */ + + max = (ac->cpos * 4) / 3 + 16 + 384; + + start = p = malloc(max); + if (!p) + goto failed; + + n = lws_b64_encode_string(ac->buf, ac->cpos, + start + 384, max - 384); + if (n < 0) { + free(start); + goto failed; + } + + pp = start + 384; + p += lws_snprintf(start, 64, "%s", + "-----BEGIN CERTIFICATE-----\n"); + + while (n) { + m = 65; + if (n < m) + m = n; + memcpy(p, pp, m); + n -= m; + p += m; + pp += m; + if (n) + *p++ = '\n'; + } + p += lws_snprintf(p, + max - lws_ptr_diff(p, start), + "%s", + "\n-----END CERTIFICATE-----\n"); + + n = lws_plat_write_cert(vhd->vhost, 0, + vhd->fd_updated_cert, start, + lws_ptr_diff(p, start)); + free(start); + if (n) { + lwsl_err("unable to write ACME cert!\n"); + goto failed; + } + /* + * don't close it... we may update the certs + * again + */ + + if (lws_plat_write_cert(vhd->vhost, 1, + vhd->fd_updated_key, + ac->alloc_privkey_pem, + ac->len_privkey_pem)) { + lwsl_err("unable to write ACME key!\n"); + goto failed; + } + + /* + * we have written the persistent copies + */ + + lwsl_notice("%s: Updated certs written for %s " + "to %s.upd and %s.upd\n", __func__, + vhd->pvop[LWS_TLS_REQ_ELEMENT_COMMON_NAME], + vhd->pvop[LWS_TLS_SET_CERT_PATH], + vhd->pvop[LWS_TLS_SET_KEY_PATH]); + + /* notify lws there was a cert update */ + + if (lws_tls_cert_updated(vhd->context, + vhd->pvop[LWS_TLS_SET_CERT_PATH], + vhd->pvop[LWS_TLS_SET_KEY_PATH], + ac->buf, ac->cpos, + ac->alloc_privkey_pem, + ac->len_privkey_pem)) { + lwsl_notice("problem setting certs\n"); + } + + lws_acme_finished(vhd); + + return 0; + } + + /* he is preparing the cert, go again with a GET */ + + if (ac->goes_around++ == 30) { + lwsl_notice("%s: too many retries\n", + __func__); + + goto failed; + } + + strcpy(buf, ac->challenge_uri); + cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, + &ac->cwsi, &ac->i, buf, + "GET"); + if (!cwsi) { + lwsl_notice("%s: failed to connect to acme\n", + __func__); + + goto failed; + } + break; + + default: + break; + } + break; + + case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: + /* + * This goes to vhost->protocols[0], but for our temp certs + * vhost we created, we have arranged that to be our protocol, + * so the callback will come here. + * + * When we created the temp vhost, we set its pvo to point + * to the ac associated with the temp vhost. + */ + lwsl_debug("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS\n"); + ac = (struct acme_connection *)lws_get_vhost_user( + (struct lws_vhost *)in); + if (lws_tls_acme_sni_cert_create((struct lws_vhost *)in, + ac->san_a, ac->san_b)) + return -1; + break; + + default: + break; + } + + return 0; + +failed: + lwsl_err("%s: failed out\n", __func__); + lws_acme_finished(vhd); + + return -1; +} + +#if !defined (LWS_PLUGIN_STATIC) + +static const struct lws_protocols protocols[] = { + LWS_PLUGIN_PROTOCOL_LWS_ACME_CLIENT +}; + +LWS_EXTERN LWS_VISIBLE int +init_protocol_lws_acme_client(struct lws_context *context, + struct lws_plugin_capability *c) +{ + if (c->api_magic != LWS_PLUGIN_API_MAGIC) { + lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, + c->api_magic); + return 1; + } + + c->protocols = protocols; + c->count_protocols = ARRAY_SIZE(protocols); + c->extensions = NULL; + c->count_extensions = 0; + + return 0; +} + +LWS_EXTERN LWS_VISIBLE int +destroy_protocol_lws_acme_client(struct lws_context *context) +{ + return 0; +} + +#endif