implement ssl_cafile/verify/cert/pkey for x509 credentials
authorTero Marttila <terom@fixme.fi>
Sun, 19 Apr 2009 04:04:42 +0300
changeset 140 aa390e52eda8
parent 139 55b9dcc2b73a
child 141 0b850238c588
implement ssl_cafile/verify/cert/pkey for x509 credentials
src/error.c
src/irc_client.c
src/irc_net.c
src/irc_net.h
src/nexus.c
src/sock.h
src/sock_gnutls.c
src/sock_gnutls.h
src/sock_ssl.h
--- a/src/error.c	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/error.c	Sun Apr 19 04:04:42 2009 +0300
@@ -45,6 +45,10 @@
     {   ERR_GNUTLS_RECORD_SEND,             "gnutls_record_send",                       ERR_EXTRA_GNUTLS    },
     {   ERR_GNUTLS_RECORD_RECV,             "gnutls_record_recv",                       ERR_EXTRA_GNUTLS    },
     {   ERR_GNUTLS_RECORD_GET_DIRECTION,    "gnutls_record_get_direction",              ERR_EXTRA_GNUTLS    },
+    {   ERR_GNUTLS_CERT_VERIFY_PEERS2,      "gnutls_certificate_verify_peers2",         ERR_EXTRA_GNUTLS    },
+    {   ERR_GNUTLS_CERT_VERIFY,             "X.509 Certificate verification failed",    ERR_EXTRA_STR       },
+    {   ERR_GNUTLS_CERT_SET_X509_TRUST_FILE,"gnutls_certificate_set_x509_trust_file",   ERR_EXTRA_GNUTLS    },
+    {   ERR_GNUTLS_CERT_SET_X509_KEY_FILE,  "gnutls_certificate_set_x509_key_file",     ERR_EXTRA_GNUTLS    },
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
 }, _irc_error_desc[] = {
--- a/src/irc_client.c	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/irc_client.c	Sun Apr 19 04:04:42 2009 +0300
@@ -54,7 +54,7 @@
 {
     struct irc_net *net;
     struct irc_net_info net_info = {
-        .use_ssl    = _net_info->use_ssl,
+        .ssl_cred   = _net_info->ssl_cred,
     };
 
     // combine _net_info and defaults to get net_info
@@ -68,7 +68,7 @@
         RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "hostname");
     
     if ((net_info.service = (_net_info->service ? _net_info->service : (
-                    _net_info->use_ssl ? client->defaults.service_ssl : client->defaults.service
+                    _net_info->ssl_cred ? client->defaults.service_ssl : client->defaults.service
             ))) == NULL)
         RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "service");
 
--- a/src/irc_net.c	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/irc_net.c	Sun Apr 19 04:04:42 2009 +0300
@@ -290,24 +290,30 @@
         return SET_ERROR(err, ERR_CALLOC);
 
     // initialize
