src/ssl_client.c
changeset 180 22967b165692
parent 163 27a112d89a73
child 183 7bfbe9070c50
equal deleted inserted replaced
179:0eaa5c1b926d 180:22967b165692
       
     1 #include "ssl_internal.h"
       
     2 
       
     3 #include <gnutls/x509.h>
       
     4 
       
     5 #include <stdlib.h>
       
     6 #include <string.h>
       
     7 #include <time.h>
       
     8 
       
     9 // XXX: remove
       
    10 #include "log.h"
       
    11 #include <assert.h>
       
    12 
       
    13 
       
    14 /**
       
    15  * Cast a ssl_client to a sock_fd.
       
    16  */
       
    17 #define SSL_CLIENT_FD(client_ptr) (&(client_ptr)->base_tcp.base_trans.base_fd)
       
    18 
       
    19 /**
       
    20  * Cast a ssl_client to a sock_stream.
       
    21  */
       
    22 #define SSL_CLIENT_TRANSPORT(client_ptr) (&(client_ptr)->base_tcp.base_trans.base_fd.base)
       
    23 
       
    24 
       
    25 
       
    26 /**
       
    27  * Enable the TCP events based on the session's gnutls_record_get_direction().
       
    28  */
       
    29 static err_t ssl_client_ev_enable (struct ssl_client *client, error_t *err)
       
    30 {
       
    31     int ret;
       
    32     short mask;
       
    33 
       
    34     // gnutls_record_get_direction tells us what I/O operation gnutls would have required for the last
       
    35     // operation, so we can use that to determine what events to register
       
    36     switch ((ret = gnutls_record_get_direction(client->session))) {
       
    37         case 0: 
       
    38             // read more data
       
    39             mask = TRANSPORT_READ;
       
    40             break;
       
    41         
       
    42         case 1:
       
    43             // write buffer full
       
    44             mask = TRANSPORT_WRITE;
       
    45             break;
       
    46         
       
    47         default:
       
    48             // random error
       
    49             RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_GET_DIRECTION, ret);
       
    50     }
       
    51     
       
    52     // do the enabling
       
    53     if ((ERROR_CODE(err) = transport_fd_enable(SSL_CLIENT_FD(client), mask)))
       
    54         return ERROR_CODE(err);
       
    55     
       
    56 
       
    57     return SUCCESS;
       
    58 }
       
    59 
       
    60 /**
       
    61  * Translate a set of gnutls_certificate_status_t values to a constant error message
       
    62  */
       
    63 static const char* ssl_client_verify_error (unsigned int status)
       
    64 {
       
    65     if (status & GNUTLS_CERT_REVOKED)
       
    66         return "certificate was revoked";
       
    67 
       
    68     else if (status & GNUTLS_CERT_INVALID) {
       
    69         if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
       
    70             return "certificate signer was not found";
       
    71 
       
    72         else if (status & GNUTLS_CERT_SIGNER_NOT_CA)
       
    73             return "certificate signer is not a Certificate Authority";
       
    74 
       
    75         else if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
       
    76             return "certificate signed using an insecure algorithm";
       
    77 
       
    78         else
       
    79             return "certificate could not be verified";
       
    80 
       
    81     } else
       
    82         return "unknown error";
       
    83 
       
    84 }
       
    85 
       
    86 /**
       
    87  * Perform the certificate validation procedure on the peer cert.
       
    88  *
       
    89  * Based on the GnuTLS examples/ex-rfc2818.c
       
    90  */
       
    91 static err_t ssl_client_verify (struct ssl_client *client, error_t *err)
       
    92 {
       
    93     unsigned int status;
       
    94     const gnutls_datum_t *cert_list;
       
    95     unsigned int cert_list_size;
       
    96     gnutls_x509_crt_t cert = NULL;
       
    97     time_t t, now;
       
    98 
       
    99     // init
       
   100     RESET_ERROR(err);
       
   101     now = time(NULL);
       
   102     
       
   103     // inspect the peer's cert chain using the installed trusted CAs
       
   104     if ((ERROR_EXTRA(err) = gnutls_certificate_verify_peers2(client->session, &status)))
       
   105         JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_VERIFY_PEERS2);
       
   106 
       
   107     // verify errors?
       
   108     if (status)
       
   109         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, ssl_client_verify_error(status));
       
   110     
       
   111     // import the main cert
       
   112     assert(gnutls_certificate_type_get(client->session) == GNUTLS_CRT_X509);
       
   113 
       
   114     if ((ERROR_EXTRA(err) = gnutls_x509_crt_init(&cert)))
       
   115         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_init");
       
   116 
       
   117     if ((cert_list = gnutls_certificate_get_peers(client->session, &cert_list_size)) == NULL)
       
   118         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_certificate_get_peers");
       
   119 
       
   120     if (!cert_list_size)
       
   121         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "cert_list_size");
       
   122 
       
   123     if ((ERROR_EXTRA(err) = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)))
       
   124         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_import");
       
   125     
       
   126     // check expire/activate... not sure if we need to do this
       
   127     if ((t = gnutls_x509_crt_get_expiration_time(cert)) == ((time_t) -1) || t < now)
       
   128         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_expiration_time");
       
   129 
       
   130     if ((t = gnutls_x509_crt_get_activation_time(cert)) == ((time_t) -1) || t > now)
       
   131         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_activation_time");
       
   132     
       
   133     // check hostname
       
   134     if (!gnutls_x509_crt_check_hostname(cert, client->hostname))
       
   135         JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_check_hostname");
       
   136 
       
   137 error:
       
   138     // cleanup
       
   139     if (cert)
       
   140         gnutls_x509_crt_deinit(cert);
       
   141     
       
   142     // should be SUCCESS
       
   143     return ERROR_CODE(err);    
       
   144 }
       
   145 
       
   146 
       
   147 /**
       
   148  * Our handshake driver. This will execute the next gnutls_handshake step, handling E_AGAIN.
       
   149  *
       
   150  * This updates the ssl_client::handshake state internally, as used by ssl_client_event_handler.
       
   151  *
       
   152  * If the client is marked as verify, this will perform the verification, returning on any errors, and then unset the
       
   153  * verify flag - this ensures that the peer cert is only verified once per connection...
       
   154  *
       
   155  * @return >0 for finished handshake, 0 for handshake-in-progress, -err_t for errors.
       
   156  */
       
   157 static int ssl_client_handshake (struct ssl_client *client, error_t *err)
       
   158 {
       
   159     int ret;
       
   160 
       
   161     // perform the handshake
       
   162     if ((ret = gnutls_handshake(client->session)) < 0 && ret != GNUTLS_E_AGAIN)
       
   163         JUMP_SET_ERROR_EXTRA(err, ERR_GNUTLS_HANDSHAKE, ret);
       
   164     
       
   165     // complete?
       
   166     if (ret == 0) {
       
   167         // update state
       
   168         client->handshake = false;
       
   169 
       
   170         // verify?
       
   171         if (client->verify) {
       
   172             // perform the validation
       
   173             if (ssl_client_verify(client, err))
       
   174                 goto error;
       
   175             
       
   176             // unmark
       
   177             client->verify = false;
       
   178         }
       
   179 
       
   180         // handshake done
       
   181         return 1;
       
   182 
       
   183     } else {
       
   184         // set state, isn't really needed every time, but easier this way
       
   185         client->handshake = true;
       
   186 
       
   187         // re-enable the event for the next iteration
       
   188         return ssl_client_ev_enable(client, err);
       
   189     }
       
   190 
       
   191 error:
       
   192     return -ERROR_CODE(err);    
       
   193 }
       
   194 
       
   195 /**
       
   196  * Our transport_fd event handler. Drive the handshake if that's current, otherwise, invoke user callbacks.
       
   197  */
       
   198 static void ssl_client_on_event (struct transport_fd *fd, short what, void *arg)
       
   199 {
       
   200     struct ssl_client *client = arg;
       
   201     error_t err;
       
   202 
       
   203     (void) fd;
       
   204 
       
   205     // XXX: timeouts
       
   206     (void) what;
       
   207 
       
   208     // are we in the handshake cycle?
       
   209     if (client->handshake) {
       
   210         RESET_ERROR(&err);
       
   211 
       
   212         // perform the next handshake step
       
   213         if (ssl_client_handshake(client, &err) == 0) {
       
   214             // handshake continues
       
   215         
       
   216             // XXX: this state flag is completely wrong
       
   217         } else if (SSL_CLIENT_TRANSPORT(client)->connected) {
       
   218             // the async connect process has now completed, either succesfully or with an error
       
   219             // invoke the user connect callback directly with appropriate error
       
   220             transport_connected(SSL_CLIENT_TRANSPORT(client), ERROR_CODE(&err) ? &err : NULL, true);
       
   221 
       
   222         } else {
       
   223             if (ERROR_CODE(&err))
       
   224                 // the re-handshake failed, so this transport is dead
       
   225                 transport_error(SSL_CLIENT_TRANSPORT(client), &err);
       
   226         
       
   227             else
       
   228                 // re-handshake completed, so continue with the transport_callbacks
       
   229                 transport_invoke(SSL_CLIENT_TRANSPORT(client), what);
       
   230         }
       
   231 
       
   232     } else {
       
   233         // normal transport operation
       
   234         // gnutls might be able to proceed now, so invoke user callbacks
       
   235         transport_invoke(SSL_CLIENT_TRANSPORT(client), what);
       
   236     }
       
   237 }
       
   238 
       
   239 static err_t ssl_client__read (transport_t *transport, void *buf, size_t *len, error_t *err)
       
   240 {
       
   241     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   242     int ret;
       
   243     
       
   244     // read gnutls record
       
   245     do {
       
   246         ret = gnutls_record_recv(client->session, buf, *len);
       
   247 
       
   248     } while (ret == GNUTLS_E_INTERRUPTED);
       
   249     
       
   250     // errors
       
   251     // XXX: E_REHANDSHAKE?
       
   252     if (ret < 0 && ret != GNUTLS_E_AGAIN)
       
   253         RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret);
       
   254     
       
   255     else if (ret == 0)
       
   256         return SET_ERROR(err, ERR_EOF);
       
   257 
       
   258 
       
   259     // EAGAIN?
       
   260     if (ret < 0) {
       
   261         *len = 0;
       
   262 
       
   263     } else {
       
   264         // updated length
       
   265         *len = ret;
       
   266 
       
   267     }
       
   268 
       
   269     return SUCCESS;
       
   270 }
       
   271 
       
   272 static err_t ssl_client__write (transport_t *transport, const void *buf, size_t *len, error_t *err)
       
   273 {
       
   274     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   275     int ret;
       
   276  
       
   277     // read gnutls record
       
   278     do {
       
   279         ret = gnutls_record_send(client->session, buf, *len);
       
   280    
       
   281     } while (ret == GNUTLS_E_INTERRUPTED);
       
   282 
       
   283     // errors
       
   284     if (ret < 0 && ret != GNUTLS_E_AGAIN)
       
   285         RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_SEND, ret);
       
   286     
       
   287     else if (ret == 0)
       
   288         return SET_ERROR(err, ERR_WRITE_EOF);
       
   289 
       
   290 
       
   291     // eagain?
       
   292     if (ret < 0) {
       
   293         *len = 0;
       
   294 
       
   295     } else {
       
   296         // updated length
       
   297         *len = ret;
       
   298     }
       
   299 
       
   300     return SUCCESS;
       
   301 }
       
   302 
       
   303 void ssl_client_deinit (struct ssl_client *client)
       
   304 {
       
   305     // close the session rudely
       
   306     gnutls_deinit(client->session);
       
   307     client->session = NULL;
       
   308  
       
   309     // terminate the TCP transport
       
   310     tcp_client_deinit(&client->base_tcp);
       
   311    
       
   312     if (client->cred) {
       
   313         // drop the cred ref
       
   314         ssl_client_cred_put(client->cred);
       
   315 
       
   316         client->cred = NULL;
       
   317     }
       
   318 
       
   319     // free
       
   320     free(client->hostname);
       
   321     client->hostname = NULL;
       
   322 }
       
   323 
       
   324 
       
   325 static void ssl_client__deinit (transport_t *transport)
       
   326 {
       
   327     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   328     
       
   329     // die
       
   330     ssl_client_deinit(client);
       
   331 }
       
   332 
       
   333 /**
       
   334  * Our tcp_client-invoked connect handler
       
   335  */
       
   336 static void ssl_client__connected (transport_t *transport, const error_t *tcp_err)
       
   337 {
       
   338     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   339     error_t err;
       
   340 
       
   341     // trap errors to let the user handle them directly
       
   342     if (tcp_err)
       
   343         JUMP_SET_ERROR_INFO(&err, tcp_err);
       
   344     
       
   345     // bind default transport functions (recv/send) to use the TCP fd
       
   346     gnutls_transport_set_ptr(client->session, (gnutls_transport_ptr_t) (long int) SSL_CLIENT_FD(client)->fd);
       
   347 
       
   348     // add ourselves as the event handler
       
   349     if ((ERROR_CODE(&err) = transport_fd_setup(SSL_CLIENT_FD(client), ssl_client_on_event, client)))
       
   350         goto error;
       
   351 
       
   352     // start handshake
       
   353     if (ssl_client_handshake(client, &err))
       
   354         // this should complete with SUCCESS if it returns >0
       
   355         goto error;
       
   356 
       
   357     // ok, so we wait...
       
   358     return;
       
   359 
       
   360 error:
       
   361     // tell the user
       
   362     transport_connected(transport, &err, true);
       
   363 }
       
   364 
       
   365 struct transport_type ssl_client_type = {
       
   366     .base_type = {
       
   367         .parent     = &tcp_client_type.base_type,
       
   368     },
       
   369     .methods = {
       
   370         .read       = ssl_client__read,
       
   371         .write      = ssl_client__write,
       
   372         .deinit     = ssl_client__deinit,
       
   373         ._connected = ssl_client__connected,
       
   374     },
       
   375 };
       
   376 
       
   377 
       
   378 
       
   379 static void ssl_client_destroy (struct ssl_client *client)
       
   380 {
       
   381     ssl_client_deinit(client);
       
   382 
       
   383     free(client);
       
   384 }
       
   385 
       
   386 err_t ssl_connect (const struct transport_info *info, transport_t **transport_ptr, 
       
   387         const char *hostname, const char *service,
       
   388         struct ssl_client_cred *cred,
       
   389         error_t *err
       
   390     )
       
   391 {
       
   392     struct ssl_client *client = NULL;
       
   393 
       
   394     // alloc
       
   395     if ((client = calloc(1, sizeof(*client))) == NULL)
       
   396         return SET_ERROR(err, ERR_CALLOC);
       
   397 
       
   398     // initialize base
       
   399     transport_init(SSL_CLIENT_TRANSPORT(client), &ssl_client_type, info);
       
   400 
       
   401     if (!cred) {
       
   402         // default credentials
       
   403         cred = &ssl_client_cred_anon;
       
   404     
       
   405     } else {
       
   406         // take a ref
       
   407         client->cred = cred;
       
   408         cred->refcount++;
       
   409     };
       
   410 
       
   411     // do verify?
       
   412     if (cred->verify)
       
   413         client->verify = true;
       
   414 
       
   415     // init
       
   416     if ((client->hostname = strdup(hostname)) == NULL)
       
   417         JUMP_SET_ERROR(err, ERR_STRDUP);
       
   418 
       
   419     // initialize TCP
       
   420     tcp_client_init(&client->base_tcp);
       
   421 
       
   422     // initialize client session
       
   423     if ((ERROR_EXTRA(err) = gnutls_init(&client->session, GNUTLS_CLIENT)) < 0)
       
   424         JUMP_SET_ERROR(err, ERR_GNUTLS_INIT);
       
   425 
       
   426     // ...default priority stuff
       
   427     if ((ERROR_EXTRA(err) = gnutls_set_default_priority(client->session)))
       
   428         JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY);
       
   429 
       
   430     // XXX: silly hack for OpenSSL interop
       
   431     gnutls_dh_set_prime_bits(client->session, 512);
       
   432 
       
   433     // bind credentials
       
   434     if ((ERROR_EXTRA(err) = gnutls_credentials_set(client->session, GNUTLS_CRD_CERTIFICATE, cred->x509)))
       
   435         JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET);
       
   436 
       
   437     // TCP connect
       
   438     if (tcp_client_connect_async(&client->base_tcp, hostname, service, err))
       
   439         goto error;
       
   440 
       
   441     // done, wait for the connect to complete
       
   442     *transport_ptr = SSL_CLIENT_TRANSPORT(client);
       
   443 
       
   444     return SUCCESS;
       
   445 
       
   446 error:
       
   447     // cleanup
       
   448     ssl_client_destroy(client);
       
   449 
       
   450     return ERROR_CODE(err);    
       
   451 }
       
   452 
       
   453