GW ESP32 WiFi Hotspot
The Gateway provides a WiFi hotspot for configuration. The hotspot has SSID with the name "Configure Ruuvi Gateway ABCD" where ABCD is the last 2 bytes of the gateway WiFi MAC address. No password is required to connect to the gateway's hotspot.
The WiFi hotspot is only active if the gateway is not configured or within one minute after pressing the Configuration button. After the configuration of the gateway is complete, WiFi credentials are stored to flash and the hotspot is turned off. If the Internet connection is lost later, connection loss is indicated by "Red LED" but the hotspot is not turned back on unless the user enters configuration mode by pressing the Configuration button.
The gateway can be partially reconfigured over LAN (network configuration can't be changed in this case). Also following pages are available through LAN connection: /metrics and /history.
If Ethernet cable is connected to an unconfigured gateway, then after one minute the gateway will automatically be set to the default configuration with Ethernet connection mode and the Wi-Fi hotspot will be deactivated. After that, the user can activate the hotspot with a short press on the "Configure" button or reset the configuration with a long press on the "Configure" button.
The gateway is reachable at http://10.10.0.1 once a client has connected to the hotspot.
Sensitive data (containing passwords) is transmitted in encrypted form. The format for transmitting encrypted data is as follows (JSON):
Attribute name | Description |
---|---|
encrypted | Encrypted data, encoded as Base64 |
iv | AES-256 initialization vector, encoded as Base64 |
hash | SHA-256 hash of unencrypted data, encoded as Base64 |
Example of encrypted data:
{"encrypted":"hbLi8K0CHELrvI0/4gO7n3E5KY5wtbmUSwM67oY6ZhzZNPIGBhHLE8CYeDuVL+FiU/K5vAO1EC+uEm/gw4LkaSE1sZbsGUGyDFelnEOlhQYQf6rGxlQYu+9xrzPhUv97tezkrPPesSy0tgea3QAmuQRPX628X0AP0OxUxE4kBh8guYKIXPYOf9EH0fUskvt/98B8p630mu66miiQfsONsxFWVX7GcUN5u1soYKdZXd9HQGVACEgl6oZ2Vxqf4JmtNgdsmWxYTmIOi1ySq4DtqtL5vfdzN1BMep0cHkgGAeMBQt6i9H5mhBZ8PnJzjvFcuolwsRG3rQTX9wRCXikjag==","iv":"I+CylR3OlmEWr127RC084w==","hash":"ADUJpequznSV9HyqkMLrj2NPhViJQa6YDdbkEDExajk="}
The data is encrypted using AES-256-CBC. ECDH protocol is used to establish a shared secret between the client and the Gateway, then SHA-256 hash is calculated for the shared secret to generate AES encryption key.
To establish a shared secret, the client must perform
GET
/ruuvi.json
request and pass its public encryption key in the HTTP header "ruuvi_ecdh_pub_key" (Base64 encoded):GET /ruuvi.json?_=1661226324528 HTTP/1.1
Host: 10.10.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
ruuvi_ecdh_pub_key: BPtMzls7i0PyOu75EPzw2OaTJscwGnrnOW7mK58pfJ6Egg9/tV5cJFVz6euHGp7x4ZXj5s0hTiotrtE0qJLczMA=
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Referer: http://10.10.0.1/
In response, the Gateway returns its public encryption key in the HTTP header "ruuvi_ecdh_pub_key" (Base64 encoded):
HTTP/1.1 200 OK
Server: Ruuvi Gateway
Date: Tue, 23 Aug 2022 03:45:27 GMT
Content-type: application/json; charset=utf-8
Content-Length: 1514
ruuvi_ecdh_pub_key: BGJXeeiqH7JPCdO3wRZgSdrcJb2EtSAVUQwr/RfeJmGxIrXuw8mTW2517RCn8pgfBwDnRWCnL9JYdvQkbf+goZQ=
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Pragma: no-cache
The client needs to compute a shared secret using its private encryption key and the Gateway's public key, then calculate SHA-256 for the shared secret to get the AES encryption key (AES_K).
To encrypt the data before sending it, the client must randomly generate a 16-byte initialization vector (IV) for AES encryption, then encrypt the data using parameters IV and AES_K. The data is validated using a SHA-256 hash, which is passed as part of JSON. All values in JSON are encoded by Base64.
To decrypt the data received from the Gateway, the client needs to decode all values in JSON from Base64 encoding, then get the IV from the "iv" attribute of the received JSON. After that, decrypt the data in the "encrypted" attribute using the IV and AES_K. And finally, calculate the SHA-256 hash for the decrypted data and check if it matches the "hash" attribute in JSON.
These API calls are available:
get
http://10.10.0.1/ruuvi.json
Requesting gateway configuration and performing encryption keys exchange
post
http://10.10.0.1/ruuvi.json
Save network configuration
Example:
{"use_eth":true,"eth_dhcp":true}
Example:
{"use_eth":false,"wifi_ap_config":{"channel":11}}
post
http://10.10.0.1/ruuvi.json
Save gateway configuration
Example:
{
"remote_cfg_use":false,
"remote_cfg_url":"",
"remote_cfg_auth_type":"no",
"use_http":true,
"http_url":"https://network.ruuvi.com/record",
"http_user":"",
"http_pass":"",
"use_http_stat":true,
"http_stat_url":"https://network.ruuvi.com/status",
"http_stat_user":"",
"http_stat_pass":"",
"use_mqtt":false,
"mqtt_transport":"TCP",
"mqtt_server":"test.mosquitto.org",
"mqtt_port":1883,
"mqtt_prefix":"ruuvi/C8:25:2D:8E:9C:2C/",
"mqtt_client_id":"C8:25:2D:8E:9C:2C",
"mqtt_user":"",
"mqtt_pass":"",
"lan_auth_api_key":"",
"company_use_filtering":true,
"scan_coded_phy":false,
"scan_1mbit_phy":true,
"scan_extended_payload":true,
"scan_channel_37":true,
"scan_channel_38":true,
"scan_channel_39":true,
"auto_update_cycle":"manual",
"auto_update_weekdays_bitmask":127,
"auto_update_interval_from":0,
"auto_update_interval_to":24,
"auto_update_tz_offset_hours":3,
"ntp_use":true,
"ntp_use_dhcp":false,
"ntp_server1":"time.google.com",
"ntp_server2":"time.cloudflare.com",
"ntp_server3":"time.nist.gov",
"ntp_server4":"pool.ntp.org"
}
get
http://10.10.0.1
/status.json
status.json
get
http://10.10.0.1
/ap.json
ap.json
post
http://10.10.0.1
/connect.json
connect.json
Example:
Unencrypted content of "/connect.json" is data in json format:
Field name | Description |
---|---|
ssid | WiFi network SSID or null for Ethernet |
password | Strings with a Wi-Fi password or null for Ethernet. The null value is also used for reconnecting to the last saved Wi-Fi network. |
stub | A string of spaces, which is chosen so that the json length is always 240 bytes, regardless of SSID length and password length. |
Example of unencrypted data for connecting to Wi-Fi with the name "my_ssid1":
{"ssid": "my_ssid1", "password": "12345678", "stub": " "}
Example of unencrypted data for connecting to Wi-Fi with the name "my_ssid1" using the saved password (the value "null" is passed to indicate that the saved value of the password is to be used):
{"ssid": "my_ssid1", "password": null, "stub": " "}
Example of unencrypted data for connecting to Ethernet:
{"ssid": null, "password": null, "stub": " "}
delete
http://10.10.0.1
/connect.json
connect.json
get
http://10.10.0.1
/metrics
metrics
get
http://10.10.0.1
/history
history
Last modified 7mo ago