Completed and fully tested refactoring of TLV8

Next up: remove old TLV code and clean up
This commit is contained in:
Gregg 2023-12-25 22:04:02 -06:00
parent 76a6a2bde9
commit 3c172da714
5 changed files with 209 additions and 198 deletions

View File

@ -112,17 +112,17 @@ void HAPClient::init(){
printControllers();
tlv8.create(kTLVType_Separator,0,"SEPARATOR"); // define the actual TLV records needed for the implementation of HAP; one for each kTLVType needed (HAP Table 5-6)
tlv8.create(kTLVType_State,1,"STATE");
tlv8.create(kTLVType_PublicKey,384,"PUBKEY");
tlv8.create(kTLVType_Method,1,"METHOD");
tlv8.create(kTLVType_Salt,16,"SALT");
tlv8.create(kTLVType_Error,1,"ERROR");
tlv8.create(kTLVType_Proof,64,"PROOF");
tlv8.create(kTLVType_EncryptedData,1024,"ENC.DATA");
tlv8.create(kTLVType_Signature,64,"SIGNATURE");
tlv8.create(kTLVType_Identifier,64,"IDENTIFIER");
tlv8.create(kTLVType_Permissions,1,"PERMISSION");
// tlv8.create(kTLVType_Separator,0,"SEPARATOR"); // define the actual TLV records needed for the implementation of HAP; one for each kTLVType needed (HAP Table 5-6)
// tlv8.create(kTLVType_State,1,"STATE");
// tlv8.create(kTLVType_PublicKey,384,"PUBKEY");
// tlv8.create(kTLVType_Method,1,"METHOD");
// tlv8.create(kTLVType_Salt,16,"SALT");
// tlv8.create(kTLVType_Error,1,"ERROR");
// tlv8.create(kTLVType_Proof,64,"PROOF");
// tlv8.create(kTLVType_EncryptedData,1024,"ENC.DATA");
// tlv8.create(kTLVType_Signature,64,"SIGNATURE");
// tlv8.create(kTLVType_Identifier,64,"IDENTIFIER");
// tlv8.create(kTLVType_Permissions,1,"PERMISSION");
if(!nvs_get_blob(hapNVS,"HAPHASH",NULL,&len)){ // if found HAP HASH structure
nvs_get_blob(hapNVS,"HAPHASH",&homeSpan.hapConfig,&len); // retrieve data
@ -217,33 +217,23 @@ void HAPClient::processRequest(){
if(cLen==0){
badRequestError();
LOG0("\n*** ERROR: HTTP POST request contains no Content\n\n");
return;
}
if(!strncmp(body,"POST /pair-setup ",17) && strstr(body,"Content-Type: application/pairing+tlv8")){ // POST PAIR-SETUP
else if(!strncmp(body,"POST /pair-setup ",17) && strstr(body,"Content-Type: application/pairing+tlv8")) // POST PAIR-SETUP
postPairSetupURL(content,cLen);
return;
}
if(!strncmp(body,"POST /pair-verify ",18) && strstr(body,"Content-Type: application/pairing+tlv8")){ // POST PAIR-VERIFY
else if(!strncmp(body,"POST /pair-verify ",18) && strstr(body,"Content-Type: application/pairing+tlv8")) // POST PAIR-VERIFY
postPairVerifyURL(content,cLen);
return;
else if(!strncmp(body,"POST /pairings ",15) && strstr(body,"Content-Type: application/pairing+tlv8")) // POST PAIRINGS
postPairingsURL(content,cLen);
else {
notFoundError();
LOG0("\n*** ERROR: Bad POST request - URL not found\n\n");
}
if(!strncmp(body,"POST /pairings ",15) && // POST PAIRINGS
strstr(body,"Content-Type: application/pairing+tlv8") && // check that content is TLV8
tlv8.unpack(content,cLen)){ // read TLV content
tlv8.print(2); // print TLV records in form "TAG(INT) LENGTH(INT) VALUES(HEX)"
LOG2("------------ END TLVS! ------------\n");
postPairingsURL(); // process URL
return;
}
notFoundError();
LOG0("\n*** ERROR: Bad POST request - URL not found\n\n");
return;
} // POST request
if(!strncmp(body,"PUT ",4)){ // this is a PUT request
@ -381,7 +371,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
auto itState=iosTLV.find(kTLVType_State);
if(iosTLV.len(itState)!=1){ // missing STATE TLV
LOG0("\n*** ERROR: Missing or invalid <M#> State TLV\n\n");
LOG0("\n*** ERROR: Missing or invalid 'State' TLV\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
}
@ -627,7 +617,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
auto itState=iosTLV.find(kTLVType_State);
if(iosTLV.len(itState)!=1){ // missing STATE TLV
LOG0("\n*** ERROR: Missing or invalid <M#> State TLV\n\n");
LOG0("\n*** ERROR: Missing or invalid 'State' TLV\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
}
@ -828,101 +818,129 @@ int HAPClient::getAccessoriesURL(){
//////////////////////////////////////
int HAPClient::postPairingsURL(){
int HAPClient::postPairingsURL(uint8_t *content, size_t len){
if(!cPair){ // unverified, unencrypted session
unauthorizedError();
return(0);
}
LOG1("In Post Pairings #");
LOG1(conNum);
LOG1(" (");
LOG1(client.remoteIP());
LOG1(")...");
HAPTLV iosTLV;
HAPTLV responseTLV;
if(tlv8.val(kTLVType_State)!=1){
LOG0("\n*** ERROR: 'State' TLV record is either missing or not set to <M1> as required\n\n");
badRequestError(); // return with 400 error, which closes connection
iosTLV.unpack(content,len);
iosTLV.print();
LOG2("------------ END TLVS! ------------\n");
LOG2("In Post Pairings #%d (%s)...",conNum,client.remoteIP().toString().c_str());
auto itState=iosTLV.find(kTLVType_State);
auto itMethod=iosTLV.find(kTLVType_Method);
if(iosTLV.len(itState)!=1 || (*itState)[0]!=1){ // missing STATE TLV
LOG0("\n*** ERROR: Parirings 'State' is either missing or not set to <M1>\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
}
switch(tlv8.val(kTLVType_Method)){
if(iosTLV.len(itMethod)!=1){ // missing METHOD TLV
LOG0("\n*** ERROR: Missing or invalid 'Method' TLV\n\n");
badRequestError(); // return with 400 error, which closes connection
return(0);
}
int tlvMethod=(*itMethod)[0];
responseTLV.add(kTLVType_State,pairState_M2); // all responses include State=M2
switch(tlvMethod){ // List-Pairings received -- process request! (HAP Sections 5.10-5.12)
case 3: {
LOG1("Add...\n");
if(!tlv8.len(kTLVType_Identifier) || !tlv8.len(kTLVType_PublicKey) || !tlv8.len(kTLVType_Permissions)){
auto itIdentifier=iosTLV.find(kTLVType_Identifier);
auto itPublicKey=iosTLV.find(kTLVType_PublicKey);
auto itPermissions=iosTLV.find(kTLVType_Permissions);
if(iosTLV.len(itIdentifier)!=hap_controller_IDBYTES || iosTLV.len(itPublicKey)!=crypto_sign_PUBLICKEYBYTES || iosTLV.len(itPermissions)!=1){
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();
tlv8.val(kTLVType_Error,tagError_Unknown);
} else if(!cPair->admin){
LOG0("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n");
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);
responseTLV.add(kTLVType_Error,tagError_Unknown);
tlvRespond(responseTLV);
return(0);
}
tlv8.val(kTLVType_State,pairState_M2);
tlvRespond();
if(!cPair->admin){
LOG0("\n*** ERROR: Controller making request does not have admin privileges to add/update other Controllers\n\n");
responseTLV.add(kTLVType_Error,tagError_Authentication);
tlvRespond(responseTLV);
return(0);
}
tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions)[0]);
if(err!=tagError_None)
responseTLV.add(kTLVType_Error,err);
tlvRespond(responseTLV);
return(1);
}
break;
case 4: {
LOG1("Remove...\n");
uint8_t id[36];
auto itIdentifier=iosTLV.find(kTLVType_Identifier);
if(!tlv8.len(kTLVType_Identifier)){
if(iosTLV.len(itIdentifier)!=hap_controller_IDBYTES){
LOG0("\n*** ERROR: Required 'Identifier' TLV record for this step is bad or missing\n\n");
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();
tlv8.val(kTLVType_Error,tagError_Authentication);
} else {
memcpy(id,tlv8.buf(kTLVType_Identifier),36);
tlv8.clear();
responseTLV.add(kTLVType_Error,tagError_Unknown);
tlvRespond(responseTLV);
return(0);
}
tlv8.val(kTLVType_State,pairState_M2);
tlvRespond(); // must send response before removing Controller below
if(!cPair->admin){
LOG0("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n");
responseTLV.add(kTLVType_Error,tagError_Authentication);
tlvRespond(responseTLV);
return(0);
}
if(tlv8.val(kTLVType_Error)==-1)
removeController(id);
tlvRespond(responseTLV); // must send response before removing Controller
removeController(*itIdentifier);
return(1);
}
break;
case 5: {
LOG1("List...\n");
TempBuffer<uint8_t> tBuf(listControllers(NULL));
if(!cPair->admin){
LOG0("\n*** ERROR: Controller making request does not have admin privileges to remove Controllers\n\n");
responseTLV.add(kTLVType_Error,tagError_Authentication);
tlvRespond(responseTLV);
return(0);
}
char *body;
asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",tBuf.len()); // create Body with Content Length = size of TLV data
boolean addSeparator=false;
LOG2("\n>>>>>>>>>> ");
LOG2(client.remoteIP());
LOG2(" >>>>>>>>>>\n");
LOG2(body);
listControllers(tBuf);
sendEncrypted(body,tBuf,tBuf.len());
free(body);
for(auto it=controllerList.begin();it!=controllerList.end();it++){
if((*it).allocated){
if(addSeparator)
responseTLV.add(kTLVType_Separator);
responseTLV.add(kTLVType_Permissions,(*it).admin);
responseTLV.add(kTLVType_Identifier,hap_controller_IDBYTES,(*it).ID);
responseTLV.add(kTLVType_PublicKey,crypto_sign_PUBLICKEYBYTES,(*it).LTPK);
addSeparator=true;
}
}
tlvRespond(responseTLV);
return(1);
}
break;
default: {
LOG0("\n*** ERROR: 'Method' TLV record is either missing or not set to either 3, 4, or 5 as required\n\n");
LOG0("\n*** ERROR: Undefined List-Pairings Method: %d. Must be 3, 4, or 5\n\n",tlvMethod);
badRequestError(); // return with 400 error, which closes connection
return(0);
}
@ -1372,31 +1390,31 @@ void HAPClient::tlvRespond(TLV8 &tlv8){
//////////////////////////////////////
void HAPClient::tlvRespond(){
TempBuffer<uint8_t> tBuf(tlv8.pack(NULL)); // create buffer to hold TLV data
tlv8.pack(tBuf); // pack TLV records into buffer
char *body;
asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",tBuf.len()); // create Body with Content Length = size of TLV data
LOG2("\n>>>>>>>>>> ");
LOG2(client.remoteIP());
LOG2(" >>>>>>>>>>\n");
LOG2(body);
tlv8.print(2);
if(!cPair){ // unverified, unencrypted session
client.print(body);
client.write(tBuf,tBuf.len());
LOG2("------------ SENT! --------------\n");
} else {
sendEncrypted(body,tBuf,tBuf.len());
}
free(body);
} // tlvRespond
//void HAPClient::tlvRespond(){
//
// TempBuffer<uint8_t> tBuf(tlv8.pack(NULL)); // create buffer to hold TLV data
// tlv8.pack(tBuf); // pack TLV records into buffer
//
// char *body;
// asprintf(&body,"HTTP/1.1 200 OK\r\nContent-Type: application/pairing+tlv8\r\nContent-Length: %d\r\n\r\n",tBuf.len()); // create Body with Content Length = size of TLV data
//
// LOG2("\n>>>>>>>>>> ");
// LOG2(client.remoteIP());
// LOG2(" >>>>>>>>>>\n");
// LOG2(body);
// tlv8.print(2);
//
// if(!cPair){ // unverified, unencrypted session
// client.print(body);
// client.write(tBuf,tBuf.len());
// LOG2("------------ SENT! --------------\n");
// } else {
// sendEncrypted(body,tBuf,tBuf.len());
// }
//
// free(body);
//
//} // tlvRespond
//////////////////////////////////////
@ -1619,33 +1637,33 @@ void HAPClient::tearDown(uint8_t *id){
//////////////////////////////////////
int HAPClient::listControllers(uint8_t *tlvBuf){
int nBytes=0;
int n;
tlv8.clear();
tlv8.val(kTLVType_State,pairState_M2);
for(auto it=controllerList.begin();it!=controllerList.end();it++){
if((*it).allocated){
if(tlv8.val(kTLVType_State)==-1) // if State is not set then this is not the first controller found
tlv8.val(kTLVType_Separator,1);
tlv8.val(kTLVType_Permissions,(*it).admin);
tlv8.buf(kTLVType_Identifier,(*it).ID,36);
tlv8.buf(kTLVType_PublicKey,(*it).LTPK,32);
n=tlv8.pack(tlvBuf);
nBytes+=n;
if(tlvBuf){
tlvBuf+=n;
tlv8.print();
}
tlv8.clear();
}
}
return(nBytes);
}
//int HAPClient::listControllers(uint8_t *tlvBuf){
//
// int nBytes=0;
// int n;
//
// tlv8.clear();
// tlv8.val(kTLVType_State,pairState_M2);
//
// for(auto it=controllerList.begin();it!=controllerList.end();it++){
// if((*it).allocated){
// if(tlv8.val(kTLVType_State)==-1) // if State is not set then this is not the first controller found
// tlv8.val(kTLVType_Separator,1);
// tlv8.val(kTLVType_Permissions,(*it).admin);
// tlv8.buf(kTLVType_Identifier,(*it).ID,36);
// tlv8.buf(kTLVType_PublicKey,(*it).LTPK,32);
// n=tlv8.pack(tlvBuf);
// nBytes+=n;
// if(tlvBuf){
// tlvBuf+=n;
// tlv8.print();
// }
// tlv8.clear();
// }
// }
//
// return(nBytes);
//}
//////////////////////////////////////
@ -1716,7 +1734,7 @@ void Nonce::inc(){
// instantiate all static HAP Client structures and data
TLV<kTLVType,11> HAPClient::tlv8;
//TLV<kTLVType,11> HAPClient::tlv8;
nvs_handle HAPClient::hapNVS;
nvs_handle HAPClient::srpNVS;
HKDF HAPClient::hkdf;

View File

@ -30,26 +30,29 @@
#include <WiFi.h>
#include "HomeSpan.h"
#include "TLV.h"
//#include "TLV.h"
#include "HAPConstants.h"
#include "HKDF.h"
#include "SRP.h"
#include "TLV8.h"
const TLV8_names HAP_Names[] = {
{kTLVType_Separator,"*SEPARATOR"},
{kTLVType_State,"*STATE"},
{kTLVType_PublicKey,"*PUBKEY"},
{kTLVType_Method,"*METHOD"},
{kTLVType_Salt,"*SALT"},
{kTLVType_Error,"*ERROR"},
{kTLVType_Proof,"*PROOF"},
{kTLVType_EncryptedData,"*ENC.DATA"},
{kTLVType_Signature,"*SIGNATURE"},
{kTLVType_Identifier,"*IDENTIFIER"},
{kTLVType_Permissions,"*PERMISSION"}
{kTLVType_Separator,"SEPARATOR"},
{kTLVType_State,"STATE"},
{kTLVType_PublicKey,"PUBKEY"},
{kTLVType_Method,"METHOD"},
{kTLVType_Salt,"SALT"},
{kTLVType_Error,"ERROR"},
{kTLVType_Proof,"PROOF"},
{kTLVType_EncryptedData,"ENC.DATA"},
{kTLVType_Signature,"SIGNATURE"},
{kTLVType_Identifier,"IDENTIFIER"},
{kTLVType_Permissions,"PERMISSION"}
};
#define hap_controller_IDBYTES 36
#define hap_accessory_IDBYTES 17
/////////////////////////////////////////////////
// NONCE Structure (HAP used last 64 of 96 bits)
@ -67,7 +70,7 @@ struct Nonce {
struct Controller {
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 ID[hap_controller_IDBYTES]; // Pairing ID
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
Controller(){}
@ -85,9 +88,9 @@ struct Controller {
// Accessory Structure for Permanently-Stored Data
struct Accessory {
uint8_t ID[17]; // Pairing ID in form "XX:XX:XX:XX:XX:XX"
uint8_t LTSK[crypto_sign_SECRETKEYBYTES]; // secret key for Ed25519 signatures
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // public key for Ed25519 signatures
uint8_t ID[hap_accessory_IDBYTES]; // Pairing ID in form "XX:XX:XX:XX:XX:XX" (no null terminator)
uint8_t LTSK[crypto_sign_SECRETKEYBYTES]; // Long Term Ed2519 Secret Key
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
};
/////////////////////////////////////////////////
@ -102,7 +105,7 @@ struct HAPClient {
static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16)
static const int MAX_ACCESSORIES=150; // maximum number of allowed Accessories (HAP limit=150)
static TLV<kTLVType,11> tlv8; // TLV8 structure (HAP Section 14.1) with space for 11 TLV records of type kTLVType (HAP Table 5-6)
// static TLV<kTLVType,11> tlv8; // TLV8 structure (HAP Section 14.1) with space for 11 TLV records of type kTLVType (HAP Table 5-6)
static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data
static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data
static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string
@ -136,14 +139,14 @@ struct HAPClient {
void processRequest(); // process HAP request
int postPairSetupURL(uint8_t *content, size_t len); // POST /pair-setup (HAP Section 5.6)
int postPairVerifyURL(uint8_t *content, size_t len); // POST /pair-verify (HAP Section 5.7)
int postPairingsURL(uint8_t *content, size_t len); // POST /pairings (HAP Sections 5.10-5.12)
int getAccessoriesURL(); // GET /accessories (HAP Section 6.6)
int postPairingsURL(); // POST /pairings (HAP Sections 5.10-5.12)
int getCharacteristicsURL(char *urlBuf); // GET /characteristics (HAP Section 6.7.4)
int putCharacteristicsURL(char *json); // PUT /characteristics (HAP Section 6.7.2)
int putPrepareURL(char *json); // PUT /prepare (HAP Section 6.7.2.4)
int getStatusURL(); // GET / status (an optional, non-HAP feature)
void tlvRespond(); // respond to client with HTTP OK header and all defined TLV data records (those with length>0)
// void tlvRespond(); // respond to client with HTTP OK header and all defined TLV data records (those with length>0)
void tlvRespond(TLV8 &tlv8); // respond to client with HTTP OK header and all defined TLV data records
void sendEncrypted(char *body, uint8_t *dataBuf, int dataLen); // send client complete ChaCha20-Poly1305 encrypted HTTP mesage comprising a null-terminated 'body' and 'dataBuf' with 'dataLen' bytes
int receiveEncrypted(uint8_t *httpBuf, int messageSize); // decrypt HTTP request (HAP Section 6.5)
@ -164,7 +167,7 @@ struct HAPClient {
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 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

View File

@ -578,16 +578,6 @@ void Span::processSerialCommand(const char *c){
switch(c[0]){
case 'Z': {
HAPClient::saveControllers();
break;
TempBuffer<uint8_t> tBuf(HAPClient::listControllers(NULL));
HAPClient::listControllers(tBuf);
Serial.printf("SIZE = %d\n",tBuf.len());
HAPClient::hexPrintRow(tBuf,tBuf.len());
break;
}
case 's': {
LOG0("\n*** HomeSpan Status ***\n\n");

View File

@ -239,30 +239,30 @@ void SRP6A::createProof(){
//////////////////////////////////////
int SRP6A::loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes){
uint8_t *buf=HAPClient::tlv8.buf(tag,nBytes);
if(!buf)
return(0);
mbedtls_mpi_write_binary(mpi,buf,nBytes);
return(1);
}
//////////////////////////////////////
int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
int nBytes=HAPClient::tlv8.len(tag);
if(nBytes>0){
mbedtls_mpi_read_binary(mpi,HAPClient::tlv8.buf(tag),nBytes);
return(1);
};
return(0);
}
//int SRP6A::loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes){
//
// uint8_t *buf=HAPClient::tlv8.buf(tag,nBytes);
//
// if(!buf)
// return(0);
//
// mbedtls_mpi_write_binary(mpi,buf,nBytes);
// return(1);
//}
//
////////////////////////////////////////
//
//int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
//
// int nBytes=HAPClient::tlv8.len(tag);
//
// if(nBytes>0){
// mbedtls_mpi_read_binary(mpi,HAPClient::tlv8.buf(tag),nBytes);
// return(1);
// };
//
// return(0);
//}
//////////////////////////////////////

View File

@ -81,8 +81,8 @@ struct SRP6A {
void createPublicKey(); // computes x, v, and B from random s, P, and b
void createSessionKey(); // computes u from A and B, and then S from A, v, u, and b
int loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes); // load binary contents of mpi into a TLV record and set its length
int writeTLV(kTLVType tag, mbedtls_mpi *mpi); // write binary contents of a TLV record into an mpi
// int loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes); // load binary contents of mpi into a TLV record and set its length
// int writeTLV(kTLVType tag, mbedtls_mpi *mpi); // write binary contents of a TLV record into an mpi
int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure)
void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client