I'm trying to write an app in flutter to perform softap provisioning for an ESP32-C6 (https://docs.espressif.com/projects/esp-idf/en/v5.4.1/esp32c6/api-reference/provisioning/wifi_provisioning.html). I am using the structures defined in the proto files, which I have generated for Dart. I've established a secure session using the shared key (docs here - https://docs.espressif.com/projects/esp-idf/en/v5.4.1/esp32c6/api-reference/provisioning/provisioning.html) and that's confirmed in the logs from the esp, but when I try to send the wifi config information using the shared key and the nonce from session establishment, it doesn't work (response I get back has the error that there's an invalid end tag, or invalid wire type, or the message is truncated). Using verbose logs from the esp shows that the original request I sent is not being decrypted correctly either.
I'm using the cryptography package (https://pub.dev/packages/cryptography) and the implementation of my code is here https://gitlab.com/jadelynx912/app-test/-/blob/raw-api-softap/lib/services/device_setup_service.dart?ref_type=heads.
I've been having to do the counting for AES-CTR manually due to the way the package is working, and I do +2 during session establishment (for the decryption of the response to verify that a secure session was established) since the message size is 64 bytes (2 blocks). I should have to do +1 for the wifi config, since that message is less than 32 bytes, but that is not working. I've tried +0, +2, +3, and +4 to the nonce as well, but that didn't work either. I know that the issue is not the message I'm sending since I am able to successfully provision on security version 0.
Any help would be appreciated, and thanks in advance for your time.
Here's the code for session establishment (which works, I can confirm secure session established on my end):
Future<(SecretKey, Uint8List)?> sessionEstablishment() async{
final algorithm = X25519();
final keyPair = await algorithm.newKeyPair();
var session0 = SessionData(
secVer: SecSchemeVersion.SecScheme1,
sec1: Sec1Payload(
msg: Sec1MsgType.Session_Command0,
sc0: SessionCmd0(clientPubkey: (await keyPair.extractPublicKey()).bytes)
)
);
var output0 = await apiCall(session0.writeToBuffer(), "prov-session");
if (output0 == null) {
return null;
}
var resp0 = SessionData.fromBuffer(output0);
var devPubKey = SimplePublicKey(resp0.sec1.sr0.devicePubkey, type: KeyPairType.x25519);
List<int> sharedKeyBytes = await (await algorithm.sharedSecretKey(keyPair: keyPair, remotePublicKey: devPubKey)).extractBytes();
var pop = "abcd1234"; //FIXME have the user enter this eventually
var encryptAlg = Sha256();
var hash = await encryptAlg.hash(utf8.encode(pop));
var sharedKey = SecretKey.new(xor(Uint8List.fromList(sharedKeyBytes), Uint8List.fromList(hash.bytes)));
var cli_verify = await AESalg.encrypt(devPubKey.bytes, secretKey: sharedKey, nonce: resp0.sec1.sr0.deviceRandom);
var session1 = SessionData(secVer: SecSchemeVersion.SecScheme1,
sec1: Sec1Payload(
msg: Sec1MsgType.Session_Command1,
sc1: SessionCmd1(clientVerifyData: cli_verify.cipherText)
)
);
var output1 = await apiCall(session1.writeToBuffer(), "prov-session");
if (output1 == null) {
return null;
}
var resp1 = SessionData.fromBuffer(output1);
var newNonce = plus2Nonce(Uint8List.fromList(resp0.sec1.sr0.deviceRandom));
var decrypted_resp1 = await AESalg.decrypt(SecretBox(resp1.sec1.sr1.deviceVerifyData, nonce: newNonce, mac: Mac.empty), secretKey: sharedKey);
print(newNonce);
var i = SimplePublicKey(decrypted_resp1, type: KeyPairType.x25519);
var publicKey = await keyPair.extractPublicKey();
if (publicKey.compareTo(i) == 0){
print("Secure session established");
}
else {
print("Failed to establish secure session");
}
newNonce = plus2Nonce(newNonce);
print(newNonce);
return (sharedKey, newNonce);
}
And here is the code for configuring the wifi:
Future<String?> main() async {
String networkSSID = "test";
String networkPassword = "testpassword";
headers["Content-type"] = "application/x-www-form-urlencoded";
headers["Accept"] = "text/plain";
var sessionInfo = await sessionEstablishment();
if (sessionInfo == null) {
print("Wrong wifi network");
return null;
}
SecretKey sharedKey = sessionInfo.$1;
Uint8List nonce = sessionInfo.$2;
print(nonce);
var body = WiFiConfigPayload(msg: WiFiConfigMsgType.TypeCmdSetConfig, cmdSetConfig: CmdSetConfig(ssid: utf8.encode(networkSSID), passphrase: utf8.encode(networkPassword))).writeToBuffer();
// var encryptedBody = await AESalg.encrypt(body, nonce: nonce, secretKey: sharedKey);
// var body = WiFiScanPayload(msg: WiFiScanMsgType.TypeCmdScanStart, cmdScanStart: CmdScanStart(blocking: true, passive: false, groupChannels: 5, periodMs: 0));
// var encrypted = await AESalg.encrypt(body.writeToBuffer(), nonce: nonce, secretKey: sharedKey);
var output1 = await apiCall(body, "prov-config");
if (output1 == null) {
return null;
}
// var nonce1 = incrementNonce(nonce);
// print(nonce1);
// print(nonce2);
// var respRaw1 = await AESalg.decrypt(SecretBox(output, nonce: nonce1, mac: Mac.empty), secretKey: sharedKey);
// var respRaw2 = await AESalg.decrypt(SecretBox(output, nonce: nonce2, mac: Mac.empty), secretKey: sharedKey);
// var resp = WiFiScanPayload.fromBuffer(respRaw);
var resp = WiFiConfigPayload.fromBuffer(output1);
// var resp2 = WiFiConfigPayload.fromBuffer(respRaw2);
// print(resp2.respGetStatus.staState);
print(resp.respSetConfig.status);
// print(resp.respScanStart);
print("Set config?");
// var nonce2 = incrementNonce(nonce1);
var body2 = WiFiConfigPayload(msg: WiFiConfigMsgType.TypeCmdApplyConfig, cmdApplyConfig: CmdApplyConfig()).writeToBuffer();
// var encryptedBody2 = await AESalg.encrypt(body2.writeToBuffer(), nonce: nonce2, secretKey: sharedKey);
final output2 = await apiCall(body2, "prov-config");
if (output2 == null) {
return null;
}
// var nonce3 = incrementNonce(nonce2);
// var respRaw2 = await AESalg.decrypt(SecretBox(output2, nonce: nonce3, mac: Mac.empty), secretKey: sharedKey);
var resp2 = WiFiConfigPayload.fromBuffer(output2);
print(resp2.respApplyConfig.status);
var body3 = WiFiConfigPayload(msg: WiFiConfigMsgType.TypeCmdGetStatus, cmdGetStatus: CmdGetStatus()).writeToBuffer();
var resp3;
for (var i = 0; i < 5; i++){
final output3 = await apiCall(body3, "prov-config");
if (output3 == null) {
return null;
}
resp3 = WiFiConfigPayload.fromBuffer(output3);
print(resp3.respGetStatus.staState.toString());
if (resp3.respGetStatus.staState.toString() == "Connected" || resp3.respGetStatus.staState.toString() == "ConnectionFailed"){
break;
}
sleep(Duration(seconds: 3));
}
return resp3.respGetStatus.staState.toString();
}
hello I wrote same reply on esp forum but it is being postponing. so I left it here again. I tested your code and found a solution, I used the package 'pointycastle' not cryptography. cause esp use aes/ctr, there are two variables keystream and nonce, you need to manage them whenever crypt and decrpt.
anyway in short, I added here code.
I wish it helps you. good luck.
class AesCtrSession {
final Uint8List key;
final Uint8List nonce;
late StreamCipher _cipher;
AesCtrSession(this.key, this.nonce) {
_cipher = StreamCipher('AES/CTR')..init(
true,
ParametersWithIV(KeyParameter(key), nonce),
); // true = encrypt
}
Uint8List encrypt(Uint8List data) => _cipher.process(data);
Uint8List decrypt(Uint8List data) => _cipher.process(data);
void reset({bool encrypt = true}) {
_cipher = StreamCipher('AES/CTR')
..init(encrypt, ParametersWithIV(KeyParameter(key), nonce));
}
}
final session = AesCtrSession(sedkey, nonce); // put sharedkey and nonce which you added +2 at the end of session establishment.
final encSetWiFi = session.encrypt(setWiFi); // encryptingfinal
respSetWiFi = session.decrypt(resp); // decrypting