1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
|
We left the basic authentication chapter with the unsatisfactory conclusion that
any traffic, including the credentials, could be intercepted by anyone between
the browser client and the server. Protecting the data while it is sent over
unsecured lines will be the goal of this chapter.
Since version 0.4, the @emph{MHD} library includes support for encrypting the
traffic by employing SSL/TSL. If @emph{GNU libmicrohttpd} has been configured to
support these, encryption and decryption can be applied transparently on the
data being sent, with only minimal changes to the actual source code of the example.
@heading Preparation
First, a private key for the server will be generated. With this key, the server
will later be able to authenticate itself to the client---preventing anyone else
from stealing the password by faking its identity. The @emph{OpenSSL} suite, which
is available on many operating systems, can generate such a key. For the scope of
this tutorial, we will be content with a 1024 bit key:
@verbatim
> openssl genrsa -out server.key 1024
@end verbatim
@noindent
In addition to the key, a certificate describing the server in human readable tokens
is also needed. This certificate will be attested with our aforementioned key. In this way,
we obtain a self-signed certificate, valid for one year.
@verbatim
> openssl req -days 365 -out server.pem -new -x509 -key server.key
@end verbatim
@noindent
To avoid unnecessary error messages in the browser, the certificate needs to
have a name that matches the @emph{URI}, for example, "localhost" or the domain.
If you plan to have a publicly reachable server, you will need to ask a trusted third party,
called @emph{Certificate Authority}, or @emph{CA}, to attest the certificate for you. This way,
any visitor can make sure the server's identity is real.
Whether the server's certificate is signed by us or a third party, once it has been accepted
by the client, both sides will be communicating over encrypted channels. From this point on,
it is the client's turn to authenticate itself. But this has already been implemented in the basic
authentication scheme.
@heading Changing the source code
We merely have to extend the server program so that it loads the two files into memory,
@verbatim
int
main ()
{
struct MHD_Daemon *daemon;
char *key_pem;
char *cert_pem;
key_pem = load_file (SERVERKEYFILE);
cert_pem = load_file (SERVERCERTFILE);
if ((key_pem == NULL) || (cert_pem == NULL))
{
printf ("The key/certificate files could not be read.\n");
return 1;
}
@end verbatim
@noindent
and then we point the @emph{MHD} daemon to it upon initalization.
@verbatim
daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
PORT, NULL, NULL,
&answer_to_connection, NULL,
MHD_OPTION_HTTPS_MEM_KEY, key_pem,
MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
MHD_OPTION_END);
if (NULL == daemon)
{
printf ("%s\n", cert_pem);
free (key_pem);
free (cert_pem);
return 1;
}
@end verbatim
@noindent
The rest consists of little new besides some additional memory cleanups.
@verbatim
getchar ();
MHD_stop_daemon (daemon);
free (key_pem);
free (cert_pem);
return 0;
}
@end verbatim
@noindent
The rather unexciting file loader can be found in the complete example @code{tlsauthentication.c}.
@heading Remarks
@itemize @bullet
@item
While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume
standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type
@code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
handle the answer properly.
@item
The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the
certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured)
that they should not accept certificates of unknown origin.
@item
The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to
hardcode certificates in embedded devices.
@item
The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}.
@end itemize
@heading Client authentication
You can also use MHD to authenticate the client via SSL/TLS certificates
(as an alternative to using the password-based Basic or Digest authentication).
To do this, you will need to link your application against @emph{gnutls}.
Next, when you start the MHD daemon, you must specify the root CA that you're
willing to trust:
@verbatim
daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL,
PORT, NULL, NULL,
&answer_to_connection, NULL,
MHD_OPTION_HTTPS_MEM_KEY, key_pem,
MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem,
MHD_OPTION_END);
@end verbatim
With this, you can then obtain client certificates for each session.
In order to obtain the identity of the client, you first need to
obtain the raw GnuTLS session handle from @emph{MHD} using
@code{MHD_get_connection_info}.
@verbatim
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
gnutls_session_t tls_session;
tls_session = MHD_get_connection_info (connection,
MHD_CONNECTION_INFO_GNUTLS_SESSION);
@end verbatim
You can then extract the client certificate:
@verbatim
/**
* Get the client's certificate
*
* @param tls_session the TLS session
* @return NULL if no valid client certificate could be found, a pointer
* to the certificate if found
*/
static gnutls_x509_crt_t
get_client_certificate (gnutls_session_t tls_session)
{
unsigned int listsize;
const gnutls_datum_t * pcert;
gnutls_certificate_status_t client_cert_status;
gnutls_x509_crt_t client_cert;
if (tls_session == NULL)
return NULL;
if (gnutls_certificate_verify_peers2(tls_session,
&client_cert_status))
return NULL;
pcert = gnutls_certificate_get_peers(tls_session,
&listsize);
if ( (pcert == NULL) ||
(listsize == 0))
{
fprintf (stderr,
"Failed to retrieve client certificate chain\n");
return NULL;
}
if (gnutls_x509_crt_init(&client_cert))
{
fprintf (stderr,
"Failed to initialize client certificate\n");
return NULL;
}
/* Note that by passing values between 0 and listsize here, you
can get access to the CA's certs */
if (gnutls_x509_crt_import(client_cert,
&pcert[0],
GNUTLS_X509_FMT_DER))
{
fprintf (stderr,
"Failed to import client certificate\n");
gnutls_x509_crt_deinit(client_cert);
return NULL;
}
return client_cert;
}
@end verbatim
Using the client certificate, you can then get the client's distinguished name
and alternative names:
@verbatim
/**
* Get the distinguished name from the client's certificate
*
* @param client_cert the client certificate
* @return NULL if no dn or certificate could be found, a pointer
* to the dn if found
*/
char *
cert_auth_get_dn(gnutls_x509_crt_c client_cert)
{
char* buf;
size_t lbuf;
lbuf = 0;
gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
buf = malloc(lbuf);
if (buf == NULL)
{
fprintf (stderr,
"Failed to allocate memory for certificate dn\n");
return NULL;
}
gnutls_x509_crt_get_dn(client_cert, buf, &lbuf);
return buf;
}
/**
* Get the alternative name of specified type from the client's certificate
*
* @param client_cert the client certificate
* @param nametype The requested name type
* @param index The position of the alternative name if multiple names are
* matching the requested type, 0 for the first matching name
* @return NULL if no matching alternative name could be found, a pointer
* to the alternative name if found
*/
char *
MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
int nametype,
unsigned int index)
{
char* buf;
size_t lbuf;
unsigned int seq;
unsigned int subseq;
unsigned int type;
int result;
subseq = 0;
for (seq=0;;seq++)
{
lbuf = 0;
result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
&type, NULL);
if (result == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
return NULL;
if (nametype != (int) type)
continue;
if (subseq == index)
break;
subseq++;
}
buf = malloc(lbuf);
if (buf == NULL)
{
fprintf (stderr,
"Failed to allocate memory for certificate alt name\n");
return NULL;
}
result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
seq,
buf,
&lbuf,
NULL, NULL);
if (result != nametype)
{
fprintf (stderr,
"Unexpected return value from gnutls: %d\n",
result);
free (buf);
return NULL;
}
return buf;
}
@end verbatim
Finally, you should release the memory associated with the client
certificate:
@verbatim
gnutls_x509_crt_deinit (client_cert);
@end verbatim
|