Обновить Telegram-бота, документацию и связанные доработки

This commit is contained in:
AidarKC
2026-06-06 13:45:02 +04:00
parent ce5c348023
commit c5ec32f87a
16 changed files with 772 additions and 56 deletions
@@ -4,9 +4,12 @@
* Результат сохраняется в NVS (внутренняя flash ESP32).
*
* Алгоритм совпадает с JS crypto-utils.js:
* salt = SHA256("shine-auth-v2|login=<login>|suffix=master.secret")[0:16]
* passBytes = UTF8("<login>\n<password>")
* loginNorm = trim(lowercase(login))
* salt = SHA256("shine-auth-v2|login=<loginNorm>|suffix=master.secret")[0:16]
* passBytes = UTF8("<loginNorm>\n<password>")
* secret = Argon2id(passBytes, salt, t=2, m=65536 KB, p=1, dkLen=32)
* legacy(empty password):
* secret = SHA256(base64(SHA256(password)) + "master.secret")
* keyPair_i = Ed25519(SHA256(base64(secret) + "|" + suffix_i))
* suffixes = ["root.key", "bch.key", "dev.key"]
*
@@ -105,6 +108,7 @@ static char gLogin[64] = {};
static char gPass[64] = {};
static uint8_t gSecret[32];
static char gSecretHex[65];
static char gInputStatus[96] = {};
static bool gKbNums = false;
// ═══════════════════════════════════════════════════════════
@@ -356,6 +360,61 @@ static void sha256calc(const uint8_t *in,size_t len,uint8_t *out32){
mbedtls_sha256_free(&ctx);
}
static void trimInPlace(char *text){
if(!text)return;
size_t len=strlen(text);
size_t start=0;
while(start<len&&(text[start]==' '||text[start]=='\t'||text[start]=='\n'||text[start]=='\r'))start++;
size_t end=len;
while(end>start&&(text[end-1]==' '||text[end-1]=='\t'||text[end-1]=='\n'||text[end-1]=='\r'))end--;
if(start>0&&end>start)memmove(text,text+start,end-start);
if(end<=start){text[0]='\0';return;}
text[end-start]='\0';
}
static void lowercaseAsciiInPlace(char *text){
if(!text)return;
for(int i=0;text[i];i++){
if(text[i]>='A'&&text[i]<='Z')text[i]+=32;
}
}
static void normalizeLoginInPlace(char *text){
trimInPlace(text);
lowercaseAsciiInPlace(text);
}
static void setInputStatus(const char *message){
snprintf(gInputStatus,sizeof(gInputStatus),"%s",message?message:"");
}
static void clearInputStatus(){
gInputStatus[0]='\0';
}
static void bytesToBase64Std(const uint8_t *data,size_t len,char *out,size_t outSz){
size_t b64len=0;
if(outSz==0)return;
if(mbedtls_base64_encode((uint8_t*)out,outSz,&b64len,data,len)!=0){
out[0]='\0';
return;
}
if(b64len>=outSz)b64len=outSz-1;
out[b64len]='\0';
}
static void deriveLegacyMasterSecret(const char *password,uint8_t *out32){
uint8_t baseHash[32];
sha256calc((const uint8_t*)password,strlen(password),baseHash);
char baseB64[64];
bytesToBase64Std(baseHash,32,baseB64,sizeof(baseB64));
char material[96];
snprintf(material,sizeof(material),"%smaster.secret",baseB64);
sha256calc((const uint8_t*)material,strlen(material),out32);
}
// ═══════════════════════════════════════════════════════════
// BASE58
// ═══════════════════════════════════════════════════════════
@@ -473,6 +532,13 @@ static void argon2Init(const char *login,const char *password){
#undef B2LE32
b2_final(&gB2S,gH0);
// Debug: print inputs and H0 to Serial for cross-check with reference
Serial.printf("[DBG] login='%s' passLen=%u saltLen=%u\n",login,passLen,saltLen);
Serial.print("[DBG] salt: ");
for(int i=0;i<16;i++)Serial.printf("%02x",salt[i]);Serial.println();
Serial.print("[DBG] H0: ");
for(int i=0;i<64;i++)Serial.printf("%02x",gH0[i]);Serial.println();
uint8_t input[72];memcpy(input,gH0,64);
for(uint32_t i=0;i<2;i++){
input[64]=i;input[65]=0;input[66]=0;input[67]=0;
@@ -541,6 +607,13 @@ static void argon2Finalize(){
Serial.println("=== END ===");
}
static void completeResultFromSecret(){
for(int i=0;i<32;i++)snprintf(gSecretHex+i*2,3,"%02x",gSecret[i]);
gSecretHex[64]='\0';
deriveKeyPairs();
nvsSave();
}
// ═══════════════════════════════════════════════════════════
// UI — HELPERS
// ═══════════════════════════════════════════════════════════
@@ -592,6 +665,9 @@ static void drawInputScreen(const char *title,const char *buf,bool isMasked){
else{strncpy(show,buf,67);show[67]='\0';}
gfx->setTextSize(2);gfx->setTextColor(C_TEXT);gfx->setCursor(30,78);gfx->print(show);
int cx=30+strlen(show)*12;gfx->drawFastVLine(cx,78,20,C_GREEN);
if(gInputStatus[0]){
gfx->setTextSize(2);gfx->setTextColor(C_RED);gfx->setCursor(20,132);gfx->print(gInputStatus);
}
drawKeyboard();
}
@@ -628,6 +704,7 @@ static void drawProgressHeader(){
// нарисовать пустую полосу один раз
int bx=20,by=90,bw=DISP_W-40,bh=36;
uiRect(bx,by,bw,bh,C_BAR_BG,C_SEP,4);
gfx->fillRect(bx+1,by+1,bw-2,bh-2,C_BG);
}
static void drawProgress(){
@@ -639,7 +716,7 @@ static void drawProgress(){
int filled=(int)((uint64_t)bw*gDone/TOTAL_FILLS);
if(gDone>0&&filled==0)filled=1; // at least 1px once started
if(filled>gBarFilledPx){
gfx->fillRect(bx+gBarFilledPx,by+1,filled-gBarFilledPx,bh-2,C_BAR_FG);
gfx->fillRect(bx+1+gBarFilledPx,by+1,filled-gBarFilledPx,bh-2,C_GREEN);
gBarFilledPx=filled;
}
@@ -722,9 +799,11 @@ static void drawResultContent(){
char buf[96];
// — заголовок —
d.line("RESULT",C_GREEN,2);
d.line(" RESULT",C_GREEN,2);
snprintf(buf,sizeof(buf),"Login: %s",gLogin);
d.line(buf,C_TEXT,2);
snprintf(buf,sizeof(buf),"Password: %s",gPass[0]?gPass:"(empty)");
d.line(buf,C_TEXT,2);
d.gap(4);
d.line("Master secret (base58):",C_HINT,2);
d.longval(gSecretB58,C_GREEN);
@@ -758,7 +837,6 @@ static void drawResultBtnBar(){
}
static void drawResultFull(){
gScrollY=0;
drawResultContent();
drawResultBtnBar();
}
@@ -790,27 +868,53 @@ static void handleTap(int tx,int ty){
char *buf=isPass?gPass:gLogin;
char c=kbTouch(tx,ty);
if(!c)return;
if(c=='\x03'){gKbNums=!gKbNums;drawInputScreen(isPass?"Password:":"Login:",buf,isPass);return;}
if(c=='\x08'){int n=strlen(buf);if(n>0)buf[n-1]='\0';}
if(c=='\x03'){gKbNums=!gKbNums;clearInputStatus();drawInputScreen(isPass?"Password:":"Login:",buf,isPass);return;}
if(c=='\x08'){int n=strlen(buf);if(n>0)buf[n-1]='\0';clearInputStatus();}
else if(c=='\x0D'){
if(!strlen(buf))return;
if(gState==ST_LOGIN){
for(int i=0;buf[i];i++)if(buf[i]>='A'&&buf[i]<='Z')buf[i]+=32;
normalizeLoginInPlace(buf);
if(!strlen(buf)){
setInputStatus("Логин обязателен");
drawInputScreen("Login:",gLogin,false);
return;
}
clearInputStatus();
gState=ST_PASS;drawInputScreen("Password:",gPass,true);return;
} else {
clearInputStatus();
gState=ST_RUNNING;
gfx->fillScreen(C_BG);drawProgressHeader();
SD_MMC.remove(SD_MEM_FILE);
gSdFile=SD_MMC.open(SD_MEM_FILE,FILE_WRITE);
gSdFile=SD_MMC.open(SD_MEM_FILE,"w+");
if(!gSdFile){
gfx->setTextSize(2);gfx->setTextColor(C_RED);
gfx->setCursor(20,200);gfx->println("SD open failed");
gState=ST_ERROR;return;
}
normalizeLoginInPlace(gLogin);
if(!gLogin[0]){
gSdFile.close();
SD_MMC.remove(SD_MEM_FILE);
gState=ST_LOGIN;
setInputStatus("Логин обязателен");
drawInputScreen("Login:",gLogin,false);
return;
}
if(!gPass[0]){
deriveLegacyMasterSecret(gPass,gSecret);
gElapsedSec=0;
completeResultFromSecret();
gSdFile.close();
SD_MMC.remove(SD_MEM_FILE);
gState=ST_DONE;
gScrollY=0;
drawResultFull();
return;
}
argon2Init(gLogin,gPass);
drawProgress();return;
}
} else {int n=strlen(buf);if(n<63){buf[n]=c;buf[n+1]='\0';}}
} else {int n=strlen(buf);if(n<63){buf[n]=c;buf[n+1]='\0';}clearInputStatus();}
drawInputScreen(isPass?"Password:":"Login:",buf,isPass);
return;
}
@@ -875,6 +979,25 @@ void setup(){
while(true)delay(1000);
}
// BLAKE2b self-test: BLAKE2b-512("") = 786a02f7...
{
static const uint8_t EXP[64]={
0x78,0x6a,0x02,0xf7,0x42,0x01,0x59,0x03,0xc6,0xc6,0xfd,0x85,0x25,0x52,0xd2,0x72,
0x91,0x2f,0x47,0x40,0xe1,0x58,0x47,0x61,0x8a,0x86,0xe2,0x17,0xf7,0x1f,0x54,0x19,
0xd2,0x5e,0x10,0x31,0xaf,0xee,0x58,0x53,0x13,0x89,0x64,0x44,0x93,0x4e,0xb0,0x4b,
0x90,0x3a,0x68,0x5b,0x14,0x48,0xb7,0x55,0xd5,0x6f,0x70,0x1a,0xfe,0x9b,0xe2,0xce
};
uint8_t got[64];
b2_init(&gB2S,64);b2_final(&gB2S,got);
bool ok=(memcmp(got,EXP,64)==0);
Serial.printf("BLAKE2b-512('') : %s\n",ok?"PASS":"FAIL");
if(!ok){
Serial.print("got: ");
for(int i=0;i<64;i++)Serial.printf("%02x",got[i]);
Serial.println();
}
}
// PSRAM буферы
gBufPrev=(uint8_t*)ps_malloc(A2_BLKSZ);gBufRef=(uint8_t*)ps_malloc(A2_BLKSZ);
gBufOut=(uint8_t*)ps_malloc(A2_BLKSZ);gBufZero=(uint8_t*)ps_calloc(1,A2_BLKSZ);