WAP PSK=Preshared key ではなく、端末をSSL証明書で認証して無線接続するお話し。カテゴリ分けすると、いわゆる WPA2 Enterprise です。
証明書
ここでは V1 の証明書で、無線基地局(AP)=認証局(CA)で構成してみた。基地局複数ある場合とか、一般的には分けたほうがいいです。
うっかり openssl デフォルトの V3 証明書の設定が使われないように、あらかじめ設定ファイル v1.conf
を用意しておく。
[req]
distinguished_name=none
[none]
まず AP 側の鍵と証明書を作る。req -x509 -new
が「鍵作成+自己署名証明書(CSRは暗黙処理)」のショートカットになっているので、これで作るのが簡単。参考
openssl req -config v1.conf -subj /C=JP/OU=Dev/CN=ap -keyout ap.key -x509 -new -nodes -out ap.crt
次に ESP8266 である無線端末(STA)側の証明書を作る。こちらは AP の証明書で署名するので CSR を作る。req -new
が「鍵作成+CSR」のショートカット。
openssl req -config v1.conf -subj /C=JP/OU=DEV/CN=sta -nodes -new -keyout sta.key -out sta.csr
署名する。x509
は -config
を取らず、すこしルールが違う。-extfile
を指定すると V3 になる。-extensions
を指定すると、デフォルトを上書きする。ということで、-extensions
に存在しない値を指定するのみ、で V1 にできる。
echo 10 > ap.srl
openssl x509 -CA ap.crt -CAkey ap.key -req -in sta.csr -out sta.crt -extensions none
これで ap.crt, ap.key と sta.crt, sta.key が揃った。
hostapd
hostapd 組み込みの eap_server
機能を利用すると簡単。
driver=nl80211
interface=wlan0
ssid=esptls
channel=1
wpa=2
wpa_key_mgmt=WPA-EAP
ieee8021x=1
eapol_version=2
eap_server=1
ca_cert=ap.crt
server_cert=ap.crt
private_key=ap.key
eap_user_file=ap.user
ap.user のファイルはこんな感じで用意する。どんなユーザでも EAP-TLS でネゴシエーションされるという意味。
* TLS
以上二つのファイルを用意して次のように起動する。
hostapd ap.conf
ところで例えば ubuntu のデフォルトの状態だと、NetworkManager が wifi のインターフェースを掴んで、うまく動作させられなかったりする。そんなときは nmcli
を使ってこんな感じに開放する。
nmcli radio wifi off
# nmcli が wifi off するときに同時に rfkill するので、それを打ち消す
rfkill off 0
ESP8266
ESP8266/Arduino な Arduino スケッチ環境で次のように作る。参考
いくつかポイントがある。
-
extern "C"
を使う。さもないと linker で問題が出る。 -
wifi_station_set_cert_key
の引数はファイルの中身そのもの。最後の NULL termination を含めた長さを引数に渡すこと。重要。ここでは文字列を渡しているけれど、多くの場合は、ファイルから読みだすことを想定しているんだと思う。 - 証明書の鍵のパスワードを設定できそうな引数になっているけれど、未実装で NULL, 0 を渡せとドキュメントに書かれている。
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
extern "C" { // We're calling ESP8266 SDK "C" interface
#include <user_interface.h>
}
const char tls_cert[] = "-----BEGIN CERTIFICATE-----\n"
"MIICxTCCAa0CAREwDQYJKoZIhvcNAQELBQAwKDELMAkGA1UEBhMCSlAxDDAKBgNV\n"
"BAsMA0RldjELMAkGA1UEAwwCYXAwHhcNMTYwNzIyMTcyOTU5WhcNMTYwODIxMTcy\n"
"OTU5WjApMQswCQYDVQQGEwJKUDEMMAoGA1UECwwDREVWMQwwCgYDVQQDDANzdGEw\n"
"ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhBuYj3mtHaJSc0UIk0xZq\n"
"12MZo2fVFD7pfsg1w4HLo2+aDcF3zgxYy8+QZPR9rGmy6Tqj/T/e0NNHMTJgOoEk\n"
"O6OxpwkjcTp6+VGjWt0IkJsO/Ikn5s0/1nz5Xs0Tudy3jyLzfzeHqFFHidxgd7oH\n"
"D8/OTQ8dQekZwMCeITmUY5xa6D4qdRSuOMjuF7FFTqfZvW/rW8eMIatW0VowuXHW\n"
"zXqVwi8CsdA7g7nwMWYPZZOAF1iDzEJs3O6eYhirk3+1wQon4DkL141ZxIi6JHl1\n"
"qzXvtVzSMHAE7QLH2rKBy6oNjUuiLjwOBQaAE8g140XgefB91r2M+akNEXTZ4sGr\n"
"AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGKeE0+5xnftMr5AXSIN3AtVeChBhA30\n"
"vXtvL209MxmsmUFqS1jT0mIVshPoZt1Irevdu6S3FBh4F9OCN9N3YtVh8S+fyzLx\n"
"WJI2eVONDiwbdU1cOEeG7tGx+igURqCEBtbYcE3gZZ80ndocLtXgkwWkyetmm7uD\n"
"9ut3t1AnXygwxHK/jfG6eCfwRUMSN5EWxaSs1VpnJ0qt6v0AfaNk9oUZUUtLTP6C\n"
"LmFI/2/hr3oPvdMFDoSxeK9FGv14LqHEfwuw2Mb34YvGBU3ptf1V5bs89tGJcF5w\n"
"RfJXAVqXplA8yzUyDpk4iIVdD2yO+Z/6LXbbR1g0Peb9VUgj6dHdQEQ=\n"
"-----END CERTIFICATE-----\n";
const char tls_key[] = "-----BEGIN PRIVATE KEY-----\n"
"MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDhBuYj3mtHaJSc\n"
"0UIk0xZq12MZo2fVFD7pfsg1w4HLo2+aDcF3zgxYy8+QZPR9rGmy6Tqj/T/e0NNH\n"
"MTJgOoEkO6OxpwkjcTp6+VGjWt0IkJsO/Ikn5s0/1nz5Xs0Tudy3jyLzfzeHqFFH\n"
"idxgd7oHD8/OTQ8dQekZwMCeITmUY5xa6D4qdRSuOMjuF7FFTqfZvW/rW8eMIatW\n"
"0VowuXHWzXqVwi8CsdA7g7nwMWYPZZOAF1iDzEJs3O6eYhirk3+1wQon4DkL141Z\n"
"xIi6JHl1qzXvtVzSMHAE7QLH2rKBy6oNjUuiLjwOBQaAE8g140XgefB91r2M+akN\n"
"EXTZ4sGrAgMBAAECggEBAJjz2YZT7lNxUGJvEih5mmkw0dlhang49LI9TNTOePDz\n"
"vC3YX8KROW85yXH6TP0HLik/wm4o+zr2ckWXuQgTfCgmqZNdWCbRFdD0mnsQjeD+\n"
"eYbsPEX06VoU7omJ2Jcp5E6YakdcF2CMFgMsP7EKcFXQd41gb3+Rh2HL1KEnMD4d\n"
"WDZyOxS+0pUXp7LwUTHAaLelqWVH4yHukG9B1xTkApBItVjfSYHwCzQLY3zJgwsc\n"
"3iqiN1Uqqt3X7jxjpcyJjLbLpC5QXVB3f7ExUyMw4rDIE3NiJp4Pczx7rhotUR6J\n"
"KZ2LJDoq5LGeFff7cqMnQaXdNKFYCEYA0P602Dlx58ECgYEA/Mv9xNgOcfVDFdwd\n"
"A4wEfZSRxXp5aWbty+x91/ap6mn5gKmX5DzuNGch3nDwjELCePJ2zL/OdCcNhGOy\n"
"61Ok8a+1oe4heZie5AtyAD+5c7IQw+TtSNmfWGUBJDv8ixSUFEvqgmxj7UirfYZW\n"
"kqnLfb80Qz3XLGf/ugSTK6GCyBsCgYEA4+DUSD/folIZ175LEKZlctRwFP507Ala\n"
"5FAMySl1Gi/bohnt+3Vs6ph/GaSIg+Q+6r39loQKT40iwW4tSQrV6qBtJ99VDbe4\n"
"JiKswc5VbSzV5rfYVITBZ41yFv4wFd1zrGItEYVshn1WRfGU904helV7up0yfIZD\n"
"t8BEgGxHpbECgYEAxkEg+vBKm9qySwF+C5sSpn4OuGXts9jSI3yL0QQUi8+iqeHX\n"
"SlryoUxEhpPiQs3UgE//FWJTgkpiUnJyDhZiJF0dwCnmPNuRuNy1Ajb3tSFv/oGa\n"
"CekKC6Pi+kzFKTnxS92hw7lHwP6d52qkmI7rFOoQDbABAUVqi7MszCn1TAMCgYEA\n"
"kmAL2/DzhL+6C+QXMbXAupcM+99LWYbU1I06+UhhCRYuvZxsSrbt5G9aTS1r51SI\n"
"uZ6assFUIi9lYNyVyDJmoFS2aQNDDhGx/wUM9VzFcOB48b+r/PZdiVfJLk3Os2zR\n"
"bayOiI+s22LNNRZt+sE8LemVFZT+JhDUlMay+c8T4rECgYEApxWKko+0nHGgdzzn\n"
"167JRzKCFFfegmlEbNaDJhwfQLjoTo2wCeBkFmERoeaSeXIHjvhA3imC7azVXlTM\n"
"paRvvYNtWpvg9hIBCpgCioJbkUQ+2tOAP5gLuQHo758D+pSArl8qlh9oLkbeI4tr\n"
"np6r/Ydn1mho+8AtCSfxK6mlrvU=\n"
"-----END PRIVATE KEY-----\n";
void setup() {
delay(1000);
Serial.begin(115200);
Serial.println("hello");
int w = wifi_station_set_cert_key((uint8*)tls_cert, strlen(tls_cert)+1, (uint8*)tls_key, strlen(tls_key)+1, NULL, 0);
Serial.printf("cert %d\n", w);
WiFi.mode(WIFI_STA);
Serial.println("before connect");
int st = WiFi.begin("eaptls", "unused_dummy");
Serial.printf("after connect %d\n", st);
}
void loop() {
}
今の ESP8266/Arduino は espressif NON-OS SDK 1.5.3 を使っている。NONOS SDK の最新版は現時点で 2.0 になっていて、EAP-PEAP のサポートが追加されたりしているので、きっと 1.5 よりも安定している。wpa2_enterprise.h
として分離されていたりする。ESP8266/Arduino の対応が待ち遠しい。
接続
hostapd 側のログに、こんな感じで出てきたら接続成功。
EAP method=13 が EAP-TLS。method=1 は Identity。method=1の後にmethod=13が出ていればokで、そのあとに starting accounting session
が出ていれば接続確立。
wlan0: STA 18:fe:34:ee:84:58 IEEE 802.11: authenticated
wlan0: STA 18:fe:34:ee:84:58 IEEE 802.11: associated (aid 1)
wlan0: STA 18:fe:34:ee:84:58 IEEE 802.11: authenticated
wlan0: STA 18:fe:34:ee:84:58 IEEE 802.11: associated (aid 1)
wlan0: CTRL-EVENT-EAP-STARTED 18:fe:34:ee:84:58
wlan0: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=1
wlan0: CTRL-EVENT-EAP-STARTED 18:fe:34:ee:84:58
wlan0: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=1
wlan0: CTRL-EVENT-EAP-PROPOSED-METHOD vendor=0 method=13
handle_beacon - too short payload (len=28)
wlan0: CTRL-EVENT-EAP-SUCCESS 18:fe:34:ee:84:58
wlan0: STA 18:fe:34:ee:84:58 WPA: pairwise key handshake completed (RSN)
wlan0: AP-STA-CONNECTED 18:fe:34:ee:84:58
wlan0: STA 18:fe:34:ee:84:58 RADIUS: starting accounting session 57923653-00000000
wlan0: STA 18:fe:34:ee:84:58 IEEE 802.1X: authenticated - EAP type: 0 (unknown)
handle_beacon - too short payload (len=25)
handle_beacon - too short payload (len=32)