terom@2: terom@2: #include "sock_gnutls.h" terom@2: terom@139: // XXX: remove terom@139: #include "log.h" terom@139: terom@140: #include terom@140: terom@2: #include terom@140: #include terom@140: #include terom@140: terom@140: #include terom@2: terom@139: /** terom@155: * Enable the TCP events based on the session's gnutls_record_get_direction(). terom@139: */ terom@155: static err_t sock_gnutls_ev_enable (struct sock_gnutls *sock, error_t *err) terom@139: { terom@139: int ret; terom@155: short mask; terom@139: terom@139: // gnutls_record_get_direction tells us what I/O operation gnutls would have required for the last terom@139: // operation, so we can use that to determine what events to register terom@139: switch ((ret = gnutls_record_get_direction(sock->session))) { terom@139: case 0: terom@139: // read more data terom@156: mask = TRANSPORT_READ; terom@139: break; terom@139: terom@139: case 1: terom@139: // write buffer full terom@156: mask = TRANSPORT_WRITE; terom@139: break; terom@139: terom@139: default: terom@139: // random error terom@139: RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_GET_DIRECTION, ret); terom@139: } terom@139: terom@155: // do the enabling terom@156: if ((ERROR_CODE(err) = transport_fd_enable(SOCK_GNUTLS_FD(sock), mask))) terom@155: return ERROR_CODE(err); terom@155: terom@155: terom@139: return SUCCESS; terom@139: } terom@139: terom@139: /** terom@140: * Translate a set of gnutls_certificate_status_t values to a constant error message terom@140: */ terom@140: static const char* sock_gnutls_verify_error (unsigned int status) terom@140: { terom@140: if (status & GNUTLS_CERT_REVOKED) terom@140: return "certificate was revoked"; terom@140: terom@140: else if (status & GNUTLS_CERT_INVALID) { terom@140: if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) terom@140: return "certificate signer was not found"; terom@140: terom@140: else if (status & GNUTLS_CERT_SIGNER_NOT_CA) terom@140: return "certificate signer is not a Certificate Authority"; terom@140: terom@140: else if (status & GNUTLS_CERT_INSECURE_ALGORITHM) terom@140: return "certificate signed using an insecure algorithm"; terom@140: terom@140: else terom@140: return "certificate could not be verified"; terom@140: terom@140: } else terom@140: return "unknown error"; terom@140: terom@140: } terom@140: terom@140: /** terom@140: * Perform the certificate validation procedure on the socket. terom@140: * terom@140: * Based on the GnuTLS examples/ex-rfc2818.c terom@140: */ terom@155: static err_t sock_gnutls_verify (struct sock_gnutls *sock, error_t *err) terom@140: { terom@140: unsigned int status; terom@140: const gnutls_datum_t *cert_list; terom@140: unsigned int cert_list_size; terom@140: gnutls_x509_crt_t cert = NULL; terom@140: time_t t, now; terom@140: terom@140: // init terom@140: RESET_ERROR(err); terom@140: now = time(NULL); terom@140: terom@140: // inspect the peer's cert chain using the installed trusted CAs terom@140: if ((ERROR_EXTRA(err) = gnutls_certificate_verify_peers2(sock->session, &status))) terom@140: JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_VERIFY_PEERS2); terom@140: terom@140: // verify errors? terom@140: if (status) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, sock_gnutls_verify_error(status)); terom@140: terom@140: // import the main cert terom@140: assert(gnutls_certificate_type_get(sock->session) == GNUTLS_CRT_X509); terom@140: terom@140: if ((ERROR_EXTRA(err) = gnutls_x509_crt_init(&cert))) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_init"); terom@140: terom@140: if ((cert_list = gnutls_certificate_get_peers(sock->session, &cert_list_size)) == NULL) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_certificate_get_peers"); terom@140: terom@140: if (!cert_list_size) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "cert_list_size"); terom@140: terom@140: if ((ERROR_EXTRA(err) = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER))) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_import"); terom@140: terom@140: // check expire/activate... not sure if we need to do this terom@140: if ((t = gnutls_x509_crt_get_expiration_time(cert)) == ((time_t) -1) || t < now) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_expiration_time"); terom@140: terom@140: if ((t = gnutls_x509_crt_get_activation_time(cert)) == ((time_t) -1) || t > now) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_get_activation_time"); terom@140: terom@140: // check hostname terom@140: if (!gnutls_x509_crt_check_hostname(cert, sock->hostname)) terom@140: JUMP_SET_ERROR_STR(err, ERR_GNUTLS_CERT_VERIFY, "gnutls_x509_crt_check_hostname"); terom@140: terom@140: error: terom@140: // cleanup terom@140: if (cert) terom@140: gnutls_x509_crt_deinit(cert); terom@140: terom@140: // should be SUCCESS terom@140: return ERROR_CODE(err); terom@140: } terom@140: terom@140: terom@140: /** terom@139: * Our handshake driver. This will execute the next gnutls_handshake step, handling E_AGAIN. terom@139: * terom@139: * This updates the sock_gnutls::handshake state internally, as used by sock_gnutls_event_handler. terom@139: * terom@140: * If the sock is marked as verify, this will perform the verification, returning on any errors, and then unset the terom@140: * verify flag - this ensures that the peer cert is only verified once per connection... terom@140: * terom@139: * @return >0 for finished handshake, 0 for handshake-in-progress, -err_t for errors. terom@139: */ terom@155: static int sock_gnutls_handshake (struct sock_gnutls *sock, error_t *err) terom@139: { terom@139: int ret; terom@139: terom@139: // perform the handshake terom@139: if ((ret = gnutls_handshake(sock->session)) < 0 && ret != GNUTLS_E_AGAIN) terom@139: JUMP_SET_ERROR_EXTRA(err, ERR_GNUTLS_HANDSHAKE, ret); terom@139: terom@139: // complete? terom@139: if (ret == 0) { terom@139: // update state terom@139: sock->handshake = false; terom@139: terom@140: // verify? terom@140: if (sock->verify) { terom@140: // perform the validation terom@140: if (sock_gnutls_verify(sock, err)) terom@140: goto error; terom@140: terom@140: // unmark terom@140: sock->verify = false; terom@140: } terom@140: terom@139: // handshake done terom@139: return 1; terom@139: terom@139: } else { terom@139: // set state, isn't really needed every time, but easier this way terom@139: sock->handshake = true; terom@139: terom@139: // re-enable the event for the next iteration terom@139: return sock_gnutls_ev_enable(sock, err); terom@139: } terom@139: terom@139: error: terom@139: return -ERROR_CODE(err); terom@139: } terom@139: terom@139: /** terom@155: * Our transport_fd event handler. Drive the handshake if that's current, otherwise, invoke user callbacks. terom@139: */ terom@155: static void sock_gnutls_on_event (struct transport_fd *fd, short what, void *arg) terom@139: { terom@139: struct sock_gnutls *sock = arg; terom@155: error_t err; terom@139: terom@139: (void) fd; terom@155: terom@155: // XXX: timeouts terom@139: (void) what; terom@139: terom@139: // are we in the handshake cycle? terom@139: if (sock->handshake) { terom@139: RESET_ERROR(&err); terom@139: terom@155: // perform the next handshake step terom@139: if (sock_gnutls_handshake(sock, &err) == 0) { terom@155: // handshake continues terom@139: terom@163: // XXX: this state flag is completely wrong terom@155: } else if (SOCK_GNUTLS_TRANSPORT(sock)->connected) { terom@140: // the async connect process has now completed, either succesfully or with an error terom@140: // invoke the user connect callback directly with appropriate error terom@155: transport_connected(SOCK_GNUTLS_TRANSPORT(sock), ERROR_CODE(&err) ? &err : NULL, true); terom@139: terom@139: } else { terom@139: if (ERROR_CODE(&err)) terom@155: // the re-handshake failed, so this transport is dead terom@155: transport_error(SOCK_GNUTLS_TRANSPORT(sock), &err); terom@155: terom@155: else terom@155: // re-handshake completed, so continue with the transport_callbacks terom@163: transport_invoke(SOCK_GNUTLS_TRANSPORT(sock), what); terom@139: } terom@139: terom@139: } else { terom@139: // normal sock_stream operation terom@155: // gnutls might be able to proceed now, so invoke user callbacks terom@163: transport_invoke(SOCK_GNUTLS_TRANSPORT(sock), what); terom@139: } terom@139: } terom@139: terom@155: static err_t sock_gnutls_read (transport_t *transport, void *buf, size_t *len, error_t *err) terom@10: { terom@155: struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type); terom@10: int ret; terom@10: terom@12: // read gnutls record terom@155: do { terom@155: ret = gnutls_record_recv(sock->session, buf, *len); terom@155: terom@155: } while (ret == GNUTLS_E_INTERRUPTED); terom@10: terom@12: // errors terom@155: // XXX: E_REHANDSHAKE? terom@12: if (ret < 0 && ret != GNUTLS_E_AGAIN) terom@12: RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret); terom@12: terom@12: else if (ret == 0) terom@163: return SET_ERROR(err, ERR_EOF); terom@12: terom@12: terom@155: // EAGAIN? terom@14: if (ret < 0) { terom@12: *len = 0; terom@12: terom@12: } else { terom@12: // updated length terom@12: *len = ret; terom@12: terom@12: } terom@10: terom@10: return SUCCESS; terom@10: } terom@10: terom@155: static err_t sock_gnutls_write (transport_t *transport, const void *buf, size_t *len, error_t *err) terom@10: { terom@155: struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type); terom@10: int ret; terom@12: terom@12: // read gnutls record terom@155: do { terom@155: ret = gnutls_record_send(sock->session, buf, *len); terom@155: terom@155: } while (ret == GNUTLS_E_INTERRUPTED); terom@155: terom@12: // errors terom@12: if (ret < 0 && ret != GNUTLS_E_AGAIN) terom@163: RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_SEND, ret); terom@10: terom@12: else if (ret == 0) terom@163: return SET_ERROR(err, ERR_WRITE_EOF); terom@12: terom@12: terom@12: // eagain? terom@14: if (ret < 0) { terom@12: *len = 0; terom@12: terom@12: } else { terom@12: // updated length terom@12: *len = ret; terom@12: } terom@10: terom@10: return SUCCESS; terom@10: } terom@10: terom@155: static void _sock_gnutls_destroy (transport_t *transport) terom@2: { terom@155: struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type); terom@12: terom@155: // die terom@29: sock_gnutls_destroy(sock); terom@28: } terom@28: terom@139: /** terom@139: * Our sock_tcp-invoked connect handler terom@139: */ terom@155: static void sock_gnutls__connected (transport_t *transport, const error_t *tcp_err) terom@139: { terom@155: struct sock_gnutls *sock = transport_check(transport, &sock_gnutls_type); terom@155: error_t err; terom@139: terom@139: // trap errors to let the user handle them directly terom@139: if (tcp_err) terom@139: JUMP_SET_ERROR_INFO(&err, tcp_err); terom@139: terom@139: // bind default transport functions (recv/send) to use the TCP fd terom@139: gnutls_transport_set_ptr(sock->session, (gnutls_transport_ptr_t) (long int) SOCK_GNUTLS_FD(sock)->fd); terom@139: terom@139: // add ourselves as the event handler terom@155: if ((ERROR_CODE(&err) = transport_fd_setup(SOCK_GNUTLS_FD(sock), sock_gnutls_on_event, sock))) terom@139: goto error; terom@139: terom@139: // start handshake terom@139: if (sock_gnutls_handshake(sock, &err)) terom@139: // this should complete with SUCCESS if it returns >0 terom@139: goto error; terom@139: terom@139: // ok, so we wait... terom@139: return; terom@139: terom@139: error: terom@139: // tell the user terom@155: transport_connected(transport, &err, true); terom@139: } terom@139: terom@155: struct transport_type sock_gnutls_type = { terom@159: .parent = &sock_tcp_type, terom@27: .methods = { terom@155: .read = sock_gnutls_read, terom@155: .write = sock_gnutls_write, terom@155: .destroy = _sock_gnutls_destroy, terom@155: ._connected = sock_gnutls__connected, terom@27: }, terom@2: }; terom@2: terom@2: /* terom@140: * Global shared anonymous client credentials terom@2: */ terom@140: static struct sock_ssl_client_cred sock_gnutls_client_cred_anon = { .x509 = NULL, .verify = false, .refcount = 0 }; terom@4: terom@140: // XXX: GnuTLS log func terom@14: void _log (int level, const char *msg) terom@14: { terom@27: printf("gnutls: %d: %s", level, msg); terom@14: } terom@14: terom@155: err_t sock_gnutls_global_init (error_t *err) terom@2: { terom@2: // global init terom@4: if ((ERROR_EXTRA(err) = gnutls_global_init()) < 0) terom@4: return SET_ERROR(err, ERR_GNUTLS_GLOBAL_INIT); terom@2: terom@140: // initialize the anon client credentials terom@140: if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&sock_gnutls_client_cred_anon.x509)) < 0) terom@140: return SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED); terom@3: terom@14: // XXX: debug terom@14: // gnutls_global_set_log_function(&_log); terom@14: // gnutls_global_set_log_level(11); terom@14: terom@3: // done terom@3: return SUCCESS; terom@2: } terom@2: terom@140: static void sock_ssl_client_cred_destroy (struct sock_ssl_client_cred *cred) terom@140: { terom@140: // simple terom@140: gnutls_certificate_free_credentials(cred->x509); terom@140: terom@140: free(cred); terom@140: } terom@140: terom@140: err_t sock_ssl_client_cred_create (struct sock_ssl_client_cred **ctx_cred, terom@140: const char *cafile_path, bool verify, terom@140: const char *cert_path, const char *pkey_path, terom@155: error_t *err terom@140: ) { terom@140: struct sock_ssl_client_cred *cred; terom@140: terom@140: // alloc it terom@140: if ((cred = calloc(1, sizeof(*cred))) == NULL) terom@140: return SET_ERROR(err, ERR_CALLOC); terom@140: terom@140: // create the cert terom@140: if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&cred->x509)) < 0) terom@140: JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED); terom@140: terom@140: // load the trusted ca certs? terom@140: if (cafile_path) { terom@140: // load them terom@140: if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_trust_file(cred->x509, cafile_path, GNUTLS_X509_FMT_PEM)) < 0) terom@140: JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_TRUST_FILE); terom@140: terom@140: } terom@140: terom@140: // set the verify flags? terom@140: cred->verify = verify; terom@140: gnutls_certificate_set_verify_flags(cred->x509, 0); terom@140: terom@140: // load the client cert? terom@140: if (cert_path || pkey_path) { terom@140: // need both... terom@140: assert(cert_path && pkey_path); terom@140: terom@140: // load terom@140: if ((ERROR_EXTRA(err) = gnutls_certificate_set_x509_key_file(cred->x509, cert_path, pkey_path, GNUTLS_X509_FMT_PEM))) terom@140: JUMP_SET_ERROR(err, ERR_GNUTLS_CERT_SET_X509_KEY_FILE); terom@140: } terom@140: terom@140: // ok terom@140: cred->refcount = 1; terom@140: *ctx_cred = cred; terom@140: terom@140: return SUCCESS; terom@140: terom@140: error: terom@140: // release terom@140: sock_ssl_client_cred_destroy(cred); terom@140: terom@140: return ERROR_CODE(err); terom@140: } terom@140: terom@140: void sock_ssl_client_cred_get (struct sock_ssl_client_cred *cred) terom@140: { terom@140: cred->refcount++; terom@140: } terom@140: terom@140: void sock_ssl_client_cred_put (struct sock_ssl_client_cred *cred) terom@140: { terom@140: if (--cred->refcount == 0) terom@140: sock_ssl_client_cred_destroy(cred); terom@140: } terom@140: terom@155: err_t sock_ssl_connect (const struct transport_info *info, transport_t **transport_ptr, terom@140: const char *hostname, const char *service, terom@140: struct sock_ssl_client_cred *cred, terom@155: error_t *err terom@140: ) terom@2: { terom@5: struct sock_gnutls *sock = NULL; terom@2: terom@2: // alloc terom@2: if ((sock = calloc(1, sizeof(*sock))) == NULL) terom@5: return SET_ERROR(err, ERR_CALLOC); terom@2: terom@5: // initialize base terom@155: transport_init(SOCK_GNUTLS_TRANSPORT(sock), &sock_gnutls_type, info); terom@2: terom@140: if (!cred) { terom@140: // default credentials terom@140: cred = &sock_gnutls_client_cred_anon; terom@140: terom@140: } else { terom@140: // take a ref terom@140: sock->cred = cred; terom@140: cred->refcount++; terom@140: }; terom@140: terom@140: // do verify? terom@140: if (cred->verify) terom@140: sock->verify = true; terom@140: terom@140: // init terom@140: if ((sock->hostname = strdup(hostname)) == NULL) terom@140: JUMP_SET_ERROR(err, ERR_STRDUP); terom@140: terom@155: // initialize TCP terom@155: sock_tcp_init(SOCK_GNUTLS_TCP(sock)); terom@155: terom@2: // initialize client session terom@5: if ((ERROR_EXTRA(err) = gnutls_init(&sock->session, GNUTLS_CLIENT)) < 0) terom@5: JUMP_SET_ERROR(err, ERR_GNUTLS_INIT); terom@2: terom@2: // ...default priority stuff terom@5: if ((ERROR_EXTRA(err) = gnutls_set_default_priority(sock->session))) terom@5: JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY); terom@2: terom@140: // XXX: silly hack for OpenSSL interop terom@140: gnutls_dh_set_prime_bits(sock->session, 512); terom@140: terom@140: // bind credentials terom@140: if ((ERROR_EXTRA(err) = gnutls_credentials_set(sock->session, GNUTLS_CRD_CERTIFICATE, cred->x509))) terom@5: JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET); terom@2: terom@2: // TCP connect terom@155: if (sock_tcp_connect_async(SOCK_GNUTLS_TCP(sock), hostname, service, err)) terom@85: goto error; terom@2: terom@139: // done, wait for the connect to complete terom@155: *transport_ptr = SOCK_GNUTLS_TRANSPORT(sock); terom@5: terom@5: return SUCCESS; terom@5: terom@5: error: terom@29: // cleanup terom@29: sock_gnutls_destroy(sock); terom@5: terom@5: return ERROR_CODE(err); terom@2: } terom@2: terom@29: void sock_gnutls_destroy (struct sock_gnutls *sock) terom@29: { terom@29: // close the session rudely terom@29: gnutls_deinit(sock->session); terom@155: terom@155: // terminate the TCP transport terom@155: sock_tcp_destroy(SOCK_GNUTLS_TCP(sock)); terom@155: terom@140: if (sock->cred) terom@140: // drop the cred ref terom@140: sock_ssl_client_cred_put(sock->cred); terom@140: terom@29: // free terom@140: free(sock->hostname); terom@29: } terom@29: