--- 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);
}