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