diff --git a/src/HAP.cpp b/src/HAP.cpp index 0947103..5a4c4d9 100644 --- a/src/HAP.cpp +++ b/src/HAP.cpp @@ -80,7 +80,7 @@ void HAPClient::init(){ if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data } else { - LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n"); + LOG0("Generating new random Accessory ID and Long-Term Ed25519 Signature Keys...\n\n"); uint8_t buf[6]; char cBuf[18]; @@ -95,21 +95,9 @@ void HAPClient::init(){ nvs_commit(hapNVS); // commit to NVS } - if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS - nvs_get_blob(hapNVS,"CONTROLLERS",controllers,&len); // retrieve data - } else { - LOG0("Initializing storage for Paired Controllers data...\n\n"); - - HAPClient::removeControllers(); // clear all Controller data - - nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update data - nvs_commit(hapNVS); // commit to NVS - } - if(!nvs_get_blob(hapNVS,"CONTROLLERS",NULL,&len)){ // if found long-term Controller Pairings data from NVS TempBuffer tBuf(len/sizeof(Controller)); nvs_get_blob(hapNVS,"CONTROLLERS",tBuf.get(),&len); // retrieve data - Serial.printf("*** SIZE %d ***\n",tBuf.size()); for(int i=0;i tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication @@ -813,6 +800,10 @@ int HAPClient::postPairVerifyURL(){ return(0); } + LOG2("\n*** Verifying session with Controller ID: "); + charPrintRow(tPair->ID,36,2); + LOG2("...\n"); + size_t iosDeviceInfoLen=32+36+32; uint8_t iosDeviceInfo[iosDeviceInfoLen]; @@ -833,7 +824,7 @@ int HAPClient::postPairVerifyURL(){ tlv8.val(kTLVType_State,pairState_M4); // set State= tlvRespond(); // send response to client (unencrypted since cPair=NULL) - cPair=tPair; // save Controller for this connection slot - connection is not verified and should be encrypted going forward + cPair=tPair; // save Controller for this connection slot - connection is now verified and should be encrypted going forward hkdf.create(a2cKey,sharedCurveKey,32,"Control-Salt","Control-Read-Encryption-Key"); // create AccessoryToControllerKey (HAP Section 6.5.2) hkdf.create(c2aKey,sharedCurveKey,32,"Control-Salt","Control-Write-Encryption-Key"); // create ControllerToAccessoryKey (HAP Section 6.5.2) @@ -897,8 +888,6 @@ int HAPClient::postPairingsURL(){ return(0); } - Controller *newCont; - LOG1("In Post Pairings #"); LOG1(conNum); LOG1(" ("); @@ -910,72 +899,61 @@ int HAPClient::postPairingsURL(){ badRequestError(); // return with 400 error, which closes connection return(0); } - + switch(tlv8.val(kTLVType_Method)){ - case 3: + case 3: { LOG1("Add...\n"); if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Permissions)){ LOG0("\n*** ERROR: One or more of required 'Identifier,' 'PublicKey,' and 'Permissions' TLV records for this step is bad or missing\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data) - break; - } + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Unknown); - if(!cPair->admin){ + } else if(!cPair->admin){ LOG0("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication - break; - } - - if((newCont=findController(tlv8.buf(kTLVType_Identifier))) && memcmp(tlv8.buf(kTLVType_PublicKey),newCont->LTPK,32)){ // requested Controller already exists, but LTPKs don't match - LOG0("\n*** ERROR: Invalid request to update the LTPK of an exsiting Controller\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown - break; - } - - if(!addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)==1?true:false)){ - LOG0("\n*** ERROR: Can't pair more than %d Controllers\n\n",MAX_CONTROLLERS); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_MaxPeers); // set Error=MaxPeers - break; + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Authentication); + + } else { + tagError err=addController(tlv8.buf(kTLVType_Identifier),tlv8.buf(kTLVType_PublicKey),tlv8.val(kTLVType_Permissions)); + tlv8.clear(); + if(err!=tagError_None) + tlv8.val(kTLVType_Error,err); } - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - break; + tlv8.val(kTLVType_State,pairState_M2); + tlvRespond(); + return(1); + } - case 4: + case 4: { LOG1("Remove...\n"); + uint8_t id[36]; + if(!tlv8.len(kTLVType_Identifier)){ LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Unknown); // set Error=Unknown (there is no specific error type for missing/bad TLV data) - break; - } - - if(!cPair->admin){ + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Unknown); + + } else if(!cPair->admin){ LOG0("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n"); - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - tlv8.val(kTLVType_Error,tagError_Authentication); // set Error=Authentication - break; + tlv8.clear(); + tlv8.val(kTLVType_Error,tagError_Authentication); + } else { + memcpy(id,tlv8.buf(kTLVType_Identifier),36); + tlv8.clear(); } - removeController(tlv8.buf(kTLVType_Identifier)); + tlv8.val(kTLVType_State,pairState_M2); + tlvRespond(); // must send response before removing Controller below - tlv8.clear(); // clear TLV records - tlv8.val(kTLVType_State,pairState_M2); // set State= - break; + if(tlv8.val(kTLVType_Error)==-1) + removeController(id); + + return(1); + } case 5: { LOG1("List...\n"); @@ -991,37 +969,17 @@ int HAPClient::postPairingsURL(){ LOG2(body); listControllers(tBuf.get()); sendEncrypted(body,tBuf.get(),tBuf.len()); - free(body); + free(body); + return(1); } - default: + default: { LOG0("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n"); badRequestError(); // return with 400 error, which closes connection return(0); - break; - } - - nvs_set_blob(hapNVS,"CONTROLLERS",controllers,sizeof(controllers)); // update Controller data - nvs_commit(hapNVS); // commit to NVS - - tlvRespond(); - - // re-check connections and close any (or all) clients as a result of controllers that were removed above - // must be performed AFTER sending the TLV response, since that connection itself may be terminated below - - for(int i=0;iclient){ // if slot is connected - - if(!nAdminControllers() || (hap[i]->cPair && !hap[i]->cPair->allocated)){ // accessory unpaired, OR client connection is verified but points to a newly *unallocated* controller - LOG1("*** Terminating Client #"); - LOG1(i); - LOG1("\n"); - hap[i]->client.stop(); - } - - } // if client connected - } // loop over all connection slots + } + } // switch return(1); } @@ -1316,8 +1274,8 @@ int HAPClient::getStatusURL(){ char mbtlsv[64]; mbedtls_version_get_string_full(mbtlsv); response+="MbedTLS Version:" + String(mbtlsv) + "\n"; - - response+="HomeKit Status:" + String(nAdminControllers()?"PAIRED":"NOT PAIRED") + "\n"; + + response+="HomeKit Status:" + String(HAPClient::nAdminControllers()?"PAIRED":"NOT PAIRED") + "\n"; response+="Max Log Entries:" + String(homeSpan.webLog.maxEntries) + "\n"; response+="\n"; response+="

"; @@ -1581,10 +1539,10 @@ void HAPClient::charPrintRow(uint8_t *buf, int n, int minLogLevel){ ////////////////////////////////////// Controller *HAPClient::findController(uint8_t *id){ - - for(int i=0;iLTPK,ltpk,32); - slot->admin=admin; - LOG2("\n*** Updated Controller: "); - charPrintRow(id,36,2); - LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n"); - return(slot); - } - - if((slot=getFreeController())){ // get slot for new controller, if available - slot->allocated=true; - memcpy(slot->ID,id,36); - memcpy(slot->LTPK,ltpk,32); - slot->admin=admin; - LOG2("\n*** Added Controller: "); - charPrintRow(id,36,2); - LOG2(slot->admin?" (admin)\n\n":" (regular)\n\n"); - return(slot); - } - - return(NULL); -} - -////////////////////////////////////// - int HAPClient::nAdminControllers(){ int n=0; - - for(int i=0;iLTPK,sizeof(cTemp->LTPK))){ // existing controller with same LTPK + LOG2("\n*** Updated Controller: "); + charPrintRow(id,36,2); + LOG2(" from %s to %s\n\n",cTemp->admin?"(admin)":"(regular)",admin?"(admin)":"(regular)"); + cTemp->admin=admin; + saveControllers(); + } else { + LOG0("\n*** ERROR: Invalid request to update the LTPK of an existing Controller\n\n"); + err=tagError_Unknown; + } + + return(err); +} ////////////////////////////////////// void HAPClient::removeController(uint8_t *id){ - Controller *slot; + auto it=std::find_if(controllerList.begin(), controllerList.end(), [id](const Controller& cTemp){return(!memcmp(cTemp.ID,id,sizeof(cTemp.ID)));}); - if((slot=findController(id))){ // remove controller if found - LOG2("\n*** Removed Controller: "); - charPrintRow(id,36,2); - LOG2(slot->admin?" (admin)\n":" (regular)\n"); - slot->allocated=false; - - if(nAdminControllers()==0){ // if no more admins, remove all controllers - removeControllers(); - LOG1("That was last Admin Controller! Removing any remaining Regular Controllers and unpairing Accessory\n"); - mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) - - STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED) - - if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been un-paired - homeSpan.pairCallback(false); - } - - LOG2("\n"); - } else { + if(it==controllerList.end()){ LOG2("\n*** Request to Remove Controller Ignored - Controller Not Found: "); charPrintRow(id,36,2); LOG2("\n"); + return; } -} + LOG1("\n*** Removing Controller: "); + charPrintRow((*it).ID,36,2); + LOG1((*it).admin?" (admin)\n":" (regular)\n"); + + tearDown((*it).ID); // teardown any connections using this Controller + controllerList.erase(it); // remove Controller + + if(!nAdminControllers()){ // no more admin Controllers + + LOG1("That was last Admin Controller! Removing any remaining Regular Controllers and unpairing Accessory\n"); + + tearDown(NULL); // teardown all remaining connections + controllerList.clear(); // remove all remaining Controllers + mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) + STATUS_UPDATE(start(LED_PAIRING_NEEDED),HS_PAIRING_NEEDED) // set optional Status LED + if(homeSpan.pairCallback) // if set, invoke user-defined Pairing Callback to indicate device has been un-paired + homeSpan.pairCallback(false); + } + + saveControllers(); +} + +////////////////////////////////////// + +void HAPClient::tearDown(uint8_t *id){ + + for(int i=0;iclient && (id==NULL || (hap[i]->cPair && !memcmp(id,hap[i]->cPair->ID,36)))){ + LOG1("*** Terminating Client #%d\n",i); + hap[i]->client.stop(); + } + } +} ////////////////////////////////////// @@ -1744,12 +1698,9 @@ void HAPClient::saveControllers(){ return; } - int n=0; - TempBuffer tBuf(controllerList.size()); // create temporary buffer to hold Controller data + TempBuffer tBuf(controllerList.size()); // create temporary buffer to hold Controller data + std::copy(controllerList.begin(),controllerList.end(),tBuf.get()); // copy data from linked list to buffer - for(auto it=controllerList.begin();it!=controllerList.end();it++) // store Controller data in temporary buffer - tBuf.get()[n++]=(*it); - nvs_set_blob(hapNVS,"CONTROLLERS",tBuf.get(),tBuf.len()); // update data nvs_commit(hapNVS); // commit to NVS } @@ -1792,7 +1743,6 @@ nvs_handle HAPClient::srpNVS; HKDF HAPClient::hkdf; pairState HAPClient::pairStatus; Accessory HAPClient::accessory; -Controller HAPClient::controllers[MAX_CONTROLLERS]; list HAPClient::controllerList; SRP6A HAPClient::srp; int HAPClient::conNum; diff --git a/src/HAP.h b/src/HAP.h index 0c7cedd..b8d2107 100644 --- a/src/HAP.h +++ b/src/HAP.h @@ -50,10 +50,20 @@ struct Nonce { // Paired Controller Structure for Permanently-Stored Data struct Controller { - boolean allocated=false; // slot is allocated with Controller data + boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info) boolean admin; // Controller has admin privileges uint8_t ID[36]; // Pairing ID uint8_t LTPK[32]; // Long Term Ed2519 Public Key + + Controller(){} + + Controller(uint8_t *id, uint8_t *ltpk, boolean ad){ + allocated=true; + admin=ad; + memcpy(ID,id,sizeof(ID)); + memcpy(LTPK,ltpk,sizeof(LTPK)); + } + }; ///////////////////////////////////////////////// @@ -84,14 +94,13 @@ struct HAPClient { static pairState pairStatus; // tracks pair-setup status static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored - static Controller controllers[MAX_CONTROLLERS]; // Paired Controller IDs and ED25519 long-term public keys - permanently stored static list controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored static int conNum; // connection number - used to keep track of per-connection EV notifications // individual structures and data defined for each Hap Client connection WiFiClient client; // handle to client - Controller *cPair; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) + Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection) // These keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period @@ -136,14 +145,13 @@ struct HAPClient { static void charPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match) - static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots) - static Controller *addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns pointer to Controller slot on success, else NULL - static int nAdminControllers(); // returns number of admin Controllers stored - static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots) + static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns tagError (if any) static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements. static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level static int listControllers(uint8_t *tlvBuf); // creates and prints a multi-TLV list of Controllers (HAP Section 5.12) static void saveControllers(); // saves Controller list in NVS + static int nAdminControllers(); // returns number of admin Controller + static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8) static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4) static void eventNotify(SpanBuf *pObj, int nObj, int ignoreClient=-1); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client diff --git a/src/HAPConstants.h b/src/HAPConstants.h index 5ed6ab3..fc87ad3 100644 --- a/src/HAPConstants.h +++ b/src/HAPConstants.h @@ -52,6 +52,7 @@ typedef enum { // HAP Error Codes (HAP Table 5-5) typedef enum { + tagError_None=0x00, tagError_Unknown=0x01, tagError_Authentication=0x02, tagError_Backoff=0x03, diff --git a/src/HomeSpan.cpp b/src/HomeSpan.cpp index a5fbc5f..dac0451 100644 --- a/src/HomeSpan.cpp +++ b/src/HomeSpan.cpp @@ -260,7 +260,7 @@ void Span::pollTask() { homeSpan.lastClientIP="0.0.0.0"; // reset stored IP address to show "0.0.0.0" if homeSpan.getClientIP() is used in any other context if(!hap[i]->client){ // client disconnected by server - LOG1("** Disconnecting Client #"); + LOG1("** Disconnected Client #"); LOG1(i); LOG1(" ("); LOG1(millis()/1000); @@ -555,6 +555,8 @@ void Span::processSerialCommand(const char *c){ switch(c[0]){ case 'Z': { + HAPClient::saveControllers(); + break; TempBuffer tBuf(HAPClient::listControllers(NULL)); HAPClient::listControllers(tBuf.get()); Serial.printf("SIZE = %d\n",tBuf.len()); @@ -688,22 +690,13 @@ void Span::processSerialCommand(const char *c){ case 'U': { - HAPClient::removeControllers(); // clear all Controller data - nvs_set_blob(HAPClient::hapNVS,"CONTROLLERS",HAPClient::controllers,sizeof(HAPClient::controllers)); // update data - nvs_commit(HAPClient::hapNVS); // commit to NVS + HAPClient::controllerList.clear(); // clear all Controller data + HAPClient::saveControllers(); LOG0("\n*** HomeSpan Pairing Data DELETED ***\n\n"); - - for(int i=0;iclient){ // if slot is connected - LOG1("*** Terminating Client #"); - LOG1(i); - LOG1("\n"); - hap[i]->client.stop(); - } - } - + HAPClient::tearDown(NULL); // tear down all verified connections + LOG0("\nDEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n"); - mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) + mdns_service_txt_item_set("_hap","_tcp","sf","1"); // set Status Flag = 1 (Table 6-8) if(homeSpan.pairCallback) homeSpan.pairCallback(false); @@ -1016,11 +1009,9 @@ void Span::processSerialCommand(const char *c){ TempBuffer tBuf(256); mbedtls_base64_encode((uint8_t *)tBuf.get(),256,&olen,(uint8_t *)&HAPClient::accessory,sizeof(struct Accessory)); LOG0("Accessory data: %s\n",tBuf.get()); - for(int i=0;i>> Controller data: "); readSerial(tBuf.get(),199); if(strlen(tBuf.get())==0){ LOG0("(done)\n"); - while(i