+    // XXX: info shouldn't be copied directly
     net->info = *info;
     TAILQ_INIT(&net->channels);
     LIST_INIT(&net->users);
 
     if (info->raw_sock) {
-        // direct sock_stream connection
         log_debug("connected using raw socket: %p", info->raw_sock);
 
+        // direct sock_stream connection
         sock = info->raw_sock;
 
         // then create the conn right away
         if (irc_net_connected(net, sock, err))
             goto error;
 
-    } else if (info->use_ssl) {
+    } else if (info->ssl_cred) {
+        // aquire a ref
+        // NOTE: before any error handling
+        sock_ssl_client_cred_get(net->info.ssl_cred);
+        
         log_debug("connecting to [%s]:%s using SSL", info->hostname, info->service);
 
-        if (sock_ssl_connect_async(&sock, info->hostname, info->service, &irc_net_on_connect, net, err))
+        // connect
+        if (sock_ssl_connect_async(&sock, info->hostname, info->service, net->info.ssl_cred, &irc_net_on_connect, net, err))
             goto error;
 
     } else {
@@ -337,21 +343,31 @@
 
 void irc_net_destroy (struct irc_net *net)
 {
-    struct irc_chan *next = TAILQ_FIRST(&net->channels), *chan;
+    struct irc_chan *chan_next = TAILQ_FIRST(&net->channels), *chan;
+    struct irc_user *user_next = LIST_FIRST(&net->users), *user;
 
     // our conn
     if (net->conn)
         irc_conn_destroy(net->conn);
 
     // our channels
-    while (next) {
-        chan = next;
-        next = TAILQ_NEXT(chan, net_channels);
+    while ((chan = chan_next)) {
+        chan_next = TAILQ_NEXT(chan, net_channels);
 
         irc_chan_destroy(chan);
     }
 
-    // XXX: our users
+    // our users
+    // XXX: this disregards external refs
+    while ((user = user_next)) {
+        user_next = LIST_NEXT(user, net_users);
+
+        irc_user_destroy(user);
+    }
+
+    // our info
+    if (net->info.ssl_cred)
+        sock_ssl_client_cred_put(net->info.ssl_cred);
 
     // ourselves
     free(net);
--- a/src/irc_net.h	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/irc_net.h	Sun Apr 19 04:04:42 2009 +0300
@@ -13,6 +13,7 @@
 #include "error.h"
 #include "irc_conn.h"
 #include "irc_chan.h"
+#include "sock_ssl.h"
 #include <sys/queue.h>
 
 /**
@@ -28,8 +29,8 @@
     /** Service name (port) */
     const char *service;
 
-    /** SSL? */
-    bool use_ssl;
+    /** Use SSL if given as non-NULL, a reference will be held by the irc_net */
+    struct sock_ssl_client_cred *ssl_cred;
 
     /** Protocol registration info (nickname etc) */
     struct irc_conn_register_info register_info;
--- a/src/nexus.c	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/nexus.c	Sun Apr 19 04:04:42 2009 +0300
@@ -60,7 +60,7 @@
     printf("\n");
     printf(" --help / -h            display this message\n");
     printf(" --defaults / -D        set the IRC client default info using '<nickname>:<username>:<realname>'\n");
-    printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl]]' format\n");
+    printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl][:ssl_cafile=<path>][:ssl_verify][:ssl_cert=<path>][:ssl_pkey=<path>]]' format\n");
     printf(" --channel              add an IRC channel using '<network>:<channel>' format\n");
     printf(" --module / -m          add a module using '<name>:<path>' format\n");
     printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
@@ -135,16 +135,18 @@
 static err_t apply_network (struct nexus *nexus, char *opt, struct error_info *err)
 {
     struct irc_net_info info;
+    const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL;
+    bool use_ssl = false, ssl_verify = false;
 
     // init to zero
     memset(&info, 0, sizeof(info));
 
     // parse the required fields
     if ((info.network = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --network");
+        JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --network");
     
     if ((info.hostname = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <hostname> field for --network");
+        JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <hostname> field for --network");
     
     // parse the optional fields
     if (opt)
@@ -152,23 +154,64 @@
     
     // parse any remaining flags
     while (opt) {
-        char *flag = strsep(&opt, ":");
+        char *flag_token = strsep(&opt, ":");
+        char *flag = strsep(&flag_token, "=");
+        char *value = flag_token;
 
-        if (strcmp(flag, "ssl") == 0)
-            info.use_ssl = true;
+        if (strcmp(flag, "ssl") == 0) {
+            use_ssl = true;
 
-        else
-            RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unrecognized flag for --network");
+        } else if (strcmp(flag, "ssl_cafile") == 0) {
+            if (!value)
+                JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing value for :ssl_cafile");
+
+            ssl_cafile = value;
+        
+        } else if (strcmp(flag, "ssl_verify") == 0) {
+            ssl_verify = true;
+
+        } else if (strcmp(flag, "ssl_cert") == 0) {
+            if (!value)
+                JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing value for :ssl_cert");
+            
+            ssl_cert = value;
+
+        } else if (strcmp(flag, "ssl_pkey") == 0) {
+            if (!value)
+                JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing value for :ssl_pkey");
+
+            ssl_pkey = value;
+
+        } else
+            JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "unrecognized flag for --network");
+    }
+
+    // SSL?
+    if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) {
+        // verify
+        if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey))
+            JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey");
+        
+        // create
+        if (sock_ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err))
+            goto error;
     }
 
     // create the net
     log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service);
 
     if (irc_client_add_net(nexus->client, NULL, &info, err))
-        return ERROR_CODE(err);
+        goto error;
 
     // ok
