Subversion Repositories ESP8266_P1_Meter

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 raymond 1
// SPDX-License-Identifier: LGPL-3.0-or-later
2
// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles
3
 
4
#include "WebAuthentication.h"
5
#include "AsyncWebServerLogging.h"
6
#include <libb64/cencode.h>
7
 
8
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(HOST)
9
#include <MD5Builder.h>
10
#else
11
#include <md5.h>
12
#endif
13
 
14
#include "./literals.h"
15
 
16
using namespace asyncsrv;
17
 
18
// Basic Auth hash = base64("username:password")
19
 
20
bool checkBasicAuthentication(const char *hash, const char *username, const char *password) {
21
  if (username == NULL || password == NULL || hash == NULL) {
22
    return false;
23
  }
24
  return generateBasicHash(username, password).equalsIgnoreCase(hash);
25
}
26
 
27
String generateBasicHash(const char *username, const char *password) {
28
  if (username == NULL || password == NULL) {
29
    return asyncsrv::emptyString;
30
  }
31
 
32
  size_t toencodeLen = strlen(username) + strlen(password) + 1;
33
 
34
  char *toencode = new char[toencodeLen + 1];
35
  if (toencode == NULL) {
36
    return asyncsrv::emptyString;
37
  }
38
  char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
39
  if (encoded == NULL) {
40
    delete[] toencode;
41
    return asyncsrv::emptyString;
42
  }
43
  sprintf_P(toencode, PSTR("%s:%s"), username, password);
44
  if (base64_encode_chars(toencode, toencodeLen, encoded) > 0) {
45
    String res = String(encoded);
46
    delete[] toencode;
47
    delete[] encoded;
48
    return res;
49
  }
50
  delete[] toencode;
51
  delete[] encoded;
52
  return asyncsrv::emptyString;
53
}
54
 
55
static bool getMD5(uint8_t *data, uint16_t len, char *output) {  // 33 bytes or more
56
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(HOST)
57
  MD5Builder md5;
58
  md5.begin();
59
  md5.add(data, len);
60
  md5.calculate();
61
  md5.getChars(output);
62
#else
63
  md5_context_t _ctx;
64
 
65
  uint8_t *_buf = (uint8_t *)malloc(16);
66
  if (_buf == NULL) {
67
    return false;
68
  }
69
  memset(_buf, 0x00, 16);
70
 
71
  MD5Init(&_ctx);
72
  MD5Update(&_ctx, data, len);
73
  MD5Final(_buf, &_ctx);
74
 
75
  for (uint8_t i = 0; i < 16; i++) {
76
    sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
77
  }
78
 
79
  free(_buf);
80
#endif
81
  return true;
82
}
83
 
84
String genRandomMD5() {
85
#ifdef ESP8266
86
  uint32_t r = RANDOM_REG32;
87
#else
88
  uint32_t r = rand();  // NOLINT(runtime/threadsafe_fn)
89
#endif
90
  char *out = (char *)malloc(33);
91
  if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) {
92
    async_ws_log_e("Failed to allocate");
93
    return asyncsrv::emptyString;
94
  }
95
  String res = String(out);
96
  free(out);
97
  return res;
98
}
99
 
100
static String stringMD5(const String &in) {
101
  char *out = (char *)malloc(33);
102
  if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
103
    async_ws_log_e("Failed to allocate");
104
    return asyncsrv::emptyString;
105
  }
106
  String res = String(out);
107
  free(out);
108
  return res;
109
}
110
 
111
String generateDigestHash(const char *username, const char *password, const char *realm) {
112
  if (username == NULL || password == NULL || realm == NULL) {
113
    return asyncsrv::emptyString;
114
  }
115
  char *out = (char *)malloc(33);
116
  if (out == NULL) {
117
    async_ws_log_e("Failed to allocate");
118
    return asyncsrv::emptyString;
119
  }
120
 
121
  String in;
122
  if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) {
123
    async_ws_log_e("Failed to allocate");
124
    free(out);
125
    return asyncsrv::emptyString;
126
  }
