You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

httpdigest.c 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. /*
  2. * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License as
  6. * published by the Free Software Foundation; either version 2 of the
  7. * License, or any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. * 02110-1301, USA.
  18. *
  19. * You can also choose to distribute this program under the terms of
  20. * the Unmodified Binary Distribution Licence (as given in the file
  21. * COPYING.UBDL), provided that you have satisfied its requirements.
  22. */
  23. FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  24. /**
  25. * @file
  26. *
  27. * Hyper Text Transfer Protocol (HTTP) Digest authentication
  28. *
  29. */
  30. #include <stdio.h>
  31. #include <errno.h>
  32. #include <strings.h>
  33. #include <ipxe/uri.h>
  34. #include <ipxe/md5.h>
  35. #include <ipxe/base16.h>
  36. #include <ipxe/vsprintf.h>
  37. #include <ipxe/http.h>
  38. /* Disambiguate the various error causes */
  39. #define EACCES_USERNAME __einfo_error ( EINFO_EACCES_USERNAME )
  40. #define EINFO_EACCES_USERNAME \
  41. __einfo_uniqify ( EINFO_EACCES, 0x01, \
  42. "No username available for Digest authentication" )
  43. /** An HTTP Digest "WWW-Authenticate" response field */
  44. struct http_digest_field {
  45. /** Name */
  46. const char *name;
  47. /** Offset */
  48. size_t offset;
  49. };
  50. /** Define an HTTP Digest "WWW-Authenticate" response field */
  51. #define HTTP_DIGEST_FIELD( _name ) { \
  52. .name = #_name, \
  53. .offset = offsetof ( struct http_transaction, \
  54. response.auth.digest._name ), \
  55. }
  56. /**
  57. * Set HTTP Digest "WWW-Authenticate" response field value
  58. *
  59. * @v http HTTP transaction
  60. * @v field Response field
  61. * @v value Field value
  62. */
  63. static inline void
  64. http_digest_field ( struct http_transaction *http,
  65. struct http_digest_field *field, char *value ) {
  66. char **ptr;
  67. ptr = ( ( ( void * ) http ) + field->offset );
  68. *ptr = value;
  69. }
  70. /** HTTP Digest "WWW-Authenticate" fields */
  71. static struct http_digest_field http_digest_fields[] = {
  72. HTTP_DIGEST_FIELD ( realm ),
  73. HTTP_DIGEST_FIELD ( qop ),
  74. HTTP_DIGEST_FIELD ( algorithm ),
  75. HTTP_DIGEST_FIELD ( nonce ),
  76. HTTP_DIGEST_FIELD ( opaque ),
  77. };
  78. /**
  79. * Parse HTTP "WWW-Authenticate" header for Digest authentication
  80. *
  81. * @v http HTTP transaction
  82. * @v line Remaining header line
  83. * @ret rc Return status code
  84. */
  85. static int http_parse_digest_auth ( struct http_transaction *http,
  86. char *line ) {
  87. struct http_digest_field *field;
  88. char *key;
  89. char *value;
  90. unsigned int i;
  91. /* Process fields */
  92. while ( ( key = http_token ( &line, &value ) ) ) {
  93. for ( i = 0 ; i < ( sizeof ( http_digest_fields ) /
  94. sizeof ( http_digest_fields[0] ) ) ; i++){
  95. field = &http_digest_fields[i];
  96. if ( strcasecmp ( key, field->name ) == 0 )
  97. http_digest_field ( http, field, value );
  98. }
  99. }
  100. /* Allow HTTP request to be retried if the request had not
  101. * already tried authentication.
  102. */
  103. if ( ! http->request.auth.auth )
  104. http->response.flags |= HTTP_RESPONSE_RETRY;
  105. return 0;
  106. }
  107. /**
  108. * Initialise HTTP Digest
  109. *
  110. * @v ctx Digest context
  111. * @v string Initial string
  112. */
  113. static void http_digest_init ( struct md5_context *ctx ) {
  114. /* Initialise MD5 digest */
  115. digest_init ( &md5_algorithm, ctx );
  116. }
  117. /**
  118. * Update HTTP Digest with new data
  119. *
  120. * @v ctx Digest context
  121. * @v string String to append
  122. */
  123. static void http_digest_update ( struct md5_context *ctx, const char *string ) {
  124. static const char colon = ':';
  125. /* Add (possibly colon-separated) field to MD5 digest */
  126. if ( ctx->len )
  127. digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) );
  128. digest_update ( &md5_algorithm, ctx, string, strlen ( string ) );
  129. }
  130. /**
  131. * Finalise HTTP Digest
  132. *
  133. * @v ctx Digest context
  134. * @v out Buffer for digest output
  135. * @v len Buffer length
  136. */
  137. static void http_digest_final ( struct md5_context *ctx, char *out,
  138. size_t len ) {
  139. uint8_t digest[MD5_DIGEST_SIZE];
  140. /* Finalise and base16-encode MD5 digest */
  141. digest_final ( &md5_algorithm, ctx, digest );
  142. base16_encode ( digest, sizeof ( digest ), out, len );
  143. }
  144. /**
  145. * Perform HTTP Digest authentication
  146. *
  147. * @v http HTTP transaction
  148. * @ret rc Return status code
  149. */
  150. static int http_digest_authenticate ( struct http_transaction *http ) {
  151. struct http_request_auth_digest *req = &http->request.auth.digest;
  152. struct http_response_auth_digest *rsp = &http->response.auth.digest;
  153. char ha1[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ];
  154. char ha2[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ];
  155. static const char md5sess[] = "MD5-sess";
  156. static const char md5[] = "MD5";
  157. struct md5_context ctx;
  158. const char *password;
  159. /* Check for required response parameters */
  160. if ( ! rsp->realm ) {
  161. DBGC ( http, "HTTP %p has no realm for Digest authentication\n",
  162. http );
  163. return -EINVAL;
  164. }
  165. if ( ! rsp->nonce ) {
  166. DBGC ( http, "HTTP %p has no nonce for Digest authentication\n",
  167. http );
  168. return -EINVAL;
  169. }
  170. /* Record username and password */
  171. if ( ! http->uri->user ) {
  172. DBGC ( http, "HTTP %p has no username for Digest "
  173. "authentication\n", http );
  174. return -EACCES_USERNAME;
  175. }
  176. req->username = http->uri->user;
  177. password = ( http->uri->password ? http->uri->password : "" );
  178. /* Handle quality of protection */
  179. if ( rsp->qop ) {
  180. /* Use "auth" in subsequent request */
  181. req->qop = "auth";
  182. /* Generate a client nonce */
  183. snprintf ( req->cnonce, sizeof ( req->cnonce ),
  184. "%08lx", random() );
  185. /* Determine algorithm */
  186. req->algorithm = md5;
  187. if ( rsp->algorithm &&
  188. ( strcasecmp ( rsp->algorithm, md5sess ) == 0 ) ) {
  189. req->algorithm = md5sess;
  190. }
  191. }
  192. /* Generate HA1 */
  193. http_digest_init ( &ctx );
  194. http_digest_update ( &ctx, req->username );
  195. http_digest_update ( &ctx, rsp->realm );
  196. http_digest_update ( &ctx, password );
  197. http_digest_final ( &ctx, ha1, sizeof ( ha1 ) );
  198. if ( req->algorithm == md5sess ) {
  199. http_digest_init ( &ctx );
  200. http_digest_update ( &ctx, ha1 );
  201. http_digest_update ( &ctx, rsp->nonce );
  202. http_digest_update ( &ctx, req->cnonce );
  203. http_digest_final ( &ctx, ha1, sizeof ( ha1 ) );
  204. }
  205. /* Generate HA2 */
  206. http_digest_init ( &ctx );
  207. http_digest_update ( &ctx, http->request.method->name );
  208. http_digest_update ( &ctx, http->request.uri );
  209. http_digest_final ( &ctx, ha2, sizeof ( ha2 ) );
  210. /* Generate response */
  211. http_digest_init ( &ctx );
  212. http_digest_update ( &ctx, ha1 );
  213. http_digest_update ( &ctx, rsp->nonce );
  214. if ( req->qop ) {
  215. http_digest_update ( &ctx, HTTP_DIGEST_NC );
  216. http_digest_update ( &ctx, req->cnonce );
  217. http_digest_update ( &ctx, req->qop );
  218. }
  219. http_digest_update ( &ctx, ha2 );
  220. http_digest_final ( &ctx, req->response, sizeof ( req->response ) );
  221. return 0;
  222. }
  223. /**
  224. * Construct HTTP "Authorization" header for Digest authentication
  225. *
  226. * @v http HTTP transaction
  227. * @v buf Buffer
  228. * @v len Length of buffer
  229. * @ret len Length of header value, or negative error
  230. */
  231. static int http_format_digest_auth ( struct http_transaction *http,
  232. char *buf, size_t len ) {
  233. struct http_request_auth_digest *req = &http->request.auth.digest;
  234. struct http_response_auth_digest *rsp = &http->response.auth.digest;
  235. size_t used = 0;
  236. /* Sanity checks */
  237. assert ( rsp->realm != NULL );
  238. assert ( rsp->nonce != NULL );
  239. assert ( req->username != NULL );
  240. if ( req->qop ) {
  241. assert ( req->algorithm != NULL );
  242. assert ( req->cnonce[0] != '\0' );
  243. }
  244. assert ( req->response[0] != '\0' );
  245. /* Construct response */
  246. used += ssnprintf ( ( buf + used ), ( len - used ),
  247. "realm=\"%s\", nonce=\"%s\", uri=\"%s\", "
  248. "username=\"%s\"", rsp->realm, rsp->nonce,
  249. http->request.uri, req->username );
  250. if ( rsp->opaque ) {
  251. used += ssnprintf ( ( buf + used ), ( len - used ),
  252. ", opaque=\"%s\"", rsp->opaque );
  253. }
  254. if ( req->qop ) {
  255. used += ssnprintf ( ( buf + used ), ( len - used ),
  256. ", qop=%s, algorithm=%s, cnonce=\"%s\", "
  257. "nc=" HTTP_DIGEST_NC, req->qop,
  258. req->algorithm, req->cnonce );
  259. }
  260. used += ssnprintf ( ( buf + used ), ( len - used ),
  261. ", response=\"%s\"", req->response );
  262. return used;
  263. }
  264. /** HTTP Digest authentication scheme */
  265. struct http_authentication http_digest_auth __http_authentication = {
  266. .name = "Digest",
  267. .parse = http_parse_digest_auth,
  268. .authenticate = http_digest_authenticate,
  269. .format = http_format_digest_auth,
  270. };
  271. /* Drag in HTTP authentication support */
  272. REQUIRING_SYMBOL ( http_digest_auth );
  273. REQUIRE_OBJECT ( httpauth );