[wifi] Hand the DPP bootstrapping key to the supplicant DER encoded

This commit is contained in:
J. Nick Koston
2026-10-15 21:48:53 -10:00
parent 51b1fdcc8b
commit 828911230e
4 changed files with 73 additions and 2 deletions
+18 -2
View File
@@ -259,6 +259,22 @@ WiFiConfigureAction = wifi_ns.class_(
)
# The supplicant parses the bootstrapping key as a DER encoded P-256 private key in hex; these wrap the
# 32 byte key the way the ESP-IDF enrollee example does
DPP_KEY_DER_PREFIX = "30310201010420"
DPP_KEY_DER_SUFFIX = "a00a06082a8648ce3d030107"
def _validate_dpp_private_key(value: object) -> str:
"""The key is 32 bytes written as 64 hexadecimal characters."""
value = cv.string_strict(value)
if len(value) != 64 or any(c not in "0123456789abcdefABCDEF" for c in value):
raise cv.Invalid(
"private_key must be 32 bytes written as 64 hexadecimal characters"
)
return value.lower()
def _validate_dpp_device_info(value: object) -> str:
"""The supplicant puts the text on a space separated command line and reads it up to the next space."""
value = cv.string_strict(value)
@@ -336,7 +352,7 @@ EAP_AUTH_SCHEMA = cv.All(
# Wi-Fi Easy Connect (DPP) enrollee; the ESP-IDF supplicant supports the QR code method
DPP_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PRIVATE_KEY): cv.sensitive(cv.string_strict),
cv.Optional(CONF_PRIVATE_KEY): cv.sensitive(_validate_dpp_private_key),
cv.Optional(CONF_DEVICE_INFO): _validate_dpp_device_info,
cv.Optional(CONF_CHANNELS, default=[6]): cv.ensure_list(
cv.int_range(min=1, max=14)
@@ -853,7 +869,7 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_DPP_SUPPORT", True)
add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_ENABLE_DPP", True)
if (key := dpp.get(CONF_PRIVATE_KEY)) is not None:
cg.add(var.set_dpp_key(key))
cg.add(var.set_dpp_key(f"{DPP_KEY_DER_PREFIX}{key}{DPP_KEY_DER_SUFFIX}"))
if (info := dpp.get(CONF_DEVICE_INFO)) is not None:
cg.add(var.set_dpp_device_info(info))
cg.add(var.set_dpp_channels(",".join(str(c) for c in dpp[CONF_CHANNELS])))
@@ -0,0 +1,12 @@
---
esphome:
name: test
esp32:
board: esp32dev
framework:
type: esp-idf
wifi:
dpp:
private_key: 00112233445566778899AABBCCDDEEFF00112233445566778899aabbccddeeff
@@ -0,0 +1,42 @@
"""The DPP bootstrapping key is given as 32 raw bytes in hex and handed to the supplicant DER encoded."""
from collections.abc import Callable
from pathlib import Path
import pytest
from esphome.components import wifi
import esphome.config_validation as cv
RAW_KEY = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
@pytest.mark.parametrize(
"value",
[
RAW_KEY[:-2],
RAW_KEY + "00",
"zz" + RAW_KEY[2:],
wifi.DPP_KEY_DER_PREFIX + RAW_KEY + wifi.DPP_KEY_DER_SUFFIX,
],
)
def test_key_must_be_64_hex_digits(value: str) -> None:
with pytest.raises(cv.Invalid, match="64 hexadecimal characters"):
wifi._validate_dpp_private_key(value)
def test_key_is_lower_cased() -> None:
assert wifi._validate_dpp_private_key(RAW_KEY.upper()) == RAW_KEY
def test_generated_key_is_der_encoded(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""The ESP-IDF supplicant expects the P-256 DER header around the raw key."""
main_cpp = generate_main(component_config_path("dpp_key.yaml"))
assert (
f'set_dpp_key("{wifi.DPP_KEY_DER_PREFIX}{RAW_KEY}{wifi.DPP_KEY_DER_SUFFIX}")'
in main_cpp
)
@@ -2,6 +2,7 @@ wifi:
dpp:
channels: [1, 6, 11]
device_info: ESPHome
private_key: 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
timeout: 30s
on_dpp_uri:
- logger.log: