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