tlsauthentication.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. We left the basic authentication chapter with the unsatisfactory conclusion that
  2. any traffic, including the credentials, could be intercepted by anyone between
  3. the browser client and the server. Protecting the data while it is sent over
  4. unsecured lines will be the goal of this chapter.
  5. Since version 0.4, the @emph{MHD} library includes support for encrypting the
  6. traffic by employing SSL/TSL. If @emph{GNU libmicrohttpd} has been configured to
  7. support these, encryption and decryption can be applied transparently on the
  8. data being sent, with only minimal changes to the actual source code of the example.
  9. @heading Preparation
  10. First, a private key for the server will be generated. With this key, the server
  11. will later be able to authenticate itself to the client---preventing anyone else
  12. from stealing the password by faking its identity. The @emph{OpenSSL} suite, which
  13. is available on many operating systems, can generate such a key. For the scope of
  14. this tutorial, we will be content with a 1024 bit key:
  15. @verbatim
  16. > openssl genrsa -out server.key 1024
  17. @end verbatim
  18. @noindent
  19. In addition to the key, a certificate describing the server in human readable tokens
  20. is also needed. This certificate will be attested with our aforementioned key. In this way,
  21. we obtain a self-signed certificate, valid for one year.
  22. @verbatim
  23. > openssl req -days 365 -out server.pem -new -x509 -key server.key
  24. @end verbatim
  25. @noindent
  26. To avoid unnecessary error messages in the browser, the certificate needs to
  27. have a name that matches the @emph{URI}, for example, "localhost" or the domain.
  28. If you plan to have a publicly reachable server, you will need to ask a trusted third party,
  29. called @emph{Certificate Authority}, or @emph{CA}, to attest the certificate for you. This way,
  30. any visitor can make sure the server's identity is real.
  31. Whether the server's certificate is signed by us or a third party, once it has been accepted
  32. by the client, both sides will be communicating over encrypted channels. From this point on,
  33. it is the client's turn to authenticate itself. But this has already been implemented in the basic
  34. authentication scheme.
  35. @heading Changing the source code
  36. We merely have to extend the server program so that it loads the two files into memory,
  37. @verbatim
  38. int
  39. main ()
  40. {
  41. struct MHD_Daemon *daemon;
  42. char *key_pem;
  43. char *cert_pem;
  44. key_pem = load_file (SERVERKEYFILE);
  45. cert_pem = load_file (SERVERCERTFILE);
  46. if ((key_pem == NULL) || (cert_pem == NULL))
  47. {
  48. printf ("The key/certificate files could not be read.\n");
  49. return 1;
  50. }
  51. @end verbatim
  52. @noindent
  53. and then we point the @emph{MHD} daemon to it upon initalization.
  54. @verbatim
  55. daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
  56. PORT, NULL, NULL,
  57. &answer_to_connection, NULL,
  58. MHD_OPTION_HTTPS_MEM_KEY, key_pem,
  59. MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
  60. MHD_OPTION_END);
  61. if (NULL == daemon)
  62. {
  63. printf ("%s\n", cert_pem);
  64. free (key_pem);
  65. free (cert_pem);
  66. return 1;
  67. }
  68. @end verbatim
  69. @noindent
  70. The rest consists of little new besides some additional memory cleanups.
  71. @verbatim
  72. getchar ();
  73. MHD_stop_daemon (daemon);
  74. free (key_pem);
  75. free (cert_pem);
  76. return 0;
  77. }
  78. @end verbatim
  79. @noindent
  80. The rather unexciting file loader can be found in the complete example @code{tlsauthentication.c}.
  81. @heading Remarks
  82. @itemize @bullet
  83. @item
  84. While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume
  85. standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type
  86. @code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
  87. handle the answer properly.
  88. @item
  89. The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the
  90. certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured)
  91. that they should not accept certificates of unknown origin.
  92. @item
  93. The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to
  94. hardcode certificates in embedded devices.
  95. @item
  96. The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
  97. both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.
  98. @end itemize
  99. @heading Client authentication
  100. You can also use MHD to authenticate the client via SSL/TLS certificates
  101. (as an alternative to using the password-based Basic or Digest authentication).
  102. To do this, you will need to link your application against @emph{gnutls}.
  103. Next, when you start the MHD daemon, you must specify the root CA that you're
  104. willing to trust:
  105. @verbatim
  106. daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
  107. PORT, NULL, NULL,
  108. &answer_to_connection, NULL,
  109. MHD_OPTION_HTTPS_MEM_KEY, key_pem,
  110. MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
  111. MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem,
  112. MHD_OPTION_END);
  113. @end verbatim
  114. With this, you can then obtain client certificates for each session.
  115. In order to obtain the identity of the client, you first need to
  116. obtain the raw GnuTLS session handle from @emph{MHD} using
  117. @code{MHD_get_connection_info}.
  118. @verbatim
  119. #include <gnutls/gnutls.h>
  120. #include <gnutls/x509.h>
  121. gnutls_session_t tls_session;
  122. union MHD_ConnectionInfo *ci;
  123. ci = MHD_get_connection_info (connection,
  124. MHD_CONNECTION_INFO_GNUTLS_SESSION);
  125. tls_session = ci->tls_session;
  126. @end verbatim
  127. You can then extract the client certificate:
  128. @verbatim
  129. /**
  130. * Get the client's certificate
  131. *
  132. * @param tls_session the TLS session
  133. * @return NULL if no valid client certificate could be found, a pointer
  134. * to the certificate if found
  135. */
  136. static gnutls_x509_crt_t
  137. get_client_certificate (gnutls_session_t tls_session)
  138. {
  139. unsigned int listsize;
  140. const gnutls_datum_t * pcert;
  141. gnutls_certificate_status_t client_cert_status;
  142. gnutls_x509_crt_t client_cert;
  143. if (tls_session == NULL)
  144. return NULL;
  145. if (gnutls_certificate_verify_peers2(tls_session,
  146. &client_cert_status))
  147. return NULL;
  148. pcert = gnutls_certificate_get_peers(tls_session,
  149. &listsize);
  150. if ( (pcert == NULL) ||
  151. (listsize == 0))
  152. {
  153. fprintf (stderr,
  154. "Failed to retrieve client certificate chain\n");
  155. return NULL;
  156. }
  157. if (gnutls_x509_crt_init(&client_cert))
  158. {
  159. fprintf (stderr,
  160. "Failed to initialize client certificate\n");
  161. return NULL;
  162. }
  163. /* Note that by passing values between 0 and listsize here, you
  164. can get access to the CA's certs */
  165. if (gnutls_x509_crt_import(client_cert,
  166. &pcert[0],
  167. GNUTLS_X509_FMT_DER))
  168. {
  169. fprintf (stderr,
  170. "Failed to import client certificate\n");
  171. gnutls_x509_crt_deinit(client_cert);
  172. return NULL;
  173. }
  174. return client_cert;
  175. }
  176. @end verbatim
  177. Using the client certificate, you can then get the client's distinguished name
  178. and alternative names:
  179. @verbatim
  180. /**
  181. * Get the distinguished name from the client's certificate
  182. *
  183. * @param client_cert the client certificate
  184. * @return NULL if no dn or certificate could be found, a pointer
  185. * to the dn if found
  186. */
  187. char *
  188. cert_auth_get_dn(gnutls_x509_crt_c client_cert)
  189. {
  190. char* buf;
  191. size_t lbuf;
  192. lbuf = 0;
  193. gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
  194. buf = malloc(lbuf);
  195. if (buf == NULL)
  196. {
  197. fprintf (stderr,
  198. "Failed to allocate memory for certificate dn\n");
  199. return NULL;
  200. }
  201. gnutls_x509_crt_get_dn(client_cert, buf, &lbuf);
  202. return buf;
  203. }
  204. /**
  205. * Get the alternative name of specified type from the client's certificate
  206. *
  207. * @param client_cert the client certificate
  208. * @param nametype The requested name type
  209. * @param index The position of the alternative name if multiple names are
  210. * matching the requested type, 0 for the first matching name
  211. * @return NULL if no matching alternative name could be found, a pointer
  212. * to the alternative name if found
  213. */
  214. char *
  215. MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
  216. int nametype,
  217. unsigned int index)
  218. {
  219. char* buf;
  220. size_t lbuf;
  221. unsigned int seq;
  222. unsigned int subseq;
  223. unsigned int type;
  224. int result;
  225. subseq = 0;
  226. for (seq=0;;seq++)
  227. {
  228. lbuf = 0;
  229. result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
  230. &type, NULL);
  231. if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
  232. return NULL;
  233. if (nametype != (int) type)
  234. continue;
  235. if (subseq == index)
  236. break;
  237. subseq++;
  238. }
  239. buf = malloc(lbuf);
  240. if (buf == NULL)
  241. {
  242. fprintf (stderr,
  243. "Failed to allocate memory for certificate alt name\n");
  244. return NULL;
  245. }
  246. result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
  247. seq,
  248. buf,
  249. &lbuf,
  250. NULL, NULL);
  251. if (result != nametype)
  252. {
  253. fprintf (stderr,
  254. "Unexpected return value from gnutls: %d\n",
  255. result);
  256. free (buf);
  257. return NULL;
  258. }
  259. return buf;
  260. }
  261. @end verbatim
  262. Finally, you should release the memory associated with the client
  263. certificate:
  264. @verbatim
  265. gnutls_x509_crt_deinit (client_cert);
  266. @end verbatim
  267. @heading Using TLS Server Name Indication (SNI)
  268. SNI enables hosting multiple domains under one IP address with TLS. So
  269. SNI is the TLS-equivalent of virtual hosting. To use SNI with MHD, you
  270. need at least GnuTLS 3.0. The main change compared to the simple hosting
  271. of one domain is that you need to provide a callback instead of the key
  272. and certificate. For example, when you start the MHD daemon, you could
  273. do this:
  274. @verbatim
  275. daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
  276. PORT, NULL, NULL,
  277. &answer_to_connection, NULL,
  278. MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
  279. MHD_OPTION_END);
  280. @end verbatim
  281. Here, @code{sni_callback} is the name of a function that you will have to
  282. implement to retrieve the X.509 certificate for an incoming connection.
  283. The callback has type @code{gnutls_certificate_retrieve_function2} and
  284. is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2}
  285. as follows:
  286. @deftypefn {Function Pointer} int {*gnutls_certificate_retrieve_function2} (gnutls_session_t, const gnutls_datum_t* req_ca_dn, int nreqs, const gnutls_pk_algorithm_t* pk_algos, int pk_algos_length, gnutls_pcert_st** pcert, unsigned int *pcert_length, gnutls_privkey_t * pkey)
  287. @table @var
  288. @item req_ca_cert
  289. is only used in X.509 certificates. Contains a list with the CA names that the server considers trusted. Normally we should send a certificate that is signed by one of these CAs. These names are DER encoded. To get a more meaningful value use the function @code{gnutls_x509_rdn_get()}.
  290. @item pk_algos
  291. contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms.
  292. @item pcert
  293. should contain a single certificate and public or a list of them.
  294. @item pcert_length
  295. is the size of the previous list.
  296. @item pkey
  297. is the private key.
  298. @end table
  299. @end deftypefn
  300. A possible implementation of this callback would look like this:
  301. @verbatim
  302. struct Hosts
  303. {
  304. struct Hosts *next;
  305. const char *hostname;
  306. gnutls_pcert_st pcrt;
  307. gnutls_privkey_t key;
  308. };
  309. static struct Hosts *hosts;
  310. int
  311. sni_callback (gnutls_session_t session,
  312. const gnutls_datum_t* req_ca_dn,
  313. int nreqs,
  314. const gnutls_pk_algorithm_t* pk_algos,
  315. int pk_algos_length,
  316. gnutls_pcert_st** pcert,
  317. unsigned int *pcert_length,
  318. gnutls_privkey_t * pkey)
  319. {
  320. char name[256];
  321. size_t name_len;
  322. struct Hosts *host;
  323. unsigned int type;
  324. name_len = sizeof (name);
  325. if (GNUTLS_E_SUCCESS !=
  326. gnutls_server_name_get (session,
  327. name,
  328. &name_len,
  329. &type,
  330. 0 /* index */))
  331. return -1;
  332. for (host = hosts; NULL != host; host = host->next)
  333. if (0 == strncmp (name, host->hostname, name_len))
  334. break;
  335. if (NULL == host)
  336. {
  337. fprintf (stderr,
  338. "Need certificate for %.*s\n",
  339. (int) name_len,
  340. name);
  341. return -1;
  342. }
  343. fprintf (stderr,
  344. "Returning certificate for %.*s\n",
  345. (int) name_len,
  346. name);
  347. *pkey = host->key;
  348. *pcert_length = 1;
  349. *pcert = &host->pcrt;
  350. return 0;
  351. }
  352. @end verbatim
  353. Note that MHD cannot offer passing a closure or any other additional information
  354. to this callback, as the GnuTLS API unfortunately does not permit this at this
  355. point.
  356. The @code{hosts} list can be initialized by loading the private keys and X.509
  357. certificats from disk as follows:
  358. @verbatim
  359. static void
  360. load_keys(const char *hostname,
  361. const char *CERT_FILE,
  362. const char *KEY_FILE)
  363. {
  364. int ret;
  365. gnutls_datum_t data;
  366. struct Hosts *host;
  367. host = malloc (sizeof (struct Hosts));
  368. host->hostname = hostname;
  369. host->next = hosts;
  370. hosts = host;
  371. ret = gnutls_load_file (CERT_FILE, &data);
  372. if (ret < 0)
  373. {
  374. fprintf (stderr,
  375. "*** Error loading certificate file %s.\n",
  376. CERT_FILE);
  377. exit(1);
  378. }
  379. ret =
  380. gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
  381. 0);
  382. if (ret < 0)
  383. {
  384. fprintf(stderr,
  385. "*** Error loading certificate file: %s\n",
  386. gnutls_strerror (ret));
  387. exit(1);
  388. }
  389. gnutls_free (data.data);
  390. ret = gnutls_load_file (KEY_FILE, &data);
  391. if (ret < 0)
  392. {
  393. fprintf (stderr,
  394. "*** Error loading key file %s.\n",
  395. KEY_FILE);
  396. exit(1);
  397. }
  398. gnutls_privkey_init (&host->key);
  399. ret =
  400. gnutls_privkey_import_x509_raw (host->key,
  401. &data, GNUTLS_X509_FMT_PEM,
  402. NULL, 0);
  403. if (ret < 0)
  404. {
  405. fprintf (stderr,
  406. "*** Error loading key file: %s\n",
  407. gnutls_strerror (ret));
  408. exit(1);
  409. }
  410. gnutls_free (data.data);
  411. }
  412. @end verbatim
  413. The code above was largely lifted from GnuTLS. You can find other
  414. methods for initializing certificates and keys in the GnuTLS manual
  415. and source code.