-    return SET_ERROR(err, SUCCESS);
+    return SUCCESS;
+
+error:
+    // cleanup
+    if (info.ssl_cred)
+        sock_ssl_client_cred_put(info.ssl_cred);
+
+    return ERROR_CODE(err);
 }
 
 /**
--- a/src/sock.h	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/sock.h	Sun Apr 19 04:04:42 2009 +0300
@@ -91,21 +91,6 @@
         sock_stream_connect_cb cb_func, void *cb_arg, struct error_info *err);
 
 /**
- * Start a non-blocking SSL connect/handshake to the given host/service. The socket will not yet be connected when the
- * function returns, but rather, the eventual redyness/failure of the connect/handshake will be indicated later using
- * the given \a cb_func.
- * 
- * @param sock_ptr the new sock_stream
- * @param host the hostname to connect to
- * @param service the TCP service name (i.e. port) to connect to
- * @param err returned error info
- *
- * XXX: doesn't do any certificate verification.
- */
-err_t sock_ssl_connect_async (struct sock_stream **sock_ptr, const char *host, const char *service, 
-        sock_stream_connect_cb cb_func, void *cb_arg, struct error_info *err);
-
-/**
  * A read-only "socket" based on a FIFO, this provides nonblocking read operations by re-opening the FIFO on EOF.
  */
 err_t fifo_open_read (struct sock_stream **stream_ptr, const char *path, struct error_info *err);
--- a/src/sock_gnutls.c	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/sock_gnutls.c	Sun Apr 19 04:04:42 2009 +0300
@@ -4,8 +4,13 @@
 // XXX: remove
 #include "log.h"
 
+#include <gnutls/x509.h>
+
 #include <stdlib.h>
-#include <err.h>
+#include <string.h>
+#include <time.h>
+
+#include <assert.h>
 
 /**
  * Register for events based on the session's gnutls_record_get_direction().
@@ -37,10 +42,100 @@
 }
 
 /**
+ * Translate a set of gnutls_certificate_status_t values to a constant error message
+ */
+static const char* sock_gnutls_verify_error (unsigned int status)
+{
+    if (status & GNUTLS_CERT_REVOKED)
+        return "certificate was revoked";
+
+    else if (status & GNUTLS_CERT_INVALID) {
+        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
+            return "certificate signer was not found";
+
+        else if (status & GNUTLS_CERT_SIGNER_NOT_CA)
+            return "certificate signer is not a Certificate Authority";
+
+        else if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
+            return "certificate signed using an insecure algorithm";
+
+        else
+            return "certificate could not be verified";
+
+    } else
+        return "unknown error";
+
+}
+
+/**
+ * Perform the certificate validation procedure on the socket.
+ *
+ * Based on the GnuTLS examples/ex-rfc2818.c
+ */
+static err_t sock_gnutls_verify (struct sock_gnutls *sock, struct error_info *err)
+{
+    unsigned int status;
+    const gnutls_datum_t *cert_list;
+    unsigned int cert_list_size;
+    gnutls_x509_crt_t cert = NULL;
+    time_t t, now;
+
+    // init
+    RESET_ERROR(err);
+    now = time(NULL);
+    
+    // inspect the peer's cert chain using the installed trusted CAs
+    if ((ERROR_EXTRA(err) = gnutls_certificate_verify_peers2(sock->session, &status)))
+        JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_VERIFY_PEERS2);
+
+    // verify errors?
+    if (status)
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, sock_gnutls_verify_error(status));
+    
+    // import the main cert
+    assert(gnutls_certificate_type_get(sock->session) == GNUTLS_CRT_X509);
+
+    if ((ERROR_EXTRA(err) = gnutls_x509_crt_init(&cert)))
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_init");
+
+    if ((cert_list = gnutls_certificate_get_peers(sock->session, &cert_list_size)) == NULL)
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_certificate_get_peers");
+
+    if (!cert_list_size)
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "cert_list_size");
+
+    if ((ERROR_EXTRA(err) = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER)))
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_import");
+    
+    // check expire/activate... not sure if we need to do this
+    if ((t = gnutls_x509_crt_get_expiration_time(cert)) == ((time_t) -1) || t < now)
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_expiration_time");
+
+    if ((t = gnutls_x509_crt_get_activation_time(cert)) == ((time_t) -1) || t > now)
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_activation_time");
+    
+    // check hostname
+    if (!gnutls_x509_crt_check_hostname(cert, sock->hostname))
+        JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_check_hostname");
+
+error:
+    // cleanup
+    if (cert)
+        gnutls_x509_crt_deinit(cert);
+    
+    // should be SUCCESS
+    return ERROR_CODE(err);    
+}
+
+
+/**
  * Our handshake driver. This will execute the next gnutls_handshake step, handling E_AGAIN.
  *
  * This updates the sock_gnutls::handshake state internally, as used by sock_gnutls_event_handler.
  *
+ * If the sock is marked as verify, this will perform the verification, returning on any errors, and then unset the
+ * verify flag - this ensures that the peer cert is only verified once per connection...
+ *
  * @return >0 for finished handshake, 0 for handshake-in-progress, -err_t for errors.
  */
 static int sock_gnutls_handshake (struct sock_gnutls *sock, struct error_info *err)
