SensESP 3.3.0
Universal Signal K sensor toolkit ESP32
Loading...
Searching...
No Matches
http_authenticator.cpp
Go to the documentation of this file.
2
3namespace sensesp {
4
6 char buffer[33]; // buffer to hold 32 Hex Digit + /0
7 int i;
8 for (i = 0; i < 4; i++) {
9 sprintf(buffer + (i * 8), "%08x", static_cast<unsigned int>(esp_random()));
10 }
11 return String(buffer);
12}
13
15 int result = authenticate_digest(req);
16
17 if (result == 1) {
18 return true;
19 } else if (result == 0) {
20 request_authentication(req, false);
21 return 0;
22 } else {
23 request_authentication(req, true);
24 return 0;
25 }
26}
27
29 char auth_header[1024];
30 if (httpd_req_get_hdr_value_str(req, "Authorization", auth_header, 1024) !=
31 ESP_OK) {
32 return 0;
33 }
34
35 String auth_header_str(auth_header);
36 int space_pos = auth_header_str.indexOf(' ');
37 if (space_pos == -1) {
38 return 0;
39 }
40
41 String auth_type = auth_header_str.substring(0, space_pos);
42 if (auth_type != "Digest") {
43 return 0;
44 }
45
46 String auth_str = auth_header_str.substring(space_pos + 1);
47
48 String username = extract_param("username", auth_str);
49 String userhash = extract_param("userhash", auth_str, false);
50
51 if (userhash == "true") {
52 String ref_username = MD5(username_ + ":" + realm_);
53 if (username != ref_username) {
54 return 0;
55 }
56 } else if (username != username_) {
57 // FIXME: Should we disallow unhashed usernames?
58 return 0;
59 }
60
61 // Extract required parameters from the auth string
62 String realm = extract_param("realm", auth_str);
63 String nonce = extract_param("nonce", auth_str);
64 String uri = extract_param("uri", auth_str);
65 String algorithm = extract_param("algorithm", auth_str, false);
66 String qop = extract_param("qop", auth_str, false);
67 String response = extract_param("response", auth_str);
68 String opaque = extract_param("opaque", auth_str);
69 String nc = extract_param("nc", auth_str, false);
70 String cnonce = extract_param("cnonce", auth_str);
71
72 // check that all required parameters are present
73 if (realm == "" || nonce == "" || uri == "" || response == "" ||
74 opaque == "" || nc == "" || cnonce == "") {
75 return 0;
76 }
77
78 // Get the nonce count as an integer
79 int count = strtol(nc.c_str(), NULL, 16);
80
81 // Check if the nonce is valid
82 int nonce_status = find_nonce(nonce, count);
83 if (nonce_status == 0) {
84 return 0;
85 } else if (nonce_status == -1) {
86 return -1;
87 }
88
89 // Check that the realm matches
90 if (realm != realm_) {
91 return 0;
92 }
93
94 int method = req->method;
95 String method_str;
96 switch (method) {
97 case HTTP_GET:
98 method_str = "GET";
99 break;
100 case HTTP_POST:
101 method_str = "POST";
102 break;
103 case HTTP_PUT:
104 method_str = "PUT";
105 break;
106 case HTTP_DELETE:
107 method_str = "DELETE";
108 break;
109 default:
110 return 0;
111 }
112
113 // Calculate the expected response
114 String a2 = method_str + ":" + uri;
115 String expected_response = MD5(ha1_ + ":" + nonce + ":" + nc + ":" +
116 cnonce + ":" + qop + ":" + MD5(a2));
117
118 return response == expected_response ? 1 : 0;
119}
120
122 bool stale) {
123 String server_nonce = create_nonce();
124 String opaque = get_random_hex_string();
125
126 httpd_resp_set_status(req, "401 Unauthorized");
127
128 String header = "Digest realm=\"" + realm_ +
129 "\", qop=\"auth\", "
130 "userhash=true, "
131 "algorithm=\"MD5\", nonce=\"" +
132 server_nonce + "\", opaque=\"" + opaque + "\"";
133
134 if (stale) {
135 header += ", stale=true";
136 }
137
138 httpd_resp_set_hdr(req, "WWW-Authenticate", header.c_str());
139 httpd_resp_send(req, "401 Unauthorized", 0);
140 return ESP_OK;
141}
142
144 // The nonce to return is of the form:
145 // "timestamp MD5(timestamp:secret)"
146
147 String timestamp_str = String(millis());
148 String to_hash = timestamp_str + ":" + secret_;
149 String hash = MD5(to_hash);
150 String nonce = timestamp_str + " " + hash;
151
152 // The nonce should be base64-encoded
153 char encoded[64];
154 size_t output_length;
155 mbedtls_base64_encode((unsigned char*)encoded, sizeof(encoded),
156 &output_length, (uint8_t*)nonce.c_str(),
157 nonce.length());
158 encoded[output_length] = '\0';
159 String nonce_str(encoded);
160 // Add un-encoded nonce to the list of nonces
161 nonces_.push_front({nonce, 0});
162 constexpr size_t kMaxNonces = 50;
163 while (nonces_.size() > kMaxNonces) {
164 nonces_.pop_back();
165 }
166 return nonce_str;
167}
168
169int HTTPDigestAuthenticator::find_nonce(String nonce, int count) {
170 std::list<NonceData>::iterator it;
171 String decoded_nonce;
172 // Decode the base64 encoding
173 size_t output_length;
174 char decoded[64];
175 mbedtls_base64_decode((unsigned char*)decoded, sizeof(decoded),
176 &output_length, (uint8_t*)nonce.c_str(),
177 nonce.length());
178 decoded[output_length] = '\0';
179 decoded_nonce = String(decoded);
180 for (it = nonces_.begin(); it != nonces_.end(); it++) {
181 if (it->nonce == decoded_nonce) {
182 // Reject replayed nonce counts. Allow a small tolerance window
183 // (nc may arrive slightly out of order with concurrent requests,
184 // e.g., Chrome sends multiple subrequests in parallel).
185 constexpr int kNcTolerance = 5;
186 if (count <= it->count && (it->count - count) >= kNcTolerance) {
187 return 0; // reject clearly replayed nonce count
188 }
189 if (count > it->count) {
190 it->count = count; // advance high-water mark
191 }
192 // Check if the nonce is stale
193 String timestamp_str = decoded_nonce.substring(0, nonce.indexOf(' '));
194 unsigned long timestamp = timestamp_str.toInt();
195 if (millis() - timestamp > nonce_max_age_) {
196 nonces_.erase(it);
197 return -1;
198 }
199 return 1;
200 }
201 // If the nonce age is 4*nonce_max_age_, remove it from the list
202 String timestamp_str = it->nonce.substring(0, it->nonce.indexOf(' '));
203 unsigned long timestamp = timestamp_str.toInt();
204 if (millis() - timestamp > 4 * nonce_max_age_) {
205 it = nonces_.erase(it);
206 }
207 }
208 return 0;
209}
210
211String HTTPDigestAuthenticator::extract_param(String param, String auth_str,
212 bool quoted) {
213 String quote = quoted ? "\"" : "";
214 int start = auth_str.indexOf(param + "=" + quote);
215 if (start == -1) {
216 return "";
217 }
218 start += param.length() + 1 + (quoted ? 1 : 0);
219 int end = auth_str.indexOf(quoted ? quote : ",", start);
220 if (end == -1) {
221 end = auth_str.length();
222 }
223 return auth_str.substring(start, end);
224}
225
226} // namespace sensesp
int authenticate_digest(httpd_req_t *req)
int find_nonce(String nonce, int count)
Find a nonce in the list of nonces.
virtual bool authenticate_request(httpd_req_t *req) override
Authenticate an incoming request.
esp_err_t request_authentication(httpd_req_t *req, bool stale=false)
String extract_param(String param, String auth_str, bool quoted=true)
String ha1_
Pre-computed MD5(username:realm:password).
String MD5(const String &payload_str)
MD5 hash function.
Definition hash.cpp:45
String get_random_hex_string()