http_server_auth.c
Go to the documentation of this file.
1 /**
2  * @file http_server_auth.c
3  * @brief HTTP authentication
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2019 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 1.9.6
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL HTTP_TRACE_LEVEL
33 
34 //Dependencies
35 #include <stdlib.h>
36 #include "core/net.h"
37 #include "http/http_server.h"
38 #include "http/http_server_auth.h"
39 #include "http/http_server_misc.h"
40 #include "str.h"
41 #include "debug.h"
42 
43 //Check TCP/IP stack configuration
44 #if (HTTP_SERVER_SUPPORT == ENABLED)
45 
46 
47 /**
48  * @brief Password verification
49  * @param[in] connection Structure representing an HTTP connection
50  * @param[in] password NULL-terminated string containing the password to be checked
51  * @param[in] mode HTTP authentication scheme to be used. Acceptable
52  * values are HTTP_AUTH_MODE_BASIC or HTTP_AUTH_MODE_DIGEST
53  * @return TRUE if the password is valid, else FALSE
54  **/
55 
57  const char_t *password, HttpAuthMode mode)
58 {
59  //This flag tells whether the password is valid
60  bool_t status = FALSE;
61 
62  //Debug message
63  TRACE_DEBUG("HTTP password verification...\r\n");
64 
65 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
66  //Basic authentication scheme?
68  {
69  //Point to the authentication credentials
70  HttpAuthorizationHeader *auth = &connection->request.auth;
71 
72  //Make sure authentication credentials have been found
73  if(auth->found && auth->mode == HTTP_AUTH_MODE_BASIC)
74  {
75  //Sanity check
76  if(auth->password != NULL)
77  {
78  //Check whether the password is valid
79  if(!strcmp(password, auth->password))
80  status = TRUE;
81  }
82  }
83  }
84 #endif
85 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
86  //Digest authentication scheme?
88  {
89  //Point to the authentication credentials
90  HttpAuthorizationHeader *auth = &connection->request.auth;
91 
92  //Make sure authentication credentials have been found
93  if(auth->found && auth->mode == HTTP_AUTH_MODE_DIGEST)
94  {
95  //Sanity check
96  if(auth->realm != NULL && auth->nonce != NULL &&
97  auth->uri != NULL && auth->qop != NULL &&
98  auth->nc != NULL && auth->cnonce != NULL &&
99  auth->response != NULL)
100  {
101  error_t error;
102  Md5Context *md5Context;
103  char_t ha1[2 * MD5_DIGEST_SIZE + 1];
104  char_t ha2[2 * MD5_DIGEST_SIZE + 1];
105 
106  //Allocate a memory buffer to hold the MD5 context
107  md5Context = osAllocMem(sizeof(Md5Context));
108 
109  //MD5 context successfully allocated?
110  if(md5Context != NULL)
111  {
112  //Compute HA1 = MD5(username : realm : password)
113  md5Init(md5Context);
114  md5Update(md5Context, auth->user, strlen(auth->user));
115  md5Update(md5Context, ":", 1);
116  md5Update(md5Context, auth->realm, strlen(auth->realm));
117  md5Update(md5Context, ":", 1);
118  md5Update(md5Context, password, strlen(password));
119  md5Final(md5Context, NULL);
120 
121  //Convert MD5 hash to hex string
123  //Debug message
124  TRACE_DEBUG(" HA1: %s\r\n", ha1);
125 
126  //Compute HA2 = MD5(method : uri)
127  md5Init(md5Context);
128  md5Update(md5Context, connection->request.method, strlen(connection->request.method));
129  md5Update(md5Context, ":", 1);
130  md5Update(md5Context, auth->uri, strlen(auth->uri));
131  md5Final(md5Context, NULL);
132 
133  //Convert MD5 hash to hex string
135  //Debug message
136  TRACE_DEBUG(" HA2: %s\r\n", ha2);
137 
138  //Compute MD5(HA1 : nonce : nc : cnonce : qop : HA2)
139  md5Init(md5Context);
140  md5Update(md5Context, ha1, strlen(ha1));
141  md5Update(md5Context, ":", 1);
142  md5Update(md5Context, auth->nonce, strlen(auth->nonce));
143  md5Update(md5Context, ":", 1);
144  md5Update(md5Context, auth->nc, strlen(auth->nc));
145  md5Update(md5Context, ":", 1);
146  md5Update(md5Context, auth->cnonce, strlen(auth->cnonce));
147  md5Update(md5Context, ":", 1);
148  md5Update(md5Context, auth->qop, strlen(auth->qop));
149  md5Update(md5Context, ":", 1);
150  md5Update(md5Context, ha2, strlen(ha2));
151  md5Final(md5Context, NULL);
152 
153  //Convert MD5 hash to hex string
155  //Debug message
156  TRACE_DEBUG(" response: %s\r\n", ha1);
157 
158  //Release MD5 context
159  osFreeMem(md5Context);
160 
161  //Check response
162  if(!strcasecmp(auth->response, ha1))
163  {
164  //Perform nonce verification
165  error = httpVerifyNonce(connection->serverContext, auth->nonce, auth->nc);
166 
167  //Valid nonce?
168  if(!error)
169  {
170  //Access to the resource is granted
171  status = TRUE;
172  }
173  else
174  {
175  //The client may wish to simply retry the request with a
176  //new encrypted response, without re-prompting the user
177  //for a new username and password
178  connection->response.auth.stale = TRUE;
179  }
180  }
181  }
182  }
183  }
184  }
185 #endif
186 
187  //Return TRUE is the password is valid, else FALSE
188  return status;
189 }
190 
191 
192 /**
193  * @brief Parse Authorization header field
194  * @param[in] connection Structure representing an HTTP connection
195  * @param[in] value Authorization field value
196  **/
197 
199 {
200  char_t *p;
201  char_t *token;
202 
203  //Retrieve the authentication scheme
204  token = strtok_r(value, " \t", &p);
205 
206  //Any parsing error?
207  if(token == NULL)
208  {
209  //Exit immediately
210  return;
211  }
212 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
213  //Basic access authentication?
214  else if(!strcasecmp(token, "Basic"))
215  {
216  error_t error;
217  size_t n;
218  char_t *separator;
219 
220  //Use the relevant authentication scheme
221  connection->request.auth.mode = HTTP_AUTH_MODE_BASIC;
222  //Retrieve the credentials
223  token = strtok_r(NULL, " \t", &p);
224 
225  //Any parsing error?
226  if(token != NULL)
227  {
228  //Decrypt the Base64-encoded string
229  error = base64Decode(token, strlen(token), token, &n);
230 
231  //Successful decoding?
232  if(!error)
233  {
234  //Properly terminate the string
235  token[n] = '\0';
236  //Check whether a separator is present
237  separator = strchr(token, ':');
238 
239  //Separator found?
240  if(separator != NULL)
241  {
242  //Split the line
243  *separator = '\0';
244 
245  //Save user name
246  strSafeCopy(connection->request.auth.user,
248 
249  //Point to the password
250  token = separator + 1;
251  //Save password
252  connection->request.auth.password = token;
253  }
254  }
255  }
256 
257  //Debug message
258  TRACE_DEBUG("Authorization header:\r\n");
259  TRACE_DEBUG(" username: %s\r\n", connection->request.auth.user);
260  TRACE_DEBUG(" password: %s\r\n", connection->request.auth.password);
261  }
262 #endif
263 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
264  //Digest access authentication?
265  else if(!strcasecmp(token, "Digest"))
266  {
267  size_t n;
268  char_t *separator;
269  char_t *name;
270 
271  //Use the relevant authentication scheme
272  connection->request.auth.mode = HTTP_AUTH_MODE_DIGEST;
273  //Get the first parameter
274  token = strtok_r(NULL, ",", &p);
275 
276  //Parse the Authorization header field
277  while(token != NULL)
278  {
279  //Check whether a separator is present
280  separator = strchr(token, '=');
281 
282  //Separator found?
283  if(separator != NULL)
284  {
285  //Split the string
286  *separator = '\0';
287 
288  //Get field name and value
290  value = strTrimWhitespace(separator + 1);
291 
292  //Retrieve the length of the value field
293  n = strlen(value);
294 
295  //Discard the surrounding quotes
296  if(n > 0 && value[n - 1] == '\"')
297  value[n - 1] = '\0';
298  if(value[0] == '\"')
299  value++;
300 
301  //Check parameter name
302  if(!strcasecmp(name, "username"))
303  {
304  //Save user name
305  strSafeCopy(connection->request.auth.user,
307  }
308  else if(!strcasecmp(name, "realm"))
309  {
310  //Save realm
311  connection->request.auth.realm = value;
312  }
313  else if(!strcasecmp(name, "nonce"))
314  {
315  //Save nonce parameter
316  connection->request.auth.nonce = value;
317  }
318  else if(!strcasecmp(name, "uri"))
319  {
320  //Save uri parameter
321  connection->request.auth.uri = value;
322  }
323  else if(!strcasecmp(name, "qop"))
324  {
325  //Save qop parameter
326  connection->request.auth.qop = value;
327  }
328  else if(!strcasecmp(name, "nc"))
329  {
330  //Save nc parameter
331  connection->request.auth.nc = value;
332  }
333  else if(!strcasecmp(name, "cnonce"))
334  {
335  //Save cnonce parameter
336  connection->request.auth.cnonce = value;
337  }
338  else if(!strcasecmp(name, "response"))
339  {
340  //Save response parameter
341  connection->request.auth.response = value;
342  }
343  else if(!strcasecmp(name, "opaque"))
344  {
345  //Save opaque parameter
346  connection->request.auth.opaque = value;
347  }
348 
349  //Get next parameter
350  token = strtok_r(NULL, ",", &p);
351  }
352  }
353 
354  //Debug message
355  TRACE_DEBUG("Authorization header:\r\n");
356  TRACE_DEBUG(" username: %s\r\n", connection->request.auth.user);
357  TRACE_DEBUG(" realm: %s\r\n", connection->request.auth.realm);
358  TRACE_DEBUG(" nonce: %s\r\n", connection->request.auth.nonce);
359  TRACE_DEBUG(" uri: %s\r\n", connection->request.auth.uri);
360  TRACE_DEBUG(" qop: %s\r\n", connection->request.auth.qop);
361  TRACE_DEBUG(" nc: %s\r\n", connection->request.auth.nc);
362  TRACE_DEBUG(" cnonce: %s\r\n", connection->request.auth.cnonce);
363  TRACE_DEBUG(" response: %s\r\n", connection->request.auth.response);
364  TRACE_DEBUG(" opaque: %s\r\n", connection->request.auth.opaque);
365  }
366 #endif
367  else
368  {
369  //The specified authentication scheme is not supported
370  return;
371  }
372 
373 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
374  //The Authorization header has been found
375  connection->request.auth.found = TRUE;
376 
377  //Invoke user-defined callback, if any
378  if(connection->settings->authCallback != NULL)
379  {
380  //Check whether the access to the specified URI is authorized
381  connection->status = connection->settings->authCallback(connection,
382  connection->request.auth.user, connection->request.uri);
383  }
384  else
385  {
386  //Access to the specified URI is allowed
387  connection->status = HTTP_ACCESS_ALLOWED;
388  }
389 #endif
390 }
391 
392 
393 /**
394  * @brief Format WWW-Authenticate header field
395  * @param[in] connection Structure representing an HTTP connection
396  * @param[out] output Buffer where to format the header field
397  * @return Total length of the header field
398  **/
399 
400 size_t httpAddAuthenticateField(HttpConnection *connection, char_t *output)
401 {
402  size_t n;
403 
404 #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED)
405  //Basic authentication scheme?
406  if(connection->response.auth.mode == HTTP_AUTH_MODE_BASIC)
407  {
408  //Set WWW-Authenticate field
409  n = sprintf(output, "WWW-Authenticate: Basic realm=\"Protected Area\"\r\n");
410  }
411  else
412 #endif
413 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
414  //Digest authentication scheme?
415  if(connection->response.auth.mode == HTTP_AUTH_MODE_DIGEST)
416  {
417  error_t error;
418  size_t k;
419  uint8_t opaque[16];
420 
421  //Set WWW-Authenticate field
422  n = sprintf(output, "WWW-Authenticate: Digest\r\n");
423  n += sprintf(output + n, " realm=\"Protected Area\",\r\n");
424  n += sprintf(output + n, " qop=\"auth\",\r\n");
425  n += sprintf(output + n, " nonce=\"");
426 
427  //The nonce is a server-specified data string which should be uniquely
428  //generated each time a 401 response is made
429  error = httpGenerateNonce(connection->serverContext, output + n, &k);
430  //Any error to report?
431  if(error)
432  return error;
433 
434  //Advance pointer
435  n += k;
436  //Properly terminate the nonce string
437  n += sprintf(output + n, "\",\r\n");
438 
439  //Format opaque parameter
440  n += sprintf(output + n, " opaque=\"");
441 
442  //Generate a random value
443  if(connection->settings->randCallback != NULL)
444  error = connection->settings->randCallback(opaque, 16);
445  else
446  error = ERROR_FAILURE;
447 
448  //Random number generation failed?
449  if(error)
450  return error;
451 
452  //Convert the byte array to hex string
453  httpConvertArrayToHexString(opaque, 16, output + n);
454 
455  //Advance pointer
456  n += 32;
457  //Properly terminate the opaque string
458  n += sprintf(output + n, "\"");
459 
460  //The STALE flag indicates that the previous request from the client
461  //was rejected because the nonce value was stale
462  if(connection->response.auth.stale)
463  n += sprintf(output + n, ",\r\n stale=TRUE");
464 
465  //Properly terminate the WWW-Authenticate field
466  n += sprintf(output + n, "\r\n");
467  }
468  else
469 #endif
470  //Unknown authentication scheme?
471  {
472  //No need to add the WWW-Authenticate header field
473  n = 0;
474  }
475 
476  //Return the total length of the WWW-Authenticate header field
477  return n;
478 }
479 
480 
481 /**
482  * @brief Nonce generation
483  * @param[in] context Pointer to the HTTP server context
484  * @param[in] output NULL-terminated string containing the nonce
485  * @param[in] length NULL-terminated string containing the nonce count
486  * @return Error code
487  **/
488 
490  char_t *output, size_t *length)
491 {
492 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
493  error_t error;
494  uint_t i;
495  systime_t time;
496  HttpNonceCacheEntry *entry;
497  HttpNonceCacheEntry *oldestEntry;
498  uint8_t nonce[HTTP_SERVER_NONCE_SIZE];
499 
500  //Acquire exclusive access to the nonce cache
501  osAcquireMutex(&context->nonceCacheMutex);
502 
503  //Get current time
504  time = osGetSystemTime();
505 
506  //Keep track of the oldest entry
507  oldestEntry = &context->nonceCache[0];
508 
509  //Loop through nonce cache entries
510  for(i = 0; i < HTTP_SERVER_NONCE_CACHE_SIZE; i++)
511  {
512  //Point to the current entry
513  entry = &context->nonceCache[i];
514 
515  //Check whether the entry is currently in used or not
516  if(!entry->count)
517  break;
518 
519  //Keep track of the oldest entry in the table
520  if((time - entry->timestamp) > (time - oldestEntry->timestamp))
521  {
522  oldestEntry = entry;
523  }
524  }
525 
526  //The oldest entry is removed whenever the table runs out of space
528  entry = oldestEntry;
529 
530  //Generate a new nonce
531  if(context->settings.randCallback != NULL)
532  error = context->settings.randCallback(nonce, HTTP_SERVER_NONCE_SIZE);
533  else
534  error = ERROR_FAILURE;
535 
536  //Check status code
537  if(!error)
538  {
539  //Convert the byte array to hex string
541  //Clear nonce count
542  entry->count = 1;
543  //Save the time at which the nonce was generated
544  entry->timestamp = osGetSystemTime();
545 
546  //Copy the nonce to the output buffer
547  strcpy(output, entry->nonce);
548  //Return the length of the nonce excluding the NULL character
550  }
551  else
552  {
553  //Random number generation failed
554  memset(entry, 0, sizeof(HttpNonceCacheEntry));
555  }
556 
557  //Release exclusive access to the nonce cache
558  osReleaseMutex(&context->nonceCacheMutex);
559  //Return status code
560  return error;
561 
562 #else
563  //Not implemented
564  return ERROR_NOT_IMPLEMENTED;
565 #endif
566 }
567 
568 
569 /**
570  * @brief Nonce verification
571  * @param[in] context Pointer to the HTTP server context
572  * @param[in] nonce NULL-terminated string containing the nonce
573  * @param[in] nc NULL-terminated string containing the nonce count
574  * @return Error code
575  **/
576 
578  const char_t *nonce, const char_t *nc)
579 {
580 #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED)
581  error_t error;
582  uint_t i;
583  uint32_t count;
584  systime_t time;
585  HttpNonceCacheEntry *entry;
586 
587  //Check parameters
588  if(nonce == NULL || nc == NULL)
590 
591  //Convert the nonce count to integer
592  count = strtoul(nc, NULL, 16);
593  //Get current time
594  time = osGetSystemTime();
595 
596  //Acquire exclusive access to the nonce cache
597  osAcquireMutex(&context->nonceCacheMutex);
598 
599  //Loop through nonce cache entries
600  for(i = 0; i < HTTP_SERVER_NONCE_CACHE_SIZE; i++)
601  {
602  //Point to the current entry
603  entry = &context->nonceCache[i];
604 
605  //Check nonce value
606  if(!strcasecmp(entry->nonce, nonce))
607  {
608  //Make sure the nonce timestamp has not expired
609  if((time - entry->timestamp) < HTTP_SERVER_NONCE_LIFETIME)
610  {
611  //Check nonce count to prevent replay attacks
612  if(count >= entry->count)
613  {
614  //Update nonce count to the next expected value
615  entry->count = count + 1;
616  //We are done
617  break;
618  }
619  }
620  }
621  }
622 
623  //Check whether the nonce is valid
625  error = NO_ERROR;
626  else
627  error = ERROR_NOT_FOUND;
628 
629  //Release exclusive access to the nonce cache
630  osReleaseMutex(&context->nonceCacheMutex);
631  //Return status code
632  return error;
633 
634 #else
635  //Not implemented
636  return ERROR_NOT_IMPLEMENTED;
637 #endif
638 }
639 
640 #endif
#define HTTP_SERVER_NONCE_LIFETIME
Definition: http_server.h:244
uint8_t length
Definition: dtls_misc.h:149
String manipulation helper functions.
int bool_t
Definition: compiler_port.h:49
uint8_t digest[16]
Definition: md5.h:63
#define HTTP_SERVER_NONCE_SIZE
Definition: http_server.h:251
HTTP server (HyperText Transfer Protocol)
systime_t timestamp
Time stamp to manage entry lifetime.
Definition: http_server.h:563
uint8_t p
Definition: ndp.h:298
char_t * strTrimWhitespace(char_t *s)
Removes all leading and trailing whitespace from a string.
Definition: str.c:78
#define TRUE
Definition: os_port.h:50
#define HttpServerContext
Definition: http_server.h:314
void md5Init(Md5Context *context)
Initialize MD5 message digest context.
Definition: md5.c:136
bool_t httpCheckPassword(HttpConnection *connection, const char_t *password, HttpAuthMode mode)
Password verification.
char_t name[]
#define strtok_r(str, delim, p)
char_t user[HTTP_SERVER_USERNAME_MAX_LEN+1]
User name.
Definition: http_server.h:432
void md5Final(Md5Context *context, uint8_t *digest)
Finish the MD5 message digest.
Definition: md5.c:197
void md5Update(Md5Context *context, const void *data, size_t length)
Update the MD5 context with a portion of the message being hashed.
Definition: md5.c:158
const char_t * password
Password.
Definition: http_server.h:434
error_t base64Decode(const char_t *input, size_t inputLen, void *output, size_t *outputLen)
Base64 decoding algorithm.
Definition: base64.c:194
#define FALSE
Definition: os_port.h:46
Invalid parameter.
Definition: error.h:47
error_t
Error codes.
Definition: error.h:42
#define HTTP_SERVER_NONCE_CACHE_SIZE
Definition: http_server.h:237
#define HttpConnection
Definition: http_server.h:318
const char_t * realm
Definition: http_server.h:437
Generic error code.
Definition: error.h:45
void httpParseAuthorizationField(HttpConnection *connection, char_t *value)
Parse Authorization header field.
Nonce cache entry.
Definition: http_server.h:559
HTTP server (miscellaneous functions)
Authorization header.
Definition: http_server.h:428
MD5 algorithm context.
Definition: md5.h:58
#define HTTP_SERVER_USERNAME_MAX_LEN
Definition: http_server.h:209
void * osAllocMem(size_t size)
Allocate a memory block.
const char_t * nc
Nonce count.
Definition: http_server.h:441
size_t httpAddAuthenticateField(HttpConnection *connection, char_t *output)
Format WWW-Authenticate header field.
#define MD5_DIGEST_SIZE
Definition: md5.h:40
bool_t found
The Authorization header has been found.
Definition: http_server.h:430
uint32_t count
Nonce count.
Definition: http_server.h:562
#define TRACE_DEBUG(...)
Definition: debug.h:106
HTTP authentication.
char char_t
Definition: compiler_port.h:43
uint32_t time
error_t strSafeCopy(char_t *dest, const char_t *src, size_t destSize)
Copy string.
Definition: str.c:167
uint8_t n
void osAcquireMutex(OsMutex *mutex)
Acquire ownership of the specified mutex object.
void osReleaseMutex(OsMutex *mutex)
Release ownership of the specified mutex object.
void httpConvertArrayToHexString(const uint8_t *input, size_t inputLen, char_t *output)
Convert byte array to hex string.
#define strcasecmp
const char_t * cnonce
Client nonce.
Definition: http_server.h:442
char_t nonce[HTTP_SERVER_NONCE_SIZE *2+1]
Nonce.
Definition: http_server.h:561
error_t httpGenerateNonce(HttpServerContext *context, char_t *output, size_t *length)
Nonce generation.
uint8_t mode
Definition: ntp_common.h:149
error_t httpVerifyNonce(HttpServerContext *context, const char_t *nonce, const char_t *nc)
Nonce verification.
HttpAuthMode mode
Authentication scheme.
Definition: http_server.h:431
const char_t * qop
Definition: http_server.h:440
uint8_t value[]
Definition: dtls_misc.h:150
unsigned int uint_t
Definition: compiler_port.h:45
TCP/IP stack core.
HttpAuthMode
HTTP authentication schemes.
Definition: http_common.h:71
const char_t * nonce
Server nonce.
Definition: http_server.h:438
const char_t * response
Definition: http_server.h:443
const char_t * uri
Digest URI.
Definition: http_server.h:439
uint32_t systime_t
Definition: compiler_port.h:46
void osFreeMem(void *p)
Release a previously allocated memory block.
Success.
Definition: error.h:44
Debugging facilities.
uint8_t token[]
Definition: coap_common.h:183
systime_t osGetSystemTime(void)
Retrieve system time.