@@ -56,6 +151,16 @@
         // update state
         sock->handshake = false;
 
+        // verify?
+        if (sock->verify) {
+            // perform the validation
+            if (sock_gnutls_verify(sock, err))
+                goto error;
+            
+            // unmark
+            sock->verify = false;
+        }
+
         // handshake done
         return 1;
 
@@ -92,8 +197,8 @@
             // wait for the next handshake step
         
         } else if (SOCK_GNUTLS_BASE(sock)->conn_cb_func) {
-            // the async connect process has now completed, either succesfully or not
-            // invoke the user connect callback directly
+            // the async connect process has now completed, either succesfully or with an error
+            // invoke the user connect callback directly with appropriate error
             sock_stream_invoke_conn_cb(SOCK_GNUTLS_BASE(sock), ERROR_CODE(&err) ? &err : NULL, true);
 
         } else {
@@ -125,6 +230,7 @@
     ret = gnutls_record_recv(sock->session, buf, *len);
     
     // errors
+    // XXX: E_INTERRUPTED, E_REHANDSHAKE?
     if (ret < 0 && ret != GNUTLS_E_AGAIN)
         RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret);
     
@@ -250,24 +356,11 @@
 };
 
 /*
- * XXX: global shared sock_gnutls_ctx
- */
-struct sock_gnutls_client_ctx _sock_gnutls_client_ctx;
-
-/*
- * Configure the given gnutls socket context to use simple anonymous client credentials
+ * Global shared anonymous client credentials
  */
-static err_t sock_gnutls_client_ctx_anon (struct sock_gnutls_client_ctx *ctx, struct error_info *err)
-{
-    // init to use anonymous x509 cert
-    if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&ctx->xcred)) < 0)
-        return SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED);
+static struct sock_ssl_client_cred sock_gnutls_client_cred_anon = { .x509 = NULL, .verify = false, .refcount = 0 };
 
-    // done
-    return SUCCESS;
-}
-
-// XXX: log func
+// XXX: GnuTLS log func
 void _log (int level, const char *msg)
 {
     printf("gnutls: %d: %s", level, msg);
@@ -279,9 +372,9 @@
     if ((ERROR_EXTRA(err) = gnutls_global_init()) < 0)
         return SET_ERROR(err, ERR_GNUTLS_GLOBAL_INIT);
 
-    // init _sock_gnutls_ctx
-    if (sock_gnutls_client_ctx_anon(&_sock_gnutls_client_ctx, err))
-        return ERROR_CODE(err);
+    // initialize the anon client credentials
+    if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&sock_gnutls_client_cred_anon.x509)) < 0)
+        return SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED);
 
     // XXX: debug
 //    gnutls_global_set_log_function(&_log);
@@ -291,11 +384,83 @@
     return SUCCESS;
 }
 
