digest.c 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*
  2. * OpenConnect (SSL + DTLS) VPN client
  3. *
  4. * Copyright © 2008-2015 Intel Corporation.
  5. *
  6. * Author: David Woodhouse <dwmw2@infradead.org>
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public License
  10. * version 2.1, as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. */
  17. #include <config.h>
  18. #include "openconnect-internal.h"
  19. #include <errno.h>
  20. #include <string.h>
  21. #include <ctype.h>
  22. #define ALGO_MD5 0
  23. #define ALGO_MD5_SESS 1
  24. static struct oc_text_buf *get_qs(char **str)
  25. {
  26. struct oc_text_buf *res;
  27. int escaped = 0;
  28. char *p = *str;
  29. if (*p != '\"')
  30. return NULL;
  31. res = buf_alloc();
  32. while (*++p) {
  33. if (!escaped && *p == '\"') {
  34. *str = p+1;
  35. if (buf_error(res))
  36. break;
  37. return res;
  38. }
  39. if (escaped)
  40. escaped = 0;
  41. else if (*p == '\\')
  42. escaped = 1;
  43. buf_append_bytes(res, p, 1);
  44. }
  45. buf_free(res);
  46. return NULL;
  47. }
  48. static void buf_append_unq(struct oc_text_buf *buf, const char *str)
  49. {
  50. while (*str) {
  51. if (*str == '\"' || *str == '\\')
  52. buf_append(buf, "\\");
  53. buf_append_bytes(buf, str, 1);
  54. str++;
  55. }
  56. }
  57. static void buf_append_md5(struct oc_text_buf *buf, void *data, int len)
  58. {
  59. unsigned char md5[16];
  60. if (openconnect_md5(md5, data, len)) {
  61. buf->error = -EIO;
  62. return;
  63. }
  64. buf_append_hex(buf, md5, 16);
  65. }
  66. int digest_authorization(struct openconnect_info *vpninfo, int proxy,
  67. struct http_auth_state *auth_state,
  68. struct oc_text_buf *hdrbuf)
  69. {
  70. char *chall;
  71. int ret = -EINVAL;
  72. int algo = ALGO_MD5;
  73. int qop_auth = 0;
  74. int nc = 1;
  75. struct oc_text_buf *realm = NULL, *nonce = NULL, *opaque = NULL;
  76. struct oc_text_buf *a1 = NULL, *a2 = NULL, *kd = NULL;
  77. struct oc_text_buf *cnonce = NULL;
  78. unsigned char cnonce_random[32];
  79. const char *user, *pass;
  80. if (proxy) {
  81. user = vpninfo->proxy_user;
  82. pass = vpninfo->proxy_pass;
  83. } else {
  84. /* Need to parse this out of the URL */
  85. return -EINVAL;
  86. }
  87. if (!user || !pass)
  88. return -EINVAL;
  89. if (auth_state->state < AUTH_AVAILABLE)
  90. return -EINVAL;
  91. if (auth_state->state == AUTH_IN_PROGRESS) {
  92. auth_state->state = AUTH_FAILED;
  93. return -EAGAIN;
  94. }
  95. chall = auth_state->challenge;
  96. if (!chall)
  97. return -EINVAL;
  98. while (*chall) {
  99. if (!realm && !strncmp(chall, "realm=", 6)) {
  100. chall += 6;
  101. realm = get_qs(&chall);
  102. if (!realm)
  103. goto err;
  104. } else if (!nonce && !strncmp(chall, "nonce=", 6)) {
  105. chall += 6;
  106. nonce = get_qs(&chall);
  107. if (!nonce)
  108. goto err;
  109. } else if (!strncmp(chall, "qop=", 4)) {
  110. chall += 4;
  111. if (strncmp(chall, "\"auth\"", 6)) {
  112. /* We don't support "auth-int" */
  113. goto err;
  114. }
  115. qop_auth = 1;
  116. chall += 6;
  117. } else if (!opaque && !strncmp(chall, "opaque=", 7)) {
  118. chall += 7;
  119. opaque = get_qs(&chall);
  120. if (!opaque)
  121. goto err;
  122. } else if (!strncmp(chall, "algorithm=", 10)) {
  123. chall += 10;
  124. if (!strncmp(chall, "MD5-sess", 8)) {
  125. algo = ALGO_MD5_SESS;
  126. chall += 8;
  127. } else if (!strncmp(chall, "MD5", 3)) {
  128. algo = ALGO_MD5;
  129. chall += 3;
  130. }
  131. } else {
  132. char *p = strchr(chall, '=');
  133. if (!p)
  134. goto err;
  135. p++;
  136. if (*p == '\"') {
  137. /* Eat and discard a quoted-string */
  138. int escaped = 0;
  139. p++;
  140. do {
  141. if (escaped)
  142. escaped = 0;
  143. else if (*p == '\\')
  144. escaped = 1;
  145. if (!*p)
  146. goto err;
  147. } while (escaped || *p != '\"');
  148. chall = p+1;
  149. } else {
  150. /* Not quoted. Just find the next comma (or EOL) */
  151. p = strchr(p, ',');
  152. if (!p)
  153. break;
  154. chall = p;
  155. }
  156. }
  157. while (isspace((int)(unsigned char)*chall))
  158. chall++;
  159. if (!*chall)
  160. break;
  161. if (*chall != ',')
  162. goto err;
  163. chall++;
  164. while (isspace((int)(unsigned char)*chall))
  165. chall++;
  166. if (!*chall)
  167. break;
  168. }
  169. if (!nonce || !realm)
  170. goto err;
  171. if (openconnect_random(&cnonce_random, sizeof(cnonce_random)))
  172. goto err;
  173. cnonce = buf_alloc();
  174. buf_append_base64(cnonce, cnonce_random, sizeof(cnonce_random), 0);
  175. if (buf_error(cnonce))
  176. goto err;
  177. /*
  178. * According to RFC2617 §3.2.2.2:
  179. * A1 = unq(username-value) ":" unq(realm-value) ":" passwd
  180. * So the username is escaped, while the password isn't.
  181. */
  182. a1 = buf_alloc();
  183. buf_append_unq(a1, user);
  184. buf_append(a1, ":%s:%s", realm->data, pass);
  185. if (buf_error(a1))
  186. goto err;
  187. if (algo == ALGO_MD5_SESS) {
  188. struct oc_text_buf *old_a1 = a1;
  189. a1 = buf_alloc();
  190. buf_append_md5(a1, old_a1->data, old_a1->pos);
  191. buf_free(old_a1);
  192. buf_append(a1, ":%s:%s\n", nonce->data, cnonce->data);
  193. if (buf_error(a1))
  194. goto err;
  195. }
  196. a2 = buf_alloc();
  197. buf_append(a2, "CONNECT:%s:%d", vpninfo->hostname, vpninfo->port);
  198. if (buf_error(a2))
  199. goto err;
  200. kd = buf_alloc();
  201. buf_append_md5(kd, a1->data, a1->pos);
  202. buf_append(kd, ":%s:", nonce->data);
  203. if (qop_auth) {
  204. buf_append(kd, "%08x:%s:auth:", nc, cnonce->data);
  205. }
  206. buf_append_md5(kd, a2->data, a2->pos);
  207. if (buf_error(kd))
  208. goto err;
  209. buf_append(hdrbuf, "%sAuthorization: Digest username=\"", proxy ? "Proxy-" : "");
  210. buf_append_unq(hdrbuf, user);
  211. buf_append(hdrbuf, "\", realm=\"%s\", nonce=\"%s\", uri=\"%s:%d\", ",
  212. realm->data, nonce->data, vpninfo->hostname, vpninfo->port);
  213. if (qop_auth)
  214. buf_append(hdrbuf, "cnonce=\"%s\", nc=%08x, qop=auth, ",
  215. cnonce->data, nc);
  216. if (opaque)
  217. buf_append(hdrbuf, "opaque=\"%s\", ", opaque->data);
  218. buf_append(hdrbuf, "response=\"");
  219. buf_append_md5(hdrbuf, kd->data, kd->pos);
  220. buf_append(hdrbuf, "\"\r\n");
  221. ret = 0;
  222. auth_state->state = AUTH_IN_PROGRESS;
  223. if (proxy)
  224. vpn_progress(vpninfo, PRG_INFO,
  225. _("Attempting Digest authentication to proxy\n"));
  226. else
  227. vpn_progress(vpninfo, PRG_INFO,
  228. _("Attempting Digest authentication to server '%s'\n"),
  229. vpninfo->hostname);
  230. err:
  231. if (a1 && a1->data)
  232. memset(a1->data, 0, a1->pos);
  233. buf_free(a1);
  234. buf_free(a2);
  235. buf_free(kd);
  236. buf_free(realm);
  237. buf_free(nonce);
  238. buf_free(cnonce);
  239. buf_free(opaque);
  240. return ret;
  241. }