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