-err_t sock_ssl_connect_async (struct sock_stream **sock_ptr, const char *host, const char *service, 
-        sock_stream_connect_cb cb_func, void *cb_arg, struct error_info *err)
+static void sock_ssl_client_cred_destroy (struct sock_ssl_client_cred *cred)
+{
+    // simple
+    gnutls_certificate_free_credentials(cred->x509);
+
+    free(cred);
+}
+
+err_t sock_ssl_client_cred_create (struct sock_ssl_client_cred **ctx_cred,
+        const char *cafile_path, bool verify,
+        const char *cert_path, const char *pkey_path,
+        struct error_info *err
+) {
+    struct sock_ssl_client_cred *cred;
+
+    // alloc it
+    if ((cred = calloc(1, sizeof(*cred))) == NULL)
+        return SET_ERROR(err, ERR_CALLOC);
+
+    // create the cert
+    if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&cred->x509)) < 0)
+        JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED);
+    
+    // load the trusted ca certs?
+    if (cafile_path) {
+        // load them
+        if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_trust_file(cred->x509, cafile_path, GNUTLS_X509_FMT_PEM)) < 0)
+            JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_TRUST_FILE);
+
+    }
+
+    // set the verify flags?
+    cred->verify = verify;
+    gnutls_certificate_set_verify_flags(cred->x509, 0);
+
+    // load the client cert?
+    if (cert_path || pkey_path) {
+        // need both...
+        assert(cert_path && pkey_path);
+
+        // load
+        if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_key_file(cred->x509, cert_path, pkey_path, GNUTLS_X509_FMT_PEM)))
+            JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_KEY_FILE);
+    }
+
+    // ok
+    cred->refcount = 1;
+    *ctx_cred = cred;
+
+    return SUCCESS;
+
+error:
+    // release
+    sock_ssl_client_cred_destroy(cred);
+
+    return ERROR_CODE(err);
+}
+
+void sock_ssl_client_cred_get (struct sock_ssl_client_cred *cred)
+{
+    cred->refcount++;
+}
+
+void sock_ssl_client_cred_put (struct sock_ssl_client_cred *cred)
+{
+    if (--cred->refcount == 0)
+        sock_ssl_client_cred_destroy(cred);
+}
+
+err_t sock_ssl_connect_async (struct sock_stream **sock_ptr, 
+        const char *hostname, const char *service,
+        struct sock_ssl_client_cred *cred,
+        sock_stream_connect_cb cb_func, void *cb_arg, 
+        struct error_info *err
+    )
 {
     struct sock_gnutls *sock = NULL;
-    struct sock_gnutls_client_ctx *ctx = &_sock_gnutls_client_ctx;
 
     // alloc
     if ((sock = calloc(1, sizeof(*sock))) == NULL)
@@ -304,6 +469,24 @@
     // initialize base
     sock_stream_init(SOCK_GNUTLS_BASE(sock), &sock_gnutls_type, cb_func, cb_arg);
 
+    if (!cred) {
+        // default credentials
+        cred = &sock_gnutls_client_cred_anon;
+    
+    } else {
+        // take a ref
+        sock->cred = cred;
+        cred->refcount++;
+    };
+
+    // do verify?
+    if (cred->verify)
+        sock->verify = true;
+
+    // init
+    if ((sock->hostname = strdup(hostname)) == NULL)
+        JUMP_SET_ERROR(err, ERR_STRDUP);
+
     // initialize client session
     if ((ERROR_EXTRA(err) = gnutls_init(&sock->session, GNUTLS_CLIENT)) < 0)
         JUMP_SET_ERROR(err, ERR_GNUTLS_INIT);
@@ -312,12 +495,15 @@
     if ((ERROR_EXTRA(err) = gnutls_set_default_priority(sock->session)))
         JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY);
 
-    // bind anon credentials
-    if ((ERROR_EXTRA(err) = gnutls_credentials_set(sock->session, GNUTLS_CRD_CERTIFICATE, ctx->xcred)))
+    // XXX: silly hack for OpenSSL interop
+    gnutls_dh_set_prime_bits(sock->session, 512);
+
+    // bind credentials
+    if ((ERROR_EXTRA(err) = gnutls_credentials_set(sock->session, GNUTLS_CRD_CERTIFICATE, cred->x509)))
         JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET);
 
     // TCP connect
-    if (sock_tcp_connect_async_begin(SOCK_GNUTLS_TCP(sock), host, service, err))
+    if (sock_tcp_connect_async_begin(SOCK_GNUTLS_TCP(sock), hostname, service, err))
         goto error;
 
     // done, wait for the connect to complete
@@ -338,10 +524,14 @@
     sock_fd_close(SOCK_GNUTLS_FD(sock));
 
     // close the session rudely
-    // XXX: does this actually do everything we need it to? Don't want to call gnutls_bye here, since we're void...
     gnutls_deinit(sock->session);
     
+    if (sock->cred)
+        // drop the cred ref
+        sock_ssl_client_cred_put(sock->cred);
+
     // free
+    free(sock->hostname);
     free(sock);
 }
 
--- a/src/sock_gnutls.h	Thu Apr 16 01:20:09 2009 +0300
+++ b/src/sock_gnutls.h	Sun Apr 19 04:04:42 2009 +0300
@@ -27,16 +27,24 @@
     ERR_GNUTLS_RECORD_SEND,
     ERR_GNUTLS_RECORD_RECV,
     ERR_GNUTLS_RECORD_GET_DIRECTION,   
+    ERR_GNUTLS_CERT_VERIFY_PEERS2,
+    ERR_GNUTLS_CERT_VERIFY,
+    ERR_GNUTLS_CERT_SET_X509_TRUST_FILE,
+    ERR_GNUTLS_CERT_SET_X509_KEY_FILE,
 };
 
 /**
- * Additional gnutls configuration for client sockets.
- *
- * XXX: currently, we just have one global instance, set up by sock_gnutls_init, used for all sockets
+ * GnuTLS credentials for client sockets.
  */
