tlsauthentication.inc (14808B)
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 initialization. 69 @verbatim 70 71 daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | 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_INTERNAL_POLLING_THREAD | 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 = (gnutls_session_t) 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 if (0 != client_cert_status) 191 { 192 fprintf (stderr, 193 "Failed client certificate invalid: %d\n", 194 client_cert_status); 195 return NULL; 196 } 197 pcert = gnutls_certificate_get_peers(tls_session, 198 &listsize); 199 if ( (pcert == NULL) || 200 (listsize == 0)) 201 { 202 fprintf (stderr, 203 "Failed to retrieve client certificate chain\n"); 204 return NULL; 205 } 206 if (gnutls_x509_crt_init(&client_cert)) 207 { 208 fprintf (stderr, 209 "Failed to initialize client certificate\n"); 210 return NULL; 211 } 212 /* Note that by passing values between 0 and listsize here, you 213 can get access to the CA's certs */ 214 if (gnutls_x509_crt_import(client_cert, 215 &pcert[0], 216 GNUTLS_X509_FMT_DER)) 217 { 218 fprintf (stderr, 219 "Failed to import client certificate\n"); 220 gnutls_x509_crt_deinit(client_cert); 221 return NULL; 222 } 223 return client_cert; 224 } 225 @end verbatim 226 227 Using the client certificate, you can then get the client's distinguished name 228 and alternative names: 229 230 @verbatim 231 /** 232 * Get the distinguished name from the client's certificate 233 * 234 * @param client_cert the client certificate 235 * @return NULL if no dn or certificate could be found, a pointer 236 * to the dn if found 237 */ 238 char * 239 cert_auth_get_dn(gnutls_x509_crt_t client_cert) 240 { 241 char* buf; 242 size_t lbuf; 243 244 lbuf = 0; 245 gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf); 246 buf = malloc(lbuf); 247 if (buf == NULL) 248 { 249 fprintf (stderr, 250 "Failed to allocate memory for certificate dn\n"); 251 return NULL; 252 } 253 gnutls_x509_crt_get_dn(client_cert, buf, &lbuf); 254 return buf; 255 } 256 257 258 /** 259 * Get the alternative name of specified type from the client's certificate 260 * 261 * @param client_cert the client certificate 262 * @param nametype The requested name type 263 * @param index The position of the alternative name if multiple names are 264 * matching the requested type, 0 for the first matching name 265 * @return NULL if no matching alternative name could be found, a pointer 266 * to the alternative name if found 267 */ 268 char * 269 MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert, 270 int nametype, 271 unsigned int index) 272 { 273 char* buf; 274 size_t lbuf; 275 unsigned int seq; 276 unsigned int subseq; 277 unsigned int type; 278 int result; 279 280 subseq = 0; 281 for (seq=0;;seq++) 282 { 283 lbuf = 0; 284 result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf, 285 &type, NULL); 286 if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) 287 return NULL; 288 if (nametype != (int) type) 289 continue; 290 if (subseq == index) 291 break; 292 subseq++; 293 } 294 buf = malloc(lbuf); 295 if (buf == NULL) 296 { 297 fprintf (stderr, 298 "Failed to allocate memory for certificate alt name\n"); 299 return NULL; 300 } 301 result = gnutls_x509_crt_get_subject_alt_name2(client_cert, 302 seq, 303 buf, 304 &lbuf, 305 NULL, NULL); 306 if (result != nametype) 307 { 308 fprintf (stderr, 309 "Unexpected return value from gnutls: %d\n", 310 result); 311 free (buf); 312 return NULL; 313 } 314 return buf; 315 } 316 @end verbatim 317 318 Finally, you should release the memory associated with the client 319 certificate: 320 321 @verbatim 322 gnutls_x509_crt_deinit (client_cert); 323 @end verbatim 324 325 326 327 @heading Using TLS Server Name Indication (SNI) 328 329 SNI enables hosting multiple domains under one IP address with TLS. So 330 SNI is the TLS-equivalent of virtual hosting. To use SNI with MHD, you 331 need at least GnuTLS 3.0. The main change compared to the simple hosting 332 of one domain is that you need to provide a callback instead of the key 333 and certificate. For example, when you start the MHD daemon, you could 334 do this: 335 @verbatim 336 daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL, 337 PORT, NULL, NULL, 338 &answer_to_connection, NULL, 339 MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback, 340 MHD_OPTION_END); 341 @end verbatim 342 Here, @code{sni_callback} is the name of a function that you will have to 343 implement to retrieve the X.509 certificate for an incoming connection. 344 The callback has type @code{gnutls_certificate_retrieve_function2} and 345 is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2} 346 as follows: 347 348 @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) 349 350 @table @var 351 @item req_ca_cert 352 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()}. 353 354 @item pk_algos 355 contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms. 356 357 @item pcert 358 should contain a single certificate and public or a list of them. 359 360 @item pcert_length 361 is the size of the previous list. 362 363 @item pkey 364 is the private key. 365 @end table 366 @end deftypefn 367 368 A possible implementation of this callback would look like this: 369 370 @verbatim 371 struct Hosts 372 { 373 struct Hosts *next; 374 const char *hostname; 375 gnutls_pcert_st pcrt; 376 gnutls_privkey_t key; 377 }; 378 379 static struct Hosts *hosts; 380 381 int 382 sni_callback (gnutls_session_t session, 383 const gnutls_datum_t* req_ca_dn, 384 int nreqs, 385 const gnutls_pk_algorithm_t* pk_algos, 386 int pk_algos_length, 387 gnutls_pcert_st** pcert, 388 unsigned int *pcert_length, 389 gnutls_privkey_t * pkey) 390 { 391 char name[256]; 392 size_t name_len; 393 struct Hosts *host; 394 unsigned int type; 395 396 name_len = sizeof (name); 397 if (GNUTLS_E_SUCCESS != 398 gnutls_server_name_get (session, 399 name, 400 &name_len, 401 &type, 402 0 /* index */)) 403 return -1; 404 for (host = hosts; NULL != host; host = host->next) 405 if (0 == strncmp (name, host->hostname, name_len)) 406 break; 407 if (NULL == host) 408 { 409 fprintf (stderr, 410 "Need certificate for %.*s\n", 411 (int) name_len, 412 name); 413 return -1; 414 } 415 fprintf (stderr, 416 "Returning certificate for %.*s\n", 417 (int) name_len, 418 name); 419 *pkey = host->key; 420 *pcert_length = 1; 421 *pcert = &host->pcrt; 422 return 0; 423 } 424 @end verbatim 425 426 Note that MHD cannot offer passing a closure or any other additional information 427 to this callback, as the GnuTLS API unfortunately does not permit this at this 428 point. 429 430 The @code{hosts} list can be initialized by loading the private keys and X.509 431 certificates from disk as follows: 432 433 @verbatim 434 static void 435 load_keys(const char *hostname, 436 const char *CERT_FILE, 437 const char *KEY_FILE) 438 { 439 int ret; 440 gnutls_datum_t data; 441 struct Hosts *host; 442 443 host = malloc (sizeof (struct Hosts)); 444 host->hostname = hostname; 445 host->next = hosts; 446 hosts = host; 447 448 ret = gnutls_load_file (CERT_FILE, &data); 449 if (ret < 0) 450 { 451 fprintf (stderr, 452 "*** Error loading certificate file %s.\n", 453 CERT_FILE); 454 exit(1); 455 } 456 ret = 457 gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM, 458 0); 459 if (ret < 0) 460 { 461 fprintf(stderr, 462 "*** Error loading certificate file: %s\n", 463 gnutls_strerror (ret)); 464 exit(1); 465 } 466 gnutls_free (data.data); 467 468 ret = gnutls_load_file (KEY_FILE, &data); 469 if (ret < 0) 470 { 471 fprintf (stderr, 472 "*** Error loading key file %s.\n", 473 KEY_FILE); 474 exit(1); 475 } 476 477 gnutls_privkey_init (&host->key); 478 ret = 479 gnutls_privkey_import_x509_raw (host->key, 480 &data, GNUTLS_X509_FMT_PEM, 481 NULL, 0); 482 if (ret < 0) 483 { 484 fprintf (stderr, 485 "*** Error loading key file: %s\n", 486 gnutls_strerror (ret)); 487 exit(1); 488 } 489 gnutls_free (data.data); 490 } 491 @end verbatim 492 493 The code above was largely lifted from GnuTLS. You can find other 494 methods for initializing certificates and keys in the GnuTLS manual 495 and source code.