@@ -36,11 +36,12 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
3636 auto lock = MyConfig.Lock ();
3737 std::string error;
3838 std::string server, clientid, user, password, port, topic_prefix;
39+ extram::string client_cert, client_key;
3940 std::string updatetime_connected, updatetime_idle, updatetime_on;
4041 std::string updatetime_charging, updatetime_awake, updatetime_sendall, updatetime_keepalive;
4142 std::string metrics_priority, metrics_include, metrics_exclude, metrics_immediately, metrics_exclude_immediately;
4243 std::string queue_sendall, queue_modified;
43- bool tls, legacy_event_topic, updatetime_priority, updatetime_immediately;
44+ bool tls, legacy_event_topic, updatetime_priority, updatetime_immediately, retain_depth_limit ;
4445
4546 if (c.method == " POST" ) {
4647 // process form submission:
@@ -50,6 +51,8 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
5051 clientid = c.getvar (" clientid" );
5152 user = c.getvar (" user" );
5253 password = c.getvar (" password" );
54+ c.getvar (" client_cert" , client_cert);
55+ c.getvar (" client_key" , client_key);
5356 port = c.getvar (" port" );
5457 topic_prefix = c.getvar (" topic_prefix" );
5558 updatetime_connected = c.getvar (" updatetime_connected" );
@@ -61,6 +64,7 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
6164 updatetime_keepalive = c.getvar (" updatetime_keepalive" );
6265 updatetime_priority = (c.getvar (" updatetime_priority" ) == " yes" );
6366 updatetime_immediately = (c.getvar (" updatetime_immediately" ) == " yes" );
67+ retain_depth_limit = (c.getvar (" retain_depth_limit" ) == " yes" );
6468 metrics_priority = c.getvar (" metrics_priority" );
6569 metrics_include = c.getvar (" metrics_include" );
6670 metrics_exclude = c.getvar (" metrics_exclude" );
@@ -111,6 +115,18 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
111115 error += " <li data-input=\" updatetime_keepalive\" >Keepalive interval must be at least 60 seconds</li>" ;
112116 }
113117 }
118+ if (!client_cert.empty () && !startsWith (client_cert, " -----BEGIN CERTIFICATE-----" )) {
119+ error += " <li data-input=\" client_cert\" >Client certificate must be in PEM CERTIFICATE format</li>" ;
120+ }
121+ if (!client_key.empty () &&
122+ !startsWith (client_key, " -----BEGIN PRIVATE KEY-----" ) &&
123+ !startsWith (client_key, " -----BEGIN RSA PRIVATE KEY-----" ) &&
124+ !startsWith (client_key, " -----BEGIN EC PRIVATE KEY-----" )) {
125+ error += " <li data-input=\" client_key\" >Client private key must be in PEM PRIVATE KEY format</li>" ;
126+ }
127+ if (client_cert.empty () != client_key.empty ()) {
128+ error += " <li data-input=\" client_cert,client_key\" >Both client certificate and private key must be given</li>" ;
129+ }
114130
115131 if (error == " " ) {
116132 // success:
@@ -132,14 +148,40 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
132148 MyConfig.SetParamValue (" server.v3" , " updatetime.keepalive" , updatetime_keepalive);
133149 MyConfig.SetParamValueBool (" server.v3" , " updatetime.priority" , updatetime_priority);
134150 MyConfig.SetParamValueBool (" server.v3" , " updatetime.immediately" , updatetime_immediately);
151+ MyConfig.SetParamValueBool (" server.v3" , " retain.depth.limit" , retain_depth_limit);
135152 MyConfig.SetParamValue (" server.v3" , " metrics.priority" , metrics_priority);
136153 MyConfig.SetParamValue (" server.v3" , " metrics.include" , metrics_include);
137154 MyConfig.SetParamValue (" server.v3" , " metrics.exclude" , metrics_exclude);
138155 MyConfig.SetParamValue (" server.v3" , " metrics.include.immediately" , metrics_immediately);
139156 MyConfig.SetParamValue (" server.v3" , " metrics.exclude.immediately" , metrics_exclude_immediately);
140157 MyConfig.SetParamValue (" server.v3" , " queue.sendall" , queue_sendall);
141158 MyConfig.SetParamValue (" server.v3" , " queue.modified" , queue_modified);
142-
159+ if (client_cert.empty ()) {
160+ unlink (" /store/tls/serverv3_client.crt" );
161+ unlink (" /store/tls/serverv3_client.key" );
162+ }
163+ else {
164+ if (save_file (" /store/tls/serverv3_client.crt" , client_cert) != 0 ) {
165+ error = " <li data-input=\" client_cert\" >Error saving TLS certificate: " ;
166+ error += strerror (errno);
167+ error += " </li>" ;
168+ error = " <p class=\" lead\" >Error!</p><ul class=\" errorlist\" >" + error + " </ul>" ;
169+ c.head (400 );
170+ c.alert (" danger" , error.c_str ());
171+ c.done ();
172+ return ;
173+ }
174+ if (save_file (" /store/tls/serverv3_client.key" , client_key) != 0 ) {
175+ error = " <li data-input=\" client_key\" >Error saving TLS private key: " ;
176+ error += strerror (errno);
177+ error += " </li>" ;
178+ error = " <p class=\" lead\" >Error!</p><ul class=\" errorlist\" >" + error + " </ul>" ;
179+ c.head (400 );
180+ c.alert (" danger" , error.c_str ());
181+ c.done ();
182+ return ;
183+ }
184+ }
143185 c.head (200 );
144186 c.alert (" success" , " <p class=\" lead\" >Server V3 (MQTT) connection configured.</p>" );
145187 OutputHome (p, c);
@@ -160,6 +202,8 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
160202 clientid = MyConfig.GetParamValue (" server.v3" , " clientid" );
161203 user = MyConfig.GetParamValue (" server.v3" , " user" );
162204 password = MyConfig.GetParamValue (" password" , " server.v3" );
205+ load_file (" /store/tls/serverv3_client.crt" , client_cert);
206+ load_file (" /store/tls/serverv3_client.key" , client_key);
163207 port = MyConfig.GetParamValue (" server.v3" , " port" );
164208 topic_prefix = MyConfig.GetParamValue (" server.v3" , " topic.prefix" );
165209 updatetime_connected = MyConfig.GetParamValue (" server.v3" , " updatetime.connected" );
@@ -171,6 +215,7 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
171215 updatetime_keepalive = MyConfig.GetParamValue (" server.v3" , " updatetime.keepalive" , " 1740" );
172216 updatetime_priority = MyConfig.GetParamValueBool (" server.v3" , " updatetime.priority" , false );
173217 updatetime_immediately = MyConfig.GetParamValueBool (" server.v3" , " updatetime.immediately" , false );
218+ retain_depth_limit = MyConfig.GetParamValueBool (" server.v3" , " retain.depth.limit" , false );
174219 metrics_priority = MyConfig.GetParamValue (" server.v3" , " metrics.priority" );
175220 metrics_include = MyConfig.GetParamValue (" server.v3" , " metrics.include" );
176221 metrics_exclude = MyConfig.GetParamValue (" server.v3" , " metrics.exclude" );
@@ -207,6 +252,46 @@ void OvmsWebServer::HandleCfgServerV3(PageEntry_t& p, PageContext_t& c)
207252 c.input_text (" Topic Prefix" , " topic_prefix" , topic_prefix.c_str (),
208253 " optional, default: ovms/<username>/<vehicle id>/" );
209254
255+ c.fieldset_start (" TLS client authentication (optional)" );
256+ c.printf (
257+ " <div class=\" form-group\" >\n "
258+ " <label class=\" control-label col-sm-3\" for=\" input-content\" >Client certificate:</label>\n "
259+ " <div class=\" col-sm-9\" >\n "
260+ " <textarea class=\" form-control font-monospace\" style=\" font-size:80%%;white-space:pre;\"\n "
261+ " autocapitalize=\" none\" autocorrect=\" off\" autocomplete=\" off\" spellcheck=\" false\"\n "
262+ " placeholder=\" -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----\"\n "
263+ " rows=\" 5\" id=\" input-client_cert\" name=\" client_cert\" >%s</textarea>\n "
264+ " </div>\n "
265+ " </div>\n "
266+ , c.encode_html (client_cert).c_str ());
267+ c.printf (
268+ " <div class=\" form-group\" >\n "
269+ " <label class=\" control-label col-sm-3\" for=\" input-content\" >Client private key:</label>\n "
270+ " <div class=\" col-sm-9\" >\n "
271+ " <textarea class=\" form-control font-monospace\" style=\" font-size:80%%;white-space:pre;\"\n "
272+ " autocapitalize=\" none\" autocorrect=\" off\" autocomplete=\" off\" spellcheck=\" false\"\n "
273+ " placeholder=\" -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----\"\n "
274+ " rows=\" 5\" id=\" input-client_key\" name=\" client_key\" >%s</textarea>\n "
275+ " <p class=\" help-block\" >Supports PKCS#8, RSA and EC PEM private keys. <br/>"
276+ " Leave both the certificate and key empty to authenticate with username/password only.</p>\n "
277+ " </div>\n "
278+ " </div>\n "
279+ , c.encode_html (client_key).c_str ());
280+ c.fieldset_end ();
281+ c.fieldset_start (" AWS IoT Core (optional)" );
282+ c.input_checkbox (" Limit retain to 8-segment topics" , " retain_depth_limit" , retain_depth_limit,
283+ " <p>Omits the MQTT RETAIN flag on topics with more than 8 path segments."
284+ " Required for AWS IoT Core. Default: disabled.</p>" );
285+ c.printf (
286+ " <div class=\" form-group\" >\n "
287+ " <div class=\" col-sm-9 col-sm-offset-3\" >\n "
288+ " <b> MQTT keepalive </b><br>\n "
289+ " <p class=\" help-block\" >AWS IoT Core requires the MQTT keepalive to be 1200 seconds or less."
290+ " Set the <em>Keepalive</em> interval below to 1200 or lower.</p>\n "
291+ " </div>\n "
292+ " </div>\n " );
293+ c.fieldset_end ();
294+
210295 c.fieldset_start (" Update intervals" );
211296 c.input (" number" , " …idle" , " updatetime_idle" , updatetime_idle.c_str (), " default: 600" , " default: 600, update interval when client not connected" , " min=\" 1\" max=\" 1200\" step=\" 1\" " , " seconds" );
212297 c.input (" number" , " …on" , " updatetime_on" , updatetime_on.c_str (), " default: 5" , " default: 5, update interval when Car is on" , " min=\" 1\" max=\" 600\" step=\" 1\" " , " seconds" );
0 commit comments