-struct sock_gnutls_client_ctx {
+struct sock_ssl_client_cred {
     /** Our client certificate */
-    gnutls_certificate_credentials_t xcred;
+    gnutls_certificate_credentials_t x509;
+
+    /** Should we verify? */
+    bool verify;
+
+    /** Refcount from sock_gnutls */
+    int refcount;
 };
 
 /**
@@ -45,16 +53,22 @@
 struct sock_gnutls {
     /** The underlying TCP connection */
     struct sock_tcp base_tcp;
+
+    /** The hostname we connected to, for verification */
+    char *hostname;
+
+    /** The credentials we are using, unless anon */
+    struct sock_ssl_client_cred *cred;
     
-    /** Additional SSL info XXX: do we need to keep a ref to this? */
-    struct sock_gnutls_ctx *ctx;
-
     /** The GnuTLS session for this connection */
     gnutls_session_t session;
 
     /** The current event_enable mask */
     int ev_mask;
 
+    /** Should we verify the peer cert? */
+    bool verify;
+
     /** Are we running a handshake? */
     bool handshake;
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sock_ssl.h	Sun Apr 19 04:04:42 2009 +0300
@@ -0,0 +1,78 @@
+#ifndef SOCK_SSL_H
+#define SOCK_SSL_H
+
+/**
+ * @file
+ *
+ * SSL-specific functionality as related to sock.h
+ */
+#include "sock.h"
+
+/**
+ * SSL client credentials for use with sock_ssl_client_credentials/sock_ssl_connect
+ */
+struct sock_ssl_client_cred;
+
+/**
+ * Set up SSL client credentials for use with sock_ssl_connect. This includes information both required to identify
+ * ourselves to the server, as well as to verify the server.
+ *
+ * To verify the server's certificate, pass in a path to a file containing the CA certificate(s) that should be used to
+ * verify the server's certificate, and then either give `verify` as true to force verification, or false to simply
+ * warn. XXX: not entirely true
+ *
+ * To supply a client certificate to the server, pass in the paths to the cert/pkey files. If given as NULL, an
+ * anonymous client certificate will be used. Both must be supplied if given.
+ *
+ * The newly created SSL client credential will initially have a refcount of one, and can then be used with sock_ssl_connect.
+ *
+ * @param ctx_cred the newly created client credentials are returned via this
+ * @param cafile_path given as non-NULL to load trusted certs for verification from the given path
+ * @param verify force verification of the peer cert
+ * @param cert_path path to the client certificate file, or NULL
+ * @param pkey_path path to the client private key, or NULL
+ * @param err returned error info
+ */
+err_t sock_ssl_client_cred_create (struct sock_ssl_client_cred **ctx_cred,
+        const char *cafile_path, bool verify,
+        const char *cert_path, const char *pkey_path,
+        struct error_info *err
+);
+
+/**
+ * Aquire a referenec for the given cred.
+ */
+void sock_ssl_client_cred_get (struct sock_ssl_client_cred *cred);
+
+/**
+ * Release a reference allocated for the given cred.
+ */
+void sock_ssl_client_cred_put (struct sock_ssl_client_cred *cred);
+
+/**
+ * Start a non-blocking SSL connect/handshake to the given host/service. The socket will not yet be connected when the
+ * function returns, but rather, the eventual redyness/failure of the connect/handshake will be indicated later using
+ * the given \a cb_func.
+ *
+ * The given sock_ssl_client_cred should either be NULL to use an anonymous client cert and not verify the server cert,
+ * or a sock_ssl_client_cred allocated using sock_ssl_client_cred_create(). The contexts are reference-counted, so once
+ * a cred context has been released, it will be destroyed once the last connection using it is destroyed.
+ * 
+ * @param sock_ptr the new sock_stream
+ * @param hostname the hostname to connect to
+ * @param service the TCP service name (i.e. port) to connect to
+ * @param cred the SSL client credentials to use, if not NULL
+ * @param cb_func the callback for connection/handshake completion
+ * @param cb_arg the callback context argument
+ * @param err returned error info
+ */
+err_t sock_ssl_connect_async (struct sock_stream **sock_ptr, 
+        const char *hostname, const char *service,
+        struct sock_ssl_client_cred *cred,
+        sock_stream_connect_cb cb_func, void *cb_arg, 
+        struct error_info *err
+    );
+
+
+
+#endif /* SOCK_SSL_H */