src/ssl_client.c
branchnew-lib-errors
changeset 219 cefec18b8268
parent 218 5229a5d098b2
equal deleted inserted replaced
218:5229a5d098b2 219:cefec18b8268
     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         // this returns zero when the handshake is not yet done, errors/completion then trigger the else-if-else below
       
   214         if (ssl_client_handshake(client, &err) == 0) {
       
   215             // handshake continues
       
   216         
       
   217         } else if (!SSL_CLIENT_TRANSPORT(client)->connected) {
       
   218             // the async connect+handshake process has completed
       
   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             // in-connection re-handshake completed
       
   224             if (ERROR_CODE(&err))
       
   225                 // the re-handshake failed, so this transport is dead
       
   226                 transport_error(SSL_CLIENT_TRANSPORT(client), &err);
       
   227         
       
   228             else
       
   229                 // re-handshake completed, so continue with the transport_callbacks
       
   230                 transport_invoke(SSL_CLIENT_TRANSPORT(client), what);
       
   231         }
       
   232 
       
   233     } else {
       
   234         // normal transport operation
       
   235         // gnutls might be able to proceed now, so invoke user callbacks
       
   236         transport_invoke(SSL_CLIENT_TRANSPORT(client), what);
       
   237     }
       
   238 }
       
   239 
       
   240 static err_t ssl_client__read (transport_t *transport, void *buf, size_t *len, error_t *err)
       
   241 {
       
   242     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   243     int ret;
       
   244     
       
   245     // read gnutls record
       
   246     do {
       
   247         ret = gnutls_record_recv(client->session, buf, *len);
       
   248 
       
   249     } while (ret == GNUTLS_E_INTERRUPTED);
       
   250     
       
   251     // errors
       
   252     // XXX: E_REHANDSHAKE?
       
   253     if (ret < 0 && ret != GNUTLS_E_AGAIN)
       
   254         RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret);
       
   255     
       
   256     else if (ret == 0)
       
   257         return SET_ERROR(err, ERR_EOF);
       
   258 
       
   259 
       
   260     // EAGAIN?
       
   261     if (ret < 0) {
       
   262         *len = 0;
       
   263 
       
   264     } else {
       
   265         // updated length
       
   266         *len = ret;
       
   267 
       
   268     }
       
   269 
       
   270     return SUCCESS;
       
   271 }
       
   272 
       
   273 static err_t ssl_client__write (transport_t *transport, const void *buf, size_t *len, error_t *err)
       
   274 {
       
   275     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   276     int ret;
       
   277  
       
   278     // read gnutls record
       
   279     do {
       
   280         ret = gnutls_record_send(client->session, buf, *len);
       
   281    
       
   282     } while (ret == GNUTLS_E_INTERRUPTED);
       
   283 
       
   284     // errors
       
   285     if (ret < 0 && ret != GNUTLS_E_AGAIN)
       
   286         RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_SEND, ret);
       
   287     
       
   288     else if (ret == 0)
       
   289         return SET_ERROR(err, ERR_WRITE_EOF);
       
   290 
       
   291 
       
   292     // eagain?
       
   293     if (ret < 0) {
       
   294         *len = 0;
       
   295 
       
   296     } else {
       
   297         // updated length
       
   298         *len = ret;
       
   299     }
       
   300 
       
   301     return SUCCESS;
       
   302 }
       
   303 
       
   304 void ssl_client_deinit (struct ssl_client *client)
       
   305 {
       
   306     // close the session rudely
       
   307     gnutls_deinit(client->session);
       
   308     client->session = NULL;
       
   309  
       
   310     // terminate the TCP transport
       
   311     tcp_client_deinit(&client->base_tcp);
       
   312    
       
   313     if (client->cred) {
       
   314         // drop the cred ref
       
   315         ssl_client_cred_put(client->cred);
       
   316 
       
   317         client->cred = NULL;
       
   318     }
       
   319 
       
   320     // free
       
   321     free(client->hostname);
       
   322     client->hostname = NULL;
       
   323 }
       
   324 
       
   325 
       
   326 static void ssl_client__deinit (transport_t *transport)
       
   327 {
       
   328     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   329     
       
   330     // die
       
   331     ssl_client_deinit(client);
       
   332 }
       
   333 
       
   334 /**
       
   335  * Our tcp_client-invoked connect handler
       
   336  */
       
   337 static void ssl_client__connected (transport_t *transport, const error_t *tcp_err)
       
   338 {
       
   339     struct ssl_client *client = transport_check(transport, &ssl_client_type);
       
   340     error_t err;
       
   341 
       
   342     // trap errors to let the user handle them directly
       
   343     if (tcp_err)
       
   344         JUMP_SET_ERROR_INFO(&err, tcp_err);
       
   345     
       
   346     // bind default transport functions (recv/send) to use the TCP fd
       
   347     gnutls_transport_set_ptr(client->session, (gnutls_transport_ptr_t) (long int) SSL_CLIENT_FD(client)->fd);
       
   348 
       
   349     // add ourselves as the event handler
       
   350     if ((ERROR_CODE(&err) = transport_fd_setup(SSL_CLIENT_FD(client), ssl_client_on_event, client)))
       
   351         goto error;
       
   352 
       
   353     // start handshake
       
   354     if (ssl_client_handshake(client, &err))
       
   355         // this should complete with SUCCESS if it returns >0
       
   356         goto error;
       
   357 
       
   358     // ok, so we wait...
       
   359     return;
       
   360 
       
   361 error:
       
   362     // tell the user
       
   363     transport_connected(transport, &err, true);
       
   364 }
       
   365 
       
   366 struct transport_type ssl_client_type = {
       
   367     .base_type = {
       
   368         .parent     = &tcp_client_type.base_type,
       
   369     },
       
   370     .methods = {
       
   371         .read       = ssl_client__read,
       
   372         .write      = ssl_client__write,
       
   373         .deinit     = ssl_client__deinit,
       
   374         ._connected = ssl_client__connected,
       
   375     },
       
   376 };
       
   377 
       
   378 
       
   379 
       
   380 static void ssl_client_destroy (struct ssl_client *client)
       
   381 {
       
   382     ssl_client_deinit(client);
       
   383 
       
   384     free(client);
       
   385 }
       
   386 
       
   387 err_t ssl_connect (const struct transport_info *info, transport_t **transport_ptr, 
       
   388         const char *hostname, const char *service,
       
   389         struct ssl_client_cred *cred,
       
   390         error_t *err
       
   391     )
       
   392 {
       
   393     struct ssl_client *client = NULL;
       
   394 
       
   395     // alloc
       
   396     if ((client = calloc(1, sizeof(*client))) == NULL)
       
   397         return SET_ERROR(err, ERR_CALLOC);
       
   398 
       
   399     // initialize base
       
   400     transport_init(SSL_CLIENT_TRANSPORT(client), &ssl_client_type, info);
       
   401 
       
   402     if (!cred) {
       
   403         // default credentials
       
   404         cred = &ssl_client_cred_anon;
       
   405     
       
   406     } else {
       
   407         // take a ref
       
   408         client->cred = cred;
       
   409         cred->refcount++;
       
   410     };
       
   411 
       
   412     // do verify?
       
   413     if (cred->verify)
       
   414         client->verify = true;
       
   415 
       
   416     // init
       
   417     if ((client->hostname = strdup(hostname)) == NULL)
       
   418         JUMP_SET_ERROR(err, ERR_STRDUP);
       
   419 
       
   420     // initialize TCP
       
   421     tcp_client_init(&client->base_tcp);
       
   422 
       
   423     // initialize client session
       
   424     if ((ERROR_EXTRA(err) = gnutls_init(&client->session, GNUTLS_CLIENT)) < 0)
       
   425         JUMP_SET_ERROR(err, ERR_GNUTLS_INIT);
       
   426 
       
   427     // ...default priority stuff
       
   428     if ((ERROR_EXTRA(err) = gnutls_set_default_priority(client->session)))
       
   429         JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY);
       
   430 
       
   431     // XXX: silly hack for OpenSSL interop
       
   432     gnutls_dh_set_prime_bits(client->session, 512);
       
   433 
       
   434     // bind credentials
       
   435     if ((ERROR_EXTRA(err) = gnutls_credentials_set(client->session, GNUTLS_CRD_CERTIFICATE, cred->x509)))
       
   436         JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET);
       
   437 
       
   438     // TCP connect
       
   439     if (tcp_client_connect_async(&client->base_tcp, hostname, service, err))
       
   440         goto error;
       
   441 
       
   442     // done, wait for the connect to complete
       
   443     *transport_ptr = SSL_CLIENT_TRANSPORT(client);
       
   444 
       
   445     return SUCCESS;
       
   446 
       
   447 error:
       
   448     // cleanup
       
   449     ssl_client_destroy(client);
       
   450 
       
   451     return ERROR_CODE(err);    
       
   452 }
       
   453 
       
   454