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