127
 
128
  in.concat(username);
129
  in.concat(':');
130
  in.concat(realm);
131
  in.concat(':');
132
  in.concat(password);
133
 
134
  if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
135
    async_ws_log_e("Failed to allocate");
136
    free(out);
137
    return asyncsrv::emptyString;
138
  }
139
 
140
  in = String(out);
141
  free(out);
142
  return in;
143
}
144
 
145
bool checkDigestAuthentication(
146
  const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
147
  const char *opaque, const char *uri
148
) {
149
  if (username == NULL || password == NULL || header == NULL || method == NULL) {
150
    // os_printf("AUTH FAIL: missing required fields\n");
151
    return false;
152
  }
153
 
154
  String myHeader(header);
155
  int nextBreak = myHeader.indexOf(',');
156
  if (nextBreak < 0) {
157
    // os_printf("AUTH FAIL: no variables\n");
158
    return false;
159
  }
160
 
161
  String myUsername;
162
  String myRealm;
163
  String myNonce;
164
  String myUri;
165
  String myResponse;
166
  String myQop;
167
  String myNc;
168
  String myCnonce;
169
 
170
  myHeader += (char)0x2c;  // ','
171
  myHeader += (char)0x20;  // ' '
172
  do {
173
    String avLine(myHeader.substring(0, nextBreak));
174
    avLine.trim();
175
    myHeader = myHeader.substring(nextBreak + 1);
176
    nextBreak = myHeader.indexOf(',');
177
 
178
    int eqSign = avLine.indexOf('=');
179
    if (eqSign < 0) {
180
      // os_printf("AUTH FAIL: no = sign\n");
181
      return false;
182
    }
183
    String varName(avLine.substring(0, eqSign));
184
    avLine = avLine.substring(eqSign + 1);
185
    if (avLine.startsWith(String('"'))) {
186
      avLine = avLine.substring(1, avLine.length() - 1);
187
    }
188
 
189
    if (varName.equals(T_username)) {
190
      if (!avLine.equals(username)) {
191
        // os_printf("AUTH FAIL: username\n");
192
        return false;
193
      }
194
      myUsername = avLine;
195
    } else if (varName.equals(T_realm)) {
196
      if (realm != NULL && !avLine.equals(realm)) {
197
        // os_printf("AUTH FAIL: realm\n");
198
        return false;
199
      }
200
      myRealm = avLine;
201
    } else if (varName.equals(T_nonce)) {
202
      if (nonce != NULL && !avLine.equals(nonce)) {
203
        // os_printf("AUTH FAIL: nonce\n");
204
        return false;
205
      }
206
      myNonce = avLine;
207
    } else if (varName.equals(T_opaque)) {
208
      if (opaque != NULL && !avLine.equals(opaque)) {
209
        // os_printf("AUTH FAIL: opaque\n");
210
        return false;
211
      }
212
    } else if (varName.equals(T_uri)) {
213
      if (uri != NULL && !avLine.equals(uri)) {
214
        // os_printf("AUTH FAIL: uri\n");
215
        return false;
216
      }
217
      myUri = avLine;
218
    } else if (varName.equals(T_response)) {
219
      myResponse = avLine;
220
    } else if (varName.equals(T_qop)) {
221
      myQop = avLine;
222
    } else if (varName.equals(T_nc)) {
223
      myNc = avLine;
224
    } else if (varName.equals(T_cnonce)) {
225
      myCnonce = avLine;
226
    }
227
  } while (nextBreak > 0);
228
 
229
  String ha1 = passwordIsHash ? password : stringMD5(myUsername + ':' + myRealm + ':' + password).c_str();
230
  String ha2 = stringMD5(String(method) + ':' + myUri);
231
  String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + ha2;
232
 
233
  if (myResponse.equals(stringMD5(response))) {
234
    // os_printf("AUTH SUCCESS\n");
235
    return true;
236
  }
237
 
238
  // os_printf("AUTH FAIL: password\n");
239
  return false;
240
}